flexdesk 0.12.0 → 0.13.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/README.md +55 -1
- package/css/flexdesk.css +106 -9
- package/css/overrides.css +29 -9
- package/dist/charts.js +4 -73
- package/dist/charts.js.map +3 -3
- package/dist/{chunk-5QPGYHBN.js → chunk-CWPMLX67.js} +2 -2
- package/dist/{chunk-2LEJQD4H.js → chunk-IYMNDPWH.js} +109 -2
- package/dist/chunk-IYMNDPWH.js.map +7 -0
- package/dist/{chunk-6EZPPN4E.js → chunk-NNFTETK3.js} +8 -4
- package/dist/chunk-NNFTETK3.js.map +7 -0
- package/dist/flexdesk.css +106 -9
- package/dist/flow.js +1 -1
- package/dist/tiles.js +1752 -1031
- package/dist/tiles.js.map +4 -4
- package/dist/widgets.js +2 -2
- package/dist/wm.js +23 -5
- package/dist/wm.js.map +2 -2
- package/package.json +1 -1
- package/src/charts/chart_types.js +40 -1
- package/src/tiles/chart_tiles.js +827 -0
- package/src/tiles/tile_grid.js +4 -3
- package/src/tiling/content_registry.js +5 -0
- package/src/tiling/tile_renderer.js +45 -3
- package/src/ui/components/modal.js +4 -2
- package/dist/chunk-2LEJQD4H.js.map +0 -7
- package/dist/chunk-6EZPPN4E.js.map +0 -7
- /package/dist/{chunk-5QPGYHBN.js.map → chunk-CWPMLX67.js.map} +0 -0
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ manager, and neither of them should drag in a plotting engine or a code editor.
|
|
|
45
45
|
| `@flexdesk/wm` | 166 kB | Tiling window manager: tabs, splits, desktops, command palette, taxonomy, `createShell()` |
|
|
46
46
|
| `@flexdesk/widgets` | 192 kB | DataTable, modals, managed windows, toasts, trees, panels, context menus |
|
|
47
47
|
| `@flexdesk/charts` | 26 kB | Plotly wrapper, chart types, subplot layouts, downsampling, plot windows |
|
|
48
|
-
| `@flexdesk/tiles` | 67 kB | Draggable/resizable widget grid, tile base class, widget registry, layout persistence |
|
|
48
|
+
| `@flexdesk/tiles` | 67 kB | Draggable/resizable widget grid, tile base class, widget registry, layout persistence, chart and KPI tiles |
|
|
49
49
|
| `@flexdesk/editor` | 62 kB | Monaco loader + factory, multi-file tab bar, search, split panes, undo manager |
|
|
50
50
|
| `@flexdesk/canvas` | 14 kB | The canvas kernel: a pannable, zoomable surface, the edge router, a generic graph layer ([Flow editors](#flow-editors)) |
|
|
51
51
|
| `@flexdesk/flow` | 381 kB | The flow-editor kit and the canvas, outline and lane editors ([Flow editors](#flow-editors)) |
|
|
@@ -130,6 +130,28 @@ await createShell({
|
|
|
130
130
|
});
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
+
### A pane that names what it shows: `ctx.setTitle` (0.12.1)
|
|
134
|
+
|
|
135
|
+
A content factory receives `(hostEl, props, ctx)`, and `ctx` carries, per pane,
|
|
136
|
+
`setTitle(text)`. It renames the pane's chrome title, at mount or any time
|
|
137
|
+
after, and the title survives repaints — including a panel's, whose tree title
|
|
138
|
+
the shell resets to Navigator / Inspector / Console on every layout pass:
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
content: {
|
|
142
|
+
'panel:bottom': (hostEl, props, ctx) => {
|
|
143
|
+
// …a panel with tabs of its own…
|
|
144
|
+
const show = (tab) => { /* swap the body */ ctx.setTitle(tab.label); };
|
|
145
|
+
show(tabs[0]);
|
|
146
|
+
return { destroy() {} };
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`setTitle(null)` or `''` gives the title back to the tree. It changes what the
|
|
152
|
+
chrome shows only — the tree, the tab labels and the saved layout are left
|
|
153
|
+
alone — and a call from content whose pane has since been rebuilt does nothing.
|
|
154
|
+
|
|
133
155
|
### Your desktops beside the app's (0.10.0)
|
|
134
156
|
|
|
135
157
|
Pass `chrome.desktops` and the shell paints a desktop bar: a chip per desktop
|
|
@@ -1709,6 +1731,38 @@ the configuration step is explicit rather than lazy.
|
|
|
1709
1731
|
A layout is keyed by *something* — a document, a user, a run — and the library has
|
|
1710
1732
|
no opinion about which. It calls that a `contextId`.
|
|
1711
1733
|
|
|
1734
|
+
### Chart and KPI tiles (0.13.0)
|
|
1735
|
+
|
|
1736
|
+
`@flexdesk/tiles` ships two widgets: `flexdesk.chart` (line, bar, area, scatter,
|
|
1737
|
+
pie, donut, funnel, histogram — Plotly, so `setPlotlySource` first) and
|
|
1738
|
+
`flexdesk.kpi` (one number, a label and an optional delta; plain DOM, no Plotly).
|
|
1739
|
+
Nothing registers at import — call `registerChartTiles()` (idempotent). You hand
|
|
1740
|
+
a tile **already-computed** series; turning records into series is yours:
|
|
1741
|
+
|
|
1742
|
+
```js
|
|
1743
|
+
import { registerChartTiles } from '@flexdesk/tiles';
|
|
1744
|
+
registerChartTiles();
|
|
1745
|
+
const chart = grid.addTile('flexdesk.chart', { x: 0, y: 0, w: 6, h: 4 },
|
|
1746
|
+
{ chart_type: 'line', title: 'Burndown', unit: 'pts' });
|
|
1747
|
+
chart.onPointClick = ({ customdata }) => openItem(customdata);
|
|
1748
|
+
chart.setData({
|
|
1749
|
+
series: [
|
|
1750
|
+
{ x: days, y: remaining, name: 'Remaining', interpolation: 'step', customdata: ids },
|
|
1751
|
+
{ x: days, y: ideal, name: 'Ideal', lineStyle: 'dashed' },
|
|
1752
|
+
],
|
|
1753
|
+
markers: [{ x: today, label: 'Today' }], // vertical lines
|
|
1754
|
+
note: 'From 41 stories', // a line under the figure
|
|
1755
|
+
});
|
|
1756
|
+
grid.addTile('flexdesk.kpi', { x: 6, y: 0, w: 3, h: 2 }, { title: 'Open', unit: 'bugs' })
|
|
1757
|
+
.setData({ value: 42, reference: 37 }); // ▲ +5 bugs
|
|
1758
|
+
```
|
|
1759
|
+
|
|
1760
|
+
`setData(null)` or an empty series is the empty state, and it releases any figure
|
|
1761
|
+
still drawing. A pie takes `{labels, values}`, a histogram `{values}`. The config
|
|
1762
|
+
(`chart_type`, `title`, `unit`, …) round-trips through `getLayout()`/`setLayout()`.
|
|
1763
|
+
`buildOverlayShapes` gained `verticalLines: [{x, label?, color?, lineStyle?}]` for
|
|
1764
|
+
the same marker outside a tile.
|
|
1765
|
+
|
|
1712
1766
|
## Styling
|
|
1713
1767
|
|
|
1714
1768
|
Three stylesheets, and that is the whole CSS contract:
|
package/css/flexdesk.css
CHANGED
|
@@ -3448,6 +3448,83 @@ body.twm-window-maximized .twm-resize-handle {
|
|
|
3448
3448
|
.twm-tile-error .material-symbols-outlined {
|
|
3449
3449
|
font-size: 32px;
|
|
3450
3450
|
}
|
|
3451
|
+
|
|
3452
|
+
/* ── the chart and KPI tiles (0.13.0, src/tiles/chart_tiles.js) ──────── */
|
|
3453
|
+
/* The figure flexes into what `.tile-content`'s column leaves; the note takes
|
|
3454
|
+
its own line BELOW it, so the height chain above is untouched. */
|
|
3455
|
+
.fd-chart-tile {
|
|
3456
|
+
display: flex;
|
|
3457
|
+
flex-direction: column;
|
|
3458
|
+
flex: 1 1 auto;
|
|
3459
|
+
min-width: 0;
|
|
3460
|
+
min-height: 0;
|
|
3461
|
+
position: relative;
|
|
3462
|
+
}
|
|
3463
|
+
|
|
3464
|
+
/* Plotly puts `.js-plotly-plot` on the figure itself, and the rule above makes
|
|
3465
|
+
that `height: 100% !important` — which, beside a note, pushes the note out of
|
|
3466
|
+
the tile. Here the flex column sizes the figure instead. */
|
|
3467
|
+
.tile-content .fd-chart-tile.js-plotly-plot {
|
|
3468
|
+
height: auto !important;
|
|
3469
|
+
}
|
|
3470
|
+
|
|
3471
|
+
.fd-chart-tile__note {
|
|
3472
|
+
flex: 0 0 auto;
|
|
3473
|
+
padding: 2px 8px 0;
|
|
3474
|
+
font-size: var(--font-size-xs);
|
|
3475
|
+
line-height: 1.35;
|
|
3476
|
+
color: var(--text-muted);
|
|
3477
|
+
white-space: nowrap;
|
|
3478
|
+
overflow: hidden;
|
|
3479
|
+
text-overflow: ellipsis;
|
|
3480
|
+
}
|
|
3481
|
+
|
|
3482
|
+
.fd-kpi {
|
|
3483
|
+
--fd-kpi-value: 30px;
|
|
3484
|
+
flex: 1 1 auto;
|
|
3485
|
+
display: flex;
|
|
3486
|
+
flex-direction: column;
|
|
3487
|
+
align-items: flex-start;
|
|
3488
|
+
justify-content: center;
|
|
3489
|
+
gap: 2px;
|
|
3490
|
+
min-height: 0;
|
|
3491
|
+
}
|
|
3492
|
+
|
|
3493
|
+
.fd-kpi__label {
|
|
3494
|
+
color: var(--text-secondary);
|
|
3495
|
+
font-size: var(--font-size-xs);
|
|
3496
|
+
text-transform: uppercase;
|
|
3497
|
+
letter-spacing: 0.06em;
|
|
3498
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
|
|
3499
|
+
}
|
|
3500
|
+
|
|
3501
|
+
.fd-kpi__value {
|
|
3502
|
+
font-size: var(--fd-kpi-value);
|
|
3503
|
+
font-weight: 700;
|
|
3504
|
+
color: var(--text-bright);
|
|
3505
|
+
line-height: 1.1;
|
|
3506
|
+
font-variant-numeric: tabular-nums;
|
|
3507
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
|
|
3508
|
+
}
|
|
3509
|
+
|
|
3510
|
+
/* AN ARROW AND A SIGN, not a colour alone: the modifiers change the colour,
|
|
3511
|
+
the character in front of the number survives a monochrome display. */
|
|
3512
|
+
.fd-kpi__delta {
|
|
3513
|
+
display: inline-flex;
|
|
3514
|
+
align-items: center;
|
|
3515
|
+
gap: 3px;
|
|
3516
|
+
font-size: var(--font-size-sm);
|
|
3517
|
+
font-variant-numeric: tabular-nums;
|
|
3518
|
+
color: var(--text-secondary);
|
|
3519
|
+
}
|
|
3520
|
+
.fd-kpi__delta--up { color: var(--color-success); }
|
|
3521
|
+
.fd-kpi__delta--down { color: var(--color-danger); }
|
|
3522
|
+
|
|
3523
|
+
.fd-kpi__foot {
|
|
3524
|
+
color: var(--text-muted);
|
|
3525
|
+
font-size: var(--font-size-xs);
|
|
3526
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
|
|
3527
|
+
}
|
|
3451
3528
|
/* ===================================================================
|
|
3452
3529
|
REUSABLE ACTION DROPDOWN COMPONENT
|
|
3453
3530
|
Used by Add Scenario, Add Widget, and other action dropdowns
|
|
@@ -4965,36 +5042,54 @@ button.twm-about-dialog__btn--close:focus {
|
|
|
4965
5042
|
color: #fff;
|
|
4966
5043
|
}
|
|
4967
5044
|
/* Content area — drop the host padding so the body host controls its
|
|
4968
|
-
* own zones (form rows + flush-bottom action strip).
|
|
5045
|
+
* own zones (form rows + flush-bottom action strip). The content never
|
|
5046
|
+
* scrolls itself: the zone above the strip does, so the strip stays whole
|
|
5047
|
+
* and in view when the fields outgrow the window. */
|
|
4969
5048
|
.twm-managed-window--modal .twm-managed-window__content {
|
|
4970
5049
|
background: var(--ea-bg-content);
|
|
4971
5050
|
padding: 0;
|
|
4972
|
-
overflow:
|
|
5051
|
+
overflow: hidden;
|
|
4973
5052
|
}
|
|
4974
5053
|
.twm-managed-window--modal .twm-modal__body-host {
|
|
4975
5054
|
display: flex;
|
|
4976
5055
|
flex-direction: column;
|
|
4977
|
-
|
|
5056
|
+
height: 100%;
|
|
4978
5057
|
}
|
|
4979
|
-
/* Form sits inside the body host as a flex column
|
|
4980
|
-
*
|
|
5058
|
+
/* Form sits inside the body host as a flex column: the fields take the
|
|
5059
|
+
* room left over and scroll, the error banner and the actions row keep
|
|
5060
|
+
* their height below them. */
|
|
4981
5061
|
.twm-managed-window--modal .twm-modal__form {
|
|
4982
5062
|
display: flex;
|
|
4983
5063
|
flex-direction: column;
|
|
4984
5064
|
flex: 1 1 auto;
|
|
5065
|
+
min-height: 0;
|
|
4985
5066
|
padding: 0;
|
|
5067
|
+
overflow: hidden;
|
|
5068
|
+
}
|
|
5069
|
+
.twm-managed-window--modal .twm-modal__fields {
|
|
5070
|
+
flex: 1 1 auto;
|
|
5071
|
+
min-height: 0;
|
|
5072
|
+
overflow-y: auto;
|
|
5073
|
+
padding-bottom: 14px;
|
|
5074
|
+
}
|
|
5075
|
+
.twm-managed-window--modal .twm-modal__form > .twm-modal__error,
|
|
5076
|
+
.twm-managed-window--modal .twm-modal__form > .twm-modal__actions {
|
|
5077
|
+
flex: 0 0 auto;
|
|
5078
|
+
}
|
|
5079
|
+
.twm-managed-window--modal .twm-modal__form > .twm-modal__error {
|
|
5080
|
+
margin-bottom: 8px;
|
|
4986
5081
|
}
|
|
4987
5082
|
/* Field rows get inset margins so the action strip can run edge-to-edge
|
|
4988
5083
|
* as a true bottom band. */
|
|
4989
|
-
.twm-managed-window--modal .twm-
|
|
4990
|
-
.twm-managed-window--modal .twm-
|
|
5084
|
+
.twm-managed-window--modal .twm-modal__fields > .twm-modal__row,
|
|
5085
|
+
.twm-managed-window--modal .twm-modal__fields > .twm-modal__section,
|
|
4991
5086
|
.twm-managed-window--modal .twm-modal__form > .twm-modal__error,
|
|
4992
5087
|
.twm-managed-window--modal .twm-modal__body {
|
|
4993
5088
|
margin-left: 14px;
|
|
4994
5089
|
margin-right: 14px;
|
|
4995
5090
|
}
|
|
4996
|
-
.twm-managed-window--modal .twm-
|
|
4997
|
-
.twm-managed-window--modal .twm-
|
|
5091
|
+
.twm-managed-window--modal .twm-modal__fields > .twm-modal__row:first-of-type,
|
|
5092
|
+
.twm-managed-window--modal .twm-modal__fields > .twm-modal__section:first-child,
|
|
4998
5093
|
.twm-managed-window--modal .twm-modal__body:first-child {
|
|
4999
5094
|
margin-top: 14px;
|
|
5000
5095
|
}
|
|
@@ -5235,6 +5330,8 @@ button.twm-about-dialog__btn--close:focus {
|
|
|
5235
5330
|
line-height: 1.5;
|
|
5236
5331
|
color: var(--ea-text);
|
|
5237
5332
|
flex: 1 1 auto;
|
|
5333
|
+
min-height: 0;
|
|
5334
|
+
overflow-y: auto;
|
|
5238
5335
|
}
|
|
5239
5336
|
/* Form-level error banner — left accent stripe, dim red panel. */
|
|
5240
5337
|
.twm-managed-window--modal .twm-modal__error {
|
package/css/overrides.css
CHANGED
|
@@ -241,36 +241,54 @@
|
|
|
241
241
|
color: #fff;
|
|
242
242
|
}
|
|
243
243
|
/* Content area — drop the host padding so the body host controls its
|
|
244
|
-
* own zones (form rows + flush-bottom action strip).
|
|
244
|
+
* own zones (form rows + flush-bottom action strip). The content never
|
|
245
|
+
* scrolls itself: the zone above the strip does, so the strip stays whole
|
|
246
|
+
* and in view when the fields outgrow the window. */
|
|
245
247
|
.twm-managed-window--modal .twm-managed-window__content {
|
|
246
248
|
background: var(--ea-bg-content);
|
|
247
249
|
padding: 0;
|
|
248
|
-
overflow:
|
|
250
|
+
overflow: hidden;
|
|
249
251
|
}
|
|
250
252
|
.twm-managed-window--modal .twm-modal__body-host {
|
|
251
253
|
display: flex;
|
|
252
254
|
flex-direction: column;
|
|
253
|
-
|
|
255
|
+
height: 100%;
|
|
254
256
|
}
|
|
255
|
-
/* Form sits inside the body host as a flex column
|
|
256
|
-
*
|
|
257
|
+
/* Form sits inside the body host as a flex column: the fields take the
|
|
258
|
+
* room left over and scroll, the error banner and the actions row keep
|
|
259
|
+
* their height below them. */
|
|
257
260
|
.twm-managed-window--modal .twm-modal__form {
|
|
258
261
|
display: flex;
|
|
259
262
|
flex-direction: column;
|
|
260
263
|
flex: 1 1 auto;
|
|
264
|
+
min-height: 0;
|
|
261
265
|
padding: 0;
|
|
266
|
+
overflow: hidden;
|
|
267
|
+
}
|
|
268
|
+
.twm-managed-window--modal .twm-modal__fields {
|
|
269
|
+
flex: 1 1 auto;
|
|
270
|
+
min-height: 0;
|
|
271
|
+
overflow-y: auto;
|
|
272
|
+
padding-bottom: 14px;
|
|
273
|
+
}
|
|
274
|
+
.twm-managed-window--modal .twm-modal__form > .twm-modal__error,
|
|
275
|
+
.twm-managed-window--modal .twm-modal__form > .twm-modal__actions {
|
|
276
|
+
flex: 0 0 auto;
|
|
277
|
+
}
|
|
278
|
+
.twm-managed-window--modal .twm-modal__form > .twm-modal__error {
|
|
279
|
+
margin-bottom: 8px;
|
|
262
280
|
}
|
|
263
281
|
/* Field rows get inset margins so the action strip can run edge-to-edge
|
|
264
282
|
* as a true bottom band. */
|
|
265
|
-
.twm-managed-window--modal .twm-
|
|
266
|
-
.twm-managed-window--modal .twm-
|
|
283
|
+
.twm-managed-window--modal .twm-modal__fields > .twm-modal__row,
|
|
284
|
+
.twm-managed-window--modal .twm-modal__fields > .twm-modal__section,
|
|
267
285
|
.twm-managed-window--modal .twm-modal__form > .twm-modal__error,
|
|
268
286
|
.twm-managed-window--modal .twm-modal__body {
|
|
269
287
|
margin-left: 14px;
|
|
270
288
|
margin-right: 14px;
|
|
271
289
|
}
|
|
272
|
-
.twm-managed-window--modal .twm-
|
|
273
|
-
.twm-managed-window--modal .twm-
|
|
290
|
+
.twm-managed-window--modal .twm-modal__fields > .twm-modal__row:first-of-type,
|
|
291
|
+
.twm-managed-window--modal .twm-modal__fields > .twm-modal__section:first-child,
|
|
274
292
|
.twm-managed-window--modal .twm-modal__body:first-child {
|
|
275
293
|
margin-top: 14px;
|
|
276
294
|
}
|
|
@@ -481,6 +499,8 @@
|
|
|
481
499
|
line-height: 1.5;
|
|
482
500
|
color: var(--ea-text);
|
|
483
501
|
flex: 1 1 auto;
|
|
502
|
+
min-height: 0;
|
|
503
|
+
overflow-y: auto;
|
|
484
504
|
}
|
|
485
505
|
/* Form-level error banner — left accent stripe, dim red panel. */
|
|
486
506
|
.twm-managed-window--modal .twm-modal__error {
|
package/dist/charts.js
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
hasPlot,
|
|
28
28
|
hexToRgba,
|
|
29
29
|
is3DChart,
|
|
30
|
+
lttb,
|
|
30
31
|
makeDefaultSubplot,
|
|
31
32
|
migrateData,
|
|
32
33
|
migrateSingleChart,
|
|
@@ -37,9 +38,11 @@ import {
|
|
|
37
38
|
resize,
|
|
38
39
|
restyle,
|
|
39
40
|
setPlotlySource,
|
|
41
|
+
strideDownsampleShared,
|
|
42
|
+
strideIndices,
|
|
40
43
|
uid,
|
|
41
44
|
updateChart
|
|
42
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-IYMNDPWH.js";
|
|
43
46
|
import {
|
|
44
47
|
createRafResizeObserver
|
|
45
48
|
} from "./chunk-M2L4XW2U.js";
|
|
@@ -660,78 +663,6 @@ function closeDataSeriesPlotWindow(seriesId) {
|
|
|
660
663
|
_openPlotWindows.delete(seriesId);
|
|
661
664
|
}
|
|
662
665
|
}
|
|
663
|
-
|
|
664
|
-
// src/charts/downsample.js
|
|
665
|
-
function strideIndices(n, targetPoints) {
|
|
666
|
-
if (!Number.isFinite(n) || n <= 0) return null;
|
|
667
|
-
const target = Math.max(2, Math.floor(targetPoints) || 0);
|
|
668
|
-
if (n <= target) return null;
|
|
669
|
-
const step = (n - 1) / (target - 1);
|
|
670
|
-
const idx = new Array(target);
|
|
671
|
-
for (let i = 0; i < target; i++) idx[i] = Math.round(i * step);
|
|
672
|
-
idx[target - 1] = n - 1;
|
|
673
|
-
let w = 0;
|
|
674
|
-
for (let r = 0; r < idx.length; r++) {
|
|
675
|
-
if (w === 0 || idx[r] > idx[w - 1]) idx[w++] = idx[r];
|
|
676
|
-
}
|
|
677
|
-
idx.length = w;
|
|
678
|
-
return idx;
|
|
679
|
-
}
|
|
680
|
-
function strideDownsampleShared(time, series, targetPoints) {
|
|
681
|
-
const idx = strideIndices(time?.length ?? 0, targetPoints);
|
|
682
|
-
if (!idx) return { time, series };
|
|
683
|
-
const dsTime = idx.map((i) => time[i]);
|
|
684
|
-
const dsSeries = {};
|
|
685
|
-
for (const [name, arr] of Object.entries(series || {})) {
|
|
686
|
-
if (!Array.isArray(arr)) {
|
|
687
|
-
dsSeries[name] = arr;
|
|
688
|
-
continue;
|
|
689
|
-
}
|
|
690
|
-
dsSeries[name] = idx.map((i) => i < arr.length ? arr[i] : null);
|
|
691
|
-
}
|
|
692
|
-
return { time: dsTime, series: dsSeries };
|
|
693
|
-
}
|
|
694
|
-
function lttb(x, y, threshold) {
|
|
695
|
-
const n = Math.min(x?.length ?? 0, y?.length ?? 0);
|
|
696
|
-
const target = Math.floor(threshold) || 0;
|
|
697
|
-
if (target <= 0 || target >= n) return { x: x.slice(0, n), y: y.slice(0, n) };
|
|
698
|
-
if (target < 3) return { x: [x[0], x[n - 1]], y: [y[0], y[n - 1]] };
|
|
699
|
-
const num = (v) => Number.isFinite(v) ? v : 0;
|
|
700
|
-
const outX = [x[0]];
|
|
701
|
-
const outY = [y[0]];
|
|
702
|
-
const bucketSize = (n - 2) / (target - 2);
|
|
703
|
-
let a = 0;
|
|
704
|
-
for (let i = 0; i < target - 2; i++) {
|
|
705
|
-
let nextStart = Math.floor((i + 1) * bucketSize) + 1;
|
|
706
|
-
let nextEnd = Math.min(Math.floor((i + 2) * bucketSize) + 1, n);
|
|
707
|
-
if (nextEnd <= nextStart) nextEnd = Math.min(nextStart + 1, n);
|
|
708
|
-
let avgX = 0, avgY = 0;
|
|
709
|
-
const count = nextEnd - nextStart;
|
|
710
|
-
for (let j = nextStart; j < nextEnd; j++) {
|
|
711
|
-
avgX += x[j];
|
|
712
|
-
avgY += num(y[j]);
|
|
713
|
-
}
|
|
714
|
-
avgX /= count;
|
|
715
|
-
avgY /= count;
|
|
716
|
-
const curStart = Math.floor(i * bucketSize) + 1;
|
|
717
|
-
const curEnd = Math.min(Math.floor((i + 1) * bucketSize) + 1, n);
|
|
718
|
-
const ax = x[a], ay = num(y[a]);
|
|
719
|
-
let maxArea = -1, chosen = curStart;
|
|
720
|
-
for (let j = curStart; j < curEnd; j++) {
|
|
721
|
-
const area = Math.abs((ax - avgX) * (num(y[j]) - ay) - (ax - x[j]) * (avgY - ay));
|
|
722
|
-
if (area > maxArea) {
|
|
723
|
-
maxArea = area;
|
|
724
|
-
chosen = j;
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
outX.push(x[chosen]);
|
|
728
|
-
outY.push(y[chosen]);
|
|
729
|
-
a = chosen;
|
|
730
|
-
}
|
|
731
|
-
outX.push(x[n - 1]);
|
|
732
|
-
outY.push(y[n - 1]);
|
|
733
|
-
return { x: outX, y: outY };
|
|
734
|
-
}
|
|
735
666
|
export {
|
|
736
667
|
ALL_CHART_TYPES,
|
|
737
668
|
CHART_TYPES_2D,
|
package/dist/charts.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/charts/data_series_plot_window.js"
|
|
4
|
-
"sourcesContent": ["/**\n * Data Series Plot Window\n *\n * Opens a data series in a ManagedWindow with a split layout:\n * - Left: Plotly chart\n * - Right: Configuration panel (column selection, chart type, styling)\n *\n * Config is persisted in workspace metadata via savePlotConfig callback.\n */\n\nimport { ManagedWindow } from '../ui/components/managed_window.js';\nimport {\n ensurePlotly,\n purge,\n DARK_THEME_LAYOUT,\n DEFAULT_CONFIG,\n getSeriesColor,\n} from './plotly_wrapper.js';\nimport {\n buildTrace,\n buildYAxisLayout,\n buildXAxisLayout,\n buildLegendLayout,\n} from './chart_types.js';\n// The two header helpers are INJECTED, not imported.\n//\n// They used to come from ui/js/ui/utils/header_utils.js, which decides whether a\n// column is a STOCK or a FLOW by looking for `asset`, `liability`, `equity`,\n// `reserves`, `deposit`. That is stock-flow-consistent accounting, not charting,\n// and that one import was the last thing keeping this window out of @flexdesk/charts.\n//\n// The defaults below are what a chart library can honestly know about a column\n// name: whether it looks like a time axis, and nothing else.\n\n/** A column called `t`/`time`/`step`/`period` is the x-axis. Everything else is data. */\nconst defaultClassifyHeader = (header) => {\n const lower = String(header ?? '').trim().toLowerCase();\n return (lower === 't' || lower === 'time' || lower === 'step' || lower === 'period')\n ? 'time' : 'indicator';\n};\n\n/** No convention to strip, absent one being supplied. */\nconst defaultLabelHeader = (header) => header;\nimport { createRafResizeObserver } from '../ui/utils/raf_resize_observer.js';\n\nconst INTERPOLATION_MODES = ['linear', 'step', 'cubic'];\nconst CHART_TYPES = ['line', 'scatter', 'bar', 'area', 'area-stacked'];\nconst LEGEND_POSITIONS = ['top', 'bottom', 'right', 'hidden'];\nconst MAX_AUTO_COLUMNS = 3;\n\nclass DataSeriesPlotWindow {\n /**\n * @param {Object} options\n * @param {string} options.seriesId\n * @param {string} options.seriesName\n * @param {string} options.datasetName\n * @param {string[]} options.headers - Column headers from the series\n * @param {Object} options.dataHub - DataHub service for fetching data\n * @param {Object} options.services - { eventBus, logger }\n * @param {Object} [options.host] - A Host (ui/js/host/host.js). Its `dialogs`\n * capability backs Export PNG/CSV; absent => Blob download fallback.\n * @param {Function} options.getPlotConfig - (seriesId) => config | null\n * @param {Function} options.savePlotConfig - (seriesId, config) => void\n */\n constructor({ seriesId, seriesName, datasetName, headers, dataHub, services, host,\n getPlotConfig, savePlotConfig,\n classifyHeader = defaultClassifyHeader,\n labelHeader = defaultLabelHeader }) {\n this.classifyHeader = classifyHeader;\n this.labelHeader = labelHeader;\n this.seriesId = seriesId;\n this.seriesName = seriesName;\n this.datasetName = datasetName;\n this.headers = headers;\n this.dataHub = dataHub;\n this.services = services;\n this._host = host || null;\n this.getPlotConfig = getPlotConfig;\n this.savePlotConfig = savePlotConfig;\n\n this.window = null;\n this.contentEl = null;\n this.plotDiv = null;\n this._resizeObserver = null;\n this._chartCreated = false;\n this._config = null;\n this._fullData = null; // { headers, rows } with all data\n this._numericColumns = null; // Set of numeric column headers\n this._loading = false;\n }\n\n async show() {\n if (this.window?.isVisible) {\n this.window.bringToFront();\n return;\n }\n\n await ensurePlotly();\n await this.#fetchData();\n\n // Determine which columns are numeric from actual data\n this._numericColumns = new Set(this.headers.filter(h => this.#isNumericColumn(h)));\n\n // Load or create config (filter out any non-numeric columns from saved config)\n const saved = this.getPlotConfig?.(this.seriesId);\n if (saved) {\n this._config = { ...saved };\n this._config.yColumns = (saved.yColumns || []).filter(c => this._numericColumns.has(c.header));\n } else {\n this._config = DataSeriesPlotWindow.createDefaultConfig(this.headers, this._numericColumns);\n }\n\n this.#createWindow();\n this.#setupResizeObserver();\n await this.#renderPlot();\n }\n\n close() {\n if (this.window?.isVisible) {\n this.window.close();\n }\n }\n\n // \u2500\u2500 Window creation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n #createWindow() {\n this.contentEl = this.#buildContent();\n\n const title = this.datasetName\n ? `${this.datasetName} \u00B7 ${this.seriesName}`\n : this.seriesName || 'Series Plot';\n\n this.window = new ManagedWindow({\n id: `data-series-plot-${this.seriesId}`,\n title,\n icon: 'monitoring',\n content: this.contentEl,\n minWidth: 600,\n minHeight: 400,\n defaultWidth: 900,\n defaultHeight: 550,\n canMinimize: true,\n canMaximize: true,\n canResize: true,\n canDrag: true,\n onClose: () => this.#dispose(),\n });\n\n this.window.show();\n }\n\n #buildContent() {\n const container = document.createElement('div');\n container.className = 'twm-data-series-plot-container';\n\n // Actions bar (download buttons)\n const actionsBar = document.createElement('div');\n actionsBar.className = 'twm-data-series-plot-actions';\n\n const pngBtn = document.createElement('button');\n pngBtn.className = 'twm-btn-icon twm-has-tooltip';\n pngBtn.type = 'button';\n pngBtn.setAttribute('data-tooltip', 'Download plot as PNG');\n pngBtn.innerHTML = '<span class=\"material-symbols-outlined\">image</span>';\n pngBtn.addEventListener('click', () => this.#downloadPNG());\n\n const csvBtn = document.createElement('button');\n csvBtn.className = 'twm-btn-icon twm-has-tooltip';\n csvBtn.type = 'button';\n csvBtn.setAttribute('data-tooltip', 'Download data as CSV');\n csvBtn.innerHTML = '<span class=\"material-symbols-outlined\">download</span>';\n csvBtn.addEventListener('click', () => this.#downloadCSV());\n\n actionsBar.appendChild(pngBtn);\n actionsBar.appendChild(csvBtn);\n\n // Chart area\n const chartWrap = document.createElement('div');\n chartWrap.className = 'twm-data-series-plot-body';\n\n this.plotDiv = document.createElement('div');\n this.plotDiv.className = 'twm-data-series-plot-chart';\n chartWrap.appendChild(this.plotDiv);\n\n // Config panel\n const configPanel = this.#buildConfigPanel();\n chartWrap.appendChild(configPanel);\n\n container.appendChild(actionsBar);\n container.appendChild(chartWrap);\n\n return container;\n }\n\n // \u2500\u2500 Config panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n #buildConfigPanel() {\n const panel = document.createElement('div');\n panel.className = 'twm-data-series-plot-config';\n\n // X Column\n panel.appendChild(this.#buildSection('X Axis', this.#buildXColumnSelect()));\n\n // Y Columns\n this._yColumnsList = document.createElement('div');\n this._yColumnsList.className = 'twm-data-series-plot-config__columns';\n this.#populateYColumns();\n panel.appendChild(this.#buildSection('Y Axis', this._yColumnsList));\n\n // Chart type\n panel.appendChild(this.#buildSection('Chart Type', this.#buildSelect(\n CHART_TYPES,\n this._config.chartType,\n (val) => { this._config.chartType = val; this.#onConfigChanged(); }\n )));\n\n // Interpolation\n panel.appendChild(this.#buildSection('Interpolation', this.#buildSelect(\n INTERPOLATION_MODES,\n this._config.interpolation,\n (val) => { this._config.interpolation = val; this.#onConfigChanged(); }\n )));\n\n // Legend position\n panel.appendChild(this.#buildSection('Legend', this.#buildSelect(\n LEGEND_POSITIONS,\n this._config.legendPosition,\n (val) => { this._config.legendPosition = val; this.#onConfigChanged(); }\n )));\n\n // Show data points\n panel.appendChild(this.#buildSection('', this.#buildCheckbox(\n 'Show data points',\n this._config.showDataPoints,\n (checked) => { this._config.showDataPoints = checked; this.#onConfigChanged(); }\n )));\n\n return panel;\n }\n\n #buildSection(label, content) {\n const section = document.createElement('div');\n section.className = 'twm-data-series-plot-config__section';\n\n if (label) {\n const labelEl = document.createElement('div');\n labelEl.className = 'twm-data-series-plot-config__label';\n labelEl.textContent = label;\n section.appendChild(labelEl);\n }\n\n if (content instanceof HTMLElement) {\n section.appendChild(content);\n }\n\n return section;\n }\n\n #buildXColumnSelect() {\n const select = document.createElement('select');\n for (const header of this.headers) {\n if (!this._numericColumns.has(header)) continue;\n const opt = document.createElement('option');\n opt.value = header;\n opt.textContent = this.labelHeader(header);\n if (header === this._config.xColumn) opt.selected = true;\n select.appendChild(opt);\n }\n select.addEventListener('change', () => {\n this._config.xColumn = select.value;\n this.#onConfigChanged();\n });\n return select;\n }\n\n #populateYColumns() {\n const list = this._yColumnsList;\n list.innerHTML = '';\n\n const selectedSet = new Set(this._config.yColumns.map(c => c.header));\n\n for (const header of this.headers) {\n if (header === this._config.xColumn) continue;\n if (!this._numericColumns.has(header)) continue;\n\n const item = document.createElement('label');\n item.className = 'twm-data-series-plot-config__column-item';\n\n const cb = document.createElement('input');\n cb.type = 'checkbox';\n cb.checked = selectedSet.has(header);\n cb.addEventListener('change', () => this.#toggleYColumn(header, cb.checked));\n\n const colorSwatch = document.createElement('input');\n colorSwatch.type = 'color';\n colorSwatch.className = 'twm-data-series-plot-config__color-swatch';\n const existing = this._config.yColumns.find(c => c.header === header);\n colorSwatch.value = existing?.color || this.#nextColor(this.headers.indexOf(header));\n colorSwatch.addEventListener('input', () => this.#updateColumnColor(header, colorSwatch.value));\n\n const label = document.createElement('span');\n label.textContent = this.labelHeader(header);\n label.title = header;\n\n item.appendChild(cb);\n item.appendChild(colorSwatch);\n item.appendChild(label);\n list.appendChild(item);\n }\n }\n\n #toggleYColumn(header, checked) {\n if (checked) {\n if (!this._config.yColumns.find(c => c.header === header)) {\n const colorSwatch = this._yColumnsList.querySelector(\n `label:has(span[title=\"${header}\"]) input[type=\"color\"]`\n );\n this._config.yColumns.push({\n header,\n color: colorSwatch?.value || this.#nextColor(this.headers.indexOf(header)),\n });\n }\n } else {\n this._config.yColumns = this._config.yColumns.filter(c => c.header !== header);\n }\n this.#onConfigChanged();\n }\n\n #updateColumnColor(header, color) {\n const col = this._config.yColumns.find(c => c.header === header);\n if (col) {\n col.color = color;\n this.#onConfigChanged();\n }\n }\n\n #nextColor(index) {\n return getSeriesColor(index);\n }\n\n #buildSelect(options, value, onChange) {\n const select = document.createElement('select');\n for (const opt of options) {\n const el = document.createElement('option');\n el.value = opt;\n el.textContent = opt.charAt(0).toUpperCase() + opt.slice(1);\n if (opt === value) el.selected = true;\n select.appendChild(el);\n }\n select.addEventListener('change', () => onChange(select.value));\n return select;\n }\n\n #buildCheckbox(label, checked, onChange) {\n const wrapper = document.createElement('label');\n wrapper.className = 'twm-data-series-plot-config__column-item';\n\n const cb = document.createElement('input');\n cb.type = 'checkbox';\n cb.checked = checked;\n cb.addEventListener('change', () => onChange(cb.checked));\n\n const span = document.createElement('span');\n span.textContent = label;\n\n wrapper.appendChild(cb);\n wrapper.appendChild(span);\n return wrapper;\n }\n\n // \u2500\u2500 Data fetching \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n async #fetchData() {\n if (this._loading) return;\n this._loading = true;\n\n try {\n const response = await this.dataHub.fetchSeriesById(this.seriesId, { limit: null, offset: 0 });\n if (response?.ok) {\n this._fullData = {\n headers: response.headers || [],\n rows: response.rows || [],\n };\n } else {\n this.#notify('Plot', response?.error || 'Failed to load series data', 'error');\n }\n } catch (err) {\n this.services.logger?.warn?.('[DataSeriesPlotWindow] Fetch failed', err);\n this.#notify('Plot', 'Failed to load data', 'error');\n } finally {\n this._loading = false;\n }\n }\n\n #isNumericColumn(header) {\n if (!this._fullData) return false;\n const data = this.#extractColumnData(header);\n // Sample up to 20 non-empty values\n let numericCount = 0;\n let sampleCount = 0;\n for (const v of data) {\n if (v == null || v === '') continue;\n sampleCount++;\n if (Number.isFinite(Number(v))) numericCount++;\n if (sampleCount >= 20) break;\n }\n return sampleCount > 0 && numericCount / sampleCount >= 0.8;\n }\n\n // \u2500\u2500 Chart rendering \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n #extractColumnData(columnHeader) {\n if (!this._fullData) return [];\n\n const { headers, rows } = this._fullData;\n const idx = headers.indexOf(columnHeader);\n if (idx < 0) return [];\n\n const TIME_NAMES = new Set(['time', 'tDisp', 'year', 'date', 'timestamp']);\n const firstIsTime = TIME_NAMES.has(headers[0]);\n const colOffset = firstIsTime ? 1 : 0;\n\n return rows.map(row => {\n if (Array.isArray(row)) return row[idx];\n if (row && typeof row === 'object') {\n // Handle {tDisp, cols} format\n if (TIME_NAMES.has(columnHeader) && row.tDisp !== undefined) return row.tDisp;\n if (Array.isArray(row.cols)) return row.cols[idx - colOffset] ?? '';\n return row[columnHeader] ?? '';\n }\n return '';\n }).map(v => {\n const n = Number(v);\n return Number.isFinite(n) ? n : v;\n });\n }\n\n async #renderPlot() {\n if (!this.plotDiv || !window.Plotly || !this._fullData) return;\n\n const cfg = this._config;\n const xData = this.#extractColumnData(cfg.xColumn);\n\n if (xData.length === 0 || cfg.yColumns.length === 0) {\n // Show empty state\n if (this._chartCreated) {\n try { purge(this.plotDiv); } catch (e) { /* ignore */ }\n this._chartCreated = false;\n }\n this.plotDiv.innerHTML = '<div style=\"display:flex;align-items:center;justify-content:center;height:100%;color:#888;font-size:13px;\">Select columns to plot</div>';\n return;\n }\n\n // Clear empty state if present\n if (!this._chartCreated) {\n this.plotDiv.innerHTML = '';\n }\n\n const traces = [];\n\n for (let i = 0; i < cfg.yColumns.length; i++) {\n const col = cfg.yColumns[i];\n const yData = this.#extractColumnData(col.header);\n\n traces.push(buildTrace({\n chartType: cfg.chartType,\n x: xData,\n y: yData,\n name: this.labelHeader(col.header),\n color: col.color,\n lineWidth: 2,\n lineStyle: 'solid',\n interpolation: cfg.interpolation,\n yAxisId: 'y',\n showMarkers: cfg.showDataPoints,\n seriesIndex: i,\n }));\n }\n\n const legendAtTop = cfg.legendPosition === 'top';\n const legendAtBottom = cfg.legendPosition === 'bottom';\n\n const layout = {\n ...DARK_THEME_LAYOUT,\n autosize: true,\n margin: {\n l: 60, r: 40,\n b: legendAtBottom ? 80 : 50,\n t: legendAtTop ? 60 : 30,\n },\n };\n\n // Remove 3D scene from layout\n delete layout.scene;\n\n // X-axis\n layout.xaxis = {\n ...buildXAxisLayout({ title: this.labelHeader(cfg.xColumn) }),\n };\n\n // Unified hover\n layout.hovermode = 'x unified';\n layout.xaxis.showspikes = true;\n layout.xaxis.spikemode = 'across';\n layout.xaxis.spikethickness = 1;\n layout.xaxis.spikecolor = '#666666';\n layout.xaxis.spikedash = 'dot';\n\n // Y-axis\n layout.yaxis = buildYAxisLayout({\n axisId: 'yaxis',\n title: '',\n position: 'left',\n scaleType: 'linear',\n showGrid: true,\n });\n\n // Legend\n const legendConfig = buildLegendLayout(cfg.legendPosition);\n if (legendConfig.legend) {\n layout.legend = legendConfig.legend;\n }\n layout.showlegend = legendConfig.showlegend !== false;\n\n const plotConfig = {\n ...DEFAULT_CONFIG,\n responsive: true,\n displayModeBar: true,\n modeBarButtonsToRemove: ['lasso2d', 'select2d', 'toImage'],\n };\n\n try {\n await window.Plotly.newPlot(this.plotDiv, traces, layout, plotConfig);\n this._chartCreated = true;\n\n requestAnimationFrame(() => {\n try {\n window.Plotly.Plots.resize(this.plotDiv);\n } catch (e) { /* ignore */ }\n });\n } catch (e) {\n console.error('[DataSeriesPlotWindow] Chart render error:', e);\n }\n }\n\n // \u2500\u2500 Config change handler \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n #onConfigChanged() {\n this.#renderPlot();\n this.savePlotConfig?.(this.seriesId, { ...this._config, yColumns: [...this._config.yColumns] });\n }\n\n // \u2500\u2500 Resize observer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n #setupResizeObserver() {\n if (!this.plotDiv) return;\n\n this._resizeObserver = createRafResizeObserver(() => {\n if (this.plotDiv && window.Plotly && this._chartCreated) {\n try {\n window.Plotly.Plots.resize(this.plotDiv);\n } catch (e) { /* ignore */ }\n }\n });\n\n this._resizeObserver.observe(this.plotDiv);\n }\n\n // \u2500\u2500 Downloads \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n async #downloadPNG() {\n if (!this.plotDiv || !window.Plotly || !this._chartCreated) {\n this.#notify('Download', 'Plot not ready', 'warn');\n return;\n }\n\n const filename = `${(this.seriesName || 'series').replace(/[^a-z0-9]/gi, '_')}.png`;\n\n try {\n const pngDataUrl = await window.Plotly.toImage(this.plotDiv, {\n format: 'png', width: 1920, height: 1080, scale: 2,\n });\n\n // null <=> no save dialog behind the capability (host.js) -> fall back.\n const dialogs = this._host?.dialogs;\n const result = dialogs\n ? await dialogs.saveFile({ data: pngDataUrl.split(',')[1], filename, kind: 'png' })\n : null;\n if (result) {\n if (result.ok) {\n this.#notify('Download', `Plot saved to ${result.path}`, 'success');\n } else if (!result.cancelled) {\n this.#notify('Download', result.error || 'Failed to save image', 'error');\n }\n } else {\n const link = document.createElement('a');\n link.href = pngDataUrl;\n link.download = filename;\n link.click();\n }\n } catch (err) {\n this.services.logger?.warn?.('[DataSeriesPlotWindow] PNG download failed', err);\n this.#notify('Download', 'Failed to generate PNG', 'error');\n }\n }\n\n async #downloadCSV() {\n if (!this._fullData || !this._fullData.rows.length) {\n this.#notify('Download', 'No data available', 'warn');\n return;\n }\n\n const cfg = this._config;\n const columns = [cfg.xColumn, ...cfg.yColumns.map(c => c.header)];\n const headers = columns.map(h => this.labelHeader(h));\n\n // Pre-extract all columns once\n const columnData = columns.map(col => this.#extractColumnData(col));\n const rowCount = this._fullData.rows.length;\n\n const lines = [headers.join(',')];\n for (let r = 0; r < rowCount; r++) {\n const vals = columnData.map(data => this.#csvEscape(data[r]));\n lines.push(vals.join(','));\n }\n const csv = lines.join('\\n');\n\n const filename = `${(this.seriesName || 'series').replace(/[^a-z0-9]/gi, '_')}_data.csv`;\n\n // null <=> no save dialog behind the capability (host.js) -> fall back.\n const dialogs = this._host?.dialogs;\n let result = null;\n if (dialogs) {\n try {\n result = await dialogs.saveFile({ data: csv, filename, kind: 'csv' });\n } catch (err) {\n this.#notify('Download', 'Failed to save file', 'error');\n return;\n }\n }\n if (result) {\n if (result.ok) {\n this.#notify('Download', `Saved to ${result.path}`, 'success');\n } else if (!result.cancelled) {\n this.#notify('Download', result.error || 'Failed to save file', 'error');\n }\n } else {\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = filename;\n link.click();\n URL.revokeObjectURL(url);\n }\n }\n\n #csvEscape(value) {\n if (value == null) return '';\n const str = String(value);\n if (/[\",\\n]/.test(str)) return `\"${str.replace(/\"/g, '\"\"')}\"`;\n return str;\n }\n\n // \u2500\u2500 Notifications \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n #notify(title, message, severity = 'info') {\n this.services.eventBus?.emit?.('toast:show', { title, message, type: severity });\n }\n\n // \u2500\u2500 Disposal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n #dispose() {\n if (this._resizeObserver) {\n this._resizeObserver.disconnect();\n this._resizeObserver = null;\n }\n\n if (this.plotDiv && window.Plotly) {\n try { purge(this.plotDiv); } catch (e) { /* ignore */ }\n }\n\n _openPlotWindows.delete(this.seriesId);\n\n this.window = null;\n this.contentEl = null;\n this.plotDiv = null;\n this._yColumnsList = null;\n this._chartCreated = false;\n this._fullData = null;\n this._numericColumns = null;\n this._config = null;\n }\n\n // \u2500\u2500 Default config factory \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n static createDefaultConfig(headers, numericColumns) {\n const isNumeric = numericColumns instanceof Set ? (h) => numericColumns.has(h) : () => true;\n\n // Find best X column (time-like, must be numeric)\n let xColumn = '';\n for (const h of headers) {\n if (isNumeric(h) && this.classifyHeader(h) === 'time') {\n xColumn = h;\n break;\n }\n }\n // Fallback: first numeric column\n if (!xColumn) {\n for (const h of headers) {\n if (isNumeric(h)) { xColumn = h; break; }\n }\n }\n\n // Pick first N non-X numeric columns for Y\n const yColumns = [];\n for (const h of headers) {\n if (h === xColumn) continue;\n if (!isNumeric(h)) continue;\n if (yColumns.length >= MAX_AUTO_COLUMNS) break;\n yColumns.push({ header: h, color: getSeriesColor(yColumns.length) });\n }\n\n return {\n xColumn,\n yColumns,\n chartType: 'line',\n showDataPoints: false,\n interpolation: 'linear',\n legendPosition: 'top',\n };\n }\n}\n\n// \u2500\u2500 Singleton window management \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst _openPlotWindows = new Map();\n\nexport async function openDataSeriesPlotWindow(options) {\n const { seriesId } = options;\n let win = _openPlotWindows.get(seriesId);\n if (win?.window?.isVisible) {\n win.window.bringToFront();\n return win;\n }\n\n win = new DataSeriesPlotWindow(options);\n _openPlotWindows.set(seriesId, win);\n await win.show();\n return win;\n}\n\nexport function closeDataSeriesPlotWindow(seriesId) {\n const win = _openPlotWindows.get(seriesId);\n if (win) {\n win.close();\n _openPlotWindows.delete(seriesId);\n }\n}\n\nexport default DataSeriesPlotWindow;\n", "/**\n * downsample.js\n *\n * Client-side time-series decimation for the render path. A long live run\n * accumulates thousands of points per series; feeding all of them to\n * Plotly.react() every refresh is what makes a running dashboard crawl.\n * These helpers cap the points actually drawn while preserving the visual\n * shape.\n *\n * Two strategies, mirroring EcoSim (App/downsampler.py = LTTB,\n * streaming/server.py = stride decimation):\n *\n * \u2022 strideDownsampleShared \u2014 one set of indices applied to the shared x\n * axis and EVERY series. Cheap, gap-safe (nulls keep their slot), and\n * alignment-safe (all series sample the same x), so it works for\n * stacked/area charts and multi-series subplots. This is the default\n * used by the dashboard tiles.\n *\n * \u2022 lttb \u2014 Largest-Triangle-Three-Buckets for a single (x, y) line.\n * Higher visual fidelity (keeps peaks/troughs) but each series picks\n * its own x, so it's only safe for independent line/scatter traces\n * with finite y. Offered for callers that want it.\n */\n\n/**\n * Pick ~targetPoints evenly-spaced indices out of [0, n), always including\n * the first and last. Returns an ascending Int array; returns null when no\n * decimation is needed (n <= targetPoints) so callers can skip the copy.\n *\n * @param {number} n\n * @param {number} targetPoints\n * @returns {number[]|null}\n */\nexport function strideIndices(n, targetPoints) {\n if (!Number.isFinite(n) || n <= 0) return null;\n const target = Math.max(2, Math.floor(targetPoints) || 0);\n if (n <= target) return null;\n const step = (n - 1) / (target - 1);\n const idx = new Array(target);\n for (let i = 0; i < target; i++) idx[i] = Math.round(i * step);\n idx[target - 1] = n - 1; // pin the last point exactly\n // Dedupe (rounding can collide on dense targets) while staying ascending.\n let w = 0;\n for (let r = 0; r < idx.length; r++) {\n if (w === 0 || idx[r] > idx[w - 1]) idx[w++] = idx[r];\n }\n idx.length = w;\n return idx;\n}\n\n/**\n * Decimate a shared x axis plus a set of aligned series using one common\n * set of indices, so every series stays sampled at the same x (alignment-\n * and gap-safe). No-op (returns the inputs untouched) when under threshold.\n *\n * @param {Array<number>} time \u2014 shared x values\n * @param {Object<string, Array>} series \u2014 name \u2192 aligned value array\n * @param {number} targetPoints\n * @returns {{ time: Array<number>, series: Object<string, Array> }}\n */\nexport function strideDownsampleShared(time, series, targetPoints) {\n const idx = strideIndices(time?.length ?? 0, targetPoints);\n if (!idx) return { time, series };\n const dsTime = idx.map((i) => time[i]);\n const dsSeries = {};\n for (const [name, arr] of Object.entries(series || {})) {\n if (!Array.isArray(arr)) { dsSeries[name] = arr; continue; }\n dsSeries[name] = idx.map((i) => (i < arr.length ? arr[i] : null));\n }\n return { time: dsTime, series: dsSeries };\n}\n\n/**\n * Largest-Triangle-Three-Buckets for one line. Preserves the first and\n * last points and the most \"feature-bearing\" point per bucket. Assumes\n * ascending, finite x; y entries that are null/NaN are treated as 0 for\n * the area math (callers with gappy series should prefer the stride path).\n *\n * @param {Array<number>} x\n * @param {Array<number>} y\n * @param {number} threshold \u2014 target point count\n * @returns {{ x: Array<number>, y: Array<number> }}\n */\nexport function lttb(x, y, threshold) {\n const n = Math.min(x?.length ?? 0, y?.length ?? 0);\n const target = Math.floor(threshold) || 0;\n if (target <= 0 || target >= n) return { x: x.slice(0, n), y: y.slice(0, n) };\n if (target < 3) return { x: [x[0], x[n - 1]], y: [y[0], y[n - 1]] };\n\n const num = (v) => (Number.isFinite(v) ? v : 0);\n const outX = [x[0]];\n const outY = [y[0]];\n const bucketSize = (n - 2) / (target - 2);\n let a = 0;\n\n for (let i = 0; i < target - 2; i++) {\n let nextStart = Math.floor((i + 1) * bucketSize) + 1;\n let nextEnd = Math.min(Math.floor((i + 2) * bucketSize) + 1, n);\n if (nextEnd <= nextStart) nextEnd = Math.min(nextStart + 1, n);\n let avgX = 0, avgY = 0;\n const count = nextEnd - nextStart;\n for (let j = nextStart; j < nextEnd; j++) { avgX += x[j]; avgY += num(y[j]); }\n avgX /= count; avgY /= count;\n\n const curStart = Math.floor(i * bucketSize) + 1;\n const curEnd = Math.min(Math.floor((i + 1) * bucketSize) + 1, n);\n const ax = x[a], ay = num(y[a]);\n let maxArea = -1, chosen = curStart;\n for (let j = curStart; j < curEnd; j++) {\n const area = Math.abs((ax - avgX) * (num(y[j]) - ay) - (ax - x[j]) * (avgY - ay));\n if (area > maxArea) { maxArea = area; chosen = j; }\n }\n outX.push(x[chosen]); outY.push(y[chosen]);\n a = chosen;\n }\n\n outX.push(x[n - 1]); outY.push(y[n - 1]);\n return { x: outX, y: outY };\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
3
|
+
"sources": ["../src/charts/data_series_plot_window.js"],
|
|
4
|
+
"sourcesContent": ["/**\n * Data Series Plot Window\n *\n * Opens a data series in a ManagedWindow with a split layout:\n * - Left: Plotly chart\n * - Right: Configuration panel (column selection, chart type, styling)\n *\n * Config is persisted in workspace metadata via savePlotConfig callback.\n */\n\nimport { ManagedWindow } from '../ui/components/managed_window.js';\nimport {\n ensurePlotly,\n purge,\n DARK_THEME_LAYOUT,\n DEFAULT_CONFIG,\n getSeriesColor,\n} from './plotly_wrapper.js';\nimport {\n buildTrace,\n buildYAxisLayout,\n buildXAxisLayout,\n buildLegendLayout,\n} from './chart_types.js';\n// The two header helpers are INJECTED, not imported.\n//\n// They used to come from ui/js/ui/utils/header_utils.js, which decides whether a\n// column is a STOCK or a FLOW by looking for `asset`, `liability`, `equity`,\n// `reserves`, `deposit`. That is stock-flow-consistent accounting, not charting,\n// and that one import was the last thing keeping this window out of @flexdesk/charts.\n//\n// The defaults below are what a chart library can honestly know about a column\n// name: whether it looks like a time axis, and nothing else.\n\n/** A column called `t`/`time`/`step`/`period` is the x-axis. Everything else is data. */\nconst defaultClassifyHeader = (header) => {\n const lower = String(header ?? '').trim().toLowerCase();\n return (lower === 't' || lower === 'time' || lower === 'step' || lower === 'period')\n ? 'time' : 'indicator';\n};\n\n/** No convention to strip, absent one being supplied. */\nconst defaultLabelHeader = (header) => header;\nimport { createRafResizeObserver } from '../ui/utils/raf_resize_observer.js';\n\nconst INTERPOLATION_MODES = ['linear', 'step', 'cubic'];\nconst CHART_TYPES = ['line', 'scatter', 'bar', 'area', 'area-stacked'];\nconst LEGEND_POSITIONS = ['top', 'bottom', 'right', 'hidden'];\nconst MAX_AUTO_COLUMNS = 3;\n\nclass DataSeriesPlotWindow {\n /**\n * @param {Object} options\n * @param {string} options.seriesId\n * @param {string} options.seriesName\n * @param {string} options.datasetName\n * @param {string[]} options.headers - Column headers from the series\n * @param {Object} options.dataHub - DataHub service for fetching data\n * @param {Object} options.services - { eventBus, logger }\n * @param {Object} [options.host] - A Host (ui/js/host/host.js). Its `dialogs`\n * capability backs Export PNG/CSV; absent => Blob download fallback.\n * @param {Function} options.getPlotConfig - (seriesId) => config | null\n * @param {Function} options.savePlotConfig - (seriesId, config) => void\n */\n constructor({ seriesId, seriesName, datasetName, headers, dataHub, services, host,\n getPlotConfig, savePlotConfig,\n classifyHeader = defaultClassifyHeader,\n labelHeader = defaultLabelHeader }) {\n this.classifyHeader = classifyHeader;\n this.labelHeader = labelHeader;\n this.seriesId = seriesId;\n this.seriesName = seriesName;\n this.datasetName = datasetName;\n this.headers = headers;\n this.dataHub = dataHub;\n this.services = services;\n this._host = host || null;\n this.getPlotConfig = getPlotConfig;\n this.savePlotConfig = savePlotConfig;\n\n this.window = null;\n this.contentEl = null;\n this.plotDiv = null;\n this._resizeObserver = null;\n this._chartCreated = false;\n this._config = null;\n this._fullData = null; // { headers, rows } with all data\n this._numericColumns = null; // Set of numeric column headers\n this._loading = false;\n }\n\n async show() {\n if (this.window?.isVisible) {\n this.window.bringToFront();\n return;\n }\n\n await ensurePlotly();\n await this.#fetchData();\n\n // Determine which columns are numeric from actual data\n this._numericColumns = new Set(this.headers.filter(h => this.#isNumericColumn(h)));\n\n // Load or create config (filter out any non-numeric columns from saved config)\n const saved = this.getPlotConfig?.(this.seriesId);\n if (saved) {\n this._config = { ...saved };\n this._config.yColumns = (saved.yColumns || []).filter(c => this._numericColumns.has(c.header));\n } else {\n this._config = DataSeriesPlotWindow.createDefaultConfig(this.headers, this._numericColumns);\n }\n\n this.#createWindow();\n this.#setupResizeObserver();\n await this.#renderPlot();\n }\n\n close() {\n if (this.window?.isVisible) {\n this.window.close();\n }\n }\n\n // \u2500\u2500 Window creation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n #createWindow() {\n this.contentEl = this.#buildContent();\n\n const title = this.datasetName\n ? `${this.datasetName} \u00B7 ${this.seriesName}`\n : this.seriesName || 'Series Plot';\n\n this.window = new ManagedWindow({\n id: `data-series-plot-${this.seriesId}`,\n title,\n icon: 'monitoring',\n content: this.contentEl,\n minWidth: 600,\n minHeight: 400,\n defaultWidth: 900,\n defaultHeight: 550,\n canMinimize: true,\n canMaximize: true,\n canResize: true,\n canDrag: true,\n onClose: () => this.#dispose(),\n });\n\n this.window.show();\n }\n\n #buildContent() {\n const container = document.createElement('div');\n container.className = 'twm-data-series-plot-container';\n\n // Actions bar (download buttons)\n const actionsBar = document.createElement('div');\n actionsBar.className = 'twm-data-series-plot-actions';\n\n const pngBtn = document.createElement('button');\n pngBtn.className = 'twm-btn-icon twm-has-tooltip';\n pngBtn.type = 'button';\n pngBtn.setAttribute('data-tooltip', 'Download plot as PNG');\n pngBtn.innerHTML = '<span class=\"material-symbols-outlined\">image</span>';\n pngBtn.addEventListener('click', () => this.#downloadPNG());\n\n const csvBtn = document.createElement('button');\n csvBtn.className = 'twm-btn-icon twm-has-tooltip';\n csvBtn.type = 'button';\n csvBtn.setAttribute('data-tooltip', 'Download data as CSV');\n csvBtn.innerHTML = '<span class=\"material-symbols-outlined\">download</span>';\n csvBtn.addEventListener('click', () => this.#downloadCSV());\n\n actionsBar.appendChild(pngBtn);\n actionsBar.appendChild(csvBtn);\n\n // Chart area\n const chartWrap = document.createElement('div');\n chartWrap.className = 'twm-data-series-plot-body';\n\n this.plotDiv = document.createElement('div');\n this.plotDiv.className = 'twm-data-series-plot-chart';\n chartWrap.appendChild(this.plotDiv);\n\n // Config panel\n const configPanel = this.#buildConfigPanel();\n chartWrap.appendChild(configPanel);\n\n container.appendChild(actionsBar);\n container.appendChild(chartWrap);\n\n return container;\n }\n\n // \u2500\u2500 Config panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n #buildConfigPanel() {\n const panel = document.createElement('div');\n panel.className = 'twm-data-series-plot-config';\n\n // X Column\n panel.appendChild(this.#buildSection('X Axis', this.#buildXColumnSelect()));\n\n // Y Columns\n this._yColumnsList = document.createElement('div');\n this._yColumnsList.className = 'twm-data-series-plot-config__columns';\n this.#populateYColumns();\n panel.appendChild(this.#buildSection('Y Axis', this._yColumnsList));\n\n // Chart type\n panel.appendChild(this.#buildSection('Chart Type', this.#buildSelect(\n CHART_TYPES,\n this._config.chartType,\n (val) => { this._config.chartType = val; this.#onConfigChanged(); }\n )));\n\n // Interpolation\n panel.appendChild(this.#buildSection('Interpolation', this.#buildSelect(\n INTERPOLATION_MODES,\n this._config.interpolation,\n (val) => { this._config.interpolation = val; this.#onConfigChanged(); }\n )));\n\n // Legend position\n panel.appendChild(this.#buildSection('Legend', this.#buildSelect(\n LEGEND_POSITIONS,\n this._config.legendPosition,\n (val) => { this._config.legendPosition = val; this.#onConfigChanged(); }\n )));\n\n // Show data points\n panel.appendChild(this.#buildSection('', this.#buildCheckbox(\n 'Show data points',\n this._config.showDataPoints,\n (checked) => { this._config.showDataPoints = checked; this.#onConfigChanged(); }\n )));\n\n return panel;\n }\n\n #buildSection(label, content) {\n const section = document.createElement('div');\n section.className = 'twm-data-series-plot-config__section';\n\n if (label) {\n const labelEl = document.createElement('div');\n labelEl.className = 'twm-data-series-plot-config__label';\n labelEl.textContent = label;\n section.appendChild(labelEl);\n }\n\n if (content instanceof HTMLElement) {\n section.appendChild(content);\n }\n\n return section;\n }\n\n #buildXColumnSelect() {\n const select = document.createElement('select');\n for (const header of this.headers) {\n if (!this._numericColumns.has(header)) continue;\n const opt = document.createElement('option');\n opt.value = header;\n opt.textContent = this.labelHeader(header);\n if (header === this._config.xColumn) opt.selected = true;\n select.appendChild(opt);\n }\n select.addEventListener('change', () => {\n this._config.xColumn = select.value;\n this.#onConfigChanged();\n });\n return select;\n }\n\n #populateYColumns() {\n const list = this._yColumnsList;\n list.innerHTML = '';\n\n const selectedSet = new Set(this._config.yColumns.map(c => c.header));\n\n for (const header of this.headers) {\n if (header === this._config.xColumn) continue;\n if (!this._numericColumns.has(header)) continue;\n\n const item = document.createElement('label');\n item.className = 'twm-data-series-plot-config__column-item';\n\n const cb = document.createElement('input');\n cb.type = 'checkbox';\n cb.checked = selectedSet.has(header);\n cb.addEventListener('change', () => this.#toggleYColumn(header, cb.checked));\n\n const colorSwatch = document.createElement('input');\n colorSwatch.type = 'color';\n colorSwatch.className = 'twm-data-series-plot-config__color-swatch';\n const existing = this._config.yColumns.find(c => c.header === header);\n colorSwatch.value = existing?.color || this.#nextColor(this.headers.indexOf(header));\n colorSwatch.addEventListener('input', () => this.#updateColumnColor(header, colorSwatch.value));\n\n const label = document.createElement('span');\n label.textContent = this.labelHeader(header);\n label.title = header;\n\n item.appendChild(cb);\n item.appendChild(colorSwatch);\n item.appendChild(label);\n list.appendChild(item);\n }\n }\n\n #toggleYColumn(header, checked) {\n if (checked) {\n if (!this._config.yColumns.find(c => c.header === header)) {\n const colorSwatch = this._yColumnsList.querySelector(\n `label:has(span[title=\"${header}\"]) input[type=\"color\"]`\n );\n this._config.yColumns.push({\n header,\n color: colorSwatch?.value || this.#nextColor(this.headers.indexOf(header)),\n });\n }\n } else {\n this._config.yColumns = this._config.yColumns.filter(c => c.header !== header);\n }\n this.#onConfigChanged();\n }\n\n #updateColumnColor(header, color) {\n const col = this._config.yColumns.find(c => c.header === header);\n if (col) {\n col.color = color;\n this.#onConfigChanged();\n }\n }\n\n #nextColor(index) {\n return getSeriesColor(index);\n }\n\n #buildSelect(options, value, onChange) {\n const select = document.createElement('select');\n for (const opt of options) {\n const el = document.createElement('option');\n el.value = opt;\n el.textContent = opt.charAt(0).toUpperCase() + opt.slice(1);\n if (opt === value) el.selected = true;\n select.appendChild(el);\n }\n select.addEventListener('change', () => onChange(select.value));\n return select;\n }\n\n #buildCheckbox(label, checked, onChange) {\n const wrapper = document.createElement('label');\n wrapper.className = 'twm-data-series-plot-config__column-item';\n\n const cb = document.createElement('input');\n cb.type = 'checkbox';\n cb.checked = checked;\n cb.addEventListener('change', () => onChange(cb.checked));\n\n const span = document.createElement('span');\n span.textContent = label;\n\n wrapper.appendChild(cb);\n wrapper.appendChild(span);\n return wrapper;\n }\n\n // \u2500\u2500 Data fetching \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n async #fetchData() {\n if (this._loading) return;\n this._loading = true;\n\n try {\n const response = await this.dataHub.fetchSeriesById(this.seriesId, { limit: null, offset: 0 });\n if (response?.ok) {\n this._fullData = {\n headers: response.headers || [],\n rows: response.rows || [],\n };\n } else {\n this.#notify('Plot', response?.error || 'Failed to load series data', 'error');\n }\n } catch (err) {\n this.services.logger?.warn?.('[DataSeriesPlotWindow] Fetch failed', err);\n this.#notify('Plot', 'Failed to load data', 'error');\n } finally {\n this._loading = false;\n }\n }\n\n #isNumericColumn(header) {\n if (!this._fullData) return false;\n const data = this.#extractColumnData(header);\n // Sample up to 20 non-empty values\n let numericCount = 0;\n let sampleCount = 0;\n for (const v of data) {\n if (v == null || v === '') continue;\n sampleCount++;\n if (Number.isFinite(Number(v))) numericCount++;\n if (sampleCount >= 20) break;\n }\n return sampleCount > 0 && numericCount / sampleCount >= 0.8;\n }\n\n // \u2500\u2500 Chart rendering \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n #extractColumnData(columnHeader) {\n if (!this._fullData) return [];\n\n const { headers, rows } = this._fullData;\n const idx = headers.indexOf(columnHeader);\n if (idx < 0) return [];\n\n const TIME_NAMES = new Set(['time', 'tDisp', 'year', 'date', 'timestamp']);\n const firstIsTime = TIME_NAMES.has(headers[0]);\n const colOffset = firstIsTime ? 1 : 0;\n\n return rows.map(row => {\n if (Array.isArray(row)) return row[idx];\n if (row && typeof row === 'object') {\n // Handle {tDisp, cols} format\n if (TIME_NAMES.has(columnHeader) && row.tDisp !== undefined) return row.tDisp;\n if (Array.isArray(row.cols)) return row.cols[idx - colOffset] ?? '';\n return row[columnHeader] ?? '';\n }\n return '';\n }).map(v => {\n const n = Number(v);\n return Number.isFinite(n) ? n : v;\n });\n }\n\n async #renderPlot() {\n if (!this.plotDiv || !window.Plotly || !this._fullData) return;\n\n const cfg = this._config;\n const xData = this.#extractColumnData(cfg.xColumn);\n\n if (xData.length === 0 || cfg.yColumns.length === 0) {\n // Show empty state\n if (this._chartCreated) {\n try { purge(this.plotDiv); } catch (e) { /* ignore */ }\n this._chartCreated = false;\n }\n this.plotDiv.innerHTML = '<div style=\"display:flex;align-items:center;justify-content:center;height:100%;color:#888;font-size:13px;\">Select columns to plot</div>';\n return;\n }\n\n // Clear empty state if present\n if (!this._chartCreated) {\n this.plotDiv.innerHTML = '';\n }\n\n const traces = [];\n\n for (let i = 0; i < cfg.yColumns.length; i++) {\n const col = cfg.yColumns[i];\n const yData = this.#extractColumnData(col.header);\n\n traces.push(buildTrace({\n chartType: cfg.chartType,\n x: xData,\n y: yData,\n name: this.labelHeader(col.header),\n color: col.color,\n lineWidth: 2,\n lineStyle: 'solid',\n interpolation: cfg.interpolation,\n yAxisId: 'y',\n showMarkers: cfg.showDataPoints,\n seriesIndex: i,\n }));\n }\n\n const legendAtTop = cfg.legendPosition === 'top';\n const legendAtBottom = cfg.legendPosition === 'bottom';\n\n const layout = {\n ...DARK_THEME_LAYOUT,\n autosize: true,\n margin: {\n l: 60, r: 40,\n b: legendAtBottom ? 80 : 50,\n t: legendAtTop ? 60 : 30,\n },\n };\n\n // Remove 3D scene from layout\n delete layout.scene;\n\n // X-axis\n layout.xaxis = {\n ...buildXAxisLayout({ title: this.labelHeader(cfg.xColumn) }),\n };\n\n // Unified hover\n layout.hovermode = 'x unified';\n layout.xaxis.showspikes = true;\n layout.xaxis.spikemode = 'across';\n layout.xaxis.spikethickness = 1;\n layout.xaxis.spikecolor = '#666666';\n layout.xaxis.spikedash = 'dot';\n\n // Y-axis\n layout.yaxis = buildYAxisLayout({\n axisId: 'yaxis',\n title: '',\n position: 'left',\n scaleType: 'linear',\n showGrid: true,\n });\n\n // Legend\n const legendConfig = buildLegendLayout(cfg.legendPosition);\n if (legendConfig.legend) {\n layout.legend = legendConfig.legend;\n }\n layout.showlegend = legendConfig.showlegend !== false;\n\n const plotConfig = {\n ...DEFAULT_CONFIG,\n responsive: true,\n displayModeBar: true,\n modeBarButtonsToRemove: ['lasso2d', 'select2d', 'toImage'],\n };\n\n try {\n await window.Plotly.newPlot(this.plotDiv, traces, layout, plotConfig);\n this._chartCreated = true;\n\n requestAnimationFrame(() => {\n try {\n window.Plotly.Plots.resize(this.plotDiv);\n } catch (e) { /* ignore */ }\n });\n } catch (e) {\n console.error('[DataSeriesPlotWindow] Chart render error:', e);\n }\n }\n\n // \u2500\u2500 Config change handler \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n #onConfigChanged() {\n this.#renderPlot();\n this.savePlotConfig?.(this.seriesId, { ...this._config, yColumns: [...this._config.yColumns] });\n }\n\n // \u2500\u2500 Resize observer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n #setupResizeObserver() {\n if (!this.plotDiv) return;\n\n this._resizeObserver = createRafResizeObserver(() => {\n if (this.plotDiv && window.Plotly && this._chartCreated) {\n try {\n window.Plotly.Plots.resize(this.plotDiv);\n } catch (e) { /* ignore */ }\n }\n });\n\n this._resizeObserver.observe(this.plotDiv);\n }\n\n // \u2500\u2500 Downloads \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n async #downloadPNG() {\n if (!this.plotDiv || !window.Plotly || !this._chartCreated) {\n this.#notify('Download', 'Plot not ready', 'warn');\n return;\n }\n\n const filename = `${(this.seriesName || 'series').replace(/[^a-z0-9]/gi, '_')}.png`;\n\n try {\n const pngDataUrl = await window.Plotly.toImage(this.plotDiv, {\n format: 'png', width: 1920, height: 1080, scale: 2,\n });\n\n // null <=> no save dialog behind the capability (host.js) -> fall back.\n const dialogs = this._host?.dialogs;\n const result = dialogs\n ? await dialogs.saveFile({ data: pngDataUrl.split(',')[1], filename, kind: 'png' })\n : null;\n if (result) {\n if (result.ok) {\n this.#notify('Download', `Plot saved to ${result.path}`, 'success');\n } else if (!result.cancelled) {\n this.#notify('Download', result.error || 'Failed to save image', 'error');\n }\n } else {\n const link = document.createElement('a');\n link.href = pngDataUrl;\n link.download = filename;\n link.click();\n }\n } catch (err) {\n this.services.logger?.warn?.('[DataSeriesPlotWindow] PNG download failed', err);\n this.#notify('Download', 'Failed to generate PNG', 'error');\n }\n }\n\n async #downloadCSV() {\n if (!this._fullData || !this._fullData.rows.length) {\n this.#notify('Download', 'No data available', 'warn');\n return;\n }\n\n const cfg = this._config;\n const columns = [cfg.xColumn, ...cfg.yColumns.map(c => c.header)];\n const headers = columns.map(h => this.labelHeader(h));\n\n // Pre-extract all columns once\n const columnData = columns.map(col => this.#extractColumnData(col));\n const rowCount = this._fullData.rows.length;\n\n const lines = [headers.join(',')];\n for (let r = 0; r < rowCount; r++) {\n const vals = columnData.map(data => this.#csvEscape(data[r]));\n lines.push(vals.join(','));\n }\n const csv = lines.join('\\n');\n\n const filename = `${(this.seriesName || 'series').replace(/[^a-z0-9]/gi, '_')}_data.csv`;\n\n // null <=> no save dialog behind the capability (host.js) -> fall back.\n const dialogs = this._host?.dialogs;\n let result = null;\n if (dialogs) {\n try {\n result = await dialogs.saveFile({ data: csv, filename, kind: 'csv' });\n } catch (err) {\n this.#notify('Download', 'Failed to save file', 'error');\n return;\n }\n }\n if (result) {\n if (result.ok) {\n this.#notify('Download', `Saved to ${result.path}`, 'success');\n } else if (!result.cancelled) {\n this.#notify('Download', result.error || 'Failed to save file', 'error');\n }\n } else {\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = filename;\n link.click();\n URL.revokeObjectURL(url);\n }\n }\n\n #csvEscape(value) {\n if (value == null) return '';\n const str = String(value);\n if (/[\",\\n]/.test(str)) return `\"${str.replace(/\"/g, '\"\"')}\"`;\n return str;\n }\n\n // \u2500\u2500 Notifications \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n #notify(title, message, severity = 'info') {\n this.services.eventBus?.emit?.('toast:show', { title, message, type: severity });\n }\n\n // \u2500\u2500 Disposal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n #dispose() {\n if (this._resizeObserver) {\n this._resizeObserver.disconnect();\n this._resizeObserver = null;\n }\n\n if (this.plotDiv && window.Plotly) {\n try { purge(this.plotDiv); } catch (e) { /* ignore */ }\n }\n\n _openPlotWindows.delete(this.seriesId);\n\n this.window = null;\n this.contentEl = null;\n this.plotDiv = null;\n this._yColumnsList = null;\n this._chartCreated = false;\n this._fullData = null;\n this._numericColumns = null;\n this._config = null;\n }\n\n // \u2500\u2500 Default config factory \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n static createDefaultConfig(headers, numericColumns) {\n const isNumeric = numericColumns instanceof Set ? (h) => numericColumns.has(h) : () => true;\n\n // Find best X column (time-like, must be numeric)\n let xColumn = '';\n for (const h of headers) {\n if (isNumeric(h) && this.classifyHeader(h) === 'time') {\n xColumn = h;\n break;\n }\n }\n // Fallback: first numeric column\n if (!xColumn) {\n for (const h of headers) {\n if (isNumeric(h)) { xColumn = h; break; }\n }\n }\n\n // Pick first N non-X numeric columns for Y\n const yColumns = [];\n for (const h of headers) {\n if (h === xColumn) continue;\n if (!isNumeric(h)) continue;\n if (yColumns.length >= MAX_AUTO_COLUMNS) break;\n yColumns.push({ header: h, color: getSeriesColor(yColumns.length) });\n }\n\n return {\n xColumn,\n yColumns,\n chartType: 'line',\n showDataPoints: false,\n interpolation: 'linear',\n legendPosition: 'top',\n };\n }\n}\n\n// \u2500\u2500 Singleton window management \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst _openPlotWindows = new Map();\n\nexport async function openDataSeriesPlotWindow(options) {\n const { seriesId } = options;\n let win = _openPlotWindows.get(seriesId);\n if (win?.window?.isVisible) {\n win.window.bringToFront();\n return win;\n }\n\n win = new DataSeriesPlotWindow(options);\n _openPlotWindows.set(seriesId, win);\n await win.show();\n return win;\n}\n\nexport function closeDataSeriesPlotWindow(seriesId) {\n const win = _openPlotWindows.get(seriesId);\n if (win) {\n win.close();\n _openPlotWindows.delete(seriesId);\n }\n}\n\nexport default DataSeriesPlotWindow;\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|