flexdesk 0.16.0 → 0.17.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/CHANGELOG.md +93 -0
- package/LICENSE +0 -0
- package/README.md +2261 -42
- package/THIRD_PARTY_LICENSES.md +0 -0
- package/css/base.css +1834 -75
- package/css/flexdesk.css +3373 -238
- package/css/overrides.css +16 -0
- package/css/reset.css +0 -0
- package/css/tokens.css +0 -0
- package/dist/canvas.js +6 -239
- package/dist/canvas.js.map +3 -3
- package/dist/charts.js +709 -18
- package/dist/charts.js.map +4 -4
- package/dist/chunk-3PHCPZHT.js +0 -0
- package/dist/chunk-3PHCPZHT.js.map +0 -0
- package/dist/chunk-4A7SJC3T.js +503 -0
- package/dist/chunk-4A7SJC3T.js.map +7 -0
- package/dist/{chunk-6JSOVNID.js → chunk-CVMCZZX2.js} +635 -10
- package/dist/chunk-CVMCZZX2.js.map +7 -0
- package/dist/chunk-D3COLZZ2.js +1311 -0
- package/dist/chunk-D3COLZZ2.js.map +7 -0
- package/dist/chunk-FOOS3T5L.js +0 -0
- package/dist/chunk-FOOS3T5L.js.map +0 -0
- package/dist/{chunk-SN4LSOAR.js → chunk-GOQLR5WR.js} +452 -2
- package/dist/chunk-GOQLR5WR.js.map +7 -0
- package/dist/{chunk-FL5KFNQH.js → chunk-HNU6MM2B.js} +52 -17
- package/dist/chunk-HNU6MM2B.js.map +7 -0
- package/dist/chunk-HYJYYEPK.js +375 -0
- package/dist/chunk-HYJYYEPK.js.map +7 -0
- package/dist/chunk-JKZLNKG6.js +2041 -0
- package/dist/chunk-JKZLNKG6.js.map +7 -0
- package/dist/chunk-JYWURG5T.js +0 -0
- package/dist/chunk-JYWURG5T.js.map +0 -0
- package/dist/{chunk-RQM5Z2MU.js → chunk-MCIXFNVF.js} +633 -311
- package/dist/chunk-MCIXFNVF.js.map +7 -0
- package/dist/chunk-N7HC3ADV.js +30 -0
- package/dist/chunk-N7HC3ADV.js.map +7 -0
- package/dist/{chunk-FBGP5KN5.js → chunk-Q36EPT2R.js} +3268 -203
- package/dist/chunk-Q36EPT2R.js.map +7 -0
- package/dist/chunk-QWQJUDPD.js +610 -0
- package/dist/chunk-QWQJUDPD.js.map +7 -0
- package/dist/{chunk-NLTOY57M.js → chunk-UURZ37SD.js} +28 -48
- package/dist/chunk-UURZ37SD.js.map +7 -0
- package/dist/chunk-UWO43BZU.js +111 -0
- package/dist/chunk-UWO43BZU.js.map +7 -0
- package/dist/chunk-WVFGV5FT.js +0 -0
- package/dist/chunk-WVFGV5FT.js.map +0 -0
- package/dist/core.js +170 -118
- package/dist/core.js.map +2 -2
- package/dist/editor.js +156 -70
- package/dist/editor.js.map +2 -2
- package/dist/flexdesk.css +3373 -238
- package/dist/flow.js +2789 -306
- package/dist/flow.js.map +4 -4
- package/dist/host.js +109 -3
- package/dist/host.js.map +3 -3
- package/dist/reset.css +0 -0
- package/dist/tile_registry-6WZPTUZV.js +0 -0
- package/dist/tile_registry-6WZPTUZV.js.map +0 -0
- package/dist/tiles.js +1131 -253
- package/dist/tiles.js.map +4 -4
- package/dist/tokens.css +0 -0
- package/dist/widgets.js +3254 -2112
- package/dist/widgets.js.map +4 -4
- package/dist/wm.js +1227 -345
- package/dist/wm.js.map +4 -4
- package/package.json +3 -1
- package/src/canvas/canvas_adapter.js +5 -0
- package/src/canvas/edge_router.js +0 -0
- package/src/canvas/graph/connector_router.js +0 -0
- package/src/canvas/graph/history_service.js +0 -0
- package/src/canvas/graph/node_platform.js +0 -0
- package/src/canvas/index.js +1 -0
- package/src/canvas/layered_layout.js +244 -0
- package/src/charts/chart_types.js +0 -0
- package/src/charts/data_series_plot_window.js +0 -0
- package/src/charts/downsample.js +2 -2
- package/src/charts/plot_config.js +53 -29
- package/src/charts/plot_config_editor.js +627 -0
- package/src/charts/plot_grid.js +449 -0
- package/src/charts/plot_popout_window.js +103 -317
- package/src/charts/plotly_wrapper.js +22 -2
- package/src/charts/series_transforms.js +206 -0
- package/src/core/event_bus.js +0 -0
- package/src/core/logging.js +1 -1
- package/src/core/settings.js +105 -19
- package/src/core/state_guard.js +93 -51
- package/src/core/state_guard_config.js +85 -74
- package/src/core/state_machine.js +0 -0
- package/src/editor/editor_pane.js +3 -3
- package/src/editor/monaco_editor_factory.js +124 -60
- package/src/editor/monaco_loader.js +103 -28
- package/src/editor/notebook_search.js +0 -0
- package/src/editor/notebook_tab_bar.js +38 -52
- package/src/editor/notebook_undo_manager.js +0 -0
- package/src/editor/split_pane_container.js +6 -3
- package/src/flow/canvas_editor/connections.js +90 -0
- package/src/flow/canvas_editor/editor.js +2351 -153
- package/src/flow/canvas_editor/index.js +2 -1
- package/src/flow/canvas_editor/layout.js +119 -12
- package/src/flow/canvas_editor/strings.js +80 -0
- package/src/flow/kit/always_before.js +0 -0
- package/src/flow/kit/catalogue.js +97 -3
- package/src/flow/kit/chip_input.js +0 -0
- package/src/flow/kit/dom.js +0 -0
- package/src/flow/kit/findings.js +0 -0
- package/src/flow/kit/history.js +0 -0
- package/src/flow/kit/index.js +3 -3
- package/src/flow/kit/keys.js +7 -1
- package/src/flow/kit/logic_graph.js +97 -15
- package/src/flow/kit/references.js +0 -0
- package/src/flow/kit/settings_panel.js +0 -0
- package/src/flow/kit/settings_schema.js +2 -1
- package/src/flow/kit/step_picker.js +0 -0
- package/src/flow/kit/strings.js +3 -1
- package/src/flow/kit/value_picker.js +0 -0
- package/src/flow/kit/widgets.js +86 -109
- package/src/flow/lanes/editor.js +136 -14
- package/src/flow/lanes/index.js +3 -1
- package/src/flow/lanes/layout.js +0 -0
- package/src/flow/lanes/pipeline.js +44 -2
- package/src/flow/lanes/preview.js +0 -0
- package/src/flow/lanes/strings.js +1 -0
- package/src/flow/outline/editor.js +0 -0
- package/src/flow/outline/index.js +0 -0
- package/src/flow/outline/mapping.js +0 -0
- package/src/flow/outline/menu.js +0 -0
- package/src/flow/outline/operations.js +0 -0
- package/src/flow/outline/recognise.js +0 -0
- package/src/flow/outline/strings.js +0 -0
- package/src/help/help_modal.js +168 -42
- package/src/help/help_registry.js +12 -1
- package/src/host/external_links.js +0 -0
- package/src/host/file_picker.js +117 -0
- package/src/host/host.js +10 -2
- package/src/host/photino_host.js +9 -0
- package/src/host/photino_host.md +21 -3
- package/src/host/pywebview_host.js +10 -0
- package/src/tiles/chart_tiles.js +53 -245
- package/src/tiles/config_schema.js +8 -0
- package/src/tiles/figure_tile.js +322 -0
- package/src/tiles/layout_persistence.js +3 -3
- package/src/tiles/list_tiles.js +422 -0
- package/src/tiles/scope_picker.js +96 -0
- package/src/tiles/tile_base.js +12 -1
- package/src/tiles/tile_config_form.js +442 -0
- package/src/tiles/tile_grid.js +38 -1
- package/src/tiles/tile_registry.js +0 -0
- package/src/tiling/bottom_panel_tabs.js +67 -99
- package/src/tiling/command_palette.js +64 -19
- package/src/tiling/content_registry.js +4 -1
- package/src/tiling/desktops.js +11 -1
- package/src/tiling/entity_sources.js +0 -0
- package/src/tiling/keymap.js +403 -128
- package/src/tiling/kind_taxonomy.js +78 -0
- package/src/tiling/landing_table.js +0 -0
- package/src/tiling/loading_overlay.js +0 -0
- package/src/tiling/nav_panel.js +59 -10
- package/src/tiling/page_factory.js +5 -5
- package/src/tiling/panel_availability.js +49 -0
- package/src/tiling/panel_keys.js +0 -0
- package/src/tiling/shell.js +114 -12
- package/src/tiling/tab_strip.js +0 -0
- package/src/tiling/tile_breadcrumb.js +0 -0
- package/src/tiling/tile_renderer.js +105 -9
- package/src/tiling/tile_tab_menu.js +0 -0
- package/src/tiling/tile_tree.js +0 -0
- package/src/tiling/wm.js +562 -30
- package/src/tiling/zoom.js +0 -0
- package/src/ui/base/component_base.js +0 -0
- package/src/ui/base/controller_base.js +0 -0
- package/src/ui/base/page_base.js +0 -0
- package/src/ui/components/about_dialog.js +0 -0
- package/src/ui/components/action_dropdown.js +30 -2
- package/src/ui/components/alert_log.js +0 -0
- package/src/ui/components/alerts_pane.js +0 -0
- package/src/ui/components/attribute_list_editor.js +0 -0
- package/src/ui/components/autocomplete_field.js +0 -0
- package/src/ui/components/cell_editor_host.js +211 -0
- package/src/ui/components/cell_grid.js +648 -0
- package/src/ui/components/cell_selection.js +220 -0
- package/src/ui/components/choice_controls.js +351 -0
- package/src/ui/components/color_field.js +211 -0
- package/src/ui/components/color_list_field.js +210 -0
- package/src/ui/components/column_fit.js +0 -0
- package/src/ui/components/column_groups.js +314 -0
- package/src/ui/components/column_order.js +142 -0
- package/src/ui/components/computing_status_window.js +98 -25
- package/src/ui/components/confirm_dialog.js +81 -38
- package/src/ui/components/context_menu.js +2 -2
- package/src/ui/components/conversation_view.js +440 -0
- package/src/ui/components/crosstab.js +289 -0
- package/src/ui/components/data_table.js +601 -62
- package/src/ui/components/detail_header.js +22 -11
- package/src/ui/components/document_outline.js +397 -0
- package/src/ui/components/drag_reorder.js +0 -0
- package/src/ui/components/fact_table.js +0 -0
- package/src/ui/components/fill_handle.js +322 -0
- package/src/ui/components/gallery_picker.js +19 -8
- package/src/ui/components/header_actions.js +55 -0
- package/src/ui/components/inline_renamer.js +0 -0
- package/src/ui/components/job_progress.js +51 -0
- package/src/ui/components/managed_window.js +27 -4
- package/src/ui/components/menu_bar.js +745 -0
- package/src/ui/components/modal.js +449 -75
- package/src/ui/components/notification_history.js +3 -3
- package/src/ui/components/progress.js +483 -0
- package/src/ui/components/record_list.js +0 -0
- package/src/ui/components/row_activation.js +0 -0
- package/src/ui/components/section_stack.js +298 -0
- package/src/ui/components/series.js +173 -0
- package/src/ui/components/settings_view.js +634 -0
- package/src/ui/components/slide_out_panel.js +43 -6
- package/src/ui/components/slider_field.js +275 -18
- package/src/ui/components/sortable_list.js +26 -4
- package/src/ui/components/staged_edits.js +343 -0
- package/src/ui/components/staged_history.js +172 -0
- package/src/ui/components/table_filter_chips.js +0 -0
- package/src/ui/components/table_state_store.js +0 -0
- package/src/ui/components/table_structure.js +182 -0
- package/src/ui/components/table_values.js +0 -0
- package/src/ui/components/tabs.js +497 -0
- package/src/ui/components/tag_input.js +215 -0
- package/src/ui/components/toast.js +25 -8
- package/src/ui/components/toggle_field.js +175 -0
- package/src/ui/components/tree_view.js +55 -34
- package/src/ui/components/window_taskbar.js +0 -0
- package/src/ui/components/wizard.js +563 -0
- package/src/ui/controllers/window_chrome_controller.js +2 -2
- package/src/ui/notification_center.js +375 -119
- package/src/ui/utils/auto_scrollbars.js +0 -0
- package/src/ui/utils/markdown.js +709 -0
- package/src/ui/utils/native_context_menu.js +0 -0
- package/src/ui/utils/overlay_scrollbar.js +16 -12
- package/src/ui/utils/raf_resize_observer.js +0 -0
- package/src/ui/utils/scroll_lock.js +0 -0
- package/src/ui/utils/strip_scroll.js +55 -0
- package/src/ui/utils/tooltip_service.js +0 -8
- package/dist/chunk-6JSOVNID.js.map +0 -7
- package/dist/chunk-CWPMLX67.js +0 -475
- package/dist/chunk-CWPMLX67.js.map +0 -7
- package/dist/chunk-FBGP5KN5.js.map +0 -7
- package/dist/chunk-FL5KFNQH.js.map +0 -7
- package/dist/chunk-NLTOY57M.js.map +0 -7
- package/dist/chunk-NNFTETK3.js +0 -633
- package/dist/chunk-NNFTETK3.js.map +0 -7
- package/dist/chunk-O5OHMWBB.js +0 -284
- package/dist/chunk-O5OHMWBB.js.map +0 -7
- package/dist/chunk-RQM5Z2MU.js.map +0 -7
- package/dist/chunk-SN4LSOAR.js.map +0 -7
- package/src/ui/controllers/panel_state_machine.js +0 -634
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DataTable,
|
|
3
3
|
createRafResizeObserver
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-Q36EPT2R.js";
|
|
5
5
|
import {
|
|
6
6
|
ManagedWindow
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-CVMCZZX2.js";
|
|
8
8
|
|
|
9
9
|
// src/charts/plotly_wrapper.js
|
|
10
10
|
var _plotlySrc = null;
|
|
@@ -96,6 +96,7 @@ var DARK_THEME_LAYOUT = {
|
|
|
96
96
|
bgcolor: "rgba(0,0,0,0)"
|
|
97
97
|
}
|
|
98
98
|
};
|
|
99
|
+
var DEFAULT_CHART_EXPORT_FILENAME = "chart";
|
|
99
100
|
var DEFAULT_CONFIG = {
|
|
100
101
|
responsive: true,
|
|
101
102
|
displaylogo: false,
|
|
@@ -105,10 +106,16 @@ var DEFAULT_CONFIG = {
|
|
|
105
106
|
// Allow zoom with scroll wheel instead
|
|
106
107
|
toImageButtonOptions: {
|
|
107
108
|
format: "png",
|
|
108
|
-
filename:
|
|
109
|
+
filename: DEFAULT_CHART_EXPORT_FILENAME,
|
|
109
110
|
scale: 2
|
|
110
111
|
}
|
|
111
112
|
};
|
|
113
|
+
function setChartExportFilename(filename = DEFAULT_CHART_EXPORT_FILENAME) {
|
|
114
|
+
DEFAULT_CONFIG.toImageButtonOptions.filename = typeof filename === "string" && filename ? filename : DEFAULT_CHART_EXPORT_FILENAME;
|
|
115
|
+
}
|
|
116
|
+
function getChartExportFilename() {
|
|
117
|
+
return DEFAULT_CONFIG.toImageButtonOptions.filename;
|
|
118
|
+
}
|
|
112
119
|
async function ensurePlotly() {
|
|
113
120
|
if (typeof Plotly !== "undefined") {
|
|
114
121
|
return Plotly;
|
|
@@ -1071,6 +1078,29 @@ var PLOT_LAYOUTS = Object.freeze({
|
|
|
1071
1078
|
"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" }] },
|
|
1072
1079
|
"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" }] }
|
|
1073
1080
|
});
|
|
1081
|
+
function makeDefaultYAxis(index = 0) {
|
|
1082
|
+
return {
|
|
1083
|
+
id: uid("y"),
|
|
1084
|
+
label: "",
|
|
1085
|
+
scale: "linear",
|
|
1086
|
+
position: index % 2 === 0 ? "left" : "right",
|
|
1087
|
+
min: null,
|
|
1088
|
+
max: null,
|
|
1089
|
+
step: null,
|
|
1090
|
+
series: []
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
function makeDefaultSeries(index = 0) {
|
|
1094
|
+
return {
|
|
1095
|
+
id: uid("s"),
|
|
1096
|
+
variable: "",
|
|
1097
|
+
label: "",
|
|
1098
|
+
color: getSeriesColor(index),
|
|
1099
|
+
lineWidth: 2,
|
|
1100
|
+
lineStyle: "solid",
|
|
1101
|
+
stackGroup: null
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1074
1104
|
function makeDefaultSubplot(index) {
|
|
1075
1105
|
return {
|
|
1076
1106
|
id: uid("sp"),
|
|
@@ -1080,21 +1110,9 @@ function makeDefaultSubplot(index) {
|
|
|
1080
1110
|
interpolation: "linear",
|
|
1081
1111
|
nanHandling: "gap",
|
|
1082
1112
|
showDataPoints: false,
|
|
1083
|
-
showHpTrend: false,
|
|
1084
|
-
showHpCycle: false,
|
|
1085
|
-
hpLambda: 1600,
|
|
1086
1113
|
xAxis: { useTime: true, variable: "" },
|
|
1087
1114
|
zAxis: null,
|
|
1088
|
-
yAxes: [
|
|
1089
|
-
id: uid("y"),
|
|
1090
|
-
label: "",
|
|
1091
|
-
scale: "linear",
|
|
1092
|
-
position: "left",
|
|
1093
|
-
min: null,
|
|
1094
|
-
max: null,
|
|
1095
|
-
step: null,
|
|
1096
|
-
series: []
|
|
1097
|
-
}]
|
|
1115
|
+
yAxes: [makeDefaultYAxis(0)]
|
|
1098
1116
|
};
|
|
1099
1117
|
}
|
|
1100
1118
|
function migrateData(data) {
|
|
@@ -1139,26 +1157,499 @@ function migrateSingleChart(data) {
|
|
|
1139
1157
|
nanHandling: data?.nanHandling ?? "gap",
|
|
1140
1158
|
showDataPoints: data?.showDataPoints ?? false,
|
|
1141
1159
|
yAxes: [{
|
|
1142
|
-
|
|
1160
|
+
...makeDefaultYAxis(0),
|
|
1143
1161
|
label: data?.yLabel ?? "",
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
}))
|
|
1162
|
+
series: series.map((s, i) => {
|
|
1163
|
+
const d = makeDefaultSeries(i);
|
|
1164
|
+
return {
|
|
1165
|
+
id: s.id ?? d.id,
|
|
1166
|
+
variable: s.variable ?? d.variable,
|
|
1167
|
+
label: s.label ?? d.label,
|
|
1168
|
+
color: s.color ?? d.color,
|
|
1169
|
+
lineWidth: s.lineWidth ?? d.lineWidth,
|
|
1170
|
+
lineStyle: s.lineStyle ?? d.lineStyle,
|
|
1171
|
+
stackGroup: s.stackGroup ?? d.stackGroup,
|
|
1172
|
+
...s.overlay ? { overlay: s.overlay } : {}
|
|
1173
|
+
};
|
|
1174
|
+
})
|
|
1158
1175
|
}]
|
|
1159
1176
|
};
|
|
1160
1177
|
}
|
|
1161
1178
|
|
|
1179
|
+
// src/charts/series_transforms.js
|
|
1180
|
+
function resolveParams(transform, state) {
|
|
1181
|
+
const params = {};
|
|
1182
|
+
for (const [name, spec] of Object.entries(transform.params ?? {})) {
|
|
1183
|
+
params[name] = spec?.default;
|
|
1184
|
+
}
|
|
1185
|
+
return { ...params, ...state?.params ?? {} };
|
|
1186
|
+
}
|
|
1187
|
+
function enabledOutputs(transform, state) {
|
|
1188
|
+
return Object.keys(transform.outputs ?? {}).filter((name) => state?.outputs?.[name] === true);
|
|
1189
|
+
}
|
|
1190
|
+
function isEligible(trace) {
|
|
1191
|
+
const type = trace.type ?? "scatter";
|
|
1192
|
+
return (type === "scatter" || type === "scattergl") && Array.isArray(trace.y);
|
|
1193
|
+
}
|
|
1194
|
+
async function deriveSeriesTraces(traces, subplot, seriesTransforms, { onError } = {}) {
|
|
1195
|
+
const derived = [];
|
|
1196
|
+
if (!seriesTransforms) return derived;
|
|
1197
|
+
const sources = traces.filter(isEligible);
|
|
1198
|
+
for (const [transformId, transform] of Object.entries(seriesTransforms)) {
|
|
1199
|
+
const state = subplot?.transforms?.[transformId];
|
|
1200
|
+
const outputs = enabledOutputs(transform, state);
|
|
1201
|
+
if (!outputs.length || typeof transform?.run !== "function") continue;
|
|
1202
|
+
const params = resolveParams(transform, state);
|
|
1203
|
+
for (const source of sources) {
|
|
1204
|
+
let result;
|
|
1205
|
+
try {
|
|
1206
|
+
result = await transform.run(source.y, params);
|
|
1207
|
+
} catch (error) {
|
|
1208
|
+
onError?.({ transformId, traceName: source.name, error });
|
|
1209
|
+
continue;
|
|
1210
|
+
}
|
|
1211
|
+
if (!result || typeof result !== "object") {
|
|
1212
|
+
onError?.({
|
|
1213
|
+
transformId,
|
|
1214
|
+
traceName: source.name,
|
|
1215
|
+
error: new Error(`transform "${transformId}" resolved no outputs`)
|
|
1216
|
+
});
|
|
1217
|
+
continue;
|
|
1218
|
+
}
|
|
1219
|
+
const color = source.line?.color || source.marker?.color;
|
|
1220
|
+
for (const name of outputs) {
|
|
1221
|
+
const y = result[name];
|
|
1222
|
+
if (!Array.isArray(y)) continue;
|
|
1223
|
+
const spec = transform.outputs[name];
|
|
1224
|
+
derived.push({
|
|
1225
|
+
...buildTrace({
|
|
1226
|
+
chartType: "line",
|
|
1227
|
+
x: source.x,
|
|
1228
|
+
y,
|
|
1229
|
+
name: `${source.name}${spec.suffix ?? ` (${spec.label ?? name})`}`,
|
|
1230
|
+
color,
|
|
1231
|
+
lineWidth: spec.lineWidth ?? 2,
|
|
1232
|
+
lineStyle: spec.lineStyle ?? "solid",
|
|
1233
|
+
yAxisId: source.yaxis || "y"
|
|
1234
|
+
}),
|
|
1235
|
+
showlegend: true
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
return derived;
|
|
1241
|
+
}
|
|
1242
|
+
function renderSeriesTransformControls(container, subplot, seriesTransforms, { onChange } = {}) {
|
|
1243
|
+
const entries = Object.entries(seriesTransforms ?? {});
|
|
1244
|
+
if (!entries.length) return { dispose() {
|
|
1245
|
+
} };
|
|
1246
|
+
const root = document.createElement("div");
|
|
1247
|
+
root.className = "twm-series-transforms";
|
|
1248
|
+
const stateOf = (transformId) => {
|
|
1249
|
+
subplot.transforms ??= {};
|
|
1250
|
+
subplot.transforms[transformId] ??= {};
|
|
1251
|
+
return subplot.transforms[transformId];
|
|
1252
|
+
};
|
|
1253
|
+
for (const [transformId, transform] of entries) {
|
|
1254
|
+
const group = document.createElement("fieldset");
|
|
1255
|
+
group.className = "twm-series-transforms__group";
|
|
1256
|
+
const legend = document.createElement("legend");
|
|
1257
|
+
legend.className = "twm-series-transforms__label";
|
|
1258
|
+
legend.textContent = transform.label ?? transformId;
|
|
1259
|
+
group.appendChild(legend);
|
|
1260
|
+
const paramInputs = [];
|
|
1261
|
+
const syncDisabled = () => {
|
|
1262
|
+
const on = enabledOutputs(transform, subplot.transforms?.[transformId]).length > 0;
|
|
1263
|
+
for (const input of paramInputs) input.disabled = !on;
|
|
1264
|
+
};
|
|
1265
|
+
for (const [name, spec] of Object.entries(transform.outputs ?? {})) {
|
|
1266
|
+
const row = document.createElement("label");
|
|
1267
|
+
row.className = "twm-series-transforms__row";
|
|
1268
|
+
const box = document.createElement("input");
|
|
1269
|
+
box.type = "checkbox";
|
|
1270
|
+
box.checked = subplot.transforms?.[transformId]?.outputs?.[name] === true;
|
|
1271
|
+
box.addEventListener("change", () => {
|
|
1272
|
+
const state = stateOf(transformId);
|
|
1273
|
+
state.outputs = { ...state.outputs ?? {}, [name]: box.checked };
|
|
1274
|
+
syncDisabled();
|
|
1275
|
+
onChange?.();
|
|
1276
|
+
});
|
|
1277
|
+
const text = document.createElement("span");
|
|
1278
|
+
text.textContent = spec?.label ?? name;
|
|
1279
|
+
row.append(box, text);
|
|
1280
|
+
group.appendChild(row);
|
|
1281
|
+
}
|
|
1282
|
+
for (const [name, spec] of Object.entries(transform.params ?? {})) {
|
|
1283
|
+
const row = document.createElement("label");
|
|
1284
|
+
row.className = "twm-series-transforms__row";
|
|
1285
|
+
const text = document.createElement("span");
|
|
1286
|
+
text.textContent = spec?.label ?? name;
|
|
1287
|
+
const input = document.createElement("input");
|
|
1288
|
+
input.type = "number";
|
|
1289
|
+
input.className = "twm-series-transforms__param";
|
|
1290
|
+
for (const attr of ["min", "max", "step"]) {
|
|
1291
|
+
if (spec?.[attr] != null) input[attr] = String(spec[attr]);
|
|
1292
|
+
}
|
|
1293
|
+
const current = subplot.transforms?.[transformId]?.params?.[name] ?? spec?.default;
|
|
1294
|
+
input.value = current == null ? "" : String(current);
|
|
1295
|
+
input.addEventListener("change", () => {
|
|
1296
|
+
const value = parseFloat(input.value);
|
|
1297
|
+
const state = stateOf(transformId);
|
|
1298
|
+
const params = { ...state.params ?? {} };
|
|
1299
|
+
if (Number.isFinite(value)) params[name] = value;
|
|
1300
|
+
else delete params[name];
|
|
1301
|
+
state.params = params;
|
|
1302
|
+
onChange?.();
|
|
1303
|
+
});
|
|
1304
|
+
paramInputs.push(input);
|
|
1305
|
+
row.append(text, input);
|
|
1306
|
+
group.appendChild(row);
|
|
1307
|
+
}
|
|
1308
|
+
syncDisabled();
|
|
1309
|
+
root.appendChild(group);
|
|
1310
|
+
}
|
|
1311
|
+
container.appendChild(root);
|
|
1312
|
+
return { dispose() {
|
|
1313
|
+
root.remove();
|
|
1314
|
+
} };
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
// src/charts/plot_grid.js
|
|
1318
|
+
var PLOT_GRID_THEME = Object.freeze({
|
|
1319
|
+
fontFamily: "Inter, -apple-system, BlinkMacSystemFont, sans-serif",
|
|
1320
|
+
fontColor: "#cccccc",
|
|
1321
|
+
fontSize: 11,
|
|
1322
|
+
paperBackground: "transparent",
|
|
1323
|
+
plotBackground: "transparent",
|
|
1324
|
+
gridColor: "rgba(255,255,255,0.05)",
|
|
1325
|
+
lineColor: "rgba(255,255,255,0.08)",
|
|
1326
|
+
tickColor: "rgba(255,255,255,0.3)",
|
|
1327
|
+
zeroLineColor: "rgba(255,255,255,0.06)",
|
|
1328
|
+
hoverBackground: "#1e2228",
|
|
1329
|
+
hoverBorder: "#444444"
|
|
1330
|
+
});
|
|
1331
|
+
var OVERLAY_DASH = { dashed: "dash", dotted: "dot", solid: "solid" };
|
|
1332
|
+
function resolveSeriesData(series, results) {
|
|
1333
|
+
const table = results?.series;
|
|
1334
|
+
const variable = series?.variable;
|
|
1335
|
+
if (!table || !variable) return null;
|
|
1336
|
+
let y = table[series._resultKey ?? variable];
|
|
1337
|
+
if (!y && series._resultKey) y = table[variable];
|
|
1338
|
+
if (!y) {
|
|
1339
|
+
const dot = variable.indexOf(".");
|
|
1340
|
+
if (dot >= 0) y = table[variable.slice(dot + 1)];
|
|
1341
|
+
}
|
|
1342
|
+
if (!y) return null;
|
|
1343
|
+
return series._resultTime ? { y, x: series._resultTime } : { y };
|
|
1344
|
+
}
|
|
1345
|
+
function renderPlotGrid(container, plotConfig, results, options = {}) {
|
|
1346
|
+
const opts = {
|
|
1347
|
+
resolveSeries: resolveSeriesData,
|
|
1348
|
+
overlays: null,
|
|
1349
|
+
seriesTransforms: null,
|
|
1350
|
+
onTransformError: null,
|
|
1351
|
+
derivedTraces: null,
|
|
1352
|
+
hiddenSeries: null,
|
|
1353
|
+
theme: null,
|
|
1354
|
+
alwaysShowLegend: false,
|
|
1355
|
+
layout: null,
|
|
1356
|
+
emptyMessage: "No data yet.",
|
|
1357
|
+
noSubplotMessage: "No subplot configured.",
|
|
1358
|
+
onError: null,
|
|
1359
|
+
...options
|
|
1360
|
+
};
|
|
1361
|
+
let config = plotConfig;
|
|
1362
|
+
let data = results;
|
|
1363
|
+
let disposed = false;
|
|
1364
|
+
let cells = [];
|
|
1365
|
+
let cellsLayout = null;
|
|
1366
|
+
let tokens = [];
|
|
1367
|
+
let lastToken = 0;
|
|
1368
|
+
const deferred = /* @__PURE__ */ new Set();
|
|
1369
|
+
const grid = document.createElement("div");
|
|
1370
|
+
grid.className = "twm-plot-grid";
|
|
1371
|
+
container.appendChild(grid);
|
|
1372
|
+
const observer = createRafResizeObserver(() => resize2());
|
|
1373
|
+
observer.observe(grid);
|
|
1374
|
+
const plotted = /* @__PURE__ */ new Set();
|
|
1375
|
+
const drawn = /* @__PURE__ */ new Set();
|
|
1376
|
+
const hasPlot2 = (cell) => plotted.has(cell);
|
|
1377
|
+
const purgeCell = (cell) => {
|
|
1378
|
+
drawn.delete(cell);
|
|
1379
|
+
if (!plotted.delete(cell)) return;
|
|
1380
|
+
try {
|
|
1381
|
+
globalThis.Plotly?.purge(cell);
|
|
1382
|
+
} catch (_) {
|
|
1383
|
+
}
|
|
1384
|
+
};
|
|
1385
|
+
function buildCells() {
|
|
1386
|
+
for (const cell of cells) purgeCell(cell);
|
|
1387
|
+
grid.replaceChildren();
|
|
1388
|
+
const tmpl = PLOT_LAYOUTS[config?.layout] || PLOT_LAYOUTS["1x1"];
|
|
1389
|
+
grid.style.gridTemplateColumns = tmpl.grid.columns;
|
|
1390
|
+
grid.style.gridTemplateRows = tmpl.grid.rows;
|
|
1391
|
+
cells = tmpl.areas.map(({ gridArea }) => {
|
|
1392
|
+
const cell = document.createElement("div");
|
|
1393
|
+
cell.className = "twm-plot-grid__cell";
|
|
1394
|
+
cell.style.gridArea = gridArea;
|
|
1395
|
+
grid.appendChild(cell);
|
|
1396
|
+
return cell;
|
|
1397
|
+
});
|
|
1398
|
+
cellsLayout = config?.layout;
|
|
1399
|
+
tokens = cells.map(() => 0);
|
|
1400
|
+
deferred.clear();
|
|
1401
|
+
}
|
|
1402
|
+
function showMessage(cell, text) {
|
|
1403
|
+
purgeCell(cell);
|
|
1404
|
+
const msg = document.createElement("div");
|
|
1405
|
+
msg.className = "twm-plot-no-data";
|
|
1406
|
+
msg.textContent = text;
|
|
1407
|
+
cell.replaceChildren(msg);
|
|
1408
|
+
}
|
|
1409
|
+
const theme = () => ({ ...PLOT_GRID_THEME, ...opts.theme ?? {} });
|
|
1410
|
+
const isHidden = (name) => {
|
|
1411
|
+
const h = opts.hiddenSeries;
|
|
1412
|
+
if (!h) return false;
|
|
1413
|
+
return typeof h.has === "function" ? h.has(name) : h.includes(name);
|
|
1414
|
+
};
|
|
1415
|
+
const axisColours = (t) => ({
|
|
1416
|
+
gridcolor: t.gridColor,
|
|
1417
|
+
linecolor: t.lineColor,
|
|
1418
|
+
tickcolor: t.tickColor,
|
|
1419
|
+
zerolinecolor: t.zeroLineColor
|
|
1420
|
+
});
|
|
1421
|
+
async function buildFigure(sp, index) {
|
|
1422
|
+
if (!data) return null;
|
|
1423
|
+
const t = theme();
|
|
1424
|
+
const time = data.time ?? [];
|
|
1425
|
+
const traces = [];
|
|
1426
|
+
const layoutAxes = {};
|
|
1427
|
+
const yRefByAxisId = {};
|
|
1428
|
+
let ownCount = 0;
|
|
1429
|
+
let hiddenCount = 0;
|
|
1430
|
+
const useX = sp.xAxis && sp.xAxis.useTime === false && sp.xAxis.variable;
|
|
1431
|
+
const xData = useX ? opts.resolveSeries({ variable: sp.xAxis.variable }, data, sp)?.y ?? time : time;
|
|
1432
|
+
const yAxes = sp.yAxes ?? [];
|
|
1433
|
+
yAxes.forEach((axis, a) => {
|
|
1434
|
+
const n = a + 1;
|
|
1435
|
+
const yAxisKey = n === 1 ? "yaxis" : `yaxis${n}`;
|
|
1436
|
+
const yRef = n === 1 ? "y" : `y${n}`;
|
|
1437
|
+
if (axis.id) yRefByAxisId[axis.id] = yRef;
|
|
1438
|
+
layoutAxes[yAxisKey] = {
|
|
1439
|
+
...buildYAxisLayout({
|
|
1440
|
+
axisId: yAxisKey,
|
|
1441
|
+
title: axis.label || "",
|
|
1442
|
+
position: axis.position || "left",
|
|
1443
|
+
scaleType: axis.scale || "linear",
|
|
1444
|
+
min: axis.min ?? null,
|
|
1445
|
+
max: axis.max ?? null,
|
|
1446
|
+
step: axis.step ?? null,
|
|
1447
|
+
showGrid: yAxes.length === 1,
|
|
1448
|
+
axisPosition: n > 1 ? axis.position === "right" ? 1 : 0 : null
|
|
1449
|
+
}),
|
|
1450
|
+
...axisColours(t)
|
|
1451
|
+
};
|
|
1452
|
+
for (const s of axis.series ?? []) {
|
|
1453
|
+
if (!s.variable) continue;
|
|
1454
|
+
const label = s.label || s.variable;
|
|
1455
|
+
ownCount++;
|
|
1456
|
+
if (isHidden(label)) {
|
|
1457
|
+
hiddenCount++;
|
|
1458
|
+
continue;
|
|
1459
|
+
}
|
|
1460
|
+
const resolved = opts.resolveSeries(s, data, sp);
|
|
1461
|
+
if (!resolved?.y) continue;
|
|
1462
|
+
const x = resolved.x ?? xData;
|
|
1463
|
+
const y = Array.isArray(resolved.y) ? applyNanHandling(resolved.y, sp.nanHandling ?? "gap", x) : resolved.y;
|
|
1464
|
+
traces.push(buildTrace({
|
|
1465
|
+
chartType: sp.chartType || "line",
|
|
1466
|
+
x,
|
|
1467
|
+
y,
|
|
1468
|
+
name: label,
|
|
1469
|
+
color: s.color,
|
|
1470
|
+
lineWidth: s.lineWidth ?? 2,
|
|
1471
|
+
lineStyle: s.lineStyle ?? "solid",
|
|
1472
|
+
interpolation: sp.interpolation ?? "linear",
|
|
1473
|
+
yAxisId: yRef,
|
|
1474
|
+
showMarkers: sp.showDataPoints ?? false,
|
|
1475
|
+
stackGroup: s.stackGroup ?? null
|
|
1476
|
+
}));
|
|
1477
|
+
const overlay = s.overlay ? opts.overlays?.[s.id] : null;
|
|
1478
|
+
if (!overlay?.years?.length) continue;
|
|
1479
|
+
const ov = s.overlay;
|
|
1480
|
+
const ovLabel = ov.label || `${label} (actual)`;
|
|
1481
|
+
if (isHidden(ovLabel)) continue;
|
|
1482
|
+
const ovTrace = {
|
|
1483
|
+
type: "scatter",
|
|
1484
|
+
mode: ov.mode || "markers+lines",
|
|
1485
|
+
x: overlay.years,
|
|
1486
|
+
y: overlay.values,
|
|
1487
|
+
name: ovLabel,
|
|
1488
|
+
yaxis: yRef === "y" ? void 0 : yRef,
|
|
1489
|
+
line: {
|
|
1490
|
+
color: ov.color || s.color,
|
|
1491
|
+
width: ov.lineWidth ?? 2,
|
|
1492
|
+
dash: OVERLAY_DASH[ov.lineStyle] || "dash"
|
|
1493
|
+
},
|
|
1494
|
+
marker: { color: ov.color || s.color, size: 4, symbol: "circle" },
|
|
1495
|
+
showlegend: true
|
|
1496
|
+
};
|
|
1497
|
+
if ((sp.chartType || "line") === "area-stacked") {
|
|
1498
|
+
ovTrace.stackgroup = `overlay-${yRef}`;
|
|
1499
|
+
ovTrace.groupnorm = "fraction";
|
|
1500
|
+
ovTrace.fill = "none";
|
|
1501
|
+
ovTrace.mode = "lines";
|
|
1502
|
+
}
|
|
1503
|
+
traces.push(ovTrace);
|
|
1504
|
+
}
|
|
1505
|
+
});
|
|
1506
|
+
traces.push(...await deriveSeriesTraces(
|
|
1507
|
+
traces,
|
|
1508
|
+
sp,
|
|
1509
|
+
opts.seriesTransforms,
|
|
1510
|
+
{ onError: opts.onTransformError ?? void 0 }
|
|
1511
|
+
));
|
|
1512
|
+
if (opts.derivedTraces) {
|
|
1513
|
+
const extra = await opts.derivedTraces(traces, sp, { results: data, index });
|
|
1514
|
+
if (Array.isArray(extra)) traces.push(...extra);
|
|
1515
|
+
}
|
|
1516
|
+
if (traces.length === 0 && !(ownCount > 0 && hiddenCount === ownCount)) return null;
|
|
1517
|
+
const legendPos = opts.alwaysShowLegend && (sp.legendPosition ?? "hidden") === "hidden" ? "top" : sp.legendPosition ?? "hidden";
|
|
1518
|
+
const xRange = !useX && time.length >= 2 ? [time[0], time[time.length - 1]] : void 0;
|
|
1519
|
+
const { shapes, annotations } = buildOverlayShapes({
|
|
1520
|
+
referenceLines: sp.referenceLines,
|
|
1521
|
+
shadedBands: sp.shadedBands,
|
|
1522
|
+
yRefByAxisId
|
|
1523
|
+
});
|
|
1524
|
+
const font = { family: t.fontFamily, color: t.fontColor };
|
|
1525
|
+
const layout = {
|
|
1526
|
+
...layoutAxes,
|
|
1527
|
+
...shapes.length ? { shapes } : {},
|
|
1528
|
+
...annotations.length ? { annotations } : {},
|
|
1529
|
+
xaxis: {
|
|
1530
|
+
...useX ? { title: { text: sp.xAxis.label || sp.xAxis.variable, font: { size: t.fontSize } } } : {},
|
|
1531
|
+
...axisColours(t),
|
|
1532
|
+
automargin: true,
|
|
1533
|
+
...xRange ? { range: xRange } : {}
|
|
1534
|
+
},
|
|
1535
|
+
...buildLegendLayout(legendPos),
|
|
1536
|
+
paper_bgcolor: t.paperBackground,
|
|
1537
|
+
plot_bgcolor: t.plotBackground,
|
|
1538
|
+
font: { ...font, size: t.fontSize },
|
|
1539
|
+
margin: { t: 10, r: 20, b: 40, l: 50 },
|
|
1540
|
+
hoverlabel: { bgcolor: t.hoverBackground, bordercolor: t.hoverBorder, font },
|
|
1541
|
+
...opts.layout ?? {}
|
|
1542
|
+
};
|
|
1543
|
+
return { traces, layout };
|
|
1544
|
+
}
|
|
1545
|
+
const hasSize = (cell) => cell.isConnected && cell.offsetWidth > 0 && cell.offsetHeight > 0;
|
|
1546
|
+
async function drawCell(index) {
|
|
1547
|
+
const cell = cells[index];
|
|
1548
|
+
const token = ++lastToken;
|
|
1549
|
+
tokens[index] = token;
|
|
1550
|
+
const current = () => !disposed && cells[index] === cell && tokens[index] === token;
|
|
1551
|
+
const sp = config?.subplots?.[index];
|
|
1552
|
+
if (!sp) {
|
|
1553
|
+
showMessage(cell, opts.noSubplotMessage);
|
|
1554
|
+
deferred.delete(index);
|
|
1555
|
+
return false;
|
|
1556
|
+
}
|
|
1557
|
+
const figure = await buildFigure(sp, index);
|
|
1558
|
+
if (!current()) return false;
|
|
1559
|
+
if (!figure) {
|
|
1560
|
+
showMessage(cell, opts.emptyMessage);
|
|
1561
|
+
deferred.delete(index);
|
|
1562
|
+
return false;
|
|
1563
|
+
}
|
|
1564
|
+
if (!hasSize(cell)) {
|
|
1565
|
+
deferred.add(index);
|
|
1566
|
+
return false;
|
|
1567
|
+
}
|
|
1568
|
+
deferred.delete(index);
|
|
1569
|
+
const Plotly2 = await ensurePlotly();
|
|
1570
|
+
if (!current()) return false;
|
|
1571
|
+
if (!hasPlot2(cell)) cell.replaceChildren();
|
|
1572
|
+
plotted.add(cell);
|
|
1573
|
+
try {
|
|
1574
|
+
await Plotly2.react(cell, figure.traces, figure.layout, { responsive: true, displayModeBar: false });
|
|
1575
|
+
if (plotted.has(cell)) drawn.add(cell);
|
|
1576
|
+
return true;
|
|
1577
|
+
} catch (_) {
|
|
1578
|
+
drawn.delete(cell);
|
|
1579
|
+
return false;
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
function drawCellOrFail(index) {
|
|
1583
|
+
const cell = cells[index];
|
|
1584
|
+
const pending = drawCell(index);
|
|
1585
|
+
const token = tokens[index];
|
|
1586
|
+
return pending.catch((error) => {
|
|
1587
|
+
if (disposed) return false;
|
|
1588
|
+
if (cells[index] === cell && tokens[index] === token) {
|
|
1589
|
+
deferred.delete(index);
|
|
1590
|
+
showMessage(cell, opts.emptyMessage);
|
|
1591
|
+
}
|
|
1592
|
+
opts.onError?.({ index, error });
|
|
1593
|
+
return false;
|
|
1594
|
+
});
|
|
1595
|
+
}
|
|
1596
|
+
async function drawAll() {
|
|
1597
|
+
if (disposed) return false;
|
|
1598
|
+
if (cellsLayout !== config?.layout || !cells.length) buildCells();
|
|
1599
|
+
const results2 = await Promise.all(cells.map((_, i) => drawCellOrFail(i)));
|
|
1600
|
+
return results2.some(Boolean);
|
|
1601
|
+
}
|
|
1602
|
+
function update(patch = {}) {
|
|
1603
|
+
if (disposed) return Promise.resolve(false);
|
|
1604
|
+
if ("plotConfig" in patch) config = patch.plotConfig;
|
|
1605
|
+
if ("results" in patch) data = patch.results;
|
|
1606
|
+
if ("overlays" in patch) opts.overlays = patch.overlays;
|
|
1607
|
+
if ("hiddenSeries" in patch) opts.hiddenSeries = patch.hiddenSeries;
|
|
1608
|
+
return drawAll();
|
|
1609
|
+
}
|
|
1610
|
+
function resize2() {
|
|
1611
|
+
if (disposed) return;
|
|
1612
|
+
for (const index of [...deferred]) {
|
|
1613
|
+
if (!hasSize(cells[index])) continue;
|
|
1614
|
+
drawCellOrFail(index);
|
|
1615
|
+
}
|
|
1616
|
+
const Plotly2 = globalThis.Plotly;
|
|
1617
|
+
if (!Plotly2?.Plots?.resize) return;
|
|
1618
|
+
for (const cell of cells) {
|
|
1619
|
+
if (hasPlot2(cell) && hasSize(cell)) {
|
|
1620
|
+
Promise.resolve().then(() => Plotly2.Plots.resize(cell)).catch(() => {
|
|
1621
|
+
});
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
function dispose() {
|
|
1626
|
+
if (disposed) return;
|
|
1627
|
+
disposed = true;
|
|
1628
|
+
observer.disconnect();
|
|
1629
|
+
for (const cell of cells) purgeCell(cell);
|
|
1630
|
+
cells = [];
|
|
1631
|
+
deferred.clear();
|
|
1632
|
+
grid.remove();
|
|
1633
|
+
}
|
|
1634
|
+
drawAll();
|
|
1635
|
+
return {
|
|
1636
|
+
update,
|
|
1637
|
+
resize: resize2,
|
|
1638
|
+
dispose,
|
|
1639
|
+
/** The cells, one per grid slot, in layout order. */
|
|
1640
|
+
get charts() {
|
|
1641
|
+
return [...cells];
|
|
1642
|
+
},
|
|
1643
|
+
/**
|
|
1644
|
+
* The cells that show a figure now, in layout order — however it got
|
|
1645
|
+
* there: an update, or a deferred draw on a resize.
|
|
1646
|
+
*/
|
|
1647
|
+
get plotted() {
|
|
1648
|
+
return cells.filter((cell) => drawn.has(cell));
|
|
1649
|
+
}
|
|
1650
|
+
};
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1162
1653
|
// src/charts/plot_popout_window.js
|
|
1163
1654
|
var PlotPopoutWindow = class {
|
|
1164
1655
|
/**
|
|
@@ -1172,7 +1663,21 @@ var PlotPopoutWindow = class {
|
|
|
1172
1663
|
* capability backs Export PNG/CSV; absent => Blob download fallback.
|
|
1173
1664
|
* @param {Function} [options.onClose] - Called when window closes
|
|
1174
1665
|
* @param {Object} [options.actuals] - ETL overlay data { seriesId: { years, values } }
|
|
1175
|
-
* @param {Function} [options.configRenderer] - (container) =>
|
|
1666
|
+
* @param {Function} [options.configRenderer] - `(container) => { dispose() } | void` —
|
|
1667
|
+
* renders the Config tab. What it returns is disposed (`dispose()`) when the
|
|
1668
|
+
* window closes, so an editor drawn there (renderPlotConfigEditor) goes with it.
|
|
1669
|
+
* @param {Object} [options.seriesTransforms] - `{ [id]: transform }` (series_transforms.js);
|
|
1670
|
+
* absent => no derived series are drawn
|
|
1671
|
+
* @param {string} [options.refreshEvent='data:changed'] - Bus event after which the
|
|
1672
|
+
* window re-reads `getResults()` and redraws.
|
|
1673
|
+
* @param {{delta?: string|null, complete?: string|null}} [options.streamEvents] - Bus
|
|
1674
|
+
* events a live data source emits while it runs (`delta`) and when it ends
|
|
1675
|
+
* (`complete`); the window redraws on each while live update is on.
|
|
1676
|
+
* There are no default names: without `streamEvents` (or with a name
|
|
1677
|
+
* left out or given as null) nothing is listened for, and the window
|
|
1678
|
+
* updates through `refreshEvent` and explicit calls only.
|
|
1679
|
+
* @param {Function} [options.resolveSeries] - `(series, results, subplot) => { y, x? } | null`
|
|
1680
|
+
* (plot_grid.js); used by the chart and the data table. Default resolveSeriesData.
|
|
1176
1681
|
*/
|
|
1177
1682
|
constructor({
|
|
1178
1683
|
id,
|
|
@@ -1184,10 +1689,13 @@ var PlotPopoutWindow = class {
|
|
|
1184
1689
|
onClose,
|
|
1185
1690
|
actuals,
|
|
1186
1691
|
configRenderer,
|
|
1692
|
+
seriesTransforms,
|
|
1693
|
+
resolveSeries = resolveSeriesData,
|
|
1187
1694
|
// The window used to listen for 'simulation:run:completed' and tell
|
|
1188
1695
|
// the user to "run simulation first". A chart window does not know
|
|
1189
1696
|
// that its data comes from a simulation; the app that opens it does.
|
|
1190
1697
|
refreshEvent = "data:changed",
|
|
1698
|
+
streamEvents = {},
|
|
1191
1699
|
emptyMessage = "No data yet.",
|
|
1192
1700
|
tableEmptyMessage = "No data yet."
|
|
1193
1701
|
}) {
|
|
@@ -1201,17 +1709,21 @@ var PlotPopoutWindow = class {
|
|
|
1201
1709
|
});
|
|
1202
1710
|
this._actuals = actuals ?? null;
|
|
1203
1711
|
this._configRenderer = configRenderer ?? null;
|
|
1712
|
+
this._seriesTransforms = seriesTransforms ?? null;
|
|
1713
|
+
this._resolveSeries = resolveSeries;
|
|
1204
1714
|
this.refreshEvent = refreshEvent;
|
|
1715
|
+
this.streamEvents = Object.freeze({
|
|
1716
|
+
delta: streamEvents?.delta ?? null,
|
|
1717
|
+
complete: streamEvents?.complete ?? null
|
|
1718
|
+
});
|
|
1205
1719
|
this.emptyMessage = emptyMessage;
|
|
1206
1720
|
this.tableEmptyMessage = tableEmptyMessage;
|
|
1207
1721
|
this._window = null;
|
|
1208
1722
|
this._contentEl = null;
|
|
1209
|
-
this.
|
|
1723
|
+
this._grid = null;
|
|
1210
1724
|
this._gridEl = null;
|
|
1211
1725
|
this._activeTab = "plot";
|
|
1212
1726
|
this._eventDisposers = [];
|
|
1213
|
-
this._resizeObserver = null;
|
|
1214
|
-
this._chartCreated = false;
|
|
1215
1727
|
this._lastResults = null;
|
|
1216
1728
|
this._dataTable = null;
|
|
1217
1729
|
this._dataTableContainer = null;
|
|
@@ -1223,6 +1735,7 @@ var PlotPopoutWindow = class {
|
|
|
1223
1735
|
this._plotContent = null;
|
|
1224
1736
|
this._dataContent = null;
|
|
1225
1737
|
this._configContent = null;
|
|
1738
|
+
this._configHandle = null;
|
|
1226
1739
|
this._toolbarActions = null;
|
|
1227
1740
|
this._zoomBtn = null;
|
|
1228
1741
|
this._panBtn = null;
|
|
@@ -1235,6 +1748,7 @@ var PlotPopoutWindow = class {
|
|
|
1235
1748
|
this._lastTogglePanelSignature = null;
|
|
1236
1749
|
this._outsideClickHandler = null;
|
|
1237
1750
|
this._renderPending = false;
|
|
1751
|
+
this._configChanged = false;
|
|
1238
1752
|
this._liveUpdate = true;
|
|
1239
1753
|
this._liveBtn = null;
|
|
1240
1754
|
}
|
|
@@ -1249,7 +1763,6 @@ var PlotPopoutWindow = class {
|
|
|
1249
1763
|
await ensurePlotly();
|
|
1250
1764
|
this.#createWindow();
|
|
1251
1765
|
this.#subscribeEvents();
|
|
1252
|
-
this.#setupResizeObserver();
|
|
1253
1766
|
await this.#render();
|
|
1254
1767
|
}
|
|
1255
1768
|
bringToFront() {
|
|
@@ -1261,7 +1774,7 @@ var PlotPopoutWindow = class {
|
|
|
1261
1774
|
/** Update plot config and re-render. */
|
|
1262
1775
|
updateConfig(plotConfig) {
|
|
1263
1776
|
this._plotConfig = migrateData(plotConfig);
|
|
1264
|
-
this
|
|
1777
|
+
this._configChanged = true;
|
|
1265
1778
|
this.#scheduleRender();
|
|
1266
1779
|
}
|
|
1267
1780
|
/** Update actuals overlay data and re-render. */
|
|
@@ -1358,7 +1871,24 @@ var PlotPopoutWindow = class {
|
|
|
1358
1871
|
this._gridEl = document.createElement("div");
|
|
1359
1872
|
this._gridEl.className = "twm-plot-window-chart";
|
|
1360
1873
|
this._plotContent.appendChild(this._gridEl);
|
|
1361
|
-
this
|
|
1874
|
+
this._grid = renderPlotGrid(this._gridEl, this._plotConfig, null, {
|
|
1875
|
+
resolveSeries: this._resolveSeries,
|
|
1876
|
+
overlays: this._actuals,
|
|
1877
|
+
hiddenSeries: this._hiddenSeries,
|
|
1878
|
+
seriesTransforms: this._seriesTransforms,
|
|
1879
|
+
onTransformError: ({ transformId, traceName, error }) => this._services?.logger?.warn?.(
|
|
1880
|
+
`[PlotPopoutWindow] transform "${transformId}" failed on "${traceName}"`,
|
|
1881
|
+
error
|
|
1882
|
+
),
|
|
1883
|
+
// Popout windows have space — always show the legend.
|
|
1884
|
+
alwaysShowLegend: true,
|
|
1885
|
+
layout: { autosize: true, hovermode: "x unified" },
|
|
1886
|
+
emptyMessage: this.emptyMessage,
|
|
1887
|
+
onError: ({ index, error }) => this._services?.logger?.warn?.(
|
|
1888
|
+
`[PlotPopoutWindow] subplot ${index + 1} failed to draw`,
|
|
1889
|
+
error
|
|
1890
|
+
)
|
|
1891
|
+
});
|
|
1362
1892
|
this._dataContent = document.createElement("div");
|
|
1363
1893
|
this._dataContent.className = "twm-plot-tab-content";
|
|
1364
1894
|
this._dataContent.setAttribute("data-tab", "data");
|
|
@@ -1373,7 +1903,7 @@ var PlotPopoutWindow = class {
|
|
|
1373
1903
|
this._configContent.className = "twm-plot-tab-content";
|
|
1374
1904
|
this._configContent.setAttribute("data-tab", "config");
|
|
1375
1905
|
this._configContent.style.cssText = "display:none; flex-direction:column; overflow:auto; padding:8px 12px;";
|
|
1376
|
-
this._configRenderer(this._configContent);
|
|
1906
|
+
this._configHandle = this._configRenderer(this._configContent) ?? null;
|
|
1377
1907
|
content.appendChild(this._configContent);
|
|
1378
1908
|
}
|
|
1379
1909
|
this._seriesTogglePanel = document.createElement("div");
|
|
@@ -1384,36 +1914,6 @@ var PlotPopoutWindow = class {
|
|
|
1384
1914
|
container.appendChild(this._seriesTogglePanel);
|
|
1385
1915
|
return container;
|
|
1386
1916
|
}
|
|
1387
|
-
// ── Subplot grid ────────────────────────────────────────────────────
|
|
1388
|
-
#buildSubplotGrid() {
|
|
1389
|
-
if (!this._gridEl) return;
|
|
1390
|
-
for (const div of this._chartDivs) {
|
|
1391
|
-
if (div && window.Plotly) {
|
|
1392
|
-
try {
|
|
1393
|
-
purge(div);
|
|
1394
|
-
} catch (_) {
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
|
-
}
|
|
1398
|
-
this._gridEl.innerHTML = "";
|
|
1399
|
-
this._chartDivs = [];
|
|
1400
|
-
const tmpl = PLOT_LAYOUTS[this._plotConfig.layout] || PLOT_LAYOUTS["1x1"];
|
|
1401
|
-
this._gridEl.style.display = "grid";
|
|
1402
|
-
this._gridEl.style.gridTemplateColumns = tmpl.grid.columns;
|
|
1403
|
-
this._gridEl.style.gridTemplateRows = tmpl.grid.rows;
|
|
1404
|
-
this._gridEl.style.gap = "4px";
|
|
1405
|
-
this._gridEl.style.height = "100%";
|
|
1406
|
-
this._gridEl.style.width = "100%";
|
|
1407
|
-
for (let i = 0; i < tmpl.slots; i++) {
|
|
1408
|
-
const div = document.createElement("div");
|
|
1409
|
-
div.className = "plot-tile-chart";
|
|
1410
|
-
div.style.gridArea = tmpl.areas[i].gridArea;
|
|
1411
|
-
div.style.minHeight = "0";
|
|
1412
|
-
this._gridEl.appendChild(div);
|
|
1413
|
-
this._chartDivs.push(div);
|
|
1414
|
-
}
|
|
1415
|
-
this._chartCreated = false;
|
|
1416
|
-
}
|
|
1417
1917
|
// ── Toolbar helpers ─────────────────────────────────────────────────
|
|
1418
1918
|
#toolbarGroup() {
|
|
1419
1919
|
const g = document.createElement("div");
|
|
@@ -1433,9 +1933,16 @@ var PlotPopoutWindow = class {
|
|
|
1433
1933
|
return btn;
|
|
1434
1934
|
}
|
|
1435
1935
|
// ── Plotly toolbar actions ───────────────────────────────────────────
|
|
1936
|
+
get #charts() {
|
|
1937
|
+
return this._grid?.charts ?? [];
|
|
1938
|
+
}
|
|
1939
|
+
/** The subplots that show a figure — the grid knows, however it drew them. */
|
|
1940
|
+
get #plottedCharts() {
|
|
1941
|
+
return this._grid?.plotted ?? [];
|
|
1942
|
+
}
|
|
1436
1943
|
#setDragMode(mode) {
|
|
1437
|
-
if (!this.
|
|
1438
|
-
for (const div of this
|
|
1944
|
+
if (!this.#plottedCharts.length) return;
|
|
1945
|
+
for (const div of this.#charts) {
|
|
1439
1946
|
if (div._fullLayout) {
|
|
1440
1947
|
window.Plotly.relayout(div, { dragmode: mode }).catch(() => {
|
|
1441
1948
|
});
|
|
@@ -1445,7 +1952,7 @@ var PlotPopoutWindow = class {
|
|
|
1445
1952
|
this._panBtn?.classList.toggle("twm-is-active", mode === "pan");
|
|
1446
1953
|
}
|
|
1447
1954
|
#zoom(factor) {
|
|
1448
|
-
for (const div of this
|
|
1955
|
+
for (const div of this.#charts) {
|
|
1449
1956
|
if (!div._fullLayout) continue;
|
|
1450
1957
|
const xa = div._fullLayout.xaxis;
|
|
1451
1958
|
const ya = div._fullLayout.yaxis;
|
|
@@ -1464,7 +1971,7 @@ var PlotPopoutWindow = class {
|
|
|
1464
1971
|
}
|
|
1465
1972
|
}
|
|
1466
1973
|
#autoscale() {
|
|
1467
|
-
for (const div of this
|
|
1974
|
+
for (const div of this.#charts) {
|
|
1468
1975
|
if (!div._fullLayout) continue;
|
|
1469
1976
|
window.Plotly.relayout(div, {
|
|
1470
1977
|
"xaxis.autorange": true,
|
|
@@ -1473,20 +1980,6 @@ var PlotPopoutWindow = class {
|
|
|
1473
1980
|
});
|
|
1474
1981
|
}
|
|
1475
1982
|
}
|
|
1476
|
-
#setupResizeObserver() {
|
|
1477
|
-
if (!this._plotContent) return;
|
|
1478
|
-
this._resizeObserver = createRafResizeObserver(() => {
|
|
1479
|
-
if (this._activeTab === "plot" && this._chartCreated && window.Plotly) {
|
|
1480
|
-
for (const div of this._chartDivs) {
|
|
1481
|
-
if (div._fullLayout) {
|
|
1482
|
-
Plotly.Plots.resize(div).catch(() => {
|
|
1483
|
-
});
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
|
-
});
|
|
1488
|
-
this._resizeObserver.observe(this._plotContent);
|
|
1489
|
-
}
|
|
1490
1983
|
#switchTab(tabName) {
|
|
1491
1984
|
this._activeTab = tabName;
|
|
1492
1985
|
this._plotTab.classList.toggle("active", tabName === "plot");
|
|
@@ -1500,14 +1993,7 @@ var PlotPopoutWindow = class {
|
|
|
1500
1993
|
if (this._configContent) {
|
|
1501
1994
|
this._configContent.style.display = tabName === "config" ? "flex" : "none";
|
|
1502
1995
|
}
|
|
1503
|
-
if (tabName === "plot"
|
|
1504
|
-
for (const div of this._chartDivs) {
|
|
1505
|
-
if (div._fullLayout) {
|
|
1506
|
-
Plotly.Plots.resize(div).catch(() => {
|
|
1507
|
-
});
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
}
|
|
1996
|
+
if (tabName === "plot") this._grid?.resize();
|
|
1511
1997
|
if (tabName === "data") {
|
|
1512
1998
|
this.#renderDataTable();
|
|
1513
1999
|
}
|
|
@@ -1666,16 +2152,15 @@ var PlotPopoutWindow = class {
|
|
|
1666
2152
|
#subscribeEvents() {
|
|
1667
2153
|
const { eventBus } = this._services;
|
|
1668
2154
|
if (!eventBus) return;
|
|
1669
|
-
this.
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
);
|
|
2155
|
+
const { delta, complete } = this.streamEvents;
|
|
2156
|
+
for (const name of /* @__PURE__ */ new Set([delta, complete])) {
|
|
2157
|
+
if (!name) continue;
|
|
2158
|
+
this._eventDisposers.push(
|
|
2159
|
+
eventBus.on(name, () => {
|
|
2160
|
+
if (this._liveUpdate) this.#scheduleRender();
|
|
2161
|
+
})
|
|
2162
|
+
);
|
|
2163
|
+
}
|
|
1679
2164
|
this._eventDisposers.push(
|
|
1680
2165
|
eventBus.on(this.refreshEvent, () => {
|
|
1681
2166
|
if (this._liveUpdate) this.#scheduleRender();
|
|
@@ -1704,10 +2189,28 @@ var PlotPopoutWindow = class {
|
|
|
1704
2189
|
async #render() {
|
|
1705
2190
|
if (!this._window?.isVisible) return;
|
|
1706
2191
|
const results = this._getResults?.();
|
|
1707
|
-
|
|
2192
|
+
const configChanged = this._configChanged;
|
|
2193
|
+
this._configChanged = false;
|
|
2194
|
+
if (!results) {
|
|
2195
|
+
if (configChanged) {
|
|
2196
|
+
await this._grid.update({
|
|
2197
|
+
plotConfig: this._plotConfig,
|
|
2198
|
+
results: null,
|
|
2199
|
+
overlays: this._actuals,
|
|
2200
|
+
hiddenSeries: this._hiddenSeries
|
|
2201
|
+
});
|
|
2202
|
+
}
|
|
2203
|
+
return;
|
|
2204
|
+
}
|
|
1708
2205
|
this._lastResults = results;
|
|
1709
2206
|
this.#collectSeriesInfo();
|
|
1710
|
-
this
|
|
2207
|
+
await this._grid.update({
|
|
2208
|
+
plotConfig: this._plotConfig,
|
|
2209
|
+
results,
|
|
2210
|
+
overlays: this._actuals,
|
|
2211
|
+
hiddenSeries: this._hiddenSeries
|
|
2212
|
+
});
|
|
2213
|
+
if (!this._window) return;
|
|
1711
2214
|
this.#updateTableData(results);
|
|
1712
2215
|
if (this._activeTab === "data") {
|
|
1713
2216
|
this.#renderDataTable();
|
|
@@ -1730,7 +2233,7 @@ var PlotPopoutWindow = class {
|
|
|
1730
2233
|
subplotName: sp.displayName || `Subplot ${spIdx + 1}`
|
|
1731
2234
|
});
|
|
1732
2235
|
this._allSeriesLabels.push(label);
|
|
1733
|
-
if (s.overlay
|
|
2236
|
+
if (s.overlay) {
|
|
1734
2237
|
const ovLabel = s.overlay.label || `${label} (actual)`;
|
|
1735
2238
|
this._allSeriesInfo.push({
|
|
1736
2239
|
label: ovLabel,
|
|
@@ -1764,184 +2267,6 @@ var PlotPopoutWindow = class {
|
|
|
1764
2267
|
}
|
|
1765
2268
|
}
|
|
1766
2269
|
}
|
|
1767
|
-
#renderAllCharts(results) {
|
|
1768
|
-
if (!window.Plotly) return;
|
|
1769
|
-
const subplots = this._plotConfig.subplots ?? [];
|
|
1770
|
-
for (let i = 0; i < this._chartDivs.length; i++) {
|
|
1771
|
-
const chartDiv = this._chartDivs[i];
|
|
1772
|
-
const sp = subplots[i];
|
|
1773
|
-
if (!chartDiv) continue;
|
|
1774
|
-
if (!sp) {
|
|
1775
|
-
chartDiv.innerHTML = '<div class="twm-plot-no-data" style="color:rgba(255,255,255,0.3);text-align:center;padding:16px;">No subplot configured.</div>';
|
|
1776
|
-
continue;
|
|
1777
|
-
}
|
|
1778
|
-
this.#renderSubplotChart(chartDiv, sp, results);
|
|
1779
|
-
}
|
|
1780
|
-
}
|
|
1781
|
-
async #renderSubplotChart(chartDiv, sp, results) {
|
|
1782
|
-
const { time, series } = results;
|
|
1783
|
-
const traces = [];
|
|
1784
|
-
const layoutAxes = {};
|
|
1785
|
-
const yRefByAxisId = {};
|
|
1786
|
-
let axisCount = 0;
|
|
1787
|
-
for (const axis of sp.yAxes ?? []) {
|
|
1788
|
-
axisCount++;
|
|
1789
|
-
const yAxisKey = axisCount === 1 ? "yaxis" : `yaxis${axisCount}`;
|
|
1790
|
-
const yRef = axisCount === 1 ? "y" : `y${axisCount}`;
|
|
1791
|
-
if (axis.id) yRefByAxisId[axis.id] = yRef;
|
|
1792
|
-
layoutAxes[yAxisKey] = buildYAxisLayout({
|
|
1793
|
-
axisId: yAxisKey,
|
|
1794
|
-
title: axis.label || "",
|
|
1795
|
-
position: axis.position || "left",
|
|
1796
|
-
scaleType: axis.scale || "linear",
|
|
1797
|
-
min: axis.min,
|
|
1798
|
-
max: axis.max,
|
|
1799
|
-
showGrid: (sp.yAxes ?? []).length === 1,
|
|
1800
|
-
axisPosition: axisCount > 1 ? axis.position === "right" ? 1 : 0 : null
|
|
1801
|
-
});
|
|
1802
|
-
for (const s of axis.series ?? []) {
|
|
1803
|
-
if (!s.variable) continue;
|
|
1804
|
-
const label = s.label || s.variable;
|
|
1805
|
-
if (this._hiddenSeries.has(label)) continue;
|
|
1806
|
-
let rawY = series?.[s._resultKey ?? s.variable];
|
|
1807
|
-
if (!rawY && s._resultKey) {
|
|
1808
|
-
rawY = series?.[s.variable];
|
|
1809
|
-
}
|
|
1810
|
-
if (!rawY) {
|
|
1811
|
-
const dotIdx = s.variable.indexOf(".");
|
|
1812
|
-
if (dotIdx >= 0) rawY = series?.[s.variable.slice(dotIdx + 1)];
|
|
1813
|
-
}
|
|
1814
|
-
if (!rawY) continue;
|
|
1815
|
-
const y = Array.isArray(rawY) ? applyNanHandling(rawY, sp.nanHandling ?? "gap", time) : rawY;
|
|
1816
|
-
const seriesTime = s._resultTime ?? time;
|
|
1817
|
-
traces.push(buildTrace({
|
|
1818
|
-
chartType: sp.chartType || "line",
|
|
1819
|
-
x: seriesTime,
|
|
1820
|
-
y,
|
|
1821
|
-
name: label,
|
|
1822
|
-
color: s.color,
|
|
1823
|
-
lineWidth: s.lineWidth ?? 2,
|
|
1824
|
-
lineStyle: s.lineStyle ?? "solid",
|
|
1825
|
-
interpolation: sp.interpolation ?? "linear",
|
|
1826
|
-
yAxisId: yRef,
|
|
1827
|
-
showMarkers: sp.showDataPoints ?? false,
|
|
1828
|
-
stackGroup: s.stackGroup ?? null
|
|
1829
|
-
}));
|
|
1830
|
-
if (s.overlay?.etlKey && this._actuals?.[s.id]) {
|
|
1831
|
-
const ovLabel = s.overlay.label || `${label} (actual)`;
|
|
1832
|
-
if (!this._hiddenSeries.has(ovLabel)) {
|
|
1833
|
-
const act = this._actuals[s.id];
|
|
1834
|
-
if (act?.years?.length) {
|
|
1835
|
-
const ov = s.overlay;
|
|
1836
|
-
const dashMap = { dashed: "dash", dotted: "dot", solid: "solid" };
|
|
1837
|
-
traces.push({
|
|
1838
|
-
type: "scatter",
|
|
1839
|
-
mode: ov.mode || "markers+lines",
|
|
1840
|
-
x: act.years,
|
|
1841
|
-
y: act.values,
|
|
1842
|
-
name: ov.label || `${label} (actual)`,
|
|
1843
|
-
yaxis: yRef === "y" ? void 0 : yRef,
|
|
1844
|
-
line: {
|
|
1845
|
-
color: ov.color || s.color,
|
|
1846
|
-
width: ov.lineWidth ?? 2,
|
|
1847
|
-
dash: dashMap[ov.lineStyle] || "dash"
|
|
1848
|
-
},
|
|
1849
|
-
marker: {
|
|
1850
|
-
color: ov.color || s.color,
|
|
1851
|
-
size: 4,
|
|
1852
|
-
symbol: "circle"
|
|
1853
|
-
},
|
|
1854
|
-
showlegend: true
|
|
1855
|
-
});
|
|
1856
|
-
}
|
|
1857
|
-
}
|
|
1858
|
-
}
|
|
1859
|
-
}
|
|
1860
|
-
}
|
|
1861
|
-
if ((sp.showHpTrend || sp.showHpCycle) && window.pywebview?.api?.hp_filter) {
|
|
1862
|
-
const hpLambda = sp.hpLambda ?? 1600;
|
|
1863
|
-
for (const trace of [...traces]) {
|
|
1864
|
-
if (trace.type && trace.type !== "scatter" && trace.type !== "scattergl") continue;
|
|
1865
|
-
try {
|
|
1866
|
-
const hpResult = await window.pywebview.api.hp_filter({
|
|
1867
|
-
data: trace.y,
|
|
1868
|
-
lambda: hpLambda
|
|
1869
|
-
});
|
|
1870
|
-
if (!hpResult?.ok) continue;
|
|
1871
|
-
const { trend, cycle } = hpResult;
|
|
1872
|
-
const origColor = trace.line?.color || trace.marker?.color || getSeriesColor(0);
|
|
1873
|
-
if (sp.showHpTrend && trend) {
|
|
1874
|
-
traces.push({
|
|
1875
|
-
x: trace.x,
|
|
1876
|
-
y: trend,
|
|
1877
|
-
type: "scatter",
|
|
1878
|
-
mode: "lines",
|
|
1879
|
-
name: `${trace.name} (HP Trend)`,
|
|
1880
|
-
yaxis: trace.yaxis || "y",
|
|
1881
|
-
line: { color: origColor, width: 2, dash: "dash" },
|
|
1882
|
-
showlegend: true
|
|
1883
|
-
});
|
|
1884
|
-
}
|
|
1885
|
-
if (sp.showHpCycle && cycle) {
|
|
1886
|
-
traces.push({
|
|
1887
|
-
x: trace.x,
|
|
1888
|
-
y: cycle,
|
|
1889
|
-
type: "scatter",
|
|
1890
|
-
mode: "lines",
|
|
1891
|
-
name: `${trace.name} (HP Cycle)`,
|
|
1892
|
-
yaxis: trace.yaxis || "y",
|
|
1893
|
-
line: { color: origColor, width: 1, dash: "dot" },
|
|
1894
|
-
showlegend: true
|
|
1895
|
-
});
|
|
1896
|
-
}
|
|
1897
|
-
} catch (_) {
|
|
1898
|
-
}
|
|
1899
|
-
}
|
|
1900
|
-
}
|
|
1901
|
-
if (traces.length === 0 && this._hiddenSeries.size === 0) {
|
|
1902
|
-
chartDiv.innerHTML = `<div class="twm-plot-no-data" style="color:rgba(255,255,255,0.3);text-align:center;padding:16px;font-size:11px;">${this.emptyMessage}</div>`;
|
|
1903
|
-
return;
|
|
1904
|
-
}
|
|
1905
|
-
const legendPos = sp.legendPosition === "hidden" ? "top" : sp.legendPosition ?? "top";
|
|
1906
|
-
const legend = buildLegendLayout(legendPos);
|
|
1907
|
-
const xRange = time?.length >= 2 ? [time[0], time[time.length - 1]] : void 0;
|
|
1908
|
-
const { shapes, annotations } = buildOverlayShapes({
|
|
1909
|
-
referenceLines: sp.referenceLines,
|
|
1910
|
-
shadedBands: sp.shadedBands,
|
|
1911
|
-
yRefByAxisId
|
|
1912
|
-
});
|
|
1913
|
-
const layout = {
|
|
1914
|
-
...layoutAxes,
|
|
1915
|
-
...shapes.length ? { shapes } : {},
|
|
1916
|
-
...annotations.length ? { annotations } : {},
|
|
1917
|
-
xaxis: {
|
|
1918
|
-
gridcolor: "rgba(255,255,255,0.05)",
|
|
1919
|
-
linecolor: "rgba(255,255,255,0.08)",
|
|
1920
|
-
tickcolor: "rgba(255,255,255,0.3)",
|
|
1921
|
-
zerolinecolor: "rgba(255,255,255,0.06)",
|
|
1922
|
-
automargin: true,
|
|
1923
|
-
...xRange ? { range: xRange } : {}
|
|
1924
|
-
},
|
|
1925
|
-
...legend,
|
|
1926
|
-
paper_bgcolor: "transparent",
|
|
1927
|
-
plot_bgcolor: "transparent",
|
|
1928
|
-
font: { family: "Inter, -apple-system, BlinkMacSystemFont, sans-serif", color: "#cccccc", size: 11 },
|
|
1929
|
-
margin: { t: 10, r: 20, b: 40, l: 50 },
|
|
1930
|
-
hoverlabel: {
|
|
1931
|
-
bgcolor: "#1e2228",
|
|
1932
|
-
bordercolor: "#444444",
|
|
1933
|
-
font: { family: "Inter, -apple-system, BlinkMacSystemFont, sans-serif", color: "#cccccc" }
|
|
1934
|
-
},
|
|
1935
|
-
autosize: true,
|
|
1936
|
-
hovermode: "x unified"
|
|
1937
|
-
};
|
|
1938
|
-
if (!chartDiv.isConnected || chartDiv.offsetWidth === 0) return;
|
|
1939
|
-
try {
|
|
1940
|
-
await Plotly.react(chartDiv, traces, layout, { responsive: true, displayModeBar: false });
|
|
1941
|
-
this._chartCreated = true;
|
|
1942
|
-
} catch (_) {
|
|
1943
|
-
}
|
|
1944
|
-
}
|
|
1945
2270
|
// ── Data table ──────────────────────────────────────────────────────
|
|
1946
2271
|
#updateTableData(results) {
|
|
1947
2272
|
if (!results) {
|
|
@@ -1951,21 +2276,14 @@ var PlotPopoutWindow = class {
|
|
|
1951
2276
|
}
|
|
1952
2277
|
const headers = ["Time"];
|
|
1953
2278
|
const seriesColumns = [];
|
|
1954
|
-
const { time
|
|
2279
|
+
const { time } = results;
|
|
1955
2280
|
const subplots = this._plotConfig.subplots ?? [];
|
|
1956
2281
|
for (const sp of subplots) {
|
|
1957
2282
|
for (const axis of sp.yAxes ?? []) {
|
|
1958
2283
|
for (const s of axis.series ?? []) {
|
|
1959
2284
|
if (!s.variable) continue;
|
|
1960
2285
|
const label = s.label || s.variable;
|
|
1961
|
-
|
|
1962
|
-
if (!rawY && s._resultKey) {
|
|
1963
|
-
rawY = series?.[s.variable];
|
|
1964
|
-
}
|
|
1965
|
-
if (!rawY) {
|
|
1966
|
-
const dotIdx = s.variable.indexOf(".");
|
|
1967
|
-
if (dotIdx >= 0) rawY = series?.[s.variable.slice(dotIdx + 1)];
|
|
1968
|
-
}
|
|
2286
|
+
const rawY = this._resolveSeries(s, results, sp)?.y;
|
|
1969
2287
|
if (!rawY) continue;
|
|
1970
2288
|
headers.push(label);
|
|
1971
2289
|
seriesColumns.push(rawY);
|
|
@@ -2057,8 +2375,8 @@ var PlotPopoutWindow = class {
|
|
|
2057
2375
|
return str;
|
|
2058
2376
|
}
|
|
2059
2377
|
async #downloadPNG() {
|
|
2060
|
-
const targetDiv = this
|
|
2061
|
-
if (!targetDiv || !window.Plotly
|
|
2378
|
+
const targetDiv = this.#plottedCharts[0];
|
|
2379
|
+
if (!targetDiv || !window.Plotly) {
|
|
2062
2380
|
this.#notify("Download", "Plot not ready", "warn");
|
|
2063
2381
|
return;
|
|
2064
2382
|
}
|
|
@@ -2105,10 +2423,6 @@ var PlotPopoutWindow = class {
|
|
|
2105
2423
|
this._dataTable = null;
|
|
2106
2424
|
}
|
|
2107
2425
|
this.#hideSeriesPanel();
|
|
2108
|
-
if (this._resizeObserver) {
|
|
2109
|
-
this._resizeObserver.disconnect();
|
|
2110
|
-
this._resizeObserver = null;
|
|
2111
|
-
}
|
|
2112
2426
|
for (const disposer of this._eventDisposers) {
|
|
2113
2427
|
try {
|
|
2114
2428
|
disposer?.dispose?.();
|
|
@@ -2116,18 +2430,17 @@ var PlotPopoutWindow = class {
|
|
|
2116
2430
|
}
|
|
2117
2431
|
}
|
|
2118
2432
|
this._eventDisposers = [];
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
} catch (_) {
|
|
2124
|
-
}
|
|
2125
|
-
}
|
|
2433
|
+
try {
|
|
2434
|
+
this._configHandle?.dispose?.();
|
|
2435
|
+
} catch (error) {
|
|
2436
|
+
this._services?.logger?.warn?.("[PlotPopoutWindow] the config panel failed to dispose", error);
|
|
2126
2437
|
}
|
|
2438
|
+
this._configHandle = null;
|
|
2439
|
+
this._grid?.dispose();
|
|
2440
|
+
this._grid = null;
|
|
2127
2441
|
this._window = null;
|
|
2128
2442
|
this._contentEl = null;
|
|
2129
2443
|
this._gridEl = null;
|
|
2130
|
-
this._chartDivs = [];
|
|
2131
2444
|
this._plotTab = null;
|
|
2132
2445
|
this._dataTab = null;
|
|
2133
2446
|
this._plotContent = null;
|
|
@@ -2142,7 +2455,6 @@ var PlotPopoutWindow = class {
|
|
|
2142
2455
|
this._hiddenSeries = null;
|
|
2143
2456
|
this._allSeriesInfo = null;
|
|
2144
2457
|
this._allSeriesLabels = null;
|
|
2145
|
-
this._chartCreated = false;
|
|
2146
2458
|
this._lastResults = null;
|
|
2147
2459
|
}
|
|
2148
2460
|
};
|
|
@@ -2435,7 +2747,10 @@ async function openRawTracesWindow({ id, title, traces, layout, frames, tableHea
|
|
|
2435
2747
|
export {
|
|
2436
2748
|
setPlotlySource,
|
|
2437
2749
|
DARK_THEME_LAYOUT,
|
|
2750
|
+
DEFAULT_CHART_EXPORT_FILENAME,
|
|
2438
2751
|
DEFAULT_CONFIG,
|
|
2752
|
+
setChartExportFilename,
|
|
2753
|
+
getChartExportFilename,
|
|
2439
2754
|
ensurePlotly,
|
|
2440
2755
|
createChart,
|
|
2441
2756
|
updateChart,
|
|
@@ -2470,11 +2785,18 @@ export {
|
|
|
2470
2785
|
uid,
|
|
2471
2786
|
applyNanHandling,
|
|
2472
2787
|
PLOT_LAYOUTS,
|
|
2788
|
+
makeDefaultYAxis,
|
|
2789
|
+
makeDefaultSeries,
|
|
2473
2790
|
makeDefaultSubplot,
|
|
2474
2791
|
migrateData,
|
|
2475
2792
|
migrateSingleChart,
|
|
2793
|
+
deriveSeriesTraces,
|
|
2794
|
+
renderSeriesTransformControls,
|
|
2795
|
+
PLOT_GRID_THEME,
|
|
2796
|
+
resolveSeriesData,
|
|
2797
|
+
renderPlotGrid,
|
|
2476
2798
|
PlotPopoutWindow,
|
|
2477
2799
|
openPlotPopoutWindow,
|
|
2478
2800
|
openRawTracesWindow
|
|
2479
2801
|
};
|
|
2480
|
-
//# sourceMappingURL=chunk-
|
|
2802
|
+
//# sourceMappingURL=chunk-MCIXFNVF.js.map
|