@fusion.dev/helix-headless 0.194.0 → 0.195.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/api.json +1 -1
- package/custom-elements.json +348 -42
- package/dist/spatial-heatmap/projection.d.ts +35 -3
- package/dist/spatial-heatmap/projection.js +58 -3
- package/dist/spatial-heatmap/root/spatial-heatmap-root.d.ts +51 -29
- package/dist/spatial-heatmap/root/spatial-heatmap-root.js +106 -27
- package/dist/spatial-scatter-chart/root/spatial-scatter-chart-root.d.ts +17 -6
- package/dist/spatial-scatter-chart/root/spatial-scatter-chart-root.js +30 -4
- package/dist/spatial-scatter-chart/view.d.ts +55 -0
- package/dist/spatial-scatter-chart/view.js +114 -17
- package/dist/spatial-surface-chart/grid.d.ts +3 -0
- package/dist/spatial-surface-chart/grid.js +3 -2
- package/dist/spatial-surface-chart/root/spatial-surface-chart-root.d.ts +18 -7
- package/dist/spatial-surface-chart/root/spatial-surface-chart-root.js +26 -5
- package/examples.json +1 -1
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +54 -56
- package/types/vue.d.ts +7 -13
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { pathData, projectionOf, shortNumber, } from '@fusion.dev/helix-core';
|
|
1
|
+
import { boxLabels, pathData, projectionOf, shortNumber, } from '@fusion.dev/helix-core';
|
|
2
2
|
import { svg } from 'lit';
|
|
3
3
|
export const SPATIAL_VIEW = { tilt: 25, turn: 42 };
|
|
4
4
|
export const VIEW_STEP = 15;
|
|
@@ -10,6 +10,7 @@ export const tickText = (value) => String(Number(value.toPrecision(6)));
|
|
|
10
10
|
const WIDE = 0.58;
|
|
11
11
|
export const TICK_SIZE = 0.13;
|
|
12
12
|
export const NAME_SIZE = 0.15;
|
|
13
|
+
const LABEL_GAP = 0.06;
|
|
13
14
|
export const boxView = (axes, camera, extra = []) => {
|
|
14
15
|
const projection = projectionOf(camera);
|
|
15
16
|
const on = ([x, y, z]) => projection.at(x, -z, y);
|
|
@@ -41,22 +42,11 @@ export const boxView = (axes, camera, extra = []) => {
|
|
|
41
42
|
line([-1, y, -1], [1, y, -1]);
|
|
42
43
|
line([-1, y, -1], [-1, y, 1]);
|
|
43
44
|
}
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
label([inBox(axes.x, tick), -1, 1.14], tickText(tick));
|
|
50
|
-
}
|
|
51
|
-
for (const tick of axes.z.ticks) {
|
|
52
|
-
label([1.14, -1, inBox(axes.z, tick)], tickText(tick));
|
|
53
|
-
}
|
|
54
|
-
for (const tick of axes.y.ticks.slice(1)) {
|
|
55
|
-
label([-1.14, inBox(axes.y, tick), 1], tickText(tick));
|
|
56
|
-
}
|
|
57
|
-
label([0, -1, 1.38], axes.x.name, true);
|
|
58
|
-
label([1.6, -1, 0], axes.z.name, true);
|
|
59
|
-
label([-1, 1.28, 1], axes.y.name, true);
|
|
45
|
+
const text = (axis) => ({
|
|
46
|
+
name: axis.name,
|
|
47
|
+
ticks: axis.ticks.map((tick) => ({ at: inBox(axis, tick), text: tickText(tick) })),
|
|
48
|
+
});
|
|
49
|
+
const labels = boxLabels((point) => ({ ...on(point), depth: depth(point) }), { x: text(axes.x), y: text(axes.y), z: text(axes.z) }, { gap: LABEL_GAP, name: NAME_SIZE, tick: TICK_SIZE, wide: WIDE });
|
|
60
50
|
const corners = [];
|
|
61
51
|
for (const x of [-1, 1]) {
|
|
62
52
|
for (const y of [-1, 1]) {
|
|
@@ -113,3 +103,110 @@ export const nearestTo = (drawing, event, places, reach = 14) => {
|
|
|
113
103
|
}
|
|
114
104
|
return found;
|
|
115
105
|
};
|
|
106
|
+
const TURN_PER_PIXEL = 0.5;
|
|
107
|
+
const TILT_PER_PIXEL = 0.35;
|
|
108
|
+
const DRAG = 4;
|
|
109
|
+
const SETTLE = 250;
|
|
110
|
+
const now = () => globalThis.performance?.now() ?? Date.now();
|
|
111
|
+
const easeOut = (t) => 1 - (1 - t) ** 3;
|
|
112
|
+
export class DragTurn {
|
|
113
|
+
constructor(turn, redraw) {
|
|
114
|
+
this.turn = turn;
|
|
115
|
+
this.redraw = redraw;
|
|
116
|
+
this.dragged = false;
|
|
117
|
+
this.click = (event) => {
|
|
118
|
+
if (this.dragged) {
|
|
119
|
+
this.dragged = false;
|
|
120
|
+
event.stopPropagation();
|
|
121
|
+
event.preventDefault();
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
get moving() {
|
|
126
|
+
return this.held?.moved === true;
|
|
127
|
+
}
|
|
128
|
+
down(event) {
|
|
129
|
+
this.dragged = false;
|
|
130
|
+
this.held =
|
|
131
|
+
event.button === 0
|
|
132
|
+
? { id: event.pointerId, moved: false, x: event.clientX, y: event.clientY }
|
|
133
|
+
: undefined;
|
|
134
|
+
}
|
|
135
|
+
move(event) {
|
|
136
|
+
const held = this.held;
|
|
137
|
+
if (!held || held.id !== event.pointerId) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
const dx = event.clientX - held.x;
|
|
141
|
+
const dy = event.clientY - held.y;
|
|
142
|
+
if (!held.moved && Math.hypot(dx, dy) < DRAG) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
if (!held.moved) {
|
|
146
|
+
held.moved = true;
|
|
147
|
+
try {
|
|
148
|
+
;
|
|
149
|
+
event.currentTarget?.setPointerCapture?.(event.pointerId);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
held.x = event.clientX;
|
|
155
|
+
held.y = event.clientY;
|
|
156
|
+
this.turn((camera) => ({
|
|
157
|
+
tilt: Math.min(Math.max(camera.tilt + dy * TILT_PER_PIXEL, 15), 90),
|
|
158
|
+
turn: camera.turn - dx * TURN_PER_PIXEL,
|
|
159
|
+
}));
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
up(event) {
|
|
163
|
+
const held = this.held;
|
|
164
|
+
if (!held || held.id !== event.pointerId) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
this.held = undefined;
|
|
168
|
+
this.dragged = held.moved;
|
|
169
|
+
if (held.moved) {
|
|
170
|
+
this.redraw();
|
|
171
|
+
}
|
|
172
|
+
return held.moved;
|
|
173
|
+
}
|
|
174
|
+
frame(fits, middle) {
|
|
175
|
+
let shown = fits;
|
|
176
|
+
if (this.moving) {
|
|
177
|
+
this.settling = undefined;
|
|
178
|
+
const from = this.last ?? { frame: fits, middle };
|
|
179
|
+
this.kept ??= {
|
|
180
|
+
frame: from.frame,
|
|
181
|
+
middle: { x: from.middle.x - from.frame.x, y: from.middle.y - from.frame.y },
|
|
182
|
+
};
|
|
183
|
+
const { frame, middle: at } = this.kept;
|
|
184
|
+
shown = { height: frame.height, width: frame.width, x: middle.x - at.x, y: middle.y - at.y };
|
|
185
|
+
}
|
|
186
|
+
else if (this.kept) {
|
|
187
|
+
this.kept = undefined;
|
|
188
|
+
const still = globalThis.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false;
|
|
189
|
+
this.settling = still || !this.last ? undefined : { from: this.last.frame, start: now() };
|
|
190
|
+
}
|
|
191
|
+
if (this.settling && !this.moving) {
|
|
192
|
+
const { from, start } = this.settling;
|
|
193
|
+
const t = (now() - start) / SETTLE;
|
|
194
|
+
if (t >= 1) {
|
|
195
|
+
this.settling = undefined;
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
const k = easeOut(Math.max(0, t));
|
|
199
|
+
const toward = (a, b) => a + (b - a) * k;
|
|
200
|
+
shown = {
|
|
201
|
+
height: toward(from.height, fits.height),
|
|
202
|
+
width: toward(from.width, fits.width),
|
|
203
|
+
x: toward(from.x, fits.x),
|
|
204
|
+
y: toward(from.y, fits.y),
|
|
205
|
+
};
|
|
206
|
+
globalThis.requestAnimationFrame?.(() => this.redraw());
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
this.last = { frame: shown, middle };
|
|
210
|
+
return shown;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -37,6 +37,9 @@ export interface SurfaceField {
|
|
|
37
37
|
depths: number[];
|
|
38
38
|
/** How many cells hold a reading. */
|
|
39
39
|
readings: number;
|
|
40
|
+
/** The lowest and the highest reading, which every level is measured between. */
|
|
41
|
+
lowest: number;
|
|
42
|
+
highest: number;
|
|
40
43
|
}
|
|
41
44
|
/**
|
|
42
45
|
* Reads the table: **the first numeric column is across, the second is in
|
|
@@ -33,6 +33,8 @@ export const surfaceOf = (rows, columns) => {
|
|
|
33
33
|
},
|
|
34
34
|
cells,
|
|
35
35
|
depths,
|
|
36
|
+
highest: read.length > 0 ? Math.max(...read.map((one) => one.value)) : 0,
|
|
37
|
+
lowest: read.length > 0 ? Math.min(...read.map((one) => one.value)) : 0,
|
|
36
38
|
readings: read.length,
|
|
37
39
|
};
|
|
38
40
|
};
|
|
@@ -40,7 +42,6 @@ export const levelOf = (field, cell) => {
|
|
|
40
42
|
if (cell.value === undefined) {
|
|
41
43
|
return undefined;
|
|
42
44
|
}
|
|
43
|
-
const
|
|
44
|
-
const [low, high] = [Math.min(...values), Math.max(...values)];
|
|
45
|
+
const { highest: high, lowest: low } = field;
|
|
45
46
|
return high === low ? 0.5 : (cell.value - low) / (high - low);
|
|
46
47
|
};
|
|
@@ -15,11 +15,12 @@ declare const HelixHeadlessSpatialSurfaceChart_base: typeof HelixElement & (new
|
|
|
15
15
|
*
|
|
16
16
|
* **Helix draws the frame, and a renderer draws the surface in 3D.** Put a
|
|
17
17
|
* box for the renderer inside the chart, and `connectSpatialSurfaceChart` from
|
|
18
|
-
* `@fusion.dev/helix-plot
|
|
19
|
-
*
|
|
18
|
+
* `@fusion.dev/helix-plot`, given `threeSurfaceDriver` from
|
|
19
|
+
* `@fusion.dev/helix-plot/three`, draws the surface in it with three.js,
|
|
20
|
+
* shaded from low to high in the theme's colors. Until a renderer has drawn, and where
|
|
20
21
|
* none is connected, the chart draws the surface in 3D itself, in SVG, from
|
|
21
|
-
* the same place the renderer's camera starts: a
|
|
22
|
-
*
|
|
22
|
+
* the same place the renderer's camera starts: a picture a print and a page
|
|
23
|
+
* without WebGL show too, which a drag or Shift with an arrow turns.
|
|
23
24
|
*
|
|
24
25
|
* **The table is the reading.** Every number is in it, and screen readers
|
|
25
26
|
* read it. The `summary` part names the measures, the number of readings,
|
|
@@ -131,13 +132,15 @@ export declare class HelixHeadlessSpatialSurfaceChart extends HelixHeadlessSpati
|
|
|
131
132
|
summaryLabel: string;
|
|
132
133
|
/** Whether a renderer has drawn the surface, which hides the chart's own drawing. */
|
|
133
134
|
private rendered;
|
|
134
|
-
/** Where the chart's own drawing is seen from, which Shift with an arrow turns. */
|
|
135
|
+
/** Where the chart's own drawing is seen from, which a drag or Shift with an arrow turns. */
|
|
135
136
|
private camera;
|
|
137
|
+
/** A drag across the chart's own drawing, which turns its view. */
|
|
138
|
+
private readonly drag;
|
|
136
139
|
/** Each reading where the chart's own drawing put it, which the pointer finds. */
|
|
137
140
|
private placed;
|
|
138
141
|
/** Where the renderer draws the reading, which the tooltip points at. */
|
|
139
142
|
private place;
|
|
140
|
-
/** The field last
|
|
143
|
+
/** The field last told, kept so a change of it can be told. */
|
|
141
144
|
private last;
|
|
142
145
|
/** The place the keys rested on last, so a change of it can be told. */
|
|
143
146
|
private told;
|
|
@@ -149,6 +152,8 @@ export declare class HelixHeadlessSpatialSurfaceChart extends HelixHeadlessSpati
|
|
|
149
152
|
* renderer reads it.
|
|
150
153
|
*/
|
|
151
154
|
get field(): SurfaceField;
|
|
155
|
+
/** The field last read, and the rows it was read from. */
|
|
156
|
+
private memo;
|
|
152
157
|
/** The place the keys or the pointer rest on, as its place in `field.cells`, or `-1`. */
|
|
153
158
|
get active(): number;
|
|
154
159
|
/** The number of places on the grid, which the keys walk. */
|
|
@@ -197,8 +202,14 @@ export declare class HelixHeadlessSpatialSurfaceChart extends HelixHeadlessSpati
|
|
|
197
202
|
* faces are painted back to front.
|
|
198
203
|
*/
|
|
199
204
|
private readonly onDrawingMove;
|
|
205
|
+
private readonly onDrawingDown;
|
|
206
|
+
/** A press the browser took back, such as for a scroll, which ends a drag as a release does. */
|
|
207
|
+
private readonly onDrawingCancel;
|
|
200
208
|
private readonly onDrawingLeave;
|
|
201
|
-
/**
|
|
209
|
+
/**
|
|
210
|
+
* A tap is a touch reader's hover, unless it was a drag, which turned the
|
|
211
|
+
* view: it shows the place nearest it until a tap lands elsewhere.
|
|
212
|
+
*/
|
|
202
213
|
private readonly onDrawingTap;
|
|
203
214
|
protected render(): TemplateResult;
|
|
204
215
|
/**
|
|
@@ -3,7 +3,7 @@ import { AriaForwardController, CHART_PLOT, customElement, drawTooltip, formatWo
|
|
|
3
3
|
import { html, nothing, svg } from 'lit';
|
|
4
4
|
import { property, state } from 'lit/decorators.js';
|
|
5
5
|
import '../../tooltip/index.js';
|
|
6
|
-
import { boxView, drawBox, inBox, nearestTo, SPATIAL_VIEW, shape, turned, } from '../../spatial-scatter-chart/view.js';
|
|
6
|
+
import { boxView, DragTurn, drawBox, inBox, nearestTo, SPATIAL_VIEW, shape, turned, } from '../../spatial-scatter-chart/view.js';
|
|
7
7
|
import { levelOf, surfaceOf } from '../grid.js';
|
|
8
8
|
const LIGHT = (() => {
|
|
9
9
|
const [x, y, z] = [-0.45, 1, 0.35];
|
|
@@ -25,8 +25,10 @@ let HelixHeadlessSpatialSurfaceChart = class HelixHeadlessSpatialSurfaceChart ex
|
|
|
25
25
|
this.summaryLabel = '{count, plural, one {# reading} other {# readings}} of {y} over {x} and {z}, from {low} to {high}.';
|
|
26
26
|
this.rendered = false;
|
|
27
27
|
this.camera = SPATIAL_VIEW;
|
|
28
|
+
this.drag = new DragTurn((next) => {
|
|
29
|
+
this.camera = next(this.camera);
|
|
30
|
+
}, () => this.requestUpdate());
|
|
28
31
|
this.placed = [];
|
|
29
|
-
this.last = '';
|
|
30
32
|
this.told = -1;
|
|
31
33
|
this.aria = new AriaForwardController(this, {
|
|
32
34
|
target: () => this.partNamed('plot'),
|
|
@@ -42,17 +44,29 @@ let HelixHeadlessSpatialSurfaceChart = class HelixHeadlessSpatialSurfaceChart ex
|
|
|
42
44
|
this.dispatchEvent(new HelixPlotTurnEvent(turn));
|
|
43
45
|
};
|
|
44
46
|
this.onDrawingMove = (event) => {
|
|
47
|
+
if (this.drag.move(event)) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
45
50
|
if (event.pointerType === 'touch') {
|
|
46
51
|
return;
|
|
47
52
|
}
|
|
48
53
|
this.requestActive(this.placeAt(event), event.pointerType === 'pen' ? 'pen' : 'mouse');
|
|
49
54
|
};
|
|
55
|
+
this.onDrawingDown = (event) => {
|
|
56
|
+
this.drag.down(event);
|
|
57
|
+
};
|
|
58
|
+
this.onDrawingCancel = (event) => {
|
|
59
|
+
this.drag.up(event);
|
|
60
|
+
};
|
|
50
61
|
this.onDrawingLeave = (event) => {
|
|
51
62
|
if (event.pointerType !== 'touch') {
|
|
52
63
|
this.requestActive(-1, 'mouse');
|
|
53
64
|
}
|
|
54
65
|
};
|
|
55
66
|
this.onDrawingTap = (event) => {
|
|
67
|
+
if (this.drag.up(event)) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
56
70
|
if (event.pointerType === 'touch') {
|
|
57
71
|
this.requestActive(this.placeAt(event, 24), 'touch');
|
|
58
72
|
}
|
|
@@ -60,7 +74,11 @@ let HelixHeadlessSpatialSurfaceChart = class HelixHeadlessSpatialSurfaceChart ex
|
|
|
60
74
|
}
|
|
61
75
|
static { this.words = 'helix-spatial-surface-chart'; }
|
|
62
76
|
get field() {
|
|
63
|
-
|
|
77
|
+
const rows = this.sourceRows;
|
|
78
|
+
if (this.memo?.rows !== rows) {
|
|
79
|
+
this.memo = { field: surfaceOf(rows, this.plottedColumns), rows };
|
|
80
|
+
}
|
|
81
|
+
return this.memo.field;
|
|
64
82
|
}
|
|
65
83
|
get active() {
|
|
66
84
|
return this.pointer.at;
|
|
@@ -98,7 +116,7 @@ let HelixHeadlessSpatialSurfaceChart = class HelixHeadlessSpatialSurfaceChart ex
|
|
|
98
116
|
}
|
|
99
117
|
updated(changed) {
|
|
100
118
|
super.updated(changed);
|
|
101
|
-
const field =
|
|
119
|
+
const field = this.field;
|
|
102
120
|
if (field !== this.last) {
|
|
103
121
|
this.last = field;
|
|
104
122
|
this.dispatchEvent(new HelixPlotChangeEvent({ kind: 'data' }));
|
|
@@ -174,7 +192,7 @@ let HelixHeadlessSpatialSurfaceChart = class HelixHeadlessSpatialSurfaceChart ex
|
|
|
174
192
|
faces.sort((one, other) => one.depth - other.depth);
|
|
175
193
|
const active = places.find((place) => place.index === at);
|
|
176
194
|
const row = field.cells[at]?.row;
|
|
177
|
-
const
|
|
195
|
+
const bounds = this.drag.frame(view.bounds, view.on([0, 0, 0]));
|
|
178
196
|
const short = (value) => Math.round(value * 1000) / 1000;
|
|
179
197
|
return html `<div aria-hidden="true" dir="ltr" ?hidden=${this.rendered} part="fallback">
|
|
180
198
|
<svg
|
|
@@ -182,6 +200,9 @@ let HelixHeadlessSpatialSurfaceChart = class HelixHeadlessSpatialSurfaceChart ex
|
|
|
182
200
|
preserveAspectRatio="xMidYMid meet"
|
|
183
201
|
viewBox="${bounds.x} ${bounds.y} ${bounds.width} ${bounds.height}"
|
|
184
202
|
xmlns="http://www.w3.org/2000/svg"
|
|
203
|
+
@click=${this.drag.click}
|
|
204
|
+
@pointercancel=${this.onDrawingCancel}
|
|
205
|
+
@pointerdown=${this.onDrawingDown}
|
|
185
206
|
@pointerleave=${this.onDrawingLeave}
|
|
186
207
|
@pointermove=${this.onDrawingMove}
|
|
187
208
|
@pointerup=${this.onDrawingTap}
|