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.
- package/CHANGELOG.md +93 -0
- package/LICENSE +0 -0
- package/README.md +2217 -42
- package/THIRD_PARTY_LICENSES.md +0 -0
- package/css/base.css +1834 -75
- package/css/flexdesk.css +3146 -164
- 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-B5Q53LAJ.js → chunk-UWO43BZU.js} +2 -2
- 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 +3146 -164
- 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 +657 -234
- package/dist/tiles.js.map +4 -4
- package/dist/tokens.css +0 -0
- package/dist/widgets.js +3252 -2124
- package/dist/widgets.js.map +4 -4
- package/dist/wm.js +1227 -347
- 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 +7 -7
- package/src/tiles/scope_picker.js +0 -0
- package/src/tiles/tile_base.js +1 -1
- package/src/tiles/tile_config_form.js +442 -0
- package/src/tiles/tile_grid.js +2 -0
- 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 +0 -0
- 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-B5Q53LAJ.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/dist/chunk-W6FBJLPW.js +0 -367
- package/dist/chunk-W6FBJLPW.js.map +0 -7
- package/src/ui/controllers/panel_state_machine.js +0 -634
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flexdesk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
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": {
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"src",
|
|
21
21
|
"css",
|
|
22
22
|
"README.md",
|
|
23
|
+
"CHANGELOG.md",
|
|
23
24
|
"LICENSE",
|
|
24
25
|
"THIRD_PARTY_LICENSES.md"
|
|
25
26
|
],
|
|
@@ -42,6 +43,7 @@
|
|
|
42
43
|
},
|
|
43
44
|
"./css": "./dist/flexdesk.css",
|
|
44
45
|
"./css/tokens": "./css/tokens.css",
|
|
46
|
+
"./css/reset": "./dist/reset.css",
|
|
45
47
|
"./charts": {
|
|
46
48
|
"import": "./dist/charts.js",
|
|
47
49
|
"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
|
package/src/canvas/index.js
CHANGED
|
@@ -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
|
package/src/charts/downsample.js
CHANGED
|
@@ -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,
|
|
11
|
-
*
|
|
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
|
-
|
|
178
|
+
...makeDefaultYAxis(0),
|
|
157
179
|
label: data?.yLabel ?? '',
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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 {
|
|
197
|
+
export {
|
|
198
|
+
uid, applyNanHandling, PLOT_LAYOUTS, makeDefaultSubplot, makeDefaultYAxis, makeDefaultSeries,
|
|
199
|
+
migrateData, migrateSingleChart,
|
|
200
|
+
};
|