@automattic/charts 4.1.0 → 4.2.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.
- package/CHANGELOG.md +28 -0
- package/TOKENS.md +7 -5
- package/dist/index.cjs +1115 -471
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +39 -1
- package/dist/index.d.cts +116 -16
- package/dist/index.d.ts +116 -16
- package/dist/index.js +1115 -473
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
- package/src/charts/area-chart/area-chart.tsx +2 -1
- package/src/charts/area-chart/private/overlays.tsx +1 -2
- package/src/charts/area-chart/test/area-chart.test.tsx +31 -0
- package/src/charts/bar-chart/bar-chart.tsx +67 -17
- package/src/charts/bar-chart/index.ts +1 -1
- package/src/charts/bar-chart/private/band-highlight.tsx +85 -0
- package/src/charts/bar-chart/private/band-scale.ts +13 -0
- package/src/charts/bar-chart/private/band-tooltip.tsx +138 -0
- package/src/charts/bar-chart/private/comparison-bars.tsx +5 -10
- package/src/charts/bar-chart/private/index.ts +2 -0
- package/src/charts/bar-chart/private/test/band-highlight.test.tsx +109 -0
- package/src/charts/bar-chart/private/test/band-tooltip.test.ts +26 -0
- package/src/charts/bar-chart/private/truncated-tick-component.tsx +1 -1
- package/src/charts/bar-chart/private/use-bar-chart-options.ts +1 -1
- package/src/charts/bar-chart/test/bar-chart-pointer-events.test.tsx +11 -23
- package/src/charts/bar-chart/test/bar-chart.test.tsx +565 -1
- package/src/charts/bar-list-chart/bar-list-chart.tsx +5 -3
- package/src/charts/conversion-funnel-chart/conversion-funnel-chart.tsx +1 -1
- package/src/charts/conversion-funnel-chart/types.ts +4 -2
- package/src/charts/geo-chart/types.ts +4 -2
- package/src/charts/heatmap-chart/{private/build-calendar-data.ts → build-calendar-data.ts} +15 -45
- package/src/charts/heatmap-chart/build-month-calendar-data.ts +124 -0
- package/src/charts/heatmap-chart/heatmap-chart.module.scss +32 -0
- package/src/charts/heatmap-chart/heatmap-chart.tsx +191 -114
- package/src/charts/heatmap-chart/index.ts +15 -3
- package/src/charts/heatmap-chart/private/civil-day.ts +26 -0
- package/src/charts/heatmap-chart/private/column-groups.ts +71 -0
- package/src/charts/heatmap-chart/private/heatmap-legend.tsx +19 -3
- package/src/charts/heatmap-chart/private/index.ts +2 -2
- package/src/charts/heatmap-chart/private/keyboard-navigation.ts +201 -0
- package/src/charts/heatmap-chart/test/build-calendar-data.test.ts +37 -1
- package/src/charts/heatmap-chart/test/build-month-calendar-data.test.ts +194 -0
- package/src/charts/heatmap-chart/test/column-groups.test.ts +83 -0
- package/src/charts/heatmap-chart/test/heatmap-chart.test.tsx +428 -2
- package/src/charts/heatmap-chart/test/keyboard-navigation.test.ts +25 -0
- package/src/charts/heatmap-chart/test/use-calendar-heatmap-data.test.tsx +1 -1
- package/src/charts/heatmap-chart/test/use-month-calendar-heatmap-data.test.tsx +90 -0
- package/src/charts/heatmap-chart/test/zoned-calendar-data.test.ts +1 -1
- package/src/charts/heatmap-chart/test/zoned-month-calendar-data.test.ts +23 -0
- package/src/charts/heatmap-chart/types.ts +88 -3
- package/src/charts/heatmap-chart/use-calendar-heatmap-data.ts +2 -2
- package/src/charts/heatmap-chart/use-month-calendar-heatmap-data.ts +34 -0
- package/src/charts/leaderboard-chart/types.ts +13 -14
- package/src/charts/line-chart/line-chart.tsx +3 -2
- package/src/charts/line-chart/private/line-chart-annotation.tsx +1 -1
- package/src/charts/line-chart/test/line-chart.test.tsx +31 -0
- package/src/charts/line-chart/types.ts +2 -2
- package/src/charts/private/time-axis-options.ts +1 -1
- package/src/charts/private/with-responsive/with-responsive.tsx +2 -2
- package/src/charts/private/x-zoom/test/x-zoom.test.tsx +1 -3
- package/src/charts/private/x-zoom/x-zoom.tsx +1 -2
- package/src/components/legend/private/base-legend.tsx +1 -1
- package/src/components/tooltip/accessible-tooltip.tsx +146 -37
- package/src/components/tooltip/base-tooltip.module.scss +6 -1
- package/src/components/tooltip/private/bounded-tooltip.tsx +12 -6
- package/src/components/tooltip/test/accessible-tooltip.test.tsx +160 -0
- package/src/components/tooltip/test/bounded-tooltip.test.tsx +17 -0
- package/src/components/tooltip/test/xy-chart-tooltip.test.tsx +29 -3
- package/src/components/tooltip/xy-chart-tooltip.tsx +11 -11
- package/src/hooks/test/use-element-size.test.tsx +13 -13
- package/src/hooks/use-xychart-theme.ts +1 -1
- package/src/index.ts +7 -1
- package/src/providers/chart-context/themes.ts +1 -0
- package/src/styles/chart-scope.scss +11 -3
- package/src/styles/test/chart-scope.test.ts +36 -2
- package/src/types.ts +6 -13
- package/src/utils/format-metric-value.ts +4 -4
- package/src/utils/get-edge-tick-widths.ts +1 -1
- package/src/utils/get-styles.ts +1 -1
- package/src/visx/types.ts +7 -10
|
@@ -1,47 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { warnOnce } from '
|
|
1
|
+
import { sanitizeFormatting } from '../../utils/date-formatting';
|
|
2
|
+
import { warnOnce } from '../../utils/warn-once';
|
|
3
3
|
import {
|
|
4
4
|
addCivilDays,
|
|
5
5
|
civilDate,
|
|
6
6
|
civilKey,
|
|
7
|
+
civilLabelFormatters,
|
|
7
8
|
civilWeekSpan,
|
|
8
9
|
instantDayReader,
|
|
9
10
|
pointDayKey,
|
|
10
11
|
startOfCivilWeek,
|
|
11
12
|
writtenDayKey,
|
|
12
|
-
} from './civil-day';
|
|
13
|
-
import type {
|
|
14
|
-
import type {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export type CalendarHeatmapOptions = {
|
|
23
|
-
weekStartsOn?: 0 | 1;
|
|
24
|
-
/**
|
|
25
|
-
* Mark the days completing the first and last week outside the series' span as
|
|
26
|
-
* hidden cells rather than blank ones, giving the calendar ragged edges. Days
|
|
27
|
-
* inside the span stay blank even where the series has no entry.
|
|
28
|
-
*/
|
|
29
|
-
hideOutOfRangeDays?: boolean;
|
|
30
|
-
/**
|
|
31
|
-
* Draw the grid over this span (`yyyy-MM-dd` bounds) instead of the series' own.
|
|
32
|
-
* Days inside the grid but outside the series become placeholder cells: painted,
|
|
33
|
-
* but reporting nothing, since they were never measured. A start bound opens from
|
|
34
|
-
* the beginning of its week, and bounds narrower than the series are ignored.
|
|
35
|
-
*/
|
|
36
|
-
gridSpan?: { start?: string; end?: string };
|
|
37
|
-
/** BCP-47 tag the labels are written in. Defaults to the runtime's locale. */
|
|
38
|
-
locale?: string;
|
|
39
|
-
/**
|
|
40
|
-
* IANA zone the series' instants are bucketed into days in. Defaults to the
|
|
41
|
-
* runtime's zone. A `dateString` carrying no offset is taken as written.
|
|
42
|
-
*/
|
|
43
|
-
timeZone?: string;
|
|
44
|
-
};
|
|
13
|
+
} from './private/civil-day';
|
|
14
|
+
import type { CivilDate, DayKey } from './private/civil-day';
|
|
15
|
+
import type {
|
|
16
|
+
CalendarHeatmapOptions,
|
|
17
|
+
CalendarHeatmapResult,
|
|
18
|
+
HeatmapCell,
|
|
19
|
+
HeatmapColumn,
|
|
20
|
+
} from './types';
|
|
21
|
+
import type { DataPointDate } from '../../types';
|
|
45
22
|
|
|
46
23
|
/** Rows that get a weekday label (Mon, Wed, Fri with a Monday week start). */
|
|
47
24
|
const LABELLED_ROWS = [ 0, 2, 4 ];
|
|
@@ -114,7 +91,7 @@ export const buildCalendarHeatmapData = (
|
|
|
114
91
|
offending
|
|
115
92
|
? `${ JSON.stringify(
|
|
116
93
|
offending
|
|
117
|
-
|
|
94
|
+
) } is not a day this can read, so its point is left out of the calendar. A \`dateString\` must start \`yyyy-MM-dd\`.`
|
|
118
95
|
: 'A point carries neither `date` nor `dateString`, so it is left out of the calendar.'
|
|
119
96
|
);
|
|
120
97
|
continue;
|
|
@@ -142,15 +119,8 @@ export const buildCalendarHeatmapData = (
|
|
|
142
119
|
const requestedMinDate = civilDate( requestedMinDayKey ) as CivilDate;
|
|
143
120
|
const gridMaxDate = civilDate( gridMaxDayKey ) as CivilDate;
|
|
144
121
|
|
|
145
|
-
// The
|
|
146
|
-
|
|
147
|
-
const labelFormatting = { locale, timeZone: 'UTC' };
|
|
148
|
-
const formatWeekday = createDateFormatter( { weekday: 'short' }, labelFormatting );
|
|
149
|
-
const formatMonth = createDateFormatter( { month: 'short' }, labelFormatting );
|
|
150
|
-
const formatDay = createDateFormatter(
|
|
151
|
-
{ weekday: 'short', month: 'short', day: 'numeric', year: 'numeric' },
|
|
152
|
-
labelFormatting
|
|
153
|
-
);
|
|
122
|
+
// The host's zone was already spent on bucketing; labels read the UTC proxies.
|
|
123
|
+
const { formatWeekday, formatMonth, formatDay } = civilLabelFormatters( locale );
|
|
154
124
|
|
|
155
125
|
const gridStart = startOfCivilWeek( requestedMinDate, weekStartsOn );
|
|
156
126
|
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { sanitizeFormatting } from '../../utils/date-formatting';
|
|
2
|
+
import { warnOnce } from '../../utils/warn-once';
|
|
3
|
+
import {
|
|
4
|
+
addCivilDays,
|
|
5
|
+
civilDate,
|
|
6
|
+
civilKey,
|
|
7
|
+
civilLabelFormatters,
|
|
8
|
+
startOfCivilWeek,
|
|
9
|
+
} from './private/civil-day';
|
|
10
|
+
import { isPresent } from './private/use-heatmap-colors';
|
|
11
|
+
import type { CivilDate, DayKey } from './private/civil-day';
|
|
12
|
+
import type {
|
|
13
|
+
HeatmapCell,
|
|
14
|
+
HeatmapColumn,
|
|
15
|
+
HeatmapColumnGroup,
|
|
16
|
+
MonthCalendarHeatmapOptions,
|
|
17
|
+
MonthCalendarHeatmapRange,
|
|
18
|
+
MonthCalendarHeatmapResult,
|
|
19
|
+
} from './types';
|
|
20
|
+
|
|
21
|
+
/** Week rows per month, fixed so every month block is the same height. */
|
|
22
|
+
const MONTH_ROWS = 6;
|
|
23
|
+
|
|
24
|
+
const DAYS_PER_WEEK = 7;
|
|
25
|
+
|
|
26
|
+
const startOfCivilMonth = ( date: CivilDate ): CivilDate => {
|
|
27
|
+
const first = new Date( date.getTime() ) as CivilDate;
|
|
28
|
+
first.setUTCDate( 1 );
|
|
29
|
+
return first;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const startOfNextCivilMonth = ( date: CivilDate ): CivilDate => {
|
|
33
|
+
const next = startOfCivilMonth( date );
|
|
34
|
+
next.setUTCMonth( next.getUTCMonth() + 1 );
|
|
35
|
+
return next;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Lay a day-keyed map out as one month calendar per month, weekdays across and
|
|
40
|
+
* weeks down, for `HeatmapChart` with `columnGroups`.
|
|
41
|
+
*
|
|
42
|
+
* @param valueByDay - Value per `yyyy-MM-dd`. A missing day is `null`.
|
|
43
|
+
* @param range - The measured days; every month from `start`'s through `end`'s is drawn.
|
|
44
|
+
* @param options - Week start and label locale.
|
|
45
|
+
* @return Columns and one group per month; empty for an invalid or reversed range.
|
|
46
|
+
*/
|
|
47
|
+
export const buildMonthCalendarHeatmapData = (
|
|
48
|
+
valueByDay: Record< string, number | null >,
|
|
49
|
+
range: MonthCalendarHeatmapRange,
|
|
50
|
+
options: MonthCalendarHeatmapOptions = {}
|
|
51
|
+
): MonthCalendarHeatmapResult => {
|
|
52
|
+
const start = civilDate( range.start );
|
|
53
|
+
const end = civilDate( range.end );
|
|
54
|
+
if ( ! start || ! end || start.getTime() > end.getTime() ) {
|
|
55
|
+
warnOnce(
|
|
56
|
+
`heatmap:monthRange:${ range.start }:${ range.end }`,
|
|
57
|
+
`range ${ JSON.stringify(
|
|
58
|
+
range
|
|
59
|
+
) } must be two \`yyyy-MM-dd\` days with start on or before end, so the calendar is empty.`
|
|
60
|
+
);
|
|
61
|
+
return { data: [], columnGroups: [] };
|
|
62
|
+
}
|
|
63
|
+
const startKey = civilKey( start );
|
|
64
|
+
const endKey = civilKey( end );
|
|
65
|
+
const weekStartsOn = options.weekStartsOn ?? 1;
|
|
66
|
+
const { locale } = sanitizeFormatting( { locale: options.locale } );
|
|
67
|
+
|
|
68
|
+
for ( const key of Object.keys( valueByDay ) ) {
|
|
69
|
+
if ( ! civilDate( key ) ) {
|
|
70
|
+
warnOnce(
|
|
71
|
+
`heatmap:monthKey:${ key }`,
|
|
72
|
+
`${ JSON.stringify(
|
|
73
|
+
key
|
|
74
|
+
) } is not a \`yyyy-MM-dd\` day, so its value is left out of the calendar.`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const readValue = ( key: DayKey ): number | null => {
|
|
79
|
+
const value = valueByDay[ key ];
|
|
80
|
+
return isPresent( value ) ? value : null;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const { formatMonth, formatMonthYear, formatDay } = civilLabelFormatters( locale );
|
|
84
|
+
|
|
85
|
+
const data: HeatmapColumn[] = [];
|
|
86
|
+
const columnGroups: HeatmapColumnGroup[] = [];
|
|
87
|
+
const lastMonthStart = startOfCivilMonth( end );
|
|
88
|
+
// Past twelve months a month name would repeat, so the year tells them apart.
|
|
89
|
+
const monthCount =
|
|
90
|
+
( lastMonthStart.getUTCFullYear() - start.getUTCFullYear() ) * 12 +
|
|
91
|
+
lastMonthStart.getUTCMonth() -
|
|
92
|
+
start.getUTCMonth() +
|
|
93
|
+
1;
|
|
94
|
+
const formatGroup = monthCount > 12 ? formatMonthYear : formatMonth;
|
|
95
|
+
|
|
96
|
+
for (
|
|
97
|
+
let monthStart = startOfCivilMonth( start );
|
|
98
|
+
monthStart.getTime() <= lastMonthStart.getTime();
|
|
99
|
+
monthStart = startOfNextCivilMonth( monthStart )
|
|
100
|
+
) {
|
|
101
|
+
const monthStartKey = civilKey( monthStart );
|
|
102
|
+
const monthEndKey = civilKey( addCivilDays( startOfNextCivilMonth( monthStart ), -1 ) );
|
|
103
|
+
const gridStart = startOfCivilWeek( monthStart, weekStartsOn );
|
|
104
|
+
|
|
105
|
+
for ( let weekday = 0; weekday < DAYS_PER_WEEK; weekday++ ) {
|
|
106
|
+
const cells: HeatmapCell[] = [];
|
|
107
|
+
for ( let week = 0; week < MONTH_ROWS; week++ ) {
|
|
108
|
+
const day = addCivilDays( gridStart, week * DAYS_PER_WEEK + weekday );
|
|
109
|
+
const key = civilKey( day );
|
|
110
|
+
if ( key < monthStartKey || key > monthEndKey ) {
|
|
111
|
+
cells.push( { value: null, hidden: true } );
|
|
112
|
+
} else if ( key < startKey || key > endKey ) {
|
|
113
|
+
cells.push( { label: formatDay( day ), value: null, placeholder: true } );
|
|
114
|
+
} else {
|
|
115
|
+
cells.push( { label: formatDay( day ), value: readValue( key ) } );
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
data.push( { data: cells } );
|
|
119
|
+
}
|
|
120
|
+
columnGroups.push( { label: formatGroup( monthStart ), span: DAYS_PER_WEEK } );
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return { data, columnGroups };
|
|
124
|
+
};
|
|
@@ -5,6 +5,8 @@ $focus-ring-width: var(--wpds-border-width-focus);
|
|
|
5
5
|
|
|
6
6
|
.heatmap-chart {
|
|
7
7
|
position: relative;
|
|
8
|
+
// Keeps the tooltip's z-index inside the chart, as `.xy-plot` does.
|
|
9
|
+
isolation: isolate;
|
|
8
10
|
width: 100%;
|
|
9
11
|
height: 100%;
|
|
10
12
|
min-height: 0;
|
|
@@ -53,6 +55,13 @@ $focus-ring-width: var(--wpds-border-width-focus);
|
|
|
53
55
|
min-height: 0;
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
// Grouped compact grids fill the container so the flexible gap tracks can share
|
|
59
|
+
// the leftover width, and never shrink below the cells plus the minimum gaps.
|
|
60
|
+
.heatmap-chart__grid--flex-gaps {
|
|
61
|
+
width: 100%;
|
|
62
|
+
min-width: max-content;
|
|
63
|
+
}
|
|
64
|
+
|
|
56
65
|
// A cell-height cap makes the row tracks content-bound, so the grid must not
|
|
57
66
|
// stretch: leftover container height would otherwise be absorbed by the auto
|
|
58
67
|
// label row. A width-only cap does not change vertical flex sizing.
|
|
@@ -70,6 +79,8 @@ $focus-ring-width: var(--wpds-border-width-focus);
|
|
|
70
79
|
.heatmap-chart__row-label {
|
|
71
80
|
color: var(--a8c-charts-color-label-secondary);
|
|
72
81
|
font-size: var(--wpds-typography-font-size-xs);
|
|
82
|
+
// Pinned so the label height does not follow the host's line-height.
|
|
83
|
+
line-height: var(--wpds-typography-line-height-xs);
|
|
73
84
|
white-space: nowrap;
|
|
74
85
|
overflow: visible;
|
|
75
86
|
}
|
|
@@ -79,6 +90,16 @@ $focus-ring-width: var(--wpds-border-width-focus);
|
|
|
79
90
|
text-align: left;
|
|
80
91
|
}
|
|
81
92
|
|
|
93
|
+
// Beneath its columns, set off from the last row by more than the cell gap.
|
|
94
|
+
.heatmap-chart__group-label {
|
|
95
|
+
color: var(--a8c-charts-color-label-secondary);
|
|
96
|
+
font-size: var(--wpds-typography-font-size-xs);
|
|
97
|
+
line-height: var(--wpds-typography-line-height-xs);
|
|
98
|
+
white-space: nowrap;
|
|
99
|
+
text-align: center;
|
|
100
|
+
padding-block-start: var(--wpds-dimension-padding-xs);
|
|
101
|
+
}
|
|
102
|
+
|
|
82
103
|
.heatmap-chart__row-label {
|
|
83
104
|
align-self: center;
|
|
84
105
|
justify-self: end;
|
|
@@ -178,3 +199,14 @@ $focus-ring-width: var(--wpds-border-width-focus);
|
|
|
178
199
|
border-radius: var(--a8c-charts-border-radius-cell);
|
|
179
200
|
background: color-mix(in sRGB, var(--a8c-charts-color-heatmap-primary) calc((0.15 + 0.85 * var(--a8c-charts-heatmap-cell-intensity)) * 100%), var(--a8c-charts-color-background, #fff));
|
|
180
201
|
}
|
|
202
|
+
|
|
203
|
+
// One band: the steps butt against each other and only the band's ends round.
|
|
204
|
+
.heatmap-chart__legend-scale--bar {
|
|
205
|
+
overflow: hidden;
|
|
206
|
+
border-radius: var(--a8c-charts-border-radius-cell);
|
|
207
|
+
|
|
208
|
+
.heatmap-chart__legend-swatch {
|
|
209
|
+
width: var(--wpds-dimension-size-sm);
|
|
210
|
+
border-radius: 0;
|
|
211
|
+
}
|
|
212
|
+
}
|