react-cheminfo 0.15.0 → 0.17.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 +96 -0
- package/lib/core.d.ts +1 -0
- package/lib/core.d.ts.map +1 -1
- package/lib/core.js +1 -0
- package/lib/core.js.map +1 -1
- package/lib/credits/core/credits.d.ts +17 -0
- package/lib/credits/core/credits.d.ts.map +1 -1
- package/lib/credits/core/credits.js +20 -0
- package/lib/credits/core/credits.js.map +1 -1
- package/lib/ecosystem/core/sites.d.ts.map +1 -1
- package/lib/ecosystem/core/sites.js +40 -0
- package/lib/ecosystem/core/sites.js.map +1 -1
- package/lib/ecosystem/core/types.d.ts +1 -1
- package/lib/ecosystem/core/types.d.ts.map +1 -1
- package/lib/ecosystem/ui/glyphs.d.ts.map +1 -1
- package/lib/ecosystem/ui/glyphs.js +11 -0
- package/lib/ecosystem/ui/glyphs.js.map +1 -1
- package/lib/parallel/core/index.d.ts +29 -0
- package/lib/parallel/core/index.d.ts.map +1 -0
- package/lib/parallel/core/index.js +22 -0
- package/lib/parallel/core/index.js.map +1 -0
- package/lib/parallel/core/parallelAxes.d.ts +103 -0
- package/lib/parallel/core/parallelAxes.d.ts.map +1 -0
- package/lib/parallel/core/parallelAxes.js +146 -0
- package/lib/parallel/core/parallelAxes.js.map +1 -0
- package/lib/parallel/core/parallelBrush.d.ts +100 -0
- package/lib/parallel/core/parallelBrush.d.ts.map +1 -0
- package/lib/parallel/core/parallelBrush.js +149 -0
- package/lib/parallel/core/parallelBrush.js.map +1 -0
- package/lib/parallel/core/parallelExtent.d.ts +28 -0
- package/lib/parallel/core/parallelExtent.d.ts.map +1 -0
- package/lib/parallel/core/parallelExtent.js +47 -0
- package/lib/parallel/core/parallelExtent.js.map +1 -0
- package/lib/parallel/core/parallelFilter.d.ts +49 -0
- package/lib/parallel/core/parallelFilter.d.ts.map +1 -0
- package/lib/parallel/core/parallelFilter.js +107 -0
- package/lib/parallel/core/parallelFilter.js.map +1 -0
- package/lib/parallel/core/parallelHit.d.ts +47 -0
- package/lib/parallel/core/parallelHit.d.ts.map +1 -0
- package/lib/parallel/core/parallelHit.js +81 -0
- package/lib/parallel/core/parallelHit.js.map +1 -0
- package/lib/parallel/core/parallelOrder.d.ts +36 -0
- package/lib/parallel/core/parallelOrder.d.ts.map +1 -0
- package/lib/parallel/core/parallelOrder.js +65 -0
- package/lib/parallel/core/parallelOrder.js.map +1 -0
- package/lib/parallel/core/parallelPaint.d.ts +108 -0
- package/lib/parallel/core/parallelPaint.d.ts.map +1 -0
- package/lib/parallel/core/parallelPaint.js +169 -0
- package/lib/parallel/core/parallelPaint.js.map +1 -0
- package/lib/parallel/core/parallelPalette.d.ts +40 -0
- package/lib/parallel/core/parallelPalette.d.ts.map +1 -0
- package/lib/parallel/core/parallelPalette.js +81 -0
- package/lib/parallel/core/parallelPalette.js.map +1 -0
- package/lib/parallel/core/parallelSelection.d.ts +43 -0
- package/lib/parallel/core/parallelSelection.d.ts.map +1 -0
- package/lib/parallel/core/parallelSelection.js +87 -0
- package/lib/parallel/core/parallelSelection.js.map +1 -0
- package/lib/parallel/core/parallelTicks.d.ts +34 -0
- package/lib/parallel/core/parallelTicks.d.ts.map +1 -0
- package/lib/parallel/core/parallelTicks.js +119 -0
- package/lib/parallel/core/parallelTicks.js.map +1 -0
- package/lib/parallel/core/parallelTypes.d.ts +140 -0
- package/lib/parallel/core/parallelTypes.d.ts.map +1 -0
- package/lib/parallel/core/parallelTypes.js +29 -0
- package/lib/parallel/core/parallelTypes.js.map +1 -0
- package/lib/parallel/ui/ParallelAxesLayer.d.ts +28 -0
- package/lib/parallel/ui/ParallelAxesLayer.d.ts.map +1 -0
- package/lib/parallel/ui/ParallelAxesLayer.js +29 -0
- package/lib/parallel/ui/ParallelAxesLayer.js.map +1 -0
- package/lib/parallel/ui/ParallelCoordinates.d.ts +21 -0
- package/lib/parallel/ui/ParallelCoordinates.d.ts.map +1 -0
- package/lib/parallel/ui/ParallelCoordinates.js +114 -0
- package/lib/parallel/ui/ParallelCoordinates.js.map +1 -0
- package/lib/parallel/ui/ParallelLabels.d.ts +32 -0
- package/lib/parallel/ui/ParallelLabels.d.ts.map +1 -0
- package/lib/parallel/ui/ParallelLabels.js +37 -0
- package/lib/parallel/ui/ParallelLabels.js.map +1 -0
- package/lib/parallel/ui/ParallelTooltip.d.ts +26 -0
- package/lib/parallel/ui/ParallelTooltip.d.ts.map +1 -0
- package/lib/parallel/ui/ParallelTooltip.js +30 -0
- package/lib/parallel/ui/ParallelTooltip.js.map +1 -0
- package/lib/parallel/ui/index.d.ts +29 -0
- package/lib/parallel/ui/index.d.ts.map +1 -0
- package/lib/parallel/ui/index.js +20 -0
- package/lib/parallel/ui/index.js.map +1 -0
- package/lib/parallel/ui/parallelCoordinatesProps.d.ts +203 -0
- package/lib/parallel/ui/parallelCoordinatesProps.d.ts.map +1 -0
- package/lib/parallel/ui/parallelCoordinatesProps.js +11 -0
- package/lib/parallel/ui/parallelCoordinatesProps.js.map +1 -0
- package/lib/parallel/ui/parallelGestureModel.d.ts +83 -0
- package/lib/parallel/ui/parallelGestureModel.d.ts.map +1 -0
- package/lib/parallel/ui/parallelGestureModel.js +110 -0
- package/lib/parallel/ui/parallelGestureModel.js.map +1 -0
- package/lib/parallel/ui/parallelInk.d.ts +76 -0
- package/lib/parallel/ui/parallelInk.d.ts.map +1 -0
- package/lib/parallel/ui/parallelInk.js +113 -0
- package/lib/parallel/ui/parallelInk.js.map +1 -0
- package/lib/parallel/ui/parallelPlotModel.d.ts +55 -0
- package/lib/parallel/ui/parallelPlotModel.d.ts.map +1 -0
- package/lib/parallel/ui/parallelPlotModel.js +113 -0
- package/lib/parallel/ui/parallelPlotModel.js.map +1 -0
- package/lib/parallel/ui/parallelStyles.d.ts +106 -0
- package/lib/parallel/ui/parallelStyles.d.ts.map +1 -0
- package/lib/parallel/ui/parallelStyles.js +153 -0
- package/lib/parallel/ui/parallelStyles.js.map +1 -0
- package/lib/parallel/ui/useParallelAxisDrag.d.ts +60 -0
- package/lib/parallel/ui/useParallelAxisDrag.d.ts.map +1 -0
- package/lib/parallel/ui/useParallelAxisDrag.js +96 -0
- package/lib/parallel/ui/useParallelAxisDrag.js.map +1 -0
- package/lib/parallel/ui/useParallelCanvas.d.ts +49 -0
- package/lib/parallel/ui/useParallelCanvas.d.ts.map +1 -0
- package/lib/parallel/ui/useParallelCanvas.js +58 -0
- package/lib/parallel/ui/useParallelCanvas.js.map +1 -0
- package/lib/parallel/ui/useParallelGesture.d.ts +72 -0
- package/lib/parallel/ui/useParallelGesture.d.ts.map +1 -0
- package/lib/parallel/ui/useParallelGesture.js +164 -0
- package/lib/parallel/ui/useParallelGesture.js.map +1 -0
- package/lib/structure/ui/useToolbarFloor.d.ts.map +1 -1
- package/lib/structure/ui/useToolbarFloor.js +29 -22
- package/lib/structure/ui/useToolbarFloor.js.map +1 -1
- package/lib/ui.d.ts +1 -0
- package/lib/ui.d.ts.map +1 -1
- package/lib/ui.js +1 -0
- package/lib/ui.js.map +1 -1
- package/package.json +1 -1
- package/src/core.ts +1 -0
- package/src/credits/core/credits.ts +21 -0
- package/src/ecosystem/core/sites.ts +40 -0
- package/src/ecosystem/core/types.ts +4 -1
- package/src/ecosystem/ui/glyphs.tsx +46 -0
- package/src/parallel/core/index.ts +96 -0
- package/src/parallel/core/parallelAxes.ts +207 -0
- package/src/parallel/core/parallelBrush.ts +211 -0
- package/src/parallel/core/parallelExtent.ts +56 -0
- package/src/parallel/core/parallelFilter.ts +131 -0
- package/src/parallel/core/parallelHit.ts +106 -0
- package/src/parallel/core/parallelOrder.ts +73 -0
- package/src/parallel/core/parallelPaint.ts +242 -0
- package/src/parallel/core/parallelPalette.ts +93 -0
- package/src/parallel/core/parallelSelection.ts +99 -0
- package/src/parallel/core/parallelTicks.ts +168 -0
- package/src/parallel/core/parallelTypes.ts +159 -0
- package/src/parallel/ui/ParallelAxesLayer.tsx +129 -0
- package/src/parallel/ui/ParallelCoordinates.tsx +252 -0
- package/src/parallel/ui/ParallelLabels.tsx +77 -0
- package/src/parallel/ui/ParallelTooltip.tsx +55 -0
- package/src/parallel/ui/index.ts +65 -0
- package/src/parallel/ui/parallelCoordinatesProps.ts +217 -0
- package/src/parallel/ui/parallelGestureModel.ts +159 -0
- package/src/parallel/ui/parallelInk.ts +182 -0
- package/src/parallel/ui/parallelPlotModel.ts +130 -0
- package/src/parallel/ui/parallelStyles.ts +188 -0
- package/src/parallel/ui/useParallelAxisDrag.ts +147 -0
- package/src/parallel/ui/useParallelCanvas.ts +104 -0
- package/src/parallel/ui/useParallelGesture.ts +260 -0
- package/src/structure/ui/useToolbarFloor.ts +27 -22
- package/src/ui.ts +1 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The colours the canvas is painted with, read off the figure itself.
|
|
3
|
+
*
|
|
4
|
+
* A canvas takes a colour, not a CSS declaration, so `var(--accent)` means
|
|
5
|
+
* nothing to it — and the family's accent is the one colour a site owns, so it
|
|
6
|
+
* cannot be written into a shared library either. The figure therefore asks
|
|
7
|
+
* the browser what its own tokens resolved to, once it is on the page, and
|
|
8
|
+
* paints with the answer.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
12
|
+
|
|
13
|
+
import { parseHexColor } from '../../color/core/hex.ts';
|
|
14
|
+
import { FAMILY_TOKEN_VALUES } from '../../tokens/core/familyTokens.ts';
|
|
15
|
+
import { PARALLEL_INCLUDED_ALPHA } from '../core/parallelPaint.ts';
|
|
16
|
+
|
|
17
|
+
import type { ParallelInk } from './parallelCoordinatesProps.ts';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The rows a brush left out. A light grey rather than a translucent black:
|
|
21
|
+
* overlapping segments of one stroked path accumulate alpha, so a translucent
|
|
22
|
+
* black saturates wherever the library is dense, while this is the colour the
|
|
23
|
+
* pile can only converge on.
|
|
24
|
+
*/
|
|
25
|
+
const EXCLUDED = 'rgba(171, 179, 191, 0.16)';
|
|
26
|
+
|
|
27
|
+
/** How solid the halo under a singled-out line is. */
|
|
28
|
+
const HALO_ALPHA = 0.85;
|
|
29
|
+
|
|
30
|
+
/** The halo a page that declares no surface colour still gets. */
|
|
31
|
+
const WHITE_HALO = `rgba(255, 255, 255, ${HALO_ALPHA})`;
|
|
32
|
+
|
|
33
|
+
/** The four tokens the figure paints from, as the page resolved them. */
|
|
34
|
+
export interface ParallelTokenInk {
|
|
35
|
+
/** What `--text` resolved to. */
|
|
36
|
+
text: string;
|
|
37
|
+
/** What `--text-muted` resolved to. */
|
|
38
|
+
textMuted: string;
|
|
39
|
+
/** What `--surface` resolved to. */
|
|
40
|
+
surface: string;
|
|
41
|
+
/** What `--accent` resolved to, or the text colour when a site declares none. */
|
|
42
|
+
accent: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Every colour the figure paints with, resolved to something a canvas takes. */
|
|
46
|
+
export interface ParallelInkValues {
|
|
47
|
+
/** The rows a brush left out. */
|
|
48
|
+
excluded: string;
|
|
49
|
+
/** The rows nothing is colouring. */
|
|
50
|
+
line: string;
|
|
51
|
+
/** The row under the pointer. */
|
|
52
|
+
hover: string;
|
|
53
|
+
/** The rows singled out elsewhere. */
|
|
54
|
+
selection: string;
|
|
55
|
+
/** The halo a singled-out line is drawn over. */
|
|
56
|
+
halo: string;
|
|
57
|
+
/** The axis lines, their ticks and their labels. */
|
|
58
|
+
axis: string;
|
|
59
|
+
/** How strongly a kept line is drawn. */
|
|
60
|
+
includedAlpha: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* What the figure's own tokens resolved to.
|
|
65
|
+
* @param element - The figure, once it is on the page, or `null` before it is.
|
|
66
|
+
* @returns The four colours. Off the page — a server render, a test — the
|
|
67
|
+
* family's own token values stand in, so the figure never paints with nothing.
|
|
68
|
+
*/
|
|
69
|
+
export function readParallelTokens(element: Element | null): ParallelTokenInk {
|
|
70
|
+
const text = tokenColor(element, '--text', FAMILY_TOKEN_VALUES['--text']);
|
|
71
|
+
return {
|
|
72
|
+
text,
|
|
73
|
+
textMuted: tokenColor(
|
|
74
|
+
element,
|
|
75
|
+
'--text-muted',
|
|
76
|
+
FAMILY_TOKEN_VALUES['--text-muted'],
|
|
77
|
+
),
|
|
78
|
+
surface: tokenColor(element, '--surface', FAMILY_TOKEN_VALUES['--surface']),
|
|
79
|
+
// A site that declares no accent still singles a row out, in its ink.
|
|
80
|
+
accent: tokenColor(element, '--accent', text),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The inks to paint with, from the page's tokens and the caller's overrides.
|
|
86
|
+
* @param tokens - What the figure's own tokens resolved to.
|
|
87
|
+
* @param ink - What the caller insists on, over every one of them.
|
|
88
|
+
* @returns The inks.
|
|
89
|
+
*/
|
|
90
|
+
export function parallelInkFrom(
|
|
91
|
+
tokens: ParallelTokenInk,
|
|
92
|
+
ink: ParallelInk = {},
|
|
93
|
+
): ParallelInkValues {
|
|
94
|
+
return {
|
|
95
|
+
excluded: ink.excluded ?? EXCLUDED,
|
|
96
|
+
line: ink.line ?? tokens.textMuted,
|
|
97
|
+
hover: ink.hover ?? tokens.text,
|
|
98
|
+
selection: ink.selection ?? tokens.accent,
|
|
99
|
+
halo: ink.halo ?? haloOf(tokens.surface),
|
|
100
|
+
axis: ink.axis ?? tokens.textMuted,
|
|
101
|
+
includedAlpha: ink.includedAlpha ?? PARALLEL_INCLUDED_ALPHA,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The inks to paint with, read off one element.
|
|
107
|
+
* @param element - The figure, once it is on the page, or `null` before it is.
|
|
108
|
+
* @param ink - What the caller insists on, over everything read off the page.
|
|
109
|
+
* @returns The inks.
|
|
110
|
+
*/
|
|
111
|
+
export function readParallelInk(
|
|
112
|
+
element: Element | null,
|
|
113
|
+
ink: ParallelInk = {},
|
|
114
|
+
): ParallelInkValues {
|
|
115
|
+
return parallelInkFrom(readParallelTokens(element), ink);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** The inks, and the ref that has to reach the figure for them to be read. */
|
|
119
|
+
export interface ParallelInkBinding {
|
|
120
|
+
/** The colours to paint with. */
|
|
121
|
+
ink: ParallelInkValues;
|
|
122
|
+
/** Put on the figure's outermost element. */
|
|
123
|
+
ref: (element: Element | null) => void;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The inks, with the page's own tokens read the moment the figure is mounted.
|
|
128
|
+
*
|
|
129
|
+
* A callback ref rather than an effect, because the question is asked once, of
|
|
130
|
+
* the element itself, and there is nothing to keep in step afterwards.
|
|
131
|
+
* @param ink - What the caller insists on.
|
|
132
|
+
* @returns The inks and the ref. See {@link ParallelInkBinding}.
|
|
133
|
+
*/
|
|
134
|
+
export function useParallelInk(ink: ParallelInk = {}): ParallelInkBinding {
|
|
135
|
+
const [tokens, setTokens] = useState<ParallelTokenInk>(readFamilyTokens);
|
|
136
|
+
const { excluded, line, hover, selection, halo, axis, includedAlpha } = ink;
|
|
137
|
+
|
|
138
|
+
const ref = useCallback((element: Element | null) => {
|
|
139
|
+
if (element !== null) setTokens(readParallelTokens(element));
|
|
140
|
+
}, []);
|
|
141
|
+
|
|
142
|
+
const resolved = useMemo(
|
|
143
|
+
() =>
|
|
144
|
+
parallelInkFrom(tokens, {
|
|
145
|
+
excluded,
|
|
146
|
+
line,
|
|
147
|
+
hover,
|
|
148
|
+
selection,
|
|
149
|
+
halo,
|
|
150
|
+
axis,
|
|
151
|
+
includedAlpha,
|
|
152
|
+
}),
|
|
153
|
+
[tokens, excluded, line, hover, selection, halo, axis, includedAlpha],
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
return { ink: resolved, ref };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function readFamilyTokens(): ParallelTokenInk {
|
|
160
|
+
return readParallelTokens(null);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function tokenColor(
|
|
164
|
+
element: Element | null,
|
|
165
|
+
name: string,
|
|
166
|
+
fallback: string,
|
|
167
|
+
): string {
|
|
168
|
+
const resolve = globalThis.getComputedStyle;
|
|
169
|
+
if (element === null || typeof resolve !== 'function') return fallback;
|
|
170
|
+
const declared = resolve(element).getPropertyValue(name).trim();
|
|
171
|
+
return declared === '' ? fallback : declared;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function haloOf(surface: string): string {
|
|
175
|
+
try {
|
|
176
|
+
const { red, green, blue } = parseHexColor(surface);
|
|
177
|
+
return `rgba(${red}, ${green}, ${blue}, ${HALO_ALPHA})`;
|
|
178
|
+
} catch {
|
|
179
|
+
// A surface written as anything but a hex colour still gets a halo.
|
|
180
|
+
return WHITE_HALO;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The small answers the figure needs before it renders: how many rows there
|
|
3
|
+
* are, what is colouring the lines, which rows are drawn over the mass, and
|
|
4
|
+
* what a screen reader is told.
|
|
5
|
+
*
|
|
6
|
+
* Kept out of the component for the reason `scatterPlotModel.ts` is: none of
|
|
7
|
+
* them needs React to be read or checked, and a component that is mostly
|
|
8
|
+
* arithmetic is a component nobody can see the layout in.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { formatInteger } from '../../format/core/numbers.ts';
|
|
12
|
+
import type { ParallelHighlight } from '../core/parallelPaint.ts';
|
|
13
|
+
import { parallelHasRange } from '../core/parallelSelection.ts';
|
|
14
|
+
import type {
|
|
15
|
+
ParallelAxis,
|
|
16
|
+
ParallelColorBy,
|
|
17
|
+
ParallelRanges,
|
|
18
|
+
} from '../core/parallelTypes.ts';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* How many rows the figure draws.
|
|
22
|
+
* @param axes - The axes, whose columns are read.
|
|
23
|
+
* @param count - What the caller insists on, if anything.
|
|
24
|
+
* @returns The count — the shortest column, so no axis is ever read past its
|
|
25
|
+
* own end.
|
|
26
|
+
*/
|
|
27
|
+
export function parallelRowCount(
|
|
28
|
+
axes: readonly ParallelAxis[],
|
|
29
|
+
count?: number,
|
|
30
|
+
): number {
|
|
31
|
+
if (count !== undefined) return Math.max(0, Math.trunc(count));
|
|
32
|
+
let shortest = 0;
|
|
33
|
+
for (let index = 0; index < axes.length; index++) {
|
|
34
|
+
const axis = axes[index];
|
|
35
|
+
if (axis === undefined) continue;
|
|
36
|
+
const length = axis.values.length;
|
|
37
|
+
if (index === 0 || length < shortest) shortest = length;
|
|
38
|
+
}
|
|
39
|
+
return shortest;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The quantity the lines are coloured by.
|
|
44
|
+
* @param color - The quantity the caller gave outright.
|
|
45
|
+
* @param colorAxis - The id of an axis already drawn, for the common case.
|
|
46
|
+
* @param axes - The axes, to find that id among.
|
|
47
|
+
* @returns The quantity, or `undefined` when the lines all take one ink. An
|
|
48
|
+
* axis named by `colorAxis` lends its own domain as well as its values, so the
|
|
49
|
+
* ramp and the axis beside it read the same range.
|
|
50
|
+
*/
|
|
51
|
+
export function parallelColorOf(
|
|
52
|
+
color: ParallelColorBy | undefined,
|
|
53
|
+
colorAxis: string | undefined,
|
|
54
|
+
axes: readonly ParallelAxis[],
|
|
55
|
+
): ParallelColorBy | undefined {
|
|
56
|
+
if (color !== undefined) return color;
|
|
57
|
+
if (colorAxis === undefined) return undefined;
|
|
58
|
+
for (const axis of axes) {
|
|
59
|
+
if (axis.id !== colorAxis) continue;
|
|
60
|
+
const domain = axis.domain;
|
|
61
|
+
if (domain === undefined) return { values: axis.values };
|
|
62
|
+
return {
|
|
63
|
+
values: axis.values,
|
|
64
|
+
min: Math.min(domain[0], domain[1]),
|
|
65
|
+
max: Math.max(domain[0], domain[1]),
|
|
66
|
+
logarithmic: axis.scale === 'log',
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Whether any axis on the figure carries an interval at all.
|
|
74
|
+
* @param axes - The axes, whose ids the intervals are keyed by.
|
|
75
|
+
* @param ranges - What each axis keeps.
|
|
76
|
+
* @returns Whether anything is brushed, which is what decides whether a line
|
|
77
|
+
* is ever drawn in the excluded ink.
|
|
78
|
+
*/
|
|
79
|
+
export function parallelIsBrushed(
|
|
80
|
+
axes: readonly ParallelAxis[],
|
|
81
|
+
ranges: ParallelRanges,
|
|
82
|
+
): boolean {
|
|
83
|
+
for (const axis of axes) {
|
|
84
|
+
if (parallelHasRange(ranges[axis.id])) return true;
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The rows drawn over the mass, in the order they are painted.
|
|
91
|
+
* @param selected - The rows singled out elsewhere.
|
|
92
|
+
* @param hovered - The row under the pointer, or `-1`.
|
|
93
|
+
* @param selectionInk - The ink a singled-out row takes.
|
|
94
|
+
* @param hoverInk - The ink the hovered row takes.
|
|
95
|
+
* @returns The highlights, the hovered row last so that it paints on top and
|
|
96
|
+
* a row that is both selected and hovered is drawn once rather than twice.
|
|
97
|
+
*/
|
|
98
|
+
export function parallelHighlightsOf(
|
|
99
|
+
selected: readonly number[] | undefined,
|
|
100
|
+
hovered: number,
|
|
101
|
+
selectionInk: string,
|
|
102
|
+
hoverInk: string,
|
|
103
|
+
): ParallelHighlight[] {
|
|
104
|
+
const highlights: ParallelHighlight[] = [];
|
|
105
|
+
if (selected !== undefined) {
|
|
106
|
+
for (const row of selected) {
|
|
107
|
+
if (row < 0 || row === hovered) continue;
|
|
108
|
+
highlights.push({ row, color: selectionInk });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (hovered >= 0) highlights.push({ row: hovered, color: hoverInk });
|
|
112
|
+
return highlights;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* What a screen reader is told the figure shows.
|
|
117
|
+
* @param axes - The axes, from left to right.
|
|
118
|
+
* @param count - How many rows are drawn.
|
|
119
|
+
* @returns The sentence, naming every axis and how much is on the figure.
|
|
120
|
+
*/
|
|
121
|
+
export function parallelFigureLabel(
|
|
122
|
+
axes: readonly ParallelAxis[],
|
|
123
|
+
count: number,
|
|
124
|
+
): string {
|
|
125
|
+
const names: string[] = [];
|
|
126
|
+
for (const axis of axes) names.push(axis.label);
|
|
127
|
+
const rows = count === 1 ? '1 row' : `${formatInteger(count)} rows`;
|
|
128
|
+
if (names.length === 0) return `Parallel coordinates of ${rows}.`;
|
|
129
|
+
return `Parallel coordinates of ${rows}, on ${names.join(', ')}.`;
|
|
130
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every rule the figure is drawn with.
|
|
3
|
+
*
|
|
4
|
+
* The library ships no stylesheet for a figure — a chart writes inline style
|
|
5
|
+
* objects, so a site that never renders one pays nothing and a site that does
|
|
6
|
+
* cannot forget to import a file. The two colours that are not fixed here, the
|
|
7
|
+
* axis ink and the band's, are resolved off the page by `parallelInk.ts` and
|
|
8
|
+
* written onto the elements, so an override applies to the canvas and to the
|
|
9
|
+
* markup alike.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { CSSProperties } from 'react';
|
|
13
|
+
|
|
14
|
+
import { TOKEN } from '../../tokens/core/familyTokens.ts';
|
|
15
|
+
|
|
16
|
+
/** A layer covering the whole figure, stacked over the ones before it. */
|
|
17
|
+
export const PARALLEL_LAYER_STYLE = {
|
|
18
|
+
position: 'absolute',
|
|
19
|
+
inset: 0,
|
|
20
|
+
} as const satisfies CSSProperties;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The block the figure occupies, which every layer is positioned against.
|
|
24
|
+
* @param height - Total height of the figure, in pixels.
|
|
25
|
+
* @returns The wrapper's rules.
|
|
26
|
+
*/
|
|
27
|
+
export function parallelFigureStyle(height: number): CSSProperties {
|
|
28
|
+
return {
|
|
29
|
+
position: 'relative',
|
|
30
|
+
height: Math.max(0, height),
|
|
31
|
+
minWidth: 0,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* One of the two canvases, stretched over the figure.
|
|
37
|
+
* @param height - Total height of the figure, in pixels.
|
|
38
|
+
* @returns The canvas's rules.
|
|
39
|
+
*/
|
|
40
|
+
export function parallelCanvasStyle(height: number): CSSProperties {
|
|
41
|
+
return {
|
|
42
|
+
...PARALLEL_LAYER_STYLE,
|
|
43
|
+
width: '100%',
|
|
44
|
+
height: Math.max(0, height),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The drawing surface the axes, the bands and the pointer target live in. */
|
|
49
|
+
export const PARALLEL_SVG_STYLE = {
|
|
50
|
+
...PARALLEL_LAYER_STYLE,
|
|
51
|
+
display: 'block',
|
|
52
|
+
overflow: 'visible',
|
|
53
|
+
} as const satisfies CSSProperties;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The layer the axis names sit in. It is HTML rather than SVG text so that a
|
|
57
|
+
* name can carry a tooltip, and it lets every pointer event through so that a
|
|
58
|
+
* brush started under a name still starts.
|
|
59
|
+
*/
|
|
60
|
+
export const PARALLEL_LABELS_STYLE = {
|
|
61
|
+
...PARALLEL_LAYER_STYLE,
|
|
62
|
+
pointerEvents: 'none',
|
|
63
|
+
} as const satisfies CSSProperties;
|
|
64
|
+
|
|
65
|
+
/** How one axis name is placed, and whether it is being moved. */
|
|
66
|
+
export interface ParallelLabelPlacement {
|
|
67
|
+
/** Where the axis stands, in pixels from the figure's left edge. */
|
|
68
|
+
left: number;
|
|
69
|
+
/**
|
|
70
|
+
* Whether the name can be dragged to move its axis.
|
|
71
|
+
* @default false
|
|
72
|
+
*/
|
|
73
|
+
draggable?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* How far it has been dragged, in pixels, or `null` when it is not moving.
|
|
76
|
+
* @default null
|
|
77
|
+
*/
|
|
78
|
+
dx?: number | null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* One axis name, centred over its axis and taking the pointer back.
|
|
83
|
+
* @param placement - See {@link ParallelLabelPlacement}.
|
|
84
|
+
* @returns The name's rules. A name being dragged follows the pointer and is
|
|
85
|
+
* drawn over its neighbours, so it is never hidden behind the name it is
|
|
86
|
+
* about to change places with.
|
|
87
|
+
*/
|
|
88
|
+
export function parallelLabelStyle(
|
|
89
|
+
placement: ParallelLabelPlacement,
|
|
90
|
+
): CSSProperties {
|
|
91
|
+
const { left, draggable = false, dx = null } = placement;
|
|
92
|
+
const moving = dx !== null;
|
|
93
|
+
return {
|
|
94
|
+
position: 'absolute',
|
|
95
|
+
zIndex: moving ? 2 : undefined,
|
|
96
|
+
top: 2,
|
|
97
|
+
left,
|
|
98
|
+
transform:
|
|
99
|
+
dx === null ? 'translateX(-50%)' : `translateX(calc(-50% + ${dx}px))`,
|
|
100
|
+
color: TOKEN.text,
|
|
101
|
+
fontSize: 11,
|
|
102
|
+
fontWeight: 600,
|
|
103
|
+
whiteSpace: 'nowrap',
|
|
104
|
+
pointerEvents: 'auto',
|
|
105
|
+
cursor: draggable ? (moving ? 'grabbing' : 'grab') : undefined,
|
|
106
|
+
opacity: moving ? 0.85 : undefined,
|
|
107
|
+
touchAction: draggable ? 'none' : undefined,
|
|
108
|
+
userSelect: draggable ? 'none' : undefined,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The axis a dragged name would take the place of, marked while it is moving.
|
|
114
|
+
* @param ink - The colour the figure singles things out in.
|
|
115
|
+
* @returns The mark's rules.
|
|
116
|
+
*/
|
|
117
|
+
export function parallelDropMarkStyle(ink: string): CSSProperties {
|
|
118
|
+
return {
|
|
119
|
+
stroke: ink,
|
|
120
|
+
strokeWidth: 2,
|
|
121
|
+
strokeOpacity: 0.7,
|
|
122
|
+
strokeDasharray: '4 3',
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** One tick written out, tabular so a column of them lines up on the point. */
|
|
127
|
+
export const PARALLEL_TICK_LABEL_STYLE = {
|
|
128
|
+
fontSize: 10,
|
|
129
|
+
fontVariantNumeric: 'tabular-nums',
|
|
130
|
+
userSelect: 'none',
|
|
131
|
+
} as const satisfies CSSProperties;
|
|
132
|
+
|
|
133
|
+
/** How far a tick mark reaches out of the axis, in pixels. */
|
|
134
|
+
export const PARALLEL_TICK_LENGTH = 4;
|
|
135
|
+
|
|
136
|
+
/** How far a tick label ends before its axis. */
|
|
137
|
+
export const PARALLEL_TICK_LABEL_INSET = 7;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The interval one brush keeps, filled in the site's own colour.
|
|
141
|
+
* @param ink - The colour to fill and outline it in.
|
|
142
|
+
* @returns The band's rules.
|
|
143
|
+
*/
|
|
144
|
+
export function parallelBandStyle(ink: string): CSSProperties {
|
|
145
|
+
return {
|
|
146
|
+
fill: ink,
|
|
147
|
+
fillOpacity: 0.16,
|
|
148
|
+
stroke: ink,
|
|
149
|
+
strokeOpacity: 0.55,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Either edge of a band, drawn so the reader can see it can be dragged.
|
|
155
|
+
* @param ink - The colour to draw it in.
|
|
156
|
+
* @returns The handle's rules.
|
|
157
|
+
*/
|
|
158
|
+
export function parallelHandleStyle(ink: string): CSSProperties {
|
|
159
|
+
return { fill: ink, fillOpacity: 0.9 };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** How tall an edge handle is drawn, in pixels. */
|
|
163
|
+
export const PARALLEL_HANDLE_HEIGHT = 3;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* The card over the row under the pointer.
|
|
167
|
+
* @param left - Where it sits, in pixels from the figure's left edge.
|
|
168
|
+
* @param top - Where it sits, in pixels from its top edge.
|
|
169
|
+
* @returns The card's rules.
|
|
170
|
+
*/
|
|
171
|
+
export function parallelTooltipStyle(left: number, top: number): CSSProperties {
|
|
172
|
+
return {
|
|
173
|
+
position: 'absolute',
|
|
174
|
+
zIndex: 5,
|
|
175
|
+
left,
|
|
176
|
+
top,
|
|
177
|
+
padding: '4px 8px',
|
|
178
|
+
borderRadius: 6,
|
|
179
|
+
background: TOKEN.text,
|
|
180
|
+
color: TOKEN.surface,
|
|
181
|
+
fontSize: 11,
|
|
182
|
+
fontVariantNumeric: 'tabular-nums',
|
|
183
|
+
lineHeight: 1.4,
|
|
184
|
+
pointerEvents: 'none',
|
|
185
|
+
whiteSpace: 'nowrap',
|
|
186
|
+
boxShadow: TOKEN.shadowMedium,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dragging an axis by its name to stand somewhere else.
|
|
3
|
+
*
|
|
4
|
+
* Two columns only show their relationship when they are neighbours, so the
|
|
5
|
+
* order the axes stand in is a question the reader asks, not a decision the
|
|
6
|
+
* caller takes once. The name is the handle: it is the only part of an axis
|
|
7
|
+
* that is not already spoken for by the brush, and it is what a reader reaches
|
|
8
|
+
* for anyway.
|
|
9
|
+
*
|
|
10
|
+
* The gesture moves the name and nothing else. Reordering the axes for real
|
|
11
|
+
* would repaint every line on every frame, which a library of ten thousand
|
|
12
|
+
* molecules cannot afford — so the figure shows where the axis would land and
|
|
13
|
+
* moves it once, on release.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { KeyboardEvent, PointerEvent } from 'react';
|
|
17
|
+
import { useRef, useState } from 'react';
|
|
18
|
+
|
|
19
|
+
import type { ParallelAxisLayout } from '../core/parallelAxes.ts';
|
|
20
|
+
import {
|
|
21
|
+
parallelAxisOrder,
|
|
22
|
+
parallelDropIndex,
|
|
23
|
+
parallelMoveAxis,
|
|
24
|
+
} from '../core/parallelOrder.ts';
|
|
25
|
+
|
|
26
|
+
/** What {@link useParallelAxisDrag} needs. */
|
|
27
|
+
export interface ParallelAxisDragOptions {
|
|
28
|
+
/** The axes, from left to right, already placed. */
|
|
29
|
+
layouts: readonly ParallelAxisLayout[];
|
|
30
|
+
/**
|
|
31
|
+
* Called with the axis ids in the order the reader put them in.
|
|
32
|
+
* @default undefined — the axes cannot be moved
|
|
33
|
+
*/
|
|
34
|
+
onAxisOrder?: ((ids: readonly string[]) => void) | undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The name being dragged, and where it would land. */
|
|
38
|
+
export interface ParallelAxisDragState {
|
|
39
|
+
/** Which axis is moving, as an index into the axes. */
|
|
40
|
+
from: number;
|
|
41
|
+
/** How far its name has been dragged, in pixels. */
|
|
42
|
+
dx: number;
|
|
43
|
+
/** Which place it would take if let go now. */
|
|
44
|
+
to: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The reorder gesture, as the figure renders it. */
|
|
48
|
+
export interface ParallelAxisDrag {
|
|
49
|
+
/** Whether the axes can be moved at all. */
|
|
50
|
+
enabled: boolean;
|
|
51
|
+
/** The name being dragged, or `null` when none is. */
|
|
52
|
+
active: ParallelAxisDragState | null;
|
|
53
|
+
/** Spread onto one axis's name. */
|
|
54
|
+
propsFor: (index: number) => ParallelAxisHandleProps;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** What one axis name carries so it can be dragged and stepped with the keys. */
|
|
58
|
+
export interface ParallelAxisHandleProps {
|
|
59
|
+
onPointerDown: (event: PointerEvent<HTMLElement>) => void;
|
|
60
|
+
onPointerMove: (event: PointerEvent<HTMLElement>) => void;
|
|
61
|
+
onPointerUp: (event: PointerEvent<HTMLElement>) => void;
|
|
62
|
+
onPointerCancel: (event: PointerEvent<HTMLElement>) => void;
|
|
63
|
+
onKeyDown: (event: KeyboardEvent<HTMLElement>) => void;
|
|
64
|
+
tabIndex: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** A name that has not been dragged, and is not moving anything. */
|
|
68
|
+
const IDLE: ParallelAxisHandleProps = {
|
|
69
|
+
onPointerDown: noop,
|
|
70
|
+
onPointerMove: noop,
|
|
71
|
+
onPointerUp: noop,
|
|
72
|
+
onPointerCancel: noop,
|
|
73
|
+
onKeyDown: noop,
|
|
74
|
+
tabIndex: -1,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Move an axis by dragging its name, or by stepping it with the arrow keys.
|
|
79
|
+
* @param options - See {@link ParallelAxisDragOptions}.
|
|
80
|
+
* @returns The gesture. See {@link ParallelAxisDrag}.
|
|
81
|
+
*/
|
|
82
|
+
export function useParallelAxisDrag(
|
|
83
|
+
options: ParallelAxisDragOptions,
|
|
84
|
+
): ParallelAxisDrag {
|
|
85
|
+
const { layouts, onAxisOrder } = options;
|
|
86
|
+
const [active, setActive] = useState<ParallelAxisDragState | null>(null);
|
|
87
|
+
const originRef = useRef(0);
|
|
88
|
+
|
|
89
|
+
function move(from: number, to: number): void {
|
|
90
|
+
if (onAxisOrder === undefined) return;
|
|
91
|
+
const order = parallelAxisOrder(layouts);
|
|
92
|
+
if (to < 0 || to >= order.length || to === from) return;
|
|
93
|
+
onAxisOrder(parallelMoveAxis(order, from, to));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function dropAt(from: number, dx: number): number {
|
|
97
|
+
const layout = layouts[from];
|
|
98
|
+
if (layout === undefined) return from;
|
|
99
|
+
return parallelDropIndex(layout.x + dx, layouts);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function propsFor(index: number): ParallelAxisHandleProps {
|
|
103
|
+
if (onAxisOrder === undefined) return IDLE;
|
|
104
|
+
return {
|
|
105
|
+
tabIndex: 0,
|
|
106
|
+
onPointerDown: (event) => {
|
|
107
|
+
if (event.button !== 0) return;
|
|
108
|
+
event.preventDefault();
|
|
109
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
110
|
+
originRef.current = event.clientX;
|
|
111
|
+
setActive({ from: index, dx: 0, to: index });
|
|
112
|
+
},
|
|
113
|
+
onPointerMove: (event) => {
|
|
114
|
+
setActive((current) => {
|
|
115
|
+
if (current?.from !== index) return current;
|
|
116
|
+
const dx = event.clientX - originRef.current;
|
|
117
|
+
return { from: index, dx, to: dropAt(index, dx) };
|
|
118
|
+
});
|
|
119
|
+
},
|
|
120
|
+
onPointerUp: (event) => {
|
|
121
|
+
event.currentTarget.releasePointerCapture?.(event.pointerId);
|
|
122
|
+
setActive((current) => {
|
|
123
|
+
if (current !== null && current.from === index) {
|
|
124
|
+
move(current.from, current.to);
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
});
|
|
128
|
+
},
|
|
129
|
+
onPointerCancel: () => {
|
|
130
|
+
setActive(null);
|
|
131
|
+
},
|
|
132
|
+
onKeyDown: (event) => {
|
|
133
|
+
const step =
|
|
134
|
+
event.key === 'ArrowLeft' ? -1 : event.key === 'ArrowRight' ? 1 : 0;
|
|
135
|
+
if (step === 0) return;
|
|
136
|
+
event.preventDefault();
|
|
137
|
+
move(index, index + step);
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return { enabled: onAxisOrder !== undefined, active, propsFor };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function noop(): void {
|
|
146
|
+
// A figure whose axes are fixed hands its names no handlers at all.
|
|
147
|
+
}
|