panelui-native 0.68.0 → 0.73.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/README.md +13 -2
- package/lib/module/components/accordion/index.js +38 -13
- package/lib/module/components/accordion/index.js.map +1 -1
- package/lib/module/components/area-chart/index.js +1 -0
- package/lib/module/components/area-chart/index.js.map +1 -1
- package/lib/module/components/breadcrumb/index.js +2 -1
- package/lib/module/components/breadcrumb/index.js.map +1 -1
- package/lib/module/components/button/index.js +21 -12
- package/lib/module/components/button/index.js.map +1 -1
- package/lib/module/components/carousel/index.js +1 -1
- package/lib/module/components/carousel/index.js.map +1 -1
- package/lib/module/components/chip/index.js +2 -2
- package/lib/module/components/chip/index.js.map +1 -1
- package/lib/module/components/code-block/index.js.map +1 -1
- package/lib/module/components/collapsible/index.js +284 -0
- package/lib/module/components/collapsible/index.js.map +1 -0
- package/lib/module/components/color-picker/index.js.map +1 -1
- package/lib/module/components/fab/index.js +7 -6
- package/lib/module/components/fab/index.js.map +1 -1
- package/lib/module/components/flow/index.js +67 -4
- package/lib/module/components/flow/index.js.map +1 -1
- package/lib/module/components/frame/index.js +2 -2
- package/lib/module/components/frame/index.js.map +1 -1
- package/lib/module/components/grid-item/index.js +2 -2
- package/lib/module/components/grid-item/index.js.map +1 -1
- package/lib/module/components/heatmap-chart/index.js +1 -0
- package/lib/module/components/heatmap-chart/index.js.map +1 -1
- package/lib/module/components/hex-chart/index.js +1 -0
- package/lib/module/components/hex-chart/index.js.map +1 -1
- package/lib/module/components/input-group/index.js +7 -2
- package/lib/module/components/input-group/index.js.map +1 -1
- package/lib/module/components/item/index.js +2 -2
- package/lib/module/components/item/index.js.map +1 -1
- package/lib/module/components/line-chart/index.js +1 -0
- package/lib/module/components/line-chart/index.js.map +1 -1
- package/lib/module/components/live-line-chart/index.js +103 -9
- package/lib/module/components/live-line-chart/index.js.map +1 -1
- package/lib/module/components/live-line-chart/live-line-accessibility.js +47 -0
- package/lib/module/components/live-line-chart/live-line-accessibility.js.map +1 -0
- package/lib/module/components/live-line-chart/live-line-lifecycle.js +33 -0
- package/lib/module/components/live-line-chart/live-line-lifecycle.js.map +1 -0
- package/lib/module/components/marker/index.js +2 -2
- package/lib/module/components/marker/index.js.map +1 -1
- package/lib/module/components/marquee/index.js +191 -0
- package/lib/module/components/marquee/index.js.map +1 -0
- package/lib/module/components/marquee/marquee-math.js +39 -0
- package/lib/module/components/marquee/marquee-math.js.map +1 -0
- package/lib/module/components/menu/index.js +11 -3
- package/lib/module/components/menu/index.js.map +1 -1
- package/lib/module/components/meter/index.js +38 -20
- package/lib/module/components/meter/index.js.map +1 -1
- package/lib/module/components/meter/meter-scale.js +62 -8
- package/lib/module/components/meter/meter-scale.js.map +1 -1
- package/lib/module/components/pagination/index.js +27 -11
- package/lib/module/components/pagination/index.js.map +1 -1
- package/lib/module/components/panelside/index.js +7 -2
- package/lib/module/components/panelside/index.js.map +1 -1
- package/lib/module/components/planner/index.js +159 -64
- package/lib/module/components/planner/index.js.map +1 -1
- package/lib/module/components/planner/planner-announcement.js +30 -0
- package/lib/module/components/planner/planner-announcement.js.map +1 -0
- package/lib/module/components/planner/planner-entries.js +15 -7
- package/lib/module/components/planner/planner-entries.js.map +1 -1
- package/lib/module/components/planner/planner-grid-navigation.js +33 -0
- package/lib/module/components/planner/planner-grid-navigation.js.map +1 -0
- package/lib/module/components/planner/planner-lifecycle.js +45 -0
- package/lib/module/components/planner/planner-lifecycle.js.map +1 -0
- package/lib/module/components/plot/index.js +105 -42
- package/lib/module/components/plot/index.js.map +1 -1
- package/lib/module/components/popover/index.js +2 -2
- package/lib/module/components/popover/index.js.map +1 -1
- package/lib/module/components/post/index.js +4 -4
- package/lib/module/components/post/index.js.map +1 -1
- package/lib/module/components/search-bar/index.js +331 -0
- package/lib/module/components/search-bar/index.js.map +1 -0
- package/lib/module/components/table/index.js +3 -4
- package/lib/module/components/table/index.js.map +1 -1
- package/lib/module/components/timeline/index.js +288 -39
- package/lib/module/components/timeline/index.js.map +1 -1
- package/lib/module/components/toggle-button/index.js +1 -1
- package/lib/module/components/toggle-button/index.js.map +1 -1
- package/lib/module/components/tree/index.js +13 -8
- package/lib/module/components/tree/index.js.map +1 -1
- package/lib/module/hooks/use-keyboard-avoidance.js.map +1 -1
- package/lib/module/index.js +3 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/primitives/focus-restoration-store.js +52 -0
- package/lib/module/primitives/focus-restoration-store.js.map +1 -0
- package/lib/module/primitives/portal.js +36 -2
- package/lib/module/primitives/portal.js.map +1 -1
- package/lib/module/utils/chart.js +50 -7
- package/lib/module/utils/chart.js.map +1 -1
- package/lib/typescript/src/components/accordion/index.d.ts +6 -2
- package/lib/typescript/src/components/accordion/index.d.ts.map +1 -1
- package/lib/typescript/src/components/area-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/breadcrumb/index.d.ts.map +1 -1
- package/lib/typescript/src/components/button/index.d.ts.map +1 -1
- package/lib/typescript/src/components/code-block/index.d.ts +3 -3
- package/lib/typescript/src/components/code-block/index.d.ts.map +1 -1
- package/lib/typescript/src/components/collapsible/index.d.ts +76 -0
- package/lib/typescript/src/components/collapsible/index.d.ts.map +1 -0
- package/lib/typescript/src/components/color-picker/index.d.ts.map +1 -1
- package/lib/typescript/src/components/fab/index.d.ts +4 -2
- package/lib/typescript/src/components/fab/index.d.ts.map +1 -1
- package/lib/typescript/src/components/flow/index.d.ts +7 -1
- package/lib/typescript/src/components/flow/index.d.ts.map +1 -1
- package/lib/typescript/src/components/heatmap-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/hex-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/input-group/index.d.ts.map +1 -1
- package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/live-line-chart/index.d.ts +21 -7
- package/lib/typescript/src/components/live-line-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/live-line-chart/live-line-accessibility.d.ts +22 -0
- package/lib/typescript/src/components/live-line-chart/live-line-accessibility.d.ts.map +1 -0
- package/lib/typescript/src/components/live-line-chart/live-line-lifecycle.d.ts +23 -0
- package/lib/typescript/src/components/live-line-chart/live-line-lifecycle.d.ts.map +1 -0
- package/lib/typescript/src/components/marquee/index.d.ts +73 -0
- package/lib/typescript/src/components/marquee/index.d.ts.map +1 -0
- package/lib/typescript/src/components/marquee/marquee-math.d.ts +21 -0
- package/lib/typescript/src/components/marquee/marquee-math.d.ts.map +1 -0
- package/lib/typescript/src/components/menu/index.d.ts +1 -1
- package/lib/typescript/src/components/menu/index.d.ts.map +1 -1
- package/lib/typescript/src/components/meter/index.d.ts +7 -4
- package/lib/typescript/src/components/meter/index.d.ts.map +1 -1
- package/lib/typescript/src/components/meter/meter-scale.d.ts +39 -0
- package/lib/typescript/src/components/meter/meter-scale.d.ts.map +1 -1
- package/lib/typescript/src/components/pagination/index.d.ts.map +1 -1
- package/lib/typescript/src/components/panelside/index.d.ts +1 -1
- package/lib/typescript/src/components/panelside/index.d.ts.map +1 -1
- package/lib/typescript/src/components/plan/index.d.ts +2 -2
- package/lib/typescript/src/components/planner/index.d.ts.map +1 -1
- package/lib/typescript/src/components/planner/planner-announcement.d.ts +15 -0
- package/lib/typescript/src/components/planner/planner-announcement.d.ts.map +1 -0
- package/lib/typescript/src/components/planner/planner-entries.d.ts +1 -1
- package/lib/typescript/src/components/planner/planner-entries.d.ts.map +1 -1
- package/lib/typescript/src/components/planner/planner-grid-navigation.d.ts +4 -0
- package/lib/typescript/src/components/planner/planner-grid-navigation.d.ts.map +1 -0
- package/lib/typescript/src/components/planner/planner-lifecycle.d.ts +20 -0
- package/lib/typescript/src/components/planner/planner-lifecycle.d.ts.map +1 -0
- package/lib/typescript/src/components/plot/index.d.ts +59 -5
- package/lib/typescript/src/components/plot/index.d.ts.map +1 -1
- package/lib/typescript/src/components/search-bar/index.d.ts +216 -0
- package/lib/typescript/src/components/search-bar/index.d.ts.map +1 -0
- package/lib/typescript/src/components/timeline/index.d.ts +35 -2
- package/lib/typescript/src/components/timeline/index.d.ts.map +1 -1
- package/lib/typescript/src/components/tree/index.d.ts +3 -4
- package/lib/typescript/src/components/tree/index.d.ts.map +1 -1
- package/lib/typescript/src/hooks/use-keyboard-avoidance.d.ts +2 -2
- package/lib/typescript/src/hooks/use-keyboard-avoidance.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +4 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/primitives/focus-restoration-store.d.ts +24 -0
- package/lib/typescript/src/primitives/focus-restoration-store.d.ts.map +1 -0
- package/lib/typescript/src/primitives/portal.d.ts +4 -0
- package/lib/typescript/src/primitives/portal.d.ts.map +1 -1
- package/lib/typescript/src/utils/chart.d.ts +28 -3
- package/lib/typescript/src/utils/chart.d.ts.map +1 -1
- package/package.json +36 -3
- package/src/components/accordion/index.tsx +48 -15
- package/src/components/area-chart/index.tsx +8 -0
- package/src/components/breadcrumb/index.tsx +2 -1
- package/src/components/button/index.tsx +14 -6
- package/src/components/carousel/index.tsx +1 -1
- package/src/components/chip/index.tsx +2 -2
- package/src/components/code-block/index.tsx +3 -3
- package/src/components/collapsible/index.tsx +287 -0
- package/src/components/color-picker/index.tsx +3 -2
- package/src/components/fab/index.tsx +21 -11
- package/src/components/flow/index.tsx +94 -15
- package/src/components/frame/index.tsx +2 -2
- package/src/components/grid-item/index.tsx +2 -2
- package/src/components/heatmap-chart/index.tsx +7 -1
- package/src/components/hex-chart/index.tsx +7 -1
- package/src/components/input-group/index.tsx +7 -2
- package/src/components/item/index.tsx +2 -2
- package/src/components/line-chart/index.tsx +9 -0
- package/src/components/live-line-chart/index.tsx +130 -13
- package/src/components/live-line-chart/live-line-accessibility.ts +61 -0
- package/src/components/live-line-chart/live-line-lifecycle.ts +63 -0
- package/src/components/marker/index.tsx +2 -2
- package/src/components/marquee/index.tsx +234 -0
- package/src/components/marquee/marquee-math.ts +37 -0
- package/src/components/menu/index.tsx +11 -3
- package/src/components/meter/index.tsx +49 -26
- package/src/components/meter/meter-scale.ts +84 -8
- package/src/components/pagination/index.tsx +26 -13
- package/src/components/panelside/index.tsx +7 -2
- package/src/components/plan/index.tsx +2 -2
- package/src/components/planner/index.tsx +202 -76
- package/src/components/planner/planner-announcement.ts +37 -0
- package/src/components/planner/planner-entries.ts +16 -7
- package/src/components/planner/planner-grid-navigation.ts +45 -0
- package/src/components/planner/planner-lifecycle.ts +76 -0
- package/src/components/plot/index.tsx +183 -39
- package/src/components/popover/index.tsx +3 -3
- package/src/components/post/index.tsx +4 -4
- package/src/components/search-bar/index.tsx +436 -0
- package/src/components/table/index.tsx +2 -2
- package/src/components/timeline/index.tsx +335 -39
- package/src/components/toggle-button/index.tsx +1 -1
- package/src/components/tree/index.tsx +17 -12
- package/src/hooks/use-keyboard-avoidance.ts +7 -2
- package/src/index.ts +15 -0
- package/src/primitives/focus-restoration-store.ts +70 -0
- package/src/primitives/portal.tsx +34 -2
- package/src/utils/chart.ts +66 -7
package/src/index.ts
CHANGED
|
@@ -416,6 +416,14 @@ export {
|
|
|
416
416
|
type ChipVariant,
|
|
417
417
|
type ChipSize,
|
|
418
418
|
} from './components/chip';
|
|
419
|
+
export {
|
|
420
|
+
Collapsible,
|
|
421
|
+
type CollapsibleProps,
|
|
422
|
+
type CollapsibleTriggerProps,
|
|
423
|
+
type CollapsibleIndicatorProps,
|
|
424
|
+
type CollapsibleContentProps,
|
|
425
|
+
type CollapsibleVariant,
|
|
426
|
+
} from './components/collapsible';
|
|
419
427
|
export {
|
|
420
428
|
ColorPicker,
|
|
421
429
|
type ColorPickerProps,
|
|
@@ -514,6 +522,7 @@ export {
|
|
|
514
522
|
type NumberInputProps,
|
|
515
523
|
} from './components/number-input';
|
|
516
524
|
export { OtpInput, type OtpInputProps } from './components/otp-input';
|
|
525
|
+
export { SearchBar, type SearchBarProps } from './components/search-bar';
|
|
517
526
|
export {
|
|
518
527
|
Panelside,
|
|
519
528
|
usePanelside,
|
|
@@ -728,6 +737,11 @@ export {
|
|
|
728
737
|
type MarkerIconProps,
|
|
729
738
|
type MarkerContentProps,
|
|
730
739
|
} from './components/marker';
|
|
740
|
+
export {
|
|
741
|
+
Marquee,
|
|
742
|
+
type MarqueeProps,
|
|
743
|
+
type MarqueeDirection,
|
|
744
|
+
} from './components/marquee';
|
|
731
745
|
export {
|
|
732
746
|
Menu,
|
|
733
747
|
type MenuProps,
|
|
@@ -1055,6 +1069,7 @@ export {
|
|
|
1055
1069
|
type TimelineStatProps,
|
|
1056
1070
|
type TimelineVariant,
|
|
1057
1071
|
type TimelineTone,
|
|
1072
|
+
type TimelineOrientation,
|
|
1058
1073
|
} from './components/timeline';
|
|
1059
1074
|
export {
|
|
1060
1075
|
Toast,
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
interface FocusDocument {
|
|
2
|
+
activeElement?: FocusTarget | null;
|
|
3
|
+
body?: FocusTarget | null;
|
|
4
|
+
documentElement?: FocusTarget | null;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface FocusTarget {
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
isConnected?: boolean;
|
|
10
|
+
focus: (options?: { preventScroll?: boolean }) => void;
|
|
11
|
+
getAttribute?: (name: string) => string | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type RestoreFocus = () => boolean;
|
|
15
|
+
|
|
16
|
+
interface FocusOwner {
|
|
17
|
+
owner: string;
|
|
18
|
+
released: boolean;
|
|
19
|
+
restore: RestoreFocus;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Restores nested overlays in last-opened order, even when parents close first. */
|
|
23
|
+
export class FocusRestorationStore {
|
|
24
|
+
private owners: FocusOwner[] = [];
|
|
25
|
+
|
|
26
|
+
acquire(owner: string, restore: RestoreFocus): (() => void) {
|
|
27
|
+
const entry = { owner, released: false, restore };
|
|
28
|
+
this.owners.push(entry);
|
|
29
|
+
|
|
30
|
+
return () => {
|
|
31
|
+
if (entry.released) return;
|
|
32
|
+
entry.released = true;
|
|
33
|
+
this.restoreReleasedOwners();
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private restoreReleasedOwners() {
|
|
38
|
+
const candidates: RestoreFocus[] = [];
|
|
39
|
+
while (this.owners.at(-1)?.released) {
|
|
40
|
+
const entry = this.owners.pop();
|
|
41
|
+
if (entry) candidates.push(entry.restore);
|
|
42
|
+
}
|
|
43
|
+
candidates.some((restore) => restore());
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Captures the current browser focus without evaluating DOM APIs on native. */
|
|
48
|
+
export function captureFocusRestore(
|
|
49
|
+
document = (globalThis as { document?: FocusDocument }).document
|
|
50
|
+
): RestoreFocus {
|
|
51
|
+
const target = document?.activeElement;
|
|
52
|
+
if (!target || target === document?.body || target === document?.documentElement) {
|
|
53
|
+
return () => false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return () => {
|
|
57
|
+
if (target.isConnected === false || target.disabled) return false;
|
|
58
|
+
if (target.getAttribute?.('aria-disabled') === 'true') return false;
|
|
59
|
+
try {
|
|
60
|
+
target.focus({ preventScroll: true });
|
|
61
|
+
} catch {
|
|
62
|
+
try {
|
|
63
|
+
target.focus();
|
|
64
|
+
} catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return document?.activeElement === target;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type ReactNode,
|
|
9
9
|
} from 'react';
|
|
10
10
|
import { ModalIsolationStore } from './modal-isolation-store';
|
|
11
|
+
import { captureFocusRestore, FocusRestorationStore } from './focus-restoration-store';
|
|
11
12
|
|
|
12
13
|
type PortalMap = ReadonlyMap<string, ReactNode>;
|
|
13
14
|
|
|
@@ -41,16 +42,21 @@ class PortalStore {
|
|
|
41
42
|
|
|
42
43
|
const PortalContext = createContext<PortalStore | null>(null);
|
|
43
44
|
const ModalIsolationContext = createContext<ModalIsolationStore | null>(null);
|
|
45
|
+
const FocusRestorationContext = createContext<FocusRestorationStore | null>(null);
|
|
44
46
|
|
|
45
47
|
export function PortalProvider({ children }: { children: ReactNode }) {
|
|
46
48
|
const portalStoreRef = useRef<PortalStore | null>(null);
|
|
47
49
|
const modalStoreRef = useRef<ModalIsolationStore | null>(null);
|
|
50
|
+
const focusStoreRef = useRef<FocusRestorationStore | null>(null);
|
|
48
51
|
portalStoreRef.current ??= new PortalStore();
|
|
49
52
|
modalStoreRef.current ??= new ModalIsolationStore();
|
|
53
|
+
focusStoreRef.current ??= new FocusRestorationStore();
|
|
50
54
|
return (
|
|
51
55
|
<PortalContext.Provider value={portalStoreRef.current}>
|
|
52
56
|
<ModalIsolationContext.Provider value={modalStoreRef.current}>
|
|
53
|
-
{
|
|
57
|
+
<FocusRestorationContext.Provider value={focusStoreRef.current}>
|
|
58
|
+
{children}
|
|
59
|
+
</FocusRestorationContext.Provider>
|
|
54
60
|
</ModalIsolationContext.Provider>
|
|
55
61
|
</PortalContext.Provider>
|
|
56
62
|
);
|
|
@@ -80,9 +86,27 @@ export function Portal({ children }: { children: ReactNode }) {
|
|
|
80
86
|
/** A portal that owns modal focus until it unmounts. */
|
|
81
87
|
export function ModalPortal({ children }: { children: ReactNode }) {
|
|
82
88
|
const store = useModalIsolationStore('ModalPortal');
|
|
89
|
+
const focusStore = useFocusRestorationStore('ModalPortal');
|
|
83
90
|
const owner = useId();
|
|
84
91
|
|
|
85
|
-
useEffect(() =>
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
const releaseIsolation = store.acquire(owner);
|
|
94
|
+
const releaseFocus = focusStore.acquire(owner, captureFocusRestore());
|
|
95
|
+
return () => {
|
|
96
|
+
releaseIsolation();
|
|
97
|
+
releaseFocus();
|
|
98
|
+
};
|
|
99
|
+
}, [focusStore, owner, store]);
|
|
100
|
+
|
|
101
|
+
return <Portal>{children}</Portal>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** A non-isolating portal that returns browser focus to what opened it. */
|
|
105
|
+
export function FocusRestorePortal({ children }: { children: ReactNode }) {
|
|
106
|
+
const store = useFocusRestorationStore('FocusRestorePortal');
|
|
107
|
+
const owner = useId();
|
|
108
|
+
|
|
109
|
+
useEffect(() => store.acquire(owner, captureFocusRestore()), [owner, store]);
|
|
86
110
|
|
|
87
111
|
return <Portal>{children}</Portal>;
|
|
88
112
|
}
|
|
@@ -101,6 +125,14 @@ function useModalIsolationStore(component: string): ModalIsolationStore {
|
|
|
101
125
|
return store;
|
|
102
126
|
}
|
|
103
127
|
|
|
128
|
+
function useFocusRestorationStore(component: string): FocusRestorationStore {
|
|
129
|
+
const store = useContext(FocusRestorationContext);
|
|
130
|
+
if (!store) {
|
|
131
|
+
throw new Error(`${component} must be used within a <PanelUIProvider>`);
|
|
132
|
+
}
|
|
133
|
+
return store;
|
|
134
|
+
}
|
|
135
|
+
|
|
104
136
|
/** Mount point for portaled content. PanelUIProvider renders one automatically. */
|
|
105
137
|
export function PortalHost() {
|
|
106
138
|
const store = usePortalStore('PortalHost');
|
package/src/utils/chart.ts
CHANGED
|
@@ -264,6 +264,50 @@ export function yOf(value: number, plot: Plot, min: number, max: number): number
|
|
|
264
264
|
return plot.top + plot.height - ((value - min) / span) * plot.height;
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
+
/**
|
|
268
|
+
* Widens a domain out to round numbers.
|
|
269
|
+
*
|
|
270
|
+
* An axis derived from the data ends wherever the data happened to end, so it
|
|
271
|
+
* gets labelled 34,650 — a number that is true and that nobody was looking for.
|
|
272
|
+
* Rounding the ends out to a step of 1, 2 or 5 times a power of ten gives the
|
|
273
|
+
* axis labels somebody can actually read a value off, and it can only ever add
|
|
274
|
+
* room, never crop.
|
|
275
|
+
*
|
|
276
|
+
* Zero survives: any step divides it, so a baseline pinned there stays there.
|
|
277
|
+
*/
|
|
278
|
+
export function niceDomain(
|
|
279
|
+
min: number,
|
|
280
|
+
max: number,
|
|
281
|
+
ticks = 5
|
|
282
|
+
): [number, number] {
|
|
283
|
+
if (!Number.isFinite(min) || !Number.isFinite(max) || max <= min) {
|
|
284
|
+
return [min, max];
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const rough = (max - min) / Math.max(1, ticks);
|
|
288
|
+
const magnitude = 10 ** Math.floor(Math.log10(rough));
|
|
289
|
+
const error = rough / magnitude;
|
|
290
|
+
|
|
291
|
+
/*
|
|
292
|
+
* 1, 2, 5 or 10 times the magnitude — the steps a reader can divide in their
|
|
293
|
+
* head. The thresholds are geometric means rather than the steps themselves,
|
|
294
|
+
* so each candidate wins over the span it is genuinely closest to. Rounding at
|
|
295
|
+
* the steps instead pushes anything above 5 up to a full decade, which turns a
|
|
296
|
+
* top of 31,500 into an axis ending at 40,000 and hands a quarter of the plot
|
|
297
|
+
* to empty space.
|
|
298
|
+
*/
|
|
299
|
+
const step =
|
|
300
|
+
(error >= Math.sqrt(50)
|
|
301
|
+
? 10
|
|
302
|
+
: error >= Math.sqrt(10)
|
|
303
|
+
? 5
|
|
304
|
+
: error >= Math.sqrt(2)
|
|
305
|
+
? 2
|
|
306
|
+
: 1) * magnitude;
|
|
307
|
+
|
|
308
|
+
return [Math.floor(min / step) * step, Math.ceil(max / step) * step];
|
|
309
|
+
}
|
|
310
|
+
|
|
267
311
|
/**
|
|
268
312
|
* A value's x on a *measured* axis, with `min` at the left edge and `max` at
|
|
269
313
|
* the right.
|
|
@@ -356,7 +400,16 @@ export function runsOf(
|
|
|
356
400
|
min: number,
|
|
357
401
|
max: number,
|
|
358
402
|
/** Baselines to stack each point on top of, for a stacked area. */
|
|
359
|
-
baselines?: number[]
|
|
403
|
+
baselines?: number[],
|
|
404
|
+
/**
|
|
405
|
+
* Place points at band centres rather than spreading them edge to edge.
|
|
406
|
+
*
|
|
407
|
+
* A line drawn over columns has to agree with them about where a row sits,
|
|
408
|
+
* and a column owns a slice of the axis rather than a position on it. Spread
|
|
409
|
+
* evenly, the first point lands half a slice left of the bar it describes and
|
|
410
|
+
* every point after it is wrong by the same amount.
|
|
411
|
+
*/
|
|
412
|
+
banded?: boolean
|
|
360
413
|
): ChartPoint[][] {
|
|
361
414
|
'worklet';
|
|
362
415
|
const runs: ChartPoint[][] = [];
|
|
@@ -370,7 +423,8 @@ export function runsOf(
|
|
|
370
423
|
continue;
|
|
371
424
|
}
|
|
372
425
|
const stacked = value + (baselines?.[i] ?? 0);
|
|
373
|
-
|
|
426
|
+
const x = banded ? bandOf(i, values.length, plot) : xOf(i, values.length, plot);
|
|
427
|
+
run.push({ x, y: yOf(stacked, plot, min, max) });
|
|
374
428
|
}
|
|
375
429
|
if (run.length) runs.push(run);
|
|
376
430
|
return runs;
|
|
@@ -404,7 +458,9 @@ export function linePath(
|
|
|
404
458
|
max: number,
|
|
405
459
|
curve: ChartCurve,
|
|
406
460
|
loading: boolean,
|
|
407
|
-
baselines?: number[]
|
|
461
|
+
baselines?: number[],
|
|
462
|
+
/** Place points at band centres, for a line drawn over columns. */
|
|
463
|
+
banded?: boolean
|
|
408
464
|
): string {
|
|
409
465
|
'worklet';
|
|
410
466
|
if (loading || plot.width <= 0) {
|
|
@@ -413,7 +469,7 @@ export function linePath(
|
|
|
413
469
|
const y = plot.top + plot.height / 2;
|
|
414
470
|
return `M${plot.left},${y} L${plot.left + plot.width},${y}`;
|
|
415
471
|
}
|
|
416
|
-
return runsOf(values, plot, min, max, baselines)
|
|
472
|
+
return runsOf(values, plot, min, max, baselines, banded)
|
|
417
473
|
.map((run) => segment(run, curve))
|
|
418
474
|
.join(' ');
|
|
419
475
|
}
|
|
@@ -433,13 +489,15 @@ export function areaPath(
|
|
|
433
489
|
max: number,
|
|
434
490
|
curve: ChartCurve,
|
|
435
491
|
loading: boolean,
|
|
436
|
-
baselines?: number[]
|
|
492
|
+
baselines?: number[],
|
|
493
|
+
/** Place points at band centres, for an area drawn against columns. */
|
|
494
|
+
banded?: boolean
|
|
437
495
|
): string {
|
|
438
496
|
'worklet';
|
|
439
497
|
if (loading || plot.width <= 0) return '';
|
|
440
498
|
|
|
441
499
|
const bottom = plot.top + plot.height;
|
|
442
|
-
const runs = runsOf(values, plot, min, max, baselines);
|
|
500
|
+
const runs = runsOf(values, plot, min, max, baselines, banded);
|
|
443
501
|
|
|
444
502
|
if (!baselines) {
|
|
445
503
|
return runs
|
|
@@ -460,7 +518,8 @@ export function areaPath(
|
|
|
460
518
|
plot,
|
|
461
519
|
min,
|
|
462
520
|
max,
|
|
463
|
-
baselines
|
|
521
|
+
baselines,
|
|
522
|
+
banded
|
|
464
523
|
);
|
|
465
524
|
|
|
466
525
|
return runs
|