How can i display the count in a better way? - d3.js

I have displayed the count of the bar graphs here. But when the value in the dataset of the bar is 0, it displays 0 and disrupts the y label. i want to display the values appropriately and only when it's non zero. The values shouldnot mess with the axes. How can i do that?
Here is the working fiddle
https://jsfiddle.net/84hp8m6p/1/
Here's what i am using to append text
groups.selectAll('.bartext')
.data(function(d) {
return d;
})
.enter()
.append("text")
.attr("class", "bartext")
.attr("text-anchor", "end")
.attr("fill", "black")
.attr('x', function(d) {
return xScale(d.x0) + xScale(d.x);
})
.attr('y', function(d, i) {
return yScale(d.y) + yScale.rangeBand() / 2;
})
.text(function(d) {
return d.x;
});

Use a ternary operator to show only values greater than zero (or any other value):
.text(function(d) {
return (d.x > 0) ? d.x : "";
});
Here is your updated fiddle: https://jsfiddle.net/1gx3kcz4/

Related

Total values of stacks in each bar at the top

I have a stacked bar chart. The jsfiddle is here.
I have added a text which is actually the sum of all stacks on top of each bar.
layer.selectAll("rect")
.data(data)
.enter().append("text")
.attr("text-anchor", "middle")
.attr("x", function (d) { return x(d[chartType]) + x.rangeBand() / 2; })
.attr("y", function(d)
{
return y(sumOfStack(chartType, data, d[chartType], xData)) - 5;
})
.text(function(d)
{
return sumOfStack(chartType, data, d[chartType], xData);
})
.style("fill", "4682b4");
And the sumOfStack() method returns the value of y actually. It sums up all the stack values.
function sumOfStack(dataType, data, xValue, sources)
{
var sum = 0;
for(var i=0; i<data.length;i++){
if(data[i][dataType] != xValue) continue;
for(var j=0; j<sources.length; j++)
{
sum+= data[i][sources[j]];
}
console.log(sum);
return sum;
}
}
What is happening here is,
So what is happening here, first time, the monthly chart is drawn and the value on the top is correct. But, when I click on the quarter/year, the values are not recalculating.
What's the problem here?
You are only using the append part of the selection, which takes care of new elements, while the texts that you need to update already exists, so they don't belong to the enter() part but to the existing selection.
Replace the totals with this code and it should work:
var totals = svg.selectAll('text.totalLabels')
.data(data);
totals
.transition()
.duration(500)
.attr("x", function (d) { return x(d[chartType]) + x.rangeBand() / 2; })
.attr("y", function(d)
{
return y(sumOfStack(chartType, data, d[chartType], xData)) - 5;
})
.text(function(d)
{
return sumOfStack(chartType, data, d[chartType], xData);
});
totals.enter().append('text')
.attr('class', 'totalLabels')
.attr("text-anchor", "middle")
.attr("x", function (d) { return x(d[chartType]) + x.rangeBand() / 2; })
.attr("y", function(d)
{
return y(sumOfStack(chartType, data, d[chartType], xData)) - 5;
})
.text(function(d)
{
return sumOfStack(chartType, data, d[chartType], xData);
})
.style("fill", "4682b4");
totals.exit().remove();

How to apply text transition to number in d3

I am new to d3. I created a bar chart. Append text and percentage in the bar chart with animation. When bar chart draw then the number and percentage go from bottom to top to the desired location. Here is the code
svg.selectAll(".bar")
.data(data)
.enter()
.append("g")
.attr("class", "g rect")
.append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.label); })
.attr("y", h)
.on("mouseover", onMouseOver) //Add listener for the mouseover event
... // attach other events
.transition()
.ease(d3.easeLinear)
.duration(2000)
.delay(function (d, i) {
return i * 50;
})
.attr("y", function(d) { return y(d.percentage.slice(0, -1)); })
.attr("width", x.bandwidth() - 15) // v4’s console.log(bands.bandwidth()) replaced v3’s console.log(bands.rangeband()).
.attr("height", function(d) { return h - y(d.percentage.slice(0, -1)); }) // use .slice to remove percentage sign at the end of number
.attr("fill", function(d) { return d.color; });
var legend = svg.append("g");
svg.selectAll(".g.rect").append("text")
.text(function(d) { return d.value })
.attr("x", function(d) { return x(d.label) + x.bandwidth() / 2 - 15; })
.attr("y", h)
.transition()
.ease(d3.easeLinear)
.duration(2000)
.attr("y", function(d) { return y(d.percentage.slice(0, -1) / 2);}) // use slice to remove percentage sign from the end of number
.attr("dy", ".35em")
.style("stroke", "papayawhip")
.style("fill", "papayawhip");
svg.selectAll(".g.rect").append("text")
.text(function(d) { return d.percentage; })
.attr("x", function(d) { return x(d.label) + x.bandwidth() / 2 - 20; })
.attr("y", h)
.transition()
.ease(d3.easeLinear)
.duration(2000)
.attr("y", function(d) { return y(d.percentage.slice(0, -1)) - 10; }) // use slice to remove percentage sign from the end of number
.attr("dy", ".35em")
.attr("fill", function(d) { return d.color; });
Now I want to apply text transition. Like instead of just printing say 90%(d.percentage). I want that it starts from 0 and goes to d.percentage gradually. How can I apply text transition in this case. I tried the following but it didn't work
svg.selectAll(".g.rect").append("text")
.text(function(d) { return d.percentage; })
.attr("x", function(d) { return x(d.label) + x.bandwidth() / 2 - 20; })
.attr("y", h)
.transition()
.ease(d3.easeLinear)
.duration(2000)
.tween("text", function(d) {
var i = d3.interpolate(0, d.percentage.slice(0, -1));
return function(t) {
d3.select(this).text(i(t));
};
})
.attr("y", function(d) { return y(d.percentage.slice(0, -1)) - 10; }) // use slice to remove percentage sign from the end of number
.attr("dy", ".35em")
.attr("fill", function(d) { return d.color; });
The problem is the this value.
Save it in the closure (that).
Use a number interpolator so you can round the result to a number of decimals.
var ptag = d3.select('body').append('p');
ptag.transition()
.ease(d3.easeLinear)
.duration(2000)
.tween("text", function(d) {
var that = this;
var i = d3.interpolate(0, 90); // Number(d.percentage.slice(0, -1))
return function(t) {
d3.select(that).text(i(t).toFixed(2));
};
})
<script src="https://d3js.org/d3.v5.min.js"></script>
Your problem is that you return function(t) { ... } and try to access this of parent function inside. The solution is to return arrow function, which does not shadow this value:
return t => d3.select(this).text(i(t));
(by the way, you may also want to round percentage before printing)
-------------Edit --------------
Here is the working code
var numberFormat = d3.format("d");
svg.selectAll(".g.rect").append("text")
.attr("x", function(d) { return x(d.label) + x.bandwidth() / 2 - 20; })
.attr("y", h)
.transition()
.ease(d3.easeLinear)
.duration(2000)
.tween("text", function(d) {
var element = d3.select(this);
var i = d3.interpolate(0, d.percentage.slice(0, -1));
return function(t) {
var percent = numberFormat(i(t));
element.text(percent + "%");
};
//return t => element.text(format(i(t)));
})
.attr("y", function(d) { return y(d.percentage.slice(0, -1)) - 10; }) // use slice to remove percentage sign from the end of number
.attr("dy", ".35em")
.attr("fill", function(d) { return d.color; });
Thanks :)

How to append value at the top of rect

I have made a vertical bar chart and have the following to append the text onto the bars.
If i set y attribute as 0,the text is shown at the top, irrespective of the length of bars i.e all values stick together on top even though bar length is 50,1,1,3.
What i need is that the value should display at the immediate top of the bar.
I tried text anchor end also but it didnt work.
sets.append("text")
.attr("class", "global")
//.attr("y", function(d) {
// return yScale(d.global) ;
// })
.attr("dy", 5)
.attr("dx",(xScale.rangeBand() / 4))
.attr("font-family", "sans-serif")
.attr("font-size", "14px")
.attr("fill", "black")
.text(function(d) {
return ( commaFormat(d.global) > 0) ? commaFormat(d.global) : "";
});
here is the rect code
sets.append("rect")
.attr("class","global")
.attr("width", xScale.rangeBand()/2)
.attr("y", function(d) {
return yScale((d.global/total)*100);
})
.attr("height", function(d){
return h - yScale((d.global/total)*100);
})
.attr('fill', function (d, i) {
return color(d.global);
})
.append("text")
.text(function(d) {
return commaFormat((d.global/total)*100);
})
//.attr('x', function(d, i) {
// return xScale(i) + xScale.rangeBand() / 2;
//})
//.attr('y', function(d) {
// return h - yScale((d.global/total)*100) + 14;
//})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")

How to add text to Cluster Force Layout bubbles

I am trying to add some text from the name field in my JSON file to each bubble in a cluster.
https://plnkr.co/edit/hwxxG34Z2wYZ0bc51Hgu?p=preview
I have added what I thought was correct attributes to the nodes with
node.append("text")
.text(function (d) {
return d.name;
})
.attr("dx", -10)
.attr("dy", "5em")
.text(function (d) {
return d.name
})
.style("stroke", "white");
function tick(e) {
node.each(cluster(10 * e.alpha * e.alpha))
.each(collide(.5))
.attr("transform", function (d) {
var k = "translate(" + d.x + "," + d.y + ")";
return k;
})
}
Everything works fine except the labels.
What am I missing here?
Thanks
Kev
For that you will need to make a group like this:
var node = svg.selectAll("g")
.data(nodes)
.enter().append("g").call(force.drag);//add drag to the group
To the group add circle.
var circles = node.append("circle")
.style("fill", function(d) {
return color(d.cluster);
})
To the group add text:
node.append("text")
.text(function(d) {
return d.name;
})
.attr("dx", -10)
.text(function(d) {
return d.name
})
.style("stroke", "white");
Add tween to the circle in group like this:
node.selectAll("circle").transition()
.duration(750)
.delay(function(d, i) {
return i * 5;
})
.attrTween("r", function(d) {
var i = d3.interpolate(0, d.radius);
return function(t) {
return d.radius = i(t);
};
});
Now the tick method will translate the group and with the group the circle and text will take its position.
working code here
The problem: a text SVG element cannot be child of a circle SVG element.
The solution is creating another selection for the texts:
var nodeText = svg.selectAll(".nodeText")
.data(nodes)
.enter().append("text")
.text(function (d) {
return d.name;
})
.attr("text-anchor", "middle")
.style("stroke", "white")
.call(force.drag);
Here is the plunker: https://plnkr.co/edit/qnx7CQox0ge89zBL9jxc?p=preview

D3, get X values (date) from multiple Y values, create text

I have searched through many answers about getting X (date) values given a Y. Most revolve around the scale.invert() function. However, I am having trouble implementing this in my task.
Task: I am creating a hydrograph (time-series line graph of water level). I'm also plotting min/max points, and median line, all with text labels. See first image. This is a 'proof of concept', so I'm ok with things being a little dirty.
Problem: I want to add the corresponding dates for those min/max points, and I can't figure out how. One caveat, sometimes there are multiple points with same min or max value. I am unable to: A) get the correct date for the point, and B) add the date for each instance (all mins and maxes, if there are multiples) to the text. Thanks for any help or direction.
Some of the code (that I think is immediately relevant) is below. Here's the codepen. I'm sure it's not pretty, I'm new to D3 and javascript.
...
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Water Level (ft)");
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
var extent = d3.extent(data, function(d) {
return d.level
})
var min = extent[0]
var max = extent[1]
var median = d3.median(data, function(d) {
return d.level
})
var medianLine = svg.append("line")
.attr("class", "medianLine")
.attr("y1", y(median))
.attr("y2", y(median))
.attr("x1", 0)
.attr("x2", width)
var points = svg.selectAll("g")
.data(data)
.enter()
.append("g")
.filter(function(d) {
return d.level === min || d.level === max;
})
points.append("circle")
.attr("class", "dot")
.attr("cx", function(d) {
return x(d.date);
})
.attr("cy", function(d) {
return y(d.level);
})
.attr("r", 5)
points.append("text")
.attr("x", function(d) {
return x(d.date);
})
.attr("y", -20)
.attr("font-family", "sans-serif")
.attr("font-size", "12px")
.style("text-anchor", "middle")
points.selectAll("circle")
.style("fill", function(d) {
if (d.level === min) {
return "red"
} else {
return "#009933"
};
});
points.selectAll("text")
.text(function(d) {
if (d.level === min) {
return "Min: " + min
} else if (d.level === max) {
return "Max: " + max
}
})
.style("fill", function(d) {
if (d.level === min) {
return "red"
} else if (d.level === max) {
return "#246B24"
}
})
svg.append("text")
.attr("transform", "translate(" + (width + 3) + "," + y(median) + ")")
.attr("dy", ".35em")
.attr("text-anchor", "start")
.style("fill", "#cc6600")
.text(function(d) {
return "Median: " + median
})
...
Well after a quick peek to the code the date is a property of your data so there should not be any problem accessing it e.g.
points.selectAll("text")
.text(function(d) {
// date is accessible through d.date
if (d.level === min) {
return "Min: " + min + ' ' + d.date
} else if (d.level === max) {
return "Max: " + max + ' ' + d.date
}
})
If you want to format the date you can read about time formatting in d3

Resources