Sizing custom C3 piechart legend dynamically (using D3?)

梦想与她 提交于 2019-12-11 18:06:13

问题


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


回答1:


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>


来源:https://stackoverflow.com/questions/54783106/sizing-custom-c3-piechart-legend-dynamically-using-d3

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!