/edu/uncc/parsets/parsets/VisualConnectionTree.java

https://code.google.com/p/parsets/ · Java · 475 lines · 289 code · 102 blank · 84 comment · 85 complexity · e6624e1dd75214c611f86edbfa08e0b4 MD5 · raw file

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