Spaces:
Configuration error
Configuration error
| import { useEffect, useRef, useCallback } from 'react'; | |
| import * as d3 from 'd3'; | |
| import { useFontMapStore } from '../../../store/fontMapStore'; | |
| const SCALE_EXTENT = [0.4, 10.0]; | |
| const INITIAL_SCALE = 0.8; | |
| const TRANSITION_DURATION = 750; | |
| /** | |
| * Hook de zoom basé sur DebugUMAP. | |
| * Fonctionne avec un SVG viewBox → antialiasing natif. | |
| * | |
| * Le handler sélectionne .viewport-group dynamiquement pour rester | |
| * compatible avec useMapRenderer qui peut le recréer. | |
| */ | |
| export function useMapZoom(svgRef, enabled = true) { | |
| const zoomRef = useRef(null); | |
| useEffect(() => { | |
| if (!enabled || !svgRef.current) return; | |
| const svg = d3.select(svgRef.current); | |
| // Le viewport-group doit exister (créé par useMapRenderer) | |
| if (svg.select('.viewport-group').empty()) return; | |
| // Nettoyer un éventuel zoom précédent | |
| svg.on('.zoom', null); | |
| const zoom = d3.zoom() | |
| .scaleExtent(SCALE_EXTENT) | |
| .on('zoom', (event) => { | |
| svg.select('.viewport-group').attr('transform', event.transform); | |
| svg.select('.highlight-group').attr('transform', event.transform); | |
| svg.select('.centroids-group').attr('transform', event.transform); | |
| if (window.updateTooltipTransform) window.updateTooltipTransform(event.transform); | |
| if (window.updateTooltipPositions) window.updateTooltipPositions(); | |
| }); | |
| svg.call(zoom); | |
| // Zoom initial centré à 80 % | |
| const svgRect = svg.node().getBoundingClientRect(); | |
| const cx = svgRect.width / 2; | |
| const cy = svgRect.height / 2; | |
| const initialTransform = d3.zoomIdentity | |
| .translate(cx * (1 - INITIAL_SCALE), cy * (1 - INITIAL_SCALE)) | |
| .scale(INITIAL_SCALE); | |
| svg.call(zoom.transform, initialTransform); | |
| zoomRef.current = zoom; | |
| // Fonctions globales pour ZoomControls | |
| window.zoomIn = () => svg.transition().duration(200).call(zoom.scaleBy, 1.5); | |
| window.zoomOut = () => svg.transition().duration(200).call(zoom.scaleBy, 1 / 1.5); | |
| window.resetZoom = () => { | |
| const rect = svg.node().getBoundingClientRect(); | |
| const rcx = rect.width / 2; | |
| const rcy = rect.height / 2; | |
| const t = d3.zoomIdentity | |
| .translate(rcx * (1 - INITIAL_SCALE), rcy * (1 - INITIAL_SCALE)) | |
| .scale(INITIAL_SCALE); | |
| const store = useFontMapStore.getState(); | |
| store.setIsTransitioning(true); | |
| store.setHoveredFont(null); | |
| svg.transition().duration(TRANSITION_DURATION).call(zoom.transform, t) | |
| .on('end', () => { | |
| useFontMapStore.getState().setIsTransitioning(false); | |
| }); | |
| }; | |
| const svgNode = svgRef.current; | |
| return () => { | |
| if (svgNode) d3.select(svgNode).on('.zoom', null); | |
| delete window.zoomIn; | |
| delete window.zoomOut; | |
| delete window.resetZoom; | |
| zoomRef.current = null; | |
| }; | |
| }, [enabled, svgRef]); | |
| const centerOnFont = useCallback((font) => { | |
| if (!font || !zoomRef.current || !svgRef.current) return; | |
| const svg = d3.select(svgRef.current); | |
| const glyphGroup = svg.select(`g.glyph-group[data-font-id="${font.id}"]`); | |
| if (glyphGroup.empty()) return; | |
| const transformAttr = glyphGroup.attr('data-original-transform'); | |
| if (!transformAttr) return; | |
| const match = transformAttr.match(/translate\(([^,]+),\s*([^)]+)\)/); | |
| if (!match) return; | |
| const fontX = parseFloat(match[1]); | |
| const fontY = parseFloat(match[2]); | |
| const svgNode = svgRef.current; | |
| const width = svgNode.clientWidth || svgNode.getBoundingClientRect().width; | |
| const height = svgNode.clientHeight || svgNode.getBoundingClientRect().height; | |
| const scale = 2.5; | |
| const translateX = width / 2 - fontX * scale; | |
| const translateY = height / 2 - fontY * scale; | |
| const transform = d3.zoomIdentity | |
| .translate(translateX, translateY) | |
| .scale(scale); | |
| const store = useFontMapStore.getState(); | |
| store.setIsTransitioning(true); | |
| store.setHoveredFont(null); | |
| svg.transition() | |
| .duration(800) | |
| .ease(d3.easeCubicInOut) | |
| .call(zoomRef.current.transform, transform) | |
| .on('end', () => { | |
| useFontMapStore.getState().setIsTransitioning(false); | |
| }); | |
| }, [svgRef]); | |
| const resetZoom = useCallback(() => { | |
| if (window.resetZoom) window.resetZoom(); | |
| }, []); | |
| return { centerOnFont, resetZoom }; | |
| } | |