Coverage Report - org.jfree.chart.plot.HCTreeNodeInfo
 
Classes in this File Line Coverage Branch Coverage Complexity
HCTreeNodeInfo
99%
77/78
100%
19/19
3.056
 
 1  
 /* =======================================================================
 2  
  * A visualisation library extension for JFreeChart. Please see JFreeChart
 3  
  * for further information.
 4  
  * =======================================================================
 5  
  * Copyright (C) 2006  University of Helsinki, Department of Computer Science
 6  
  *
 7  
  * This library is free software; you can redistribute it and/or
 8  
  * modify it under the terms of the GNU Lesser General Public
 9  
  * License as published by the Free Software Foundation; either
 10  
  * version 2.1 of the License, or (at your option) any later version.
 11  
  *
 12  
  * This library is distributed in the hope that it will be useful,
 13  
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14  
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 15  
  * Lesser General Public License for more details.
 16  
  *
 17  
  * You should have received a copy of the GNU Lesser General Public
 18  
  * License along with this library; if not, write to the Free Software
 19  
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 20  
  * -----------------------------
 21  
  * Contact:  ohtu@cs.helsinki.fi
 22  
  * -----------------------------
 23  
  *
 24  
  */
 25  
 
 26  
 package org.jfree.chart.plot;
 27  
 
 28  
 import java.util.Iterator;
 29  
 import java.util.LinkedList;
 30  
 
 31  
 import javax.swing.event.ChangeEvent;
 32  
 import javax.swing.event.ChangeListener;
 33  
 
 34  
 import org.jfree.data.hc.HCTreeNode;
 35  
 import org.jfree.data.hc.DataRange;
 36  
 import org.jfree.data.hc.DataRangeMismatchException;
 37  
 
 38  
 /**
 39  
  * A class that stores information about the state of {@link HCTreeNode}-nodes
 40  
  * used in {@link HCPlot}. This is organized as a tree with the same topology
 41  
  * as the original clustering tree. 
 42  
  *
 43  
  * @author  viski project
 44  
  */
 45  
 public class HCTreeNodeInfo {
 46  
 
 47  
     private boolean open;
 48  
     private HCTreeNode node;
 49  
     private HCTreeNodeInfo parent;
 50  
     private HCTreeNodeInfo leftChild;
 51  
     private HCTreeNodeInfo rightChild;
 52  
     private DataRange visibleDataRange;
 53  
     private StandardHCClusteringInfo clusteringInfo;
 54  
 
 55  
     /**
 56  
      * Creates a new HCTreeNodeInfo reflecting data in HCTreeNode.
 57  
      * The constructor also creates objects for all the childs
 58  
      * of HCTreeNode.
 59  
      * After creating the HCTreeNode-tree, the objects are not yet complete.
 60  
      * You still have to call updateVisibleDataRange() on the root
 61  
      * node to make visible data range related functionality working ok.
 62  
      *
 63  
      * @param node the root node of a clustering tree for which
 64  
      * HCTreeNodeInfo-objects are created for.
 65  
      * @param location  column or leaf tree?
 66  
      */
 67  
     public HCTreeNodeInfo(
 68  
             StandardHCClusteringInfo clusteringInfo,
 69  
             HCTreeNode node
 70  197
     ) {
 71  
 
 72  197
         if (node == null) throw new NullPointerException(
 73  
             "A HCTreeNodeInfo object must have a non-null HCTreeNode."
 74  
         );
 75  196
         if (clusteringInfo == null) throw new NullPointerException(
 76  
             "A HCTreeNodeInfo object must have a non-null clustering info."
 77  
         );
 78  
 
 79  195
         this.open = false;
 80  195
         this.node = node;
 81  195
         this.parent = null;
 82  195
         this.clusteringInfo = clusteringInfo;
 83  195
         this.visibleDataRange = new DataRange(0,0);
 84  
 
 85  195
         if (node.getLeftChild() == null) this.leftChild = null;
 86  
         else {
 87  
 
 88  72
             this.leftChild = new HCTreeNodeInfo(
 89  
                 clusteringInfo,
 90  
                 node.getLeftChild()
 91  
             );
 92  72
             this.leftChild.setParent(this);
 93  72
             this.open = true;
 94  
 
 95  
         }
 96  195
         if (node.getRightChild() == null) this.rightChild = null;
 97  
         else {
 98  
 
 99  72
             this.rightChild = new HCTreeNodeInfo(
 100  
                 clusteringInfo,
 101  
                 node.getRightChild()
 102  
             );
 103  72
             this.rightChild.setParent(this);
 104  72
             this.open = true;
 105  
 
 106  
         }
 107  
 
 108  195
     }
 109  
 
 110  
     /**
 111  
      * Open or close a single node.
 112  
      *
 113  
      * @param open  true, if we wish to open the node or false otherwise.
 114  
      */
 115  
     public void setNodeOpen(boolean open) {
 116  
 
 117  5
         if ((this.leftChild != null) && (this.rightChild != null)) {
 118  
 
 119  
             //throw new IllegalArgumentException();
 120  
 
 121  5
            this.open = open;
 122  5
            this.updateVisibleDataRange(); // could be optimized
 123  5
            this.clusteringInfo.notifyChangeListeners(new ChangeEvent(this));
 124  
 
 125  
         }
 126  
 
 127  5
     }
 128  
 
 129  
     /**
 130  
      * The recursive part of setSubTreeOpen().
 131  
      * This should not be invoked by any other method.
 132  
      *
 133  
      * @param open  true, if we wish to open the nodes or false otherwise.
 134  
      */
 135  
     private void setSubTreeOpenRecursively(boolean open) {
 136  
 
 137  20
         if ((this.leftChild != null) && (this.rightChild != null)) {
 138  
 
 139  8
             this.leftChild.setSubTreeOpenRecursively(open);
 140  8
             this.rightChild.setSubTreeOpenRecursively(open);
 141  8
             this.open = open;
 142  
 
 143  
         }
 144  
 
 145  20
     }
 146  
 
 147  
     /**
 148  
      * Open or close each node in the whole subtree.
 149  
      *
 150  
      * @param open  true, if we wish to open the nodes or false otherwise.
 151  
      */
 152  
     public void setSubTreeOpen(boolean open) {
 153  
 
 154  4
         if ((this.leftChild != null) && (this.rightChild != null)) {
 155  
 
 156  
             //throw new IllegalArgumentException();
 157  
 
 158  4
             this.setSubTreeOpenRecursively(open);
 159  4
             this.updateVisibleDataRange(); // could be optimized
 160  4
             this.clusteringInfo.notifyChangeListeners(
 161  
                     new ChangeEvent(this));
 162  
 
 163  
         }
 164  
 
 165  4
     }
 166  
 
 167  
     /**
 168  
      * Is this node open or closed.
 169  
      * It is aranged so that leaf nodes are always closed.
 170  
      *
 171  
      * @return true for nodes that are open and false, if the node is closed.
 172  
      */
 173  
     public boolean isNodeOpen() {
 174  
 
 175  227
         return this.open;
 176  
 
 177  
     }
 178  
 
 179  
     /**
 180  
      * Returns the dataset node corresponding to this node.
 181  
      *
 182  
      * @return  HCTreeNode object.
 183  
      */
 184  
     public HCTreeNode getNode() {
 185  
 
 186  472
         return this.node;
 187  
 
 188  
     }
 189  
 
 190  
     /**
 191  
      * Returns the left child of this node.
 192  
      *
 193  
      * @return a child object or null, if this node has no left child.
 194  
      */
 195  
     public HCTreeNodeInfo getLeftChild() {
 196  
 
 197  321
         return this.leftChild;
 198  
 
 199  
     }
 200  
 
 201  
     /**
 202  
      * Returns the right child of this node.
 203  
      *
 204  
      * @return a child object or null, if this node has no right child.
 205  
      */
 206  
     public HCTreeNodeInfo getRightChild() {
 207  
 
 208  284
         return this.rightChild;
 209  
 
 210  
     }
 211  
 
 212  
     /**
 213  
      * Returns the parent of this node.
 214  
      *
 215  
      * @return a parent object or null, if this is the root node.
 216  
      */
 217  
     public HCTreeNodeInfo getParent() {
 218  
 
 219  3
         return this.parent;
 220  
 
 221  
     }
 222  
 
 223  
     /**
 224  
      * Sets the parent of this node to be a specified node.
 225  
      * This is only intended to be invoked by setLeftChild and
 226  
      * setRightChild methods.
 227  
      *
 228  
      * @param node  specifies the parent.
 229  
      *
 230  
      * @throws IllegalArgumentException  if the parent does not report
 231  
      * this as a child already.
 232  
      * @throws IllegalArgumentException  if this node already has a parent.
 233  
      */
 234  
     public void setParent(HCTreeNodeInfo node) {
 235  
 
 236  
         
 237  148
         if (
 238  
             (parent != null) &&
 239  
             ((parent.getLeftChild() != this) &&
 240  
             (parent.getRightChild() != this))
 241  2
         ) throw new IllegalArgumentException(
 242  
             "A HCTreeNodeInfo object can only be set as a parent of another " +
 243  
             "object, if that node is already its child.");
 244  146
         if (this.parent != null) throw new IllegalArgumentException(
 245  
             "You cannot set a parent for a HCTreeNodeInfo node that already " +
 246  
             "has one.");
 247  145
         this.parent = node;
 248  
 
 249  145
     }
 250  
 
 251  
     /**
 252  
      * Returns the name of this node as a string.
 253  
      *
 254  
      * @return  the name of this node.
 255  
      */
 256  
     public String toString() {
 257  
 
 258  112
         return this.clusteringInfo.getName(this.getNode().getDataRange());
 259  
 
 260  
     }
 261  
 
 262  
     /**
 263  
      * Returns the ClustringInfo object associated with this tree.
 264  
      *
 265  
      * @return  a StandardHCClusteringInfo object.
 266  
      */
 267  
     public StandardHCClusteringInfo getClusteringInfo() {
 268  
       
 269  3
         return this.clusteringInfo;
 270  
 
 271  
     }
 272  
 
 273  
     /**
 274  
      * Returns the visible data range of this subtree.
 275  
      *
 276  
      * @return  a DataRange object.
 277  
      */
 278  
     public DataRange getVisibleDataRange() {
 279  
 
 280  627
         return this.visibleDataRange;
 281  
 
 282  
     }
 283  
 
 284  
     /**
 285  
      * Returns a node representing the specified dataset node.
 286  
      *
 287  
      * @param node  the dataset node specifying a HCTreeNodeInfo node.
 288  
      *
 289  
      * @throws IndexOutOfBoundsException  if the specified node
 290  
      * can't be found from this subtree.
 291  
      *
 292  
      * @return HCTreeNodeInfo object.
 293  
      */
 294  
     public HCTreeNodeInfo getNodeByNode(HCTreeNode node) {
 295  
 
 296  7
         DataRange dr = node.getDataRange();
 297  
 
 298  6
         if (dr.equals(this.getNode().getDataRange())) return this;
 299  3
         if ((this.getLeftChild() != null) &&
 300  
             (this.getLeftChild().getNode().getDataRange().contains(dr)))
 301  1
             return this.getLeftChild().getNodeByNode(node);
 302  2
         if ((this.getRightChild() != null) &&
 303  
             (this.getRightChild().getNode().getDataRange().contains(dr)))
 304  1
             return this.getRightChild().getNodeByNode(node);
 305  
 
 306  1
         throw new IndexOutOfBoundsException();
 307  
 
 308  
     }
 309  
 
 310  
     /**
 311  
      * Returns a node representing the specified dataset row/column.
 312  
      *
 313  
      * @param index  the index of the dataset row/column.
 314  
      *
 315  
      * @throws IndexOutOfBoundsException  if the specified node
 316  
      * can't be found from this subtree.
 317  
      *
 318  
      * @return HCTreeNodeInfo object.
 319  
      */
 320  
     public HCTreeNodeInfo getNodeByIndex(int index) {
 321  
 
 322  7
         if (
 323  
             (this.leftChild != null) &&
 324  
             (this.leftChild.getNode().getDataRange().contains(index))) {
 325  
 
 326  1
             return this.leftChild.getNodeByIndex(index);
 327  
 
 328  
         }
 329  6
         if (
 330  
             (this.rightChild != null) &&
 331  
             (this.rightChild.getNode().getDataRange().contains(index))) {
 332  
 
 333  1
             return this.rightChild.getNodeByIndex(index);
 334  
 
 335  
         }
 336  5
         if (this.getNode().getDataRange().equals(new DataRange(index,index))) {
 337  
 
 338  3
             return this;
 339  
 
 340  
         }
 341  2
         throw new IndexOutOfBoundsException(
 342  
             "There is no node " + index + "under this node.");
 343  
 
 344  
     }
 345  
 
 346  
     /**
 347  
      * Returns a node representing the specified visible row/column.
 348  
      *
 349  
      * @param index  the index of the visible row/column.
 350  
      *
 351  
      * @throws IndexOutOfBoundsException  if the specified node
 352  
      * can't be found from this subtree.
 353  
      *
 354  
      * @return HCTreeNodeInfo object.
 355  
      */
 356  
     public HCTreeNodeInfo getNodeByVisibleIndex(int index) {
 357  
 
 358  352
         if (
 359  
             (this.leftChild != null) &&
 360  
             (this.leftChild.getVisibleDataRange().contains(index)) &&
 361  
             (this.open)) {
 362  
 
 363  91
             return this.leftChild.getNodeByVisibleIndex(index);
 364  
 
 365  
         }
 366  261
         if (
 367  
             (this.rightChild != null) &&
 368  
             (this.rightChild.getVisibleDataRange().contains(index)) &&
 369  
             (this.open)) {
 370  
 
 371  126
             return this.rightChild.getNodeByVisibleIndex(index);
 372  
 
 373  
         }
 374  135
         if (this.visibleDataRange.equals(new DataRange(index,index))) {
 375  
 
 376  125
             return this;
 377  
 
 378  
         }
 379  10
         throw new IndexOutOfBoundsException(
 380  
             "There is no visible node " + index + "under this node.");
 381  
 
 382  
     }
 383  
 
 384  
     /**
 385  
      * Updates visible data range of the subtree rooted at this.
 386  
      * This is only intended to be invoked by updateVisibleDataRange().
 387  
      * This can break the tree, so don't use it.
 388  
      *
 389  
      * @param index  index for the leftmost leaf or closed node of this
 390  
      * subtree.
 391  
      *
 392  
      * @return index for the other subtrees of any of the grandparents
 393  
      * of this node.
 394  
      */
 395  
     private int updateVisibleDataRange(int index) {
 396  
 
 397  202
         if (
 398  
             (this.leftChild == null) ||
 399  
             (this.rightChild == null) ||
 400  
             (!this.open)
 401  
         ) {
 402  
 
 403  124
             this.visibleDataRange = new DataRange(index,index);
 404  124
             return index+1;
 405  
 
 406  
         } else {
 407  
 
 408  78
             index = this.getLeftChild().updateVisibleDataRange(index);
 409  78
             index = this.getRightChild().updateVisibleDataRange(index);
 410  78
             this.visibleDataRange = (DataRange)
 411  
                 (this.getLeftChild().getVisibleDataRange().clone());
 412  
             try {
 413  78
                     this.visibleDataRange.add(
 414  
                     this.getRightChild().getVisibleDataRange()
 415  
                     );
 416  0
             } catch (DataRangeMismatchException e) {
 417  
                 // this never happens if used properly.
 418  78
             }
 419  78
             return index;
 420  
 
 421  
         }
 422  
 
 423  
     }
 424  
 
 425  
     /**
 426  
      * Updates the visibleDataRange attributes of the tree.
 427  
      * This needs to be explicitly invoked only after creating a new
 428  
      * tree. Otherwise, it is invoked automatically.
 429  
      */
 430  
     public void updateVisibleDataRange() {
 431  
 
 432  46
         this.clusteringInfo.getRootNode().updateVisibleDataRange(0);
 433  
 
 434  46
     }
 435  
 
 436  
 }
 437