简体   繁体   中英

Sizing custom C3 piechart legend dynamically (using D3?)

I have added a custom legend using name, value & ratio to a C3 piechart.

Currently a fixed width is used for positioning. Is there a smart way, maybe using D3, to dynamically calculate the pixel widths the legend rows?

 var columns = ['data11', 'data2', 'data347', 'data40091']; var data = [150, 250, 300, 50]; var colors = ['#0065A3', '#767670', '#D73648', '#7FB2CE', '#00345B']; var padding = 5; var legendData = []; var sumTotal = 0 //prepare pie data var columnData = []; var columnNames = {}; for (i = 0; i < columns.length; i++) { columnData.push([columns[i]].concat(data[i])); var val = (Array.isArray(data[i])) ? data[i].reduce(function(pv, cv) { return pv + cv; }, 0) : data[i]; sumTotal += val; legendData.push({ id: columns[i], value: val, ratio: 0.0 }); } legendData.forEach(function(el, i) { el.ratio = el.value / sumTotal columnNames[el.id] = [el.id, d3.format(",.0f")(el.value), d3.format(",.1%")(el.ratio)].join(';'); }); var chart = c3.generate({ bindto: d3.select('#chart'), data: { columns: [ [columns[0]].concat(data[0]) ], names: columnNames, type: 'pie', }, legend: { position: 'right', show: true }, pie: { label: { threshold: 0.001, format: function(value, ratio, id) { return [id, d3.format(",.0f")(value), "[" + d3.format(",.1%")(ratio) + "]"].join(';'); } } }, color: { pattern: colors }, onrendered: function() { redrawLabelBackgrounds(); redrawLegend(); } }); function addLabelBackground(index) { //get label text element var textLabel = d3.select(".c3-target-" + columns[index] + " > text"); //add rect to parent var labelNode = textLabel.node(); if (labelNode /*&& labelNode.innerHTML.length > 0*/ ) { var p = d3.select(labelNode.parentNode).insert("rect", "text") .style("fill", colors[index]); } } for (var i = 0; i < columns.length; i++) { if (i > 0) { setTimeout(function(column) { chart.load({ columns: [ columnData[column], ] }); //chart.data.names(columnNames[column]) addLabelBackground(column); }, (i * 5000 / columnData.length), i); } else { addLabelBackground(i); } } function redrawLegend() { d3.select('#chart').selectAll(".c3-legend-item > text").each(function(v) { // get d3 node var legendItem = d3.select(this); legendItem.attr("style", "font-size: 8pt;"); var legendItemNode = legendItem.node(); //check if label is drawn if (legendItemNode) { if (legendItemNode.childElementCount === 0 && legendItemNode.innerHTML.length > 0) { //build data var data = legendItemNode.innerHTML.split(';'); legendItem.text(""); // TODO: size the "rows" dynamically legendItem.append("tspan") .text(data[0] + ": ") .attr("class", "id-row") .attr("text-anchor", "start"); legendItem.append("tspan") .text(data[1] + " = ") .attr("class", "value-row") .attr("x", 170) .attr("text-anchor", "end"); legendItem.append("tspan") .text(data[2]) .attr("class", "ratio-row") .attr("x", 200) .attr("text-anchor", "end"); //.attr("transform", (n === 0) ? "translate(0,0)" : "translate(200,0)") } } }); d3.select('#chart').selectAll(".c3-legend-item > rect").each(function(v) { var legendItem = d3.select(this); legendItem.attr("width", 170); }); } function redrawLabelBackgrounds() { //for all label texts drawn yet d3.select('#chart').selectAll(".c3-chart-arc > text").each(function(v) { // get d3 node var label = d3.select(this); var labelNode = label.node(); //check if label is drawn if (labelNode) { if (labelNode.childElementCount === 0 && labelNode.innerHTML.length > 0) { //build data var data = labelNode.innerHTML.split(';'); label.text(""); data.forEach(function(i, n) { label.append("tspan") .text(i) .attr("dy", (n === 0) ? 0 : "1.2em") .attr("x", 0) .attr("text-anchor", "middle"); }, label); } //check if element is visible if (d3.select(labelNode.parentNode).style("display") !== 'none') { //get pos of the label text var pos = label.attr("transform").match(/-?\\d+(\\.\\d+)?/g); if (pos) { //get surrounding box of the label var bbox = labelNode.getBBox(); //now draw and move the rects d3.select(labelNode.parentNode).select("rect") .attr("transform", "translate(" + (pos[0] - (bbox.width + padding) / 2) + "," + (pos[1] - bbox.height / labelNode.childElementCount) + ")") .attr("width", bbox.width + padding) .attr("height", bbox.height + padding); } } } }); } 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script> <link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.9/c3.min.css" rel="stylesheet" /> <script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.9/c3.min.js"></script> <div id="chart"> </div> 

This question is based on my old question about custom legend / label overlap: C3/D3 pie legend format / label overlap

I figured out how to solve this, although there might be smarter solutions. I now use "getComputedTextLength" of each tspan node to calculate the max.

 var columns = ['data11', 'data2', 'data347', 'data40098']; var data = [150, 250, 300, 50]; var colors = ['#0065A3', '#767670', '#D73648', '#7FB2CE', '#00345B']; var padding = 5; var legendData = []; var sumTotal = 0 //prepare pie data var columnData = []; var columnNames = {}; for (i = 0; i < columns.length; i++) { columnData.push([columns[i]].concat(data[i])); var val = (Array.isArray(data[i])) ? data[i].reduce(function(pv, cv) { return pv + cv; }, 0) : data[i]; sumTotal += val; legendData.push({ id: columns[i], value: val, ratio: 0.0 }); } legendData.forEach(function(el, i) { el.ratio = el.value / sumTotal columnNames[el.id] = [el.id, d3.format(",.0f")(el.value), d3.format(",.1%")(el.ratio)].join(';'); }); var chart = c3.generate({ bindto: d3.select('#chart'), data: { columns: [ [columns[0]].concat(data[0]) ], names: columnNames, type: 'pie', }, legend: { position: 'right', show: true }, pie: { label: { threshold: 0.001, format: function(value, ratio, id) { return [id, d3.format(",.0f")(value), "[" + d3.format(",.1%")(ratio) + "]"].join(';'); } } }, color: { pattern: colors }, onrendered: function() { redrawLabelBackgrounds(); redrawLegend(); } }); function addLabelBackground(index) { //get label text element var textLabel = d3.select(".c3-target-" + columns[index] + " > text"); //add rect to parent var labelNode = textLabel.node(); if (labelNode /*&& labelNode.innerHTML.length > 0*/ ) { var p = d3.select(labelNode.parentNode).insert("rect", "text") .style("fill", colors[index]); } } for (var i = 0; i < columns.length; i++) { if (i > 0) { setTimeout(function(column) { chart.load({ columns: [ columnData[column], ] }); //chart.data.names(columnNames[column]) addLabelBackground(column); }, (i * 5000 / columnData.length), i); } else { addLabelBackground(i); } } function redrawLegend() { var maxIdRowWidth = 0, maxValueRowWidth = 0, maxRatioRowWidth = 0; d3.select('#chart').selectAll(".c3-legend-item > text").each(function() { // get d3 node var legendItem = d3.select(this); var legendItemNode = legendItem.node(); //check if label is drawn if (legendItemNode) { if (legendItemNode.childElementCount === 0 && legendItemNode.innerHTML.length > 0) { //build data var data = legendItemNode.innerHTML.split(';'); legendItem.text(""); var idRowWidth = legendItem.append("tspan") .text(data[0] + ":") .attr("class", "id-row") .attr("text-anchor", "start") .node().getComputedTextLength(); maxIdRowWidth = Math.max(maxIdRowWidth, idRowWidth); var valueRowWidth = legendItem.append("tspan") .text(data[1] + " =") .attr("class", "value-row") .attr("text-anchor", "end") .node().getComputedTextLength(); maxValueRowWidth = Math.max(maxValueRowWidth, valueRowWidth); var ratioRowWidth = legendItem.append("tspan") .text(data[2]) .attr("class", "ratio-row") .attr("text-anchor", "end") .node().getComputedTextLength(); maxRatioRowWidth = Math.max(maxRatioRowWidth, ratioRowWidth); } } }); var xOffset = parseInt(d3.select('#chart').select(".c3-legend-item > text").attr("x")); var padding = 2; d3.select('#chart').selectAll(".id-row") .attr("width", Math.round(maxIdRowWidth)); d3.select('#chart').selectAll(".value-row") .attr("x", Math.round(maxValueRowWidth+maxIdRowWidth)+xOffset+padding) .attr("width", Math.round(maxValueRowWidth)); d3.select('#chart').selectAll(".ratio-row") .attr("x", Math.round(maxValueRowWidth+maxIdRowWidth+maxRatioRowWidth)+xOffset+2*padding) .attr("width", Math.round(maxRatioRowWidth)); d3.select('#chart').selectAll(".c3-legend-item > rect") .attr("width", Math.round(maxValueRowWidth+maxIdRowWidth+maxRatioRowWidth)); } function redrawLabelBackgrounds() { //for all label texts drawn yet d3.select('#chart').selectAll(".c3-chart-arc > text").each(function(v) { // get d3 node var label = d3.select(this); var labelNode = label.node(); //check if label is drawn if (labelNode) { if (labelNode.childElementCount === 0 && labelNode.innerHTML.length > 0) { //build data var data = labelNode.innerHTML.split(';'); label.text(""); data.forEach(function(i, n) { label.append("tspan") .text(i) .attr("dy", (n === 0) ? 0 : "1.2em") .attr("x", 0) .attr("text-anchor", "middle"); }, label); } //check if element is visible if (d3.select(labelNode.parentNode).style("display") !== 'none') { //get pos of the label text var pos = label.attr("transform").match(/-?\\d+(\\.\\d+)?/g); if (pos) { //get surrounding box of the label var bbox = labelNode.getBBox(); //now draw and move the rects d3.select(labelNode.parentNode).select("rect") .attr("transform", "translate(" + (pos[0] - (bbox.width + padding) / 2) + "," + (pos[1] - bbox.height / labelNode.childElementCount) + ")") .attr("width", bbox.width + padding) .attr("height", bbox.height + padding); } } } }); } 
 <link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.12/c3.min.css" rel="stylesheet" /> <script src="https://d3js.org/d3.v5.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.12/c3.min.js"></script> <div id="chart"> </div> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM