多折线图的 SVG 图例 d3 v6

SVG Legend for multi line chart d3 v6

我正在尝试为这个多折线图创建一个图例,这将是一条直线,那么如何根据先前分组的项目宽度计算分组项目的变换转换值。

<g class="legend">
   <g class="legend-group" transform="translate(0, 0)">
      <rect x="0" y="0" width="12" height="12" style="fill: red"/>
      <text x="16.79999" y="6" text-anchor="left" style="alignment-baseline: middle">Text 1</text>
   </g>
   <g class="legend-group" transform="translate(120, 0)">
      <rect x="0" y="0" width="12" height="12" style="fill: green"/>
      <text x="16.79999" y="6" text-anchor="left" style="alignment-baseline: middle">Text 2</text>
   </g>
   <g class="legend-group" transform="translate(240, 0)">
      <rect x="0" y="0" width="12" height="12" style="fill: red"/>
      <text x="16.79999" y="6" text-anchor="left" style="alignment-baseline: middle">Text 3 - Long Texttttt</text>
   </g>
</g>

所以在这里,就像这些文本有不同的长度,所以给所有图例组一个固定的宽度是行不通的,最好的解决方案是什么,这是代码,有没有更好的方法这个?

let {data} = this.props,
  size = 12, 
  width = 120;

let legendGroup = select(node)
     .selectAll('.legend-group')
     .data(data)
     .enter()
     .append('g')
     .class('class', 'legend-group')
     .attr('transform', function(d, i) {
        return `translate(${width * i}, 0)`
     });

     legendGroup
       .append('rect')
       .attr('x', 0)
       .attr('y', 0)
       .attr('width', size)
       .attr('height', size)
       .style('fill', d => d.color);

     legendGroup
        .append('text')
        .attr('x', size * 1.4)
        .attr('y', size/2)
        .text(d => d.name)
        .attr('text-anchor', 'left')
        .style('alignment-baseline', 'middle')

这是一个获取每个组的宽度并使用它来设置位置的示例。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <script src="https://d3js.org/d3.v7.js"></script>
</head>

<body>
    <div id="chart"></div>

    <script>
      // standard margin convention set up

      const margin = { top: 5, bottom: 5, left: 5, right: 5 };

      const width = 500 - margin.left - margin.right;
      const height = 500 - margin.top - margin.bottom;

      const svg = d3.select('#chart')
        .append('svg')
          .attr('width', width + margin.left + margin.right)
          .attr('height', height + margin.top + margin.bottom)
          .attr('font-family', 'sans-serif');

      const g = svg.append('g')
          .attr('transform', `translate(${margin.left},${margin.top})`);

      // color scale

      const color = d3.scaleOrdinal()
          .domain(['Label 1', 'Much much much longer label 2', 'Label 3'])
          .range(d3.schemeCategory10);

      // color legend
      
      const legend = g.append('g')
          .attr('font-family', 'sans-serif');

      // create one g for each entry in the color scale
      const cell = legend.selectAll('g')
        .data(color.domain())
        .join('g');

      const squareSize = 14;

      // add the colored square for each entry
      cell.append('rect')
          .attr('fill', d => color(d))
          .attr('width', squareSize)
          .attr('height', squareSize)

      // add the text label for each entry
      cell.append('text')
          .attr('dominant-baseline', 'middle')
          .attr('x', squareSize * 1.5)
          .attr('y', squareSize / 2)
          .text(d => d);

      // position the cells
      let xPosition = 0;

      cell.each(function(d, i) {
        d3.select(this)
            .attr('transform', `translate(${xPosition})`);
        
        xPosition += (this.getBBox().width + squareSize);
      });
    </script>
</body>
</html>

或者,您也可以使用 HTML 创建图例。然后你可以利用 flexbox 来定位图例条目。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <script src="https://d3js.org/d3.v7.js"></script>
</head>

<body>
    <div id="chart"></div>

    <script>
      // color scale

      const color = d3.scaleOrdinal()
          .domain(['Label 1', 'Much much much longer label 2', 'Label 3'])
          .range(d3.schemeCategory10);

      // color legend

      // create div for the legend to go in
      const legend = d3.select('#chart')
        .append('div')
          .style('display', 'flex')
          .style('font-family', 'sans-serif');

      // create one div for each entry in the color scale
      const cell = legend.selectAll('div')
        .data(color.domain())
        .join('div')
          .style('margin-right', '1em')
          .style('display', 'flex')
          .style('align-items', 'center');

      // add the colored square for each entry
      cell.append('div')
          .style('background', d => color(d))
          .style('min-width', '14px')
          .style('min-height', '14px')
          .style('margin-right', '0.5em');

      // add the text label for each entry
      cell.append('div')
          .text(d => d);
    </script>
</body>
</html>

或者,如果图例需要在 SVG 元素中,那么您可以将 HTML 放在 <foreignObject>.

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <script src="https://d3js.org/d3.v7.js"></script>
</head>

<body>
    <div id="chart"></div>

    <script>
      // standard margin convention set up

      const margin = { top: 5, bottom: 5, left: 5, right: 5 };

      const width = 500 - margin.left - margin.right;
      const height = 500 - margin.top - margin.bottom;

      const svg = d3.select('#chart')
        .append('svg')
          .attr('width', width + margin.left + margin.right)
          .attr('height', height + margin.top + margin.bottom)
          .attr('font-family', 'sans-serif');

      const g = svg.append('g')
          .attr('transform', `translate(${margin.left},${margin.top})`);

      // color scale

      const color = d3.scaleOrdinal()
          .domain(['Label 1', 'Much much much longer label 2', 'Label 3'])
          .range(d3.schemeCategory10);

      // color legend
      
      const legend = g.append('g')
        .append('foreignObject')
          .attr('x', 0)
          .attr('y', 0)
          .attr('width', width)
          .attr('height', 20)
        .append('xhtml:div')
          .style('display', 'flex')
          .style('font-family', 'sans-serif');

      // create one div for each entry in the color scale
      const cell = legend.selectAll('div')
        .data(color.domain())
        .join('div')
          .style('margin-right', '1em')
          .style('display', 'flex')
          .style('align-items', 'center');

      // add the colored square for each entry
      cell.append('div')
          .style('background', d => color(d))
          .style('min-width', '14px')
          .style('min-height', '14px')
          .style('margin-right', '0.5em');

      // add the text label for each entry
      cell.append('div')
          .text(d => d);
    </script>
</body>
</html>