Using d3 I want to draw several time series line charts on a single page, each one featuring two lines.
Using the example on this page for multiple charts, I've got code working with single lines on each chart. But I'm not sure how best to modify that example to work with multi-line charts.
The example does this:
d3.csv("sp500.csv", function(data) {
var formatDate = d3.time.format("%b %Y");
d3.select("#example")
.datum(data)
.call(timeSeriesChart()
.x(function(d) { return formatDate.parse(d.date); })
.y(function(d) { return +d.price; }));
});
with TimeSeriesChart() defined in this file.
How would I best adapt this to cope with two (or more) lines (with the same x-axis values, and the same y scales)?
FWIW, my data is in JS arrays/hashes, rather than being read from CSV, but I guess the principle will be the same.
You can pass in your data structure that contains data for several lines in the same way. The only difference would in how you would handle the data in the referenced file. You need to change the function in
selection.each(function(data) {
First, you need to adapt the preprocessing being done and similarly the code that determines the limits of the axes. Further down, you would change
// Update the line path.
g.select(".line")
.attr("d", line);
to something like
g.selectAll(".line").data(<data for your two lines here>)
.enter()
.append("path")
.attr("class", "line")
.attr("d", line);
and remove the line
gEnter.append("path").attr("class", "line");
before that.
The exact changes will depend on the format that you're passing in.
An alternative (and if you're just starting probably easier) approach would be to simply add the additional data in a new attribute, add a new line generator that accesses that data and repeat the code that generates the line and calls the line generator with a different class name and the different generator. This is a hacky approach that I wouldn't recommend in general, but it might be easier to get started that way.
Related
I'm trying to create several charts using the d3.chart framework. This seems like it would be a common use case: the whole point of d3.chart is for the charts to be reusable after all! If you can just point me to an example that would be awesome (:
I went through this (https://github.com/misoproject/d3.chart/wiki/quickstart) tutorial to create a very basic "circle graph". (I copied the code exactly). Now what I want to do is create a separate chart for several sets of data.
I edited it slightly.
Before editing, to set up the chart we called:
var data = [1,3,4,6,10];
var chart = d3.select("body")
.append("svg")
.chart("Circles")
.width(100)
.height(50)
.radius(5);
chart.draw(data);
I tried to change it to:
var data = [{key:1, values:[1,3,4,6,10]},
{key:2, values:[5,2,10,8,11]},
{key:3, values:[1,5,9,16,12]}]
var chart = d3.select("body")
.selectAll("chart)
.data(data)
.enter()
.append("svg")
.chart("Circles")
.width(100)
.height(50)
.radius(5);
chart.draw(function(d) { return d.values; });
This doesn't work however. You can see the corner of a circle in 3 diferent places, but
the rest of it is cut off. However if replace
chart.draw(function(d) { return d.values; });
with
chart.draw([1,3,4,6,10]);
it correctly generates 3 circle graphs, all with that one dataset. And when I add
chart.draw(function(d) { console.log(d.values) return d.values; });
The console shows the 3 arrays I'm trying to pass it! I don't understand what is happening here that's breaking the code. Shouldn't it be the exact same thing as passing the actual arrays to 3 separate charts?
Here's a link to the JS bin with it set up: http://jsbin.com/jenofovogoke/1/edit?html,js,console,output Feel free to mess around with it!
The code is wayyy at the bottom.
I'm pretty new to java script and d3, and entirely new to d3.chart. Any help would be super appreciated!
I asked Irene Ros, who helps run d3.chart, and she informed me that the problem is that d3.chart's draw method can only take an array or a data object- it cannot take a function. She gave me a few helpful hints for ways to get around this: by using a transform function to edit my data within the chart, rather than using a function, or by creating a chart that holds multiple charts (see https://gist.github.com/jugglinmike/6004102 for a great example of this).
However in the end I found the simplest solution for me was to manually set the data. It feels like a bit off a hack because D3 does this for you already, but it was much simpler than changing the whole set up of my chart, and allows for nested data (yay!).
var svg = d3.select("body")
.selectAll("svg")
.data(data)
.enter()
.append("svg");
svg.each(function(d, i) {
var chart = d3.select(this)
.chart("Circles")
.height(50)
.width(100)
.radius(5)
var data = d.values;
chart.draw(data);
});
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.
I have a D3.js multiline graph with circles added on every path peak. When I update my graph, the paths update just fine with the new data but the circles don't seem to get updated at all. Here's my code: http://jsbin.com/eMuQOHoV/3/edit
Does anyone know what I'm doing wrong?
You need to update the data point circles in the same way that you've created them. In particular, you're using a nested selection when creating them, but not when updating. This means that the data won't get matched correctly on update and nothing happens.
The code for the update should look like this.
var sel = svg.selectAll('.series')
.data(sources);
sel.select('path')
.transition()
// etc
// update circles
sel.selectAll('.datapoint')
.data(function (d) {
return d.values;
})
// etc
Complete jsbin here.
I'm trying to add an overlay of some paths defined by GeoJSON data using the Google Maps API. I started by using the examples in this thread and it worked fine with my data -- until I tried using two different paths. The paths need to be rendered with different colors, so I can't combine the datasets.
I've encountered an issue where not all of the paths will render fully. Here's the gist of what I'm attempting to do:
path2 = d3.geo.path().projection(googleMapProjection);
path4 = d3.geo.path().projection(googleMapProjection);
svg.selectAll("path")
.data(line2_geoJson.features)
.attr("d", path2) // update existing paths
.attr("stroke", "red")
.enter().append("svg:path");
svg.selectAll("path")
.data(line4_geoJson.features)
.attr("d", path4) // update existing paths
.attr("stroke", "green")
.enter().append("svg:path");
To show some examples:
http://jsfiddle.net/HWxKu/ -- Notice that the red path renders, but the green path never shows up. (Zoom in a bit.)
http://jsfiddle.net/X644x/ -- The only different is that I switched the order of the two svg.selectAll statements. The green path mostly renders, and bits of the red path render after you zoom in a bit.
Can anyone explain what might be happening? My thought is that either the Google Maps API is imposing some kind of limit on the overlay (perhaps a timeout?), or is something asynchronous happening here? I'm a novice to d3, so any explanation is greatly appreciated.
You're using the enter() selection wrong. The first set of statements to set "d" and "stroke" doesn't do anything at all because there are no paths yet. You need to put these after appending the new elements.
The second problem is that with the second statement, you're overwriting the first paths. By default, D3 matches new to existing data by the array index. That is, the first new feature is matched to the first existing path and so on. You need to supply a function to tell D3 how to match.
The code I think you want looks like this.
svg.selectAll("path")
.data(line2_geoJson.features, function(d) { return d.properties.route_id; })
.enter().append("path")
.attr("d", path2)
.attr("stroke", "red");
svg.selectAll("path")
.data(line4_geoJson.features, function(d) { return d.properties.route_id; })
.enter().append("path")
.attr("d", path4)
.attr("stroke", "green");
I have a working d3.js visualization that shows data return rates as a composite line and area graph (http://anf.ucsd.edu/tools/data_return_rates/).
The x-axis is time, the y-axis is percent data return rates. You can click various buttons at the top of the visualization to switch between datasets (just parsed JSON files).
In addition to the raw data plot, I want to add a simple line that defines the minimum data return rate required (85%). This is purely a visual aid to help users determine if the data return rates are above this minimum/threshold value. I calculate the x-values (time) for this 'minima'-line (only two points) using the d3.min() and d3.max() methods on the dataset. The y-values are just integers (85):
var min_data_return = [
{
"readable_time": d3.min(data, function(d) { return d.readable_time; }),
"value": 85
},
{
"readable_time": d3.max(data, function(d) { return d.readable_time; }),
"value": 85
}
]
(I do some other transformations to make sure everything plots okay)
Now, before I wanted this minima line added to the visualization, I just did the following to create the area and line plot, which worked:
svg.select("path.area").data([data]);
svg.select("path.line").data([data]);
There is some other plotting code later in the script:
svg.select("path.area").attr("d", area);
svg.select("path.line").attr("d", line);
All the d3.js examples I have read say that to create multiple lines, you just need to make your data array have all the datasets you want to plot, so in my example above, this:
svg.select("path.line").data([data]);
Becomes:
svg.select("path.line").data([data, min_data_return]);
And this should work. However, it doesn't. I see the data set line plotted as before, but not the min_data_return line.
Any ideas what I am doing wrong?
Gist here: https://gist.github.com/2662793
In the Gist, look at lines 133 - 140 (search for the commented string OPTION). These are the only lines that are relevant to getting this working. I put the whole script into the Gist for completeness.
Thanks in advance!
Instead of trying to draw both the reference line and the data line together, consider drawing them separately:
svg.append("svg:path")
.attr("class", "minline")
.attr("clip-path", "url(#clip)");
...
svg.select("path.minline").data([min_data_return]);
...
svg.select("path.minline").attr("d", line);
JSfiddle here with full example: http://jsfiddle.net/qAHC2/6/