flexdesk 0.17.7 → 0.17.8
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/CHANGELOG.md +18 -0
- package/css/flexdesk.css +9 -2
- package/dist/charts.js +1 -1
- package/dist/{chunk-RMTRWWEO.js → chunk-EIYNF4BD.js} +66 -25
- package/dist/chunk-EIYNF4BD.js.map +7 -0
- package/dist/flexdesk.css +9 -2
- package/dist/tiles.js +1 -1
- package/docs/components/charts.md +66 -0
- package/package.json +1 -1
- package/src/charts/plot_grid.js +16 -4
- package/src/charts/plot_popout_window.js +2 -1
- package/src/charts/series_transforms.js +114 -20
- package/dist/chunk-RMTRWWEO.js.map +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.17.8 (2026-10-24)
|
|
4
|
+
|
|
5
|
+
A patch release: everything below is additive. Every fix is documented in a
|
|
6
|
+
section of the docs headed "(0.17.8)"; each entry links to it.
|
|
7
|
+
|
|
8
|
+
### Added and changed
|
|
9
|
+
|
|
10
|
+
- [A series transform knows its series, and takes a choice parameter](docs/components/charts.md#a-series-transform-knows-its-series-and-takes-a-choice-parameter-0178): a `seriesTransforms` entry's `run` gets a third argument, `run(y, params, { series, subplot, results, resolveSeries })`: the subplot's series entry the trace was drawn from (null for any other trace, such as an overlay), the subplot config, the results the figure was drawn from and the `resolveSeries` it was drawn with, so a transform can resolve the same series from other results (a baseline scenario) the same way. `renderPlotGrid`, and so `PlotPopoutWindow`, run transforms on the same traces as in 0.17.7 and draw the same derived traces; `deriveSeriesTraces` takes the new optional `seriesOf`, `results` and `resolveSeries` options, and without them passes `series`, `results` and `resolveSeries` as null. A parameter declared `{ type: 'choice', options }` (an array of `{ value, label }`, or a function `(subplot) => options`) is drawn by `renderSeriesTransformControls` as a select (choice_controls' `selectBox`, styled by the new `.twm-series-transforms__select` rule) instead of a number field; the pick is saved under `subplot.transforms.<id>.params` and passed to `run` like a number, and without a `default` or a saved value it is the first enabled option. A transform written as `run(y, params)` with number parameters works and draws exactly as before (STORY-0111).
|
|
11
|
+
|
|
12
|
+
### Removed or renamed exports
|
|
13
|
+
|
|
14
|
+
None. Compared with 0.17.7, across every entry point (`core`, `widgets`, `wm`,
|
|
15
|
+
`host`, `canvas`, `flow`, `charts`, `tiles`, `editor`) and the package
|
|
16
|
+
`exports` map, no export was added, removed or renamed. Not an export but
|
|
17
|
+
worth knowing: a series transform's `run` gets a third argument and
|
|
18
|
+
`deriveSeriesTraces` takes three more options, all optional, and flexdesk.css
|
|
19
|
+
gains a `.twm-series-transforms__select` rule (see above).
|
|
20
|
+
|
|
3
21
|
## 0.17.7 (2026-10-24)
|
|
4
22
|
|
|
5
23
|
A patch release: everything below is additive. Every fix is documented in a
|
package/css/flexdesk.css
CHANGED
|
@@ -10808,8 +10808,8 @@ button.twm-alerts__ref:hover { text-decoration: underline; }
|
|
|
10808
10808
|
/* ══ SERIES TRANSFORMS ═══════════════════════════════════════════════
|
|
10809
10809
|
*
|
|
10810
10810
|
* `renderSeriesTransformControls` (src/charts/series_transforms.js): per
|
|
10811
|
-
* injected transform, a group with a toggle per output and a number field
|
|
10812
|
-
* parameter, in a subplot's config. */
|
|
10811
|
+
* injected transform, a group with a toggle per output and a number field (or,
|
|
10812
|
+
* for a choice parameter, a select) per parameter, in a subplot's config. */
|
|
10813
10813
|
.twm-series-transforms {
|
|
10814
10814
|
display: flex;
|
|
10815
10815
|
flex-direction: column;
|
|
@@ -10849,6 +10849,13 @@ button.twm-alerts__ref:hover { text-decoration: underline; }
|
|
|
10849
10849
|
.twm-series-transforms__param:disabled {
|
|
10850
10850
|
opacity: 0.5;
|
|
10851
10851
|
}
|
|
10852
|
+
/* A choice parameter's select (0.17.8): as wide as its option labels. */
|
|
10853
|
+
.twm-series-transforms__select {
|
|
10854
|
+
width: auto;
|
|
10855
|
+
min-width: 96px;
|
|
10856
|
+
max-width: 60%;
|
|
10857
|
+
cursor: pointer;
|
|
10858
|
+
}
|
|
10852
10859
|
/* ══ end SERIES TRANSFORMS ══ */
|
|
10853
10860
|
|
|
10854
10861
|
/* ══ PLOT GRID ════════════════════════════════════════════════════════
|
package/dist/charts.js
CHANGED
|
@@ -3,7 +3,9 @@ import {
|
|
|
3
3
|
createRafResizeObserver
|
|
4
4
|
} from "./chunk-LWV23YVE.js";
|
|
5
5
|
import {
|
|
6
|
-
ManagedWindow
|
|
6
|
+
ManagedWindow,
|
|
7
|
+
flatChoices,
|
|
8
|
+
selectBox
|
|
7
9
|
} from "./chunk-CVMCZZX2.js";
|
|
8
10
|
|
|
9
11
|
// src/charts/plotly_wrapper.js
|
|
@@ -1177,12 +1179,25 @@ function migrateSingleChart(data) {
|
|
|
1177
1179
|
}
|
|
1178
1180
|
|
|
1179
1181
|
// src/charts/series_transforms.js
|
|
1180
|
-
|
|
1182
|
+
var isChoice = (spec) => spec?.type === "choice";
|
|
1183
|
+
function choiceOptions(spec, subplot) {
|
|
1184
|
+
const options = typeof spec.options === "function" ? spec.options(subplot) : spec.options;
|
|
1185
|
+
return Array.isArray(options) ? options : [];
|
|
1186
|
+
}
|
|
1187
|
+
function firstChoice(spec, subplot) {
|
|
1188
|
+
const flat = flatChoices(choiceOptions(spec, subplot));
|
|
1189
|
+
return (flat.find((o) => !o.disabled) ?? flat[0])?.value;
|
|
1190
|
+
}
|
|
1191
|
+
function resolveParams(transform, state, subplot) {
|
|
1192
|
+
const saved = state?.params ?? {};
|
|
1181
1193
|
const params = {};
|
|
1182
1194
|
for (const [name, spec] of Object.entries(transform.params ?? {})) {
|
|
1183
1195
|
params[name] = spec?.default;
|
|
1196
|
+
if (isChoice(spec) && spec.default === void 0 && saved[name] === void 0) {
|
|
1197
|
+
params[name] = firstChoice(spec, subplot);
|
|
1198
|
+
}
|
|
1184
1199
|
}
|
|
1185
|
-
return { ...params, ...
|
|
1200
|
+
return { ...params, ...saved };
|
|
1186
1201
|
}
|
|
1187
1202
|
function enabledOutputs(transform, state) {
|
|
1188
1203
|
return Object.keys(transform.outputs ?? {}).filter((name) => state?.outputs?.[name] === true);
|
|
@@ -1191,19 +1206,20 @@ function isEligible(trace) {
|
|
|
1191
1206
|
const type = trace.type ?? "scatter";
|
|
1192
1207
|
return (type === "scatter" || type === "scattergl") && Array.isArray(trace.y);
|
|
1193
1208
|
}
|
|
1194
|
-
async function deriveSeriesTraces(traces, subplot, seriesTransforms, { onError } = {}) {
|
|
1209
|
+
async function deriveSeriesTraces(traces, subplot, seriesTransforms, { onError, seriesOf, results, resolveSeries } = {}) {
|
|
1195
1210
|
const derived = [];
|
|
1196
1211
|
if (!seriesTransforms) return derived;
|
|
1197
|
-
const sources = traces.filter(isEligible);
|
|
1212
|
+
const sources = traces.filter(isEligible).map((trace) => ({ trace, series: typeof seriesOf === "function" ? seriesOf(trace) ?? null : null }));
|
|
1198
1213
|
for (const [transformId, transform] of Object.entries(seriesTransforms)) {
|
|
1199
1214
|
const state = subplot?.transforms?.[transformId];
|
|
1200
1215
|
const outputs = enabledOutputs(transform, state);
|
|
1201
1216
|
if (!outputs.length || typeof transform?.run !== "function") continue;
|
|
1202
|
-
const params = resolveParams(transform, state);
|
|
1203
|
-
for (const source of sources) {
|
|
1217
|
+
const params = resolveParams(transform, state, subplot);
|
|
1218
|
+
for (const { trace: source, series } of sources) {
|
|
1219
|
+
const context = { series, subplot, results: results ?? null, resolveSeries: resolveSeries ?? null };
|
|
1204
1220
|
let result;
|
|
1205
1221
|
try {
|
|
1206
|
-
result = await transform.run(source.y, params);
|
|
1222
|
+
result = await transform.run(source.y, params, context);
|
|
1207
1223
|
} catch (error) {
|
|
1208
1224
|
onError?.({ transformId, traceName: source.name, error });
|
|
1209
1225
|
continue;
|
|
@@ -1284,22 +1300,43 @@ function renderSeriesTransformControls(container, subplot, seriesTransforms, { o
|
|
|
1284
1300
|
row.className = "twm-series-transforms__row";
|
|
1285
1301
|
const text = document.createElement("span");
|
|
1286
1302
|
text.textContent = spec?.label ?? name;
|
|
1303
|
+
const writeParam = (value) => {
|
|
1304
|
+
const state = stateOf(transformId);
|
|
1305
|
+
const params = { ...state.params ?? {} };
|
|
1306
|
+
if (value === void 0) delete params[name];
|
|
1307
|
+
else params[name] = value;
|
|
1308
|
+
state.params = params;
|
|
1309
|
+
onChange?.();
|
|
1310
|
+
};
|
|
1311
|
+
const saved = subplot.transforms?.[transformId]?.params?.[name];
|
|
1312
|
+
if (isChoice(spec)) {
|
|
1313
|
+
const current2 = saved ?? spec.default ?? firstChoice(spec, subplot);
|
|
1314
|
+
const choice = selectBox(choiceOptions(spec, subplot), current2, {
|
|
1315
|
+
label: spec?.label ?? name,
|
|
1316
|
+
valueAttr: "value",
|
|
1317
|
+
className: "twm-series-transforms__param twm-series-transforms__select"
|
|
1318
|
+
});
|
|
1319
|
+
choice.box.addEventListener("change", () => {
|
|
1320
|
+
const value = choice.valueOf();
|
|
1321
|
+
choice.setOptions(choiceOptions(spec, subplot), value);
|
|
1322
|
+
writeParam(value);
|
|
1323
|
+
});
|
|
1324
|
+
paramInputs.push(choice.box);
|
|
1325
|
+
row.append(text, choice.box);
|
|
1326
|
+
group.appendChild(row);
|
|
1327
|
+
continue;
|
|
1328
|
+
}
|
|
1287
1329
|
const input = document.createElement("input");
|
|
1288
1330
|
input.type = "number";
|
|
1289
1331
|
input.className = "twm-series-transforms__param";
|
|
1290
1332
|
for (const attr of ["min", "max", "step"]) {
|
|
1291
1333
|
if (spec?.[attr] != null) input[attr] = String(spec[attr]);
|
|
1292
1334
|
}
|
|
1293
|
-
const current =
|
|
1335
|
+
const current = saved ?? spec?.default;
|
|
1294
1336
|
input.value = current == null ? "" : String(current);
|
|
1295
1337
|
input.addEventListener("change", () => {
|
|
1296
1338
|
const value = parseFloat(input.value);
|
|
1297
|
-
|
|
1298
|
-
const params = { ...state.params ?? {} };
|
|
1299
|
-
if (Number.isFinite(value)) params[name] = value;
|
|
1300
|
-
else delete params[name];
|
|
1301
|
-
state.params = params;
|
|
1302
|
-
onChange?.();
|
|
1339
|
+
writeParam(Number.isFinite(value) ? value : void 0);
|
|
1303
1340
|
});
|
|
1304
1341
|
paramInputs.push(input);
|
|
1305
1342
|
row.append(text, input);
|
|
@@ -1427,6 +1464,7 @@ function renderPlotGrid(container, plotConfig, results, options = {}) {
|
|
|
1427
1464
|
const yRefByAxisId = {};
|
|
1428
1465
|
let ownCount = 0;
|
|
1429
1466
|
let hiddenCount = 0;
|
|
1467
|
+
const seriesByTrace = /* @__PURE__ */ new Map();
|
|
1430
1468
|
const useX = sp.xAxis && sp.xAxis.useTime === false && sp.xAxis.variable;
|
|
1431
1469
|
const xData = useX ? opts.resolveSeries({ variable: sp.xAxis.variable }, data, sp)?.y ?? time : time;
|
|
1432
1470
|
const yAxes = sp.yAxes ?? [];
|
|
@@ -1461,7 +1499,7 @@ function renderPlotGrid(container, plotConfig, results, options = {}) {
|
|
|
1461
1499
|
if (!resolved?.y) continue;
|
|
1462
1500
|
const x = resolved.x ?? xData;
|
|
1463
1501
|
const y = Array.isArray(resolved.y) ? applyNanHandling(resolved.y, sp.nanHandling ?? "gap", x) : resolved.y;
|
|
1464
|
-
|
|
1502
|
+
const trace = buildTrace({
|
|
1465
1503
|
chartType: sp.chartType || "line",
|
|
1466
1504
|
x,
|
|
1467
1505
|
y,
|
|
@@ -1473,7 +1511,9 @@ function renderPlotGrid(container, plotConfig, results, options = {}) {
|
|
|
1473
1511
|
yAxisId: yRef,
|
|
1474
1512
|
showMarkers: sp.showDataPoints ?? false,
|
|
1475
1513
|
stackGroup: s.stackGroup ?? null
|
|
1476
|
-
})
|
|
1514
|
+
});
|
|
1515
|
+
traces.push(trace);
|
|
1516
|
+
seriesByTrace.set(trace, s);
|
|
1477
1517
|
const overlay = s.overlay ? opts.overlays?.[s.id] : null;
|
|
1478
1518
|
if (!overlay?.years?.length) continue;
|
|
1479
1519
|
const ov = s.overlay;
|
|
@@ -1503,12 +1543,12 @@ function renderPlotGrid(container, plotConfig, results, options = {}) {
|
|
|
1503
1543
|
traces.push(ovTrace);
|
|
1504
1544
|
}
|
|
1505
1545
|
});
|
|
1506
|
-
traces.push(...await deriveSeriesTraces(
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
));
|
|
1546
|
+
traces.push(...await deriveSeriesTraces(traces, sp, opts.seriesTransforms, {
|
|
1547
|
+
onError: opts.onTransformError ?? void 0,
|
|
1548
|
+
seriesOf: (trace) => seriesByTrace.get(trace) ?? null,
|
|
1549
|
+
results: data,
|
|
1550
|
+
resolveSeries: opts.resolveSeries
|
|
1551
|
+
}));
|
|
1512
1552
|
if (opts.derivedTraces) {
|
|
1513
1553
|
const extra = await opts.derivedTraces(traces, sp, { results: data, index });
|
|
1514
1554
|
if (Array.isArray(extra)) traces.push(...extra);
|
|
@@ -1667,7 +1707,8 @@ var PlotPopoutWindow = class {
|
|
|
1667
1707
|
* renders the Config tab. What it returns is disposed (`dispose()`) when the
|
|
1668
1708
|
* window closes, so an editor drawn there (renderPlotConfigEditor) goes with it.
|
|
1669
1709
|
* @param {Object} [options.seriesTransforms] - `{ [id]: transform }` (series_transforms.js);
|
|
1670
|
-
* absent => no derived series are drawn
|
|
1710
|
+
* absent => no derived series are drawn. Each runs as
|
|
1711
|
+
* `run(y, params, { series, subplot, results, resolveSeries })` (renderPlotGrid).
|
|
1671
1712
|
* @param {string} [options.refreshEvent='data:changed'] - Bus event after which the
|
|
1672
1713
|
* window re-reads `getResults()` and redraws.
|
|
1673
1714
|
* @param {{delta?: string|null, complete?: string|null}} [options.streamEvents] - Bus
|
|
@@ -2799,4 +2840,4 @@ export {
|
|
|
2799
2840
|
openPlotPopoutWindow,
|
|
2800
2841
|
openRawTracesWindow
|
|
2801
2842
|
};
|
|
2802
|
-
//# sourceMappingURL=chunk-
|
|
2843
|
+
//# sourceMappingURL=chunk-EIYNF4BD.js.map
|