@yuneta/gobj-ui 7.23.148 → 7.23.149

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/README.md CHANGED
@@ -1425,6 +1425,19 @@ layouts by three thin adapters in `c_g6_nodes_tree.js`:
1425
1425
  construction, across the ring or along the radius; several roots share the
1426
1426
  circle around an empty centre.
1427
1427
 
1428
+ **Elbow edges** (`7.23.149`) are an option of the edges, not a layout: the
1429
+ toolbar's `GRAPH_EDGE_ELBOW` toggle sets `edge_shape` to `elbow`, and every
1430
+ edge goes straight out of the parent's port to the channel half way to the
1431
+ child, along it, and straight into the child's port — mxGraph's tree routing.
1432
+ The children of one hook share a row and a port, so they share the channel
1433
+ and read as one bus. The two edge types (`treedb-elbow-v` / `-h`) subclass
1434
+ G6's `Polyline` and compute the elbow from the ports on every draw, so
1435
+ dragging, folding and relayout need nothing else. It follows the reading
1436
+ direction, and a layout with no rows (`rowless_layouts`: `radial`,
1437
+ `d3-force`, `force-atlas2`) keeps the curve, with the toggle disabled.
1438
+ `edge_shape` is `SDF_PERSIST` on `C_YUI_TREEDB_GRAPH`, a preference per
1439
+ treedb like `node_labels`. Consumer key: `elbow edges`.
1440
+
1428
1441
  All three take the **same spanning tree**, chosen deterministically: roots are the
1429
1442
  nodes with no incoming edge, in node order; a node belongs to the **first
1430
1443
  parent that reaches it** in a breadth-first walk (the place, not the
@@ -45644,7 +45644,7 @@ var _excluded$1 = ["style"];
45644
45644
  /**
45645
45645
  * Polyline inherits the marker-related capabilities of Polygon.
45646
45646
  */
45647
- var Polyline = /*#__PURE__*/ function(_Polygon) {
45647
+ var Polyline$1 = /*#__PURE__*/ function(_Polygon) {
45648
45648
  function Polyline() {
45649
45649
  var _ref = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {}, style = _ref.style, rest = _objectWithoutProperties(_ref, _excluded$1);
45650
45650
  _classCallCheck(this, Polyline);
@@ -45736,7 +45736,7 @@ var Polyline = /*#__PURE__*/ function(_Polygon) {
45736
45736
  }
45737
45737
  ]);
45738
45738
  }(Polygon);
45739
- Polyline.PARSED_STYLE_LIST = new Set([].concat(_toConsumableArray(Polygon.PARSED_STYLE_LIST), [
45739
+ Polyline$1.PARSED_STYLE_LIST = new Set([].concat(_toConsumableArray(Polygon.PARSED_STYLE_LIST), [
45740
45740
  "points",
45741
45741
  "markerStart",
45742
45742
  "markerMid",
@@ -56571,6 +56571,12 @@ var attrs_table$4 = [
56571
56571
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.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"),
56572
56572
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.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"),
56573
56573
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.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)"),
56574
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.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"),
56575
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_LIST, "rowless_layouts", _yuneta_gobj_js.sdata_flag_t.SDF_RD, JSON.stringify([
56576
+ "radial",
56577
+ "d3-force",
56578
+ "force-atlas2"
56579
+ ]), "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)"),
56574
56580
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.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"),
56575
56581
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "confirm_delete_node", 0, true, "Ask confirmation before deleting a node"),
56576
56582
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "confirm_unlink_edge", 0, true, "Ask confirmation before unlinking an edge"),
@@ -56822,6 +56828,8 @@ function register_layouts(gobj) {
56822
56828
  (0, _antv_g6.register)(_antv_g6.ExtensionCategory.LAYOUT, "manual", ManualLayout);
56823
56829
  (0, _antv_g6.register)(_antv_g6.ExtensionCategory.LAYOUT, "treedb-tree", TreedbTreeLayout);
56824
56830
  (0, _antv_g6.register)(_antv_g6.ExtensionCategory.LAYOUT, "treedb-compact", TreedbCompactLayout);
56831
+ (0, _antv_g6.register)(_antv_g6.ExtensionCategory.EDGE, "treedb-elbow-v", TreedbElbowV);
56832
+ (0, _antv_g6.register)(_antv_g6.ExtensionCategory.EDGE, "treedb-elbow-h", TreedbElbowH);
56825
56833
  (0, _antv_g6.register)(_antv_g6.ExtensionCategory.LAYOUT, "treedb-radial", TreedbRadialLayout);
56826
56834
  (0, _antv_g6.register)(_antv_g6.ExtensionCategory.NODE, "light", LightNode);
56827
56835
  (0, _antv_g6.register)(_antv_g6.ExtensionCategory.NODE, "treedb-card", TreedbCard);
@@ -57514,7 +57522,10 @@ function layout_direction(gobj) {
57514
57522
  return (0, _yuneta_gobj_js.str_in_list)(LR_LAYOUTS, gobj.priv.layout) ? "LR" : "TB";
57515
57523
  }
57516
57524
  function edge_type_for(gobj) {
57517
- return layout_direction(gobj) === "LR" ? "cubic-horizontal" : "cubic";
57525
+ let lr = layout_direction(gobj) === "LR";
57526
+ let rowless = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "rowless_layouts") || [];
57527
+ if ((0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "edge_shape") === "elbow" && !(0, _yuneta_gobj_js.str_in_list)(rowless, gobj.priv.layout)) return lr ? "treedb-elbow-h" : "treedb-elbow-v";
57528
+ return lr ? "cubic-horizontal" : "cubic";
57518
57529
  }
57519
57530
  /************************************************************
57520
57531
  * Where the ports sit, by direction: fkeys (the way in from a
@@ -58989,6 +59000,38 @@ var TreedbTreeLayout = class extends _antv_g6.BaseLayout {
58989
59000
  return treedb_layout_output(layout_tree(input.nodes, input.edges, options));
58990
59001
  }
58991
59002
  };
59003
+ /************************************************************
59004
+ * An ELBOW edge, the way mxGraph drew a tree: straight out of
59005
+ * the parent's port to the channel half way to the child, along
59006
+ * it, and straight into the child's port. The children of one
59007
+ * hook sit on one row and leave by one port, so they share the
59008
+ * channel and the lines read as one bus.
59009
+ *
59010
+ * Computed on every draw from where the two ends ARE, so a
59011
+ * dragged card, a fold or a layout needs nothing else. `-v` runs
59012
+ * between rows (top to bottom), `-h` between columns.
59013
+ ************************************************************/
59014
+ var ELBOW_RADIUS = 6;
59015
+ var TreedbElbowV = class extends _antv_g6.Polyline {
59016
+ getControlPoints(attributes) {
59017
+ let [s, t] = this.getEndpoints(attributes, false);
59018
+ let mid = (s[1] + t[1]) / 2;
59019
+ return [[s[0], mid], [t[0], mid]];
59020
+ }
59021
+ getKeyPath(attributes) {
59022
+ return super.getKeyPath(Object.assign({}, attributes, { radius: attributes.radius || ELBOW_RADIUS }));
59023
+ }
59024
+ };
59025
+ var TreedbElbowH = class extends _antv_g6.Polyline {
59026
+ getControlPoints(attributes) {
59027
+ let [s, t] = this.getEndpoints(attributes, false);
59028
+ let mid = (s[0] + t[0]) / 2;
59029
+ return [[mid, s[1]], [mid, t[1]]];
59030
+ }
59031
+ getKeyPath(attributes) {
59032
+ return super.getKeyPath(Object.assign({}, attributes, { radius: attributes.radius || ELBOW_RADIUS }));
59033
+ }
59034
+ };
58992
59035
  var TreedbCompactLayout = class extends _antv_g6.BaseLayout {
58993
59036
  async execute(data, options) {
58994
59037
  let input = treedb_layout_input(data);
@@ -63432,6 +63475,20 @@ function ac_set_node_labels(gobj, event, kw, src) {
63432
63475
  return 0;
63433
63476
  }
63434
63477
  /************************************************************
63478
+ * Curved or elbow edges (the host's toggle). Nothing moves: every
63479
+ * edge takes the type that goes with the shape and the layout,
63480
+ * and draws again.
63481
+ ************************************************************/
63482
+ function ac_set_edge_shape(gobj, event, kw, src) {
63483
+ let priv = gobj.priv;
63484
+ let shape = kw && kw.edge_shape === "elbow" ? "elbow" : "curved";
63485
+ (0, _yuneta_gobj_js.gobj_write_str_attr)(gobj, "edge_shape", shape);
63486
+ if (!priv.graph || !priv.graph_rendered) return 0;
63487
+ apply_layout_direction(gobj);
63488
+ graph_draw(gobj);
63489
+ return 0;
63490
+ }
63491
+ /************************************************************
63435
63492
  * ONE node opened or closed against the mode (a double click,
63436
63493
  * or the context menu). It holds still on screen while the
63437
63494
  * rest of the graph makes room, as a fold does.
@@ -63991,6 +64048,11 @@ function create_gclass$4(gclass_name) {
63991
64048
  ac_set_node_labels,
63992
64049
  null
63993
64050
  ],
64051
+ [
64052
+ "EV_SET_EDGE_SHAPE",
64053
+ ac_set_edge_shape,
64054
+ null
64055
+ ],
63994
64056
  [
63995
64057
  "EV_TOGGLE_NODE_MODE",
63996
64058
  ac_toggle_node_mode,
@@ -64112,6 +64174,7 @@ function create_gclass$4(gclass_name) {
64112
64174
  ["EV_SET_LOOSE_TOPICS", 0],
64113
64175
  ["EV_SET_NODE_MODE", 0],
64114
64176
  ["EV_SET_NODE_LABELS", 0],
64177
+ ["EV_SET_EDGE_SHAPE", 0],
64115
64178
  ["EV_TOGGLE_NODE_MODE", 0],
64116
64179
  ["EV_NODE_DBLCLICK", 0],
64117
64180
  ["EV_LEGEND_STATE", _yuneta_gobj_js.event_flag_t.EVF_OUTPUT_EVENT],
@@ -64188,6 +64251,7 @@ var attrs_table$3 = [
64188
64251
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "main_topic", _yuneta_gobj_js.sdata_flag_t.SDF_PERSIST, "", "The topic the tree hangs from; empty = deduced by the graph. User preference, per treedb"),
64189
64252
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_LIST, "loose_topics", _yuneta_gobj_js.sdata_flag_t.SDF_PERSIST, "[]", "Topics whose LOOSE records (no parent) are shown. User preference, per treedb"),
64190
64253
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "node_mode", _yuneta_gobj_js.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"),
64254
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "edge_shape", _yuneta_gobj_js.sdata_flag_t.SDF_PERSIST, "curved", "How the edges are drawn: `curved` or `elbow` (the bus of an org chart). User preference"),
64191
64255
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "node_labels", _yuneta_gobj_js.sdata_flag_t.SDF_PERSIST, true, "A `shape` node says its name under its figure. User preference"),
64192
64256
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_DICT, "camera", _yuneta_gobj_js.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"),
64193
64257
  (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_INTEGER, "fold_level", _yuneta_gobj_js.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"),
@@ -64280,6 +64344,7 @@ function mt_create$3(gobj) {
64280
64344
  loose_topics: (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "loose_topics") || [],
64281
64345
  node_mode: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "node_mode") || "expanded",
64282
64346
  node_labels: (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "node_labels"),
64347
+ edge_shape: (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "edge_shape") || "curved",
64283
64348
  camera: (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "camera") || {}
64284
64349
  }, gobj);
64285
64350
  populate_nodes_tree_options(gobj);
@@ -64490,58 +64555,82 @@ function option_label(name) {
64490
64555
  * its context menu (the child's business).
64491
64556
  ************************************************************/
64492
64557
  function node_shape_items(gobj, wide) {
64493
- return [[
64494
- "div",
64495
- {
64496
- class: "GRAPH_NODE_MODE buttons has-addons",
64497
- style: "margin:0 0 0 .5rem; flex:0 0 auto; flex-wrap:nowrap;"
64498
- },
64499
- NODE_MODES.map((mode) => {
64500
- let key = NODE_MODE_LABELS[mode];
64501
- return [
64502
- "button",
64503
- {
64504
- class: "GRAPH_NODE_MODE_BTN button",
64505
- type: "button",
64506
- "data-mode": mode,
64507
- style: {
64508
- height: wide,
64509
- width: "2.5em"
64558
+ return [
64559
+ [
64560
+ "div",
64561
+ {
64562
+ class: "GRAPH_NODE_MODE buttons has-addons",
64563
+ style: "margin:0 0 0 .5rem; flex:0 0 auto; flex-wrap:nowrap;"
64564
+ },
64565
+ NODE_MODES.map((mode) => {
64566
+ let key = NODE_MODE_LABELS[mode];
64567
+ return [
64568
+ "button",
64569
+ {
64570
+ class: "GRAPH_NODE_MODE_BTN button",
64571
+ type: "button",
64572
+ "data-mode": mode,
64573
+ style: {
64574
+ height: wide,
64575
+ width: "2.5em"
64576
+ },
64577
+ title: (0, i18next.t)(key),
64578
+ "data-i18n-title": key,
64579
+ "aria-label": (0, i18next.t)(key),
64580
+ "data-i18n-aria-label": key,
64581
+ "aria-pressed": "false"
64510
64582
  },
64511
- title: (0, i18next.t)(key),
64512
- "data-i18n-title": key,
64513
- "aria-label": (0, i18next.t)(key),
64514
- "data-i18n-aria-label": key,
64515
- "aria-pressed": "false"
64583
+ yui_toolbar_icon(NODE_MODE_ICONS[mode]),
64584
+ { click: (evt) => {
64585
+ evt.stopPropagation();
64586
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_SET_NODE_MODE", { node_mode: mode }, gobj);
64587
+ } }
64588
+ ];
64589
+ })
64590
+ ],
64591
+ [
64592
+ "button",
64593
+ {
64594
+ class: "GRAPH_NODE_LABELS button",
64595
+ type: "button",
64596
+ style: {
64597
+ height: wide,
64598
+ width: "2.5em"
64516
64599
  },
64517
- yui_toolbar_icon(NODE_MODE_ICONS[mode]),
64518
- { click: (evt) => {
64519
- evt.stopPropagation();
64520
- (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_SET_NODE_MODE", { node_mode: mode }, gobj);
64521
- } }
64522
- ];
64523
- })
64524
- ], [
64525
- "button",
64526
- {
64527
- class: "GRAPH_NODE_LABELS button",
64528
- type: "button",
64529
- style: {
64530
- height: wide,
64531
- width: "2.5em"
64600
+ title: (0, i18next.t)("node labels"),
64601
+ "data-i18n-title": "node labels",
64602
+ "aria-label": (0, i18next.t)("node labels"),
64603
+ "data-i18n-aria-label": "node labels",
64604
+ "aria-pressed": "false"
64532
64605
  },
64533
- title: (0, i18next.t)("node labels"),
64534
- "data-i18n-title": "node labels",
64535
- "aria-label": (0, i18next.t)("node labels"),
64536
- "data-i18n-aria-label": "node labels",
64537
- "aria-pressed": "false"
64538
- },
64539
- yui_toolbar_icon("yi-font"),
64540
- { click: (evt) => {
64541
- evt.stopPropagation();
64542
- (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_TOGGLE_NODE_LABELS", {}, gobj);
64543
- } }
64544
- ]];
64606
+ yui_toolbar_icon("yi-font"),
64607
+ { click: (evt) => {
64608
+ evt.stopPropagation();
64609
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_TOGGLE_NODE_LABELS", {}, gobj);
64610
+ } }
64611
+ ],
64612
+ [
64613
+ "button",
64614
+ {
64615
+ class: "GRAPH_EDGE_ELBOW button",
64616
+ type: "button",
64617
+ style: {
64618
+ height: wide,
64619
+ width: "2.5em"
64620
+ },
64621
+ title: (0, i18next.t)("elbow edges"),
64622
+ "data-i18n-title": "elbow edges",
64623
+ "aria-label": (0, i18next.t)("elbow edges"),
64624
+ "data-i18n-aria-label": "elbow edges",
64625
+ "aria-pressed": "false"
64626
+ },
64627
+ yui_toolbar_icon("yi-diagram-project"),
64628
+ { click: (evt) => {
64629
+ evt.stopPropagation();
64630
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_TOGGLE_EDGE_SHAPE", {}, gobj);
64631
+ } }
64632
+ ]
64633
+ ];
64545
64634
  }
64546
64635
  var NODE_MODES = [
64547
64636
  "expanded",
@@ -64574,6 +64663,16 @@ function refresh_node_shape_buttons(gobj, $root) {
64574
64663
  $labels.setAttribute("aria-pressed", labels ? "true" : "false");
64575
64664
  $labels.disabled = mode !== "shape";
64576
64665
  }
64666
+ let elbow = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "edge_shape") === "elbow";
64667
+ set_pressed_state($root, ".GRAPH_EDGE_ELBOW", elbow);
64668
+ let $elbow = $root.querySelector(".GRAPH_EDGE_ELBOW");
64669
+ if ($elbow) {
64670
+ $elbow.setAttribute("aria-pressed", elbow ? "true" : "false");
64671
+ let child = gobj.priv.gobj_nodes_tree;
64672
+ let rowless = child ? (0, _yuneta_gobj_js.gobj_read_attr)(child, "rowless_layouts") || [] : [];
64673
+ let layout = child ? (0, _yuneta_gobj_js.gobj_read_str_attr)(child, "layout") : "";
64674
+ $elbow.disabled = rowless.includes(layout);
64675
+ }
64577
64676
  }
64578
64677
  /************************************************************
64579
64678
  * The fold STEPPER: one level less, where the tree stands, one
@@ -65122,6 +65221,7 @@ function populate_nodes_tree_options(gobj) {
65122
65221
  let current_layout = (0, _yuneta_gobj_js.gobj_read_str_attr)(priv.gobj_nodes_tree, "layout");
65123
65222
  if (current_layout) $layout_select.value = current_layout;
65124
65223
  refresh_layout_stepper(gobj);
65224
+ refresh_node_shape_buttons(gobj, $container);
65125
65225
  }
65126
65226
  let $operation_mode_select = $container.querySelector(".GRAPH_MODE_SELECT");
65127
65227
  $operation_mode_select.value = priv.operation_mode;
@@ -65815,6 +65915,7 @@ function ac_set_layout(gobj, event, kw, src) {
65815
65915
  $layout_select.value = priv.layout;
65816
65916
  (0, _yuneta_gobj_js.gobj_send_event)(priv.gobj_nodes_tree, "EV_SET_LAYOUT", { layout }, gobj);
65817
65917
  refresh_layout_stepper(gobj);
65918
+ refresh_node_shape_buttons(gobj, priv.$container);
65818
65919
  return 0;
65819
65920
  }
65820
65921
  /************************************************************
@@ -66166,6 +66267,7 @@ function ac_layout_autoset(gobj, event, kw, src) {
66166
66267
  if (!$select || !layout) return 0;
66167
66268
  $select.value = layout;
66168
66269
  refresh_layout_stepper(gobj);
66270
+ refresh_node_shape_buttons(gobj, $container);
66169
66271
  return 0;
66170
66272
  }
66171
66273
  /************************************************************
@@ -66238,6 +66340,15 @@ function ac_set_node_mode(gobj, event, kw, src) {
66238
66340
  if (priv.gobj_nodes_tree) (0, _yuneta_gobj_js.gobj_send_event)(priv.gobj_nodes_tree, "EV_SET_NODE_MODE", { node_mode: mode }, gobj);
66239
66341
  return 0;
66240
66342
  }
66343
+ function ac_toggle_edge_shape(gobj, event, kw, src) {
66344
+ let priv = gobj.priv;
66345
+ let shape = (0, _yuneta_gobj_js.gobj_read_str_attr)(gobj, "edge_shape") === "elbow" ? "curved" : "elbow";
66346
+ (0, _yuneta_gobj_js.gobj_write_str_attr)(gobj, "edge_shape", shape);
66347
+ (0, _yuneta_gobj_js.gobj_save_persistent_attrs)(gobj, "edge_shape");
66348
+ refresh_node_shape_buttons(gobj, priv.$container);
66349
+ if (priv.gobj_nodes_tree) (0, _yuneta_gobj_js.gobj_send_event)(priv.gobj_nodes_tree, "EV_SET_EDGE_SHAPE", { edge_shape: shape }, gobj);
66350
+ return 0;
66351
+ }
66241
66352
  function ac_toggle_node_labels(gobj, event, kw, src) {
66242
66353
  let priv = gobj.priv;
66243
66354
  let labels = !(0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "node_labels");
@@ -66482,6 +66593,11 @@ function create_gclass$3(gclass_name) {
66482
66593
  ac_toggle_node_labels,
66483
66594
  null
66484
66595
  ],
66596
+ [
66597
+ "EV_TOGGLE_EDGE_SHAPE",
66598
+ ac_toggle_edge_shape,
66599
+ null
66600
+ ],
66485
66601
  [
66486
66602
  "EV_LAYOUT_AUTOSET",
66487
66603
  ac_layout_autoset,
@@ -66559,6 +66675,7 @@ function create_gclass$3(gclass_name) {
66559
66675
  ["EV_COLLAPSE_LEVEL", 0],
66560
66676
  ["EV_SET_NODE_MODE", 0],
66561
66677
  ["EV_TOGGLE_NODE_LABELS", 0],
66678
+ ["EV_TOGGLE_EDGE_SHAPE", 0],
66562
66679
  ["EV_FIND_RESULT", 0],
66563
66680
  ["EV_LAYOUT_AUTOSET", 0],
66564
66681
  ["EV_CAMERA_CHANGED", 0],
@@ -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;
@@ -45639,7 +45639,7 @@ var _excluded$1 = ["style"];
45639
45639
  /**
45640
45640
  * Polyline inherits the marker-related capabilities of Polygon.
45641
45641
  */
45642
- var Polyline = /*#__PURE__*/ function(_Polygon) {
45642
+ var Polyline$1 = /*#__PURE__*/ function(_Polygon) {
45643
45643
  function Polyline() {
45644
45644
  var _ref = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {}, style = _ref.style, rest = _objectWithoutProperties(_ref, _excluded$1);
45645
45645
  _classCallCheck(this, Polyline);
@@ -45731,7 +45731,7 @@ var Polyline = /*#__PURE__*/ function(_Polygon) {
45731
45731
  }
45732
45732
  ]);
45733
45733
  }(Polygon);
45734
- Polyline.PARSED_STYLE_LIST = new Set([].concat(_toConsumableArray(Polygon.PARSED_STYLE_LIST), [
45734
+ Polyline$1.PARSED_STYLE_LIST = new Set([].concat(_toConsumableArray(Polygon.PARSED_STYLE_LIST), [
45735
45735
  "points",
45736
45736
  "markerStart",
45737
45737
  "markerMid",
@@ -56566,6 +56566,12 @@ var attrs_table$4 = [
56566
56566
  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
56567
  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
56568
  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)"),
56569
+ 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"),
56570
+ SDATA(data_type_t.DTP_LIST, "rowless_layouts", sdata_flag_t.SDF_RD, JSON.stringify([
56571
+ "radial",
56572
+ "d3-force",
56573
+ "force-atlas2"
56574
+ ]), "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
56575
  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
56576
  SDATA(data_type_t.DTP_BOOLEAN, "confirm_delete_node", 0, true, "Ask confirmation before deleting a node"),
56571
56577
  SDATA(data_type_t.DTP_BOOLEAN, "confirm_unlink_edge", 0, true, "Ask confirmation before unlinking an edge"),
@@ -56817,6 +56823,8 @@ function register_layouts(gobj) {
56817
56823
  register(ExtensionCategory.LAYOUT, "manual", ManualLayout);
56818
56824
  register(ExtensionCategory.LAYOUT, "treedb-tree", TreedbTreeLayout);
56819
56825
  register(ExtensionCategory.LAYOUT, "treedb-compact", TreedbCompactLayout);
56826
+ register(ExtensionCategory.EDGE, "treedb-elbow-v", TreedbElbowV);
56827
+ register(ExtensionCategory.EDGE, "treedb-elbow-h", TreedbElbowH);
56820
56828
  register(ExtensionCategory.LAYOUT, "treedb-radial", TreedbRadialLayout);
56821
56829
  register(ExtensionCategory.NODE, "light", LightNode);
56822
56830
  register(ExtensionCategory.NODE, "treedb-card", TreedbCard);
@@ -57509,7 +57517,10 @@ function layout_direction(gobj) {
57509
57517
  return str_in_list(LR_LAYOUTS, gobj.priv.layout) ? "LR" : "TB";
57510
57518
  }
57511
57519
  function edge_type_for(gobj) {
57512
- return layout_direction(gobj) === "LR" ? "cubic-horizontal" : "cubic";
57520
+ let lr = layout_direction(gobj) === "LR";
57521
+ let rowless = gobj_read_attr(gobj, "rowless_layouts") || [];
57522
+ if (gobj_read_str_attr(gobj, "edge_shape") === "elbow" && !str_in_list(rowless, gobj.priv.layout)) return lr ? "treedb-elbow-h" : "treedb-elbow-v";
57523
+ return lr ? "cubic-horizontal" : "cubic";
57513
57524
  }
57514
57525
  /************************************************************
57515
57526
  * Where the ports sit, by direction: fkeys (the way in from a
@@ -58984,6 +58995,38 @@ var TreedbTreeLayout = class extends BaseLayout {
58984
58995
  return treedb_layout_output(layout_tree(input.nodes, input.edges, options));
58985
58996
  }
58986
58997
  };
58998
+ /************************************************************
58999
+ * An ELBOW edge, the way mxGraph drew a tree: straight out of
59000
+ * the parent's port to the channel half way to the child, along
59001
+ * it, and straight into the child's port. The children of one
59002
+ * hook sit on one row and leave by one port, so they share the
59003
+ * channel and the lines read as one bus.
59004
+ *
59005
+ * Computed on every draw from where the two ends ARE, so a
59006
+ * dragged card, a fold or a layout needs nothing else. `-v` runs
59007
+ * between rows (top to bottom), `-h` between columns.
59008
+ ************************************************************/
59009
+ var ELBOW_RADIUS = 6;
59010
+ var TreedbElbowV = class extends Polyline {
59011
+ getControlPoints(attributes) {
59012
+ let [s, t] = this.getEndpoints(attributes, false);
59013
+ let mid = (s[1] + t[1]) / 2;
59014
+ return [[s[0], mid], [t[0], mid]];
59015
+ }
59016
+ getKeyPath(attributes) {
59017
+ return super.getKeyPath(Object.assign({}, attributes, { radius: attributes.radius || ELBOW_RADIUS }));
59018
+ }
59019
+ };
59020
+ var TreedbElbowH = class extends Polyline {
59021
+ getControlPoints(attributes) {
59022
+ let [s, t] = this.getEndpoints(attributes, false);
59023
+ let mid = (s[0] + t[0]) / 2;
59024
+ return [[mid, s[1]], [mid, t[1]]];
59025
+ }
59026
+ getKeyPath(attributes) {
59027
+ return super.getKeyPath(Object.assign({}, attributes, { radius: attributes.radius || ELBOW_RADIUS }));
59028
+ }
59029
+ };
58987
59030
  var TreedbCompactLayout = class extends BaseLayout {
58988
59031
  async execute(data, options) {
58989
59032
  let input = treedb_layout_input(data);
@@ -63427,6 +63470,20 @@ function ac_set_node_labels(gobj, event, kw, src) {
63427
63470
  return 0;
63428
63471
  }
63429
63472
  /************************************************************
63473
+ * Curved or elbow edges (the host's toggle). Nothing moves: every
63474
+ * edge takes the type that goes with the shape and the layout,
63475
+ * and draws again.
63476
+ ************************************************************/
63477
+ function ac_set_edge_shape(gobj, event, kw, src) {
63478
+ let priv = gobj.priv;
63479
+ let shape = kw && kw.edge_shape === "elbow" ? "elbow" : "curved";
63480
+ gobj_write_str_attr(gobj, "edge_shape", shape);
63481
+ if (!priv.graph || !priv.graph_rendered) return 0;
63482
+ apply_layout_direction(gobj);
63483
+ graph_draw(gobj);
63484
+ return 0;
63485
+ }
63486
+ /************************************************************
63430
63487
  * ONE node opened or closed against the mode (a double click,
63431
63488
  * or the context menu). It holds still on screen while the
63432
63489
  * rest of the graph makes room, as a fold does.
@@ -63986,6 +64043,11 @@ function create_gclass$4(gclass_name) {
63986
64043
  ac_set_node_labels,
63987
64044
  null
63988
64045
  ],
64046
+ [
64047
+ "EV_SET_EDGE_SHAPE",
64048
+ ac_set_edge_shape,
64049
+ null
64050
+ ],
63989
64051
  [
63990
64052
  "EV_TOGGLE_NODE_MODE",
63991
64053
  ac_toggle_node_mode,
@@ -64107,6 +64169,7 @@ function create_gclass$4(gclass_name) {
64107
64169
  ["EV_SET_LOOSE_TOPICS", 0],
64108
64170
  ["EV_SET_NODE_MODE", 0],
64109
64171
  ["EV_SET_NODE_LABELS", 0],
64172
+ ["EV_SET_EDGE_SHAPE", 0],
64110
64173
  ["EV_TOGGLE_NODE_MODE", 0],
64111
64174
  ["EV_NODE_DBLCLICK", 0],
64112
64175
  ["EV_LEGEND_STATE", event_flag_t.EVF_OUTPUT_EVENT],
@@ -64183,6 +64246,7 @@ var attrs_table$3 = [
64183
64246
  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
64247
  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
64248
  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"),
64249
+ 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
64250
  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
64251
  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
64252
  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 +64339,7 @@ function mt_create$3(gobj) {
64275
64339
  loose_topics: gobj_read_attr(gobj, "loose_topics") || [],
64276
64340
  node_mode: gobj_read_str_attr(gobj, "node_mode") || "expanded",
64277
64341
  node_labels: gobj_read_bool_attr(gobj, "node_labels"),
64342
+ edge_shape: gobj_read_str_attr(gobj, "edge_shape") || "curved",
64278
64343
  camera: gobj_read_attr(gobj, "camera") || {}
64279
64344
  }, gobj);
64280
64345
  populate_nodes_tree_options(gobj);
@@ -64485,58 +64550,82 @@ function option_label(name) {
64485
64550
  * its context menu (the child's business).
64486
64551
  ************************************************************/
64487
64552
  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"
64553
+ return [
64554
+ [
64555
+ "div",
64556
+ {
64557
+ class: "GRAPH_NODE_MODE buttons has-addons",
64558
+ style: "margin:0 0 0 .5rem; flex:0 0 auto; flex-wrap:nowrap;"
64559
+ },
64560
+ NODE_MODES.map((mode) => {
64561
+ let key = NODE_MODE_LABELS[mode];
64562
+ return [
64563
+ "button",
64564
+ {
64565
+ class: "GRAPH_NODE_MODE_BTN button",
64566
+ type: "button",
64567
+ "data-mode": mode,
64568
+ style: {
64569
+ height: wide,
64570
+ width: "2.5em"
64571
+ },
64572
+ title: t(key),
64573
+ "data-i18n-title": key,
64574
+ "aria-label": t(key),
64575
+ "data-i18n-aria-label": key,
64576
+ "aria-pressed": "false"
64505
64577
  },
64506
- title: t(key),
64507
- "data-i18n-title": key,
64508
- "aria-label": t(key),
64509
- "data-i18n-aria-label": key,
64510
- "aria-pressed": "false"
64578
+ yui_toolbar_icon(NODE_MODE_ICONS[mode]),
64579
+ { click: (evt) => {
64580
+ evt.stopPropagation();
64581
+ gobj_send_event(gobj, "EV_SET_NODE_MODE", { node_mode: mode }, gobj);
64582
+ } }
64583
+ ];
64584
+ })
64585
+ ],
64586
+ [
64587
+ "button",
64588
+ {
64589
+ class: "GRAPH_NODE_LABELS button",
64590
+ type: "button",
64591
+ style: {
64592
+ height: wide,
64593
+ width: "2.5em"
64511
64594
  },
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"
64595
+ title: t("node labels"),
64596
+ "data-i18n-title": "node labels",
64597
+ "aria-label": t("node labels"),
64598
+ "data-i18n-aria-label": "node labels",
64599
+ "aria-pressed": "false"
64527
64600
  },
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
- ]];
64601
+ yui_toolbar_icon("yi-font"),
64602
+ { click: (evt) => {
64603
+ evt.stopPropagation();
64604
+ gobj_send_event(gobj, "EV_TOGGLE_NODE_LABELS", {}, gobj);
64605
+ } }
64606
+ ],
64607
+ [
64608
+ "button",
64609
+ {
64610
+ class: "GRAPH_EDGE_ELBOW button",
64611
+ type: "button",
64612
+ style: {
64613
+ height: wide,
64614
+ width: "2.5em"
64615
+ },
64616
+ title: t("elbow edges"),
64617
+ "data-i18n-title": "elbow edges",
64618
+ "aria-label": t("elbow edges"),
64619
+ "data-i18n-aria-label": "elbow edges",
64620
+ "aria-pressed": "false"
64621
+ },
64622
+ yui_toolbar_icon("yi-diagram-project"),
64623
+ { click: (evt) => {
64624
+ evt.stopPropagation();
64625
+ gobj_send_event(gobj, "EV_TOGGLE_EDGE_SHAPE", {}, gobj);
64626
+ } }
64627
+ ]
64628
+ ];
64540
64629
  }
64541
64630
  var NODE_MODES = [
64542
64631
  "expanded",
@@ -64569,6 +64658,16 @@ function refresh_node_shape_buttons(gobj, $root) {
64569
64658
  $labels.setAttribute("aria-pressed", labels ? "true" : "false");
64570
64659
  $labels.disabled = mode !== "shape";
64571
64660
  }
64661
+ let elbow = gobj_read_str_attr(gobj, "edge_shape") === "elbow";
64662
+ set_pressed_state($root, ".GRAPH_EDGE_ELBOW", elbow);
64663
+ let $elbow = $root.querySelector(".GRAPH_EDGE_ELBOW");
64664
+ if ($elbow) {
64665
+ $elbow.setAttribute("aria-pressed", elbow ? "true" : "false");
64666
+ let child = gobj.priv.gobj_nodes_tree;
64667
+ let rowless = child ? gobj_read_attr(child, "rowless_layouts") || [] : [];
64668
+ let layout = child ? gobj_read_str_attr(child, "layout") : "";
64669
+ $elbow.disabled = rowless.includes(layout);
64670
+ }
64572
64671
  }
64573
64672
  /************************************************************
64574
64673
  * The fold STEPPER: one level less, where the tree stands, one
@@ -65117,6 +65216,7 @@ function populate_nodes_tree_options(gobj) {
65117
65216
  let current_layout = gobj_read_str_attr(priv.gobj_nodes_tree, "layout");
65118
65217
  if (current_layout) $layout_select.value = current_layout;
65119
65218
  refresh_layout_stepper(gobj);
65219
+ refresh_node_shape_buttons(gobj, $container);
65120
65220
  }
65121
65221
  let $operation_mode_select = $container.querySelector(".GRAPH_MODE_SELECT");
65122
65222
  $operation_mode_select.value = priv.operation_mode;
@@ -65810,6 +65910,7 @@ function ac_set_layout(gobj, event, kw, src) {
65810
65910
  $layout_select.value = priv.layout;
65811
65911
  gobj_send_event(priv.gobj_nodes_tree, "EV_SET_LAYOUT", { layout }, gobj);
65812
65912
  refresh_layout_stepper(gobj);
65913
+ refresh_node_shape_buttons(gobj, priv.$container);
65813
65914
  return 0;
65814
65915
  }
65815
65916
  /************************************************************
@@ -66161,6 +66262,7 @@ function ac_layout_autoset(gobj, event, kw, src) {
66161
66262
  if (!$select || !layout) return 0;
66162
66263
  $select.value = layout;
66163
66264
  refresh_layout_stepper(gobj);
66265
+ refresh_node_shape_buttons(gobj, $container);
66164
66266
  return 0;
66165
66267
  }
66166
66268
  /************************************************************
@@ -66233,6 +66335,15 @@ function ac_set_node_mode(gobj, event, kw, src) {
66233
66335
  if (priv.gobj_nodes_tree) gobj_send_event(priv.gobj_nodes_tree, "EV_SET_NODE_MODE", { node_mode: mode }, gobj);
66234
66336
  return 0;
66235
66337
  }
66338
+ function ac_toggle_edge_shape(gobj, event, kw, src) {
66339
+ let priv = gobj.priv;
66340
+ let shape = gobj_read_str_attr(gobj, "edge_shape") === "elbow" ? "curved" : "elbow";
66341
+ gobj_write_str_attr(gobj, "edge_shape", shape);
66342
+ gobj_save_persistent_attrs(gobj, "edge_shape");
66343
+ refresh_node_shape_buttons(gobj, priv.$container);
66344
+ if (priv.gobj_nodes_tree) gobj_send_event(priv.gobj_nodes_tree, "EV_SET_EDGE_SHAPE", { edge_shape: shape }, gobj);
66345
+ return 0;
66346
+ }
66236
66347
  function ac_toggle_node_labels(gobj, event, kw, src) {
66237
66348
  let priv = gobj.priv;
66238
66349
  let labels = !gobj_read_bool_attr(gobj, "node_labels");
@@ -66477,6 +66588,11 @@ function create_gclass$3(gclass_name) {
66477
66588
  ac_toggle_node_labels,
66478
66589
  null
66479
66590
  ],
66591
+ [
66592
+ "EV_TOGGLE_EDGE_SHAPE",
66593
+ ac_toggle_edge_shape,
66594
+ null
66595
+ ],
66480
66596
  [
66481
66597
  "EV_LAYOUT_AUTOSET",
66482
66598
  ac_layout_autoset,
@@ -66554,6 +66670,7 @@ function create_gclass$3(gclass_name) {
66554
66670
  ["EV_COLLAPSE_LEVEL", 0],
66555
66671
  ["EV_SET_NODE_MODE", 0],
66556
66672
  ["EV_TOGGLE_NODE_LABELS", 0],
66673
+ ["EV_TOGGLE_EDGE_SHAPE", 0],
66557
66674
  ["EV_FIND_RESULT", 0],
66558
66675
  ["EV_LAYOUT_AUTOSET", 0],
66559
66676
  ["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.149",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -146,6 +146,7 @@ import {
146
146
  HTML,
147
147
  Toolbar,
148
148
  register,
149
+ Polyline,
149
150
  } from '@antv/g6';
150
151
 
151
152
  import {Circle as CircleGeometry, Rect as RectGeometry} from '@antv/g';
@@ -456,6 +457,8 @@ SDATA(data_type_t.DTP_LIST, "loose_topics", 0, "[]", "Topics whos
456
457
 
457
458
  /*---------------- Node shape ----------------*/
458
459
  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)"),
460
+ 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"),
461
+ 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
462
  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
463
  SDATA(data_type_t.DTP_BOOLEAN, "confirm_delete_node", 0, true, "Ask confirmation before deleting a node"),
461
464
  SDATA(data_type_t.DTP_BOOLEAN, "confirm_unlink_edge", 0, true, "Ask confirmation before unlinking an edge"),
@@ -936,6 +939,8 @@ function register_layouts(gobj)
936
939
  register(ExtensionCategory.LAYOUT, 'manual', ManualLayout);
937
940
  register(ExtensionCategory.LAYOUT, 'treedb-tree', TreedbTreeLayout);
938
941
  register(ExtensionCategory.LAYOUT, 'treedb-compact', TreedbCompactLayout);
942
+ register(ExtensionCategory.EDGE, 'treedb-elbow-v', TreedbElbowV);
943
+ register(ExtensionCategory.EDGE, 'treedb-elbow-h', TreedbElbowH);
939
944
  register(ExtensionCategory.LAYOUT, 'treedb-radial', TreedbRadialLayout);
940
945
  register(ExtensionCategory.NODE, 'light', LightNode);
941
946
  register(ExtensionCategory.NODE, 'treedb-card', TreedbCard);
@@ -2036,9 +2041,17 @@ function layout_direction(gobj)
2036
2041
  return str_in_list(LR_LAYOUTS, gobj.priv.layout)? "LR" : "TB";
2037
2042
  }
2038
2043
 
2044
+ /* A curve, or an elbow when the reader asked for one and the layout
2045
+ * has rows to run it between. */
2039
2046
  function edge_type_for(gobj)
2040
2047
  {
2041
- return (layout_direction(gobj) === "LR")? "cubic-horizontal" : "cubic";
2048
+ let lr = (layout_direction(gobj) === "LR");
2049
+ let rowless = gobj_read_attr(gobj, "rowless_layouts") || [];
2050
+ if(gobj_read_str_attr(gobj, "edge_shape") === "elbow" &&
2051
+ !str_in_list(rowless, gobj.priv.layout)) {
2052
+ return lr? "treedb-elbow-h" : "treedb-elbow-v";
2053
+ }
2054
+ return lr? "cubic-horizontal" : "cubic";
2042
2055
  }
2043
2056
 
2044
2057
  /************************************************************
@@ -4199,6 +4212,47 @@ class TreedbTreeLayout extends BaseLayout
4199
4212
  }
4200
4213
  }
4201
4214
 
4215
+ /************************************************************
4216
+ * An ELBOW edge, the way mxGraph drew a tree: straight out of
4217
+ * the parent's port to the channel half way to the child, along
4218
+ * it, and straight into the child's port. The children of one
4219
+ * hook sit on one row and leave by one port, so they share the
4220
+ * channel and the lines read as one bus.
4221
+ *
4222
+ * Computed on every draw from where the two ends ARE, so a
4223
+ * dragged card, a fold or a layout needs nothing else. `-v` runs
4224
+ * between rows (top to bottom), `-h` between columns.
4225
+ ************************************************************/
4226
+ const ELBOW_RADIUS = 6;
4227
+
4228
+ class TreedbElbowV extends Polyline
4229
+ {
4230
+ getControlPoints(attributes) {
4231
+ let [s, t] = this.getEndpoints(attributes, false);
4232
+ let mid = (s[1] + t[1]) / 2;
4233
+ return [[s[0], mid], [t[0], mid]];
4234
+ }
4235
+
4236
+ getKeyPath(attributes) {
4237
+ return super.getKeyPath(Object.assign({}, attributes,
4238
+ {radius: attributes.radius || ELBOW_RADIUS}));
4239
+ }
4240
+ }
4241
+
4242
+ class TreedbElbowH extends Polyline
4243
+ {
4244
+ getControlPoints(attributes) {
4245
+ let [s, t] = this.getEndpoints(attributes, false);
4246
+ let mid = (s[0] + t[0]) / 2;
4247
+ return [[mid, s[1]], [mid, t[1]]];
4248
+ }
4249
+
4250
+ getKeyPath(attributes) {
4251
+ return super.getKeyPath(Object.assign({}, attributes,
4252
+ {radius: attributes.radius || ELBOW_RADIUS}));
4253
+ }
4254
+ }
4255
+
4202
4256
  class TreedbCompactLayout extends BaseLayout
4203
4257
  {
4204
4258
  async execute(data, options) {
@@ -10875,6 +10929,25 @@ function ac_set_node_labels(gobj, event, kw, src)
10875
10929
  return 0;
10876
10930
  }
10877
10931
 
10932
+ /************************************************************
10933
+ * Curved or elbow edges (the host's toggle). Nothing moves: every
10934
+ * edge takes the type that goes with the shape and the layout,
10935
+ * and draws again.
10936
+ ************************************************************/
10937
+ function ac_set_edge_shape(gobj, event, kw, src)
10938
+ {
10939
+ let priv = gobj.priv;
10940
+
10941
+ let shape = (kw && kw.edge_shape === "elbow")? "elbow" : "curved";
10942
+ gobj_write_str_attr(gobj, "edge_shape", shape);
10943
+ if(!priv.graph || !priv.graph_rendered) {
10944
+ return 0; /* every edge is typed as it is drawn */
10945
+ }
10946
+ apply_layout_direction(gobj);
10947
+ graph_draw(gobj);
10948
+ return 0;
10949
+ }
10950
+
10878
10951
  /************************************************************
10879
10952
  * ONE node opened or closed against the mode (a double click,
10880
10953
  * or the context menu). It holds still on screen while the
@@ -11518,6 +11591,7 @@ function create_gclass(gclass_name)
11518
11591
  ["EV_SET_LOOSE_TOPICS", ac_set_loose_topics, null],
11519
11592
  ["EV_SET_NODE_MODE", ac_set_node_mode, null],
11520
11593
  ["EV_SET_NODE_LABELS", ac_set_node_labels, null],
11594
+ ["EV_SET_EDGE_SHAPE", ac_set_edge_shape, null],
11521
11595
  ["EV_TOGGLE_NODE_MODE", ac_toggle_node_mode, null],
11522
11596
  ["EV_NODE_DBLCLICK", ac_node_dblclick, null],
11523
11597
  ["EV_CENTER", ac_center, null],
@@ -11585,6 +11659,7 @@ function create_gclass(gclass_name)
11585
11659
  ["EV_SET_LOOSE_TOPICS", 0],
11586
11660
  ["EV_SET_NODE_MODE", 0],
11587
11661
  ["EV_SET_NODE_LABELS", 0],
11662
+ ["EV_SET_EDGE_SHAPE", 0],
11588
11663
  ["EV_TOGGLE_NODE_MODE", 0],
11589
11664
  ["EV_NODE_DBLCLICK", 0],
11590
11665
  ["EV_LEGEND_STATE", event_flag_t.EVF_OUTPUT_EVENT],
@@ -134,6 +134,7 @@ SDATA(data_type_t.DTP_LIST, "hidden_topics", sdata_flag_t.SDF_PERSIST, "[
134
134
  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"),
135
135
  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"),
136
136
  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"),
137
+ 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"),
137
138
  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"),
138
139
  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"),
139
140
  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"),
@@ -283,6 +284,7 @@ function mt_create(gobj)
283
284
  loose_topics: gobj_read_attr(gobj, "loose_topics") || [],
284
285
  node_mode: gobj_read_str_attr(gobj, "node_mode") || "expanded",
285
286
  node_labels: gobj_read_bool_attr(gobj, "node_labels"),
287
+ edge_shape: gobj_read_str_attr(gobj, "edge_shape") || "curved",
286
288
  camera: gobj_read_attr(gobj, "camera") || {},
287
289
  },
288
290
  gobj
@@ -625,6 +627,22 @@ function node_shape_items(gobj, wide)
625
627
  }
626
628
  }
627
629
  ],
630
+ /* How the lines are drawn: curves, or the elbows of an org
631
+ * chart. A toggle, so a pressed pill; disabled on a layout
632
+ * with no rows, where the edges stay curved anyway. */
633
+ ['button', {class: 'GRAPH_EDGE_ELBOW button', type: 'button',
634
+ style: {height: wide, width: '2.5em'},
635
+ title: t('elbow edges'), 'data-i18n-title': 'elbow edges',
636
+ 'aria-label': t('elbow edges'), 'data-i18n-aria-label': 'elbow edges',
637
+ 'aria-pressed': 'false'},
638
+ yui_toolbar_icon('yi-diagram-project'),
639
+ {
640
+ click: (evt) => {
641
+ evt.stopPropagation();
642
+ gobj_send_event(gobj, "EV_TOGGLE_EDGE_SHAPE", {}, gobj);
643
+ }
644
+ }
645
+ ],
628
646
  ];
629
647
  }
630
648
 
@@ -672,6 +690,17 @@ function refresh_node_shape_buttons(gobj, $root)
672
690
  $labels.setAttribute('aria-pressed', labels? 'true' : 'false');
673
691
  $labels.disabled = (mode !== 'shape');
674
692
  }
693
+
694
+ let elbow = (gobj_read_str_attr(gobj, "edge_shape") === "elbow");
695
+ set_pressed_state($root, '.GRAPH_EDGE_ELBOW', elbow);
696
+ let $elbow = $root.querySelector('.GRAPH_EDGE_ELBOW');
697
+ if($elbow) {
698
+ $elbow.setAttribute('aria-pressed', elbow? 'true' : 'false');
699
+ let child = gobj.priv.gobj_nodes_tree;
700
+ let rowless = child? (gobj_read_attr(child, "rowless_layouts") || []) : [];
701
+ let layout = child? gobj_read_str_attr(child, "layout") : "";
702
+ $elbow.disabled = rowless.includes(layout);
703
+ }
675
704
  }
676
705
 
677
706
  /************************************************************
@@ -1319,6 +1348,7 @@ function populate_nodes_tree_options(gobj)
1319
1348
  $layout_select.value = current_layout;
1320
1349
  }
1321
1350
  refresh_layout_stepper(gobj);
1351
+ refresh_node_shape_buttons(gobj, $container);
1322
1352
  }
1323
1353
 
1324
1354
  // Restore persisted operation_mode selection
@@ -2614,6 +2644,7 @@ function ac_set_layout(gobj, event, kw, src)
2614
2644
  gobj
2615
2645
  );
2616
2646
  refresh_layout_stepper(gobj);
2647
+ refresh_node_shape_buttons(gobj, priv.$container);
2617
2648
 
2618
2649
  return 0;
2619
2650
  }
@@ -3116,6 +3147,7 @@ function ac_layout_autoset(gobj, event, kw, src)
3116
3147
  }
3117
3148
  $select.value = layout;
3118
3149
  refresh_layout_stepper(gobj);
3150
+ refresh_node_shape_buttons(gobj, $container);
3119
3151
  return 0;
3120
3152
  }
3121
3153
 
@@ -3213,6 +3245,20 @@ function ac_set_node_mode(gobj, event, kw, src)
3213
3245
  return 0;
3214
3246
  }
3215
3247
 
3248
+ function ac_toggle_edge_shape(gobj, event, kw, src)
3249
+ {
3250
+ let priv = gobj.priv;
3251
+ let shape = (gobj_read_str_attr(gobj, "edge_shape") === "elbow")? "curved" : "elbow";
3252
+
3253
+ gobj_write_str_attr(gobj, "edge_shape", shape);
3254
+ gobj_save_persistent_attrs(gobj, "edge_shape");
3255
+ refresh_node_shape_buttons(gobj, priv.$container);
3256
+ if(priv.gobj_nodes_tree) {
3257
+ gobj_send_event(priv.gobj_nodes_tree, "EV_SET_EDGE_SHAPE", {edge_shape: shape}, gobj);
3258
+ }
3259
+ return 0;
3260
+ }
3261
+
3216
3262
  function ac_toggle_node_labels(gobj, event, kw, src)
3217
3263
  {
3218
3264
  let priv = gobj.priv;
@@ -3395,6 +3441,7 @@ function create_gclass(gclass_name)
3395
3441
  ["EV_COLLAPSE_LEVEL", ac_fold_level_step, null],
3396
3442
  ["EV_SET_NODE_MODE", ac_set_node_mode, null],
3397
3443
  ["EV_TOGGLE_NODE_LABELS", ac_toggle_node_labels, null],
3444
+ ["EV_TOGGLE_EDGE_SHAPE", ac_toggle_edge_shape, null],
3398
3445
  ["EV_LAYOUT_AUTOSET", ac_layout_autoset, null],
3399
3446
  ["EV_CAMERA_CHANGED", ac_camera_changed, null],
3400
3447
  ["EV_LEGEND_TOPIC", ac_legend_topic, null],
@@ -3441,6 +3488,7 @@ function create_gclass(gclass_name)
3441
3488
  ["EV_COLLAPSE_LEVEL", 0],
3442
3489
  ["EV_SET_NODE_MODE", 0],
3443
3490
  ["EV_TOGGLE_NODE_LABELS", 0],
3491
+ ["EV_TOGGLE_EDGE_SHAPE", 0],
3444
3492
  ["EV_FIND_RESULT", 0],
3445
3493
  ["EV_LAYOUT_AUTOSET", 0],
3446
3494
  ["EV_CAMERA_CHANGED", 0],
package/src/yui_icons.css CHANGED
@@ -444,3 +444,9 @@
444
444
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M256 512a256 256 0 1 0 0-512 256 256 0 1 0 0 512zm11.3-387.3l104 104c4.6 4.6 5.9 11.5 3.5 17.4S366.5 256 360 256l-56 0 0 96c0 17.7-14.3 32-32 32l-32 0c-17.7 0-32-14.3-32-32l0-96-56 0c-6.5 0-12.3-3.9-14.8-9.9s-1.1-12.9 3.5-17.4l104-104c6.2-6.2 16.4-6.2 22.6 0z'/%3E%3C/svg%3E");
445
445
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M256 512a256 256 0 1 0 0-512 256 256 0 1 0 0 512zm11.3-387.3l104 104c4.6 4.6 5.9 11.5 3.5 17.4S366.5 256 360 256l-56 0 0 96c0 17.7-14.3 32-32 32l-32 0c-17.7 0-32-14.3-32-32l0-96-56 0c-6.5 0-12.3-3.9-14.8-9.9s-1.1-12.9 3.5-17.4l104-104c6.2-6.2 16.4-6.2 22.6 0z'/%3E%3C/svg%3E");
446
446
  }
447
+
448
+ /* Elbow edges: boxes joined by right-angled lines (FontAwesome Free 7.3.1). */
449
+ .yi-diagram-project::before {
450
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M0 80C0 53.5 21.5 32 48 32l96 0c26.5 0 48 21.5 48 48l0 16 128 0 0-16c0-26.5 21.5-48 48-48l96 0c26.5 0 48 21.5 48 48l0 96c0 26.5-21.5 48-48 48l-96 0c-26.5 0-48-21.5-48-48l0-16-128 0 0 16c0 7.3-1.7 14.3-4.6 20.5l68.6 91.5 80 0c26.5 0 48 21.5 48 48l0 96c0 26.5-21.5 48-48 48l-96 0c-26.5 0-48-21.5-48-48l0-96c0-7.3 1.7-14.3 4.6-20.5L128 224 48 224c-26.5 0-48-21.5-48-48L0 80z'/%3E%3C/svg%3E");
451
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M0 80C0 53.5 21.5 32 48 32l96 0c26.5 0 48 21.5 48 48l0 16 128 0 0-16c0-26.5 21.5-48 48-48l96 0c26.5 0 48 21.5 48 48l0 96c0 26.5-21.5 48-48 48l-96 0c-26.5 0-48-21.5-48-48l0-16-128 0 0 16c0 7.3-1.7 14.3-4.6 20.5l68.6 91.5 80 0c26.5 0 48 21.5 48 48l0 96c0 26.5-21.5 48-48 48l-96 0c-26.5 0-48-21.5-48-48l0-96c0-7.3 1.7-14.3 4.6-20.5L128 224 48 224c-26.5 0-48-21.5-48-48L0 80z'/%3E%3C/svg%3E");
452
+ }