flexdesk 0.1.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/LICENSE +21 -0
- package/README.md +195 -0
- package/THIRD_PARTY_LICENSES.md +53 -0
- package/css/base.css +3439 -0
- package/css/flexdesk.css +5797 -0
- package/css/overrides.css +2349 -0
- package/css/reset.css +46 -0
- package/css/tokens.css +137 -0
- package/dist/charts.js +780 -0
- package/dist/charts.js.map +7 -0
- package/dist/chunk-3PHCPZHT.js +222 -0
- package/dist/chunk-3PHCPZHT.js.map +7 -0
- package/dist/chunk-CT4YXXLP.js +1770 -0
- package/dist/chunk-CT4YXXLP.js.map +7 -0
- package/dist/chunk-DRYCDMEG.js +2217 -0
- package/dist/chunk-DRYCDMEG.js.map +7 -0
- package/dist/chunk-DVU44T77.js +855 -0
- package/dist/chunk-DVU44T77.js.map +7 -0
- package/dist/chunk-FL5KFNQH.js +640 -0
- package/dist/chunk-FL5KFNQH.js.map +7 -0
- package/dist/chunk-FOOS3T5L.js +117 -0
- package/dist/chunk-FOOS3T5L.js.map +7 -0
- package/dist/chunk-JYWURG5T.js +32 -0
- package/dist/chunk-JYWURG5T.js.map +7 -0
- package/dist/chunk-TLZUUFOE.js +276 -0
- package/dist/chunk-TLZUUFOE.js.map +7 -0
- package/dist/chunk-UCJ2WD4D.js +625 -0
- package/dist/chunk-UCJ2WD4D.js.map +7 -0
- package/dist/chunk-WVFGV5FT.js +205 -0
- package/dist/chunk-WVFGV5FT.js.map +7 -0
- package/dist/core.js +1098 -0
- package/dist/core.js.map +7 -0
- package/dist/editor.js +1661 -0
- package/dist/editor.js.map +7 -0
- package/dist/flexdesk.css +5797 -0
- package/dist/host.js +132 -0
- package/dist/host.js.map +7 -0
- package/dist/reset.css +46 -0
- package/dist/tile_registry-6WZPTUZV.js +28 -0
- package/dist/tile_registry-6WZPTUZV.js.map +7 -0
- package/dist/tiles.js +2068 -0
- package/dist/tiles.js.map +7 -0
- package/dist/tokens.css +137 -0
- package/dist/widgets.js +5438 -0
- package/dist/widgets.js.map +7 -0
- package/dist/wm.js +4830 -0
- package/dist/wm.js.map +7 -0
- package/package.json +80 -0
- package/src/charts/chart_types.js +699 -0
- package/src/charts/data_series_plot_window.js +761 -0
- package/src/charts/downsample.js +119 -0
- package/src/charts/plot_config.js +176 -0
- package/src/charts/plot_popout_window.js +1560 -0
- package/src/charts/plotly_wrapper.js +432 -0
- package/src/core/event_bus.js +230 -0
- package/src/core/logging.js +300 -0
- package/src/core/settings.js +813 -0
- package/src/core/state_guard.js +382 -0
- package/src/core/state_guard_config.js +309 -0
- package/src/core/state_machine.js +258 -0
- package/src/editor/editor_pane.js +264 -0
- package/src/editor/monaco_editor_factory.js +449 -0
- package/src/editor/monaco_loader.js +74 -0
- package/src/editor/notebook_search.js +460 -0
- package/src/editor/notebook_tab_bar.js +456 -0
- package/src/editor/notebook_undo_manager.js +108 -0
- package/src/editor/split_pane_container.js +197 -0
- package/src/help/help_modal.js +409 -0
- package/src/help/help_registry.js +65 -0
- package/src/host/host.js +69 -0
- package/src/host/pywebview_host.js +133 -0
- package/src/tiles/config_schema.js +256 -0
- package/src/tiles/layout_persistence.js +662 -0
- package/src/tiles/tile_base.js +785 -0
- package/src/tiles/tile_grid.js +905 -0
- package/src/tiles/tile_registry.js +136 -0
- package/src/tiling/command_palette.js +328 -0
- package/src/tiling/content_registry.js +110 -0
- package/src/tiling/desktops.js +133 -0
- package/src/tiling/entity_sources.js +179 -0
- package/src/tiling/keymap.js +156 -0
- package/src/tiling/kind_taxonomy.js +172 -0
- package/src/tiling/landing_table.js +717 -0
- package/src/tiling/loading_overlay.js +33 -0
- package/src/tiling/nav_panel.js +142 -0
- package/src/tiling/page_factory.js +215 -0
- package/src/tiling/panel_keys.js +145 -0
- package/src/tiling/shell.js +437 -0
- package/src/tiling/tile_breadcrumb.js +222 -0
- package/src/tiling/tile_renderer.js +459 -0
- package/src/tiling/tile_tab_menu.js +340 -0
- package/src/tiling/tile_tree.js +880 -0
- package/src/tiling/wm.js +1352 -0
- package/src/ui/base/component_base.js +30 -0
- package/src/ui/base/controller_base.js +48 -0
- package/src/ui/base/page_base.js +102 -0
- package/src/ui/components/about_dialog.js +224 -0
- package/src/ui/components/action_dropdown.js +389 -0
- package/src/ui/components/attribute_list_editor.js +652 -0
- package/src/ui/components/autocomplete_field.js +732 -0
- package/src/ui/components/computing_status_window.js +81 -0
- package/src/ui/components/confirm_dialog.js +307 -0
- package/src/ui/components/context_menu.js +87 -0
- package/src/ui/components/data_table.js +2228 -0
- package/src/ui/components/detail_header.js +177 -0
- package/src/ui/components/drag_reorder.js +0 -0
- package/src/ui/components/gallery_picker.js +147 -0
- package/src/ui/components/inline_renamer.js +129 -0
- package/src/ui/components/managed_window.js +804 -0
- package/src/ui/components/modal.js +512 -0
- package/src/ui/components/notification_history.js +264 -0
- package/src/ui/components/slide_out_panel.js +185 -0
- package/src/ui/components/slider_field.js +122 -0
- package/src/ui/components/sortable_list.js +309 -0
- package/src/ui/components/table_state_store.js +81 -0
- package/src/ui/components/toast.js +59 -0
- package/src/ui/components/tree_view.js +460 -0
- package/src/ui/components/window_taskbar.js +143 -0
- package/src/ui/controllers/panel_state_machine.js +634 -0
- package/src/ui/controllers/window_chrome_controller.js +659 -0
- package/src/ui/notification_center.js +576 -0
- package/src/ui/utils/overlay_scrollbar.js +492 -0
- package/src/ui/utils/raf_resize_observer.js +31 -0
- package/src/ui/utils/tooltip_service.js +406 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* downsample.js
|
|
3
|
+
*
|
|
4
|
+
* Client-side time-series decimation for the render path. A long live run
|
|
5
|
+
* accumulates thousands of points per series; feeding all of them to
|
|
6
|
+
* Plotly.react() every refresh is what makes a running dashboard crawl.
|
|
7
|
+
* These helpers cap the points actually drawn while preserving the visual
|
|
8
|
+
* shape.
|
|
9
|
+
*
|
|
10
|
+
* Two strategies, mirroring EcoSim (App/downsampler.py = LTTB,
|
|
11
|
+
* streaming/server.py = stride decimation):
|
|
12
|
+
*
|
|
13
|
+
* • strideDownsampleShared — one set of indices applied to the shared x
|
|
14
|
+
* axis and EVERY series. Cheap, gap-safe (nulls keep their slot), and
|
|
15
|
+
* alignment-safe (all series sample the same x), so it works for
|
|
16
|
+
* stacked/area charts and multi-series subplots. This is the default
|
|
17
|
+
* used by the dashboard tiles.
|
|
18
|
+
*
|
|
19
|
+
* • lttb — Largest-Triangle-Three-Buckets for a single (x, y) line.
|
|
20
|
+
* Higher visual fidelity (keeps peaks/troughs) but each series picks
|
|
21
|
+
* its own x, so it's only safe for independent line/scatter traces
|
|
22
|
+
* with finite y. Offered for callers that want it.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Pick ~targetPoints evenly-spaced indices out of [0, n), always including
|
|
27
|
+
* the first and last. Returns an ascending Int array; returns null when no
|
|
28
|
+
* decimation is needed (n <= targetPoints) so callers can skip the copy.
|
|
29
|
+
*
|
|
30
|
+
* @param {number} n
|
|
31
|
+
* @param {number} targetPoints
|
|
32
|
+
* @returns {number[]|null}
|
|
33
|
+
*/
|
|
34
|
+
export function strideIndices(n, targetPoints) {
|
|
35
|
+
if (!Number.isFinite(n) || n <= 0) return null;
|
|
36
|
+
const target = Math.max(2, Math.floor(targetPoints) || 0);
|
|
37
|
+
if (n <= target) return null;
|
|
38
|
+
const step = (n - 1) / (target - 1);
|
|
39
|
+
const idx = new Array(target);
|
|
40
|
+
for (let i = 0; i < target; i++) idx[i] = Math.round(i * step);
|
|
41
|
+
idx[target - 1] = n - 1; // pin the last point exactly
|
|
42
|
+
// Dedupe (rounding can collide on dense targets) while staying ascending.
|
|
43
|
+
let w = 0;
|
|
44
|
+
for (let r = 0; r < idx.length; r++) {
|
|
45
|
+
if (w === 0 || idx[r] > idx[w - 1]) idx[w++] = idx[r];
|
|
46
|
+
}
|
|
47
|
+
idx.length = w;
|
|
48
|
+
return idx;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Decimate a shared x axis plus a set of aligned series using one common
|
|
53
|
+
* set of indices, so every series stays sampled at the same x (alignment-
|
|
54
|
+
* and gap-safe). No-op (returns the inputs untouched) when under threshold.
|
|
55
|
+
*
|
|
56
|
+
* @param {Array<number>} time — shared x values
|
|
57
|
+
* @param {Object<string, Array>} series — name → aligned value array
|
|
58
|
+
* @param {number} targetPoints
|
|
59
|
+
* @returns {{ time: Array<number>, series: Object<string, Array> }}
|
|
60
|
+
*/
|
|
61
|
+
export function strideDownsampleShared(time, series, targetPoints) {
|
|
62
|
+
const idx = strideIndices(time?.length ?? 0, targetPoints);
|
|
63
|
+
if (!idx) return { time, series };
|
|
64
|
+
const dsTime = idx.map((i) => time[i]);
|
|
65
|
+
const dsSeries = {};
|
|
66
|
+
for (const [name, arr] of Object.entries(series || {})) {
|
|
67
|
+
if (!Array.isArray(arr)) { dsSeries[name] = arr; continue; }
|
|
68
|
+
dsSeries[name] = idx.map((i) => (i < arr.length ? arr[i] : null));
|
|
69
|
+
}
|
|
70
|
+
return { time: dsTime, series: dsSeries };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Largest-Triangle-Three-Buckets for one line. Preserves the first and
|
|
75
|
+
* last points and the most "feature-bearing" point per bucket. Assumes
|
|
76
|
+
* ascending, finite x; y entries that are null/NaN are treated as 0 for
|
|
77
|
+
* the area math (callers with gappy series should prefer the stride path).
|
|
78
|
+
*
|
|
79
|
+
* @param {Array<number>} x
|
|
80
|
+
* @param {Array<number>} y
|
|
81
|
+
* @param {number} threshold — target point count
|
|
82
|
+
* @returns {{ x: Array<number>, y: Array<number> }}
|
|
83
|
+
*/
|
|
84
|
+
export function lttb(x, y, threshold) {
|
|
85
|
+
const n = Math.min(x?.length ?? 0, y?.length ?? 0);
|
|
86
|
+
const target = Math.floor(threshold) || 0;
|
|
87
|
+
if (target <= 0 || target >= n) return { x: x.slice(0, n), y: y.slice(0, n) };
|
|
88
|
+
if (target < 3) return { x: [x[0], x[n - 1]], y: [y[0], y[n - 1]] };
|
|
89
|
+
|
|
90
|
+
const num = (v) => (Number.isFinite(v) ? v : 0);
|
|
91
|
+
const outX = [x[0]];
|
|
92
|
+
const outY = [y[0]];
|
|
93
|
+
const bucketSize = (n - 2) / (target - 2);
|
|
94
|
+
let a = 0;
|
|
95
|
+
|
|
96
|
+
for (let i = 0; i < target - 2; i++) {
|
|
97
|
+
let nextStart = Math.floor((i + 1) * bucketSize) + 1;
|
|
98
|
+
let nextEnd = Math.min(Math.floor((i + 2) * bucketSize) + 1, n);
|
|
99
|
+
if (nextEnd <= nextStart) nextEnd = Math.min(nextStart + 1, n);
|
|
100
|
+
let avgX = 0, avgY = 0;
|
|
101
|
+
const count = nextEnd - nextStart;
|
|
102
|
+
for (let j = nextStart; j < nextEnd; j++) { avgX += x[j]; avgY += num(y[j]); }
|
|
103
|
+
avgX /= count; avgY /= count;
|
|
104
|
+
|
|
105
|
+
const curStart = Math.floor(i * bucketSize) + 1;
|
|
106
|
+
const curEnd = Math.min(Math.floor((i + 1) * bucketSize) + 1, n);
|
|
107
|
+
const ax = x[a], ay = num(y[a]);
|
|
108
|
+
let maxArea = -1, chosen = curStart;
|
|
109
|
+
for (let j = curStart; j < curEnd; j++) {
|
|
110
|
+
const area = Math.abs((ax - avgX) * (num(y[j]) - ay) - (ax - x[j]) * (avgY - ay));
|
|
111
|
+
if (area > maxArea) { maxArea = area; chosen = j; }
|
|
112
|
+
}
|
|
113
|
+
outX.push(x[chosen]); outY.push(y[chosen]);
|
|
114
|
+
a = chosen;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
outX.push(x[n - 1]); outY.push(y[n - 1]);
|
|
118
|
+
return { x: outX, y: outY };
|
|
119
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* plot_config.js — the shape of a plot, independent of what is being plotted.
|
|
3
|
+
*
|
|
4
|
+
* Subplot grid layouts, the default subplot, the legacy-format migration, and the
|
|
5
|
+
* NaN strategies (gap / zero / hold / interpolate). Not one line of it knows what
|
|
6
|
+
* an economy is.
|
|
7
|
+
*
|
|
8
|
+
* It lived in ui/js/notebook/cells/plot_cell.js — an EcoAgent notebook cell with
|
|
9
|
+
* 60 domain words in it — and that single import was the only thing keeping the
|
|
10
|
+
* plot windows out of @flexdesk/charts. Five modules imported these helpers THROUGH the
|
|
11
|
+
* cell, which is how you end up with a chart library that depends on a notebook.
|
|
12
|
+
*
|
|
13
|
+
* plot_cell.js re-exports everything here, so its existing importers are unchanged.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { getSeriesColor } from './plotly_wrapper.js';
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
let _uid = 0;
|
|
20
|
+
function uid(prefix = 'p') { return `${prefix}-${++_uid}-${Date.now().toString(36)}`; }
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Apply NaN handling strategy to a data array.
|
|
24
|
+
* @param {number[]} data
|
|
25
|
+
* @param {'gap'|'zero'|'hold'|'interpolate'} strategy
|
|
26
|
+
* @param {number[]} xData — for interpolation
|
|
27
|
+
* @returns {Array}
|
|
28
|
+
*/
|
|
29
|
+
function applyNanHandling(data, strategy, xData) {
|
|
30
|
+
const isMissing = (v) => v === null || v === undefined || (typeof v === 'number' && !Number.isFinite(v));
|
|
31
|
+
|
|
32
|
+
if (strategy === 'zero') return data.map(v => isMissing(v) ? 0 : v);
|
|
33
|
+
|
|
34
|
+
if (strategy === 'hold') {
|
|
35
|
+
const result = [];
|
|
36
|
+
let last = null;
|
|
37
|
+
for (const v of data) {
|
|
38
|
+
if (isMissing(v)) result.push(last);
|
|
39
|
+
else { last = v; result.push(v); }
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (strategy === 'interpolate') {
|
|
45
|
+
const result = [...data];
|
|
46
|
+
for (let i = 0; i < result.length; i++) {
|
|
47
|
+
if (!isMissing(result[i])) continue;
|
|
48
|
+
let left = -1, right = -1;
|
|
49
|
+
for (let j = i - 1; j >= 0; j--) { if (!isMissing(result[j])) { left = j; break; } }
|
|
50
|
+
for (let j = i + 1; j < result.length; j++) { if (!isMissing(result[j])) { right = j; break; } }
|
|
51
|
+
if (left >= 0 && right >= 0 && xData) {
|
|
52
|
+
const frac = (xData[i] - xData[left]) / (xData[right] - xData[left]);
|
|
53
|
+
result[i] = result[left] + frac * (result[right] - result[left]);
|
|
54
|
+
} else if (left >= 0) {
|
|
55
|
+
result[i] = result[left];
|
|
56
|
+
} else if (right >= 0) {
|
|
57
|
+
result[i] = result[right];
|
|
58
|
+
} else {
|
|
59
|
+
result[i] = null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 'gap' (default) — replace with null for Plotly gap rendering
|
|
66
|
+
return data.map(v => isMissing(v) ? null : v);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ─── Layout templates (matching figure_cell.js) ─────────────────────────────
|
|
70
|
+
|
|
71
|
+
const PLOT_LAYOUTS = Object.freeze({
|
|
72
|
+
'1x1': { label: 'Single', slots: 1, grid: { columns: '1fr', rows: '1fr' }, areas: [{ slot: 0, gridArea: '1/1/2/2' }] },
|
|
73
|
+
'1x2': { label: 'Side by side', slots: 2, grid: { columns: '1fr 1fr', rows: '1fr' }, areas: [{ slot: 0, gridArea: '1/1/2/2' }, { slot: 1, gridArea: '1/2/2/3' }] },
|
|
74
|
+
'2x1': { label: 'Stacked', slots: 2, grid: { columns: '1fr', rows: '1fr 1fr' }, areas: [{ slot: 0, gridArea: '1/1/2/2' }, { slot: 1, gridArea: '2/1/3/2' }] },
|
|
75
|
+
'2x2': { label: '2\u00d72 Grid', slots: 4, grid: { columns: '1fr 1fr', rows: '1fr 1fr' }, areas: [{ slot: 0, gridArea: '1/1/2/2' }, { slot: 1, gridArea: '1/2/2/3' }, { slot: 2, gridArea: '2/1/3/2' }, { slot: 3, gridArea: '2/2/3/3' }] },
|
|
76
|
+
'1x3': { label: 'Three columns', slots: 3, grid: { columns: '1fr 1fr 1fr', rows: '1fr' }, areas: [{ slot: 0, gridArea: '1/1/2/2' }, { slot: 1, gridArea: '1/2/2/3' }, { slot: 2, gridArea: '1/3/2/4' }] },
|
|
77
|
+
'wide-top': { label: '1 + 2', slots: 3, grid: { columns: '1fr 1fr', rows: '1fr 1fr' }, areas: [{ slot: 0, gridArea: '1/1/2/3' }, { slot: 1, gridArea: '2/1/3/2' }, { slot: 2, gridArea: '2/2/3/3' }] },
|
|
78
|
+
'wide-bottom': { label: '2 + 1', slots: 3, grid: { columns: '1fr 1fr', rows: '1fr 1fr' }, areas: [{ slot: 0, gridArea: '1/1/2/2' }, { slot: 1, gridArea: '1/2/2/3' }, { slot: 2, gridArea: '2/1/3/3' }] },
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
function makeDefaultSubplot(index) {
|
|
82
|
+
return {
|
|
83
|
+
id: uid('sp'),
|
|
84
|
+
displayName: '',
|
|
85
|
+
chartType: 'line',
|
|
86
|
+
legendPosition: 'hidden',
|
|
87
|
+
interpolation: 'linear',
|
|
88
|
+
nanHandling: 'gap',
|
|
89
|
+
showDataPoints: false,
|
|
90
|
+
showHpTrend: false,
|
|
91
|
+
showHpCycle: false,
|
|
92
|
+
hpLambda: 1600,
|
|
93
|
+
xAxis: { useTime: true, variable: '' },
|
|
94
|
+
zAxis: null,
|
|
95
|
+
yAxes: [{
|
|
96
|
+
id: uid('y'),
|
|
97
|
+
label: '',
|
|
98
|
+
scale: 'linear',
|
|
99
|
+
position: 'left',
|
|
100
|
+
min: null,
|
|
101
|
+
max: null,
|
|
102
|
+
step: null,
|
|
103
|
+
series: [],
|
|
104
|
+
}],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function migrateData(data) {
|
|
109
|
+
// Already in multi-layout format
|
|
110
|
+
if (data?.layout && Array.isArray(data?.subplots)) {
|
|
111
|
+
// Migrate caption → description (old field name)
|
|
112
|
+
if ('caption' in data && !('description' in data)) {
|
|
113
|
+
data.description = data.caption;
|
|
114
|
+
data.caption = '';
|
|
115
|
+
}
|
|
116
|
+
return data;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Migrate legacy single-chart format → multi-layout with one subplot
|
|
120
|
+
const legacySingle = migrateSingleChart(data);
|
|
121
|
+
return {
|
|
122
|
+
layout: '1x1',
|
|
123
|
+
subplots: [{ id: uid('sp'), ...legacySingle }],
|
|
124
|
+
caption: '',
|
|
125
|
+
description: data?.caption ?? data?.description ?? '',
|
|
126
|
+
doc: data?.doc ?? '',
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Migrate legacy flat series[]/variables[] to yAxes[] single-chart format. */
|
|
131
|
+
function migrateSingleChart(data) {
|
|
132
|
+
if (data?.yAxes) {
|
|
133
|
+
return {
|
|
134
|
+
displayName: data.displayName ?? '',
|
|
135
|
+
chartType: data.chartType ?? 'line',
|
|
136
|
+
legendPosition: data.legendPosition ?? 'hidden',
|
|
137
|
+
interpolation: data.interpolation ?? 'linear',
|
|
138
|
+
nanHandling: data.nanHandling ?? 'gap',
|
|
139
|
+
showDataPoints: data.showDataPoints ?? false,
|
|
140
|
+
yAxes: data.yAxes,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const series = data?.series ?? data?.variables?.map((v, i) => ({
|
|
145
|
+
variable: v, label: '', color: getSeriesColor(i),
|
|
146
|
+
})) ?? [];
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
displayName: data?.title ?? data?.displayName ?? '',
|
|
150
|
+
chartType: data?.chartType ?? data?.widgetType ?? 'line',
|
|
151
|
+
legendPosition: data?.legendPosition ?? 'hidden',
|
|
152
|
+
interpolation: data?.interpolation ?? 'linear',
|
|
153
|
+
nanHandling: data?.nanHandling ?? 'gap',
|
|
154
|
+
showDataPoints: data?.showDataPoints ?? false,
|
|
155
|
+
yAxes: [{
|
|
156
|
+
id: uid('y'),
|
|
157
|
+
label: data?.yLabel ?? '',
|
|
158
|
+
scale: 'linear',
|
|
159
|
+
position: 'left',
|
|
160
|
+
min: null,
|
|
161
|
+
max: null,
|
|
162
|
+
series: series.map((s, i) => ({
|
|
163
|
+
id: s.id ?? uid('s'),
|
|
164
|
+
variable: s.variable ?? '',
|
|
165
|
+
label: s.label ?? '',
|
|
166
|
+
color: s.color ?? getSeriesColor(i),
|
|
167
|
+
lineWidth: s.lineWidth ?? 2,
|
|
168
|
+
lineStyle: s.lineStyle ?? 'solid',
|
|
169
|
+
stackGroup: s.stackGroup ?? null,
|
|
170
|
+
...(s.overlay ? { overlay: s.overlay } : {}),
|
|
171
|
+
})),
|
|
172
|
+
}],
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export { uid, applyNanHandling, PLOT_LAYOUTS, makeDefaultSubplot, migrateData, migrateSingleChart };
|