Home > Blockchain >  SVG Legend for multi line chart d3 v6
SVG Legend for multi line chart d3 v6

Time:09-18

I'm trying to create a legend for this multiple line Chart and this will be in a straight line, so how to calculate the transform translate values for the grouped items according to the previously grouped item width.

<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>

so Here, its like these texts are having diffrent length, so giving a fixed width for all legend-group doesn't work, what is the best solution, this is the code, is there any better way of doing this ?

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')

CodePudding user response:

Here's an example that gets the width of each group and uses that to set the positions.

<!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>

Alternatively, you could also use HTML to create the legend. Then you can take advantage of flexbox to position the legend entries.

<!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>

Or, if the legend needs to be in the SVG element, then you could put the HTML inside a <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>

  • Related