I'm trying to draw a line graph in D3, with a transition based on a click. I can do it the long way round, describing each line as it goes, but I wanted to simplify the code using a key. I got the idea from D3 tips and tricks
Drawing the lines is fine, but transitioning to the second set of data is proving difficult - all the lines just converge to one. That line would be where the fifth line ought to be.
Here's the relevant bit of code - first defining the scales and he two sets of lines
/ Set the ranges
var x = d3.scale.linear().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
// Define the lines
var line1 = d3.svg.line()
.x(function(d) { return x(d.year); })
.y(function(d) { return y(d.data1); });
var line2 = d3.svg.line()
.x(function(d) { return x(d.year); })
.y(function(d) { return y(d.data2); });
Then getting the data, and nesting it, then setting up the key, based on the "symbol" column in the data (the data is all here, it's a bit boring to type out http://www.graphitti.org/admin2/files/experiments/lines_exp_data.tsv)
// Get the data
d3.tsv("lines_exp_data.tsv", function(error, data) {
data.forEach(function(d) {
d.year = +d.year;
d.data1 = +d.data1;
d.data2 = +d.data2;
d.data3 = +d.data3;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.year; }));
y.domain([80, d3.max(data, function(d) { return d.data1, d.data2; })]);
// Nest the entries by symbol
var dataNest = d3.nest()
.key(function(d) {return d.symbol;})
.entries(data);
// Loop through each symbol / key
dataNest.forEach(function(d) {
graph.append("path")
.attr("class", "line")
.attr("id", "lines")
.attr("d", line1(d.values));
});
This all works fine. Then I want to change them based on the click, so I do this
d3.select("#clickone")
.on("click", function() {
dataNest.forEach(function(d) {
graph.selectAll("#lines")
.transition()
.duration(1000)
.attr("d", line2(d.values));
;})
;})
And what happens then is that all the lines collapse to one line. You can see it all here http://www.graphitti.org/admin2/files/experiments/lines_exp_03.html
Is there a way of doing this? I've seen other questions on SO that suggest it can't be done (e.g. d3.js: Is it possible to line transition by key instead of index?) but I wasn't sure, as the circumstances were different. Sorry if this is a repeat
Related
There seems to be a bug in my d3 (version 4) code and I can't quite figure out why my line won't draw. My axes are there but my line path won't compute. I thought it was an issue with my data types (right now they're all numbers) But I must be missing something. Can you help me find what it is?
Here is the codepen
Here are some key parts:
var line = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.ratio); });
g.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", line);
Structuring my data:
var baseValue = +data[0].avg_water_temp;
data.forEach(function(d) {
d.date = d.date;
d.ratio = +(d.avg_water_temp /baseValue);
});
Just an fyi, The y axis is actually a distance from a base Value, not distance from 0. So my ratios (y values) will be percentages.
Like This(without the log scale)
Thanks!! Let me know if there's anything I can clarify.
So I'm using d3, v4. Trying to graph multiple lines individually, by date (x) and value (y). The problem is I cannot parse the date after doing it once for the first line. What can I do differently (for the first function in particular) to make it work for both?
Here is the code pen for the entire project (where I've included multiple arrays of data, but am only in the process of graphing the first two. Sorry for the messiness. The lines below can be found starting line 314)
Important pieces:
function type(data) {
data[0].forEach(function(d) {
d.inspected_at = parseTime(d.inspected_at);
console.log(d)
return d;
})
};
// define the line
var line1 = d3.line()
.x(function(d) { return x(d.inspected_at); })
.y(function(d) { return y(d.flow_data);});
var line2 = d3.line()
.x(function(d) { return x(d.inspected_at); })
.y(function(d) { return y(d.flow_data);});
// Add the line path(s)
//line CB-01
svg.append('g')
.attr('clip-path', 'url(#clipper)')
.selectAll('path#line').data([data[0]])
.enter().append("path")
.attr("class", "line")
.attr("id", "downstreamLine")
.attr("d", line1)
.attr("stroke", "blue");
//line CB-02
svg.append('g')
.attr('clip-path', 'url(#clipper)')
.selectAll('path#line').data([data[1]])
.enter().append("path")
.attr("class", "line")
// .attr("id", "downstreamLine")
.attr("d", line2)
.attr("stroke", "green");
Here is what my data looks like:
var data = [{"id":"CB-01","inspected_at":"2018-04-08T12:44:30.000Z","flow_data":"4"},
{"id":"CB-01","inspected_at":"2018-04-08T12:44:30.000Z","flow_data":"5"},
{"id":"CB-01","inspected_at":"2018-04-08T12:54:36.000Z","flow_data":"4"},
{"id":"CB-01","inspected_at":"2018-04-08T12:54:36.000Z","flow_data":"7"},
{"id":"CB-01","inspected_at":"2018-04-08T12:54:36.000Z","flow_data":"2"},
{"id":"CB-01","inspected_at":"2018-04-08T13:04:42.000Z","flow_data":"3"},
{"id":"CB-01","inspected_at":"2018-04-08T13:04:42.000Z","flow_data":"9"}],
[{"id":"CB-02","inspected_at":"2018-04-08T12:44:30.000Z","flow_data":"3"},
{"id":"CB-02","inspected_at":"2018-04-08T12:44:30.000Z","flow_data":"2"},
{"id":"CB-02","inspected_at":"2018-04-08T12:54:36.000Z","flow_data":"3"},
{"id":"CB-02","inspected_at":"2018-04-08T12:54:36.000Z","flow_data":"5"},
{"id":"CB-02","inspected_at":"2018-04-08T12:54:36.000Z","flow_data":"6"},
{"id":"CB-02","inspected_at":"2018-04-08T13:04:42.000Z","flow_data":"8"},
{"id":"CB-02","inspected_at":"2018-04-08T13:04:42.000Z","flow_data":"7"}]
The first array being one line, the second array is the next.
Thanks in advance! Let me know if I can provide any more info.
This should work for any amount of lines, iterate every line array and do the same process for each point in that line array:
function type(data) {
data.forEach(arr => arr.forEach(d => d.inspected_at = parseTime(d.inspected_at)))
};
I have already a code parallel coordinates graph, everything works fine. Now i'm trying to use colors to color-code the parallel coordinates visualization, but something is wrong. In dataset (http://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data) i've got different names of wine species (1st column is class identifier (1-3)), but in graph draws only one color. Could anybody help me?
Graph :
enter code here
// CREATE A COLOR SCALE
var color = d3.scale.ordinal()
.domain(['1','2','3'])
.range(['red','blue','green'])
d3.csv("wine.csv", function(error, wine) {
// Extract the list of dimensions and create a scale for each.
x.domain(dimensions = d3.keys(wine[0]).filter(function(d) {
return d != "name" && (y[d] = d3.scale.linear()
.domain(d3.extent(wine, function(p) { return +p[d]; }))
.range([h, 0]));
}));
// Add grey background lines for context.
background = svg.append("g")
.attr("class", "background")
.selectAll("path")
.data(wine)
.enter().append("path")
.attr("d", path);
// USE THE COLOR SCALE TO SET THE STROKE BASED ON THE DATA
foreground = svg.append("g")
.attr("class", "foreground")
.selectAll("path")
.data(wine)
.enter().append("path")
.attr("d", path)
.attr("stroke", function(d) {
var species = d.name.slice(0,d.name.indexOf(' '));
return color(species);
})
Once you already have your ordinal scale for the colors with the domain and range defined, you only need to color your lines according to d.name:
.attr("stroke", function(d) {
return color(d.name);
});
So I have one view that creates paths and I'm trying to have it so that each line that I draw has a random color.
I'm currently doing this-
var color = d3.scale.category20();
//other code that does stuff
this.path = svg.append("path")
.attr("d", line(newData))
.style("stroke", function(d,i) {
var colorIndex = Math.random() * (20 - 0) + 0;
return color(colorIndex); })
.attr("fill","none")
.attr("class","line");
This does not draw lines with different colours. Further, when I do this
this.path = svg.append("path")
.attr("d", line(newData))
.style("stroke", function(d,i) {
return color(4); })
.attr("fill","none")
.attr("class","line");
The color is still blue.
Why is that happening?
This is happening because domain was not set -
d3.scale.category10() not behaving as expected
has the answer
I have set up a minimal fiddle to show you the proper way to set up the line function. I also changed the color scheme to category(10) to show more contrasting colors (you can still use category20 and see a difference in colors though). Here is the FIDDLE.
var lineFunction = d3.svg.line()
.x(function (d) {
return d.x;
})
.y(function (d) {
return d.y;
})
.interpolate("linear");
I'm attempting to fill the area under the graph with different colors, depending on x value ranges, say for example, for x values 0 to 10 yellow, from 10 to 20 red and so on. Is there a way to do that?
My javascript for single fill color is
var m = 80;
var w = 900 - 3*m;
var h = 600- 3*m;
var x = d3.scale.linear().range([0, w]);
var y = d3.scale.linear().range([h, 0]);
x.domain(d3.extent(data, function(d) { return d.time; }));
y.domain(d3.extent(data, function(d) { return d.points; }));
var line = d3.svg.line()
.x(function(d) {
return x(d.time);
})
.y(function(d) {
return y(d.points);
})
var graph = d3.select("#graph").append("svg:svg")
.attr("width", w+3*m)
.attr("height", h+3*m)
.append("svg:g")
.attr("transform", "translate(" + 1.5*m + "," + 1.5*m + ")");
var area = d3.svg.area()
.x(function(d) { return x(d.time); })
.y0(h)
.y1(function(d) { return y(d.points); });
graph.append("path")
.datum(data)
.attr("class", "area")
.attr("d", area)
.style("fill","steelblue");
Thanks in advance!
You basically have two options for doing this.
You can define separate areas for the different colours.
You can define a single area and use a gradient to simulate different colours.
The second one is probably easier, as you don't need to draw any separate paths, you can simply fill the one like you're currently doing.
For the gradient, you would need to define the stops (i.e. colour changes) to correspond to the values. In particular, you would need to introduce two stops at the same place to make it appear like the colour is changing suddenly. More information on gradients here. The code would look something like this.
var grad = graph.append("defs")
.append("linearGradient")
.attr("id", "grad");
grad.append("stop").attr("offset", "0%").attr("stop-color", "yellow");
grad.append("stop").attr("offset", "10%").attr("stop-color", "yellow");
grad.append("stop").attr("offset", "10%").attr("stop-color", "red");
grad.append("stop").attr("offset", "20%").attr("stop-color", "red");
// etc
graph.append("path")
.style("fill", "url(#grad)");
The positions of the stops would be determined by your scale.