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,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How one axis is graduated.
|
|
3
|
+
*
|
|
4
|
+
* Five evenly spaced marks across the raw extent, not `chartAxisScale`'s nice
|
|
5
|
+
* ones: nicing moves the ends of the axis, and a figure whose whole job is to
|
|
6
|
+
* compare rows against each other must end exactly where the data does. A
|
|
7
|
+
* caller who wants round numbers asks for them with `nice`, per axis.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
chartAxisScale,
|
|
12
|
+
chartTickDecimals,
|
|
13
|
+
chartTickLabel,
|
|
14
|
+
} from '../../chart/core/chartAxisScale.ts';
|
|
15
|
+
import { formatDecimal } from '../../format/core/numbers.ts';
|
|
16
|
+
import { roundTo } from '../../format/core/roundTo.ts';
|
|
17
|
+
|
|
18
|
+
import type { ParallelAxis, ParallelTick } from './parallelTypes.ts';
|
|
19
|
+
|
|
20
|
+
/** How many graduations an axis carries when the caller asks for no number. */
|
|
21
|
+
export const PARALLEL_TICK_COUNT = 5;
|
|
22
|
+
|
|
23
|
+
/** The most decimals a graduation is ever written with. */
|
|
24
|
+
const MOST_TICK_DECIMALS = 6;
|
|
25
|
+
|
|
26
|
+
/** How far a label may sit from its own value: a tenth of the spacing. */
|
|
27
|
+
const LABEL_TOLERANCE = 10;
|
|
28
|
+
|
|
29
|
+
/** An axis's ends and the graduations written along it. */
|
|
30
|
+
export interface ParallelGraduation {
|
|
31
|
+
/** The two ends of the axis, low first, after any nicing. */
|
|
32
|
+
domain: readonly [number, number];
|
|
33
|
+
/** The graduations, from the bottom of the axis to the top. */
|
|
34
|
+
ticks: readonly ParallelTick[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The graduations of one axis, and the ends they are spread between.
|
|
39
|
+
*
|
|
40
|
+
* Three ways, in order: the caller's own list wins outright — a coded quantity
|
|
41
|
+
* reading `none`, `low`, `high` is nobody else's to invent; `nice` hands the
|
|
42
|
+
* axis to `chartAxisScale`, which rounds both ends outward to whole steps; and
|
|
43
|
+
* otherwise the extent is divided evenly and a mark whose label repeats the
|
|
44
|
+
* one below it is dropped, so an axis of whole numbers from 0 to 2 reads
|
|
45
|
+
* `0 1 2` rather than writing `1` twice.
|
|
46
|
+
* @param axis - The axis, for its formatting and how many marks it wants.
|
|
47
|
+
* @param min - The value at the bottom of the axis.
|
|
48
|
+
* @param max - The value at its top.
|
|
49
|
+
* @returns The ends and the graduations. See {@link ParallelGraduation}.
|
|
50
|
+
*/
|
|
51
|
+
export function parallelTicks(
|
|
52
|
+
axis: ParallelAxis,
|
|
53
|
+
min: number,
|
|
54
|
+
max: number,
|
|
55
|
+
): ParallelGraduation {
|
|
56
|
+
const domain = [min, max] as const;
|
|
57
|
+
if (axis.ticks !== undefined) return { domain, ticks: axis.ticks };
|
|
58
|
+
|
|
59
|
+
const wanted = Math.max(2, Math.trunc(axis.tickCount ?? PARALLEL_TICK_COUNT));
|
|
60
|
+
if (axis.nice === true && axis.scale !== 'log') {
|
|
61
|
+
return nicedGraduation(axis, min, max, wanted);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const values = evenValues(min, max, wanted, axis.scale === 'log');
|
|
65
|
+
return { domain, ticks: labelled(values, axis.format) };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function nicedGraduation(
|
|
69
|
+
axis: ParallelAxis,
|
|
70
|
+
min: number,
|
|
71
|
+
max: number,
|
|
72
|
+
wanted: number,
|
|
73
|
+
): ParallelGraduation {
|
|
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);
|
|
79
|
+
const ticks: ParallelTick[] = [];
|
|
80
|
+
for (const value of scale.values) {
|
|
81
|
+
const label =
|
|
82
|
+
axis.format === undefined
|
|
83
|
+
? chartTickLabel(value, decimals)
|
|
84
|
+
: axis.format(value);
|
|
85
|
+
if (ticks.at(-1)?.label === label) continue;
|
|
86
|
+
ticks.push({ value, label });
|
|
87
|
+
}
|
|
88
|
+
return { domain: scale.domain, ticks };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function evenValues(
|
|
92
|
+
min: number,
|
|
93
|
+
max: number,
|
|
94
|
+
wanted: number,
|
|
95
|
+
logarithmic: boolean,
|
|
96
|
+
): number[] {
|
|
97
|
+
const values: number[] = [];
|
|
98
|
+
const low = logarithmic ? Math.log10(Math.max(min, smallestStep(max))) : min;
|
|
99
|
+
const high = logarithmic ? Math.log10(Math.max(max, smallestStep(max))) : max;
|
|
100
|
+
const span = high - low;
|
|
101
|
+
for (let index = 0; index < wanted; index++) {
|
|
102
|
+
const placed = low + (span * index) / (wanted - 1);
|
|
103
|
+
values.push(logarithmic ? 10 ** placed : placed);
|
|
104
|
+
}
|
|
105
|
+
return values;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function labelled(
|
|
109
|
+
values: readonly number[],
|
|
110
|
+
format: ((value: number) => string) | undefined,
|
|
111
|
+
): ParallelTick[] {
|
|
112
|
+
const decimals = format === undefined ? tickDecimals(values) : 0;
|
|
113
|
+
const ticks: ParallelTick[] = [];
|
|
114
|
+
for (const value of values) {
|
|
115
|
+
const label =
|
|
116
|
+
format === undefined ? formatDecimal(value, decimals) : format(value);
|
|
117
|
+
if (ticks.at(-1)?.label === label) continue;
|
|
118
|
+
ticks.push({ value, label });
|
|
119
|
+
}
|
|
120
|
+
return ticks;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* As many decimals as the spacing needs, and one more whenever rounding would
|
|
125
|
+
* move a mark by more than a tenth of that spacing — which is what keeps an
|
|
126
|
+
* axis from 12.3 to 157 reading `12 48 85 121 157` while one from −1.5 to 2.5
|
|
127
|
+
* still reads `−1.5 −0.5 0.5 1.5 2.5` rather than rounding both away.
|
|
128
|
+
* @param values - The graduations, in the axis's own units.
|
|
129
|
+
* @returns How many decimals each of their labels carries.
|
|
130
|
+
*/
|
|
131
|
+
function tickDecimals(values: readonly number[]): number {
|
|
132
|
+
const step = smallestGap(values);
|
|
133
|
+
const tolerance = step / LABEL_TOLERANCE;
|
|
134
|
+
let decimals = chartTickDecimals(step);
|
|
135
|
+
while (
|
|
136
|
+
decimals < MOST_TICK_DECIMALS &&
|
|
137
|
+
!faithful(values, decimals, tolerance)
|
|
138
|
+
) {
|
|
139
|
+
decimals++;
|
|
140
|
+
}
|
|
141
|
+
return decimals;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function faithful(
|
|
145
|
+
values: readonly number[],
|
|
146
|
+
decimals: number,
|
|
147
|
+
tolerance: number,
|
|
148
|
+
): boolean {
|
|
149
|
+
for (const value of values) {
|
|
150
|
+
if (Math.abs(value - roundTo(value, decimals)) > tolerance) return false;
|
|
151
|
+
}
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function smallestGap(values: readonly number[]): number {
|
|
156
|
+
let smallest = Number.POSITIVE_INFINITY;
|
|
157
|
+
for (let index = 1; index < values.length; index++) {
|
|
158
|
+
const gap = Math.abs(
|
|
159
|
+
(values[index] as number) - (values[index - 1] as number),
|
|
160
|
+
);
|
|
161
|
+
if (gap > 0 && gap < smallest) smallest = gap;
|
|
162
|
+
}
|
|
163
|
+
return Number.isFinite(smallest) ? smallest : 1;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function smallestStep(max: number): number {
|
|
167
|
+
return max > 0 ? max * 1e-12 : 1;
|
|
168
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a parallel-coordinates figure is asked to draw: one axis per column,
|
|
3
|
+
* the intervals the reader brushed on them, and the quantity the lines are
|
|
4
|
+
* coloured by.
|
|
5
|
+
*
|
|
6
|
+
* The figure itself is index-based — every axis hands over its column as an
|
|
7
|
+
* `ArrayLike<number>` read in place — so a repaint costs an indexed read per
|
|
8
|
+
* value rather than a call. {@link parallelAxisOf} is the one place a row type
|
|
9
|
+
* is named, and it is run once per data change rather than once per repaint.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { ColorScale } from '../../color/core/interpolate.ts';
|
|
13
|
+
|
|
14
|
+
/** How a value is placed along an axis. */
|
|
15
|
+
export type ParallelAxisScale = 'linear' | 'log';
|
|
16
|
+
|
|
17
|
+
/** One labelled graduation a caller writes itself. */
|
|
18
|
+
export interface ParallelTick {
|
|
19
|
+
/** Where it sits, in the axis's own units. */
|
|
20
|
+
value: number;
|
|
21
|
+
/** What is written beside it. */
|
|
22
|
+
label: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** One vertical axis of a parallel-coordinates figure, and the column it draws. */
|
|
26
|
+
export interface ParallelAxis {
|
|
27
|
+
/**
|
|
28
|
+
* A stable key, which is how a brushed range is reported and how a caller
|
|
29
|
+
* names the axis the colour is read from. Never an index: axes are added and
|
|
30
|
+
* reordered, and a range keyed on a position outlives the axis it belonged
|
|
31
|
+
* to.
|
|
32
|
+
*/
|
|
33
|
+
id: string;
|
|
34
|
+
/** What it is called, written over the axis. */
|
|
35
|
+
label: string;
|
|
36
|
+
/**
|
|
37
|
+
* Every row's value, in the figure's row order. Read in place — nothing is
|
|
38
|
+
* copied — so a `Float64Array` is the cheapest thing to pass.
|
|
39
|
+
*/
|
|
40
|
+
values: ArrayLike<number>;
|
|
41
|
+
/**
|
|
42
|
+
* The two ends of the axis, low first. Left out, they are the column's own
|
|
43
|
+
* smallest and largest finite value, and a column with no spread at all is
|
|
44
|
+
* opened by half a unit on each side so that it still draws.
|
|
45
|
+
* @default undefined — the column's own range
|
|
46
|
+
*/
|
|
47
|
+
domain?: readonly [number, number];
|
|
48
|
+
/**
|
|
49
|
+
* How values are placed between the ends. `log` needs a domain whose low end
|
|
50
|
+
* is above zero; a value at or below zero is pinned to the low end.
|
|
51
|
+
* @default 'linear'
|
|
52
|
+
*/
|
|
53
|
+
scale?: ParallelAxisScale;
|
|
54
|
+
/**
|
|
55
|
+
* How a tick is written. Left out, the ticks carry as many decimals as the
|
|
56
|
+
* spacing needs.
|
|
57
|
+
* @default undefined
|
|
58
|
+
*/
|
|
59
|
+
format?: (value: number) => string;
|
|
60
|
+
/**
|
|
61
|
+
* The graduations, written out by the caller — what a coded quantity needs,
|
|
62
|
+
* where `0`, `1`, `2` should read `none`, `low`, `high`.
|
|
63
|
+
* @default undefined — the figure graduates the axis itself
|
|
64
|
+
*/
|
|
65
|
+
ticks?: readonly ParallelTick[];
|
|
66
|
+
/**
|
|
67
|
+
* Roughly how many graduations to aim for, when the figure picks them.
|
|
68
|
+
* @default 5
|
|
69
|
+
*/
|
|
70
|
+
tickCount?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Whether the ends are rounded outward to whole tick steps. Off, so the axis
|
|
73
|
+
* ends exactly on the data — a reader comparing two molecules on a drug
|
|
74
|
+
* score must see the best one touch the top of its axis, not float below a
|
|
75
|
+
* round number nothing reached.
|
|
76
|
+
* @default false
|
|
77
|
+
*/
|
|
78
|
+
nice?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* The unit written after the axis name, e.g. `g/mol`.
|
|
81
|
+
* @default '' — nothing is written
|
|
82
|
+
*/
|
|
83
|
+
unit?: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** An interval kept on one axis, in that axis's own units, low first. */
|
|
87
|
+
export type ParallelRange = readonly [number, number];
|
|
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
|
+
|
|
101
|
+
/** Which rows a set of brushes keeps, keyed by axis id. */
|
|
102
|
+
export type ParallelRanges = Readonly<Record<string, ParallelSelection>>;
|
|
103
|
+
|
|
104
|
+
/** How the lines are coloured. */
|
|
105
|
+
export interface ParallelColorBy {
|
|
106
|
+
/** The quantity read for each row, in the figure's row order. */
|
|
107
|
+
values: ArrayLike<number>;
|
|
108
|
+
/**
|
|
109
|
+
* The value at the low end of the ramp.
|
|
110
|
+
* @default the smallest finite value
|
|
111
|
+
*/
|
|
112
|
+
min?: number;
|
|
113
|
+
/**
|
|
114
|
+
* The value at its high end.
|
|
115
|
+
* @default the largest finite value
|
|
116
|
+
*/
|
|
117
|
+
max?: number;
|
|
118
|
+
/**
|
|
119
|
+
* The ramp, as the registry hands it over or as a plain list of colours.
|
|
120
|
+
* @default viridis — `resolveColorScale(DEFAULT_COLOR_SCALE_ID).scale`
|
|
121
|
+
*/
|
|
122
|
+
scale?: ColorScale | readonly string[];
|
|
123
|
+
/**
|
|
124
|
+
* Whether the quantity is read on a base-10 logarithmic ramp.
|
|
125
|
+
* @default false
|
|
126
|
+
*/
|
|
127
|
+
logarithmic?: boolean;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** An axis described by the field to read off each row, rather than by its column. */
|
|
131
|
+
export type ParallelAxisSource<Row> = Omit<ParallelAxis, 'values'> & {
|
|
132
|
+
/**
|
|
133
|
+
* The value this axis reads off one row. Called once per row per data
|
|
134
|
+
* change, never per repaint.
|
|
135
|
+
*/
|
|
136
|
+
readonly value: (row: Row) => number;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* One axis built by reading a field off each row.
|
|
141
|
+
*
|
|
142
|
+
* The one place a row type is named: the figure itself reads numbers out of a
|
|
143
|
+
* typed array, so a repaint costs an indexed read rather than a call per
|
|
144
|
+
* value. Call it inside a `useMemo` keyed on the rows.
|
|
145
|
+
* @param rows - The rows, in the order the figure will index them.
|
|
146
|
+
* @param axis - The axis, with the field to read instead of its values.
|
|
147
|
+
* @returns The axis, with its values filled in.
|
|
148
|
+
*/
|
|
149
|
+
export function parallelAxisOf<Row>(
|
|
150
|
+
rows: readonly Row[],
|
|
151
|
+
axis: ParallelAxisSource<Row>,
|
|
152
|
+
): ParallelAxis {
|
|
153
|
+
const { value, ...rest } = axis;
|
|
154
|
+
const values = new Float64Array(rows.length);
|
|
155
|
+
for (let row = 0; row < rows.length; row++) {
|
|
156
|
+
values[row] = value(rows[row] as Row);
|
|
157
|
+
}
|
|
158
|
+
return { ...rest, values };
|
|
159
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { ParallelAxisLayout } from '../core/parallelAxes.ts';
|
|
4
|
+
import { PARALLEL_MARGIN, parallelValueToY } from '../core/parallelAxes.ts';
|
|
5
|
+
import type { ParallelBand } from '../core/parallelBrush.ts';
|
|
6
|
+
import { PARALLEL_BRUSH_HALF_WIDTH } from '../core/parallelBrush.ts';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
PARALLEL_HANDLE_HEIGHT,
|
|
10
|
+
PARALLEL_TICK_LABEL_INSET,
|
|
11
|
+
PARALLEL_TICK_LABEL_STYLE,
|
|
12
|
+
PARALLEL_TICK_LENGTH,
|
|
13
|
+
parallelBandStyle,
|
|
14
|
+
parallelHandleStyle,
|
|
15
|
+
} from './parallelStyles.ts';
|
|
16
|
+
|
|
17
|
+
/** What {@link ParallelAxesLayer} draws. */
|
|
18
|
+
export interface ParallelAxesLayerProps {
|
|
19
|
+
/** The axes, from left to right, already placed and graduated. */
|
|
20
|
+
layouts: readonly ParallelAxisLayout[];
|
|
21
|
+
/** Height of the drawing area, in pixels. */
|
|
22
|
+
innerHeight: number;
|
|
23
|
+
/** The bands each axis carries right now, the one being dragged included. */
|
|
24
|
+
bands: ReadonlyMap<string, ParallelBand[]>;
|
|
25
|
+
/** The ink the axes, their ticks and their labels are drawn in. */
|
|
26
|
+
axisInk: string;
|
|
27
|
+
/** The ink a band is filled and outlined in. */
|
|
28
|
+
bandInk: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The axes, their graduations and the intervals brushed on them.
|
|
33
|
+
*
|
|
34
|
+
* Every axis carries a `data-parallel-brush` rectangle over the strip a press
|
|
35
|
+
* on it reaches, which nothing points at: one surface over the whole figure
|
|
36
|
+
* takes every event, so the rectangle exists to be found — by a test driving
|
|
37
|
+
* a drag, and by a reader's eye, which is the same thing.
|
|
38
|
+
* @param props - See {@link ParallelAxesLayerProps}.
|
|
39
|
+
* @returns The axes.
|
|
40
|
+
*/
|
|
41
|
+
export function ParallelAxesLayer(props: ParallelAxesLayerProps): ReactElement {
|
|
42
|
+
const { layouts, innerHeight, bands, axisInk, bandInk } = props;
|
|
43
|
+
const stroke = { stroke: axisInk };
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<g
|
|
47
|
+
transform={`translate(${PARALLEL_MARGIN.left},${PARALLEL_MARGIN.top})`}
|
|
48
|
+
style={{ fill: axisInk }}
|
|
49
|
+
textAnchor="middle"
|
|
50
|
+
>
|
|
51
|
+
{layouts.map((layout) => {
|
|
52
|
+
const drawn = bands.get(layout.id);
|
|
53
|
+
return (
|
|
54
|
+
<g
|
|
55
|
+
key={layout.id}
|
|
56
|
+
className="parallel-axis"
|
|
57
|
+
data-parallel-axis={layout.id}
|
|
58
|
+
transform={`translate(${layout.x},0)`}
|
|
59
|
+
>
|
|
60
|
+
<line y2={innerHeight} style={stroke} />
|
|
61
|
+
{layout.ticks.map((tick) => (
|
|
62
|
+
<g
|
|
63
|
+
key={tick.value}
|
|
64
|
+
transform={`translate(0,${parallelValueToY(tick.value, layout)})`}
|
|
65
|
+
>
|
|
66
|
+
<line x1={-PARALLEL_TICK_LENGTH} style={stroke} />
|
|
67
|
+
<text
|
|
68
|
+
x={-PARALLEL_TICK_LABEL_INSET}
|
|
69
|
+
dy="0.32em"
|
|
70
|
+
textAnchor="end"
|
|
71
|
+
style={PARALLEL_TICK_LABEL_STYLE}
|
|
72
|
+
>
|
|
73
|
+
{tick.label}
|
|
74
|
+
</text>
|
|
75
|
+
</g>
|
|
76
|
+
))}
|
|
77
|
+
<rect
|
|
78
|
+
data-parallel-brush={layout.id}
|
|
79
|
+
x={-PARALLEL_BRUSH_HALF_WIDTH}
|
|
80
|
+
y={0}
|
|
81
|
+
width={PARALLEL_BRUSH_HALF_WIDTH * 2}
|
|
82
|
+
height={Math.max(0, innerHeight)}
|
|
83
|
+
fill="transparent"
|
|
84
|
+
pointerEvents="none"
|
|
85
|
+
/>
|
|
86
|
+
{drawn?.map((band) => (
|
|
87
|
+
<BrushBand
|
|
88
|
+
key={`${band.top}:${band.bottom}`}
|
|
89
|
+
band={band}
|
|
90
|
+
ink={bandInk}
|
|
91
|
+
/>
|
|
92
|
+
))}
|
|
93
|
+
</g>
|
|
94
|
+
);
|
|
95
|
+
})}
|
|
96
|
+
</g>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function BrushBand(props: { band: ParallelBand; ink: string }): ReactElement {
|
|
101
|
+
const { band, ink } = props;
|
|
102
|
+
const height = Math.max(0, band.bottom - band.top);
|
|
103
|
+
const width = PARALLEL_BRUSH_HALF_WIDTH * 2;
|
|
104
|
+
return (
|
|
105
|
+
<g className="parallel-axis-band" pointerEvents="none">
|
|
106
|
+
<rect
|
|
107
|
+
x={-PARALLEL_BRUSH_HALF_WIDTH}
|
|
108
|
+
y={band.top}
|
|
109
|
+
width={width}
|
|
110
|
+
height={height}
|
|
111
|
+
style={parallelBandStyle(ink)}
|
|
112
|
+
/>
|
|
113
|
+
<rect
|
|
114
|
+
x={-PARALLEL_BRUSH_HALF_WIDTH}
|
|
115
|
+
y={band.top - PARALLEL_HANDLE_HEIGHT / 2}
|
|
116
|
+
width={width}
|
|
117
|
+
height={PARALLEL_HANDLE_HEIGHT}
|
|
118
|
+
style={parallelHandleStyle(ink)}
|
|
119
|
+
/>
|
|
120
|
+
<rect
|
|
121
|
+
x={-PARALLEL_BRUSH_HALF_WIDTH}
|
|
122
|
+
y={band.bottom - PARALLEL_HANDLE_HEIGHT / 2}
|
|
123
|
+
width={width}
|
|
124
|
+
height={PARALLEL_HANDLE_HEIGHT}
|
|
125
|
+
style={parallelHandleStyle(ink)}
|
|
126
|
+
/>
|
|
127
|
+
</g>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
import { useMemo, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
import { OverlayLayer } from '../../overlay/ui/OverlayLayer.tsx';
|
|
5
|
+
import { joinClassNames } from '../../shared/ui/joinClassNames.ts';
|
|
6
|
+
import { PARALLEL_MARGIN, parallelAxisLayouts } from '../core/parallelAxes.ts';
|
|
7
|
+
import { parallelIncludedMask } from '../core/parallelFilter.ts';
|
|
8
|
+
import {
|
|
9
|
+
parallelColorSteps,
|
|
10
|
+
parallelPalette,
|
|
11
|
+
} from '../core/parallelPalette.ts';
|
|
12
|
+
import type {
|
|
13
|
+
ParallelAxis,
|
|
14
|
+
ParallelRange,
|
|
15
|
+
ParallelRanges,
|
|
16
|
+
} from '../core/parallelTypes.ts';
|
|
17
|
+
|
|
18
|
+
import { ParallelAxesLayer } from './ParallelAxesLayer.tsx';
|
|
19
|
+
import { ParallelLabels } from './ParallelLabels.tsx';
|
|
20
|
+
import { ParallelTooltip } from './ParallelTooltip.tsx';
|
|
21
|
+
import type { ParallelCoordinatesProps } from './parallelCoordinatesProps.ts';
|
|
22
|
+
import { useParallelInk } from './parallelInk.ts';
|
|
23
|
+
import {
|
|
24
|
+
parallelColorOf,
|
|
25
|
+
parallelFigureLabel,
|
|
26
|
+
parallelHighlightsOf,
|
|
27
|
+
parallelIsBrushed,
|
|
28
|
+
parallelRowCount,
|
|
29
|
+
} from './parallelPlotModel.ts';
|
|
30
|
+
import {
|
|
31
|
+
PARALLEL_SVG_STYLE,
|
|
32
|
+
parallelCanvasStyle,
|
|
33
|
+
parallelDropMarkStyle,
|
|
34
|
+
parallelFigureStyle,
|
|
35
|
+
} from './parallelStyles.ts';
|
|
36
|
+
import { useParallelAxisDrag } from './useParallelAxisDrag.ts';
|
|
37
|
+
import { useParallelCanvas } from './useParallelCanvas.ts';
|
|
38
|
+
import { useParallelGesture } from './useParallelGesture.ts';
|
|
39
|
+
|
|
40
|
+
/** Height the figure takes when the caller names none. */
|
|
41
|
+
const DEFAULT_HEIGHT = 320;
|
|
42
|
+
|
|
43
|
+
/** The smallest drawing area the figure will lay out, in pixels. */
|
|
44
|
+
const SMALLEST_SIDE = 10;
|
|
45
|
+
|
|
46
|
+
/** No axis is brushed, shared so an uncontrolled figure never rebuilds it. */
|
|
47
|
+
const NO_RANGES: ParallelRanges = Object.freeze({});
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* One vertical axis per column, one line per row, and a brush on every axis.
|
|
51
|
+
*
|
|
52
|
+
* It is told nothing about what the rows are: a library of molecules, a set of
|
|
53
|
+
* runs and a table of measurements all arrive as one `ArrayLike<number>` per
|
|
54
|
+
* axis, read in place. The lines are painted into a canvas because ten
|
|
55
|
+
* thousand of them are not ten thousand elements; everything the reader points
|
|
56
|
+
* at, brushes or reads is SVG and HTML over the top.
|
|
57
|
+
*
|
|
58
|
+
* The brush reports on release, never while the band is being dragged, so a
|
|
59
|
+
* table filtering on the same intervals re-renders once per gesture. What it
|
|
60
|
+
* keeps is `parallelIncludedMask`, which that table should filter with — two
|
|
61
|
+
* definitions of "kept" is how a figure and the list under it come to disagree.
|
|
62
|
+
* @param props - See {@link ParallelCoordinatesProps}.
|
|
63
|
+
* @returns The figure, or what the caller asked to show in its place when
|
|
64
|
+
* there is nothing to draw.
|
|
65
|
+
*/
|
|
66
|
+
export function ParallelCoordinates(
|
|
67
|
+
props: ParallelCoordinatesProps,
|
|
68
|
+
): ReactElement {
|
|
69
|
+
const { axes, width, height = DEFAULT_HEIGHT, empty, overlay } = props;
|
|
70
|
+
const { selected, renderTooltip, renderAxisLabel } = props;
|
|
71
|
+
const { className, testId, ink: inkProp, label: given } = props;
|
|
72
|
+
const { ranges: ownedRanges, defaultRanges, included: givenMask } = props;
|
|
73
|
+
const { color: givenColor, colorAxis, onRangeChange: report } = props;
|
|
74
|
+
const { onRangePreview, hovered, onHoverChange, onRowClick } = props;
|
|
75
|
+
const { several = false, onAxisOrder } = props;
|
|
76
|
+
const count = parallelRowCount(axes, props.count);
|
|
77
|
+
const innerWidth = Math.max(
|
|
78
|
+
width - PARALLEL_MARGIN.left - PARALLEL_MARGIN.right,
|
|
79
|
+
SMALLEST_SIDE,
|
|
80
|
+
);
|
|
81
|
+
const innerHeight = Math.max(
|
|
82
|
+
height - PARALLEL_MARGIN.top - PARALLEL_MARGIN.bottom,
|
|
83
|
+
SMALLEST_SIDE,
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const [ownRanges, setOwnRanges] = useState<ParallelRanges>(
|
|
87
|
+
defaultRanges ?? NO_RANGES,
|
|
88
|
+
);
|
|
89
|
+
const ranges = ownedRanges ?? ownRanges;
|
|
90
|
+
|
|
91
|
+
const layouts = useMemo(
|
|
92
|
+
() => parallelAxisLayouts(axes, count, innerWidth, innerHeight),
|
|
93
|
+
[axes, count, innerWidth, innerHeight],
|
|
94
|
+
);
|
|
95
|
+
const values = useMemo(() => columnsOf(axes), [axes]);
|
|
96
|
+
const { ink, ref: figure } = useParallelInk(inkProp);
|
|
97
|
+
const color = useMemo(
|
|
98
|
+
() => parallelColorOf(givenColor, colorAxis, axes),
|
|
99
|
+
[givenColor, colorAxis, axes],
|
|
100
|
+
);
|
|
101
|
+
const steps = useMemo(
|
|
102
|
+
() => (color === undefined ? null : parallelColorSteps(color, count)),
|
|
103
|
+
[color, count],
|
|
104
|
+
);
|
|
105
|
+
const palette = useMemo(
|
|
106
|
+
() => (color === undefined ? [] : parallelPalette(color.scale)),
|
|
107
|
+
[color],
|
|
108
|
+
);
|
|
109
|
+
const included = useMemo(() => {
|
|
110
|
+
if (givenMask !== undefined) return givenMask;
|
|
111
|
+
if (!parallelIsBrushed(axes, ranges)) return null;
|
|
112
|
+
return parallelIncludedMask(axes, ranges, count);
|
|
113
|
+
}, [givenMask, axes, ranges, count]);
|
|
114
|
+
|
|
115
|
+
function commitRange(axisId: string, kept: readonly ParallelRange[]): void {
|
|
116
|
+
if (ownedRanges === undefined) {
|
|
117
|
+
setOwnRanges((previous) => ({ ...previous, [axisId]: kept }));
|
|
118
|
+
}
|
|
119
|
+
report?.(axisId, kept);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const gesture = useParallelGesture({
|
|
123
|
+
layouts,
|
|
124
|
+
values,
|
|
125
|
+
count,
|
|
126
|
+
innerHeight,
|
|
127
|
+
included,
|
|
128
|
+
ranges,
|
|
129
|
+
several,
|
|
130
|
+
onRangeChange: commitRange,
|
|
131
|
+
onRangePreview,
|
|
132
|
+
hovered,
|
|
133
|
+
onHoverChange,
|
|
134
|
+
onRowClick,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const highlights = useMemo(
|
|
138
|
+
() =>
|
|
139
|
+
parallelHighlightsOf(selected, gesture.hovered, ink.selection, ink.hover),
|
|
140
|
+
[selected, gesture.hovered, ink.selection, ink.hover],
|
|
141
|
+
);
|
|
142
|
+
const { lines: lineCanvas, marks: markCanvas } = useParallelCanvas({
|
|
143
|
+
width,
|
|
144
|
+
height,
|
|
145
|
+
layouts,
|
|
146
|
+
values,
|
|
147
|
+
count,
|
|
148
|
+
included,
|
|
149
|
+
steps,
|
|
150
|
+
palette,
|
|
151
|
+
highlights,
|
|
152
|
+
ink,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const axisDrag = useParallelAxisDrag({ layouts, onAxisOrder });
|
|
156
|
+
const dropAt =
|
|
157
|
+
axisDrag.active === null ? undefined : layouts[axisDrag.active.to];
|
|
158
|
+
|
|
159
|
+
const label = given ?? parallelFigureLabel(axes, count);
|
|
160
|
+
const canvasStyle = parallelCanvasStyle(height);
|
|
161
|
+
const hover =
|
|
162
|
+
gesture.pointer === null || gesture.hovered < 0
|
|
163
|
+
? null
|
|
164
|
+
: { index: gesture.hovered, ...gesture.pointer };
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<div
|
|
168
|
+
ref={figure}
|
|
169
|
+
className={joinClassNames('parallel-coordinates', className)}
|
|
170
|
+
style={parallelFigureStyle(height)}
|
|
171
|
+
data-testid={testId}
|
|
172
|
+
>
|
|
173
|
+
{count === 0 || layouts.length === 0 ? (
|
|
174
|
+
empty
|
|
175
|
+
) : (
|
|
176
|
+
<>
|
|
177
|
+
<canvas
|
|
178
|
+
ref={lineCanvas}
|
|
179
|
+
className="parallel-coordinates-canvas"
|
|
180
|
+
style={canvasStyle}
|
|
181
|
+
/>
|
|
182
|
+
<canvas
|
|
183
|
+
ref={markCanvas}
|
|
184
|
+
className="parallel-coordinates-canvas"
|
|
185
|
+
style={canvasStyle}
|
|
186
|
+
/>
|
|
187
|
+
<svg
|
|
188
|
+
width={Math.max(0, width)}
|
|
189
|
+
height={Math.max(0, height)}
|
|
190
|
+
style={PARALLEL_SVG_STYLE}
|
|
191
|
+
role="img"
|
|
192
|
+
aria-label={label}
|
|
193
|
+
>
|
|
194
|
+
<ParallelAxesLayer
|
|
195
|
+
layouts={layouts}
|
|
196
|
+
innerHeight={innerHeight}
|
|
197
|
+
bands={gesture.bands}
|
|
198
|
+
axisInk={ink.axis}
|
|
199
|
+
bandInk={ink.selection}
|
|
200
|
+
/>
|
|
201
|
+
<rect
|
|
202
|
+
x={0}
|
|
203
|
+
y={0}
|
|
204
|
+
width={Math.max(0, width)}
|
|
205
|
+
height={Math.max(0, height)}
|
|
206
|
+
fill="transparent"
|
|
207
|
+
onClick={gesture.onClick}
|
|
208
|
+
{...gesture.surface}
|
|
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
|
+
)}
|
|
220
|
+
</svg>
|
|
221
|
+
<ParallelLabels
|
|
222
|
+
axes={axes}
|
|
223
|
+
layouts={layouts}
|
|
224
|
+
render={renderAxisLabel}
|
|
225
|
+
drag={axisDrag}
|
|
226
|
+
/>
|
|
227
|
+
{hover === null || renderTooltip === undefined ? null : (
|
|
228
|
+
<ParallelTooltip
|
|
229
|
+
x={hover.x}
|
|
230
|
+
y={hover.y}
|
|
231
|
+
boxWidth={width}
|
|
232
|
+
boxHeight={height}
|
|
233
|
+
>
|
|
234
|
+
{renderTooltip(hover)}
|
|
235
|
+
</ParallelTooltip>
|
|
236
|
+
)}
|
|
237
|
+
</>
|
|
238
|
+
)}
|
|
239
|
+
{overlay === undefined ? null : (
|
|
240
|
+
<OverlayLayer width={width} busy={gesture.brushing}>
|
|
241
|
+
{overlay}
|
|
242
|
+
</OverlayLayer>
|
|
243
|
+
)}
|
|
244
|
+
</div>
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function columnsOf(axes: readonly ParallelAxis[]): Array<ArrayLike<number>> {
|
|
249
|
+
const columns: Array<ArrayLike<number>> = [];
|
|
250
|
+
for (const axis of axes) columns.push(axis.values);
|
|
251
|
+
return columns;
|
|
252
|
+
}
|