I am trying to create a D3 scatter Plot graph with Zooming facility. While zooming , I want to hide all the data points that goes beyond both the axis. For that I am using clipping Path. However, Using it, hides all the ticks and values on the y-axis. Just the line remains visible.
Here is my code in angular 4 :
import { Component, OnInit, Input, ViewChild, ElementRef } from '#angular/core';
import * as d3 from 'd3';
#Component({
selector: 'app-scatterplot',
templateUrl: './scatterplot.component.html',
styleUrls: ['./scatterplot.component.css']
})
export class ScatterplotComponent implements OnInit {
#ViewChild('chart1') private chartContainer: ElementRef;
dataValue = [{ x: "67", y: "188", },
{ x: "200", y: "163" },
{ x: "254", y: "241" },
{ x: "175", y: "241" },
];
ngOnInit() {
this.graph();
}
graph() {
const element = this.chartContainer.nativeElement;
var svgWidth = 400;
var svgHeight = 400;
var margin = { top: 30, right: 40, bottom: 50, left: 60 };
var width = svgWidth - margin.left - margin.right;
var height = svgHeight - margin.top - margin.bottom;
var originalCircle = {
"cx": -150,
"cy": -15,
"r": 20
};
var svgViewport = d3.select(element)
.append('svg')
.attr('width', svgWidth)
.attr('height', svgHeight)
// clipping path
svgViewport.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", svgWidth)
.attr("height", svgHeight)
.attr("fill", "red")
.attr("fill-opacity", 0.1);
// create scale objects
var x = d3.scaleLinear()
.domain([1, 500])
.range([0, width]);
var y = d3.scaleLinear()
.domain([1, 500])
.range([height, 0]);
// create axis objects
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
// Zoom Function
var zoom = d3.zoom()
.on("zoom", zoomFunction);
// Inner Drawing Space
var innerSpace = svgViewport.append("g")
.attr("class", "inner_space")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("clip-path", "url(#clip)")
.call(zoom);
// append some dummy data
var data = innerSpace.selectAll("circle")
.data(this.dataValue)
.enter().append("circle")
.attr("class", "dot")
.attr("cx", function (d) {
return x(d.x)
;
})
.attr("cy", function (d) {
return y(d.y);
})
.attr("r", 2);
// Draw Axis
var gX = innerSpace.append("g")
.attr("class", "axis")
.attr("transform", "translate(0, " + height + ")")
.call(xAxis);
var gY = innerSpace.append("g")
.attr("class", "axis axis--y")
.call(yAxis);
// append zoom area
var view = innerSpace.append("rect")
.attr("class", "zoom")
.attr("width", width)
.attr("height", height - 10)
.attr("fill", "transparent")
.attr("fill-opacity", 0.1)
.call(zoom)
function zoomFunction() {
// create new scale ojects based on event
var new_xScale = d3.event.transform.rescaleX(x)
var new_yScale = d3.event.transform.rescaleY(y)
console.log(d3.event.transform)
// update axes
gX.call(xAxis.scale(new_xScale));
gY.call(yAxis.scale(new_yScale));
// update circle
data.attr("transform", d3.event.transform)
};
}
}
From the above code , if I remove the code
.attr("clip-path", "url(#clip)")
from the code block:
// Inner Drawing Space
var innerSpace = svgViewport.append("g")
.attr("class", "inner_space")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("clip-path", "url(#clip)")
.call(zoom);
than the graph is comming perfect. But on zooming the data values keep appearing even if it goes beyond both axis lines. How do I hide it.
Related
I am trying to modify below code from this example
https://enappd.com/blog/adding-charts-in-ionic-4-apps-and-pwa-part-2/54/
to a line plot or connected scatter plot (preferred). How?
Nearly all simple D3 examples I find a bar charts or have different syntax since they read data from a cvs and not locally in the example above.
D3 is a bit cryptic for me so far.
this.g.selectAll('.bar')
.data(this.barData)
.enter()
.append('rect')
.attr('class', 'bar')
.attr('fill', 'rgb(34, 167, 240)')
.attr('x', (d) => this.x(d.season))
.attr('y', (d) => this.y(d.viewers))
.attr('width', this.x.bandwidth())
.attr('height', (d) => this.height - this.y(d.viewers));
I believe this is a line plot, not sure if it meets your requirements, let me know:
// set the dimensions and margins of the graph
var margin = {top: 10, right: 30, bottom: 30, left: 60},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
//d3.csv("https://raw.githubusercontent.com/holtzy/data_to_viz/master/Example_dataset/2_TwoNum.csv", function(data) {
//console.log(data);
//});
var data = [
{ x: 1, y: 2500000 },
{ x: 2, y: 3800000 },
{ x: 3, y: 5000000 },
{ x: 4, y: 6900000 },
{ x: 5, y: 6900000 },
{ x: 6, y: 7500000 },
{ x: 7, y: 10000000 },
{ x: 8, y: 17000000 }
];
// Add X axis
var x = d3.scaleLinear()
.domain([0, 10])
.range([ 0, width ]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 20000000])
.range([ height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// Add dots - scatter plot
//svg.append('g')
// .selectAll("dot")
// .data(data)
// .enter()
// .append("circle")
// .attr("cx", function (d) { return x(d.x); } )
// .attr("cy", function (d) { return y(d.y); } )
// .attr("r", 1.5)
// .style("fill", "#69b3a2")
svg.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function(d) { return x(d.x) })
.y(function(d) { return y(d.y) })
)
svg.append("text")
.attr("transform",
"translate(" + (width/2) + " ," +
(height + margin.top + 20) + ")")
.style("text-anchor", "middle")
.text("season");
// Add the y Axis
svg.append("g")
.call(d3.axisLeft(y));
// text label for the y axis
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x",0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("viewers");
</script>
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
Important as for reading from CSV, it's relatively simple to grasp how to get rid of it in the examples. Here's how you could load data from csv:
d3.csv("https://.../master/Example_dataset/2_TwoNum.csv", function(data) {
//do something with data here
});
So, basically you do the drawing within the callback (once you've got the data, right?). If you know data when writing the script (like in your example), you simply assign the data and use it in d3 transformations. Here's a great example of scatter plot drawing in d3. All I've done is simply got rid of the callback and assign your data + re-scale the x and y axes.
i am using D3 to draw line graph in JavaScript. Line of Line graph is drawn right but date on x axis is not correct and it is also showing one extra tick on x axis. I had also tried changing tick format but i failed. I don't know where i am doing wrong. please help. here is my code
var margin = {top: 50, right: 50, bottom: 50, left: 50}
, width = window.innerWidth - margin.left - margin.right // Use the window's width
, height = window.innerHeight - margin.top - margin.bottom; // Use the window's height
// The number of datapoints
var n = 4;
var xScale = d3.scaleLinear()
.domain([0, n-1]) // input
.range([0, width]); // output
var parseTime = d3.timeParse("%d-%b-%y");
// 6. Y scale will use the randomly generate number
var yScale = d3.scaleLinear()
.domain([0, 100]) // input
.range([height, 0]); // output
d3.csv("Data_vis.csv", function(data){
//console.log(data);
var nov_11_percent= 22;
var oct_16_percent= 25;
var nov_13_percent= 24;
var oct_22_percent= 21;
var dataset2=[{"date":'2018-09-11', "value": nov_11_percent},
{"date":'2018-10-16', "value": oct_16_percent},
{"date":'2018-10-22', "value": oct_22_percent},
{"date":'2018-11-13', "value": nov_13_percent}];
dataset2.forEach(function(d) {
d.date = new Date(d.date);
});
var line = d3.line()
.x(function(d, i) {
//alert(parseTime(d.date));
return xScale(d.date);
})
.y(function(d,i ) {
return yScale(d.value);
})
.curve(d3.curveMonotoneX)
xScale.domain(d3.extent(dataset2, function(d) {
return d.date; }));
yScale.domain([0, d3.max(dataset2, function(d) {
return d.value;
})]);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale)
.ticks(4)
.tickFormat(d3.timeFormat("%Y-%m-%d")));
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale));
svg.append("path")
.datum(dataset2)
.attr("class", "line")
.attr("d", line);
svg.selectAll(".dot")
.data(dataset2)
.enter().append("circle") // Uses the enter().append() method
.attr("class", "dot") // Assign a class for styling
.attr("cx", function(d) { return xScale(d.date) })
.attr("cy", function(d) { return yScale(d.value) })
.attr("r", 10);
});
any help would be much appreciated. thank you
Use a point scale instead of a linear scale for x.
var xScale = d3
.scalePoint()
.range([0, width])
.domain(dataset2.map(i => i.date))
const margin = { top: 50, right: 50, bottom: 50, left: 50 }
const width = window.innerWidth - margin.left - margin.right // Use the window's width
const height = window.innerHeight - margin.top - margin.bottom // Use the window's height
const dataset2 = [{ date: "2018-09-11", value: 22 },{ date: "2018-10-16", value: 25 },{ date: "2018-10-22", value: 24 },{ date: "2018-11-13", value: 21 }]
dataset2.forEach(function(d) {
d.date = new Date(d.date)
})
// 6. Y scale will use the randomly generate number
const yScale = d3
.scaleLinear()
.domain([
0,
d3.max(dataset2, function(d) {
return d.value
})
]) // input
.range([height, 0]) // output
const xScale = d3
.scalePoint()
.range([0, width])
.domain(dataset2.map(d => d.date))
const line = d3
.line()
.x(function(d, i) {
return xScale(d.date)
})
.y(function(d, i) {
return yScale(d.value)
})
.curve(d3.curveMonotoneX)
const svg = d3
.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
svg
.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(
d3
.axisBottom(xScale)
.tickFormat(d3.timeFormat("%m/%d"))
)
svg
.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale))
svg
.append("path")
.datum(dataset2)
.attr("class", "line")
.attr("d", line)
.attr("fill", "none")
.attr("stroke", "black")
svg
.selectAll(".dot")
.data(dataset2)
.enter()
.append("circle") // Uses the enter().append() method
.attr("class", "dot") // Assign a class for styling
.attr("cx", function(d) {
return xScale(d.date)
})
.attr("cy", function(d) {
return yScale(d.value)
})
.attr("r", 10)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Codepen
I'm trying to create a function that creates a histogram for a given array. Clearly, there's no data for the X-Axis and I have to choose the bins arbitrarily. What would be the best way to do so?
My code:
var width = 700, height = 500, pad = 30, barPadding = 1;
function plotHistogram(element, dataset) {
var svg = d3.select(element)
.append("svg")
.attr("width", width)
.attr("height", height)
var bars = svg.append("g")
// Container box
var rectangle = svg.append("rect")
.attr("height", height)
.attr("width", width)
.attr("stroke-width", 2).attr("stroke", "#ccc")
.attr("fill", "transparent")
var xScale = d3.scaleLinear()
// Using default domain (0 - 1)
.range([pad, width - pad * 2])
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, function(d) { return d; })])
.range([height - pad, pad])
var xAxis = d3.axisBottom(xScale)
var yAxis = d3.axisLeft(yScale)
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (height - pad) + ")")
.call(xAxis)
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + pad +", 0)")
.call(yAxis)
svg.selectAll("bars")
.data(dataset)
.enter()
.append("rect")
// Evenly spacing out bars
.attr("x", function(d, i) { return i * width/dataset.length; })
// Top of each bar as the top of svg. To remove inverted bar graph.
.attr("y", function(d) { return height - (d * 4); })
// To give padding between bars
.attr("width", width / dataset.length - barPadding)
// To make the bars taller
.attr("height", function(d) { return d * 4; })
.attr("fill", "teal");
}
// #normal is the id of the div element.
plotHistogram("#normal", [10, 20, 30, 40, 50, 60, 70, 80]);
Edit 1: I have decided to use the default bin size (0 - 1) for the xScale above. I'm facing problems creating the bars.
Generate the bins as in https://bl.ocks.org/mbostock/3048450
The array in your plotHistogram is like the array data in #mbostock's bl.ock...
HTH
I've got the following piece of code that takes a mouse movement on an SVG container and will shrink/grow the height/width of a visualization based on a mouse movement. When the user has a mouse movement in the x direction, the bars on the chart appear jittery: the x attribute will increase by, 2 or three and then revert to what it was previously: the rects on the page will scoot right a couple pixels, and then snap back to their original position.
Is there a bug with how I'm changing the ordinal scale? Or should I use a transform instead of manipulating the X value?
'use strict';
var d3 = require("d3");
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var chartData;
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
let render = (e,data)=>{
width += d3.event ? d3.event.movementX : 0; //changing x axis here
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.range([data.height, 0]);
//
x.domain(data.map(function(d) { return d.letter; }));
y.domain([0, d3.max(data, function(d) { return d.frequency; })]);
//
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10, "%")
.tickSize(1);
var yAxisEl = svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Frequency");
var bars = svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) {
console.log(x(d.letter));
var currX = x(d.letter); //sometimes gives wrong valuse
return currX;
})
.attr("width", function(){
return x.rangeBand();
})
.attr("y", function(d) { return y(d.frequency); })
.attr("height", function(d) { return Math.abs(height - y(d.frequency)); })
}
let rerender=(data)=>{
d3.select("svg").select("g").selectAll("*").remove();
render(null,data);
}
d3.tsv("data.tsv", type, function(error, data) {
if (error) throw error;
chartData = data;
chartData.height = height;
chartData.width = width;
render(error,chartData);
});
d3.selectAll('svg').on('mousemove',function(){
if(chartData){
chartData.height += d3.event.movementY;
rerender(chartData);
}
});
data
letter frequency
A .08167
B .01492
C .02782
D .04253
E .12702
F .02288
G .02015
I made the rerender function inside time out (hope this fixes the problem you referring)
var rerender = (data) => {
if (myVar){
clearTimeout(myVar);//clear timeout if called before 1 millisecond
}
myVar = setTimeout(function(){
d3.select("svg").select("g").selectAll("*").remove();
render(null, data);
}, 1);
}
working code here
I've got a function that draws a curve through dots I give as argument, as follows:
var data = [
// stage 1-9, intensity %, draw disk
{x:1, y: 0, point:true},
{x:4, y: 30, point:true},
{x:5, y: 70, point:true},
{x:6, y:100, point:true},
{x:7, y: 90, point:true},
{x:8, y: 40, point:true},
{x:9, y: 10, point:false}
];
I'd like to handle the point members that tells whether or not to draw an additional spot.
How to do that?
The function that draws the curve as it is per today:
function curveChart(data) {
for (i in data) {
data[i].y = 5.5*data[i].y/100; // normalize
data[i].id = i;
}
var margin = {top: 10, right: 190, bottom: 275, left: 35},
width = 915 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear() //.time.scale()
.domain([1, 9]) // 9 stages
.range([0, width]);
var y = d3.scale.linear()
.domain([0, 6])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.interpolate("monotone")
.x(function(d) { return x(d.x); })
.y(function(d) { return y(d.y); });
var svg = d3.select(".curveChart").append("svg")
.datum(data)
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("path")
.attr("class", "line")
.attr("d", line);
var n = 1;
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("class", "dot")
.attr("cx", line.x())
.attr("cy", line.y())
.attr("r", 2)
.attr("bubbleid", function(d){return d.id; })
.transition(1000)
.duration(800)
.attr("r", 10);
svg.selectAll("circle").on("click", function(){
d3.selectAll(".active").classed("active", false);
d3.select(this).classed("active", true);
var id = $(this).attr('bubbleid');
console.log("clicked on "+$(this).attr('bubbleid'));
$(".bubble").removeClass("show");
$("#bubble"+id).addClass("show");
d3.select(this)
.transition()
.duration(400)
.attr('r', 25)
.transition()
.duration(400)
.attr('r', 10)
;
});
}
The fiddle : http://jsfiddle.net/stephanedeluca/R44cB/
The easiest way to do this is to filter the data before passing it to .data(), retaining only the elements where point is true:
svg.selectAll("circle")
.data(data.filter(function(d) { return d.point; }))
Complete demo here.