@svgrid/grid 3.0.2 → 3.0.4
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/README.md +2 -1
- package/dist/GridMenus.svelte +44 -2
- package/dist/SvChartMenu.svelte +69 -0
- package/dist/SvChartMenu.svelte.d.ts +11 -0
- package/dist/SvChartPanes.svelte +113 -0
- package/dist/SvChartPanes.svelte.d.ts +20 -0
- package/dist/SvGrid.controller.svelte.d.ts +110 -39
- package/dist/SvGrid.controller.svelte.js +655 -200
- package/dist/SvGrid.css +105 -2
- package/dist/SvGrid.svelte +386 -514
- package/dist/SvGrid.types.d.ts +210 -9
- package/dist/SvGridCellEditor.svelte +17 -0
- package/dist/SvGridChart.svelte +2257 -329
- package/dist/SvGridChart.svelte.d.ts +6 -106
- package/dist/SvGridChart.types.d.ts +338 -0
- package/dist/SvGridChart.types.js +1 -0
- package/dist/SvGridChartBuilder.svelte +323 -0
- package/dist/SvGridChartBuilder.svelte.d.ts +34 -0
- package/dist/SvGridChartPanel.svelte +221 -275
- package/dist/SvGridChartPickers.svelte +417 -0
- package/dist/SvGridChartPickers.svelte.d.ts +70 -0
- package/dist/SvGridChartView.svelte +13 -14
- package/dist/SvModal.svelte +7 -1
- package/dist/SvModal.svelte.d.ts +5 -0
- package/dist/ai.d.ts +40 -8
- package/dist/ai.js +135 -24
- package/dist/aria.d.ts +11 -0
- package/dist/build-api.js +108 -22
- package/dist/cdn/GridMenus-7kbpnnBW.js +635 -0
- package/dist/cdn/GridMenus-BL7ZgQvU.js +636 -0
- package/dist/cdn/SvChartMenu-Bl6PBbkT.js +58 -0
- package/dist/cdn/SvChartMenu-FBSMINA6.js +59 -0
- package/dist/cdn/{SvDateRangeInput-DMLKmGEc.js → SvDateRangeInput-BeU_TkbQ.js} +15 -14
- package/dist/cdn/{SvDateRangeInput-CaOuMs8O.js → SvDateRangeInput-yX8vzleW.js} +38 -37
- package/dist/cdn/{SvDateTimePicker-sonaH0oh.js → SvDateTimePicker-DQwt4UAs.js} +139 -180
- package/dist/cdn/SvDateTimePicker-vNU6bZ-q.js +775 -0
- package/dist/cdn/{SvGridCellEditor-CjEJMJKc.js → SvGridCellEditor-B1p-vCK5.js} +188 -178
- package/dist/cdn/{SvGridCellEditor-BWnTi2N7.js → SvGridCellEditor-D_0q4xAu.js} +98 -88
- package/dist/cdn/SvGridChart-C3EWAZaM.js +3574 -0
- package/dist/cdn/SvGridChart-DzLkSwCH.js +3572 -0
- package/dist/cdn/SvGridChartBuilder-BE2T1ykB.js +783 -0
- package/dist/cdn/SvGridChartBuilder-CfII62sZ.js +784 -0
- package/dist/cdn/SvGridChartPanel-CCX5_Wgd.js +675 -0
- package/dist/cdn/SvGridChartPanel-xf4sWVTo.js +699 -0
- package/dist/cdn/SvGridChartView-CfuXmY5I.js +48 -0
- package/dist/cdn/SvGridChartView-DYwabQWj.js +47 -0
- package/dist/cdn/SvMenuList-CLN8OuIK.js +452 -0
- package/dist/cdn/SvMenuList-DkGhNKjf.js +451 -0
- package/dist/cdn/SvModal-BAE-pjZX.js +396 -0
- package/dist/cdn/SvModal-CIZWCcad.js +397 -0
- package/dist/cdn/chart-Dz7SqMXH.js +4341 -0
- package/dist/cdn/chart-export-BXqueeWJ.js +135 -0
- package/dist/cdn/chart-export-pdf-DONFUnNY.js +110 -0
- package/dist/cdn/chart-panel-messages-CUbf2R4i.js +1052 -0
- package/dist/cdn/chart-panel-messages-CmNrMdsr.js +1053 -0
- package/dist/cdn/chart-summary-BJW_tg_X.js +138 -0
- package/dist/cdn/chart-trend-CaN9mDEV.js +50 -0
- package/dist/cdn/chart-validate-HcOVFAfJ.js +188 -0
- package/dist/cdn/{column-resize-DsfNXMom.js → column-resize-DpmOLfRp.js} +3 -3
- package/dist/cdn/{date-format-BnnHlqGw.js → date-format-CtquV-p3.js} +583 -603
- package/dist/cdn/{date-format-BNii4zeD.js → date-format-D6KFzU_W.js} +347 -367
- package/dist/cdn/dismissable-DAHetSNk.js +44 -0
- package/dist/cdn/editor-contract-LXgQJAgd.js +22 -0
- package/dist/cdn/focus-trap-BBOQYBma.js +65 -0
- package/dist/cdn/{row-resize-BRcimkUT.js → row-resize-niQCp040.js} +44 -36
- package/dist/cdn/{src-DNbz94Ct.js → src-BKhZ6eXd.js} +10227 -9787
- package/dist/cdn/{src-BJ2opd0j.js → src-V1uu8iE9.js} +6568 -6128
- package/dist/cdn/svgrid.js +26 -15
- package/dist/cdn/svgrid.svelte-external.js +26 -15
- package/dist/chart-axes.d.ts +53 -0
- package/dist/chart-axes.js +351 -0
- package/dist/chart-cartesian.d.ts +88 -0
- package/dist/chart-cartesian.js +1862 -0
- package/dist/chart-decimate.d.ts +51 -0
- package/dist/chart-decimate.js +199 -0
- package/dist/chart-export-pdf.d.ts +47 -0
- package/dist/chart-export-pdf.js +187 -0
- package/dist/chart-export.d.ts +2 -1
- package/dist/chart-export.js +18 -3
- package/dist/chart-financial.d.ts +91 -0
- package/dist/chart-financial.js +175 -0
- package/dist/chart-flow.d.ts +10 -0
- package/dist/chart-flow.js +298 -0
- package/dist/chart-format.d.ts +18 -0
- package/dist/chart-format.js +65 -0
- package/dist/chart-grid.d.ts +8 -0
- package/dist/chart-grid.js +190 -0
- package/dist/chart-hierarchy.d.ts +17 -0
- package/dist/chart-hierarchy.js +146 -0
- package/dist/chart-indicators.d.ts +115 -0
- package/dist/chart-indicators.js +389 -0
- package/dist/chart-messages.d.ts +95 -0
- package/dist/chart-messages.js +92 -0
- package/dist/chart-motion.d.ts +16 -0
- package/dist/chart-motion.js +141 -0
- package/dist/chart-panel-messages.d.ts +265 -0
- package/dist/chart-panel-messages.js +285 -0
- package/dist/chart-pivot.d.ts +87 -0
- package/dist/chart-pivot.js +156 -0
- package/dist/chart-polar.d.ts +25 -0
- package/dist/chart-polar.js +700 -0
- package/dist/chart-samples.d.ts +16 -0
- package/dist/chart-samples.js +153 -0
- package/dist/chart-scale.d.ts +113 -0
- package/dist/chart-scale.js +405 -0
- package/dist/chart-stats.d.ts +111 -0
- package/dist/chart-stats.js +419 -0
- package/dist/chart-stream.d.ts +45 -0
- package/dist/chart-stream.js +91 -0
- package/dist/chart-summary.d.ts +27 -0
- package/dist/chart-summary.js +205 -0
- package/dist/chart-sync.svelte.d.ts +32 -0
- package/dist/chart-sync.svelte.js +23 -0
- package/dist/chart-table.d.ts +35 -0
- package/dist/chart-table.js +165 -0
- package/dist/chart-trend.d.ts +19 -0
- package/dist/chart-trend.js +61 -0
- package/dist/chart-types.d.ts +1456 -0
- package/dist/chart-types.js +6 -0
- package/dist/chart-validate.d.ts +43 -0
- package/dist/chart-validate.js +272 -0
- package/dist/chart-zoom.d.ts +40 -0
- package/dist/chart-zoom.js +145 -0
- package/dist/chart.d.ts +186 -865
- package/dist/chart.js +460 -2204
- package/dist/clipboard.d.ts +1 -0
- package/dist/clipboard.js +137 -18
- package/dist/column-resize.d.ts +3 -0
- package/dist/column-resize.js +4 -1
- package/dist/columns.js +3 -0
- package/dist/command-context.d.ts +19 -0
- package/dist/command-context.js +116 -0
- package/dist/core.d.ts +7 -0
- package/dist/createPopoverSelect.svelte.d.ts +2 -2
- package/dist/editing.d.ts +2 -8
- package/dist/editing.js +262 -33
- package/dist/fill-patterns.d.ts +0 -5
- package/dist/fill-patterns.js +56 -2
- package/dist/grid-messages.d.ts +10 -3
- package/dist/grid-messages.js +5 -0
- package/dist/history.d.ts +83 -0
- package/dist/history.js +133 -0
- package/dist/index.d.ts +24 -6
- package/dist/index.js +20 -2
- package/dist/keyboard-handlers.js +88 -31
- package/dist/keyboard.d.ts +55 -0
- package/dist/keyboard.js +132 -0
- package/dist/menus.d.ts +3 -1
- package/dist/menus.js +7 -2
- package/dist/merges.d.ts +97 -0
- package/dist/merges.js +147 -0
- package/dist/row-resize.d.ts +3 -0
- package/dist/row-resize.js +14 -0
- package/dist/scroll-sync.d.ts +1 -0
- package/dist/scroll-sync.js +24 -0
- package/dist/selection.d.ts +16 -0
- package/dist/selection.js +108 -16
- package/dist/shortcut-registry.d.ts +145 -0
- package/dist/shortcut-registry.js +44 -0
- package/dist/svgrid-wrapper.types.d.ts +213 -22
- package/dist/virtualization/virtualizer.js +38 -17
- package/package.json +6 -1
- package/src/GridMenus.svelte +44 -2
- package/src/SvChartMenu.svelte +69 -0
- package/src/SvChartPanes.svelte +113 -0
- package/src/SvChartPanes.test.ts +66 -0
- package/src/SvGrid.controller.svelte.ts +629 -172
- package/src/SvGrid.css +105 -2
- package/src/SvGrid.svelte +386 -514
- package/src/SvGrid.types.ts +188 -8
- package/src/SvGridCellEditor.svelte +17 -0
- package/src/SvGridChart.svelte +2257 -329
- package/src/SvGridChart.test.ts +1419 -1
- package/src/SvGridChart.types.ts +336 -0
- package/src/SvGridChartBuilder.svelte +323 -0
- package/src/SvGridChartPanel.svelte +221 -275
- package/src/SvGridChartPickers.svelte +417 -0
- package/src/SvGridChartView.svelte +13 -14
- package/src/SvModal.svelte +7 -1
- package/src/SvModal.test.ts +9 -0
- package/src/ai.test.ts +57 -2
- package/src/ai.ts +166 -22
- package/src/aria.d.ts +11 -0
- package/src/build-api.coverage.test.ts +184 -0
- package/src/build-api.ts +107 -18
- package/src/chart-axes.test.ts +573 -0
- package/src/chart-axes.ts +342 -0
- package/src/chart-cartesian.ts +1863 -0
- package/src/chart-decimate.test.ts +249 -0
- package/src/chart-decimate.ts +186 -0
- package/src/chart-export-pdf.test.ts +91 -0
- package/src/chart-export-pdf.ts +207 -0
- package/src/chart-export.test.ts +14 -0
- package/src/chart-export.ts +13 -3
- package/src/chart-financial.ts +208 -0
- package/src/chart-flow.ts +286 -0
- package/src/chart-format.ts +67 -0
- package/src/chart-grid.ts +179 -0
- package/src/chart-hierarchy.ts +135 -0
- package/src/chart-indicators.test.ts +443 -0
- package/src/chart-indicators.ts +384 -0
- package/src/chart-messages.test.ts +21 -0
- package/src/chart-messages.ts +184 -0
- package/src/chart-motion.test.ts +71 -0
- package/src/chart-motion.ts +148 -0
- package/src/chart-panel-messages.test.ts +29 -0
- package/src/chart-panel-messages.ts +550 -0
- package/src/chart-pivot.test.ts +54 -0
- package/src/chart-pivot.ts +225 -0
- package/src/chart-polar.ts +674 -0
- package/src/chart-samples.ts +106 -0
- package/src/chart-scale.ts +394 -0
- package/src/chart-series-types.test.ts +714 -0
- package/src/chart-stats.ts +409 -0
- package/src/chart-stream.test.ts +75 -0
- package/src/chart-stream.ts +114 -0
- package/src/chart-summary.test.ts +90 -0
- package/src/chart-summary.ts +197 -0
- package/src/chart-sync.svelte.ts +41 -0
- package/src/chart-table.test.ts +76 -0
- package/src/chart-table.ts +178 -0
- package/src/chart-trend.ts +47 -0
- package/src/chart-types.ts +1309 -0
- package/src/chart-validate.test.ts +100 -0
- package/src/chart-validate.ts +247 -0
- package/src/chart-zoom.test.ts +137 -0
- package/src/chart-zoom.ts +124 -0
- package/src/chart.coverage.test.ts +81 -0
- package/src/chart.test.ts +56 -3
- package/src/chart.ts +557 -2784
- package/src/clipboard.test.ts +151 -0
- package/src/clipboard.ts +145 -16
- package/src/column-resize.ts +7 -1
- package/src/columns.ts +2 -0
- package/src/command-context.test.ts +200 -0
- package/src/command-context.ts +137 -0
- package/src/core.ts +7 -0
- package/src/editing.test.ts +281 -3
- package/src/editing.ts +256 -38
- package/src/fill-patterns.test.ts +35 -0
- package/src/fill-patterns.ts +56 -2
- package/src/grid-messages.ts +16 -3
- package/src/history.test.ts +196 -0
- package/src/history.ts +162 -0
- package/src/icon-seam.test.ts +3 -2
- package/src/index.ts +150 -2
- package/src/keyboard-handlers.coverage.test.ts +126 -6
- package/src/keyboard-handlers.ts +91 -30
- package/src/keyboard-shortcuts.seam.test.ts +200 -0
- package/src/keyboard.test.ts +124 -1
- package/src/keyboard.ts +142 -0
- package/src/menus.test.ts +26 -0
- package/src/menus.ts +10 -3
- package/src/merges.test.ts +112 -0
- package/src/merges.ts +168 -0
- package/src/pivot.grid.test.ts +40 -5
- package/src/row-resize.test.ts +44 -0
- package/src/row-resize.ts +17 -0
- package/src/scroll-sync.test.ts +42 -1
- package/src/scroll-sync.ts +25 -0
- package/src/selection.multi-range.test.ts +34 -0
- package/src/selection.test.ts +30 -2
- package/src/selection.ts +111 -19
- package/src/shortcut-registry.test.ts +97 -0
- package/src/shortcut-registry.ts +181 -0
- package/src/svgrid-wrapper.types.ts +184 -13
- package/src/svgrid.behavior.test.ts +135 -0
- package/src/svgrid.charting.test.ts +513 -3
- package/src/svgrid.context-menu.test.ts +31 -0
- package/src/svgrid.new-features.wrapper.test.ts +10 -3
- package/src/virtualization/virtualizer.ts +39 -17
- package/dist/cdn/GridMenus-DyFA-QxA.js +0 -602
- package/dist/cdn/GridMenus-M2XP9t0_.js +0 -603
- package/dist/cdn/SvDateTimePicker-CCbDZNZB.js +0 -816
- package/dist/cdn/SvGridChart-DAb9RbRs.js +0 -1480
- package/dist/cdn/SvGridChart-Yl-IXZFO.js +0 -1481
- package/dist/cdn/SvGridChartPanel-DayQpDsR.js +0 -834
- package/dist/cdn/SvGridChartPanel-aqf8n-vJ.js +0 -810
- package/dist/cdn/SvGridChartView-CxfakxR0.js +0 -56
- package/dist/cdn/SvGridChartView-Dm_YEx9L.js +0 -55
- package/dist/cdn/chart-CKJRCPOk.js +0 -1652
|
@@ -16,13 +16,33 @@
|
|
|
16
16
|
import { cubicOut } from "svelte/easing";
|
|
17
17
|
import SvGridChart from "./SvGridChart.svelte";
|
|
18
18
|
import { downloadChartPng, downloadChartSvg, downloadChartCsv, chartCsvExportable, chartToPngBlob } from "./chart-export";
|
|
19
|
-
import type {
|
|
19
|
+
import type { ChartSelection, RowData, TableFeatures } from "./index";
|
|
20
|
+
import SvChartPanes from "./SvChartPanes.svelte";
|
|
21
|
+
import SvGridChartPickers, { chartPanelTypes } from "./SvGridChartPickers.svelte";
|
|
22
|
+
import { chartPanelMessage, resolveChartPanelMessages } from "./chart-panel-messages";
|
|
23
|
+
// The builder is a lazy chunk: a modal with a thumbnail per type, loaded
|
|
24
|
+
// the first time someone opens it.
|
|
25
|
+
let Builder = $state<typeof import("./SvGridChartBuilder.svelte").default | null>(null);
|
|
26
|
+
let builderOpen = $state(false);
|
|
27
|
+
function openBuilder() {
|
|
28
|
+
if (Builder) { builderOpen = true; return; }
|
|
29
|
+
import("./SvGridChartBuilder.svelte").then((m) => { Builder = m.default; builderOpen = true; });
|
|
30
|
+
}
|
|
20
31
|
import type { SvGridController } from "./SvGrid.controller.svelte";
|
|
21
32
|
|
|
22
33
|
let { ctrl }: { ctrl: SvGridController<TFeatures, TData> } = $props();
|
|
23
34
|
|
|
35
|
+
/** The panel's strings, from `localization.text`; unset keys stay English. */
|
|
36
|
+
const m = $derived(resolveChartPanelMessages(ctrl.localizationText));
|
|
24
37
|
const config = $derived(ctrl.chartingConfig);
|
|
25
38
|
const cfg = $derived(ctrl.chartCfg);
|
|
39
|
+
/** Legend placement from the builder's format; the default is below. */
|
|
40
|
+
const legendProp = $derived(ctrl.chartFormat?.legend === undefined ? true : ctrl.chartFormat.legend);
|
|
41
|
+
const dataLabelsProp = $derived(
|
|
42
|
+
ctrl.chartFormat?.dataLabels === undefined ? ctrl.chartDataLabels : ctrl.chartFormat.dataLabels === null ? false : { placement: ctrl.chartFormat.dataLabels },
|
|
43
|
+
);
|
|
44
|
+
/** The crosshair's axis pills, from the builder's format; on by default. */
|
|
45
|
+
const crosshairLabelsProp = $derived(ctrl.chartFormat?.crosshairLabels !== false);
|
|
26
46
|
const isCustom = $derived(ctrl.chartIsCustom);
|
|
27
47
|
const position = $derived(config?.position ?? "bottom");
|
|
28
48
|
const crossFilter = $derived(config?.crossFilter !== false);
|
|
@@ -115,124 +135,13 @@
|
|
|
115
135
|
: `height:${size}px`,
|
|
116
136
|
);
|
|
117
137
|
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
let bodyW = $state(0);
|
|
123
|
-
let bodyH = $state(0);
|
|
124
|
-
const seriesCount = $derived(spec ? (spec.type === "pie" ? spec.categories.length : spec.series.length) : 0);
|
|
125
|
-
const reserve = $derived((seriesCount > 1 ? 36 : 0) + (cfg?.export === true ? 30 : 0));
|
|
126
|
-
const chartW = $derived(bodyW > 24 ? bodyW - 8 : undefined);
|
|
127
|
-
const chartH = $derived(bodyH > 24 ? Math.max(120, bodyH - 8 - reserve) : undefined);
|
|
138
|
+
// The docked panel is a fixed-size container, and the chart sizes itself
|
|
139
|
+
// to it: measures the body and subtracts the chart's own toolbar,
|
|
140
|
+
// legend and brush. This used to be a reserve heuristic here (36px for a
|
|
141
|
+
// legend, 30px for the toolbar) that was wrong whenever the legend wrapped.
|
|
128
142
|
|
|
129
|
-
/**
|
|
130
|
-
|
|
131
|
-
*
|
|
132
|
-
* This used to be four entries while the engine drew fifteen, so nine types
|
|
133
|
-
* were reachable only by hand-writing a spec - and `charting.defaultType`
|
|
134
|
-
* accepted them all, which meant setting one left the picker showing
|
|
135
|
-
* something else entirely.
|
|
136
|
-
*
|
|
137
|
-
* `needs` gates on COLUMN SHAPE only, never on the data. Column shape is
|
|
138
|
-
* stable while the user filters; row counts are not, and a picker whose
|
|
139
|
-
* options vanish mid-session because a filter emptied a group is worse than
|
|
140
|
-
* one that explains itself. Data-level problems get a sentence instead, see
|
|
141
|
-
* `typeIssue` below.
|
|
142
|
-
*/
|
|
143
|
-
type TypeDef = {
|
|
144
|
-
value: ChartType;
|
|
145
|
-
label: string;
|
|
146
|
-
group: string;
|
|
147
|
-
needs?: (c: { dims: unknown[]; measures: unknown[]; dates: unknown[] }) => boolean;
|
|
148
|
-
};
|
|
149
|
-
const TYPES: TypeDef[] = [
|
|
150
|
-
{ value: "bar", label: "Bar", group: "Compare" },
|
|
151
|
-
{ value: "line", label: "Line", group: "Compare" },
|
|
152
|
-
{ value: "area", label: "Area", group: "Compare" },
|
|
153
|
-
{ value: "pie", label: "Pie", group: "Part of a whole" },
|
|
154
|
-
{ value: "treemap", label: "Tree map", group: "Part of a whole" },
|
|
155
|
-
{ value: "funnel", label: "Funnel", group: "Flow" },
|
|
156
|
-
{ value: "waterfall", label: "Waterfall", group: "Flow" },
|
|
157
|
-
{ value: "sankey", label: "Sankey", group: "Flow", needs: (c) => c.dims.length >= 2 },
|
|
158
|
-
{ value: "radar", label: "Radar", group: "Distribution" },
|
|
159
|
-
{ value: "heatmap", label: "Heat map", group: "Distribution", needs: (c) => c.dims.length >= 2 },
|
|
160
|
-
{ value: "scatter", label: "Scatter", group: "Distribution", needs: (c) => c.measures.length >= 2 },
|
|
161
|
-
{ value: "boxplot", label: "Box plot", group: "Distribution" },
|
|
162
|
-
{ value: "gauge", label: "Gauge", group: "Single value" },
|
|
163
|
-
{ value: "calendar", label: "Calendar", group: "Over time", needs: (c) => c.dates.length >= 1 },
|
|
164
|
-
];
|
|
165
|
-
const availableTypes = $derived(TYPES.filter((t) => !t.needs || t.needs(columns)));
|
|
166
|
-
|
|
167
|
-
// Which controls this type can actually use. Allow-lists rather than the old
|
|
168
|
-
// "not pie" guards: with thirteen types, "everything except pie" hands a
|
|
169
|
-
// gauge a Stacked checkbox.
|
|
170
|
-
const CARTESIAN: ChartType[] = ["bar", "line", "area"];
|
|
171
|
-
|
|
172
|
-
/** Named series palettes. "Theme" defers to --sg-chart-1..8 and the defaults. */
|
|
173
|
-
const PALETTES: Array<{ key: string; label: string; colors: string[] | null }> = [
|
|
174
|
-
{ key: "theme", label: "Theme", colors: null },
|
|
175
|
-
{ key: "ocean", label: "Ocean", colors: ["#0ea5e9", "#14b8a6", "#6366f1", "#8b5cf6", "#06b6d4"] },
|
|
176
|
-
{ key: "sunset", label: "Sunset", colors: ["#f59e0b", "#ef4444", "#ec4899", "#8b5cf6", "#f97316"] },
|
|
177
|
-
{ key: "forest", label: "Forest", colors: ["#16a34a", "#65a30d", "#0d9488", "#4d7c0f", "#0891b2"] },
|
|
178
|
-
{ key: "slate", label: "Slate", colors: ["#475569", "#0ea5e9", "#64748b", "#38bdf8", "#94a3b8"] },
|
|
179
|
-
];
|
|
180
|
-
const activePalette = $derived(
|
|
181
|
-
PALETTES.find((x) => x.colors && ctrl.chartPalette && x.colors.join() === ctrl.chartPalette.join())?.key ??
|
|
182
|
-
"theme",
|
|
183
|
-
);
|
|
184
|
-
const wantsHorizontal = $derived(ctrl.chartType === "bar");
|
|
185
|
-
const wantsDonut = $derived(ctrl.chartType === "pie");
|
|
186
|
-
const wantsGroupBy = $derived(ctrl.chartType !== "gauge");
|
|
187
|
-
const wantsSplitBy = $derived(
|
|
188
|
-
columns.dims.length > 1 &&
|
|
189
|
-
!["pie", "gauge", "waterfall", "funnel", "calendar"].includes(ctrl.chartType),
|
|
190
|
-
);
|
|
191
|
-
const wantsStacked = $derived(CARTESIAN.includes(ctrl.chartType));
|
|
192
|
-
const wantsLogScale = $derived(
|
|
193
|
-
CARTESIAN.includes(ctrl.chartType) || ctrl.chartType === "scatter" || ctrl.chartType === "boxplot",
|
|
194
|
-
);
|
|
195
|
-
const wantsDataLabels = $derived(
|
|
196
|
-
!["gauge", "sankey", "calendar", "treemap", "scatter", "boxplot"].includes(ctrl.chartType),
|
|
197
|
-
);
|
|
198
|
-
const wantsSecondMeasure = $derived(ctrl.chartType === "scatter");
|
|
199
|
-
/**
|
|
200
|
-
* A box plot is the one type that reduces its groups itself: the whole point
|
|
201
|
-
* is the spread of the sample, so `sum | avg | count` has nothing to say
|
|
202
|
-
* about it. Showing an Aggregate select that changes nothing is worse than
|
|
203
|
-
* not showing one.
|
|
204
|
-
*/
|
|
205
|
-
const wantsReduce = $derived(!wantsSecondMeasure && ctrl.chartType !== "boxplot");
|
|
206
|
-
const typeGroups = $derived([...new Set(availableTypes.map((t) => t.group))]);
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Why the current type cannot draw anything useful yet, as a sentence.
|
|
210
|
-
*
|
|
211
|
-
* The alternative - removing the option - means a chart can disappear from
|
|
212
|
-
* under the user when they filter. Saying what is missing keeps the picker
|
|
213
|
-
* honest without making it unstable.
|
|
214
|
-
*/
|
|
215
|
-
const typeIssue = $derived.by<string | null>(() => {
|
|
216
|
-
const t = ctrl.chartType;
|
|
217
|
-
const cats = ctrl.chartSpec?.categories.length ?? 0;
|
|
218
|
-
if (t === "heatmap" && !ctrl.chartSeriesId && columns.dims.length > 1)
|
|
219
|
-
return "Heat map needs a Split by column for its rows.";
|
|
220
|
-
if (t === "sankey" && !ctrl.chartSeriesId)
|
|
221
|
-
return "Sankey needs a Split by column: Group by is the source, Split by the target.";
|
|
222
|
-
if (t === "radar" && cats > 0 && cats < 3) return "Radar needs at least 3 groups.";
|
|
223
|
-
if (t === "funnel" && cats === 1) return "Funnel needs at least 2 stages.";
|
|
224
|
-
return null;
|
|
225
|
-
});
|
|
226
|
-
const REDUCERS = [
|
|
227
|
-
{ value: "sum", label: "Sum" },
|
|
228
|
-
{ value: "avg", label: "Average" },
|
|
229
|
-
{ value: "count", label: "Count" },
|
|
230
|
-
] as const;
|
|
231
|
-
const FORMATS = [
|
|
232
|
-
{ value: "number", label: "Number" },
|
|
233
|
-
{ value: "currency", label: "Currency" },
|
|
234
|
-
{ value: "percent", label: "Percent" },
|
|
235
|
-
] as const;
|
|
143
|
+
/** The types the builder's gallery shows for the current columns. */
|
|
144
|
+
const availableTypes = $derived(chartPanelTypes(columns, m));
|
|
236
145
|
|
|
237
146
|
function onChartSelect(sel: ChartSelection) {
|
|
238
147
|
if (crossFilter && sel.category != null) ctrl.applyChartCrossFilter(String(sel.category));
|
|
@@ -260,6 +169,20 @@
|
|
|
260
169
|
if (spec) downloadChartCsv(spec, "chart.csv");
|
|
261
170
|
exportOpen = false;
|
|
262
171
|
}
|
|
172
|
+
async function exportPdf() {
|
|
173
|
+
const el = svgEl();
|
|
174
|
+
exportOpen = false;
|
|
175
|
+
if (!el) return;
|
|
176
|
+
const m = await import("./chart-export-pdf");
|
|
177
|
+
await m.downloadChartPdf(el, "chart.pdf", { title: spec?.title ?? ctrl.charts[ctrl.activeChartIndex]?.title });
|
|
178
|
+
}
|
|
179
|
+
async function printChart() {
|
|
180
|
+
const el = svgEl();
|
|
181
|
+
exportOpen = false;
|
|
182
|
+
if (!el) return;
|
|
183
|
+
const m = await import("./chart-export-pdf");
|
|
184
|
+
m.printChart(el, { title: spec?.title ?? ctrl.charts[ctrl.activeChartIndex]?.title });
|
|
185
|
+
}
|
|
263
186
|
async function copyImage() {
|
|
264
187
|
const el = svgEl();
|
|
265
188
|
exportOpen = false;
|
|
@@ -273,17 +196,77 @@
|
|
|
273
196
|
/* clipboard blocked - no-op */
|
|
274
197
|
}
|
|
275
198
|
}
|
|
199
|
+
// A popover closes on a click outside and on Escape, and Escape hands
|
|
200
|
+
// focus back to the button that opened it. Without the key the export
|
|
201
|
+
// menu and the saved-charts dialog stayed open until something was
|
|
202
|
+
// clicked, which the keyboard could not do.
|
|
203
|
+
let exportBtn = $state<HTMLButtonElement | null>(null);
|
|
204
|
+
let savedBtn = $state<HTMLButtonElement | null>(null);
|
|
205
|
+
function dismissable(scope: string, close: () => void, trigger: () => HTMLButtonElement | null) {
|
|
206
|
+
const onDown = (e: PointerEvent) => {
|
|
207
|
+
if (!(e.target as HTMLElement)?.closest(scope)) close();
|
|
208
|
+
};
|
|
209
|
+
const onKey = (e: KeyboardEvent) => {
|
|
210
|
+
if (e.key !== "Escape") return;
|
|
211
|
+
e.stopPropagation();
|
|
212
|
+
close();
|
|
213
|
+
trigger()?.focus();
|
|
214
|
+
};
|
|
215
|
+
window.addEventListener("pointerdown", onDown);
|
|
216
|
+
window.addEventListener("keydown", onKey, true);
|
|
217
|
+
return () => {
|
|
218
|
+
window.removeEventListener("pointerdown", onDown);
|
|
219
|
+
window.removeEventListener("keydown", onKey, true);
|
|
220
|
+
};
|
|
221
|
+
}
|
|
276
222
|
$effect(() => {
|
|
277
223
|
if (!exportOpen) return;
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
224
|
+
return dismissable(".sv-grid-chart-export", () => (exportOpen = false), () => exportBtn);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// ---- Saved charts: name the active tab, apply or remove a saved one -----
|
|
228
|
+
let savedOpen = $state(false);
|
|
229
|
+
let saveName = $state("");
|
|
230
|
+
function saveChart() {
|
|
231
|
+
const name = saveName.trim();
|
|
232
|
+
if (!name) return;
|
|
233
|
+
ctrl.saveChart(name);
|
|
234
|
+
saveName = "";
|
|
235
|
+
}
|
|
236
|
+
$effect(() => {
|
|
237
|
+
if (!savedOpen) return;
|
|
238
|
+
return dismissable(".sv-grid-chart-saved", () => (savedOpen = false), () => savedBtn);
|
|
283
239
|
});
|
|
284
240
|
|
|
241
|
+
// ---- Describe: the chart's summary sentence to the clipboard ------------
|
|
242
|
+
async function describeChart() {
|
|
243
|
+
exportOpen = false;
|
|
244
|
+
if (!spec) return;
|
|
245
|
+
const { chartSummary } = await import("./chart-summary");
|
|
246
|
+
const text = chartSummary(spec);
|
|
247
|
+
try { await navigator.clipboard?.writeText(text); } catch { /* no clipboard */ }
|
|
248
|
+
aiRationale = text;
|
|
249
|
+
}
|
|
250
|
+
|
|
285
251
|
// ---- AI "chart this" (enterprise-registered handler) -------------------
|
|
286
252
|
const aiEnabled = $derived(!!ctrl.chartAiHandler);
|
|
253
|
+
const explainEnabled = $derived(!!ctrl.chartExplainHandler);
|
|
254
|
+
async function explainChart() {
|
|
255
|
+
const handler = ctrl.chartExplainHandler;
|
|
256
|
+
if (!handler || aiBusy) return;
|
|
257
|
+
aiBusy = true;
|
|
258
|
+
aiError = "";
|
|
259
|
+
aiRationale = "";
|
|
260
|
+
try {
|
|
261
|
+
const out = await handler();
|
|
262
|
+
aiRationale = out ? [out.summary, ...out.insights].filter(Boolean).join(" ") : "";
|
|
263
|
+
if (!out) aiError = m.chartAiNoExplanation;
|
|
264
|
+
} catch (e) {
|
|
265
|
+
aiError = e instanceof Error ? e.message : m.chartAiFailed;
|
|
266
|
+
} finally {
|
|
267
|
+
aiBusy = false;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
287
270
|
let aiOpen = $state(false);
|
|
288
271
|
let aiPrompt = $state("");
|
|
289
272
|
let aiBusy = $state(false);
|
|
@@ -302,10 +285,10 @@
|
|
|
302
285
|
ctrl.applyChartConfig(cfg as Parameters<typeof ctrl.applyChartConfig>[0]);
|
|
303
286
|
aiRationale = typeof cfg.rationale === "string" ? cfg.rationale : "";
|
|
304
287
|
} else {
|
|
305
|
-
aiError =
|
|
288
|
+
aiError = m.chartAiNoChart;
|
|
306
289
|
}
|
|
307
290
|
} catch (e) {
|
|
308
|
-
aiError = e instanceof Error ? e.message :
|
|
291
|
+
aiError = e instanceof Error ? e.message : m.chartAiFailed;
|
|
309
292
|
} finally {
|
|
310
293
|
aiBusy = false;
|
|
311
294
|
}
|
|
@@ -341,7 +324,7 @@
|
|
|
341
324
|
class:is-floating={floating}
|
|
342
325
|
class:is-maximized={floating && maximized}
|
|
343
326
|
style={floatStyle}
|
|
344
|
-
aria-label=
|
|
327
|
+
aria-label={m.chartPanelTitle}
|
|
345
328
|
transition:fly={{
|
|
346
329
|
x: !floating && position === "right" ? size : 0,
|
|
347
330
|
y: !floating && position === "bottom" ? size : 0,
|
|
@@ -357,7 +340,7 @@
|
|
|
357
340
|
onpointerdown={startResize}
|
|
358
341
|
role="separator"
|
|
359
342
|
aria-orientation={position === "right" ? "vertical" : "horizontal"}
|
|
360
|
-
aria-label=
|
|
343
|
+
aria-label={m.chartResize}
|
|
361
344
|
></div>
|
|
362
345
|
{/if}
|
|
363
346
|
|
|
@@ -371,13 +354,13 @@
|
|
|
371
354
|
<svg class="sv-grid-chart-title-icon" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
372
355
|
<line x1="12" y1="20" x2="12" y2="10" /><line x1="18" y1="20" x2="18" y2="4" /><line x1="6" y1="20" x2="6" y2="16" />
|
|
373
356
|
</svg>
|
|
374
|
-
<span class="sv-grid-chart-title">
|
|
357
|
+
<span class="sv-grid-chart-title">{m.chartPanelTitle}</span>
|
|
375
358
|
<div class="sv-grid-chart-actions">
|
|
376
359
|
{#if crossFilter && !isCustom}
|
|
377
|
-
<button type="button" class="sv-grid-chart-btn" onclick={() => ctrl.clearChartCrossFilter()}>
|
|
360
|
+
<button type="button" class="sv-grid-chart-btn" onclick={() => ctrl.clearChartCrossFilter()}>{m.chartClearFilter}</button>
|
|
378
361
|
{/if}
|
|
379
362
|
<div class="sv-grid-chart-export">
|
|
380
|
-
<button type="button" class="sv-grid-chart-icon-btn" aria-label=
|
|
363
|
+
<button type="button" class="sv-grid-chart-icon-btn" aria-label={m.chartExportLabel} title={m.chartExport} aria-expanded={exportOpen} bind:this={exportBtn} onclick={() => (exportOpen = !exportOpen)}>
|
|
381
364
|
{#if copied}
|
|
382
365
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="20 6 9 17 4 12" /></svg>
|
|
383
366
|
{:else}
|
|
@@ -386,45 +369,92 @@
|
|
|
386
369
|
</button>
|
|
387
370
|
{#if exportOpen}
|
|
388
371
|
<div class="sv-grid-chart-export-menu" role="menu">
|
|
389
|
-
<button type="button" role="menuitem" onclick={exportPng}>
|
|
390
|
-
<button type="button" role="menuitem" onclick={exportSvg}>
|
|
391
|
-
<button type="button" role="menuitem"
|
|
392
|
-
<button type="button" role="menuitem" onclick={
|
|
372
|
+
<button type="button" role="menuitem" onclick={exportPng}>{m.chartExportPng}</button>
|
|
373
|
+
<button type="button" role="menuitem" onclick={exportSvg}>{m.chartExportSvg}</button>
|
|
374
|
+
<button type="button" role="menuitem" onclick={exportPdf}>{m.chartExportPdf}</button>
|
|
375
|
+
<button type="button" role="menuitem" disabled={!csvExportable} onclick={exportCsv}>{m.chartExportCsv}</button>
|
|
376
|
+
<button type="button" role="menuitem" onclick={copyImage}>{m.chartExportCopy}</button>
|
|
377
|
+
<button type="button" role="menuitem" onclick={printChart}>{m.chartExportPrint}</button>
|
|
378
|
+
<button type="button" role="menuitem" onclick={describeChart}>{m.chartExportDescribe}</button>
|
|
393
379
|
</div>
|
|
394
380
|
{/if}
|
|
395
381
|
</div>
|
|
396
382
|
{#if aiEnabled && !isCustom}
|
|
397
|
-
<button type="button" class="sv-grid-chart-btn sv-grid-chart-ai-btn" class:is-active={aiOpen} aria-label=
|
|
383
|
+
<button type="button" class="sv-grid-chart-btn sv-grid-chart-ai-btn" class:is-active={aiOpen} aria-label={m.chartAiLabel} title={m.chartAiLabel} aria-expanded={aiOpen} onclick={() => (aiOpen = !aiOpen)}>
|
|
398
384
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2l1.9 4.6L18.5 8.5 13.9 10.4 12 15l-1.9-4.6L5.5 8.5l4.6-1.9L12 2zm6 11l1 2.4 2.4 1-2.4 1L18 21l-1-2.6-2.4-1 2.4-1L18 13zM6 14l.8 2 2 .8-2 .8L6 20l-.8-2.4-2-.8 2-.8L6 14z" /></svg>
|
|
399
|
-
|
|
385
|
+
{m.chartAi}
|
|
400
386
|
</button>
|
|
401
387
|
{/if}
|
|
402
388
|
{#if !isCustom}
|
|
403
|
-
<button type="button" class="sv-grid-chart-
|
|
389
|
+
<button type="button" class="sv-grid-chart-btn sv-grid-chart-build-btn" aria-label={m.chartBuildLabel} title={m.chartBuildTitle} aria-haspopup="dialog" onclick={openBuilder}>
|
|
390
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="7" height="7" rx="1" /><rect x="14" y="3" width="7" height="7" rx="1" /><rect x="3" y="14" width="7" height="7" rx="1" /><rect x="14" y="14" width="7" height="7" rx="1" /></svg>
|
|
391
|
+
{m.chartBuild}
|
|
392
|
+
</button>
|
|
393
|
+
<div class="sv-grid-chart-export sv-grid-chart-saved">
|
|
394
|
+
<button type="button" class="sv-grid-chart-icon-btn sv-grid-chart-saved-btn" aria-label={m.chartSaveLabel} title={m.chartSavedTitle} aria-expanded={savedOpen} aria-haspopup="dialog" bind:this={savedBtn} onclick={() => (savedOpen = !savedOpen)}>
|
|
395
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" /><polyline points="17 21 17 13 7 13 7 21" /><polyline points="7 3 7 8 15 8" /></svg>
|
|
396
|
+
</button>
|
|
397
|
+
{#if savedOpen}
|
|
398
|
+
<div class="sv-grid-chart-export-menu sv-grid-chart-saved-menu" role="dialog" aria-label={m.chartSavedTitle}>
|
|
399
|
+
<form class="sv-grid-chart-saved-row" onsubmit={(e) => { e.preventDefault(); saveChart(); }}>
|
|
400
|
+
<input class="sv-grid-chart-saved-name" type="text" aria-label={m.chartSaveName} placeholder={m.chartSaveNamePlaceholder} bind:value={saveName} />
|
|
401
|
+
<button type="submit" class="sv-grid-chart-btn" disabled={!saveName.trim()}>{m.chartSave}</button>
|
|
402
|
+
</form>
|
|
403
|
+
{#if ctrl.savedCharts.length}
|
|
404
|
+
<ul class="sv-grid-chart-saved-list">
|
|
405
|
+
{#each ctrl.savedCharts as sc (sc.name)}
|
|
406
|
+
<li class="sv-grid-chart-saved-item">
|
|
407
|
+
<button type="button" class="sv-grid-chart-saved-apply" title={m.chartSaveApply} onclick={() => { ctrl.applySavedChart(sc.name); savedOpen = false; }}>{sc.name}</button>
|
|
408
|
+
<button type="button" class="sv-grid-chart-saved-x" aria-label={`${m.chartSaveRemove}: ${sc.name}`} title={m.chartSaveRemove} onclick={() => ctrl.removeSavedChart(sc.name)}>×</button>
|
|
409
|
+
</li>
|
|
410
|
+
{/each}
|
|
411
|
+
</ul>
|
|
412
|
+
{:else}
|
|
413
|
+
<p class="sv-grid-chart-saved-empty">{m.chartSavedEmpty}</p>
|
|
414
|
+
{/if}
|
|
415
|
+
</div>
|
|
416
|
+
{/if}
|
|
417
|
+
</div>
|
|
418
|
+
<button
|
|
419
|
+
type="button"
|
|
420
|
+
class="sv-grid-chart-icon-btn sv-grid-chart-link-btn"
|
|
421
|
+
class:is-unlinked={!!ctrl.chartFrozen}
|
|
422
|
+
aria-label={ctrl.chartFrozen ? m.chartLinkBack : m.chartUnlink}
|
|
423
|
+
aria-pressed={!!ctrl.chartFrozen}
|
|
424
|
+
title={ctrl.chartFrozen ? m.chartUnlinkedTitle : m.chartLinkedTitle}
|
|
425
|
+
onclick={() => (ctrl.chartFrozen ? ctrl.unfreezeChart() : ctrl.freezeChart())}
|
|
426
|
+
>
|
|
427
|
+
{#if ctrl.chartFrozen}
|
|
428
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18.84 12.25l1.72-1.71a5 5 0 0 0-7.07-7.07l-1.72 1.71" /><path d="M5.17 11.75l-1.71 1.71a5 5 0 0 0 7.07 7.07l1.71-1.71" /><line x1="8" y1="2" x2="8" y2="5" /><line x1="2" y1="8" x2="5" y2="8" /><line x1="16" y1="19" x2="16" y2="22" /><line x1="19" y1="16" x2="22" y2="16" /></svg>
|
|
429
|
+
{:else}
|
|
430
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" /><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" /></svg>
|
|
431
|
+
{/if}
|
|
432
|
+
</button>
|
|
433
|
+
<button type="button" class="sv-grid-chart-icon-btn" aria-label={m.chartAdd} title={m.chartAdd} onclick={() => ctrl.addChart()}>
|
|
404
434
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="12" y1="5" x2="12" y2="19" /><line x1="5" y1="12" x2="19" y2="12" /></svg>
|
|
405
435
|
</button>
|
|
406
436
|
{/if}
|
|
407
437
|
{#if floating}
|
|
408
|
-
<button type="button" class="sv-grid-chart-icon-btn" aria-label={maximized ?
|
|
438
|
+
<button type="button" class="sv-grid-chart-icon-btn" aria-label={maximized ? m.chartRestore : m.chartMaximize} title={maximized ? m.chartRestore : m.chartMaximize} onclick={toggleMax}>
|
|
409
439
|
{#if maximized}
|
|
410
440
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 3v3a2 2 0 0 1-2 2H3" /><path d="M21 8h-3a2 2 0 0 1-2-2V3" /><path d="M3 16h3a2 2 0 0 1 2 2v3" /><path d="M16 21v-3a2 2 0 0 1 2-2h3" /></svg>
|
|
411
441
|
{:else}
|
|
412
442
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 3H5a2 2 0 0 0-2 2v3" /><path d="M21 8V5a2 2 0 0 0-2-2h-3" /><path d="M3 16v3a2 2 0 0 0 2 2h3" /><path d="M16 21h3a2 2 0 0 0 2-2v-3" /></svg>
|
|
413
443
|
{/if}
|
|
414
444
|
</button>
|
|
415
|
-
<button type="button" class="sv-grid-chart-icon-btn" aria-label=
|
|
445
|
+
<button type="button" class="sv-grid-chart-icon-btn" aria-label={m.chartDockLabel} title={m.chartDock} onclick={dockBack}>
|
|
416
446
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2" /><line x1="15" y1="3" x2="15" y2="21" /></svg>
|
|
417
447
|
</button>
|
|
418
448
|
{:else}
|
|
419
|
-
<button type="button" class="sv-grid-chart-icon-btn" aria-label=
|
|
449
|
+
<button type="button" class="sv-grid-chart-icon-btn" aria-label={m.chartPopOutLabel} title={m.chartPopOut} onclick={popOut}>
|
|
420
450
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 3h6v6" /><path d="M10 14 21 3" /><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></svg>
|
|
421
451
|
</button>
|
|
422
452
|
{/if}
|
|
423
|
-
<button type="button" class="sv-grid-chart-close" aria-label=
|
|
453
|
+
<button type="button" class="sv-grid-chart-close" aria-label={m.chartClose} onclick={() => (ctrl.chartPanelOpen = false)}>×</button>
|
|
424
454
|
</div>
|
|
425
455
|
</div>
|
|
426
456
|
{#if !isCustom && ctrl.charts.length > 1}
|
|
427
|
-
<div class="sv-grid-chart-tabs" role="tablist" aria-label=
|
|
457
|
+
<div class="sv-grid-chart-tabs" role="tablist" aria-label={m.chartTabs}>
|
|
428
458
|
{#each ctrl.charts as c, i (c.id)}
|
|
429
459
|
<div class="sv-grid-chart-tab" class:is-active={i === ctrl.activeChartIndex}>
|
|
430
460
|
<button
|
|
@@ -437,8 +467,8 @@
|
|
|
437
467
|
<button
|
|
438
468
|
type="button"
|
|
439
469
|
class="sv-grid-chart-tab-x"
|
|
440
|
-
aria-label={
|
|
441
|
-
title=
|
|
470
|
+
aria-label={chartPanelMessage(m.chartRemoveTab, { title: c.title })}
|
|
471
|
+
title={m.chartRemove}
|
|
442
472
|
onclick={() => ctrl.removeChart(i)}
|
|
443
473
|
>×</button>
|
|
444
474
|
</div>
|
|
@@ -457,13 +487,16 @@
|
|
|
457
487
|
<input
|
|
458
488
|
class="sv-grid-chart-ai-input"
|
|
459
489
|
type="text"
|
|
460
|
-
placeholder=
|
|
490
|
+
placeholder={m.chartAiPlaceholder}
|
|
461
491
|
bind:value={aiPrompt}
|
|
462
492
|
disabled={aiBusy}
|
|
463
493
|
/>
|
|
464
494
|
<button type="submit" class="sv-grid-chart-btn" disabled={aiBusy || !aiPrompt.trim()}>
|
|
465
|
-
{aiBusy ?
|
|
495
|
+
{aiBusy ? m.chartAiThinking : m.chartAiRun}
|
|
466
496
|
</button>
|
|
497
|
+
{#if explainEnabled}
|
|
498
|
+
<button type="button" class="sv-grid-chart-btn sv-grid-chart-explain-btn" disabled={aiBusy || !spec} onclick={explainChart} title={m.chartAiExplainTitle}>{m.chartAiExplain}</button>
|
|
499
|
+
{/if}
|
|
467
500
|
</form>
|
|
468
501
|
{#if aiError}
|
|
469
502
|
<p class="sv-grid-chart-ai-msg is-error">{aiError}</p>
|
|
@@ -472,162 +505,75 @@
|
|
|
472
505
|
{/if}
|
|
473
506
|
</div>
|
|
474
507
|
{/if}
|
|
475
|
-
|
|
476
|
-
{
|
|
477
|
-
|
|
478
|
-
<span class="sv-grid-chart-ctl-lbl">Type</span>
|
|
479
|
-
<select value={ctrl.chartType} onchange={(e) => (ctrl.chartType = e.currentTarget.value as ChartType)}>
|
|
480
|
-
<!-- Grouped: a flat thirteen-item list is a wall. optgroup is
|
|
481
|
-
native, so this costs no new component. -->
|
|
482
|
-
{#each typeGroups as g (g)}
|
|
483
|
-
<optgroup label={g}>
|
|
484
|
-
{#each availableTypes.filter((t) => t.group === g) as t (t.value)}
|
|
485
|
-
<option value={t.value}>{t.label}</option>
|
|
486
|
-
{/each}
|
|
487
|
-
</optgroup>
|
|
488
|
-
{/each}
|
|
489
|
-
</select>
|
|
490
|
-
</label>
|
|
491
|
-
{#if wantsGroupBy}
|
|
492
|
-
<label class="sv-grid-chart-ctl">
|
|
493
|
-
<span class="sv-grid-chart-ctl-lbl">{ctrl.chartType === "sankey" ? "From" : ctrl.chartType === "heatmap" ? "Columns" : "Group by"}</span>
|
|
494
|
-
<select value={ctrl.chartDimensionId ?? ""} onchange={(e) => (ctrl.chartDimensionId = e.currentTarget.value || null)}>
|
|
495
|
-
{#each columns.dims as d (d.id)}<option value={d.id}>{d.label}</option>{/each}
|
|
496
|
-
</select>
|
|
497
|
-
</label>
|
|
498
|
-
{/if}
|
|
499
|
-
{#if wantsSplitBy}
|
|
500
|
-
<label class="sv-grid-chart-ctl">
|
|
501
|
-
<span class="sv-grid-chart-ctl-lbl">{ctrl.chartType === "sankey" ? "To" : ctrl.chartType === "heatmap" ? "Rows" : "Split by"}</span>
|
|
502
|
-
<select value={ctrl.chartSeriesId ?? ""} onchange={(e) => (ctrl.chartSeriesId = e.currentTarget.value || null)}>
|
|
503
|
-
<option value="">(none)</option>
|
|
504
|
-
{#each columns.dims as d (d.id)}<option value={d.id}>{d.label}</option>{/each}
|
|
505
|
-
</select>
|
|
506
|
-
</label>
|
|
507
|
-
{/if}
|
|
508
|
-
<label class="sv-grid-chart-ctl">
|
|
509
|
-
<span class="sv-grid-chart-ctl-lbl">{wantsSecondMeasure ? "X" : ctrl.chartType === "gauge" ? "Metric" : "Value"}</span>
|
|
510
|
-
<select value={ctrl.chartMeasureId ?? ""} onchange={(e) => (ctrl.chartMeasureId = e.currentTarget.value || null)}>
|
|
511
|
-
{#each columns.measures as m (m.id)}<option value={m.id}>{m.label}</option>{/each}
|
|
512
|
-
</select>
|
|
513
|
-
</label>
|
|
514
|
-
{#if wantsSecondMeasure}
|
|
515
|
-
<label class="sv-grid-chart-ctl">
|
|
516
|
-
<span class="sv-grid-chart-ctl-lbl">Y</span>
|
|
517
|
-
<select value={ctrl.effectiveChartMeasure2Id ?? ""} onchange={(e) => (ctrl.chartMeasure2Id = e.currentTarget.value || null)}>
|
|
518
|
-
{#each columns.measures as m (m.id)}<option value={m.id}>{m.label}</option>{/each}
|
|
519
|
-
</select>
|
|
520
|
-
</label>
|
|
521
|
-
{/if}
|
|
522
|
-
{#if wantsReduce}
|
|
523
|
-
<label class="sv-grid-chart-ctl">
|
|
524
|
-
<span class="sv-grid-chart-ctl-lbl">Aggregate</span>
|
|
525
|
-
<select value={ctrl.chartReduce} onchange={(e) => (ctrl.chartReduce = e.currentTarget.value as "sum" | "avg" | "count")}>
|
|
526
|
-
{#each REDUCERS as r (r.value)}<option value={r.value}>{r.label}</option>{/each}
|
|
527
|
-
</select>
|
|
528
|
-
</label>
|
|
529
|
-
{/if}
|
|
530
|
-
<label class="sv-grid-chart-ctl">
|
|
531
|
-
<span class="sv-grid-chart-ctl-lbl">Format</span>
|
|
532
|
-
<select value={ctrl.chartValueFormat} onchange={(e) => (ctrl.chartValueFormat = e.currentTarget.value as "number" | "currency" | "percent")}>
|
|
533
|
-
{#each FORMATS as f (f.value)}<option value={f.value}>{f.label}</option>{/each}
|
|
534
|
-
</select>
|
|
535
|
-
</label>
|
|
536
|
-
{#if wantsStacked}
|
|
537
|
-
<label class="sv-grid-chart-toggle">
|
|
538
|
-
<input type="checkbox" checked={ctrl.chartStacked} onchange={(e) => (ctrl.chartStacked = e.currentTarget.checked)} />
|
|
539
|
-
Stacked
|
|
540
|
-
</label>
|
|
541
|
-
<label class="sv-grid-chart-toggle" title="Normalise each category to 100 percent">
|
|
542
|
-
<input type="checkbox" checked={ctrl.chartStacked100} onchange={(e) => (ctrl.chartStacked100 = e.currentTarget.checked)} />
|
|
543
|
-
100%
|
|
544
|
-
</label>
|
|
545
|
-
{/if}
|
|
546
|
-
{#if wantsHorizontal}
|
|
547
|
-
<label class="sv-grid-chart-toggle" title="Bars grow rightwards - suits long category labels">
|
|
548
|
-
<input
|
|
549
|
-
type="checkbox"
|
|
550
|
-
checked={ctrl.chartOrientation === "horizontal"}
|
|
551
|
-
onchange={(e) => (ctrl.chartOrientation = e.currentTarget.checked ? "horizontal" : "vertical")}
|
|
552
|
-
/>
|
|
553
|
-
Horizontal
|
|
554
|
-
</label>
|
|
555
|
-
{/if}
|
|
556
|
-
{#if wantsDonut}
|
|
557
|
-
<label class="sv-grid-chart-toggle">
|
|
558
|
-
<input type="checkbox" checked={ctrl.chartDonut} onchange={(e) => (ctrl.chartDonut = e.currentTarget.checked)} />
|
|
559
|
-
Donut
|
|
560
|
-
</label>
|
|
561
|
-
{/if}
|
|
562
|
-
<label class="sv-grid-chart-ctl">
|
|
563
|
-
<span class="sv-grid-chart-ctl-lbl">Colours</span>
|
|
564
|
-
<select
|
|
565
|
-
value={activePalette}
|
|
566
|
-
onchange={(e) => (ctrl.chartPalette = PALETTES.find((x) => x.key === e.currentTarget.value)?.colors ?? null)}
|
|
567
|
-
>
|
|
568
|
-
{#each PALETTES as pal (pal.key)}<option value={pal.key}>{pal.label}</option>{/each}
|
|
569
|
-
</select>
|
|
570
|
-
</label>
|
|
571
|
-
{#if wantsDataLabels}
|
|
572
|
-
<label class="sv-grid-chart-toggle">
|
|
573
|
-
<input type="checkbox" checked={ctrl.chartDataLabels} onchange={(e) => (ctrl.chartDataLabels = e.currentTarget.checked)} />
|
|
574
|
-
Labels
|
|
575
|
-
</label>
|
|
576
|
-
{/if}
|
|
577
|
-
{#if wantsLogScale}
|
|
578
|
-
<label class="sv-grid-chart-toggle" title="Logarithmic value axis - flattens wide-range data">
|
|
579
|
-
<input type="checkbox" checked={ctrl.chartLogScale} onchange={(e) => (ctrl.chartLogScale = e.currentTarget.checked)} />
|
|
580
|
-
Log scale
|
|
581
|
-
</label>
|
|
582
|
-
{/if}
|
|
583
|
-
{#if ctrl.chartDimensionIsDate && CARTESIAN.includes(ctrl.chartType)}
|
|
584
|
-
<label class="sv-grid-chart-toggle" title="Space points by actual date - real time ticks and proportional gaps">
|
|
585
|
-
<input type="checkbox" checked={ctrl.chartTimeAxis} onchange={(e) => (ctrl.chartTimeAxis = e.currentTarget.checked)} />
|
|
586
|
-
Date axis
|
|
587
|
-
</label>
|
|
588
|
-
{/if}
|
|
589
|
-
{/if}
|
|
590
|
-
{#if typeIssue}
|
|
591
|
-
<p class="sv-grid-chart-hint" role="note">{typeIssue}</p>
|
|
592
|
-
{/if}
|
|
593
|
-
</div>
|
|
508
|
+
{#if !isCustom}
|
|
509
|
+
<SvGridChartPickers {ctrl} messages={m} />
|
|
510
|
+
{/if}
|
|
594
511
|
</header>
|
|
595
512
|
|
|
596
|
-
<div class="sv-grid-chart-panel-body" bind:this={bodyEl}
|
|
513
|
+
<div class="sv-grid-chart-panel-body" bind:this={bodyEl}>
|
|
597
514
|
<!-- A gauge is one number, so it legitimately carries no series and no
|
|
598
515
|
categories. Gating purely on `series.length` sent it to the "No data"
|
|
599
516
|
message even with a perfectly good spec. -->
|
|
600
|
-
{#if spec &&
|
|
517
|
+
{#if spec && ctrl.chartIndicatorPanes.length && spec.series[0]?.ohlc}
|
|
518
|
+
<div class="sv-grid-chart-panes-host">
|
|
519
|
+
<SvChartPanes
|
|
520
|
+
{spec}
|
|
521
|
+
indicators={ctrl.chartIndicatorPanes}
|
|
522
|
+
onSelect={onChartSelect}
|
|
523
|
+
interactive
|
|
524
|
+
legend={false}
|
|
525
|
+
zoomable={cfg?.zoom ?? false}
|
|
526
|
+
bind:zoom={ctrl.chartZoom}
|
|
527
|
+
rangePresets={cfg?.rangePresets}
|
|
528
|
+
syncGroup={cfg?.syncGroup}
|
|
529
|
+
contextMenu={cfg?.contextMenu}
|
|
530
|
+
animate={cfg?.animate}
|
|
531
|
+
localeText={cfg?.localeText}
|
|
532
|
+
crosshairLabels={crosshairLabelsProp}
|
|
533
|
+
toolbar={cfg?.export === true ? true : undefined}
|
|
534
|
+
/>
|
|
535
|
+
</div>
|
|
536
|
+
{:else if spec && (spec.series.length || spec.gaugeValue != null || isCustom)}
|
|
601
537
|
<SvGridChart
|
|
602
538
|
{spec}
|
|
603
|
-
|
|
604
|
-
height={chartH}
|
|
539
|
+
autosize
|
|
605
540
|
onSelect={onChartSelect}
|
|
606
541
|
interactive
|
|
607
|
-
legend
|
|
608
|
-
dataLabels={
|
|
609
|
-
zoomable={cfg?.zoom
|
|
542
|
+
legend={legendProp}
|
|
543
|
+
dataLabels={dataLabelsProp}
|
|
544
|
+
zoomable={cfg?.zoom ?? false}
|
|
545
|
+
bind:zoom={ctrl.chartZoom}
|
|
610
546
|
brush={cfg?.brush === true}
|
|
547
|
+
rangePresets={cfg?.rangePresets}
|
|
548
|
+
syncGroup={cfg?.syncGroup}
|
|
549
|
+
contextMenu={cfg?.contextMenu}
|
|
550
|
+
animate={cfg?.animate}
|
|
551
|
+
localeText={cfg?.localeText}
|
|
552
|
+
crosshairLabels={crosshairLabelsProp}
|
|
611
553
|
toolbar={cfg?.export === true ? true : undefined}
|
|
612
554
|
/>
|
|
613
555
|
{:else}
|
|
614
556
|
<p class="sv-grid-chart-empty">
|
|
615
557
|
{#if !columns.dims.length || !columns.measures.length}
|
|
616
|
-
|
|
558
|
+
{m.chartEmptyColumns}
|
|
617
559
|
{:else}
|
|
618
|
-
|
|
560
|
+
{m.chartEmptyData}
|
|
619
561
|
{/if}
|
|
620
562
|
</p>
|
|
621
563
|
{/if}
|
|
622
564
|
</div>
|
|
623
565
|
|
|
566
|
+
{#if Builder}
|
|
567
|
+
<Builder {ctrl} bind:open={builderOpen} types={availableTypes} messages={m} />
|
|
568
|
+
{/if}
|
|
569
|
+
|
|
624
570
|
{#if floating && !maximized}
|
|
625
571
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
626
572
|
<div
|
|
627
573
|
class="sv-grid-chart-float-resizer"
|
|
628
574
|
onpointerdown={startFloatResize}
|
|
629
575
|
role="separator"
|
|
630
|
-
aria-label=
|
|
576
|
+
aria-label={m.chartResizeWindow}
|
|
631
577
|
></div>
|
|
632
578
|
{/if}
|
|
633
579
|
</section>
|