Skip to content

Commit 4c899dc

Browse files
committed
claude PoC
1 parent fe5209d commit 4c899dc

6 files changed

Lines changed: 249 additions & 0 deletions

File tree

packages/x-charts-premium/src/ChartsGeoDataProviderPremium/ChartsGeoDataProviderPremium.plugins.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ import {
1818
useGeoProjection,
1919
type UseGeoProjectionSignature,
2020
} from '../internals/plugins/useGeoProjection';
21+
import {
22+
useGeoProjectionZoom,
23+
type UseGeoProjectionZoomSignature,
24+
} from '../internals/plugins/useGeoProjectionZoom';
2125

2226
export const GEO_PREMIUM_PLUGINS = [
2327
useChartZAxis,
@@ -28,6 +32,7 @@ export const GEO_PREMIUM_PLUGINS = [
2832
useChartVisibilityManager,
2933
useChartProExport,
3034
useGeoProjection,
35+
useGeoProjectionZoom,
3136
] as const;
3237

3338
export type GeoPremiumPluginSignatures<SeriesType extends ChartSeriesType = ChartSeriesType> = [
@@ -39,4 +44,5 @@ export type GeoPremiumPluginSignatures<SeriesType extends ChartSeriesType = Char
3944
UseChartVisibilityManagerSignature<SeriesType>,
4045
UseChartProExportSignature,
4146
UseGeoProjectionSignature,
47+
UseGeoProjectionZoomSignature,
4248
];
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export { useGeoProjectionZoom } from './useGeoProjectionZoom';
2+
export type {
3+
UseGeoProjectionZoomSignature,
4+
UseGeoProjectionZoomParameters,
5+
UseGeoProjectionZoomInstance,
6+
} from './useGeoProjectionZoom.types';
7+
export type { MapZoomTransform } from './mapZoom.utils';
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { type GeoProjection } from '@mui/x-charts-vendor/d3-geo';
2+
3+
export interface MapZoomTransform {
4+
/** Absolute d3-geo projection scale. */
5+
scale: number;
6+
/** Projection translation `[x, y]` in SVG pixels. */
7+
translate: [number, number];
8+
}
9+
10+
/**
11+
* Pan the projection by a screen-space pixel delta.
12+
*/
13+
export function panProjection(projection: GeoProjection, dx: number, dy: number): MapZoomTransform {
14+
const scale = projection.scale();
15+
const [tx, ty] = projection.translate();
16+
return { scale, translate: [tx + dx, ty + dy] };
17+
}
18+
19+
/**
20+
* Modify projection scale by a given factor such that focused point stay at the same SVG coordinates.
21+
* @param {GeoProjection} projection The initial projection
22+
* @param {number} factor the factor to apply to the projection scale
23+
* @param {{ x: number; y: number }} focal the focal point that must stay unchanged in SVG coordinates
24+
* @param {number} minScale the minimum scale allowed
25+
* @param {number} maxScale the maximum scale allowed
26+
* @return {MapZoomTransform} The new projection scale and translate that achieve the desired zoom.
27+
*/
28+
export function zoomProjectionAtPoint(
29+
projection: GeoProjection,
30+
factor: number,
31+
focal: { x: number; y: number },
32+
minScale: number,
33+
maxScale: number,
34+
): MapZoomTransform {
35+
const scale = projection.scale();
36+
const [tx, ty] = projection.translate();
37+
38+
const nextScale = Math.max(minScale, Math.min(maxScale, scale * factor));
39+
const k = nextScale / scale;
40+
41+
return {
42+
scale: nextScale,
43+
translate: [k * tx + (1 - k) * focal.x, k * ty + (1 - k) * focal.y],
44+
};
45+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
'use client';
2+
import * as React from 'react';
3+
import { type ChartPlugin, selectorChartDrawingArea } from '@mui/x-charts/internals';
4+
import { usePanGesture, useWheelGesture, usePinchGesture } from '@mui/x-charts-pro/internals';
5+
import { type GeoProjection } from '@mui/x-charts-vendor/d3-geo';
6+
import { selectorChartProjection } from '../useGeoProjection/useGeoProjection.selectors';
7+
import { type UseGeoProjectionZoomSignature } from './useGeoProjectionZoom.types';
8+
import { panProjection, zoomProjectionAtPoint, type MapZoomTransform } from './mapZoom.utils';
9+
10+
/** Multiplicative zoom step applied per wheel tick. */
11+
const WHEEL_ZOOM_STEP = 1.1;
12+
/** Multiplicative zoom step applied per `zoomIn`/`zoomOut` call. */
13+
const BUTTON_ZOOM_STEP = 1.3;
14+
15+
export const useGeoProjectionZoom: ChartPlugin<UseGeoProjectionZoomSignature> = ({
16+
store,
17+
instance,
18+
params,
19+
}) => {
20+
const { zoom: enabled, minScaleRatio, maxScaleRatio, onZoomChange } = params;
21+
22+
// The scale that fits the data in the drawing area. Captured lazily on first
23+
// interaction and used as the reference for the min/max zoom clamp.
24+
const fitScaleRef = React.useRef<number | null>(null);
25+
26+
const getProjection = React.useCallback(
27+
(): GeoProjection | null => selectorChartProjection(store.state),
28+
[store],
29+
);
30+
31+
const applyTransform = React.useCallback(
32+
(transform: MapZoomTransform) => {
33+
store.set('geoProjection', {
34+
...store.state.geoProjection,
35+
scale: transform.scale,
36+
translate: transform.translate,
37+
});
38+
onZoomChange?.(transform);
39+
},
40+
[store, onZoomChange],
41+
);
42+
43+
const zoomAtPoint = React.useCallback(
44+
(factor: number, focal: { x: number; y: number }) => {
45+
const projection = getProjection();
46+
if (!projection) {
47+
return;
48+
}
49+
if (fitScaleRef.current === null) {
50+
fitScaleRef.current = projection.scale();
51+
}
52+
applyTransform(
53+
zoomProjectionAtPoint(
54+
projection,
55+
factor,
56+
focal,
57+
fitScaleRef.current * minScaleRatio,
58+
fitScaleRef.current * maxScaleRatio,
59+
),
60+
);
61+
},
62+
[getProjection, applyTransform, minScaleRatio, maxScaleRatio],
63+
);
64+
65+
const drawingAreaCenter = React.useCallback(() => {
66+
const { left, top, width, height } = selectorChartDrawingArea(store.state);
67+
return { x: left + width / 2, y: top + height / 2 };
68+
}, [store]);
69+
70+
// --- gestures: reuse the generic primitives from x-charts-pro -------------
71+
usePanGesture(instance, {
72+
enabled,
73+
onPan: (delta) => {
74+
const projection = getProjection();
75+
if (!projection) {
76+
return;
77+
}
78+
applyTransform(panProjection(projection, delta.x, delta.y));
79+
},
80+
});
81+
82+
useWheelGesture(instance, {
83+
enabled,
84+
onWheel: (point, event) => {
85+
const factor = event.deltaY < 0 ? WHEEL_ZOOM_STEP : 1 / WHEEL_ZOOM_STEP;
86+
zoomAtPoint(factor, point);
87+
},
88+
});
89+
90+
usePinchGesture(instance, {
91+
enabled,
92+
onPinch: (point, deltaScale) => {
93+
zoomAtPoint(1 + deltaScale, point);
94+
},
95+
});
96+
97+
return {
98+
instance: {
99+
zoomIn: () => zoomAtPoint(BUTTON_ZOOM_STEP, drawingAreaCenter()),
100+
zoomOut: () => zoomAtPoint(1 / BUTTON_ZOOM_STEP, drawingAreaCenter()),
101+
resetMapZoom: () => {
102+
fitScaleRef.current = null;
103+
store.set('geoProjection', {
104+
...store.state.geoProjection,
105+
scale: null,
106+
translate: null,
107+
});
108+
},
109+
},
110+
};
111+
};
112+
113+
useGeoProjectionZoom.params = {
114+
zoom: true,
115+
minScaleRatio: true,
116+
maxScaleRatio: true,
117+
onZoomChange: true,
118+
};
119+
120+
useGeoProjectionZoom.getDefaultizedParams = ({ params }) => ({
121+
...params,
122+
zoom: params.zoom ?? false,
123+
minScaleRatio: params.minScaleRatio ?? 1,
124+
maxScaleRatio: params.maxScaleRatio ?? 8,
125+
});
126+
127+
useGeoProjectionZoom.getInitialState = () => ({});
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { type ChartPluginSignature } from '@mui/x-charts/internals';
2+
import { type UseGeoProjectionSignature } from '../useGeoProjection';
3+
import { type MapZoomTransform } from './mapZoom.utils';
4+
5+
export interface UseGeoProjectionZoomParameters {
6+
/**
7+
* If `true`, the map can be panned (drag) and zoomed (wheel / pinch).
8+
* @default false
9+
*/
10+
zoom?: boolean;
11+
/**
12+
* The minimum zoom level, as a multiple of the scale that fits the data in the drawing area.
13+
* @default 1
14+
*/
15+
minScaleRatio?: number;
16+
/**
17+
* The maximum zoom level, as a multiple of the scale that fits the data in the drawing area.
18+
* @default 8
19+
*/
20+
maxScaleRatio?: number;
21+
/**
22+
* Callback fired when the map zoom or pan changes.
23+
* @param {MapZoomTransform} transform The new projection scale and translation.
24+
*/
25+
onZoomChange?: (transform: MapZoomTransform) => void;
26+
}
27+
28+
interface UseGeoProjectionZoomDefaultizedParameters extends UseGeoProjectionZoomParameters {
29+
zoom: boolean;
30+
minScaleRatio: number;
31+
maxScaleRatio: number;
32+
}
33+
34+
export interface UseGeoProjectionZoomInstance {
35+
/**
36+
* Zoom the map in by a fixed step, centered on the drawing area.
37+
*/
38+
zoomIn: () => void;
39+
/**
40+
* Zoom the map out by a fixed step, centered on the drawing area.
41+
*/
42+
zoomOut: () => void;
43+
/**
44+
* Reset the map to the default scale and translation that fit the data in the drawing area.
45+
*/
46+
resetMapZoom: () => void;
47+
}
48+
49+
export type UseGeoProjectionZoomSignature = ChartPluginSignature<{
50+
params: UseGeoProjectionZoomParameters;
51+
defaultizedParams: UseGeoProjectionZoomDefaultizedParameters;
52+
instance: UseGeoProjectionZoomInstance;
53+
dependencies: [UseGeoProjectionSignature];
54+
}>;

packages/x-charts-pro/src/internals/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@ export type { UseChartsContainerProPropsReturnValue } from '../ChartsContainerPr
33
export type { ChartsSlotsPro, ChartsSlotPropsPro } from './material';
44
export { seriesPreviewPlotMap } from '../ChartsZoomSlider/internals/seriesPreviewPlotMap';
55
export type { PreviewPlotProps } from '../ChartsZoomSlider/internals/previews/PreviewPlot.types';
6+
export {
7+
usePanGesture,
8+
useWheelGesture,
9+
usePinchGesture,
10+
type UsePanGestureOptions,
11+
type UseWheelGestureOptions,
12+
type UsePinchGestureOptions,
13+
type GestureInstance,
14+
type PanGestureConfig,
15+
} from './plugins/zoomGestures';
616
export { defaultSeriesConfigPro } from '../ChartsDataProviderPro/ChartsDataProviderPro';
717
export type { ProPluginsPerSeriesType } from '../context/ChartProApi';
818
export { useHeatmapProps } from '../Heatmap/useHeatmapProps';

0 commit comments

Comments
 (0)