@automattic/charts 4.14.0 → 4.15.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 +18 -0
- package/TOKENS.md +1 -1
- package/dist/index.cjs +649 -882
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +58 -1
- package/dist/index.d.cts +41 -7
- package/dist/index.d.ts +41 -7
- package/dist/index.js +650 -883
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
- package/src/charts/area-chart/area-chart.tsx +29 -20
- package/src/charts/area-chart/types.ts +3 -1
- package/src/charts/bar-chart/bar-chart.module.scss +38 -0
- package/src/charts/bar-chart/bar-chart.tsx +159 -56
- package/src/charts/bar-chart/private/band-tooltip.tsx +40 -5
- package/src/charts/bar-chart/private/bar-layer.tsx +66 -13
- package/src/charts/bar-chart/private/category.ts +25 -0
- package/src/charts/bar-chart/private/comparison-bars-geometry.ts +13 -1
- package/src/charts/bar-chart/private/index.ts +3 -1
- package/src/charts/bar-chart/private/selected-bar-ring.tsx +17 -6
- package/src/charts/bar-chart/private/stack-layout.ts +99 -0
- package/src/charts/bar-chart/private/stacked-bar-series.tsx +145 -0
- package/src/charts/bar-chart/private/test/stack-layout.test.ts +81 -0
- package/src/charts/bar-chart/private/test/value-domain.test.ts +10 -0
- package/src/charts/bar-chart/private/use-bar-chart-options.ts +35 -11
- package/src/charts/bar-chart/private/{use-overlap-slots.ts → use-bar-slots.ts} +36 -38
- package/src/charts/bar-chart/private/use-bar-stops.ts +19 -19
- package/src/charts/bar-chart/private/value-domain.ts +10 -8
- package/src/charts/bar-chart/test/bar-chart-stacked.test.tsx +546 -0
- package/src/charts/bar-chart/test/bar-chart.test.tsx +75 -0
- package/src/charts/bar-chart/test/use-bar-legend-items.test.tsx +18 -0
- package/src/charts/bar-chart/types.ts +3 -1
- package/src/charts/bar-chart/use-bar-colors.ts +12 -0
- package/src/charts/bar-chart/use-bar-legend-items.ts +4 -2
- package/src/charts/bar-list-chart/bar-list-chart.tsx +2 -0
- package/src/charts/leaderboard-chart/leaderboard-chart.module.scss +43 -1
- package/src/charts/leaderboard-chart/leaderboard-chart.tsx +25 -20
- package/src/charts/leaderboard-chart/test/leaderboard-chart.test.tsx +15 -20
- package/src/charts/line-chart/line-chart.tsx +55 -26
- package/src/charts/line-chart/test/line-chart.test.tsx +86 -0
- package/src/charts/line-chart/types.ts +3 -1
- package/src/charts/private/test/compact-when-short.test.tsx +170 -0
- package/src/charts/private/test/tick-decimals.test.tsx +95 -0
- package/src/charts/private/tick-decimals.tsx +132 -0
- package/src/charts/private/use-compact-when-short.ts +18 -0
- package/src/providers/chart-context/themes.ts +4 -0
- package/src/types.ts +35 -2
- package/src/charts/private/test/whole-number-ticks.test.tsx +0 -57
- package/src/charts/private/whole-number-ticks.tsx +0 -79
|
@@ -88,6 +88,16 @@ describe( 'getValueScaleDomain', () => {
|
|
|
88
88
|
expect( getValueScaleDomain( data, true, isSeriesRendered ) ).toEqual( [ 0, 25 ] );
|
|
89
89
|
} );
|
|
90
90
|
|
|
91
|
+
test.each( [
|
|
92
|
+
[ 'widens a varying series inside one step', [ 1.2, 1.8 ], [ 1, 2 ] ],
|
|
93
|
+
[ 'widens a flat series under one step', [ 0.4, 0.4 ], [ 0, 1 ] ],
|
|
94
|
+
[ 'leaves a varying series holding two steps to visx', [ 1.2, 3.4 ], null ],
|
|
95
|
+
] )( 'with tickDecimals and zero opted out, %s', ( _, values, expected ) => {
|
|
96
|
+
expect( getValueScaleDomain( [ series( values ) ], false, allRendered, 0 ) ).toEqual(
|
|
97
|
+
expected
|
|
98
|
+
);
|
|
99
|
+
} );
|
|
100
|
+
|
|
91
101
|
test( 'reads visualValue in preference to value', () => {
|
|
92
102
|
const data: SeriesData[] = [
|
|
93
103
|
{
|
|
@@ -6,8 +6,10 @@ import { useChartFormatting } from '../../../providers';
|
|
|
6
6
|
import { getBucketResolution } from '../../../utils/bucket-info';
|
|
7
7
|
import { createDateFormatter } from '../../../utils/date-formatting';
|
|
8
8
|
import { getBandTickValues, getFormatter } from '../../private/time-axis';
|
|
9
|
+
import { getStackedValueDomain } from './stack-layout';
|
|
9
10
|
import { TruncatedXTickComponent, TruncatedYTickComponent } from './truncated-tick-component';
|
|
10
11
|
import { getBarValue, getValueScaleDomain } from './value-domain';
|
|
12
|
+
import type { StackLayout } from './stack-layout';
|
|
11
13
|
import type {
|
|
12
14
|
DataPointDate,
|
|
13
15
|
BaseChartProps,
|
|
@@ -152,21 +154,24 @@ const getGroupPadding = ( scale: Record< string, unknown > ): number => {
|
|
|
152
154
|
* @param horizontal - Whether the chart is horizontal or vertical.
|
|
153
155
|
* @param options - The options for the chart.
|
|
154
156
|
* @param isSeriesRendered - Whether visx mounts a series, i.e. the legend shows it.
|
|
157
|
+
* @param stack - The precomputed stack, when series share one bar.
|
|
155
158
|
* @return The merged options for the chart.
|
|
156
159
|
*/
|
|
157
160
|
export function useBarChartOptions(
|
|
158
161
|
data: SeriesData[],
|
|
159
162
|
horizontal: boolean,
|
|
160
163
|
options: BaseChartProps[ 'options' ] = {},
|
|
161
|
-
isSeriesRendered: ( series: SeriesData ) => boolean = ALL_RENDERED
|
|
164
|
+
isSeriesRendered: ( series: SeriesData ) => boolean = ALL_RENDERED,
|
|
165
|
+
stack?: StackLayout
|
|
162
166
|
) {
|
|
163
167
|
// Callers reasonably pass an object literal, which is a fresh reference every
|
|
164
168
|
// render and would defeat every memo below.
|
|
165
169
|
const stableOptions = useDeepMemo( options );
|
|
166
170
|
const formatting = useChartFormatting();
|
|
171
|
+
const isStacked = !! stack;
|
|
167
172
|
|
|
168
|
-
// `labelOverflow` and `
|
|
169
|
-
// forwarded — visx has an axis prop for
|
|
173
|
+
// `labelOverflow`, `tickResolution` and `tickDecimals` are consumed by this hook rather than
|
|
174
|
+
// forwarded — visx has an axis prop for none of them — and `tickFormat` is merged
|
|
170
175
|
// with the derived default explicitly, so an `undefined` passed by a caller
|
|
171
176
|
// cannot clobber it through the spread. They are split off the caller's axis
|
|
172
177
|
// options once, here, and only the rest reaches visx below.
|
|
@@ -175,14 +180,22 @@ export function useBarChartOptions(
|
|
|
175
180
|
labelOverflow: xLabelOverflow,
|
|
176
181
|
tickResolution: xTickResolution,
|
|
177
182
|
tickFormat: xTickFormat,
|
|
183
|
+
tickDecimals: xTickDecimals,
|
|
178
184
|
...xAxisOptions
|
|
179
185
|
} = stableOptions.axis?.x || {};
|
|
180
186
|
const {
|
|
181
187
|
labelOverflow: yLabelOverflow,
|
|
182
188
|
tickResolution: yTickResolution,
|
|
183
189
|
tickFormat: yTickFormat,
|
|
190
|
+
tickDecimals: yTickDecimals,
|
|
184
191
|
...yAxisOptions
|
|
185
192
|
} = stableOptions.axis?.y || {};
|
|
193
|
+
const [ valueAxisOptions, valueTickDecimals ] = horizontal
|
|
194
|
+
? [ xAxisOptions, xTickDecimals ]
|
|
195
|
+
: [ yAxisOptions, yTickDecimals ];
|
|
196
|
+
const valueScaleType = isStacked
|
|
197
|
+
? 'linear'
|
|
198
|
+
: ( horizontal ? stableOptions.xScale : stableOptions.yScale )?.type;
|
|
186
199
|
|
|
187
200
|
return {
|
|
188
201
|
xLabelOverflow,
|
|
@@ -194,8 +207,11 @@ export function useBarChartOptions(
|
|
|
194
207
|
// The dates sit on the x axis normally, and on the y axis when the
|
|
195
208
|
// chart is horizontal, so the hint follows them.
|
|
196
209
|
tickResolution: horizontal ? yTickResolution : xTickResolution,
|
|
210
|
+
// A caller's own `tickValues` or a log scale turn `tickDecimals` off, domain widening included.
|
|
211
|
+
valueTickDecimals:
|
|
212
|
+
valueAxisOptions.tickValues || valueScaleType === 'log' ? undefined : valueTickDecimals,
|
|
197
213
|
};
|
|
198
|
-
}, [ stableOptions, horizontal ] );
|
|
214
|
+
}, [ stableOptions, horizontal, isStacked ] );
|
|
199
215
|
const { tickResolution } = axisConfig;
|
|
200
216
|
|
|
201
217
|
const defaultOptions = useMemo( () => {
|
|
@@ -270,20 +286,27 @@ export function useBarChartOptions(
|
|
|
270
286
|
const hasComparisonSeries = data.some( s => s.options?.type === 'comparison' );
|
|
271
287
|
// Comparison shadows share the primary bars' scale, so they force zero even when the caller opted out.
|
|
272
288
|
const includeZero = hasComparisonSeries || valueScaleOptions?.zero !== false;
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
289
|
+
let domain: [ number, number ] | null = null;
|
|
290
|
+
if ( ! valueScaleOptions?.domain ) {
|
|
291
|
+
domain = stack
|
|
292
|
+
? getStackedValueDomain( stack, axisConfig.valueTickDecimals )
|
|
293
|
+
: getValueScaleDomain( data, includeZero, isSeriesRendered, axisConfig.valueTickDecimals );
|
|
294
|
+
}
|
|
295
|
+
// Every stack starts at zero, which a log scale cannot place.
|
|
296
|
+
const valueScaleOverride: { domain?: [ number, number ]; type?: 'linear' } = {
|
|
297
|
+
...( domain && { domain } ),
|
|
298
|
+
...( stack && { type: 'linear' as const } ),
|
|
299
|
+
};
|
|
277
300
|
|
|
278
301
|
const xScale = {
|
|
279
302
|
...baseXScale,
|
|
280
303
|
...( stableOptions.xScale || {} ),
|
|
281
|
-
...( horizontal ?
|
|
304
|
+
...( horizontal ? valueScaleOverride : {} ),
|
|
282
305
|
};
|
|
283
306
|
const yScale = {
|
|
284
307
|
...baseYScale,
|
|
285
308
|
...( stableOptions.yScale || {} ),
|
|
286
|
-
...( ! horizontal ?
|
|
309
|
+
...( ! horizontal ? valueScaleOverride : {} ),
|
|
287
310
|
};
|
|
288
311
|
const { xLabelOverflow, yLabelOverflow, xTickFormat, yTickFormat, xAxisOptions, yAxisOptions } =
|
|
289
312
|
axisConfig;
|
|
@@ -339,6 +362,7 @@ export function useBarChartOptions(
|
|
|
339
362
|
tooltip: {
|
|
340
363
|
labelFormatter: dateAxisTickFormat || defaultTooltipLabelFormatter,
|
|
341
364
|
},
|
|
365
|
+
valueTickDecimals: axisConfig.valueTickDecimals,
|
|
342
366
|
};
|
|
343
|
-
}, [ defaultOptions, axisConfig, stableOptions, horizontal, data, isSeriesRendered ] );
|
|
367
|
+
}, [ defaultOptions, axisConfig, stableOptions, horizontal, data, isSeriesRendered, stack ] );
|
|
344
368
|
}
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
|
+
import { categoryKey, isSameCategory } from './category';
|
|
2
3
|
import { COMPARISON_SLOT, PRIMARY_SLOT, getOverlapPlacements } from './overlap-bars';
|
|
4
|
+
import { getStackLayout } from './stack-layout';
|
|
3
5
|
import { isBarRendered } from './value-domain';
|
|
4
6
|
import type { SelectedBar } from './selected-bar-ring';
|
|
5
7
|
import type { SlotMember } from './slot-tooltip-members';
|
|
6
|
-
import type {
|
|
8
|
+
import type { StackLayout } from './stack-layout';
|
|
9
|
+
import type { BarChartLayout, DataPointDate, SeriesData } from '../../../types';
|
|
7
10
|
|
|
8
11
|
type SeriesEntry = { series: SeriesData; index: number };
|
|
9
12
|
|
|
10
|
-
export type
|
|
11
|
-
/** The visible series, back to front. */
|
|
13
|
+
export type BarSlots = {
|
|
14
|
+
/** The visible series, back to front; bottom to top in a stack. */
|
|
12
15
|
entries: SeriesEntry[];
|
|
13
16
|
keys: string[];
|
|
14
17
|
/** The slots of a category, in the order the band lays them out. */
|
|
@@ -17,7 +20,7 @@ export type OverlapSlots = {
|
|
|
17
20
|
thicknessOf: ( key: string ) => number;
|
|
18
21
|
/** The keyboard stops: every category a slot draws a bar at, category by category. */
|
|
19
22
|
positions: StopPosition[];
|
|
20
|
-
};
|
|
23
|
+
} & ( { kind: 'overlap' } | { kind: 'stacked'; stack: StackLayout } );
|
|
21
24
|
|
|
22
25
|
/** A slot at one category, named by its front-most bar there. */
|
|
23
26
|
export type StopPosition = {
|
|
@@ -30,36 +33,39 @@ export type StopPosition = {
|
|
|
30
33
|
export type SelectedSlot = SelectedBar & { members: SlotMember[] };
|
|
31
34
|
|
|
32
35
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* @param point - A data point.
|
|
36
|
-
* @param category - The data point naming the category.
|
|
37
|
-
* @return Whether `point` is at `category`.
|
|
38
|
-
*/
|
|
39
|
-
export function isSameCategory( point: DataPointDate, category: DataPointDate ): boolean {
|
|
40
|
-
// An empty label falls back to the date, as the band axis's accessor does.
|
|
41
|
-
return category.label
|
|
42
|
-
? point.label === category.label
|
|
43
|
-
: !! category.date && !! point.date && point.date.getTime() === category.date.getTime();
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Lays out the visible series of an overlap layout in slots and layers.
|
|
36
|
+
* Lays out the visible series in slots: overlap layers primaries and comparisons in two, stacked puts every primary in one.
|
|
48
37
|
*
|
|
49
|
-
* @param
|
|
38
|
+
* @param layout - How the chart places its series in a category.
|
|
50
39
|
* @param seriesWithVisibility - Every series, with its index and whether it is visible.
|
|
51
|
-
* @return The
|
|
40
|
+
* @return The slots, or undefined for a grouped layout.
|
|
52
41
|
*/
|
|
53
|
-
export function
|
|
54
|
-
|
|
42
|
+
export function useBarSlots(
|
|
43
|
+
layout: BarChartLayout,
|
|
55
44
|
seriesWithVisibility: ( SeriesEntry & { isVisible: boolean } )[]
|
|
56
|
-
):
|
|
45
|
+
): BarSlots | undefined {
|
|
57
46
|
return useMemo( () => {
|
|
58
|
-
if (
|
|
47
|
+
if ( layout === 'grouped' ) {
|
|
59
48
|
return undefined;
|
|
60
49
|
}
|
|
61
50
|
|
|
62
51
|
const visible = seriesWithVisibility.filter( ( { isVisible } ) => isVisible );
|
|
52
|
+
|
|
53
|
+
if ( layout === 'stacked' ) {
|
|
54
|
+
// Bottom to top, so the top segment names each keyboard stop.
|
|
55
|
+
const entries = visible.map( ( { series, index } ) => ( { series, index } ) );
|
|
56
|
+
const slotKeys = [ PRIMARY_SLOT ];
|
|
57
|
+
const slotOf = () => PRIMARY_SLOT;
|
|
58
|
+
return {
|
|
59
|
+
kind: 'stacked',
|
|
60
|
+
stack: getStackLayout( entries.map( ( { series } ) => series ) ),
|
|
61
|
+
entries,
|
|
62
|
+
keys: entries.map( ( { series } ) => series.label ),
|
|
63
|
+
slotKeys,
|
|
64
|
+
slotOf,
|
|
65
|
+
thicknessOf: () => 1,
|
|
66
|
+
positions: getStopPositions( entries, slotKeys, slotOf ),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
63
69
|
const placements = getOverlapPlacements( visible.map( ( { series } ) => series ) );
|
|
64
70
|
const placed = visible
|
|
65
71
|
.map( ( { series, index }, position ) => ( {
|
|
@@ -85,6 +91,7 @@ export function useOverlapSlots(
|
|
|
85
91
|
];
|
|
86
92
|
|
|
87
93
|
return {
|
|
94
|
+
kind: 'overlap',
|
|
88
95
|
entries,
|
|
89
96
|
keys: entries.map( ( { series } ) => series.label ),
|
|
90
97
|
slotKeys,
|
|
@@ -92,17 +99,7 @@ export function useOverlapSlots(
|
|
|
92
99
|
thicknessOf,
|
|
93
100
|
positions: getStopPositions( entries, slotKeys, slotOf ),
|
|
94
101
|
};
|
|
95
|
-
}, [
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* The category a data point sits at, as a key two points share when `isSameCategory` holds.
|
|
100
|
-
*
|
|
101
|
-
* @param point - A data point.
|
|
102
|
-
* @return The category key.
|
|
103
|
-
*/
|
|
104
|
-
function categoryKey( point: DataPointDate ): string {
|
|
105
|
-
return point.label ? `label:${ point.label }` : `date:${ point.date?.getTime() }`;
|
|
102
|
+
}, [ layout, seriesWithVisibility ] );
|
|
106
103
|
}
|
|
107
104
|
|
|
108
105
|
/**
|
|
@@ -184,12 +181,12 @@ export function membersAt(
|
|
|
184
181
|
/**
|
|
185
182
|
* The slot a keyboard index selects, and every bar at its category.
|
|
186
183
|
*
|
|
187
|
-
* @param slots - The overlap
|
|
184
|
+
* @param slots - The overlap or stacked slots.
|
|
188
185
|
* @param selectedIndex - The keyboard index into `slots.positions`.
|
|
189
186
|
* @return The selected slot, or undefined when nothing is selected or drawn there.
|
|
190
187
|
*/
|
|
191
188
|
export function getSelectedSlot(
|
|
192
|
-
slots:
|
|
189
|
+
slots: BarSlots | undefined,
|
|
193
190
|
selectedIndex: number | undefined
|
|
194
191
|
): SelectedSlot | undefined {
|
|
195
192
|
const position = selectedIndex === undefined ? undefined : slots?.positions[ selectedIndex ];
|
|
@@ -209,5 +206,6 @@ export function getSelectedSlot(
|
|
|
209
206
|
slotKey,
|
|
210
207
|
thickness: Math.max( ...slotBars.map( ( { key } ) => slots.thicknessOf( key ) ) ),
|
|
211
208
|
members,
|
|
209
|
+
...( slots.kind === 'stacked' && { valueExtent: slots.stack.extentAt( category ) } ),
|
|
212
210
|
};
|
|
213
211
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useCallback, useMemo } from 'react';
|
|
2
|
-
import { isSameCategory } from './
|
|
3
|
-
import type {
|
|
2
|
+
import { isSameCategory } from './category';
|
|
3
|
+
import type { BarSlots } from './use-bar-slots';
|
|
4
4
|
import type { DataPointDate, SeriesData } from '../../../types';
|
|
5
5
|
|
|
6
6
|
type SeriesEntry = { series: SeriesData; index: number };
|
|
@@ -17,34 +17,34 @@ export type BarStops = {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* Lays out the keyboard stops: one per slot and category in
|
|
20
|
+
* Lays out the keyboard stops: one per slot and category in a slotted layout, else one per primary bar, series-major within each category.
|
|
21
21
|
*
|
|
22
|
-
* @param
|
|
22
|
+
* @param barSlots - The overlap or stacked slots, or undefined for a grouped layout.
|
|
23
23
|
* @param primaryEntries - The visible primary series.
|
|
24
24
|
* @return The stops.
|
|
25
25
|
*/
|
|
26
26
|
export function useBarStops(
|
|
27
|
-
|
|
27
|
+
barSlots: BarSlots | undefined,
|
|
28
28
|
primaryEntries: SeriesEntry[]
|
|
29
29
|
): BarStops {
|
|
30
30
|
const series = useMemo(
|
|
31
|
-
() => (
|
|
32
|
-
[
|
|
31
|
+
() => ( barSlots ? [] : primaryEntries.map( entry => entry.series ) ),
|
|
32
|
+
[ barSlots, primaryEntries ]
|
|
33
33
|
);
|
|
34
34
|
|
|
35
35
|
const count =
|
|
36
|
-
|
|
36
|
+
barSlots?.positions.length ??
|
|
37
37
|
Math.max( 0, ...series.map( item => item.data.length ) ) * series.length;
|
|
38
38
|
|
|
39
39
|
const key = useMemo(
|
|
40
|
-
() => JSON.stringify(
|
|
41
|
-
[
|
|
40
|
+
() => JSON.stringify( barSlots?.keys ?? series.map( item => item.label ) ),
|
|
41
|
+
[ barSlots, series ]
|
|
42
42
|
);
|
|
43
43
|
|
|
44
44
|
const barAt = useCallback(
|
|
45
45
|
( stop: number ): StopBar | undefined => {
|
|
46
|
-
if (
|
|
47
|
-
const position =
|
|
46
|
+
if ( barSlots ) {
|
|
47
|
+
const position = barSlots.positions[ stop ];
|
|
48
48
|
return (
|
|
49
49
|
position && {
|
|
50
50
|
datum: position.datum,
|
|
@@ -61,17 +61,17 @@ export function useBarStops(
|
|
|
61
61
|
const datum = item?.data[ index ] as DataPointDate | undefined;
|
|
62
62
|
return item && datum ? { datum, index, key: item.label } : undefined;
|
|
63
63
|
},
|
|
64
|
-
[
|
|
64
|
+
[ barSlots, series ]
|
|
65
65
|
);
|
|
66
66
|
|
|
67
67
|
const stopOf = useCallback(
|
|
68
68
|
( seriesKey: string, dataPointIndex: number ): number | undefined => {
|
|
69
|
-
if (
|
|
70
|
-
const hovered =
|
|
71
|
-
|
|
72
|
-
const slotKey =
|
|
69
|
+
if ( barSlots ) {
|
|
70
|
+
const hovered = barSlots.entries.find( entry => entry.series.label === seriesKey )?.series
|
|
71
|
+
.data[ dataPointIndex ] as DataPointDate | undefined;
|
|
72
|
+
const slotKey = barSlots.slotOf( seriesKey );
|
|
73
73
|
const stop = hovered
|
|
74
|
-
?
|
|
74
|
+
? barSlots.positions.findIndex(
|
|
75
75
|
position => position.slotKey === slotKey && isSameCategory( position.datum, hovered )
|
|
76
76
|
)
|
|
77
77
|
: -1;
|
|
@@ -80,7 +80,7 @@ export function useBarStops(
|
|
|
80
80
|
const seriesIndex = series.findIndex( item => item.label === seriesKey );
|
|
81
81
|
return seriesIndex >= 0 ? dataPointIndex * series.length + seriesIndex : undefined;
|
|
82
82
|
},
|
|
83
|
-
[
|
|
83
|
+
[ barSlots, series ]
|
|
84
84
|
);
|
|
85
85
|
|
|
86
86
|
return { count, key, barAt, stopOf };
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { getTickDecimalsDomain } from '../../private/tick-decimals';
|
|
1
2
|
import type { EnhancedDataPoint } from '../../../hooks/use-zero-value-display';
|
|
2
3
|
import type { DataPoint, DataPointDate, SeriesData } from '../../../types';
|
|
3
4
|
|
|
4
5
|
/** Domain for a value axis that has nothing to scale, so its ticks still read 0 to 1. */
|
|
5
|
-
const EMPTY_DOMAIN: [ number, number ] = [ 0, 1 ];
|
|
6
|
+
export const EMPTY_DOMAIN: [ number, number ] = [ 0, 1 ];
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* The value a bar is drawn at, preferring `visualValue` so a zero still shows a sliver.
|
|
@@ -37,12 +38,14 @@ export const isBarRendered = ( point: DataPoint | DataPointDate | EnhancedDataPo
|
|
|
37
38
|
* @param data - Every series handed to the chart.
|
|
38
39
|
* @param includeZero - Whether the domain must span zero.
|
|
39
40
|
* @param isSeriesRendered - Whether visx mounts a series, i.e. the legend shows it.
|
|
41
|
+
* @param tickDecimals - The value axis's `tickDecimals`.
|
|
40
42
|
* @return Value domain, or null when visx should fit its own.
|
|
41
43
|
*/
|
|
42
44
|
export const getValueScaleDomain = (
|
|
43
45
|
data: SeriesData[],
|
|
44
46
|
includeZero: boolean,
|
|
45
|
-
isSeriesRendered: ( series: SeriesData ) => boolean
|
|
47
|
+
isSeriesRendered: ( series: SeriesData ) => boolean,
|
|
48
|
+
tickDecimals?: number
|
|
46
49
|
): [ number, number ] | null => {
|
|
47
50
|
let min = Infinity;
|
|
48
51
|
let max = -Infinity;
|
|
@@ -67,14 +70,13 @@ export const getValueScaleDomain = (
|
|
|
67
70
|
return [ ...EMPTY_DOMAIN ];
|
|
68
71
|
}
|
|
69
72
|
|
|
73
|
+
let domain: [ number, number ] | null = null;
|
|
70
74
|
// A flat series fitted by visx collapses to zero height, so it is anchored at zero regardless.
|
|
71
75
|
if ( min === max ) {
|
|
72
|
-
|
|
76
|
+
domain = min === 0 ? [ ...EMPTY_DOMAIN ] : [ Math.min( 0, min ), Math.max( 0, max ) ];
|
|
77
|
+
} else if ( includeZero ) {
|
|
78
|
+
domain = [ Math.min( 0, min ), Math.max( 0, max ) ];
|
|
73
79
|
}
|
|
74
80
|
|
|
75
|
-
|
|
76
|
-
return [ Math.min( 0, min ), Math.max( 0, max ) ];
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return null;
|
|
81
|
+
return getTickDecimalsDomain( domain ?? [ min, max ], tickDecimals ) ?? domain;
|
|
80
82
|
};
|