Why doesn't my d3.js line chart work? - javascript
I'm currently learning d3.js, and as a task I am trying to build a line chart using a custom data source. For some reason, I can't get the line generator to work, and it seems like it can't create the d attribute for the path element. I don't seem to get any error messages either. Could someone please take a look?
<!DOCTYPE html>
<meta charset="utf-8">
<style>
rect.bar {
//fill: steelblue;
}
.axis text {
font: 10px sans-serif;
}
.axis path, .axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
</style>
<body>
<script src="http://d3js.org/d3.v2.js"></script>
<script>
data = [{
name:'A Negative',
data:[{x:1346103936,y:0.10252502},{x:1346190336,y:0.10838352},{x:1346276736,y:0.11182367},{x:1346363136,y:0.1469633},{x:1346449536,y:0.108044505},{x:1346535936,y:0.10141762},{x:1346622336,y:0.13505103},{x:1346708736,y:0.11661343},{x:1346795136,y:0.09985885},{x:1346881536,y:0.10367505},{x:1346967936,y:0.12067748},{x:1347054336,y:0.1329808},{x:1347140736,y:0.14677866},{x:1347227136,y:0.14087029},{x:1347313536,y:0.13160454},{x:1347399936,y:0.1313771},{x:1347486336,y:0.14144897},{x:1347572736,y:0.15051538},{x:1347659136,y:0.15604788},{x:1347745536,y:0.14364798},{x:1347831936,y:0.12961338},{x:1347918336,y:0.11450371},{x:1348004736,y:0.11712107},{x:1348091136,y:0.12876798},{x:1348177536,y:0.10429894},{x:1348263936,y:0.110398784},{x:1348350336,y:0.10483569},{x:1348436736,y:0.14220005},{x:1348523136,y:0.11701017},{x:1348609536,y:0.12221267},{x:1348696576,y:0.11133491}]},{
name:'A Positive',
data:[{x:1346105088,y:0.20869565},{x:1346191488,y:0.14895636},{x:1346277888,y:0.2819277},{x:1346364288,y:0.19342105},{x:1346450688,y:0.35833332},{x:1346537088,y:0.19473684},{x:1346623488,y:0.23015076},{x:1346709888,y:0.15840708},{x:1346796288,y:0.23293903},{x:1346882688,y:0.21885246},{x:1346969088,y:0.22707888},{x:1347055488,y:0.26593626},{x:1347141888,y:0.22822087},{x:1347228288,y:0.24236642},{x:1347314688,y:0.17460318},{x:1347401088,y:0.19075145},{x:1347487488,y:0.1594203},{x:1347573888,y:0.13432837},{x:1347660288,y:0.0},{x:1347746688,y:0.18100129},{x:1347833088,y:0.1605938},{x:1347919488,y:0.12987013},{x:1348005888,y:0.12683824},{x:1348092288,y:0.15542522},{x:1348178688,y:0.13584906},{x:1348265088,y:0.14351852},{x:1348351488,y:0.1322314},{x:1348437888,y:0.13709678},{x:1348524288,y:0.17438692},{x:1348610688,y:0.0},{x:1348700160,y:0.18169399}]},{
name:'A Uncertain',
data:[{x:1346104576,y:0.04397342},{x:1346190976,y:0.044665344},{x:1346277376,y:0.049782943},{x:1346363776,y:0.051038638},{x:1346450176,y:0.050243802},{x:1346536576,y:0.03118218},{x:1346622976,y:0.04424348},{x:1346709376,y:0.04498049},{x:1346795776,y:0.04105231},{x:1346882176,y:0.04970384},{x:1346968576,y:0.045589853},{x:1347054976,y:0.046243627},{x:1347141376,y:0.05226451},{x:1347227776,y:0.047814183},{x:1347314176,y:0.04413969},{x:1347400576,y:0.03914877},{x:1347486976,y:0.042237047},{x:1347573376,y:0.054126237},{x:1347659776,y:0.04697353},{x:1347746176,y:0.04476943},{x:1347832576,y:0.042521983},{x:1347918976,y:0.05310476},{x:1348005376,y:0.059566505},{x:1348091776,y:0.043783925},{x:1348178176,y:0.043761015},{x:1348264576,y:0.046513315},{x:1348350976,y:0.0384231},{x:1348437376,y:0.04024283},{x:1348523776,y:0.040613018},{x:1348610176,y:0.04732518},{x:1348696576,y:0.06337391}]},{
name:'A Positive',
data:[{x:1346104320,y:0.109645985},{x:1346190720,y:0.092952825},{x:1346277120,y:0.10988262},{x:1346363520,y:0.12258253},{x:1346449920,y:0.12162819},{x:1346536320,y:0.11145041},{x:1346622720,y:0.17937773},{x:1346709120,y:0.1605882},{x:1346795520,y:0.15555955},{x:1346881920,y:0.15066825},{x:1346968320,y:0.17311412},{x:1347054720,y:0.21528025},{x:1347141120,y:0.20169735},{x:1347227520,y:0.15779452},{x:1347313920,y:0.1469917},{x:1347400320,y:0.15995567},{x:1347486720,y:0.17675863},{x:1347573120,y:0.14658852},{x:1347659520,y:0.2049946},{x:1347745920,y:0.15699232},{x:1347832320,y:0.14301357},{x:1347918720,y:0.1457654},{x:1348005120,y:0.1532571},{x:1348091520,y:0.17817244},{x:1348177920,y:0.13126957},{x:1348264320,y:0.12135763},{x:1348350720,y:0.14930858},{x:1348437120,y:0.14171022},{x:1348523520,y:0.12027296},{x:1348609920,y:0.13843122},{x:1348696576,y:0.15421592}]},{
name:'A Uncertain',
data:[{x:1346103936,y:0.046369042},{x:1346190336,y:0.042160377},{x:1346276736,y:0.06631727},{x:1346363136,y:0.043078776},{x:1346449536,y:0.049522486},{x:1346535936,y:0.041241966},{x:1346622336,y:0.041665666},{x:1346708736,y:0.0461979},{x:1346795136,y:0.044713285},{x:1346881536,y:0.041361943},{x:1346967936,y:0.051421918},{x:1347054336,y:0.04684727},{x:1347140736,y:0.048165746},{x:1347227136,y:0.053684916},{x:1347313536,y:0.05550549},{x:1347399936,y:0.05435959},{x:1347486336,y:0.04710294},{x:1347572736,y:0.05433203},{x:1347659136,y:0.06015368},{x:1347745536,y:0.047590178},{x:1347831936,y:0.045565397},{x:1347918336,y:0.056516618},{x:1348004736,y:0.06080917},{x:1348091136,y:0.068452686},{x:1348177536,y:0.049881306},{x:1348263936,y:0.04221391},{x:1348350336,y:0.0484556},{x:1348436736,y:0.0402809},{x:1348523136,y:0.058744337},{x:1348609536,y:0.054147776},{x:1348696576,y:0.056016088}]},{
name:'A Negative',
data:[{x:1346104832,y:0.25386313},{x:1346191232,y:0.14606741},{x:1346277632,y:0.17222223},{x:1346364032,y:0.19863014},{x:1346450432,y:0.17857143},{x:1346536832,y:0.14606741},{x:1346623232,y:0.12448133},{x:1346709632,y:0.12931034},{x:1346796032,y:0.25714287},{x:1346882432,y:0.22222222},{x:1346968832,y:0.1764706},{x:1347055232,y:0.28846154},{x:1347141632,y:0.1826923},{x:1347228032,y:0.2236842},{x:1347314432,y:0.091836736},{x:1347400832,y:0.25},{x:1347487232,y:0.17567568},{x:1347573632,y:0.15384616},{x:1347660032,y:0.0},{x:1347746432,y:0.23584905},{x:1347832832,y:0.13718411},{x:1347919232,y:0.0},{x:1348005632,y:0.13533835},{x:1348092032,y:0.0},{x:1348178432,y:0.06315789},{x:1348264832,y:0.0},{x:1348351232,y:0.14457831},{x:1348437632,y:0.13253012},{x:1348524032,y:0.1},{x:1348610432,y:0.0},{x:1348700160,y:0.29826254}]}];
var x = d3.scale.linear()
.range([0, "100%"])
.domain(d3.extent(data[0].data, function(d) { return d.x }));
var y = d3.scale.linear()
.domain([0, d3.max(data[0].data, function(d) { return d.y; })])
.range([0, "100%"]);
var line = d3.svg.line()
.x(function(d) { return x(d.x)})
.y(function(d) { return y(d.y)})
.interpolate("basis");
var svg = d3.select("body").append("svg")
.attr("width", "100%")
.attr("height", "500px");
var colors = d3.scale.category20().range();
var group = svg.selectAll("g").data(data);
group.enter().append("g")
.attr("fill", function(d, i) { return colors[i % colors.length]; })
.attr("opacity", "0.5").attr("stroke", "black").attr("stroke-width", "2");
group.selectAll("path")
.data(function(d) {return d.data})
.enter().append("path")
.attr("d", line)
.attr("class", "line");
</script>
I see a couple problems. The first is that the width and height attributes of SVG elements should be specified as unit-less numbers—always in pixels. This defines the coordinate space of the SVG element as well as its size on-screen. You can also set width and height style properties using px or percentages, but you should only do this in addition to setting the width and height attributes. The typical pattern is:
var width = 960,
height = 500;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
You might also want to look at the margin conventions example for more information.
The second thing I would change is to use this coordinate space to set the range of your scales, rather than using the percentage positioning:
var x = d3.scale.linear()
.domain(…)
.range([0, width]);
var y = d3.scale.linear()
.domain(…)
.range([height, 0]);
Note that the y-scale's range is inverted, so that y-0 is at the bottom of the chart rather than the default top. Again, see the conventions example for details.
Lastly, it looks like your x-values are seconds since UNIX epoch, so I would recommend converting your data to Date objects and then using a d3.time.scale. This makes it much easier to add an x-axis with date labels in the future. You can convert to dates as a preprocessing step like so:
data.forEach(function(series) {
series.data.forEach(function(d) {
d.x = new Date(d.x * 1000);
});
});
Related
Increasing size of d3 scatterplot
I have a scatter plot made in d3.v3 and no matter how large i increase the width and height variables it does not take up more screen space. var w = 700; var h = 700; var dataset = [ "Over 50 pairs of coordinates that look like [0,1][0,43], ]; //Create SVG element var svg = d3.select("body") .append("svg") .attr("width", w) .attr("height", h); svg.selectAll("rect") .data(dataset) .enter() .append("rect") .attr("x", function(d) { return d[0]; }) .attr("y", function(d) { return d[1]; }) .attr("width",5) .attr("height",5); There are more than 50 coordinates in my dataset and i want to be able to display them well so that is why i want this to take up more screen space. Currently there is nothing in my html, and no css for this. How can i adjust this so that the scatter plot takes more screen space?
The code you show doesn't place data points with any consideration of width or height, it places data points based on the values in the data: .attr("x", function(d) { return d[0]; }) .attr("y", function(d) { return d[1]; }) The x and y attributes, without any SVG transformation, expect pixel values. If a point has the datum [25,25] it will be placed 25 pixels from the top and left. Height and width of the svg do not matter - you are stating the pixel position based on the data, not based on the svg dimensions in combination with the data. Scales are a fundamental part of d3 - you can scale the x and y values of your data points across a range of pixel values. To do this we need to know the domain of the input data - the extent of input values - and the range of the output values - the extent of output values. There are a number of scales built into D3, including power, logarithmic and linear. I'll demonstrate a linear scale below, but the form is very similar for other continuous scales, such as those noted above. var xScale = d3.scaleLinear() // create a new linear scale .domain([0,50]) // map values from 0 to 50 to: .range([0,width]) // to pixel values of 0 through width var yScale = d3.scaleLinear() // create a new linear scale .domain([0,50]) // map values from 0 to 50 to: .range([height,0]) // to pixel values of height through 0 Since in SVG coordinate space y=0 is the top of the SVG, and normally we want data with y=0 to be displayed at the bottom of the graph, we use a range of [height,0] rather than [0,height] With the scales set up, to return the pixel value for a given data value we use: xScale(d[0]); // assuming d[0] holds the x value or yScale(d[1]); // assuming d[1] holds the y value Together this gives us: var w = 700; var h = 700; var dataset = d3.range(50).map(function(d) { return [Math.random()*50,Math.random()*50]; }) var x = d3.scaleLinear() .domain([0,50]) // input extent .range([0,w]) // output extent var y = d3.scaleLinear() .domain([0,50]) .range([h,0]) //Create SVG element var svg = d3.select("body") .append("svg") .attr("width", w) .attr("height", h); svg.selectAll("rect") .data(dataset) .enter() .append("rect") .attr("x", function(d) { return x(d[0]); }) .attr("y", function(d) { return y(d[1]); }) .attr("width",5) .attr("height",5); <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script> Of course we might not know the domain of the data if its dynamic, so d3 has a few built in helpers including d3.min, d3.max, d3.extent. d3.min and d3.max iterate through an array to find the minimum and maximum values of some property of each item in the data array: d3.min(dataArray, function(d) { return d.property; }) d3.max(dataArray, function(d) { return d.property; }) D3.extent does both at the same time returning an array containing min and max: d3.extent(dataArray, function(d) { return d.property; }) We can plug those into the scales too: var w = 700; var h = 700; var dataset = d3.range(50).map(function(d) { return [Math.random()*50,Math.random()*50]; }) var x = d3.scaleLinear() .domain([0,d3.max(dataset, function(d) { return d[0]; }) ]) // input extent .range([0,w]) // output extent var y = d3.scaleLinear() .domain( d3.extent(dataset, function(d) { return d[1]; }) ) .range([h,0]) //Create SVG element var svg = d3.select("body") .append("svg") .attr("width", w) .attr("height", h); svg.selectAll("rect") .data(dataset) .enter() .append("rect") .attr("x", function(d) { return x(d[0]); }) .attr("y", function(d) { return y(d[1]); }) .attr("width",5) .attr("height",5); <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
The below works perfectly fine for me. The things you might need to check is : The coordinates range is more than 5. (or you might need to use scale and axis) Is there a overriding styles to your g var w = 700; var h = 700; var dataset = [[10,10],[10,30],[30,50],[30,70]]; //Create SVG element var svg = d3.select("#scatterplot") .append("svg") .attr("width", w) .attr("height", h); svg.selectAll("rect") .data(dataset) .enter() .append("rect") .attr("x", function(d) { return d[0]; }) .attr("y", function(d) { return d[1]; }) .attr("width",5) .attr("height",5); <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script> <div id='scatterplot'></div>
How to include HTML entity symbols with tickFormat? [duplicate]
I need to display micromoles per liter (µmol/L) in my chart's tickFormat, but when I pass in "µmol/L", it shows the characters "µ" instead of the symbol for mu. How do I get it to render the symbol?
In that case, you shouldn't use an HTML entity. Once you're dealing with an SVG , use this: \u00B5 Check this snippet: var svg = d3.select("body") .append("svg") .attr("width", 500) .attr("height", 200); var scale = d3.scaleLinear() .range([40, 460]) .domain([0, 100]); var axis = d3.axisBottom(scale) .tickFormat(function(d){ return d + "\u00B5mol/L"}) .ticks(5); svg.append("g") .attr("transform", "translate(0,100)") .call(axis); text { font-size: 14px;} <script src="https://d3js.org/d3.v4.min.js"></script>
D3 Transition. Working with multiple lines on line graph and would like to transition smoothly between lines when button is pressed
I've got a little line graph written in d3, and I would like it to behave in such a way that one svg line path is visible when the graph is opened, and then when a user clicks a button, I would like the existing svg line path to smoothly transition into a line path representing a new and slightly different dataset. I can transition regular attributes just fine, but it is transitioning the "d" attribute that has me stumped. The code is here var w = 500; var h = 300; var padding = 30; var dataset = [ [1.3, 2015], [2.1, 2036.25], [3.4, 2057.5], [3.5, 2057.5], [4, 2100] ]; //Create scale functions var yScale = d3.scale.linear() .domain([0, 4]) .range([h - padding, padding]); var xScale = d3.scale.linear() .domain([2015, 2100]) .range([padding, w - padding * 2]); //Define X axis var xAxis = d3.svg.axis() .scale(xScale) .orient("bottom"); //Define Y axis var yAxis = d3.svg.axis() .scale(yScale) .orient("left"); //Create SVG element var svg = d3.select("body") .append("svg") .attr("width", w) .attr("height", h); var lineFunc = d3.svg.line() .x(function(d) { return xScale(d[1]); }) .y(function(d) { return yScale(d[0]); }) .interpolate('linear'); svg.append('svg:path') .transition() .attr('d', lineFunc(dataset)) .attr('class', 'dataline'); //Create X axis svg.append("g") .attr("class", "axis") .attr("transform", "translate(0," + (h - padding) + ")") .call(xAxis); //Create Y axis svg.append("g") .attr("class", "axis") .attr("transform", "translate(" + padding + ",0)") .call(yAxis); //create event listener and data reset functions d3.select("button") .on("click", function() { dataset = [ [1, 2015], [2.05, 2036.25], [3.25, 2057.5], [3.3, 2057.5], [3.7, 2100] ]; var dynamoBar = svg.append('svg:path') .transition() .attr("d", lineFunc(dataset)) .attr("class", "dataline"); }) .axis path, .axis line { fill: black; stroke: none; shape-rendering: crispEdges; } .axis text { font-family: sans-serif; font-size: 11px; } .dataline { stroke: purple; stroke-width: 1; fill: none; } <button id="button">Try it</button> <script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
When you select the line to transition with new data (after the button click), you're creating a new path: var dynamoBar = svg.append('svg:path')... Instead, select the existing path: var dynamoBar = svg.select('.dataline')... See this fiddle: http://jsfiddle.net/henbox/L2hehcry/
d3 Line Chart Filling at Arbitrary Line
I'm attempting to create a simple line chart with d3, however for some reason it's filling between the line and some midpoint. Here's the output: My javascript is the following: var width = 500, height = 500, padding = 10; var extentVisits = d3.extent(visits, function(obj){ return obj['visits']; }); var extentDates = d3.extent(visits, function(obj){ return obj['datestamp']; }); var yScale = d3.scale.linear() .domain(extentVisits) .range([height - padding, padding]); var xScale = d3.time.scale() .domain(extentDates) .range([0, width]); var line = d3.svg.line() .x(function(d) { return xScale(d['datestamp']); }) .y(function(d) { return yScale(d['visits']); }) d3.select('#chart') .append('svg') .attr('width', width) .attr('height', height) .append("g") .attr("transform", "translate(5,5)") .append('path') .datum(visits) .attr("class", "line") .attr('d', line); Where visits is of the form: visits = [{'datestamp': timestampA, 'visits': 1000}, {'datestamp': timestampB, 'visits': 1500}] I'm pretty new at d3 so I'm sure it's something simple, but it's driving me crazy. Thanks in advance.
The midpoint you're seeing is just the connection of the first and last point. This is because the path you've created has (by default) a black fill. Even though it's an open path (i.e., the first and last point are not actually connected), if filled, it will appear closed: The fill operation fills open subpaths by performing the fill operation as if an additional "closepath" command were added to the path to connect the last point of the subpath with the first point of the subpath. Source: SVG specification via this SO answer The solution here is to eliminate the fill and instead set a stroke. You can do this directly in JS with d3 or through CSS. path.line { fill: none; stroke: #000; } Demo showing both the CSS and JS method (commented out) on jsFiddle
D3 line graph not filling entire svg width
As the title states, I have created a D3 line/area graph, and I am finding it difficult to get the graph's width to remain constant, depending on the amount of data I have given it to render, it scales the width of the graph accordingly, but I am unsure of how I can get it to remain at a constant width, regardless of the amount of data given, which is what I would like to achieve. I imagine it has something to do with the scaling of the x and y coordinates, but I am stuck at the moment and can't seem to figure out why it is doing this. Here is the code I have thus far, //dimensions and margins var width = 625, height = 350, margin = 5, // get the svg and set it's width/height svg = d3.select("#main") .attr("width", width) .attr("height", height); //initialize the graph init([ [12345,42345,32345,22345,72345,62345,32345,92345,52345,22345], [1234,4234,3234,2234,7234,6234,3234,9234,5234,2234] ]); $("button").live('click', function(){ var id = $(this).attr("id"); if(id == "one"){ updateGraph([ [52345,32345,12345,22345,62345,72345,92345,32345,22345,22345,52345,32345,12345,22345,62345,72345,92345,32345,22345,22345,52345,32345,12345,22345,62345,72345,92345,32345,22345,22345], [4234,12345,2234,32345,6234,7234,9234,3234,2234,2234,4234,1234,2234,3234,6234,7234,9234,3234,2234,2234,4234,1234,2234,3234,6234,7234,9234,3234,2234,2234] ]); }else if(id == "two"){ updateGraph([ [12345,42345,32345,22345,72345,62345,32345,92345,52345,22345,12345,42345,32345,22345,72345,62345,32345,92345,52345,22345,12345,42345,32345,22345,72345], [1234,2345,3234,2234,7234,6234,3234,9234,5234,2234,1234,4234,3234,2234,7234,6234,3234,9234,5234,2234,1234,4234,3234,2234,7234] ]); } }); function init(data){ var x = d3.scale.linear() .domain([0,data[0].length]) .range([margin, width-margin]), y = d3.scale.linear() .domain([0,d3.max(data[0])]) .range([height-margin, margin]), /* line path generator */ line = d3.svg.line().interpolate('monotone') .x(function(d,i) { return x(i); }) .y(function(d) { return y(d); }), /* area path generator */ area = d3.svg.area().interpolate('monotone') .x(line.x()) .y1(line.y()) .y0(y(0)), groups = svg.selectAll("g") .data(data) .enter() .append("g"); svg.select("g") .selectAll("circle") .data(data[0]) .enter() .append("circle") .attr("class", "dot") .attr("cx", line.x()) .attr("cy", line.y()) .attr("r", 4); /* add the areas */ groups.append("path") .attr("class", "area") .attr("d",area) .style("fill", function(d,i) { return (i == 0 ? "steelblue" : "red" ); }); /* add the lines */ groups.append("path") .attr("class", "line") .attr("d", line); } function updateGraph(data){ var x = d3.scale.linear() .domain([0,data[0].length]) .range([margin, width-margin]), y = d3.scale.linear() .domain([0,d3.max(data[0])]) .range([height-margin, margin]), /* line path generator */ line = d3.svg.line().interpolate('monotone') .x(function(d,i) { return x(i); }) .y(function(d) { return y(d); }), /* area path generator */ area = d3.svg.area().interpolate('monotone') .x(line.x()) .y1(line.y()) .y0(y(0)); groups = svg.selectAll("g") .data(data), circles = svg.select("g") .selectAll("circle"); circles.data(data[0]) .exit().remove(); circles.data(data[0]) .enter().append("circle") .attr("class", "dot") .attr("cx", line.x()) .attr("cy", line.y()) .attr("r", 4); /* animate circles */ circles.data(data[0]) .transition() .duration(1000) .attr("cx", line.x()) .attr("cy", line.y()); /* animate the lines */ groups.select('.line') .transition() .duration(1000) .attr("d",line); /* animate the areas */ groups.select('.area') .transition() .duration(1000) .attr("d",area); } As well as a fiddle http://jsfiddle.net/JL33M/ Thank you!
The width of the graph depends on the range() you give it. range([0,100]) will always "stretch" the domain() values to take up 100 units. That's what your code is currently doing: var x = d3.scale.linear() .domain([0,data[0].length]) .range([margin, width-margin]);// <-- always a fixed width You want the width to depend on the number of data entries. Say you've decided you want each data point to take up 5 units, then range() needs to depend on the size of the dataset: var x = d3.scale.linear() .domain([0,data[0].length]) .range([margin, 5 * data[0].length]);// <-- 5 units per data point Of course, under these conditions, your graph width grows with the dataset; if you give it a really long data array of, say, 500 points, the graph would be 2500 units wide and likely run off screen. But if your data is such that you know the maximum length of it, then you'll be fine. On an unrelated note, I think your code could use a refactoring to be less repetitive. You should be able to achieve what you're doing with a single update() function, without the need for the init() function. This tutorial by mbostock describe the "general update pattern" I'm referring to. Parts II and III then go on to explaining how to work transitions into this pattern.