Files
lcp-visualizer/blocks/components/LCPLegend.js

118 lines
4.0 KiB
JavaScript

import { useEffect, useRef, useState } from '@wordpress/element';
import * as d3 from 'd3';
import PropTypes from 'prop-types';
const LCPLegend = ({
items = [],
itemSize = 20,
spacing = 5,
startX = 20,
startY = 20,
fontFamily = 'Arial',
fontSize = 14
}) => {
const svgRef = useRef(null);
const [containerWidth, setContainerWidth] = useState(0);
// Update the container's width when it changes
useEffect(() => {
if (svgRef.current) {
setContainerWidth(svgRef.current.clientWidth);
}
}, [svgRef.current]);
useEffect(() => {
if (!items.length || !svgRef.current || containerWidth === 0) return;
// Clear any existing content
d3.select(svgRef.current).selectAll("*").remove();
// Variables for item dimensions and row management
const rowHeight = itemSize + spacing;
let currentX = 0;
let currentY = 0;
// Calculate the maximum number of items that can fit per row
const itemWidth = itemSize + spacing + fontSize * 0.6; // Item width (rect + label)
const itemsPerRow = Math.floor(containerWidth / itemWidth); // Items per row based on container width
// Create the SVG container
const svg = d3.select(svgRef.current)
.attr('width', '100%')
.style('height', null) // Let height be dynamic
.style('overflow', 'visible');
// Create the legend items group
const legend = svg.append('g')
.attr('class', 'legend-group')
.attr('transform', `translate(${startX}, ${startY})`);
// Loop over items to position them
items.forEach((item, i) => {
// Calculate the width of each item (rect + label)
const labelWidth = item.Label.length * fontSize * 0.6; // Approximate label width
const itemWidth = itemSize + spacing + labelWidth; // Total width of the item (rect + label)
// If the item doesn't fit in the current row, move to the next row
if (i % itemsPerRow === 0 && i !== 0) {
currentX = 0; // Reset X position
currentY += rowHeight; // Move down to next row
}
// Create the rectangle
legend.append('rect')
.attr('x', currentX)
.attr('y', currentY)
.attr('width', itemSize)
.attr('height', itemSize)
.style('fill', item.color || '#cccccc');
// Create the label
legend.append('text')
.attr('x', currentX + itemSize + spacing)
.attr('y', currentY + itemSize / 2)
.text(item.Label || '')
.style('font-family', fontFamily)
.style('font-size', `${fontSize}px`)
.style('dominant-baseline', 'middle')
.style('fill', '#333333');
// Update the current X position for the next item
currentX += itemWidth;
});
// Calculate the height of the SVG element based on the number of rows
const totalRows = Math.ceil(items.length / itemsPerRow);
const calculatedHeight = totalRows * rowHeight + startY * 2;
// Set the correct height based on totalRows
svg.attr('height', calculatedHeight);
}, [items, containerWidth, itemSize, spacing, startX, startY, fontFamily, fontSize]);
return (
<svg
ref={svgRef}
className="lcp-legend"
style={{
width: '100%' // Ensure width is 100% of the parent container
}}
/>
);
};
LCPLegend.propTypes = {
items: PropTypes.arrayOf(PropTypes.shape({
Label: PropTypes.string.isRequired,
color: PropTypes.string.isRequired
})),
itemSize: PropTypes.number,
spacing: PropTypes.number,
startX: PropTypes.number,
startY: PropTypes.number,
fontFamily: PropTypes.string,
fontSize: PropTypes.number
};
export default LCPLegend;