Legend to D3.js scatterplot - javascript

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

Related

D3 - forEach is not a function when upgrading from v3 to v4

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.

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: Passing object array data to a barchart on mouseover

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.

Remove old circles from scatter graph on update

I have a scatter graph built with d3.js. It plots circles in the graph for the spending habits of specific people.
I have a select menu that changes the specific user and updates the circles on the scatter graph.
The problem is the old circles are not removed on update.
Where are how should I use .remove() .update(), please see this plnkr for a working example
http://plnkr.co/edit/qtj1ulsVVCW2vGBvDLXO?p=info
First, Alan, I suggest you to adhere to some coding style convention to make your code readable. I know that D3 examples, and the library code per se, almost never promote code readability, but it's in your interest first, because it's much easier to maintain readable code.
Second, you need to understand how D3 works with enter, update and exit sets, when you change data. Mike Bostock's Thinking with Joins may be a good start. Unless you understand how the joins work, you won't be able to program dynamic D3 charts.
Third, here's a bug in updateScatter. name.length makes no sense because your first name variable is value. So it's not the case of deleting old data in the first place.
// Update circles for the selected user chosen in the select menu.
svg.selectAll(".markers")
.data(data.filter(function(d){ return d.FirstName.substring(0, name.length) === value;}))
.transition().duration(1000)
.attr("cx", function(d) { return xScale(d.timestamp); })
.attr("cy", function(d) { return yScale(d.price); });
Also what that weird equality comparison is d.FirstName.substring(0, name.length) === name. Your first name data is not even spaced in CSV file. Plain d.FirstName == name is fair enough. If you expect trailing spaces anyway, just trim your strings in the place where you coerce prices and dates.
This is how correct updateScatter may look look like:
function updateScatter()
{
var selectedFirstName = this.value;
var selectedData = data.filter(function(d)
{
return d.FirstName == selectedFirstName;
});
yScale.domain([
0,
d3.max(selectedData.map(function(d)
{
return d.price;
}))
]);
svg.select(".y.axis")
.transition().duration(750)
.call(yAxis);
// create *update* set
var markers = svg.selectAll(".markers").data(selectedData);
// create new circles, *enter* set
markers.enter()
.append('circle')
.attr("class", 'markers')
.attr("cx", function(d)
{
return xScale(d.timestamp);
})
.attr("cy", function(d)
{
return yScale(d.price);
})
.attr('r', 5)
.style('fill', function(d)
{
return colour(cValue(d));
});
// transition *update* set
markers.transition().duration(1000)
.attr("cx", function(d)
{
return xScale(d.timestamp);
})
.attr("cy", function(d)
{
return yScale(d.price);
});
// remove *exit* set
markers.exit().remove();
}

Bar chart with d3.js and an associative array

I give up, I can't figure it out.
I was trying to create a bar chart with 3d.js but I can't get it working. Probably I don't understand it enough to deal with my complicate associative array.
My array has the following structure:
{"January"=>{"fail"=>13, "success"=>6},
"February"=>{"success"=>10, "fail"=>4},
"March"=>{"success"=>9, "fail"=>13},
"April"=>{"success"=>16, "fail"=>5},
"May"=>{"fail"=>52, "success"=>23},
"June"=>{"fail"=>7, "success"=>2},
"July"=>{},
"August"=>{"fail"=>6, "success"=>3},
"September"=>{"success"=>54, "fail"=>59},
"October"=>{"success"=>48, "fail"=>78},
"November"=>{"fail"=>4, "success"=>6},
"December"=>{"fail"=>1, "success"=>0}}`
I got the displaying of the axis working:
The code looks really ugly because I converted the names to a "normal" array:
monthsNames = new Array();
i = 0;
for (key in data) {
monthsNames[i] = key;
i++;
}
x.domain(monthsNames);
y.domain([0, 100]);
But I can't figure it out how to deal with the data.
I tried things like, svg.selectAll(".bar").data(d3.entries(data))
What is a good beginning I guess but I can't get the connection to the axis working.
What I want to create is a bar-chart that has the months as x-axis and every month has two bars (respectively one bar with two colours) - one for success and one for fail.
Can anybody please help me how to handle the data? Thanks in advance!
EDIT:
I cannot figure out how to scale x and y. If I use this code:
var x = d3.scale.ordinal()
.domain(monthsNames)
.range([0, width]);
var y = d3.scale.linear()
.domain([0,100])
.range([0, height]);
nothing is shown up then. If I print out the values that evaluate after using e.g. x(d.key) or x(d.value.fail) they are really strange numbers, sometimes even NaN.
EDIT:
d3.selectAll(".barsuccess")
.on('mouseover', function(d){
svg.append('text')
.attr("x", x(d.key))
.attr("y", y(d.value.success))
.text(d.value.success+"%")
.attr('class','success')
.style("font-size","0.7em")
})
.on('mouseout', function(d){
d3.selectAll(".success").remove()
});
d3.selectAll(".barfail")
.on('mouseover', function(d){
svg.append('text')
.attr("x", x(d.key)+x.rangeBand()/2)
.attr("y", y(d.value.fail))
.text(d.value.fail+"%")
.attr('class','fail')
.style("font-size","0.7em")
})
.on('mouseout', function(d){
d3.selectAll(".fail").remove()
});
Be sure to check out the bar chart tutorials here and here. You have basically all you need already. The connection between the axes and the data are the functions that map input values (e.g. "March") to output coordinates (e.g. 125). You (presumably) created these functions using d3.scale.*. Now all you need to do is use the same functions to map your data to coordinates in the SVG.
The basic structure of the code you need to add looks like
svg.selectAll(".barfail").data(d3.entries(data))
.enter().append("rect")
.attr("class", "barfail")
.attr("x", function(d) { x(d.key) })
.attr("width", 10)
.attr("y", function(d) { y(d.value.fail) })
.attr("height", function(d) { y(d.value.fail) });
and similar for success. If you use the same scale for the x axis for both types of bar, add a constant offset to one of them so the bars don't overlap. Colours etc can be set in the CSS classes.

Categories

Resources