How to append value at the top of rect - d3.js

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")

Related

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 :)

Replace space with new line charcter in texts d3js

I have a script in which I make txts in the following way:
texts = svg.selectAll(null)
.data(data)
.enter()
.append('text')
.attr("text-anchor", "middle")
.text(function(d) {
return d.abbreviation;
})
.attr("pointer-events", "none")
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.attr("fill", "black");
texts.each(function(d) {
console.log(this.getComputedTextLength());
d.size = this.getComputedTextLength() / 2 ;
});
simulation.nodes(data).on("tick", function() {
circles.attr("cx", function(d) {
return d.x = Math.max(20, Math.min(width - 20, d.x));
})
.attr("cy", function(d) {
return d.y = Math.max(20, Math.min(height - 20, d.y));
})
texts.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
});
});
I am using property abbreviation in data to put text over the bubbles but I want to replace all spaces in input with new line character.
I tried some soutions like given at this link: How to linebreak an svg text in javascript?
but all texts go to left most corner or if I remove the .attr("x", 0) property from this, then the alignment of texts in not right. See picture below:
"State" should come directly below "Iowa"
Updated script:
texts = svg.selectAll(null)
.data(data)
.enter()
.append('text')
.attr("text-anchor", "middle")
.each(function (d) {
var arr = d.abbreviation.split(" ");
for (i = 0; i < arr.length; i++) {
d3.select(this).append("tspan")
.text(arr[i])
.attr("dy", i ? "1.2em" : 0)
.attr("text-anchor", "middle")
.attr("class", "tspan" + i);
}
})
.attr("pointer-events", "none")
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.attr("fill", "black");
What should I do to make the alignment right or is there any other way to do this?
There are different ways to fix this. An easy one is appending the <tspan> to a <g> element, setting all their x properties to 0 and text-anchor to middle.
Have a look at the demo:
var svg = d3.select("svg");
var data = [{
text: "some text"
}, {
text: "a longer text here"
}, {
text: "an even longer text here"
}, {
text: "short text"
}, {
text: "a long text"
}];
var texts = svg.selectAll(null)
.data(data)
.enter()
.append("g");
texts.append("text")
.attr("text-anchor", "middle")
.each(function(d) {
var arr = d.text.split(" ");
d3.select(this).selectAll(null)
.data(arr)
.enter()
.append("tspan")
.attr("text-anchor", "middle")
.attr("x", 0)
.attr("dy", function(d, i) {
return "1.2em"
})
.text(String)
})
var simulation = d3.forceSimulation(data)
.force("center", d3.forceCenter(200, 100))
.force("collide", d3.forceCollide(40))
.on("tick", tick);
function tick() {
texts.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"
})
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="400" height="250"></svg>
PS: Don't use that for loop inside the each. That's not idiomatic D3. Instead of that, just use an enter selection (refer to the demo to see how to do it).

How Can I Use Symbols With a Legend

I've got a legend, with colored rectangles...
I'd like to replace the rectangles with symbols (i.e., circle, cross, diamond, square). I can't figure out how to do that.
I've been using variations of .attr("d", d3.svg.symbol().type('circle'). For instance, I tried:
legendRect
.attr("d", d3.svg.symbol().type(function (d) { return d[2] })
and I tried:
legendRect.append("svg:path")
.attr("d", d3.svg.symbol().type((d: any) => { return d[2] }))
d[2] is "supposed to be" pulling from legendData, as shown in the below code example...like it does with d[1] for the fill.
But I don't ever see anything change.
Here's the code I'm using for the legend, without the symbol stuff, below. What am I doing wrong and how can I change the rectangles to symbols? Where do I need to add what?
var legendData = [["OA", "yellow", "circle"], ["OI", "blue", "cross"], ["RARC", "green", "diamond"], ["CAPE", "red", "square"], ["Other", "black", "triangleDown"]];
var legend = this.svg.append("g")
.attr("class", "legend")
.attr("height", 0)
.attr("width", 0)
.attr('transform', 'translate(-20,250)');
var legendRect = legend.selectAll('rect').data(legendData);
legendRect.enter()
.append("rect")
.attr("x", width - 65)
.attr("width", 10)
.attr("height", 10)
;
legendRect
.attr("y", function (d, i) {
return i * 20;
})
.style("fill", function (d) {
return d[1];
})
var legendText = legend.selectAll('text').data(legendData);
legendText.enter()
.append("text")
.attr("x", width - 52);
legendText
.attr("y", function (d, i) {
return i * 20 + 9;
})
.text(function (d) {
return d[0];
});
Here's how I would code it. Notice, that I data-bind to a wrapper g element and then place the symbol and text into it for each legend item. You can then position the g instead of positioning the text and "symbol" separately. This also removes the need for double-binding the data.
var legendData = [["OA", "yellow", "circle"], ["OI", "blue", "cross"], ["RARC", "green", "diamond"], ["CAPE", "red", "square"], ["Other", "black", "triangleDown"]];
var svg = d3.select('body').append('svg').attr('width', 500).attr('height', 500);
var legend = svg.append('g')
.attr("class", "legend")
.attr("height", 0)
.attr("width", 0)
.attr('transform', 'translate(20,20)');
var legendRect = legend
.selectAll('g')
.data(legendData);
var legendRectE = legendRect.enter()
.append("g")
.attr("transform", function(d,i){
return 'translate(0, ' + (i * 20) + ')';
});
legendRectE
.append('path')
.attr("d", d3.svg.symbol().type((d) => { return d[2] }))
.style("fill", function (d) {
return d[1];
});
legendRectE
.append("text")
.attr("x", 10)
.attr("y", 5)
.text(function (d) {
return d[0];
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
This is a implementation which uses symbols for your legend. You can use the symbols like the following:
svg.selectAll('.symbol')
.data(legendData)
.enter()
.append('path')
.attr('transform', function(d, i) {
return 'translate(' + (20) + ',' + ((i * 20) + 10) + ')';
})
.attr('d', d3.symbol().type(function(d, i) {
if (d[2] === "circle") {
return d3.symbolCircle;
} else if (d[2] === "cross") {
return d3.symbolCross;
} else if (d[2] === "diamond") {
return d3.symbolDiamond;
} else if (d[2] === "square") {
return d3.symbolSquare;
} else {
return d3.symbolTriangle;
}
})
.size(100))
.style("fill", function(d) {
return d[1];
});
Then you can set your legend labels like the following:
svg.selectAll('.label')
.data(legendData)
.enter()
.append('text')
.attr("x", "40")
.attr("y", function(d, i){ return ((i * 20)+15);})
.text(function(d) {
return d[0];
});
Check fiddle here - https://jsfiddle.net/zoxckLe3/
P.S. - Above solution uses d3 v4. To achieve the same in v3, use the following line .attr('d', d3.svg.symbol().type(function(d){return d[2];})) instead of the part where I match d[2] to the symbol name.
For adding image icons, you can use below code.
legend.append("**image**")
.attr("x", 890)
.attr("y", 70)
.attr("width", 20)
.attr("height", 18)
.attr("xlink:href",function (d) {
**return "../assets/images/dev/"+d+".png";**
})
This works for me..

How can i display the count in a better way?

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/

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

Resources