import { useEffect, useRef } from 'react';
import * as d3 from 'd3';
/**
* Hook for managing D3 selections with React lifecycle
* Provides a ref to the SVG/container element and runs a render function when dependencies change
*
* @param renderFn - Function that receives the D3 selection and performs rendering
* @param dependencies - Array of dependencies that trigger re-render
* @returns Ref to attach to the SVG/container element
*
* @example
* ```tsx
* function BarChart({ data }: { data: number[] }) {
* const ref = useD3(
* (svg) => {
* const width = 600;
* const height = 400;
* const margin = { top: 20, right: 20, bottom: 30, left: 40 };
*
* // Clear previous content
* svg.selectAll('*').remove();
*
* // Set up scales
* const x = d3.scaleBand()
* .domain(data.map((_, i) => i.toString()))
* .range([margin.left, width - margin.right])
* .padding(0.1);
*
* const y = d3.scaleLinear()
* .domain([0, d3.max(data) || 0])
* .range([height - margin.bottom, margin.top]);
*
* // Draw bars
* svg.selectAll('rect')
* .data(data)
* .join('rect')
* .attr('x', (_, i) => x(i.toString())!)
* .attr('y', d => y(d))
* .attr('width', x.bandwidth())
* .attr('height', d => y(0) - y(d))
* .attr('fill', 'steelblue');
* },
* [data]
* );
*
* return ;
* }
* ```
*/
export function useD3(
renderFn: (selection: d3.Selection) => void,
dependencies: React.DependencyList = []
): React.RefObject {
const ref = useRef(null);
useEffect(() => {
if (ref.current) {
const selection = d3.select(ref.current);
renderFn(selection);
}
}, dependencies);
return ref;
}
/**
* Hook for managing D3 selections with automatic resize handling
* Similar to useD3 but also triggers re-render on window resize
*
* @param renderFn - Function that receives the D3 selection and performs rendering
* @param dependencies - Array of dependencies that trigger re-render
* @returns Ref to attach to the SVG/container element
*
* @example
* ```tsx
* function ResponsiveChart({ data }: { data: number[] }) {
* const ref = useD3WithResize(
* (svg) => {
* const container = svg.node();
* const width = container?.clientWidth || 600;
* const height = container?.clientHeight || 400;
*
* // Render with responsive dimensions
* // ...
* },
* [data]
* );
*
* return ;
* }
* ```
*/
export function useD3WithResize(
renderFn: (selection: d3.Selection) => void,
dependencies: React.DependencyList = []
): React.RefObject {
const ref = useRef(null);
useEffect(() => {
if (!ref.current) return;
const selection = d3.select(ref.current);
const render = () => renderFn(selection);
// Initial render
render();
// Set up resize observer
const resizeObserver = new ResizeObserver(() => {
render();
});
resizeObserver.observe(ref.current);
// Cleanup
return () => {
resizeObserver.disconnect();
};
}, dependencies);
return ref;
}