@fusion.dev/helix-headless 0.188.0 → 0.189.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 +39844 -36049
- package/dist/element-events.generated.js +21 -7
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/spatial-heatmap/index.d.ts +1 -0
- package/dist/spatial-heatmap/index.js +1 -0
- package/dist/{isometric-heatmap → spatial-heatmap}/projection.d.ts +32 -6
- package/dist/{isometric-heatmap → spatial-heatmap}/projection.js +33 -8
- package/dist/spatial-heatmap/root/spatial-heatmap-root.d.ts +239 -0
- package/dist/spatial-heatmap/root/spatial-heatmap-root.js +252 -0
- package/dist/spatial-scatter-chart/field.d.ts +63 -0
- package/dist/spatial-scatter-chart/field.js +61 -0
- package/dist/spatial-scatter-chart/index.d.ts +2 -0
- package/dist/spatial-scatter-chart/index.js +2 -0
- package/dist/spatial-scatter-chart/root/spatial-scatter-chart-root.d.ts +207 -0
- package/dist/spatial-scatter-chart/root/spatial-scatter-chart-root.js +222 -0
- package/dist/spatial-scatter-chart/view.d.ts +93 -0
- package/dist/spatial-scatter-chart/view.js +115 -0
- package/dist/spatial-surface-chart/grid.d.ts +50 -0
- package/dist/spatial-surface-chart/grid.js +46 -0
- package/dist/spatial-surface-chart/index.d.ts +2 -0
- package/dist/spatial-surface-chart/index.js +2 -0
- package/dist/spatial-surface-chart/root/spatial-surface-chart-root.d.ts +234 -0
- package/dist/spatial-surface-chart/root/spatial-surface-chart-root.js +281 -0
- package/examples.json +1 -1
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +5417 -3661
- package/types/svelte.d.ts +3 -1
- package/types/vue.d.ts +590 -298
- package/types/words.d.ts +153 -69
- package/dist/isometric-heatmap/index.d.ts +0 -1
- package/dist/isometric-heatmap/index.js +0 -1
- package/dist/isometric-heatmap/root/isometric-heatmap-root.d.ts +0 -118
- package/dist/isometric-heatmap/root/isometric-heatmap-root.js +0 -127
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { AriaForwardController, HelixElement } from '@fusion.dev/helix-core';
|
|
2
|
+
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
|
+
import '../../tooltip/index.js';
|
|
4
|
+
import { type SurfaceField } from '../grid.js';
|
|
5
|
+
declare const HelixHeadlessSpatialSurfaceChart_base: typeof HelixElement & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinChartSourceInterface) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinChartStatusInterface);
|
|
6
|
+
/**
|
|
7
|
+
* @summary A chart that draws a height over a grid as a surface in three dimensions, for a measure that varies over two others.
|
|
8
|
+
*
|
|
9
|
+
* @description
|
|
10
|
+
* A 3D surface chart shows how one measure varies over two others, such as
|
|
11
|
+
* load by hour and by day. Each row of the table is one reading: **the
|
|
12
|
+
* first numeric column is across, the second is in depth, and the third is
|
|
13
|
+
* the height.** The readings make a grid from every distinct place across
|
|
14
|
+
* and in depth, and a place with no reading is left open.
|
|
15
|
+
*
|
|
16
|
+
* **Helix draws the frame, and a renderer draws the surface in 3D.** Put a
|
|
17
|
+
* box for the renderer inside the chart, and `connectSpatialSurfaceChart` from
|
|
18
|
+
* `@fusion.dev/helix-plot` draws the surface in it with three.js, shaded from
|
|
19
|
+
* low to high in the theme's colors. Until a renderer has drawn, and where
|
|
20
|
+
* none is connected, the chart draws the surface in 3D itself, in SVG, from
|
|
21
|
+
* the same place the renderer's camera starts: a still picture the server, a
|
|
22
|
+
* print, and a page without WebGL all show, which Shift with an arrow turns.
|
|
23
|
+
*
|
|
24
|
+
* **The table is the reading.** Every number is in it, and screen readers
|
|
25
|
+
* read it. The `summary` part names the measures, the number of readings,
|
|
26
|
+
* and the range of heights, and describes the plot.
|
|
27
|
+
*
|
|
28
|
+
* **The plot is read by key, across and in depth.** It takes focus, named by
|
|
29
|
+
* `label` or the caption. The left and right arrows move across, the down
|
|
30
|
+
* and up arrows move in depth, forward and back, and Home and End go to the
|
|
31
|
+
* first and the last place. The `live` part says the reading at the place in
|
|
32
|
+
* words, and the renderer marks it. Shift with an arrow turns the view, and
|
|
33
|
+
* Escape lets the place go.
|
|
34
|
+
*
|
|
35
|
+
* **A reading shows its row in a tooltip**, as on every chart: the place a
|
|
36
|
+
* mouse or a pen rests on in the drawing, the place tapped, or the one the
|
|
37
|
+
* keys move to, once a renderer has drawn. A place with no reading shows
|
|
38
|
+
* none.
|
|
39
|
+
*
|
|
40
|
+
* **Readings are sent as every chart sends them.** `helix-reading-change`
|
|
41
|
+
* says each move of the reading, and `helix-reading-press` a place clicked or
|
|
42
|
+
* pressed with Enter or Space, each with the row of the table in `index`, or
|
|
43
|
+
* `-1` for a place with no reading.
|
|
44
|
+
*
|
|
45
|
+
* **Keyboard.** The plot is one tab stop once it has readings.
|
|
46
|
+
*
|
|
47
|
+
* | Key | What it does |
|
|
48
|
+
* |---|---|
|
|
49
|
+
* | Tab | Moves focus to the plot. |
|
|
50
|
+
* | Right, Left | Moves across to the next or the previous place. |
|
|
51
|
+
* | Down, Up | Moves in depth to the next or the previous place. |
|
|
52
|
+
* | Home, End | Moves to the first or the last place. |
|
|
53
|
+
* | Shift with an arrow | Turns the view across, or tilts it up and down. |
|
|
54
|
+
* | Enter, Space | Presses the place, which sends `helix-reading-press`. |
|
|
55
|
+
* | Escape | Lets the place go. |
|
|
56
|
+
*
|
|
57
|
+
* @since 0.189.0
|
|
58
|
+
* @status experimental
|
|
59
|
+
* @category charts
|
|
60
|
+
* @aliases 3D surface chart, 3D surface plot, Surface chart, Height map, Terrain chart
|
|
61
|
+
* @documentation https://fusion.dev/helix/components/spatial-surface-chart
|
|
62
|
+
*
|
|
63
|
+
* @slot - The renderer's box, which the 3D drawing is made in.
|
|
64
|
+
* @slot empty - Content shown when the table has no readings. Falls back to `empty-text`.
|
|
65
|
+
* @slot loading - Content shown while `loading` is set and there are no readings yet. Falls back to `loading-text`.
|
|
66
|
+
*
|
|
67
|
+
* @event {HelixPlotChangeEvent} helix-plot-change - The readings or the place the keys rest on changed, which the renderer is told. It stays on the chart.
|
|
68
|
+
* @event {HelixPlotTurnEvent} helix-plot-turn - Shift with an arrow asked the view to turn, by a step across or up.
|
|
69
|
+
* @event {HelixReadingChangeEvent} helix-reading-change - The reading moved, or was put away: `index` is the row, or `-1` for none or a place with no reading, with its `label` and the `pointerType` that moved it.
|
|
70
|
+
* @event {HelixReadingPressEvent} helix-reading-press - A place was clicked or pressed with Enter or Space, with the same detail as `helix-reading-change`.
|
|
71
|
+
*
|
|
72
|
+
* @csspart axis-name - A measure's name, along its edge of the box, in the chart's own drawing.
|
|
73
|
+
* @csspart drawing - The chart's own drawing in SVG: the box, its grid and numbers, and the surface.
|
|
74
|
+
* @csspart empty - The words when the table has no readings: the `empty` slot, falling back to `empty-text`.
|
|
75
|
+
* @csspart face - One square of the surface between four readings, or a triangle where one is missing, shaded by its height and by which way it faces.
|
|
76
|
+
* @csspart fallback - The box the chart's own drawing fills, shown until a renderer has drawn and where none is connected. Hidden from screen readers.
|
|
77
|
+
* @csspart grid - The box's edges and its grid at the ticks, in the chart's own drawing.
|
|
78
|
+
* @csspart live - The readout: a polite live region that says the place the keys moved to. Hide it visually.
|
|
79
|
+
* @csspart loading - The words while `loading` is set and there are no readings.
|
|
80
|
+
* @csspart mark - The invisible point the tooltip is anchored to, where the renderer draws the reading.
|
|
81
|
+
* @csspart plot - The box the drawing fills, which takes focus to be read by key. It is named by the host's `aria-labelledby` or `aria-label`, then by `label`, then by the caption of the table.
|
|
82
|
+
* @csspart ring - The ring around the reading the pointer or the keys rest on, drawn over the surface.
|
|
83
|
+
* @csspart summary - The measures, the number of readings, and the range of heights, written as a sentence for screen readers, which describes the plot. Hide it visually.
|
|
84
|
+
* @csspart table - The readings as a table, for a screen reader, rendered only where they were drawn from `data`. Hide it visually.
|
|
85
|
+
* @csspart tick - A number at a tick, along its edge of the box, in the chart's own drawing.
|
|
86
|
+
* @csspart tooltip - The bubble, forwarded from `Tooltip`.
|
|
87
|
+
* @csspart tooltip-anchor - The `Tooltip` element itself.
|
|
88
|
+
* @csspart tooltip-arrow - The bubble's point, forwarded from `Tooltip`.
|
|
89
|
+
* @csspart tooltip-arrow-bottom - The point while the bubble is below the reading, on its top edge. Set alongside `tooltip-arrow`.
|
|
90
|
+
* @csspart tooltip-arrow-left - The point while the bubble is left of the reading, on its right edge. Set alongside `tooltip-arrow`.
|
|
91
|
+
* @csspart tooltip-arrow-right - The point while the bubble is right of the reading, on its left edge. Set alongside `tooltip-arrow`.
|
|
92
|
+
* @csspart tooltip-arrow-top - The point while the bubble is above the reading, on its bottom edge. Set alongside `tooltip-arrow`.
|
|
93
|
+
* @csspart tooltip-bottom - The bubble while it is below the reading. Set alongside `tooltip`.
|
|
94
|
+
* @csspart tooltip-cell - One column's name and value inside the tooltip.
|
|
95
|
+
* @csspart tooltip-cell-current - The cell a chart's series keys rest on, which every chart's tooltip can mark. This chart marks none, since its readout says the measure. Set alongside `tooltip-cell`.
|
|
96
|
+
* @csspart tooltip-label - The row's own name inside the tooltip.
|
|
97
|
+
* @csspart tooltip-left - The bubble while it is left of the reading. Set alongside `tooltip`.
|
|
98
|
+
* @csspart tooltip-name - A column's name inside the tooltip.
|
|
99
|
+
* @csspart tooltip-right - The bubble while it is right of the reading. Set alongside `tooltip`.
|
|
100
|
+
* @csspart tooltip-top - The bubble while it is above the reading. Set alongside `tooltip`.
|
|
101
|
+
* @csspart tooltip-value - A column's value inside the tooltip.
|
|
102
|
+
*
|
|
103
|
+
* @cssproperty --spatial-surface-chart-level - How high a face of the surface stands between the lowest reading and the highest, from 0 to 1: its corners' average. The component writes it on each face, and the stylesheet that colors the face along the ramp reads it.
|
|
104
|
+
* @cssproperty --spatial-surface-chart-shade - How far a face of the surface turns from the light, as a share of lightness to take from its color, from 0 facing it to about 0.3. The component writes it on each face, and the stylesheet that shades the face reads it.
|
|
105
|
+
*
|
|
106
|
+
* @cssstate active - While a place is shown, under the pointer or the keys.
|
|
107
|
+
* @cssstate drawn - While a renderer has drawn the surface, and the chart's own drawing is hidden.
|
|
108
|
+
* @cssstate empty - While the `empty` part stands in place of the readings.
|
|
109
|
+
* @cssstate loading - While the `loading` part stands in place of the readings.
|
|
110
|
+
*/
|
|
111
|
+
export declare class HelixHeadlessSpatialSurfaceChart extends HelixHeadlessSpatialSurfaceChart_base {
|
|
112
|
+
/**
|
|
113
|
+
* The key this family's words are registered under, its styled tag:
|
|
114
|
+
* `registerWords('de', { 'helix-spatial-surface-chart': { emptyText: 'Keine Werte' } })`.
|
|
115
|
+
*/
|
|
116
|
+
static words: string;
|
|
117
|
+
/**
|
|
118
|
+
* What the readout says for a place with no reading. `{x}` and `{z}` are
|
|
119
|
+
* the place's numbers across and in depth, and `{across}` and `{depth}`
|
|
120
|
+
* the two measures' names.
|
|
121
|
+
*/
|
|
122
|
+
openLabel: string;
|
|
123
|
+
/** The plot's name where the chart has none: no `aria-label`, `label`, or table caption. */
|
|
124
|
+
plotLabel: string;
|
|
125
|
+
/**
|
|
126
|
+
* The `summary` part's sentence. `{count}` is the number of readings,
|
|
127
|
+
* written in the locale, `{x}`, `{z}`, and `{y}` are the measures across,
|
|
128
|
+
* in depth, and up, and `{low}` and `{high}` the lowest and the highest
|
|
129
|
+
* reading.
|
|
130
|
+
*/
|
|
131
|
+
summaryLabel: string;
|
|
132
|
+
/** Whether a renderer has drawn the surface, which hides the chart's own drawing. */
|
|
133
|
+
private rendered;
|
|
134
|
+
/** Where the chart's own drawing is seen from, which Shift with an arrow turns. */
|
|
135
|
+
private camera;
|
|
136
|
+
/** Each reading where the chart's own drawing put it, which the pointer finds. */
|
|
137
|
+
private placed;
|
|
138
|
+
/** Where the renderer draws the reading, which the tooltip points at. */
|
|
139
|
+
private place;
|
|
140
|
+
/** The field last read, kept so a change of it can be told. */
|
|
141
|
+
private last;
|
|
142
|
+
/** The place the keys rested on last, so a change of it can be told. */
|
|
143
|
+
private told;
|
|
144
|
+
/** Carries a host `aria-labelledby` and `aria-describedby` to the plot. */
|
|
145
|
+
protected readonly aria: AriaForwardController;
|
|
146
|
+
/**
|
|
147
|
+
* The table read as a grid of heights. **The first numeric column is
|
|
148
|
+
* across, the second is in depth, and the third is the height.** A
|
|
149
|
+
* renderer reads it.
|
|
150
|
+
*/
|
|
151
|
+
get field(): SurfaceField;
|
|
152
|
+
/** The place the keys or the pointer rest on, as its place in `field.cells`, or `-1`. */
|
|
153
|
+
get active(): number;
|
|
154
|
+
/** The number of places on the grid, which the keys walk. */
|
|
155
|
+
get readings(): number;
|
|
156
|
+
/**
|
|
157
|
+
* Says whether a renderer has drawn the surface, which hides the chart's
|
|
158
|
+
* own drawing or shows it again. Called by the connector.
|
|
159
|
+
*/
|
|
160
|
+
rendererDrawn(on?: boolean): void;
|
|
161
|
+
/**
|
|
162
|
+
* Puts the reading on the place at `index`, or lets it go with `-1`, as a
|
|
163
|
+
* pointer resting on it in the renderer's drawing or a press there does.
|
|
164
|
+
* A touch keeps it until a tap lands elsewhere, and a mouse leaving a
|
|
165
|
+
* reading the keys moved to leaves it. Called by the connector.
|
|
166
|
+
*/
|
|
167
|
+
requestActive(index: number, kind?: 'mouse' | 'pen' | 'touch'): void;
|
|
168
|
+
/**
|
|
169
|
+
* Says where the renderer draws the reading, as fractions of the plot from
|
|
170
|
+
* its top left, or `undefined` while it draws none. The tooltip points
|
|
171
|
+
* there. Called by the connector.
|
|
172
|
+
*/
|
|
173
|
+
placeReading(at: {
|
|
174
|
+
x: number;
|
|
175
|
+
y: number;
|
|
176
|
+
} | undefined): void;
|
|
177
|
+
/**
|
|
178
|
+
* Tells the keys how many places stand in depth for each place across, so
|
|
179
|
+
* the up and down arrows move in depth and the left and right arrows
|
|
180
|
+
* across. Here rather than in `updated`, since the server calls this one.
|
|
181
|
+
*/
|
|
182
|
+
protected willUpdate(changed: PropertyValues<this>): void;
|
|
183
|
+
connectedCallback(): void;
|
|
184
|
+
protected updated(changed: PropertyValues): void;
|
|
185
|
+
/**
|
|
186
|
+
* Shift with an arrow turns the view, the chart's own drawing and the
|
|
187
|
+
* renderer's alike, and moves no reading.
|
|
188
|
+
*/
|
|
189
|
+
private readonly onPlotKeydown;
|
|
190
|
+
/** The reading a pointer event is over, as `onDrawingMove` finds it, or `-1`. */
|
|
191
|
+
private placeAt;
|
|
192
|
+
/**
|
|
193
|
+
* A mouse or a pen over the chart's own drawing puts the reading on the
|
|
194
|
+
* place nearest it: over a face, the face's nearest corner on the screen,
|
|
195
|
+
* so any point of the surface finds a reading, and otherwise a place
|
|
196
|
+
* within reach. The face under the pointer is the one in front, since the
|
|
197
|
+
* faces are painted back to front.
|
|
198
|
+
*/
|
|
199
|
+
private readonly onDrawingMove;
|
|
200
|
+
private readonly onDrawingLeave;
|
|
201
|
+
/** A tap is a touch reader's hover: it shows the place nearest it until a tap lands elsewhere. */
|
|
202
|
+
private readonly onDrawingTap;
|
|
203
|
+
protected render(): TemplateResult;
|
|
204
|
+
/**
|
|
205
|
+
* The surface in 3D, in SVG: the box with its grid, numbers, and names, and
|
|
206
|
+
* each square of the grid between four readings as a face, painted back to
|
|
207
|
+
* front, colored by its height and shaded by which way it faces. A place
|
|
208
|
+
* with no reading leaves a hole, and a square missing one corner is the
|
|
209
|
+
* triangle of the other three. The chart's own drawing, until a renderer
|
|
210
|
+
* has drawn and where none is connected.
|
|
211
|
+
*/
|
|
212
|
+
private renderFallback;
|
|
213
|
+
/** The readout: the reading at the place in words, once the keys have moved to one. */
|
|
214
|
+
private renderLive;
|
|
215
|
+
/**
|
|
216
|
+
* The reading's row in the tooltip, pointed at its place in the chart's own
|
|
217
|
+
* drawing, which holds it there.
|
|
218
|
+
*/
|
|
219
|
+
private renderOwnTooltip;
|
|
220
|
+
/**
|
|
221
|
+
* The reading's row in the tooltip every chart shows, pointed at where the
|
|
222
|
+
* renderer draws it. Only once a renderer has drawn; until then the
|
|
223
|
+
* chart's own drawing holds the tooltip at the reading.
|
|
224
|
+
*/
|
|
225
|
+
private renderTooltip;
|
|
226
|
+
/** What the plot shows, in a sentence, which describes the plot. */
|
|
227
|
+
private renderSummary;
|
|
228
|
+
}
|
|
229
|
+
declare global {
|
|
230
|
+
interface HTMLElementTagNameMap {
|
|
231
|
+
'helix-headless-spatial-surface-chart': HelixHeadlessSpatialSurfaceChart;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
export {};
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { AriaForwardController, CHART_PLOT, customElement, drawTooltip, formatWords, HelixElement, HelixPlotChangeEvent, HelixPlotTurnEvent, HelixReadingChangeEvent, HelixReadingPressEvent, isRtl, MixinChartSource, MixinChartStatus, spokenReading, words, } from '@fusion.dev/helix-core';
|
|
3
|
+
import { html, nothing, svg } from 'lit';
|
|
4
|
+
import { property, state } from 'lit/decorators.js';
|
|
5
|
+
import '../../tooltip/index.js';
|
|
6
|
+
import { boxView, drawBox, inBox, nearestTo, SPATIAL_VIEW, shape, turned, } from '../../spatial-scatter-chart/view.js';
|
|
7
|
+
import { levelOf, surfaceOf } from '../grid.js';
|
|
8
|
+
const LIGHT = (() => {
|
|
9
|
+
const [x, y, z] = [-0.45, 1, 0.35];
|
|
10
|
+
const length = Math.hypot(x, y, z);
|
|
11
|
+
return [x / length, y / length, z / length];
|
|
12
|
+
})();
|
|
13
|
+
const SHADE = 0.3;
|
|
14
|
+
const TURNS = {
|
|
15
|
+
ArrowDown: { across: 0, up: -1 },
|
|
16
|
+
ArrowLeft: { across: -1, up: 0 },
|
|
17
|
+
ArrowRight: { across: 1, up: 0 },
|
|
18
|
+
ArrowUp: { across: 0, up: 1 },
|
|
19
|
+
};
|
|
20
|
+
let HelixHeadlessSpatialSurfaceChart = class HelixHeadlessSpatialSurfaceChart extends MixinChartStatus(MixinChartSource(HelixElement)) {
|
|
21
|
+
constructor() {
|
|
22
|
+
super(...arguments);
|
|
23
|
+
this.openLabel = '{across} {x}, {depth} {z}: no reading';
|
|
24
|
+
this.plotLabel = '3D surface chart';
|
|
25
|
+
this.summaryLabel = '{count, plural, one {# reading} other {# readings}} of {y} over {x} and {z}, from {low} to {high}.';
|
|
26
|
+
this.rendered = false;
|
|
27
|
+
this.camera = SPATIAL_VIEW;
|
|
28
|
+
this.placed = [];
|
|
29
|
+
this.last = '';
|
|
30
|
+
this.told = -1;
|
|
31
|
+
this.aria = new AriaForwardController(this, {
|
|
32
|
+
target: () => this.partNamed('plot'),
|
|
33
|
+
});
|
|
34
|
+
this.onPlotKeydown = (event) => {
|
|
35
|
+
const turn = TURNS[event.key];
|
|
36
|
+
if (!event.shiftKey || !turn || event.altKey || event.ctrlKey || event.metaKey) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
event.preventDefault();
|
|
40
|
+
event.stopPropagation();
|
|
41
|
+
this.camera = turned(this.camera, turn.across, turn.up);
|
|
42
|
+
this.dispatchEvent(new HelixPlotTurnEvent(turn));
|
|
43
|
+
};
|
|
44
|
+
this.onDrawingMove = (event) => {
|
|
45
|
+
if (event.pointerType === 'touch') {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
this.requestActive(this.placeAt(event), event.pointerType === 'pen' ? 'pen' : 'mouse');
|
|
49
|
+
};
|
|
50
|
+
this.onDrawingLeave = (event) => {
|
|
51
|
+
if (event.pointerType !== 'touch') {
|
|
52
|
+
this.requestActive(-1, 'mouse');
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
this.onDrawingTap = (event) => {
|
|
56
|
+
if (event.pointerType === 'touch') {
|
|
57
|
+
this.requestActive(this.placeAt(event, 24), 'touch');
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
static { this.words = 'helix-spatial-surface-chart'; }
|
|
62
|
+
get field() {
|
|
63
|
+
return surfaceOf(this.sourceRows, this.plottedColumns);
|
|
64
|
+
}
|
|
65
|
+
get active() {
|
|
66
|
+
return this.pointer.at;
|
|
67
|
+
}
|
|
68
|
+
get readings() {
|
|
69
|
+
return this.field.cells.length;
|
|
70
|
+
}
|
|
71
|
+
rendererDrawn(on = true) {
|
|
72
|
+
this.rendered = on;
|
|
73
|
+
this.toggleState('drawn', on);
|
|
74
|
+
}
|
|
75
|
+
requestActive(index, kind = 'mouse') {
|
|
76
|
+
if (kind === 'touch') {
|
|
77
|
+
this.pointer.tapTo(index);
|
|
78
|
+
}
|
|
79
|
+
else if (index >= 0 || !this.pointer.keyed) {
|
|
80
|
+
this.pointer.set(index);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
placeReading(at) {
|
|
84
|
+
this.place = at;
|
|
85
|
+
}
|
|
86
|
+
willUpdate(changed) {
|
|
87
|
+
super.willUpdate(changed);
|
|
88
|
+
this.pointer.grid = Math.max(1, this.field.depths.length);
|
|
89
|
+
}
|
|
90
|
+
connectedCallback() {
|
|
91
|
+
super.connectedCallback();
|
|
92
|
+
this.pointer.axis = 'none';
|
|
93
|
+
this.pointer.onReading = (heard, detail) => {
|
|
94
|
+
const cell = this.field.cells[detail.index];
|
|
95
|
+
const told = { ...detail, index: cell?.row ?? -1, label: cell?.label ?? '' };
|
|
96
|
+
this.dispatchEvent(heard === 'press' ? new HelixReadingPressEvent(told) : new HelixReadingChangeEvent(told));
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
updated(changed) {
|
|
100
|
+
super.updated(changed);
|
|
101
|
+
const field = JSON.stringify(this.field);
|
|
102
|
+
if (field !== this.last) {
|
|
103
|
+
this.last = field;
|
|
104
|
+
this.dispatchEvent(new HelixPlotChangeEvent({ kind: 'data' }));
|
|
105
|
+
}
|
|
106
|
+
if (this.pointer.at !== this.told) {
|
|
107
|
+
this.told = this.pointer.at;
|
|
108
|
+
this.dispatchEvent(new HelixPlotChangeEvent({ kind: 'focus' }));
|
|
109
|
+
}
|
|
110
|
+
if (changed.has('place')) {
|
|
111
|
+
;
|
|
112
|
+
this.renderRoot.querySelector('[for="helix-chart-mark"]')?.reposition?.();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
placeAt(event, reach) {
|
|
116
|
+
const drawing = event.currentTarget;
|
|
117
|
+
const face = event.composedPath()[0]?.closest?.('[part="face"]');
|
|
118
|
+
const corners = new Set((face?.getAttribute('data-corners') ?? '').split(' ').filter(Boolean).map(Number));
|
|
119
|
+
if (corners.size > 0) {
|
|
120
|
+
return nearestTo(drawing, event, this.placed.filter((place) => corners.has(place.index)), Number.POSITIVE_INFINITY);
|
|
121
|
+
}
|
|
122
|
+
return nearestTo(drawing, event, this.placed, reach);
|
|
123
|
+
}
|
|
124
|
+
render() {
|
|
125
|
+
const field = this.field;
|
|
126
|
+
const readable = field.readings > 0;
|
|
127
|
+
const at = this.pointer.at;
|
|
128
|
+
return html `<div
|
|
129
|
+
aria-describedby=${readable ? 'helix-chart-summary' : nothing}
|
|
130
|
+
aria-label=${this.named || this.plotLabel}
|
|
131
|
+
id=${CHART_PLOT}
|
|
132
|
+
part="plot"
|
|
133
|
+
role=${readable ? 'application' : 'img'}
|
|
134
|
+
tabindex=${readable ? '0' : nothing}
|
|
135
|
+
@keydown=${this.onPlotKeydown}
|
|
136
|
+
>
|
|
137
|
+
<slot></slot>
|
|
138
|
+
${readable ? this.renderFallback(field, at) : nothing}
|
|
139
|
+
${readable ? this.renderLive(field, at) : nothing}
|
|
140
|
+
${this.renderTooltip(field.cells[at]?.row)}
|
|
141
|
+
</div>
|
|
142
|
+
${readable ? this.renderSummary(field) : this.renderStatus()}${this.renderTable()}`;
|
|
143
|
+
}
|
|
144
|
+
renderFallback(field, at) {
|
|
145
|
+
const view = boxView(field.axes, this.camera);
|
|
146
|
+
const depths = field.depths.length;
|
|
147
|
+
const boxed = field.cells.map((cell) => cell.value === undefined
|
|
148
|
+
? undefined
|
|
149
|
+
: [
|
|
150
|
+
inBox(field.axes.x, cell.x),
|
|
151
|
+
inBox(field.axes.y, cell.value),
|
|
152
|
+
inBox(field.axes.z, cell.z),
|
|
153
|
+
]);
|
|
154
|
+
const places = boxed.flatMap((box, index) => (box ? [{ index, ...view.on(box) }] : []));
|
|
155
|
+
this.placed = places;
|
|
156
|
+
const faces = [];
|
|
157
|
+
for (let across = 0; across + 1 < field.acrosses.length; across++) {
|
|
158
|
+
for (let depth = 0; depth + 1 < depths; depth++) {
|
|
159
|
+
const here = across * depths + depth;
|
|
160
|
+
const corners = [here, here + depths, here + depths + 1, here + 1].filter((index) => boxed[index] !== undefined);
|
|
161
|
+
if (corners.length < 3) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const points = corners.map((index) => boxed[index]);
|
|
165
|
+
faces.push({
|
|
166
|
+
corners,
|
|
167
|
+
depth: points.reduce((sum, point) => sum + view.depth(point), 0) / points.length,
|
|
168
|
+
level: corners.reduce((sum, index) => sum + (levelOf(field, field.cells[index]) ?? 0), 0) / corners.length,
|
|
169
|
+
points,
|
|
170
|
+
shade: shadeOf(points),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
faces.sort((one, other) => one.depth - other.depth);
|
|
175
|
+
const active = places.find((place) => place.index === at);
|
|
176
|
+
const row = field.cells[at]?.row;
|
|
177
|
+
const { bounds } = view;
|
|
178
|
+
const short = (value) => Math.round(value * 1000) / 1000;
|
|
179
|
+
return html `<div aria-hidden="true" dir="ltr" ?hidden=${this.rendered} part="fallback">
|
|
180
|
+
<svg
|
|
181
|
+
part="drawing"
|
|
182
|
+
preserveAspectRatio="xMidYMid meet"
|
|
183
|
+
viewBox="${bounds.x} ${bounds.y} ${bounds.width} ${bounds.height}"
|
|
184
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
185
|
+
@pointerleave=${this.onDrawingLeave}
|
|
186
|
+
@pointermove=${this.onDrawingMove}
|
|
187
|
+
@pointerup=${this.onDrawingTap}
|
|
188
|
+
>${drawBox(view)}${faces.map((face) => svg `<path d=${shape(face.points.map((point) => view.on(point)))} data-corners=${face.corners.join(' ')} part="face" style="--spatial-surface-chart-level: ${short(face.level)}; --spatial-surface-chart-shade: ${short(face.shade)}"></path>`)}${active
|
|
189
|
+
? svg `<circle cx=${active.x} cy=${active.y} part="ring" r="0.085"></circle><foreignObject height="0.001" overflow="visible" width="0.001" x=${active.x} y=${active.y}>${this.renderOwnTooltip(row)}</foreignObject>`
|
|
190
|
+
: nothing}</svg>
|
|
191
|
+
</div>`;
|
|
192
|
+
}
|
|
193
|
+
renderLive(field, at) {
|
|
194
|
+
const cell = field.cells[at];
|
|
195
|
+
const row = cell?.row === undefined ? undefined : this.sourceRows[cell.row];
|
|
196
|
+
let said = '';
|
|
197
|
+
if (this.pointer.keyed && cell) {
|
|
198
|
+
said = row
|
|
199
|
+
? spokenReading(row, this.pointer.column, (one) => this.pointer.shows(one), this.pointer.position)
|
|
200
|
+
: formatWords(this, 'openLabel', {
|
|
201
|
+
across: field.axes.x.name,
|
|
202
|
+
depth: field.axes.z.name,
|
|
203
|
+
x: cell.x,
|
|
204
|
+
z: cell.z,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return html `<span aria-live="polite" part="live" role="status">${said}</span>`;
|
|
208
|
+
}
|
|
209
|
+
renderOwnTooltip(row) {
|
|
210
|
+
if (this.rendered || row === undefined || !this.sourceRows[row]) {
|
|
211
|
+
return nothing;
|
|
212
|
+
}
|
|
213
|
+
return drawTooltip(this.sourceRows, row, this.tag('tooltip'), { dir: isRtl(this) ? 'rtl' : 'ltr', fixed: true, placement: 'top', x: '0%', y: '0%' }, () => undefined);
|
|
214
|
+
}
|
|
215
|
+
renderTooltip(row) {
|
|
216
|
+
const place = this.place;
|
|
217
|
+
if (!this.rendered || !place || row === undefined || !this.sourceRows[row]) {
|
|
218
|
+
return nothing;
|
|
219
|
+
}
|
|
220
|
+
return drawTooltip(this.sourceRows, row, this.tag('tooltip'), {
|
|
221
|
+
dir: isRtl(this) ? 'rtl' : 'ltr',
|
|
222
|
+
fixed: true,
|
|
223
|
+
placement: 'top',
|
|
224
|
+
x: `${place.x * 100}%`,
|
|
225
|
+
y: `${place.y * 100}%`,
|
|
226
|
+
}, () => undefined);
|
|
227
|
+
}
|
|
228
|
+
renderSummary(field) {
|
|
229
|
+
const values = field.cells.flatMap((cell) => (cell.value === undefined ? [] : [cell.value]));
|
|
230
|
+
const said = formatWords(this, 'summaryLabel', {
|
|
231
|
+
count: field.readings,
|
|
232
|
+
high: Math.max(...values),
|
|
233
|
+
low: Math.min(...values),
|
|
234
|
+
x: field.axes.x.name,
|
|
235
|
+
y: field.axes.y.name,
|
|
236
|
+
z: field.axes.z.name,
|
|
237
|
+
});
|
|
238
|
+
return html `<p id="helix-chart-summary" part="summary">${said}</p>`;
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
__decorate([
|
|
242
|
+
words,
|
|
243
|
+
property({ attribute: 'open-label' })
|
|
244
|
+
], HelixHeadlessSpatialSurfaceChart.prototype, "openLabel", void 0);
|
|
245
|
+
__decorate([
|
|
246
|
+
words,
|
|
247
|
+
property({ attribute: 'plot-label' })
|
|
248
|
+
], HelixHeadlessSpatialSurfaceChart.prototype, "plotLabel", void 0);
|
|
249
|
+
__decorate([
|
|
250
|
+
words,
|
|
251
|
+
property({ attribute: 'summary-label' })
|
|
252
|
+
], HelixHeadlessSpatialSurfaceChart.prototype, "summaryLabel", void 0);
|
|
253
|
+
__decorate([
|
|
254
|
+
state()
|
|
255
|
+
], HelixHeadlessSpatialSurfaceChart.prototype, "rendered", void 0);
|
|
256
|
+
__decorate([
|
|
257
|
+
state()
|
|
258
|
+
], HelixHeadlessSpatialSurfaceChart.prototype, "camera", void 0);
|
|
259
|
+
__decorate([
|
|
260
|
+
state()
|
|
261
|
+
], HelixHeadlessSpatialSurfaceChart.prototype, "place", void 0);
|
|
262
|
+
HelixHeadlessSpatialSurfaceChart = __decorate([
|
|
263
|
+
customElement('helix-headless-spatial-surface-chart')
|
|
264
|
+
], HelixHeadlessSpatialSurfaceChart);
|
|
265
|
+
export { HelixHeadlessSpatialSurfaceChart };
|
|
266
|
+
const shadeOf = (points) => {
|
|
267
|
+
const [a, b, c] = points;
|
|
268
|
+
const u = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
|
|
269
|
+
const v = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
|
|
270
|
+
let n = [
|
|
271
|
+
u[1] * v[2] - u[2] * v[1],
|
|
272
|
+
u[2] * v[0] - u[0] * v[2],
|
|
273
|
+
u[0] * v[1] - u[1] * v[0],
|
|
274
|
+
];
|
|
275
|
+
if (n[1] < 0) {
|
|
276
|
+
n = [-n[0], -n[1], -n[2]];
|
|
277
|
+
}
|
|
278
|
+
const length = Math.hypot(...n) || 1;
|
|
279
|
+
const lit = (n[0] * LIGHT[0] + n[1] * LIGHT[1] + n[2] * LIGHT[2]) / length;
|
|
280
|
+
return Math.min(SHADE, Math.max(0, (1 - lit) * SHADE));
|
|
281
|
+
};
|