@perspective-dev/viewer-charts 5.3.0 → 5.4.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 +1 -2
- package/dist/cdn/perspective-viewer-charts.js.map +4 -4
- package/dist/esm/axis/legend.d.ts +53 -5
- package/dist/esm/axis/map-ticks.d.ts +15 -0
- package/dist/esm/charts/candlestick/candlestick-interact.d.ts +2 -6
- package/dist/esm/charts/cartesian/cartesian.d.ts +6 -0
- package/dist/esm/charts/cartesian/glyph.d.ts +2 -1
- package/dist/esm/charts/cartesian/glyphs/density.d.ts +2 -1
- package/dist/esm/charts/cartesian/glyphs/lines.d.ts +2 -1
- package/dist/esm/charts/cartesian/glyphs/points.d.ts +2 -1
- package/dist/esm/charts/cartesian/tooltip-lines.d.ts +1 -1
- package/dist/esm/charts/chart-base.d.ts +2 -0
- package/dist/esm/charts/chart.d.ts +77 -6
- package/dist/esm/charts/common/draw-tooltip-box.d.ts +2 -1
- package/dist/esm/charts/common/tree-interact.d.ts +1 -1
- package/dist/esm/charts/series/series-build.d.ts +7 -0
- package/dist/esm/charts/series/series-interact.d.ts +1 -1
- package/dist/esm/charts/series/series.d.ts +6 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/interaction/axis-indicators.d.ts +11 -0
- package/dist/esm/interaction/host-sink-dom.d.ts +3 -2
- package/dist/esm/interaction/host-sink-message.d.ts +3 -2
- package/dist/esm/interaction/lazy-tooltip.d.ts +2 -2
- package/dist/esm/interaction/legend-controller.d.ts +246 -0
- package/dist/esm/interaction/tooltip-controller.d.ts +20 -95
- package/dist/esm/interaction/tooltip-grid.d.ts +33 -0
- package/dist/esm/layout/facet-grid.d.ts +6 -0
- package/dist/esm/layout/ticks.d.ts +1 -0
- package/dist/esm/map/tile-source.d.ts +77 -8
- package/dist/esm/perspective-viewer-charts.js +1 -2
- package/dist/esm/perspective-viewer-charts.js.map +4 -4
- package/dist/esm/plugin/charts.d.ts +2 -0
- package/dist/esm/plugin/format-defaults.d.ts +9 -0
- package/dist/esm/plugin/plugin.d.ts +20 -0
- package/dist/esm/theme/gradient.d.ts +1 -0
- package/dist/esm/transport/protocol.d.ts +45 -3
- package/dist/esm/transport/renderer-transport.d.ts +3 -0
- package/dist/esm/worker/renderer.worker.d.ts +19 -1
- package/package.json +6 -6
- package/src/css/perspective-viewer-charts.css +0 -1
- package/src/ts/axis/bar-axis.ts +2 -7
- package/src/ts/axis/legend.ts +351 -24
- package/src/ts/axis/map-ticks.ts +94 -0
- package/src/ts/axis/numeric-axis.ts +2 -9
- package/src/ts/charts/candlestick/candlestick-interact.ts +46 -40
- package/src/ts/charts/candlestick/candlestick-render.ts +73 -10
- package/src/ts/charts/cartesian/cartesian-interact.ts +9 -3
- package/src/ts/charts/cartesian/cartesian-render.ts +369 -75
- package/src/ts/charts/cartesian/cartesian.ts +9 -0
- package/src/ts/charts/cartesian/glyph.ts +6 -2
- package/src/ts/charts/cartesian/glyphs/density.ts +2 -2
- package/src/ts/charts/cartesian/glyphs/lines.ts +15 -11
- package/src/ts/charts/cartesian/glyphs/points.ts +9 -5
- package/src/ts/charts/cartesian/tooltip-lines.ts +8 -8
- package/src/ts/charts/chart-base.ts +34 -32
- package/src/ts/charts/chart.ts +106 -7
- package/src/ts/charts/common/draw-tooltip-box.ts +13 -30
- package/src/ts/charts/common/tree-chrome.ts +102 -23
- package/src/ts/charts/common/tree-interact.ts +18 -17
- package/src/ts/charts/heatmap/heatmap-interact.ts +65 -7
- package/src/ts/charts/heatmap/heatmap-render.ts +100 -24
- package/src/ts/charts/map/map.ts +3 -5
- package/src/ts/charts/series/series-build.ts +14 -2
- package/src/ts/charts/series/series-interact.ts +12 -11
- package/src/ts/charts/series/series-render.ts +305 -132
- package/src/ts/charts/series/series.ts +10 -0
- package/src/ts/charts/sunburst/sunburst-render.ts +7 -1
- package/src/ts/charts/treemap/treemap-render.ts +7 -1
- package/src/ts/data/view-reader.ts +1 -4
- package/src/ts/index.ts +2 -0
- package/src/ts/interaction/axis-indicators.ts +137 -0
- package/src/ts/interaction/host-sink-dom.ts +66 -2
- package/src/ts/interaction/host-sink-message.ts +8 -2
- package/src/ts/interaction/lazy-tooltip.ts +5 -5
- package/src/ts/interaction/legend-controller.ts +1084 -0
- package/src/ts/interaction/tooltip-controller.ts +43 -131
- package/src/ts/interaction/tooltip-grid.ts +187 -0
- package/src/ts/layout/facet-grid.ts +8 -1
- package/src/ts/layout/ticks.ts +12 -0
- package/src/ts/map/tile-loader.ts +16 -1
- package/src/ts/map/tile-source.ts +199 -53
- package/src/ts/map/tile-sources.json +18 -0
- package/src/ts/plugin/charts.ts +40 -6
- package/src/ts/plugin/format-defaults.ts +37 -0
- package/src/ts/plugin/plugin.ts +236 -67
- package/src/ts/shaders/line.vert.glsl +5 -2
- package/src/ts/shaders/scatter.vert.glsl +0 -7
- package/src/ts/theme/gradient.ts +24 -0
- package/src/ts/transport/protocol.ts +46 -2
- package/src/ts/transport/renderer-transport.ts +83 -4
- package/src/ts/worker/dispatch.ts +6 -2
- package/src/ts/worker/renderer.worker.ts +78 -2
|
@@ -309,12 +309,12 @@ export class DensityGlyph implements Glyph {
|
|
|
309
309
|
buildTooltipLines(
|
|
310
310
|
chart: CartesianChart,
|
|
311
311
|
flatIdx: number,
|
|
312
|
-
): Promise<string[]> {
|
|
312
|
+
): Promise<string[][]> {
|
|
313
313
|
return buildPointRowTooltipLines(chart, flatIdx);
|
|
314
314
|
}
|
|
315
315
|
|
|
316
316
|
tooltipOptions() {
|
|
317
|
-
return { crosshair: true, highlightRadius: 0 };
|
|
317
|
+
return { crosshair: true, highlightRadius: 0, axisIndicators: true };
|
|
318
318
|
}
|
|
319
319
|
|
|
320
320
|
destroy(chart: CartesianChart): void {
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
getInstancing,
|
|
20
20
|
} from "../../../webgl/instanced-attrs";
|
|
21
21
|
import { compileProgram } from "../../../webgl/program-cache";
|
|
22
|
+
import { colorRangePivot } from "../../../theme/gradient";
|
|
22
23
|
import { formatTickValue, formatDateTickValue } from "../../../layout/ticks";
|
|
23
24
|
import lineVert from "../../../shaders/line.vert.glsl";
|
|
24
25
|
import lineFrag from "../../../shaders/line.frag.glsl";
|
|
@@ -124,17 +125,17 @@ export class LineGlyph implements Glyph {
|
|
|
124
125
|
async buildTooltipLines(
|
|
125
126
|
chart: CartesianChart,
|
|
126
127
|
flatIdx: number,
|
|
127
|
-
): Promise<string[]> {
|
|
128
|
-
const
|
|
128
|
+
): Promise<string[][]> {
|
|
129
|
+
const grid: string[][] = [];
|
|
129
130
|
if (!chart._xData || !chart._yData) {
|
|
130
|
-
return
|
|
131
|
+
return grid;
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
if (chart._splitGroups.length > 0 && chart._seriesCapacity > 0) {
|
|
134
135
|
const seriesIdx = Math.floor(flatIdx / chart._seriesCapacity);
|
|
135
136
|
const sg = chart._splitGroups[seriesIdx];
|
|
136
137
|
if (sg) {
|
|
137
|
-
|
|
138
|
+
grid.push([sg.prefix]);
|
|
138
139
|
}
|
|
139
140
|
}
|
|
140
141
|
|
|
@@ -146,20 +147,20 @@ export class LineGlyph implements Glyph {
|
|
|
146
147
|
const xFormatted = xIsDate
|
|
147
148
|
? formatDateTickValue(xVal)
|
|
148
149
|
: formatTickValue(xVal);
|
|
149
|
-
|
|
150
|
+
grid.push([chart._xLabel || "Row", xFormatted]);
|
|
150
151
|
|
|
151
152
|
const yType = chart._columnTypes[chart._yLabel] || "";
|
|
152
153
|
const yIsDate = yType === "date" || yType === "datetime";
|
|
153
154
|
const yFormatted = yIsDate
|
|
154
155
|
? formatDateTickValue(yVal)
|
|
155
156
|
: formatTickValue(yVal);
|
|
156
|
-
|
|
157
|
+
grid.push([chart._yLabel, yFormatted]);
|
|
157
158
|
|
|
158
|
-
return
|
|
159
|
+
return grid;
|
|
159
160
|
}
|
|
160
161
|
|
|
161
162
|
tooltipOptions() {
|
|
162
|
-
return { crosshair: true, highlightRadius: 5 };
|
|
163
|
+
return { crosshair: true, highlightRadius: 5, axisIndicators: true };
|
|
163
164
|
}
|
|
164
165
|
|
|
165
166
|
destroy(chart: CartesianChart): void {
|
|
@@ -195,10 +196,13 @@ function bindLineState(
|
|
|
195
196
|
gl.uniformMatrix4fv(cache.u_projection, false, projection);
|
|
196
197
|
gl.uniform2f(cache.u_resolution, gl.canvas.width, gl.canvas.height);
|
|
197
198
|
gl.uniform1f(cache.u_line_width, chart._pluginConfig.line_width_px * dpr);
|
|
198
|
-
if (chart._colorMin
|
|
199
|
-
gl.uniform2f(cache.u_color_range, chart._colorMin, chart._colorMax);
|
|
200
|
-
} else {
|
|
199
|
+
if (chart._colorMin >= chart._colorMax) {
|
|
201
200
|
gl.uniform2f(cache.u_color_range, 0.0, 0.0);
|
|
201
|
+
} else if (chart._colorName && !chart._colorIsString) {
|
|
202
|
+
const [lo, hi] = colorRangePivot(chart._colorMin, chart._colorMax);
|
|
203
|
+
gl.uniform2f(cache.u_color_range, lo, hi);
|
|
204
|
+
} else {
|
|
205
|
+
gl.uniform2f(cache.u_color_range, chart._colorMin, chart._colorMax);
|
|
202
206
|
}
|
|
203
207
|
|
|
204
208
|
bindGradientTexture(
|
|
@@ -14,6 +14,7 @@ import type { WebGLContextManager } from "../../../webgl/context-manager";
|
|
|
14
14
|
import type { CartesianChart } from "../cartesian";
|
|
15
15
|
import type { Glyph } from "../glyph";
|
|
16
16
|
import { bindGradientTexture } from "../../../webgl/gradient-texture";
|
|
17
|
+
import { colorRangePivot } from "../../../theme/gradient";
|
|
17
18
|
import { compileProgram } from "../../../webgl/program-cache";
|
|
18
19
|
import { buildPointRowTooltipLines } from "../tooltip-lines";
|
|
19
20
|
import scatterVert from "../../../shaders/scatter.vert.glsl";
|
|
@@ -130,12 +131,12 @@ export class PointGlyph implements Glyph {
|
|
|
130
131
|
buildTooltipLines(
|
|
131
132
|
chart: CartesianChart,
|
|
132
133
|
flatIdx: number,
|
|
133
|
-
): Promise<string[]> {
|
|
134
|
+
): Promise<string[][]> {
|
|
134
135
|
return buildPointRowTooltipLines(chart, flatIdx);
|
|
135
136
|
}
|
|
136
137
|
|
|
137
138
|
tooltipOptions() {
|
|
138
|
-
return { crosshair: true, highlightRadius: 6 };
|
|
139
|
+
return { crosshair: true, highlightRadius: 6, axisIndicators: true };
|
|
139
140
|
}
|
|
140
141
|
|
|
141
142
|
destroy(_chart: CartesianChart): void {
|
|
@@ -155,10 +156,13 @@ function setUniforms(
|
|
|
155
156
|
gl.uniformMatrix4fv(cache.u_projection, false, projection);
|
|
156
157
|
gl.uniform1f(cache.u_point_size, chart._pluginConfig.point_size_px * dpr);
|
|
157
158
|
|
|
158
|
-
if (chart._colorMin
|
|
159
|
-
gl.uniform2f(cache.u_color_range, chart._colorMin, chart._colorMax);
|
|
160
|
-
} else {
|
|
159
|
+
if (chart._colorMin >= chart._colorMax) {
|
|
161
160
|
gl.uniform2f(cache.u_color_range, 0.0, 0.0);
|
|
161
|
+
} else if (chart._colorName && !chart._colorIsString) {
|
|
162
|
+
const [lo, hi] = colorRangePivot(chart._colorMin, chart._colorMax);
|
|
163
|
+
gl.uniform2f(cache.u_color_range, lo, hi);
|
|
164
|
+
} else {
|
|
165
|
+
gl.uniform2f(cache.u_color_range, chart._colorMin, chart._colorMax);
|
|
162
166
|
}
|
|
163
167
|
|
|
164
168
|
if (chart._sizeMin < chart._sizeMax) {
|
|
@@ -24,22 +24,22 @@ import type { CartesianChart } from "./cartesian";
|
|
|
24
24
|
export async function buildPointRowTooltipLines(
|
|
25
25
|
chart: CartesianChart,
|
|
26
26
|
flatIdx: number,
|
|
27
|
-
): Promise<string[]> {
|
|
28
|
-
const
|
|
27
|
+
): Promise<string[][]> {
|
|
28
|
+
const grid: string[][] = [];
|
|
29
29
|
if (!chart._rowIndexData || !chart._lazyRows) {
|
|
30
|
-
return
|
|
30
|
+
return grid;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
const rowIdx = chart._rowIndexData[flatIdx];
|
|
34
34
|
if (rowIdx < 0) {
|
|
35
|
-
return
|
|
35
|
+
return grid;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
if (chart._splitGroups.length > 0 && chart._seriesCapacity > 0) {
|
|
39
39
|
const seriesIdx = Math.floor(flatIdx / chart._seriesCapacity);
|
|
40
40
|
const sg = chart._splitGroups[seriesIdx];
|
|
41
41
|
if (sg?.prefix) {
|
|
42
|
-
|
|
42
|
+
grid.push([sg.prefix]);
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -70,11 +70,11 @@ export async function buildPointRowTooltipLines(
|
|
|
70
70
|
|
|
71
71
|
if (typeof value === "number") {
|
|
72
72
|
const formatted = chart.getColumnFormatter(colName, "value")(value);
|
|
73
|
-
|
|
73
|
+
grid.push([displayName, formatted]);
|
|
74
74
|
} else {
|
|
75
|
-
|
|
75
|
+
grid.push([displayName, String(value)]);
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
return
|
|
79
|
+
return grid;
|
|
80
80
|
}
|
|
@@ -21,7 +21,11 @@ import {
|
|
|
21
21
|
} from "@perspective-dev/viewer/src/ts/column-format.js";
|
|
22
22
|
import type { ColumnDataMap } from "../data/view-reader";
|
|
23
23
|
import { LazyRowFetcher } from "../data/lazy-row";
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
CHART_DATE_DEFAULTS,
|
|
26
|
+
CHART_DATETIME_DEFAULTS,
|
|
27
|
+
CHART_NUMBER_DEFAULTS,
|
|
28
|
+
} from "../plugin/format-defaults";
|
|
25
29
|
import type { WebGLContextManager } from "../webgl/context-manager";
|
|
26
30
|
import {
|
|
27
31
|
ZoomController,
|
|
@@ -41,52 +45,37 @@ import {
|
|
|
41
45
|
type UserClickPayload,
|
|
42
46
|
type UserSelectPayload,
|
|
43
47
|
} from "../interaction/tooltip-controller";
|
|
48
|
+
import { LegendController } from "../interaction/legend-controller";
|
|
44
49
|
import type { PerspectiveClickDetail } from "../event-detail";
|
|
45
50
|
import type { ViewConfig } from "@perspective-dev/client";
|
|
46
51
|
import { resolveThemeFromVars, type Theme } from "../theme/theme";
|
|
47
52
|
import { applyColumnColorOverrides } from "../theme/overrides";
|
|
48
53
|
import { requestRender as scheduleRender } from "../render/scheduler";
|
|
49
54
|
|
|
50
|
-
// TODO I don't know if this is the behavior we want. On the plus side, this
|
|
51
|
-
// ad-hoc formatter scales well to small and large data ranges, making a good
|
|
52
|
-
// guess at the right format without user input. On the minus side, this
|
|
53
|
-
// behavior is inconsistent with datagrid and the rest of the app, and the ad-hoc
|
|
54
|
-
// surprising behavior when overriding one field in `number_format` and suddenly
|
|
55
|
-
// the entire formatter is replaced.
|
|
56
|
-
const REGRESSION_BEHAVIOR = true;
|
|
57
|
-
|
|
58
55
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* fractional digits matches the legacy datagrid default and gives
|
|
62
|
-
* tooltips a stable display width.
|
|
56
|
+
* Fallback formatter applied to numeric tooltip / legend values when the
|
|
57
|
+
* column has no `number_format` configured.
|
|
63
58
|
*/
|
|
64
59
|
const DEFAULT_VALUE_FORMATTER: (v: number) => string = ((): ((
|
|
65
60
|
v: number,
|
|
66
61
|
) => string) => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
62
|
+
const intl = createNumberFormatter(
|
|
63
|
+
"float",
|
|
64
|
+
undefined,
|
|
65
|
+
CHART_NUMBER_DEFAULTS,
|
|
66
|
+
);
|
|
67
|
+
return (v) => intl.format(v);
|
|
73
68
|
})();
|
|
74
69
|
|
|
75
70
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
* default (no `dateStyle` / `timeStyle`) to match what most users
|
|
79
|
-
* expect from an `Intl.DateTimeFormat()` constructed with no options.
|
|
71
|
+
* Fallback formatter for datetime tooltip / legend values when the
|
|
72
|
+
* column has no `date_format` configured.
|
|
80
73
|
*/
|
|
81
74
|
const DEFAULT_DATETIME_FORMATTER: (v: number) => string = ((): ((
|
|
82
75
|
v: number,
|
|
83
76
|
) => string) => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
} else {
|
|
87
|
-
const intl = createDatetimeFormatter();
|
|
88
|
-
return (v) => intl.format(v);
|
|
89
|
-
}
|
|
77
|
+
const intl = createDatetimeFormatter(undefined, CHART_DATETIME_DEFAULTS);
|
|
78
|
+
return (v) => intl.format(v);
|
|
90
79
|
})();
|
|
91
80
|
|
|
92
81
|
/**
|
|
@@ -238,6 +227,7 @@ export abstract class AbstractChart implements ChartImplementation {
|
|
|
238
227
|
*/
|
|
239
228
|
_pluginConfig: PluginConfig = { ...DEFAULT_PLUGIN_CONFIG };
|
|
240
229
|
|
|
230
|
+
_legend = new LegendController();
|
|
241
231
|
_tooltip = new TooltipController();
|
|
242
232
|
|
|
243
233
|
/**
|
|
@@ -495,7 +485,11 @@ export abstract class AbstractChart implements ChartImplementation {
|
|
|
495
485
|
return undefined;
|
|
496
486
|
}
|
|
497
487
|
|
|
498
|
-
const intl = createNumberFormatter(
|
|
488
|
+
const intl = createNumberFormatter(
|
|
489
|
+
type,
|
|
490
|
+
numberFormat,
|
|
491
|
+
CHART_NUMBER_DEFAULTS,
|
|
492
|
+
);
|
|
499
493
|
return (v) => intl.format(v);
|
|
500
494
|
}
|
|
501
495
|
|
|
@@ -505,7 +499,10 @@ export abstract class AbstractChart implements ChartImplementation {
|
|
|
505
499
|
return undefined;
|
|
506
500
|
}
|
|
507
501
|
|
|
508
|
-
const intl = createDatetimeFormatter(
|
|
502
|
+
const intl = createDatetimeFormatter(
|
|
503
|
+
dateFormat,
|
|
504
|
+
CHART_DATETIME_DEFAULTS,
|
|
505
|
+
);
|
|
509
506
|
return (v) => intl.format(v);
|
|
510
507
|
}
|
|
511
508
|
|
|
@@ -515,7 +512,7 @@ export abstract class AbstractChart implements ChartImplementation {
|
|
|
515
512
|
return undefined;
|
|
516
513
|
}
|
|
517
514
|
|
|
518
|
-
const intl = createDateFormatter(dateFormat);
|
|
515
|
+
const intl = createDateFormatter(dateFormat, CHART_DATE_DEFAULTS);
|
|
519
516
|
return (v) => intl.format(v);
|
|
520
517
|
}
|
|
521
518
|
|
|
@@ -608,6 +605,11 @@ export abstract class AbstractChart implements ChartImplementation {
|
|
|
608
605
|
* inputs in `uploadAndRender`; they take effect on next data load.
|
|
609
606
|
*/
|
|
610
607
|
setPluginConfig(cfg: PluginConfig): void {
|
|
608
|
+
// Persistence echo vs. real change: a restore whose legend
|
|
609
|
+
// fields equal the current values (the round-trip of a
|
|
610
|
+
// completed drag) must not disturb legend scroll or an
|
|
611
|
+
// in-flight gesture; different values win and cancel any drag.
|
|
612
|
+
this._legend.reconcileConfig(this._pluginConfig, cfg);
|
|
611
613
|
this._pluginConfig = { ...cfg };
|
|
612
614
|
this._facetConfig = {
|
|
613
615
|
...this._facetConfig,
|
package/src/ts/charts/chart.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
13
|
import type { View } from "@perspective-dev/client";
|
|
14
|
+
import { TILE_SOURCES } from "../map/tile-source";
|
|
14
15
|
import type { ColumnDataMap } from "../data/view-reader";
|
|
15
16
|
import type { WebGLContextManager } from "../webgl/context-manager";
|
|
16
17
|
import type { ZoomController } from "../interaction/zoom-controller";
|
|
@@ -198,6 +199,8 @@ export interface ChartImplementation {
|
|
|
198
199
|
*/
|
|
199
200
|
deselect?(): void;
|
|
200
201
|
|
|
202
|
+
repaintChrome?(): void;
|
|
203
|
+
|
|
201
204
|
destroy(): void;
|
|
202
205
|
}
|
|
203
206
|
|
|
@@ -431,13 +434,15 @@ export interface PluginConfig {
|
|
|
431
434
|
gradient_color_mode: "mean" | "density" | "extreme" | "signed";
|
|
432
435
|
|
|
433
436
|
/**
|
|
434
|
-
* Map basemap tile provider
|
|
435
|
-
*
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
*
|
|
437
|
+
* Map basemap tile provider — a `TileSourceSpec` id from the
|
|
438
|
+
* tile-source registry ([map/tile-sources.json] entries plus any
|
|
439
|
+
* runtime `registerTileSource` additions). Applies only to map
|
|
440
|
+
* plugin tags (`map-scatter`, `map-line`, `map-density`); other
|
|
441
|
+
* charts ignore the field. The default is the JSON's FIRST entry
|
|
442
|
+
* (reordering the file changes the default), and unknown ids fall
|
|
443
|
+
* back to that same entry rather than blanking the map.
|
|
439
444
|
*/
|
|
440
|
-
map_tile_provider:
|
|
445
|
+
map_tile_provider: string;
|
|
441
446
|
|
|
442
447
|
/**
|
|
443
448
|
* Map basemap alpha (0..1). Pre-multiplied into the tile fragment
|
|
@@ -446,8 +451,91 @@ export interface PluginConfig {
|
|
|
446
451
|
* shows the tiles at full opacity.
|
|
447
452
|
*/
|
|
448
453
|
map_tile_alpha: number;
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Map plugins only. `true` (default): standard numeric axes in the
|
|
457
|
+
* usual cartesian gutters, with tick labels in degrees
|
|
458
|
+
* longitude/latitude (`122.4°W`) rather than Mercator meters.
|
|
459
|
+
* `false`: no axes, and the plot is full-bleed — the basemap fills
|
|
460
|
+
* the entire canvas, minus only the sidebar legend gutter when a
|
|
461
|
+
* legend is actually shown. Gridlines are never drawn in map mode
|
|
462
|
+
* — the gridline canvas composites BELOW the GL layer, so opaque
|
|
463
|
+
* basemap tiles would hide them.
|
|
464
|
+
*/
|
|
465
|
+
numeric_axes: boolean;
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Legend presentation mode. `"auto"` (default) resolves per frame
|
|
469
|
+
* to `"floating"` when every entry fits the default floating panel
|
|
470
|
+
* without scrolling (≤ 7 entries; continuous gradient legends
|
|
471
|
+
* always qualify) and to `"sidebar"` otherwise — see
|
|
472
|
+
* `resolveLegendMode`. Treemap overrides the default to
|
|
473
|
+
* `"sidebar"` (a floating panel over edge-to-edge tiles always
|
|
474
|
+
* occludes data).
|
|
475
|
+
*/
|
|
476
|
+
legend_mode: "auto" | "sidebar" | "none" | "floating";
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Floating-panel sizing regime. `"auto"` (default) sizes the panel
|
|
480
|
+
* to its CONTENT every frame — the height hugs the entry rows
|
|
481
|
+
* exactly, and the width hugs the widest entry label (measured on
|
|
482
|
+
* the chrome canvas). `"fixed"` uses the saved `legend_width_px` /
|
|
483
|
+
* `legend_height_px` verbatim.
|
|
484
|
+
*
|
|
485
|
+
* Applies to `legend_mode: "floating"` ONLY; the sidebar gutter is
|
|
486
|
+
* always `legend_width_px` (its height is the plot's). Dragging a
|
|
487
|
+
* resize handle switches an auto panel to `"fixed"` — otherwise
|
|
488
|
+
* the gesture would be undone by the next paint — and
|
|
489
|
+
* double-clicking a resize handle switches it back.
|
|
490
|
+
*/
|
|
491
|
+
legend_size_mode: LegendSizeMode;
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Legend width in CSS pixels. `0` (default) = automatic — each
|
|
495
|
+
* chart family keeps its historical gutter width (80–96px). In
|
|
496
|
+
* `"sidebar"` mode this is the full right-gutter width; in
|
|
497
|
+
* `"floating"` mode it is the panel width. Clamped at paint time
|
|
498
|
+
* to at most half the canvas width so a saved wide legend cannot
|
|
499
|
+
* crush a small panel. Ignored by a floating panel in
|
|
500
|
+
* `legend_size_mode: "auto"`.
|
|
501
|
+
*/
|
|
502
|
+
legend_width_px: number;
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Floating-legend panel height in CSS pixels. Ignored in
|
|
506
|
+
* `"sidebar"` mode (the legend spans the plot height) and by a
|
|
507
|
+
* floating panel in `legend_size_mode: "auto"`. Clamped at paint
|
|
508
|
+
* time to the canvas height.
|
|
509
|
+
*/
|
|
510
|
+
legend_height_px: number;
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Canvas corner that `legend_x` / `legend_y` are measured FROM.
|
|
514
|
+
* Floating mode only. The panel keeps its distance to this corner
|
|
515
|
+
* across panel resizes — anchor `"bottom-right"` with small
|
|
516
|
+
* offsets stays glued to the bottom-right.
|
|
517
|
+
*/
|
|
518
|
+
legend_anchor: LegendAnchor;
|
|
519
|
+
|
|
520
|
+
legend_x: number;
|
|
521
|
+
legend_y: number;
|
|
522
|
+
legend_opacity: number;
|
|
523
|
+
|
|
524
|
+
/** Per-column width cap for tooltip grid cells, in CSS pixels. */
|
|
525
|
+
tooltip_max_column_px: number;
|
|
526
|
+
|
|
527
|
+
/** Tooltip background/border alpha (0..1). */
|
|
528
|
+
tooltip_opacity: number;
|
|
449
529
|
}
|
|
450
530
|
|
|
531
|
+
export type LegendAnchor =
|
|
532
|
+
| "top-left"
|
|
533
|
+
| "top-right"
|
|
534
|
+
| "bottom-left"
|
|
535
|
+
| "bottom-right";
|
|
536
|
+
|
|
537
|
+
export type LegendSizeMode = "auto" | "fixed";
|
|
538
|
+
|
|
451
539
|
export const DEFAULT_PLUGIN_CONFIG: PluginConfig = {
|
|
452
540
|
auto_alt_y_axis: false,
|
|
453
541
|
facet_mode: "grid",
|
|
@@ -465,6 +553,17 @@ export const DEFAULT_PLUGIN_CONFIG: PluginConfig = {
|
|
|
465
553
|
gradient_intensity: 0.6,
|
|
466
554
|
gradient_heat_max: 4.0,
|
|
467
555
|
gradient_color_mode: "mean",
|
|
468
|
-
map_tile_provider:
|
|
556
|
+
map_tile_provider: TILE_SOURCES.list()[0].id,
|
|
469
557
|
map_tile_alpha: 1.0,
|
|
558
|
+
numeric_axes: true,
|
|
559
|
+
legend_mode: "auto",
|
|
560
|
+
legend_size_mode: "auto",
|
|
561
|
+
legend_width_px: 0,
|
|
562
|
+
legend_height_px: 160,
|
|
563
|
+
legend_anchor: "top-right",
|
|
564
|
+
legend_x: 0,
|
|
565
|
+
legend_y: 0,
|
|
566
|
+
legend_opacity: 0.8,
|
|
567
|
+
tooltip_max_column_px: 160,
|
|
568
|
+
tooltip_opacity: 0.8,
|
|
470
569
|
};
|
|
@@ -12,6 +12,13 @@
|
|
|
12
12
|
|
|
13
13
|
import type { Context2D } from "../canvas-types";
|
|
14
14
|
import type { Theme } from "../../theme/theme";
|
|
15
|
+
import {
|
|
16
|
+
measureTooltipGrid,
|
|
17
|
+
paintTooltipGrid,
|
|
18
|
+
DEFAULT_TOOLTIP_STYLE,
|
|
19
|
+
type TooltipContent,
|
|
20
|
+
type TooltipStyle,
|
|
21
|
+
} from "../../interaction/tooltip-grid";
|
|
15
22
|
|
|
16
23
|
/**
|
|
17
24
|
* Draw a freestanding tooltip box anchored near (cx, cy), measuring
|
|
@@ -22,32 +29,21 @@ import type { Theme } from "../../theme/theme";
|
|
|
22
29
|
export function drawTooltipBox(
|
|
23
30
|
ctx: Context2D,
|
|
24
31
|
theme: Theme,
|
|
25
|
-
|
|
32
|
+
grid: TooltipContent,
|
|
26
33
|
cx: number,
|
|
27
34
|
cy: number,
|
|
28
35
|
cssWidth: number,
|
|
29
36
|
cssHeight: number,
|
|
30
37
|
fontFamily: string,
|
|
38
|
+
style: TooltipStyle = DEFAULT_TOOLTIP_STYLE,
|
|
31
39
|
): void {
|
|
32
|
-
if (
|
|
40
|
+
if (grid.length === 0) {
|
|
33
41
|
return;
|
|
34
42
|
}
|
|
35
43
|
|
|
36
|
-
const { tooltipBg, tooltipText, tooltipBorder } = theme;
|
|
37
|
-
|
|
38
44
|
ctx.font = `11px ${fontFamily}`;
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
let maxWidth = 0;
|
|
42
|
-
for (const line of lines) {
|
|
43
|
-
const w = ctx.measureText(line).width;
|
|
44
|
-
if (w > maxWidth) {
|
|
45
|
-
maxWidth = w;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const boxW = maxWidth + padding * 2;
|
|
50
|
-
const boxH = lines.length * lineHeight + padding * 2 - 4;
|
|
45
|
+
const measured = measureTooltipGrid(ctx, grid, style.maxColumnPx);
|
|
46
|
+
const { boxW, boxH } = measured;
|
|
51
47
|
|
|
52
48
|
let tx = cx + 12;
|
|
53
49
|
let ty = cy - boxH - 8;
|
|
@@ -67,18 +63,5 @@ export function drawTooltipBox(
|
|
|
67
63
|
ty = cssHeight - boxH - 4;
|
|
68
64
|
}
|
|
69
65
|
|
|
70
|
-
ctx
|
|
71
|
-
ctx.strokeStyle = tooltipBorder;
|
|
72
|
-
ctx.lineWidth = 1;
|
|
73
|
-
ctx.beginPath();
|
|
74
|
-
ctx.roundRect(tx, ty, boxW, boxH, 4);
|
|
75
|
-
ctx.fill();
|
|
76
|
-
ctx.stroke();
|
|
77
|
-
|
|
78
|
-
ctx.fillStyle = tooltipText;
|
|
79
|
-
ctx.textAlign = "left";
|
|
80
|
-
ctx.textBaseline = "top";
|
|
81
|
-
for (let i = 0; i < lines.length; i++) {
|
|
82
|
-
ctx.fillText(lines[i], tx + padding, ty + padding + i * lineHeight);
|
|
83
|
-
}
|
|
66
|
+
paintTooltipGrid(ctx, measured, tx, ty, theme, style.opacity);
|
|
84
67
|
}
|