@fusion.dev/helix-headless 0.198.0 → 0.199.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.
Files changed (53) hide show
  1. package/api.json +1 -1
  2. package/custom-elements.json +8267 -2270
  3. package/dist/carousel/root/carousel-root.d.ts +14 -0
  4. package/dist/carousel/root/carousel-root.js +15 -1
  5. package/dist/element-events.generated.js +19 -0
  6. package/dist/graph/root/graph-root.d.ts +33 -0
  7. package/dist/graph/root/graph-root.js +57 -0
  8. package/dist/index.d.ts +4 -0
  9. package/dist/index.js +4 -0
  10. package/dist/map/raster/map-raster.d.ts +7 -3
  11. package/dist/map/raster/map-raster.js +5 -3
  12. package/dist/motion-toggle/index.d.ts +1 -0
  13. package/dist/motion-toggle/index.js +1 -0
  14. package/dist/motion-toggle/root/motion-toggle-root.d.ts +77 -0
  15. package/dist/motion-toggle/root/motion-toggle-root.js +56 -0
  16. package/dist/spatial-bar-chart/bars.d.ts +79 -0
  17. package/dist/spatial-bar-chart/bars.js +72 -0
  18. package/dist/spatial-bar-chart/index.d.ts +2 -0
  19. package/dist/spatial-bar-chart/index.js +2 -0
  20. package/dist/spatial-bar-chart/root/spatial-bar-chart-root.d.ts +245 -0
  21. package/dist/spatial-bar-chart/root/spatial-bar-chart-root.js +262 -0
  22. package/dist/spatial-ribbon-chart/index.d.ts +2 -0
  23. package/dist/spatial-ribbon-chart/index.js +2 -0
  24. package/dist/spatial-ribbon-chart/ribbons.d.ts +55 -0
  25. package/dist/spatial-ribbon-chart/ribbons.js +99 -0
  26. package/dist/spatial-ribbon-chart/root/spatial-ribbon-chart-root.d.ts +243 -0
  27. package/dist/spatial-ribbon-chart/root/spatial-ribbon-chart-root.js +259 -0
  28. package/dist/spatial-scatter-chart/field.d.ts +8 -0
  29. package/dist/spatial-scatter-chart/root/spatial-scatter-chart-root.d.ts +14 -0
  30. package/dist/spatial-scatter-chart/root/spatial-scatter-chart-root.js +20 -6
  31. package/dist/spatial-scatter-chart/view.d.ts +14 -1
  32. package/dist/spatial-scatter-chart/view.js +29 -9
  33. package/dist/spatial-surface-chart/grid.d.ts +22 -0
  34. package/dist/spatial-surface-chart/grid.js +67 -1
  35. package/dist/spatial-surface-chart/index.d.ts +1 -1
  36. package/dist/spatial-surface-chart/index.js +1 -1
  37. package/dist/spatial-surface-chart/root/spatial-surface-chart-root.d.ts +20 -0
  38. package/dist/spatial-surface-chart/root/spatial-surface-chart-root.js +38 -5
  39. package/dist/spatial-treemap/blocks.d.ts +106 -0
  40. package/dist/spatial-treemap/blocks.js +215 -0
  41. package/dist/spatial-treemap/index.d.ts +2 -0
  42. package/dist/spatial-treemap/index.js +2 -0
  43. package/dist/spatial-treemap/root/spatial-treemap-root.d.ts +236 -0
  44. package/dist/spatial-treemap/root/spatial-treemap-root.js +249 -0
  45. package/dist/video/root/video-root.d.ts +14 -0
  46. package/dist/video/root/video-root.js +30 -2
  47. package/examples.json +1 -1
  48. package/lint.json +1 -1
  49. package/package.json +5 -5
  50. package/types/jsx.d.ts +3037 -99
  51. package/types/svelte.d.ts +4 -0
  52. package/types/vue.d.ts +519 -3
  53. 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 { depth: view.depth(box), index, point, ...view.on(box) };
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 on = ([x, y, z]) => projection.at(x, -z, y);
17
- const depth = ([x, , z]) => projection.depth(x, -z);
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) => ({ at: inBox(axis, tick), text: tickText(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 = 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 });
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
- return {
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>`)}${active
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[];