C3/D3 pie legend format / label overlap

前端 未结 2 703
一向
一向 2021-01-28 10:41

I have a piechart using C3. I have changed the default legend names by adding values and percentages and now.

  1. I am looking for a way to format this legend nicely
相关标签:
2条回答
  • 2021-01-28 11:18

    I know I can add a custom legend but I want to use the original SVG legendand I added tspans now:

    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("");
            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) {
              // TODO: mofify the pos of the text
              //            pos[0] = (pos[0]/h*90000);
              //            pos[1] = (pos[1]/h*90000);
              // remove dy and move label
              //d3.select(this).attr("dy", 0);
              //d3.select(this).attr("transform", "translate(" + pos[0] + "," + pos[1] + ")");
    
              //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 solves the Problem #1 to 98% ;)

    Now I only need to figue our how to calculate the width of the spans dynamically depending on the content the most elegant way.

    (and #2 of course)

    0 讨论(0)
  • 2021-01-28 11:24

    It is possible to create a custom legend. Then you can create & style it as you like in either SVG or HTML.

    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);
    });
    
    var chart = c3.generate({
      bindto: d3.select('#chart'),
      data: {
        columns: [
          [columns[0]].concat(data[0])
        ],
        names: columnNames,
        type: 'pie',
      },
      legend: {
        position: 'right',
        show: false
      },
      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: redrawLabelBackgrounds
    });
    
    
    
    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 redrawLabelBackgrounds() {
      //for all label texts drawn yet
      //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) {
              // TODO: mofify the pos of the text
              //            pos[0] = (pos[0]/h*90000);
              //            pos[1] = (pos[1]/h*90000);
              // remove dy and move label
              //d3.select(this).attr("dy", 0);
              //d3.select(this).attr("transform", "translate(" + pos[0] + "," + pos[1] + ")");
    
              //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);
            }
          }
        }
      });
    }
    
    //console.log(columnData)
    
    
    function toggle(id) {
      chart.toggle(id);
    }
    
    var table = d3.select('#chart')
      .append('table').attr('class', 'legend')
    
    var row = table.selectAll('row').data(columnData)
    var rowEnter = row.enter().append('tr').attr('class', 'legend-row')
    rowEnter.append('td').attr('style', (d, i) => `width: 20px;background-color:${colors[i]}`)
    rowEnter.append('td').text(d => d[0])
    rowEnter.append('td').text(d => d[1])
    
    
    rowEnter.on('mouseover', function(id) {
      if (!this.classList.contains('legend-row-disabled')) {
        rowEnter.classed('legend-row-inactive', true)
        chart.focus(id);
      }
    
    });
    
    rowEnter.on('mouseout', function(id) {
      rowEnter.classed('legend-row-inactive', false)
      if (!this.classList.contains('legend-row-disabled')) {
        chart.focus()
      }
    })
    
    rowEnter.on('click', function(id) {
      chart.toggle(id);
      this.classList.toggle('legend-row-disabled');
      chart.focus()
    
    });
    .legend {
      font: 12px sans-serif;
      border-collapse: collapse;
    }
    
    .legend .legend-row {
      cursor: pointer;
    }
    
    .legend .legend-row td:first-child {
      width: 14px !important;
    }
    
    .legend .legend-row.legend-row-inactive {
      opacity: 0.3;
    }
    
    .legend .legend-row.legend-row-inactive:hover {
      opacity: 1.0;
    }
    
    .legend .legend-row.legend-row-disabled,
    .legend .legend-row.legend-row-disabled:hover {
      opacity: 0.2;
    }
    
    .legend td {
      padding: 5px;
    }
    <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>

    0 讨论(0)
提交回复
热议问题