My code looks like the following (omitting the scales that I'm using):
// Nesting data attaching "name" as data key.
var dataNest = d3.nest()
.key(function(d) { return d.name; })
.entries(data);
// Defining what the transition for the path.
var baseTransition = d3.transition()
.duration(1000)
.ease(d3.easeLinear);
// Bind data to SVG (use name as key).
var state = svg2
.selectAll(".line2")
.data(dataNest, function(d, i) { return d.key; });
// Enter data join.
state.enter()
.append("path")
.attr("class", "line2");
// Set transition and define new path being transition to.
state.transition(baseTransition)
.style("stroke", "#000")
.style("fill", "none")
.attr("id", function(d) { return 'tag'+d.key.replace(/\s+/g, ''); })
.attr("d", function(d) { return line(d.values); });
state.exit().remove();
I'm mostly following this example in which the transitions are working on top of a drop down list. However, while the code I have above displays paths, it does not transition between the paths. Are there any obvious flaws in my approach?
EDIT: I have a more concrete example of what I want to do with this JSFiddle. I want to transition from data to data2 but the code immediately renders data2.
Related
I tried to toggle the data using
d3.interval and render the data as bar graphs on my svg.
As below.
interval--
d3.interval(function(){update(data);
flag =! flag;
},1000)
rendering--
function update(input){
var value = flag ? 'revenue':'profit'
x.domain(input.map(function(d){return d.month}));
y.domain([0,d3.max(input, function(d){return d[value]})])
var xAxisCall = d3.axisBottom(x);
var yAxisCall = d3.axisLeft(y).tickFormat(function(d){return '$'+d})
xAxisGroup.call(xAxisCall.tickSizeOuter(0))
yAxisGroup.call(yAxisCall.tickSizeOuter(0))
var rects = svg.selectAll('rect').data(data)
rects.exit().remove()
rects.enter().append('rect').attr('y', (d)=>{return height-y.range([0,height])(d[value])})
.attr('x', (d)=>{ return 50+x(d.month)})
.attr('height',(d)=>{return y(d[value])})
.attr('width',x.bandwidth())
.attr('fill','grey')
}
However,
it doesn't toggle the graph depending on the data fed into the rectangles.
It only worked when I put .append() at the end of rectangle rendering like below.
rects.attr('y', (d)=>{return height-y.range([0,height])(d[value])})
.attr('x', (d)=>{ return 50+x(d.month)})
.attr('height',(d)=>{return y(d[value])})
.attr('width',x.bandwidth())
.attr('fill','grey')
.enter().append('rect')
Why is it that I need to add enter and append at the end?
The reason that I don't get this is
Even if I put the .enter().append('rect') at the beginning,
the properties of the rect can be redefined and updated.
However, the properties of the rectangles weren't updated once I put the enter() argument beforehand.
I tested out by adding 'console.log' like below.
rects.enter().append('rect').attr('fill',function(){return flag? "rgba(100,0,0,0.2)":'rgba(0,100,0,0.2)'}).attr('y', (d)=>{return height-y.range([0,height])(d[value])})
.attr('x', (d)=>{ return 50+x(d.month)})
.attr('height',(d)=>{console.log(d[value])
;return y(d[value])})
.attr('width',x.bandwidth())
It doesn't log anything, it logs only when I put enter at the end.
Why all the arguments don't run?
In your first case you have an enter selection but nothing changing your update selection. In your second case you're changing your update selection while doing nothing to your enter selection.
Write your update and enter selections clearly and separately. This normally leads to duplicated code... if you don't like those duplications, use merge():
var rects = svg.selectAll('rect').data(data)
rects.exit().remove()
rects = rects.enter()
.append('rect')
.attr('fill', 'grey')
.merge(rects)
.attr('y', (d) => {
return height - y.range([0, height])(d[value])
})
.attr('x', (d) => {
return 50 + x(d.month)
})
.attr('height', (d) => {
return y(d[value])
})
.attr('width', x.bandwidth())
D3 uses the data join, which doens't act on elements directly, but describes their transformations. When you do:
var rects = svg.selectAll('rect').data(data)
you're not selecting all the rectangles, but only those in the "update" selection. Here's a more detailled explaination by the author of D3.
Nowadays, it's recommended to use the selection.join() method which makes selection-based code clearer:
svg.selectAll('rect')
.data(data)
.join(
enter => enter.append('rect')
.attr('fill', 'grey'),
update => update
.attr('x', (d) => 50 + x(d.month))
.attr('y', (d) => height-y.range([0,height])(d[value]))
.attr('width', x.bandwidth())
.attr('height', (d) => y(d[value])),
exit => exit.remove()
)
So I am drawing a tree (somewhat similar to a decision tree) with D3.js, using a premade template I found online. My goal at this point is to color the edges (or links) according to a property of each link object.
My first step was to color all the edges regardless of their property, so I added the following line:
// Update the links…
var link = svgGroup.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
//.style("stroke", "red") ---- this new line changes the color of all links
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
});
Now, I want to color them according to their property. It is accessible through the first link initialization:
// Update the links…
var link = svgGroup.selectAll("path.link")
.data(links, function(d) {
// const booleanProperty = d.target.colorProperty; --- change color according to this property
return d.target.id;
});
My thought was to create 2 different variables - one storing the links with the property set to true and another with the property set to false, so I could change the color of each link group. Somewhat like this:
// Update the links…
var trueLink = svgGroup.selectAll("path.link")
.data(links, function(d) {
const booleanProperty = d.target.colorProperty; --- change color according to this property
if (booleanProperty) return d.target.id;
});
var falseLink = svgGroup.selectAll("path.link")
.data(links, function(d) {
const booleanProperty = d.target.colorProperty; --- change color according to this property
if (!booleanProperty) return d.target.id;
});
This attempt didn't work, since selectAll("path.link") returns all the links regardless if there is a condition to return only in specific cases. I also tried using select("path.link"), but I got exactly the same result.
Is there a way I can pull this off?
I assume that you want to set that style in the "enter" selection for new items, so you could try to do the condition inside the .style call, something like this:
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.style("stroke", function(d) { return condition ? "red" : "blue"; } )
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
});
Where "condition" may be anything you need. If the condition or the color is stored in the datum, you can return d.target.colorProperty or use it for the condition.
I've made a plunker that updates data from one csv file to another, the yaxis updates accordingly but the rectangles don't.
The .attr("height", function(d) { return Math.abs(y(d[0])) - y(d[1]); }); portion of the code still has the old data from the previous file (I'm guessing).
I'm guessing this is because I haven't declared .data(series) in the updateData() function, I remember doing something like this in another chart
g.selectAll(".bar").data(series).transition()
etc...
but this doesn't work in this chart.
I can't figure it out, any help is appreciated!
The problem was that you didn't join the new data to existing bars.
To make this work well, you will want to specify a key for category of data when you join the series to the g elements to ensure consistency (although I notice that category-1 is positive in the first dataset, and negative in the second, but this is test data i guess)
Here's the updated plunkr (https://plnkr.co/edit/EoEvVWiTji7y5V3SQTKJ?p=info), with the relevant code highlighted below:
g.append("g")
.selectAll("g")
.data(series, function(d){ return d.key }) //add function to assign a key
.enter().append("g")
.attr("class", "bars") //so its easy to select later on
//etc
...
function updateData() {
d3.csv("data2.csv", type, function(error, data) {
///etc
let bars = d3.selectAll(".bars") //select the g elements
bars.data(series, function(d){ return d.key }) //join the new data
.selectAll(".bar")
.data(function(d) { return d; })
.transition()
.duration(750)
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return Math.abs(y(d[0])) - y(d[1]); });
I have this table and chart with scattergraph:
https://jsfiddle.net/horacebury/bygscx8b/6/
And I'm trying to update the positions of the scatter dots when the values in the second table column change.
Based on this SO I thought I could just use a single line (as I'm not changing the number of points, just their positions):
https://stackoverflow.com/a/16071155/71376
However, this code:
svg.selectAll("circle")
.data(data)
.transition()
.duration(1000)
.attr("cx", function(d) {
return xScale(d[0]);
})
.attr("cy", function(d) {
return yScale(d[1]);
});
Is giving me this error:
Uncaught TypeError: svg.selectAll(...).data is not a function
The primary issue is that:
svg.selectAll("circle") is not a typical selection as you have redefined svg to be a transition rather than a generic selection:
var svg = d3.select("#chart").transition();
Any selection using this svg variable will return a transition (from the API documentation), for example with transition.selectAll():
For each selected element, selects all descendant elements that match
the specified selector string, if any, and returns a transition on the
resulting selection.
For transitions, the .data method is not available.
If you use d3.selectAll('circle') you will have more success. Alternatively, you could drop the .transition() when you define svg and apply it only to individual elements:
var svg = d3.select('#chart');
svg.select(".line").transition()
.duration(1000).attr("d", valueline(data));
...
Here is an updated fiddle taking the latter approach.
Also, for your update transition you might want to change scale and values you are using to get your new x,y values (to match your variable names):
//Update all circles
svg.selectAll("circle")
.data(data)
.transition()
.duration(1000)
.attr("cx", function(d) {
return x(d.date);
})
.attr("cy", function(d) {
return y(d.close);
});
}
it seems like i am doing everything right except the fact that i can't change the colors to distinguish between the lines, this code should work:
colors = ["blue","red","yellow","green","black","blue","gray"];
linesGroup = svg.append("g").attr("class", "lines");
var linedata;
for (var i in chart_data) {
linedata = chart_data[i];
console.log(linedata);
linesGroup.append("path")
.attr("d", line(linedata.points))
.attr("class", "line")
.attr("fill", "none")
.attr("stroke", function(d, i) {
console.log(colors[Math.floor((Math.random()*6)+1)]);
return colors[colors[Math.floor((Math.random()*6)+1)]];
});;
};
i am also using jsfiddle for the full example
http://jsfiddle.net/yr2Nw/
Set the stroke as an inline style instead and access the color array properly:
.style("stroke", function(d, i) {
return colors[Math.floor((Math.random()*6)+1)];
});
Using a for in loop isn't a super idiomatic way of getting things done in d3 (you'll run into problems if you try to use i).