@seekalfred/primitives 0.6.0 → 0.7.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/README.md +19 -11
- package/dist/activity-heatmap/activity-heatmap.d.ts +92 -0
- package/dist/activity-heatmap/activity-heatmap.js +699 -0
- package/dist/activity-heatmap/activity-heatmap.js.map +1 -0
- package/dist/activity-heatmap/index.d.ts +2 -0
- package/dist/activity-heatmap/index.js +2 -0
- package/dist/alert/alert.d.ts +5 -1
- package/dist/alert/alert.js +68 -7
- package/dist/alert/alert.js.map +1 -1
- package/dist/alert-dialog/alert-dialog.d.ts +45 -18
- package/dist/alert-dialog/alert-dialog.js +128 -9
- package/dist/alert-dialog/alert-dialog.js.map +1 -1
- package/dist/alert-dialog/index.d.ts +2 -2
- package/dist/alert-dialog/index.js +2 -2
- package/dist/animated-counter/animated-counter.d.ts +38 -0
- package/dist/animated-counter/animated-counter.js +230 -0
- package/dist/animated-counter/animated-counter.js.map +1 -0
- package/dist/animated-counter/index.d.ts +2 -0
- package/dist/animated-counter/index.js +2 -0
- package/dist/announcement-bar/announcement-bar.d.ts +67 -0
- package/dist/announcement-bar/announcement-bar.js +364 -0
- package/dist/announcement-bar/announcement-bar.js.map +1 -0
- package/dist/announcement-bar/index.d.ts +2 -0
- package/dist/announcement-bar/index.js +2 -0
- package/dist/badge/badge.d.ts +1 -1
- package/dist/bar-chart/bar-chart.d.ts +51 -0
- package/dist/bar-chart/bar-chart.js +565 -0
- package/dist/bar-chart/bar-chart.js.map +1 -0
- package/dist/bar-chart/index.d.ts +2 -0
- package/dist/bar-chart/index.js +2 -0
- package/dist/breadcrumb/breadcrumb.d.ts +2 -2
- package/dist/breadcrumb/breadcrumb.js +12 -3
- package/dist/breadcrumb/breadcrumb.js.map +1 -1
- package/dist/brush-chart/brush-chart.d.ts +64 -0
- package/dist/brush-chart/brush-chart.js +1056 -0
- package/dist/brush-chart/brush-chart.js.map +1 -0
- package/dist/brush-chart/index.d.ts +2 -0
- package/dist/brush-chart/index.js +2 -0
- package/dist/button/button.d.ts +2 -2
- package/dist/button/button.js +31 -21
- package/dist/button/button.js.map +1 -1
- package/dist/button-group/button-group.js +3 -4
- package/dist/button-group/button-group.js.map +1 -1
- package/dist/card/card.js +1 -1
- package/dist/card/card.js.map +1 -1
- package/dist/card-stack/card-stack.d.ts +54 -0
- package/dist/card-stack/card-stack.js +492 -0
- package/dist/card-stack/card-stack.js.map +1 -0
- package/dist/card-stack/index.d.ts +2 -0
- package/dist/card-stack/index.js +2 -0
- package/dist/carousel/carousel.d.ts +56 -0
- package/dist/carousel/carousel.js +389 -0
- package/dist/carousel/carousel.js.map +1 -0
- package/dist/carousel/index.d.ts +2 -0
- package/dist/carousel/index.js +2 -0
- package/dist/checkbox/checkbox.js +4 -4
- package/dist/checkbox/checkbox.js.map +1 -1
- package/dist/chip-group/chip-group.d.ts +37 -0
- package/dist/chip-group/chip-group.js +148 -0
- package/dist/chip-group/chip-group.js.map +1 -0
- package/dist/chip-group/index.d.ts +2 -0
- package/dist/chip-group/index.js +2 -0
- package/dist/countdown/countdown.d.ts +45 -0
- package/dist/countdown/countdown.js +298 -0
- package/dist/countdown/countdown.js.map +1 -0
- package/dist/countdown/index.d.ts +2 -0
- package/dist/countdown/index.js +2 -0
- package/dist/date-picker/picker-parts.js +1 -1
- package/dist/date-picker/picker-parts.js.map +1 -1
- package/dist/dialog/dialog.js +1 -1
- package/dist/dialog/dialog.js.map +1 -1
- package/dist/donut-chart/donut-chart.d.ts +64 -0
- package/dist/donut-chart/donut-chart.js +576 -0
- package/dist/donut-chart/donut-chart.js.map +1 -0
- package/dist/donut-chart/index.d.ts +2 -0
- package/dist/donut-chart/index.js +2 -0
- package/dist/empty-state/empty-state.d.ts +21 -10
- package/dist/empty-state/empty-state.js +32 -26
- package/dist/empty-state/empty-state.js.map +1 -1
- package/dist/empty-state/index.d.ts +2 -2
- package/dist/empty-state/index.js +2 -2
- package/dist/file-drop-zone/file-drop-zone.js +1 -1
- package/dist/file-drop-zone/file-drop-zone.js.map +1 -1
- package/dist/filter-bar/filter-bar.d.ts +47 -26
- package/dist/filter-bar/filter-bar.js +272 -156
- package/dist/filter-bar/filter-bar.js.map +1 -1
- package/dist/gauge/gauge.d.ts +45 -0
- package/dist/gauge/gauge.js +219 -0
- package/dist/gauge/gauge.js.map +1 -0
- package/dist/gauge/index.d.ts +2 -0
- package/dist/gauge/index.js +2 -0
- package/dist/in-view-title/in-view-title.d.ts +33 -0
- package/dist/in-view-title/in-view-title.js +119 -0
- package/dist/in-view-title/in-view-title.js.map +1 -0
- package/dist/in-view-title/index.d.ts +2 -0
- package/dist/in-view-title/index.js +2 -0
- package/dist/index.d.ts +49 -6
- package/dist/index.js +48 -8
- package/dist/lib/cn.js +11 -1
- package/dist/lib/cn.js.map +1 -1
- package/dist/lib/field-message.js +28 -10
- package/dist/lib/field-message.js.map +1 -1
- package/dist/lib/field-surface.js +9 -8
- package/dist/lib/field-surface.js.map +1 -1
- package/dist/lib/flag-emoji.js +50 -0
- package/dist/lib/flag-emoji.js.map +1 -0
- package/dist/lib/flip.js +11 -3
- package/dist/lib/flip.js.map +1 -1
- package/dist/lib/menu-highlight.js +64 -0
- package/dist/lib/menu-highlight.js.map +1 -0
- package/dist/lib/motion.js +9 -2
- package/dist/lib/motion.js.map +1 -1
- package/dist/lib/rolling-value.js +1 -1
- package/dist/lib/rolling-value.js.map +1 -1
- package/dist/lib/spring-tween.js +500 -0
- package/dist/lib/spring-tween.js.map +1 -0
- package/dist/lib/swap-text.js +3 -1
- package/dist/lib/swap-text.js.map +1 -1
- package/dist/line-chart/index.d.ts +2 -0
- package/dist/line-chart/index.js +2 -0
- package/dist/line-chart/line-chart.d.ts +76 -0
- package/dist/line-chart/line-chart.js +611 -0
- package/dist/line-chart/line-chart.js.map +1 -0
- package/dist/mention-input/index.d.ts +2 -0
- package/dist/mention-input/index.js +2 -0
- package/dist/mention-input/mention-input.d.ts +98 -0
- package/dist/mention-input/mention-input.js +576 -0
- package/dist/mention-input/mention-input.js.map +1 -0
- package/dist/menu/menu.d.ts +2 -0
- package/dist/menu/menu.js +23 -12
- package/dist/menu/menu.js.map +1 -1
- package/dist/money-input/index.d.ts +2 -0
- package/dist/money-input/index.js +2 -0
- package/dist/money-input/money-input.d.ts +65 -0
- package/dist/money-input/money-input.js +607 -0
- package/dist/money-input/money-input.js.map +1 -0
- package/dist/morph-select/index.d.ts +2 -0
- package/dist/morph-select/index.js +2 -0
- package/dist/morph-select/morph-select.d.ts +71 -0
- package/dist/morph-select/morph-select.js +693 -0
- package/dist/morph-select/morph-select.js.map +1 -0
- package/dist/multi-select/index.d.ts +2 -0
- package/dist/multi-select/index.js +2 -0
- package/dist/multi-select/multi-select.d.ts +36 -0
- package/dist/multi-select/multi-select.js +152 -0
- package/dist/multi-select/multi-select.js.map +1 -0
- package/dist/number-field/index.d.ts +2 -0
- package/dist/number-field/index.js +2 -0
- package/dist/number-field/number-field.d.ts +42 -0
- package/dist/number-field/number-field.js +199 -0
- package/dist/number-field/number-field.js.map +1 -0
- package/dist/onboarding-checklist/index.d.ts +2 -0
- package/dist/onboarding-checklist/index.js +2 -0
- package/dist/onboarding-checklist/onboarding-checklist.d.ts +55 -0
- package/dist/onboarding-checklist/onboarding-checklist.js +577 -0
- package/dist/onboarding-checklist/onboarding-checklist.js.map +1 -0
- package/dist/password-field/password-field.js +2 -2
- package/dist/password-field/password-field.js.map +1 -1
- package/dist/phone-input/index.d.ts +3 -0
- package/dist/phone-input/index.js +3 -0
- package/dist/phone-input/phone-countries.d.ts +29 -0
- package/dist/phone-input/phone-countries.js +170 -0
- package/dist/phone-input/phone-countries.js.map +1 -0
- package/dist/phone-input/phone-input.d.ts +66 -0
- package/dist/phone-input/phone-input.js +440 -0
- package/dist/phone-input/phone-input.js.map +1 -0
- package/dist/popover/popover.d.ts +12 -0
- package/dist/popover/popover.js +31 -7
- package/dist/popover/popover.js.map +1 -1
- package/dist/progress/progress.js +1 -1
- package/dist/radio-group/radio-group.js +1 -1
- package/dist/radio-group/radio-group.js.map +1 -1
- package/dist/reactions/index.d.ts +2 -0
- package/dist/reactions/index.js +2 -0
- package/dist/reactions/reactions.d.ts +50 -0
- package/dist/reactions/reactions.js +315 -0
- package/dist/reactions/reactions.js.map +1 -0
- package/dist/rich-text-editor/index.d.ts +3 -0
- package/dist/rich-text-editor/index.js +2 -0
- package/dist/rich-text-editor/rich-text-editor.d.ts +70 -0
- package/dist/rich-text-editor/rich-text-editor.js +909 -0
- package/dist/rich-text-editor/rich-text-editor.js.map +1 -0
- package/dist/rich-text-editor/rich-text-engine.d.ts +16 -0
- package/dist/rich-text-editor/rich-text-engine.js +366 -0
- package/dist/rich-text-editor/rich-text-engine.js.map +1 -0
- package/dist/rich-text-editor/rich-text-html.js +174 -0
- package/dist/rich-text-editor/rich-text-html.js.map +1 -0
- package/dist/rich-text-editor/rich-text-icons.js +43 -0
- package/dist/rich-text-editor/rich-text-icons.js.map +1 -0
- package/dist/ridgeline/index.d.ts +2 -0
- package/dist/ridgeline/index.js +2 -0
- package/dist/ridgeline/ridgeline.d.ts +55 -0
- package/dist/ridgeline/ridgeline.js +701 -0
- package/dist/ridgeline/ridgeline.js.map +1 -0
- package/dist/scroll-area/index.d.ts +2 -2
- package/dist/scroll-area/index.js +2 -2
- package/dist/scroll-area/scroll-area.d.ts +68 -31
- package/dist/scroll-area/scroll-area.js +208 -59
- package/dist/scroll-area/scroll-area.js.map +1 -1
- package/dist/scrub-input/evaluate.d.ts +14 -0
- package/dist/scrub-input/evaluate.js +72 -0
- package/dist/scrub-input/evaluate.js.map +1 -0
- package/dist/scrub-input/index.d.ts +3 -0
- package/dist/scrub-input/index.js +3 -0
- package/dist/scrub-input/scrub-input.d.ts +74 -0
- package/dist/scrub-input/scrub-input.js +486 -0
- package/dist/scrub-input/scrub-input.js.map +1 -0
- package/dist/search-field/search-field.d.ts +2 -1
- package/dist/search-field/search-field.js +4 -4
- package/dist/search-field/search-field.js.map +1 -1
- package/dist/select/select.d.ts +3 -1
- package/dist/select/select.js +20 -8
- package/dist/select/select.js.map +1 -1
- package/dist/separator/separator.d.ts +1 -1
- package/dist/sheet/sheet.js +2 -1
- package/dist/sheet/sheet.js.map +1 -1
- package/dist/shortcut-recorder/index.d.ts +3 -0
- package/dist/shortcut-recorder/index.js +3 -0
- package/dist/shortcut-recorder/shortcut-model.d.ts +27 -0
- package/dist/shortcut-recorder/shortcut-model.js +206 -0
- package/dist/shortcut-recorder/shortcut-model.js.map +1 -0
- package/dist/shortcut-recorder/shortcut-recorder.d.ts +93 -0
- package/dist/shortcut-recorder/shortcut-recorder.js +489 -0
- package/dist/shortcut-recorder/shortcut-recorder.js.map +1 -0
- package/dist/slider/index.d.ts +2 -2
- package/dist/slider/slider.d.ts +46 -14
- package/dist/slider/slider.js +151 -67
- package/dist/slider/slider.js.map +1 -1
- package/dist/slope-chart/index.d.ts +2 -0
- package/dist/slope-chart/index.js +2 -0
- package/dist/slope-chart/slope-chart.d.ts +54 -0
- package/dist/slope-chart/slope-chart.js +568 -0
- package/dist/slope-chart/slope-chart.js.map +1 -0
- package/dist/sparkline/index.d.ts +2 -0
- package/dist/sparkline/index.js +2 -0
- package/dist/sparkline/sparkline.d.ts +46 -0
- package/dist/sparkline/sparkline.js +390 -0
- package/dist/sparkline/sparkline.js.map +1 -0
- package/dist/split-bar/index.d.ts +2 -0
- package/dist/split-bar/index.js +2 -0
- package/dist/split-bar/split-bar.d.ts +62 -0
- package/dist/split-bar/split-bar.js +513 -0
- package/dist/split-bar/split-bar.js.map +1 -0
- package/dist/split-button/split-button.d.ts +1 -1
- package/dist/split-button/split-button.js +12 -11
- package/dist/split-button/split-button.js.map +1 -1
- package/dist/stat-card/index.d.ts +2 -2
- package/dist/stat-card/index.js +2 -2
- package/dist/stat-card/stat-card.d.ts +24 -4
- package/dist/stat-card/stat-card.js +114 -17
- package/dist/stat-card/stat-card.js.map +1 -1
- package/dist/stepper/index.d.ts +2 -0
- package/dist/stepper/index.js +2 -0
- package/dist/stepper/stepper.d.ts +51 -0
- package/dist/stepper/stepper.js +215 -0
- package/dist/stepper/stepper.js.map +1 -0
- package/dist/streamgraph/index.d.ts +2 -0
- package/dist/streamgraph/index.js +2 -0
- package/dist/streamgraph/streamgraph.d.ts +70 -0
- package/dist/streamgraph/streamgraph.js +613 -0
- package/dist/streamgraph/streamgraph.js.map +1 -0
- package/dist/styles.css +1 -1
- package/dist/swipe-actions/index.d.ts +2 -0
- package/dist/swipe-actions/index.js +2 -0
- package/dist/swipe-actions/swipe-actions.d.ts +67 -0
- package/dist/swipe-actions/swipe-actions.js +552 -0
- package/dist/swipe-actions/swipe-actions.js.map +1 -0
- package/dist/switch/switch.js +2 -2
- package/dist/switch/switch.js.map +1 -1
- package/dist/table/index.d.ts +2 -2
- package/dist/table/index.js +2 -2
- package/dist/table/table.d.ts +56 -4
- package/dist/table/table.js +340 -165
- package/dist/table/table.js.map +1 -1
- package/dist/tabs/tabs.js +2 -2
- package/dist/tabs/tabs.js.map +1 -1
- package/dist/tag-input/index.d.ts +2 -0
- package/dist/tag-input/index.js +2 -0
- package/dist/tag-input/tag-input.d.ts +40 -0
- package/dist/tag-input/tag-input.js +269 -0
- package/dist/tag-input/tag-input.js.map +1 -0
- package/dist/text-morph/index.d.ts +2 -0
- package/dist/text-morph/index.js +2 -0
- package/dist/text-morph/text-morph.d.ts +23 -0
- package/dist/text-morph/text-morph.js +21 -0
- package/dist/text-morph/text-morph.js.map +1 -0
- package/dist/text-reveal/index.d.ts +2 -0
- package/dist/text-reveal/index.js +2 -0
- package/dist/text-reveal/text-reveal.d.ts +27 -0
- package/dist/text-reveal/text-reveal.js +51 -0
- package/dist/text-reveal/text-reveal.js.map +1 -0
- package/dist/text-shimmer/index.d.ts +2 -0
- package/dist/text-shimmer/index.js +2 -0
- package/dist/text-shimmer/text-shimmer.d.ts +29 -0
- package/dist/text-shimmer/text-shimmer.js +29 -0
- package/dist/text-shimmer/text-shimmer.js.map +1 -0
- package/dist/time-picker/time-picker.js +2 -2
- package/dist/time-picker/time-picker.js.map +1 -1
- package/dist/toast/index.d.ts +2 -2
- package/dist/toast/index.js +2 -2
- package/dist/toast/toast.d.ts +6 -0
- package/dist/toast/toast.js +82 -40
- package/dist/toast/toast.js.map +1 -1
- package/dist/tooltip/tooltip.d.ts +9 -2
- package/dist/tooltip/tooltip.js +7 -7
- package/dist/tooltip/tooltip.js.map +1 -1
- package/dist/treemap/index.d.ts +2 -0
- package/dist/treemap/index.js +2 -0
- package/dist/treemap/treemap.d.ts +53 -0
- package/dist/treemap/treemap.js +699 -0
- package/dist/treemap/treemap.js.map +1 -0
- package/dist/usage-meter/index.d.ts +2 -0
- package/dist/usage-meter/index.js +2 -0
- package/dist/usage-meter/usage-meter.d.ts +50 -0
- package/dist/usage-meter/usage-meter.js +440 -0
- package/dist/usage-meter/usage-meter.js.map +1 -0
- package/dist/waffle-chart/index.d.ts +2 -0
- package/dist/waffle-chart/index.js +2 -0
- package/dist/waffle-chart/waffle-chart.d.ts +57 -0
- package/dist/waffle-chart/waffle-chart.js +426 -0
- package/dist/waffle-chart/waffle-chart.js.map +1 -0
- package/package.json +165 -3
|
@@ -0,0 +1,1056 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { mergeRefs } from "../lib/merge-refs.js";
|
|
4
|
+
import { prefersReducedMotion, useIsomorphicLayoutEffect } from "../lib/motion.js";
|
|
5
|
+
import { SMOOTH, live, retarget, tween } from "../lib/spring-tween.js";
|
|
6
|
+
import { forwardRef, useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
7
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
//#region src/brush-chart/brush-chart.tsx
|
|
9
|
+
const DAY = 864e5;
|
|
10
|
+
/** Room above the top gridline, the gap between a point and its tooltip, and how close to an edge a press grabs it. */
|
|
11
|
+
const TOP = 28;
|
|
12
|
+
const GAP = 14;
|
|
13
|
+
const GRIP = 14;
|
|
14
|
+
/** Width of an edge handle's hit area. */
|
|
15
|
+
const HANDLE = 28;
|
|
16
|
+
const COMPACT = new Intl.NumberFormat("en-US", {
|
|
17
|
+
notation: "compact",
|
|
18
|
+
maximumFractionDigits: 1
|
|
19
|
+
});
|
|
20
|
+
const GROUPED = new Intl.NumberFormat("en-US", { maximumFractionDigits: 1 });
|
|
21
|
+
const utc = (options) => new Intl.DateTimeFormat("en-US", {
|
|
22
|
+
timeZone: "UTC",
|
|
23
|
+
...options
|
|
24
|
+
});
|
|
25
|
+
const FULL_DATE = utc({
|
|
26
|
+
weekday: "short",
|
|
27
|
+
month: "short",
|
|
28
|
+
day: "numeric",
|
|
29
|
+
year: "numeric"
|
|
30
|
+
});
|
|
31
|
+
const DAY_TICK = utc({
|
|
32
|
+
month: "short",
|
|
33
|
+
day: "numeric"
|
|
34
|
+
});
|
|
35
|
+
const MONTH_TICK = utc({ month: "short" });
|
|
36
|
+
const YEAR_TICK = utc({ year: "numeric" });
|
|
37
|
+
const SHORT_DATE = utc({
|
|
38
|
+
month: "short",
|
|
39
|
+
day: "numeric",
|
|
40
|
+
year: "numeric"
|
|
41
|
+
});
|
|
42
|
+
const clamp = (value, low, high) => Math.min(high, Math.max(low, value));
|
|
43
|
+
const time = (date) => typeof date === "number" ? date : date.getTime();
|
|
44
|
+
const clean = (value) => Number(value.toPrecision(12));
|
|
45
|
+
/** Clean gridlines from zero: the smallest step of 1, 2, 2.5 or 5 that covers the data in four rows or fewer. */
|
|
46
|
+
function niceTop(high) {
|
|
47
|
+
const hi = Math.max(high, 1);
|
|
48
|
+
const magnitude = 10 ** Math.floor(Math.log10(hi / 4));
|
|
49
|
+
for (const factor of [
|
|
50
|
+
1,
|
|
51
|
+
2,
|
|
52
|
+
2.5,
|
|
53
|
+
5,
|
|
54
|
+
10
|
|
55
|
+
]) {
|
|
56
|
+
const step = factor * magnitude;
|
|
57
|
+
const top = Math.ceil(hi / step) * step;
|
|
58
|
+
if (top / step <= 4 + 1e-9) return {
|
|
59
|
+
top: clean(top),
|
|
60
|
+
step: clean(step)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
top: hi,
|
|
65
|
+
step: hi / 4
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** Calendar ticks that fit the width: days, weeks, then months, quarters and years, each landing on a real boundary. */
|
|
69
|
+
function timeTicks(start, end, width) {
|
|
70
|
+
const room = Math.max(2, Math.floor(width / 76));
|
|
71
|
+
const span = end - start;
|
|
72
|
+
for (const days of [
|
|
73
|
+
1,
|
|
74
|
+
2,
|
|
75
|
+
7,
|
|
76
|
+
14
|
|
77
|
+
]) {
|
|
78
|
+
if (span / (days * DAY) > room) continue;
|
|
79
|
+
const ticks = [];
|
|
80
|
+
let t = Math.ceil(start / DAY) * DAY;
|
|
81
|
+
if (days >= 7) while (new Date(t).getUTCDay() !== 1) t += DAY;
|
|
82
|
+
for (; t <= end; t += days * DAY) ticks.push({
|
|
83
|
+
t,
|
|
84
|
+
label: DAY_TICK.format(t)
|
|
85
|
+
});
|
|
86
|
+
return ticks;
|
|
87
|
+
}
|
|
88
|
+
for (const months of [
|
|
89
|
+
1,
|
|
90
|
+
2,
|
|
91
|
+
3,
|
|
92
|
+
6,
|
|
93
|
+
12
|
|
94
|
+
]) {
|
|
95
|
+
if (span / (months * 30.44 * DAY) > room) continue;
|
|
96
|
+
const ticks = [];
|
|
97
|
+
const first = new Date(start);
|
|
98
|
+
let y = first.getUTCFullYear();
|
|
99
|
+
let m = first.getUTCMonth() + (first.getUTCDate() > 1 || first.getUTCHours() > 0 ? 1 : 0);
|
|
100
|
+
for (;;) {
|
|
101
|
+
y += Math.floor(m / 12);
|
|
102
|
+
m %= 12;
|
|
103
|
+
const t = Date.UTC(y, m, 1);
|
|
104
|
+
if (t > end) break;
|
|
105
|
+
if (m % months === 0) ticks.push({
|
|
106
|
+
t,
|
|
107
|
+
label: m === 0 ? YEAR_TICK.format(t) : MONTH_TICK.format(t)
|
|
108
|
+
});
|
|
109
|
+
m += 1;
|
|
110
|
+
}
|
|
111
|
+
return ticks;
|
|
112
|
+
}
|
|
113
|
+
const ticks = [];
|
|
114
|
+
for (let y = new Date(start).getUTCFullYear() + 1; Date.UTC(y, 0, 1) <= end; y++) ticks.push({
|
|
115
|
+
t: Date.UTC(y, 0, 1),
|
|
116
|
+
label: String(y)
|
|
117
|
+
});
|
|
118
|
+
return ticks;
|
|
119
|
+
}
|
|
120
|
+
/** First index at or after t. */
|
|
121
|
+
function lowerBound(points, t) {
|
|
122
|
+
let low = 0;
|
|
123
|
+
let high = points.length;
|
|
124
|
+
while (low < high) {
|
|
125
|
+
const middle = low + high >> 1;
|
|
126
|
+
if (points[middle].t < t) low = middle + 1;
|
|
127
|
+
else high = middle;
|
|
128
|
+
}
|
|
129
|
+
return low;
|
|
130
|
+
}
|
|
131
|
+
/** A line through the points in view. Past two points per pixel, each pixel column keeps only its low and high so spikes survive. */
|
|
132
|
+
function linePath(points, from, to, x, y, width) {
|
|
133
|
+
const slice = points.slice(Math.max(0, from), Math.min(points.length, to));
|
|
134
|
+
if (!slice.length) return "";
|
|
135
|
+
const parts = [];
|
|
136
|
+
const add = (point) => parts.push(`${x(point.t).toFixed(1)},${y(point.value).toFixed(1)}`);
|
|
137
|
+
if (slice.length <= width * 2) for (const point of slice) add(point);
|
|
138
|
+
else {
|
|
139
|
+
let column = NaN;
|
|
140
|
+
let low = null;
|
|
141
|
+
let high = null;
|
|
142
|
+
const flush = () => {
|
|
143
|
+
if (!low || !high) return;
|
|
144
|
+
for (const point of low.t < high.t ? [low, high] : [high, low]) add(point);
|
|
145
|
+
};
|
|
146
|
+
for (const point of slice) {
|
|
147
|
+
const at = Math.floor(x(point.t));
|
|
148
|
+
if (at !== column) {
|
|
149
|
+
flush();
|
|
150
|
+
column = at;
|
|
151
|
+
low = high = point;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (point.value < low.value) low = point;
|
|
155
|
+
if (point.value > high.value) high = point;
|
|
156
|
+
}
|
|
157
|
+
flush();
|
|
158
|
+
}
|
|
159
|
+
return `M${parts.join("L")}`;
|
|
160
|
+
}
|
|
161
|
+
/** A centred seven point average and its low to high band, each averaged once more so the edges ease instead of stepping. */
|
|
162
|
+
function smoothed(points) {
|
|
163
|
+
const around = (index, read) => {
|
|
164
|
+
const out = [];
|
|
165
|
+
for (let at = Math.max(0, index - 3); at <= Math.min(points.length - 1, index + 3); at++) out.push(read(at));
|
|
166
|
+
return out;
|
|
167
|
+
};
|
|
168
|
+
const raw = points.map((_, index) => {
|
|
169
|
+
const values = around(index, (at) => points[at].value);
|
|
170
|
+
return {
|
|
171
|
+
value: values.reduce((sum, value) => sum + value, 0) / values.length,
|
|
172
|
+
low: Math.min(...values),
|
|
173
|
+
high: Math.max(...values)
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
return points.map((point, index) => {
|
|
177
|
+
const near = around(index, (at) => raw[at]);
|
|
178
|
+
return {
|
|
179
|
+
t: point.t,
|
|
180
|
+
value: raw[index].value,
|
|
181
|
+
low: near.reduce((sum, item) => sum + item.low, 0) / near.length,
|
|
182
|
+
high: near.reduce((sum, item) => sum + item.high, 0) / near.length
|
|
183
|
+
};
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
const BrushChart = forwardRef(function BrushChart({ data, label, unit = "", formatValue = (value) => GROUPED.format(value), formatTick = (value) => COMPACT.format(value), formatDate = (date) => FULL_DATE.format(date), annotations, range, defaultRange, onRangeChange, minSpan = 7 * DAY, height = 240, overviewHeight = 52, emptyLabel = "No data yet", className }, ref) {
|
|
187
|
+
const uid = `brush${useId().replace(/[^a-zA-Z0-9-]/g, "")}`;
|
|
188
|
+
const figure = useRef(null);
|
|
189
|
+
const refs = useMemo(() => mergeRefs(ref, figure), [ref]);
|
|
190
|
+
const plot = useRef(null);
|
|
191
|
+
const plotSvg = useRef(null);
|
|
192
|
+
const strip = useRef(null);
|
|
193
|
+
const tip = useRef(null);
|
|
194
|
+
const gutter = useRef(null);
|
|
195
|
+
const axis = useRef(null);
|
|
196
|
+
const points = useMemo(() => data.map((item) => ({
|
|
197
|
+
t: time(item.date),
|
|
198
|
+
value: item.value
|
|
199
|
+
})), [data]);
|
|
200
|
+
const smooth = useMemo(() => smoothed(points), [points]);
|
|
201
|
+
const events = useMemo(() => (annotations ?? []).map((item) => ({
|
|
202
|
+
...item,
|
|
203
|
+
t: time(item.date)
|
|
204
|
+
})).sort((a, b) => a.t - b.t), [annotations]);
|
|
205
|
+
const empty = points.length < 2;
|
|
206
|
+
const first = points[0]?.t ?? 0;
|
|
207
|
+
const lastT = points[points.length - 1]?.t ?? 1;
|
|
208
|
+
const whole = [first, lastT];
|
|
209
|
+
const fit = ([start, end]) => {
|
|
210
|
+
const span = clamp(end - start, Math.min(minSpan, lastT - first), lastT - first);
|
|
211
|
+
const s = clamp(start, first, lastT - span);
|
|
212
|
+
return [s, s + span];
|
|
213
|
+
};
|
|
214
|
+
const view = useRef(range ?? defaultRange ?? whole);
|
|
215
|
+
const [shown, setShown] = useState(view.current);
|
|
216
|
+
const touched = useRef(false);
|
|
217
|
+
const settleTimer = useRef(0);
|
|
218
|
+
const flight = useRef(null);
|
|
219
|
+
const flush = useCallback(() => {
|
|
220
|
+
window.clearTimeout(settleTimer.current);
|
|
221
|
+
settleTimer.current = 0;
|
|
222
|
+
setShown((now) => now[0] === view.current[0] && now[1] === view.current[1] ? now : [...view.current]);
|
|
223
|
+
}, []);
|
|
224
|
+
const settleSoon = useCallback(() => {
|
|
225
|
+
if (!settleTimer.current) settleTimer.current = window.setTimeout(flush, 100);
|
|
226
|
+
}, [flush]);
|
|
227
|
+
const latest = useRef({
|
|
228
|
+
points,
|
|
229
|
+
smooth,
|
|
230
|
+
events,
|
|
231
|
+
first,
|
|
232
|
+
lastT,
|
|
233
|
+
height,
|
|
234
|
+
overviewHeight,
|
|
235
|
+
empty
|
|
236
|
+
});
|
|
237
|
+
const activeRef = useRef({
|
|
238
|
+
index: null,
|
|
239
|
+
event: null
|
|
240
|
+
});
|
|
241
|
+
const plotW = useRef(0);
|
|
242
|
+
const stripW = useRef(0);
|
|
243
|
+
const topLive = useRef(live(0));
|
|
244
|
+
const aimed = useRef(null);
|
|
245
|
+
const paintRef = useRef(() => {});
|
|
246
|
+
const [plotWidth, setPlotWidth] = useState(0);
|
|
247
|
+
const [stripWidth, setStripWidth] = useState(0);
|
|
248
|
+
/** One writer: the window, every path, the axes, the markers, the crosshair and the tooltip, straight onto the elements. */
|
|
249
|
+
const paint = useCallback(() => {
|
|
250
|
+
const L = latest.current;
|
|
251
|
+
const w = plotW.current;
|
|
252
|
+
const root = plotSvg.current;
|
|
253
|
+
const sw = stripW.current;
|
|
254
|
+
if (!root || L.empty) return;
|
|
255
|
+
const [start, end] = view.current;
|
|
256
|
+
const span = Math.max(1, end - start);
|
|
257
|
+
const P = L.points;
|
|
258
|
+
const x = (t) => (t - start) / span * w;
|
|
259
|
+
const from = Math.max(0, lowerBound(P, start) - 1);
|
|
260
|
+
const to = Math.min(P.length, lowerBound(P, end) + 1);
|
|
261
|
+
let peak = 0;
|
|
262
|
+
for (let index = from; index < to; index++) peak = Math.max(peak, P[index].value);
|
|
263
|
+
const want = niceTop(peak * 1.04).top;
|
|
264
|
+
if (aimed.current === null) {
|
|
265
|
+
aimed.current = want;
|
|
266
|
+
topLive.current.value = want;
|
|
267
|
+
} else if (aimed.current !== want) {
|
|
268
|
+
aimed.current = want;
|
|
269
|
+
retarget(topLive.current, want, SMOOTH, () => paintRef.current());
|
|
270
|
+
}
|
|
271
|
+
const top = topLive.current.value || want;
|
|
272
|
+
const H = L.height;
|
|
273
|
+
const y = (value) => TOP + (1 - value / (top || 1)) * (H - TOP);
|
|
274
|
+
const pxPerPoint = w && P.length > 1 ? w * (L.lastT - L.first) / (P.length - 1) / span : w;
|
|
275
|
+
const detail = clamp((pxPerPoint - 2.75) / 1.5, 0, 1);
|
|
276
|
+
if (w) {
|
|
277
|
+
const line = linePath(P, from, to, x, y, w);
|
|
278
|
+
const area = line ? `${line}L${x(P[to - 1].t).toFixed(1)},${H}L${x(P[from].t).toFixed(1)},${H}Z` : "";
|
|
279
|
+
const S = L.smooth;
|
|
280
|
+
const trend = linePath(S, from, to, x, y, w * 4);
|
|
281
|
+
const upper = linePath(S.map((point) => ({
|
|
282
|
+
t: point.t,
|
|
283
|
+
value: point.high
|
|
284
|
+
})), from, to, x, y, w * 4);
|
|
285
|
+
const lower = linePath(S.map((point) => ({
|
|
286
|
+
t: point.t,
|
|
287
|
+
value: point.low
|
|
288
|
+
})), from, to, x, y, w * 4);
|
|
289
|
+
const band = trend ? `${upper}L${lower.slice(1).split("L").reverse().join("L")}Z` : "";
|
|
290
|
+
const set = (part, d, opacity) => {
|
|
291
|
+
const node = root.querySelector(`[data-part=${part}]`);
|
|
292
|
+
node?.setAttribute("d", d);
|
|
293
|
+
if (node) node.style.opacity = String(opacity);
|
|
294
|
+
};
|
|
295
|
+
set("line", line, detail);
|
|
296
|
+
set("area", area, detail);
|
|
297
|
+
set("trend", trend, 1 - detail);
|
|
298
|
+
set("band", band, 1 - detail);
|
|
299
|
+
}
|
|
300
|
+
for (const node of root.querySelectorAll("[data-grid]")) {
|
|
301
|
+
const value = Number(node.dataset.grid);
|
|
302
|
+
const yy = String(Math.round(y(value)) + .5);
|
|
303
|
+
node.setAttribute("y1", yy);
|
|
304
|
+
node.setAttribute("y2", yy);
|
|
305
|
+
node.style.visibility = value > top * 1.001 ? "hidden" : "visible";
|
|
306
|
+
}
|
|
307
|
+
for (const node of gutter.current?.querySelectorAll("[data-tick]") ?? []) {
|
|
308
|
+
const value = Number(node.dataset.tick);
|
|
309
|
+
node.style.translate = `0 ${Math.round(y(value))}px`;
|
|
310
|
+
node.style.visibility = value > top * 1.001 ? "hidden" : "visible";
|
|
311
|
+
}
|
|
312
|
+
for (const node of axis.current?.querySelectorAll("[data-t]") ?? []) {
|
|
313
|
+
const px = x(Number(node.dataset.t));
|
|
314
|
+
node.style.left = `${px}px`;
|
|
315
|
+
node.style.translate = `${px < 24 ? 0 : px > w - 24 ? -100 : -50}% 0`;
|
|
316
|
+
}
|
|
317
|
+
const marks = [...plot.current?.querySelectorAll("[data-event]") ?? []];
|
|
318
|
+
const inside = marks.filter((node) => {
|
|
319
|
+
const t = L.events[Number(node.dataset.event)].t;
|
|
320
|
+
return t >= start && t <= end;
|
|
321
|
+
});
|
|
322
|
+
marks.forEach((node) => {
|
|
323
|
+
const index = Number(node.dataset.event);
|
|
324
|
+
const t = L.events[index].t;
|
|
325
|
+
const visible = inside.includes(node);
|
|
326
|
+
node.toggleAttribute("data-out", !visible);
|
|
327
|
+
const px = x(t);
|
|
328
|
+
node.style.left = `${px}px`;
|
|
329
|
+
const rule = root.querySelector(`[data-rule="${index}"]`);
|
|
330
|
+
rule?.toggleAttribute("data-out", !visible);
|
|
331
|
+
rule?.setAttribute("x1", String(Math.round(px) + .5));
|
|
332
|
+
rule?.setAttribute("x2", String(Math.round(px) + .5));
|
|
333
|
+
node.removeAttribute("data-labelled");
|
|
334
|
+
node.removeAttribute("data-flip");
|
|
335
|
+
});
|
|
336
|
+
let edge = -Infinity;
|
|
337
|
+
inside.forEach((node, at) => {
|
|
338
|
+
const index = Number(node.dataset.event);
|
|
339
|
+
const px = x(L.events[index].t);
|
|
340
|
+
const text = L.events[index].label.length * 6.2 + 4;
|
|
341
|
+
const next = at + 1 < inside.length ? x(L.events[Number(inside[at + 1].dataset.event)].t) - 10 : w;
|
|
342
|
+
if (px + 14 >= edge && px + 14 + text <= next) {
|
|
343
|
+
node.setAttribute("data-labelled", "");
|
|
344
|
+
edge = px + 14 + text + 8;
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
const before = at === 0 ? -Infinity : x(L.events[Number(inside[at - 1].dataset.event)].t);
|
|
348
|
+
if (px - 14 - text >= Math.max(0, edge) && (at === 0 || px - 14 - text >= before + 12)) {
|
|
349
|
+
node.setAttribute("data-labelled", "");
|
|
350
|
+
node.setAttribute("data-flip", "");
|
|
351
|
+
edge = px + 12;
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
edge = Math.max(edge, px + 12);
|
|
355
|
+
});
|
|
356
|
+
const { index, event } = activeRef.current;
|
|
357
|
+
const at = index !== null && index >= from && index < to ? index : null;
|
|
358
|
+
const cx = at === null ? 0 : x(P[at].t);
|
|
359
|
+
const cy = at === null ? 0 : y(L.smooth[at].value + (P[at].value - L.smooth[at].value) * detail);
|
|
360
|
+
const crossX = String(Math.round(cx) + .5);
|
|
361
|
+
const cross = root.querySelector("[data-part=crosshair]");
|
|
362
|
+
cross?.setAttribute("x1", crossX);
|
|
363
|
+
cross?.setAttribute("x2", crossX);
|
|
364
|
+
if (cross) cross.style.opacity = at === null ? "0" : "";
|
|
365
|
+
const dot = root.querySelector("[data-part=dot]");
|
|
366
|
+
dot?.setAttribute("cx", String(cx));
|
|
367
|
+
dot?.setAttribute("cy", String(cy));
|
|
368
|
+
if (dot) dot.style.opacity = at === null ? "0" : "";
|
|
369
|
+
const bubble = tip.current;
|
|
370
|
+
if (bubble) {
|
|
371
|
+
const ax = event !== null ? x(L.events[event].t) : cx;
|
|
372
|
+
const ay = event !== null ? TOP : cy;
|
|
373
|
+
const tw = bubble.offsetWidth;
|
|
374
|
+
const th = bubble.offsetHeight;
|
|
375
|
+
let left = ax + GAP;
|
|
376
|
+
if (left + tw > w) left = ax - GAP - tw;
|
|
377
|
+
let topY = ay - th - GAP;
|
|
378
|
+
if (topY < 0) topY = ay + GAP;
|
|
379
|
+
bubble.style.translate = `${clamp(left, 0, Math.max(0, w - tw))}px ${clamp(topY, 0, Math.max(0, H - th))}px`;
|
|
380
|
+
}
|
|
381
|
+
const ox = (t) => (t - L.first) / (L.lastT - L.first || 1) * sw;
|
|
382
|
+
const wx0 = ox(start);
|
|
383
|
+
const wx1 = ox(end);
|
|
384
|
+
const box = strip.current;
|
|
385
|
+
if (box) {
|
|
386
|
+
const place = (selector, css) => {
|
|
387
|
+
const node = box.querySelector(selector);
|
|
388
|
+
if (node) Object.assign(node.style, css);
|
|
389
|
+
};
|
|
390
|
+
place("[data-part=window-box]", {
|
|
391
|
+
left: `${wx0}px`,
|
|
392
|
+
width: `${Math.max(0, wx1 - wx0)}px`
|
|
393
|
+
});
|
|
394
|
+
const inward = wx0 < HANDLE;
|
|
395
|
+
place("[data-handle=start]", { left: `${inward ? wx0 : wx0 - HANDLE}px` });
|
|
396
|
+
box.querySelector("[data-handle=start]")?.toggleAttribute("data-inward", inward);
|
|
397
|
+
place("[data-handle=end]", { left: `${wx1}px` });
|
|
398
|
+
place("[data-edge=start]", {
|
|
399
|
+
left: "0px",
|
|
400
|
+
width: `${Math.max(0, wx0)}px`
|
|
401
|
+
});
|
|
402
|
+
place("[data-edge=end]", {
|
|
403
|
+
left: `${wx1}px`,
|
|
404
|
+
right: "0px"
|
|
405
|
+
});
|
|
406
|
+
const clip = box.querySelector("[data-part=window-clip]");
|
|
407
|
+
clip?.setAttribute("x", String(wx0));
|
|
408
|
+
clip?.setAttribute("width", String(Math.max(0, wx1 - wx0)));
|
|
409
|
+
const text = `${SHORT_DATE.format(start)} to ${SHORT_DATE.format(end)}`;
|
|
410
|
+
const slider = (selector, min, max, now, valuetext) => {
|
|
411
|
+
const node = box.querySelector(selector);
|
|
412
|
+
if (!node) return;
|
|
413
|
+
node.setAttribute("aria-valuemin", String(Math.round(min)));
|
|
414
|
+
node.setAttribute("aria-valuemax", String(Math.round(max)));
|
|
415
|
+
node.setAttribute("aria-valuenow", String(Math.round(now)));
|
|
416
|
+
node.setAttribute("aria-valuetext", valuetext);
|
|
417
|
+
};
|
|
418
|
+
slider("[data-part=window]", L.first, L.lastT, start, text);
|
|
419
|
+
slider("[data-handle=start]", L.first, end, start, SHORT_DATE.format(start));
|
|
420
|
+
slider("[data-handle=end]", start, L.lastT, end, SHORT_DATE.format(end));
|
|
421
|
+
}
|
|
422
|
+
}, []);
|
|
423
|
+
useIsomorphicLayoutEffect(() => {
|
|
424
|
+
latest.current = {
|
|
425
|
+
points,
|
|
426
|
+
smooth,
|
|
427
|
+
events,
|
|
428
|
+
first,
|
|
429
|
+
lastT,
|
|
430
|
+
height,
|
|
431
|
+
overviewHeight,
|
|
432
|
+
empty
|
|
433
|
+
};
|
|
434
|
+
paintRef.current = paint;
|
|
435
|
+
paint();
|
|
436
|
+
});
|
|
437
|
+
const setView = (next) => {
|
|
438
|
+
view.current = next;
|
|
439
|
+
paint();
|
|
440
|
+
settleSoon();
|
|
441
|
+
};
|
|
442
|
+
const glideTo = (next) => {
|
|
443
|
+
flight.current?.();
|
|
444
|
+
const from = view.current;
|
|
445
|
+
flight.current = tween(SMOOTH, (p) => setView([from[0] + (next[0] - from[0]) * p, from[1] + (next[1] - from[1]) * p]), flush);
|
|
446
|
+
};
|
|
447
|
+
const commit = (next, smoothly) => {
|
|
448
|
+
const target = fit(next);
|
|
449
|
+
touched.current = true;
|
|
450
|
+
if (smoothly) glideTo(target);
|
|
451
|
+
else {
|
|
452
|
+
flight.current?.();
|
|
453
|
+
setView(target);
|
|
454
|
+
}
|
|
455
|
+
onRangeChange?.(target);
|
|
456
|
+
};
|
|
457
|
+
const rangeKey = range ? `${range[0]}:${range[1]}` : "";
|
|
458
|
+
useEffect(() => {
|
|
459
|
+
if (!range || empty) return;
|
|
460
|
+
const target = fit(range);
|
|
461
|
+
const now = view.current;
|
|
462
|
+
if (Math.abs(target[0] - now[0]) + Math.abs(target[1] - now[1]) > 1e3) glideTo(target);
|
|
463
|
+
}, [rangeKey, empty]);
|
|
464
|
+
useEffect(() => {
|
|
465
|
+
if (empty) return;
|
|
466
|
+
const target = touched.current || range || defaultRange ? fit(view.current) : whole;
|
|
467
|
+
if (target[0] !== view.current[0] || target[1] !== view.current[1]) {
|
|
468
|
+
view.current = target;
|
|
469
|
+
paint();
|
|
470
|
+
flush();
|
|
471
|
+
}
|
|
472
|
+
}, [
|
|
473
|
+
first,
|
|
474
|
+
lastT,
|
|
475
|
+
empty
|
|
476
|
+
]);
|
|
477
|
+
useEffect(() => () => {
|
|
478
|
+
flight.current?.();
|
|
479
|
+
topLive.current.stop();
|
|
480
|
+
window.clearTimeout(settleTimer.current);
|
|
481
|
+
}, []);
|
|
482
|
+
useIsomorphicLayoutEffect(() => {
|
|
483
|
+
const nodes = [[
|
|
484
|
+
plot.current,
|
|
485
|
+
plotW,
|
|
486
|
+
setPlotWidth
|
|
487
|
+
], [
|
|
488
|
+
strip.current,
|
|
489
|
+
stripW,
|
|
490
|
+
setStripWidth
|
|
491
|
+
]];
|
|
492
|
+
const stops = [];
|
|
493
|
+
for (const [node, store, setter] of nodes) {
|
|
494
|
+
if (!node) continue;
|
|
495
|
+
const read = () => {
|
|
496
|
+
store.current = node.clientWidth;
|
|
497
|
+
setter(node.clientWidth);
|
|
498
|
+
paintRef.current();
|
|
499
|
+
};
|
|
500
|
+
read();
|
|
501
|
+
if (typeof ResizeObserver === "undefined") continue;
|
|
502
|
+
const observer = new ResizeObserver(read);
|
|
503
|
+
observer.observe(node);
|
|
504
|
+
stops.push(() => observer.disconnect());
|
|
505
|
+
}
|
|
506
|
+
return () => stops.forEach((stop) => stop());
|
|
507
|
+
}, []);
|
|
508
|
+
const [drawn, setDrawn] = useState(false);
|
|
509
|
+
useEffect(() => {
|
|
510
|
+
if (empty || drawn) return;
|
|
511
|
+
const node = figure.current;
|
|
512
|
+
if (!node || prefersReducedMotion() || typeof IntersectionObserver === "undefined") {
|
|
513
|
+
setDrawn(true);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
517
|
+
if (!entry?.isIntersecting) return;
|
|
518
|
+
observer.disconnect();
|
|
519
|
+
setDrawn(true);
|
|
520
|
+
}, { threshold: .3 });
|
|
521
|
+
observer.observe(node);
|
|
522
|
+
return () => observer.disconnect();
|
|
523
|
+
}, [drawn, empty]);
|
|
524
|
+
const [active, setActive] = useState(null);
|
|
525
|
+
const [hoverEvent, setHoverEvent] = useState(null);
|
|
526
|
+
const [start, end] = shown;
|
|
527
|
+
const from = Math.max(0, lowerBound(points, start) - 1);
|
|
528
|
+
const to = Math.min(points.length, lowerBound(points, end) + 1);
|
|
529
|
+
const index = active !== null && active >= from && active < to ? active : null;
|
|
530
|
+
const eventAt = index === null ? null : events.find((item) => Math.abs(item.t - points[index].t) < DAY / 2) ?? null;
|
|
531
|
+
const shownEvent = hoverEvent !== null ? events[hoverEvent] ?? null : null;
|
|
532
|
+
const tipOn = index !== null || shownEvent !== null;
|
|
533
|
+
useIsomorphicLayoutEffect(() => {
|
|
534
|
+
activeRef.current = {
|
|
535
|
+
index,
|
|
536
|
+
event: shownEvent ? events.indexOf(shownEvent) : null
|
|
537
|
+
};
|
|
538
|
+
paint();
|
|
539
|
+
});
|
|
540
|
+
const nearest = (clientX) => {
|
|
541
|
+
const rect = plot.current?.getBoundingClientRect();
|
|
542
|
+
if (!rect?.width || empty) return null;
|
|
543
|
+
const [s, e] = view.current;
|
|
544
|
+
const t = s + (clientX - rect.left) / rect.width * (e - s);
|
|
545
|
+
const at = clamp(lowerBound(points, t), 1, points.length - 1);
|
|
546
|
+
const lo = Math.max(0, lowerBound(points, s) - 1);
|
|
547
|
+
const hi = Math.min(points.length, lowerBound(points, e) + 1);
|
|
548
|
+
return clamp(t - points[at - 1].t < points[at].t - t ? at - 1 : at, lo, hi - 1);
|
|
549
|
+
};
|
|
550
|
+
const windowBounds = () => {
|
|
551
|
+
const [s, e] = view.current;
|
|
552
|
+
return [Math.max(0, lowerBound(points, s) - 1), Math.min(points.length, lowerBound(points, e) + 1)];
|
|
553
|
+
};
|
|
554
|
+
const onPlotKey = (event) => {
|
|
555
|
+
if (empty) return;
|
|
556
|
+
if (event.key === "Escape" && index !== null) {
|
|
557
|
+
event.preventDefault();
|
|
558
|
+
setActive(null);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
const [lo, hi] = windowBounds();
|
|
562
|
+
const current = index ?? hi - 1;
|
|
563
|
+
const page = Math.max(1, Math.round((hi - lo) / 8));
|
|
564
|
+
const next = {
|
|
565
|
+
ArrowLeft: current - 1,
|
|
566
|
+
ArrowRight: index === null ? hi - 1 : current + 1,
|
|
567
|
+
PageDown: current - page,
|
|
568
|
+
PageUp: current + page,
|
|
569
|
+
Home: lo,
|
|
570
|
+
End: hi - 1
|
|
571
|
+
}[event.key];
|
|
572
|
+
if (next === void 0) return;
|
|
573
|
+
event.preventDefault();
|
|
574
|
+
setActive(clamp(next, lo, hi - 1));
|
|
575
|
+
};
|
|
576
|
+
const drag = useRef(null);
|
|
577
|
+
const toTime = (clientX) => {
|
|
578
|
+
const rect = strip.current?.getBoundingClientRect();
|
|
579
|
+
return rect?.width ? first + clamp((clientX - rect.left) / rect.width, 0, 1) * (lastT - first) : first;
|
|
580
|
+
};
|
|
581
|
+
const onStripDown = (event) => {
|
|
582
|
+
if (empty || event.button > 0) return;
|
|
583
|
+
const rect = strip.current.getBoundingClientRect();
|
|
584
|
+
const px = event.clientX - rect.left;
|
|
585
|
+
const [s, e] = view.current;
|
|
586
|
+
const wx0 = (s - first) / (lastT - first) * rect.width;
|
|
587
|
+
const wx1 = (e - first) / (lastT - first) * rect.width;
|
|
588
|
+
const mode = Math.abs(px - wx0) <= GRIP && px <= (wx0 + wx1) / 2 ? "start" : Math.abs(px - wx1) <= GRIP ? "end" : px > wx0 && px < wx1 ? "pan" : "new";
|
|
589
|
+
drag.current = {
|
|
590
|
+
mode,
|
|
591
|
+
pointerX: event.clientX,
|
|
592
|
+
from: view.current,
|
|
593
|
+
anchor: toTime(event.clientX),
|
|
594
|
+
moved: false
|
|
595
|
+
};
|
|
596
|
+
try {
|
|
597
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
598
|
+
} catch {}
|
|
599
|
+
flight.current?.();
|
|
600
|
+
};
|
|
601
|
+
const onStripMove = (event) => {
|
|
602
|
+
const current = drag.current;
|
|
603
|
+
if (!current) return;
|
|
604
|
+
if (!current.moved && Math.abs(event.clientX - current.pointerX) < 3) return;
|
|
605
|
+
current.moved = true;
|
|
606
|
+
const t = toTime(event.clientX);
|
|
607
|
+
const shift = t - current.anchor;
|
|
608
|
+
const [s, e] = current.from;
|
|
609
|
+
const least = Math.min(minSpan, lastT - first);
|
|
610
|
+
const next = current.mode === "pan" ? [s + shift, e + shift] : current.mode === "start" ? [clamp(t, first, e - least), e] : current.mode === "end" ? [s, clamp(t, s + least, lastT)] : [Math.min(current.anchor, t), Math.max(current.anchor, t)];
|
|
611
|
+
if (current.mode === "new" && next[1] - next[0] < least) {
|
|
612
|
+
if (t >= current.anchor) next[1] = next[0] + least;
|
|
613
|
+
else next[0] = next[1] - least;
|
|
614
|
+
}
|
|
615
|
+
commit(next, false);
|
|
616
|
+
};
|
|
617
|
+
const onStripUp = () => {
|
|
618
|
+
const current = drag.current;
|
|
619
|
+
drag.current = null;
|
|
620
|
+
if (current?.moved) flush();
|
|
621
|
+
if (!current || current.moved || current.mode !== "new") return;
|
|
622
|
+
const half = (view.current[1] - view.current[0]) / 2;
|
|
623
|
+
commit([current.anchor - half, current.anchor + half], true);
|
|
624
|
+
};
|
|
625
|
+
const reset = () => commit(whole, true);
|
|
626
|
+
const stepSize = Math.max(DAY, (lastT - first) / Math.max(1, points.length - 1));
|
|
627
|
+
const onHandleKey = (event, edge) => {
|
|
628
|
+
const [s, e] = view.current;
|
|
629
|
+
const win = e - s;
|
|
630
|
+
const big = event.shiftKey || event.key.startsWith("Page");
|
|
631
|
+
const delta = {
|
|
632
|
+
ArrowLeft: -1,
|
|
633
|
+
ArrowDown: -1,
|
|
634
|
+
PageDown: -1,
|
|
635
|
+
ArrowRight: 1,
|
|
636
|
+
ArrowUp: 1,
|
|
637
|
+
PageUp: 1
|
|
638
|
+
}[event.key];
|
|
639
|
+
let next = null;
|
|
640
|
+
if (event.key === "Escape" || event.key === "0") next = whole;
|
|
641
|
+
else if (edge === "window" && (event.key === "+" || event.key === "=")) next = [s + win / 4, e - win / 4];
|
|
642
|
+
else if (edge === "window" && (event.key === "-" || event.key === "_")) next = [s - win / 2, e + win / 2];
|
|
643
|
+
else if (event.key === "Home") next = edge === "end" ? [s, s + Math.min(minSpan, lastT - first)] : [first, edge === "window" ? first + win : e];
|
|
644
|
+
else if (event.key === "End") next = edge === "start" ? [e - Math.min(minSpan, lastT - first), e] : [edge === "window" ? lastT - win : s, lastT];
|
|
645
|
+
else if (delta) {
|
|
646
|
+
const move = delta * (edge === "window" ? win * (big ? .5 : .1) : stepSize * (big ? 10 : 1));
|
|
647
|
+
next = edge === "window" ? [s + move, e + move] : edge === "start" ? [Math.min(s + move, e - minSpan), e] : [s, Math.max(e + move, s + minSpan)];
|
|
648
|
+
}
|
|
649
|
+
if (!next) return;
|
|
650
|
+
event.preventDefault();
|
|
651
|
+
commit(next, false);
|
|
652
|
+
flush();
|
|
653
|
+
};
|
|
654
|
+
const scale = niceTop(useMemo(() => {
|
|
655
|
+
let high = 0;
|
|
656
|
+
for (let at = from; at < to; at++) high = Math.max(high, points[at].value);
|
|
657
|
+
return high;
|
|
658
|
+
}, [
|
|
659
|
+
points,
|
|
660
|
+
from,
|
|
661
|
+
to
|
|
662
|
+
]) * 1.04);
|
|
663
|
+
const detailShown = clamp(((plotWidth && points.length > 1 ? plotWidth * (lastT - first) / (points.length - 1) / Math.max(1, end - start) : plotWidth) - 2.75) / 1.5, 0, 1);
|
|
664
|
+
const ticks = scale.top > 0 ? Array.from({ length: Math.round(scale.top / scale.step) + 1 }, (_, at) => clean(at * scale.step)) : [];
|
|
665
|
+
const xTicks = plotWidth && !empty ? timeTicks(start, end, plotWidth) : [];
|
|
666
|
+
const overview = useMemo(() => {
|
|
667
|
+
if (!stripWidth || empty) return {
|
|
668
|
+
line: "",
|
|
669
|
+
area: "",
|
|
670
|
+
marks: []
|
|
671
|
+
};
|
|
672
|
+
const ox = (t) => (t - first) / (lastT - first || 1) * stripWidth;
|
|
673
|
+
const max = niceTop(smooth.reduce((m, point) => Math.max(m, point.value), 0)).top || 1;
|
|
674
|
+
const oy = (value) => 4 + (1 - value / max) * (overviewHeight - 4);
|
|
675
|
+
const path = linePath(smooth, 0, smooth.length, ox, oy, stripWidth * 4);
|
|
676
|
+
const marks = events.map((item) => {
|
|
677
|
+
const at = clamp(lowerBound(smooth, item.t), 0, smooth.length - 1);
|
|
678
|
+
return {
|
|
679
|
+
t: item.t,
|
|
680
|
+
x: ox(item.t),
|
|
681
|
+
y: oy(smooth[at].value)
|
|
682
|
+
};
|
|
683
|
+
});
|
|
684
|
+
return {
|
|
685
|
+
line: path,
|
|
686
|
+
area: `${path}L${stripWidth},${overviewHeight}L0,${overviewHeight}Z`,
|
|
687
|
+
marks
|
|
688
|
+
};
|
|
689
|
+
}, [
|
|
690
|
+
smooth,
|
|
691
|
+
events,
|
|
692
|
+
stripWidth,
|
|
693
|
+
overviewHeight,
|
|
694
|
+
empty,
|
|
695
|
+
first,
|
|
696
|
+
lastT
|
|
697
|
+
]);
|
|
698
|
+
const format = (value) => `${formatValue(value)}${unit ? ` ${unit}` : ""}`;
|
|
699
|
+
const reading = index === null ? null : points[index];
|
|
700
|
+
const previous = index !== null && index >= 7 ? points[index - 7] : null;
|
|
701
|
+
const change = reading && previous && previous.value ? (reading.value - previous.value) / previous.value : null;
|
|
702
|
+
const windowText = `${SHORT_DATE.format(start)} to ${SHORT_DATE.format(end)}`;
|
|
703
|
+
const announce = reading ? `${formatDate(new Date(reading.t))}, ${format(reading.value)}${eventAt ? `. ${eventAt.label}` : ""}` : "";
|
|
704
|
+
const summary = empty ? `${label}. ${emptyLabel}.` : `${label}, ${SHORT_DATE.format(first)} to ${SHORT_DATE.format(lastT)}. Showing ${windowText}.`;
|
|
705
|
+
const rows = empty ? [] : points.slice(lowerBound(points, start), lowerBound(points, end + 1));
|
|
706
|
+
return /* @__PURE__ */ jsxs("figure", {
|
|
707
|
+
ref: refs,
|
|
708
|
+
"data-slot": "brush-chart",
|
|
709
|
+
"aria-label": label,
|
|
710
|
+
className: cn("brush-chart", className),
|
|
711
|
+
style: { "--series": "var(--color-chart-1)" },
|
|
712
|
+
children: [
|
|
713
|
+
/* @__PURE__ */ jsxs("div", {
|
|
714
|
+
"data-slot": "brush-chart-chart",
|
|
715
|
+
"data-scrubbing": tipOn || void 0,
|
|
716
|
+
className: "brush-chart-frame",
|
|
717
|
+
children: [
|
|
718
|
+
/* @__PURE__ */ jsxs("div", {
|
|
719
|
+
ref: plot,
|
|
720
|
+
"data-slot": "brush-chart-plot",
|
|
721
|
+
role: "slider",
|
|
722
|
+
tabIndex: empty ? -1 : 0,
|
|
723
|
+
"aria-label": `${label}, ${windowText}. Use left and right arrows to read values.`,
|
|
724
|
+
"aria-orientation": "horizontal",
|
|
725
|
+
"aria-valuemin": 1,
|
|
726
|
+
"aria-valuemax": Math.max(1, points.length),
|
|
727
|
+
"aria-valuenow": (index ?? Math.max(0, to - 1)) + 1,
|
|
728
|
+
"aria-valuetext": reading ? announce : summary,
|
|
729
|
+
onPointerMove: (event) => {
|
|
730
|
+
if (event.pointerType === "mouse" || event.buttons) setActive(nearest(event.clientX));
|
|
731
|
+
},
|
|
732
|
+
onPointerDown: (event) => {
|
|
733
|
+
if (event.pointerType === "mouse") return;
|
|
734
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
735
|
+
setActive(nearest(event.clientX));
|
|
736
|
+
},
|
|
737
|
+
onPointerUp: (event) => {
|
|
738
|
+
if (event.pointerType !== "mouse") setActive(null);
|
|
739
|
+
},
|
|
740
|
+
onPointerCancel: () => setActive(null),
|
|
741
|
+
onPointerLeave: (event) => {
|
|
742
|
+
if (event.pointerType === "mouse") setActive(null);
|
|
743
|
+
},
|
|
744
|
+
onKeyDown: onPlotKey,
|
|
745
|
+
onBlur: () => setActive(null),
|
|
746
|
+
onDoubleClick: reset,
|
|
747
|
+
onFocus: (event) => {
|
|
748
|
+
if (event.currentTarget.matches(":focus-visible") && !empty) setActive((current) => current ?? windowBounds()[1] - 1);
|
|
749
|
+
},
|
|
750
|
+
className: "line-chart-plot",
|
|
751
|
+
style: { height },
|
|
752
|
+
children: [
|
|
753
|
+
/* @__PURE__ */ jsxs("svg", {
|
|
754
|
+
ref: plotSvg,
|
|
755
|
+
width: "100%",
|
|
756
|
+
height,
|
|
757
|
+
"aria-hidden": "true",
|
|
758
|
+
focusable: "false",
|
|
759
|
+
className: "block overflow-visible",
|
|
760
|
+
children: [
|
|
761
|
+
/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("clipPath", {
|
|
762
|
+
id: `${uid}-draw`,
|
|
763
|
+
clipPathUnits: "userSpaceOnUse",
|
|
764
|
+
children: /* @__PURE__ */ jsx("rect", {
|
|
765
|
+
className: "line-chart-reveal",
|
|
766
|
+
x: -8,
|
|
767
|
+
y: -16,
|
|
768
|
+
height: height + 32,
|
|
769
|
+
style: { width: drawn ? plotWidth + 16 : 0 }
|
|
770
|
+
})
|
|
771
|
+
}) }),
|
|
772
|
+
ticks.slice(1).map((value) => /* @__PURE__ */ jsx("line", {
|
|
773
|
+
"data-grid": value,
|
|
774
|
+
className: "line-chart-grid",
|
|
775
|
+
x1: 0,
|
|
776
|
+
x2: "100%"
|
|
777
|
+
}, value)),
|
|
778
|
+
/* @__PURE__ */ jsx("line", {
|
|
779
|
+
className: "line-chart-baseline",
|
|
780
|
+
x1: 0,
|
|
781
|
+
x2: "100%",
|
|
782
|
+
y1: height - .5,
|
|
783
|
+
y2: height - .5
|
|
784
|
+
}),
|
|
785
|
+
events.map((item, at) => /* @__PURE__ */ jsx("line", {
|
|
786
|
+
"data-rule": at,
|
|
787
|
+
"data-active": shownEvent === item || eventAt === item || void 0,
|
|
788
|
+
className: "brush-chart-rule",
|
|
789
|
+
y1: 18,
|
|
790
|
+
y2: height
|
|
791
|
+
}, item.t)),
|
|
792
|
+
/* @__PURE__ */ jsxs("g", {
|
|
793
|
+
clipPath: `url(#${uid}-draw)`,
|
|
794
|
+
children: [
|
|
795
|
+
/* @__PURE__ */ jsx("path", {
|
|
796
|
+
"data-part": "band",
|
|
797
|
+
className: "brush-chart-band"
|
|
798
|
+
}),
|
|
799
|
+
/* @__PURE__ */ jsx("path", {
|
|
800
|
+
"data-part": "area",
|
|
801
|
+
className: "line-chart-area"
|
|
802
|
+
}),
|
|
803
|
+
/* @__PURE__ */ jsx("path", {
|
|
804
|
+
"data-part": "trend",
|
|
805
|
+
className: "brush-chart-trend"
|
|
806
|
+
}),
|
|
807
|
+
/* @__PURE__ */ jsx("path", {
|
|
808
|
+
"data-part": "line",
|
|
809
|
+
className: "line-chart-line"
|
|
810
|
+
})
|
|
811
|
+
]
|
|
812
|
+
}),
|
|
813
|
+
/* @__PURE__ */ jsx("line", {
|
|
814
|
+
"data-part": "crosshair",
|
|
815
|
+
className: "line-chart-crosshair",
|
|
816
|
+
y1: 18,
|
|
817
|
+
y2: height
|
|
818
|
+
}),
|
|
819
|
+
/* @__PURE__ */ jsx("circle", {
|
|
820
|
+
"data-part": "dot",
|
|
821
|
+
r: 4,
|
|
822
|
+
className: "line-chart-dot"
|
|
823
|
+
})
|
|
824
|
+
]
|
|
825
|
+
}),
|
|
826
|
+
events.map((item, at) => /* @__PURE__ */ jsxs("span", {
|
|
827
|
+
"data-event": at,
|
|
828
|
+
"data-active": shownEvent === item || eventAt === item || void 0,
|
|
829
|
+
"aria-hidden": "true",
|
|
830
|
+
className: "brush-chart-event",
|
|
831
|
+
onPointerEnter: () => setHoverEvent(at),
|
|
832
|
+
onPointerLeave: () => setHoverEvent(null),
|
|
833
|
+
onPointerDown: (event) => event.stopPropagation(),
|
|
834
|
+
children: [/* @__PURE__ */ jsx("span", { className: "brush-chart-event-dot" }), /* @__PURE__ */ jsx("span", {
|
|
835
|
+
className: "brush-chart-event-label",
|
|
836
|
+
children: item.label
|
|
837
|
+
})]
|
|
838
|
+
}, item.t)),
|
|
839
|
+
/* @__PURE__ */ jsx("div", {
|
|
840
|
+
ref: tip,
|
|
841
|
+
"aria-hidden": "true",
|
|
842
|
+
"data-slot": "brush-chart-tooltip",
|
|
843
|
+
className: "line-chart-tooltip",
|
|
844
|
+
children: shownEvent ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
845
|
+
/* @__PURE__ */ jsx("p", {
|
|
846
|
+
className: "m-0 mb-0.5 whitespace-nowrap text-xs tabular-nums text-text-secondary",
|
|
847
|
+
children: formatDate(new Date(shownEvent.t))
|
|
848
|
+
}),
|
|
849
|
+
/* @__PURE__ */ jsx("p", {
|
|
850
|
+
className: "m-0 text-base font-medium tabular-nums text-foreground",
|
|
851
|
+
children: shownEvent.label
|
|
852
|
+
}),
|
|
853
|
+
shownEvent.description ? /* @__PURE__ */ jsx("p", {
|
|
854
|
+
className: "m-0 text-xs text-text-secondary",
|
|
855
|
+
children: shownEvent.description
|
|
856
|
+
}) : null
|
|
857
|
+
] }) : reading ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
858
|
+
/* @__PURE__ */ jsx("p", {
|
|
859
|
+
className: "m-0 mb-0.5 whitespace-nowrap text-xs tabular-nums text-text-secondary",
|
|
860
|
+
children: formatDate(new Date(reading.t))
|
|
861
|
+
}),
|
|
862
|
+
/* @__PURE__ */ jsx("p", {
|
|
863
|
+
className: "m-0 text-base font-medium tabular-nums text-foreground",
|
|
864
|
+
children: format(reading.value)
|
|
865
|
+
}),
|
|
866
|
+
index !== null && detailShown < 1 && smooth[index] ? /* @__PURE__ */ jsx("p", {
|
|
867
|
+
className: "m-0 text-xs tabular-nums text-text-secondary",
|
|
868
|
+
children: `${formatValue(Math.round(smooth[index].value))} seven day average`
|
|
869
|
+
}) : null,
|
|
870
|
+
change !== null ? /* @__PURE__ */ jsx("p", {
|
|
871
|
+
className: "m-0 text-xs tabular-nums text-text-secondary",
|
|
872
|
+
children: `${change >= 0 ? "+" : "−"}${Math.abs(change * 100).toFixed(1)}% vs a week earlier`
|
|
873
|
+
}) : null,
|
|
874
|
+
eventAt ? /* @__PURE__ */ jsxs("p", {
|
|
875
|
+
className: "m-0 mt-1 flex items-center gap-1.5 text-xs text-foreground",
|
|
876
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
877
|
+
"aria-hidden": "true",
|
|
878
|
+
className: "brush-chart-tip-dot"
|
|
879
|
+
}), eventAt.label]
|
|
880
|
+
}) : null
|
|
881
|
+
] }) : null
|
|
882
|
+
}),
|
|
883
|
+
empty ? /* @__PURE__ */ jsx("p", {
|
|
884
|
+
className: "absolute inset-0 m-0 grid place-items-center text-sm text-muted-foreground pointer-events-none motion-safe:animate-fade-in",
|
|
885
|
+
children: emptyLabel
|
|
886
|
+
}) : null
|
|
887
|
+
]
|
|
888
|
+
}),
|
|
889
|
+
/* @__PURE__ */ jsx("div", {
|
|
890
|
+
ref: gutter,
|
|
891
|
+
"aria-hidden": "true",
|
|
892
|
+
className: "relative min-w-0 [grid-area:1/2]",
|
|
893
|
+
children: ticks.map((value) => /* @__PURE__ */ jsx("span", {
|
|
894
|
+
"data-tick": value,
|
|
895
|
+
className: "line-chart-tick motion-safe:animate-fade-in",
|
|
896
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
897
|
+
className: "block -translate-y-1/2 text-xs leading-none tabular-nums text-muted-foreground",
|
|
898
|
+
children: formatTick(value)
|
|
899
|
+
})
|
|
900
|
+
}, value))
|
|
901
|
+
}),
|
|
902
|
+
/* @__PURE__ */ jsx("div", {
|
|
903
|
+
ref: axis,
|
|
904
|
+
"aria-hidden": "true",
|
|
905
|
+
className: "relative min-w-0 [grid-area:2/1]",
|
|
906
|
+
children: xTicks.map((tick) => /* @__PURE__ */ jsx("span", {
|
|
907
|
+
"data-t": tick.t,
|
|
908
|
+
className: "absolute top-2 whitespace-nowrap text-xs leading-none tabular-nums text-muted-foreground",
|
|
909
|
+
children: tick.label
|
|
910
|
+
}, tick.t))
|
|
911
|
+
}),
|
|
912
|
+
/* @__PURE__ */ jsxs("div", {
|
|
913
|
+
ref: strip,
|
|
914
|
+
"data-slot": "brush-chart-overview",
|
|
915
|
+
className: "brush-chart-strip",
|
|
916
|
+
style: { height: overviewHeight },
|
|
917
|
+
onPointerDown: onStripDown,
|
|
918
|
+
onPointerMove: onStripMove,
|
|
919
|
+
onPointerUp: onStripUp,
|
|
920
|
+
onPointerCancel: () => {
|
|
921
|
+
drag.current = null;
|
|
922
|
+
},
|
|
923
|
+
onDoubleClick: reset,
|
|
924
|
+
children: [/* @__PURE__ */ jsxs("svg", {
|
|
925
|
+
width: "100%",
|
|
926
|
+
height: overviewHeight,
|
|
927
|
+
"aria-hidden": "true",
|
|
928
|
+
focusable: "false",
|
|
929
|
+
className: "brush-chart-overview-svg",
|
|
930
|
+
children: [
|
|
931
|
+
/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("clipPath", {
|
|
932
|
+
id: `${uid}-window`,
|
|
933
|
+
children: /* @__PURE__ */ jsx("rect", {
|
|
934
|
+
"data-part": "window-clip",
|
|
935
|
+
y: -2,
|
|
936
|
+
height: overviewHeight + 4
|
|
937
|
+
})
|
|
938
|
+
}) }),
|
|
939
|
+
/* @__PURE__ */ jsx("path", {
|
|
940
|
+
d: overview.area,
|
|
941
|
+
className: "brush-chart-overview-area"
|
|
942
|
+
}),
|
|
943
|
+
/* @__PURE__ */ jsx("path", {
|
|
944
|
+
d: overview.line,
|
|
945
|
+
className: "brush-chart-overview-line"
|
|
946
|
+
}),
|
|
947
|
+
/* @__PURE__ */ jsxs("g", {
|
|
948
|
+
clipPath: `url(#${uid}-window)`,
|
|
949
|
+
children: [/* @__PURE__ */ jsx("path", {
|
|
950
|
+
d: overview.area,
|
|
951
|
+
className: "brush-chart-overview-area-in"
|
|
952
|
+
}), /* @__PURE__ */ jsx("path", {
|
|
953
|
+
d: overview.line,
|
|
954
|
+
className: "brush-chart-overview-line-in"
|
|
955
|
+
})]
|
|
956
|
+
}),
|
|
957
|
+
overview.marks.map((mark) => /* @__PURE__ */ jsx("circle", {
|
|
958
|
+
cx: mark.x,
|
|
959
|
+
cy: mark.y,
|
|
960
|
+
r: 2.5,
|
|
961
|
+
className: "brush-chart-overview-event"
|
|
962
|
+
}, mark.t))
|
|
963
|
+
]
|
|
964
|
+
}), empty ? null : /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
965
|
+
/* @__PURE__ */ jsx("div", {
|
|
966
|
+
"data-edge": "start",
|
|
967
|
+
className: "brush-chart-shade"
|
|
968
|
+
}),
|
|
969
|
+
/* @__PURE__ */ jsx("div", {
|
|
970
|
+
"data-edge": "end",
|
|
971
|
+
className: "brush-chart-shade"
|
|
972
|
+
}),
|
|
973
|
+
/* @__PURE__ */ jsx("div", {
|
|
974
|
+
"data-part": "window",
|
|
975
|
+
role: "slider",
|
|
976
|
+
tabIndex: 0,
|
|
977
|
+
"aria-label": `${label} window`,
|
|
978
|
+
"aria-roledescription": "range window",
|
|
979
|
+
onKeyDown: (event) => onHandleKey(event, "window"),
|
|
980
|
+
className: "brush-chart-window-key"
|
|
981
|
+
}),
|
|
982
|
+
/* @__PURE__ */ jsx("div", {
|
|
983
|
+
"data-part": "window-box",
|
|
984
|
+
"aria-hidden": "true",
|
|
985
|
+
className: "brush-chart-window"
|
|
986
|
+
}),
|
|
987
|
+
/* @__PURE__ */ jsx("div", {
|
|
988
|
+
"data-handle": "start",
|
|
989
|
+
role: "slider",
|
|
990
|
+
tabIndex: 0,
|
|
991
|
+
"aria-label": "Window start",
|
|
992
|
+
onKeyDown: (event) => onHandleKey(event, "start"),
|
|
993
|
+
className: "brush-chart-handle",
|
|
994
|
+
children: /* @__PURE__ */ jsx("span", { className: "brush-chart-grip" })
|
|
995
|
+
}),
|
|
996
|
+
/* @__PURE__ */ jsx("div", {
|
|
997
|
+
"data-handle": "end",
|
|
998
|
+
role: "slider",
|
|
999
|
+
tabIndex: 0,
|
|
1000
|
+
"aria-label": "Window end",
|
|
1001
|
+
onKeyDown: (event) => onHandleKey(event, "end"),
|
|
1002
|
+
className: "brush-chart-handle",
|
|
1003
|
+
children: /* @__PURE__ */ jsx("span", { className: "brush-chart-grip" })
|
|
1004
|
+
})
|
|
1005
|
+
] })]
|
|
1006
|
+
})
|
|
1007
|
+
]
|
|
1008
|
+
}),
|
|
1009
|
+
/* @__PURE__ */ jsx("p", {
|
|
1010
|
+
className: "sr-only",
|
|
1011
|
+
"aria-live": "polite",
|
|
1012
|
+
"aria-atomic": "true",
|
|
1013
|
+
children: announce
|
|
1014
|
+
}),
|
|
1015
|
+
/* @__PURE__ */ jsx("p", {
|
|
1016
|
+
className: "sr-only",
|
|
1017
|
+
children: summary
|
|
1018
|
+
}),
|
|
1019
|
+
empty ? null : /* @__PURE__ */ jsx("div", {
|
|
1020
|
+
className: "sr-only",
|
|
1021
|
+
children: /* @__PURE__ */ jsxs("table", { children: [
|
|
1022
|
+
/* @__PURE__ */ jsx("caption", { children: `${label}, ${windowText}` }),
|
|
1023
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
1024
|
+
/* @__PURE__ */ jsx("th", {
|
|
1025
|
+
scope: "col",
|
|
1026
|
+
children: "Date"
|
|
1027
|
+
}),
|
|
1028
|
+
/* @__PURE__ */ jsx("th", {
|
|
1029
|
+
scope: "col",
|
|
1030
|
+
children: label
|
|
1031
|
+
}),
|
|
1032
|
+
/* @__PURE__ */ jsx("th", {
|
|
1033
|
+
scope: "col",
|
|
1034
|
+
children: "Event"
|
|
1035
|
+
})
|
|
1036
|
+
] }) }),
|
|
1037
|
+
/* @__PURE__ */ jsx("tbody", { children: rows.map((point) => {
|
|
1038
|
+
const event = events.find((item) => Math.abs(item.t - point.t) < DAY / 2);
|
|
1039
|
+
return /* @__PURE__ */ jsxs("tr", { children: [
|
|
1040
|
+
/* @__PURE__ */ jsx("th", {
|
|
1041
|
+
scope: "row",
|
|
1042
|
+
children: SHORT_DATE.format(point.t)
|
|
1043
|
+
}),
|
|
1044
|
+
/* @__PURE__ */ jsx("td", { children: format(point.value) }),
|
|
1045
|
+
/* @__PURE__ */ jsx("td", { children: event ? `${event.label}${event.description ? `. ${event.description}` : ""}` : "" })
|
|
1046
|
+
] }, point.t);
|
|
1047
|
+
}) })
|
|
1048
|
+
] })
|
|
1049
|
+
})
|
|
1050
|
+
]
|
|
1051
|
+
});
|
|
1052
|
+
});
|
|
1053
|
+
//#endregion
|
|
1054
|
+
export { BrushChart };
|
|
1055
|
+
|
|
1056
|
+
//# sourceMappingURL=brush-chart.js.map
|