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.
Related
I have some javascript inside a Flask application that uses d3 to render a bar graph from some randomly generated data in a specific range. It allows sorting by label or value, in ascending or descending order. Sorting works fine in every situation except specifically when sorting in ascending order by value.Most of the time, at least 2 of the bars are not in the right order.
From inspecting the code, the problem seems to be that the 'x' d3 attribute for these bars is not set correctly, so they're displayed in the wrong spot on the x axis. I figure it must be a javascript issue and not a Flask issue since the rest works but I can't seem to figure it out.
Data generation(done in Python in the original code but I generated it in js with jiphy here):
function data_bar_graph(num=32) {
data_min = 0.0;
data_max = 100.0;
output = [];
for (i in range(int(num))) {
output.push(dict(label=str(uuid.uuid4())[:2], value=random.uniform(data_min, data_max)));
return output;
}
}
The js file in question: https://jsfiddle.net/turtles_/bk3svowo/ (it doesn't work properly since I'm not sure how to pass the data or generate the graph in jsfiddle outside of flask, but you can see the code. I'll try to figure out the data passing if it's too abstract like this).
EDIT: I'm about 95% sure the error is somewhere in this following segment(only the x is sometimes wrong,rest of the values are sorted correctly so I'm thinking the .attr("x" has something weird. Does the following seem correct?
data = data.sort(sortComparator);
// Map x-axis labels
xScale.domain(
data.map(function(d)
{
return d.label;
}
));
// Map y-axis values
yScale.domain([0, d3.max(data,
function(d)
{
return d.value;
}
)]);
// Draw the axes
drawAxisLines(chart.g, xAxis, yAxis, computedHeight, 0, 0, 0);
var bars = chart.g.append("g")
.attr("class", "bars")
.selectAll(".bar")
.data(data)
.enter()
.append("rect")
.attr("x",
function(d)
{
return xScale(d.label);
}
)
.attr("y",
function(d)
{
return yScale(d.value);
}
)
.attr("fill",
function(d)
{
return colours(d.label);
}
)
.attr("height",
function(d)
{
return computedHeight - yScale(d.value);
}
)
.attr("width", xScale.rangeBand())
.style(cssStyling.bar);
}
sortComparator is the d3.ascending/d3.descending that was mentioned.
Since you don't have a working example, it's difficult to know exactly what the issue is, but there is commonly pitfall here: you're most likely comparing the numbers as strings (e.g. "12" < "2", whereas 12 > 2).
To make sure you're comparing numbers, you can do the following:
data = data.sort(function(a,b) { return d3.ascending(+a.value, +b.value); })
By preceding the fields with a +, you're coaxing those fields to numbers (which your Python code may not do automatically).
I am trying to build a d3 graph (currently only unconnected nodes) to handle relatively small node values. For this reason I am using a linear scale that reacts dynamically on the input values and scales them accordingly:
var xScale = d3.scale.linear().range([0, width]);
var yScale = d3.scale.linear().range([height, 0]);
[...]
xScale.domain(d3.extent(layout, function (d){ return d.x; }));
yScale.domain(d3.extent(layout, function (d){ return d.y; }));
In the current version (you can find it here: https://jsfiddle.net/mn1wmbe3/6/) the dragging and value changing of single nodes works perfectly fine. This is the code I am using to move a node:
node.filter(function(d) { return d.selected; })
.each(function(d) {
d.x = xScale.invert(d3.event.x);
d.y = yScale.invert(d3.event.y);
})
.attr("cx", xScale(d.x))
.attr("cy", yScale(d.y));
As seen in other examples, to be able to move several nodes simultaneously I would need to add the relative coordinates of the drag movement (d3.event.dx) to each selected node value. Like this:
node.filter(function(d) { return d.selected; })
.each(function(d) {
d.x += xScale.invert(d3.event.dx);
d.y += yScale.invert(d3.event.dy);
})
.attr("cx", xScale(d.x))
.attr("cy", yScale(d.y));
However, this behaves not as expected and moves the nodes way to far. You can see it in action here.
Do you have any suggestions why that is? Is there a possibility to work around that and move several nodes in a different way?
You should only need a few minor changes to make things work properly. First, change your drag event from:
d.x += xScale.invert(d3.event.dx);
d.y += yScale.invert(d3.event.dy);
To:
d.x = xScale.invert(d3.event.x);
d.y = yScale.invert(d3.event.y);
And things will no longer jump around like before. However, if you increase the size of the radius you will notice that when you first start dragging, the object will jump to center around the mouse position. This is because you now need to specify an origin function. Change this code:
d3.behavior.drag()
.on("drag", function(d) {
To this:
.call(d3.behavior.drag()
.origin(function(d){return {x:xScale(d.x), y:yScale(d.y)};})
.on("drag", function(d) {
With this, when you drag something it will remember the offset. And now things will behave nicely.
Now to move many nodes relatively you can write your own dx,dy function. Simply log the coordinates of the object on dragstart and then calculate dx,dy on drag, and add these values to the other nodes to be moved.
I figured it out. Basically, the error was using dynamic scaling:
xScale.domain(d3.extent(layout, function (d){ return d.x; }));
yScale.domain(d3.extent(layout, function (d){ return d.y; }));
Because of my test data, this lead to the domain beeing:
xScale.domain(-2, 1);
yScale.domain(-1, 0);
Which behaves perfectly fine, if you moove your nodes this way:
d.x = xScale.invert(d3.event.x);
d.y = yScale.invert(d3.event.y);
However, when trying to move nodes by adding the delta distances (d3.event.dx) to them, the invertion of the scale lead to unexpected results. The reason for this is the fact that xScale.invert(1) returned -1.994, which when added to the original value, gave unexpected results.
Solution
The workaround I used calculates the distance between the min and max value of the data and sets the domain from zero to this value:
extentX = Math.abs(d3.max(graph.layout, function(d) { return d.x;}) - d3.min(graph.layout, function(d) { return d.x;}));
extentY = Math.abs(d3.max(graph.layout, function(d) { return d.y;}) - d3.min(graph.layout, function(d) { return d.y;}));
xScale.domain([0, extentX]);
yScale.domain([0, extentY]);
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.
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];
});
I have followed the instructions at: http://bost.ocks.org/mike/path/ for creating and animating single graphs with single lines.
And, figured out how to create multiple lines in a graph: Drawing Multiple Lines in D3.js
Main Issue: I am having a hard time transitioning multiple lines after I shift & push in new data into my data array.
I create the N lines with: (time: epoch time, steps forward)
var seriesData = [[{time:1335972631000, value:23}, {time:1335972631500, value:42},...],
[{time:1335972631000, value:45}, {time:1335972631500, value:13},...],
[{time:1335972631000, value:33}, {time:1335972631500, value:23},...}],
[...],[...],...
];
var seriesColors = ['red', 'green', 'blue',...];
line = d3.svg.line()
.interpolate(interpolation)
.x(function(d) { return x(d.time); })
.y(function(d) { return y(d.value); });
graphGroup.selectAll(".line")
.data(seriesData)
.enter().append("path")
.attr("class", "line")
.attr("d", line)
.style('stroke', function(d, i) { return seriesColors[i]; })
.style('stroke-width', 1)
.style('fill', 'none');
And am trying to update N lines with a Javascript setInterval(...) calling a method with:
graph.selectAll("path")
.data(seriesData)
.attr("transform", "translate(" + x(1) + ")")
.attr("d", line)
.transition()
.ease("linear")
.duration(transitionDelay)
.attr("transform", "translate(" + x(0) + ")");
It can draw the initial set perfectly, but as soon as I update the data array, the lines just disappear.
UPDATE 01
I realized that I am using epoch time values in the x (xAxis shows date:time) as my example would probably work if I used the illustrative seriesData above.
The problem was the "transform", "translate" using x(1), x(0) was returning huge numbers, way larger than my graph needed to be transitioned.
I modified the update N lines method (above) to use a manual approach:
New Issue:
Now the graph moves left correctly, but the lines/graph pops back to the right, each setInterval update executes.
It's push/shift'ing the seriesData array correctly but it doesn't keep scrolling to the left to show the new data that IS actually being drawn.
graph.selectAll("path")
.data(seriesData)
.attr("d", line)
.attr("transform", null)
.transition()
.ease("linear")
.duration(2000)
.attr("transform", "translate(-200)");
Another reference that I have used is this: http://bl.ocks.org/1148374
Any thoughts?
One thing that jumps out at me as a possibility for error is the data calls that are used, the initial is
.data(seriesData)
the update uses
.data([seriesData])
which may cause issues, but its hard to tell without seeing the rest of what is going on, can you perhaps put it on jsfiddle?