Graphs update only when I write enter() at the end - javascript

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()
)

Related

d3 node sub structure/styling based on data attribute

I'd like to create multiple functions which "style" a rectangle differently based on the data (i.e. call different .attr() or .style() functions or even appending other elements based on the data).
I managed to use .call(myDecorator) to dispatch the styling to a separate function, but here I am a little struck. In rectDecorator, I wish to call different sub-selections based on d.type. At the moment (.style('fill'...).style(...).style(...)) shows just one of those possible ways to decorate the rectangle, but ideally, I'd like to "branch" to different chained commands based on d.type. Any idea how I could accomplish that?
function rectDecorator(selection) {
// call different selection.xyz functions to style rect differently based on `d.type`
return selection
.style('fill', function(d) {
return d.color;
})
.style('fill-opacity', 1)
.style('stroke-width', 0);
}
d3.select('svg')
.selectAll('g')
.data(data)
.join(
enter => enter
.append('g')
.append('rect')
.attr('x', ...)
.attr('y', ...)
.attr('height', 20)
.attr('width', 40)
.call(rectDecorator)
update => update(),
exit => exit.remove()
There are multiple possible solutions to this, although, at some point, you will have to iterate through the selection to check each element's individual type to find the specific decorator. You could set up a Map whereby mapping the types to their corresponding decorators.
const decorators = new Map([
["type1", selection => selection.attr("attr1", "...")],
["type2", selection => selection.attr("attr2", "...")]
]);
Afterwards, you can easily loop through the selection using .each() and get the suitable decorator from the map based on the datum's type property:
.each(function(d) {
d3.select(this).call(decorators.get(d.type));
})
For a working demo have a look at the following snippet:
const decorators = new Map([
["red", selection => selection.attr("fill", "red")],
["green", selection => selection.attr("fill", "none").attr("stroke", "green")]
]);
const data = [{ type: "green" }, { type: "red" }];
d3.select('body').append("svg")
.selectAll('g')
.data(data)
.join(
enter => enter
.append('g')
.append('rect')
.attr('x', (_, i) => i * 50 + 50)
.attr('y', 50)
.attr('height', 20)
.attr('width', 40)
.each(function(d) {
d3.select(this).call(decorators.get(d.type));
})
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.15.0/d3.js"></script>

Why is my d3 path not transitioning?

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.

How to update data in a diverging stacked bar-chart d3js

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]); });

d3.js: Unusual Error

I have this d3.js project donut chart. For some reason, I am not able to access the data with in the onmousemove. The i value become zero is all the functions I pass within that event. I want to access the data of the particular slice where the mouse has moved.
How do I resolve this? Someone pls hlp!
Here is my code so far:
piesvg.selectAll("path")
.data(pie(data))
.enter().append("g")
.attr('class', 'slice')
var slice = d3.selectAll('g.slice')
.append('path')
.each(function(d) {
d.outerRadius = outerRadius - 20;
})
.attr("d", arc)
.attr('fill', function(d, i) {
return colorspie(i)
})
.on("mouseover", arcTween(outerRadius, 0))
.on("mouseout", arcTween(outerRadius - 20, 150))
.on("mousemove", function(data){
piesvg.select(".text-tooltip")
.attr("fill", function(d,i){return colorspie(i)})
.text(function(d, i){return d[i].domain + ":" + parseInt(d[i].value * 20)}); //Considers i as 0, so no matter whichever slice the mouse is on, the data of only first one is shown
});
Here is the full code:
https://jsfiddle.net/QuikProBro/xveyLfyd/1/
I dont know how to add external files in js fiddle so it doesn't work....
Here is the .tsv that is missing:
value domain
1.3038675 Cloud
2.2541437 Networking
0.15469614 Security
0.8287293 Storage
0.7292818 Analytics
0.61878455 Intelligence
1.7016574 Infra
0.4088398 Platform
Your piesvg.select is bound to be zero-indexed for i and in all probability undefined for d as it takes those values from a single tooltip element, not the slices. Hard to be 100% sure from the snippet, but I suspect you're wanting to access and use the 'data' and 'i' from the original selectAll on the slices.
.on("mousemove", function(d, i){
piesvg.select(".text-tooltip")
.attr("fill", colorspie(i))
.text(d.data.domain + ":" + parseInt(d.data.value * 20));
});
Edited as pie slices store original data in d.data property ^^^

Restart D3 bar chart animation

I am working on a widget that shows several D3 bar charts with different values, one after the other, in a sliding carousel.
When the page loads, the bar chart animate as it should, but when the page goes on to the next chart - whether it be on click or by itself - I would like it to restart the animation again each time.
I have tried calling animateChart() in the console but this doesn't work.
I am looking for a function that I can call from the console or from another function, like animateChart(), that will reload the D3 bar chart animation.
Here is a link to my widget:
http://jsfiddle.net/alocdk/oa5tg1qu/1/
I've found where you could enhance your animateChart function.
In fact you were modifying only data that were enterring your graph.
By calling :
d3.select(svg)
.selectAll("rect")
.data(data)
.enter().append("rect")
[...]
Everything following this, will only apply on the new data.
You may want to read these to understand the pattern to follow with data update in D3.
General Update Pattern, I
General Update Pattern, II
General Update Pattern, III
Here is my shot now http://jsfiddle.net/uknynmqa/1/
I've removed the loop you were doing on all your svg, because I assumed you wanted to only animate the current one.
And your function is updating all of the data, and not only those enterring thanks to :
// Update the data for all
var join = d3.select(svg)
.selectAll("rect")
.data(data);
// Append new data.
join.enter().append("rect")
.attr("class", function (d, i) {
var low = ""
i == minIndex ? low = " low" : "";
return "bar" + " " + "index_" + i + low;
})
// Update everyone.
join.attr("width", barWidth)
.attr("x", function (d, i) {
return barWidth * i + barSpace * i;
})
.attr("y", chartHeight)
.attr("height", 0)
.transition()
.delay(function (d, i) {
return i * 100;
})
.attr("y", function (d, i) {
return chartHeight - y(d);
})
.attr("height", function (d) {
return y(d);
});
D3 is following a really specific data update pattern.
Depending on what you want to do, you can follow this. It's up to you what you want to animate or not.
// Link data to your graph and get the join
var join = svg.selectAll('rect')
.data(data);
// Update data already there
join.attr('x', 0);
// Append the new data
join.enter().append('rect')
.attr('x', 0);
// Remove exiting elements not linked to any data anymore
join.exit().remove();
// Update all resulting elements
join.attr('x', 0);

Categories

Resources