I'm using a D3 V3 stacked bar like in this plunker:
http://plnkr.co/edit/rjO5vgYyeytTJjuv4emB?preview
My question is how would i render a stacked bar using this multiple measure method with some negative values, i.e. if the rect is negative then have it below the y-axis zero line and combine this with rects with positive values rendering above the y-axis zero line?
I've tried changin the rect y and height like:
//adding the rect for group chart
state.selectAll("rect")
.data(function(d) { return d.group; })
.enter().append("rect")
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(Math.max(0, d.y1)); })
.attr("height", function(d) { return Math.abs((y(d.y0) - y(d.y1))-y(0)); })
.style("fill", function(d) { return color(d.name); });
I've been trying but I'm a D3 noob so advice would be very welcome.
Thanks
Dom
A great idea would be to store the actual heights in the data object even if the values are negative.
This may be the solution that you need:
var y = d3.scale.linear()
.rangeRound([height/2, 0]);
state.selectAll("rect")
.data(function(d) {
return d.group;
})
.enter().append("rect")
.attr("width", x.rangeBand())
.attr("y", function(d) {
if(d.y1 < 0){
return y(d.y0)
}
return y(d.y1);
})
.attr("height", function(d) {
return Math.abs(y(d.y0) - y(d.y1));
})
.style("fill", function(d) {
return color(d.name);
});
your solution didn't work but your suggestion about altering the data object was a good one, in the end I changed the data object like:
//get group and total for stack
vars.data.forEach(function (d) {
var y0 = 0;
var y0n = 0;
d.group = color.domain().map(function (name) {
return {
name: name,
yt: d[name] < 0 ? 1 : 0,
y0: y0,
y1: (y0 += +Math.max(0, d[name])),
y0n: y0n,
y1n: (y0n += +Math.min(0, d[name])),
dimension: d.dimension,
qElemNumber: d.qElemNumber,
};
});
d.total = d.group[d.group.length - 1].y1;
d.totaln = d.group[d.group.length - 1].y1n;
});
so I created y0n and y1n for negative values and yt as an indicator that shows if the value is negative or not, then you can apply the y and height like:
.attr("y", function (d) {
if (d.yt === 1) {
return y(d.y0n);
}
return y(d.y1); //NEGFIX NEEDED
})
.attr("height", function (d) {
if (d.yt === 1) {
return Math.abs(y(d.y0n) - y(d.y1n));
}
return y(d.y0) - y(d.y1); //NEGFIX NEEDED
})
hopefully thi will help anyone looking for a similar fix :)
cheers
Dom
Related
I'm trying to get only the x-values of the nodes data (node.data()). At the moment I get the whole Object {x: 1, y: 0.4}. I want to compare the previous elements value with the actual. Maybe there is also a way to access the previous value with d.x instead of accessing the x-value using node.data()[i-1]?
node.append("text")
.attr("x", function(d) { return x(d.x); })
.attr("y", function(d) { return y(d.y); })
.text(function(d,i) {
console.log(node.data()[i])
var header = d3.select(this);
if(d.y < 0.7){
header.style("fill", "green");
}
return prozent(d.y); });
Well d.x would be the current x value and node.data()[i-1].x would be the previous x value, where i > 0.
The alternative is you could just use a var like so:
var lastx = undefined;
node.append("text")
.attr("x", function(d) { return x(d.x); })
.attr("y", function(d) { return y(d.y); })
.text(function(d,i) {
console.log(d.x, lastx); // do whatever you want to do comparing d.x and lastx
var header = d3.select(this);
if(d.y < 0.7){
header.style("fill", "green");
}
lastx = d.x; // make this the previous value for the next element
return prozent(d.y); });
I am making grouped bar chart based on Mike Bostock's tutorial.
I can't figure out how to put circles on top of my bars to act as tooltip when hovering, just like in this tutorial except it's on bars and not on a line.
I tried appending the circles like this :
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 5)
.attr("cx", function(d) { return x1(d.name); })
.attr("cy", function(d) { return y(d.value); })
});
But I get NaN values. I am very confused about which variable I should use to get the right cx and cy.
Here is my code.
Any ideas ?
Thank you
You will get NaN values since your data join is not correct, you are trying to get values that are not currently present in your data. In order to get those values you would need to make a reference to data.years.
Here is my approach:
// Inheriting data from parent node and setting it up,
// add year to each object so we can make use for our
// mouse interactions.
year.selectAll('.gender-circles')
.data(function(data) {
return data.years.map(function(d) {
d.year = data.year;
return d;
})
})
.enter().append('circle')
.attr("class", function(d) {
return "gender-circles gender-circles-" + d.year;
})
.attr("r", 10)
.attr('cx', function(d) {
console.log(d)
return x1(d.name) + 6.5;
})
.attr('cy', function(d) {
return y(d.value) - 15;
})
.style('display', 'none'); // default display
// ....
// Using an invisible rect for mouseover interactions
year.selectAll('.gender-rect-interaction')
.data(function(d) { // Inheriting data from parent node and setting it up
return [d];
})
.enter().append('rect')
.attr("width", x0.rangeBand()) // full width of x0 rangeband
.attr("x", function(d) {
return 0;
})
.attr("y", function(d) {
return 0;
})
.attr("height", function(d) { // full height
return height;
})
.style('opacity', 0) // invisible!
.on('mousemove', function(d) { // show all our circles by class
d3.selectAll('.gender-circles-' + d.year)
.style('display', 'block');
})
.on('mouseout', function(d) { // hide all our circles by class
d3.selectAll('.gender-circles-' + d.year)
.style('display', 'none');
});
Working plnkr: https://plnkr.co/edit/oH4KXdxdIW82nLGv46NI?p=preview
I'm following the part III tutorial of "Let' Make Some Charts" as an introduction to D3. Part of the tutorial calls for data insertion via TSV. Given I don't see this being an eventual use case for me, I'm attempting to modify the tutorial with the code below using a simple javascript array. However, nothing shows up on the page when I render in the browser. Can anyone shed some light on this?
Here's the tutorial link for some reference to the original code: http://bost.ocks.org/mike/bar/3/
My JS code:
<script>
var data = [4,8,15,16,23,42,57,89,100,160];
var width = 960,
height = 500; // have to make sure variables are case sensitive
var y = d3.scale.linear()
.domain([0, d3.max(data)]) // scaling based on max value
.range([height, 0]);
var chart = d3.select(".chart")
.attr("width", width)
.attr("height", height);
var barWidth = width / data.length;
var bar = chart.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d,i) { return "translate(" + i * barWidth + ",0)";});
bar.append("rect")
.attr("y", function(d) { return y(d.value); })
.attr("width", barWidth - 1)
.attr("height", function(d) { return height - y(d.value); });
bar.append("text")
.attr("x", barWidth / 2)
.attr("y", function(d) { return y(d.value) + 3; })
.attr("dy", ".75em")
.text(function(d) { return d.value; });
function type(d) {
d.value = +d.value;
return d;
}
</script>
The code you've copied references a named attribute value to determine what to draw. The data you've created doesn't have this but just the data. So everywhere you have d.value, you need to reference just d.
Complete demo here.
Your problem stems from the fact that you're using an Array of numbers for your data, while in Mike Bostock's example he was using an Array of Objects (for example, var data = [{value: 30}, ...]). Thus you need to change all cases of d.value to d in your code, since your data is not longer an Object but just a number.
bar.append("rect")
.attr("y", function(d) { return y(d); }) // <---- delete .value
.attr("width", barWidth - 1)
.attr("height", function(d) { return height - y(d); }); // <---- delete .value
bar.append("text")
.attr("x", barWidth / 2)
.attr("y", function(d) { return y(d) + 3; }) // <---- delete .value
.attr("dy", ".75em")
.text(function(d) { return d; }); // <---- delete .value
Making these changes produces the following bar chart:
I am trying to follow Mike Bostock's tutorial on d3js (http://mbostock.github.io/d3/tutorial/bar-2.html) to understand how to update charts dynamically but I am facing some hurdles.
In my chart, my bars on the left, rather than being simply removed, are sent behind my chart and I can't figure out why:
JS:
var t = 1297110663, // start time (seconds since epoch)
v = 70, // start value (subscribers)
data = d3.range(33).map(next); // starting dataset
function next() {
return {
time: ++t,
value: v = ~~Math.max(10, Math.min(90, v + 10 * (Math.random() - .5)))
};
}
setInterval(function(){
data.shift();
data.push(next());
console.log(data);
redraw();
}, 1000);
var w = 20,
h =80;
var x = d3.scale.linear()
.domain([0, 1])
.range([0, w]);
var y = d3.scale.linear()
.domain([0, 100])
.rangeRound([0, h]);
var chart = d3.select(".container").append("svg")
.attr("class", "chart")
.attr("width", w * data.length - 1)
.attr("height", h);
chart.selectAll("rect")
.data(data)
.enter().append("rect")
.attr("x", function(d, i){ return x(i) - 0.5; })
.attr("y", function(d) { return h - y(d.value) - .5; })
.attr("width", w)
.attr("height", function(d) { return y(d.value); });
function redraw(){
console.log(data);
var rect = chart.selectAll('rect')
.data(data, function(d){ return d.time; });
rect.enter().insert("rect")
.attr("x", function(d, i) { return x(i + 1) - .5; })
.attr("y", function(d) { return h - y(d.value) - .5; })
.attr("width", w)
.attr("height", function(d) { return y(d.value); })
rect.transition() // Shouldn't I use .update() here?
.duration(1000)
.attr("x", function(d, i) { return x(i) - .5; });
rect.exit().transition()
.duration(1000)
.attr('x', function(d, i) { return x(i - 1) - .5})
.remove();
}
Here is the jsfiddle: http://jsfiddle.net/kkMR4/
Another thing I don't understand is why we dont use .update()? If I understand correctly .enter() is used to create the DOM element where data didnt find any match in the DOM and .exit() is used to find the DOM elements which are not in data, so shouldn't I use update() to move all the other column to the left?
Many thanks
Best
The problem is in this block:
rect.exit().transition()
.duration(1000)
.attr('x', function(d, i) { return x(i - 1) - .5})
.remove();
The third line (.attr), reassigns the coordinates. If you want them to truly exit, you can remove this line.
rect.exit().transition()
.duration(1000)
.remove();
I've created a stacked chart animation/update app. However there appears to be NaN values being passed into the y and height variables. I am unsure as to what is wrong. If you toggle the data the charts eventually fill up.
jsFiddle
but the problem may occur first in setting the yaxis
svg.select("g.y")
.transition()
.duration(500)
.call(methods.yAxis);
It looks like something goes wrong in the bar rect enter/exit code.
//_morph bars
var bar = stacks.selectAll("rect")
.data(function(d) {
return d.blocks;
});
// Enter
bar.enter()
.append("rect")
.attr("class", "bar")
.attr("y", function(d) { return methods.y(d.y1); })
.attr("width", methods.x.rangeBand())
.style("fill", function(d) { return methods.color(d.name); });
// Update
bar
.attr("y", methods.height)
.attr("height", initialHeight)
.attr("width", methods.x.rangeBand())
.transition()
.duration(500)
.attr("x", function(d) { return methods.x(d.Label); })
.attr("width", methods.x.rangeBand())
.attr("y", function(d) { return methods.y(d.y1); })
.attr("height", function(d) { return methods.y(d.y0) - methods.y(d.y1); })
// Exit
bar.exit()
.transition()
.duration(250)
.attr("y", function(d) { return methods.y(d.y1); })
.attr("height", function(d) { methods.y(d.y0) - methods.y(d.y1); })
.remove();
//__morph bars
I've managed to narrow down the problem to the setDBlock function.
It appears if another chart has the same set of data, it takes on additional object parameters inside the dblock obj.
http://jsfiddle.net/XnngU/44/
I'm not sure at this stage as to how to clean it up. But I have isolated this via a legend and a function.
setDBlocks: function(incomingdata){
var data = incomingdata.slice(0);
methods.color.domain(d3.keys(data[0]).filter(function(key) { return key !== "Label"; }));
data.forEach(function(d) {
console.log("D", d);
var y0 = 0;
if(d["blocks"] == undefined){
d.blocks = methods.color.domain().map(function(name) {
var val = d[name];
if(isNaN(val)){
val = 0;
}
return {name: name, values: val, y0: y0, y1: y0 += +val};
});
}
d.total = d.blocks[d.blocks.length - 1].y1;
});
}
I've fixed the anomaly by deleting data in the update function. I'm not sure why though the data is not unique - it looks like if the data is the same - as the last chart - it gets modified accordingly and used again for its next chart. Is there a better way of cleaning this up - I've tried to keep objects unique and clean by cloning/splicing but maybe that is contributing towards the problem.
delete d.blocks;
delete d.total;
http://jsfiddle.net/XnngU/53/
update: function(data){
methods.el = this;
var selector = methods.el["selector"];
data.forEach(function(d) {
delete d.blocks;
delete d.total;
});
methods.animateBars(selector, data);
}