How to display values on top of nvd3 multi par graph? - javascript

I want the actual value of each bar displayed on top in the way it's shown here
I am trying this on multi bar chart.
Can't find reference anywhere.

Duplicate of How to display values in Stacked Multi-bar chart - nvd3 Graphs
There is a fix you can implement yourself at https://gist.github.com/topicus/217444acb4204f364e46
EDIT: Copied the code if the github link gets removed:
// You need to apply this once all the animations are already finished. Otherwise labels will be placed wrongly.
d3.selectAll('.nv-multibar .nv-group').each(function(group){
var g = d3.select(this);
// Remove previous labels if there is any
g.selectAll('text').remove();
g.selectAll('.nv-bar').each(function(bar){
var b = d3.select(this);
var barWidth = b.attr('width');
var barHeight = b.attr('height');
g.append('text')
// Transforms shift the origin point then the x and y of the bar
// is altered by this transform. In order to align the labels
// we need to apply this transform to those.
.attr('transform', b.attr('transform'))
.text(function(){
// Two decimals format
return parseFloat(bar.y).toFixed(2);
})
.attr('y', function(){
// Center label vertically
var height = this.getBBox().height;
return parseFloat(b.attr('y')) - 10; // 10 is the label's magin from the bar
})
.attr('x', function(){
// Center label horizontally
var width = this.getBBox().width;
return parseFloat(b.attr('x')) + (parseFloat(barWidth) / 2) - (width / 2);
})
.attr('class', 'bar-values');
});
});

Apparently this doesn't exist yet. There is an issue (https://github.com/novus/nvd3/issues/150) that was closed because this is (apparently) hard to implement.

I am not sure what you have tried so fat, but the example in here is pretty straight forward.
.showValues(true) pretty much does the trick.
Hope it helps.

Related

How can I make the D3 Collide force apply only on X

I would like to use the Collide force in D3 to prevent overlaps between nodes in a force layout, but my y-axis is time-based. I would like to only use the force on the nodes' x positions.
I have tried to combine the collide force with a forceY but if I increase the collide radius I can see that nodes get pushed off frame so the Y position is not preserved.
var simulation = d3.forceSimulation(data.nodes)
.force('links', d3.forceLink(data.links))
.force('x', d3.forceX(width/2))
.force('collision', d3.forceCollide().radius(5))
.force('y', d3.forceY( function(d) {
var date = moment(d.properties.date, "YYYY-MM-DD");
var timepos = y_timescale(date)
return timepos; }));
My hunch is that I could modify the source for forceCollide() and remove y but I am just using D3 with <script src="https://d3js.org/d3.v5.min.js"></script> and I'm not sure how to start making a custom version of the force.
Edit: I have added more context in response to the answer below:
- full code sample here
- screenshot here
Not quite enough code in the question to guarantee this is what you need, but making some assumptions:
Often when using a force layout you would allow the forces to calculate the positions and then reposition the node to a given [x,y] co-ordinate on tick e.g.
function ticked() {
nodeSelection.attr('cx', d => d.x).attr('cy', d => d.y);
}
Since you don't want the forces to affect the y co-ordinate just remove it from here i.e.
nodeSelection.attr('cx', d => d.x);
And set the y position on, say, enter:
nodeSelection = nodeSelection
.enter()
.append('circle')
.attr('class', 'node')
.attr('r', 2)
.attr('cy', d => {
// Set y position based on scale here
})
.merge(nodeSelection);

dc.js: Show sum totals at end of rowChart

This is similar to a problem that I asked before. However instead of a bar chart, I'm trying to display the totals for a row chart.
I've tried to adjust the code accordingly, but my familiarity with JS is pretty low. Below is my attempt, any help would be greatly appreciated.
genderChart.on('renderlet', function (chart) {
var rowData = [];
var rows = chart.selectAll('.row').each(function (d) {
rowsData.push(d);
});
//Remove old values (if found)
d3.select(rows[0][0].parentNode).select('#inline-labels').remove();
//Create group for labels
var gLabels = d3.select(bars[0][0].parentNode).append('g').attr('id', 'inline-labels');
for (var i = rows[0].length - 1; i >= 0; i--) {
var b = rows[0][i];
gLabels.append("text")
.text(d3.format(",f")(rowsData[i].data.value))
.attr('x', +b.getAttribute('x') + (b.getAttribute('width') + 20)
.attr('y', +b.getAttribute('y'))
.attr('text-anchor', 'middle')
.attr('fill', 'black');
}
});
Right now there's no error in the console, so it's being render correctly...somwhere. So far the text does not show up anywhere near my row chart. Thanks!
As an alternative to your approach, you could try using renderTitleLabel.
Something like this:
genderChart
.title(function(d) {
return d.value; // or your custom value accessor
})
.renderTitleLabel(true)
.titleLabelOffsetX(10); // optional offset from the right side of the chart
(This is available in the 2.0 betas, I am not sure about previous versions.)

How to update both the content and location of text labels on a D3 pie chart

I'm trying to make a pie chart with d3.js that looks like this:
Note that the labels are placed along the edges of the pie chart. Initially, I am able to draw the pie charts and properly place the text nodes (the fiddle only displays one pie chart; assume, however, that they all have data that works and is appropriate, as this one does). However, when I go to adjust the data, I can't seem to .attr(translate, transform) them to the correct region along the edge of the pie chart (or do anything to them, for that matter):
changeFunctions[i] = function (data, i) {
path.data(pie(data))
.transition()
.duration(750)
.attrTween("d", arcTween);
text.each(function (d, num) {
d3.select(this)
.text(function (t) {
return t.data.name+". "+(100 * t.data.votes/totalVotes).toFixed(0) + "%";
})
/*
.attr("transform", function (d) {
//console.log("the d", d)
var c = arc.centroid(d),
x = c[0], y = c[1],
h = Math.sqrt(x * x + y * y);
return "translate(" + (x/h * 100) + ',' + (y/h * 100) + ")";
})*/
.attr("opacity", function (t) {
return t.data.votes == 0 ? 0 : 1;
});
})
}
I have omitted the general code to draw the pie chart; it's in the jsfiddle. Basically, I draw each of the pie charts in a for loop and store this function, changeFunctions[i], in a closure, so that I have access to variables like path and text.
The path.data part of this function works; the pie chart properly adjusts its wedges. The text.each part, however, does not.
How should I go about making the text nodes update both their values and locations?
fiddle
When updating the text elements, you also need to update the data that's bound to them, else nothing will happen. When you create the elements, you're binding the data to the g element that contains the arc segment and text. By then appending path and text, the data is "inherited" to those elements. You're exploiting this fact by referencing d when setting attributes for those elements.
Probably the best way to make it work is to use the same pattern on update. That is, instead of updating only the data bound to the path elements as you're doing at the moment, update the data for the g elements. Then you can .select() the descendant path and text elements, which will again inherit the new data to them. Then you can set the attributes in the usual manner.
This requires a few changes to your code. In particular, there should be a variable for the g element selection and not just for the paths to make things easier:
var g = svg.selectAll("g.arc")
.data(pie(data));
g.enter()
.append("g").attr("class", "arc");
var path = g.append("path");
The changeFunction code changes as follows:
var gnew = g.data(pie(data));
gnew.select("path")
.transition()
.duration(750)
.attrTween("d", arcTween);
Now, to update the text, you just need to select it and reset the attributes:
gnew.select("text")
.attr("transform", function(d) { ... });
Complete demo here.

D3 bar chart by date with context brushing

I have a D3 bar chart with date/time on the X axis. This is working well now, but I'd like to add a smaller brushable chart below it and am having trouble due to some of the date manipulations I had to do to make the bars center over the X axis tick lines - see this post for details on that.
The brush does not seem to be calculating the X axis date range correctly.
Here is the x domain definition and brush function. JSFiddle for the full code.
main_x.domain([
d3.min(data.result, function(d) { return d.date.setDate(d.date.getDate() - 1); }),
d3.max(data.result, function(d) { return d.date.setDate(d.date.getDate() + 2); })
]);
function brushed() {
// I thought that re-defining the x domain without the date manipulations might work, but I
// was getting some odd results
//main_x.domain(d3.extent(data.result, function(d) { return d.date; }));
main_x.domain(brush.empty() ? main_x.domain() : brush.extent());
console.log(brush.extent());
bar.selectAll("rect")
.attr("width", function(d) { return main_width/len; })
.attr("x", function(d) { return main_x(d.date) - (main_width/len)/2; });
main.select(".x.axis").call(main_xAxis);
}
The problem is that you're using the same scale for the focus and the context chart. As soon as you change that, the range of the selection changes (same size, but different underlying scale). To fix, use two different scales.
I've done this here, introducing mini_x as the x scale for the context chart.

d3 scoping causes SVG to revert to initial data?

I'm new to d3.js and still a beginner in javascript in general. I've got d3 correctly drawing a single SVG rectangle based on a single value in an array. When I increase the value of the number in the area via an input field and then call the reDraw function, the rectangle changes to the new size for just a second and then switches back to the initial size. I'm thinking I must have a scoping problem, but can't figure it out. Any ideas?
var dataset, h, reDraw, svg, w;
w = 300;
h = 400;
dataset = [1000];
// Create SVG element
svg = d3.select("#left").append("svg").attr("width", w).attr("height", h);
// set size and position of bar
svg.selectAll("rect").data(dataset).enter().append("rect").attr("x", 120).attr("y", function(d) {
return h - (d / 26) - 2;
}).attr("width", 60).attr("height", function(d) {
return d / 26;
}).attr("fill", "rgb(3, 100, 0)");
// set size and position of text on bar
svg.selectAll("text").data(dataset).enter().append("text").text(function(d) {
return "$" + d;
}).attr("text-anchor", "middle").attr("x", 150).attr("y", function(d) {
return h - (d / 26) + 14;
}).attr("font-family", "sans-serif").attr("font-size", "12px").attr("fill", "white");
// grab the value from the input, add to existing value of dataset
$("#submitbtn").click(function() {
localStorage.setItem("donationTotal", Number(localStorage.getItem("donationTotal")) + Number($("#donation").val()));
dataset.shift();
dataset.push(localStorage.getItem("donationTotal"));
return reDraw();
});
// redraw the rectangle to new size
reDraw = function() {
return svg.selectAll("rect").data(dataset).attr("y", function(d) {
return h - (d / 26) - 2;
}).attr("height", function(d) {
return d / 26;
});
};
You need to tell d3 how to match new data to existing data in the reDraw function. That is, you're selecting all rectangles in reDraw and then binding data to it without telling it the relation between the old and the new. So after the call to data(), the current selection (the one you're operating on) should be empty (not sure why something happens for you at all) while the enter() selection contains the new data and the exit() selection the old one.
You have several options to make this work. You could either operate on the enter() and exit() selections in your current setup, i.e. remove the old and add the new, or restructure your data such that you're able to match old and new. You could for example use an object with appropriate attributes instead of a single number. The latter has the advantage that you could add a transition from the old to the new size.
Have a look at this tutorial for some more information on data joins.
Edit:
Turns out that this was actually not the issue, see comments.

Categories

Resources