@fusion.dev/helix-headless 0.198.0 → 0.199.1
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 +8267 -2270
- package/dist/carousel/root/carousel-root.d.ts +14 -0
- package/dist/carousel/root/carousel-root.js +15 -1
- package/dist/element-events.generated.js +19 -0
- package/dist/graph/root/graph-root.d.ts +33 -0
- package/dist/graph/root/graph-root.js +57 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/map/raster/map-raster.d.ts +7 -3
- package/dist/map/raster/map-raster.js +5 -3
- package/dist/motion-toggle/index.d.ts +1 -0
- package/dist/motion-toggle/index.js +1 -0
- package/dist/motion-toggle/root/motion-toggle-root.d.ts +77 -0
- package/dist/motion-toggle/root/motion-toggle-root.js +56 -0
- package/dist/spatial-bar-chart/bars.d.ts +79 -0
- package/dist/spatial-bar-chart/bars.js +72 -0
- package/dist/spatial-bar-chart/index.d.ts +2 -0
- package/dist/spatial-bar-chart/index.js +2 -0
- package/dist/spatial-bar-chart/root/spatial-bar-chart-root.d.ts +245 -0
- package/dist/spatial-bar-chart/root/spatial-bar-chart-root.js +262 -0
- package/dist/spatial-ribbon-chart/index.d.ts +2 -0
- package/dist/spatial-ribbon-chart/index.js +2 -0
- package/dist/spatial-ribbon-chart/ribbons.d.ts +55 -0
- package/dist/spatial-ribbon-chart/ribbons.js +99 -0
- package/dist/spatial-ribbon-chart/root/spatial-ribbon-chart-root.d.ts +243 -0
- package/dist/spatial-ribbon-chart/root/spatial-ribbon-chart-root.js +259 -0
- package/dist/spatial-scatter-chart/field.d.ts +8 -0
- package/dist/spatial-scatter-chart/root/spatial-scatter-chart-root.d.ts +14 -0
- package/dist/spatial-scatter-chart/root/spatial-scatter-chart-root.js +20 -6
- package/dist/spatial-scatter-chart/view.d.ts +14 -1
- package/dist/spatial-scatter-chart/view.js +29 -9
- package/dist/spatial-surface-chart/grid.d.ts +22 -0
- package/dist/spatial-surface-chart/grid.js +67 -1
- package/dist/spatial-surface-chart/index.d.ts +1 -1
- package/dist/spatial-surface-chart/index.js +1 -1
- package/dist/spatial-surface-chart/root/spatial-surface-chart-root.d.ts +20 -0
- package/dist/spatial-surface-chart/root/spatial-surface-chart-root.js +38 -5
- package/dist/spatial-treemap/blocks.d.ts +106 -0
- package/dist/spatial-treemap/blocks.js +215 -0
- package/dist/spatial-treemap/index.d.ts +2 -0
- package/dist/spatial-treemap/index.js +2 -0
- package/dist/spatial-treemap/root/spatial-treemap-root.d.ts +236 -0
- package/dist/spatial-treemap/root/spatial-treemap-root.js +249 -0
- package/dist/video/root/video-root.d.ts +14 -0
- package/dist/video/root/video-root.js +30 -2
- package/examples.json +1 -1
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +3037 -99
- package/types/svelte.d.ts +4 -0
- package/types/vue.d.ts +519 -3
- package/types/words.d.ts +149 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
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';
|
|
2
|
+
import { AriaForwardController, CHART_PLOT, customElement, drawTooltip, formatWords, HelixElement, HelixPlotChangeEvent, HelixPlotTurnEvent, HelixReadingChangeEvent, HelixReadingPressEvent, isRtl, MixinChartSource, MixinChartStatus, shortNumber, spokenReading, words, } from '@fusion.dev/helix-core';
|
|
3
3
|
import { html, nothing, svg } from 'lit';
|
|
4
4
|
import { property, state } from 'lit/decorators.js';
|
|
5
5
|
import '../../tooltip/index.js';
|
|
6
6
|
import { fieldOf } from '../field.js';
|
|
7
|
-
import { boxView, DragTurn, drawBox, inBox, nearestTo, SPATIAL_VIEW, turned, } from '../view.js';
|
|
7
|
+
import { boundsOf, boxView, DragTurn, drawBox, inBox, nearestTo, SPATIAL_VIEW, turned, } from '../view.js';
|
|
8
8
|
const SERIES = 24;
|
|
9
9
|
const TURNS = {
|
|
10
10
|
ArrowDown: { across: 0, up: -1 },
|
|
@@ -15,7 +15,9 @@ const TURNS = {
|
|
|
15
15
|
let HelixHeadlessSpatialScatterChart = class HelixHeadlessSpatialScatterChart extends MixinChartStatus(MixinChartSource(HelixElement)) {
|
|
16
16
|
constructor() {
|
|
17
17
|
super(...arguments);
|
|
18
|
+
this.dropLines = false;
|
|
18
19
|
this.groupBy = '';
|
|
20
|
+
this.noBox = false;
|
|
19
21
|
this.plotLabel = '3D scatter chart';
|
|
20
22
|
this.summaryLabel = '{count, plural, one {# point} other {# points}}: {x} across, {y} up, and {z} in depth.';
|
|
21
23
|
this.rendered = false;
|
|
@@ -153,19 +155,25 @@ let HelixHeadlessSpatialScatterChart = class HelixHeadlessSpatialScatterChart ex
|
|
|
153
155
|
${readable ? this.renderSummary(field) : this.renderStatus()}${this.renderTable()}`;
|
|
154
156
|
}
|
|
155
157
|
renderFallback(field, at) {
|
|
156
|
-
const view = boxView(field.axes, this.camera);
|
|
158
|
+
const view = boxView(field.axes, this.camera, [], this.noBox);
|
|
157
159
|
const places = field.points.map((point, index) => {
|
|
158
160
|
const box = [
|
|
159
161
|
inBox(field.axes.x, point.x),
|
|
160
162
|
inBox(field.axes.y, point.y),
|
|
161
163
|
inBox(field.axes.z, point.z),
|
|
162
164
|
];
|
|
163
|
-
return {
|
|
165
|
+
return {
|
|
166
|
+
depth: view.depth(box),
|
|
167
|
+
foot: view.on([box[0], -1, box[2]]),
|
|
168
|
+
index,
|
|
169
|
+
point,
|
|
170
|
+
...view.on(box),
|
|
171
|
+
};
|
|
164
172
|
});
|
|
165
173
|
this.placed = places;
|
|
166
174
|
const painted = [...places].sort((one, other) => one.depth - other.depth);
|
|
167
175
|
const active = places[at];
|
|
168
|
-
const bounds = this.drag.frame(view.bounds, view.on([0, 0, 0]));
|
|
176
|
+
const bounds = this.drag.frame(this.noBox ? boundsOf(places, 0.1) : view.bounds, view.on([0, 0, 0]));
|
|
169
177
|
return html `<div aria-hidden="true" dir="ltr" ?hidden=${this.rendered} part="fallback">
|
|
170
178
|
<svg
|
|
171
179
|
part="drawing"
|
|
@@ -178,7 +186,7 @@ let HelixHeadlessSpatialScatterChart = class HelixHeadlessSpatialScatterChart ex
|
|
|
178
186
|
@pointerleave=${this.onDrawingLeave}
|
|
179
187
|
@pointermove=${this.onDrawingMove}
|
|
180
188
|
@pointerup=${this.onDrawingTap}
|
|
181
|
-
>${drawBox(view)}${painted.map((place) => svg `<circle cx=${place.x} cy=${place.y} part="point${place.index === at ? ' point-active' : ''}" r="0.045" style="--chart-series: var(--chart-series-${(place.point.groupIndex % SERIES) + 1})"></circle>`)}${active
|
|
189
|
+
>${this.noBox ? nothing : drawBox(view)}${painted.map((place) => svg `${this.dropLines ? svg `<path d="M${shortNumber(place.x)} ${shortNumber(place.y)}L${shortNumber(place.foot.x)} ${shortNumber(place.foot.y)}" part="drop-line" style="--chart-series: var(--chart-series-${(place.point.groupIndex % SERIES) + 1})"></path>` : nothing}<circle cx=${place.x} cy=${place.y} part="point${place.index === at ? ' point-active' : ''}" r="0.045" style="--chart-series: var(--chart-series-${(place.point.groupIndex % SERIES) + 1})"></circle>`)}${active
|
|
182
190
|
? 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(active.point.row)}</foreignObject>`
|
|
183
191
|
: nothing}</svg>
|
|
184
192
|
</div>`;
|
|
@@ -222,9 +230,15 @@ let HelixHeadlessSpatialScatterChart = class HelixHeadlessSpatialScatterChart ex
|
|
|
222
230
|
return html `<p id="helix-chart-summary" part="summary">${said}</p>`;
|
|
223
231
|
}
|
|
224
232
|
};
|
|
233
|
+
__decorate([
|
|
234
|
+
property({ attribute: 'drop-lines', reflect: true, type: Boolean })
|
|
235
|
+
], HelixHeadlessSpatialScatterChart.prototype, "dropLines", void 0);
|
|
225
236
|
__decorate([
|
|
226
237
|
property({ attribute: 'group-by' })
|
|
227
238
|
], HelixHeadlessSpatialScatterChart.prototype, "groupBy", void 0);
|
|
239
|
+
__decorate([
|
|
240
|
+
property({ attribute: 'no-box', reflect: true, type: Boolean })
|
|
241
|
+
], HelixHeadlessSpatialScatterChart.prototype, "noBox", void 0);
|
|
228
242
|
__decorate([
|
|
229
243
|
words,
|
|
230
244
|
property({ attribute: 'plot-label' })
|
|
@@ -25,6 +25,13 @@ export declare const SPATIAL_VIEW: Camera;
|
|
|
25
25
|
export declare const VIEW_STEP = 15;
|
|
26
26
|
/** A place in the box: across, up, and in depth, each from -1 to 1. */
|
|
27
27
|
export type BoxPoint = readonly [number, number, number];
|
|
28
|
+
/**
|
|
29
|
+
* How long the box is along each axis, across, up, and in depth, as a share
|
|
30
|
+
* of the cube's: `[1, 1, 1]` where a chart does not say, and less along an
|
|
31
|
+
* axis whose grid is shorter, as a bar chart of one series is a long, low
|
|
32
|
+
* row. A place in the box is still from -1 to 1 along each.
|
|
33
|
+
*/
|
|
34
|
+
export type BoxExtent = readonly [number, number, number];
|
|
28
35
|
/** A number or a name written in the drawing, where it stands on the page. */
|
|
29
36
|
export interface ViewLabel {
|
|
30
37
|
/** Whether it is a measure's name, drawn stronger, rather than a tick's number. */
|
|
@@ -63,10 +70,16 @@ export declare const NAME_SIZE = 0.15;
|
|
|
63
70
|
* the page, which `extra` adds to the bounds.
|
|
64
71
|
*/
|
|
65
72
|
export declare const boxView: (axes: {
|
|
73
|
+
extent?: BoxExtent | undefined;
|
|
66
74
|
x: PlotAxis;
|
|
67
75
|
y: PlotAxis;
|
|
68
76
|
z: PlotAxis;
|
|
69
|
-
}, camera: Camera, extra?: readonly PagePoint[]) => BoxView;
|
|
77
|
+
}, camera: Camera, extra?: readonly PagePoint[], bare?: boolean) => BoxView;
|
|
78
|
+
/**
|
|
79
|
+
* The part of the page that holds `points`, `pad` beyond them on every side:
|
|
80
|
+
* the frame of a drawing with no box, which fits its readings alone.
|
|
81
|
+
*/
|
|
82
|
+
export declare const boundsOf: (points: readonly PagePoint[], pad: number) => BoxView['bounds'];
|
|
70
83
|
/** A place on the page as a share of the bounds, from their top left, for a mark the tooltip points at. */
|
|
71
84
|
export declare const shareOf: (view: BoxView, point: PagePoint) => {
|
|
72
85
|
x: number;
|
|
@@ -11,10 +11,11 @@ const WIDE = 0.58;
|
|
|
11
11
|
export const TICK_SIZE = 0.13;
|
|
12
12
|
export const NAME_SIZE = 0.15;
|
|
13
13
|
const LABEL_GAP = 0.06;
|
|
14
|
-
export const boxView = (axes, camera, extra = []) => {
|
|
14
|
+
export const boxView = (axes, camera, extra = [], bare = false) => {
|
|
15
15
|
const projection = projectionOf(camera);
|
|
16
|
-
const
|
|
17
|
-
const
|
|
16
|
+
const [ex, ey, ez] = axes.extent ?? [1, 1, 1];
|
|
17
|
+
const on = ([x, y, z]) => projection.at(x * ex, -z * ez, y * ey);
|
|
18
|
+
const depth = ([x, , z]) => projection.depth(x * ex, -z * ez);
|
|
18
19
|
const lines = [];
|
|
19
20
|
const line = (from, to) => {
|
|
20
21
|
const [a, b] = [on(from), on(to)];
|
|
@@ -27,26 +28,31 @@ export const boxView = (axes, camera, extra = []) => {
|
|
|
27
28
|
line([a, b, -1], [a, b, 1]);
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
|
-
for (const tick of axes.x.ticks) {
|
|
31
|
+
for (const tick of axes.x.rules ?? axes.x.ticks) {
|
|
31
32
|
const x = inBox(axes.x, tick);
|
|
32
33
|
line([x, -1, -1], [x, -1, 1]);
|
|
33
34
|
line([x, -1, -1], [x, 1, -1]);
|
|
34
35
|
}
|
|
35
|
-
for (const tick of axes.z.ticks) {
|
|
36
|
+
for (const tick of axes.z.rules ?? axes.z.ticks) {
|
|
36
37
|
const z = inBox(axes.z, tick);
|
|
37
38
|
line([-1, -1, z], [1, -1, z]);
|
|
38
39
|
line([-1, -1, z], [-1, 1, z]);
|
|
39
40
|
}
|
|
40
|
-
for (const tick of axes.y.ticks) {
|
|
41
|
+
for (const tick of axes.y.rules ?? axes.y.ticks) {
|
|
41
42
|
const y = inBox(axes.y, tick);
|
|
42
43
|
line([-1, y, -1], [1, y, -1]);
|
|
43
44
|
line([-1, y, -1], [-1, y, 1]);
|
|
44
45
|
}
|
|
45
46
|
const text = (axis) => ({
|
|
46
47
|
name: axis.name,
|
|
47
|
-
ticks: axis.ticks.map((tick) => ({
|
|
48
|
+
ticks: axis.ticks.map((tick, index) => ({
|
|
49
|
+
at: inBox(axis, tick),
|
|
50
|
+
text: axis.labels?.[index] ?? tickText(tick),
|
|
51
|
+
})),
|
|
48
52
|
});
|
|
49
|
-
const labels =
|
|
53
|
+
const labels = bare
|
|
54
|
+
? []
|
|
55
|
+
: 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 });
|
|
50
56
|
const corners = [];
|
|
51
57
|
for (const x of [-1, 1]) {
|
|
52
58
|
for (const y of [-1, 1]) {
|
|
@@ -67,12 +73,26 @@ export const boxView = (axes, camera, extra = []) => {
|
|
|
67
73
|
return {
|
|
68
74
|
bounds: { height: Math.max(...ys) - top, width: Math.max(...xs) - left, x: left, y: top },
|
|
69
75
|
depth,
|
|
70
|
-
grid: lines.join(''),
|
|
76
|
+
grid: bare ? '' : lines.join(''),
|
|
71
77
|
labels,
|
|
72
78
|
on,
|
|
73
79
|
projection,
|
|
74
80
|
};
|
|
75
81
|
};
|
|
82
|
+
export const boundsOf = (points, pad) => {
|
|
83
|
+
if (points.length === 0) {
|
|
84
|
+
return { height: 2 * pad, width: 2 * pad, x: -pad, y: -pad };
|
|
85
|
+
}
|
|
86
|
+
const xs = points.map((point) => point.x);
|
|
87
|
+
const ys = points.map((point) => point.y);
|
|
88
|
+
const [left, top] = [Math.min(...xs) - pad, Math.min(...ys) - pad];
|
|
89
|
+
return {
|
|
90
|
+
height: Math.max(...ys) + pad - top,
|
|
91
|
+
width: Math.max(...xs) + pad - left,
|
|
92
|
+
x: left,
|
|
93
|
+
y: top,
|
|
94
|
+
};
|
|
95
|
+
};
|
|
76
96
|
export const shareOf = (view, point) => ({
|
|
77
97
|
x: (point.x - view.bounds.x) / view.bounds.width,
|
|
78
98
|
y: (point.y - view.bounds.y) / view.bounds.height,
|
|
@@ -40,6 +40,8 @@ export interface SurfaceField {
|
|
|
40
40
|
/** The lowest and the highest reading, which every level is measured between. */
|
|
41
41
|
lowest: number;
|
|
42
42
|
highest: number;
|
|
43
|
+
/** The contour lines at each tick of the height inside the readings, which `contours` draws on the surface. */
|
|
44
|
+
contours: Contour[];
|
|
43
45
|
}
|
|
44
46
|
/**
|
|
45
47
|
* Reads the table: **the first numeric column is across, the second is in
|
|
@@ -51,3 +53,23 @@ export interface SurfaceField {
|
|
|
51
53
|
export declare const surfaceOf: (rows: readonly FieldRow[], columns: readonly number[]) => SurfaceField;
|
|
52
54
|
/** How high a cell stands between the lowest and the highest reading, from 0 to 1, or undefined where it has none. */
|
|
53
55
|
export declare const levelOf: (field: SurfaceField, cell: SurfaceCell) => number | undefined;
|
|
56
|
+
/** A contour line's pieces at one height: each a straight segment from one place on the floor to another, in the table's own numbers. */
|
|
57
|
+
export interface Contour {
|
|
58
|
+
/** The height the line runs at. */
|
|
59
|
+
level: number;
|
|
60
|
+
/** Each segment as two places, across and in depth. */
|
|
61
|
+
segments: Array<[[number, number], [number, number]]>;
|
|
62
|
+
/** The square of the grid each segment crosses, by its first corner's place in `cells`, in the order of `segments`. */
|
|
63
|
+
squares: number[];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The surface's contour lines at `levels`, by marching squares: in each
|
|
67
|
+
* square of the grid whose four readings are all there, a line crosses each
|
|
68
|
+
* edge whose two ends stand either side of the level, at the place along it
|
|
69
|
+
* the level falls. A square whose diagonal corners stand on the same side
|
|
70
|
+
* is read by the middle of the square, the average of its four corners, so
|
|
71
|
+
* the two lines through it never cross.
|
|
72
|
+
*/
|
|
73
|
+
export declare const contoursOf: (field: SurfaceField, levels: readonly number[]) => Contour[];
|
|
74
|
+
/** The levels a surface's contour lines run at: its height's ticks strictly between its lowest and highest reading. */
|
|
75
|
+
export declare const contourLevels: (field: SurfaceField) => number[];
|
|
@@ -24,7 +24,7 @@ export const surfaceOf = (rows, columns) => {
|
|
|
24
24
|
const range = exact && low < high ? [low, high] : niceRange(low, high);
|
|
25
25
|
return { name: nameOf(column), range, ticks: ticks(range) };
|
|
26
26
|
};
|
|
27
|
-
|
|
27
|
+
const field = {
|
|
28
28
|
acrosses,
|
|
29
29
|
axes: {
|
|
30
30
|
x: axis(xAt, acrosses, true),
|
|
@@ -32,11 +32,14 @@ export const surfaceOf = (rows, columns) => {
|
|
|
32
32
|
z: axis(zAt, depths, true),
|
|
33
33
|
},
|
|
34
34
|
cells,
|
|
35
|
+
contours: [],
|
|
35
36
|
depths,
|
|
36
37
|
highest: read.length > 0 ? Math.max(...read.map((one) => one.value)) : 0,
|
|
37
38
|
lowest: read.length > 0 ? Math.min(...read.map((one) => one.value)) : 0,
|
|
38
39
|
readings: read.length,
|
|
39
40
|
};
|
|
41
|
+
field.contours = contoursOf(field, contourLevels(field));
|
|
42
|
+
return field;
|
|
40
43
|
};
|
|
41
44
|
export const levelOf = (field, cell) => {
|
|
42
45
|
if (cell.value === undefined) {
|
|
@@ -45,3 +48,66 @@ export const levelOf = (field, cell) => {
|
|
|
45
48
|
const { highest: high, lowest: low } = field;
|
|
46
49
|
return high === low ? 0.5 : (cell.value - low) / (high - low);
|
|
47
50
|
};
|
|
51
|
+
export const contoursOf = (field, levels) => {
|
|
52
|
+
const depths = field.depths.length;
|
|
53
|
+
const value = (across, depth) => field.cells[across * depths + depth]?.value;
|
|
54
|
+
return levels.map((level) => {
|
|
55
|
+
const segments = [];
|
|
56
|
+
const squares = [];
|
|
57
|
+
for (let across = 0; across + 1 < field.acrosses.length; across++) {
|
|
58
|
+
for (let depth = 0; depth + 1 < depths; depth++) {
|
|
59
|
+
const corners = [
|
|
60
|
+
[across, depth],
|
|
61
|
+
[across + 1, depth],
|
|
62
|
+
[across + 1, depth + 1],
|
|
63
|
+
[across, depth + 1],
|
|
64
|
+
];
|
|
65
|
+
const values = corners.map(([a, d]) => value(a, d));
|
|
66
|
+
if (values.some((one) => one === undefined)) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const [v0, v1, v2, v3] = values;
|
|
70
|
+
const place = (from, to) => {
|
|
71
|
+
const [a, b] = [
|
|
72
|
+
corners[from],
|
|
73
|
+
corners[to],
|
|
74
|
+
];
|
|
75
|
+
const [va, vb] = [values[from], values[to]];
|
|
76
|
+
const t = vb === va ? 0.5 : (level - va) / (vb - va);
|
|
77
|
+
const at = (i, j) => field.acrosses[i] +
|
|
78
|
+
(field.acrosses[j] - field.acrosses[i]) * t;
|
|
79
|
+
const deep = (i, j) => field.depths[i] +
|
|
80
|
+
(field.depths[j] - field.depths[i]) * t;
|
|
81
|
+
return [at(a[0], b[0]), deep(a[1], b[1])];
|
|
82
|
+
};
|
|
83
|
+
const edges = [
|
|
84
|
+
[0, 1],
|
|
85
|
+
[1, 2],
|
|
86
|
+
[3, 2],
|
|
87
|
+
[0, 3],
|
|
88
|
+
];
|
|
89
|
+
const crossed = edges
|
|
90
|
+
.filter(([from, to]) => values[from] >= level !== values[to] >= level)
|
|
91
|
+
.map(([from, to]) => place(from, to));
|
|
92
|
+
const square = across * depths + depth;
|
|
93
|
+
if (crossed.length === 2) {
|
|
94
|
+
segments.push([crossed[0], crossed[1]]);
|
|
95
|
+
squares.push(square);
|
|
96
|
+
}
|
|
97
|
+
else if (crossed.length === 4) {
|
|
98
|
+
const [bottom, right, top, left] = crossed;
|
|
99
|
+
const middle = (v0 + v1 + v2 + v3) / 4 >= level;
|
|
100
|
+
if (middle === v0 >= level) {
|
|
101
|
+
segments.push([bottom, right], [top, left]);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
segments.push([bottom, left], [right, top]);
|
|
105
|
+
}
|
|
106
|
+
squares.push(square, square);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return { level, segments, squares };
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
export const contourLevels = (field) => field.axes.y.ticks.filter((tick) => tick > field.lowest && tick < field.highest);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { levelOf, type SurfaceCell, type SurfaceField, surfaceOf } from './grid.js';
|
|
1
|
+
export { type Contour, contourLevels, contoursOf, levelOf, type SurfaceCell, type SurfaceField, surfaceOf, } from './grid.js';
|
|
2
2
|
export { HelixHeadlessSpatialSurfaceChart } from './root/spatial-surface-chart-root.js';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { levelOf, surfaceOf } from './grid.js';
|
|
1
|
+
export { contourLevels, contoursOf, levelOf, surfaceOf, } from './grid.js';
|
|
2
2
|
export { HelixHeadlessSpatialSurfaceChart } from './root/spatial-surface-chart-root.js';
|
|
@@ -71,6 +71,7 @@ declare const HelixHeadlessSpatialSurfaceChart_base: typeof HelixElement & (new
|
|
|
71
71
|
* @event {HelixReadingPressEvent} helix-reading-press - A place was clicked or pressed with Enter or Space, with the same detail as `helix-reading-change`.
|
|
72
72
|
*
|
|
73
73
|
* @csspart axis-name - A measure's name, along its edge of the box, in the chart's own drawing.
|
|
74
|
+
* @csspart contour - The pieces of one contour line that cross one square of the surface, drawn under `contours` at their height, colored along the ramp by it.
|
|
74
75
|
* @csspart drawing - The chart's own drawing in SVG: the box, its grid and numbers, and the surface.
|
|
75
76
|
* @csspart empty - The words when the table has no readings: the `empty` slot, falling back to `empty-text`.
|
|
76
77
|
* @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.
|
|
@@ -115,6 +116,19 @@ export declare class HelixHeadlessSpatialSurfaceChart extends HelixHeadlessSpati
|
|
|
115
116
|
* `registerWords('de', { 'helix-spatial-surface-chart': { emptyText: 'Keine Werte' } })`.
|
|
116
117
|
*/
|
|
117
118
|
static words: string;
|
|
119
|
+
/**
|
|
120
|
+
* Draws the surface's contour lines on it, at each tick of its height, in
|
|
121
|
+
* the ramp's colors, so its shape reads as on a map and two places at the
|
|
122
|
+
* same height read as such. A renderer draws them too.
|
|
123
|
+
*/
|
|
124
|
+
contours: boolean;
|
|
125
|
+
/**
|
|
126
|
+
* Draws no box: no edges, grid, numbers, or names, so the readings stand
|
|
127
|
+
* alone and fill the plot, as a picture on a product's own page does. The
|
|
128
|
+
* table, the summary, the tooltip, and the keys still carry every value,
|
|
129
|
+
* and a renderer draws no box either.
|
|
130
|
+
*/
|
|
131
|
+
noBox: boolean;
|
|
118
132
|
/**
|
|
119
133
|
* What the readout says for a place with no reading. `{x}` and `{z}` are
|
|
120
134
|
* the place's numbers across and in depth, and `{across}` and `{depth}`
|
|
@@ -221,6 +235,12 @@ export declare class HelixHeadlessSpatialSurfaceChart extends HelixHeadlessSpati
|
|
|
221
235
|
* has drawn and where none is connected.
|
|
222
236
|
*/
|
|
223
237
|
private renderFallback;
|
|
238
|
+
/**
|
|
239
|
+
* The contour lines that cross one square of the grid, at their heights, as
|
|
240
|
+
* one path for each, drawn straight after the square's face so the faces
|
|
241
|
+
* in front of it cover them as they cover the face.
|
|
242
|
+
*/
|
|
243
|
+
private renderContoursIn;
|
|
224
244
|
/** The readout: the reading at the place in words, once the keys have moved to one. */
|
|
225
245
|
private renderLive;
|
|
226
246
|
/**
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { __decorate } from "tslib";
|
|
2
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';
|
|
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, DragTurn, drawBox, inBox, nearestTo, SPATIAL_VIEW, shape, turned, } from '../../spatial-scatter-chart/view.js';
|
|
6
|
+
import { boundsOf, 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];
|
|
@@ -20,6 +20,8 @@ const TURNS = {
|
|
|
20
20
|
let HelixHeadlessSpatialSurfaceChart = class HelixHeadlessSpatialSurfaceChart extends MixinChartStatus(MixinChartSource(HelixElement)) {
|
|
21
21
|
constructor() {
|
|
22
22
|
super(...arguments);
|
|
23
|
+
this.contours = false;
|
|
24
|
+
this.noBox = false;
|
|
23
25
|
this.openLabel = '{across} {x}, {depth} {z}: no reading';
|
|
24
26
|
this.plotLabel = '3D surface chart';
|
|
25
27
|
this.summaryLabel = '{count, plural, one {# reading} other {# readings}} of {y} over {x} and {z}, from {low} to {high}.';
|
|
@@ -160,7 +162,7 @@ let HelixHeadlessSpatialSurfaceChart = class HelixHeadlessSpatialSurfaceChart ex
|
|
|
160
162
|
${readable ? this.renderSummary(field) : this.renderStatus()}${this.renderTable()}`;
|
|
161
163
|
}
|
|
162
164
|
renderFallback(field, at) {
|
|
163
|
-
const view = boxView(field.axes, this.camera);
|
|
165
|
+
const view = boxView(field.axes, this.camera, [], this.noBox);
|
|
164
166
|
const depths = field.depths.length;
|
|
165
167
|
const boxed = field.cells.map((cell) => cell.value === undefined
|
|
166
168
|
? undefined
|
|
@@ -186,13 +188,14 @@ let HelixHeadlessSpatialSurfaceChart = class HelixHeadlessSpatialSurfaceChart ex
|
|
|
186
188
|
level: corners.reduce((sum, index) => sum + (levelOf(field, field.cells[index]) ?? 0), 0) / corners.length,
|
|
187
189
|
points,
|
|
188
190
|
shade: shadeOf(points),
|
|
191
|
+
square: here,
|
|
189
192
|
});
|
|
190
193
|
}
|
|
191
194
|
}
|
|
192
195
|
faces.sort((one, other) => one.depth - other.depth);
|
|
193
196
|
const active = places.find((place) => place.index === at);
|
|
194
197
|
const row = field.cells[at]?.row;
|
|
195
|
-
const bounds = this.drag.frame(view.bounds, view.on([0, 0, 0]));
|
|
198
|
+
const bounds = this.drag.frame(this.noBox ? boundsOf(places, 0.1) : view.bounds, view.on([0, 0, 0]));
|
|
196
199
|
const short = (value) => Math.round(value * 1000) / 1000;
|
|
197
200
|
return html `<div aria-hidden="true" dir="ltr" ?hidden=${this.rendered} part="fallback">
|
|
198
201
|
<svg
|
|
@@ -206,11 +209,35 @@ let HelixHeadlessSpatialSurfaceChart = class HelixHeadlessSpatialSurfaceChart ex
|
|
|
206
209
|
@pointerleave=${this.onDrawingLeave}
|
|
207
210
|
@pointermove=${this.onDrawingMove}
|
|
208
211
|
@pointerup=${this.onDrawingTap}
|
|
209
|
-
>${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
|
|
212
|
+
>${this.noBox ? nothing : 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>${this.contours ? this.renderContoursIn(field, face.square, view.on) : nothing}`)}${active
|
|
210
213
|
? 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>`
|
|
211
214
|
: nothing}</svg>
|
|
212
215
|
</div>`;
|
|
213
216
|
}
|
|
217
|
+
renderContoursIn(field, square, on) {
|
|
218
|
+
const { highest, lowest } = field;
|
|
219
|
+
const short = (value) => Math.round(value * 1000) / 1000;
|
|
220
|
+
return field.contours.flatMap((contour) => {
|
|
221
|
+
const pieces = contour.segments.filter((_, at) => contour.squares[at] === square);
|
|
222
|
+
if (pieces.length === 0) {
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
const height = inBox(field.axes.y, contour.level);
|
|
226
|
+
const d = pieces
|
|
227
|
+
.map(([from, to]) => {
|
|
228
|
+
const [a, b] = [
|
|
229
|
+
on([inBox(field.axes.x, from[0]), height, inBox(field.axes.z, from[1])]),
|
|
230
|
+
on([inBox(field.axes.x, to[0]), height, inBox(field.axes.z, to[1])]),
|
|
231
|
+
];
|
|
232
|
+
return `M${short(a.x)} ${short(a.y)}L${short(b.x)} ${short(b.y)}`;
|
|
233
|
+
})
|
|
234
|
+
.join('');
|
|
235
|
+
const level = highest === lowest ? 0.5 : (contour.level - lowest) / (highest - lowest);
|
|
236
|
+
return [
|
|
237
|
+
svg `<path d=${d} part="contour" style="--spatial-surface-chart-level: ${short(level)}"></path>`,
|
|
238
|
+
];
|
|
239
|
+
});
|
|
240
|
+
}
|
|
214
241
|
renderLive(field, at) {
|
|
215
242
|
const cell = field.cells[at];
|
|
216
243
|
const row = cell?.row === undefined ? undefined : this.sourceRows[cell.row];
|
|
@@ -259,6 +286,12 @@ let HelixHeadlessSpatialSurfaceChart = class HelixHeadlessSpatialSurfaceChart ex
|
|
|
259
286
|
return html `<p id="helix-chart-summary" part="summary">${said}</p>`;
|
|
260
287
|
}
|
|
261
288
|
};
|
|
289
|
+
__decorate([
|
|
290
|
+
property({ reflect: true, type: Boolean })
|
|
291
|
+
], HelixHeadlessSpatialSurfaceChart.prototype, "contours", void 0);
|
|
292
|
+
__decorate([
|
|
293
|
+
property({ attribute: 'no-box', reflect: true, type: Boolean })
|
|
294
|
+
], HelixHeadlessSpatialSurfaceChart.prototype, "noBox", void 0);
|
|
262
295
|
__decorate([
|
|
263
296
|
words,
|
|
264
297
|
property({ attribute: 'open-label' })
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A 3D treemap's table read as blocks on a floor, each as large as its size.
|
|
3
|
+
*
|
|
4
|
+
* Kept apart from the element because it is arithmetic a picture hides: a
|
|
5
|
+
* floor not shared out in proportion makes one item look larger than another
|
|
6
|
+
* that is the same, and blocks painted in the wrong order cover the ones in
|
|
7
|
+
* front of them.
|
|
8
|
+
*/
|
|
9
|
+
import { type Drawn, type PagePoint, type Projection } from '@fusion.dev/helix-core';
|
|
10
|
+
import { type FieldRow, type PlotAxis } from '../spatial-scatter-chart/field.js';
|
|
11
|
+
import { type BoxExtent } from '../spatial-scatter-chart/view.js';
|
|
12
|
+
/** A part of the floor: across from `x0` to `x1`, and in depth from `z0` to `z1`, each from -1 to 1. */
|
|
13
|
+
export interface FloorArea {
|
|
14
|
+
x0: number;
|
|
15
|
+
x1: number;
|
|
16
|
+
z0: number;
|
|
17
|
+
z1: number;
|
|
18
|
+
}
|
|
19
|
+
/** One row drawn as a block. */
|
|
20
|
+
export interface TreemapBlock {
|
|
21
|
+
/** Its part of the floor, inside its group's. */
|
|
22
|
+
area: FloorArea;
|
|
23
|
+
/** Its group, from the `group-by` column, or `''` where there is none. */
|
|
24
|
+
group: string;
|
|
25
|
+
/** Its group's place among the groups, which its color is read by. */
|
|
26
|
+
groupIndex: number;
|
|
27
|
+
/** Its height, from the second numeric column, where the table has one. */
|
|
28
|
+
height?: number | undefined;
|
|
29
|
+
/** The row's own label. */
|
|
30
|
+
label: string;
|
|
31
|
+
/** The row it was read from, counting from zero. */
|
|
32
|
+
row: number;
|
|
33
|
+
/** Its size, from the first numeric column, which its share of the floor is. */
|
|
34
|
+
size: number;
|
|
35
|
+
}
|
|
36
|
+
/** The table as a 3D treemap reads it. */
|
|
37
|
+
export interface TreemapField {
|
|
38
|
+
/**
|
|
39
|
+
* Across and in depth, the floor, which the blocks share out and which
|
|
40
|
+
* carries no measure; up, the height, from zero.
|
|
41
|
+
*/
|
|
42
|
+
axes: {
|
|
43
|
+
extent: BoxExtent;
|
|
44
|
+
x: PlotAxis;
|
|
45
|
+
y: PlotAxis;
|
|
46
|
+
z: PlotAxis;
|
|
47
|
+
};
|
|
48
|
+
/** Every block, in the table's order, which the keys walk. */
|
|
49
|
+
blocks: TreemapBlock[];
|
|
50
|
+
/** Every group, in the order its first row comes. */
|
|
51
|
+
groups: string[];
|
|
52
|
+
/** The height column's heading, or `''` where there is none. */
|
|
53
|
+
height: string;
|
|
54
|
+
/** The size column's heading. */
|
|
55
|
+
size: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The floor shared out among `sizes`, by squarified layout: in proportion to
|
|
59
|
+
* each, and as near to square as a row of them allows, so a long, thin sliver
|
|
60
|
+
* is rare and two sizes compare by area. `sizes` run from the largest down,
|
|
61
|
+
* and so does what is returned, the largest at the low corner.
|
|
62
|
+
*/
|
|
63
|
+
export declare const squarified: (sizes: readonly number[], within: FloorArea) => FloorArea[];
|
|
64
|
+
/**
|
|
65
|
+
* Reads the table: **the first numeric column is each block's size, and the
|
|
66
|
+
* second, where there is one, its height**, and `groupBy` names a column
|
|
67
|
+
* whose text puts each block in a group. A row with no size, or a size of
|
|
68
|
+
* zero or less, has no share of the floor and is left out.
|
|
69
|
+
*
|
|
70
|
+
* The floor is shared among the groups by their totals, and each group's
|
|
71
|
+
* share among its blocks, so a group's whole area is its total. **The
|
|
72
|
+
* largest stand at the back left**, where they hide the least.
|
|
73
|
+
*/
|
|
74
|
+
export declare const treemapOf: (rows: readonly FieldRow[], columns: readonly number[], groupBy?: string) => TreemapField;
|
|
75
|
+
/** A block as the chart's own drawing paints it. */
|
|
76
|
+
export interface DrawnBlock {
|
|
77
|
+
/** Its eight corners on the page, which a drawing with no box is framed around. */
|
|
78
|
+
corners: PagePoint[];
|
|
79
|
+
drawn: Drawn;
|
|
80
|
+
/** Its footprint on the projection's floor: across from `x0` to `x1`, and back from `y0` to `y1`. */
|
|
81
|
+
floor: {
|
|
82
|
+
x0: number;
|
|
83
|
+
x1: number;
|
|
84
|
+
y0: number;
|
|
85
|
+
y1: number;
|
|
86
|
+
};
|
|
87
|
+
/** Its place in `field.blocks`. */
|
|
88
|
+
index: number;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Each block as a solid, seen through `projection`: on its part of the floor,
|
|
92
|
+
* from zero to its height, or as a slab where the table gives no heights. A
|
|
93
|
+
* height below zero hangs from zero.
|
|
94
|
+
*/
|
|
95
|
+
export declare const blocksDrawn: (field: TreemapField, projection: Projection) => DrawnBlock[];
|
|
96
|
+
/**
|
|
97
|
+
* The blocks in the order to paint them, back to front.
|
|
98
|
+
*
|
|
99
|
+
* **Two blocks whose footprints do not overlap are parted by a line across or
|
|
100
|
+
* in depth**, and the camera sees the far side of that line first, so the
|
|
101
|
+
* block on it goes first. Ordering by each block's middle alone fails on a
|
|
102
|
+
* treemap, where a long block beside a small one has its middle further back
|
|
103
|
+
* though its near end stands in front. Where two lines give opposite orders,
|
|
104
|
+
* no view sees one block through the other, and neither is held first.
|
|
105
|
+
*/
|
|
106
|
+
export declare const paintOrder: (projection: Projection, blocks: readonly DrawnBlock[]) => DrawnBlock[];
|