@automattic/charts 1.4.3 → 1.5.1
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.
- package/CHANGELOG.md +15 -0
- package/SECURITY.md +0 -1
- package/dist/index.cjs +544 -195
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +38 -0
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +22 -1
- package/dist/index.d.ts +22 -1
- package/dist/index.js +839 -490
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/charts/area-chart/area-chart.tsx +39 -21
- package/src/charts/area-chart/test/area-chart.test.tsx +83 -3
- package/src/charts/area-chart/types.ts +15 -0
- package/src/charts/line-chart/line-chart.tsx +102 -84
- package/src/charts/line-chart/test/line-chart.test.tsx +41 -0
- package/src/charts/line-chart/types.ts +6 -0
- package/src/charts/private/test/x-zoom.test.tsx +142 -0
- package/src/charts/private/x-zoom.module.scss +45 -0
- package/src/charts/private/x-zoom.tsx +209 -0
- package/src/types.ts +1 -1
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { act, renderHook } from '@testing-library/react';
|
|
2
|
+
import { useRef } from 'react';
|
|
3
|
+
import { useXZoom } from '../x-zoom';
|
|
4
|
+
import type { SingleChartRef } from '../single-chart-context';
|
|
5
|
+
import type { EventHandlerParams } from '@visx/xychart';
|
|
6
|
+
|
|
7
|
+
// A fake scale that lets the hook convert pixel positions to data values.
|
|
8
|
+
// The hook only uses `.invert()`; everything else can be stubbed.
|
|
9
|
+
const fakeXScale = ( ( x: number ) => x * 2 ) as unknown as ( ( v: number ) => number ) & {
|
|
10
|
+
invert: ( v: number ) => number;
|
|
11
|
+
};
|
|
12
|
+
( fakeXScale as unknown as { invert: ( v: number ) => number } ).invert = v => v * 2;
|
|
13
|
+
|
|
14
|
+
const makeChartRef = (
|
|
15
|
+
scale: { invert?: ( v: number ) => unknown } | null = fakeXScale as unknown as {
|
|
16
|
+
invert: ( v: number ) => unknown;
|
|
17
|
+
}
|
|
18
|
+
) =>
|
|
19
|
+
( {
|
|
20
|
+
current: {
|
|
21
|
+
getScales: () => ( scale ? { xScale: scale, yScale: scale } : null ),
|
|
22
|
+
getChartDimensions: () => ( { width: 0, height: 0, margin: {} } ),
|
|
23
|
+
},
|
|
24
|
+
} ) as unknown as ReturnType< typeof useRef< SingleChartRef > >;
|
|
25
|
+
|
|
26
|
+
const makeParams = ( x: number ): EventHandlerParams< object > =>
|
|
27
|
+
( {
|
|
28
|
+
key: '',
|
|
29
|
+
index: 0,
|
|
30
|
+
datum: {},
|
|
31
|
+
event: new Event( 'pointerdown' ) as unknown as PointerEvent,
|
|
32
|
+
svgPoint: { x, y: 0 },
|
|
33
|
+
} ) as unknown as EventHandlerParams< object >;
|
|
34
|
+
|
|
35
|
+
describe( 'useXZoom', () => {
|
|
36
|
+
test( 'commits a domain on pointerup after dragging more than minDragPixels', () => {
|
|
37
|
+
const chartRef = makeChartRef();
|
|
38
|
+
const { result } = renderHook( () => useXZoom< number >( { enabled: true, chartRef } ) );
|
|
39
|
+
|
|
40
|
+
act( () => result.current.handlers.onPointerDown( makeParams( 100 ) ) );
|
|
41
|
+
act( () => result.current.handlers.onPointerMove( makeParams( 200 ) ) );
|
|
42
|
+
act( () => result.current.handlers.onPointerUp( makeParams( 200 ) ) );
|
|
43
|
+
|
|
44
|
+
// fakeXScale.invert(x) = x * 2.
|
|
45
|
+
expect( result.current.domain ).toEqual( [ 200, 400 ] );
|
|
46
|
+
expect( result.current.drag ).toBeNull();
|
|
47
|
+
} );
|
|
48
|
+
|
|
49
|
+
test( 'discards drags shorter than minDragPixels', () => {
|
|
50
|
+
const chartRef = makeChartRef();
|
|
51
|
+
const { result } = renderHook( () => useXZoom< number >( { enabled: true, chartRef } ) );
|
|
52
|
+
|
|
53
|
+
act( () => result.current.handlers.onPointerDown( makeParams( 100 ) ) );
|
|
54
|
+
act( () => result.current.handlers.onPointerMove( makeParams( 103 ) ) );
|
|
55
|
+
act( () => result.current.handlers.onPointerUp( makeParams( 103 ) ) );
|
|
56
|
+
|
|
57
|
+
expect( result.current.domain ).toBeNull();
|
|
58
|
+
expect( result.current.drag ).toBeNull();
|
|
59
|
+
} );
|
|
60
|
+
|
|
61
|
+
test( 'normalises right-to-left drags', () => {
|
|
62
|
+
const chartRef = makeChartRef();
|
|
63
|
+
const { result } = renderHook( () => useXZoom< number >( { enabled: true, chartRef } ) );
|
|
64
|
+
|
|
65
|
+
act( () => result.current.handlers.onPointerDown( makeParams( 300 ) ) );
|
|
66
|
+
act( () => result.current.handlers.onPointerMove( makeParams( 100 ) ) );
|
|
67
|
+
act( () => result.current.handlers.onPointerUp( makeParams( 100 ) ) );
|
|
68
|
+
|
|
69
|
+
expect( result.current.domain ).toEqual( [ 200, 600 ] );
|
|
70
|
+
} );
|
|
71
|
+
|
|
72
|
+
test( 'reset clears the committed domain', () => {
|
|
73
|
+
const chartRef = makeChartRef();
|
|
74
|
+
const { result } = renderHook( () => useXZoom< number >( { enabled: true, chartRef } ) );
|
|
75
|
+
|
|
76
|
+
act( () => result.current.handlers.onPointerDown( makeParams( 100 ) ) );
|
|
77
|
+
act( () => result.current.handlers.onPointerMove( makeParams( 200 ) ) );
|
|
78
|
+
act( () => result.current.handlers.onPointerUp( makeParams( 200 ) ) );
|
|
79
|
+
expect( result.current.domain ).not.toBeNull();
|
|
80
|
+
act( () => result.current.reset() );
|
|
81
|
+
expect( result.current.domain ).toBeNull();
|
|
82
|
+
} );
|
|
83
|
+
|
|
84
|
+
test( 'is a passthrough when disabled', () => {
|
|
85
|
+
const chartRef = makeChartRef();
|
|
86
|
+
const userOnPointerDown = jest.fn();
|
|
87
|
+
const { result } = renderHook( () =>
|
|
88
|
+
useXZoom< number >( {
|
|
89
|
+
enabled: false,
|
|
90
|
+
chartRef,
|
|
91
|
+
userHandlers: { onPointerDown: userOnPointerDown },
|
|
92
|
+
} )
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
act( () => result.current.handlers.onPointerDown( makeParams( 100 ) ) );
|
|
96
|
+
act( () => result.current.handlers.onPointerMove( makeParams( 200 ) ) );
|
|
97
|
+
act( () => result.current.handlers.onPointerUp( makeParams( 200 ) ) );
|
|
98
|
+
|
|
99
|
+
expect( userOnPointerDown ).toHaveBeenCalledTimes( 1 );
|
|
100
|
+
expect( result.current.domain ).toBeNull();
|
|
101
|
+
expect( result.current.drag ).toBeNull();
|
|
102
|
+
} );
|
|
103
|
+
|
|
104
|
+
test( 'forwards events to user handlers when zoom is enabled', () => {
|
|
105
|
+
const chartRef = makeChartRef();
|
|
106
|
+
const userOnPointerDown = jest.fn();
|
|
107
|
+
const userOnPointerMove = jest.fn();
|
|
108
|
+
const userOnPointerUp = jest.fn();
|
|
109
|
+
const { result } = renderHook( () =>
|
|
110
|
+
useXZoom< number >( {
|
|
111
|
+
enabled: true,
|
|
112
|
+
chartRef,
|
|
113
|
+
userHandlers: {
|
|
114
|
+
onPointerDown: userOnPointerDown,
|
|
115
|
+
onPointerMove: userOnPointerMove,
|
|
116
|
+
onPointerUp: userOnPointerUp,
|
|
117
|
+
},
|
|
118
|
+
} )
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
act( () => result.current.handlers.onPointerDown( makeParams( 100 ) ) );
|
|
122
|
+
act( () => result.current.handlers.onPointerMove( makeParams( 200 ) ) );
|
|
123
|
+
act( () => result.current.handlers.onPointerUp( makeParams( 200 ) ) );
|
|
124
|
+
|
|
125
|
+
expect( userOnPointerDown ).toHaveBeenCalledTimes( 1 );
|
|
126
|
+
expect( userOnPointerMove ).toHaveBeenCalledTimes( 1 );
|
|
127
|
+
expect( userOnPointerUp ).toHaveBeenCalledTimes( 1 );
|
|
128
|
+
} );
|
|
129
|
+
|
|
130
|
+
test( 'leaves domain null when the X scale has no invert function', () => {
|
|
131
|
+
const chartRef = makeChartRef( {
|
|
132
|
+
/* no invert */
|
|
133
|
+
} );
|
|
134
|
+
const { result } = renderHook( () => useXZoom< number >( { enabled: true, chartRef } ) );
|
|
135
|
+
|
|
136
|
+
act( () => result.current.handlers.onPointerDown( makeParams( 100 ) ) );
|
|
137
|
+
act( () => result.current.handlers.onPointerMove( makeParams( 200 ) ) );
|
|
138
|
+
act( () => result.current.handlers.onPointerUp( makeParams( 200 ) ) );
|
|
139
|
+
|
|
140
|
+
expect( result.current.domain ).toBeNull();
|
|
141
|
+
} );
|
|
142
|
+
} );
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
.x-zoom {
|
|
2
|
+
|
|
3
|
+
&__selection {
|
|
4
|
+
fill: var(--charts-zoom-selection-fill, rgba(56, 88, 233, 0.16));
|
|
5
|
+
stroke: var(--charts-zoom-selection-stroke, rgba(56, 88, 233, 0.65));
|
|
6
|
+
stroke-width: var(--wpds-border-width-xs, 1px);
|
|
7
|
+
pointer-events: none;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
&__reset {
|
|
11
|
+
position: absolute;
|
|
12
|
+
top: var(--wpds-dimension-gap-sm, 8px);
|
|
13
|
+
right: var(--wpds-dimension-gap-sm, 8px);
|
|
14
|
+
z-index: 2;
|
|
15
|
+
display: inline-flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
justify-content: center;
|
|
18
|
+
width: 28px;
|
|
19
|
+
height: 28px;
|
|
20
|
+
padding: 0;
|
|
21
|
+
background: var(--charts-zoom-reset-bg, rgba(255, 255, 255, 0.92));
|
|
22
|
+
color: var(--charts-zoom-reset-fg, #1e1e1e);
|
|
23
|
+
border:
|
|
24
|
+
var(--wpds-border-width-xs, 1px) solid
|
|
25
|
+
var(--charts-zoom-reset-border, rgba(0, 0, 0, 0.16));
|
|
26
|
+
border-radius: var(--wpds-border-radius-md, 4px);
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
|
|
29
|
+
|
|
30
|
+
&:hover {
|
|
31
|
+
background: var(--charts-zoom-reset-bg-hover, rgba(255, 255, 255, 1));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
&:focus-visible {
|
|
35
|
+
outline: 2px solid var(--charts-zoom-reset-focus, #3858e9);
|
|
36
|
+
outline-offset: 1px;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
&__reset-icon {
|
|
41
|
+
width: 16px;
|
|
42
|
+
height: 16px;
|
|
43
|
+
flex-shrink: 0;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
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, ReactNode } 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
|
+
* Wraps a chart's series in a group that is clipped to the inner plot rectangle
|
|
127
|
+
* while `active`. Reads the plot geometry from visx's `DataContext` (the same
|
|
128
|
+
* source as `ZoomSelectionRect`), so the host charts don't compute any margins.
|
|
129
|
+
* The group is always rendered (only its `clip-path` toggles) so toggling zoom
|
|
130
|
+
* never remounts or re-animates the series.
|
|
131
|
+
*
|
|
132
|
+
* @param props - Props.
|
|
133
|
+
* @param props.active - Whether to clip (e.g. `zoomable`, or `zoomable && zoomed`).
|
|
134
|
+
* @param props.chartId - Chart id; used to build a unique clip-path id.
|
|
135
|
+
* @param props.children - The series to clip.
|
|
136
|
+
* @return JSX element.
|
|
137
|
+
*/
|
|
138
|
+
export function ZoomClip( {
|
|
139
|
+
active,
|
|
140
|
+
chartId,
|
|
141
|
+
children,
|
|
142
|
+
}: {
|
|
143
|
+
active: boolean;
|
|
144
|
+
chartId?: string;
|
|
145
|
+
children: ReactNode;
|
|
146
|
+
} ) {
|
|
147
|
+
const { margin, innerWidth, innerHeight } = useContext( DataContext );
|
|
148
|
+
// Sanitise the chart id to a valid SVG/CSS id, and keep it unique per chart.
|
|
149
|
+
const id = `chart-zoom-clip-${ String( chartId ?? '' ).replace( /[^A-Za-z0-9_-]/g, '' ) }`;
|
|
150
|
+
const clip = active && ( innerWidth ?? 0 ) > 0 && ( innerHeight ?? 0 ) > 0;
|
|
151
|
+
return (
|
|
152
|
+
<>
|
|
153
|
+
{ clip && (
|
|
154
|
+
<defs>
|
|
155
|
+
<clipPath id={ id } data-testid="chart-zoom-clip">
|
|
156
|
+
<rect
|
|
157
|
+
x={ margin?.left ?? 0 }
|
|
158
|
+
y={ margin?.top ?? 0 }
|
|
159
|
+
width={ innerWidth }
|
|
160
|
+
height={ innerHeight }
|
|
161
|
+
/>
|
|
162
|
+
</clipPath>
|
|
163
|
+
</defs>
|
|
164
|
+
) }
|
|
165
|
+
<g clipPath={ clip ? `url(#${ id })` : undefined } data-testid="chart-series-clip-group">
|
|
166
|
+
{ children }
|
|
167
|
+
</g>
|
|
168
|
+
</>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Visible icon-only reset button rendered as an HTML overlay on top of
|
|
174
|
+
* the chart container. The host should wrap its SVG in a `position: relative`
|
|
175
|
+
* container so the button anchors correctly.
|
|
176
|
+
*
|
|
177
|
+
* @param props - Props.
|
|
178
|
+
* @param props.onClick - Click handler. Typically the `reset` from `useXZoom`.
|
|
179
|
+
* @return JSX element.
|
|
180
|
+
*/
|
|
181
|
+
export function ZoomResetButton( { onClick }: { onClick: () => void } ) {
|
|
182
|
+
const label = __( 'Reset zoom', 'jetpack-charts' );
|
|
183
|
+
return (
|
|
184
|
+
<button
|
|
185
|
+
type="button"
|
|
186
|
+
className={ styles[ 'x-zoom__reset' ] }
|
|
187
|
+
onClick={ onClick }
|
|
188
|
+
aria-label={ label }
|
|
189
|
+
title={ label }
|
|
190
|
+
data-testid="chart-zoom-reset"
|
|
191
|
+
>
|
|
192
|
+
<svg
|
|
193
|
+
className={ styles[ 'x-zoom__reset-icon' ] }
|
|
194
|
+
viewBox="0 0 24 24"
|
|
195
|
+
fill="none"
|
|
196
|
+
stroke="currentColor"
|
|
197
|
+
strokeWidth="2"
|
|
198
|
+
strokeLinecap="round"
|
|
199
|
+
strokeLinejoin="round"
|
|
200
|
+
aria-hidden="true"
|
|
201
|
+
focusable="false"
|
|
202
|
+
>
|
|
203
|
+
<circle cx="10" cy="10" r="6" />
|
|
204
|
+
<line x1="15" y1="15" x2="20" y2="20" />
|
|
205
|
+
<line x1="7" y1="10" x2="13" y2="10" />
|
|
206
|
+
</svg>
|
|
207
|
+
</button>
|
|
208
|
+
);
|
|
209
|
+
}
|
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.
|