View Javadoc
1 /* 2 * ParallelDisplay.java 3 * 4 * Created on 18. November 2001, 19:49 5 * 6 * Copyright 2001 Flo Ledermann flo@subnet.at 7 * 8 * Licensed under GNU General Public License (GPL). 9 * See http://www.gnu.org/copyleft/gpl.html 10 */ 11 12 package edu.psu.geovista.app.parvis.gui; 13 14 import edu.psu.geovista.app.parvis.model.*; 15 16 import edu.psu.geovista.ui.event.*; 17 import edu.psu.geovista.ui.*; 18 19 import java.awt.*; 20 import java.awt.event.*; 21 import java.awt.geom.Rectangle2D; 22 23 import javax.swing.*; 24 import javax.swing.event.*; 25 26 import java.util.*; 27 28 29 /*** 30 * The swing GUI Component for displaying a parallel coordinate visualisation. 31 * Note that the actual rendering is done by the UI delegate, ParallelDisplayUI 32 * (with its single subclass BasiParallelDisplayUI). This class is used to store 33 * the state of the component and interact with the environment. 34 * 35 * @author Flo Ledermann flo@subnet.at 36 * @version 0.1 37 */ 38 public class ParallelDisplay extends JComponent implements ChangeListener, IndicationListener, 39 SelectionListener, 40 ExcentricLabelClient{ 41 42 43 private transient ExcentricLabels exLabels; 44 /*** Scale values for the axes.*/ 45 private float axisScale[] = null; 46 /*** Offset values for the axes.*/ 47 private float axisOffset[] = null; 48 49 /*** axis -> dimension linking. */ 50 protected int axisOrder[] = null; 51 52 /*** brushed values of records */ 53 protected float brushValues[] = null; 54 55 protected Color recordColor = Color.black; 56 protected Color brushedColor = Color.blue; 57 58 /*** Our model. */ 59 private ParallelSpaceModel model; 60 61 /*** The mode of interaction we are in.*/ 62 protected int editMode = 0; 63 64 public static final int REORDER = 0; 65 public static final int SCALE = 1; 66 public static final int TRANSLATE = 2; 67 /*** used to invert axis orientation */ 68 public static final int INVERT = 3; 69 public static final int BRUSH = 4; 70 71 /*** Whether we have to redraw the whole background. This is usually only 72 needed if the model changes. */ 73 public boolean deepRepaint = true; 74 75 static { 76 UIManager.put("edu.psu.geovista.app.parvis.gui.ParallelDisplayUI", "edu.psu.geovista.app.parvis.gui.BasicParallelDisplayUI"); 77 } 78 /*** 79 * Creates a new ParallelDisplay. 80 */ 81 public ParallelDisplay() { 82 init(null); 83 } 84 85 /*** 86 * Creates a new ParallelDisplay with the given model. 87 * 88 * @param model The model to display. 89 */ 90 public ParallelDisplay(ParallelSpaceModel model){ 91 init(model); 92 } 93 94 /*** 95 * Initializes the component with the given model. 96 * 97 * @param model The model to use. 98 */ 99 protected void init(ParallelSpaceModel model){ 100 101 //System.out.println("Initializing ParallelDisplay Component"); 102 setModel(model); 103 104 setMinimumSize(new Dimension(100, 100)); 105 setPreferredSize(new Dimension(700,400)); 106 107 setBackground(Color.white); 108 setDoubleBuffered(false); 109 setOpaque(true); 110 111 setDefaultPreferences(); 112 113 updateUI(); 114 } 115 116 /*** 117 * Returns the dimension associated with the given axis. Note that the assignment 118 * axisNumber (in the display) => dimensionNumber (in the model) is only the default 119 * setting, the axes might be reordered, or some dimensions may even be left out 120 * or displayed twice. So be sure to always call this function before querying the model 121 * to "convert" the axis number to the right deímension value. 122 * 123 * @param order The number of the axis to get the dimension number for. 124 * 125 * @return The number of the dimnesion to display on the given axis. 126 */ 127 protected int getAxis(int order){ 128 return axisOrder[order]; 129 } 130 131 /*** Returns the number of axes to display. Note that this is not necessarily 132 * equal to the number of dimensions in the model (see above: getAxis()). 133 * 134 * @return The number of axes to display. 135 */ 136 public int getNumAxes(){ 137 if (axisOrder != null) 138 return axisOrder.length; 139 else 140 return 0; 141 } 142 143 /*** 144 * Swaps two axes. This means the dimensions assigned to the two axes are swapped. 145 * 146 * @param axis1 The first axis. 147 * @param axis2 The second axis. 148 */ 149 public void swapAxes(int axis1, int axis2){ 150 int temp = axisOrder[axis1]; 151 152 axisOrder[axis1] = axisOrder[axis2]; 153 axisOrder[axis2] = temp; 154 } 155 156 /*** 157 * Sets up the axis -> dimension assignment. See above: getAxis(); 158 * 159 * @param order An array containing the int ids of the dimensions to be displayed 160 * on the axes. 161 */ 162 public void setAxisOrder(int[] order){ 163 axisOrder = order; 164 } 165 166 /*** 167 * Sets the model to display. 168 * 169 * @param model The model to display. 170 */ 171 public void setModel(ParallelSpaceModel model){ 172 if (this.model != null) { 173 this.model.removeChangeListener(this); 174 175 axisOffset = null; 176 axisScale = null; 177 178 brushValues = null; 179 } 180 181 this.model = model; 182 183 if (model != null){ 184 model.addChangeListener(this); 185 186 187 axisOffset = new float[model.getNumDimensions()]; 188 axisScale = new float[model.getNumDimensions()]; 189 axisOrder = new int[model.getNumDimensions()]; 190 191 brushValues = new float[model.getNumRecords()]; 192 193 for (int i=0; i<model.getNumDimensions(); i++){ 194 // initialize scaling of axis to show maximum detail 195 axisOffset[i] = model.getMaxValue(i); 196 axisScale[i] = model.getMinValue(i) - axisOffset[i]; 197 axisOrder[i] = i; 198 } 199 } 200 201 brushCount = 0; 202 deepRepaint = true; 203 repaint(); 204 205 } 206 207 /*** 208 * Sets the mode for user interaction with the display. 209 * 210 * @param mode The interaction mode to use. 211 */ 212 public void setEditMode(int mode){ 213 editMode = mode; 214 215 resetCursor(); 216 } 217 218 void resetCursor(){ 219 switch (editMode){ 220 case BRUSH: 221 setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); 222 break; 223 default: 224 setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 225 } 226 } 227 /*** 228 * Returns the currently active interaction mode. 229 * 230 * @return The currently active interaction mode. 231 */ 232 public int getEditMode(){ 233 return editMode; 234 } 235 236 /*** 237 * Returns the model. 238 * Actually the model should be hidden, because of the confusion that might 239 * occur by mistaking axes and dimensions (see above: getAxis()). This requires 240 * a rewrite of some parts of the code, so this is marked to do! 241 * 242 * @return The model that is currently displayed ba the component. 243 */ 244 protected ParallelSpaceModel getModel(){ 245 return model; 246 } 247 248 /*** 249 * Returns the number of Records in the model. 250 */ 251 public int getNumRecords(){ 252 if (model != null) 253 return model.getNumRecords(); 254 else 255 return 0; 256 } 257 258 public String getRecordLabel(int num){ 259 if (model != null) 260 return model.getRecordLabel(num); 261 else 262 return null; 263 } 264 265 public float getValue(int recordNum, int axisNum){ 266 if (model != null) { 267 return model.getValue(recordNum, axisOrder[axisNum]); 268 } 269 else { 270 return 0; 271 } 272 } 273 274 private int brushCount = 0; 275 public int[] findSelection(){ 276 int numSelected = 0; 277 for (int i = 0; i < brushValues.length; i++) { 278 if (brushValues[i] > 0f) { 279 numSelected++; 280 } 281 } 282 int[] selection = new int[numSelected]; 283 int counter = 0; 284 for (int i = 0; i < brushValues.length; i++) { 285 if (brushValues[i] > 0f) { 286 selection[counter] = i; 287 counter++; 288 } 289 } 290 return selection; 291 } 292 public float getBrushValue(int num){ 293 if (brushValues != null) 294 return brushValues[num]; 295 else 296 return 0.0f; 297 } 298 299 public void setBrushValue(int num, float val){ 300 if (brushValues != null) 301 brushValues[num] = val; 302 if (val > 0.0f) brushCount = 1; 303 } 304 305 public int getBrushedCount(){ 306 return brushCount; 307 } 308 public int[] getRecordsByValueRange(int axisnum, float min, float max){ 309 310 int ids[] = new int[getNumRecords()]; 311 int count = 0; 312 313 for (int i=0; i<getNumRecords(); i++){ 314 float val = getValue(i, axisnum); 315 if ((val >= min) && (val <= max)) 316 ids[count++] = i; 317 } 318 319 if (count > 0) { 320 int newids[] = new int[count]; 321 System.arraycopy(ids,0,newids,0,count); 322 return newids; 323 } 324 else return new int[0]; 325 } 326 327 public Color getRecordColor(){ 328 return recordColor; 329 } 330 331 public Color getBrushedColor(){ 332 return brushedColor; 333 } 334 335 /*** 336 * Sets the user interface delegate for the component. 337 */ 338 public void setUI(ParallelDisplayUI ui){ 339 super.setUI(ui); 340 } 341 342 public ParallelDisplayUI getUI(){ 343 ParallelDisplayUI pUI = (ParallelDisplayUI)this.ui; 344 return pUI; 345 } 346 347 /*** 348 * Invalidates the component and causes a complete repaint. 349 */ 350 public void invalidate(){ 351 super.invalidate(); 352 deepRepaint = true; 353 } 354 355 /*** 356 * Swing method. 357 */ 358 public void updateUI(){ 359 try { 360 setUI((ParallelDisplayUI)UIManager.getUI(this)); 361 } 362 catch (ClassCastException ccex){ 363 } 364 invalidate(); 365 } 366 367 /*** 368 * Swing method. 369 */ 370 public String getUIClassID(){ 371 ////System.out.println("retrieving classID"); 372 return "edu.psu.geovista.app.parvis.gui.ParallelDisplayUI"; 373 } 374 375 /*** 376 * Invoked when the model has changed its state. 377 * 378 * @param e A ChangeEvent object 379 */ 380 public void stateChanged(ChangeEvent e) { 381 repaint(); 382 } 383 384 /*** 385 * Added by Frank Hardisty 19 July 2002 386 387 */ 388 public void indicationChanged(IndicationEvent e) { 389 int indication = e.getIndication(); 390 this.indication = indication; 391 //model. 392 //BasicParallelDisplayUI bui = (BasicParallelDisplayUI)UIManager.getUI(this); 393 //can't use previous line b/c this makes a new UI when called 394 //bui.setHoverRecord(indication,this); 395 repaint(); 396 397 } 398 public int indication = -1; 399 400 public void selectionChanged(SelectionEvent e){ 401 this.getUI().createBrushImage(this); 402 403 //first zero out old one 404 for (int i = 0; i < brushValues.length; i++) { 405 this.setBrushValue(i,0f); 406 } 407 408 int[] selection = e.getSelection(); 409 if (selection.length == 0) { 410 this.brushCount = 0; 411 this.deepRepaint = true; 412 repaint(); 413 return; 414 } 415 //then put new one in 416 for (int i = 0; i < selection.length; i++) { 417 int selVal = selection[i]; 418 this.setBrushValue(selVal,1f); 419 } 420 this.getUI().renderBrush(); 421 422 repaint(); 423 } 424 425 /*** 426 * Returns the current offset (translation in axis units) for the axis. 427 * 428 * @param num The axis number. 429 * 430 * @return The offset value. 431 **/ 432 public float getAxisOffset(int num){ 433 if (axisOffset != null){ 434 return axisOffset[axisOrder[num]]; 435 } 436 else return 0; 437 } 438 439 /*** 440 * Returns the current scale (visible region in axis units) for the axis. 441 * 442 * @param num The axis number. 443 * 444 * @return The scale value. 445 **/ 446 public float getAxisScale(int num){ 447 if (axisScale != null){ 448 return axisScale[axisOrder[num]]; 449 } 450 else return 0; 451 } 452 453 /*** 454 * Returns a String label for a specific axis. 455 * 456 * @param num The axis number. 457 * 458 * @return A Human-readable label for the axis. 459 */ 460 461 public String getAxisLabel(int num){ 462 if (model != null){ 463 String label = model.getAxisLabel(axisOrder[num]); 464 if (label != null) return label; 465 else return ("X" + axisOrder[num]); 466 } 467 else { 468 return null; 469 } 470 } 471 472 /*** 473 * Sets the offset (translation in axis units) for the axis. 474 * 475 * @param axis The axis number. 476 * @param offset The offset value. 477 **/ 478 public void setAxisOffset(int axis, float offset){ 479 if (axisOffset != null && axisOffset.length > axisOrder[axis]){ 480 axisOffset[axisOrder[axis]] = offset; 481 } 482 483 repaint(); 484 } 485 486 /*** 487 * Sets the scale (visible region in axis units) for the axis. 488 * 489 * @param axis The axis number. 490 * @param scale The scale value. 491 **/ 492 public void setAxisScale(int axis, float scale){ 493 if (axisScale != null && axisScale.length > axisOrder[axis]){ 494 axisScale[axisOrder[axis]] = scale; 495 } 496 497 repaint(); 498 } 499 500 /*** 501 * Configures (scales, translates) all axes to show all values between its 502 * minimum and its maximum on a maximum scale. 503 */ 504 public void minMaxScale(){ 505 for (int i=0; i<getNumAxes(); i++){ 506 // initialize scaling of axis to show maximum detail 507 axisOffset[i] = model.getMaxValue(i); 508 axisScale[i] = model.getMinValue(i) - axisOffset[i]; 509 } 510 511 deepRepaint = true; 512 repaint(); 513 } 514 515 /*** 516 * Configures (scales, translates) all axes to show all values between the 517 * minimum and its maximum of the given variable. 518 */ 519 public void varMinMaxScale(int var){ 520 float min = model.getMinValue(var); 521 float max = model.getMaxValue(var); 522 float range = max - min; 523 //float range = model.getMinValue(var) - axisOffset[var]; 524 for (int i=0; i<getNumAxes(); i++){ 525 //here we have a problem if the scales are too wildly different 526 //specifically, if thisScale is big and range is small 527 //in such a case we just move everything off the screen range 528 529 float thisMax = model.getMaxValue(i); 530 float thisMin = model.getMinValue(i); 531 float scaleFactor = this.getHeight()/range; 532 float endMax = (thisMax - min) * scaleFactor; 533 float endMin = (thisMin - min) * scaleFactor; 534 535 //System.out.println(i); 536 //System.out.println(endMin); 537 //System.out.println(endMax); 538 endMin = Math.abs(endMin); 539 endMax = Math.abs(endMax); 540 //endMax = Math.abs(endMax); 541 if (endMin > 300000 || endMax > 300000){ 542 //System.out.println("fudge!"); 543 //System.out.println(); 544 if (max > 0) { 545 axisOffset[i] = Math.abs(thisMax) * 2f; 546 } else { 547 axisOffset[i] = Math.abs(thisMax) * -2f; 548 } 549 550 axisScale[i] = thisMax - thisMin; 551 } else { 552 axisOffset[i] = max; 553 axisScale[i] = min - max; 554 } 555 } 556 557 deepRepaint = true; 558 repaint(); 559 } 560 private float findScreenY(float value, int axis, float scale){ 561 float newValue = value; 562 BasicParallelDisplayUI comp = (BasicParallelDisplayUI)this.getUI(); 563 564 value -= this.getAxisOffset(axis);; 565 value *= (this.getHeight() - 2 * comp.borderV) / scale; 566 567 return newValue; 568 } 569 /*** 570 * Configures (scales, translates) all axes to show all values between zero 571 * and its maximum on a maximum scale. 572 */ 573 public void zeroMaxScale(){ 574 for (int i=0; i<getNumAxes(); i++){ 575 // initialize scaling of axis to show maximum detail 576 axisOffset[i] = model.getMaxValue(i); 577 axisScale[i] = -1 * axisOffset[i]; 578 } 579 580 deepRepaint = true; 581 repaint(); 582 } 583 584 /*** 585 * Configures (scales, translates) all axes to show values between zero 586 * (or the nagative minimum of all axes) and the maximum value of all axes 587 * on a maximum scale. 588 */ 589 public void minMaxAbsScale(){ 590 int i; 591 592 float absmax = Float.NEGATIVE_INFINITY; 593 float absmin = 0.0f; 594 595 for (i=0; i<getNumAxes(); i++){ 596 // initialize scaling of axis to show maximum detail 597 float val = model.getMaxValue(i); 598 if (val > absmax) absmax = val; 599 val = model.getMinValue(i); 600 if (val < absmin) absmin = val; 601 } 602 603 for (i=0; i<getNumAxes(); i++){ 604 axisOffset[i] = absmax; 605 axisScale[i] = absmin - absmax; 606 } 607 608 deepRepaint = true; 609 repaint(); 610 } 611 612 private Vector progressListeners = new Vector(); 613 614 public void addProgressListener(ProgressListener l){ 615 progressListeners.add(l); 616 } 617 618 public void removeProgressListener(ProgressListener l){ 619 progressListeners.remove(l); 620 } 621 622 public void fireProgressEvent(ProgressEvent e){ 623 Vector list = (Vector)progressListeners.clone(); 624 for (int i=0; i<list.size(); i++){ 625 ProgressListener l = (ProgressListener)list.elementAt(i); 626 l.processProgressEvent(e); 627 } 628 } 629 630 Hashtable preferences = new Hashtable(); 631 632 public void setDefaultPreferences(){ 633 preferences.put("brushRadius",new Float(0.2f)); 634 preferences.put("softBrush",new Boolean(true)); 635 preferences.put("hoverText",new Boolean(true)); 636 preferences.put("hoverLine",new Boolean(true)); 637 } 638 639 public void setFloatPreference(String key, float val){ 640 Object obj = new Float(val); 641 preferences.put(key, obj); 642 } 643 644 public void setBoolPreference(String key, boolean val){ 645 Object obj = new Boolean(val); 646 preferences.put(key, obj); 647 } 648 649 public boolean getBoolPreference(String key){ 650 Object obj = preferences.get(key); 651 if ((obj != null) && (obj instanceof Boolean)){ 652 return ((Boolean)obj).booleanValue(); 653 } 654 // we should throw an exception here; 655 else return false; 656 } 657 658 public float getFloatPreference(String key){ 659 Object obj = preferences.get(key); 660 if ((obj != null) && (obj instanceof Float)){ 661 return ((Float)obj).floatValue(); 662 } 663 // we should throw an exception here; 664 else return 0.0f; 665 } 666 667 //start excentric labeling stuff 668 private void initExcentricLabels(){ 669 this.exLabels = new ExcentricLabels(); 670 exLabels.setComponent(this); 671 exLabels.setOpaque(true); 672 Color halfWhite = new Color(255,255,255,123); 673 exLabels.setBackgroundColor(halfWhite); 674 this.addMouseListener(exLabels); 675 } 676 public String getObservationLabel(int i){ 677 String label = model.getRecordLabel(i); 678 return label; 679 } 680 681 public Shape getShapeAt(int i){ 682 683 Shape shp = new Rectangle2D.Float(0,0,0,0); 684 return shp; 685 } 686 public int[] pickAll(Rectangle2D hitBox){ 687 688 int[] selObs = null;//ls.findSelection(hitBox); 689 return selObs; 690 } 691 //end excentric labeling stuff 692 /*** 693 * adds an IndicationListener 694 */ 695 public void addIndicationListener (IndicationListener l) { 696 listenerList.add(IndicationListener.class, l); 697 } 698 /*** 699 * removes an IndicationListener from the component 700 */ 701 public void removeIndicationListener (IndicationListener l) { 702 listenerList.remove(IndicationListener.class, l); 703 } 704 /*** 705 * Notify all listeners that have registered interest for 706 * notification on this event type. The event instance 707 * is lazily created using the parameters passed into 708 * the fire method. 709 * @see EventListenerList 710 */ 711 public void fireIndicationChanged (int newIndication) { 712 713 // Guaranteed to return a non-null array 714 Object[] listeners = listenerList.getListenerList(); 715 IndicationEvent e = null; 716 // Process the listeners last to first, notifying 717 // those that are interested in this event 718 for (int i = listeners.length - 2; i >= 0; i -= 2) { 719 if (listeners[i] == IndicationListener.class) { 720 // Lazily create the event: 721 if (e == null) { 722 e = new IndicationEvent(this, newIndication); 723 } 724 ((IndicationListener)listeners[i + 1]).indicationChanged(e); 725 } 726 }//next i 727 728 } 729 730 /*** 731 * adds an SelectionListener 732 */ 733 public void addSelectionListener (SelectionListener l) { 734 listenerList.add(SelectionListener.class, l); 735 } 736 /*** 737 * removes an SelectionListener from the component 738 */ 739 public void removeSelectionListener (SelectionListener l) { 740 listenerList.remove(SelectionListener.class, l); 741 } 742 /*** 743 * Notify all listeners that have registered interest for 744 * notification on this event type. The event instance 745 * is lazily created using the parameters passed into 746 * the fire method. 747 * @see EventListenerList 748 */ 749 public void fireSelectionChanged () { 750 751 // Guaranteed to return a non-null array 752 Object[] listeners = listenerList.getListenerList(); 753 SelectionEvent e = null; 754 // Process the listeners last to first, notifying 755 // those that are interested in this event 756 for (int i = listeners.length - 2; i >= 0; i -= 2) { 757 if (listeners[i] == SelectionListener.class) { 758 // Lazily create the event: 759 if (e == null) { 760 int[] newSelection = this.findSelection(); 761 e = new SelectionEvent(this, newSelection); 762 } 763 ((SelectionListener)listeners[i + 1]).selectionChanged(e); 764 } 765 }//next i 766 767 } 768 769 }

This page was automatically generated by Maven