@yuneta/gobj-ui 7.23.156 → 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.
@@ -274,414 +274,6 @@ function tidy_tree_lr(nodes, edges, o)
274
274
  return pos;
275
275
  }
276
276
 
277
- /************************************************************
278
- * The compact tree.
279
- *
280
- * The tidy tree above gives every subtree its whole BLOCK: open a
281
- * hall with twenty-four devices and its block widens, and with it
282
- * every block above it, so the siblings of its ancestors move apart
283
- * at depths where nothing of theirs touches. This one keeps the
284
- * CONTOUR of each subtree instead -- its outline on each side, depth
285
- * by depth -- and slides every sibling against the outline of the
286
- * ones before it. A folded region then sits as close to an open one
287
- * as their cards allow at each depth, which is what a folded treedb
288
- * is: one branch open among closed ones.
289
- *
290
- * Same spanning tree, same order, same rows as the tidy tree: only
291
- * the packing across the depths differs, so switching between the
292
- * two moves cards sideways and never reorders them.
293
- *
294
- * And a run of three or more LEAF children -- a hall's devices, a
295
- * region's places when none is open -- is STACKED: two columns
296
- * under the parent with a corridor between them, row after row.
297
- * That is what makes it compact when a whole level is open, where
298
- * the contour alone gains nothing: every parent needed the whole
299
- * row of its children beside it. The tree grows taller instead of
300
- * wider. Its elbow edges run down the corridor (elbow_combs).
301
- *
302
- * The contour arithmetic is Moen's ("Drawing dynamic trees", IEEE
303
- * Software, 1990) in the form mxGraph gave it in mxCompactTreeLayout
304
- * -- Copyright (c) 2006-2015 JGraph Ltd, Apache License 2.0 -- read in
305
- * maxGraph's CompactTreeLayout.ts: leaf, join, merge, offset and
306
- * bridge below follow that form. What is ours: it runs on the
307
- * spanning tree of a FOREST, every depth is a row as wide as its
308
- * widest card (mxGraph's `alignRanks`), several roots sit side by
309
- * side, and there is no recursion.
310
- ************************************************************/
311
- export function layout_compact(nodes, edges, opts)
312
- {
313
- let o = Object.assign({}, DEFAULTS, opts || {});
314
- if(o.direction === "LR") {
315
- return compact_tree_lr(nodes, edges, o);
316
- }
317
- let transposed = nodes.map((n) => Object.assign({}, n, {w: n.h, h: n.w}));
318
- let lr = compact_tree_lr(transposed, edges, o);
319
- let pos = new Map();
320
- for(let [id, p] of lr) {
321
- pos.set(id, {x: p.y, y: p.x});
322
- }
323
- return pos;
324
- }
325
-
326
- /* One segment of a contour: a step of `dx` along the depth and `dy`
327
- * across it, then the next segment. */
328
- function contour_line(dx, dy, next)
329
- {
330
- return {dx: dx, dy: dy, next: next || null};
331
- }
332
-
333
- /* How far the segment (a1, a2) at (p1, p2) has to move across to
334
- * clear the segment (b1, b2) at the origin. */
335
- function contour_offset(p1, p2, a1, a2, b1, b2)
336
- {
337
- if(b1 <= p1 || p1 + a1 <= 0) {
338
- return 0;
339
- }
340
- let d;
341
- let t = b1 * a2 - a1 * b2;
342
- if(t > 0) {
343
- if(p1 < 0) {
344
- d = (p1 * a2) / a1 - p2;
345
- } else if(p1 > 0) {
346
- d = (p1 * b2) / b1 - p2;
347
- } else {
348
- d = -p2;
349
- }
350
- } else if(b1 < p1 + a1) {
351
- d = b2 - (p2 + ((b1 - p1) * a2) / a1);
352
- } else if(b1 > p1 + a1) {
353
- d = ((a1 + p1) * b2) / b1 - (p2 + a2);
354
- } else {
355
- d = b2 - (p2 + a2);
356
- }
357
- return (d > 0)? d : 0;
358
- }
359
-
360
- function contour_bridge(line1, x1, y1, line2, x2, y2)
361
- {
362
- let dx = x2 + line2.dx - x1;
363
- let dy = (line2.dx === 0)? line2.dy : (dx * line2.dy) / line2.dx;
364
- let r = contour_line(dx, dy, line2.next);
365
- line1.next = contour_line(0, y2 + line2.dy - dy - y1, r);
366
- return r;
367
- }
368
-
369
- /* Slide contour `p2` against `p1` until they clear; returns how far,
370
- * and leaves in `p1` the outline of the two together. */
371
- function contour_merge(p1, p2)
372
- {
373
- let x = 0;
374
- let y = 0;
375
- let total = 0;
376
- let upper = p1.lower_head;
377
- let lower = p2.upper_head;
378
-
379
- while(lower && upper) {
380
- let d = contour_offset(x, y, lower.dx, lower.dy, upper.dx, upper.dy);
381
- y += d;
382
- total += d;
383
- if(x + lower.dx <= upper.dx) {
384
- x += lower.dx;
385
- y += lower.dy;
386
- lower = lower.next;
387
- } else {
388
- x -= upper.dx;
389
- y -= upper.dy;
390
- upper = upper.next;
391
- }
392
- }
393
-
394
- if(lower) {
395
- let b = contour_bridge(p1.upper_tail, 0, 0, lower, x, y);
396
- p1.upper_tail = b.next? p2.upper_tail : b;
397
- p1.lower_tail = p2.lower_tail;
398
- } else {
399
- let b = contour_bridge(p2.lower_tail, x, y, upper, 0, 0);
400
- if(!b.next) {
401
- p1.lower_tail = b;
402
- }
403
- }
404
- p1.lower_head = p2.lower_head;
405
- return total;
406
- }
407
-
408
- /* How the compact tree STACKS a run of leaf children: two columns
409
- * under their parent, a CORRIDOR between them that the elbow edges
410
- * run down (treedb_elbow.js, elbow_combs), a gap between the rows
411
- * that they turn into. The corridor is sized here from how many
412
- * lines will run down it; the edges count the lanes from where the
413
- * cards stand. */
414
- export const STACK_MIN_RUN = 3; /* shorter runs stay in the row */
415
- export const STACK_GAP = 40; /* between two rows of a stack */
416
- export const STACK_LANE_PAD = 10; /* from a column to its first lane */
417
- export const STACK_LANE_STEP = 6; /* between two lanes */
418
- export const STACK_TRUNK_MIN = 36;
419
-
420
- /* The corridor of a stack of `k` cards: every card below the first
421
- * row gets a lane. */
422
- export function stack_trunk_width(k)
423
- {
424
- let lanes = Math.max(0, k - 2);
425
- return Math.max(STACK_TRUNK_MIN,
426
- 2 * STACK_LANE_PAD + Math.max(0, lanes - 1) * STACK_LANE_STEP);
427
- }
428
-
429
- /************************************************************
430
- * The children of every node, with each run of STACK_MIN_RUN or
431
- * more consecutive LEAF children replaced by one STACK. Runs, not
432
- * all the leaves of a parent: the children keep their order. A
433
- * stack's key is an OBJECT, so it can never be taken for a node id,
434
- * which is a string. Shared by the compact tree and the radial.
435
- *
436
- * Returns {kids_of: Map<id, [id | stack]>, stacks: Map<stack, {ids, depth}>}
437
- ************************************************************/
438
- function leaf_runs(t)
439
- {
440
- let kids_of = new Map();
441
- let stacks = new Map();
442
- for(let [id, kids] of t.children) {
443
- let out = [];
444
- let run = [];
445
- let flush = () => {
446
- if(run.length >= STACK_MIN_RUN) {
447
- let key = {stack: stacks.size};
448
- stacks.set(key, {ids: run, depth: t.depth.get(run[0])});
449
- out.push(key);
450
- } else {
451
- out.push(...run);
452
- }
453
- run = [];
454
- };
455
- for(let kid of kids) {
456
- if((t.children.get(kid) || []).length === 0) {
457
- run.push(kid);
458
- } else {
459
- flush();
460
- out.push(kid);
461
- }
462
- }
463
- flush();
464
- kids_of.set(id, out);
465
- }
466
- return {kids_of: kids_of, stacks: stacks};
467
- }
468
-
469
- /* Parents before children, a stack being one child (walk_order's
470
- * form, over `kids_of`). */
471
- function stacked_order(t, kids_of)
472
- {
473
- let order = [];
474
- let todo = t.roots.slice().reverse();
475
- while(todo.length) {
476
- let id = todo.pop();
477
- order.push(id);
478
- let kids = kids_of.get(id) || [];
479
- for(let i = kids.length - 1; i >= 0; i--) {
480
- todo.push(kids[i]);
481
- }
482
- }
483
- return order;
484
- }
485
-
486
- /* A stack in the LR frame (depth along x, siblings along y): `side`
487
- * is how far each column reaches across, `starts`/`row_len` where
488
- * each row begins and how long it is along the depth. Its first row
489
- * IS the depth's column, so it lines up with every other card there. */
490
- function stack_geometry(t, st, col_x, col_w)
491
- {
492
- let d = st.depth;
493
- let k = st.ids.length;
494
- let rows = Math.ceil(k / 2);
495
- let side = [0, 0];
496
- let row_len = [];
497
- for(let i = 0; i < k; i++) {
498
- let n = t.by_id.get(st.ids[i]);
499
- let r = Math.floor(i / 2);
500
- side[i % 2] = Math.max(side[i % 2], n.h);
501
- row_len[r] = Math.max(row_len[r] || 0, n.w);
502
- }
503
- row_len[0] = col_w[d];
504
- let trunk = stack_trunk_width(k);
505
- let starts = [col_x[d]];
506
- for(let r = 1; r < rows; r++) {
507
- starts[r] = starts[r - 1] + row_len[r - 1] + STACK_GAP;
508
- }
509
- return {
510
- side: side,
511
- trunk: trunk,
512
- starts: starts,
513
- row_len: row_len,
514
- len: starts[rows - 1] + row_len[rows - 1] - col_x[d],
515
- ext: side[0] + trunk + side[1],
516
- };
517
- }
518
-
519
- /* The cards of a stack whose block starts across at `top`: row by
520
- * row, the first of each row against the corridor from one side,
521
- * the second against it from the other. */
522
- function place_stack(t, st, g, top, pos)
523
- {
524
- for(let i = 0; i < st.ids.length; i++) {
525
- let n = t.by_id.get(st.ids[i]);
526
- let r = Math.floor(i / 2);
527
- let across = (i % 2 === 0)
528
- ? top + g.side[0] - n.h / 2
529
- : top + g.side[0] + g.trunk + n.h / 2;
530
- pos.set(st.ids[i], {x: g.starts[r] + g.row_len[r] / 2, y: across});
531
- }
532
- }
533
-
534
- function compact_tree_lr(nodes, edges, o)
535
- {
536
- let t = spanning_tree(nodes, edges);
537
- let pos = new Map();
538
-
539
- /* Runs of leaf children become STACKS (leaf_runs); the contour
540
- * packing below takes a stack as one leaf of its own length, so
541
- * nothing is packed under it. */
542
- let {kids_of, stacks} = leaf_runs(t);
543
- let order = stacked_order(t, kids_of);
544
-
545
- /* Columns, as in the tidy tree: a depth is as wide as its widest
546
- * card, and a column's gap is `ranksep`. */
547
- let col_w = [];
548
- for(let [id, d] of t.depth) {
549
- col_w[d] = Math.max(col_w[d] || 0, t.by_id.get(id).w);
550
- }
551
- let col_x = [];
552
- let x = 0;
553
- for(let d = 0; d < col_w.length; d++) {
554
- col_x[d] = x;
555
- x += (col_w[d] || 0) + o.ranksep;
556
- }
557
-
558
- /* mxGraph's nodeDistance is padding on EACH side of a card, so two
559
- * neighbours end `nodesep` apart; its levelDistance is what is
560
- * left of the column gap after that padding. */
561
- let pad = o.nodesep / 2;
562
- let dist = o.nodesep;
563
- let level_distance = o.ranksep - pad;
564
-
565
- /* `len` along the depth (the column's width: the rows are aligned;
566
- * a stack's own length), `ext` across it. */
567
- let tn = new Map();
568
- for(let id of order) {
569
- let st = stacks.get(id);
570
- if(st) {
571
- let g = stack_geometry(t, st, col_x, col_w);
572
- tn.set(id, {len: g.len, ext: g.ext, geom: g, off_x: 0, off_y: 0, y: 0, contour: null});
573
- } else {
574
- tn.set(id, {
575
- len: col_w[t.depth.get(id)],
576
- ext: t.by_id.get(id).h,
577
- off_x: 0,
578
- off_y: 0,
579
- y: 0,
580
- contour: null,
581
- });
582
- }
583
- }
584
-
585
- /* Contours, children before parents. */
586
- for(let i = order.length - 1; i >= 0; i--) {
587
- let id = order[i];
588
- let n = tn.get(id);
589
- let kids = kids_of.get(id) || [];
590
-
591
- if(!kids.length) {
592
- let upper = contour_line(n.len + dist, 0);
593
- let lower_tail = contour_line(0, -n.ext - dist);
594
- n.contour = {
595
- upper_head: upper,
596
- upper_tail: upper,
597
- lower_tail: lower_tail,
598
- lower_head: contour_line(n.len + dist, 0, lower_tail),
599
- };
600
- continue;
601
- }
602
-
603
- /* join: the children side by side, each slid against the
604
- * outline of the ones before it. */
605
- let first = tn.get(kids[0]);
606
- n.contour = first.contour;
607
- let h = first.ext + dist;
608
- let span = h;
609
- for(let k = 1; k < kids.length; k++) {
610
- let kid = tn.get(kids[k]);
611
- let d = contour_merge(n.contour, kid.contour);
612
- kid.off_y = d + h;
613
- kid.off_x = 0;
614
- h = kid.ext + dist;
615
- span += d + h;
616
- }
617
-
618
- /* attach: the parent centred across the span of its children,
619
- * one column before them. */
620
- let gap = pad + level_distance;
621
- let y2 = (span - n.ext) / 2 - pad;
622
- let y1 = y2 + n.ext + 2 * pad - span;
623
- first.off_x = gap + n.len;
624
- first.off_y = y1;
625
- n.contour.upper_head = contour_line(n.len, 0,
626
- contour_line(gap, y1, n.contour.upper_head));
627
- n.contour.lower_head = contour_line(n.len, 0,
628
- contour_line(gap, y2, n.contour.lower_head));
629
- }
630
-
631
- /* Positions across, parents before children: the first child at
632
- * its offset from the parent, each next one at its offset from
633
- * the one before. */
634
- let root_of = new Map();
635
- for(let id of order) {
636
- let n = tn.get(id);
637
- if(!root_of.has(id)) {
638
- root_of.set(id, id);
639
- }
640
- let kids = kids_of.get(id) || [];
641
- if(!kids.length) {
642
- continue;
643
- }
644
- let running = n.y + tn.get(kids[0]).off_y;
645
- tn.get(kids[0]).y = running;
646
- root_of.set(kids[0], root_of.get(id));
647
- for(let k = 1; k < kids.length; k++) {
648
- let kid = tn.get(kids[k]);
649
- kid.y = running + kid.off_y;
650
- running = kid.y;
651
- root_of.set(kids[k], root_of.get(id));
652
- }
653
- }
654
-
655
- /* Several roots: each tree after the one before, by its extent. */
656
- let lo = new Map();
657
- let hi = new Map();
658
- for(let id of order) {
659
- let r = root_of.get(id);
660
- let n = tn.get(id);
661
- lo.set(r, Math.min(lo.has(r)? lo.get(r) : Infinity, n.y));
662
- hi.set(r, Math.max(hi.has(r)? hi.get(r) : -Infinity, n.y + n.ext));
663
- }
664
- let shift = new Map();
665
- let top = 0;
666
- for(let r of t.roots) {
667
- shift.set(r, top - lo.get(r));
668
- top += hi.get(r) - lo.get(r) + o.nodesep * 2;
669
- }
670
-
671
- for(let id of order) {
672
- let n = tn.get(id);
673
- let y = n.y + shift.get(root_of.get(id));
674
- let st = stacks.get(id);
675
- if(st) {
676
- place_stack(t, st, n.geom, y, pos);
677
- continue;
678
- }
679
- let d = t.depth.get(id);
680
- pos.set(id, {x: col_x[d] + col_w[d] / 2, y: y + n.ext / 2});
681
- }
682
- return pos;
683
- }
684
-
685
277
  /* How much of a card lies along the direction `a`: the projection
686
278
  * of its rectangle on that direction. Full width when the card is
687
279
  * read along its width, full height across it, and the honest
@@ -711,148 +303,76 @@ function extent_at(n, a)
711
303
  *
712
304
  * ranksep the step between rings
713
305
  * nodesep the gap between neighbours on a ring
714
- * stack (default true) a run of three or more LEAF children
715
- * is a STACK, as in the compact tree: it asks its
716
- * ring for one row of about the square root of its
717
- * cards, not for every leaf -- a hall with a hundred
718
- * devices no longer blows the ring up -- and lays
719
- * them in rows going OUTWARD, each row as full as
720
- * the room it has: rings round the parent when it is
721
- * alone, a fan inside its sector when it is not.
722
- * false keeps every leaf on the ring.
723
306
  ************************************************************/
724
307
  export function layout_radial(nodes, edges, opts)
725
308
  {
726
- let o = Object.assign({}, DEFAULTS, {stack: true}, opts || {});
309
+ let o = Object.assign({}, DEFAULTS, opts || {});
727
310
  let t = spanning_tree(nodes, edges);
728
311
  let pos = new Map();
729
312
  if(!t.roots.length) {
730
313
  return pos;
731
314
  }
732
315
 
733
- let runs = o.stack? leaf_runs(t) : {kids_of: t.children, stacks: new Map()};
734
- let kids_of = runs.kids_of;
735
- let stacks = runs.stacks;
736
- let order = stacked_order(t, kids_of);
737
-
738
- /* A stack asks its ring for one row of about the square root of
739
- * its cards; how many each row really holds is decided once the
740
- * ring's radius is known (pass 2), by the room it has there. */
741
- for(let st of stacks.values()) {
742
- st.per_row = Math.ceil(Math.sqrt(st.ids.length));
743
- st.cards = st.ids.map((id) => t.by_id.get(id));
744
- }
745
- /* A stack across its ring, at angle `a`: a card's width there, and
746
- * the row it asks for. Along the radius: how far one row reaches. */
747
- let stack_across = (st, a) => {
748
- let w = 0;
749
- for(let c of st.cards) {
750
- w = Math.max(w, extent_at(c, a + Math.PI / 2));
751
- }
752
- return {card: w, row: st.per_row * w + (st.per_row - 1) * o.nodesep};
753
- };
754
- let stack_along = (st, a) => {
755
- let e = 0;
756
- for(let c of st.cards) {
757
- e = Math.max(e, extent_at(c, a));
758
- }
759
- return e;
760
- };
761
- /* The widest a set of cards is along the direction `th`, over
762
- * the arc a-half..a+half: a stack's cards stand anywhere in its
763
- * sector, and a card is wider slanted than square. */
764
- let widest_along = (cards, a, half) => {
765
- let e = 0;
766
- for(let k = 0; k <= 16; k++) {
767
- let th = a - half + 2 * half * k / 16;
768
- for(let c of cards) {
769
- e = Math.max(e, extent_at(c, th));
770
- }
771
- }
772
- return e;
773
- };
774
- let depth_of = (id) => stacks.has(id)? stacks.get(id).depth : t.depth.get(id);
775
- let circ = (a, b) => {
776
- let d = Math.abs(a - b) % (2 * Math.PI);
777
- return (d > Math.PI)? 2 * Math.PI - d : d;
778
- };
779
-
780
- /* What each node needs across its ring, the sector it deserves: a
781
- * card is one, a stack is one ROW of cards, a parent the sum of
782
- * its children. Post-order. */
783
- let weight = new Map();
316
+ /* Leaves under each node: the sector it deserves. Post-order. */
317
+ let order = walk_order(t);
318
+ let leaves = new Map();
784
319
  for(let i = order.length - 1; i >= 0; i--) {
785
320
  let id = order[i];
786
- let st = stacks.get(id);
787
- if(st) {
788
- weight.set(id, st.per_row);
789
- continue;
790
- }
791
- let kids = kids_of.get(id) || [];
321
+ let kids = t.children.get(id) || [];
792
322
  if(!kids.length) {
793
- weight.set(id, 1);
323
+ leaves.set(id, 1);
794
324
  continue;
795
325
  }
796
326
  let n = 0;
797
327
  for(let kid of kids) {
798
- n += weight.get(kid);
328
+ n += leaves.get(kid);
799
329
  }
800
- weight.set(id, n);
330
+ leaves.set(id, n);
801
331
  }
802
332
  let total = 0;
803
333
  for(let root of t.roots) {
804
- total += weight.get(root);
334
+ total += leaves.get(root);
805
335
  }
806
336
 
807
337
  /* One root sits at the centre; several sit on the first ring
808
338
  * around an empty centre, so every depth shifts out by one. */
809
339
  let single = (t.roots.length === 1);
810
- let ring_of = (id) => depth_of(id) + (single? 0 : 1);
340
+ let ring_of = (id) => t.depth.get(id) + (single? 0 : 1);
811
341
 
812
342
  /* Pass 1, the ANGLES: a node's children split its sector by
813
- * their weight, and a node sits at the middle of its sector.
343
+ * their leaves, and a node sits at the middle of its sector.
814
344
  * Angles do not depend on the radii, so they come first. */
815
345
  let angle = new Map();
816
- let rings = []; /* ring -> [{id, angle, span, diag, ext, parent_ext}] */
346
+ let rings = []; /* ring -> [{id, angle, diag}] */
817
347
  /* An explicit stack, not recursion: the sectors of a ring are
818
348
  * sorted by angle in pass 2, so the order they are pushed in does
819
349
  * not matter -- only that every node gets its own. */
820
350
  let sector = (root_id, root_from, root_span, root_parent) => {
821
- let todo = [{id: root_id, from: root_from, span: root_span, parent: root_parent}];
822
- while(todo.length) {
823
- let {id, from, span, parent} = todo.pop();
351
+ let stack = [{id: root_id, from: root_from, span: root_span, parent: root_parent}];
352
+ while(stack.length) {
353
+ let {id, from, span, parent} = stack.pop();
824
354
  let a = from + span / 2;
825
355
  angle.set(id, a);
826
356
  let ring = ring_of(id);
827
357
  if(!rings[ring]) {
828
358
  rings[ring] = [];
829
359
  }
830
- let st = stacks.get(id);
831
- let n = st? null : t.by_id.get(id);
360
+ let n = t.by_id.get(id);
832
361
  rings[ring].push({
833
362
  id: id,
834
363
  angle: a,
835
- span: span,
836
- /* How much it needs along the ring: a card's diagonal,
837
- * a stack's first row. */
838
- diag: st? stack_across(st, a).row : Math.hypot(n.w, n.h),
364
+ diag: Math.hypot(n.w, n.h),
839
365
  /* How much of the card lies along the RADIUS at this
840
366
  * angle -- the projection of the rectangle on that
841
367
  * direction -- for itself and for the node it hangs
842
368
  * from, which is the one it has to clear. */
843
- /* A stack is measured, and measures its parent, over
844
- * the whole sector it can cover: round a parent its
845
- * first row stands at every angle. */
846
- ext: st? widest_along(st.cards, a, Math.min(span / 2, Math.PI))
847
- : extent_at(n, a),
848
- parent_ext: !parent? 0
849
- : (st? widest_along([t.by_id.get(parent)], a, Math.min(span / 2, Math.PI))
850
- : extent_at(t.by_id.get(parent), a)),
369
+ ext: extent_at(n, a),
370
+ parent_ext: parent? extent_at(t.by_id.get(parent), a) : 0,
851
371
  });
852
372
  let cursor = from;
853
- for(let kid of (kids_of.get(id) || [])) {
854
- let part = span * weight.get(kid) / weight.get(id);
855
- todo.push({id: kid, from: cursor, span: part, parent: id});
373
+ for(let kid of (t.children.get(id) || [])) {
374
+ let part = span * leaves.get(kid) / leaves.get(id);
375
+ stack.push({id: kid, from: cursor, span: part, parent: id});
856
376
  cursor += part;
857
377
  }
858
378
  }
@@ -861,15 +381,15 @@ export function layout_radial(nodes, edges, opts)
861
381
  let root = t.roots[0];
862
382
  angle.set(root, 0);
863
383
  let cursor = -Math.PI / 2; /* the first child at the top */
864
- for(let kid of (kids_of.get(root) || [])) {
865
- let part = 2 * Math.PI * weight.get(kid) / weight.get(root);
384
+ for(let kid of (t.children.get(root) || [])) {
385
+ let part = 2 * Math.PI * leaves.get(kid) / leaves.get(root);
866
386
  sector(kid, cursor, part, root);
867
387
  cursor += part;
868
388
  }
869
389
  } else {
870
390
  let cursor = -Math.PI / 2;
871
391
  for(let root of t.roots) {
872
- let part = 2 * Math.PI * weight.get(root) / total;
392
+ let part = 2 * Math.PI * leaves.get(root) / total;
873
393
  sector(root, cursor, part, null);
874
394
  cursor += part;
875
395
  }
@@ -893,132 +413,31 @@ export function layout_radial(nodes, edges, opts)
893
413
  * the one it HANGS FROM, in the direction it hangs in -- the
894
414
  * two are radially aligned, or nearly, since a child lives
895
415
  * inside its parent's sector -- and the ring goes out far
896
- * enough for the worst of them.
897
- *
898
- * Then the STACKS of the ring get their rows: each row holds as
899
- * many cards as fit in the room the stack has -- its own sector,
900
- * less what its neighbours on the ring take up -- at that row's
901
- * radius. A stack alone on its ring has the whole circle, and its
902
- * rows are rings round its parent; one between neighbours fans
903
- * out inside its sector, wider as it goes. The rows reaching out
904
- * are neighbours of the outer rings too, by the ANGLE they cover. */
905
- let fit = (list, r0) => {
906
- let r = r0;
907
- let sorted = list.slice().sort((a, b) => a.angle - b.angle);
908
- for(let i = 0; i < sorted.length && sorted.length > 1; i++) {
909
- let a = sorted[i];
910
- let b = sorted[(i + 1) % sorted.length];
911
- let gap = (i + 1 < sorted.length)
912
- ? b.angle - a.angle
913
- : b.angle + 2 * Math.PI - a.angle;
914
- if(gap > 1e-9) {
915
- r = Math.max(r, (a.diag / 2 + b.diag / 2 + o.nodesep) / gap);
916
- }
917
- }
918
- return r;
919
- };
416
+ * enough for the worst of them. */
920
417
  let radius = [0];
921
- let reaching = []; /* planned stacks: {angle, half, reach} */
922
418
  for(let ring = 1; ring < rings.length; ring++) {
923
- let members = rings[ring] || [];
924
419
  let r = radius[ring - 1] + o.ranksep;
925
- let band = 0;
926
- for(let n of members) {
420
+ for(let n of (rings[ring] || [])) {
927
421
  r = Math.max(r, radius[ring - 1] + (n.parent_ext + n.ext) / 2 + o.nodesep);
928
- band = Math.max(band, n.ext);
929
422
  }
930
- r = fit(members, r);
931
- /* Clear the rows of the inner stacks that reach this far: by
932
- * the angle they cover, or, for a card standing in it, by
933
- * going past their reach. */
934
- for(let pass = 0; pass < 2; pass++) {
935
- for(let g of reaching) {
936
- if(g.reach < r - band / 2 - o.nodesep) {
937
- continue;
938
- }
939
- for(let m of members) {
940
- let d = circ(g.angle, m.angle);
941
- if(d > g.half + 1e-9) {
942
- r = Math.max(r, (m.diag / 2 + o.nodesep) / (d - g.half));
943
- } else {
944
- r = Math.max(r, g.reach + m.ext / 2 + o.nodesep);
945
- }
946
- }
423
+ let list = (rings[ring] || []).slice().sort((a, b) => a.angle - b.angle);
424
+ for(let i = 0; i < list.length && list.length > 1; i++) {
425
+ let a = list[i];
426
+ let b = list[(i + 1) % list.length];
427
+ let gap = (i + 1 < list.length)
428
+ ? b.angle - a.angle
429
+ : b.angle + 2 * Math.PI - a.angle;
430
+ if(gap > 1e-9) {
431
+ r = Math.max(r, (a.diag / 2 + b.diag / 2 + o.nodesep) / gap);
947
432
  }
948
433
  }
949
434
  radius[ring] = r;
950
-
951
- let sorted = members.slice().sort((a, b) => a.angle - b.angle);
952
- for(let i = 0; i < sorted.length; i++) {
953
- let m = sorted[i];
954
- let st = stacks.get(m.id);
955
- if(!st) {
956
- continue;
957
- }
958
- /* The room each side: to the neighbour, less its half and
959
- * the gap; never past the stack's own sector. */
960
- let room = m.span / 2;
961
- if(sorted.length > 1) {
962
- let left = sorted[(i - 1 + sorted.length) % sorted.length];
963
- let right = sorted[(i + 1) % sorted.length];
964
- room = Math.min(room,
965
- circ(m.angle, left.angle) - (left.diag / 2 + o.nodesep) / r,
966
- circ(m.angle, right.angle) - (right.diag / 2 + o.nodesep) / r);
967
- }
968
- room = Math.min(room, Math.PI);
969
- /* Measured over the whole arc it will cover, not at its
970
- * middle: round a parent a card stands at every angle,
971
- * and a 172x96 card is wider at 45 degrees than facing
972
- * down. */
973
- let across = 0;
974
- let along = 0;
975
- for(let k = 0; k <= 16; k++) {
976
- let th = m.angle - room + 2 * room * k / 16;
977
- for(let c of st.cards) {
978
- across = Math.max(across, extent_at(c, th + Math.PI / 2));
979
- along = Math.max(along, extent_at(c, th));
980
- }
981
- }
982
- let rows = [];
983
- let left_to_place = st.ids.length;
984
- let rr = r;
985
- let half = 0;
986
- while(left_to_place > 0) {
987
- let fits = Math.floor(2 * room * rr / (across + o.nodesep));
988
- let cnt = Math.max(1, Math.min(left_to_place, fits));
989
- rows.push({rr: rr, cnt: cnt});
990
- half = Math.max(half, ((cnt * (across + o.nodesep) - o.nodesep) / 2) / rr);
991
- left_to_place -= cnt;
992
- rr += along + STACK_GAP;
993
- }
994
- st.plan = {angle: m.angle, across: across, rows: rows};
995
- reaching.push({
996
- angle: m.angle,
997
- half: half,
998
- reach: rows[rows.length - 1].rr + along / 2,
999
- });
1000
- }
1001
435
  }
1002
436
 
1003
- /* Pass 3, the positions: a node at its angle on its ring; a
1004
- * stack's cards row by row outward, each row centred on the
1005
- * stack's angle, a card's width plus the gap apart along the arc. */
437
+ /* Pass 3, the positions. */
1006
438
  for(let [id, a] of angle) {
1007
- let st = stacks.get(id);
1008
- if(!st) {
1009
- let r0 = radius[ring_of(id)] || 0;
1010
- pos.set(id, {x: r0 * Math.cos(a), y: r0 * Math.sin(a)});
1011
- continue;
1012
- }
1013
- let i = 0;
1014
- for(let row of st.plan.rows) {
1015
- let step = (st.plan.across + o.nodesep) / row.rr;
1016
- for(let j = 0; j < row.cnt; j++) {
1017
- let th = a + (j - (row.cnt - 1) / 2) * step;
1018
- pos.set(st.ids[i], {x: row.rr * Math.cos(th), y: row.rr * Math.sin(th)});
1019
- i++;
1020
- }
1021
- }
439
+ let r = radius[ring_of(id)] || 0;
440
+ pos.set(id, {x: r * Math.cos(a), y: r * Math.sin(a)});
1022
441
  }
1023
442
 
1024
443
  return pos;