Moving and Changing Text Labels while Sorting Bar Chart - javascript

I'm trying to have my text labels move and change while sorting. I have a multidimensional array including bar height, width, and opacity. A kind contributor helped me out yesterday with getting the bars sorted correctly, but I'd also like to get the text labels to move and change at the top of each bar as the bar sorts and moves.
var s = -1;
svg.on("click", function() {
s = (s + 1) % 3;
var xPosition = parseFloat(d3.select(this).attr("x")) xScale.bandwidth() / 2;
var yPosition = parseFloat(d3.select(this).attr("y")) + 14;
svg.selectAll("rect")
.sort(function(a, b) {
return d3.ascending(a[s], b[s]);
})
.transition()
.delay(function(d, i) {
return i * 65;
})
.duration(250)
.attr("x", function(d, i) {
return xScale(i);
});
svg.selectAll("text")
.append("text")
.text(function(d) {
return d[s];
})
.attr("text-anchor", "left")
.attr("x", xPosition)
.attr("y", yPosition)
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "red");
});
As you can see, I'm quite lost as to where to go moving forward. I took the xPosition and yPosition variable idea from a textbook but it doesn't seem to change anything. Also, the text labels never change while I sort through height, width, and opacity. Thanks for the help.

When you do this...
svg.selectAll("text")
.append("text")
//etc...
... you are appending <text> elements inside <text> elements, which makes no sense.
Instead of that, assuming that your texts were previously created, just select them and sort them accordingly:
svg.selectAll("text")
.text(function(d) {
return d[s];
})
.attr("x", xPosition)
.attr("y", yPosition);

Related

d3.js add second label to bar chart

2 part question:
I have a bar chart with created using multiple arrays. These arrays contain the % wins of baseball teams; the relevant team colours; and their names.
I can create one set of labels on the chart, either the names or the win %. However I can't get both on at the same time. See below.
The code I am using is:
let WinsLabel = svgContainer.selectAll("text")
.data(d3.zip(TeamArray, WinPercArray, Colours));
WinsLabel.enter()
.append("text")
.attr("fill", "black")
.attr("x", function(d, i) {
return 45 + (i * 50);
})
.attr("y", 700)
.transition()
.duration(1000)
.attr("x", function(d,i){
return 70 + (i*50);
})
.attr("y", function(d){
return 685 - d[1];
})
.attr("text-anchor","middle")
.attr("font-family", "sans-serif")
.attr("font-size", "15px")
.attr("fill", "black")
.text(function(d){
return d[1]/10 + "%";
});
let TeamLabel = svgContainer.selectAll("text")
.data(d3.zip(TeamArray, WinPercArray, Colours));
TeamLabel.enter()
.append("text")
.attr("fill", "black")
.attr("x", function(d, i) {
return 45 + (i * 50);
})
.attr("y", 700)
.transition()
.duration(1000)
.attr("x", function(d,i){
return 70 + (i*50);
})
.attr("y", function(d){
return 700 - d[1]/2;
})
.attr("text-anchor","middle")
.attr("font-family", "sans-serif")
.attr("font-size", "15px")
.attr("fill", "white")
.text(function(d){
return d[0];
});
When I run the code with both scripts, only the win % shows up, but the names don't. In order to get the names to show up I have to remove the first label.
The 2 parts to my question are:
How would I get both sets of labels to show up at the same time?
How can I get the names to be arranged vertically in the rectangles/bars?
D3 stands for data driven something; and it's core principle is based on linking elements / selection, with data. When you set data, (var selection = selectAll(...).data(...)), you get 3 cases to think about:
Some existing elements can be linked to certain item in new data. You access them using selection
Some elements cannot be linked to any item in new data. You access them using selection.exit()
Some items in new data cannot be linked to any element from selection. You access them by using selection.enter()
In its simplest case, the linking between data and elements is made by index -- ie first element in selection is linked with first item in data array, second with second, and so on. The d3 cannot find element for the data item (= gets put into .enter() selection) if and only if (in this by-index context) the index of that data item is bigger than the size of the selection.
On your initial select
let WinsLabel = svgContainer.selectAll("text")
.data(d3.zip(TeamArray, WinPercArray, Colours));
The selection is empty, since there are no text tags yet. And since its empty, all of the to-be-created placeholders are inside .enter() selection. However, on your next select for the other label type
let TeamLabel = svgContainer.selectAll("text")
.data(d3.zip(TeamArray, WinPercArray, Colours));
The selection is of the size of the passed data, and thus .enter() selection is empty; it's the TeamLabel selection that contains all of the old elements (percentage label text tags), but they got their data values reassigned.
Andrew proposed one solution to assign classes, but personally I'd take all elements that relate to same team and put it under one group.
var TeamArray = ["Yankees", "Rays", "RedSox", "Jays","Orioles", "Twin", "Indians", "WhiteSox", "Detroit", "Royals", "Astros", "Rangers", "A's", "Angels","Mariners"];
var WinPercArray = [653, 609, 540, 400, 300, 667, 521, 458, 383, 347, 660, 511, 500, 458, 442];
var Colours = ["#003087", "#092C5C", "#BD3039", "#134A8E", "#DF4601", "#002B5C", "#0C2340", "#C4CED4", "#FA4616", "#BD9B60", "#EB6E1F", "#C0111F", "#003831", "#003263", "#005C5C"];
var data = d3.zip(TeamArray, WinPercArray, Colours);
var svg = d3.select('body').append('svg').attr('height', 300).attr('width', 800);
var teams = svg.selectAll('g.teams')
.data(data);
var scale = d3.scaleLinear()
.domain([0, 1000])
.range([200, 0]);
var teamsEnter = teams.enter()
.append('g')
.classed('team', true)
.attr('transform', function(d, i){
return 'translate(' + (i*50) + ',0)';
})
teamsEnter.append('rect')
.attr('width', 50)
.attr('y', function(d) { return scale(d[1]); })
.attr('height', function(d) { return scale(0) - scale(d[1]); })
.style('fill', function(d) { return d[2]; });
teamsEnter.append('text')
.attr('x', 25)
.attr('y', function(d) { return scale(d[1]) - 30; })
.text(function(d){ return d[0]; });
teamsEnter.append('text')
.attr('x', 25)
.attr('y', function(d) { return scale(d[1]) - 15; })
.text(function(d){ return d[1]; });
text {
text-anchor: middle;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Groups in some way act as encapsulation of inner items, so you can mentally separate data binding to groups (ie when to create / update / delete it), from actual logic that takes place when working with its children

Add tooltip to d3.js hierarchical bar chart

I am trying to add tooltip with mouseover hierarchical bars chart, as it is created by Mike Bostock here (https://bl.ocks.org/mbostock/1283663). I can't add tooltip by the traditional way, since the code is a bit different. Can anyone help me?
You could do it like this:
bar.append("text")
.attr("class", "moreText")
.attr("x", function(d) { return x(d.value) + 20; })
.attr("y", barHeight / 2)
.attr("dy", ".35em")
.attr("fill", "none")
.text(function(d) { return d.value; })
in the bar function and add this to have the hover effect
bar.on("mouseover", function() {
d3.select(this).select(".moreText").attr("fill", "#333")
.attr("x", function(d) { return x(d.value) + 15; })
})
.on("mouseout", function() {
d3.select(this).select(".moreText").attr("fill", "none")
})
in the same function, adding the .attr("x", function(d) { return x(d.value) + 15; }) here to make sure we get the right coordinate for the right rectangle both before and after transitions as well and referencing the text by class like this:
d3.select(this).select(".moreText")
Here's a working plunker

Why is d3 ignoring my color array?

I'm trying to build out a simple color chart, as an introductory d3 exercise, and I'm already stuck.
I have the following:
var colors = ["#ffffcc","#c7e9b4","#7fcdbb","#41b6c4","#2c7fb8","#253494"];
var barHeight = 20,
barWidth = 20,
width = (barWidth + 5) * colors.length;
d3.select("body").selectAll("svg")
.data(colors)
.enter().append("rect")
.attr("class", "block")
.attr("width", barWidth)
.attr("height", barHeight - 1)
.text(function(d) { return d; })
.attr("fill", function(d) { return d; });
https://jsfiddle.net/xryamdkf/1/
The text works fine. I see the hex codes, but the height and width are definitely not respected, and I can't seem to set the color.
This works to set the color: .style("background", function(d) { return d; }) but I think that is the text background, not the rect fill.
What am I doing wrong here? How can I make 20x20 rectangles filled with color in d3?
As you are not giving any index and reference of colors array into your function the code will not understand from where to pick colors. try with below code it will help.
d3.select("body").selectAll("svg")
.data(colors).enter().append("rect")
.attr("class", "block")
.attr("width", barWidth)
.attr("height", barHeight - 1)
.text(function(d) {
return d;
})
.attr("fill", function(d,i) { return colors[i]; });
So, a few things. You should call data() on what will be an empty selection of the things you will be adding.
svg.selectAll("rect").data(colors)
.enter().append("rect")
The rect doesn't have a text property. There is an svg text node that shows text and you'll want to add it separately.
I hope this https://jsfiddle.net/xryamdkf/8/ gets you closer.

Adding 'measures' label to D3 bullet chart

I'm working with D3's bullet chart and am trying to figure out how to display the actual measures number just to the right of the measures rectangle. Since I want to do this for every bullet chart, I figure it'd be best to do it right in the bullet.js code. I'm rather new to D3 so any help would be much appreciated! Here is the link to Mike Bostock's bullet chart example with the bullet.js included at the bottom.
It looks like the measures code is handled in this snippet:
// Update the measure rects.
var measure = g.selectAll("rect.measure")
.data(measurez);
measure.enter().append("rect")
.attr("class", function (d, i) { return "measure s" + i; })
.attr("width", w0)
.attr("height", height / 3)
.attr("x", reverse ? x0 : 0)
.attr("y", height / 3)
.transition()
.duration(duration)
.attr("width", w1)
.attr("x", reverse ? x1 : 0);
measure.transition()
.duration(duration)
.attr("width", w1)
.attr("height", height / 3)
.attr("x", reverse ? x1 : 0)
.attr("y", height / 3);
I thought I could just add something like this after the rect is appended but I've had no such luck.
measure.enter().append("text")
.attr("dy", "1em")
.text(function (d) { return d.measurez; })
.attr("x", reverse ? x0 : 0)
.attr("y", height / 3)
.transition()
.duration(duration)
.attr("width", w1)
.attr("x", reverse ? x1 : 0);
Thank you in advance for your consideration!
You almost got it -- there're just two small things to consider. First, you can't call .enter() twice. Once the enter selection has been operated on, it's merged into the update selection and your second selection will be empty. This is fixed easily by saving the selection in a variable, but in this case I would recommend making a separate selection for the text labels.
var measureLabel = g.selectAll("text.measure")
.data(measurez);
measureLabel.enter()....;
Second, to position the text to the right of the rect, you need to take not only the position, but also the width into account when computing the position of the text element. Also, you can omit a few elements that are not relevant to text elements.
measureLabel.enter()
.append("text")
.attr("class", function(d, i) { return "measure s" + i; })
.attr("dy", "1em")
.attr("dx", "1em")
.text(String)
.attr("x", reverse ? function(d) { return w0(d) + x0(d); } : w0)
.attr("y", height / 3);
measureLabel.transition()
.duration(duration)
.attr("x", reverse ? function(d) { return w1(d) + x1(d); } : w1);
Complete example here.

Fitting data for D3 graph to create legend

I have a data variable which contains the following:
[Object { score="2.8", word="Blue"}, Object { score="2.8", word="Red"}, Object { score="3.9", word="Green"}]
I'm interested in modifying a piece of a D3 graph http://bl.ocks.org/3887051 to display the legend, which would be the list of the "word", for my data set.
The legend script looks like this (from link above):
var ageNames = d3.keys(data[0]).filter(function(key) { return key !== "State"; });
var legend = svg.selectAll(".legend")
.data(ageNames.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
How do I modify their ageNames function to display the "word" set from my data? I'm not sure how they're utilizing the d3.keys. Is there another way to do it?
This should work more or less, but you may need to reverse() (as the original example does) or otherwise rearrange the elements of words, in order to correctly map a word to the right color. Depends on how you've implemented your graph.
var words = yourDataArray.map(function(entry) { return entry.word; });
var legend = svg.selectAll(".legend")
.data(words)
// The rest stays the same

Categories