Working with two charts in D3. I have a pie chat displaying parent data regarding a budget. When the user mouses over a pie slice, I am trying to push that slice's array data to a bar chart.
My data is setup like so:
{"Department":"Judiciary",
"Funds1415":317432,
"Fundsb":"317.4",
"annual": [ 282,288,307,276,276,298,309,317,317 ]
},
I'm trying to use this to pass the annual array to the barchart:
path.on('mouseover', function(d) {
...
bars.selectAll('rect').transition().attr("y", function(d) { return h - d.data.annual /125; });
bars.selectAll('rect').transition().attr("height", function(d) { return d.data.annual / 125; });
});
And here's the barchart I'm trying to send it to:
var bars = svg.selectAll("rect")
.data(budget)
.enter()
.append("rect")
.attr("class", "barchart")
.attr("transform", "translate(26,109)")
.attr("fill", function(d, i) {
return color2(i);
})
.attr('class', 'barchart')
.attr("x", function(d, i) {
return i * 14;
})
.attr("width", 12)
.attr("y", 100)
.attr("height", 100);
Link to full code here:
http://jsbin.com/zayopecuto/1/edit?html,js,output
Everything 'seems' to be working, except the data either isn't passing or it isn't updating the bar chart.
I've been banging my head up against this for a couple of days, to no avail. Originally I was thinking of placing the annual data in separate arrays and just transitioning from data source to data source on mouseover, but that seems backward and unnecessary.
First, your selector is wrong. bars is already a collection of rects, so you can't re-select the rects. Second, you haven't bound "updated" data to those rects. So, with this in mind, it becomes:
bars
.data(d.data.annual)
.transition()
.attr("height", function(d) {
return d / 125;
})
.attr("y", function(d) {
return h - d /125;
});
Here's an updated example.
What I understand from your code and comment is that, you have data points for your donut chart and each data object contains a property called 'annual' which you want to use as a input data for the bar chart.
You should be calling a separate function to plot your bar chart passing it the annual data array.
Clear the existing bar chart on 'mouseout' event, so that a new bar chart can be plotted on the next 'mouseover' event. You can use jQuery empty() function for clearing out the chart container.
Related
I am trying to upgrade this stackable bar chart to v4.
Everything works except for one thing.
When I filter one category the bars don't drop to the start of the x-axis. I get an error which says:
state.selectAll(...).forEach is not a function
I've tried multiple things but I can't figure this one out.
This is the broken code:
function plotSingle(d) {
class_keep = d.id.split("id").pop();
idx = legendClassArray.indexOf(class_keep);
//erase all but selected bars by setting opacity to 0
d3.selectAll(".bars:not(.class" + class_keep + ")")
.transition()
.duration(1000)
.attr("width", 0) // use because svg has no zindex to hide bars so can't select visible bar underneath
.style("opacity", 0);
//lower the bars to start on x-axis
state.selectAll("rect").forEach(function(d, i) {
//get height and y posn of base bar and selected bar
h_keep = d3.select(d[idx]).attr("height");
y_keep = d3.select(d[idx]).attr("y");
h_base = d3.select(d[0]).attr("height");
y_base = d3.select(d[0]).attr("y");
h_shift = h_keep - h_base;
y_new = y_base - h_shift;
//reposition selected bars
d3.select(d[idx])
.transition()
.ease("bounce")
.duration(1000)
.delay(750)
.attr("y", y_new);
})
}
I find it strange that this works flawlessly in D3 v3, why wouldn't this work in v4?
In d3 v3 selectAll returned an array, in d3 v4 it returns an object.
From the v3 notes:
Selections are arrays of elements—literally (maybe not literally...).
D3 binds additional methods to the array so that you can apply
operators to the selected elements, such as setting an attribute on
all the selected elements.
Where as changes in v4 include:
Selections no longer subclass Array using prototype chain injection;
they are now plain objects, improving performance. The internal fields
(selection._groups, selection._parents) are private; please use the
documented public API to manipulate selections. The new
selection.nodes method generates an array of all nodes in a selection.
If you want to access each node in v4 try:
selection.nodes().forEach( function(d,i) { ... })
But, this is just the node, to get the data you would need to select each node:
var data = [0,1,2];
var svg = d3.select("body").append("svg")
.attr("width",500)
.attr("height",200)
var circles = svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d,i) { return i * 20 + 50 })
.attr("cy", 50)
.attr("r", 4);
circles.nodes().forEach(function(d,i) {
console.log(d3.select(d).data());
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
But, if you need the data or to modify the selection properties, it could be easier to use selection.each(). d3.each iterates through each element of a d3 selection itself, and allows you to invoke a function for each element in a selection (see API docs here):
var data = [0,1,2];
var svg = d3.select("body").append("svg")
.attr("width",500)
.attr("height",200)
var circles = svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d,i) { return i * 20 + 50 })
.attr("cy", 50)
.attr("r", 4);
circles.each( function() {
console.log(d3.select(this).data());
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
In v3 of this bar chart, in the forEach loop
`states.selectAll("rect").forEach(function(d,i) {`
d is an array of nodes (the rectangles in each .g).
But, in v4 d3 selections aren't arrays, you can't use a forEach loop in the same way. But you can still get the nodes in it without much modification using selection.nodes() and than get the childNodes to replicate the array in the v3 version:
state.nodes().forEach(function(d, i) {
var nodes = d.childNodes;
Here we go through each element/node in state and get the child rects, returned as an array. Here's an updated fiddle.
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 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);
I have created a scatter chart with multiple y-axis.
I need to create a legend to indicate the scattering.
I have created a fiddle of the same in the link provided in comments.
Please help me out.
The reason is that in your code colors is an object, not an array.
D3 expects the data that is passed to it to be an array:
I've updated your fiddle - see http://jsfiddle.net/y3LEt/3/
The critical updates are:
var colors = [["Local", "#377EB8"],
["Global", "#4DAF4A"]];
legendRect
.attr("y", function(d, i) {
return i * 20;
})
.style("fill", function(d) {
return d[1];
});
Fairly new to d3.js, so hoping there's something obvious that I'm missing. Have been looking at this code over and over again, not sure where it's going wrong.
I have a bargraph, which displays 28 bars. I'm trying to:
Replicate this tutorial, where new data is added to graph, and oldest data is removed.
Instead of using shift to remove data, I'd like to push to the graph/array, but only display the last 28 numbers. I'd like to use the whole array for another display. This said, I can't get the above to work.
This is a jsFiddle to the troublesome code.
I have a graph located within a group (with a unique ID, #kI1data, plan on having multiple graphs later). When that group is clicked, a value from the data array is shifted, and another pushed. The graph is then redrawn. I believe it's this redraw function that is causing an issue; it removes rectangles, but doesn't add any new ones after the first click.
graphGroup.append("g")
.attr("id", "kInv1")
.append("rect")
.attr("class", "invBack")
.attr("x", 0).attr("y", 0)
.attr("width", kI1width).attr("height", kI1height)
.attr("fill", "grey")
.on("click", dataChange); // Add/remove data, and update graph
function dataChange() {
kInvd1.shift();
kInvd1.push(30); // 30 a placeholder number
updateGraph();
};
function updateGraph() {
// Select the group containing the rectangles that need updating
// (to avoid selecting other rectangles within the svg)
var kI3dataSelect = d3.select("#kI1data").selectAll("rect").data(kInvd1, function(d) { return d; });
// Enter new data
kI3dataSelect.enter().append("rect")
.attr("x", function(d, i) { return 1 + ((i+1) * ((kI1width)/28)); })
.attr("y", function(d) { return ykI1(d); })
.attr("height", function(d) { return (kI1height) - ykI1(d); })
.attr("width", (kI1width)/28)
.attr("fill", "black");
// Update positions (shift one bar left)
kI3dataSelect
.transition().duration(100)
.attr("x", function(d, i) { return 1 + (i * ((kI1width)/28)); });
kI3dataSelect.exit()
.transition().duration(100)
.attr("x", function(d, i) { return 1 + ((i-1) * ((kI1width)/28)); })
.remove();
};
For now I'm just trying to get the newly added data to display, whilst the oldest data is removed. Once that's done, if there are any pointers on how to display just the last 28 numbers in the array, it'd be very much appreciated!
The jsFiddle shows how a new bar is added on the first click, but subsequent clicks only translate and remove data, whilst new bars are not added.
Changing your dataChange function to the following gets you new insertions and limits the over all array to 28 elements.
function dataChange() {
if (kInvd1.length >= 28) kInvd1.shift()
kInvd1.push(Math.random() * 60); // 30 a placeholder number
updateGraph();
};