@automattic/charts 1.4.3 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,162 @@
1
+ import { DataContext } from '@visx/xychart';
2
+ import { __ } from '@wordpress/i18n';
3
+ import { useCallback, useContext, useMemo, useState } from 'react';
4
+ import styles from './x-zoom.module.scss';
5
+ import type { SingleChartRef } from './single-chart-context';
6
+ import type { AxisScale } from '@visx/axis';
7
+ import type { EventHandlerParams } from '@visx/xychart';
8
+ import type { MutableRefObject } from 'react';
9
+
10
+ const MIN_DRAG_PIXELS = 6;
11
+
12
+ type Drag = { a: number; b: number };
13
+ type PointerHandler = ( params: EventHandlerParams< object > ) => void;
14
+
15
+ /**
16
+ * Drag-to-zoom state + pointer handlers for an XY chart. Designed to be
17
+ * embedded in a chart parent: the parent owns the result, spreads the
18
+ * `domain` into its `xScale.domain` config, and renders the selection
19
+ * rect and reset button this returns.
20
+ *
21
+ * The X scale `.invert()` is read lazily from the chart's existing
22
+ * `internalChartRef.getScales()` at commit time, so no DataContext access
23
+ * is required from the parent.
24
+ *
25
+ * @param params - Hook params.
26
+ * @param params.enabled - When false, the hook becomes a passthrough.
27
+ * @param params.chartRef - Chart's internal scales ref.
28
+ * @param params.userHandlers - User-supplied pointer handlers to chain.
29
+ * @param params.userHandlers.onPointerDown - Forwarded user pointerdown handler.
30
+ * @param params.userHandlers.onPointerMove - Forwarded user pointermove handler.
31
+ * @param params.userHandlers.onPointerUp - Forwarded user pointerup handler.
32
+ * @return An object with `domain`, `drag`, `reset`, and chained `handlers`.
33
+ */
34
+ export function useXZoom< T extends Date | number = Date >( {
35
+ enabled,
36
+ chartRef,
37
+ userHandlers,
38
+ }: {
39
+ enabled: boolean;
40
+ chartRef: MutableRefObject< SingleChartRef | null >;
41
+ userHandlers?: {
42
+ onPointerDown?: PointerHandler;
43
+ onPointerMove?: PointerHandler;
44
+ onPointerUp?: PointerHandler;
45
+ };
46
+ } ) {
47
+ const [ domain, setDomain ] = useState< [ T, T ] | null >( null );
48
+ const [ drag, setDrag ] = useState< Drag | null >( null );
49
+
50
+ const reset = useCallback( () => setDomain( null ), [] );
51
+
52
+ const onPointerDown = useCallback< PointerHandler >(
53
+ params => {
54
+ userHandlers?.onPointerDown?.( params );
55
+ if ( ! enabled || ! params.svgPoint ) return;
56
+ setDrag( { a: params.svgPoint.x, b: params.svgPoint.x } );
57
+ },
58
+ [ enabled, userHandlers ]
59
+ );
60
+
61
+ const onPointerMove = useCallback< PointerHandler >(
62
+ params => {
63
+ userHandlers?.onPointerMove?.( params );
64
+ if ( ! enabled || ! params.svgPoint ) return;
65
+ setDrag( current => ( current ? { a: current.a, b: params.svgPoint!.x } : current ) );
66
+ },
67
+ [ enabled, userHandlers ]
68
+ );
69
+
70
+ const onPointerUp = useCallback< PointerHandler >(
71
+ params => {
72
+ userHandlers?.onPointerUp?.( params );
73
+ if ( ! enabled ) return;
74
+ const finalDrag = drag;
75
+ setDrag( null );
76
+ if ( ! finalDrag ) return;
77
+ const lo = Math.min( finalDrag.a, finalDrag.b );
78
+ const hi = Math.max( finalDrag.a, finalDrag.b );
79
+ if ( hi - lo < MIN_DRAG_PIXELS ) return;
80
+ const xScale = chartRef.current?.getScales()?.xScale as
81
+ | ( AxisScale & { invert?: ( v: number ) => T } )
82
+ | undefined;
83
+ if ( ! xScale || typeof xScale.invert !== 'function' ) return;
84
+ setDomain( [ xScale.invert( lo ), xScale.invert( hi ) ] );
85
+ },
86
+ [ enabled, drag, chartRef, userHandlers ]
87
+ );
88
+
89
+ return useMemo(
90
+ () => ( {
91
+ domain,
92
+ drag,
93
+ reset,
94
+ handlers: { onPointerDown, onPointerMove, onPointerUp },
95
+ } ),
96
+ [ domain, drag, reset, onPointerDown, onPointerMove, onPointerUp ]
97
+ );
98
+ }
99
+
100
+ /**
101
+ * Live selection rectangle drawn inside `<XYChart>` while the user is
102
+ * dragging. Reads plot dimensions from visx's `DataContext`.
103
+ *
104
+ * @param props - Props.
105
+ * @param props.drag - Current drag, or null when idle.
106
+ * @return JSX or null.
107
+ */
108
+ export function ZoomSelectionRect( { drag }: { drag: Drag | null } ) {
109
+ const { margin, innerHeight } = useContext( DataContext );
110
+ if ( ! drag || drag.a === drag.b ) return null;
111
+ const x = Math.min( drag.a, drag.b );
112
+ const w = Math.abs( drag.b - drag.a );
113
+ return (
114
+ <rect
115
+ className={ styles[ 'x-zoom__selection' ] }
116
+ x={ x }
117
+ y={ margin?.top ?? 0 }
118
+ width={ w }
119
+ height={ innerHeight ?? 0 }
120
+ data-testid="chart-zoom-selection"
121
+ />
122
+ );
123
+ }
124
+
125
+ /**
126
+ * Visible icon-only reset button rendered as an HTML overlay on top of
127
+ * the chart container. The host should wrap its SVG in a `position: relative`
128
+ * container so the button anchors correctly.
129
+ *
130
+ * @param props - Props.
131
+ * @param props.onClick - Click handler. Typically the `reset` from `useXZoom`.
132
+ * @return JSX element.
133
+ */
134
+ export function ZoomResetButton( { onClick }: { onClick: () => void } ) {
135
+ const label = __( 'Reset zoom', 'jetpack-charts' );
136
+ return (
137
+ <button
138
+ type="button"
139
+ className={ styles[ 'x-zoom__reset' ] }
140
+ onClick={ onClick }
141
+ aria-label={ label }
142
+ title={ label }
143
+ data-testid="chart-zoom-reset"
144
+ >
145
+ <svg
146
+ className={ styles[ 'x-zoom__reset-icon' ] }
147
+ viewBox="0 0 24 24"
148
+ fill="none"
149
+ stroke="currentColor"
150
+ strokeWidth="2"
151
+ strokeLinecap="round"
152
+ strokeLinejoin="round"
153
+ aria-hidden="true"
154
+ focusable="false"
155
+ >
156
+ <circle cx="10" cy="10" r="6" />
157
+ <line x1="15" y1="15" x2="20" y2="20" />
158
+ <line x1="7" y1="10" x2="13" y2="10" />
159
+ </svg>
160
+ </button>
161
+ );
162
+ }
package/src/types.ts CHANGED
@@ -362,7 +362,7 @@ export type ScaleOptions = {
362
362
  * an explicit `domain` to keep the tick values you set exactly.
363
363
  */
364
364
  nice?: boolean;
365
- domain?: [ number, number ];
365
+ domain?: [ number, number ] | [ Date, Date ];
366
366
  range?: [ number, number ];
367
367
  /**
368
368
  * For band scale, shortcut for setting `paddingInner` and `paddingOuter` to the same value.