1 | <!DOCTYPE html>
|
2 | <html lang="en">
|
3 | <head>
|
4 | <meta charset="utf-8"/>
|
5 | <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover"/>
|
6 | <meta name="description" content="TypeScript: Display hierarchical data by nesting, where the area of each node is proportional to some value for the node. Clicking consecutively results in selecting containing Groups."/>
|
7 | <link rel="stylesheet" href="../assets/css/style.css"/>
|
8 |
|
9 | <title>Tree Map</title>
|
10 | </head>
|
11 |
|
12 | <body>
|
13 |
|
14 | <nav id="navTop" class="w-full z-30 top-0 text-white bg-nwoods-primary">
|
15 | <div class="w-full container max-w-screen-lg mx-auto flex flex-wrap sm:flex-nowrap items-center justify-between mt-0 py-2">
|
16 | <div class="md:pl-4">
|
17 | <a class="text-white hover:text-white no-underline hover:no-underline
|
18 | font-bold text-2xl lg:text-4xl rounded-lg hover:bg-nwoods-secondary " href="../">
|
19 | <h1 class="mb-0 p-1 ">GoJS</h1>
|
20 | </a>
|
21 | </div>
|
22 | <button id="topnavButton" class="rounded-lg sm:hidden focus:outline-none focus:ring" aria-label="Navigation">
|
23 | <svg fill="currentColor" viewBox="0 0 20 20" class="w-6 h-6">
|
24 | <path id="topnavOpen" fill-rule="evenodd" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM9 15a1 1 0 011-1h6a1 1 0 110 2h-6a1 1 0 01-1-1z" clip-rule="evenodd"></path>
|
25 | <path id="topnavClosed" class="hidden" fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
26 | </svg>
|
27 | </button>
|
28 | <div id="topnavList" class="hidden sm:block items-center w-auto mt-0 text-white p-0 z-20">
|
29 | <ul class="list-reset list-none font-semibold flex justify-end flex-wrap sm:flex-nowrap items-center px-0 pb-0">
|
30 | <li class="p-1 sm:p-0"><a class="topnav-link" href="../learn/">Learn</a></li>
|
31 | <li class="p-1 sm:p-0"><a class="topnav-link" href="../samples/">Samples</a></li>
|
32 | <li class="p-1 sm:p-0"><a class="topnav-link" href="../intro/">Intro</a></li>
|
33 | <li class="p-1 sm:p-0"><a class="topnav-link" href="../api/">API</a></li>
|
34 | <li class="p-1 sm:p-0"><a class="topnav-link" href="https://www.nwoods.com/products/register.html">Register</a></li>
|
35 | <li class="p-1 sm:p-0"><a class="topnav-link" href="../download.html">Download</a></li>
|
36 | <li class="p-1 sm:p-0"><a class="topnav-link" href="https://forum.nwoods.com/c/gojs/11">Forum</a></li>
|
37 | <li class="p-1 sm:p-0"><a class="topnav-link" href="https://www.nwoods.com/contact.html"
|
38 | target="_blank" rel="noopener" onclick="getOutboundLink('https://www.nwoods.com/contact.html', 'contact');">Contact</a></li>
|
39 | <li class="p-1 sm:p-0"><a class="topnav-link" href="https://www.nwoods.com/sales/index.html"
|
40 | target="_blank" rel="noopener" onclick="getOutboundLink('https://www.nwoods.com/sales/index.html', 'buy');">Buy</a></li>
|
41 | </ul>
|
42 | </div>
|
43 | </div>
|
44 | <hr class="border-b border-gray-600 opacity-50 my-0 py-0" />
|
45 | </nav>
|
46 | <div class="md:flex flex-col md:flex-row md:min-h-screen w-full max-w-screen-xl mx-auto">
|
47 | <div id="navSide" class="flex flex-col w-full md:w-48 text-gray-700 bg-white flex-shrink-0"></div>
|
48 |
|
49 |
|
50 |
|
51 |
|
52 | <div class="p-4 w-full">
|
53 | <div id="sample">
|
54 | <div style="margin-bottom: 5px; padding: 5px; background-color: aliceblue">
|
55 | <span style="display: inline-block; vertical-align: top; padding: 5px">
|
56 | <b>New Tree</b><br />
|
57 | MinNodes: <input type="number" width="2" id="minNodes" value="300" /><br />
|
58 | MaxNodes: <input type="number" width="2" id="maxNodes" value="500" /><br />
|
59 | MinChildren: <input type="number" width="2" id="minChil" value="2" /><br />
|
60 | MaxChildren: <input type="number" width="2" id="maxChil" value="5" /><br />
|
61 | <button type="button" id="rebuildGraph">Generate Tree</button>
|
62 | </span>
|
63 | </div>
|
64 | <div id="myDiagramDiv" style="background-color: white; border: solid 1px black; width: 100%; height: 500px"></div>
|
65 | <p>
|
66 | This sample demonstrates a custom Layout, TreeMapLayout, which assumes that the diagram consists of nested Groups and simple Nodes.
|
67 | Each node is positioned and sized to fill an area of the viewport proportionate to its "size", as determined by its Node.data.size property.
|
68 | Each Group gets a size that is the sum of all of its member Nodes.
|
69 | </p>
|
70 | <p>
|
71 | The layout is defined in its own file, as <a href="TreeMapLayout.ts">TreeMapLayout.ts</a>.
|
72 | </p>
|
73 | <p>
|
74 | Clicking repeatedly at the same point will initially select the Node at that point, and then its containing Group, and so on up the chain of containers.
|
75 | </p>
|
76 | </div>
|
77 |
|
78 | <script type="module" id="code">
|
79 | import * as go from "../release/go-module.js";
|
80 | import { TreeMapLayout } from './TreeMapLayout.js';
|
81 |
|
82 | if (window.goSamples) goSamples();
|
83 | const $ = go.GraphObject.make;
|
84 |
|
85 | const myDiagram =
|
86 | $(go.Diagram, 'myDiagramDiv',
|
87 | {
|
88 | initialAutoScale: go.Diagram.Uniform,
|
89 | 'animationManager.isEnabled': false,
|
90 | layout: $(TreeMapLayout, { isTopLevelHorizontal: false }),
|
91 | allowMove: false, allowCopy: false, allowDelete: false
|
92 | });
|
93 |
|
94 |
|
95 | myDiagram.toolManager.clickSelectingTool.standardMouseSelect = function() {
|
96 | const diagram = this.diagram;
|
97 | if (diagram === null || !diagram.allowSelect) return;
|
98 | const e = diagram.lastInput;
|
99 | if (!(e.control || e.meta) && !e.shift) {
|
100 | const part = diagram.findPartAt(e.documentPoint, false);
|
101 | if (part !== null) {
|
102 | let firstselected = null;
|
103 | let node = part;
|
104 | while (node !== null) {
|
105 | if (node.isSelected) {
|
106 | firstselected = node;
|
107 | break;
|
108 | } else {
|
109 | node = node.containingGroup;
|
110 | }
|
111 | }
|
112 | if (firstselected !== null) {
|
113 | firstselected.isSelected = false;
|
114 | const group = firstselected.containingGroup;
|
115 | if (group !== null)
|
116 | group.isSelected = true;
|
117 | return;
|
118 | }
|
119 | }
|
120 | }
|
121 | go.ClickSelectingTool.prototype.standardMouseSelect.call(this);
|
122 | };
|
123 |
|
124 |
|
125 | myDiagram.nodeTemplate =
|
126 | $(go.Node,
|
127 | { background: 'rgba(99,99,99,0.2)' },
|
128 | new go.Binding('background', 'fill'),
|
129 | {
|
130 | toolTip: $('ToolTip',
|
131 | $(go.TextBlock, new go.Binding('text', '', tooltipString).ofObject()))
|
132 | });
|
133 |
|
134 | myDiagram.groupTemplate =
|
135 | $(go.Group, 'Auto',
|
136 | { layout: null },
|
137 | { background: 'rgba(99,99,99,0.2)' },
|
138 | new go.Binding('background', 'fill'),
|
139 | {
|
140 | toolTip: $('ToolTip',
|
141 | $(go.TextBlock, new go.Binding('text', '', tooltipString).ofObject()))
|
142 | });
|
143 |
|
144 | function tooltipString(part) {
|
145 | if (part instanceof go.Adornment && part.adornedPart !== null) part = part.adornedPart;
|
146 | let msg = createPath(part);
|
147 | msg += '\nsize: ' + part.data.size;
|
148 | if (part instanceof go.Group) {
|
149 | const group = part;
|
150 | msg += '\n# children: ' + group.memberParts.count;
|
151 | msg += '\nsubtotal size: ' + group.data.total;
|
152 | }
|
153 | return msg;
|
154 | }
|
155 |
|
156 | function createPath(part) {
|
157 | const parent = part.containingGroup;
|
158 | return (parent !== null ? createPath(parent) + '/' : '') + part.data.text;
|
159 | }
|
160 |
|
161 |
|
162 | rebuildGraph();
|
163 |
|
164 | function rebuildGraph() {
|
165 | let minNodes = document.getElementById('minNodes').value;
|
166 | minNodes = parseInt(minNodes, 10);
|
167 | let maxNodes = document.getElementById('maxNodes').value;
|
168 | maxNodes = parseInt(maxNodes, 10);
|
169 | let minChil = document.getElementById('minChil').value;
|
170 | minChil = parseInt(minChil, 10);
|
171 | let maxChil = document.getElementById('maxChil').value;
|
172 | maxChil = parseInt(maxChil, 10);
|
173 |
|
174 | const model = new go.GraphLinksModel();
|
175 | model.nodeGroupKeyProperty = 'parent';
|
176 | model.nodeDataArray = generateNodeData(minNodes, maxNodes, minChil, maxChil);
|
177 | myDiagram.model = model;
|
178 | }
|
179 |
|
180 |
|
181 | function generateNodeData(minNodes, maxNodes, minChil, maxChil) {
|
182 | const nodeArray = [];
|
183 | if (minNodes === undefined || isNaN(minNodes) || minNodes < 1)
|
184 | minNodes = 1;
|
185 | if (maxNodes === undefined || isNaN(maxNodes) || maxNodes < minNodes)
|
186 | maxNodes = minNodes;
|
187 |
|
188 | const numNodes = Math.floor(Math.random() * (maxNodes - minNodes + 1)) + minNodes;
|
189 | for (let i = 0; i < numNodes; i++) {
|
190 | const size = Math.random() * Math.random() * 10000;
|
191 |
|
192 | nodeArray.push({
|
193 | key: i,
|
194 | isGroup: false,
|
195 | parent: undefined,
|
196 | text: i.toString(),
|
197 | fill: go.Brush.randomColor(),
|
198 | size: size,
|
199 | total: -1
|
200 | });
|
201 | }
|
202 |
|
203 |
|
204 |
|
205 | if (nodeArray.length > 1) {
|
206 | if (minChil === undefined || isNaN(minChil) || minChil < 0)
|
207 | minChil = 0;
|
208 | if (maxChil === undefined || isNaN(maxChil) || maxChil < minChil)
|
209 | maxChil = minChil;
|
210 |
|
211 | const available = new go.Set();
|
212 | available.addAll(nodeArray);
|
213 | for (let i = 0; i < nodeArray.length; i++) {
|
214 | const parent = nodeArray[i];
|
215 | available.remove(parent);
|
216 |
|
217 | const children = Math.floor(Math.random() * (maxChil - minChil + 1)) + minChil;
|
218 | for (let j = 0; j < children; j++) {
|
219 | const child = available.first();
|
220 | if (child === null)
|
221 | break;
|
222 | available.remove(child);
|
223 |
|
224 | child.parent = parent.key;
|
225 | if (!parent.isGroup) {
|
226 | parent.isGroup = true;
|
227 | }
|
228 | let par = parent;
|
229 | while (par !== null) {
|
230 | par.total += child.total;
|
231 | if (par.parent !== undefined) {
|
232 | par = nodeArray[par.parent];
|
233 | }
|
234 | else {
|
235 | break;
|
236 | }
|
237 | }
|
238 | }
|
239 | }
|
240 | }
|
241 | return nodeArray;
|
242 | }
|
243 | document.getElementById("rebuildGraph").onclick = rebuildGraph;
|
244 |
|
245 | window.myDiagram = myDiagram;
|
246 | </script>
|
247 | </div>
|
248 |
|
249 |
|
250 | </div>
|
251 | </body>
|
252 |
|
253 | <script src="../assets/js/goSamples.js"></script>
|
254 | </html>
|