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