react-cheminfo 0.15.0 → 0.16.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 +27 -0
- package/lib/parallel/core/index.d.ts.map +1 -0
- package/lib/parallel/core/index.js +20 -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 +79 -0
- package/lib/parallel/core/parallelBrush.d.ts.map +1 -0
- package/lib/parallel/core/parallelBrush.js +99 -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 +36 -0
- package/lib/parallel/core/parallelFilter.d.ts.map +1 -0
- package/lib/parallel/core/parallelFilter.js +74 -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/parallelPaint.d.ts +104 -0
- package/lib/parallel/core/parallelPaint.d.ts.map +1 -0
- package/lib/parallel/core/parallelPaint.js +160 -0
- package/lib/parallel/core/parallelPaint.js.map +1 -0
- package/lib/parallel/core/parallelPalette.d.ts +31 -0
- package/lib/parallel/core/parallelPalette.d.ts.map +1 -0
- package/lib/parallel/core/parallelPalette.js +68 -0
- package/lib/parallel/core/parallelPalette.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 +116 -0
- package/lib/parallel/core/parallelTicks.js.map +1 -0
- package/lib/parallel/core/parallelTypes.d.ts +129 -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 +109 -0
- package/lib/parallel/ui/ParallelCoordinates.js.map +1 -0
- package/lib/parallel/ui/ParallelLabels.d.ts +26 -0
- package/lib/parallel/ui/ParallelLabels.d.ts.map +1 -0
- package/lib/parallel/ui/ParallelLabels.js +25 -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 +25 -0
- package/lib/parallel/ui/index.d.ts.map +1 -0
- package/lib/parallel/ui/index.js +18 -0
- package/lib/parallel/ui/index.js.map +1 -0
- package/lib/parallel/ui/parallelCoordinatesProps.d.ts +185 -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 +52 -0
- package/lib/parallel/ui/parallelGestureModel.d.ts.map +1 -0
- package/lib/parallel/ui/parallelGestureModel.js +80 -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 +83 -0
- package/lib/parallel/ui/parallelStyles.d.ts.map +1 -0
- package/lib/parallel/ui/parallelStyles.js +131 -0
- package/lib/parallel/ui/parallelStyles.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 +67 -0
- package/lib/parallel/ui/useParallelGesture.d.ts.map +1 -0
- package/lib/parallel/ui/useParallelGesture.js +139 -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 +80 -0
- package/src/parallel/core/parallelAxes.ts +207 -0
- package/src/parallel/core/parallelBrush.ts +145 -0
- package/src/parallel/core/parallelExtent.ts +56 -0
- package/src/parallel/core/parallelFilter.ts +94 -0
- package/src/parallel/core/parallelHit.ts +106 -0
- package/src/parallel/core/parallelPaint.ts +231 -0
- package/src/parallel/core/parallelPalette.ts +80 -0
- package/src/parallel/core/parallelTicks.ts +164 -0
- package/src/parallel/core/parallelTypes.ts +147 -0
- package/src/parallel/ui/ParallelAxesLayer.tsx +123 -0
- package/src/parallel/ui/ParallelCoordinates.tsx +233 -0
- package/src/parallel/ui/ParallelLabels.tsx +56 -0
- package/src/parallel/ui/ParallelTooltip.tsx +55 -0
- package/src/parallel/ui/index.ts +50 -0
- package/src/parallel/ui/parallelCoordinatesProps.ts +199 -0
- package/src/parallel/ui/parallelGestureModel.ts +109 -0
- package/src/parallel/ui/parallelInk.ts +182 -0
- package/src/parallel/ui/parallelPlotModel.ts +130 -0
- package/src/parallel/ui/parallelStyles.ts +146 -0
- package/src/parallel/ui/useParallelCanvas.ts +104 -0
- package/src/parallel/ui/useParallelGesture.ts +227 -0
- package/src/structure/ui/useToolbarFloor.ts +27 -22
- package/src/ui.ts +1 -0
|
@@ -0,0 +1,123 @@
|
|
|
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 band 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 band = bands.get(layout.id) ?? null;
|
|
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
|
+
{band === null ? null : <BrushBand band={band} ink={bandInk} />}
|
|
87
|
+
</g>
|
|
88
|
+
);
|
|
89
|
+
})}
|
|
90
|
+
</g>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function BrushBand(props: { band: ParallelBand; ink: string }): ReactElement {
|
|
95
|
+
const { band, ink } = props;
|
|
96
|
+
const height = Math.max(0, band.bottom - band.top);
|
|
97
|
+
const width = PARALLEL_BRUSH_HALF_WIDTH * 2;
|
|
98
|
+
return (
|
|
99
|
+
<g className="parallel-axis-band" pointerEvents="none">
|
|
100
|
+
<rect
|
|
101
|
+
x={-PARALLEL_BRUSH_HALF_WIDTH}
|
|
102
|
+
y={band.top}
|
|
103
|
+
width={width}
|
|
104
|
+
height={height}
|
|
105
|
+
style={parallelBandStyle(ink)}
|
|
106
|
+
/>
|
|
107
|
+
<rect
|
|
108
|
+
x={-PARALLEL_BRUSH_HALF_WIDTH}
|
|
109
|
+
y={band.top - PARALLEL_HANDLE_HEIGHT / 2}
|
|
110
|
+
width={width}
|
|
111
|
+
height={PARALLEL_HANDLE_HEIGHT}
|
|
112
|
+
style={parallelHandleStyle(ink)}
|
|
113
|
+
/>
|
|
114
|
+
<rect
|
|
115
|
+
x={-PARALLEL_BRUSH_HALF_WIDTH}
|
|
116
|
+
y={band.bottom - PARALLEL_HANDLE_HEIGHT / 2}
|
|
117
|
+
width={width}
|
|
118
|
+
height={PARALLEL_HANDLE_HEIGHT}
|
|
119
|
+
style={parallelHandleStyle(ink)}
|
|
120
|
+
/>
|
|
121
|
+
</g>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
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
|
+
parallelFigureStyle,
|
|
34
|
+
} from './parallelStyles.ts';
|
|
35
|
+
import { useParallelCanvas } from './useParallelCanvas.ts';
|
|
36
|
+
import { useParallelGesture } from './useParallelGesture.ts';
|
|
37
|
+
|
|
38
|
+
/** Height the figure takes when the caller names none. */
|
|
39
|
+
const DEFAULT_HEIGHT = 320;
|
|
40
|
+
|
|
41
|
+
/** The smallest drawing area the figure will lay out, in pixels. */
|
|
42
|
+
const SMALLEST_SIDE = 10;
|
|
43
|
+
|
|
44
|
+
/** No axis is brushed, shared so an uncontrolled figure never rebuilds it. */
|
|
45
|
+
const NO_RANGES: ParallelRanges = Object.freeze({});
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* One vertical axis per column, one line per row, and a brush on every axis.
|
|
49
|
+
*
|
|
50
|
+
* It is told nothing about what the rows are: a library of molecules, a set of
|
|
51
|
+
* runs and a table of measurements all arrive as one `ArrayLike<number>` per
|
|
52
|
+
* axis, read in place. The lines are painted into a canvas because ten
|
|
53
|
+
* thousand of them are not ten thousand elements; everything the reader points
|
|
54
|
+
* at, brushes or reads is SVG and HTML over the top.
|
|
55
|
+
*
|
|
56
|
+
* The brush reports on release, never while the band is being dragged, so a
|
|
57
|
+
* table filtering on the same intervals re-renders once per gesture. What it
|
|
58
|
+
* keeps is `parallelIncludedMask`, which that table should filter with — two
|
|
59
|
+
* definitions of "kept" is how a figure and the list under it come to disagree.
|
|
60
|
+
* @param props - See {@link ParallelCoordinatesProps}.
|
|
61
|
+
* @returns The figure, or what the caller asked to show in its place when
|
|
62
|
+
* there is nothing to draw.
|
|
63
|
+
*/
|
|
64
|
+
export function ParallelCoordinates(
|
|
65
|
+
props: ParallelCoordinatesProps,
|
|
66
|
+
): ReactElement {
|
|
67
|
+
const { axes, width, height = DEFAULT_HEIGHT, empty, overlay } = props;
|
|
68
|
+
const { selected, renderTooltip, renderAxisLabel } = props;
|
|
69
|
+
const { className, testId, ink: inkProp, label: given } = props;
|
|
70
|
+
const { ranges: ownedRanges, defaultRanges, included: givenMask } = props;
|
|
71
|
+
const { color: givenColor, colorAxis, onRangeChange: report } = props;
|
|
72
|
+
const { onRangePreview, hovered, onHoverChange, onRowClick } = props;
|
|
73
|
+
const count = parallelRowCount(axes, props.count);
|
|
74
|
+
const innerWidth = Math.max(
|
|
75
|
+
width - PARALLEL_MARGIN.left - PARALLEL_MARGIN.right,
|
|
76
|
+
SMALLEST_SIDE,
|
|
77
|
+
);
|
|
78
|
+
const innerHeight = Math.max(
|
|
79
|
+
height - PARALLEL_MARGIN.top - PARALLEL_MARGIN.bottom,
|
|
80
|
+
SMALLEST_SIDE,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const [ownRanges, setOwnRanges] = useState<ParallelRanges>(
|
|
84
|
+
defaultRanges ?? NO_RANGES,
|
|
85
|
+
);
|
|
86
|
+
const ranges = ownedRanges ?? ownRanges;
|
|
87
|
+
|
|
88
|
+
const layouts = useMemo(
|
|
89
|
+
() => parallelAxisLayouts(axes, count, innerWidth, innerHeight),
|
|
90
|
+
[axes, count, innerWidth, innerHeight],
|
|
91
|
+
);
|
|
92
|
+
const values = useMemo(() => columnsOf(axes), [axes]);
|
|
93
|
+
const { ink, ref: figure } = useParallelInk(inkProp);
|
|
94
|
+
const color = useMemo(
|
|
95
|
+
() => parallelColorOf(givenColor, colorAxis, axes),
|
|
96
|
+
[givenColor, colorAxis, axes],
|
|
97
|
+
);
|
|
98
|
+
const steps = useMemo(
|
|
99
|
+
() => (color === undefined ? null : parallelColorSteps(color, count)),
|
|
100
|
+
[color, count],
|
|
101
|
+
);
|
|
102
|
+
const palette = useMemo(
|
|
103
|
+
() => (color === undefined ? [] : parallelPalette(color.scale)),
|
|
104
|
+
[color],
|
|
105
|
+
);
|
|
106
|
+
const included = useMemo(() => {
|
|
107
|
+
if (givenMask !== undefined) return givenMask;
|
|
108
|
+
if (!parallelIsBrushed(axes, ranges)) return null;
|
|
109
|
+
return parallelIncludedMask(axes, ranges, count);
|
|
110
|
+
}, [givenMask, axes, ranges, count]);
|
|
111
|
+
|
|
112
|
+
function commitRange(axisId: string, range: ParallelRange | null): void {
|
|
113
|
+
if (ownedRanges === undefined) {
|
|
114
|
+
setOwnRanges((previous) => ({ ...previous, [axisId]: range }));
|
|
115
|
+
}
|
|
116
|
+
report?.(axisId, range);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const gesture = useParallelGesture({
|
|
120
|
+
layouts,
|
|
121
|
+
values,
|
|
122
|
+
count,
|
|
123
|
+
innerHeight,
|
|
124
|
+
included,
|
|
125
|
+
ranges,
|
|
126
|
+
onRangeChange: commitRange,
|
|
127
|
+
onRangePreview,
|
|
128
|
+
hovered,
|
|
129
|
+
onHoverChange,
|
|
130
|
+
onRowClick,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const highlights = useMemo(
|
|
134
|
+
() =>
|
|
135
|
+
parallelHighlightsOf(selected, gesture.hovered, ink.selection, ink.hover),
|
|
136
|
+
[selected, gesture.hovered, ink.selection, ink.hover],
|
|
137
|
+
);
|
|
138
|
+
const { lines: lineCanvas, marks: markCanvas } = useParallelCanvas({
|
|
139
|
+
width,
|
|
140
|
+
height,
|
|
141
|
+
layouts,
|
|
142
|
+
values,
|
|
143
|
+
count,
|
|
144
|
+
included,
|
|
145
|
+
steps,
|
|
146
|
+
palette,
|
|
147
|
+
highlights,
|
|
148
|
+
ink,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const label = given ?? parallelFigureLabel(axes, count);
|
|
152
|
+
const canvasStyle = parallelCanvasStyle(height);
|
|
153
|
+
const hover =
|
|
154
|
+
gesture.pointer === null || gesture.hovered < 0
|
|
155
|
+
? null
|
|
156
|
+
: { index: gesture.hovered, ...gesture.pointer };
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<div
|
|
160
|
+
ref={figure}
|
|
161
|
+
className={joinClassNames('parallel-coordinates', className)}
|
|
162
|
+
style={parallelFigureStyle(height)}
|
|
163
|
+
data-testid={testId}
|
|
164
|
+
>
|
|
165
|
+
{count === 0 || layouts.length === 0 ? (
|
|
166
|
+
empty
|
|
167
|
+
) : (
|
|
168
|
+
<>
|
|
169
|
+
<canvas
|
|
170
|
+
ref={lineCanvas}
|
|
171
|
+
className="parallel-coordinates-canvas"
|
|
172
|
+
style={canvasStyle}
|
|
173
|
+
/>
|
|
174
|
+
<canvas
|
|
175
|
+
ref={markCanvas}
|
|
176
|
+
className="parallel-coordinates-canvas"
|
|
177
|
+
style={canvasStyle}
|
|
178
|
+
/>
|
|
179
|
+
<svg
|
|
180
|
+
width={Math.max(0, width)}
|
|
181
|
+
height={Math.max(0, height)}
|
|
182
|
+
style={PARALLEL_SVG_STYLE}
|
|
183
|
+
role="img"
|
|
184
|
+
aria-label={label}
|
|
185
|
+
>
|
|
186
|
+
<ParallelAxesLayer
|
|
187
|
+
layouts={layouts}
|
|
188
|
+
innerHeight={innerHeight}
|
|
189
|
+
bands={gesture.bands}
|
|
190
|
+
axisInk={ink.axis}
|
|
191
|
+
bandInk={ink.selection}
|
|
192
|
+
/>
|
|
193
|
+
<rect
|
|
194
|
+
x={0}
|
|
195
|
+
y={0}
|
|
196
|
+
width={Math.max(0, width)}
|
|
197
|
+
height={Math.max(0, height)}
|
|
198
|
+
fill="transparent"
|
|
199
|
+
onClick={gesture.onClick}
|
|
200
|
+
{...gesture.surface}
|
|
201
|
+
/>
|
|
202
|
+
</svg>
|
|
203
|
+
<ParallelLabels
|
|
204
|
+
axes={axes}
|
|
205
|
+
layouts={layouts}
|
|
206
|
+
render={renderAxisLabel}
|
|
207
|
+
/>
|
|
208
|
+
{hover === null || renderTooltip === undefined ? null : (
|
|
209
|
+
<ParallelTooltip
|
|
210
|
+
x={hover.x}
|
|
211
|
+
y={hover.y}
|
|
212
|
+
boxWidth={width}
|
|
213
|
+
boxHeight={height}
|
|
214
|
+
>
|
|
215
|
+
{renderTooltip(hover)}
|
|
216
|
+
</ParallelTooltip>
|
|
217
|
+
)}
|
|
218
|
+
</>
|
|
219
|
+
)}
|
|
220
|
+
{overlay === undefined ? null : (
|
|
221
|
+
<OverlayLayer width={width} busy={gesture.brushing}>
|
|
222
|
+
{overlay}
|
|
223
|
+
</OverlayLayer>
|
|
224
|
+
)}
|
|
225
|
+
</div>
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function columnsOf(axes: readonly ParallelAxis[]): Array<ArrayLike<number>> {
|
|
230
|
+
const columns: Array<ArrayLike<number>> = [];
|
|
231
|
+
for (const axis of axes) columns.push(axis.values);
|
|
232
|
+
return columns;
|
|
233
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import type { ParallelAxisLayout } from '../core/parallelAxes.ts';
|
|
4
|
+
import { PARALLEL_MARGIN } from '../core/parallelAxes.ts';
|
|
5
|
+
import type { ParallelAxis } from '../core/parallelTypes.ts';
|
|
6
|
+
|
|
7
|
+
import { PARALLEL_LABELS_STYLE, parallelLabelStyle } from './parallelStyles.ts';
|
|
8
|
+
|
|
9
|
+
/** What {@link ParallelLabels} writes. */
|
|
10
|
+
export interface ParallelLabelsProps {
|
|
11
|
+
/** The axes as the caller described them, for a caller's own renderer. */
|
|
12
|
+
axes: readonly ParallelAxis[];
|
|
13
|
+
/** The same axes, placed, so each name sits over its own axis. */
|
|
14
|
+
layouts: readonly ParallelAxisLayout[];
|
|
15
|
+
/**
|
|
16
|
+
* How a name is written. Left out, it is the label and the unit after it.
|
|
17
|
+
* @default undefined
|
|
18
|
+
*/
|
|
19
|
+
render?: ((axis: ParallelAxis, index: number) => ReactNode) | undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The axis names, as HTML over the figure.
|
|
24
|
+
*
|
|
25
|
+
* HTML rather than SVG text so that a name can carry a tooltip explaining what
|
|
26
|
+
* the axis measures. The layer lets every pointer event through and only the
|
|
27
|
+
* names themselves take it back, so a brush started under a name still starts.
|
|
28
|
+
* @param props - See {@link ParallelLabelsProps}.
|
|
29
|
+
* @returns The names.
|
|
30
|
+
*/
|
|
31
|
+
export function ParallelLabels(props: ParallelLabelsProps): ReactElement {
|
|
32
|
+
const { axes, layouts, render } = props;
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className="parallel-coordinates-labels" style={PARALLEL_LABELS_STYLE}>
|
|
36
|
+
{layouts.map((layout, index) => {
|
|
37
|
+
const axis = axes[index];
|
|
38
|
+
return (
|
|
39
|
+
<div
|
|
40
|
+
key={layout.id}
|
|
41
|
+
className="parallel-axis-label"
|
|
42
|
+
style={parallelLabelStyle(PARALLEL_MARGIN.left + layout.x)}
|
|
43
|
+
>
|
|
44
|
+
{axis !== undefined && render !== undefined
|
|
45
|
+
? render(axis, index)
|
|
46
|
+
: defaultLabel(layout)}
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
})}
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function defaultLabel(layout: ParallelAxisLayout): string {
|
|
55
|
+
return layout.unit === '' ? layout.label : `${layout.label} (${layout.unit})`;
|
|
56
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from 'react';
|
|
2
|
+
import { useRef } from 'react';
|
|
3
|
+
|
|
4
|
+
import { useContainerSize } from '../../hooks/ui/useContainerSize.ts';
|
|
5
|
+
import { placeOverlayCard } from '../../overlay/core/placeOverlayCard.ts';
|
|
6
|
+
|
|
7
|
+
import { parallelTooltipStyle } from './parallelStyles.ts';
|
|
8
|
+
|
|
9
|
+
/** What {@link ParallelTooltip} shows, and where. */
|
|
10
|
+
export interface ParallelTooltipProps {
|
|
11
|
+
/** Where the pointer is, in pixels from the figure's left edge. */
|
|
12
|
+
x: number;
|
|
13
|
+
/** Where it is, in pixels from its top edge. */
|
|
14
|
+
y: number;
|
|
15
|
+
/** Width of the figure, so the card is flipped rather than clipped. */
|
|
16
|
+
boxWidth: number;
|
|
17
|
+
/** Its height. */
|
|
18
|
+
boxHeight: number;
|
|
19
|
+
/** What the card says, written by the caller. */
|
|
20
|
+
children: ReactNode;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The card over the row under the pointer.
|
|
25
|
+
*
|
|
26
|
+
* It measures itself and flips rather than being clipped, because a card that
|
|
27
|
+
* runs off the bottom right of a figure is exactly where a reader points last
|
|
28
|
+
* — and it never takes the pointer, so moving towards it never dismisses it by
|
|
29
|
+
* moving off the line underneath.
|
|
30
|
+
* @param props - See {@link ParallelTooltipProps}.
|
|
31
|
+
* @returns The card.
|
|
32
|
+
*/
|
|
33
|
+
export function ParallelTooltip(props: ParallelTooltipProps): ReactElement {
|
|
34
|
+
const { x, y, boxWidth, boxHeight, children } = props;
|
|
35
|
+
const card = useRef<HTMLDivElement>(null);
|
|
36
|
+
const size = useContainerSize(card);
|
|
37
|
+
const placed = placeOverlayCard({
|
|
38
|
+
pointerX: x,
|
|
39
|
+
pointerY: y,
|
|
40
|
+
cardWidth: size.width,
|
|
41
|
+
cardHeight: size.height,
|
|
42
|
+
boxWidth,
|
|
43
|
+
boxHeight,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div
|
|
48
|
+
ref={card}
|
|
49
|
+
className="parallel-coordinates-tooltip"
|
|
50
|
+
style={parallelTooltipStyle(placed.left, placed.top)}
|
|
51
|
+
>
|
|
52
|
+
{children}
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A parallel-coordinates figure the reader can interrogate: brush any axis to
|
|
3
|
+
* keep an interval of it, rest on a line for what is known about that row, and
|
|
4
|
+
* colour every line by whichever column answers the question being asked.
|
|
5
|
+
*
|
|
6
|
+
* Nothing here knows what the rows are. The same figure draws a library of
|
|
7
|
+
* molecules, a set of experimental runs and a table of measurements, which is
|
|
8
|
+
* why it is its own domain rather than part of the first viewer to want one.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export { ParallelAxesLayer } from './ParallelAxesLayer.tsx';
|
|
12
|
+
export type { ParallelAxesLayerProps } from './ParallelAxesLayer.tsx';
|
|
13
|
+
export { ParallelCoordinates } from './ParallelCoordinates.tsx';
|
|
14
|
+
export type {
|
|
15
|
+
ParallelCoordinatesProps,
|
|
16
|
+
ParallelHover,
|
|
17
|
+
ParallelInk,
|
|
18
|
+
} from './parallelCoordinatesProps.ts';
|
|
19
|
+
export type {
|
|
20
|
+
ParallelInkBinding,
|
|
21
|
+
ParallelInkValues,
|
|
22
|
+
ParallelTokenInk,
|
|
23
|
+
} from './parallelInk.ts';
|
|
24
|
+
export {
|
|
25
|
+
parallelInkFrom,
|
|
26
|
+
readParallelInk,
|
|
27
|
+
readParallelTokens,
|
|
28
|
+
useParallelInk,
|
|
29
|
+
} from './parallelInk.ts';
|
|
30
|
+
export { ParallelLabels } from './ParallelLabels.tsx';
|
|
31
|
+
export type { ParallelLabelsProps } from './ParallelLabels.tsx';
|
|
32
|
+
export {
|
|
33
|
+
parallelColorOf,
|
|
34
|
+
parallelFigureLabel,
|
|
35
|
+
parallelHighlightsOf,
|
|
36
|
+
parallelIsBrushed,
|
|
37
|
+
parallelRowCount,
|
|
38
|
+
} from './parallelPlotModel.ts';
|
|
39
|
+
export { ParallelTooltip } from './ParallelTooltip.tsx';
|
|
40
|
+
export type { ParallelTooltipProps } from './ParallelTooltip.tsx';
|
|
41
|
+
export type {
|
|
42
|
+
ParallelCanvasOptions,
|
|
43
|
+
ParallelCanvases,
|
|
44
|
+
} from './useParallelCanvas.ts';
|
|
45
|
+
export { useParallelCanvas } from './useParallelCanvas.ts';
|
|
46
|
+
export type {
|
|
47
|
+
ParallelGesture,
|
|
48
|
+
ParallelGestureOptions,
|
|
49
|
+
} from './useParallelGesture.ts';
|
|
50
|
+
export { useParallelGesture } from './useParallelGesture.ts';
|