1// BEGIN LICENSE BLOCK 2// Version: CMPL 1.1 3// 4// The contents of this file are subject to the Cisco-style Mozilla Public 5// License Version 1.1 (the "License"); you may not use this file except 6// in compliance with the License. You may obtain a copy of the License 7// at www.eclipse-clp.org/license. 8// 9// Software distributed under the License is distributed on an "AS IS" 10// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 11// the License for the specific language governing rights and limitations 12// under the License. 13// 14// The Original Code is The ECLiPSe Constraint Logic Programming System. 15// The Initial Developer of the Original Code is Cisco Systems, Inc. 16// Portions created by the Initial Developer are 17// Copyright (C) 2006 Cisco Systems, Inc. All Rights Reserved. 18// 19// Contributor(s): 20// 21// END LICENSE BLOCK 22 23package com.parctechnologies.eclipse.visualisation; 24 25 26import java.util.*; 27import java.io.*; 28import java.awt.*; 29import java.awt.geom.*; 30import java.beans.*; 31import java.awt.event.*; 32import java.awt.print.*; 33import javax.swing.*; 34import javax.swing.event.*; 35import javax.swing.table.*; 36import javax.swing.filechooser.*; 37import com.parctechnologies.eclipse.*; 38import com.parctechnologies.eclipse.visualisation.*; 39import com.parctechnologies.eclipse.visualisation.viewers.*; 40import att.grappa.*; 41 42/** 43 * A 2D chart viewer (bar chart, points, lines etc..) 44 */ 45public class Chart2DViewer 46 extends GraphViewer 47{ 48 /** component representing the labelled time axis **/ 49 TickBar xTickBar; 50 TickBar yTickBar; 51 52 /** xScale **/ 53 double xScale; 54 55 /** yScale **/ 56 double yScale; 57 58 /** If true, the chart will auto scale to keep the entitre Y range 59 visible **/ 60 boolean scaleToFitHeight; 61 62 /** minimum X value **/ 63 double minimumX; 64 65 /** minimum Y value **/ 66 double minimumY; 67 68 /** maximum X value **/ 69 double maximumX; 70 71 /** maximum Y value **/ 72 double maximumY; 73 74 /** Hold the available viewlet types **/ 75 Collection viewletTypeCollection; 76 77 /** Inital value of xScale **/ 78 static final double INITIAL_XSCALE = 10.0; 79 80 /** Inital value of yScale **/ 81 static final double INITIAL_YSCALE = 5.0; 82 83 /** hold the store listener, so that we can trigger display updates 84 without going via the data store */ 85 StoreListener storeListener; 86 87 /** 88 * Color to use for transparent task bars 89 **/ 90 static final Color transparentColor = new Color(0.0f, 0.0f, 1.0f, 0.2f); 91 92 /** 93 * Color to use for solid task bars 94 **/ 95 static final Color solidColor = new Color(0.0f, 0.0f, 1.0f); 96 97 public Chart2DViewer(Collection viewletTypeCollection, 98 VisClientStateModel stateModel, 99 Viewable viewable) { 100 super((ViewletType)(viewletTypeCollection.iterator().next()), 101 stateModel, viewable, NETWORK_TYPE); 102 this.viewletTypeCollection = viewletTypeCollection; 103 this.moveable = false; 104 this.minimumX = 0.0; 105 this.minimumY = 0.0; 106 this.maximumX = 1.0; 107 this.maximumY = 1.0; 108 this.scaleToFitHeight = false; 109 } 110 111 112 /** 113 * To prepare for the creation event we initialise the viewlet array 114 */ 115 protected void prepareForCreate(CreateEvent createEvent) 116 { 117 // for 2D charts, the entire viewable is queried in advance 118 viewletDataStore = 119 new ViewletArray(createEvent.getViewableSize(), 120 ((ViewableType.ArrayType)createEvent.getViewableType()).getFixityList(), 121 getViewable(), 122 (ViewletFactory)viewletType); 123 viewletDataStore.setSymRef(new SymRef(viewletDataStore, 124 this.getSymRef(), 125 "store")); 126 storeListener = new StoreListener(); 127 viewletDataStore.addViewletDataStoreListener(storeListener); 128 } 129 130 131 protected void initialiseMenu() 132 { 133 //super.initialiseMenu(); 134 addMenuAndPopupMenuItem("View",null); 135 addMenuAndPopupMenuItem("View",new GrappaAntiAliasToggleAction()); 136 137 addMenuAndPopupMenuItem("Graph", new SetBackgroundAction(this, false)); 138 addMenuAndPopupMenuItem("Graph", new SetBackgroundAction(this, true)); 139 140 JMenuItem scaleToFitHeightMenuItem = 141 new JCheckBoxMenuItem("Scale to fit height"); 142 scaleToFitHeightMenuItem. 143 setModel(new BooleanPropertyModel("scaleToFitHeight", 144 this, getPropertyChangeSupport())); 145 setScaleToFitHeightPrivate(true); 146 addMenuAndPopupMenuItem("View", null); 147 addMenuAndPopupMenuItem("View", scaleToFitHeightMenuItem); 148 149 150// addMenuAndPopupMenuItem("Gantt", new SetTaskFillAction(this, "Hollow", null)); 151// addMenuAndPopupMenuItem("Gantt", new SetTaskFillAction(this, "Transparent", transparentColor)); 152// addMenuAndPopupMenuItem("Gantt", new SetTaskFillAction(this, "Solid", solidColor)); 153 //addMenuAndPopupMenuItem("Gantt",new SetXScaleAction()); 154 } 155 156 /** 157 * Returns the mnemonic to use for a given menuTitle 158 */ 159 protected int getMenuMnemonic(String menuTitle) { 160 if ("Chart".equals(menuTitle)) return KeyEvent.VK_C; 161 return super.getMenuMnemonic(menuTitle); 162 } 163 164 /** Returns any actions or sub-menus that should be available via a 165 right-click popup menu */ 166 public Collection getViewerPopupMenuCollection() { 167 Collection result = super.getViewerPopupMenuCollection(); 168 result.add(getPopupMenu("Chart")); 169 return result; 170 } 171 172 173 /** 174 * Construct graph structures 175 */ 176 protected void initialiseGraph() { 177 graph = new SteadyGraph(getViewable().getNameString(), 178 true, 179 false, 180 STEADY_GRAPH_SOFT_MARGIN); 181 // interpret the viewable as a gantt structure 182 initialiseChart(); 183 } 184 185 protected void initialiseComponent() { 186 super.initialiseComponent(); 187 // add ticker bar to the top of the chart 188 GrappaSize graphMargins = (GrappaSize)(graph.getAttributeValue(GrappaConstants.MARGIN_ATTR)); 189 double margin = 0; 190 if (graphMargins != null) { 191 margin = GrappaConstants.PointsPerInch * graphMargins.width; 192 } 193 xTickBar = new TickBar(graphPanel, 194 false, 195 10, 196 1, 197 INITIAL_XSCALE, 198 margin); 199 yTickBar = new TickBar(graphPanel, 200 true, 201 10, 202 1, 203 INITIAL_YSCALE, 204 margin); 205 TickBarMouseListener tbml = new TickBarMouseListener(); 206 xTickBar.addMouseListener(tbml); 207 xTickBar.addMouseMotionListener(tbml); 208 scrollPane.setColumnHeaderView(xTickBar); 209 scrollPane.setRowHeaderView(yTickBar); 210 setXScalePrivate(new Double(INITIAL_XSCALE)); 211 setYScalePrivate(new Double(INITIAL_YSCALE)); 212 } 213 214 class TickBarMouseListener extends MouseAdapter implements MouseMotionListener { 215 int initialX; 216 double initialXScale; 217 218 public void mousePressed(MouseEvent e) { 219 initialX=e.getX()-(int)(xTickBar.displayMargin*xTickBar.zoomLevel); 220 initialXScale=xScale; 221 if (DebuggingSupport.logMessages) { 222 DebuggingSupport.logMessage(this,"initialX="+initialX); 223 } 224 } 225 226 public void mouseDragged(MouseEvent e) { 227 int currentX=e.getX()-(int)(xTickBar.displayMargin*xTickBar.zoomLevel); 228 if (DebuggingSupport.logMessages) { 229 DebuggingSupport.logMessage(this,"currentX="+currentX); 230 } 231 xTickBar.setScalingRight(currentX > initialX); 232 xTickBar.setScale((initialXScale * currentX)/initialX); 233 } 234 235 public void mouseReleased(MouseEvent e) { 236 int currentX=e.getX()-(int)(xTickBar.displayMargin*xTickBar.zoomLevel); 237 if (DebuggingSupport.logMessages) { 238 DebuggingSupport.logMessage(this,"currentX="+currentX); 239 } 240 setXScale((initialXScale * currentX)/initialX); 241 } 242 243 public void mouseMoved(MouseEvent e) { 244 // do nothing 245 } 246 } 247 248 249 public void startEvent(VisEvent event, java.util.List goalResults) 250 { 251 if(event instanceof ExpandEvent) 252 { 253 super.startEvent(event, goalResults); 254 // add new nodes for these elements 255 addNewNodes(getExpandingElementIndices()); 256 // trigger a refresh of the display to take account of the new nodes 257 storeListener.rangeUpdated(viewletDataStore, viewletDataStore.getEntireViewletRange()); 258 graph.resetBoundingBox(); 259 graph.repaint(); 260 return; 261 } 262 super.startEvent(event, goalResults); 263 } 264 265 String makeLabel(java.util.List index) { 266 StringBuffer sb = 267 new StringBuffer(getLocationName(1,((Integer)index.get(0)).intValue())); 268 for(int i = 1 ; i < index.size(); i++) { 269 sb.append(','); 270 sb.append(getLocationName(i+1,((Integer)index.get(i)).intValue())); 271 } 272 return sb.toString(); 273 } 274 275 void addNewNodes(ViewletRange range) { 276 for(Iterator it = range.iterator(); 277 it.hasNext(); ) { 278 java.util.List index = (java.util.List)it.next(); 279 ViewletData data = null; 280 281 if (DebuggingSupport.logMessages) { 282 DebuggingSupport.logMessage(this,"data="+data+" index="+index); 283 } 284 //Node node = new Node(graph, index.toString()); 285 Node node; 286 287 node = insertNode(viewletDataStore, index, data, viewletType); 288 node.getGrappaNexus().boundText = false; 289 //node.setAttribute("label",makeLabel(index)); 290 } 291 } 292 293 protected void initialiseChart() { 294 if (DebuggingSupport.logMessages) { 295 DebuggingSupport.logMessage(this, "initialiseChart called"); 296 } 297 addNewNodes(viewletDataStore.getEntireViewletRange()); 298 } 299 300 301 public String getToolTip(java.util.List index) { 302 return makeLabel(index)+":"+viewletDataStore.getViewletDataAt(index).toString(); 303 } 304 305 306 /** 307 * configure all the viewlets prior to having the graph drawn for 308 * the first time. 309 */ 310 void customizeViewlets() { 311 int numElements = ((Integer)(viewletDataStore.getSize().get(0))).intValue(); 312 int numColumns = ((Integer)(viewletDataStore.getSize().get(1))).intValue(); 313 ArrayList index = new ArrayList(2); 314 index.add(new Integer(1)); 315 index.add(new Integer(1)); 316 for(int i = 1; i < numElements+1; i++) { 317 if (DebuggingSupport.logMessages) { 318 DebuggingSupport.logMessage(this, "i="+i); 319 } 320 ViewletData data; 321 Integer integerI = new Integer(i); 322 index.set(0,integerI); 323 324 index.set(1,new Integer(1)); 325 //data = viewletDataStore.getViewletDataAt(index); 326 327 Element element = getElement(index); 328 329 elementToViewletType(index).customizeElement(viewletDataStore, index, element); 330 } 331 } 332 333 public void zoomToLevel(float zoomLevel) 334 { 335 // scale the graph 336 super.zoomToLevel(zoomLevel); 337 // scale the tickbar 338 xTickBar.zoomToLevel(zoomLevel); 339 yTickBar.zoomToLevel(zoomLevel); 340 } 341 342 public void zoomInByRatio(float zoomRatio) 343 { 344 // scale the graph 345 super.zoomInByRatio(zoomRatio); 346 // scale the tickbar 347 xTickBar.zoomInByRatio(zoomRatio); 348 yTickBar.zoomInByRatio(zoomRatio); 349 } 350 351 352 /** 353 * Set the amount by which the display should be stretched in the X 354 * direction. 355 **/ 356 public void setXScale(double xScale) { 357 if (DebuggingSupport.logMessages) { 358 DebuggingSupport.logMessage(this,"old xScale="+this.xScale+" new xScale="+xScale); 359 } 360 new ViewerSetXScaleCommand(this, xScale).issue(); 361 } 362 363 /** 364 * Actualy set the amount by which the display should be stretched 365 * in the X direction. This method is called from the 366 * ViewerSetXScaleCommand command. 367 **/ 368 public void setXScalePrivate(Object xScale) { 369 this.xScale=((Double)xScale).doubleValue(); 370 ((ChartBarViewletType)viewletType).setXScale(this.xScale); 371 xTickBar.setScale(this.xScale); 372 storeListener.rangeUpdated(viewletDataStore, viewletDataStore.getEntireViewletRange()); 373 graph.resetBoundingBox(); 374 } 375 376 /** 377 * Actualy set the amount by which the display should be stretched 378 * in the Y direction. This method is called from the 379 * ViewerSetXScaleCommand command. 380 **/ 381 public void setYScalePrivate(Object yScale) { 382 this.yScale=((Double)yScale).doubleValue(); 383 ((ChartBarViewletType)viewletType).setYScale(this.yScale); 384 yTickBar.setScale(this.yScale); 385 storeListener.rangeUpdated(viewletDataStore, viewletDataStore.getEntireViewletRange()); 386 graph.resetBoundingBox(); 387 } 388 389 /** 390 * The Y scale needs to be recalculated (probably because a data 391 * point has increased) 392 **/ 393 protected void reScaleY() { 394 double paneHeight = scrollPane.getViewport().getExtentSize().getHeight() - 395 STEADY_GRAPH_SOFT_MARGIN; 396 //double graphHeight = graph.getBoundingBox().getBounds().getHeight(); 397 double graphHeight = (maximumY - minimumY); 398 setYScalePrivate(new Double(paneHeight/graphHeight)); 399 } 400 401 public boolean getScaleToFitHeight() 402 { 403 return(scaleToFitHeight); 404 } 405 406 407 public void setScaleToFitHeight(boolean newValue) 408 { 409 (new ViewerSetBooleanPropertyCommand(this, "scaleToFitHeight", newValue)).issue(); 410 } 411 412 public void setScaleToFitHeightPrivate(boolean newValue) 413 { 414 boolean oldValue = scaleToFitHeight; 415 scaleToFitHeight = newValue; 416 this.getPropertyChangeSupport(). 417 firePropertyChange("scaleToFitHeight", oldValue, newValue); 418 } 419 420 421 /** 422 * Class to synchronize the graph elements when changes happen to 423 * the ViewletData. */ 424 protected class StoreListener implements ViewletDataStoreListener { 425 public StoreListener() { 426 } 427 428 public void rangeUpdated(ViewletDataStore store, 429 ViewletRange range) { 430 for(Iterator it = range.iterator(); it.hasNext(); ) { 431 java.util.List index = new ArrayList((java.util.List)(it.next())); 432 //index.set(0,new Integer(-1)); 433 Element element = getElement(index); 434 // configure the renderer 435 ViewletType type = elementToViewletType(index); 436 if (type instanceof BoundsViewletType && scaleToFitHeight) { 437 BoundsViewletType boundsType = 438 (BoundsViewletType)type; 439 double min = boundsType.getMin(store, index); 440 double max = boundsType.getMax(store, index); 441 boolean change = false; 442 if ( min < minimumY ) { 443 minimumY = min; 444 change = true; 445 } 446 if ( max > maximumY ) { 447 maximumY = max; 448 change = true; 449 } 450 if (change) { 451 reScaleY(); 452 } 453 } 454 type.customizeElement(store, index, element); 455 } 456 graph.repaint(); 457 } 458 } 459 460} 461 462