@yuneta/gobj-ui 7.23.155 → 7.23.157
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 +5 -48
- package/dist/gobj-ui.cjs.js +21 -694
- package/dist/gobj-ui.es.js +22 -695
- package/package.json +1 -1
- package/src/c_g6_nodes_tree.js +10 -148
- package/src/c_yui_treedb_graph.js +0 -2
- package/src/treedb_elbow.js +13 -344
- package/src/treedb_elbow.test.js +1 -236
- package/src/treedb_layout.js +0 -390
- package/src/treedb_layout.test.js +1 -174
package/package.json
CHANGED
package/src/c_g6_nodes_tree.js
CHANGED
|
@@ -132,11 +132,8 @@ import {
|
|
|
132
132
|
fold_split_group_key,
|
|
133
133
|
fold_root_group_key,
|
|
134
134
|
} from "./treedb_fold_model.js";
|
|
135
|
-
import {layout_tree, layout_radial
|
|
136
|
-
import {
|
|
137
|
-
elbow_lane, elbow_rank, elbow_route, elbow_combs, curve_hits, rounded_path, CURVE_RADIUS,
|
|
138
|
-
ELBOW_STEP,
|
|
139
|
-
} from "./treedb_elbow.js";
|
|
135
|
+
import {layout_tree, layout_radial} from "./treedb_layout.js";
|
|
136
|
+
import {elbow_lane, elbow_route} from "./treedb_elbow.js";
|
|
140
137
|
|
|
141
138
|
import {
|
|
142
139
|
BaseLayout,
|
|
@@ -151,8 +148,6 @@ import {
|
|
|
151
148
|
Toolbar,
|
|
152
149
|
register,
|
|
153
150
|
Polyline,
|
|
154
|
-
Cubic,
|
|
155
|
-
CubicHorizontal,
|
|
156
151
|
} from '@antv/g6';
|
|
157
152
|
|
|
158
153
|
import {Circle as CircleGeometry, Rect as RectGeometry} from '@antv/g';
|
|
@@ -357,16 +352,6 @@ const _layouts = {
|
|
|
357
352
|
nodesep: 18,
|
|
358
353
|
ranksep: 90,
|
|
359
354
|
},
|
|
360
|
-
/* The same tree packed by CONTOUR (Moen, as mxGraph drew it): a
|
|
361
|
-
* closed branch sits beside an open one at the depth they share,
|
|
362
|
-
* instead of beside the whole block of the open one. Same rows
|
|
363
|
-
* and order as `treedb-tree` (treedb_layout.js). */
|
|
364
|
-
"compact-tree": {
|
|
365
|
-
type: 'treedb-compact',
|
|
366
|
-
direction: 'TB',
|
|
367
|
-
nodesep: 18,
|
|
368
|
-
ranksep: 90,
|
|
369
|
-
},
|
|
370
355
|
"dagre": {
|
|
371
356
|
type: 'dagre',
|
|
372
357
|
rankdir: 'LR',
|
|
@@ -944,11 +929,8 @@ function register_layouts(gobj)
|
|
|
944
929
|
_g6_extensions_registered = true;
|
|
945
930
|
register(ExtensionCategory.LAYOUT, 'manual', ManualLayout);
|
|
946
931
|
register(ExtensionCategory.LAYOUT, 'treedb-tree', TreedbTreeLayout);
|
|
947
|
-
register(ExtensionCategory.LAYOUT, 'treedb-compact', TreedbCompactLayout);
|
|
948
932
|
register(ExtensionCategory.EDGE, 'treedb-elbow-v', TreedbElbowV);
|
|
949
933
|
register(ExtensionCategory.EDGE, 'treedb-elbow-h', TreedbElbowH);
|
|
950
|
-
register(ExtensionCategory.EDGE, 'treedb-curve-v', TreedbCurveV);
|
|
951
|
-
register(ExtensionCategory.EDGE, 'treedb-curve-h', TreedbCurveH);
|
|
952
934
|
register(ExtensionCategory.LAYOUT, 'treedb-radial', TreedbRadialLayout);
|
|
953
935
|
register(ExtensionCategory.NODE, 'light', LightNode);
|
|
954
936
|
register(ExtensionCategory.NODE, 'treedb-card', TreedbCard);
|
|
@@ -2049,20 +2031,16 @@ function layout_direction(gobj)
|
|
|
2049
2031
|
return str_in_list(LR_LAYOUTS, gobj.priv.layout)? "LR" : "TB";
|
|
2050
2032
|
}
|
|
2051
2033
|
|
|
2052
|
-
/*
|
|
2053
|
-
*
|
|
2054
|
-
* cubic on a layout with no rows to go round by. */
|
|
2034
|
+
/* A curve, or an elbow when the reader asked for one and the layout
|
|
2035
|
+
* has rows to run it between. */
|
|
2055
2036
|
function edge_type_for(gobj)
|
|
2056
2037
|
{
|
|
2057
2038
|
let lr = (layout_direction(gobj) === "LR");
|
|
2058
2039
|
let rowless = gobj_read_attr(gobj, "rowless_layouts") || [];
|
|
2059
|
-
|
|
2060
|
-
|
|
2040
|
+
if(gobj_read_str_attr(gobj, "edge_shape") === "elbow" &&
|
|
2041
|
+
!str_in_list(rowless, gobj.priv.layout)) {
|
|
2061
2042
|
return lr? "treedb-elbow-h" : "treedb-elbow-v";
|
|
2062
2043
|
}
|
|
2063
|
-
if(rows) {
|
|
2064
|
-
return lr? "treedb-curve-h" : "treedb-curve-v";
|
|
2065
|
-
}
|
|
2066
2044
|
return lr? "cubic-horizontal" : "cubic";
|
|
2067
2045
|
}
|
|
2068
2046
|
|
|
@@ -4235,9 +4213,8 @@ class TreedbTreeLayout extends BaseLayout
|
|
|
4235
4213
|
* dragged card, a fold or a layout needs nothing else. `-v` runs
|
|
4236
4214
|
* between rows (top to bottom), `-h` between columns. An edge that
|
|
4237
4215
|
* does not run forward goes round the two cards, the edges joining
|
|
4238
|
-
* the same two cards take lanes,
|
|
4239
|
-
*
|
|
4240
|
-
* routed round it (treedb_elbow.js).
|
|
4216
|
+
* the same two cards take lanes, and a line that would cross any
|
|
4217
|
+
* other card is routed round it (treedb_elbow.js).
|
|
4241
4218
|
************************************************************/
|
|
4242
4219
|
const ELBOW_RADIUS = 6;
|
|
4243
4220
|
|
|
@@ -4309,73 +4286,12 @@ function elbow_lane_of(edge)
|
|
|
4309
4286
|
}
|
|
4310
4287
|
}
|
|
4311
4288
|
|
|
4312
|
-
/* Where `edge` stands among the forward edges sharing one of its
|
|
4313
|
-
* ends, for the stagger (treedb_elbow.js): the ones leaving its
|
|
4314
|
-
* source card, and the ones reaching its target card. How far each
|
|
4315
|
-
* runs ACROSS is measured from the real port it leaves by -- the
|
|
4316
|
-
* hooks of one card sit at different places along its edge. */
|
|
4317
|
-
function elbow_stagger_of(edge, vertical)
|
|
4318
|
-
{
|
|
4319
|
-
let graph = edge.context && edge.context.graph;
|
|
4320
|
-
let element = edge.context && edge.context.element;
|
|
4321
|
-
let s = edge.sourceNode && edge.sourceNode.id;
|
|
4322
|
-
let t = edge.targetNode && edge.targetNode.id;
|
|
4323
|
-
if(!graph || !element || s === undefined || t === undefined) {
|
|
4324
|
-
return null;
|
|
4325
|
-
}
|
|
4326
|
-
let across = vertical? 0 : 1;
|
|
4327
|
-
let along = vertical? 1 : 0;
|
|
4328
|
-
let pos = (id) => graph.getElementPosition(id);
|
|
4329
|
-
let port_of = (node_id, key) => {
|
|
4330
|
-
let node = element.getElement(node_id);
|
|
4331
|
-
let ports = (node && typeof node.getPorts === 'function')? node.getPorts() : {};
|
|
4332
|
-
let port = key? ports[key] : null;
|
|
4333
|
-
return port? port.getPosition() : pos(node_id);
|
|
4334
|
-
};
|
|
4335
|
-
try {
|
|
4336
|
-
let ps = pos(s);
|
|
4337
|
-
let pt = pos(t);
|
|
4338
|
-
let leaving = graph.getRelatedEdgesData(s, 'out')
|
|
4339
|
-
.filter((d) => d.source === s && pos(d.target)[along] > ps[along]);
|
|
4340
|
-
/* The cards they reach, for the combs of a stack; a comb's
|
|
4341
|
-
* run ends at its lane, so that is what its span measures. */
|
|
4342
|
-
let combs = elbow_combs(leaving.map((d) => ({
|
|
4343
|
-
id: d.id,
|
|
4344
|
-
box: elbow_box(element.getElement(d.target)),
|
|
4345
|
-
})), vertical);
|
|
4346
|
-
let dep = leaving.map((d) => {
|
|
4347
|
-
let comb = combs.get(d.id);
|
|
4348
|
-
let far = comb? comb.lane : pos(d.target)[across];
|
|
4349
|
-
return {
|
|
4350
|
-
id: d.id,
|
|
4351
|
-
span: Math.abs(far - port_of(s, d.style && d.style.sourcePort)[across]),
|
|
4352
|
-
};
|
|
4353
|
-
});
|
|
4354
|
-
let arr = graph.getRelatedEdgesData(t, 'in')
|
|
4355
|
-
.filter((d) => d.target === t && pos(d.source)[along] < pt[along])
|
|
4356
|
-
.map((d) => ({
|
|
4357
|
-
id: d.id,
|
|
4358
|
-
span: Math.abs(port_of(d.source, d.style && d.style.sourcePort)[across] -
|
|
4359
|
-
pt[across]),
|
|
4360
|
-
}));
|
|
4361
|
-
return {
|
|
4362
|
-
dep: elbow_rank(dep, edge.id),
|
|
4363
|
-
arr: elbow_rank(arr, edge.id),
|
|
4364
|
-
comb: combs.get(edge.id) || null,
|
|
4365
|
-
};
|
|
4366
|
-
} catch(e) {
|
|
4367
|
-
log_error(`elbow edge ${edge.id}: cannot read the edges sharing its ends: ${e}`);
|
|
4368
|
-
return null;
|
|
4369
|
-
}
|
|
4370
|
-
}
|
|
4371
|
-
|
|
4372
4289
|
class TreedbElbowV extends Polyline
|
|
4373
4290
|
{
|
|
4374
4291
|
getControlPoints(attributes) {
|
|
4375
4292
|
let [s, t] = this.getEndpoints(attributes, false);
|
|
4376
4293
|
return elbow_route(s, t, elbow_box(this.sourceNode), elbow_box(this.targetNode),
|
|
4377
|
-
elbow_lane_of(this), true, elbow_obstacles(this)
|
|
4378
|
-
elbow_stagger_of(this, true));
|
|
4294
|
+
elbow_lane_of(this), true, elbow_obstacles(this));
|
|
4379
4295
|
}
|
|
4380
4296
|
|
|
4381
4297
|
getKeyPath(attributes) {
|
|
@@ -4389,8 +4305,7 @@ class TreedbElbowH extends Polyline
|
|
|
4389
4305
|
getControlPoints(attributes) {
|
|
4390
4306
|
let [s, t] = this.getEndpoints(attributes, false);
|
|
4391
4307
|
return elbow_route(s, t, elbow_box(this.sourceNode), elbow_box(this.targetNode),
|
|
4392
|
-
elbow_lane_of(this), false, elbow_obstacles(this)
|
|
4393
|
-
elbow_stagger_of(this, false));
|
|
4308
|
+
elbow_lane_of(this), false, elbow_obstacles(this));
|
|
4394
4309
|
}
|
|
4395
4310
|
|
|
4396
4311
|
getKeyPath(attributes) {
|
|
@@ -4399,59 +4314,6 @@ class TreedbElbowH extends Polyline
|
|
|
4399
4314
|
}
|
|
4400
4315
|
}
|
|
4401
4316
|
|
|
4402
|
-
/************************************************************
|
|
4403
|
-
* A CURVED edge that keeps off the cards. G6's cubic, as before,
|
|
4404
|
-
* unless that very curve -- sampled, not guessed from the
|
|
4405
|
-
* rectangle round it -- would cross a card other than its own two,
|
|
4406
|
-
* or when it does not run forward and so would cross its own:
|
|
4407
|
-
* the card above a lower row of a stack, a row between a parent
|
|
4408
|
-
* and a far child. Then it goes the way an elbow would (the comb,
|
|
4409
|
-
* the detour, the route: the same lanes and staggered turns) with
|
|
4410
|
-
* round corners, so it still reads as a curve. `-v` for rows
|
|
4411
|
-
* stacked top to bottom, `-h` for columns.
|
|
4412
|
-
************************************************************/
|
|
4413
|
-
function curve_key_path(edge, attributes, vertical, plain)
|
|
4414
|
-
{
|
|
4415
|
-
let [s, t] = edge.getEndpoints(attributes, false);
|
|
4416
|
-
let along = vertical? 1 : 0;
|
|
4417
|
-
let box_s = elbow_box(edge.sourceNode);
|
|
4418
|
-
let box_t = elbow_box(edge.targetNode);
|
|
4419
|
-
let boxes = elbow_obstacles(edge);
|
|
4420
|
-
/* An edge that does not run forward -- one of every reciprocal
|
|
4421
|
-
* pair, an edge within one row -- runs through its OWN two cards
|
|
4422
|
-
* as a cubic, which curve_hits does not look at: it goes round
|
|
4423
|
-
* them, as its elbow does. */
|
|
4424
|
-
let forward = (t[along] - s[along] > 2 * ELBOW_STEP);
|
|
4425
|
-
if(forward && !curve_hits(plain, boxes, box_s, box_t)) {
|
|
4426
|
-
return plain;
|
|
4427
|
-
}
|
|
4428
|
-
let pts = elbow_route(s, t, box_s, box_t, elbow_lane_of(edge), vertical, boxes,
|
|
4429
|
-
elbow_stagger_of(edge, vertical));
|
|
4430
|
-
return rounded_path([s, ...pts, t], CURVE_RADIUS);
|
|
4431
|
-
}
|
|
4432
|
-
|
|
4433
|
-
class TreedbCurveV extends Cubic
|
|
4434
|
-
{
|
|
4435
|
-
getKeyPath(attributes) {
|
|
4436
|
-
return curve_key_path(this, attributes, true, super.getKeyPath(attributes));
|
|
4437
|
-
}
|
|
4438
|
-
}
|
|
4439
|
-
|
|
4440
|
-
class TreedbCurveH extends CubicHorizontal
|
|
4441
|
-
{
|
|
4442
|
-
getKeyPath(attributes) {
|
|
4443
|
-
return curve_key_path(this, attributes, false, super.getKeyPath(attributes));
|
|
4444
|
-
}
|
|
4445
|
-
}
|
|
4446
|
-
|
|
4447
|
-
class TreedbCompactLayout extends BaseLayout
|
|
4448
|
-
{
|
|
4449
|
-
async execute(data, options) {
|
|
4450
|
-
let input = treedb_layout_input(data);
|
|
4451
|
-
return treedb_layout_output(layout_compact(input.nodes, input.edges, options));
|
|
4452
|
-
}
|
|
4453
|
-
}
|
|
4454
|
-
|
|
4455
4317
|
class TreedbRadialLayout extends BaseLayout
|
|
4456
4318
|
{
|
|
4457
4319
|
async execute(data, options) {
|
package/src/treedb_elbow.js
CHANGED
|
@@ -25,25 +25,6 @@
|
|
|
25
25
|
* the order the edges were made, so a new edge never moves the
|
|
26
26
|
* ones already drawn.
|
|
27
27
|
*
|
|
28
|
-
* - Several forward edges sharing one END: the children of one
|
|
29
|
-
* card, or the parents of one card (all its fkeys enter by one
|
|
30
|
-
* point). They are STAGGERED: each turns at its own height in
|
|
31
|
-
* the channel, so their runs lie side by side instead of on top
|
|
32
|
-
* of each other. Leaving a card, the one that runs farthest
|
|
33
|
-
* turns highest; reaching a card, the one that comes from
|
|
34
|
-
* farthest turns lowest -- the two orders in which the lines of
|
|
35
|
-
* one end nest without crossing.
|
|
36
|
-
*
|
|
37
|
-
* - A card in a lower row of a STACK (treedb_layout.js lays a
|
|
38
|
-
* run of leaf children out as two columns with a corridor
|
|
39
|
-
* between them): the card above it stands in the way of the
|
|
40
|
-
* channel, so its edge runs down the corridor in a lane of
|
|
41
|
-
* its own -- a COMB -- and turns into the gap above its card.
|
|
42
|
-
*
|
|
43
|
-
* And for CURVED edges (curve_hits, rounded_path): a curve
|
|
44
|
-
* that would cross another card follows the same way an elbow
|
|
45
|
-
* would, with round corners, so it reads as a curve.
|
|
46
|
-
*
|
|
47
28
|
* Written for the top-down reading; left to right is the same
|
|
48
29
|
* geometry with the axes swapped.
|
|
49
30
|
*
|
|
@@ -51,12 +32,9 @@
|
|
|
51
32
|
* All Rights Reserved.
|
|
52
33
|
***********************************************************************/
|
|
53
34
|
|
|
54
|
-
import {STACK_GAP, STACK_LANE_PAD, STACK_LANE_STEP, STACK_TRUNK_MIN} from "./treedb_layout.js";
|
|
55
|
-
|
|
56
35
|
export const ELBOW_STEP = 10; /* between two lanes */
|
|
57
36
|
export const ELBOW_JETTY = 16; /* straight run out of a port before a detour turns */
|
|
58
37
|
export const ELBOW_CLEAR = 24; /* how far outside the two cards a detour runs */
|
|
59
|
-
export const ELBOW_STAGGER_PAD = 6; /* staggered turns keep this far from the rows */
|
|
60
38
|
|
|
61
39
|
/************************************************************
|
|
62
40
|
* The lane of `id` among `ids`, the edges joining the same two
|
|
@@ -76,24 +54,6 @@ export function elbow_lane(ids, id)
|
|
|
76
54
|
return side * Math.ceil(k / 2);
|
|
77
55
|
}
|
|
78
56
|
|
|
79
|
-
/************************************************************
|
|
80
|
-
* The rank of `id` among `items` ([{id, span}]): the forward
|
|
81
|
-
* edges that share one end with it, each with how far it runs
|
|
82
|
-
* ACROSS. Farthest first; a tie goes to the edge made first.
|
|
83
|
-
* Returns {rank, count}.
|
|
84
|
-
************************************************************/
|
|
85
|
-
export function elbow_rank(items, id)
|
|
86
|
-
{
|
|
87
|
-
let sorted = items.slice().sort((a, b) => {
|
|
88
|
-
if(b.span !== a.span) {
|
|
89
|
-
return b.span - a.span;
|
|
90
|
-
}
|
|
91
|
-
return String(a.id).localeCompare(String(b.id), undefined, {numeric: true});
|
|
92
|
-
});
|
|
93
|
-
let rank = sorted.findIndex((it) => it.id === id);
|
|
94
|
-
return {rank: (rank < 0)? 0 : rank, count: sorted.length};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
57
|
function swap(p)
|
|
98
58
|
{
|
|
99
59
|
return [p[1], p[0]];
|
|
@@ -104,154 +64,6 @@ function swap_box(b)
|
|
|
104
64
|
return {x1: b.y1, y1: b.x1, x2: b.y2, y2: b.x2};
|
|
105
65
|
}
|
|
106
66
|
|
|
107
|
-
/* Where a forward edge turns between `lo` and `hi`: the middle, or
|
|
108
|
-
* its staggered place among the edges sharing one of its ends --
|
|
109
|
-
* leaving, the farthest highest; arriving, the farthest lowest. */
|
|
110
|
-
function staggered_turn(lo, hi, st)
|
|
111
|
-
{
|
|
112
|
-
let mid = (lo + hi) / 2;
|
|
113
|
-
let group = null;
|
|
114
|
-
let k = 0;
|
|
115
|
-
if(st.dep && st.dep.count > 1) {
|
|
116
|
-
group = st.dep;
|
|
117
|
-
k = st.dep.rank;
|
|
118
|
-
} else if(st.arr && st.arr.count > 1) {
|
|
119
|
-
group = st.arr;
|
|
120
|
-
k = st.arr.count - 1 - st.arr.rank;
|
|
121
|
-
}
|
|
122
|
-
if(group) {
|
|
123
|
-
let room = Math.max(0, hi - lo - 2 * ELBOW_STAGGER_PAD);
|
|
124
|
-
let step = Math.min(ELBOW_STEP, room / (group.count - 1));
|
|
125
|
-
mid = mid - (group.count - 1) * step / 2 + k * step;
|
|
126
|
-
}
|
|
127
|
-
return mid;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/************************************************************
|
|
131
|
-
* The combs of the stacks under one card.
|
|
132
|
-
*
|
|
133
|
-
* treedb_layout.js lays a run of leaf children out as two columns
|
|
134
|
-
* with a corridor between them. A card in the first row is reached
|
|
135
|
-
* from the channel like any other; one in a lower row cannot be --
|
|
136
|
-
* the card above it stands in the way -- so its edge runs down
|
|
137
|
-
* the corridor in a LANE of its own and turns into the gap above
|
|
138
|
-
* its card, which it shares only with the other card of its row,
|
|
139
|
-
* reached from the other side.
|
|
140
|
-
*
|
|
141
|
-
* targets [{id, box}]: the forward edges leaving one card,
|
|
142
|
-
* each with the box of the card it reaches
|
|
143
|
-
* vertical as in elbow_points; false swaps the boxes
|
|
144
|
-
*
|
|
145
|
-
* Returns Map id -> {lane, z, top}: the lane, the height of the
|
|
146
|
-
* gap it turns into, and the top of the stack's first row (where
|
|
147
|
-
* the channel over the stack ends). Only for a card that stands
|
|
148
|
-
* under another of the group, within a stack gap and lined up
|
|
149
|
-
* with it on the corridor side -- what a stack looks like.
|
|
150
|
-
*
|
|
151
|
-
* Lanes go outermost first, row by row: a line into a higher row
|
|
152
|
-
* keeps outside the lines going deeper, and none crosses another.
|
|
153
|
-
************************************************************/
|
|
154
|
-
export function elbow_combs(targets, vertical)
|
|
155
|
-
{
|
|
156
|
-
let list = vertical? targets : targets.map((it) => ({id: it.id, box: swap_box(it.box)}));
|
|
157
|
-
let cy = (b) => (b.y1 + b.y2) / 2;
|
|
158
|
-
let near = (a, b) => Math.abs(a - b) < 1.5;
|
|
159
|
-
|
|
160
|
-
let under = [];
|
|
161
|
-
for(let it of list) {
|
|
162
|
-
let b = it.box;
|
|
163
|
-
let above = null;
|
|
164
|
-
for(let o of list) {
|
|
165
|
-
let a = o.box;
|
|
166
|
-
if(o === it || a.y2 > b.y1 || b.y1 - a.y2 > STACK_GAP) {
|
|
167
|
-
continue;
|
|
168
|
-
}
|
|
169
|
-
if(!(near(a.x2, b.x2) || near(a.x1, b.x1))) {
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
172
|
-
if(!above || a.y2 > above.y2) {
|
|
173
|
-
above = a;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
if(!above) {
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
/* Its column: lined up on the corridor side, which is the
|
|
180
|
-
* right edge of the first column and the left edge of the
|
|
181
|
-
* second. Two cards of one width line up on both; then the
|
|
182
|
-
* card beside it in its row says. */
|
|
183
|
-
let side;
|
|
184
|
-
if(near(above.x2, b.x2) && !near(above.x1, b.x1)) {
|
|
185
|
-
side = 0;
|
|
186
|
-
} else if(near(above.x1, b.x1) && !near(above.x2, b.x2)) {
|
|
187
|
-
side = 1;
|
|
188
|
-
} else {
|
|
189
|
-
let left_of_it = list.some((o) => o !== it && near(cy(o.box), cy(b)) &&
|
|
190
|
-
o.box.x2 <= b.x1);
|
|
191
|
-
side = left_of_it? 1 : 0;
|
|
192
|
-
}
|
|
193
|
-
under.push({id: it.id, box: b, above: above, side: side});
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/* The corridor of a card: from the first column's edge to the
|
|
197
|
-
* second's, read off any row of the stack that has both. */
|
|
198
|
-
let corridor = (u) => {
|
|
199
|
-
if(u.side === 0) {
|
|
200
|
-
let l = u.box.x2;
|
|
201
|
-
let col = list.filter((o) => near(o.box.x2, l));
|
|
202
|
-
let r = Infinity;
|
|
203
|
-
for(let c of col) {
|
|
204
|
-
for(let o of list) {
|
|
205
|
-
if(o !== c && near(cy(o.box), cy(c.box)) && o.box.x1 >= l - 0.5) {
|
|
206
|
-
r = Math.min(r, o.box.x1);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
return {l: l, r: isFinite(r)? r : l + STACK_TRUNK_MIN, col: col};
|
|
211
|
-
}
|
|
212
|
-
let r = u.box.x1;
|
|
213
|
-
let col = list.filter((o) => near(o.box.x1, r));
|
|
214
|
-
let l = -Infinity;
|
|
215
|
-
for(let c of col) {
|
|
216
|
-
for(let o of list) {
|
|
217
|
-
if(o !== c && near(cy(o.box), cy(c.box)) && o.box.x2 <= r + 0.5) {
|
|
218
|
-
l = Math.max(l, o.box.x2);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
return {l: isFinite(l)? l : r - STACK_TRUNK_MIN, r: r, col: col};
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
let groups = new Map();
|
|
226
|
-
for(let u of under) {
|
|
227
|
-
let c = corridor(u);
|
|
228
|
-
u.top = Math.min(...c.col.map((o) => o.box.y1));
|
|
229
|
-
let key = `${Math.round(c.l)}|${Math.round(c.r)}`;
|
|
230
|
-
if(!groups.has(key)) {
|
|
231
|
-
groups.set(key, {l: c.l, r: c.r, left: [], right: []});
|
|
232
|
-
}
|
|
233
|
-
groups.get(key)[(u.side === 0)? 'left' : 'right'].push(u);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
let out = new Map();
|
|
237
|
-
for(let g of groups.values()) {
|
|
238
|
-
g.left.sort((a, b) => a.box.y1 - b.box.y1);
|
|
239
|
-
g.right.sort((a, b) => a.box.y1 - b.box.y1);
|
|
240
|
-
let n = g.left.length + g.right.length;
|
|
241
|
-
let room = g.r - g.l - 2 * STACK_LANE_PAD;
|
|
242
|
-
let step = (n > 1)? Math.max(0, Math.min(STACK_LANE_STEP, room / (n - 1))) : 0;
|
|
243
|
-
g.left.forEach((u, i) => {
|
|
244
|
-
out.set(u.id, {lane: g.l + STACK_LANE_PAD + i * step,
|
|
245
|
-
z: (u.above.y2 + u.box.y1) / 2, top: u.top});
|
|
246
|
-
});
|
|
247
|
-
g.right.forEach((u, j) => {
|
|
248
|
-
out.set(u.id, {lane: g.r - STACK_LANE_PAD - j * step,
|
|
249
|
-
z: (u.above.y2 + u.box.y1) / 2, top: u.top});
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
return out;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
67
|
/************************************************************
|
|
256
68
|
* The control points of an elbow from the port `s` to the port
|
|
257
69
|
* `t` ([x, y] each), knowing the boxes of the two cards
|
|
@@ -259,39 +71,21 @@ export function elbow_combs(targets, vertical)
|
|
|
259
71
|
*
|
|
260
72
|
* vertical the rows are stacked (top to bottom); false
|
|
261
73
|
* when they are side by side (left to right)
|
|
262
|
-
* stagger {dep, arr, comb}: the edge's rank among the
|
|
263
|
-
* forward edges leaving its source card and among
|
|
264
|
-
* those reaching its target card (elbow_rank), and
|
|
265
|
-
* its comb when the target is in a lower row of a
|
|
266
|
-
* stack (elbow_combs). Absent, the channel is the
|
|
267
|
-
* middle.
|
|
268
74
|
************************************************************/
|
|
269
|
-
export function elbow_points(s, t, box_s, box_t, lane, vertical
|
|
75
|
+
export function elbow_points(s, t, box_s, box_t, lane, vertical)
|
|
270
76
|
{
|
|
271
77
|
if(!vertical) {
|
|
272
|
-
return elbow_points(swap(s), swap(t), swap_box(box_s), swap_box(box_t), lane, true
|
|
273
|
-
|
|
78
|
+
return elbow_points(swap(s), swap(t), swap_box(box_s), swap_box(box_t), lane, true)
|
|
79
|
+
.map(swap);
|
|
274
80
|
}
|
|
275
81
|
|
|
276
82
|
let shift = lane * ELBOW_STEP;
|
|
277
83
|
|
|
278
|
-
/* Forward: the channel half way
|
|
279
|
-
*
|
|
280
|
-
* lane, and kept between the two rows. Into a lower row of a
|
|
281
|
-
* stack: along the channel to its corridor lane, down the lane,
|
|
282
|
-
* and into the gap above its card. */
|
|
84
|
+
/* Forward: the channel half way down, moved by the lane and
|
|
85
|
+
* kept strictly between the two ports. */
|
|
283
86
|
if(t[1] - s[1] > 2 * ELBOW_STEP) {
|
|
284
|
-
let
|
|
285
|
-
|
|
286
|
-
if(st.comb) {
|
|
287
|
-
let c = st.comb;
|
|
288
|
-
let turn = staggered_turn(lo, c.top, {dep: st.dep}) + shift;
|
|
289
|
-
turn = Math.min(Math.max(turn, lo + 4), c.top - 4);
|
|
290
|
-
return [[s[0], turn], [c.lane, turn], [c.lane, c.z], [t[0], c.z]];
|
|
291
|
-
}
|
|
292
|
-
let hi = Math.min(t[1], box_t.y1);
|
|
293
|
-
let mid = staggered_turn(lo, hi, st) + shift;
|
|
294
|
-
mid = Math.min(Math.max(mid, lo + 4), hi - 4);
|
|
87
|
+
let mid = (s[1] + t[1]) / 2 + shift;
|
|
88
|
+
mid = Math.min(Math.max(mid, s[1] + ELBOW_STEP), t[1] - ELBOW_STEP);
|
|
295
89
|
return [[s[0], mid], [t[0], mid]];
|
|
296
90
|
}
|
|
297
91
|
|
|
@@ -421,12 +215,8 @@ function heap_pop(h)
|
|
|
421
215
|
/************************************************************
|
|
422
216
|
* The shortest orthogonal line from `a` to `z` that enters none
|
|
423
217
|
* of `boxes` (already expanded by the clearance), a turn costing
|
|
424
|
-
* ROUTE_BEND. The line leaves `a`
|
|
425
|
-
*
|
|
426
|
-
* over it belong to the forward edges that share that port, which
|
|
427
|
-
* run along them to their staggered turns -- a route going down
|
|
428
|
-
* that column lay on top of them. So a routed line shares only
|
|
429
|
-
* the stub between the port and `a` (or `z`).
|
|
218
|
+
* ROUTE_BEND. The line leaves `a` going down and ends at `z`
|
|
219
|
+
* going down, since after `z` it drops into a port.
|
|
430
220
|
*
|
|
431
221
|
* The grid is SPARSE: only the lines of the boxes' borders and
|
|
432
222
|
* of the two ends, which is all an orthogonal shortest path ever
|
|
@@ -512,10 +302,7 @@ function orth_search(a, z, boxes)
|
|
|
512
302
|
let k = state >> 2;
|
|
513
303
|
let dir = state & 3;
|
|
514
304
|
if(k === goal) {
|
|
515
|
-
|
|
516
|
-
continue; /* down the port's own column: not allowed */
|
|
517
|
-
}
|
|
518
|
-
let total = cost + ROUTE_BEND; /* the turn into the port */
|
|
305
|
+
let total = cost + ((dir === 2)? 0 : ROUTE_BEND);
|
|
519
306
|
if(total < found_cost) {
|
|
520
307
|
found_cost = total;
|
|
521
308
|
found = state;
|
|
@@ -523,9 +310,6 @@ function orth_search(a, z, boxes)
|
|
|
523
310
|
continue;
|
|
524
311
|
}
|
|
525
312
|
for(let [nk, nd, len] of moves(k)) {
|
|
526
|
-
if(k === start && nd >= 2) {
|
|
527
|
-
continue; /* out of the port sideways, not down its column */
|
|
528
|
-
}
|
|
529
313
|
let c = cost + len + ((nd === dir)? 0 : ROUTE_BEND);
|
|
530
314
|
let ns = nk * 4 + nd;
|
|
531
315
|
if(c < best[ns]) {
|
|
@@ -576,21 +360,15 @@ function orth_search(a, z, boxes)
|
|
|
576
360
|
* coincide. Nearby cards first; all of them if that finds no way;
|
|
577
361
|
* the simple elbow if nothing does.
|
|
578
362
|
************************************************************/
|
|
579
|
-
export function elbow_route(s, t, box_s, box_t, lane, vertical, boxes
|
|
363
|
+
export function elbow_route(s, t, box_s, box_t, lane, vertical, boxes)
|
|
580
364
|
{
|
|
581
365
|
let others = boxes || [];
|
|
582
366
|
if(!vertical) {
|
|
583
367
|
return elbow_route(swap(s), swap(t), swap_box(box_s), swap_box(box_t), lane, true,
|
|
584
|
-
others.map(swap_box)
|
|
368
|
+
others.map(swap_box)).map(swap);
|
|
585
369
|
}
|
|
586
370
|
|
|
587
|
-
|
|
588
|
-
* and its gap are empty by construction. */
|
|
589
|
-
if(stagger && stagger.comb) {
|
|
590
|
-
return elbow_points(s, t, box_s, box_t, lane, true, stagger);
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
let simple = elbow_points(s, t, box_s, box_t, lane, true, stagger);
|
|
371
|
+
let simple = elbow_points(s, t, box_s, box_t, lane, true);
|
|
594
372
|
if(!elbow_path_hits([s, ...simple, t], others, box_s, box_t)) {
|
|
595
373
|
return simple;
|
|
596
374
|
}
|
|
@@ -613,112 +391,3 @@ export function elbow_route(s, t, box_s, box_t, lane, vertical, boxes, stagger)
|
|
|
613
391
|
}
|
|
614
392
|
return route || simple;
|
|
615
393
|
}
|
|
616
|
-
|
|
617
|
-
/*----------------------------------------------------------------*
|
|
618
|
-
* CURVED edges that would cross a card
|
|
619
|
-
*----------------------------------------------------------------*/
|
|
620
|
-
export const CURVE_RADIUS = 24; /* the corners of a curve that goes round */
|
|
621
|
-
export const CURVE_STEPS = 16; /* samples of each curved stretch */
|
|
622
|
-
|
|
623
|
-
/* Points along a G6 path: the ends of its runs and CURVE_STEPS
|
|
624
|
-
* samples of each quadratic or cubic stretch. */
|
|
625
|
-
function path_points(path)
|
|
626
|
-
{
|
|
627
|
-
let pts = [];
|
|
628
|
-
let cur = null;
|
|
629
|
-
for(let c of path) {
|
|
630
|
-
if(c[0] === 'M' || c[0] === 'L') {
|
|
631
|
-
cur = [c[1], c[2]];
|
|
632
|
-
pts.push(cur);
|
|
633
|
-
} else if(c[0] === 'C' && cur) {
|
|
634
|
-
let [x0, y0] = cur;
|
|
635
|
-
for(let k = 1; k <= CURVE_STEPS; k++) {
|
|
636
|
-
let u = k / CURVE_STEPS;
|
|
637
|
-
let v = 1 - u;
|
|
638
|
-
pts.push([
|
|
639
|
-
v * v * v * x0 + 3 * v * v * u * c[1] + 3 * v * u * u * c[3] + u * u * u * c[5],
|
|
640
|
-
v * v * v * y0 + 3 * v * v * u * c[2] + 3 * v * u * u * c[4] + u * u * u * c[6],
|
|
641
|
-
]);
|
|
642
|
-
}
|
|
643
|
-
cur = [c[5], c[6]];
|
|
644
|
-
} else if(c[0] === 'Q' && cur) {
|
|
645
|
-
let [x0, y0] = cur;
|
|
646
|
-
for(let k = 1; k <= CURVE_STEPS; k++) {
|
|
647
|
-
let u = k / CURVE_STEPS;
|
|
648
|
-
let v = 1 - u;
|
|
649
|
-
pts.push([
|
|
650
|
-
v * v * x0 + 2 * v * u * c[1] + u * u * c[3],
|
|
651
|
-
v * v * y0 + 2 * v * u * c[2] + u * u * c[4],
|
|
652
|
-
]);
|
|
653
|
-
}
|
|
654
|
-
cur = [c[3], c[4]];
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
return pts;
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
/************************************************************
|
|
661
|
-
* Does the G6 path `path` of a curved edge cross a card other than
|
|
662
|
-
* its own two? Measured on the curve itself -- the path G6 would
|
|
663
|
-
* draw, sampled -- and not on the rectangle round it, which took
|
|
664
|
-
* every card the curve merely passed near: the ports of a row
|
|
665
|
-
* stick well into the space above it. Its own two cards are left
|
|
666
|
-
* out: the curve starts and ends on their ports.
|
|
667
|
-
************************************************************/
|
|
668
|
-
export function curve_hits(path, boxes, box_s, box_t)
|
|
669
|
-
{
|
|
670
|
-
let pts = path_points(path);
|
|
671
|
-
if(pts.length < 2) {
|
|
672
|
-
return false;
|
|
673
|
-
}
|
|
674
|
-
let bb = {x1: Infinity, y1: Infinity, x2: -Infinity, y2: -Infinity};
|
|
675
|
-
for(let p of pts) {
|
|
676
|
-
bb.x1 = Math.min(bb.x1, p[0]);
|
|
677
|
-
bb.y1 = Math.min(bb.y1, p[1]);
|
|
678
|
-
bb.x2 = Math.max(bb.x2, p[0]);
|
|
679
|
-
bb.y2 = Math.max(bb.y2, p[1]);
|
|
680
|
-
}
|
|
681
|
-
for(let b of (boxes || [])) {
|
|
682
|
-
if(same_box(b, box_s) || same_box(b, box_t) || !box_overlaps(b, box_expand(bb, 1))) {
|
|
683
|
-
continue;
|
|
684
|
-
}
|
|
685
|
-
let inner = box_expand(b, -1);
|
|
686
|
-
for(let k = 0; k + 1 < pts.length; k++) {
|
|
687
|
-
if(segment_hits(pts[k], pts[k + 1], inner)) {
|
|
688
|
-
return true;
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
return false;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
/************************************************************
|
|
696
|
-
* A G6 path (['M'|'L'|'Q', ...]) along `points` with every corner
|
|
697
|
-
* rounded -- by `radius`, or by half the shorter of its two
|
|
698
|
-
* segments, so a short run is never overshot. The rounding is the
|
|
699
|
-
* quadratic G6's own polyline uses.
|
|
700
|
-
************************************************************/
|
|
701
|
-
export function rounded_path(points, radius)
|
|
702
|
-
{
|
|
703
|
-
let path = [['M', points[0][0], points[0][1]]];
|
|
704
|
-
for(let i = 1; i + 1 < points.length; i++) {
|
|
705
|
-
let a = points[i - 1];
|
|
706
|
-
let m = points[i];
|
|
707
|
-
let b = points[i + 1];
|
|
708
|
-
let len_in = Math.hypot(m[0] - a[0], m[1] - a[1]);
|
|
709
|
-
let len_out = Math.hypot(b[0] - m[0], b[1] - m[1]);
|
|
710
|
-
let r = Math.min(radius, len_in / 2, len_out / 2);
|
|
711
|
-
let collinear = Math.abs((m[0] - a[0]) * (b[1] - m[1]) - (m[1] - a[1]) * (b[0] - m[0])) < 1e-9;
|
|
712
|
-
if(r <= 0 || collinear) {
|
|
713
|
-
path.push(['L', m[0], m[1]]);
|
|
714
|
-
continue;
|
|
715
|
-
}
|
|
716
|
-
let p1 = [m[0] - (m[0] - a[0]) / len_in * r, m[1] - (m[1] - a[1]) / len_in * r];
|
|
717
|
-
let p2 = [m[0] + (b[0] - m[0]) / len_out * r, m[1] + (b[1] - m[1]) / len_out * r];
|
|
718
|
-
path.push(['L', p1[0], p1[1]]);
|
|
719
|
-
path.push(['Q', m[0], m[1], p2[0], p2[1]]);
|
|
720
|
-
}
|
|
721
|
-
let last = points[points.length - 1];
|
|
722
|
-
path.push(['L', last[0], last[1]]);
|
|
723
|
-
return path;
|
|
724
|
-
}
|