How to draw a line in d3 v4 using javascript objects? - javascript

I have the following object
var data =[
{"steps":200,calories:200,distance:200,date:new Date(2012,09,1)},
{"steps":200,calories:200,distance:200,date:new Date(2012,09,2)},
{"steps":200,calories:200,distance:200,date:new Date(2012,09,3)},
{"steps":200,calories:200,distance:200,date:new Date(2012,09,4)},
{"steps":200,calories:200,distance:200,date:new Date(2012,09,5)},
]
I'd like to draw a graph between the steps and the date object in d3 v4
I'm doing something like this to draw a line. Here's the full code..
var dataLine = [
{"x":new Date(2012,0,1),"y":10},
{"x":new Date(2012,0,2),"y":9},
{"x":new Date(2012,0,3),"y":3},
{"x":new Date(2012,0,4),"y":2}
];
var parseTime = d3.timeParse("%d-%b-%y");
var svgContainer = d3.select(".dsh-svg-element");
var MARGIN = {left:50,right:20,top:20,bottom:30};
var WIDTH = 960 - (MARGIN.left + MARGIN.right);
var HEIGHT = 500 - (MARGIN.top + MARGIN.bottom);
svgContainer.attr("width",WIDTH+(MARGIN.left + MARGIN.right))
.attr("height",HEIGHT+(MARGIN.top+MARGIN.bottom))
.attr("transform","translate(" + MARGIN.left + "," + MARGIN.top + ")");
var xMax =100;
var yMax =100;
var x = d3.scaleTime().domain([new Date(2012,0,1), new Date(2012,0,31)]).range([0, WIDTH])
var y = d3.scaleLinear().domain([0,yMax]).range([HEIGHT,0]);
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
svgContainer.append("g").attr("transform", "translate(50," + (HEIGHT+MARGIN.top) + ")").call(xAxis)
svgContainer.append("g").attr("transform", "translate(50,20)").call(yAxis).attr("id","yAxis")
var lineFunction = d3.line().x(function(d){return x(d.y)}).y(function(d){return y(d.x)})
svgContainer.append("path")
.attr("d",lineFunction(dataLine))
.attr("stroke","blue").attr("stroke-width", 2).attr("fill", "none");
I checked the inspector, the x() and y() functions seem to be returning the right pixels to be drawn on the graph.
But the path of the line is "M-455079.8680521219,-5964102899550L-455079.86805246526,-5964491699550L-455079.86805452546,-5964880499550L-455079.8680548688,-5965269299550" in the inspector.. It seems to be drawing the line outside the svg element. Any idea how to fix this?
Any tips or simple code on drawing a line are appreciated.
Fiddle

You have 2 main problems:
First, your x domain is completely unrelated to your data array. Just use d3.extent to get the first and last date:
var x = d3.scaleTime()
.domain(d3.extent(dataLine, function(d) {
return d.x
}))
.range([MARGIN.left, WIDTH])
Second, your line generator is wrong, you're using d.x with the y scale and d.y with the x scale. It should be:
var lineFunction = d3.line()
.x(function(d) {
return x(d.x)
}).y(function(d) {
return y(d.y)
})
Here is your updated fiddle: https://jsfiddle.net/mxsLdntg/
Have in mind that the line in the fiddle is based on dataLine, not data. You have to decide which data array you want to use and set the domains accordingly.

Related

Not able to center D3 pie chart

I'm trying to draw a circle with different data values as angles but for some reason, it's only the last data point that gets the color and display. I've tried to translate the svg but it seems not to budge.
I'm fairly new to D3 so I'm sure I've done something less intelligent without realizing it. As far I could tell, the angles in the g and path elements are as supposed to.
var height = 400, width = 600, radius = Math.min(height, width) / 2;
var colors = ["#red", "pink", "green", "yellow", "blue","magent","brown","olive","orange"];
var data = [1,2,1,2,1,2,1,3,1];
var chart = d3.select("#chart").append("svg")
.attr("width", width).attr("height", height);
chart.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var pie = d3.layout.pie().sort(null).value(function (d) { return d; });
var arc = d3.svg.arc().startAngle(0).innerRadius(0).outerRadius(radius);
var grx = chart.selectAll(".sector").data(pie(data))
.enter().append("g").attr("class", "sector");
grx.append("path")
.attr("d", arc)
.style("fill", function (d, i) {
console.log(d);
return colors[i];
});
The problem is that you're appending all the sectors of the pie to the svg node when they should be appended to the translated g node, you have two options to solve this problem
make chart equal to the translated g node
select g before all the .sectors and store that in grx
The first solution is simpler e.g.
var chart = d3.select("#chart").append("svg")
.attr("width", width).attr("height", height);
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
demo

Why the line in this D3 chart isn't properly displayed when updated?

I'm a beginner with D3.js and I want to display a dynamic line chart where the line is always growing with random fluctuations.
I don't need an X axis but I'd like to get a dynamic Y axis based on the last point inserted in the line.
var n = 40,
random = function(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; },
data = d3.range(n).map(random);
var margin = {top: 20, right: 20, bottom: 20, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([1, n - 2])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, 100])
.range([height, 0]);
var line = d3.svg.line()
.interpolate("basis")
.x(function(d, i) { return x(i); })
.y(function(d, i) { return y(d); });
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("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("g")
.attr("class", "y axis")
.call(d3.svg.axis().scale(y).orient("left"));
var path = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
var min = 0, max = min + 40;
tick();
//Update the chart
function tick() {
// push a new data point onto the back
var r = random(min, max);
data.push(r);
min += 10;
max += 10;
// update Y Axis
var y = d3.scale.linear().domain([r - 20,r + 20]).range([height, 0]);
var yAxis = d3.svg.axis().scale(y).orient("left");
svg.selectAll(".y.axis").call(yAxis);
// redraw the line, and slide it to the left
path
.attr("d", line)
.attr("transform", null)
.transition()
.duration(500)
.ease("linear")
.attr("transform", "translate(" + x(0) + ",0)")
.each("end", tick);
// pop the old data point off the front
data.shift();
}
JSFiddle : https://jsfiddle.net/ugj8g9wu/
If I didn't increase the min / max and don't update the Y Axis everything is ok.
But with the code above, my line quickly go above the the Y axis, which doesn't make any sens since the randomized value is include in the domain of the Y axis...
Could you tell me what's going on and why my line isn't properly displayed?
The issue is a bit hidden. In tick(), you made a new y to handle the new domain and range, but you only updated yAxis with this y. What about the line which is still referencing the original y? It also needs update! You can either add code to update the line:
// update Y Axis
var y = d3.scale.linear().domain([r - 20,r + 20]).range([height, 0]);
var yAxis = d3.svg.axis().scale(y).orient("left");
svg.selectAll(".y.axis").call(yAxis);
// NEW CODE
line.y(function(d, i) { return y(d); });
Or (better I think), instead of creating a new y every tick, you can modify the existing one, saving all the efforts to assign it to everywhere else using it. Just change this line:
var y = d3.scale.linear().domain([minY, maxY]).range([height, 0]);
into:
y.domain([minY, maxY]);
Then you'll be able to see the newest point coming in the right.
But there's one more problem with the code: you are increasing the value too quickly so that it's hard to see old points on the chart, so I tuned the arguments a bit to make it look better. Ideally, the minY and maxY should be calculated according to the values in data, not guessing magic boundarys. :)
Fiddle: https://jsfiddle.net/gbwycmrd/

Basic math 101 scatter plot with Plottable.js

How does one make a basic scatter plot like the one below using Plottable.js?
Is there something wrong with my JSON?
How to reveal the minus scales?
Would you have done anything else differently?
Style doesn't matter, the default Plottable.js one is fine.
window.onload = function() {
var coordinates = [
{
x:"-5",
y:"3"
}, {
x:"2",
y:"-1,5"
}, {
x:"5",
y:"2,5"
}
];
var xScale = new Plottable.Scale.Linear();
var yScale = new Plottable.Scale.Linear();
var colorScale = new Plottable.Scale.Color("10");
var xAxis = new Plottable.Axis.Numeric(xScale, "bottom");
var yAxis = new Plottable.Axis.Numeric(yScale, "left");
var plot = new Plottable.Plot.Scatter(xScale, yScale)
.addDataset(coordinates)
.project("x", "", xScale)
.project("y", "", yScale)
.project("fill", "", colorScale);
var chart = new Plottable.Component.Table([
[yAxis, plot],
[null, xAxis]
]);
chart.renderTo("#my_chart");
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Test</title>
<link rel="stylesheet" href="https://rawgit.com/palantir/plottable/develop/plottable.css">
</head>
<body>
<svg width="100%" height="600" id="my_chart"></svg>
<script src="https://rawgit.com/mbostock/d3/master/d3.min.js"></script>
<script src="https://rawgit.com/palantir/plottable/develop/plottable.min.js"></script>
</body>
</html>
Mark has the right idea - the table system doesn't natively support this layout, so you need to take some manual control over how they are laid out. However, using somewhat obscure parts of the Plottable API, there is a cleaner and better-supported way to lay out the chart you want, which doesn't have the problem of the axes being slightly offset.
The first change is we are going to stop using the table layout engine entirely, since it isn't able to do what we want. Instead, we will plop all the components together in a Component.Group. A Group just overlays components in the same space without trying to position them at all.
var chart = new Plottable.Component.Group([yAxis, xAxis, plot]);
Then we are going to use the alignment and offset methods that are defined on the base (abstract) component class. We set the x-alignment of the y axis to "center" and the y-alignment of the x axis to "center" This will put the axes in the center of the chart.
var xAxis = new Plottable.Axis.Numeric(xScale, "bottom").yAlign("center");
var yAxis = new Plottable.Axis.Numeric(yScale, "left").xAlign("center");
We're not quite done at this point, since to really center the axes we need to shift them back by one half of their own width. The width is only calculated when the chart is rendered (strictly speaking, in the computeLayout call, but that is an internal detail), so we need to set an offset after the chart is rendered:
chart.renderTo("#plottable");
xAxis.yOffset(xAxis.height()/2);
yAxis.xOffset(-yAxis.width()/2);
You can see the final result here (it's a fork of Mark's plnkr). Note that now the axes are aligned on the center of the chart, as the center dot is perfectly on 0,0.
Here's a couple examples I just put together. The first is the straight d3 way of doing what you are asking. The second is a hacked up plottable.js. With plottable.js I can't find a way to position the axis outside of their table system, I had to resort to manually moving them. The table system they use is designed to relieve the developer of having to manually position things. This is great and easy, of course, until you want to control where to position things.
Here's the hack, after you render your plottable:
// move the axis...
d3.select(".y-axis")
.attr('transform',"translate(" + width / 2 + "," + 0 + ")");
d3.select(".x-axis")
.attr("transform", "translate(" + 48 + "," + height / 2 + ")");
Note, I didn't remove the left side margin (the 48 above) that plottable puts in. This could be hacked in as well, but at that point, what is plottable providing for you anyway...
It should be noted that the different appearance of each plot is entirely controlled through the CSS.
Complete d3 scatter plot:
// D3 EXAMPLE
var margin = {
top: 20,
right: 20,
bottom: 20,
left: 20
},
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var svg = d3.select("#d3").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 + ")");
x.domain([-100, 100]);
y.domain([-100, 100]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + 0 + "," + height / 2 + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width / 2 + "," + 0 + ")")
.call(yAxis)
.append("text");
svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", function(d) {
return d.r;
})
.attr("cx", function(d) {
return x(d.x);
})
.attr("cy", function(d) {
return y(d.y);
})
.style("fill", function(d) {
return d.c;
});
Plottable.js:
// PLOTTABLE.JS
var xScale = new Plottable.Scale.Linear();
var yScale = new Plottable.Scale.Linear();
var xAxis = new Plottable.Axis.Numeric(xScale, "bottom");
var yAxis = new Plottable.Axis.Numeric(yScale, "left");
var plot = new Plottable.Plot.Scatter(xScale, yScale);
plot.addDataset(data);
function getXDataValue(d) {
return d.x;
}
plot.project("x", getXDataValue, xScale);
function getYDataValue(d) {
return d.y;
}
plot.project("y", getYDataValue, yScale);
function getRDataValue(d){
return d.r;
}
plot.project("r", getRDataValue);
function getFillValue(d){
return d.c;
}
plot.project("fill", getFillValue);
var chart = new Plottable.Component.Table([
[yAxis, plot],
[null, xAxis]
]);
chart.renderTo("#plottable");
d3.select(".y-axis")
.attr('transform',"translate(" + width / 2 + "," + 0 + ")");
d3.select(".x-axis")
.attr("transform", "translate(" + 48 + "," + height / 2 + ")");

d3 timeseries, reading date from data, counting entries by date

I'm trying to to build a time-series line in d3, using date for the x axis and the number of entries per date as the y axis. I'm having trouble moving the date part of the data object through a date formatter, then a scale, then into my line.
See it in Codepen http://codepen.io/equivalentideas/pen/HaoIs/
Thanks in advance for your help!
var data = [{"title":"1","date":"20140509"},{"title":"2)","date":"20140401"},{"title":"3","date":"20140415"},{"title":"4","date":"20140416"},{"title":"5","date":"20140416"},{"title":"6","date":"20140422"},{"title":"7","date":"20140422"},{"title":"8","date":"20140423"},{"title":"9","date":"20140423"},{"title":"10","date":"20140423"},{"title":"11","date":"20140502"},{"title":"12","date":"20140502"}
var width = "100%",
height = "8em";
var parseDate = d3.time.format("%Y%m%d").parse;
// X Scale
var x = d3.time.scale()
.range([0, width]);
// Y Scale
var y = d3.scale.linear()
.range([height, 0]);
// define the line
var line = d3.svg.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(+d);
})
data.forEach(function(d) {
d.date = parseDate(d.date);
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain(d3.extent(data, function(d) { return d; }));
// build the svg canvas
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
// build the line
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
Currently I get a js console error
Error: Invalid value for <path> attribute d="MNaN,NaNLNaN,NaNLNaN,NaNLNaN,NaNLNaN,NaNLNaN,NaNLNaN,NaNLNaN,NaNLNaN,NaNLNaN,NaNLNaN,NaNLNaN,NaN"
You have not used parseDate. You are missing this :
data.forEach(function(d) {
d.date = parseDate(d.date);
});
Have a look at this example.
Some obvious visible problems:
1) You are not appending your svg to any part of the body or div. You should have a line look like this:
d3.select("body").append("svg").attr("width", width).attr("height", height);
2) I doubt d3 can understand your definition for width and
height. The width and height is the definition of chart size
3) I think there has no need for the dateParse as d3 will internally do it for you.
Finally, check the example provided by Niranjan.
There's a few other issues going on here. First, the width/height are not numbers, so the yScale and xScale ranges are invalid (that's why you get the "NaN" in the line path).
This is bad:
var width = "100%",
height = "8em";
Because these will not have valid, numerical ranges as required by the following scale definitions:
// X Scale
var x = d3.time.scale().range([0, width]);
// Y Scale
var y = d3.scale.linear().range([height, 0]);
...what does "8em" to 0 mean in a numerical svg path coordinate? So, make them numbers instead:
var width = 500,
height = 100;
After you fix that, you'll still have errors because your mapping for the y values isn't going to work. You want a histogram of the counts for the different dates. You should generate the data that way and feed it into the line generator.
var generateData = function(data){
var newData = [];
var dateMap = {};
data.forEach(function(element){
var newElement;
if(dateMap[element.date]){
newElement = dateMap[element.date];
} else {
newElement = { date: parseDate(element.date), count: 0 };
dateMap[element.date] = newElement;
newData.push(newElement);
}
newElement.count += 1;
});
newData.sort(function(a,b){
return a.date.getTime() - b.date.getTime();
});
return newData;
};
Once you fix those two things it should work. Here's a jsFiddle: http://jsfiddle.net/reblace/j3LzY/

d3 Hexagonal Binning domain and range

I'm trying to create a hexagonal binning graph based on this example: http://bl.ocks.org/mbostock/4248145
I've already figured out how to set the domain of each axis. But I'm having troubles setting the points, and I think this is because of the range.
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 800 - margin.left - margin.right, // 740px
height = 500 - margin.top - margin.bottom; // 450px
var points = [[1000,30],[5000,40],[8000,50]]
var x = d3.scale.linear().domain([0, 10000]).range([0, width]);
var y = d3.scale.linear().domain([18, 65]).range([height, 0]);
Using these points, the graph comes out blank.
But if I try these points:
var points = [[100,30],[200,40],[300,50]]
They appear on the graph but not even close to where they should be. [100,30], for example, it appears what should be something like [1400,62].
I already read this post about scales on d3. But I haven't figured out how to display the points correctly.
JSFiddle: http://jsfiddle.net/P6KWZ/
There are two things in your jsfiddle. First, you're not actually using the scales you've created to position the hexagons. Second, you're computing the domains based on the original values and the drawing the values computed by the hexbin.
So first compute the domains based on the binned values:
var projectedPoints = hexbin(points);
var x = d3.scale.linear()
.domain(d3.extent(projectedPoints, function(d) { return d.x; }))
.range([0, width]);
var y = d3.scale.linear()
.domain(d3.extent(projectedPoints, function(d) { return d.y; }))
.range([height, 0]);
And then translate the hexagons using the scales:
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; })
Complete demo here.

Categories

Resources