I'm working with a design that has a legend and that legend has two bits of text next to each other. Due to their styling, I believe I have to append two different g to make it work.
I was sort of success with it and with some online resources but what I'm seeing is that with a small primary text my second text is not being placed correctly.
Here's a codesanbox with the running example (notice how the object with the Previous versions label shows the percentage with some distance but the one with 0.0.1 doesn't.
Here's my code with the relevant bits
import { FC, useEffect, useRef } from 'react'
import * as d3 from 'd3'
interface Props {
data: Array<any>
}
/**
* Taken from
* https://bl.ocks.org/eesur/287b5700b5881e8899cc7301a5fefb94
*/
const dataConstructor = (data: any[], total: number) => {
// use scale to get percent values
const percent = d3.scaleLinear().domain([0, total]).range([0, 100])
// filter out data that has zero values
// also get mapping for next placement
// (save having to format data for d3 stack)
let cumulative = 0
const _data = data
.map((d) => {
cumulative += d.value!
return {
value: d.value,
// want the cumulative to prior value (start of rect)
cumulative: cumulative - d.value!,
label: d.label,
percent: percent(d.value!),
color: d.color,
}
})
.filter((d) => d.value! > 0)
return _data
}
/**
* Taken from
* https://gist.github.com/huytd/327e453c95ca3edadb32d0c867e2561b
*/
function textSize(text: any) {
let container = d3.select('body').append('svg')
container
.append('text')
.style('transform', 'translate(-99999, y: -99999')
.text(text)
const size = container.node()!.getBBox()
container.remove()
return { width: size.width, height: size.height }
}
const drawChart = (element: any, data: any) => {
const f = d3.format('.1f')
const totalValueFromData = data.reduce(
(prev: number, curr: any) => prev + curr.value!,
0
)
const height = 36
const width = 472
const halfBarHeight = height / 2
const _data = dataConstructor(data, totalValueFromData)
const xScale = d3
.scaleLinear()
.domain([0, totalValueFromData])
.range([0, width])
const svg = d3
.select(element)
.append('svg')
.attr('preserveAspectRatio', 'xMinYMin meet')
.attr('viewBox', '0 0 472 102')
[... boring graph stuff]
const legend = svg.append('g').attr('transform', 'translate(0, 24)')
/**
* Legend Squares
*/
legend
.append('g')
.selectAll('legendSquares')
.data(_data)
.enter()
.append('rect')
.attr('x', 2)
.attr('y', function (d, i) {
return 30 + i * 20
})
.attr('width', 10)
.attr('height', 10)
.attr('rx', '1.2')
.style('fill', (d: any) => d.color)
/**
* Legend Strings
*/
legend
.append('g')
.selectAll('legendStrings')
.data(_data)
.enter()
.append('text')
.attr('text-anchor', 'start')
.attr('x', 20)
.attr('y', function (d, i) {
return 30 + i * 20 + 9
})
.text(function (d: any) {
return d.label
})
.style('fill', '#4D5056')
.style('font-size', '11px')
.style('line-height', '14px')
/**
* Legend Percentages
*/
legend
.append('g')
.selectAll('legendPercentages')
.data(_data)
.enter()
.append('text')
.attr('text-anchor', 'start')
.attr('x', function (d: any) {
// here's where I'm trying to place the percentage close to my other text in the legend
return textSize(d.label).width
})
.attr('y', function (d, i) {
return 30 + i * 20 + 9
})
.text((d) => f(d.percent) + '%')
.style('fill', '#868C97')
.style('font-size', '11px')
.style('line-height', '14px')
}
const HorizontalStackedLineChart: FC<Props> = ({ data }) => {
const horizontalStackedLineChartRef = useRef(null)
useEffect(() => {
if (horizontalStackedLineChartRef.current && data) {
d3.select(horizontalStackedLineChartRef.current).selectAll('svg').remove()
drawChart(horizontalStackedLineChartRef.current, data)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [horizontalStackedLineChartRef, data])
return (
<div
ref={horizontalStackedLineChartRef}
data-testid="horizontalStackedLineChar"
/>
)
}
export default HorizontalStackedLineChart
const data = [
{
label: "0.0.1",
value: 100,
color: "#ff0000"
},
{
label: "Previous versions",
value: 200,
color: "#00ff00"
}
];
What am I doing wrong? And if there's a better option to make this work? From what I could see, this seemed to be the only actual way of doing it but I can't figure out why the positioning of the text is inconsistent
