/edu/uncc/parsets/parsets/VisualConnectionTree.java
Java | 475 lines | 289 code | 102 blank | 84 comment | 85 complexity | e6624e1dd75214c611f86edbfa08e0b4 MD5 | raw file
1package edu.uncc.parsets.parsets;
2
3import java.awt.Graphics2D;
4import java.util.ArrayList;
5import java.util.List;
6
7import edu.uncc.parsets.data.CategoryHandle;
8import edu.uncc.parsets.data.CategoryNode;
9import edu.uncc.parsets.data.CategoryTree;
10import edu.uncc.parsets.data.DimensionHandle;
11import edu.uncc.parsets.parsets.RibbonState;
12
13/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
14 * Copyright (c) 2009, Robert Kosara, Caroline Ziemkiewicz,
15 * and others (see Authors.txt for full list)
16 * All rights reserved.
17 *
18 * Redistribution and use in source and binary forms, with or without
19 * modification, are permitted provided that the following conditions are met:
20 *
21 * * Redistributions of source code must retain the above copyright
22 * notice, this list of conditions and the following disclaimer.
23 * * Redistributions in binary form must reproduce the above copyright
24 * notice, this list of conditions and the following disclaimer in the
25 * documentation and/or other materials provided with the distribution.
26 * * Neither the name of UNC Charlotte nor the names of its contributors
27 * may be used to endorse or promote products derived from this software
28 * without specific prior written permission.
29 *
30 * THIS SOFTWARE IS PROVIDED BY ITS AUTHORS ''AS IS'' AND ANY
31 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
32 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
33 * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
34 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
35 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
36 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
37 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
39 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
41
42public class VisualConnectionTree {
43
44 private VisualConnection root;
45 private VisualConnection test;
46 private RibbonLayoutStyle style = RibbonLayoutStyle.BRANCHING;
47 private RibbonState currentRState;
48
49 public VisualConnectionTree() {
50 super();
51 root = new VisualConnection();
52 setRibbonState(RibbonState.BASIC);
53 }
54
55 public void print(VisualConnection node) {
56 System.out.println(node.toString());
57 for (VisualConnection child : node.getChildren())
58 print(child);
59 }
60
61
62 /**
63 * Build a visual tree based on a data tree without any layout information.
64 *
65 * @param tree The categorical data tree on which to base the visual connections.
66 */
67 public void buildConnectionTree(ArrayList<VisualAxis> axes, CategoryTree tree) {
68
69 root = new VisualConnection();
70 addChildren(root, tree.getRootNode(), axes, 1);
71 }
72
73 private void addChildren(VisualConnection parentNode, CategoryNode dataNode, ArrayList<VisualAxis> axes, int childLevel) {
74
75 if (childLevel == 1) {
76
77 /* The first level is made up of invisible connections.
78 * These can be thought of as ribbons that represent only
79 * a single category in the first dimension, which then
80 * branch out to form the lower ribbons. They're not visible
81 * because they're redundant with the first level category
82 * bars, but they're convenient for treating the ribbons as
83 * a tree.
84 */
85 for (CategoryNode child : dataNode.getChildren()) {
86 VisualConnection newChild = parentNode.addChild(new InvisibleConnection(parentNode, child));
87 addChildren(newChild, child, axes, childLevel+1);
88 }
89
90 orderChildren(parentNode, ((CategoricalAxis)axes.get(0)).getCategoryOrder());
91 } else if (childLevel <= axes.size()){
92
93 // The lower levels are ribbons.
94 if(currentRState == RibbonState.BASIC){
95 for (CategoryNode child : dataNode.getChildren()) {
96 VisualConnection newChild = parentNode.addChild(new BasicRibbon(parentNode, child,
97 (CategoricalAxis)axes.get(childLevel-2),
98 (CategoricalAxis)axes.get(childLevel-1)));
99
100 addChildren(newChild, child, axes, childLevel+1);
101 }
102
103 orderChildren(parentNode, ((CategoricalAxis)axes.get(childLevel-1)).getCategoryOrder());
104 }
105 else if(currentRState == RibbonState.CURVED){
106 for (CategoryNode child : dataNode.getChildren()) {
107 VisualConnection newChild = parentNode.addChild(new CurvedRibbon(parentNode, child,
108 (CategoricalAxis)axes.get(childLevel-2),
109 (CategoricalAxis)axes.get(childLevel-1)));
110
111 addChildren(newChild, child, axes, childLevel+1);
112 }
113
114 orderChildren(parentNode, ((CategoricalAxis)axes.get(childLevel-1)).getCategoryOrder());
115
116 }
117 }
118 }
119
120 private void orderChildren(VisualConnection parentNode, List<CategoryHandle> categoryOrder) {
121
122 ArrayList<VisualConnection> newList = new ArrayList<VisualConnection>();
123
124 newList.addAll(parentNode.getChildren());
125
126 parentNode.getChildren().clear();
127
128 for (CategoryHandle cat : categoryOrder) {
129 for (VisualConnection ribbon : newList) {
130 if (ribbon.getNode().getToCategory().equals(cat)) {
131 parentNode.getChildren().add(ribbon);
132 }
133 }
134 }
135 }
136
137 public void orderChildren(DimensionHandle dimension, List<CategoryHandle> order) {
138 orderChildren(dimension, order, root);
139 }
140
141 private void orderChildren(DimensionHandle dimension, List<CategoryHandle> order, VisualConnection node) {
142 if (node.getChildren() == null)
143 return;
144
145 if (!node.getChildren().isEmpty() && node.getChildren().get(0).getNode().getToCategory().getDimension().equals(dimension))
146 orderChildren(node, order);
147 else
148 for (VisualConnection vc : node.getChildren())
149 orderChildren(dimension, order, vc);
150 }
151
152
153 public void doLayout(int minX, int maxX, BarState currentState) {
154 if (style == RibbonLayoutStyle.BRANCHING)
155 doLayoutBranching(minX, maxX, currentState);
156 else if (style == RibbonLayoutStyle.BUNDLED)
157 doLayoutBundled(minX, maxX, currentState);
158 }
159
160
161 public void doLayoutBranching(int minX, int maxX, BarState currentState) {
162 root.setWidth(maxX - minX);
163 layoutChildren(root, currentState);
164 setColors(root);
165 }
166
167 public void layoutChildren(VisualConnection connectionNode, BarState currentState) {
168 for (VisualConnection child : connectionNode.getChildren())
169 if (child.getNode().isVisible()) {
170 child.setState(currentState);
171 child.layout(connectionNode.getFutureWidth());
172 layoutChildren(child, currentState);
173
174 }
175 }
176
177 public void updateState(int minX, int maxX, BarState currentState){
178 root.setWidth(maxX - minX);
179 updateChildren(root, currentState);
180 }
181
182 public void updateChildren(VisualConnection connectionNode, BarState currentState){
183 for(VisualConnection child : connectionNode.getChildren())
184 if(child.getNode().isVisible()){
185 child.setState(currentState);
186 updateChildren(child,currentState);
187 }
188 }
189
190
191 /**
192 * Lays out the ribbons in a bundled style. Basically, this means doing a
193 * series of phased depth-first traversals on each of the top-level category
194 * nodes.
195 *
196 * @param minX
197 * The left bound of the display space.
198 * @param maxX
199 * The right bound of the display space.
200 */
201 public void doLayoutBundled(int minX, int maxX, BarState currentState) {
202
203 root.setWidth(maxX - minX);
204
205 // Set all the layout and completeness flags to false.
206 resetForBundling(root);
207
208 // If there are no connections, return.
209 if (!root.getChildren().isEmpty()) {
210
211 // While the root is not set to complete...
212 while (!root.isComplete()) {
213
214 // Iterate through each of the invisible nodes, each of
215 // which represents a top-level category bar. At each
216 // step, lay out the leftmost incomplete branch of the
217 // top-level node.
218 for (VisualConnection invisible : root.getChildren()) {
219
220 if (!invisible.isComplete())
221 layoutBranch(invisible, currentState, (maxX-minX));
222
223 // If this node is complete and is the last of the root's
224 // children, then we're done.
225 else if (invisible == root.getChildren().get(root.getChildren().size() - 1))
226 root.setComplete(true);
227 }
228 }
229 }
230 setColors(root);
231 }
232
233 /**
234 * Lay out the leftmost incomplete branch of this connection node.
235 *
236 * @param connectionNode
237 */
238 public boolean layoutBranch(VisualConnection connectionNode, BarState currentState, int totalWidth) {
239
240 // If this node isn't laid out already, lay it out.
241 if (!connectionNode.isLaidOut()) {
242
243 connectionNode.layout(connectionNode.getParent().getWidth());
244 connectionNode.setLaidOut(true);
245
246 // If this is a leaf, this node is completed.
247 if (connectionNode.getChildren().isEmpty()) {
248 connectionNode.setComplete(true);
249 return true;
250 }
251 }
252
253 // If this is not a leaf, call layoutBranch on this node's
254 // leftmost incomplete child.
255 for (VisualConnection child : connectionNode.getChildren())
256 if (!child.isComplete())
257 if (layoutBranch(child, currentState, totalWidth) == false)
258 return false;
259
260 // If all of the children are complete, this node is
261 // completed.
262 connectionNode.setComplete(true);
263
264 return true;
265 }
266
267 public void display(Graphics2D g, float alpha) {
268 display(g, root, alpha);
269 displaySelected(g, root);
270 }
271
272 private void display(Graphics2D g, VisualConnection node, float alpha) {
273
274 node.paint(g, alpha);
275
276 for (VisualConnection child : node.getChildren())
277 if (child.getNode().isVisible())
278 display(g, child, alpha);
279 }
280
281 private void displaySelected(Graphics2D g, VisualConnection node) {
282 if (node.isSelected())
283 node.paintSelected(g);
284
285 for (VisualConnection child : node.getChildren())
286 if (child.getNode().isVisible())
287 displaySelected(g, child);
288 }
289
290 public void setColors(VisualConnection connectionNode) {
291
292 // The top level categorical axis determines the color scheme.
293 if (connectionNode.getChildren().isEmpty())
294 return;
295
296 if (connectionNode == root) {
297 for (VisualConnection childNode : connectionNode.getChildren()) {
298 childNode.setColorBrewerIndex(childNode.getNode().getToCategory().getCategoryNum() - 1);
299 setColors(childNode);
300 }
301 } else {
302 for (VisualConnection childNode : connectionNode.getChildren()) {
303 childNode.setColorBrewerIndex(connectionNode.getColorBrewerIndex());
304 setColors(childNode);
305 }
306 }
307 }
308
309 public String highlightRibbon(int x, int y, CategoryTree dataTree) {
310 clearSelection();
311 VisualConnection selectedRibbon = highlightRibbon(x, y, root);
312 if (selectedRibbon != null) {
313 setSelected(selectedRibbon);
314 return selectedRibbon.getTooltip(dataTree.getFilteredTotal());
315 }
316 return null;
317 }
318
319 public VisualConnection getRibbon(int x, int y, CategoryTree dataTree) {
320 clearSelection();
321 VisualConnection selectedRibbon = highlightRibbon(x, y, root);
322 if (selectedRibbon != null) {
323 setSelected(selectedRibbon);
324 return selectedRibbon;
325 }
326 return null;
327 }
328
329 private VisualConnection highlightRibbon(int x, int y, VisualConnection node) {
330
331 VisualConnection returnNode = null;
332
333 if (node.contains(x, y))
334 returnNode = node;
335
336 for (VisualConnection child : node.getChildren()) {
337 VisualConnection temp = highlightRibbon(x, y, child);
338 if (returnNode == null)
339 returnNode = temp;
340 }
341
342 return returnNode;
343 }
344
345 /**
346 * Sets the selection flags on a given visual connection, as well as all its
347 * direct ancestors and descendants.
348 *
349 * @param connection
350 * The connection to highlight.
351 */
352 public void setSelected(VisualConnection connection) {
353 selectUp(connection);
354 selectDown(connection);
355 }
356
357 private void selectDown(VisualConnection connection) {
358
359 connection.setSelected(true);
360
361 for (VisualConnection child : connection.getChildren())
362 selectDown(child);
363 }
364
365 private void selectUp(VisualConnection connection) {
366
367 connection.setSelected(true);
368
369 if (!connection.equals(root))
370 selectUp(connection.getParent());
371 }
372
373 public void clearSelection() {
374 clearSelection(root);
375 }
376
377 private void clearSelection(VisualConnection node) {
378
379 node.setSelected(false);
380
381 for (VisualConnection child : node.getChildren())
382 clearSelection(child);
383 }
384
385 public void clearConnections() {
386 root = new VisualConnection();
387 }
388
389 public void resetForBundling(VisualConnection connectionNode) {
390 connectionNode.setComplete(false);
391 connectionNode.setLaidOut(false);
392
393 for (VisualConnection child : connectionNode.getChildren())
394 resetForBundling(child);
395 }
396
397 private void selectCategory(CategoryHandle category, VisualConnection node) {
398
399 if (!node.equals(root) && node.getNode().getToCategory().equals(category))
400 for (VisualConnection child : node.getChildren())
401 selectDown(child);
402 else
403 for (VisualConnection child : node.getChildren())
404 selectCategory(category, child);
405 }
406
407 public void selectCategory(CategoryHandle category) {
408 clearSelection();
409 selectCategory(category, root);
410 }
411
412
413 // added in for activecategorybar selection instead of one ribbon
414 public VisualConnection getCategoryBarNode(CategoryHandle category){
415 findCategoryBarNode(category, root);
416 return test;
417
418 }
419
420 public void findCategoryBarNode(CategoryHandle category, VisualConnection node){
421 if (!node.equals(root) && node.getNode().getToCategory().equals(category)){
422 test = node;
423 }
424 else
425 for (VisualConnection child : node.getChildren())
426 findCategoryBarNode(category, child);
427
428
429 }
430
431 /**
432 * @param style the style to set
433 */
434 public void setStyle(RibbonLayoutStyle style) {
435 this.style = style;
436 }
437
438 public void moveCategory(DimensionHandle dimension, CategoryHandle category, int index) {
439 moveCategory(dimension, category, index, root);
440 }
441
442 private void moveCategory(DimensionHandle dimension, CategoryHandle category, int index, VisualConnection node) {
443
444 if (node.getChildren() == null)
445 return;
446
447 ArrayList<VisualConnection> children = node.getChildren();
448
449 if (!children.isEmpty() && children.get(0).getNode().getToCategory().getDimension().equals(dimension)) {
450
451 VisualConnection moveCat = null;
452
453 for (VisualConnection child : children)
454 if (child.getNode().getToCategory().equals(category))
455 moveCat = child;
456
457 if (moveCat != null) {
458 children.remove(moveCat);
459 if (index < children.size())
460 children.add(index, moveCat);
461 else
462 children.add(moveCat);
463 }
464 } else {
465 for (VisualConnection child : children) {
466 moveCategory(dimension, category, index, child);
467 }
468 }
469 }
470
471 public void setRibbonState(RibbonState state){
472 currentRState = state;
473 }
474
475}