How to add numbers to bars with D3.js? - javascript

I've created a chart and it works fine, but I can't find out how to add numbers to columns. Numbers appear only if I hover the columns.
Tried different variants:
svg.selectAll("text").
data(data).
enter().
append("svg:text").
attr("x", function(datum, index) { return x(index) + barWidth; }).
attr("y", function(datum) { return height - y(datum.days); }).
attr("dx", -barWidth/2).
attr("dy", "1.2em").
attr("text-anchor", "middle").
text(function(datum) { return datum.days;}).
attr("fill", "white");
A link to my example: https://jsfiddle.net/rinatoptimus/db98bzyk/5/

Alternate idea to #gerardofurtado is to instead of a rect append a g, then group the text and rect together. This prevents the need for double-data binding.
var bars = svg.selectAll(".bar")
.data(newData)
.enter().append("g")
.attr("class", "bar")
// this might be affected:
.attr("transform", function(d, i) {
return "translate(" + i * barWidth + ",0)";
});
bars.append("rect")
.attr("y", function(d) {
return y(d.days);
})
.attr("height", function(d) {
return height - y(d.days) + 1;
})
.style({
fill: randomColor
}) // color bars
.attr("width", barWidth - 1)
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
bars.append("text")
.text(function(d) {
return d.days;
})
.attr("y", function(d) {
return y(d.days);
})
.attr("x", barWidth / 2)
.style("text-anchor", "middle");
Updated fiddle.

When you do this:
svg.selectAll("text")
you are selecting text elements that already exist in your SVG. In an enter selection, it's important selecting something that doesn't exist:
svg.selectAll(".text")
.data(newData)
.enter()
.append("svg:text")
.attr("x", function(datum) {
return x(datum.name) + x.rangeBand()/2
})
.attr("y", function(datum) {
return y(datum.days) - 10;
})
.attr("text-anchor", "middle")
.text(function(datum) {
return datum.days;
})
.attr("fill", "white");
Here is your fiddle: https://jsfiddle.net/c210osht/

You could try using the d3fc bar series component, which allows you to add data-labels via the decorate pattern.
Here's what the code would look like:
var svgBar = fc.seriesSvgBar()
.xScale(xScale)
.yScale(yScale)
.crossValue(function(_, i) { return i; })
.mainValue(function(d) { return d; })
.decorate(function(selection) {
selection.enter()
.append("text")
.style("text-anchor", "middle")
.attr("transform", "translate(0, -10)")
.text(function(d) { return d3.format(".2f")(d); })
.attr("fill", "black");
});
Here's a codepen with the complete example.
Disclosure: I am a core contributor to the d3fc project!

Related

d3 xScale is broken

I'm splitting data into categories e.g. rich poor and all. Using dropdown to get those values to display on a scatterplot. First transition happens, everything works as expected. Text labels are correctly displayed too, however when another option is selected and second transition happened half of circles are disappearing and every other transition is messed up. Only works if option all selected than again, first transition works, after that it is all messed up.
Codepen
function render(someData) {
xScale
.domain([
d3.min(someData, function(d) {
return +d.poorToys;
}),
d3.max(someData, function(d) {
return +d.poorToys;
})
]);
yScale
.domain([
d3.min(someData, function(d) {
return +d.richToys;
}),
d3.max(someData, function(d) {
return +d.richToys;
})+ 20
]);
//Adding circles
var circles = svg.selectAll("circle")
.data(someData, function(d) {
return d.country;
});
I believe a problem starts here.
circles
.enter()
.append("circle")
.attr("cx", function(d) {
if (currentSelection === "rich") {
return width - margin.right;
} else if (currentSelection === "poor") {
return margin.left;
} else if (currentSelection === "all") {}
return xScale(+d.poorToys);
})
.attr("cy", function(d) {
if (currentSelection === "rich") {
return margin.top;
} else if (currentSelection === "poor") {
return height - margin.bottom;
} else if (currentSelection === "all") {}
return yScale(+d.richToys);
})
.attr("r", function(d) {
if (currentSelection === "all") {
return rad;
}
})
.style("fill", "red")
.append("title")
.text(function(d) {
return d.country + " reports books for " + d.poorToys + "% in poor areas and " + d.richToys + "% in rich areas.";
});
circles
.transition()
.duration(2000)
.attr("cx", function(d) {
return xScale(+d.poorToys);
})
.attr("cy", function(d) {
return yScale(+d.richToys);
})
.attr("r", function() {
if (currentSelection !== "all") {
return rad * 1.5;
} else {
return rad;
}
});
circles
.exit()
.transition()
.duration(1000)
.style("opacity", 0)
.remove();
//Update x axis
svg.select(".x.axis")
.transition()
.duration(1000)
.call(xAxis);
//Update y axis
svg.select(".y.axis")
.transition()
.duration(1000)
.call(yAxis);
if (currentSelection !== "all"){
var labels = svg.selectAll("text.labels")
.data(someData, function(d){
return d.country;
});
labels
.enter()
.append("text")
.attr("transform", function(d){
return "translate(" + xScale(+d.poorToys) + "," + yScale(+d.richToys) + ")";
})
.attr("dx", 2)
.attr("dy", 1)
.attr("class", "labels")
.style("fill", "white")
.style("font-size", "5px")
.text(function(d){
return d.country;
});
labels
.transition()
.duration(2000)
.style("opacity", 1);
labels
.exit()
.remove();
} else {
svg.selectAll("text.labels")
.transition()
.duration(1000)
.style("opacity", 0)
.remove();
}
}
You incorrectly give your x axis a class of x_axis on line 57 then later try to select it as x.axis in your render function on line 179.
Once you fix that up, I think it should work as expected.
svg
.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + -14 + "," + (height + 30) + ")")
.call(xAxis);
Updated Pen
Apart from the axis problem found by #ksav your main problem is that you don't position the labels. Many labels are present in rich and poor.
var labels = svg.selectAll("text.labels")
.data(someData, function(d){ return d.country; });
labels
.enter()
.append("text")
.attr("x", function(d){ return xScale(+d.poorToys); })
.attr("y", function(d){ return yScale(+d.richToys); })
.attr("dx", 2)
.attr("dy", 1)
.attr("class", "labels")
.attr("opacity", 0)
.style("fill", "white")
.style("font-size", "8px")
.text(function(d){ return d.country; })
.merge(labels)
.transition()
.duration(2000)
.attr("x", function(d){ return xScale(+d.poorToys); })
.attr("y", function(d){ return yScale(+d.richToys); })
.attr("opacity", 1);
Also don't position the circles based on the selection
circles
.enter()
.append("circle")
.attr("cx", function(d) { return xScale(+d.poorToys); })
.attr("cy", function(d) { return yScale(+d.richToys); })
.attr("r", function(d) { return rad; })
.style("fill", "red")
.append("title")
.text(function(d) {
return d.country + " reports books for " + d.poorToys + "% in poor areas and " + d.richToys + "% in rich areas.";
});

d3.js appending different SVG elements based on a data value

I am working on an d3.js tree to display XML Schema elements in the tree based on their element type.
I have code like the following:
// Enter any new modes at the parent's previous position.
var nodeEnter = node.enter().append('g')
.attr('class', 'node')
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on('click', clickXSD)
.on('dblclick', dblclickXSD);
nodeEnter.append('rect')
.filter(function(d) { return d.data.elementType == "xs:element" })
.attr('class', 'node')
.attr('y', -16)
.attr('rx', 5)
.attr('ry', 5)
.attr('width', function(d) { return d.data.y_size + 50; })
.attr('height', function(d) { return 32; })
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "lemonchiffon";
});
I would like to be able to make the code a little cleaner by implementing something like:
nodeEnter.xs-element()
.filter(function(d) { return d.data.elementType == "xs:element" })
or something similar and then have a function to draw the xs:element and then one to draw the xs:attribute, etc.
I found my answer here: https://www.atlassian.com/blog/archives/extending-d3-js.
There are two possible ways. One is to make a prototype and the other is to use the call function. I am using the second way.
nodeEnter.filter(function(d) { return d.data.elementType == "xs:element" }).call(xsElement);
function xsElement(selection) {
selection.append('rect')
.attr('class', 'node')
.attr('y', -16)
.attr('rx', 5)
.attr('ry', 5)
.attr('width', function(d) {
return d.data.y_size + 50;
})
.attr('height', function(d) {
return 32;
})
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "lemonchiffon";
})
.filter(function(d) { return d.data.documentation != null })
.append("title").text(function(d) { return d.data.documentation; });
// Add labels for the nodes
selection.append('text')
.attr("dy", ".35em")
.attr("y", -6)
.attr("x", 6)
.attr("text-anchor", "start")
.text(function(d) { return d.data.name; });
.
.
.
}
Where selection is the value of the filtered nodeEnter.

Adding Label Text on Barchart D3js

I'm trying to figure out the correct way to displays labels that will sit on top of each bar in my bar chart. I'd also like them to display a % after the number.
Here is my Plunker: https://plnkr.co/edit/FbIquWxfLjcRTg7tiX4E?p=preview
I experimented with using this code from the question found in the link below. However, I wasnt able to get it to work properly (meaning the whole chart failed to display)
Adding label on a D3 bar chart
var yTextPadding = 20;
svg.selectAll(".bartext")
.data(data)
.enter()
.append("text")
.attr("class", "bartext")
.attr("text-anchor", "top")
.attr("fill", "white")
.attr("x", function(d,i) {
return x(i)+x.rangeBand()/2;
})
.attr("y", function(d,i) {
return height-y(d)+yTextPadding;
})
.text(function(d){
return d;
});
This is the most straight forward way given your existing code:
// keep a reference to the g holding the rects
var rectG = g.append("g")
.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d) { return "translate(" + x0(d.State) + ",0)"; })
.selectAll("rect")
.data(function(d) { return keys.map(function(key) { return {key: key, value: d[key]}; }); })
.enter();
// append the rects the same way as before
rectG.append("rect")
.attr("x", function(d) { return x1(d.key); })
.attr("y", function(d) { return y(d.value); })
.attr("width", x1.bandwidth())
.attr("height", function(d) { return height - y(d.value); })
.attr("fill", function(d) { return z(d.key); });
// now add the text to the g
rectG.append("text")
.text(function(d){
return d.value + '%';
})
.attr("x", function(d) { return x1(d.key) + (x1.bandwidth()/2); })
.attr("y", function(d) { return y(d.value); })
.style("text-anchor", "middle");
Updated plunker.

D3 Sankey Diagram: Adding New Node and Link with Transition

I am creating a sankey diagram using D3. I am trying to redraw the diagram with additional node and link and using transition to animate the previous diagram to the new diagram. I was able to add in new node and link but the old nodes and links did not change position. Since the new node and link could be added at any place within the diagram, I do not want to clear and redraw the entire svg, but use transition to get from the old diagram to the new one. The code to draw the sankey diagram is this:
function draw(data){
// Set the sankey diagram properties
var sankey = d3sankey()
.nodeWidth(17)
.nodePadding(27)
.size([width, height]);
var path = sankey.link();
var graph = data;
sankey.nodes(graph.nodes)
.links(graph.links)
.layout(32);
sankey.relayout();
// add in the links
link.selectAll(".link")
.data(graph.links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("fill", "none")
.style("stroke", function(d){
return "grey";
})
.style("stroke-opacity", "0.4")
.on("mouseover", function() { d3.select(this).style("stroke-opacity", "0.7") } )
.on("mouseout", function() { d3.select(this).style("stroke-opacity", "0.4") } )
.style("stroke-width", function (d) {
return Math.max(1, d.dy);
})
.sort(function (a, b) {
return b.dy - a.dy;
});
link.transition().duration(750);
//link.exit();
// add in the nodes
var node = nodes.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
// add the rectangles for the nodes
node.append("rect")
.attr("height", function (d) {
return d.dy;
})
.attr("width", sankey.nodeWidth())
.style("fill", function (d) {
return d.color = color(d.name.replace(/ .*/, ""));
})
.style("fill-opacity", ".9")
.style("shape-rendering", "crispEdges")
.style("stroke", function (d) {
return d3.rgb(d.color).darker(2);
})
.append("title")
.text(function (d) {
return d.name + "\n" + format(d.value);
});
// add in the title for the nodes
node.append("text")
.attr("x", -6)
.attr("y", function (d) {
return d.dy / 2;
})
.attr("dy", ".35em")
.attr("text-anchor", "end")
.attr("text-shadow", "0 1px 0 #fff")
.attr("transform", null)
.text(function (d) {
return d.name;
})
.filter(function (d) {
return d.x < width / 2;
})
.attr("x", 6 + sankey.nodeWidth())
.attr("text-anchor", "start");
node.transition().duration(750);
}
The JSFiddle
Is it possible to use transition to add in new node and link and reposition
old nodes and links?
Thanks!
I was able to do this by using moving the nodes and links to new position. The code for that is:
var nodes = d3.selectAll(".node")
.transition().duration(750)
.attr('opacity', 1.0)
.attr("transform", function (d) {
if(d.node == 3){
console.log(d.x, d.y);
}
return "translate(" + d.x + "," + d.y + ")";
});
var nodeRects = d3.selectAll(".node rect")
.attr("height", function (d) {
if(d.node == 3){
console.log(d.dy);
}
return d.dy;
})
var links = d3.selectAll(".link")
.transition().duration(750)
.attr('d', path)
.attr('opacity', 1.0)
Updated JSFiddle

Create Bars as Sums of Values

This one is sort of hard to define, but I'll try my best. Here's one piece of my 1000 line data:
{
"chartData":[
{"vNm":"Cyrus Shadfar","vId":5167,"values":[
{"period":"2014-12-12","id":37419,"amount":99.995},
{"period":"2014-01-09","id":32630,"amount":180.00},
{"period":"2014-08-25","id":35100,"amount":371.25},
{"period":"2014-08-26","id":35102,"amount":393.75},
{"period":"2014-12-08","id":37545,"amount":49.11},
{"period":"2014-09-03","id":35848,"amount":90.00},
{"period":"2014-12-16","id":37673,"amount":69.48},
{"period":"2014-08-27","id":35909,"amount":371.25},
{"period":"2014-06-04","id":34727,"amount":80.00},
{"period":"2014-06-09","id":34810,"amount":90.00},
{"period":"2014-11-17","id":37191,"amount":130.00},
{"period":"2014-03-27","id":33642,"amount":90.00},
{"period":"2014-06-11","id":34300,"amount":90.00},
{"period":"2014-09-16","id":36115,"amount":99.995},
{"period":"2014-06-18","id":34958,"amount":90.00},
{"period":"2014-05-07","id":34428,"amount":90.00},
{"period":"2014-04-10","id":33855,"amount":90.00},
{"period":"2014-10-24","id":36830,"amount":100.00},
{"period":"2014-12-19","id":37424,"amount":100.00},
{"period":"2014-11-26","id":37446,"amount":90.00},
{"period":"2014-02-20","id":32678,"amount":180.00},
{"period":"2014-03-20","id":33360,"amount":90.00},
{"period":"2014-12-12","id":37550,"amount":69.48},
{"period":"2014-04-22","id":34010,"amount":90.00},
{"period":"2014-04-24","id":34068,"amount":90.00},
{"period":"2014-03-13","id":33500,"amount":90.00},
{"period":"2014-07-16","id":35287,"amount":52.505},
{"period":"2014-06-05","id":34726,"amount":90.00},
{"period":"2014-02-24","id":32933,"amount":90.00},
{"period":"2014-11-10","id":37183,"amount":192.50},
{"period":"2014-08-28","id":36015,"amount":202.50},
{"period":"2014-03-20","id":33643,"amount":90.00},
{"period":"2014-06-13","id":34951,"amount":90.00},
{"period":"2014-06-12","id":34381,"amount":80.00},
{"period":"2014-06-20","id":34959,"amount":90.00}
]}
]
}
There are 25 sets exactly like this with different amounts, periods and id's. All I want is for the bars in my graph to be made of the sum of the amounts, instead of the individual amounts, as I have done here:
svg.selectAll(".bars")
.data(data.chartData, function(d){return d.vNm;})
.enter().append("g")
.attr("class", "bars")
.selectAll(".bar")
.data(function(d){return d.values;})
.enter().append("rect")
.attr("x", function(d) {
return x(d.vNm);
})
.attr("width", sizeOfSpace)
.attr("y", function(d){
return y(d.amount)
})
.attr("height", function(d) {
return height - y(d.amount); })
.attr("class", "bar")
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
Here's a working fiddle: http://jsfiddle.net/o5fbqqnq/ (rather large, both codewise and visually)
Seems like an easy question, so perhaps I'm misunderstanding it. Why doesn't something like the following work for you?
svg.selectAll(".bar")
.data(data.chartData, function(d){return d.vNm;})
.enter().append("rect")
.attr("x", function(d) {
return x(d.vNm);
})
.attr("width", sizeOfSpace)
.attr("y", function(d) {
return y(d.values.reduce(function(sum, d) {
return sum + d.amount;
}, 0));
})
.attr("height", function(d) {
return height - y(d.values.reduce(function(sum, d) {
return sum + d.amount;
}, 0));
})
.attr("class", "bar")
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
You can obviously optimize the code if your data sets are large.

Categories

Resources