UNPKG

5.47 kBPlain TextView Raw
1/*
2* Copyright (C) 1998-2021 by Northwoods Software Corporation. All Rights Reserved.
3*/
4
5/*
6* This is an extension and not part of the main GoJS library.
7* Note that the API for this class may change with any version, even point releases.
8* If you intend to use an extension in production, you should copy the code to your own source directory.
9* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
10* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
11*/
12
13import * as go from '../release/go-module.js';
14
15/**
16 * A custom {@link Layout} that lays out hierarchical data using nested rectangles.
17 *
18 * If you want to experiment with this extension, try the <a href="../../extensionsJSM/TreeMap.html">Tree Map Layout</a> sample.
19 * @category Layout Extension
20 */
21export class TreeMapLayout extends go.Layout {
22 private _isTopLevelHorizontal: boolean = false;
23
24 /**
25 * Gets or sets whether top level Parts are laid out horizontally.
26 */
27 get isTopLevelHorizontal(): boolean { return this._isTopLevelHorizontal; }
28 set isTopLevelHorizontal(val: boolean) {
29 if (this._isTopLevelHorizontal !== val) {
30 this._isTopLevelHorizontal = val;
31 this.invalidateLayout();
32 }
33 }
34
35 /**
36 * Copies properties to a cloned Layout.
37 */
38 public cloneProtected(copy: this): void {
39 super.cloneProtected(copy);
40 copy._isTopLevelHorizontal = this._isTopLevelHorizontal;
41 }
42
43 /**
44 * This method actually positions all of the nodes by determining total area and then recursively tiling nodes from the top-level down.
45 * @param {Diagram|Group|Iterable.<Part>} coll A {@link Diagram} or a {@link Group} or a collection of {@link Part}s.
46 */
47 public doLayout(coll: go.Diagram | go.Group | go.Iterable<go.Part>): void {
48 if (!(coll instanceof go.Diagram)) throw new Error('TreeMapLayout only works as the Diagram.layout');
49 const diagram = coll;
50 this.computeTotals(diagram); // make sure data.total has been computed for every node
51 // figure out how large an area to cover;
52 // perhaps this should be a property that could be set, rather than depending on the current viewport
53 this.arrangementOrigin = this.initialOrigin(this.arrangementOrigin);
54 const x = this.arrangementOrigin.x;
55 const y = this.arrangementOrigin.y;
56 let w = diagram.viewportBounds.width;
57 let h = diagram.viewportBounds.height;
58 if (isNaN(w)) w = 1000;
59 if (isNaN(h)) h = 1000;
60 // collect all top-level nodes, and sum their totals
61 const tops = new go.Set<go.Node>();
62 let total = 0;
63 diagram.nodes.each((n) => {
64 if (n.isTopLevel) {
65 tops.add(n);
66 total += n.data.total;
67 }
68 });
69 const horiz = this.isTopLevelHorizontal; // initially horizontal layout?
70 // the following was copied from the layoutNode method
71 let gx = x;
72 let gy = y;
73 const lay = this;
74 tops.each((n: go.Node) => {
75 const tot = n.data.total;
76 if (horiz) {
77 const pw = w * tot / total;
78 lay.layoutNode(!horiz, n, gx, gy, pw, h);
79 gx += pw;
80 } else {
81 const ph = h * tot / total;
82 lay.layoutNode(!horiz, n, gx, gy, w, ph);
83 gy += ph;
84 }
85 });
86 }
87
88 /**
89 * @hidden @internal
90 */
91 public layoutNode(horiz: boolean, n: go.Panel, x: number, y: number, w: number, h: number): void {
92 n.position = new go.Point(x, y);
93 n.desiredSize = new go.Size(w, h);
94 if (n instanceof go.Group) {
95 const g = n;
96 const total = g.data.total;
97 let gx = x;
98 let gy = y;
99 const lay = this;
100 g.memberParts.each((p) => {
101 if (p instanceof go.Link) return;
102 const tot = p.data.total;
103 if (horiz) {
104 const pw = w * tot / total;
105 lay.layoutNode(!horiz, p, gx, gy, pw, h);
106 gx += pw;
107 } else {
108 const ph = h * tot / total;
109 lay.layoutNode(!horiz, p, gx, gy, w, ph);
110 gy += ph;
111 }
112 });
113 }
114 }
115
116 /**
117 * Compute the `data.total` for each node in the Diagram, with a {@link Group}'s being a sum of its members.
118 */
119 public computeTotals(diagram: go.Diagram): void {
120 if (!diagram.nodes.all((g: go.Node) => !(g instanceof go.Group) || g.data.total >= 0)) {
121 let groups = new go.Set<go.Group>();
122 diagram.nodes.each((n: go.Node) => {
123 if (n instanceof go.Group) { // collect all groups
124 groups.add(n);
125 } else { // regular nodes just have their total == size
126 n.data.total = n.data.size;
127 }
128 });
129 // keep looking for groups whose total can be computed, until all groups have been processed
130 while (groups.count > 0) {
131 const grps = new go.Set<go.Group>();
132 groups.each((g: go.Group) => {
133 // for a group all of whose member nodes have an initialized data.total,
134 if (g.memberParts.all((m) => !(m instanceof go.Group) || m.data.total >= 0)) {
135 // compute the group's total as the sum of the sizes of all of the member nodes
136 g.data.total = 0;
137 g.memberParts.each((m) => { if (m instanceof go.Node) g.data.total += m.data.total; });
138 } else { // remember for the next iteration
139 grps.add(g);
140 }
141 });
142 groups = grps;
143 }
144 }
145 }
146
147}