@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.
- 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
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { solid, ticks } from '@fusion.dev/helix-core';
|
|
2
|
+
import { niceRange } from '../spatial-scatter-chart/field.js';
|
|
3
|
+
import { inBox } from '../spatial-scatter-chart/view.js';
|
|
4
|
+
const HEIGHT = 0.6;
|
|
5
|
+
const named = (count, labels, nearest = false) => {
|
|
6
|
+
const places = Math.max(count, 1);
|
|
7
|
+
return {
|
|
8
|
+
labels,
|
|
9
|
+
name: '',
|
|
10
|
+
range: nearest ? [places - 0.5, -0.5] : [-0.5, places - 0.5],
|
|
11
|
+
rules: Array.from({ length: places + 1 }, (_, at) => at - 0.5),
|
|
12
|
+
ticks: labels.map((_, at) => at),
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export const barsOf = (rows, columns) => {
|
|
16
|
+
const series = columns.map((column) => rows.find((row) => row.cells[column])?.cells[column]?.name ?? '');
|
|
17
|
+
const cells = [];
|
|
18
|
+
const values = [];
|
|
19
|
+
rows.forEach((row, at) => {
|
|
20
|
+
columns.forEach((column, depth) => {
|
|
21
|
+
const value = row.cells[column]?.value;
|
|
22
|
+
const read = Number.isFinite(value) ? value : undefined;
|
|
23
|
+
if (read !== undefined) {
|
|
24
|
+
values.push(read);
|
|
25
|
+
}
|
|
26
|
+
cells.push({ across: at, column, depth, label: row.label, row: at, value: read });
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
const [low, high] = [Math.min(0, ...values), Math.max(0, ...values)];
|
|
30
|
+
const range = low === high ? [0, 1] : niceRange(low, high);
|
|
31
|
+
const longest = Math.max(rows.length, series.length, 1);
|
|
32
|
+
return {
|
|
33
|
+
axes: {
|
|
34
|
+
extent: [Math.max(rows.length, 1) / longest, HEIGHT, Math.max(series.length, 1) / longest],
|
|
35
|
+
x: named(rows.length, rows.map((row) => row.label)),
|
|
36
|
+
y: { name: series.length === 1 ? (series[0] ?? '') : '', range, ticks: ticks(range) },
|
|
37
|
+
z: named(series.length, series.length === 1 ? [] : series, true),
|
|
38
|
+
},
|
|
39
|
+
categories: rows.map((row) => row.label),
|
|
40
|
+
cells,
|
|
41
|
+
readings: values.length,
|
|
42
|
+
series,
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
const FOOTPRINT = 0.6;
|
|
46
|
+
export const barsDrawn = (field, projection) => {
|
|
47
|
+
const [ex, ey, ez] = field.axes.extent;
|
|
48
|
+
const spacing = Math.min((2 * ex) / Math.max(field.categories.length, 1), (2 * ez) / Math.max(field.series.length, 1));
|
|
49
|
+
const zero = inBox(field.axes.y, 0) * ey;
|
|
50
|
+
return field.cells.flatMap((cell, index) => {
|
|
51
|
+
if (cell.value === undefined) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
const x = inBox(field.axes.x, cell.across) * ex;
|
|
55
|
+
const y = -inBox(field.axes.z, cell.depth) * ez;
|
|
56
|
+
const top = inBox(field.axes.y, cell.value) * ey;
|
|
57
|
+
const base = Math.min(zero, top);
|
|
58
|
+
const height = Math.abs(top - zero);
|
|
59
|
+
const lifted = {
|
|
60
|
+
...projection,
|
|
61
|
+
at: (px, py, pz = 0) => projection.at(px, py, pz + base),
|
|
62
|
+
};
|
|
63
|
+
const width = spacing * FOOTPRINT;
|
|
64
|
+
const corners = [];
|
|
65
|
+
for (const cx of [x - width / 2, x + width / 2]) {
|
|
66
|
+
for (const cy of [y - width / 2, y + width / 2]) {
|
|
67
|
+
corners.push(lifted.at(cx, cy, 0), lifted.at(cx, cy, height));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return [{ corners, drawn: solid(lifted, 'block', x, y, width, height), height, index, x, y }];
|
|
71
|
+
});
|
|
72
|
+
};
|
|
@@ -0,0 +1,245 @@
|
|
|
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 BarField } from '../bars.js';
|
|
5
|
+
declare const HelixHeadlessSpatialBarChart_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 bar chart drawn in three dimensions, each bar a block as tall as its value, for categories in one or more series.
|
|
8
|
+
*
|
|
9
|
+
* @description
|
|
10
|
+
* A 3D bar chart reads its table as `Bar Chart` does: **each row is a
|
|
11
|
+
* category, named by its label, and each numeric column a series**, named by
|
|
12
|
+
* its heading. The bars stand on a grid of categories across and series in
|
|
13
|
+
* depth, each as tall as its value from zero and colored by its series, so
|
|
14
|
+
* one series is a single row of bars and several make a block of them.
|
|
15
|
+
*
|
|
16
|
+
* **The flat `Bar Chart` is read more exactly.** Measured, a reader judges
|
|
17
|
+
* a 3D bar's value less closely than a flat one's, and a bar can stand
|
|
18
|
+
* behind another. Use this one where the shape across two sets of names
|
|
19
|
+
* matters more than any one exact value, such as sales by region and by
|
|
20
|
+
* quarter. The tooltip and the table carry the exact values, and a drag
|
|
21
|
+
* turns the view to show a bar another hides.
|
|
22
|
+
*
|
|
23
|
+
* **Helix draws the frame, and the chart draws the bars in 3D itself, in
|
|
24
|
+
* SVG**: a picture a print and a page without WebGL show too, which a drag
|
|
25
|
+
* or Shift with an arrow turns. Put a box inside the chart, and
|
|
26
|
+
* `connectSpatialBarChart` from `@fusion.dev/helix-plot`, given
|
|
27
|
+
* `threeBarDriver` from `@fusion.dev/helix-plot/three`, draws the bars in it
|
|
28
|
+
* with three.js, lit and in perspective.
|
|
29
|
+
*
|
|
30
|
+
* **The table is the reading.** Every number is in it, and screen readers
|
|
31
|
+
* read it. The `summary` part names the number of bars, series, and
|
|
32
|
+
* categories, and the range of values, and describes the plot.
|
|
33
|
+
*
|
|
34
|
+
* **The plot is read by key, across and in depth.** It takes focus, named by
|
|
35
|
+
* `label` or the caption. The left and right arrows move from category to
|
|
36
|
+
* category, the down and up arrows from series to series, and Home and End
|
|
37
|
+
* go to the first and the last bar. The `live` part says the bar's category,
|
|
38
|
+
* series, and value in words. Shift with an arrow turns the view, and
|
|
39
|
+
* Escape lets the bar go.
|
|
40
|
+
*
|
|
41
|
+
* **A reading shows its row in a tooltip**, as on every chart: the bar a
|
|
42
|
+
* mouse or a pen rests on, the bar tapped, or the one the keys move to.
|
|
43
|
+
*
|
|
44
|
+
* **Readings are sent as every chart sends them.** `helix-reading-change`
|
|
45
|
+
* says each move of the reading, and `helix-reading-press` a bar clicked or
|
|
46
|
+
* pressed with Enter or Space, each with the row of the table in `index`.
|
|
47
|
+
*
|
|
48
|
+
* **Keyboard.** The plot is one tab stop once it has readings.
|
|
49
|
+
*
|
|
50
|
+
* | Key | What it does |
|
|
51
|
+
* |---|---|
|
|
52
|
+
* | Tab | Moves focus to the plot. |
|
|
53
|
+
* | Right, Left | Moves to the next or the previous category. |
|
|
54
|
+
* | Down, Up | Moves to the next or the previous series. |
|
|
55
|
+
* | Home, End | Moves to the first or the last bar. |
|
|
56
|
+
* | Shift with an arrow | Turns the view across, or tilts it up and down. |
|
|
57
|
+
* | Enter, Space | Presses the bar, which sends `helix-reading-press`. |
|
|
58
|
+
* | Escape | Lets the bar go. |
|
|
59
|
+
*
|
|
60
|
+
* @since 0.199.0
|
|
61
|
+
* @status experimental
|
|
62
|
+
* @category charts
|
|
63
|
+
* @aliases 3D bar chart, 3D column chart, Isometric bar chart, Isometric column chart, Bar chart in 3D
|
|
64
|
+
* @documentation https://fusion.dev/helix/components/spatial-bar-chart
|
|
65
|
+
*
|
|
66
|
+
* @slot - The renderer's box, which the 3D drawing is made in.
|
|
67
|
+
* @slot empty - Content shown when the table has no readings. Falls back to `empty-text`.
|
|
68
|
+
* @slot loading - Content shown while `loading` is set and there are no readings yet. Falls back to `loading-text`.
|
|
69
|
+
*
|
|
70
|
+
* @event {HelixPlotChangeEvent} helix-plot-change - The bars or the bar the keys rest on changed, which the renderer is told. It stays on the chart.
|
|
71
|
+
* @event {HelixPlotTurnEvent} helix-plot-turn - Shift with an arrow asked the view to turn, by a step across or up.
|
|
72
|
+
* @event {HelixReadingChangeEvent} helix-reading-change - The reading moved, or was put away: `index` is the row, or `-1` for none, with its `label` and the `pointerType` that moved it.
|
|
73
|
+
* @event {HelixReadingPressEvent} helix-reading-press - A bar was clicked or pressed with Enter or Space, with the same detail as `helix-reading-change`.
|
|
74
|
+
*
|
|
75
|
+
* @csspart axis-name - A measure's name, along its edge of the box, in the chart's own drawing.
|
|
76
|
+
* @csspart bar - One bar, its faces in its series' color.
|
|
77
|
+
* @csspart bar-active - The bar the pointer or the keys rest on. Set alongside `bar`.
|
|
78
|
+
* @csspart drawing - The chart's own drawing in SVG: the box, its grid and names, and the bars.
|
|
79
|
+
* @csspart empty - The words when the table has no readings: the `empty` slot, falling back to `empty-text`.
|
|
80
|
+
* @csspart face - One face of a bar the view shows, shaded by which way it faces.
|
|
81
|
+
* @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.
|
|
82
|
+
* @csspart grid - The box's edges and its grid, in the chart's own drawing.
|
|
83
|
+
* @csspart live - The readout: a polite live region that says the bar the keys moved to. Hide it visually.
|
|
84
|
+
* @csspart loading - The words while `loading` is set and there are no readings.
|
|
85
|
+
* @csspart mark - The invisible point the tooltip is anchored to, where the renderer draws the reading.
|
|
86
|
+
* @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.
|
|
87
|
+
* @csspart ring - The outline around the bar the pointer or the keys rest on, painted after it, so the bars in front of it cover it.
|
|
88
|
+
* @csspart summary - The number of bars, series, and categories, and the range of values, written as a sentence for screen readers, which describes the plot. Hide it visually.
|
|
89
|
+
* @csspart table - The readings as a table, for a screen reader, rendered only where they were drawn from `data`. Hide it visually.
|
|
90
|
+
* @csspart tick - A category's or a series' name, or a value at a tick, along its edge of the box, in the chart's own drawing.
|
|
91
|
+
* @csspart tooltip - The bubble, forwarded from `Tooltip`.
|
|
92
|
+
* @csspart tooltip-anchor - The `Tooltip` element itself.
|
|
93
|
+
* @csspart tooltip-arrow - The bubble's point, forwarded from `Tooltip`.
|
|
94
|
+
* @csspart tooltip-arrow-bottom - The point while the bubble is below the reading, on its top edge. Set alongside `tooltip-arrow`.
|
|
95
|
+
* @csspart tooltip-arrow-left - The point while the bubble is left of the reading, on its right edge. Set alongside `tooltip-arrow`.
|
|
96
|
+
* @csspart tooltip-arrow-right - The point while the bubble is right of the reading, on its left edge. Set alongside `tooltip-arrow`.
|
|
97
|
+
* @csspart tooltip-arrow-top - The point while the bubble is above the reading, on its bottom edge. Set alongside `tooltip-arrow`.
|
|
98
|
+
* @csspart tooltip-bottom - The bubble while it is below the reading. Set alongside `tooltip`.
|
|
99
|
+
* @csspart tooltip-cell - One column's name and value inside the tooltip.
|
|
100
|
+
* @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`.
|
|
101
|
+
* @csspart tooltip-label - The row's own name inside the tooltip.
|
|
102
|
+
* @csspart tooltip-left - The bubble while it is left of the reading. Set alongside `tooltip`.
|
|
103
|
+
* @csspart tooltip-name - A column's name inside the tooltip.
|
|
104
|
+
* @csspart tooltip-right - The bubble while it is right of the reading. Set alongside `tooltip`.
|
|
105
|
+
* @csspart tooltip-top - The bubble while it is above the reading. Set alongside `tooltip`.
|
|
106
|
+
* @csspart tooltip-value - A column's value inside the tooltip.
|
|
107
|
+
*
|
|
108
|
+
* @cssproperty --spatial-bar-chart-shade - How far a bar's face turns from the light, from 0 for its top. The component writes it on each face, and the stylesheet that shades the face reads it.
|
|
109
|
+
*
|
|
110
|
+
* @cssstate active - While a bar is shown, under the pointer or the keys.
|
|
111
|
+
* @cssstate drawn - While a renderer has drawn the bars, and the chart's own drawing is hidden.
|
|
112
|
+
* @cssstate empty - While the `empty` part stands in place of the readings.
|
|
113
|
+
* @cssstate loading - While the `loading` part stands in place of the readings.
|
|
114
|
+
*/
|
|
115
|
+
export declare class HelixHeadlessSpatialBarChart extends HelixHeadlessSpatialBarChart_base {
|
|
116
|
+
/**
|
|
117
|
+
* The key this family's words are registered under, its styled tag:
|
|
118
|
+
* `registerWords('de', { 'helix-spatial-bar-chart': { emptyText: 'Keine Werte' } })`.
|
|
119
|
+
*/
|
|
120
|
+
static words: string;
|
|
121
|
+
/**
|
|
122
|
+
* Draws no box: no edges, grid, numbers, or names, so the readings stand
|
|
123
|
+
* alone and fill the plot, as a picture on a product's own page does. The
|
|
124
|
+
* table, the summary, the tooltip, and the keys still carry every value,
|
|
125
|
+
* and a renderer draws no box either.
|
|
126
|
+
*/
|
|
127
|
+
noBox: boolean;
|
|
128
|
+
/**
|
|
129
|
+
* What the readout says for a place with no value. `{category}` and
|
|
130
|
+
* `{series}` name the place.
|
|
131
|
+
*/
|
|
132
|
+
openLabel: string;
|
|
133
|
+
/** The plot's name where the chart has none: no `aria-label`, `label`, or table caption. */
|
|
134
|
+
plotLabel: string;
|
|
135
|
+
/**
|
|
136
|
+
* The `summary` part's sentence. `{count}`, `{series}`, and `{categories}`
|
|
137
|
+
* are the number of bars, series, and categories, written in the locale,
|
|
138
|
+
* and `{low}` and `{high}` the lowest and the highest value.
|
|
139
|
+
*/
|
|
140
|
+
summaryLabel: string;
|
|
141
|
+
/** Whether a renderer has drawn the bars, which hides the chart's own drawing. */
|
|
142
|
+
private rendered;
|
|
143
|
+
/** Where the chart's own drawing is seen from, which a drag or Shift with an arrow turns. */
|
|
144
|
+
private camera;
|
|
145
|
+
/** A drag across the chart's own drawing, which turns its view. */
|
|
146
|
+
private readonly drag;
|
|
147
|
+
/** Where the renderer draws the reading, which the tooltip points at. */
|
|
148
|
+
private place;
|
|
149
|
+
/** The field last told, kept so a change of it can be told. */
|
|
150
|
+
private last;
|
|
151
|
+
/** The bar the keys rested on last, so a change of it can be told. */
|
|
152
|
+
private told;
|
|
153
|
+
/** Carries a host `aria-labelledby` and `aria-describedby` to the plot. */
|
|
154
|
+
protected readonly aria: AriaForwardController;
|
|
155
|
+
/**
|
|
156
|
+
* The table read as bars. **Each row is a category, and each numeric
|
|
157
|
+
* column a series.** A renderer reads it.
|
|
158
|
+
*/
|
|
159
|
+
get field(): BarField;
|
|
160
|
+
/** The field last read, and the rows and columns it was read from. */
|
|
161
|
+
private memo;
|
|
162
|
+
/** The bar the keys or the pointer rest on, as its place in `field.cells`, or `-1`. */
|
|
163
|
+
get active(): number;
|
|
164
|
+
/** The number of places on the grid, which the keys walk. */
|
|
165
|
+
get readings(): number;
|
|
166
|
+
/**
|
|
167
|
+
* Says whether a renderer has drawn the bars, which hides the chart's own
|
|
168
|
+
* drawing or shows it again. Called by the connector.
|
|
169
|
+
*/
|
|
170
|
+
rendererDrawn(on?: boolean): void;
|
|
171
|
+
/**
|
|
172
|
+
* Puts the reading on the bar at `index`, or lets it go with `-1`, as a
|
|
173
|
+
* pointer resting on it in the renderer's drawing or a press there does.
|
|
174
|
+
* A touch keeps it until a tap lands elsewhere, and a mouse leaving a
|
|
175
|
+
* reading the keys moved to leaves it. Called by the connector.
|
|
176
|
+
*/
|
|
177
|
+
requestActive(index: number, kind?: 'mouse' | 'pen' | 'touch'): void;
|
|
178
|
+
/**
|
|
179
|
+
* Says where the renderer draws the reading, as fractions of the plot from
|
|
180
|
+
* its top left, or `undefined` while it draws none. The tooltip points
|
|
181
|
+
* there. Called by the connector.
|
|
182
|
+
*/
|
|
183
|
+
placeReading(at: {
|
|
184
|
+
x: number;
|
|
185
|
+
y: number;
|
|
186
|
+
} | undefined): void;
|
|
187
|
+
/**
|
|
188
|
+
* Tells the keys how many series stand in depth for each category, so the
|
|
189
|
+
* up and down arrows move from series to series and the left and right
|
|
190
|
+
* arrows from category to category. Here rather than in `updated`, since
|
|
191
|
+
* the server calls this one.
|
|
192
|
+
*/
|
|
193
|
+
protected willUpdate(changed: PropertyValues<this>): void;
|
|
194
|
+
connectedCallback(): void;
|
|
195
|
+
protected updated(changed: PropertyValues): void;
|
|
196
|
+
/**
|
|
197
|
+
* Shift with an arrow turns the view, the chart's own drawing and the
|
|
198
|
+
* renderer's alike, and moves no reading.
|
|
199
|
+
*/
|
|
200
|
+
private readonly onPlotKeydown;
|
|
201
|
+
/** The bar a pointer event is over, or `-1`: the one in front, since the bars are painted back to front. */
|
|
202
|
+
private barAt;
|
|
203
|
+
/** A mouse or a pen over the chart's own drawing puts the reading on the bar it rests on. */
|
|
204
|
+
private readonly onDrawingMove;
|
|
205
|
+
private readonly onDrawingDown;
|
|
206
|
+
/** A press the browser took back, such as for a scroll, which ends a drag as a release does. */
|
|
207
|
+
private readonly onDrawingCancel;
|
|
208
|
+
private readonly onDrawingLeave;
|
|
209
|
+
/**
|
|
210
|
+
* A tap is a touch reader's hover, unless it was a drag, which turned the
|
|
211
|
+
* view: it shows the bar under it until a tap lands elsewhere.
|
|
212
|
+
*/
|
|
213
|
+
private readonly onDrawingTap;
|
|
214
|
+
protected render(): TemplateResult;
|
|
215
|
+
/**
|
|
216
|
+
* The bars in 3D, in SVG: the box with its grid and names, and each bar a
|
|
217
|
+
* block standing from zero to its value, painted back to front, in its
|
|
218
|
+
* series' color and shaded by which way each face turns. The chart's own
|
|
219
|
+
* drawing, until a renderer has drawn and where none is connected.
|
|
220
|
+
*/
|
|
221
|
+
private renderFallback;
|
|
222
|
+
/** One bar, its faces in its series' color, each shaded by which way it faces. */
|
|
223
|
+
private renderBar;
|
|
224
|
+
/** The readout: the bar's category, series, and value in words, once the keys have moved to one. */
|
|
225
|
+
private renderLive;
|
|
226
|
+
/**
|
|
227
|
+
* The reading's row in the tooltip, pointed at its bar in the chart's own
|
|
228
|
+
* drawing, which holds it there.
|
|
229
|
+
*/
|
|
230
|
+
private renderOwnTooltip;
|
|
231
|
+
/**
|
|
232
|
+
* The reading's row in the tooltip every chart shows, pointed at where the
|
|
233
|
+
* renderer draws it. Only once a renderer has drawn; until then the
|
|
234
|
+
* chart's own drawing holds the tooltip at the bar.
|
|
235
|
+
*/
|
|
236
|
+
private renderTooltip;
|
|
237
|
+
/** What the plot shows, in a sentence, which describes the plot. */
|
|
238
|
+
private renderSummary;
|
|
239
|
+
}
|
|
240
|
+
declare global {
|
|
241
|
+
interface HTMLElementTagNameMap {
|
|
242
|
+
'helix-headless-spatial-bar-chart': HelixHeadlessSpatialBarChart;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
export {};
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { AriaForwardController, CHART_PLOT, customElement, depthOrder, 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 { boundsOf, boxView, DragTurn, drawBox, SPATIAL_VIEW, turned, } from '../../spatial-scatter-chart/view.js';
|
|
7
|
+
import { barsDrawn, barsOf } from '../bars.js';
|
|
8
|
+
const TURNS = {
|
|
9
|
+
ArrowDown: { across: 0, up: -1 },
|
|
10
|
+
ArrowLeft: { across: -1, up: 0 },
|
|
11
|
+
ArrowRight: { across: 1, up: 0 },
|
|
12
|
+
ArrowUp: { across: 0, up: 1 },
|
|
13
|
+
};
|
|
14
|
+
const SERIES = 24;
|
|
15
|
+
let HelixHeadlessSpatialBarChart = class HelixHeadlessSpatialBarChart extends MixinChartStatus(MixinChartSource(HelixElement)) {
|
|
16
|
+
constructor() {
|
|
17
|
+
super(...arguments);
|
|
18
|
+
this.noBox = false;
|
|
19
|
+
this.openLabel = '{category}, {series}: no value';
|
|
20
|
+
this.plotLabel = '3D bar chart';
|
|
21
|
+
this.summaryLabel = '{count, plural, one {# bar} other {# bars}} in {series, plural, one {# series} other {# series}} over {categories, plural, one {# category} other {# categories}}, from {low} to {high}.';
|
|
22
|
+
this.rendered = false;
|
|
23
|
+
this.camera = SPATIAL_VIEW;
|
|
24
|
+
this.drag = new DragTurn((next) => {
|
|
25
|
+
this.camera = next(this.camera);
|
|
26
|
+
}, () => this.requestUpdate());
|
|
27
|
+
this.told = -1;
|
|
28
|
+
this.aria = new AriaForwardController(this, {
|
|
29
|
+
target: () => this.partNamed('plot'),
|
|
30
|
+
});
|
|
31
|
+
this.onPlotKeydown = (event) => {
|
|
32
|
+
const turn = TURNS[event.key];
|
|
33
|
+
if (!event.shiftKey || !turn || event.altKey || event.ctrlKey || event.metaKey) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
event.preventDefault();
|
|
37
|
+
event.stopPropagation();
|
|
38
|
+
this.camera = turned(this.camera, turn.across, turn.up);
|
|
39
|
+
this.dispatchEvent(new HelixPlotTurnEvent(turn));
|
|
40
|
+
};
|
|
41
|
+
this.onDrawingMove = (event) => {
|
|
42
|
+
if (this.drag.move(event)) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (event.pointerType === 'touch') {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
this.requestActive(this.barAt(event), event.pointerType === 'pen' ? 'pen' : 'mouse');
|
|
49
|
+
};
|
|
50
|
+
this.onDrawingDown = (event) => {
|
|
51
|
+
this.drag.down(event);
|
|
52
|
+
};
|
|
53
|
+
this.onDrawingCancel = (event) => {
|
|
54
|
+
this.drag.up(event);
|
|
55
|
+
};
|
|
56
|
+
this.onDrawingLeave = (event) => {
|
|
57
|
+
if (event.pointerType !== 'touch') {
|
|
58
|
+
this.requestActive(-1, 'mouse');
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
this.onDrawingTap = (event) => {
|
|
62
|
+
if (this.drag.up(event)) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (event.pointerType === 'touch') {
|
|
66
|
+
this.requestActive(this.barAt(event), 'touch');
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
static { this.words = 'helix-spatial-bar-chart'; }
|
|
71
|
+
get field() {
|
|
72
|
+
const rows = this.sourceRows;
|
|
73
|
+
const columns = this.plottedColumns;
|
|
74
|
+
if (this.memo?.rows !== rows || this.memo.columns !== columns) {
|
|
75
|
+
this.memo = { columns, field: barsOf(rows, columns), rows };
|
|
76
|
+
}
|
|
77
|
+
return this.memo.field;
|
|
78
|
+
}
|
|
79
|
+
get active() {
|
|
80
|
+
return this.pointer.at;
|
|
81
|
+
}
|
|
82
|
+
get readings() {
|
|
83
|
+
return this.field.cells.length;
|
|
84
|
+
}
|
|
85
|
+
rendererDrawn(on = true) {
|
|
86
|
+
this.rendered = on;
|
|
87
|
+
this.toggleState('drawn', on);
|
|
88
|
+
}
|
|
89
|
+
requestActive(index, kind = 'mouse') {
|
|
90
|
+
if (kind === 'touch') {
|
|
91
|
+
this.pointer.tapTo(index);
|
|
92
|
+
}
|
|
93
|
+
else if (index >= 0 || !this.pointer.keyed) {
|
|
94
|
+
this.pointer.set(index);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
placeReading(at) {
|
|
98
|
+
this.place = at;
|
|
99
|
+
}
|
|
100
|
+
willUpdate(changed) {
|
|
101
|
+
super.willUpdate(changed);
|
|
102
|
+
this.pointer.grid = Math.max(1, this.field.series.length);
|
|
103
|
+
}
|
|
104
|
+
connectedCallback() {
|
|
105
|
+
super.connectedCallback();
|
|
106
|
+
this.pointer.axis = 'none';
|
|
107
|
+
this.pointer.onReading = (heard, detail) => {
|
|
108
|
+
const cell = this.field.cells[detail.index];
|
|
109
|
+
const told = {
|
|
110
|
+
...detail,
|
|
111
|
+
index: cell?.value === undefined ? -1 : cell.row,
|
|
112
|
+
label: cell?.label ?? '',
|
|
113
|
+
};
|
|
114
|
+
this.dispatchEvent(heard === 'press' ? new HelixReadingPressEvent(told) : new HelixReadingChangeEvent(told));
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
updated(changed) {
|
|
118
|
+
super.updated(changed);
|
|
119
|
+
const field = this.field;
|
|
120
|
+
if (field !== this.last) {
|
|
121
|
+
this.last = field;
|
|
122
|
+
this.dispatchEvent(new HelixPlotChangeEvent({ kind: 'data' }));
|
|
123
|
+
}
|
|
124
|
+
if (this.pointer.at !== this.told) {
|
|
125
|
+
this.told = this.pointer.at;
|
|
126
|
+
this.dispatchEvent(new HelixPlotChangeEvent({ kind: 'focus' }));
|
|
127
|
+
}
|
|
128
|
+
if (changed.has('place')) {
|
|
129
|
+
;
|
|
130
|
+
this.renderRoot.querySelector('[for="helix-chart-mark"]')?.reposition?.();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
barAt(event) {
|
|
134
|
+
const bar = event.composedPath()[0]?.closest?.('[data-index]');
|
|
135
|
+
const index = Number(bar?.getAttribute('data-index') ?? -1);
|
|
136
|
+
return Number.isInteger(index) ? index : -1;
|
|
137
|
+
}
|
|
138
|
+
render() {
|
|
139
|
+
const field = this.field;
|
|
140
|
+
const readable = field.readings > 0;
|
|
141
|
+
const at = this.pointer.at;
|
|
142
|
+
const cell = field.cells[at];
|
|
143
|
+
return html `<div
|
|
144
|
+
aria-describedby=${readable ? 'helix-chart-summary' : nothing}
|
|
145
|
+
aria-label=${this.named || this.plotLabel}
|
|
146
|
+
id=${CHART_PLOT}
|
|
147
|
+
part="plot"
|
|
148
|
+
role=${readable ? 'application' : 'img'}
|
|
149
|
+
tabindex=${readable ? '0' : nothing}
|
|
150
|
+
@keydown=${this.onPlotKeydown}
|
|
151
|
+
>
|
|
152
|
+
<slot></slot>
|
|
153
|
+
${readable ? this.renderFallback(field, at) : nothing}
|
|
154
|
+
${readable ? this.renderLive(field, at) : nothing}
|
|
155
|
+
${this.renderTooltip(cell?.value === undefined ? undefined : cell.row)}
|
|
156
|
+
</div>
|
|
157
|
+
${readable ? this.renderSummary(field) : this.renderStatus()}${this.renderTable()}`;
|
|
158
|
+
}
|
|
159
|
+
renderFallback(field, at) {
|
|
160
|
+
const view = boxView(field.axes, this.camera, [], this.noBox);
|
|
161
|
+
const bars = barsDrawn(field, view.projection);
|
|
162
|
+
const active = bars.find((bar) => bar.index === at);
|
|
163
|
+
const cell = field.cells[at];
|
|
164
|
+
const bounds = this.drag.frame(this.noBox
|
|
165
|
+
? boundsOf(bars.flatMap((bar) => bar.corners), 0.1)
|
|
166
|
+
: view.bounds, view.on([0, 0, 0]));
|
|
167
|
+
return html `<div aria-hidden="true" dir="ltr" ?hidden=${this.rendered} part="fallback">
|
|
168
|
+
<svg
|
|
169
|
+
part="drawing"
|
|
170
|
+
preserveAspectRatio="xMidYMid meet"
|
|
171
|
+
viewBox="${bounds.x} ${bounds.y} ${bounds.width} ${bounds.height}"
|
|
172
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
173
|
+
@click=${this.drag.click}
|
|
174
|
+
@pointercancel=${this.onDrawingCancel}
|
|
175
|
+
@pointerdown=${this.onDrawingDown}
|
|
176
|
+
@pointerleave=${this.onDrawingLeave}
|
|
177
|
+
@pointermove=${this.onDrawingMove}
|
|
178
|
+
@pointerup=${this.onDrawingTap}
|
|
179
|
+
>${this.noBox ? nothing : drawBox(view)}${depthOrder(view.projection, bars).map((bar) => svg `${this.renderBar(field, bar, at)}${bar === active ? svg `<path d=${active.drawn.outline} part="ring"></path>` : nothing}`)}${active
|
|
180
|
+
? svg `<foreignObject height="0.001" overflow="visible" width="0.001" x=${active.drawn.peak.x} y=${active.drawn.peak.y}>${this.renderOwnTooltip(cell?.row)}</foreignObject>`
|
|
181
|
+
: nothing}</svg>
|
|
182
|
+
</div>`;
|
|
183
|
+
}
|
|
184
|
+
renderBar(field, bar, at) {
|
|
185
|
+
const depth = field.cells[bar.index]?.depth ?? 0;
|
|
186
|
+
const short = (value) => Math.round(value * 1000) / 1000;
|
|
187
|
+
return svg `<g data-index=${bar.index} part="bar${bar.index === at ? ' bar-active' : ''}" style="--chart-series: var(--chart-series-${(depth % SERIES) + 1})">${bar.drawn.faces.map((face) => svg `<path d=${face.d} part="face" style="--spatial-bar-chart-shade: ${short(face.darken)}"></path>`)}</g>`;
|
|
188
|
+
}
|
|
189
|
+
renderLive(field, at) {
|
|
190
|
+
const cell = field.cells[at];
|
|
191
|
+
const row = cell === undefined ? undefined : this.sourceRows[cell.row];
|
|
192
|
+
let said = '';
|
|
193
|
+
if (this.pointer.keyed && cell) {
|
|
194
|
+
said =
|
|
195
|
+
row && cell.value !== undefined
|
|
196
|
+
? spokenReading(row, cell.column, (one) => this.pointer.shows(one), this.pointer.position)
|
|
197
|
+
: formatWords(this, 'openLabel', {
|
|
198
|
+
category: cell.label,
|
|
199
|
+
series: field.series[cell.depth] ?? '',
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
return html `<span aria-live="polite" part="live" role="status">${said}</span>`;
|
|
203
|
+
}
|
|
204
|
+
renderOwnTooltip(row) {
|
|
205
|
+
if (this.rendered || row === undefined || !this.sourceRows[row]) {
|
|
206
|
+
return nothing;
|
|
207
|
+
}
|
|
208
|
+
return drawTooltip(this.sourceRows, row, this.tag('tooltip'), { dir: isRtl(this) ? 'rtl' : 'ltr', fixed: true, placement: 'top', x: '0%', y: '0%' }, () => undefined);
|
|
209
|
+
}
|
|
210
|
+
renderTooltip(row) {
|
|
211
|
+
const place = this.place;
|
|
212
|
+
if (!this.rendered || !place || row === undefined || !this.sourceRows[row]) {
|
|
213
|
+
return nothing;
|
|
214
|
+
}
|
|
215
|
+
return drawTooltip(this.sourceRows, row, this.tag('tooltip'), {
|
|
216
|
+
dir: isRtl(this) ? 'rtl' : 'ltr',
|
|
217
|
+
fixed: true,
|
|
218
|
+
placement: 'top',
|
|
219
|
+
x: `${place.x * 100}%`,
|
|
220
|
+
y: `${place.y * 100}%`,
|
|
221
|
+
}, () => undefined);
|
|
222
|
+
}
|
|
223
|
+
renderSummary(field) {
|
|
224
|
+
const values = field.cells.flatMap((cell) => (cell.value === undefined ? [] : [cell.value]));
|
|
225
|
+
const said = formatWords(this, 'summaryLabel', {
|
|
226
|
+
categories: field.categories.length,
|
|
227
|
+
count: field.readings,
|
|
228
|
+
high: Math.max(...values),
|
|
229
|
+
low: Math.min(...values),
|
|
230
|
+
series: field.series.length,
|
|
231
|
+
});
|
|
232
|
+
return html `<p id="helix-chart-summary" part="summary">${said}</p>`;
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
__decorate([
|
|
236
|
+
property({ attribute: 'no-box', reflect: true, type: Boolean })
|
|
237
|
+
], HelixHeadlessSpatialBarChart.prototype, "noBox", void 0);
|
|
238
|
+
__decorate([
|
|
239
|
+
words,
|
|
240
|
+
property({ attribute: 'open-label' })
|
|
241
|
+
], HelixHeadlessSpatialBarChart.prototype, "openLabel", void 0);
|
|
242
|
+
__decorate([
|
|
243
|
+
words,
|
|
244
|
+
property({ attribute: 'plot-label' })
|
|
245
|
+
], HelixHeadlessSpatialBarChart.prototype, "plotLabel", void 0);
|
|
246
|
+
__decorate([
|
|
247
|
+
words,
|
|
248
|
+
property({ attribute: 'summary-label' })
|
|
249
|
+
], HelixHeadlessSpatialBarChart.prototype, "summaryLabel", void 0);
|
|
250
|
+
__decorate([
|
|
251
|
+
state()
|
|
252
|
+
], HelixHeadlessSpatialBarChart.prototype, "rendered", void 0);
|
|
253
|
+
__decorate([
|
|
254
|
+
state()
|
|
255
|
+
], HelixHeadlessSpatialBarChart.prototype, "camera", void 0);
|
|
256
|
+
__decorate([
|
|
257
|
+
state()
|
|
258
|
+
], HelixHeadlessSpatialBarChart.prototype, "place", void 0);
|
|
259
|
+
HelixHeadlessSpatialBarChart = __decorate([
|
|
260
|
+
customElement('helix-headless-spatial-bar-chart')
|
|
261
|
+
], HelixHeadlessSpatialBarChart);
|
|
262
|
+
export { HelixHeadlessSpatialBarChart };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A 3D ribbon chart's table read as ribbons, one for each series, and the
|
|
3
|
+
* faces each is drawn with.
|
|
4
|
+
*
|
|
5
|
+
* Kept apart from the element because it is arithmetic a picture hides: a
|
|
6
|
+
* ribbon whose faces are painted in the wrong order shows its back over its
|
|
7
|
+
* front, and a value read under the wrong column bends a ribbon where the
|
|
8
|
+
* data never did.
|
|
9
|
+
*/
|
|
10
|
+
import { type PagePoint, type Projection } from '@fusion.dev/helix-core';
|
|
11
|
+
import { type BarCell, type BarField } from '../spatial-bar-chart/bars.js';
|
|
12
|
+
import type { FieldRow } from '../spatial-scatter-chart/field.js';
|
|
13
|
+
/**
|
|
14
|
+
* The table as a 3D ribbon chart reads it: as `Bar Chart` and `Line Chart`
|
|
15
|
+
* read theirs, **each row a place across, named by its label, and each
|
|
16
|
+
* numeric column a series**, drawn as a ribbon in its own depth.
|
|
17
|
+
*/
|
|
18
|
+
export type RibbonField = BarField;
|
|
19
|
+
export type RibbonCell = BarCell;
|
|
20
|
+
/**
|
|
21
|
+
* Reads the table: each row a place across and each numeric column a
|
|
22
|
+
* series, every series in a ribbon of its own, the first in front. **The box
|
|
23
|
+
* is long across and shallow in depth**, a ribbon's place in depth a third
|
|
24
|
+
* of the length across at most, so a long series reads as a line and not as
|
|
25
|
+
* a wall.
|
|
26
|
+
*/
|
|
27
|
+
export declare const ribbonsOf: (rows: readonly FieldRow[], columns: readonly number[]) => RibbonField;
|
|
28
|
+
/** One face of a ribbon, as path data, and how far it turns from the light. */
|
|
29
|
+
export interface RibbonFace {
|
|
30
|
+
d: string;
|
|
31
|
+
/** From 0, the series' color, as the top takes it, to about 0.3, shaded. */
|
|
32
|
+
shade: number;
|
|
33
|
+
}
|
|
34
|
+
/** A ribbon as the chart's own drawing paints it: its faces in the order to paint them, and where each reading stands. */
|
|
35
|
+
export interface DrawnRibbon {
|
|
36
|
+
/** How near the camera the ribbon stands: the larger, the nearer. */
|
|
37
|
+
depth: number;
|
|
38
|
+
faces: RibbonFace[];
|
|
39
|
+
/** Every corner of the ribbon on the page, which a drawing with no box is framed around. */
|
|
40
|
+
outline: PagePoint[];
|
|
41
|
+
/** Each reading's place on the top of the ribbon, by its place in `field.cells`. */
|
|
42
|
+
readings: Array<PagePoint & {
|
|
43
|
+
index: number;
|
|
44
|
+
}>;
|
|
45
|
+
series: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Each series as a ribbon seen through `projection`, which the box's own
|
|
49
|
+
* places go through as the box view's `on` does. **Painted so its faces
|
|
50
|
+
* cover each other rightly from any side**: the side turned away is left
|
|
51
|
+
* out, the top's pieces go back to front, and the side facing the reader
|
|
52
|
+
* last. The ribbons themselves are painted back to front by the caller, in
|
|
53
|
+
* `depth`.
|
|
54
|
+
*/
|
|
55
|
+
export declare const ribbonsDrawn: (field: RibbonField, projection: Projection) => DrawnRibbon[];
|