flexdesk 0.16.1 → 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.
Files changed (252) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/LICENSE +0 -0
  3. package/README.md +2217 -42
  4. package/THIRD_PARTY_LICENSES.md +0 -0
  5. package/css/base.css +1834 -75
  6. package/css/flexdesk.css +3146 -164
  7. package/css/overrides.css +16 -0
  8. package/css/reset.css +0 -0
  9. package/css/tokens.css +0 -0
  10. package/dist/canvas.js +6 -239
  11. package/dist/canvas.js.map +3 -3
  12. package/dist/charts.js +709 -18
  13. package/dist/charts.js.map +4 -4
  14. package/dist/chunk-3PHCPZHT.js +0 -0
  15. package/dist/chunk-3PHCPZHT.js.map +0 -0
  16. package/dist/chunk-4A7SJC3T.js +503 -0
  17. package/dist/chunk-4A7SJC3T.js.map +7 -0
  18. package/dist/{chunk-6JSOVNID.js → chunk-CVMCZZX2.js} +635 -10
  19. package/dist/chunk-CVMCZZX2.js.map +7 -0
  20. package/dist/chunk-D3COLZZ2.js +1311 -0
  21. package/dist/chunk-D3COLZZ2.js.map +7 -0
  22. package/dist/chunk-FOOS3T5L.js +0 -0
  23. package/dist/chunk-FOOS3T5L.js.map +0 -0
  24. package/dist/{chunk-SN4LSOAR.js → chunk-GOQLR5WR.js} +452 -2
  25. package/dist/chunk-GOQLR5WR.js.map +7 -0
  26. package/dist/{chunk-FL5KFNQH.js → chunk-HNU6MM2B.js} +52 -17
  27. package/dist/chunk-HNU6MM2B.js.map +7 -0
  28. package/dist/chunk-HYJYYEPK.js +375 -0
  29. package/dist/chunk-HYJYYEPK.js.map +7 -0
  30. package/dist/chunk-JKZLNKG6.js +2041 -0
  31. package/dist/chunk-JKZLNKG6.js.map +7 -0
  32. package/dist/chunk-JYWURG5T.js +0 -0
  33. package/dist/chunk-JYWURG5T.js.map +0 -0
  34. package/dist/{chunk-RQM5Z2MU.js → chunk-MCIXFNVF.js} +633 -311
  35. package/dist/chunk-MCIXFNVF.js.map +7 -0
  36. package/dist/chunk-N7HC3ADV.js +30 -0
  37. package/dist/chunk-N7HC3ADV.js.map +7 -0
  38. package/dist/{chunk-FBGP5KN5.js → chunk-Q36EPT2R.js} +3268 -203
  39. package/dist/chunk-Q36EPT2R.js.map +7 -0
  40. package/dist/chunk-QWQJUDPD.js +610 -0
  41. package/dist/chunk-QWQJUDPD.js.map +7 -0
  42. package/dist/{chunk-NLTOY57M.js → chunk-UURZ37SD.js} +28 -48
  43. package/dist/chunk-UURZ37SD.js.map +7 -0
  44. package/dist/{chunk-B5Q53LAJ.js → chunk-UWO43BZU.js} +2 -2
  45. package/dist/chunk-UWO43BZU.js.map +7 -0
  46. package/dist/chunk-WVFGV5FT.js +0 -0
  47. package/dist/chunk-WVFGV5FT.js.map +0 -0
  48. package/dist/core.js +170 -118
  49. package/dist/core.js.map +2 -2
  50. package/dist/editor.js +156 -70
  51. package/dist/editor.js.map +2 -2
  52. package/dist/flexdesk.css +3146 -164
  53. package/dist/flow.js +2789 -306
  54. package/dist/flow.js.map +4 -4
  55. package/dist/host.js +109 -3
  56. package/dist/host.js.map +3 -3
  57. package/dist/reset.css +0 -0
  58. package/dist/tile_registry-6WZPTUZV.js +0 -0
  59. package/dist/tile_registry-6WZPTUZV.js.map +0 -0
  60. package/dist/tiles.js +657 -234
  61. package/dist/tiles.js.map +4 -4
  62. package/dist/tokens.css +0 -0
  63. package/dist/widgets.js +3252 -2124
  64. package/dist/widgets.js.map +4 -4
  65. package/dist/wm.js +1227 -347
  66. package/dist/wm.js.map +4 -4
  67. package/package.json +3 -1
  68. package/src/canvas/canvas_adapter.js +5 -0
  69. package/src/canvas/edge_router.js +0 -0
  70. package/src/canvas/graph/connector_router.js +0 -0
  71. package/src/canvas/graph/history_service.js +0 -0
  72. package/src/canvas/graph/node_platform.js +0 -0
  73. package/src/canvas/index.js +1 -0
  74. package/src/canvas/layered_layout.js +244 -0
  75. package/src/charts/chart_types.js +0 -0
  76. package/src/charts/data_series_plot_window.js +0 -0
  77. package/src/charts/downsample.js +2 -2
  78. package/src/charts/plot_config.js +53 -29
  79. package/src/charts/plot_config_editor.js +627 -0
  80. package/src/charts/plot_grid.js +449 -0
  81. package/src/charts/plot_popout_window.js +103 -317
  82. package/src/charts/plotly_wrapper.js +22 -2
  83. package/src/charts/series_transforms.js +206 -0
  84. package/src/core/event_bus.js +0 -0
  85. package/src/core/logging.js +1 -1
  86. package/src/core/settings.js +105 -19
  87. package/src/core/state_guard.js +93 -51
  88. package/src/core/state_guard_config.js +85 -74
  89. package/src/core/state_machine.js +0 -0
  90. package/src/editor/editor_pane.js +3 -3
  91. package/src/editor/monaco_editor_factory.js +124 -60
  92. package/src/editor/monaco_loader.js +103 -28
  93. package/src/editor/notebook_search.js +0 -0
  94. package/src/editor/notebook_tab_bar.js +38 -52
  95. package/src/editor/notebook_undo_manager.js +0 -0
  96. package/src/editor/split_pane_container.js +6 -3
  97. package/src/flow/canvas_editor/connections.js +90 -0
  98. package/src/flow/canvas_editor/editor.js +2351 -153
  99. package/src/flow/canvas_editor/index.js +2 -1
  100. package/src/flow/canvas_editor/layout.js +119 -12
  101. package/src/flow/canvas_editor/strings.js +80 -0
  102. package/src/flow/kit/always_before.js +0 -0
  103. package/src/flow/kit/catalogue.js +97 -3
  104. package/src/flow/kit/chip_input.js +0 -0
  105. package/src/flow/kit/dom.js +0 -0
  106. package/src/flow/kit/findings.js +0 -0
  107. package/src/flow/kit/history.js +0 -0
  108. package/src/flow/kit/index.js +3 -3
  109. package/src/flow/kit/keys.js +7 -1
  110. package/src/flow/kit/logic_graph.js +97 -15
  111. package/src/flow/kit/references.js +0 -0
  112. package/src/flow/kit/settings_panel.js +0 -0
  113. package/src/flow/kit/settings_schema.js +2 -1
  114. package/src/flow/kit/step_picker.js +0 -0
  115. package/src/flow/kit/strings.js +3 -1
  116. package/src/flow/kit/value_picker.js +0 -0
  117. package/src/flow/kit/widgets.js +86 -109
  118. package/src/flow/lanes/editor.js +136 -14
  119. package/src/flow/lanes/index.js +3 -1
  120. package/src/flow/lanes/layout.js +0 -0
  121. package/src/flow/lanes/pipeline.js +44 -2
  122. package/src/flow/lanes/preview.js +0 -0
  123. package/src/flow/lanes/strings.js +1 -0
  124. package/src/flow/outline/editor.js +0 -0
  125. package/src/flow/outline/index.js +0 -0
  126. package/src/flow/outline/mapping.js +0 -0
  127. package/src/flow/outline/menu.js +0 -0
  128. package/src/flow/outline/operations.js +0 -0
  129. package/src/flow/outline/recognise.js +0 -0
  130. package/src/flow/outline/strings.js +0 -0
  131. package/src/help/help_modal.js +168 -42
  132. package/src/help/help_registry.js +12 -1
  133. package/src/host/external_links.js +0 -0
  134. package/src/host/file_picker.js +117 -0
  135. package/src/host/host.js +10 -2
  136. package/src/host/photino_host.js +9 -0
  137. package/src/host/photino_host.md +21 -3
  138. package/src/host/pywebview_host.js +10 -0
  139. package/src/tiles/chart_tiles.js +53 -245
  140. package/src/tiles/config_schema.js +8 -0
  141. package/src/tiles/figure_tile.js +322 -0
  142. package/src/tiles/layout_persistence.js +3 -3
  143. package/src/tiles/list_tiles.js +7 -7
  144. package/src/tiles/scope_picker.js +0 -0
  145. package/src/tiles/tile_base.js +1 -1
  146. package/src/tiles/tile_config_form.js +442 -0
  147. package/src/tiles/tile_grid.js +2 -0
  148. package/src/tiles/tile_registry.js +0 -0
  149. package/src/tiling/bottom_panel_tabs.js +67 -99
  150. package/src/tiling/command_palette.js +64 -19
  151. package/src/tiling/content_registry.js +4 -1
  152. package/src/tiling/desktops.js +11 -1
  153. package/src/tiling/entity_sources.js +0 -0
  154. package/src/tiling/keymap.js +403 -128
  155. package/src/tiling/kind_taxonomy.js +78 -0
  156. package/src/tiling/landing_table.js +0 -0
  157. package/src/tiling/loading_overlay.js +0 -0
  158. package/src/tiling/nav_panel.js +59 -10
  159. package/src/tiling/page_factory.js +5 -5
  160. package/src/tiling/panel_availability.js +49 -0
  161. package/src/tiling/panel_keys.js +0 -0
  162. package/src/tiling/shell.js +114 -12
  163. package/src/tiling/tab_strip.js +0 -0
  164. package/src/tiling/tile_breadcrumb.js +0 -0
  165. package/src/tiling/tile_renderer.js +105 -9
  166. package/src/tiling/tile_tab_menu.js +0 -0
  167. package/src/tiling/tile_tree.js +0 -0
  168. package/src/tiling/wm.js +562 -30
  169. package/src/tiling/zoom.js +0 -0
  170. package/src/ui/base/component_base.js +0 -0
  171. package/src/ui/base/controller_base.js +0 -0
  172. package/src/ui/base/page_base.js +0 -0
  173. package/src/ui/components/about_dialog.js +0 -0
  174. package/src/ui/components/action_dropdown.js +30 -2
  175. package/src/ui/components/alert_log.js +0 -0
  176. package/src/ui/components/alerts_pane.js +0 -0
  177. package/src/ui/components/attribute_list_editor.js +0 -0
  178. package/src/ui/components/autocomplete_field.js +0 -0
  179. package/src/ui/components/cell_editor_host.js +211 -0
  180. package/src/ui/components/cell_grid.js +648 -0
  181. package/src/ui/components/cell_selection.js +220 -0
  182. package/src/ui/components/choice_controls.js +351 -0
  183. package/src/ui/components/color_field.js +211 -0
  184. package/src/ui/components/color_list_field.js +210 -0
  185. package/src/ui/components/column_fit.js +0 -0
  186. package/src/ui/components/column_groups.js +314 -0
  187. package/src/ui/components/column_order.js +142 -0
  188. package/src/ui/components/computing_status_window.js +98 -25
  189. package/src/ui/components/confirm_dialog.js +81 -38
  190. package/src/ui/components/context_menu.js +2 -2
  191. package/src/ui/components/conversation_view.js +440 -0
  192. package/src/ui/components/crosstab.js +289 -0
  193. package/src/ui/components/data_table.js +601 -62
  194. package/src/ui/components/detail_header.js +22 -11
  195. package/src/ui/components/document_outline.js +397 -0
  196. package/src/ui/components/drag_reorder.js +0 -0
  197. package/src/ui/components/fact_table.js +0 -0
  198. package/src/ui/components/fill_handle.js +322 -0
  199. package/src/ui/components/gallery_picker.js +19 -8
  200. package/src/ui/components/header_actions.js +55 -0
  201. package/src/ui/components/inline_renamer.js +0 -0
  202. package/src/ui/components/job_progress.js +51 -0
  203. package/src/ui/components/managed_window.js +27 -4
  204. package/src/ui/components/menu_bar.js +745 -0
  205. package/src/ui/components/modal.js +449 -75
  206. package/src/ui/components/notification_history.js +3 -3
  207. package/src/ui/components/progress.js +483 -0
  208. package/src/ui/components/record_list.js +0 -0
  209. package/src/ui/components/row_activation.js +0 -0
  210. package/src/ui/components/section_stack.js +298 -0
  211. package/src/ui/components/series.js +173 -0
  212. package/src/ui/components/settings_view.js +634 -0
  213. package/src/ui/components/slide_out_panel.js +43 -6
  214. package/src/ui/components/slider_field.js +275 -18
  215. package/src/ui/components/sortable_list.js +0 -0
  216. package/src/ui/components/staged_edits.js +343 -0
  217. package/src/ui/components/staged_history.js +172 -0
  218. package/src/ui/components/table_filter_chips.js +0 -0
  219. package/src/ui/components/table_state_store.js +0 -0
  220. package/src/ui/components/table_structure.js +182 -0
  221. package/src/ui/components/table_values.js +0 -0
  222. package/src/ui/components/tabs.js +497 -0
  223. package/src/ui/components/tag_input.js +215 -0
  224. package/src/ui/components/toast.js +25 -8
  225. package/src/ui/components/toggle_field.js +175 -0
  226. package/src/ui/components/tree_view.js +55 -34
  227. package/src/ui/components/window_taskbar.js +0 -0
  228. package/src/ui/components/wizard.js +563 -0
  229. package/src/ui/controllers/window_chrome_controller.js +2 -2
  230. package/src/ui/notification_center.js +375 -119
  231. package/src/ui/utils/auto_scrollbars.js +0 -0
  232. package/src/ui/utils/markdown.js +709 -0
  233. package/src/ui/utils/native_context_menu.js +0 -0
  234. package/src/ui/utils/overlay_scrollbar.js +16 -12
  235. package/src/ui/utils/raf_resize_observer.js +0 -0
  236. package/src/ui/utils/scroll_lock.js +0 -0
  237. package/src/ui/utils/strip_scroll.js +55 -0
  238. package/src/ui/utils/tooltip_service.js +0 -8
  239. package/dist/chunk-6JSOVNID.js.map +0 -7
  240. package/dist/chunk-B5Q53LAJ.js.map +0 -7
  241. package/dist/chunk-FBGP5KN5.js.map +0 -7
  242. package/dist/chunk-FL5KFNQH.js.map +0 -7
  243. package/dist/chunk-NLTOY57M.js.map +0 -7
  244. package/dist/chunk-NNFTETK3.js +0 -633
  245. package/dist/chunk-NNFTETK3.js.map +0 -7
  246. package/dist/chunk-O5OHMWBB.js +0 -284
  247. package/dist/chunk-O5OHMWBB.js.map +0 -7
  248. package/dist/chunk-RQM5Z2MU.js.map +0 -7
  249. package/dist/chunk-SN4LSOAR.js.map +0 -7
  250. package/dist/chunk-W6FBJLPW.js +0 -367
  251. package/dist/chunk-W6FBJLPW.js.map +0 -7
  252. package/src/ui/controllers/panel_state_machine.js +0 -634
package/dist/charts.js CHANGED
@@ -1,10 +1,18 @@
1
+ import {
2
+ AutocompleteField
3
+ } from "./chunk-QWQJUDPD.js";
4
+ import {
5
+ createTabs
6
+ } from "./chunk-HYJYYEPK.js";
1
7
  import {
2
8
  ALL_CHART_TYPES,
3
9
  CHART_TYPES_2D,
4
10
  CHART_TYPES_3D,
5
11
  COLOR_PALETTE,
6
12
  DARK_THEME_LAYOUT,
13
+ DEFAULT_CHART_EXPORT_FILENAME,
7
14
  DEFAULT_CONFIG,
15
+ PLOT_GRID_THEME,
8
16
  PLOT_LAYOUTS,
9
17
  PlotPopoutWindow,
10
18
  applyNanHandling,
@@ -21,35 +29,48 @@ import {
21
29
  buildYAxisLayout,
22
30
  createChart,
23
31
  createChartDeferred,
32
+ deriveSeriesTraces,
24
33
  ensurePlotly,
25
34
  extendTraces,
35
+ getChartExportFilename,
26
36
  getSeriesColor,
27
37
  hasPlot,
28
38
  hexToRgba,
29
39
  is3DChart,
30
40
  lttb,
41
+ makeDefaultSeries,
31
42
  makeDefaultSubplot,
43
+ makeDefaultYAxis,
32
44
  migrateData,
33
45
  migrateSingleChart,
34
46
  openPlotPopoutWindow,
35
47
  openRawTracesWindow,
36
48
  purge,
37
49
  relayout,
50
+ renderPlotGrid,
51
+ renderSeriesTransformControls,
38
52
  resize,
53
+ resolveSeriesData,
39
54
  restyle,
55
+ setChartExportFilename,
40
56
  setPlotlySource,
41
57
  strideDownsampleShared,
42
58
  strideIndices,
43
59
  uid,
44
60
  updateChart
45
- } from "./chunk-RQM5Z2MU.js";
61
+ } from "./chunk-MCIXFNVF.js";
46
62
  import {
47
63
  createRafResizeObserver
48
- } from "./chunk-FBGP5KN5.js";
64
+ } from "./chunk-Q36EPT2R.js";
49
65
  import {
50
- ManagedWindow
51
- } from "./chunk-6JSOVNID.js";
52
- import "./chunk-FL5KFNQH.js";
66
+ ColorField,
67
+ ManagedWindow,
68
+ ToggleField,
69
+ selectBox
70
+ } from "./chunk-CVMCZZX2.js";
71
+ import "./chunk-HNU6MM2B.js";
72
+ import "./chunk-N7HC3ADV.js";
73
+ import "./chunk-WVFGV5FT.js";
53
74
  import "./chunk-JYWURG5T.js";
54
75
 
55
76
  // src/charts/data_series_plot_window.js
@@ -243,20 +264,20 @@ var DataSeriesPlotWindow = class _DataSeriesPlotWindow {
243
264
  return section;
244
265
  }
245
266
  #buildXColumnSelect() {
246
- const select = document.createElement("select");
267
+ const select2 = document.createElement("select");
247
268
  for (const header of this.headers) {
248
269
  if (!this._numericColumns.has(header)) continue;
249
270
  const opt = document.createElement("option");
250
271
  opt.value = header;
251
272
  opt.textContent = this.labelHeader(header);
252
273
  if (header === this._config.xColumn) opt.selected = true;
253
- select.appendChild(opt);
274
+ select2.appendChild(opt);
254
275
  }
255
- select.addEventListener("change", () => {
256
- this._config.xColumn = select.value;
276
+ select2.addEventListener("change", () => {
277
+ this._config.xColumn = select2.value;
257
278
  this.#onConfigChanged();
258
279
  });
259
- return select;
280
+ return select2;
260
281
  }
261
282
  #populateYColumns() {
262
283
  const list = this._yColumnsList;
@@ -313,16 +334,16 @@ var DataSeriesPlotWindow = class _DataSeriesPlotWindow {
313
334
  return getSeriesColor(index);
314
335
  }
315
336
  #buildSelect(options, value, onChange) {
316
- const select = document.createElement("select");
337
+ const select2 = document.createElement("select");
317
338
  for (const opt of options) {
318
- const el = document.createElement("option");
319
- el.value = opt;
320
- el.textContent = opt.charAt(0).toUpperCase() + opt.slice(1);
321
- if (opt === value) el.selected = true;
322
- select.appendChild(el);
339
+ const el2 = document.createElement("option");
340
+ el2.value = opt;
341
+ el2.textContent = opt.charAt(0).toUpperCase() + opt.slice(1);
342
+ if (opt === value) el2.selected = true;
343
+ select2.appendChild(el2);
323
344
  }
324
- select.addEventListener("change", () => onChange(select.value));
325
- return select;
345
+ select2.addEventListener("change", () => onChange(select2.value));
346
+ return select2;
326
347
  }
327
348
  #buildCheckbox(label, checked, onChange) {
328
349
  const wrapper = document.createElement("label");
@@ -663,13 +684,674 @@ function closeDataSeriesPlotWindow(seriesId) {
663
684
  _openPlotWindows.delete(seriesId);
664
685
  }
665
686
  }
687
+
688
+ // src/charts/plot_config_editor.js
689
+ var CHART_TYPE_OPTIONS = Object.freeze([
690
+ { group: "2D", options: CHART_TYPES_2D },
691
+ { group: "3D", options: CHART_TYPES_3D }
692
+ ]);
693
+ var SCALES = Object.freeze([{ value: "linear", label: "Linear" }, { value: "log", label: "Log" }]);
694
+ var POSITIONS = Object.freeze([{ value: "left", label: "Left" }, { value: "right", label: "Right" }]);
695
+ var DASHES = Object.freeze([
696
+ { value: "solid", label: "Solid" },
697
+ { value: "dashed", label: "Dashed" },
698
+ { value: "dotted", label: "Dotted" }
699
+ ]);
700
+ var LEGEND_POSITIONS2 = Object.freeze([
701
+ { value: "hidden", label: "Hidden" },
702
+ { value: "top", label: "Top" },
703
+ { value: "bottom", label: "Bottom" },
704
+ { value: "left", label: "Left" },
705
+ { value: "right", label: "Right" }
706
+ ]);
707
+ var INTERPOLATIONS = Object.freeze([
708
+ { value: "linear", label: "Linear" },
709
+ { value: "step", label: "Step" },
710
+ { value: "cubic", label: "Cubic spline" }
711
+ ]);
712
+ var NAN_STRATEGIES = Object.freeze([
713
+ { value: "gap", label: "Gap" },
714
+ { value: "zero", label: "Zero" },
715
+ { value: "hold", label: "Hold last value" },
716
+ { value: "interpolate", label: "Interpolate" }
717
+ ]);
718
+ var X_SOURCES = Object.freeze([{ value: "time", label: "Time" }, { value: "series", label: "Series" }]);
719
+ var OVERLAY_MODES = Object.freeze([
720
+ { value: "markers+lines", label: "Markers and lines" },
721
+ { value: "markers", label: "Markers" },
722
+ { value: "lines", label: "Lines" }
723
+ ]);
724
+ function el(tag, className, text) {
725
+ const node = document.createElement(tag);
726
+ if (className) node.className = className;
727
+ if (text != null) node.textContent = String(text);
728
+ return node;
729
+ }
730
+ function button(text, onClick, { title = null, className = "twm-btn twm-plot-config__add" } = {}) {
731
+ const b = el("button", className, text);
732
+ b.type = "button";
733
+ if (title) b.title = title;
734
+ b.addEventListener("click", () => {
735
+ if (!b.disabled) onClick();
736
+ });
737
+ return b;
738
+ }
739
+ function removeButton(title, onClick) {
740
+ const b = button("", onClick, { title, className: "twm-btn-icon twm-plot-config__remove" });
741
+ b.setAttribute("aria-label", title);
742
+ b.appendChild(el("span", "material-symbols-outlined", "close"));
743
+ return b;
744
+ }
745
+ function field(label, control) {
746
+ const single = control.matches("input, select, textarea");
747
+ const row = el(single ? "label" : "div", "twm-plot-config__field");
748
+ row.append(el("span", "twm-plot-config__label", label), control);
749
+ return row;
750
+ }
751
+ function textInput(value, onSet, { placeholder = "", label = null } = {}) {
752
+ const input = el("input", "twm-plot-config__input");
753
+ input.type = "text";
754
+ input.value = value ?? "";
755
+ input.placeholder = placeholder;
756
+ if (label) input.setAttribute("aria-label", label);
757
+ input.addEventListener("input", () => onSet(input.value));
758
+ return input;
759
+ }
760
+ function numberInput(value, onSet, { placeholder = "Auto", label = null, min = null, max = null, step = "any" } = {}) {
761
+ const input = el("input", "twm-plot-config__input twm-plot-config__input--number");
762
+ input.type = "number";
763
+ input.step = String(step);
764
+ if (min != null) input.min = String(min);
765
+ if (max != null) input.max = String(max);
766
+ input.value = value == null ? "" : String(value);
767
+ input.placeholder = placeholder;
768
+ if (label) input.setAttribute("aria-label", label);
769
+ input.addEventListener("change", () => {
770
+ const n = parseFloat(input.value);
771
+ onSet(Number.isFinite(n) ? n : null);
772
+ });
773
+ return input;
774
+ }
775
+ function select(options, value, onPick, { label = null, empty = null } = {}) {
776
+ const s = selectBox(options, value, {
777
+ label: label ?? "",
778
+ empty,
779
+ className: "twm-plot-config__input twm-plot-config__input--select"
780
+ });
781
+ s.box.addEventListener("change", () => onPick(s.valueOf()));
782
+ return s.box;
783
+ }
784
+ function makeZAxis() {
785
+ return { label: "", scale: "linear", min: null, max: null, step: null, variable: "" };
786
+ }
787
+ var editorCount = 0;
788
+ function renderPlotConfigEditor(container, config, options = {}) {
789
+ const opts = {
790
+ onChange: null,
791
+ seriesProvider: null,
792
+ overlaySources: null,
793
+ seriesTransforms: null,
794
+ extraSeriesFields: null,
795
+ logger: null,
796
+ ...options
797
+ };
798
+ const editorId = `twm-plot-config-${++editorCount}`;
799
+ let fieldCount = 0;
800
+ let active = 0;
801
+ let disposed = false;
802
+ let bodyDisposers = [];
803
+ let generation = 0;
804
+ const notify = () => {
805
+ if (!disposed) opts.onChange?.(config);
806
+ };
807
+ const track = (fn) => {
808
+ bodyDisposers.push(fn);
809
+ };
810
+ const template = () => PLOT_LAYOUTS[config.layout] || PLOT_LAYOUTS["1x1"];
811
+ const slots = () => template().slots;
812
+ function ensureSubplots() {
813
+ if (!Array.isArray(config.subplots)) config.subplots = [];
814
+ while (config.subplots.length < slots()) {
815
+ config.subplots.push(makeDefaultSubplot(config.subplots.length));
816
+ }
817
+ }
818
+ const root = el("div", "twm-plot-config");
819
+ const layoutButtons = /* @__PURE__ */ new Map();
820
+ const picker = el("div", "twm-plot-config__layouts");
821
+ picker.setAttribute("role", "group");
822
+ picker.setAttribute("aria-label", "Layout");
823
+ for (const [key, tmpl] of Object.entries(PLOT_LAYOUTS)) {
824
+ const b = button(tmpl.label, () => setLayout(key), { className: "twm-btn twm-plot-config__layout" });
825
+ layoutButtons.set(key, b);
826
+ picker.appendChild(b);
827
+ }
828
+ const syncLayoutButtons = () => {
829
+ for (const [key, b] of layoutButtons) {
830
+ const on = key === config.layout;
831
+ b.setAttribute("aria-pressed", on ? "true" : "false");
832
+ b.classList.toggle("twm-plot-config__layout--on", on);
833
+ }
834
+ };
835
+ root.appendChild(section("Layout", picker));
836
+ const tabsHost = el("div", "twm-plot-config__tabs");
837
+ const body = el("div", "twm-plot-config__body");
838
+ root.append(tabsHost, body);
839
+ container.appendChild(root);
840
+ ensureSubplots();
841
+ const tabItems = () => Array.from({ length: slots() }, (_, i) => ({ id: i, label: `Subplot ${i + 1}` }));
842
+ const tabs = createTabs(tabsHost, {
843
+ items: tabItems(),
844
+ active: 0,
845
+ compact: true,
846
+ label: "Subplots",
847
+ onSelect: (id) => {
848
+ if (id == null || id === active) return;
849
+ active = id;
850
+ renderBody();
851
+ }
852
+ });
853
+ const syncChrome = () => {
854
+ syncLayoutButtons();
855
+ tabs.update(tabItems(), active);
856
+ tabsHost.hidden = slots() < 2;
857
+ };
858
+ function setLayout(key) {
859
+ if (disposed || key === config.layout) return;
860
+ config.layout = key;
861
+ ensureSubplots();
862
+ active = Math.min(active, slots() - 1);
863
+ syncChrome();
864
+ renderBody();
865
+ notify();
866
+ }
867
+ function section(title, ...children) {
868
+ const s = el("section", "twm-plot-config__section");
869
+ s.appendChild(el("h4", "twm-plot-config__heading", title));
870
+ for (const child of children) if (child) s.appendChild(child);
871
+ return s;
872
+ }
873
+ function disposeBody() {
874
+ generation += 1;
875
+ const disposers = bodyDisposers;
876
+ bodyDisposers = [];
877
+ for (const fn of disposers) {
878
+ try {
879
+ fn();
880
+ } catch (error) {
881
+ opts.logger?.warn?.("[plot config editor] a field failed to dispose", error);
882
+ }
883
+ }
884
+ }
885
+ function seriesPicker(target, key, role, sp, placeholder) {
886
+ let alive = true;
887
+ const ac = new AutocompleteField({
888
+ fieldId: `${editorId}-field-${++fieldCount}`,
889
+ field: { placeholder, value: target[key] ?? "" },
890
+ skipNamespacePrefix: true,
891
+ logger: opts.logger,
892
+ provider: ({ value }) => opts.seriesProvider ? opts.seriesProvider({ value, role, subplot: sp }) : [],
893
+ onChange: (value) => {
894
+ if (!alive || (target[key] ?? "") === value) return;
895
+ target[key] = value;
896
+ notify();
897
+ }
898
+ });
899
+ track(() => {
900
+ alive = false;
901
+ ac.dispose();
902
+ });
903
+ const node = ac.render();
904
+ node.classList.add("twm-plot-config__picker");
905
+ return node;
906
+ }
907
+ function colorField(value, onSet, label) {
908
+ const f = new ColorField({
909
+ value: value ?? "",
910
+ label,
911
+ onChange: (v) => {
912
+ if (v !== null) {
913
+ onSet(v || null);
914
+ notify();
915
+ }
916
+ }
917
+ });
918
+ track(() => f.dispose());
919
+ return f.render();
920
+ }
921
+ function toggle(label, checked, onSet) {
922
+ const t = new ToggleField({ label, checked, compact: true, onChange: (on) => {
923
+ onSet(on);
924
+ notify();
925
+ } });
926
+ track(() => t.dispose());
927
+ return t.render();
928
+ }
929
+ function listEditor(sp, key, { addLabel, makeItem, fields }) {
930
+ if (!Array.isArray(sp[key])) sp[key] = [];
931
+ const items = sp[key];
932
+ const wrap = el("div", "twm-plot-config__list");
933
+ items.forEach((item, i) => {
934
+ const row = el("div", "twm-plot-config__row");
935
+ row.append(...fields(item), removeButton("Remove", () => {
936
+ items.splice(i, 1);
937
+ renderBody();
938
+ notify();
939
+ }));
940
+ wrap.appendChild(row);
941
+ });
942
+ wrap.appendChild(button(addLabel, () => {
943
+ items.push(makeItem());
944
+ renderBody();
945
+ notify();
946
+ }));
947
+ return wrap;
948
+ }
949
+ function normalise(sp) {
950
+ if (!sp.xAxis) sp.xAxis = { useTime: true, variable: "" };
951
+ if (!Array.isArray(sp.yAxes) || !sp.yAxes.length) sp.yAxes = [makeDefaultYAxis(0)];
952
+ for (const axis of sp.yAxes) if (!Array.isArray(axis.series)) axis.series = [];
953
+ if (is3DChart(sp.chartType) && !sp.zAxis) sp.zAxis = makeZAxis();
954
+ }
955
+ function renderBody() {
956
+ if (disposed) return;
957
+ disposeBody();
958
+ body.replaceChildren();
959
+ const sp = config.subplots[active];
960
+ if (!sp) return;
961
+ normalise(sp);
962
+ const is3D = is3DChart(sp.chartType);
963
+ let sources = null;
964
+ const overlaySources = () => sources ??= Promise.resolve().then(() => opts.overlaySources());
965
+ body.append(
966
+ section("Subplot", field("Title", textInput(
967
+ sp.displayName,
968
+ (v) => {
969
+ sp.displayName = v;
970
+ notify();
971
+ },
972
+ { placeholder: "Untitled" }
973
+ ))),
974
+ section("Display", ...displayFields(sp)),
975
+ section("X axis", ...xAxisFields(sp)),
976
+ section("Y axes", yAxesEditor(sp, is3D, overlaySources)),
977
+ is3D ? section("Z axis", ...zAxisFields(sp)) : null,
978
+ section("Reference lines", referenceLinesEditor(sp)),
979
+ section("Shaded bands", shadedBandsEditor(sp)),
980
+ transformsSection(sp)
981
+ );
982
+ }
983
+ function displayFields(sp) {
984
+ return [
985
+ field("Chart type", select(CHART_TYPE_OPTIONS, sp.chartType ?? "line", (v) => {
986
+ const was3D = is3DChart(sp.chartType);
987
+ sp.chartType = v;
988
+ const now3D = is3DChart(v);
989
+ if (now3D) sp.zAxis ??= makeZAxis();
990
+ else sp.zAxis = null;
991
+ if (was3D !== now3D) renderBody();
992
+ notify();
993
+ })),
994
+ field("Legend", select(
995
+ LEGEND_POSITIONS2,
996
+ sp.legendPosition ?? "hidden",
997
+ (v) => {
998
+ sp.legendPosition = v;
999
+ notify();
1000
+ }
1001
+ )),
1002
+ field("Interpolation", select(
1003
+ INTERPOLATIONS,
1004
+ sp.interpolation ?? "linear",
1005
+ (v) => {
1006
+ sp.interpolation = v;
1007
+ notify();
1008
+ }
1009
+ )),
1010
+ field("Missing values", select(
1011
+ NAN_STRATEGIES,
1012
+ sp.nanHandling ?? "gap",
1013
+ (v) => {
1014
+ sp.nanHandling = v;
1015
+ notify();
1016
+ }
1017
+ )),
1018
+ toggle("Data points", sp.showDataPoints === true, (on) => {
1019
+ sp.showDataPoints = on;
1020
+ })
1021
+ ];
1022
+ }
1023
+ function xAxisFields(sp) {
1024
+ const useTime = sp.xAxis.useTime !== false;
1025
+ const out = [field("Against", select(X_SOURCES, useTime ? "time" : "series", (v) => {
1026
+ sp.xAxis.useTime = v === "time";
1027
+ renderBody();
1028
+ notify();
1029
+ }))];
1030
+ if (!useTime) {
1031
+ out.push(
1032
+ field("Series", seriesPicker(sp.xAxis, "variable", "x", sp, "X series")),
1033
+ field("Label", textInput(
1034
+ sp.xAxis.label,
1035
+ (v) => {
1036
+ sp.xAxis.label = v;
1037
+ notify();
1038
+ },
1039
+ { placeholder: sp.xAxis.variable || "Axis title" }
1040
+ ))
1041
+ );
1042
+ }
1043
+ return out;
1044
+ }
1045
+ function zAxisFields(sp) {
1046
+ const z = sp.zAxis;
1047
+ return [
1048
+ field("Series", seriesPicker(z, "variable", "z", sp, "Z series")),
1049
+ field("Label", textInput(z.label, (v) => {
1050
+ z.label = v;
1051
+ notify();
1052
+ }, { placeholder: "Axis title" })),
1053
+ field("Scale", select(SCALES, z.scale ?? "linear", (v) => {
1054
+ z.scale = v;
1055
+ notify();
1056
+ })),
1057
+ field("Min", numberInput(z.min, (v) => {
1058
+ z.min = v;
1059
+ notify();
1060
+ })),
1061
+ field("Max", numberInput(z.max, (v) => {
1062
+ z.max = v;
1063
+ notify();
1064
+ })),
1065
+ field("Step", numberInput(z.step, (v) => {
1066
+ z.step = v;
1067
+ notify();
1068
+ }))
1069
+ ];
1070
+ }
1071
+ function yAxesEditor(sp, is3D, overlaySources) {
1072
+ const wrap = el("div", "twm-plot-config__axes");
1073
+ sp.yAxes.forEach((axis, i) => wrap.appendChild(yAxisEditor(sp, axis, i, is3D, overlaySources)));
1074
+ const add = button("Add Y axis", () => {
1075
+ sp.yAxes.push(makeDefaultYAxis(sp.yAxes.length));
1076
+ renderBody();
1077
+ notify();
1078
+ }, { title: is3D ? "A 3D chart has one Y axis" : null });
1079
+ add.disabled = is3D;
1080
+ wrap.appendChild(add);
1081
+ return wrap;
1082
+ }
1083
+ function yAxisEditor(sp, axis, index, is3D, overlaySources) {
1084
+ const group = el("fieldset", "twm-plot-config__axis");
1085
+ const legend = el("legend", "twm-plot-config__axis-title", `Y axis ${index + 1}`);
1086
+ group.appendChild(legend);
1087
+ if (sp.yAxes.length > 1) {
1088
+ legend.appendChild(removeButton("Remove Y axis", () => {
1089
+ sp.yAxes.splice(index, 1);
1090
+ renderBody();
1091
+ notify();
1092
+ }));
1093
+ }
1094
+ if (is3D && index > 0) {
1095
+ group.disabled = true;
1096
+ group.classList.add("twm-plot-config__axis--disabled");
1097
+ group.title = "A 3D chart uses the first Y axis only";
1098
+ }
1099
+ const fields = el("div", "twm-plot-config__axis-fields");
1100
+ fields.append(
1101
+ field("Label", textInput(axis.label, (v) => {
1102
+ axis.label = v;
1103
+ notify();
1104
+ }, { placeholder: "Axis title" })),
1105
+ field("Scale", select(SCALES, axis.scale ?? "linear", (v) => {
1106
+ axis.scale = v;
1107
+ notify();
1108
+ })),
1109
+ field("Position", select(POSITIONS, axis.position ?? "left", (v) => {
1110
+ axis.position = v;
1111
+ notify();
1112
+ })),
1113
+ field("Min", numberInput(axis.min, (v) => {
1114
+ axis.min = v;
1115
+ notify();
1116
+ })),
1117
+ field("Max", numberInput(axis.max, (v) => {
1118
+ axis.max = v;
1119
+ notify();
1120
+ })),
1121
+ field("Step", numberInput(axis.step, (v) => {
1122
+ axis.step = v;
1123
+ notify();
1124
+ }))
1125
+ );
1126
+ group.appendChild(fields);
1127
+ const list = el("div", "twm-plot-config__series-list");
1128
+ if (!axis.series.length) list.appendChild(el("div", "twm-plot-config__empty", "No series."));
1129
+ for (const s of axis.series) list.appendChild(seriesRow(sp, axis, s, overlaySources));
1130
+ group.appendChild(list);
1131
+ group.appendChild(button("Add series", () => {
1132
+ const count = sp.yAxes.reduce((n, a) => n + (a.series?.length ?? 0), 0);
1133
+ axis.series.push(makeDefaultSeries(count));
1134
+ renderBody();
1135
+ notify();
1136
+ }));
1137
+ return group;
1138
+ }
1139
+ function seriesRow(sp, axis, s, overlaySources) {
1140
+ const row = el("div", "twm-plot-config__series");
1141
+ const main = el("div", "twm-plot-config__series-main");
1142
+ main.append(
1143
+ seriesPicker(s, "variable", "series", sp, "Series"),
1144
+ textInput(s.label, (v) => {
1145
+ s.label = v;
1146
+ notify();
1147
+ }, { placeholder: "Legend label", label: "Legend label" }),
1148
+ colorField(s.color, (v) => {
1149
+ if (v) s.color = v;
1150
+ else delete s.color;
1151
+ }, "Colour"),
1152
+ numberInput(
1153
+ s.lineWidth ?? 2,
1154
+ (v) => {
1155
+ s.lineWidth = v ?? 2;
1156
+ notify();
1157
+ },
1158
+ { placeholder: "2", label: "Line width", min: 1, max: 10, step: 1 }
1159
+ ),
1160
+ select(DASHES, s.lineStyle ?? "solid", (v) => {
1161
+ s.lineStyle = v;
1162
+ notify();
1163
+ }, { label: "Dash" }),
1164
+ textInput(
1165
+ s.stackGroup,
1166
+ (v) => {
1167
+ s.stackGroup = v || null;
1168
+ notify();
1169
+ },
1170
+ { placeholder: "Stack group", label: "Stack group" }
1171
+ ),
1172
+ removeButton("Remove series", () => {
1173
+ const i = axis.series.indexOf(s);
1174
+ if (i >= 0) axis.series.splice(i, 1);
1175
+ renderBody();
1176
+ notify();
1177
+ })
1178
+ );
1179
+ row.appendChild(main);
1180
+ if (opts.extraSeriesFields) {
1181
+ const extra = el("div", "twm-plot-config__series-extra");
1182
+ row.appendChild(extra);
1183
+ const handle = opts.extraSeriesFields(extra, s, { subplot: sp, axis, onChange: notify });
1184
+ if (typeof handle?.dispose === "function") track(() => handle.dispose());
1185
+ }
1186
+ if (opts.overlaySources) row.appendChild(overlayEditor(s, overlaySources));
1187
+ return row;
1188
+ }
1189
+ function overlayEditor(s, overlaySources) {
1190
+ const wrap = el("div", "twm-plot-config__overlay");
1191
+ const head = el("div", "twm-plot-config__overlay-source", "Loading overlay sources\u2026");
1192
+ const details = el("div", "twm-plot-config__overlay-fields");
1193
+ wrap.append(head, details);
1194
+ const drawDetails = () => {
1195
+ details.replaceChildren();
1196
+ const ov = s.overlay;
1197
+ if (!ov) return;
1198
+ details.append(
1199
+ field("Label", textInput(
1200
+ ov.label,
1201
+ (v) => {
1202
+ ov.label = v;
1203
+ notify();
1204
+ },
1205
+ { placeholder: `${s.label || s.variable || "Series"} (actual)` }
1206
+ )),
1207
+ field("Colour", colorField(ov.color, (v) => {
1208
+ if (v) ov.color = v;
1209
+ else delete ov.color;
1210
+ }, "Overlay colour")),
1211
+ field("Dash", select(DASHES, ov.lineStyle ?? "dashed", (v) => {
1212
+ ov.lineStyle = v;
1213
+ notify();
1214
+ })),
1215
+ field("Width", numberInput(
1216
+ ov.lineWidth ?? 2,
1217
+ (v) => {
1218
+ ov.lineWidth = v ?? 2;
1219
+ notify();
1220
+ },
1221
+ { placeholder: "2", min: 1, max: 10, step: 1 }
1222
+ )),
1223
+ field("Mode", select(OVERLAY_MODES, ov.mode ?? "markers+lines", (v) => {
1224
+ ov.mode = v;
1225
+ notify();
1226
+ }))
1227
+ );
1228
+ };
1229
+ const gen = generation;
1230
+ overlaySources().then((list) => {
1231
+ if (disposed || gen !== generation) return;
1232
+ head.replaceChildren(field("Overlay", select(Array.isArray(list) ? list : [], s.overlay?.source, (v) => {
1233
+ if (v === void 0) delete s.overlay;
1234
+ else s.overlay = { ...s.overlay ?? {}, source: v };
1235
+ drawDetails();
1236
+ notify();
1237
+ }, { empty: "None" })));
1238
+ drawDetails();
1239
+ }, () => {
1240
+ if (disposed || gen !== generation) return;
1241
+ head.textContent = "Could not load overlay sources.";
1242
+ head.classList.add("twm-plot-config__overlay-source--error");
1243
+ });
1244
+ return wrap;
1245
+ }
1246
+ function referenceLinesEditor(sp) {
1247
+ const axisOptions = sp.yAxes.map((a, i) => ({ value: a.id, label: a.label || `Y axis ${i + 1}` }));
1248
+ return listEditor(sp, "referenceLines", {
1249
+ addLabel: "Add reference line",
1250
+ makeItem: () => ({ yAxisId: sp.yAxes[0].id, value: 0, label: "", lineStyle: "dashed" }),
1251
+ fields: (line) => [
1252
+ select(
1253
+ axisOptions,
1254
+ line.yAxisId ?? sp.yAxes[0].id,
1255
+ (v) => {
1256
+ line.yAxisId = v;
1257
+ notify();
1258
+ },
1259
+ { label: "Axis" }
1260
+ ),
1261
+ numberInput(line.value, (v) => {
1262
+ line.value = v;
1263
+ notify();
1264
+ }, { placeholder: "Value", label: "Value" }),
1265
+ textInput(line.label, (v) => {
1266
+ line.label = v;
1267
+ notify();
1268
+ }, { placeholder: "Label", label: "Label" }),
1269
+ colorField(line.color, (v) => {
1270
+ if (v) line.color = v;
1271
+ else delete line.color;
1272
+ }, "Colour"),
1273
+ select(DASHES, line.lineStyle ?? "dashed", (v) => {
1274
+ line.lineStyle = v;
1275
+ notify();
1276
+ }, { label: "Dash" })
1277
+ ]
1278
+ });
1279
+ }
1280
+ function shadedBandsEditor(sp) {
1281
+ return listEditor(sp, "shadedBands", {
1282
+ addLabel: "Add shaded band",
1283
+ makeItem: () => ({ x0: null, x1: null, label: "" }),
1284
+ fields: (band) => [
1285
+ numberInput(band.x0, (v) => {
1286
+ band.x0 = v;
1287
+ notify();
1288
+ }, { placeholder: "From", label: "From" }),
1289
+ numberInput(band.x1, (v) => {
1290
+ band.x1 = v;
1291
+ notify();
1292
+ }, { placeholder: "To", label: "To" }),
1293
+ textInput(band.label, (v) => {
1294
+ band.label = v;
1295
+ notify();
1296
+ }, { placeholder: "Label", label: "Label" }),
1297
+ colorField(band.color, (v) => {
1298
+ if (v) band.color = v;
1299
+ else delete band.color;
1300
+ }, "Colour"),
1301
+ numberInput(band.opacity, (v) => {
1302
+ if (v == null) delete band.opacity;
1303
+ else band.opacity = v;
1304
+ notify();
1305
+ }, { placeholder: "Opacity", label: "Opacity", min: 0, max: 1, step: 0.05 })
1306
+ ]
1307
+ });
1308
+ }
1309
+ function transformsSection(sp) {
1310
+ if (!Object.keys(opts.seriesTransforms ?? {}).length) return null;
1311
+ const s = section("Derived series");
1312
+ const controls = renderSeriesTransformControls(s, sp, opts.seriesTransforms, { onChange: notify });
1313
+ track(() => controls.dispose());
1314
+ return s;
1315
+ }
1316
+ syncChrome();
1317
+ renderBody();
1318
+ return {
1319
+ /** Dispose every field it created and remove the editor. Later calls do nothing. */
1320
+ dispose() {
1321
+ if (disposed) return;
1322
+ disposed = true;
1323
+ disposeBody();
1324
+ tabs.destroy();
1325
+ root.remove();
1326
+ },
1327
+ /** Redraw from the config, after it was changed from outside. */
1328
+ refresh() {
1329
+ if (disposed) return;
1330
+ ensureSubplots();
1331
+ active = Math.min(active, slots() - 1);
1332
+ syncChrome();
1333
+ renderBody();
1334
+ },
1335
+ /** Bring subplot `index` to the front. */
1336
+ selectSubplot(index) {
1337
+ if (disposed || !Number.isInteger(index) || index < 0 || index >= slots()) return;
1338
+ tabs.select(index);
1339
+ },
1340
+ /** The index of the subplot in front. */
1341
+ get activeSubplot() {
1342
+ return active;
1343
+ }
1344
+ };
1345
+ }
666
1346
  export {
667
1347
  ALL_CHART_TYPES,
668
1348
  CHART_TYPES_2D,
669
1349
  CHART_TYPES_3D,
670
1350
  COLOR_PALETTE,
671
1351
  DARK_THEME_LAYOUT,
1352
+ DEFAULT_CHART_EXPORT_FILENAME,
672
1353
  DEFAULT_CONFIG,
1354
+ PLOT_GRID_THEME,
673
1355
  PLOT_LAYOUTS,
674
1356
  PlotPopoutWindow,
675
1357
  applyNanHandling,
@@ -687,14 +1369,18 @@ export {
687
1369
  closeDataSeriesPlotWindow,
688
1370
  createChart,
689
1371
  createChartDeferred,
1372
+ deriveSeriesTraces,
690
1373
  ensurePlotly,
691
1374
  extendTraces,
1375
+ getChartExportFilename,
692
1376
  getSeriesColor,
693
1377
  hasPlot,
694
1378
  hexToRgba,
695
1379
  is3DChart,
696
1380
  lttb,
1381
+ makeDefaultSeries,
697
1382
  makeDefaultSubplot,
1383
+ makeDefaultYAxis,
698
1384
  migrateData,
699
1385
  migrateSingleChart,
700
1386
  openDataSeriesPlotWindow,
@@ -702,8 +1388,13 @@ export {
702
1388
  openRawTracesWindow,
703
1389
  purge,
704
1390
  relayout,
1391
+ renderPlotConfigEditor,
1392
+ renderPlotGrid,
1393
+ renderSeriesTransformControls,
705
1394
  resize,
1395
+ resolveSeriesData,
706
1396
  restyle,
1397
+ setChartExportFilename,
707
1398
  setPlotlySource,
708
1399
  strideDownsampleShared,
709
1400
  strideIndices,