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>