react-cheminfo 0.16.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/lib/parallel/core/index.d.ts +7 -5
- package/lib/parallel/core/index.d.ts.map +1 -1
- package/lib/parallel/core/index.js +5 -3
- package/lib/parallel/core/index.js.map +1 -1
- package/lib/parallel/core/parallelBrush.d.ts +22 -1
- package/lib/parallel/core/parallelBrush.d.ts.map +1 -1
- package/lib/parallel/core/parallelBrush.js +53 -3
- package/lib/parallel/core/parallelBrush.js.map +1 -1
- package/lib/parallel/core/parallelFilter.d.ts +14 -1
- package/lib/parallel/core/parallelFilter.d.ts.map +1 -1
- package/lib/parallel/core/parallelFilter.js +44 -11
- package/lib/parallel/core/parallelFilter.js.map +1 -1
- 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 +4 -0
- package/lib/parallel/core/parallelPaint.d.ts.map +1 -1
- package/lib/parallel/core/parallelPaint.js +15 -6
- package/lib/parallel/core/parallelPaint.js.map +1 -1
- package/lib/parallel/core/parallelPalette.d.ts +10 -1
- package/lib/parallel/core/parallelPalette.d.ts.map +1 -1
- package/lib/parallel/core/parallelPalette.js +14 -1
- package/lib/parallel/core/parallelPalette.js.map +1 -1
- 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.map +1 -1
- package/lib/parallel/core/parallelTicks.js +7 -4
- package/lib/parallel/core/parallelTicks.js.map +1 -1
- package/lib/parallel/core/parallelTypes.d.ts +12 -1
- package/lib/parallel/core/parallelTypes.d.ts.map +1 -1
- package/lib/parallel/core/parallelTypes.js.map +1 -1
- package/lib/parallel/ui/ParallelAxesLayer.d.ts +2 -2
- package/lib/parallel/ui/ParallelAxesLayer.d.ts.map +1 -1
- package/lib/parallel/ui/ParallelAxesLayer.js +2 -2
- package/lib/parallel/ui/ParallelAxesLayer.js.map +1 -1
- package/lib/parallel/ui/ParallelCoordinates.d.ts.map +1 -1
- package/lib/parallel/ui/ParallelCoordinates.js +10 -5
- package/lib/parallel/ui/ParallelCoordinates.js.map +1 -1
- package/lib/parallel/ui/ParallelLabels.d.ts +6 -0
- package/lib/parallel/ui/ParallelLabels.d.ts.map +1 -1
- package/lib/parallel/ui/ParallelLabels.js +14 -2
- package/lib/parallel/ui/ParallelLabels.js.map +1 -1
- package/lib/parallel/ui/index.d.ts +4 -0
- package/lib/parallel/ui/index.d.ts.map +1 -1
- package/lib/parallel/ui/index.js +2 -0
- package/lib/parallel/ui/index.js.map +1 -1
- package/lib/parallel/ui/parallelCoordinatesProps.d.ts +26 -8
- package/lib/parallel/ui/parallelCoordinatesProps.d.ts.map +1 -1
- package/lib/parallel/ui/parallelGestureModel.d.ts +37 -6
- package/lib/parallel/ui/parallelGestureModel.d.ts.map +1 -1
- package/lib/parallel/ui/parallelGestureModel.js +42 -12
- package/lib/parallel/ui/parallelGestureModel.js.map +1 -1
- package/lib/parallel/ui/parallelPlotModel.d.ts +1 -1
- package/lib/parallel/ui/parallelPlotModel.d.ts.map +1 -1
- package/lib/parallel/ui/parallelPlotModel.js +3 -3
- package/lib/parallel/ui/parallelPlotModel.js.map +1 -1
- package/lib/parallel/ui/parallelStyles.d.ts +26 -3
- package/lib/parallel/ui/parallelStyles.d.ts.map +1 -1
- package/lib/parallel/ui/parallelStyles.js +26 -4
- package/lib/parallel/ui/parallelStyles.js.map +1 -1
- 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/useParallelGesture.d.ts +10 -5
- package/lib/parallel/ui/useParallelGesture.d.ts.map +1 -1
- package/lib/parallel/ui/useParallelGesture.js +46 -21
- package/lib/parallel/ui/useParallelGesture.js.map +1 -1
- package/package.json +1 -1
- package/src/parallel/core/index.ts +16 -0
- package/src/parallel/core/parallelBrush.ts +69 -3
- package/src/parallel/core/parallelFilter.ts +52 -15
- package/src/parallel/core/parallelOrder.ts +73 -0
- package/src/parallel/core/parallelPaint.ts +16 -5
- package/src/parallel/core/parallelPalette.ts +14 -1
- package/src/parallel/core/parallelSelection.ts +99 -0
- package/src/parallel/core/parallelTicks.ts +7 -3
- package/src/parallel/core/parallelTypes.ts +13 -1
- package/src/parallel/ui/ParallelAxesLayer.tsx +10 -4
- package/src/parallel/ui/ParallelCoordinates.tsx +22 -3
- package/src/parallel/ui/ParallelLabels.tsx +23 -2
- package/src/parallel/ui/index.ts +15 -0
- package/src/parallel/ui/parallelCoordinatesProps.ts +26 -8
- package/src/parallel/ui/parallelGestureModel.ts +63 -13
- package/src/parallel/ui/parallelPlotModel.ts +3 -3
- package/src/parallel/ui/parallelStyles.ts +46 -4
- package/src/parallel/ui/useParallelAxisDrag.ts +147 -0
- package/src/parallel/ui/useParallelGesture.ts +61 -28
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an axis keeps, when it keeps more than one interval.
|
|
3
|
+
*
|
|
4
|
+
* The figure reports a list and accepts either a list or a bare interval, so a
|
|
5
|
+
* caller that only ever keeps one is not made to write `[[0, 1]]`. Everything
|
|
6
|
+
* downstream — the filter, the bands, the gesture — reads the list, which is
|
|
7
|
+
* what {@link parallelRangeList} is for: one normalisation, at the edge, and
|
|
8
|
+
* no `Array.isArray` scattered through the painter.
|
|
9
|
+
*
|
|
10
|
+
* Intervals on one axis are held sorted and disjoint. Two that touch are one
|
|
11
|
+
* interval drawn twice, and a reader dragging a band across its neighbour means
|
|
12
|
+
* to widen it rather than to stack two bands nobody can then take hold of.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { ParallelRange, ParallelSelection } from './parallelTypes.ts';
|
|
16
|
+
|
|
17
|
+
/** Nothing kept, shared so a normalisation never allocates for the common case. */
|
|
18
|
+
const NONE: readonly ParallelRange[] = [];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The intervals one axis keeps, however the caller wrote them.
|
|
22
|
+
* @param selection - One interval, several, or nothing at all.
|
|
23
|
+
* @returns The intervals, in the order given. Read in place — the caller's own
|
|
24
|
+
* array comes back when it gave one.
|
|
25
|
+
*/
|
|
26
|
+
export function parallelRangeList(
|
|
27
|
+
selection: ParallelSelection | undefined,
|
|
28
|
+
): readonly ParallelRange[] {
|
|
29
|
+
if (selection === null || selection === undefined) return NONE;
|
|
30
|
+
if (selection.length === 0) return NONE;
|
|
31
|
+
// A bare interval is two numbers; a list of them holds arrays.
|
|
32
|
+
return typeof selection[0] === 'number'
|
|
33
|
+
? [selection as ParallelRange]
|
|
34
|
+
: (selection as readonly ParallelRange[]);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Whether an axis keeps anything at all.
|
|
39
|
+
* @param selection - What that axis keeps.
|
|
40
|
+
* @returns Whether any interval is in force.
|
|
41
|
+
*/
|
|
42
|
+
export function parallelHasRange(
|
|
43
|
+
selection: ParallelSelection | undefined,
|
|
44
|
+
): boolean {
|
|
45
|
+
return parallelRangeList(selection).length > 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The same intervals, sorted low to high and with the overlapping ones joined.
|
|
50
|
+
* @param ranges - The intervals, in any order, each low value first.
|
|
51
|
+
* @returns One list, sorted and disjoint. Intervals that touch at a point are
|
|
52
|
+
* joined too: a reader who dragged one band onto another meant one band.
|
|
53
|
+
*/
|
|
54
|
+
export function parallelMergeRanges(
|
|
55
|
+
ranges: readonly ParallelRange[],
|
|
56
|
+
): ParallelRange[] {
|
|
57
|
+
if (ranges.length === 0) return [];
|
|
58
|
+
const sorted = ranges
|
|
59
|
+
.map((range) =>
|
|
60
|
+
range[0] <= range[1] ? range : ([range[1], range[0]] as ParallelRange),
|
|
61
|
+
)
|
|
62
|
+
.toSorted((left, right) => left[0] - right[0]);
|
|
63
|
+
const merged: ParallelRange[] = [];
|
|
64
|
+
let low = (sorted[0] as ParallelRange)[0];
|
|
65
|
+
let high = (sorted[0] as ParallelRange)[1];
|
|
66
|
+
for (let index = 1; index < sorted.length; index++) {
|
|
67
|
+
const range = sorted[index] as ParallelRange;
|
|
68
|
+
if (range[0] > high) {
|
|
69
|
+
merged.push([low, high]);
|
|
70
|
+
low = range[0];
|
|
71
|
+
high = range[1];
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (range[1] > high) high = range[1];
|
|
75
|
+
}
|
|
76
|
+
merged.push([low, high]);
|
|
77
|
+
return merged;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The intervals an axis keeps once one of them is replaced, removed or added.
|
|
82
|
+
* @param ranges - The intervals in force.
|
|
83
|
+
* @param index - Which one is being written, or `-1` to add one.
|
|
84
|
+
* @param range - The interval it becomes, or `null` to take it away.
|
|
85
|
+
* @returns The new list, sorted and disjoint. Never the same array.
|
|
86
|
+
*/
|
|
87
|
+
export function parallelWriteRange(
|
|
88
|
+
ranges: readonly ParallelRange[],
|
|
89
|
+
index: number,
|
|
90
|
+
range: ParallelRange | null,
|
|
91
|
+
): ParallelRange[] {
|
|
92
|
+
const kept: ParallelRange[] = [];
|
|
93
|
+
for (let at = 0; at < ranges.length; at++) {
|
|
94
|
+
if (at === index) continue;
|
|
95
|
+
kept.push(ranges[at] as ParallelRange);
|
|
96
|
+
}
|
|
97
|
+
if (range !== null) kept.push(range);
|
|
98
|
+
return parallelMergeRanges(kept);
|
|
99
|
+
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import {
|
|
11
11
|
chartAxisScale,
|
|
12
12
|
chartTickDecimals,
|
|
13
|
+
chartTickLabel,
|
|
13
14
|
} from '../../chart/core/chartAxisScale.ts';
|
|
14
15
|
import { formatDecimal } from '../../format/core/numbers.ts';
|
|
15
16
|
import { roundTo } from '../../format/core/roundTo.ts';
|
|
@@ -71,12 +72,15 @@ function nicedGraduation(
|
|
|
71
72
|
wanted: number,
|
|
72
73
|
): ParallelGraduation {
|
|
73
74
|
const scale = chartAxisScale(min, max, { count: wanted, nice: true });
|
|
75
|
+
// Not `scale.labels`: those have a common power of ten lifted out of them,
|
|
76
|
+
// on the understanding that the caller writes it beside the axis name, and
|
|
77
|
+
// this figure writes only the name. Each graduation carries its own value.
|
|
78
|
+
const decimals = chartTickDecimals(scale.step);
|
|
74
79
|
const ticks: ParallelTick[] = [];
|
|
75
|
-
for (
|
|
76
|
-
const value = scale.values[index] as number;
|
|
80
|
+
for (const value of scale.values) {
|
|
77
81
|
const label =
|
|
78
82
|
axis.format === undefined
|
|
79
|
-
? (
|
|
83
|
+
? chartTickLabel(value, decimals)
|
|
80
84
|
: axis.format(value);
|
|
81
85
|
if (ticks.at(-1)?.label === label) continue;
|
|
82
86
|
ticks.push({ value, label });
|
|
@@ -86,8 +86,20 @@ export interface ParallelAxis {
|
|
|
86
86
|
/** An interval kept on one axis, in that axis's own units, low first. */
|
|
87
87
|
export type ParallelRange = readonly [number, number];
|
|
88
88
|
|
|
89
|
+
/**
|
|
90
|
+
* What one axis keeps: nothing, one interval, or several disjoint ones.
|
|
91
|
+
*
|
|
92
|
+
* A reader comparing molecules wants the light ones *and* the heavy ones, with
|
|
93
|
+
* nothing in between, which one interval cannot say. Several are read as a
|
|
94
|
+
* union — a row is kept when it falls in any of them — while the axes are
|
|
95
|
+
* still read as an intersection, so each axis narrows what the ones before it
|
|
96
|
+
* left. A bare interval means the same as a list holding only it, so a caller
|
|
97
|
+
* that only ever keeps one writes one.
|
|
98
|
+
*/
|
|
99
|
+
export type ParallelSelection = ParallelRange | readonly ParallelRange[] | null;
|
|
100
|
+
|
|
89
101
|
/** Which rows a set of brushes keeps, keyed by axis id. */
|
|
90
|
-
export type ParallelRanges = Readonly<Record<string,
|
|
102
|
+
export type ParallelRanges = Readonly<Record<string, ParallelSelection>>;
|
|
91
103
|
|
|
92
104
|
/** How the lines are coloured. */
|
|
93
105
|
export interface ParallelColorBy {
|
|
@@ -20,8 +20,8 @@ export interface ParallelAxesLayerProps {
|
|
|
20
20
|
layouts: readonly ParallelAxisLayout[];
|
|
21
21
|
/** Height of the drawing area, in pixels. */
|
|
22
22
|
innerHeight: number;
|
|
23
|
-
/** The
|
|
24
|
-
bands: ReadonlyMap<string, ParallelBand>;
|
|
23
|
+
/** The bands each axis carries right now, the one being dragged included. */
|
|
24
|
+
bands: ReadonlyMap<string, ParallelBand[]>;
|
|
25
25
|
/** The ink the axes, their ticks and their labels are drawn in. */
|
|
26
26
|
axisInk: string;
|
|
27
27
|
/** The ink a band is filled and outlined in. */
|
|
@@ -49,7 +49,7 @@ export function ParallelAxesLayer(props: ParallelAxesLayerProps): ReactElement {
|
|
|
49
49
|
textAnchor="middle"
|
|
50
50
|
>
|
|
51
51
|
{layouts.map((layout) => {
|
|
52
|
-
const
|
|
52
|
+
const drawn = bands.get(layout.id);
|
|
53
53
|
return (
|
|
54
54
|
<g
|
|
55
55
|
key={layout.id}
|
|
@@ -83,7 +83,13 @@ export function ParallelAxesLayer(props: ParallelAxesLayerProps): ReactElement {
|
|
|
83
83
|
fill="transparent"
|
|
84
84
|
pointerEvents="none"
|
|
85
85
|
/>
|
|
86
|
-
{band
|
|
86
|
+
{drawn?.map((band) => (
|
|
87
|
+
<BrushBand
|
|
88
|
+
key={`${band.top}:${band.bottom}`}
|
|
89
|
+
band={band}
|
|
90
|
+
ink={bandInk}
|
|
91
|
+
/>
|
|
92
|
+
))}
|
|
87
93
|
</g>
|
|
88
94
|
);
|
|
89
95
|
})}
|
|
@@ -30,8 +30,10 @@ import {
|
|
|
30
30
|
import {
|
|
31
31
|
PARALLEL_SVG_STYLE,
|
|
32
32
|
parallelCanvasStyle,
|
|
33
|
+
parallelDropMarkStyle,
|
|
33
34
|
parallelFigureStyle,
|
|
34
35
|
} from './parallelStyles.ts';
|
|
36
|
+
import { useParallelAxisDrag } from './useParallelAxisDrag.ts';
|
|
35
37
|
import { useParallelCanvas } from './useParallelCanvas.ts';
|
|
36
38
|
import { useParallelGesture } from './useParallelGesture.ts';
|
|
37
39
|
|
|
@@ -70,6 +72,7 @@ export function ParallelCoordinates(
|
|
|
70
72
|
const { ranges: ownedRanges, defaultRanges, included: givenMask } = props;
|
|
71
73
|
const { color: givenColor, colorAxis, onRangeChange: report } = props;
|
|
72
74
|
const { onRangePreview, hovered, onHoverChange, onRowClick } = props;
|
|
75
|
+
const { several = false, onAxisOrder } = props;
|
|
73
76
|
const count = parallelRowCount(axes, props.count);
|
|
74
77
|
const innerWidth = Math.max(
|
|
75
78
|
width - PARALLEL_MARGIN.left - PARALLEL_MARGIN.right,
|
|
@@ -109,11 +112,11 @@ export function ParallelCoordinates(
|
|
|
109
112
|
return parallelIncludedMask(axes, ranges, count);
|
|
110
113
|
}, [givenMask, axes, ranges, count]);
|
|
111
114
|
|
|
112
|
-
function commitRange(axisId: string,
|
|
115
|
+
function commitRange(axisId: string, kept: readonly ParallelRange[]): void {
|
|
113
116
|
if (ownedRanges === undefined) {
|
|
114
|
-
setOwnRanges((previous) => ({ ...previous, [axisId]:
|
|
117
|
+
setOwnRanges((previous) => ({ ...previous, [axisId]: kept }));
|
|
115
118
|
}
|
|
116
|
-
report?.(axisId,
|
|
119
|
+
report?.(axisId, kept);
|
|
117
120
|
}
|
|
118
121
|
|
|
119
122
|
const gesture = useParallelGesture({
|
|
@@ -123,6 +126,7 @@ export function ParallelCoordinates(
|
|
|
123
126
|
innerHeight,
|
|
124
127
|
included,
|
|
125
128
|
ranges,
|
|
129
|
+
several,
|
|
126
130
|
onRangeChange: commitRange,
|
|
127
131
|
onRangePreview,
|
|
128
132
|
hovered,
|
|
@@ -148,6 +152,10 @@ export function ParallelCoordinates(
|
|
|
148
152
|
ink,
|
|
149
153
|
});
|
|
150
154
|
|
|
155
|
+
const axisDrag = useParallelAxisDrag({ layouts, onAxisOrder });
|
|
156
|
+
const dropAt =
|
|
157
|
+
axisDrag.active === null ? undefined : layouts[axisDrag.active.to];
|
|
158
|
+
|
|
151
159
|
const label = given ?? parallelFigureLabel(axes, count);
|
|
152
160
|
const canvasStyle = parallelCanvasStyle(height);
|
|
153
161
|
const hover =
|
|
@@ -199,11 +207,22 @@ export function ParallelCoordinates(
|
|
|
199
207
|
onClick={gesture.onClick}
|
|
200
208
|
{...gesture.surface}
|
|
201
209
|
/>
|
|
210
|
+
{dropAt === undefined ? null : (
|
|
211
|
+
<line
|
|
212
|
+
data-parallel-drop={dropAt.id}
|
|
213
|
+
x1={PARALLEL_MARGIN.left + dropAt.x}
|
|
214
|
+
x2={PARALLEL_MARGIN.left + dropAt.x}
|
|
215
|
+
y1={PARALLEL_MARGIN.top}
|
|
216
|
+
y2={PARALLEL_MARGIN.top + innerHeight}
|
|
217
|
+
style={parallelDropMarkStyle(ink.selection)}
|
|
218
|
+
/>
|
|
219
|
+
)}
|
|
202
220
|
</svg>
|
|
203
221
|
<ParallelLabels
|
|
204
222
|
axes={axes}
|
|
205
223
|
layouts={layouts}
|
|
206
224
|
render={renderAxisLabel}
|
|
225
|
+
drag={axisDrag}
|
|
207
226
|
/>
|
|
208
227
|
{hover === null || renderTooltip === undefined ? null : (
|
|
209
228
|
<ParallelTooltip
|
|
@@ -5,6 +5,7 @@ import { PARALLEL_MARGIN } from '../core/parallelAxes.ts';
|
|
|
5
5
|
import type { ParallelAxis } from '../core/parallelTypes.ts';
|
|
6
6
|
|
|
7
7
|
import { PARALLEL_LABELS_STYLE, parallelLabelStyle } from './parallelStyles.ts';
|
|
8
|
+
import type { ParallelAxisDrag } from './useParallelAxisDrag.ts';
|
|
8
9
|
|
|
9
10
|
/** What {@link ParallelLabels} writes. */
|
|
10
11
|
export interface ParallelLabelsProps {
|
|
@@ -17,6 +18,11 @@ export interface ParallelLabelsProps {
|
|
|
17
18
|
* @default undefined
|
|
18
19
|
*/
|
|
19
20
|
render?: ((axis: ParallelAxis, index: number) => ReactNode) | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* The gesture that moves an axis by its name.
|
|
23
|
+
* @default undefined — the axes are fixed
|
|
24
|
+
*/
|
|
25
|
+
drag?: ParallelAxisDrag | undefined;
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
/**
|
|
@@ -29,17 +35,32 @@ export interface ParallelLabelsProps {
|
|
|
29
35
|
* @returns The names.
|
|
30
36
|
*/
|
|
31
37
|
export function ParallelLabels(props: ParallelLabelsProps): ReactElement {
|
|
32
|
-
const { axes, layouts, render } = props;
|
|
38
|
+
const { axes, layouts, render, drag } = props;
|
|
39
|
+
const draggable = drag?.enabled ?? false;
|
|
40
|
+
const active = drag?.active ?? null;
|
|
33
41
|
|
|
34
42
|
return (
|
|
35
43
|
<div className="parallel-coordinates-labels" style={PARALLEL_LABELS_STYLE}>
|
|
36
44
|
{layouts.map((layout, index) => {
|
|
37
45
|
const axis = axes[index];
|
|
46
|
+
const moving = active !== null && active.from === index;
|
|
38
47
|
return (
|
|
39
48
|
<div
|
|
40
49
|
key={layout.id}
|
|
41
50
|
className="parallel-axis-label"
|
|
42
|
-
|
|
51
|
+
data-parallel-label={layout.id}
|
|
52
|
+
style={parallelLabelStyle({
|
|
53
|
+
left: PARALLEL_MARGIN.left + layout.x,
|
|
54
|
+
draggable,
|
|
55
|
+
dx: moving ? active.dx : null,
|
|
56
|
+
})}
|
|
57
|
+
{...drag?.propsFor(index)}
|
|
58
|
+
{...(draggable
|
|
59
|
+
? {
|
|
60
|
+
role: 'button',
|
|
61
|
+
'aria-label': `${layout.label}: move this axis with the left and right arrow keys`,
|
|
62
|
+
}
|
|
63
|
+
: null)}
|
|
43
64
|
>
|
|
44
65
|
{axis !== undefined && render !== undefined
|
|
45
66
|
? render(axis, index)
|
package/src/parallel/ui/index.ts
CHANGED
|
@@ -29,6 +29,14 @@ export {
|
|
|
29
29
|
} from './parallelInk.ts';
|
|
30
30
|
export { ParallelLabels } from './ParallelLabels.tsx';
|
|
31
31
|
export type { ParallelLabelsProps } from './ParallelLabels.tsx';
|
|
32
|
+
export type { ParallelCommit, ParallelDraft } from './parallelGestureModel.ts';
|
|
33
|
+
export {
|
|
34
|
+
parallelAxisById,
|
|
35
|
+
parallelAxisNear,
|
|
36
|
+
parallelBandsOf,
|
|
37
|
+
parallelCommitOf,
|
|
38
|
+
parallelPointOf,
|
|
39
|
+
} from './parallelGestureModel.ts';
|
|
32
40
|
export {
|
|
33
41
|
parallelColorOf,
|
|
34
42
|
parallelFigureLabel,
|
|
@@ -43,6 +51,13 @@ export type {
|
|
|
43
51
|
ParallelCanvases,
|
|
44
52
|
} from './useParallelCanvas.ts';
|
|
45
53
|
export { useParallelCanvas } from './useParallelCanvas.ts';
|
|
54
|
+
export type {
|
|
55
|
+
ParallelAxisDrag,
|
|
56
|
+
ParallelAxisDragOptions,
|
|
57
|
+
ParallelAxisDragState,
|
|
58
|
+
ParallelAxisHandleProps,
|
|
59
|
+
} from './useParallelAxisDrag.ts';
|
|
60
|
+
export { useParallelAxisDrag } from './useParallelAxisDrag.ts';
|
|
46
61
|
export type {
|
|
47
62
|
ParallelGesture,
|
|
48
63
|
ParallelGestureOptions,
|
|
@@ -84,10 +84,11 @@ export interface ParallelCoordinatesProps {
|
|
|
84
84
|
height?: number;
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
|
-
*
|
|
88
|
-
* the brushes — which is what lets a table beside
|
|
89
|
-
* same ranges. The band being dragged stays inside
|
|
90
|
-
* so a controlled parent is never asked to
|
|
87
|
+
* What each axis keeps, keyed by axis id: one interval, several, or `null`.
|
|
88
|
+
* Present, the caller owns the brushes — which is what lets a table beside
|
|
89
|
+
* the figure filter on the same ranges. The band being dragged stays inside
|
|
90
|
+
* the component either way, so a controlled parent is never asked to
|
|
91
|
+
* re-render sixty times a second.
|
|
91
92
|
* @default undefined — the figure keeps its own
|
|
92
93
|
*/
|
|
93
94
|
ranges?: ParallelRanges;
|
|
@@ -97,17 +98,25 @@ export interface ParallelCoordinatesProps {
|
|
|
97
98
|
*/
|
|
98
99
|
defaultRanges?: ParallelRanges;
|
|
99
100
|
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
101
|
+
* Whether one axis may keep several intervals at once — the light molecules
|
|
102
|
+
* and the heavy ones, with nothing in between. A reader brushes a second
|
|
103
|
+
* interval by dragging on a bare part of an axis that already carries one,
|
|
104
|
+
* and takes one away by clicking it.
|
|
105
|
+
* @default false — a new brush replaces the one the axis carried
|
|
106
|
+
*/
|
|
107
|
+
several?: boolean;
|
|
108
|
+
/**
|
|
109
|
+
* Called when a brush is released, with every interval that axis now keeps —
|
|
110
|
+
* empty when the reader cleared it, and never while a band is being dragged.
|
|
102
111
|
* @default undefined
|
|
103
112
|
*/
|
|
104
|
-
onRangeChange?: (axisId: string,
|
|
113
|
+
onRangeChange?: (axisId: string, ranges: readonly ParallelRange[]) => void;
|
|
105
114
|
/**
|
|
106
115
|
* Called on every frame a band grows, for a caption saying what letting go
|
|
107
116
|
* would keep. Nothing else should be wired to it.
|
|
108
117
|
* @default undefined
|
|
109
118
|
*/
|
|
110
|
-
onRangePreview?: (axisId: string,
|
|
119
|
+
onRangePreview?: (axisId: string, ranges: readonly ParallelRange[]) => void;
|
|
111
120
|
/**
|
|
112
121
|
* Which rows the brushes keep, one entry per row, a zero meaning excluded.
|
|
113
122
|
* Left out, it is worked out from the ranges with `parallelIncludedMask`,
|
|
@@ -170,6 +179,15 @@ export interface ParallelCoordinatesProps {
|
|
|
170
179
|
* @default the axis label, and its unit after it
|
|
171
180
|
*/
|
|
172
181
|
renderAxisLabel?: (axis: ParallelAxis, index: number) => ReactNode;
|
|
182
|
+
/**
|
|
183
|
+
* Called with the axis ids in the order the reader dragged them into.
|
|
184
|
+
* Present, every axis name becomes a handle: drag it, or focus it and press
|
|
185
|
+
* the left and right arrow keys. Two columns only show their relationship
|
|
186
|
+
* when they stand next to each other, so this is how a reader asks a
|
|
187
|
+
* different question of the same figure.
|
|
188
|
+
* @default undefined — the axes stand in the order the caller gave them
|
|
189
|
+
*/
|
|
190
|
+
onAxisOrder?: (ids: readonly string[]) => void;
|
|
173
191
|
/**
|
|
174
192
|
* What is shown in place of the figure when there is nothing to draw.
|
|
175
193
|
* @default undefined — an empty figure of the given height
|
|
@@ -15,13 +15,21 @@ import type { ParallelBand } from '../core/parallelBrush.ts';
|
|
|
15
15
|
import {
|
|
16
16
|
PARALLEL_BRUSH_HALF_WIDTH,
|
|
17
17
|
parallelBandOf,
|
|
18
|
+
parallelIsBand,
|
|
19
|
+
parallelRangeOf,
|
|
18
20
|
} from '../core/parallelBrush.ts';
|
|
19
|
-
import
|
|
21
|
+
import {
|
|
22
|
+
parallelRangeList,
|
|
23
|
+
parallelWriteRange,
|
|
24
|
+
} from '../core/parallelSelection.ts';
|
|
25
|
+
import type { ParallelRange, ParallelRanges } from '../core/parallelTypes.ts';
|
|
20
26
|
|
|
21
27
|
/** A band being dragged, before the interval it keeps is reported. */
|
|
22
28
|
export interface ParallelDraft {
|
|
23
29
|
/** The axis it is on. */
|
|
24
30
|
axis: string;
|
|
31
|
+
/** Which of that axis's bands it is, or `-1` when it is a new one. */
|
|
32
|
+
index: number;
|
|
25
33
|
/** Where it stands this frame. */
|
|
26
34
|
band: ParallelBand;
|
|
27
35
|
}
|
|
@@ -83,27 +91,69 @@ export function parallelAxisById(
|
|
|
83
91
|
}
|
|
84
92
|
|
|
85
93
|
/**
|
|
86
|
-
* The
|
|
87
|
-
* the
|
|
94
|
+
* The bands to draw on each axis: the one being dragged where there is one,
|
|
95
|
+
* and the intervals in force everywhere else.
|
|
88
96
|
* @param axes - The axes, from left to right.
|
|
89
|
-
* @param ranges -
|
|
97
|
+
* @param ranges - What each axis keeps: one interval, several, or none.
|
|
90
98
|
* @param draft - The band being dragged, or `null` when nothing is.
|
|
91
|
-
* @returns The bands, keyed by axis id; an axis with none is left out.
|
|
99
|
+
* @returns The bands, keyed by axis id; an axis with none is left out. The
|
|
100
|
+
* band being dragged replaces the interval it came from rather than being
|
|
101
|
+
* drawn beside it, so an interval never appears twice mid-gesture.
|
|
92
102
|
*/
|
|
93
103
|
export function parallelBandsOf(
|
|
94
104
|
axes: readonly ParallelAxisLayout[],
|
|
95
105
|
ranges: ParallelRanges,
|
|
96
106
|
draft: ParallelDraft | null,
|
|
97
|
-
): ReadonlyMap<string, ParallelBand> {
|
|
98
|
-
const drawn = new Map<string, ParallelBand>();
|
|
107
|
+
): ReadonlyMap<string, ParallelBand[]> {
|
|
108
|
+
const drawn = new Map<string, ParallelBand[]>();
|
|
99
109
|
for (const axis of axes) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
110
|
+
const list = parallelRangeList(ranges[axis.id]);
|
|
111
|
+
const dragging = draft !== null && draft.axis === axis.id;
|
|
112
|
+
const bands: ParallelBand[] = [];
|
|
113
|
+
for (let index = 0; index < list.length; index++) {
|
|
114
|
+
if (dragging && index === draft.index) continue;
|
|
115
|
+
bands.push(parallelBandOf(list[index] as ParallelRange, axis));
|
|
103
116
|
}
|
|
104
|
-
|
|
105
|
-
if (
|
|
106
|
-
drawn.set(axis.id, parallelBandOf(range, axis));
|
|
117
|
+
if (dragging) bands.push(draft.band);
|
|
118
|
+
if (bands.length > 0) drawn.set(axis.id, bands);
|
|
107
119
|
}
|
|
108
120
|
return drawn;
|
|
109
121
|
}
|
|
122
|
+
|
|
123
|
+
/** What a released band does to the intervals its axis already kept. */
|
|
124
|
+
export interface ParallelCommit {
|
|
125
|
+
/** The intervals in force on that axis. */
|
|
126
|
+
list: readonly ParallelRange[];
|
|
127
|
+
/** Which of them the drag took hold of, or `-1` when it began a new one. */
|
|
128
|
+
index: number;
|
|
129
|
+
/** The band as the pointer left it. */
|
|
130
|
+
band: ParallelBand;
|
|
131
|
+
/** The axis it is on, to read the band back into its own units. */
|
|
132
|
+
layout: ParallelAxisLayout;
|
|
133
|
+
/** Whether the press took an interval and let it go without moving it. */
|
|
134
|
+
still: boolean;
|
|
135
|
+
/** Whether the axis may keep more than one interval. */
|
|
136
|
+
several: boolean;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The intervals an axis keeps once a band has been let go.
|
|
141
|
+
*
|
|
142
|
+
* A press that keeps nothing is a click, and a click says "take this away":
|
|
143
|
+
* the interval under the pointer when it landed on one, and every interval on
|
|
144
|
+
* the axis when it landed on bare axis. That is the only way to be rid of one
|
|
145
|
+
* interval on an axis carrying four, and it is the gesture that already
|
|
146
|
+
* cleared an axis before it could carry more than one.
|
|
147
|
+
* @param commit - See {@link ParallelCommit}.
|
|
148
|
+
* @returns The intervals, sorted and disjoint.
|
|
149
|
+
*/
|
|
150
|
+
export function parallelCommitOf(commit: ParallelCommit): ParallelRange[] {
|
|
151
|
+
const { list, index, band, layout, still, several } = commit;
|
|
152
|
+
if (still) return parallelWriteRange(list, index, null);
|
|
153
|
+
if (!parallelIsBand(band)) {
|
|
154
|
+
return index === -1 ? [] : parallelWriteRange(list, index, null);
|
|
155
|
+
}
|
|
156
|
+
const range = parallelRangeOf(band, layout);
|
|
157
|
+
if (!several && index === -1) return [range];
|
|
158
|
+
return parallelWriteRange(list, index, range);
|
|
159
|
+
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { formatInteger } from '../../format/core/numbers.ts';
|
|
12
12
|
import type { ParallelHighlight } from '../core/parallelPaint.ts';
|
|
13
|
+
import { parallelHasRange } from '../core/parallelSelection.ts';
|
|
13
14
|
import type {
|
|
14
15
|
ParallelAxis,
|
|
15
16
|
ParallelColorBy,
|
|
@@ -71,7 +72,7 @@ export function parallelColorOf(
|
|
|
71
72
|
/**
|
|
72
73
|
* Whether any axis on the figure carries an interval at all.
|
|
73
74
|
* @param axes - The axes, whose ids the intervals are keyed by.
|
|
74
|
-
* @param ranges -
|
|
75
|
+
* @param ranges - What each axis keeps.
|
|
75
76
|
* @returns Whether anything is brushed, which is what decides whether a line
|
|
76
77
|
* is ever drawn in the excluded ink.
|
|
77
78
|
*/
|
|
@@ -80,8 +81,7 @@ export function parallelIsBrushed(
|
|
|
80
81
|
ranges: ParallelRanges,
|
|
81
82
|
): boolean {
|
|
82
83
|
for (const axis of axes) {
|
|
83
|
-
|
|
84
|
-
if (range !== null && range !== undefined) return true;
|
|
84
|
+
if (parallelHasRange(ranges[axis.id])) return true;
|
|
85
85
|
}
|
|
86
86
|
return false;
|
|
87
87
|
}
|
|
@@ -62,22 +62,64 @@ export const PARALLEL_LABELS_STYLE = {
|
|
|
62
62
|
pointerEvents: 'none',
|
|
63
63
|
} as const satisfies CSSProperties;
|
|
64
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
|
+
|
|
65
81
|
/**
|
|
66
82
|
* One axis name, centred over its axis and taking the pointer back.
|
|
67
|
-
* @param
|
|
68
|
-
* @returns The name's rules.
|
|
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.
|
|
69
87
|
*/
|
|
70
|
-
export function parallelLabelStyle(
|
|
88
|
+
export function parallelLabelStyle(
|
|
89
|
+
placement: ParallelLabelPlacement,
|
|
90
|
+
): CSSProperties {
|
|
91
|
+
const { left, draggable = false, dx = null } = placement;
|
|
92
|
+
const moving = dx !== null;
|
|
71
93
|
return {
|
|
72
94
|
position: 'absolute',
|
|
95
|
+
zIndex: moving ? 2 : undefined,
|
|
73
96
|
top: 2,
|
|
74
97
|
left,
|
|
75
|
-
transform:
|
|
98
|
+
transform:
|
|
99
|
+
dx === null ? 'translateX(-50%)' : `translateX(calc(-50% + ${dx}px))`,
|
|
76
100
|
color: TOKEN.text,
|
|
77
101
|
fontSize: 11,
|
|
78
102
|
fontWeight: 600,
|
|
79
103
|
whiteSpace: 'nowrap',
|
|
80
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',
|
|
81
123
|
};
|
|
82
124
|
}
|
|
83
125
|
|