How to Include Newlines in Labels in D3 Charts

How do I include newlines in labels in D3 charts?

I ended up using the following code to break each x-axis label across lines:

var insertLinebreaks = function (d) {
var el = d3.select(this);
var words = d.split(' ');
el.text('');

for (var i = 0; i < words.length; i++) {
var tspan = el.append('tspan').text(words[i]);
if (i > 0)
tspan.attr('x', 0).attr('dy', '15');
}
};

svg.selectAll('g.x.axis g text').each(insertLinebreaks);

Note that this assumes that the labels have already been created. (If you follow the canonical histogram example then the labels will have been set up in just the way you need.) There also isn't any real line-breaking logic present; the function converts every space into a newline. This fits my purposes fine but you may need to edit the split() line to be smarter about how it partitions the parts of the string into lines.

D3 vertical bar chart add newline to label text

This example that you reference is the canonical way to do wrap text when you are using an svg text node. You are simply calling it wrong (on the wrong class, you are wrapping the numbers). Simplify this to:

// Draw labels
bar.append("text")
.attr("class", "label_txt")
.attr("x", function(d) {
return -10;
})
.attr("y", groupHeight / 2) //<-- not sure you need this
.attr("dy", ".35em")
.text(function(d, i) {
if (i % data.series.length === 0)
return data.labels[Math.floor(i / data.series.length)];
else
return ""
})
.call(wrap, 40);

Full code:

<!DOCTYPE html><html>
<head> <script data-require="d3@3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script></head>
<body> <svg id="table1"></svg> <script> var window_width = 1000; var main_width = 400; var w = 400;
var data = { labels: [ 'the text to be splitted 1', 'the text to be splitted 2' ], series: [{ label: '2012', values: [4, 8] }, { label: '2013', values: [12, 43] }] };
var chartWidth = w, barHeight = 25, groupHeight = barHeight * data.series.length, gapBetweenGroups = 10, spaceForLabels = 175, spaceForLegend = 0;
var color = d3.scale.category10();
// Zip the series data together (first values, second values, etc.) var zippedData = []; for (var i = 0; i < data.labels.length; i++) { for (var j = 0; j < data.series.length; j++) { zippedData.push(data.series[j].values[i]); } }
var chartHeight = barHeight * zippedData.length + gapBetweenGroups * data.labels.length; var x = d3.scale.linear() .domain([0, d3.max(zippedData)]) .range([0, chartWidth]);
var y = d3.scale.linear() .range([chartHeight + gapBetweenGroups, 0]);
var yAxis = d3.svg.axis() .scale(y) .tickFormat('') .tickSize(0) .orient("left");
// Specify the chart area and dimensions var chart = d3.select("#table1") .attr("width", spaceForLabels + chartWidth + spaceForLegend) .attr("height", chartHeight);
// Create bars var bar = chart.selectAll("g") .data(zippedData) .enter().append("g") .attr("transform", function(d, i) { return "translate(" + spaceForLabels + "," + (i * barHeight + gapBetweenGroups * (0.5 + Math.floor(i / data.series.length))) + ")"; });
// Create rectangles of the correct width bar.append("rect") .attr("fill", function(d, i) { return color(i % data.series.length); }) .attr("class", "bar") .attr("width", x) .attr("height", barHeight - 1);
// Add text label in bar bar.append("text") .attr("class", "label_txt1") .attr("x", function(d) { return x(d) - 3; }) .attr("y", barHeight / 2) .attr("fill", "red") .attr("dy", ".35em") .text(function(d) { return d; });
// Draw labels bar.append("text") .attr("class", "label_txt") .attr("x", function(d) { return -10; }) //.attr("y", groupHeight / 2) //<-- you don't need this... .attr("dy", ".35em") .text(function(d, i) { if (i % data.series.length === 0) return data.labels[Math.floor(i / data.series.length)]; else return "" }) .call(wrap, 40);
function wrap(text, width) { text.each(function() { var text = d3.select(this), words = text.text().split(/\s+/).reverse(), word, line = [], lineNumber = 0, y = text.attr("y"), dy = parseFloat(text.attr("dy")), lineHeight = 1.1, // ems tspan = text.text(null).append("tspan").attr("x", function(d) { return d.children || d._children ? -10 : 10; }).attr("y", y).attr("dy", dy + "em"); while (word = words.pop()) { line.push(word); tspan.text(line.join(" ")); var textWidth = tspan.node().getComputedTextLength(); if (tspan.node().getComputedTextLength() > width) { line.pop(); tspan.text(line.join(" ")); line = [word]; ++lineNumber; tspan = text.append("tspan").attr("x", function(d) { return d.children || d._children ? -10 : 10; }).attr("y", 0).attr("dy", lineNumber * lineHeight + dy + "em").text(word); } } }); }
chart.append("g") .attr("class", "y axis") .attr("transform", "translate(" + spaceForLabels + ", " + -gapBetweenGroups / 2 + ")") .call(yAxis); </script></body>
</html>

How do I split labels for my donut chart to multiple lines using d3.js?

I figured out the answer; each() does not work if the data() function has been called on that selection, so the answer directly from the linked post will not work.

The other option is to use the call() on every text element made, like this;

this.graphGroup
.selectAll('allLabels')
.data(data_ready)
.enter()
.append('text').style('font-size', '24px')
.attr('transform',`${calculatedValue}`)
.style('text-anchor', 'start')
.text(d => {
return d.data.name;
})
.call(lineBreak, 40);

Where lineBreak is another function I have defined, the first parameter that gets passed into the function will be a reference to the looped element in the case, the different text elements.

the linebreak function:

function lineBreak(text, width) {
text.each(function () {
var el = d3.select(this);
let words = el.text().split(' ');
let wordsFormatted = [];

let string = '';
for (let i = 0; i < words.length; i++) {
if (words[i].length + string.length <= width) {
string = string + words[i] + ' ';
}
else {
wordsFormatted.push(string);
string = words[i] + ' ';
}
}
wordsFormatted.push(string);

el.text('');
for (var i = 0; i < wordsFormatted.length; i++) {
var tspan = el.append('tspan').text(wordsFormatted[i]);
if (i > 0)
tspan.attr('x', 0).attr('dy', '20');
}
});
}

Will optimize this function and edit later. Thanks!

Colored text and line-breaks for D3 node labels

Not directly related to the question, but to use the <pre> element to hold your data, you have to use:

var text = d3.select("pre").text();

Instead of d3.text().

Back to the question:

For printing those values, you just need:

node.append("text")
.attr("dy", "1.3em")
.style("text-anchor", "middle")
.style("fill", "white")
.text(function(d) {
return d.size;
});

Adjusting dy the way you want. However, there is an additional problem: you're not populating size in the data array. Therefore, add this in the create_nodes function:

size: data[node_counter].size,

Here is the code with those changes:

<!DOCTYPE html><meta charset="utf-8"><style type="text/css">  text {    font: 10px sans-serif;  }    pre {    display: none;  }    circle {    stroke: #565352;    stroke-width: 1;  }</style>
<body> <pre id="data">Toyota Motor,61.84,Asia,239Volkswagen,44.54,Europe,124Daimler,40.79,Europe,104BMW,35.78,Europe,80Ford Motor,31.75,America,63General Motors,30.98,America,60</pre>
<script src="https://d3js.org/d3.v3.min.js"></script> <script> Array.prototype.contains = function(v) { for (var i = 0; i < this.length; i++) { if (this[i] === v) return true; } return false; };

var width = 500, height = 500, padding = 1.5, // separation between same-color nodes clusterPadding = 6, // separation between different-color nodes maxRadius = 12;
var color = d3.scale.ordinal() .range(["#0033cc", "#33cc66", "#990033"]);

var text = d3.select("pre").text(); var colNames = "text,size,group,revenue\n" + text; var data = d3.csv.parse(colNames);
data.forEach(function(d) { d.size = +d.size; });

//unique cluster/group id's var cs = []; data.forEach(function(d) { if (!cs.contains(d.group)) { cs.push(d.group); } });
var n = data.length, // total number of nodes m = cs.length; // number of distinct clusters
//create clusters and nodes var clusters = new Array(m); var nodes = []; for (var i = 0; i < n; i++) { nodes.push(create_nodes(data, i)); }
var force = d3.layout.force() .nodes(nodes) .size([width, height]) .gravity(.02) .charge(0) .on("tick", tick) .start();
var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height);

var node = svg.selectAll("circle") .data(nodes) .enter().append("g").call(force.drag);

node.append("circle") .style("fill", function(d) { return color(d.cluster); }) .attr("r", function(d) { return d.radius })

node.append("text") .attr("dy", ".3em") .style("text-anchor", "middle") .style("fill", "white") .text(function(d) { return d.text; });
node.append("text") .attr("dy", "1.3em") .style("text-anchor", "middle") .style("fill", "white") .text(function(d) { return d.size; });
function create_nodes(data, node_counter) { var i = cs.indexOf(data[node_counter].group), r = Math.sqrt((i + 1) / m * -Math.log(Math.random())) * maxRadius, d = { cluster: i, radius: data[node_counter].size * 1.5, text: data[node_counter].text, size: data[node_counter].size, revenue: data[node_counter].revenue, x: Math.cos(i / m * 2 * Math.PI) * 200 + width / 2 + Math.random(), y: Math.sin(i / m * 2 * Math.PI) * 200 + height / 2 + Math.random() }; if (!clusters[i] || (r > clusters[i].radius)) clusters[i] = d; return d; };

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; })
}
// Move d to be adjacent to the cluster node. function cluster(alpha) { return function(d) { var cluster = clusters[d.cluster]; if (cluster === d) return; var x = d.x - cluster.x, y = d.y - cluster.y, l = Math.sqrt(x * x + y * y), r = d.radius + cluster.radius; if (l != r) { l = (l - r) / l * alpha; d.x -= x *= l; d.y -= y *= l; cluster.x += x; cluster.y += y; } }; }
// Resolves collisions between d and all other circles. function collide(alpha) { var quadtree = d3.geom.quadtree(nodes); return function(d) { var r = d.radius + maxRadius + Math.max(padding, clusterPadding), nx1 = d.x - r, nx2 = d.x + r, ny1 = d.y - r, ny2 = d.y + r; quadtree.visit(function(quad, x1, y1, x2, y2) { if (quad.point && (quad.point !== d)) { var x = d.x - quad.point.x, y = d.y - quad.point.y, l = Math.sqrt(x * x + y * y), r = d.radius + quad.point.radius + (d.cluster === quad.point.cluster ? padding : clusterPadding); if (l < r) { l = (l - r) / l * alpha; d.x -= x *= l; d.y -= y *= l; quad.point.x += x; quad.point.y += y; } } return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; }); }; } </script>

D3 Charting Tool: How to add label at right of target line (additional horizontal line) in column chart

To add a label to your target line, you are best to create group (g) element, and then append a line and text element to it. The g element can be translated to the correct y position, so that the line and text can be positioned relatively to the g.

var targetGoalArr = [7];

var target = g.selectAll(".targetgoal")
.data(targetGoalArr)
.enter()
.append("g")
.attr("transform", function(d){
return "translate(0, " + y(d) +")"
})

target.append("line")
.attr("class", "targetgoal")
.attr("x1", 0)
.attr("x2", width)
.attr("y1", 0) //these can be omitted
.attr("y2", 0)
.style("stroke", "#cc0000");

target.append("text")
.text(function(d){ return "Target growth: " + d })
.attr("x", width)
.attr("y", "0.35em")

How to break a line in d3.axis.tickFormat?

You can access the elements created by the axis: Demo

d3.select('svg')
.append('g')
.attr('transform', 'translate(180, 10)')
.call(xAxis)
.selectAll('text') // `text` has already been created
.selectAll('tspan')
.data(function (d) { return bytesToString(d); }) // Returns two vals
.enter()
.append('tspan')
.attr('x', 0)
.attr('dx', '-1em')
.attr('dy', function (d, i) { return (2 * i - 1) + 'em'; })
.text(String);

Also, you'll have to set .tickFormat to '' on the axis.

d3js write labels on multiline

If you want to always break your text after the colon, the easiest approach is using a <tspan>:

.append("tspan")
.attr("x", 0)
.attr("dy", "1.3em")
.text(function(d) {
return d.data.label.split(":")[1];
})

Here is your code with that change:

var dataGroupPotential = [];var pieGroup;var svg;
processData("pieGPChart");
function processData(chartDivId) { $("#pieGPLegend").text("Title : myTitle");
//Construction du tableau data
dataGroupPotential.push({ label: "long text 1 : 100 000 000", value: 100000000 }); dataGroupPotential.push({ label: "long text 2 : 200 000 000", value: 200000000 }); dataGroupPotential.push({ label: "long text 3 : 300 000 000", value: 300000000 });

var width = $("#" + chartDivId).width(); var dividor = 2.75; var height = width / dividor;
//Ajout des éléments svg pour tracer le graphique svg = d3.select("#" + chartDivId).append("svg") .attr("width", '100%') .attr("height", '100%') .attr('viewBox', '0 0 ' + Math.min(width, height) + ' ' + Math.min(width, height)) .attr('preserveAspectRatio', 'xMinYMin') .append("g") .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");;
svg.append("g").attr("class", "slices"); svg.append("g").attr("class", "labels"); svg.append("g").attr("class", "lines");
$("#" + chartDivId).height(height);
drawMyPie(svg, dataGroupPotential, width, height);
window.addEventListener('resize', function(event) { // do stuff here var chartDivId = "pieGPChart"; var width = $("#" + chartDivId).width(); var dividor = 2.75; var height = width / dividor; $("#" + chartDivId).height(height);
drawMyPie(svg, dataGroupPotential, width, height); });}

function drawMyPie(svg, data, width, height) { var radius = Math.min(width, height) / 2;
var pie = d3.pie() .sort(null) .value(function(d) { return d.value; });
var arc = d3.arc() .outerRadius(radius * 0.8) .innerRadius(radius * 0.4);
var outerArc = d3.arc() .innerRadius(radius * 0.9) .outerRadius(radius * 0.9);
//svg.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var key = function(d) { return d.data.label; };
var color = d3.scaleOrdinal(d3.schemeCategory20); change(data);
function mergeWithFirstEqualZero(first, second) { var secondSet = d3.set(); second.forEach(function(d) { secondSet.add(d.label); });
var onlyFirst = first .filter(function(d) { return !secondSet.has(d.label) }) .map(function(d) { return { label: d.label, value: 0 }; }); return d3.merge([second, onlyFirst]) .sort(function(a, b) { return d3.ascending(a.label, b.label); }); }
function change(data) { var duration = 3000; var data0 = svg.select(".slices").selectAll("path.slice") .data().map(function(d) { return d.data }); if (data0.length == 0) data0 = data; var was = mergeWithFirstEqualZero(data, data0); var is = mergeWithFirstEqualZero(data0, data);
/* ------- SLICE ARCS -------*/
var slice = svg.select(".slices").selectAll("path.slice") .data(pie(was), key);
slice.enter() .insert("path") .attr("class", "slice") .style("fill", function(d) { return color(d.data.label); }) .each(function(d) { this._current = d; });
slice = svg.select(".slices").selectAll("path.slice") .data(pie(is), key);
slice .transition().duration(duration) .attrTween("d", function(d) { var interpolate = d3.interpolate(this._current, d); var _this = this; return function(t) { _this._current = interpolate(t); return arc(_this._current); }; });
slice = svg.select(".slices").selectAll("path.slice") .data(pie(data), key);
slice .exit().transition().delay(duration).duration(0) .remove();
/* ------- TEXT LABELS -------*/
var text = svg.select(".labels").selectAll("text") .data(pie(was), key);
text.enter() .append("text") .attr("dy", ".35em") .style("opacity", 0) .text(function(d) { return d.data.label.split(":")[0] + ":"; }) .each(function(d) { this._current = d; }) .append("tspan") .attr("x", 0) .attr("dy", "1.3em") .text(function(d) { return d.data.label.split(":")[1]; }) .each(function(d) { this._current = d; });
//var legend = text.enter();
//text.append("text") // .attr("dy", "-10") // .style("opacity", 0) // .text(function (d) { // return d.data.label; // }) //.each(function (d) { // this._current = d; //}); //text.append("text") // .attr("dy", "10") // .style("opacity", 0) // .text(function (d) { // return d.data.label; // }) //.each(function (d) { // this._current = d; //});
function midAngle(d) { return d.startAngle + (d.endAngle - d.startAngle) / 2; }
text = svg.select(".labels").selectAll("text") .data(pie(is), key);
text.transition().duration(duration) .style("opacity", function(d) { return d.data.value == 0 ? 0 : 1; }) .attrTween("transform", function(d) { var interpolate = d3.interpolate(this._current, d); var _this = this; return function(t) { var d2 = interpolate(t); _this._current = d2; var pos = outerArc.centroid(d2); pos[0] = radius * (midAngle(d2) < Math.PI ? 1 : -1); return "translate(" + pos + ")"; }; }) .styleTween("text-anchor", function(d) { var interpolate = d3.interpolate(this._current, d); return function(t) { var d2 = interpolate(t); return midAngle(d2) < Math.PI ? "start" : "end"; }; });
text = svg.select(".labels").selectAll("text") .data(pie(data), key);
text .exit().transition().delay(duration) .remove();
/* ------- SLICE TO TEXT POLYLINES -------*/
var polyline = svg.select(".lines").selectAll("polyline") .data(pie(was), key);
polyline.enter() .append("polyline") .style("opacity", 0) .each(function(d) { this._current = d; });
polyline = svg.select(".lines").selectAll("polyline") .data(pie(is), key);
polyline.transition().duration(duration) .style("opacity", function(d) { return d.data.value == 0 ? 0 : .5; }) .attrTween("points", function(d) { this._current = this._current; var interpolate = d3.interpolate(this._current, d); var _this = this; return function(t) { var d2 = interpolate(t); _this._current = d2; var pos = outerArc.centroid(d2); pos[0] = radius * 0.95 * (midAngle(d2) < Math.PI ? 1 : -1); return [arc.centroid(d2), outerArc.centroid(d2), pos]; }; });
polyline = svg.select(".lines").selectAll("polyline") .data(pie(data), key);
polyline .exit().transition().delay(duration) .remove(); };}
svg {        width: 100%;        height: 100%;    }
path.slice { stroke-width: 2px; }
polyline { opacity: .3; stroke: black; stroke-width: 2px; fill: none; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.js"></script><link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css" rel="stylesheet"/><script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.js"></script>

<div class="panel panel-default"> <div class="panel-body"> <div style="text-align:center;" class="row"> <h4 id="pieGPLegend"></h4> <div id="pieGPChart"></div> </div> </div></div>

D3 Angular - line break in axis label

The first line in insertLinebreak is the source of the problem, because it retrieves all the label elements every time you want to process one of them (watch the console in this stackblitz):

private insertLinebreak(d) {
let labels = d3.select(".x-axis .tick text"); // <-- This line causes the problem
...
}

In order to select only the label that you are processing, use d3.select(this):

private insertLinebreak(d) {
let labels = d3.select(this);
...
}

See this stackblitz for a demo.



Related Topics



Leave a reply



Submit