d3 Multi Series Chart with Incorrect Values - javascript

I'm trying to implement the d3 multi-series line chart example at http://bl.ocks.org/mbostock/3884955.
But I'm having a problem when using my own data - as the lines don't seem to match the legend/ values in the JSON data. For example at 10:00, the "high" line should be measuring 3512 "threats" on the y-axis.
city.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); }) // Problem here?
.style("stroke", function(d) { return color(d.name); });
Please take a look at this js fiddle:- http://jsfiddle.net/Ca8Uj/
Many thanks.

You're seeing this behaviour because you're using the basis interpolation on your lines, which smooths out peaks (ie the line is not guaranteed to go through each point).
If you change to:
var line = d3.svg.line()
.interpolate("linear")
You should see the correct values. Alternatively to get a smooth-ish line that shows correct exact y-values you could use monotone. To experiment with interpolations, have a look at: http://bl.ocks.org/mbostock/4342190

Related

d3.js: Multiple Series Line Chart

With this code as a starting point: https://bl.ocks.org/d3noob/15e4f2a49d0d25468e76ab6717cd95e7 I'm attempting to make a simple line graph with multiple data series. Per #wasserholz suggestion, I've added a complete MVP: https://github.com/djmcmath/d3-fail-mvp
The data parsing portion seems to be working correctly, in that I'm creating a map with 5 elements, each of which is a "sailing date" and array of values associated with that date. This is the variable named "group_by_sailing_date".
The axes appear to be reasonable: For the X-axis, I'm taking the "since_midnight" value, pulling the extents, and formatting it as a time. The Y-axis, similarly, is just the extents of the "margin" value. I get this -- so far so good:
Next, I want to add some lines to my chart. My thinking is that I iterate through the map, and for each of the map elements, I add the element as data to a series. What I get is a gigantic "Nothing Happens" though. No lines, no errors, just "Gosh, your data looks great, but I'm going to ignore it."
//line generator?
var valueline = d3.line()
.x(function(d) { return x(d.since_midnight); })
.y(function(d) { return y(d.margin); });
group_by_sailing_date.forEach(function (s) {
svg_summ.append("path")
.data(s)
.attr("stroke", "steelblue")
.attr("stroke-width", "3px")
.attr("d", valueline);
});
I feel like I'm missing something really fundamental here, but I'm drawing a complete blank (pun intended, ha ha ha). Help?
Following the rules of selection within d3.js (https://bost.ocks.org/mike/selection/) the code should look something like this:
svg_summ.selectAll("path")
.data(group_by_sailing_date)
.enter()
.append("path")
.attr("stroke", "steelblue")
.attr("fill", "none")
.attr("stroke-width", "1px")
.attr("d", (d) => valueline(d));
Ok, I've made it work, but I don't know why. I removed the .data(s) line, and changed "valueline" to "valueline(s)". No idea why this works. Technically, it's all good, but I'd be thrilled if someone could help me understand what this code actually means.
group_by_sailing_date.forEach(function (s) {
svg_summ.append("path")
.attr("stroke", "steelblue")
.attr("fill", "none")
.attr("stroke-width", "1px")
.attr("d", valueline(s));
});

How to enter data from several variables into one chart using d3?

It is quite easy to enter data in d3 as long as all of the data are in one file or in one variable like this:
svg.selectAll("path").data(datavariable).enter()
.append("path")
....
I need to enter data stored in separate variables into one graph. I tried
svg.selectAll("path").data(datavariable1).enter()
.append("path")
....
svg.selectAll("path").data(datavariable2).enter()
.append("path")
....
It doesn't seem to work. I also tried svg.selectAll("path1")... and svg.selectAll("path.datavariable1")..., but it just plots the second path over the first using the same data (datavariable1). Does anyone know how to get data from two variables to work on one graph at the same time?
I figured it out with the help of Nixie's comment. This is what worked for me. For the paths I didn't need to svg.selectAll anything. line1 and line2 get the data from data variables:
var line1 = d3.line()
.x(function(d,i) { return x(datavariable1[i].xvalue); })
.y(function(d,i) { return y(datavariable1[i].yvalue); });
var line2 = d3.line()
.x(function(d,i) { return x(datavariable2[i].xvalue); })
.y(function(d,i) { return y(datavariable2[i].yvalue); });
Then I just appended SVG as follows:
svg.append("path")
.attr("class", "line")
.attr("d", line1(datavariable1))
svg.append("path")
.attr("class", "line")
.attr("d", line2(datavariable2))
Answer in Nixie's comment to my question wouldn't work in my case because path has to stay path for the line graph, but it works for a dot graph. Hopefully this helps someone who might run into similar issue.

Adapting nvd3 linePlusBarChart to a histogram with cumulative percentage line?

I was looking for options to build histograms, and before I tried to do it in barebones d3, I found nvd3's linePlusBarChart desirable, although the given example wasn't designed for histograms with a cumulative percentage line.
With community help, I managed to get such a chart done in d3. I then went to try to adapt it and got this far.
It seems for some reason, I get 2 charts, a mini preview at the bottom with the larger chart at top, and the line has circular points, that are too big on the large chart. How do I get it to be just one chart, no points on line (or at least very small points), and make it match more like the basic d3 version?
Any suggestions?
I'm not sure if using nvd3 (look/feel) is a requirement for you, but it sounds like you're trying to replicate the d3 histogram example "...make it match more like the basic d3 version?".
If you're fine with a pure d3 version take a look at this fiddle which make small changes to this histogram example.
First, we add a cumulative property to the result of the d3 histogram function
data.forEach(function(d,i){
if(i === 0){
d.cum = d.y
}else{
d.cum = d.y + data[i-1].cum
}
})
Next we create a y scale for the cumulative line
var yc = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return d.cum; })])
.range([height, 0]);
Then, declare a line function to make use of our new cum property and yc scale
var line = d3.svg.line()
.x(function(d) { return x(d.x); })
.y(function(d) { return yc(d.cum); });
And finally draw the cumulative line
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
Regarding the nvd3 example you posted, you can set the chart's 'focusEnable' to false to get rid of the bottom chart.
var chart = nv.models.linePlusBarChart()
.margin({top: m.top, right: m.right, bottom: m.bottom, left: m.left})
.x(function(d,i) { return i })
.y(function(d,i) {return d[1] })
.focusEnable(false)
;
The other issues (big dots and shaded region around line) are css related. Adding nvd3's css file should solve them. See this fiddle.
Hope this helps.

Missing Initial Step from Step Plot D3.js

This is an issue that I have not discovered a clean fix for.
Within my step plot below:
http://jsfiddle.net/q47r3pyk/7/
You can see that I have a vertical blue line at the start of the chart.
This is because I added an additional x-value and y-value so that the first step will show up in the step plot.
If you look at
http://jsfiddle.net/q47r3pyk/8/
where I remove the first dummy entry with
x-value of "12-Jul-14"
y-value of 0
The first step of my step plot will have a width of zero.
Is there a recommended approach within d3.js for having the first step show without losing the last step by using step-after? or a fix for removing the vertical blue line that shows up with my hack of adding a dummy value?
Step is specified in
var line = d3.svg.line()
.x(function (d) {
return x(d.x);
})
.y(function (d) {
return y(+d.y);
})
.interpolate("step-after");
You could clip the path using SVG clip-path:
svg.append("clipPath").attr("id", "canvasClip")
.append("rect")
.attr("height", height)
.attr("width", width)
Then reference the clip-path on your line:
svg.append("path")
.datum(formatted_data)
.attr("class", "line")
.attr("d", line)
.attr("clip-path", "url(#canvasClip)")
Here's the fiddle, it seems to work fine:
http://jsfiddle.net/q47r3pyk/11/
But, ultimately, I think this is a case where the interpolator is acting as it should, and you really shouldn't be thinking about how to subvert it but rather whether it's the right interpolator for your data. In this case, I'd say no, and suggest you use "step" as your interpolator instead of "step-before" or "step-after". I think that would give you the result you're looking for and lines up well with your interactivity.
Is this help?
I have tried several times, i think it is the best result i have got, hope it helps!
x.domain(d3.extent(x_axis, function (d) {
return parseDate(d);
})).nice(x_axis.length);
fiddle

Drawing multiple sets of multiple lines on a d3.js chart

I have a d3 chart that displays two lines showing a country's imports and exports over time. It works fine, and uses the modular style described in 'Developing a D3.js Edge' so that I could quite easily draw multiple charts on the same page.
However, I now want to pass in data for two countries and draw imports and exports lines for both of them. After a day of experimentation, and getting closer to making it work, I can't figure out how to do this with what I have. I've successfully drawn multi-line charts with d3 before, but can't see how to get there from here.
You can view what I have here: http://bl.ocks.org/philgyford/af4933f298301df47854 (or the gist)
I realise there's a lot of code. I've marked with "Hello" the point in script.js where the lines are drawn. I can't work out how to draw those lines once for each country, as opposed to just for the first one, which is what it's doing now.
I'm guessing that where I'm applying data() isn't correct for this usage, but I'm stumped.
UPDATE: I've put a simpler version on jsfiddle: http://jsfiddle.net/philgyford/RCgaL/
The key to achieving what you want are nested selections. You first bind the entire data to the SVG element, then add a group for each group in the data (each country), and finally get the values for each line from the data bound to the group. In code, it looks like this (I've simplified the real code here):
var svg = d3.select(this)
.selectAll('svg')
.data([data]);
var g = svg.enter().append('svg').append('g');
var inner = g.selectAll("g.lines").data(function(d) { return d; });
inner.enter().append("g").attr("class", "lines");
inner.selectAll("path.line.imports").data(function(d) { return [d.values]; })
.enter().append("path").attr('class', 'line imports')
.attr("d", function(d) { return imports_line(d); });
The structure generated by this looks like svg > g > g.lines > path.line.imports. I've omitted the code for the export line here -- that would be below g.lines as well. Your data consists of a list of key-value pairs with a list as value. This is mirrored by the SVG structure -- each g.lines corresponds to a key-value pair and each path to the value list.
Complete demo here.
The point is that you're thinking to imperative. That's why you have so much code. I really can't put it better than Mike Bostock, you have to start Thinking with Joins:
svg.append("circle")
.attr("cx", d.x)
.attr("cy", d.y)
.attr("r", 2.5);
But that’s just a single circle, and you want many circles: one for each data point. Before you bust out a for loop and brute-force it, consider this mystifying sequence from one of D3’s examples.
Here data is an array of JSON objects with x and y properties, such as: [{"x": 1.0, "y": 1.1}, {"x": 2.0, "y": 2.5}, …].
svg.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 2.5);
I'll leave translating this example to the "from one line to many lines" as an excerxise.

Categories

Resources