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(() => { const handleResize = () => { if (svgRef.current) { setContainerWidth(svgRef.current.clientWidth); // Measure the width of the container } }; handleResize(); // Initial measurement window.addEventListener('resize', handleResize); // Update on resize return () => { window.removeEventListener('resize', handleResize); // Cleanup on component unmount }; }, []); useEffect(() => { if (!items.length || !svgRef.current || containerWidth === 0) return; // Clear any existing content d3.select(svgRef.current).selectAll("*").remove(); // Calculate item widths first const itemWidths = items.map(item => { const labelWidth = item.Label.length * fontSize * 0.6; return itemSize + spacing + labelWidth; }); // Calculate total width of all items const totalWidth = itemWidths.reduce((sum, width) => sum + width + spacing, 0); // Calculate optimal number of rows const availableWidth = containerWidth - (startX * 2); const minRows = Math.ceil(totalWidth / availableWidth); // Create arrays to store items for each row const rows = Array(minRows).fill().map(() => []); let rowWidths = Array(minRows).fill(0); // Distribute items across rows to minimize empty space itemWidths.forEach((width, index) => { // Find the row with the most remaining space const rowIndex = rowWidths .map((rowWidth, i) => ({ width: rowWidth, index: i })) .sort((a, b) => a.width - b.width)[0].index; rows[rowIndex].push(index); rowWidths[rowIndex] += width + spacing; }); // Create the SVG container const svg = d3.select(svgRef.current) .attr('width', '100%') .style('overflow', 'visible'); // Create the legend items group const legend = svg.append('g') .attr('class', 'legend-group') .attr('transform', `translate(${startX}, ${startY})`); // Place items in their calculated positions rows.forEach((rowItems, rowIndex) => { let currentX = 0; const currentY = rowIndex * (itemSize + spacing); rowItems.forEach((itemIndex) => { const item = items[itemIndex]; const labelWidth = item.Label.length * fontSize * 0.6; // Create the rectangle (swatch) 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'); currentX += itemSize + spacing + labelWidth + spacing; }); }); // Set the SVG height based on the number of rows const calculatedHeight = (minRows * (itemSize + spacing)) + (startY * 2); svg.attr('height', calculatedHeight); }, [items, containerWidth, itemSize, spacing, startX, startY, fontFamily, fontSize]); return ( ); }; 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;