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.
Related
I have a demo here
Its a line bar chart using D3 in an Angular app.
I want the chart to be responsive so when the page is resized the chart width will increase and the height will be stay the same.
I'm doing this by capturing the window resize and then calling the function that draws the chart.
This works for the axis but I cant get the line and points to redraw.
I think it's to do with the way I'm trying to us the update pattern
How can I use the update pattern to redraw this line graph
const that = this;
const valueline = d3.line()
.x(function (d, i) {
return that.x(d.date) + 0.5 * that.x.bandwidth();
})
.y(function (d) {
return that.y(d.value);
});
this.x.domain(data.map((d: any) => d.date));
this.y.domain(d3.extent(data, function (d) {
return d.value
}));
const thisLine = this.chart.append("path")
.data([data])
.attr("class", "line")
.attr("d", valueline);
const totalLength = thisLine.node().getTotalLength();
thisLine.attr("stroke-dasharray", totalLength + " " + totalLength)
.attr("stroke-dashoffset", totalLength);
thisLine.transition()
.duration(1500)
.attr("stroke-dashoffset", 0)
let circle = this.chart.selectAll("line-circle")
.data(data);
circle = circle
.enter()
.append("circle")
.attr("class", "line-circle")
.attr("r", 4)
.attr("cx", function (d) {
return that.x(d.date) + 0.5 * that.x.bandwidth();
})
.attr("cy", function (d) {
return that.y(d.value);
})
circle
.attr("r", 4)
.attr("cx", function (d) {
return that.x(d.date) + 0.5 * that.x.bandwidth();
})
.attr("cy", function (d) {
return that.y(d.value);
})
circle
.exit()
.remove()
You have problems in both circles' selection and the line selection.
The circles' selection:
You're selecting "line-circle". Instead of that, you have to select by class: ".line-circle";
You're reassigning the circle selection:
circle = circle.enter()//etc...
Don't do that, otherwise circle will point to the enter selection, not to the update selection anymore. Just do:
circle.enter()//etc...
The path:
You're appending a new path every time you call the function. Don't do that. Instead, select the existing path and change its d attribute, or append a new path if there is none. Both behaviours can be achieved with this code:
let thisLine = this.chart.selectAll(".line")
.data([data]);
thisLine = thisLine.enter()
.append("path")
.attr("class", "line")
.merge(thisLine)
.attr("d", valueline);
Here is your forked code: https://stackblitz.com/edit/basic-scatter-mt-vvdxqr?file=src/app/bar-chart.ts
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'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
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 am trying to use D3 to render lines, but when I try to do this, the lines get rendered as polygons. I am not sure why. I included a screenshot to show you what it looks like.
Here is the code:
// Creates a time scale using the x_extent
// defined above
var x_scale = d3.time.scale()
.range([margin, width - margin])
.domain(x_extent);
// Creates a similarity scale using the y_extent.
// defined above.
var y_scale = d3.scale.linear()
.range([height - margin, margin])
.domain(y_extent);
// Construct a line.
var line = d3.svg.line()
.x(function(d) {
return x_scale(d.date);
})
.y(function(d) {
return y_scale(d.similarity);
});
// Render a line.
d3.select("svg")
.append("path")
.attr("d", line(data));
Try setting fill and stroke explicitly when appending the path, i.e.
d3.select("svg")
.append("path")
.attr("fill", "none")
.attr("stroke", "black")
.attr("d", line(data));