@yuneta/gobj-ui 7.23.148 → 7.23.150

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.
@@ -6,7 +6,7 @@ import { createJSONEditor } from "vanilla-jsoneditor";
6
6
  import { TabulatorFull } from "tabulator-tables";
7
7
  import * as maplibregl from "maplibre-gl";
8
8
  import uPlot from "uplot";
9
- import { BaseLayout, BrushSelect, CanvasEvent, Circle, CommonEvent, DragCanvas, EdgeEvent, ExtensionCategory, Graph, HTML, HistoryEvent, NodeEvent, Toolbar, ZoomCanvas, register } from "@antv/g6";
9
+ import { BaseLayout, BrushSelect, CanvasEvent, Circle, CommonEvent, DragCanvas, EdgeEvent, ExtensionCategory, Graph, HTML, HistoryEvent, NodeEvent, Polyline, Toolbar, ZoomCanvas, register } from "@antv/g6";
10
10
  //#region \0rolldown/runtime.js
11
11
  var __create = Object.create;
12
12
  var __defProp = Object.defineProperty;
@@ -29625,6 +29625,54 @@ function layout_radial(nodes, edges, opts) {
29625
29625
  }
29626
29626
  return pos;
29627
29627
  }
29628
+ /************************************************************
29629
+ * The lane of `id` among `ids`, the edges joining the same two
29630
+ * cards: 0, then +1, -1, +2, -2... in the order they were made.
29631
+ * Edge ids end in a counter, so a numeric sort is that order.
29632
+ ************************************************************/
29633
+ function elbow_lane(ids, id) {
29634
+ let k = ids.slice().sort((a, b) => String(a).localeCompare(String(b), void 0, { numeric: true })).indexOf(id);
29635
+ if (k <= 0) return 0;
29636
+ return (k % 2 ? 1 : -1) * Math.ceil(k / 2);
29637
+ }
29638
+ function swap(p) {
29639
+ return [p[1], p[0]];
29640
+ }
29641
+ function swap_box(b) {
29642
+ return {
29643
+ x1: b.y1,
29644
+ y1: b.x1,
29645
+ x2: b.y2,
29646
+ y2: b.x2
29647
+ };
29648
+ }
29649
+ /************************************************************
29650
+ * The control points of an elbow from the port `s` to the port
29651
+ * `t` ([x, y] each), knowing the boxes of the two cards
29652
+ * ({x1, y1, x2, y2}) and the edge's lane.
29653
+ *
29654
+ * vertical the rows are stacked (top to bottom); false
29655
+ * when they are side by side (left to right)
29656
+ ************************************************************/
29657
+ function elbow_points(s, t, box_s, box_t, lane, vertical) {
29658
+ if (!vertical) return elbow_points(swap(s), swap(t), swap_box(box_s), swap_box(box_t), lane, true).map(swap);
29659
+ let shift = lane * 10;
29660
+ if (t[1] - s[1] > 20) {
29661
+ let mid = (s[1] + t[1]) / 2 + shift;
29662
+ mid = Math.min(Math.max(mid, s[1] + 10), t[1] - 10);
29663
+ return [[s[0], mid], [t[0], mid]];
29664
+ }
29665
+ let out = 16 + Math.abs(shift);
29666
+ let side = lane < 0 ? Math.min(box_s.x1, box_t.x1) - 24 - Math.abs(shift) : Math.max(box_s.x2, box_t.x2) + 24 + Math.abs(shift);
29667
+ let y1 = s[1] + out;
29668
+ let y2 = t[1] - out;
29669
+ return [
29670
+ [s[0], y1],
29671
+ [side, y1],
29672
+ [side, y2],
29673
+ [t[0], y2]
29674
+ ];
29675
+ }
29628
29676
  //#endregion
29629
29677
  //#region node_modules/@babel/runtime/helpers/esm/typeof.js
29630
29678
  function _typeof(o) {
@@ -45639,7 +45687,7 @@ var _excluded$1 = ["style"];
45639
45687
  /**
45640
45688
  * Polyline inherits the marker-related capabilities of Polygon.
45641
45689
  */
45642
- var Polyline = /*#__PURE__*/ function(_Polygon) {
45690
+ var Polyline$1 = /*#__PURE__*/ function(_Polygon) {
45643
45691
  function Polyline() {
45644
45692
  var _ref = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {}, style = _ref.style, rest = _objectWithoutProperties(_ref, _excluded$1);
45645
45693
  _classCallCheck(this, Polyline);
@@ -45731,7 +45779,7 @@ var Polyline = /*#__PURE__*/ function(_Polygon) {
45731
45779
  }
45732
45780
  ]);
45733
45781
  }(Polygon);
45734
- Polyline.PARSED_STYLE_LIST = new Set([].concat(_toConsumableArray(Polygon.PARSED_STYLE_LIST), [
45782
+ Polyline$1.PARSED_STYLE_LIST = new Set([].concat(_toConsumableArray(Polygon.PARSED_STYLE_LIST), [
45735
45783
  "points",
45736
45784
  "markerStart",
45737
45785
  "markerMid",
@@ -56566,6 +56614,12 @@ var attrs_table$4 = [
56566
56614
  SDATA(data_type_t.DTP_STRING, "main_topic", 0, "", "The topic the tree hangs from; empty = deduced (the one whose hooks reach the most others). Its parentless records are the roots; a parentless record of a topic linked to it is LOOSE and shown only on request"),
56567
56615
  SDATA(data_type_t.DTP_LIST, "loose_topics", 0, "[]", "Topics whose loose records (no parent, in a topic the schema hangs from the main one) are shown as roots"),
56568
56616
  SDATA(data_type_t.DTP_STRING, "node_mode", 0, "expanded", "How a record is drawn: `expanded` = the card with its pills and ports; `compact` = a one-line pill with the name and small ports; `shape` = a figure of the topic's colour (square, circle, diamond... per node or topic, from the node properties), no ports, no text -- the topology alone. One node can be toggled against it (EV_TOGGLE_NODE_MODE)"),
56617
+ SDATA(data_type_t.DTP_STRING, "edge_shape", 0, "curved", "How an edge is drawn: `curved` (the default) or `elbow` -- straight out of its port to the channel between the two rows, along it, and straight into the other port: the bus of an org chart, as mxGraph drew its trees. Layouts with no rows (`rowless_layouts`) keep the curve"),
56618
+ SDATA(data_type_t.DTP_LIST, "rowless_layouts", sdata_flag_t.SDF_RD, JSON.stringify([
56619
+ "radial",
56620
+ "d3-force",
56621
+ "force-atlas2"
56622
+ ]), "Layouts with no rows for an elbow to run between: their edges stay curved whatever `edge_shape` says (read-only, for the parent to query)"),
56569
56623
  SDATA(data_type_t.DTP_BOOLEAN, "node_labels", 0, true, "A `shape` node says its name under the figure. Off, it is a coloured figure and nothing else"),
56570
56624
  SDATA(data_type_t.DTP_BOOLEAN, "confirm_delete_node", 0, true, "Ask confirmation before deleting a node"),
56571
56625
  SDATA(data_type_t.DTP_BOOLEAN, "confirm_unlink_edge", 0, true, "Ask confirmation before unlinking an edge"),
@@ -56817,6 +56871,8 @@ function register_layouts(gobj) {
56817
56871
  register(ExtensionCategory.LAYOUT, "manual", ManualLayout);
56818
56872
  register(ExtensionCategory.LAYOUT, "treedb-tree", TreedbTreeLayout);
56819
56873
  register(ExtensionCategory.LAYOUT, "treedb-compact", TreedbCompactLayout);
56874
+ register(ExtensionCategory.EDGE, "treedb-elbow-v", TreedbElbowV);
56875
+ register(ExtensionCategory.EDGE, "treedb-elbow-h", TreedbElbowH);
56820
56876
  register(ExtensionCategory.LAYOUT, "treedb-radial", TreedbRadialLayout);
56821
56877
  register(ExtensionCategory.NODE, "light", LightNode);
56822
56878
  register(ExtensionCategory.NODE, "treedb-card", TreedbCard);
@@ -57509,7 +57565,10 @@ function layout_direction(gobj) {
57509
57565
  return str_in_list(LR_LAYOUTS, gobj.priv.layout) ? "LR" : "TB";
57510
57566
  }
57511
57567
  function edge_type_for(gobj) {
57512
- return layout_direction(gobj) === "LR" ? "cubic-horizontal" : "cubic";
57568
+ let lr = layout_direction(gobj) === "LR";
57569
+ let rowless = gobj_read_attr(gobj, "rowless_layouts") || [];
57570
+ if (gobj_read_str_attr(gobj, "edge_shape") === "elbow" && !str_in_list(rowless, gobj.priv.layout)) return lr ? "treedb-elbow-h" : "treedb-elbow-v";
57571
+ return lr ? "cubic-horizontal" : "cubic";
57513
57572
  }
57514
57573
  /************************************************************
57515
57574
  * Where the ports sit, by direction: fkeys (the way in from a
@@ -58984,6 +59043,70 @@ var TreedbTreeLayout = class extends BaseLayout {
58984
59043
  return treedb_layout_output(layout_tree(input.nodes, input.edges, options));
58985
59044
  }
58986
59045
  };
59046
+ /************************************************************
59047
+ * An ELBOW edge, the way mxGraph drew a tree: straight out of
59048
+ * the parent's port to the channel half way to the child, along
59049
+ * it, and straight into the child's port. The children of one
59050
+ * hook sit on one row and leave by one port, so they share the
59051
+ * channel and the lines read as one bus.
59052
+ *
59053
+ * Computed on every draw from where the two ends ARE, so a
59054
+ * dragged card, a fold or a layout needs nothing else. `-v` runs
59055
+ * between rows (top to bottom), `-h` between columns. An edge that
59056
+ * does not run forward goes round the two cards, and the edges
59057
+ * joining the same two cards take lanes (treedb_elbow.js).
59058
+ ************************************************************/
59059
+ var ELBOW_RADIUS = 6;
59060
+ function elbow_box(node) {
59061
+ try {
59062
+ let b = node.getShape("key").getBounds();
59063
+ return {
59064
+ x1: b.min[0],
59065
+ y1: b.min[1],
59066
+ x2: b.max[0],
59067
+ y2: b.max[1]
59068
+ };
59069
+ } catch (e) {
59070
+ log_error(`elbow edge: no bounds for node ${node && node.id}: ${e}`);
59071
+ let c = node.getCenter();
59072
+ return {
59073
+ x1: c[0],
59074
+ y1: c[1],
59075
+ x2: c[0],
59076
+ y2: c[1]
59077
+ };
59078
+ }
59079
+ }
59080
+ function elbow_lane_of(edge) {
59081
+ let graph = edge.context && edge.context.graph;
59082
+ let s = edge.sourceNode && edge.sourceNode.id;
59083
+ let t = edge.targetNode && edge.targetNode.id;
59084
+ if (!graph || s === void 0 || t === void 0 || s === t) return 0;
59085
+ try {
59086
+ return elbow_lane(graph.getRelatedEdgesData(s).filter((d) => d.source === s && d.target === t || d.source === t && d.target === s).map((d) => d.id), edge.id);
59087
+ } catch (e) {
59088
+ log_error(`elbow edge ${edge.id}: cannot read the edges of ${s}: ${e}`);
59089
+ return 0;
59090
+ }
59091
+ }
59092
+ var TreedbElbowV = class extends Polyline {
59093
+ getControlPoints(attributes) {
59094
+ let [s, t] = this.getEndpoints(attributes, false);
59095
+ return elbow_points(s, t, elbow_box(this.sourceNode), elbow_box(this.targetNode), elbow_lane_of(this), true);
59096
+ }
59097
+ getKeyPath(attributes) {
59098
+ return super.getKeyPath(Object.assign({}, attributes, { radius: attributes.radius || ELBOW_RADIUS }));
59099
+ }
59100
+ };
59101
+ var TreedbElbowH = class extends Polyline {
59102
+ getControlPoints(attributes) {
59103
+ let [s, t] = this.getEndpoints(attributes, false);
59104
+ return elbow_points(s, t, elbow_box(this.sourceNode), elbow_box(this.targetNode), elbow_lane_of(this), false);
59105
+ }
59106
+ getKeyPath(attributes) {
59107
+ return super.getKeyPath(Object.assign({}, attributes, { radius: attributes.radius || ELBOW_RADIUS }));
59108
+ }
59109
+ };
58987
59110
  var TreedbCompactLayout = class extends BaseLayout {
58988
59111
  async execute(data, options) {
58989
59112
  let input = treedb_layout_input(data);
@@ -63427,6 +63550,20 @@ function ac_set_node_labels(gobj, event, kw, src) {
63427
63550
  return 0;
63428
63551
  }
63429
63552
  /************************************************************
63553
+ * Curved or elbow edges (the host's toggle). Nothing moves: every
63554
+ * edge takes the type that goes with the shape and the layout,
63555
+ * and draws again.
63556
+ ************************************************************/
63557
+ function ac_set_edge_shape(gobj, event, kw, src) {
63558
+ let priv = gobj.priv;
63559
+ let shape = kw && kw.edge_shape === "elbow" ? "elbow" : "curved";
63560
+ gobj_write_str_attr(gobj, "edge_shape", shape);
63561
+ if (!priv.graph || !priv.graph_rendered) return 0;
63562
+ apply_layout_direction(gobj);
63563
+ graph_draw(gobj);
63564
+ return 0;
63565
+ }
63566
+ /************************************************************
63430
63567
  * ONE node opened or closed against the mode (a double click,
63431
63568
  * or the context menu). It holds still on screen while the
63432
63569
  * rest of the graph makes room, as a fold does.
@@ -63986,6 +64123,11 @@ function create_gclass$4(gclass_name) {
63986
64123
  ac_set_node_labels,
63987
64124
  null
63988
64125
  ],
64126
+ [
64127
+ "EV_SET_EDGE_SHAPE",
64128
+ ac_set_edge_shape,
64129
+ null
64130
+ ],
63989
64131
  [
63990
64132
  "EV_TOGGLE_NODE_MODE",
63991
64133
  ac_toggle_node_mode,
@@ -64107,6 +64249,7 @@ function create_gclass$4(gclass_name) {
64107
64249
  ["EV_SET_LOOSE_TOPICS", 0],
64108
64250
  ["EV_SET_NODE_MODE", 0],
64109
64251
  ["EV_SET_NODE_LABELS", 0],
64252
+ ["EV_SET_EDGE_SHAPE", 0],
64110
64253
  ["EV_TOGGLE_NODE_MODE", 0],
64111
64254
  ["EV_NODE_DBLCLICK", 0],
64112
64255
  ["EV_LEGEND_STATE", event_flag_t.EVF_OUTPUT_EVENT],
@@ -64183,6 +64326,7 @@ var attrs_table$3 = [
64183
64326
  SDATA(data_type_t.DTP_STRING, "main_topic", sdata_flag_t.SDF_PERSIST, "", "The topic the tree hangs from; empty = deduced by the graph. User preference, per treedb"),
64184
64327
  SDATA(data_type_t.DTP_LIST, "loose_topics", sdata_flag_t.SDF_PERSIST, "[]", "Topics whose LOOSE records (no parent) are shown. User preference, per treedb"),
64185
64328
  SDATA(data_type_t.DTP_STRING, "node_mode", sdata_flag_t.SDF_PERSIST, "expanded", "How the records are drawn: `expanded` = the cards with their pills and ports, `compact` = one-line pills with the name, `shape` = coloured figures, the topology alone. User preference"),
64329
+ SDATA(data_type_t.DTP_STRING, "edge_shape", sdata_flag_t.SDF_PERSIST, "curved", "How the edges are drawn: `curved` or `elbow` (the bus of an org chart). User preference"),
64186
64330
  SDATA(data_type_t.DTP_BOOLEAN, "node_labels", sdata_flag_t.SDF_PERSIST, true, "A `shape` node says its name under its figure. User preference"),
64187
64331
  SDATA(data_type_t.DTP_DICT, "camera", sdata_flag_t.SDF_PERSIST, "{}", "Where the reader left the viewport: {zoom, x, y}. Handed to the engine on creation and written back when a move settles, so the graph opens where it was left. User preference, per treedb"),
64188
64332
  SDATA(data_type_t.DTP_INTEGER, "fold_level", sdata_flag_t.SDF_PERSIST, 0, "The level of the MAIN tree the toolbar's stepper stands on: 1 = the main topic's roots, N = N-1 levels under them; 0 = never stepped, `expand_depth`. Handed to the engine on creation, so the graph opens at the level it was left. User preference, per treedb"),
@@ -64275,6 +64419,7 @@ function mt_create$3(gobj) {
64275
64419
  loose_topics: gobj_read_attr(gobj, "loose_topics") || [],
64276
64420
  node_mode: gobj_read_str_attr(gobj, "node_mode") || "expanded",
64277
64421
  node_labels: gobj_read_bool_attr(gobj, "node_labels"),
64422
+ edge_shape: gobj_read_str_attr(gobj, "edge_shape") || "curved",
64278
64423
  camera: gobj_read_attr(gobj, "camera") || {}
64279
64424
  }, gobj);
64280
64425
  populate_nodes_tree_options(gobj);
@@ -64485,58 +64630,82 @@ function option_label(name) {
64485
64630
  * its context menu (the child's business).
64486
64631
  ************************************************************/
64487
64632
  function node_shape_items(gobj, wide) {
64488
- return [[
64489
- "div",
64490
- {
64491
- class: "GRAPH_NODE_MODE buttons has-addons",
64492
- style: "margin:0 0 0 .5rem; flex:0 0 auto; flex-wrap:nowrap;"
64493
- },
64494
- NODE_MODES.map((mode) => {
64495
- let key = NODE_MODE_LABELS[mode];
64496
- return [
64497
- "button",
64498
- {
64499
- class: "GRAPH_NODE_MODE_BTN button",
64500
- type: "button",
64501
- "data-mode": mode,
64502
- style: {
64503
- height: wide,
64504
- width: "2.5em"
64633
+ return [
64634
+ [
64635
+ "div",
64636
+ {
64637
+ class: "GRAPH_NODE_MODE buttons has-addons",
64638
+ style: "margin:0 0 0 .5rem; flex:0 0 auto; flex-wrap:nowrap;"
64639
+ },
64640
+ NODE_MODES.map((mode) => {
64641
+ let key = NODE_MODE_LABELS[mode];
64642
+ return [
64643
+ "button",
64644
+ {
64645
+ class: "GRAPH_NODE_MODE_BTN button",
64646
+ type: "button",
64647
+ "data-mode": mode,
64648
+ style: {
64649
+ height: wide,
64650
+ width: "2.5em"
64651
+ },
64652
+ title: t(key),
64653
+ "data-i18n-title": key,
64654
+ "aria-label": t(key),
64655
+ "data-i18n-aria-label": key,
64656
+ "aria-pressed": "false"
64505
64657
  },
64506
- title: t(key),
64507
- "data-i18n-title": key,
64508
- "aria-label": t(key),
64509
- "data-i18n-aria-label": key,
64510
- "aria-pressed": "false"
64658
+ yui_toolbar_icon(NODE_MODE_ICONS[mode]),
64659
+ { click: (evt) => {
64660
+ evt.stopPropagation();
64661
+ gobj_send_event(gobj, "EV_SET_NODE_MODE", { node_mode: mode }, gobj);
64662
+ } }
64663
+ ];
64664
+ })
64665
+ ],
64666
+ [
64667
+ "button",
64668
+ {
64669
+ class: "GRAPH_NODE_LABELS button",
64670
+ type: "button",
64671
+ style: {
64672
+ height: wide,
64673
+ width: "2.5em"
64511
64674
  },
64512
- yui_toolbar_icon(NODE_MODE_ICONS[mode]),
64513
- { click: (evt) => {
64514
- evt.stopPropagation();
64515
- gobj_send_event(gobj, "EV_SET_NODE_MODE", { node_mode: mode }, gobj);
64516
- } }
64517
- ];
64518
- })
64519
- ], [
64520
- "button",
64521
- {
64522
- class: "GRAPH_NODE_LABELS button",
64523
- type: "button",
64524
- style: {
64525
- height: wide,
64526
- width: "2.5em"
64675
+ title: t("node labels"),
64676
+ "data-i18n-title": "node labels",
64677
+ "aria-label": t("node labels"),
64678
+ "data-i18n-aria-label": "node labels",
64679
+ "aria-pressed": "false"
64527
64680
  },
64528
- title: t("node labels"),
64529
- "data-i18n-title": "node labels",
64530
- "aria-label": t("node labels"),
64531
- "data-i18n-aria-label": "node labels",
64532
- "aria-pressed": "false"
64533
- },
64534
- yui_toolbar_icon("yi-font"),
64535
- { click: (evt) => {
64536
- evt.stopPropagation();
64537
- gobj_send_event(gobj, "EV_TOGGLE_NODE_LABELS", {}, gobj);
64538
- } }
64539
- ]];
64681
+ yui_toolbar_icon("yi-font"),
64682
+ { click: (evt) => {
64683
+ evt.stopPropagation();
64684
+ gobj_send_event(gobj, "EV_TOGGLE_NODE_LABELS", {}, gobj);
64685
+ } }
64686
+ ],
64687
+ [
64688
+ "button",
64689
+ {
64690
+ class: "GRAPH_EDGE_ELBOW button",
64691
+ type: "button",
64692
+ style: {
64693
+ height: wide,
64694
+ width: "2.5em"
64695
+ },
64696
+ title: t("elbow edges"),
64697
+ "data-i18n-title": "elbow edges",
64698
+ "aria-label": t("elbow edges"),
64699
+ "data-i18n-aria-label": "elbow edges",
64700
+ "aria-pressed": "false"
64701
+ },
64702
+ yui_toolbar_icon("yi-diagram-project"),
64703
+ { click: (evt) => {
64704
+ evt.stopPropagation();
64705
+ gobj_send_event(gobj, "EV_TOGGLE_EDGE_SHAPE", {}, gobj);
64706
+ } }
64707
+ ]
64708
+ ];
64540
64709
  }
64541
64710
  var NODE_MODES = [
64542
64711
  "expanded",
@@ -64569,6 +64738,16 @@ function refresh_node_shape_buttons(gobj, $root) {
64569
64738
  $labels.setAttribute("aria-pressed", labels ? "true" : "false");
64570
64739
  $labels.disabled = mode !== "shape";
64571
64740
  }
64741
+ let elbow = gobj_read_str_attr(gobj, "edge_shape") === "elbow";
64742
+ set_pressed_state($root, ".GRAPH_EDGE_ELBOW", elbow);
64743
+ let $elbow = $root.querySelector(".GRAPH_EDGE_ELBOW");
64744
+ if ($elbow) {
64745
+ $elbow.setAttribute("aria-pressed", elbow ? "true" : "false");
64746
+ let child = gobj.priv.gobj_nodes_tree;
64747
+ let rowless = child ? gobj_read_attr(child, "rowless_layouts") || [] : [];
64748
+ let layout = child ? gobj_read_str_attr(child, "layout") : "";
64749
+ $elbow.disabled = rowless.includes(layout);
64750
+ }
64572
64751
  }
64573
64752
  /************************************************************
64574
64753
  * The fold STEPPER: one level less, where the tree stands, one
@@ -65117,6 +65296,7 @@ function populate_nodes_tree_options(gobj) {
65117
65296
  let current_layout = gobj_read_str_attr(priv.gobj_nodes_tree, "layout");
65118
65297
  if (current_layout) $layout_select.value = current_layout;
65119
65298
  refresh_layout_stepper(gobj);
65299
+ refresh_node_shape_buttons(gobj, $container);
65120
65300
  }
65121
65301
  let $operation_mode_select = $container.querySelector(".GRAPH_MODE_SELECT");
65122
65302
  $operation_mode_select.value = priv.operation_mode;
@@ -65810,6 +65990,7 @@ function ac_set_layout(gobj, event, kw, src) {
65810
65990
  $layout_select.value = priv.layout;
65811
65991
  gobj_send_event(priv.gobj_nodes_tree, "EV_SET_LAYOUT", { layout }, gobj);
65812
65992
  refresh_layout_stepper(gobj);
65993
+ refresh_node_shape_buttons(gobj, priv.$container);
65813
65994
  return 0;
65814
65995
  }
65815
65996
  /************************************************************
@@ -66161,6 +66342,7 @@ function ac_layout_autoset(gobj, event, kw, src) {
66161
66342
  if (!$select || !layout) return 0;
66162
66343
  $select.value = layout;
66163
66344
  refresh_layout_stepper(gobj);
66345
+ refresh_node_shape_buttons(gobj, $container);
66164
66346
  return 0;
66165
66347
  }
66166
66348
  /************************************************************
@@ -66233,6 +66415,15 @@ function ac_set_node_mode(gobj, event, kw, src) {
66233
66415
  if (priv.gobj_nodes_tree) gobj_send_event(priv.gobj_nodes_tree, "EV_SET_NODE_MODE", { node_mode: mode }, gobj);
66234
66416
  return 0;
66235
66417
  }
66418
+ function ac_toggle_edge_shape(gobj, event, kw, src) {
66419
+ let priv = gobj.priv;
66420
+ let shape = gobj_read_str_attr(gobj, "edge_shape") === "elbow" ? "curved" : "elbow";
66421
+ gobj_write_str_attr(gobj, "edge_shape", shape);
66422
+ gobj_save_persistent_attrs(gobj, "edge_shape");
66423
+ refresh_node_shape_buttons(gobj, priv.$container);
66424
+ if (priv.gobj_nodes_tree) gobj_send_event(priv.gobj_nodes_tree, "EV_SET_EDGE_SHAPE", { edge_shape: shape }, gobj);
66425
+ return 0;
66426
+ }
66236
66427
  function ac_toggle_node_labels(gobj, event, kw, src) {
66237
66428
  let priv = gobj.priv;
66238
66429
  let labels = !gobj_read_bool_attr(gobj, "node_labels");
@@ -66477,6 +66668,11 @@ function create_gclass$3(gclass_name) {
66477
66668
  ac_toggle_node_labels,
66478
66669
  null
66479
66670
  ],
66671
+ [
66672
+ "EV_TOGGLE_EDGE_SHAPE",
66673
+ ac_toggle_edge_shape,
66674
+ null
66675
+ ],
66480
66676
  [
66481
66677
  "EV_LAYOUT_AUTOSET",
66482
66678
  ac_layout_autoset,
@@ -66554,6 +66750,7 @@ function create_gclass$3(gclass_name) {
66554
66750
  ["EV_COLLAPSE_LEVEL", 0],
66555
66751
  ["EV_SET_NODE_MODE", 0],
66556
66752
  ["EV_TOGGLE_NODE_LABELS", 0],
66753
+ ["EV_TOGGLE_EDGE_SHAPE", 0],
66557
66754
  ["EV_FIND_RESULT", 0],
66558
66755
  ["EV_LAYOUT_AUTOSET", 0],
66559
66756
  ["EV_CAMERA_CHANGED", 0],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.23.148",
3
+ "version": "7.23.150",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -133,6 +133,7 @@ import {
133
133
  fold_root_group_key,
134
134
  } from "./treedb_fold_model.js";
135
135
  import {layout_tree, layout_radial, layout_compact} from "./treedb_layout.js";
136
+ import {elbow_lane, elbow_points} from "./treedb_elbow.js";
136
137
 
137
138
  import {
138
139
  BaseLayout,
@@ -146,6 +147,7 @@ import {
146
147
  HTML,
147
148
  Toolbar,
148
149
  register,
150
+ Polyline,
149
151
  } from '@antv/g6';
150
152
 
151
153
  import {Circle as CircleGeometry, Rect as RectGeometry} from '@antv/g';
@@ -456,6 +458,8 @@ SDATA(data_type_t.DTP_LIST, "loose_topics", 0, "[]", "Topics whos
456
458
 
457
459
  /*---------------- Node shape ----------------*/
458
460
  SDATA(data_type_t.DTP_STRING, "node_mode", 0, "expanded", "How a record is drawn: `expanded` = the card with its pills and ports; `compact` = a one-line pill with the name and small ports; `shape` = a figure of the topic's colour (square, circle, diamond... per node or topic, from the node properties), no ports, no text -- the topology alone. One node can be toggled against it (EV_TOGGLE_NODE_MODE)"),
461
+ SDATA(data_type_t.DTP_STRING, "edge_shape", 0, "curved", "How an edge is drawn: `curved` (the default) or `elbow` -- straight out of its port to the channel between the two rows, along it, and straight into the other port: the bus of an org chart, as mxGraph drew its trees. Layouts with no rows (`rowless_layouts`) keep the curve"),
462
+ SDATA(data_type_t.DTP_LIST, "rowless_layouts", sdata_flag_t.SDF_RD, JSON.stringify(["radial", "d3-force", "force-atlas2"]), "Layouts with no rows for an elbow to run between: their edges stay curved whatever `edge_shape` says (read-only, for the parent to query)"),
459
463
  SDATA(data_type_t.DTP_BOOLEAN, "node_labels", 0, true, "A `shape` node says its name under the figure. Off, it is a coloured figure and nothing else"),
460
464
  SDATA(data_type_t.DTP_BOOLEAN, "confirm_delete_node", 0, true, "Ask confirmation before deleting a node"),
461
465
  SDATA(data_type_t.DTP_BOOLEAN, "confirm_unlink_edge", 0, true, "Ask confirmation before unlinking an edge"),
@@ -936,6 +940,8 @@ function register_layouts(gobj)
936
940
  register(ExtensionCategory.LAYOUT, 'manual', ManualLayout);
937
941
  register(ExtensionCategory.LAYOUT, 'treedb-tree', TreedbTreeLayout);
938
942
  register(ExtensionCategory.LAYOUT, 'treedb-compact', TreedbCompactLayout);
943
+ register(ExtensionCategory.EDGE, 'treedb-elbow-v', TreedbElbowV);
944
+ register(ExtensionCategory.EDGE, 'treedb-elbow-h', TreedbElbowH);
939
945
  register(ExtensionCategory.LAYOUT, 'treedb-radial', TreedbRadialLayout);
940
946
  register(ExtensionCategory.NODE, 'light', LightNode);
941
947
  register(ExtensionCategory.NODE, 'treedb-card', TreedbCard);
@@ -2036,9 +2042,17 @@ function layout_direction(gobj)
2036
2042
  return str_in_list(LR_LAYOUTS, gobj.priv.layout)? "LR" : "TB";
2037
2043
  }
2038
2044
 
2045
+ /* A curve, or an elbow when the reader asked for one and the layout
2046
+ * has rows to run it between. */
2039
2047
  function edge_type_for(gobj)
2040
2048
  {
2041
- return (layout_direction(gobj) === "LR")? "cubic-horizontal" : "cubic";
2049
+ let lr = (layout_direction(gobj) === "LR");
2050
+ let rowless = gobj_read_attr(gobj, "rowless_layouts") || [];
2051
+ if(gobj_read_str_attr(gobj, "edge_shape") === "elbow" &&
2052
+ !str_in_list(rowless, gobj.priv.layout)) {
2053
+ return lr? "treedb-elbow-h" : "treedb-elbow-v";
2054
+ }
2055
+ return lr? "cubic-horizontal" : "cubic";
2042
2056
  }
2043
2057
 
2044
2058
  /************************************************************
@@ -4199,6 +4213,84 @@ class TreedbTreeLayout extends BaseLayout
4199
4213
  }
4200
4214
  }
4201
4215
 
4216
+ /************************************************************
4217
+ * An ELBOW edge, the way mxGraph drew a tree: straight out of
4218
+ * the parent's port to the channel half way to the child, along
4219
+ * it, and straight into the child's port. The children of one
4220
+ * hook sit on one row and leave by one port, so they share the
4221
+ * channel and the lines read as one bus.
4222
+ *
4223
+ * Computed on every draw from where the two ends ARE, so a
4224
+ * dragged card, a fold or a layout needs nothing else. `-v` runs
4225
+ * between rows (top to bottom), `-h` between columns. An edge that
4226
+ * does not run forward goes round the two cards, and the edges
4227
+ * joining the same two cards take lanes (treedb_elbow.js).
4228
+ ************************************************************/
4229
+ const ELBOW_RADIUS = 6;
4230
+
4231
+ /* The box of a card on the canvas, for a detour to go round it. */
4232
+ function elbow_box(node)
4233
+ {
4234
+ try {
4235
+ let b = node.getShape('key').getBounds();
4236
+ return {x1: b.min[0], y1: b.min[1], x2: b.max[0], y2: b.max[1]};
4237
+ } catch(e) {
4238
+ log_error(`elbow edge: no bounds for node ${node && node.id}: ${e}`);
4239
+ let c = node.getCenter();
4240
+ return {x1: c[0], y1: c[1], x2: c[0], y2: c[1]};
4241
+ }
4242
+ }
4243
+
4244
+ /* The lane of an edge among the edges joining the same two cards,
4245
+ * either way round (treedb_elbow.js). */
4246
+ function elbow_lane_of(edge)
4247
+ {
4248
+ let graph = edge.context && edge.context.graph;
4249
+ let s = edge.sourceNode && edge.sourceNode.id;
4250
+ let t = edge.targetNode && edge.targetNode.id;
4251
+ if(!graph || s === undefined || t === undefined || s === t) {
4252
+ return 0;
4253
+ }
4254
+ try {
4255
+ let ids = graph.getRelatedEdgesData(s)
4256
+ .filter((d) => (d.source === s && d.target === t) ||
4257
+ (d.source === t && d.target === s))
4258
+ .map((d) => d.id);
4259
+ return elbow_lane(ids, edge.id);
4260
+ } catch(e) {
4261
+ log_error(`elbow edge ${edge.id}: cannot read the edges of ${s}: ${e}`);
4262
+ return 0;
4263
+ }
4264
+ }
4265
+
4266
+ class TreedbElbowV extends Polyline
4267
+ {
4268
+ getControlPoints(attributes) {
4269
+ let [s, t] = this.getEndpoints(attributes, false);
4270
+ return elbow_points(s, t, elbow_box(this.sourceNode), elbow_box(this.targetNode),
4271
+ elbow_lane_of(this), true);
4272
+ }
4273
+
4274
+ getKeyPath(attributes) {
4275
+ return super.getKeyPath(Object.assign({}, attributes,
4276
+ {radius: attributes.radius || ELBOW_RADIUS}));
4277
+ }
4278
+ }
4279
+
4280
+ class TreedbElbowH extends Polyline
4281
+ {
4282
+ getControlPoints(attributes) {
4283
+ let [s, t] = this.getEndpoints(attributes, false);
4284
+ return elbow_points(s, t, elbow_box(this.sourceNode), elbow_box(this.targetNode),
4285
+ elbow_lane_of(this), false);
4286
+ }
4287
+
4288
+ getKeyPath(attributes) {
4289
+ return super.getKeyPath(Object.assign({}, attributes,
4290
+ {radius: attributes.radius || ELBOW_RADIUS}));
4291
+ }
4292
+ }
4293
+
4202
4294
  class TreedbCompactLayout extends BaseLayout
4203
4295
  {
4204
4296
  async execute(data, options) {
@@ -10875,6 +10967,25 @@ function ac_set_node_labels(gobj, event, kw, src)
10875
10967
  return 0;
10876
10968
  }
10877
10969
 
10970
+ /************************************************************
10971
+ * Curved or elbow edges (the host's toggle). Nothing moves: every
10972
+ * edge takes the type that goes with the shape and the layout,
10973
+ * and draws again.
10974
+ ************************************************************/
10975
+ function ac_set_edge_shape(gobj, event, kw, src)
10976
+ {
10977
+ let priv = gobj.priv;
10978
+
10979
+ let shape = (kw && kw.edge_shape === "elbow")? "elbow" : "curved";
10980
+ gobj_write_str_attr(gobj, "edge_shape", shape);
10981
+ if(!priv.graph || !priv.graph_rendered) {
10982
+ return 0; /* every edge is typed as it is drawn */
10983
+ }
10984
+ apply_layout_direction(gobj);
10985
+ graph_draw(gobj);
10986
+ return 0;
10987
+ }
10988
+
10878
10989
  /************************************************************
10879
10990
  * ONE node opened or closed against the mode (a double click,
10880
10991
  * or the context menu). It holds still on screen while the
@@ -11518,6 +11629,7 @@ function create_gclass(gclass_name)
11518
11629
  ["EV_SET_LOOSE_TOPICS", ac_set_loose_topics, null],
11519
11630
  ["EV_SET_NODE_MODE", ac_set_node_mode, null],
11520
11631
  ["EV_SET_NODE_LABELS", ac_set_node_labels, null],
11632
+ ["EV_SET_EDGE_SHAPE", ac_set_edge_shape, null],
11521
11633
  ["EV_TOGGLE_NODE_MODE", ac_toggle_node_mode, null],
11522
11634
  ["EV_NODE_DBLCLICK", ac_node_dblclick, null],
11523
11635
  ["EV_CENTER", ac_center, null],
@@ -11585,6 +11697,7 @@ function create_gclass(gclass_name)
11585
11697
  ["EV_SET_LOOSE_TOPICS", 0],
11586
11698
  ["EV_SET_NODE_MODE", 0],
11587
11699
  ["EV_SET_NODE_LABELS", 0],
11700
+ ["EV_SET_EDGE_SHAPE", 0],
11588
11701
  ["EV_TOGGLE_NODE_MODE", 0],
11589
11702
  ["EV_NODE_DBLCLICK", 0],
11590
11703
  ["EV_LEGEND_STATE", event_flag_t.EVF_OUTPUT_EVENT],