(
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [chart]); // Re-run when instance changes
- // Set up zoom change polling (100ms interval)
+ // Register/unregister zoomRangeChange event handler.
+ // Also emits the current zoom range once on subscribe (initial hydration)
+ // so consumers don't need to wait for user interaction to receive the first value.
useEffect(() => {
const instance = chart;
if (!instance || instance.disposed || !onZoomChange) return;
- const checkZoomChange = () => {
- if (!instance || instance.disposed) return;
-
- const currentRange = instance.getZoomRange();
- const lastRange = lastZoomRangeRef.current;
-
- // Check if zoom range changed
- if (currentRange !== null) {
- if (
- lastRange === null ||
- lastRange.start !== currentRange.start ||
- lastRange.end !== currentRange.end
- ) {
- lastZoomRangeRef.current = currentRange;
- onZoomChange(currentRange);
- }
- } else {
- // Range is null (no zoom), reset last range
- if (lastRange !== null) {
- lastZoomRangeRef.current = null;
- }
- }
+ const handler = (payload: ChartGPUZoomRangeChangePayload) => {
+ // Map upstream payload to ZoomRange (strip `source`)
+ onZoomChange({ start: payload.start, end: payload.end });
};
- const intervalId = setInterval(checkZoomChange, 100);
- zoomPollIntervalRef.current = intervalId;
+ instance.on('zoomRangeChange', handler);
- // Initial check
- checkZoomChange();
+ // Hydrate: fire once with the current zoom range (if non-null)
+ const current = instance.getZoomRange();
+ if (current) {
+ onZoomChange({ start: current.start, end: current.end });
+ }
return () => {
- if (zoomPollIntervalRef.current) {
- clearInterval(zoomPollIntervalRef.current);
- zoomPollIntervalRef.current = null;
+ try {
+ instance.off('zoomRangeChange', handler);
+ } catch {
+ // instance may already be disposed; swallow
}
- lastZoomRangeRef.current = null;
};
- }, [chart, onZoomChange]); // Re-run when instance or callback changes
+ }, [chart, onZoomChange]);
return (
;
diff --git a/src/index.ts b/src/index.ts
index b34e708..7a30b1b 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -36,20 +36,28 @@ export { ChartGPUChart } from './ChartGPUChart';
export type { ChartGPUChartProps } from './ChartGPUChart';
// Re-export chartgpu helpers (avoid colliding with our `ChartGPU` React component)
-export { createChart, connectCharts } from 'chartgpu';
+export { createChart, connectCharts } from '@chartgpu/chartgpu';
export { createAnnotationAuthoring } from './createAnnotationAuthoring';
-// Re-export types from chartgpu for convenience
+// Re-export types from @chartgpu/chartgpu for convenience
// This provides a single import point for all ChartGPU types
export type {
// Core instance and options
ChartGPUInstance,
ChartGPUOptions,
-
+
// Event payloads
ChartGPUEventPayload,
ChartGPUCrosshairMovePayload,
-
+ ChartGPUZoomRangeChangePayload,
+
+ // Hit testing
+ ChartGPUHitTestResult,
+ ChartGPUHitTestMatch,
+
+ // Chart sync
+ ChartSyncOptions,
+
// Annotation authoring
AnnotationAuthoringInstance,
AnnotationAuthoringOptions,
@@ -63,20 +71,38 @@ export type {
ScatterSeriesConfig,
CandlestickSeriesConfig,
SeriesConfig,
-
+
+ // Style configurations
+ LineStyleConfig,
+ AreaStyleConfig,
+
// Data types
DataPoint,
OHLCDataPoint,
ScatterPointTuple,
-
+
// Zoom / interaction
DataZoomConfig,
+ // Legend
+ LegendConfig,
+ LegendPosition,
+
+ // Animation
+ AnimationConfig,
+
+ // Tooltip
+ TooltipConfig,
+ TooltipParams,
+
+ // Performance
+ PerformanceMetrics,
+
// Theme configuration
ThemeConfig,
ThemeName,
-
+
// Axis and grid configurations
AxisConfig,
GridConfig,
-} from 'chartgpu';
+} from '@chartgpu/chartgpu';
diff --git a/src/types.ts b/src/types.ts
index 745b77a..8bea7b6 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -8,10 +8,11 @@ import type {
ChartGPUOptions,
ChartGPUInstance,
ChartGPUEventPayload,
+ ChartGPUHitTestResult,
DataPoint,
OHLCDataPoint,
ChartGPUCrosshairMovePayload,
-} from 'chartgpu';
+} from '@chartgpu/chartgpu';
/**
* Bivariant callback helper (matches React's event handler variance behavior).
@@ -163,6 +164,38 @@ export interface ChartGPUHandle {
* @param options - New complete chart configuration
*/
setOption(options: ChartGPUOptions): void;
+
+ /**
+ * Programmatically set the zoom range (percent-space).
+ * No-op when zoom is disabled on the chart.
+ *
+ * @param start - Start of zoom range (0-100)
+ * @param end - End of zoom range (0-100)
+ */
+ setZoomRange(start: number, end: number): void;
+
+ /**
+ * Programmatically drive the crosshair / tooltip to a domain-space x value.
+ * Passing `null` clears the crosshair.
+ *
+ * @param x - Domain-space x value, or null to clear
+ * @param source - Optional source identifier (useful for sync disambiguation)
+ */
+ setInteractionX(x: number | null, source?: unknown): void;
+
+ /**
+ * Read the current interaction x (domain units), or `null` when inactive.
+ */
+ getInteractionX(): number | null;
+
+ /**
+ * Perform hit-testing on a pointer or mouse event.
+ * Returns coordinates and matched chart element (if any).
+ *
+ * @param e - Pointer or mouse event to test
+ * @returns Hit-test result with coordinates and optional match
+ */
+ hitTest(e: PointerEvent | MouseEvent): ChartGPUHitTestResult;
}
/**
@@ -176,4 +209,4 @@ export type {
ChartGPUCrosshairMovePayload,
DataPoint,
OHLCDataPoint,
-} from 'chartgpu';
+} from '@chartgpu/chartgpu';
diff --git a/src/useChartGPU.ts b/src/useChartGPU.ts
index d7934bb..5ed43a4 100644
--- a/src/useChartGPU.ts
+++ b/src/useChartGPU.ts
@@ -1,6 +1,6 @@
import { useEffect, useRef, useState } from 'react';
-import { ChartGPU as ChartGPULib } from 'chartgpu';
-import type { ChartGPUOptions } from 'chartgpu';
+import { ChartGPU as ChartGPULib } from '@chartgpu/chartgpu';
+import type { ChartGPUOptions } from '@chartgpu/chartgpu';
import type { ChartInstance } from './types';
/**
diff --git a/src/useConnectCharts.ts b/src/useConnectCharts.ts
index 4153611..37c9c97 100644
--- a/src/useConnectCharts.ts
+++ b/src/useConnectCharts.ts
@@ -1,18 +1,23 @@
import { useEffect, useRef } from 'react';
-import { connectCharts } from 'chartgpu';
-import type { ChartGPUInstance } from 'chartgpu';
+import { connectCharts } from '@chartgpu/chartgpu';
+import type { ChartGPUInstance, ChartSyncOptions } from '@chartgpu/chartgpu';
type DisconnectCharts = ReturnType;
/**
- * React hook to connect multiple ChartGPU instances for synced crosshair/tooltip x.
+ * React hook to connect multiple ChartGPU instances for synced interactions.
+ *
+ * Supports optional `syncOptions` to control which interactions are synced:
+ * - `syncCrosshair` (default `true`): sync crosshair + tooltip x across charts
+ * - `syncZoom` (default `false`): sync zoom/pan across charts
*
* Safety:
* - Will not connect until all instances exist and are not disposed.
- * - Automatically disconnects on unmount and when the set of instances changes.
+ * - Automatically disconnects on unmount and when the set of instances or options change.
*/
export function useConnectCharts(
- charts: ReadonlyArray
+ charts: ReadonlyArray,
+ syncOptions?: ChartSyncOptions
): void {
const disconnectRef = useRef(null);
const idsRef = useRef>(new WeakMap());
@@ -27,13 +32,18 @@ export function useConnectCharts(
};
// Build a stable signature so callers can pass new arrays without forcing reconnect.
- const signature = charts
- .map((c) => {
- if (!c) return 'null';
- const id = getId(c);
- return `${id}:${c.disposed ? 1 : 0}`;
- })
- .join('|');
+ // Include syncOptions so changes to them trigger reconnection.
+ const optionsSig = syncOptions
+ ? `cr=${syncOptions.syncCrosshair ?? ''},zm=${syncOptions.syncZoom ?? ''}`
+ : '';
+ const signature =
+ charts
+ .map((c) => {
+ if (!c) return 'null';
+ const id = getId(c);
+ return `${id}:${c.disposed ? 1 : 0}`;
+ })
+ .join('|') + `|${optionsSig}`;
useEffect(() => {
// Always tear down any previous connection first.
@@ -51,7 +61,7 @@ export function useConnectCharts(
}
try {
- const disconnect = connectCharts(resolved);
+ const disconnect = connectCharts(resolved, syncOptions);
disconnectRef.current = disconnect;
return () => {
disconnect();
@@ -60,10 +70,23 @@ export function useConnectCharts(
} catch (err) {
// Avoid crashing render trees if upstream throws (e.g. mismatched chart state).
// Consumers can still manually call connectCharts if they need error handling.
- console.error('useConnectCharts: failed to connect charts', err);
+ // Only log in development; tree-shaken in production builds
+ try {
+ if (
+ // @ts-expect-error -- process may not exist in browser environments
+ typeof process === 'undefined' || process.env?.NODE_ENV !== 'production'
+ ) {
+ // eslint-disable-next-line no-console
+ console.error('useConnectCharts: failed to connect charts', err);
+ }
+ } catch {
+ // process access threw; assume dev
+ // eslint-disable-next-line no-console
+ console.error('useConnectCharts: failed to connect charts', err);
+ }
return;
}
- // `charts` is intentionally not a dependency; `signature` captures identity + disposed state.
+ // `charts` is intentionally not a dependency; `signature` captures identity + disposed state + options.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [signature]);
}
diff --git a/vite.config.ts b/vite.config.ts
index 47cd046..c1d090a 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -34,7 +34,7 @@ export default defineConfig(({ command }) => {
fileName: () => 'index.js',
},
rollupOptions: {
- external: ['react', 'react-dom', 'react/jsx-runtime', 'chartgpu'],
+ external: ['react', 'react-dom', 'react/jsx-runtime', '@chartgpu/chartgpu'],
output: {
preserveModules: false,
},