@perspective-dev/viewer-charts 5.1.0 → 5.3.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/dist/cdn/perspective-viewer-charts.js +2 -2
- package/dist/cdn/perspective-viewer-charts.js.map +4 -4
- package/dist/esm/charts/candlestick/candlestick-render.d.ts +3 -0
- package/dist/esm/charts/cartesian/cartesian.d.ts +1 -0
- package/dist/esm/charts/chart-base.d.ts +22 -9
- package/dist/esm/charts/chart.d.ts +4 -0
- package/dist/esm/charts/common/category-axis-resolver.d.ts +1 -1
- package/dist/esm/charts/common/tree-chart.d.ts +1 -0
- package/dist/esm/charts/heatmap/heatmap.d.ts +1 -0
- package/dist/esm/charts/series/series-type.d.ts +18 -0
- package/dist/esm/charts/series/series.d.ts +4 -1
- package/dist/esm/charts/sunburst/sunburst-render.d.ts +4 -0
- package/dist/esm/perspective-viewer-charts.js +2 -2
- package/dist/esm/perspective-viewer-charts.js.map +4 -4
- package/dist/esm/plugin/charts.d.ts +24 -0
- package/dist/esm/plugin/plugin.d.ts +8 -1
- package/dist/esm/theme/gradient.d.ts +31 -0
- package/dist/esm/theme/overrides.d.ts +19 -0
- package/dist/esm/transport/protocol.d.ts +13 -2
- package/dist/esm/utils/css.d.ts +4 -0
- package/dist/esm/worker/renderer.worker.d.ts +27 -0
- package/package.json +1 -1
- package/src/ts/charts/candlestick/candlestick-build.ts +1 -0
- package/src/ts/charts/candlestick/candlestick-render.ts +8 -0
- package/src/ts/charts/candlestick/candlestick.ts +4 -3
- package/src/ts/charts/cartesian/cartesian-render.ts +5 -0
- package/src/ts/charts/cartesian/cartesian.ts +4 -0
- package/src/ts/charts/chart-base.ts +42 -10
- package/src/ts/charts/chart.ts +5 -0
- package/src/ts/charts/common/category-axis-resolver.ts +44 -5
- package/src/ts/charts/common/tree-chart.ts +4 -0
- package/src/ts/charts/heatmap/heatmap-build.ts +7 -1
- package/src/ts/charts/heatmap/heatmap-interact.ts +10 -1
- package/src/ts/charts/heatmap/heatmap-render.ts +5 -0
- package/src/ts/charts/heatmap/heatmap.ts +8 -3
- package/src/ts/charts/series/series-build.ts +1 -0
- package/src/ts/charts/series/series-interact.ts +2 -13
- package/src/ts/charts/series/series-render.ts +5 -0
- package/src/ts/charts/series/series-type.ts +65 -0
- package/src/ts/charts/series/series.ts +21 -8
- package/src/ts/charts/sunburst/sunburst-render.ts +9 -0
- package/src/ts/charts/treemap/treemap-render.ts +5 -0
- package/src/ts/layout/ticks.ts +36 -12
- package/src/ts/plugin/charts.ts +69 -3
- package/src/ts/plugin/plugin.ts +99 -10
- package/src/ts/theme/gradient.ts +241 -1
- package/src/ts/theme/overrides.ts +76 -0
- package/src/ts/transport/protocol.ts +14 -2
- package/src/ts/transport/renderer-transport.ts +24 -10
- package/src/ts/utils/css.ts +12 -0
- package/src/ts/worker/renderer.worker.ts +113 -5
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
|
+
import { parseCssColorList, parseCssColorStrict } from "../../theme/gradient";
|
|
14
|
+
|
|
13
15
|
export type ChartType = "bar" | "line" | "scatter" | "area";
|
|
14
16
|
|
|
15
17
|
/**
|
|
@@ -45,6 +47,18 @@ export interface ColumnChartConfig {
|
|
|
45
47
|
* `"skip"`). Default `"skip"`. No effect on bar / scatter.
|
|
46
48
|
*/
|
|
47
49
|
interpolate?: InterpolateMode;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Single series-color override (`#rrggbb`) — the no-`split_by`
|
|
53
|
+
* shape.
|
|
54
|
+
*/
|
|
55
|
+
color?: string;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Discrete color palette cycled over this column's split-generated
|
|
59
|
+
* series — the `split_by` shape.
|
|
60
|
+
*/
|
|
61
|
+
palette?: string;
|
|
48
62
|
}
|
|
49
63
|
|
|
50
64
|
/**
|
|
@@ -98,6 +112,57 @@ export function resolveAltAxis(
|
|
|
98
112
|
return cfg?.[aggName]?.alt_axis === true;
|
|
99
113
|
}
|
|
100
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Resolve a per-series color override from `columns_config`, or `null`
|
|
117
|
+
* when the theme palette applies.
|
|
118
|
+
*/
|
|
119
|
+
export function resolveSeriesColorOverride(
|
|
120
|
+
series: { aggName: string; splitIdx: number },
|
|
121
|
+
facetActive: boolean,
|
|
122
|
+
cfg: Record<string, ColumnChartConfig> | undefined,
|
|
123
|
+
): [number, number, number] | null {
|
|
124
|
+
const entry = cfg?.[series.aggName];
|
|
125
|
+
if (!entry) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const palette =
|
|
130
|
+
typeof entry.palette === "string"
|
|
131
|
+
? parsePaletteCached(entry.palette)
|
|
132
|
+
: null;
|
|
133
|
+
if (palette && palette.length > 0) {
|
|
134
|
+
const idx = facetActive ? 0 : series.splitIdx % palette.length;
|
|
135
|
+
return palette[idx];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (typeof entry.color === "string") {
|
|
139
|
+
return parseCssColorStrict(entry.color) ?? null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const PALETTE_PARSE_CACHE = new Map<
|
|
146
|
+
string,
|
|
147
|
+
Array<[number, number, number]> | null
|
|
148
|
+
>();
|
|
149
|
+
|
|
150
|
+
function parsePaletteCached(
|
|
151
|
+
src: string,
|
|
152
|
+
): Array<[number, number, number]> | null {
|
|
153
|
+
let parsed = PALETTE_PARSE_CACHE.get(src);
|
|
154
|
+
if (parsed === undefined) {
|
|
155
|
+
parsed = parseCssColorList(src);
|
|
156
|
+
if (PALETTE_PARSE_CACHE.size > 256) {
|
|
157
|
+
PALETTE_PARSE_CACHE.clear();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
PALETTE_PARSE_CACHE.set(src, parsed);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return parsed;
|
|
164
|
+
}
|
|
165
|
+
|
|
101
166
|
/**
|
|
102
167
|
* Resolve the interpolation mode for this aggregate.
|
|
103
168
|
*/
|
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
showBarPinnedTooltipForSample,
|
|
42
42
|
} from "./series-interact";
|
|
43
43
|
import { resolvePalette } from "../../theme/palette";
|
|
44
|
+
import { resolveSeriesColorOverride } from "./series-type";
|
|
44
45
|
import { LineGlyph } from "./glyphs/draw-lines";
|
|
45
46
|
import { ScatterGlyph } from "./glyphs/draw-scatter";
|
|
46
47
|
import { AreaGlyph } from "./glyphs/draw-areas";
|
|
@@ -250,13 +251,16 @@ export class SeriesChart extends CategoricalYChart {
|
|
|
250
251
|
/**
|
|
251
252
|
* Cached palette + identity-keys for short-circuiting per-frame
|
|
252
253
|
* resolution. Inputs (`seriesPalette` ref, `gradientStops` ref,
|
|
253
|
-
* `series.length`
|
|
254
|
+
* `series.length`, `_columnsConfig` ref — per-series color
|
|
255
|
+
* overrides) only change on data load, `restyle()`, or
|
|
256
|
+
* `setColumnsConfig`.
|
|
254
257
|
*/
|
|
255
258
|
_paletteCache: [number, number, number][] | null = null;
|
|
256
259
|
_paletteCacheKey: {
|
|
257
260
|
seriesPalette: [number, number, number][] | null;
|
|
258
261
|
gradientStops: unknown;
|
|
259
262
|
seriesLength: number;
|
|
263
|
+
columnsConfig: unknown;
|
|
260
264
|
} | null = null;
|
|
261
265
|
|
|
262
266
|
/**
|
|
@@ -514,9 +518,10 @@ export class SeriesChart extends CategoricalYChart {
|
|
|
514
518
|
return;
|
|
515
519
|
}
|
|
516
520
|
|
|
517
|
-
const groupByValues: (string | null)[] =
|
|
518
|
-
|
|
519
|
-
|
|
521
|
+
const groupByValues: (string | number | null)[] =
|
|
522
|
+
this._categoryAxisMode === "numeric" && this._categoryPositions
|
|
523
|
+
? [this._categoryPositions[b.catIdx] ?? null]
|
|
524
|
+
: this._rowPaths.map((level) => level.labels[b.catIdx] ?? null);
|
|
520
525
|
const splitKey = this._splitPrefixes[b.splitIdx] ?? "";
|
|
521
526
|
const splitByValues =
|
|
522
527
|
this._splitBy.length > 0 && splitKey !== ""
|
|
@@ -903,6 +908,7 @@ export function ensurePalette(chart: SeriesChart): boolean {
|
|
|
903
908
|
const theme = chart._resolveTheme();
|
|
904
909
|
const seriesPalette = theme.seriesPalette;
|
|
905
910
|
const gradientStops = theme.gradientStops;
|
|
911
|
+
const columnsConfig = chart._columnsConfig;
|
|
906
912
|
const paletteCount = chart._facetActive
|
|
907
913
|
? Math.max(1, chart._aggregates.length)
|
|
908
914
|
: chart._series.length;
|
|
@@ -913,7 +919,8 @@ export function ensurePalette(chart: SeriesChart): boolean {
|
|
|
913
919
|
key &&
|
|
914
920
|
key.seriesPalette === seriesPalette &&
|
|
915
921
|
key.gradientStops === gradientStops &&
|
|
916
|
-
key.seriesLength === paletteCount
|
|
922
|
+
key.seriesLength === paletteCount &&
|
|
923
|
+
key.columnsConfig === columnsConfig
|
|
917
924
|
) {
|
|
918
925
|
return false;
|
|
919
926
|
}
|
|
@@ -928,12 +935,18 @@ export function ensurePalette(chart: SeriesChart): boolean {
|
|
|
928
935
|
seriesPalette,
|
|
929
936
|
gradientStops,
|
|
930
937
|
seriesLength: paletteCount,
|
|
938
|
+
columnsConfig,
|
|
931
939
|
};
|
|
932
940
|
|
|
933
941
|
for (let i = 0; i < chart._series.length; i++) {
|
|
934
|
-
chart._series[i]
|
|
935
|
-
|
|
936
|
-
|
|
942
|
+
const series = chart._series[i];
|
|
943
|
+
const themed = chart._facetActive ? palette[series.aggIdx] : palette[i];
|
|
944
|
+
series.color =
|
|
945
|
+
resolveSeriesColorOverride(
|
|
946
|
+
series,
|
|
947
|
+
chart._facetActive,
|
|
948
|
+
columnsConfig,
|
|
949
|
+
) ?? themed;
|
|
937
950
|
}
|
|
938
951
|
|
|
939
952
|
return true;
|
|
@@ -501,7 +501,16 @@ function drawArcs(
|
|
|
501
501
|
|
|
502
502
|
// Chrome overlay (Canvas2D)
|
|
503
503
|
|
|
504
|
+
/**
|
|
505
|
+
* Render the chrome overlay (labels, center text, hover highlight +
|
|
506
|
+
* tooltip).
|
|
507
|
+
*/
|
|
504
508
|
export function renderSunburstChromeOverlay(chart: SunburstChart): void {
|
|
509
|
+
paintSunburstChromeOverlay(chart);
|
|
510
|
+
chart.presentOverlay();
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function paintSunburstChromeOverlay(chart: SunburstChart): void {
|
|
505
514
|
if (!chart._chromeCanvas || chart._currentRootId === NULL_NODE) {
|
|
506
515
|
return;
|
|
507
516
|
}
|
|
@@ -467,6 +467,11 @@ function emitRect(
|
|
|
467
467
|
* tooltip + highlight on top.
|
|
468
468
|
*/
|
|
469
469
|
export function renderTreemapChromeOverlay(chart: TreemapChart): void {
|
|
470
|
+
paintTreemapChromeOverlay(chart);
|
|
471
|
+
chart.presentOverlay();
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function paintTreemapChromeOverlay(chart: TreemapChart): void {
|
|
470
475
|
if (!chart._chromeCanvas || chart._currentRootId === NULL_NODE) {
|
|
471
476
|
return;
|
|
472
477
|
}
|
package/src/ts/layout/ticks.ts
CHANGED
|
@@ -111,6 +111,30 @@ export function formatTickValue(val: number): string {
|
|
|
111
111
|
return val.toFixed(2);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Cached `Intl.DateTimeFormat` per option shape. `Date.prototype.
|
|
116
|
+
* toLocale*` constructs a fresh `DateTimeFormat` (plus its ICU
|
|
117
|
+
* backing) on EVERY call — ~30µs each — which turned per-row label
|
|
118
|
+
* synthesis over large pivots into a multi-second stall. A cached
|
|
119
|
+
* formatter's `format()` is ~1µs. Keyed by precision tier; the
|
|
120
|
+
* default locale is fixed for the lifetime of the worker, so entries
|
|
121
|
+
* never invalidate.
|
|
122
|
+
*/
|
|
123
|
+
const DATE_FORMAT_CACHE = new Map<string, Intl.DateTimeFormat>();
|
|
124
|
+
|
|
125
|
+
function cachedDateFormat(
|
|
126
|
+
key: string,
|
|
127
|
+
options: Intl.DateTimeFormatOptions,
|
|
128
|
+
): Intl.DateTimeFormat {
|
|
129
|
+
let fmt = DATE_FORMAT_CACHE.get(key);
|
|
130
|
+
if (!fmt) {
|
|
131
|
+
fmt = new Intl.DateTimeFormat(undefined, options);
|
|
132
|
+
DATE_FORMAT_CACHE.set(key, fmt);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return fmt;
|
|
136
|
+
}
|
|
137
|
+
|
|
114
138
|
/**
|
|
115
139
|
* Format a timestamp (ms since epoch) as a human-readable date/time label.
|
|
116
140
|
* Adapts precision based on the tick spacing.
|
|
@@ -129,49 +153,49 @@ export function formatDateTickValue(val: number, stepMs?: number): string {
|
|
|
129
153
|
|
|
130
154
|
if (stepMs >= DAY * 28) {
|
|
131
155
|
// Monthly or longer — show year-month
|
|
132
|
-
return
|
|
156
|
+
return cachedDateFormat("ym", {
|
|
133
157
|
year: "numeric",
|
|
134
158
|
month: "short",
|
|
135
|
-
});
|
|
159
|
+
}).format(d);
|
|
136
160
|
}
|
|
137
161
|
|
|
138
162
|
if (stepMs >= DAY) {
|
|
139
163
|
// Daily — show month and day
|
|
140
|
-
return
|
|
164
|
+
return cachedDateFormat("md", {
|
|
141
165
|
month: "short",
|
|
142
166
|
day: "numeric",
|
|
143
|
-
});
|
|
167
|
+
}).format(d);
|
|
144
168
|
}
|
|
145
169
|
|
|
146
170
|
if (stepMs >= HOUR) {
|
|
147
171
|
// Hourly
|
|
148
|
-
return
|
|
172
|
+
return cachedDateFormat("mdh", {
|
|
149
173
|
month: "short",
|
|
150
174
|
day: "numeric",
|
|
151
175
|
hour: "numeric",
|
|
152
|
-
});
|
|
176
|
+
}).format(d);
|
|
153
177
|
}
|
|
154
178
|
|
|
155
179
|
if (stepMs >= MINUTE) {
|
|
156
180
|
// Minutes
|
|
157
|
-
return
|
|
181
|
+
return cachedDateFormat("hm", {
|
|
158
182
|
hour: "numeric",
|
|
159
183
|
minute: "2-digit",
|
|
160
|
-
});
|
|
184
|
+
}).format(d);
|
|
161
185
|
}
|
|
162
186
|
|
|
163
187
|
// Sub-minute
|
|
164
|
-
return
|
|
188
|
+
return cachedDateFormat("hms", {
|
|
165
189
|
hour: "numeric",
|
|
166
190
|
minute: "2-digit",
|
|
167
191
|
second: "2-digit",
|
|
168
|
-
});
|
|
192
|
+
}).format(d);
|
|
169
193
|
}
|
|
170
194
|
|
|
171
195
|
// Default: show date only
|
|
172
|
-
return
|
|
196
|
+
return cachedDateFormat("ymd", {
|
|
173
197
|
year: "numeric",
|
|
174
198
|
month: "short",
|
|
175
199
|
day: "numeric",
|
|
176
|
-
});
|
|
200
|
+
}).format(d);
|
|
177
201
|
}
|
package/src/ts/plugin/charts.ts
CHANGED
|
@@ -44,6 +44,33 @@ export interface ChartTypeConfig {
|
|
|
44
44
|
*/
|
|
45
45
|
applicable_plugin_fields: readonly PluginConfigField[];
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* What `group_by` DRAWS in this chart, e.g. `"X Axis"` for the
|
|
49
|
+
* Y-series charts. Omitted where the field has no visual role of
|
|
50
|
+
* its own and is a plain aggregation key — the X/Y and map charts,
|
|
51
|
+
* whose axes both come from `columns`.
|
|
52
|
+
*
|
|
53
|
+
* The `group_by` counterpart of `initial.names`: one declaration
|
|
54
|
+
* says what every view field draws, so the settings UI's labels and
|
|
55
|
+
* the agent's `list_plugins` contract read the same source instead
|
|
56
|
+
* of restating the mapping.
|
|
57
|
+
*/
|
|
58
|
+
group_by_role?: string;
|
|
59
|
+
|
|
60
|
+
/** What `split_by` DRAWS in this chart. See {@link group_by_role}. */
|
|
61
|
+
split_by_role?: string;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Set on the charts that CONNECT their points in row order, so the
|
|
65
|
+
* view's row order shows up in the drawing: without a `sort` the
|
|
66
|
+
* line follows the table's natural order, which reads as a tangle
|
|
67
|
+
* unless the rows already arrive ordered along the X axis. The point
|
|
68
|
+
* charts (scatter, density) are unaffected, and a pre-ordered table
|
|
69
|
+
* needs no `sort` — which is why this is declared per chart rather
|
|
70
|
+
* than inferred from an empty `sort`.
|
|
71
|
+
*/
|
|
72
|
+
connects_row_order?: boolean;
|
|
73
|
+
|
|
47
74
|
/**
|
|
48
75
|
* Per-chart-type overrides for `DEFAULT_PLUGIN_CONFIG`. Used when a
|
|
49
76
|
* field's sensible default differs by chart family — currently
|
|
@@ -187,6 +214,9 @@ function make(
|
|
|
187
214
|
| "max_columns"
|
|
188
215
|
| "default_chart_type"
|
|
189
216
|
| "plugin_field_defaults"
|
|
217
|
+
| "group_by_role"
|
|
218
|
+
| "split_by_role"
|
|
219
|
+
| "connects_row_order"
|
|
190
220
|
>
|
|
191
221
|
>,
|
|
192
222
|
): ChartTypeConfig {
|
|
@@ -205,30 +235,52 @@ function make(
|
|
|
205
235
|
...(overrides?.plugin_field_defaults
|
|
206
236
|
? { plugin_field_defaults: overrides.plugin_field_defaults }
|
|
207
237
|
: {}),
|
|
238
|
+
...(overrides?.group_by_role
|
|
239
|
+
? { group_by_role: overrides.group_by_role }
|
|
240
|
+
: {}),
|
|
241
|
+
...(overrides?.split_by_role
|
|
242
|
+
? { split_by_role: overrides.split_by_role }
|
|
243
|
+
: {}),
|
|
244
|
+
...(overrides?.connects_row_order
|
|
245
|
+
? { connects_row_order: overrides.connects_row_order }
|
|
246
|
+
: {}),
|
|
208
247
|
};
|
|
209
248
|
}
|
|
210
249
|
|
|
211
250
|
const FIN_NAMES = ["Open", "Close", "High", "Low", "Tooltip"];
|
|
212
251
|
const HIER_NAMES = ["Size", "Color", "Tooltip"];
|
|
213
252
|
|
|
253
|
+
const SERIES_Y_ROLES = { group_by_role: "X Axis", split_by_role: "Series" };
|
|
254
|
+
const SERIES_X_ROLES = { group_by_role: "Y Axis", split_by_role: "Series" };
|
|
255
|
+
const CART_ROLES = {};
|
|
256
|
+
const HIER_ROLES = { group_by_role: "Hierarchy", split_by_role: "Facets" };
|
|
257
|
+
const HEATMAP_ROLES = { group_by_role: "X Axis", split_by_role: "Y Axis" };
|
|
258
|
+
const FIN_ROLES = { group_by_role: "X Axis", split_by_role: "Series" };
|
|
259
|
+
const MAP_ROLES = {};
|
|
260
|
+
|
|
214
261
|
const CHARTS: ChartTypeConfig[] = [
|
|
215
262
|
make("X Bar", "x-bar", SERIES, SELECT, 1, X_AXIS, SERIES_FIELDS, {
|
|
263
|
+
...SERIES_X_ROLES,
|
|
216
264
|
default_chart_type: "bar",
|
|
217
265
|
plugin_field_defaults: ZERO_ANCHORED_DEFAULTS,
|
|
218
266
|
}),
|
|
219
267
|
make("Y Bar", "y-bar", SERIES, SELECT, 1, Y_AXIS, SERIES_FIELDS, {
|
|
268
|
+
...SERIES_Y_ROLES,
|
|
220
269
|
default_chart_type: "bar",
|
|
221
270
|
plugin_field_defaults: ZERO_ANCHORED_DEFAULTS,
|
|
222
271
|
}),
|
|
223
272
|
make("Y Line", "y-line", SERIES, SELECT, 1, Y_AXIS, SERIES_FIELDS, {
|
|
273
|
+
...SERIES_Y_ROLES,
|
|
224
274
|
default_chart_type: "line",
|
|
225
275
|
plugin_field_defaults: SERIES_DEFAULTS,
|
|
226
276
|
}),
|
|
227
277
|
make("Y Scatter", "y-scatter", SERIES, SELECT, 1, Y_AXIS, SERIES_FIELDS, {
|
|
278
|
+
...SERIES_Y_ROLES,
|
|
228
279
|
default_chart_type: "scatter",
|
|
229
280
|
plugin_field_defaults: SERIES_DEFAULTS,
|
|
230
281
|
}),
|
|
231
282
|
make("Y Area", "y-area", SERIES, SELECT, 1, Y_AXIS, SERIES_FIELDS, {
|
|
283
|
+
...SERIES_Y_ROLES,
|
|
232
284
|
default_chart_type: "area",
|
|
233
285
|
plugin_field_defaults: ZERO_ANCHORED_DEFAULTS,
|
|
234
286
|
}),
|
|
@@ -240,6 +292,7 @@ const CHARTS: ChartTypeConfig[] = [
|
|
|
240
292
|
2,
|
|
241
293
|
["X Axis", "Y Axis", "Color", "Size", "Label", "Tooltip"],
|
|
242
294
|
CARTESIAN_FIELDS,
|
|
295
|
+
{ ...CART_ROLES },
|
|
243
296
|
),
|
|
244
297
|
make(
|
|
245
298
|
"X/Y Line",
|
|
@@ -249,6 +302,7 @@ const CHARTS: ChartTypeConfig[] = [
|
|
|
249
302
|
2,
|
|
250
303
|
["X Axis", "Y Axis", "Tooltip"],
|
|
251
304
|
CARTESIAN_FIELDS,
|
|
305
|
+
{ ...CART_ROLES, connects_row_order: true },
|
|
252
306
|
),
|
|
253
307
|
make(
|
|
254
308
|
"Density",
|
|
@@ -258,15 +312,24 @@ const CHARTS: ChartTypeConfig[] = [
|
|
|
258
312
|
2,
|
|
259
313
|
["X Axis", "Y Axis", "Color", "Tooltip"],
|
|
260
314
|
DENSITY_FIELDS,
|
|
315
|
+
{ ...CART_ROLES },
|
|
261
316
|
),
|
|
262
|
-
make("Treemap", "treemap", HIER, TOGGLE, 1, HIER_NAMES, NO_FIELDS
|
|
263
|
-
|
|
264
|
-
|
|
317
|
+
make("Treemap", "treemap", HIER, TOGGLE, 1, HIER_NAMES, NO_FIELDS, {
|
|
318
|
+
...HIER_ROLES,
|
|
319
|
+
}),
|
|
320
|
+
make("Sunburst", "sunburst", HIER, TOGGLE, 1, HIER_NAMES, NO_FIELDS, {
|
|
321
|
+
...HIER_ROLES,
|
|
322
|
+
}),
|
|
323
|
+
make("Heatmap", "heatmap", HIER, SELECT, 1, ["Color"], HEATMAP_FIELDS, {
|
|
324
|
+
...HEATMAP_ROLES,
|
|
325
|
+
}),
|
|
265
326
|
make("Candlestick", "candlestick", FIN, TOGGLE, 1, FIN_NAMES, FIN_FIELDS, {
|
|
327
|
+
...FIN_ROLES,
|
|
266
328
|
default_chart_type: "candlestick",
|
|
267
329
|
plugin_field_defaults: SERIES_DEFAULTS,
|
|
268
330
|
}),
|
|
269
331
|
make("OHLC", "ohlc", FIN, TOGGLE, 1, FIN_NAMES, FIN_FIELDS, {
|
|
332
|
+
...FIN_ROLES,
|
|
270
333
|
default_chart_type: "ohlc",
|
|
271
334
|
plugin_field_defaults: SERIES_DEFAULTS,
|
|
272
335
|
}),
|
|
@@ -278,6 +341,7 @@ const CHARTS: ChartTypeConfig[] = [
|
|
|
278
341
|
2,
|
|
279
342
|
["Longitude", "Latitude", "Color", "Size", "Label", "Tooltip"],
|
|
280
343
|
MAP_SCATTER_FIELDS,
|
|
344
|
+
{ ...MAP_ROLES },
|
|
281
345
|
),
|
|
282
346
|
make(
|
|
283
347
|
"Map Line",
|
|
@@ -287,6 +351,7 @@ const CHARTS: ChartTypeConfig[] = [
|
|
|
287
351
|
2,
|
|
288
352
|
["Longitude", "Latitude", "Tooltip"],
|
|
289
353
|
MAP_LINE_FIELDS,
|
|
354
|
+
{ ...MAP_ROLES, connects_row_order: true },
|
|
290
355
|
),
|
|
291
356
|
make(
|
|
292
357
|
"Map Density",
|
|
@@ -296,6 +361,7 @@ const CHARTS: ChartTypeConfig[] = [
|
|
|
296
361
|
2,
|
|
297
362
|
["Longitude", "Latitude", "Color", "Tooltip"],
|
|
298
363
|
MAP_DENSITY_FIELDS,
|
|
364
|
+
{ ...MAP_ROLES },
|
|
299
365
|
),
|
|
300
366
|
];
|
|
301
367
|
|
package/src/ts/plugin/plugin.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
|
+
import { colorsToCss, stopsToCss } from "../theme/gradient";
|
|
13
14
|
import type { View } from "@perspective-dev/client";
|
|
14
15
|
import type {
|
|
15
16
|
HTMLPerspectiveViewerElement,
|
|
@@ -27,6 +28,10 @@ import {
|
|
|
27
28
|
import { RawEventForwarder } from "../interaction/raw-event-forwarder";
|
|
28
29
|
import { RendererTransport } from "../transport/renderer-transport";
|
|
29
30
|
import { RENDER_BLIT_MODE } from "../config";
|
|
31
|
+
import { snapshotThemeVars } from "../theme/theme-snapshot";
|
|
32
|
+
import { resolveThemeFromVars, type Theme } from "../theme/theme";
|
|
33
|
+
import { resolvePalette } from "../theme/palette";
|
|
34
|
+
import { vec3ToHexColor } from "../utils/css";
|
|
30
35
|
|
|
31
36
|
/**
|
|
32
37
|
* Facet-rendering defaults shared by every chart. Per-chart overrides
|
|
@@ -442,26 +447,86 @@ export class HTMLPerspectiveViewerWebGLPluginElement
|
|
|
442
447
|
max_cells: this._chartType.max_cells,
|
|
443
448
|
max_columns: this._chartType.max_columns,
|
|
444
449
|
group_rollup_modes: ["flat"],
|
|
450
|
+
split_rollup_modes: ["flat"],
|
|
451
|
+
group_by_role: this._chartType.group_by_role,
|
|
452
|
+
split_by_role: this._chartType.split_by_role,
|
|
453
|
+
connects_row_order: !!this._chartType.connects_row_order,
|
|
445
454
|
priority: 0,
|
|
446
455
|
can_render_column_styles:
|
|
447
456
|
!!this._chartType.default_chart_type ||
|
|
448
|
-
this._chartType.category === "Cartesian Charts"
|
|
457
|
+
this._chartType.category === "Cartesian Charts" ||
|
|
458
|
+
this._chartType.category === "Hierarchical Charts" ||
|
|
459
|
+
this._chartType.category === "Map Charts",
|
|
449
460
|
};
|
|
450
461
|
}
|
|
451
462
|
|
|
452
463
|
column_config_schema(
|
|
453
464
|
column_type: string,
|
|
454
|
-
|
|
455
|
-
|
|
465
|
+
group: string | undefined,
|
|
466
|
+
column_name: string,
|
|
456
467
|
current_value: Record<string, unknown> | null,
|
|
457
|
-
|
|
468
|
+
viewer_config?: {
|
|
469
|
+
columns?: (string | null)[];
|
|
470
|
+
group_by?: string[];
|
|
471
|
+
split_by?: string[];
|
|
472
|
+
group_rollup_mode?: string;
|
|
473
|
+
},
|
|
458
474
|
) {
|
|
459
475
|
const fields: Array<Record<string, unknown> & { kind: string }> = [];
|
|
460
476
|
|
|
477
|
+
if (group === "Color") {
|
|
478
|
+
const numeric_gradient =
|
|
479
|
+
this._chartType.category === "Hierarchical Charts"
|
|
480
|
+
? column_type === "integer" ||
|
|
481
|
+
column_type === "float" ||
|
|
482
|
+
column_type === "date" ||
|
|
483
|
+
column_type === "datetime"
|
|
484
|
+
: column_type !== "string";
|
|
485
|
+
if (numeric_gradient) {
|
|
486
|
+
fields.push({
|
|
487
|
+
kind: "GradientStops",
|
|
488
|
+
key: "gradient",
|
|
489
|
+
default: this._themeGradientStopsSpec(),
|
|
490
|
+
});
|
|
491
|
+
} else {
|
|
492
|
+
fields.push({
|
|
493
|
+
kind: "Palette",
|
|
494
|
+
key: "palette",
|
|
495
|
+
default: this._themeSeriesPaletteHex(),
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
461
500
|
// Y-series plugins expose the per-column chart_type picker; non-Y
|
|
462
501
|
// plugins leave `default_chart_type` unset.
|
|
463
502
|
const def = this._chartType.default_chart_type;
|
|
464
503
|
if (def && (column_type === "integer" || column_type === "float")) {
|
|
504
|
+
const is_series_glyph =
|
|
505
|
+
def === "bar" ||
|
|
506
|
+
def === "line" ||
|
|
507
|
+
def === "scatter" ||
|
|
508
|
+
def === "area";
|
|
509
|
+
|
|
510
|
+
if (is_series_glyph) {
|
|
511
|
+
const has_split = (viewer_config?.split_by?.length ?? 0) > 0;
|
|
512
|
+
if (has_split) {
|
|
513
|
+
fields.push({
|
|
514
|
+
kind: "Palette",
|
|
515
|
+
key: "palette",
|
|
516
|
+
default: this._themeSeriesPaletteHex(),
|
|
517
|
+
});
|
|
518
|
+
} else {
|
|
519
|
+
const slot = (viewer_config?.columns ?? [])
|
|
520
|
+
.filter((c): c is string => !!c)
|
|
521
|
+
.indexOf(column_name);
|
|
522
|
+
fields.push({
|
|
523
|
+
kind: "Color",
|
|
524
|
+
key: "color",
|
|
525
|
+
default: this._themeSeriesColorHex(Math.max(0, slot)),
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
465
530
|
fields.push({
|
|
466
531
|
kind: "Enum",
|
|
467
532
|
key: "chart_type",
|
|
@@ -489,12 +554,6 @@ export class HTMLPerspectiveViewerWebGLPluginElement
|
|
|
489
554
|
});
|
|
490
555
|
}
|
|
491
556
|
|
|
492
|
-
const is_series_glyph =
|
|
493
|
-
def === "bar" ||
|
|
494
|
-
def === "line" ||
|
|
495
|
-
def === "scatter" ||
|
|
496
|
-
def === "area";
|
|
497
|
-
|
|
498
557
|
if (is_series_glyph) {
|
|
499
558
|
fields.push({
|
|
500
559
|
kind: "Bool",
|
|
@@ -553,6 +612,36 @@ export class HTMLPerspectiveViewerWebGLPluginElement
|
|
|
553
612
|
return { fields };
|
|
554
613
|
}
|
|
555
614
|
|
|
615
|
+
private _resolvedTheme(): Theme {
|
|
616
|
+
return resolveThemeFromVars(snapshotThemeVars(this));
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
private static readonly GRADIENT_PALETTE_FALLBACK_COUNT = 6;
|
|
620
|
+
|
|
621
|
+
private _themeSeriesPaletteHex(): string {
|
|
622
|
+
const theme = this._resolvedTheme();
|
|
623
|
+
const count =
|
|
624
|
+
theme.seriesPalette.length ||
|
|
625
|
+
HTMLPerspectiveViewerWebGLPluginElement.GRADIENT_PALETTE_FALLBACK_COUNT;
|
|
626
|
+
return colorsToCss(
|
|
627
|
+
resolvePalette(theme.seriesPalette, theme.gradientStops, count),
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
private _themeSeriesColorHex(idx: number): string {
|
|
632
|
+
const theme = this._resolvedTheme();
|
|
633
|
+
const count = Math.max(theme.seriesPalette.length, idx + 1);
|
|
634
|
+
return vec3ToHexColor(
|
|
635
|
+
resolvePalette(theme.seriesPalette, theme.gradientStops, count)[
|
|
636
|
+
idx
|
|
637
|
+
],
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
private _themeGradientStopsSpec(): string {
|
|
642
|
+
return stopsToCss(this._resolvedTheme().gradientStops);
|
|
643
|
+
}
|
|
644
|
+
|
|
556
645
|
async draw(view: View): Promise<void> {
|
|
557
646
|
// `draw` always indicates a view-level change (pivots, columns,
|
|
558
647
|
// filters, sorts, schema, …) — invalidate the `domain_mode:
|