flexdesk 0.16.1 → 0.17.1

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 (262) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/LICENSE +0 -0
  3. package/README.md +2435 -42
  4. package/THIRD_PARTY_LICENSES.md +0 -0
  5. package/canvas.js +18 -0
  6. package/charts.js +21 -0
  7. package/core.js +14 -0
  8. package/css/base.css +1834 -75
  9. package/css/flexdesk.css +3165 -159
  10. package/css/overrides.css +16 -0
  11. package/css/reset.css +0 -0
  12. package/css/tokens.css +0 -0
  13. package/dist/canvas.js +6 -239
  14. package/dist/canvas.js.map +3 -3
  15. package/dist/charts.js +707 -18
  16. package/dist/charts.js.map +4 -4
  17. package/dist/chunk-3PHCPZHT.js +0 -0
  18. package/dist/chunk-3PHCPZHT.js.map +0 -0
  19. package/dist/chunk-4A7SJC3T.js +503 -0
  20. package/dist/chunk-4A7SJC3T.js.map +7 -0
  21. package/dist/{chunk-6JSOVNID.js → chunk-CVMCZZX2.js} +635 -10
  22. package/dist/chunk-CVMCZZX2.js.map +7 -0
  23. package/dist/chunk-D3COLZZ2.js +1311 -0
  24. package/dist/chunk-D3COLZZ2.js.map +7 -0
  25. package/dist/chunk-FOOS3T5L.js +0 -0
  26. package/dist/chunk-FOOS3T5L.js.map +0 -0
  27. package/dist/{chunk-SN4LSOAR.js → chunk-GOQLR5WR.js} +452 -2
  28. package/dist/chunk-GOQLR5WR.js.map +7 -0
  29. package/dist/{chunk-FL5KFNQH.js → chunk-HNU6MM2B.js} +52 -17
  30. package/dist/chunk-HNU6MM2B.js.map +7 -0
  31. package/dist/chunk-HYJYYEPK.js +375 -0
  32. package/dist/chunk-HYJYYEPK.js.map +7 -0
  33. package/dist/chunk-JKZLNKG6.js +2041 -0
  34. package/dist/chunk-JKZLNKG6.js.map +7 -0
  35. package/dist/chunk-JYWURG5T.js +0 -0
  36. package/dist/chunk-JYWURG5T.js.map +0 -0
  37. package/dist/{chunk-FBGP5KN5.js → chunk-LWV23YVE.js} +5073 -532
  38. package/dist/chunk-LWV23YVE.js.map +7 -0
  39. package/dist/chunk-N7HC3ADV.js +30 -0
  40. package/dist/chunk-N7HC3ADV.js.map +7 -0
  41. package/dist/{chunk-RQM5Z2MU.js → chunk-RMTRWWEO.js} +633 -311
  42. package/dist/chunk-RMTRWWEO.js.map +7 -0
  43. package/dist/{chunk-NLTOY57M.js → chunk-UURZ37SD.js} +28 -48
  44. package/dist/chunk-UURZ37SD.js.map +7 -0
  45. package/dist/{chunk-B5Q53LAJ.js → chunk-UWO43BZU.js} +2 -2
  46. package/dist/chunk-UWO43BZU.js.map +7 -0
  47. package/dist/chunk-WVFGV5FT.js +0 -0
  48. package/dist/chunk-WVFGV5FT.js.map +0 -0
  49. package/dist/core.js +170 -118
  50. package/dist/core.js.map +2 -2
  51. package/dist/editor.js +156 -70
  52. package/dist/editor.js.map +2 -2
  53. package/dist/flexdesk.css +3165 -159
  54. package/dist/flow.js +2789 -306
  55. package/dist/flow.js.map +4 -4
  56. package/dist/host.js +109 -3
  57. package/dist/host.js.map +3 -3
  58. package/dist/reset.css +0 -0
  59. package/dist/tile_registry-6WZPTUZV.js +0 -0
  60. package/dist/tile_registry-6WZPTUZV.js.map +0 -0
  61. package/dist/tiles.js +657 -234
  62. package/dist/tiles.js.map +4 -4
  63. package/dist/tokens.css +0 -0
  64. package/dist/widgets.js +3290 -2124
  65. package/dist/widgets.js.map +4 -4
  66. package/dist/wm.js +1227 -347
  67. package/dist/wm.js.map +4 -4
  68. package/editor.js +20 -0
  69. package/flow.js +21 -0
  70. package/host.js +14 -0
  71. package/package.json +12 -1
  72. package/src/canvas/canvas_adapter.js +5 -0
  73. package/src/canvas/edge_router.js +0 -0
  74. package/src/canvas/graph/connector_router.js +0 -0
  75. package/src/canvas/graph/history_service.js +0 -0
  76. package/src/canvas/graph/node_platform.js +0 -0
  77. package/src/canvas/index.js +1 -0
  78. package/src/canvas/layered_layout.js +244 -0
  79. package/src/charts/chart_types.js +0 -0
  80. package/src/charts/data_series_plot_window.js +0 -0
  81. package/src/charts/downsample.js +2 -2
  82. package/src/charts/plot_config.js +53 -29
  83. package/src/charts/plot_config_editor.js +627 -0
  84. package/src/charts/plot_grid.js +449 -0
  85. package/src/charts/plot_popout_window.js +103 -317
  86. package/src/charts/plotly_wrapper.js +22 -2
  87. package/src/charts/series_transforms.js +206 -0
  88. package/src/core/event_bus.js +0 -0
  89. package/src/core/logging.js +1 -1
  90. package/src/core/settings.js +105 -19
  91. package/src/core/state_guard.js +93 -51
  92. package/src/core/state_guard_config.js +85 -74
  93. package/src/core/state_machine.js +0 -0
  94. package/src/editor/editor_pane.js +3 -3
  95. package/src/editor/monaco_editor_factory.js +124 -60
  96. package/src/editor/monaco_loader.js +103 -28
  97. package/src/editor/notebook_search.js +0 -0
  98. package/src/editor/notebook_tab_bar.js +38 -52
  99. package/src/editor/notebook_undo_manager.js +0 -0
  100. package/src/editor/split_pane_container.js +6 -3
  101. package/src/flow/canvas_editor/connections.js +90 -0
  102. package/src/flow/canvas_editor/editor.js +2351 -153
  103. package/src/flow/canvas_editor/index.js +2 -1
  104. package/src/flow/canvas_editor/layout.js +119 -12
  105. package/src/flow/canvas_editor/strings.js +80 -0
  106. package/src/flow/kit/always_before.js +0 -0
  107. package/src/flow/kit/catalogue.js +97 -3
  108. package/src/flow/kit/chip_input.js +0 -0
  109. package/src/flow/kit/dom.js +0 -0
  110. package/src/flow/kit/findings.js +0 -0
  111. package/src/flow/kit/history.js +0 -0
  112. package/src/flow/kit/index.js +3 -3
  113. package/src/flow/kit/keys.js +7 -1
  114. package/src/flow/kit/logic_graph.js +97 -15
  115. package/src/flow/kit/references.js +0 -0
  116. package/src/flow/kit/settings_panel.js +0 -0
  117. package/src/flow/kit/settings_schema.js +2 -1
  118. package/src/flow/kit/step_picker.js +0 -0
  119. package/src/flow/kit/strings.js +3 -1
  120. package/src/flow/kit/value_picker.js +0 -0
  121. package/src/flow/kit/widgets.js +86 -109
  122. package/src/flow/lanes/editor.js +136 -14
  123. package/src/flow/lanes/index.js +3 -1
  124. package/src/flow/lanes/layout.js +0 -0
  125. package/src/flow/lanes/pipeline.js +44 -2
  126. package/src/flow/lanes/preview.js +0 -0
  127. package/src/flow/lanes/strings.js +1 -0
  128. package/src/flow/outline/editor.js +0 -0
  129. package/src/flow/outline/index.js +0 -0
  130. package/src/flow/outline/mapping.js +0 -0
  131. package/src/flow/outline/menu.js +0 -0
  132. package/src/flow/outline/operations.js +0 -0
  133. package/src/flow/outline/recognise.js +0 -0
  134. package/src/flow/outline/strings.js +0 -0
  135. package/src/help/help_modal.js +168 -42
  136. package/src/help/help_registry.js +12 -1
  137. package/src/host/external_links.js +0 -0
  138. package/src/host/file_picker.js +117 -0
  139. package/src/host/host.js +10 -2
  140. package/src/host/photino_host.js +9 -0
  141. package/src/host/photino_host.md +21 -3
  142. package/src/host/pywebview_host.js +10 -0
  143. package/src/tiles/chart_tiles.js +53 -245
  144. package/src/tiles/config_schema.js +8 -0
  145. package/src/tiles/figure_tile.js +322 -0
  146. package/src/tiles/layout_persistence.js +3 -3
  147. package/src/tiles/list_tiles.js +7 -7
  148. package/src/tiles/scope_picker.js +0 -0
  149. package/src/tiles/tile_base.js +1 -1
  150. package/src/tiles/tile_config_form.js +442 -0
  151. package/src/tiles/tile_grid.js +2 -0
  152. package/src/tiles/tile_registry.js +0 -0
  153. package/src/tiling/bottom_panel_tabs.js +67 -99
  154. package/src/tiling/command_palette.js +64 -19
  155. package/src/tiling/content_registry.js +4 -1
  156. package/src/tiling/desktops.js +11 -1
  157. package/src/tiling/entity_sources.js +0 -0
  158. package/src/tiling/keymap.js +403 -128
  159. package/src/tiling/kind_taxonomy.js +78 -0
  160. package/src/tiling/landing_table.js +0 -0
  161. package/src/tiling/loading_overlay.js +0 -0
  162. package/src/tiling/nav_panel.js +59 -10
  163. package/src/tiling/page_factory.js +5 -5
  164. package/src/tiling/panel_availability.js +49 -0
  165. package/src/tiling/panel_keys.js +0 -0
  166. package/src/tiling/shell.js +114 -12
  167. package/src/tiling/tab_strip.js +0 -0
  168. package/src/tiling/tile_breadcrumb.js +0 -0
  169. package/src/tiling/tile_renderer.js +105 -9
  170. package/src/tiling/tile_tab_menu.js +0 -0
  171. package/src/tiling/tile_tree.js +0 -0
  172. package/src/tiling/wm.js +562 -30
  173. package/src/tiling/zoom.js +0 -0
  174. package/src/ui/base/component_base.js +0 -0
  175. package/src/ui/base/controller_base.js +0 -0
  176. package/src/ui/base/page_base.js +0 -0
  177. package/src/ui/components/about_dialog.js +0 -0
  178. package/src/ui/components/action_dropdown.js +30 -2
  179. package/src/ui/components/alert_log.js +0 -0
  180. package/src/ui/components/alerts_pane.js +0 -0
  181. package/src/ui/components/attribute_list_editor.js +0 -0
  182. package/src/ui/components/autocomplete_field.js +0 -0
  183. package/src/ui/components/cell_editor_host.js +293 -0
  184. package/src/ui/components/cell_editors.js +480 -0
  185. package/src/ui/components/cell_grid.js +761 -0
  186. package/src/ui/components/cell_paste.js +243 -0
  187. package/src/ui/components/cell_selection.js +220 -0
  188. package/src/ui/components/choice_controls.js +351 -0
  189. package/src/ui/components/color_field.js +211 -0
  190. package/src/ui/components/color_list_field.js +210 -0
  191. package/src/ui/components/column_fit.js +0 -0
  192. package/src/ui/components/column_groups.js +314 -0
  193. package/src/ui/components/column_order.js +142 -0
  194. package/src/ui/components/computing_status_window.js +98 -25
  195. package/src/ui/components/confirm_dialog.js +81 -38
  196. package/src/ui/components/context_menu.js +2 -2
  197. package/src/ui/components/conversation_view.js +440 -0
  198. package/src/ui/components/crosstab.js +289 -0
  199. package/src/ui/components/data_table.js +696 -71
  200. package/src/ui/components/detail_header.js +22 -11
  201. package/src/ui/components/document_outline.js +397 -0
  202. package/src/ui/components/drag_reorder.js +0 -0
  203. package/src/ui/components/fact_table.js +0 -0
  204. package/src/ui/components/fill_handle.js +322 -0
  205. package/src/ui/components/gallery_picker.js +19 -8
  206. package/src/ui/components/header_actions.js +55 -0
  207. package/src/ui/components/inline_renamer.js +0 -0
  208. package/src/ui/components/job_progress.js +51 -0
  209. package/src/ui/components/managed_window.js +27 -4
  210. package/src/ui/components/menu_bar.js +745 -0
  211. package/src/ui/components/modal.js +449 -75
  212. package/src/ui/components/notification_history.js +3 -3
  213. package/src/ui/components/progress.js +483 -0
  214. package/src/ui/components/record_list.js +0 -0
  215. package/src/ui/components/row_activation.js +0 -0
  216. package/src/ui/components/section_stack.js +298 -0
  217. package/src/ui/components/series.js +173 -0
  218. package/src/ui/components/settings_view.js +634 -0
  219. package/src/ui/components/slide_out_panel.js +43 -6
  220. package/src/ui/components/slider_field.js +275 -18
  221. package/src/ui/components/sortable_list.js +0 -0
  222. package/src/ui/components/staged_edits.js +361 -0
  223. package/src/ui/components/staged_history.js +172 -0
  224. package/src/ui/components/table_filter_chips.js +0 -0
  225. package/src/ui/components/table_state_store.js +0 -0
  226. package/src/ui/components/table_structure.js +182 -0
  227. package/src/ui/components/table_values.js +0 -0
  228. package/src/ui/components/tabs.js +497 -0
  229. package/src/ui/components/tag_input.js +215 -0
  230. package/src/ui/components/toast.js +25 -8
  231. package/src/ui/components/toggle_field.js +175 -0
  232. package/src/ui/components/tree_view.js +55 -34
  233. package/src/ui/components/tsv.js +74 -0
  234. package/src/ui/components/window_taskbar.js +0 -0
  235. package/src/ui/components/wizard.js +563 -0
  236. package/src/ui/controllers/window_chrome_controller.js +2 -2
  237. package/src/ui/notification_center.js +375 -119
  238. package/src/ui/utils/auto_scrollbars.js +0 -0
  239. package/src/ui/utils/markdown.js +709 -0
  240. package/src/ui/utils/native_context_menu.js +0 -0
  241. package/src/ui/utils/overlay_scrollbar.js +16 -12
  242. package/src/ui/utils/raf_resize_observer.js +0 -0
  243. package/src/ui/utils/scroll_lock.js +0 -0
  244. package/src/ui/utils/strip_scroll.js +55 -0
  245. package/src/ui/utils/tooltip_service.js +0 -8
  246. package/tiles.js +27 -0
  247. package/widgets.js +69 -0
  248. package/wm.js +28 -0
  249. package/dist/chunk-6JSOVNID.js.map +0 -7
  250. package/dist/chunk-B5Q53LAJ.js.map +0 -7
  251. package/dist/chunk-FBGP5KN5.js.map +0 -7
  252. package/dist/chunk-FL5KFNQH.js.map +0 -7
  253. package/dist/chunk-NLTOY57M.js.map +0 -7
  254. package/dist/chunk-NNFTETK3.js +0 -633
  255. package/dist/chunk-NNFTETK3.js.map +0 -7
  256. package/dist/chunk-O5OHMWBB.js +0 -284
  257. package/dist/chunk-O5OHMWBB.js.map +0 -7
  258. package/dist/chunk-RQM5Z2MU.js.map +0 -7
  259. package/dist/chunk-SN4LSOAR.js.map +0 -7
  260. package/dist/chunk-W6FBJLPW.js +0 -367
  261. package/dist/chunk-W6FBJLPW.js.map +0 -7
  262. package/src/ui/controllers/panel_state_machine.js +0 -634
package/editor.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @flexdesk/editor — A Monaco-backed multi-file editor: the loader, the editor factory, the
3
+ * tab bar, search, a split-pane container and an undo manager.
4
+ *
5
+ * Monaco is a PEER dependency — 14 MB this bundle deliberately does NOT
6
+ * contain, and whose location it refuses to guess. Call setMonacoBasePath()
7
+ * and setDefaultLanguage().
8
+ *
9
+ * GENERATED by scripts/gen_barrels.py. Do not hand-edit.
10
+ *
11
+ * 7 modules.
12
+ */
13
+
14
+ export * from './src/editor/editor_pane.js';
15
+ export * from './src/editor/monaco_editor_factory.js';
16
+ export * from './src/editor/monaco_loader.js';
17
+ export * from './src/editor/notebook_search.js';
18
+ export * from './src/editor/notebook_tab_bar.js';
19
+ export * from './src/editor/notebook_undo_manager.js';
20
+ export * from './src/editor/split_pane_container.js';
package/flow.js ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @flexdesk/flow — flow editors: the shared kit (a step-type catalogue, a
3
+ * settings panel generated from a JSON-Schema subset, a widget registry,
4
+ * reference chips, the step and value pickers, findings, undo over whole
5
+ * snapshots, keys) and the three editors built on it — the canvas, the outline
6
+ * and the lanes.
7
+ *
8
+ * Every editor is domain-neutral: it never fetches, never saves and never
9
+ * names a consumer's vocabulary. The step types, the reference grammar, every
10
+ * finding and every byte of I/O are the consumer's, passed in.
11
+ *
12
+ * HAND-WRITTEN, not generated. The other barrels say "GENERATED by
13
+ * scripts/gen_barrels.py", and that script is not in this repository, so this
14
+ * one is kept by hand. Each line below is one owner's module (36 §2): the kit
15
+ * is laid down first, and each editor fills its own index.js.
16
+ */
17
+
18
+ export * from './src/flow/kit/index.js';
19
+ export * from './src/flow/canvas_editor/index.js';
20
+ export * from './src/flow/outline/index.js';
21
+ export * from './src/flow/lanes/index.js';
package/host.js ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @flexdesk/host — The host port. The library never touches pywebview, fetch or localStorage
3
+ * directly — it asks a Host, and the application supplies one.
4
+ *
5
+ * GENERATED by scripts/gen_barrels.py. Do not hand-edit.
6
+ *
7
+ * 5 modules.
8
+ */
9
+
10
+ export * from './src/host/host.js';
11
+ export * from './src/host/pywebview_host.js';
12
+ export * from './src/host/photino_host.js';
13
+ export * from './src/host/external_links.js';
14
+ export * from './src/host/file_picker.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flexdesk",
3
- "version": "0.16.1",
3
+ "version": "0.17.1",
4
4
  "description": "A tiling window manager and widget set for desktop-class web applications. Zero runtime dependencies.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -18,8 +18,18 @@
18
18
  "files": [
19
19
  "dist",
20
20
  "src",
21
+ "core.js",
22
+ "host.js",
23
+ "wm.js",
24
+ "widgets.js",
25
+ "charts.js",
26
+ "tiles.js",
27
+ "editor.js",
28
+ "canvas.js",
29
+ "flow.js",
21
30
  "css",
22
31
  "README.md",
32
+ "CHANGELOG.md",
23
33
  "LICENSE",
24
34
  "THIRD_PARTY_LICENSES.md"
25
35
  ],
@@ -42,6 +52,7 @@
42
52
  },
43
53
  "./css": "./dist/flexdesk.css",
44
54
  "./css/tokens": "./css/tokens.css",
55
+ "./css/reset": "./dist/reset.css",
45
56
  "./charts": {
46
57
  "import": "./dist/charts.js",
47
58
  "development": "./charts.js"
@@ -43,6 +43,11 @@ const COARSE_GRID_EVERY = 5;
43
43
  const SUB_PIXEL = 0.5;
44
44
 
45
45
  export class CanvasAdapter {
46
+ /** The zoom limits every `setViewport`, `zoomAt` and `fit` clamps to, for an
47
+ * embedder whose own zoom controls must stop where the kernel stops. */
48
+ static MIN_SCALE = MIN_SCALE;
49
+ static MAX_SCALE = MAX_SCALE;
50
+
46
51
  /**
47
52
  * @param {object} o
48
53
  * @param {HTMLElement} o.container the box the canvas fills (it needs a height)
File without changes
File without changes
File without changes
File without changes
@@ -11,6 +11,7 @@ export { CanvasAdapter } from './canvas_adapter.js';
11
11
  export {
12
12
  OBSTACLE_ROUTING_NODE_LIMIT, routeEdge, footAnchor, crowsFoot, cardinality, selfLoop, returnEdge, arrange,
13
13
  } from './edge_router.js';
14
+ export { layeredLayout } from './layered_layout.js';
14
15
  export { NodePlatform } from './graph/node_platform.js';
15
16
  export { ConnectorRouter, CONNECTOR_STRINGS } from './graph/connector_router.js';
16
17
  export { HistoryService } from './graph/history_service.js';
@@ -0,0 +1,244 @@
1
+ /**
2
+ * A LAYERED LAYOUT for any directed graph — PURE: no DOM, runs under plain node.
3
+ *
4
+ * layeredLayout(nodes, edges, {rank, sizeOf, columnGap, rowGap, componentGap})
5
+ * → Map of node id → {x, y}, the top-left corner of each node's box, from (0, 0)
6
+ *
7
+ * The kernel's `arrange` stays what Tables' diagrams were built on (an edge
8
+ * from child to parent, columns sorted by label). This one reads an edge the
9
+ * way a diagram is read — `{from, to}` puts `from` LEFT of `to` — and, in
10
+ * order:
11
+ *
12
+ * 1. **separate components.** Nodes no line joins are laid out on their own
13
+ * and stacked top to bottom, the largest first, `componentGap` apart, so
14
+ * two of them never overlap.
15
+ * 2. **cycles broken.** A depth-first walk in the order the nodes are given
16
+ * turns each line back to a node still on the walk round; nothing hangs.
17
+ * 3. **longest-path columns.** A node sits one column right of the furthest
18
+ * of the nodes that lead to it.
19
+ * 4. **rank hints.** `rank(node)` may answer `'source'` (the first column) or
20
+ * `'sink'` (the last column of its component); a line INTO a source or OUT
21
+ * of a sink then pulls nothing. Anything else is no hint.
22
+ * 5. **crossing reduction.** Barycentre sweeps, down the columns and back,
23
+ * each node to the mean place of its neighbours in the column it is swept
24
+ * from (a node with none keeps its place, the sort is stable). The order
25
+ * with the FEWEST crossings between neighbouring columns is kept — the
26
+ * order the nodes were given included, so a sweep never makes it worse.
27
+ * 6. **measured sizes.** `sizeOf(node)` → `{width, height}` (default: the
28
+ * node's own `width`/`height`, else 160 × 80). A column is as wide as its
29
+ * widest node, `columnGap` before the next; a column's nodes are
30
+ * `rowGap` apart and the column is centred on its component's height.
31
+ *
32
+ * Unknown ends, a node's line to itself and a repeated line are ignored. A
33
+ * second node with an id already seen is left out.
34
+ */
35
+
36
+ const DEFAULT_SIZE = Object.freeze({ width: 160, height: 80 });
37
+
38
+ const defaultSizeOf = (node) => ({ width: Number(node?.width) || DEFAULT_SIZE.width,
39
+ height: Number(node?.height) || DEFAULT_SIZE.height });
40
+
41
+ /** Crossings between two neighbouring columns: the pairs of lines whose ends come in opposite orders. */
42
+ function crossingsBetween(pairs) {
43
+ let n = 0;
44
+ for (let i = 0; i < pairs.length; i++) {
45
+ const [a1, b1] = pairs[i];
46
+ for (let j = i + 1; j < pairs.length; j++) {
47
+ const [a2, b2] = pairs[j];
48
+ if ((a1 - a2) * (b1 - b2) < 0) n++;
49
+ }
50
+ }
51
+ return n;
52
+ }
53
+
54
+ /**
55
+ * @param {{id: string}[]} nodes
56
+ * @param {{from: string, to: string}[]} edges `from` is laid out left of `to`
57
+ * @param {object} [options]
58
+ * @param {(node: object) => ('source'|'sink'|null|undefined)} [options.rank]
59
+ * @param {(node: object) => {width: number, height: number}} [options.sizeOf]
60
+ * @param {number} [options.columnGap=64] between the widest node of a column and the next column
61
+ * @param {number} [options.rowGap=32] between two nodes of a column
62
+ * @param {number} [options.componentGap=80] between two components, stacked top to bottom
63
+ * @param {number} [options.passes=8] barycentre sweeps (each one down and back)
64
+ * @returns {Map<string, {x: number, y: number}>}
65
+ */
66
+ export function layeredLayout(nodes, edges, { rank = null, sizeOf = null, columnGap = 64, rowGap = 32,
67
+ componentGap = 80, passes = 8 } = {}) {
68
+ const list = [];
69
+ const index = new Map();
70
+ for (const node of Array.isArray(nodes) ? nodes : []) {
71
+ if (!node || node.id === undefined || node.id === null || index.has(String(node.id))) continue;
72
+ index.set(String(node.id), list.length);
73
+ list.push(node);
74
+ }
75
+ const count = list.length;
76
+ const positions = new Map();
77
+ if (!count) return positions;
78
+
79
+ const hint = list.map((n) => {
80
+ const r = typeof rank === 'function' ? rank(n) : null;
81
+ return r === 'source' || r === 'sink' ? r : null;
82
+ });
83
+ const measure = typeof sizeOf === 'function' ? sizeOf : defaultSizeOf;
84
+ const size = list.map((n) => {
85
+ const s = measure(n) || {};
86
+ return { width: Math.max(0, Number(s.width) || 0), height: Math.max(0, Number(s.height) || 0) };
87
+ });
88
+
89
+ // ── the lines, deduplicated, both ways ─────────────────────────────
90
+ const out = Array.from({ length: count }, () => []);
91
+ const near = Array.from({ length: count }, () => []);
92
+ const seen = new Set();
93
+ const lines = [];
94
+ for (const e of Array.isArray(edges) ? edges : []) {
95
+ const u = index.get(String(e?.from));
96
+ const v = index.get(String(e?.to));
97
+ if (u === undefined || v === undefined || u === v) continue;
98
+ const key = `${u}>${v}`;
99
+ if (seen.has(key)) continue;
100
+ seen.add(key);
101
+ out[u].push(v);
102
+ lines.push([u, v]);
103
+ if (!seen.has(`${u}~${v}`)) {
104
+ seen.add(`${u}~${v}`); seen.add(`${v}~${u}`);
105
+ near[u].push(v); near[v].push(u);
106
+ }
107
+ }
108
+
109
+ // ── 1. components, the largest first, then in the order given ─────
110
+ const componentOf = new Int32Array(count).fill(-1);
111
+ const components = [];
112
+ for (let s = 0; s < count; s++) {
113
+ if (componentOf[s] >= 0) continue;
114
+ const members = [];
115
+ const stack = [s];
116
+ componentOf[s] = components.length;
117
+ while (stack.length) {
118
+ const u = stack.pop();
119
+ members.push(u);
120
+ for (const v of near[u]) if (componentOf[v] < 0) { componentOf[v] = components.length; stack.push(v); }
121
+ }
122
+ members.sort((a, b) => a - b);
123
+ components.push(members);
124
+ }
125
+ const ordered = components.map((m, i) => ({ m, i })).sort((a, b) => (b.m.length - a.m.length) || (a.i - b.i));
126
+ const linesOf = components.map(() => []);
127
+ for (const line of lines) linesOf[componentOf[line[0]]].push(line);
128
+
129
+ // ── 2. cycles broken: a line back onto the walk is turned round ────
130
+ const reversed = new Set();
131
+ const state = new Uint8Array(count); // 0 new, 1 on the walk, 2 done
132
+ for (let s = 0; s < count; s++) {
133
+ if (state[s]) continue;
134
+ const stack = [[s, 0]];
135
+ state[s] = 1;
136
+ while (stack.length) {
137
+ const top = stack[stack.length - 1];
138
+ const [u, k] = top;
139
+ if (k < out[u].length) {
140
+ top[1] = k + 1;
141
+ const v = out[u][k];
142
+ if (state[v] === 1) reversed.add(`${u}>${v}`);
143
+ else if (state[v] === 0) { state[v] = 1; stack.push([v, 0]); }
144
+ } else {
145
+ state[u] = 2;
146
+ stack.pop();
147
+ }
148
+ }
149
+ }
150
+
151
+ // The lines that decide columns: acyclic, none into a source or out of a sink.
152
+ const preds = Array.from({ length: count }, () => []);
153
+ const succs = Array.from({ length: count }, () => []);
154
+ for (const [u0, v0] of lines) {
155
+ const [u, v] = reversed.has(`${u0}>${v0}`) ? [v0, u0] : [u0, v0];
156
+ if (hint[v] === 'source' || hint[u] === 'sink') continue;
157
+ preds[v].push(u);
158
+ succs[u].push(v);
159
+ }
160
+
161
+ // ── 3. longest path, in topological order ──────────────────────────
162
+ const layer = new Int32Array(count);
163
+ const waiting = preds.map((p) => p.length);
164
+ const queue = [];
165
+ for (let u = 0; u < count; u++) if (!waiting[u]) queue.push(u);
166
+ for (let q = 0; q < queue.length; q++) {
167
+ const u = queue[q];
168
+ for (const v of succs[u]) {
169
+ if (layer[u] + 1 > layer[v]) layer[v] = layer[u] + 1;
170
+ if (--waiting[v] === 0) queue.push(v);
171
+ }
172
+ }
173
+
174
+ let top = 0;
175
+ for (const { m, i: c } of ordered) {
176
+ // ── 4. rank hints: sources first, sinks last, no empty column ─
177
+ let last = -1;
178
+ for (const u of m) if (hint[u] !== 'sink') last = Math.max(last, hint[u] === 'source' ? 0 : layer[u]);
179
+ for (const u of m) {
180
+ if (hint[u] === 'source') layer[u] = 0;
181
+ else if (hint[u] === 'sink') layer[u] = last + 1;
182
+ }
183
+ const used = [...new Set(m.map((u) => layer[u]))].sort((a, b) => a - b);
184
+ const renumber = new Map(used.map((l, i) => [l, i]));
185
+ const columns = used.map(() => []);
186
+ for (const u of m) { layer[u] = renumber.get(layer[u]); columns[layer[u]].push(u); }
187
+
188
+ // ── 5. crossing reduction ──────────────────────────────────────
189
+ const place = new Map();
190
+ const remember = () => { for (const c of columns) c.forEach((u, i) => place.set(u, i)); };
191
+ remember();
192
+ const between = columns.map(() => []);
193
+ for (const [u, v] of linesOf[c]) {
194
+ const a = Math.min(layer[u], layer[v]);
195
+ if (Math.abs(layer[u] - layer[v]) === 1) between[a].push(layer[u] < layer[v] ? [u, v] : [v, u]);
196
+ }
197
+ const crossings = () => {
198
+ let n = 0;
199
+ for (let l = 0; l + 1 < columns.length; l++) {
200
+ n += crossingsBetween(between[l].map(([u, v]) => [place.get(u), place.get(v)]));
201
+ }
202
+ return n;
203
+ };
204
+ const sweep = (l, from) => {
205
+ const column = columns[l];
206
+ const centre = new Map();
207
+ for (const u of column) {
208
+ let sum = 0;
209
+ let k = 0;
210
+ for (const v of near[u]) {
211
+ if (from(layer[v])) { sum += place.get(v); k++; }
212
+ }
213
+ centre.set(u, k ? sum / k : place.get(u));
214
+ }
215
+ column.sort((a, b) => (centre.get(a) - centre.get(b)) || (place.get(a) - place.get(b)));
216
+ column.forEach((u, i) => place.set(u, i));
217
+ };
218
+ let best = crossings();
219
+ let bestColumns = columns.map((c) => [...c]);
220
+ for (let p = 0; p < passes && best > 0; p++) {
221
+ for (let l = 1; l < columns.length; l++) sweep(l, (x) => x < l);
222
+ for (let l = columns.length - 2; l >= 0; l--) sweep(l, (x) => x > l);
223
+ const now = crossings();
224
+ if (now < best) { best = now; bestColumns = columns.map((c) => [...c]); }
225
+ }
226
+
227
+ // ── 6. measured sizes ─────────────────────────────────────────
228
+ const widths = bestColumns.map((c) => Math.max(0, ...c.map((u) => size[u].width)));
229
+ const heights = bestColumns.map((c) => c.reduce((h, u) => h + size[u].height, 0)
230
+ + rowGap * Math.max(0, c.length - 1));
231
+ const tallest = Math.max(0, ...heights);
232
+ let x = 0;
233
+ bestColumns.forEach((c, l) => {
234
+ let y = top + (tallest - heights[l]) / 2;
235
+ for (const u of c) {
236
+ positions.set(String(list[u].id), { x, y });
237
+ y += size[u].height + rowGap;
238
+ }
239
+ x += widths[l] + columnGap;
240
+ });
241
+ top += tallest + componentGap;
242
+ }
243
+ return positions;
244
+ }
File without changes
File without changes
@@ -7,8 +7,8 @@
7
7
  * These helpers cap the points actually drawn while preserving the visual
8
8
  * shape.
9
9
  *
10
- * Two strategies, mirroring EcoSim (App/downsampler.py = LTTB,
11
- * streaming/server.py = stride decimation):
10
+ * Two strategies, the usual pair on the server side as well (LTTB for one
11
+ * line, stride decimation for a stream):
12
12
  *
13
13
  * • strideDownsampleShared — one set of indices applied to the shared x
14
14
  * axis and EVERY series. Cheap, gap-safe (nulls keep their slot), and
@@ -78,6 +78,40 @@ const PLOT_LAYOUTS = Object.freeze({
78
78
  'wide-bottom': { label: '2 + 1', slots: 3, grid: { columns: '1fr 1fr', rows: '1fr 1fr' }, areas: [{ slot: 0, gridArea: '1/1/2/2' }, { slot: 1, gridArea: '1/2/2/3' }, { slot: 2, gridArea: '2/1/3/3' }] },
79
79
  });
80
80
 
81
+ /**
82
+ * A new y axis with no series. Axis `index` 0, 2, 4, ... sits on the left,
83
+ * 1, 3, 5, ... on the right.
84
+ * @param {number} [index=0] - its place among the subplot's y axes
85
+ */
86
+ function makeDefaultYAxis(index = 0) {
87
+ return {
88
+ id: uid('y'),
89
+ label: '',
90
+ scale: 'linear',
91
+ position: index % 2 === 0 ? 'left' : 'right',
92
+ min: null,
93
+ max: null,
94
+ step: null,
95
+ series: [],
96
+ };
97
+ }
98
+
99
+ /**
100
+ * A new series with no variable, coloured by its place in the subplot.
101
+ * @param {number} [index=0] - how many series the subplot already has
102
+ */
103
+ function makeDefaultSeries(index = 0) {
104
+ return {
105
+ id: uid('s'),
106
+ variable: '',
107
+ label: '',
108
+ color: getSeriesColor(index),
109
+ lineWidth: 2,
110
+ lineStyle: 'solid',
111
+ stackGroup: null,
112
+ };
113
+ }
114
+
81
115
  function makeDefaultSubplot(index) {
82
116
  return {
83
117
  id: uid('sp'),
@@ -87,21 +121,9 @@ function makeDefaultSubplot(index) {
87
121
  interpolation: 'linear',
88
122
  nanHandling: 'gap',
89
123
  showDataPoints: false,
90
- showHpTrend: false,
91
- showHpCycle: false,
92
- hpLambda: 1600,
93
124
  xAxis: { useTime: true, variable: '' },
94
125
  zAxis: null,
95
- yAxes: [{
96
- id: uid('y'),
97
- label: '',
98
- scale: 'linear',
99
- position: 'left',
100
- min: null,
101
- max: null,
102
- step: null,
103
- series: [],
104
- }],
126
+ yAxes: [makeDefaultYAxis(0)],
105
127
  };
106
128
  }
107
129
 
@@ -153,24 +175,26 @@ function migrateSingleChart(data) {
153
175
  nanHandling: data?.nanHandling ?? 'gap',
154
176
  showDataPoints: data?.showDataPoints ?? false,
155
177
  yAxes: [{
156
- id: uid('y'),
178
+ ...makeDefaultYAxis(0),
157
179
  label: data?.yLabel ?? '',
158
- scale: 'linear',
159
- position: 'left',
160
- min: null,
161
- max: null,
162
- series: series.map((s, i) => ({
163
- id: s.id ?? uid('s'),
164
- variable: s.variable ?? '',
165
- label: s.label ?? '',
166
- color: s.color ?? getSeriesColor(i),
167
- lineWidth: s.lineWidth ?? 2,
168
- lineStyle: s.lineStyle ?? 'solid',
169
- stackGroup: s.stackGroup ?? null,
170
- ...(s.overlay ? { overlay: s.overlay } : {}),
171
- })),
180
+ series: series.map((s, i) => {
181
+ const d = makeDefaultSeries(i);
182
+ return {
183
+ id: s.id ?? d.id,
184
+ variable: s.variable ?? d.variable,
185
+ label: s.label ?? d.label,
186
+ color: s.color ?? d.color,
187
+ lineWidth: s.lineWidth ?? d.lineWidth,
188
+ lineStyle: s.lineStyle ?? d.lineStyle,
189
+ stackGroup: s.stackGroup ?? d.stackGroup,
190
+ ...(s.overlay ? { overlay: s.overlay } : {}),
191
+ };
192
+ }),
172
193
  }],
173
194
  };
174
195
  }
175
196
 
176
- export { uid, applyNanHandling, PLOT_LAYOUTS, makeDefaultSubplot, migrateData, migrateSingleChart };
197
+ export {
198
+ uid, applyNanHandling, PLOT_LAYOUTS, makeDefaultSubplot, makeDefaultYAxis, makeDefaultSeries,
199
+ migrateData, migrateSingleChart,
200
+ };