File size: 4,302 Bytes
2fc4361
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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 };
}