KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > info > monitorenter > gui > chart > Chart2D


1 /*
2  * Chart2D, a component for displaying ITrace2D instances.
3  * Copyright (C) 2002 Achim Westermann, Achim.Westermann@gmx.de
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18  *
19  * If you modify or optimize the code in a useful way please let me know.
20  * Achim.Westermann@gmx.de
21  */

22 package info.monitorenter.gui.chart;
23
24 import info.monitorenter.gui.chart.axis.AxisLinear;
25 import info.monitorenter.gui.chart.labelpainters.LabelPainterDefault;
26 import info.monitorenter.gui.chart.rangepolicies.ARangePolicy;
27 import info.monitorenter.util.collections.TreeSetGreedy;
28
29 import java.awt.Color JavaDoc;
30 import java.awt.Cursor JavaDoc;
31 import java.awt.Dimension JavaDoc;
32 import java.awt.Font JavaDoc;
33 import java.awt.FontMetrics JavaDoc;
34 import java.awt.Graphics JavaDoc;
35 import java.awt.Graphics2D JavaDoc;
36 import java.awt.Stroke JavaDoc;
37 import java.awt.event.MouseEvent JavaDoc;
38 import java.awt.image.BufferedImage JavaDoc;
39 import java.beans.PropertyChangeEvent JavaDoc;
40 import java.beans.PropertyChangeListener JavaDoc;
41 import java.util.Iterator JavaDoc;
42 import java.util.LinkedList JavaDoc;
43 import java.util.List JavaDoc;
44 import java.util.Set JavaDoc;
45
46 import javax.swing.JPanel JavaDoc;
47 import javax.swing.JToolTip JavaDoc;
48 import javax.swing.SwingUtilities JavaDoc;
49
50 /**
51  * <code> Chart2D</code> is a component for diplaying the data contained in a
52  * <code>ITrace2D</code>. It inherits many features from
53  * <code>javax.swing.JPanel</code> and allows specific configuration. <br>
54  * In order to simplify the use of it, the scaling, labeling and choosing of
55  * display- range is done automatical which flattens the free configuration.
56  * <p>
57  * There are several default settings that may be changed in
58  * <code>Chart2D</code><br>
59  * <ul>
60  * <li>The display range is choosen always big enough to show every
61  * <code>TracePoint2D</code> contained in the all <code>ITrace2d</code>
62  * instances connected. This is because the
63  * {@link info.monitorenter.gui.chart.IAxis} of the chart (for x and y) use by
64  * default a
65  * {@link info.monitorenter.gui.chart.rangepolicies.RangePolicyUnbounded}. To
66  * change this, get the axis of the chart to change (via {@link #getAxisX()},
67  * {@link #getAxisY()}) and invoke
68  * {@link info.monitorenter.gui.chart.IAxis#setRangePolicy(IRangePolicy)} with
69  * the desired viewport behaviour.
70  * <li>During the <code>paint()</code> operation every
71  * <code>TracePoint2D</code> is taken from the <code>ITrace2D</code>-
72  * instance exactly in the order, it's iterator returns them. From every
73  * <code>TracePoint2D</code> then a line is drawn to the next. <br>
74  * Unordered traces may cause a weird display. Choose the right implementation
75  * of <code>ITrace2D</code> to avoid this. To change this line painting
76  * behaviour you can use custom renderers at the level of traces via
77  * {@link info.monitorenter.gui.chart.ITrace2D#addTracePainter(ITracePainter)}
78  * or
79  * {@link info.monitorenter.gui.chart.ITrace2D#setTracePainter(ITracePainter)}.
80  * <li>If no scaling is choosen, no grids will be painted. See:
81  * <code>{@link IAxis#setPaintScale(boolean)}</code> This allows saving of
82  * many computations.
83  * <li>The distance of the scalepoints is always big enough to display the
84  * labels fully without overwriting each ohter.</li>
85  * </ul>
86  * <p>
87  * <h3>Demo- code:</h3>
88  * <pre>
89  * ...
90  * Chart2D test = new Chart2D();
91  * JFrame frame = new JFrame(&quot;Chart2D- Debug&quot;);
92  *
93  * frame.setSize(400,200);
94  * frame.setVisible(true);
95  * ITrace2D atrace = new Trace2DLtd(100);
96  * ...
97  * &lt;further configuration of trace&gt;
98  * ...
99  * test.addTrace(atrace);
100  * ....
101  * while(expression){
102  * atrace.addPoint(adouble,bdouble);
103  * ....
104  * }
105  * </pre>
106  * <p>
107  * <h3>PropertyChangeEvents</h3>
108  * {@link java.beans.PropertyChangeListener} instances may be added via
109  * {@link javax.swing.JComponent#addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)}.
110  * They inherit the properties to listen from
111  * {@link java.awt.Container#addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)}.
112  * Additionally more <code>PropertyChangeEvents</code> are triggered.
113  * <p>
114  * As the set of traces inside this class is a collection (and no single
115  * property) the {@link java.beans.PropertyChangeEvent} fired for a change of
116  * properties property will contain a reference to the <code>Chart2D</code>
117  * instance as well as the <code>ITrace2D</code> (if involved in the change).
118  * <br>
119  * <table width="100%">
120  * <tr>
121  * <th ><code>getPropertyName()</code></th>
122  * <th><code>getSource()</code></th>
123  * <th><code>getOldValue()</code></th>
124  * <th><code>getNewValue()</code></th>
125  * <th>occurance</th>
126  * </tr>
127  * <tr>
128  * <td>{@link #PROPERTY_ADD_REMOVE_TRACE}</td>
129  * <td>{@link Chart2D}</td>
130  * <td>null</td>
131  * <td>{@link info.monitorenter.gui.chart.ITrace2D}</td>
132  * <td>if a new instance is added.</td>
133  * </tr>
134  * <tr>
135  * <td>{@link #PROPERTY_ADD_REMOVE_TRACE}</td>
136  * <td>{@link Chart2D}</td>
137  * <td>{@link info.monitorenter.gui.chart.ITrace2D}</td>
138  * <td>null</td>
139  * <td>if an instance is deleted.</td>
140  * </tr>
141  * <tr>
142  * <td>{@link #PROPERTY_BACKGROUND_COLOR}</td>
143  * <td>{@link Chart2D}</td>
144  * <td>{@link java.awt.Color}</td>
145  * <td>{@link java.awt.Color}</td>
146  * <td>if a change of the background color occurs.</td>
147  * </tr>
148  * <tr>
149  * <td>{@link #PROPERTY_AXIS_X}</td>
150  * <td>{@link Chart2D}</td>
151  * <td>{@link info.monitorenter.gui.chart.IAxis}</td>
152  * <td>{@link info.monitorenter.gui.chart.IAxis}</td>
153  * <td>if a new axis is set in x dimension.</td>
154  * </tr>
155  * <tr>
156  * <td>{@link #PROPERTY_AXIS_Y}</td>
157  * <td>{@link Chart2D}</td>
158  * <td>{@link info.monitorenter.gui.chart.IAxis}</td>
159  * <td>{@link info.monitorenter.gui.chart.IAxis}</td>
160  * <td>if a new axis is set in y dimension.</td>
161  * </tr>
162  * <tr>
163  * <td>{@link #PROPERTY_GRID_COLOR}</td>
164  * <td>{@link Chart2D}</td>
165  * <td>{@link java.awt.Color}</td>
166  * <td>{@link java.awt.Color}</td>
167  * <td>if a change of the grid color occurs.</td>
168  * </tr>
169  * <tr>
170  * <td>{@link #PROPERTY_PAINTLABELS}</td>
171  * <td>{@link Chart2D}</td>
172  * <td>{@link java.lang.Boolean}</td>
173  * <td>{@link java.lang.Boolean}</td>
174  * <td>if a change of the paint labels flag occurs.</td>
175  * </tr>
176  * </table>
177  * <p>
178  *
179  * @author <a HREF='mailto:Achim.Westermann@gmx.de'>Achim Westermann </a>
180  *
181  * @version $Revision: 1.14 $
182  */

183
184 public class Chart2D extends JPanel JavaDoc implements PropertyChangeListener JavaDoc {
185
186   /**
187    * The adapting paint Thread. It adapts its frequency of invoking
188    * {@link java.awt.Component#repaint()} depending on the amount of points
189    * added since it's last cycle (reported by
190    * {@link Chart2D#traceChanged(Trace2DChangeEvent)}.
191    * <p>
192    * It also triggers (re-)scaling of (within {@link Chart2D#paint(Graphics)})
193    * of the {@link TracePoint2D} instances in the contained {@link ITrace2D}
194    * instances.
195    * <p>
196    * The speed adaption depends on the internal constants {@link #MIN_SLEEP} and
197    * {@link #MAX_SLEEP}. Increasing speed is factorial (amount of new points
198    * times 2), decreasing is fixed by 10 ms (if no new points are there).
199    * <p>
200    *
201    * @author <a HREF="mailto:Achim.Westermann@gmx.de">Achim Westermann </a>
202    */

203   class Painter extends Thread JavaDoc {
204     /** The maximum sleep time between to paint invocations. */
205     static final long MAX_SLEEP = 10000;
206
207     /** The minimum sleep time between to paint invocations. */
208     static final long MIN_SLEEP = 100;
209
210     /**
211      * Dynamically adapts to the update speed of data. Calculated in run().
212      */

213     private long m_sleepTime = Chart2D.Painter.MIN_SLEEP;
214
215     /**
216      * @see java.lang.Runnable#run()
217      */

218     public void run() {
219       try {
220         while (!this.isInterrupted()) {
221           sleep(this.m_sleepTime);
222           // Calculation of sleeptime:
223
// has to be done before repaint, as paint removes
224
// pending changes! But after sleep, as in that time
225
// most new points will be added.
226
if (Chart2D.DEBUG_THREADING) {
227             System.out.println("Painter, 0 locks");
228           }
229
230           synchronized (Chart2D.this) {
231             if (Chart2D.DEBUG_THREADING) {
232               System.out.println("Painter, 1 lock");
233             }
234             if (!Chart2D.this.isDirtyScaling() || Chart2D.this.m_unscaledPoints.size() == 0) {
235               // lazy slow down:
236
if (this.m_sleepTime < Chart2D.Painter.MAX_SLEEP) {
237                 this.m_sleepTime += 10;
238               }
239             } else {
240               // fast speed-up:
241
this.m_sleepTime = Math.max(this.m_sleepTime
242                   - (Chart2D.this.m_unscaledPoints.size() * 2), Chart2D.Painter.MIN_SLEEP);
243               repaint();
244             }
245           }
246           if (Chart2D.DEBUG_THREADING) {
247             System.out.println("Painter, left lock");
248           }
249         }
250
251       } catch (InterruptedException JavaDoc ie) {
252         /*
253          * This is the case, if call to interrupt() came while Thread was
254          * sleeping.
255          */

256
257       }
258     }
259   }
260
261   /**
262    * A package wide switch for debugging problems with scaling. Set to false the
263    * compiler will remove the debugging statements.
264    */

265   protected static final boolean DEBUG_SCALING = false;
266
267   /**
268    * A package wide switch for debugging problems with multithreading. Set to
269    * false the compiler will remove the debugging statements.
270    */

271   public static final boolean DEBUG_THREADING = false;
272
273   /**
274    * The bean property <code>constant</code> identifying a change of the
275    * internal set of <code>{@link ITrace2D}</code> instances.
276    * <p>
277    *
278    * Use this constant to register a {@link java.beans.PropertyChangeListener}
279    * with the <code>Chart2D</code>.
280    * <p>
281    * See the class description for property change events fired.
282    */

283   public static final String JavaDoc PROPERTY_ADD_REMOVE_TRACE = "chart.addTrace";
284
285   /**
286    * The bean property <code>constant</code> identifying a change of the
287    * internal <code>{@link IAxis}</code> instance for the x dimension.
288    * <p>
289    * Use this constant to register a {@link java.beans.PropertyChangeListener}
290    * with the <code>Chart2D</code>.
291    * <p>
292    * See the class description for property change events fired.
293    * <p>
294    */

295   public static final String JavaDoc PROPERTY_AXIS_X = "chart.axis.x";
296
297   /**
298    * The bean property <code>constant</code> identifying a change of the
299    * internal <code>{@link IAxis}</code> instance for the y dimension.
300    * <p>
301    * Use this constant to register a {@link java.beans.PropertyChangeListener}
302    * with the <code>Chart2D</code>.
303    * <p>
304    * See the class description for property change events fired.
305    * <p>
306    */

307   public static final String JavaDoc PROPERTY_AXIS_Y = "chart.axis.y";
308
309   /**
310    * The bean property <code>constant</code> identifying a change of the
311    * background color. <br>
312    * Use this constant to register a {@link java.beans.PropertyChangeListener}
313    * with the <code>Chart2D</code>.
314    * <p>
315    * The property change events for this change are constructed and fired by the
316    * superclass {@link java.awt.Container} so this constant is just for
317    * clarification of the String that is related to that property.
318    * <p>
319    */

320   public static final String JavaDoc PROPERTY_BACKGROUND_COLOR = "background";
321
322   /**
323    * <p>
324    * The bean property <code>constant</code> identifying a change of the font.
325    * <br>
326    * Use this constant to register a {@link java.beans.PropertyChangeListener}
327    * with the <code>Chart2D</code>.
328    * </p>
329    * <p>
330    * The property change events for this change are constructed and fired by the
331    * superclass {@link java.awt.Container} so this constant is just for
332    * clarification of the String that is related to that property.
333    * </p>
334    */

335   public static final String JavaDoc PROPERTY_FONT = "font";
336
337   /**
338    * The bean property <code>constant</code> identifying a change of the
339    * foreground color. <br>
340    * Use this constant to register a {@link java.beans.PropertyChangeListener}
341    * with the <code>Chart2D</code>.
342    * <p>
343    * The property change events for this change are constructed and fired by the
344    * superclass {@link java.awt.Container} so this constant is just for
345    * clarification of the String that is related to that property.
346    * <p>
347    */

348   public static final String JavaDoc PROPERTY_FOREGROUND_COLOR = "foreground";
349
350   /**
351    * The bean property <code>constant</code> identifying a change of the grid
352    * color.
353    * <p>
354    * Use this constant to register a {@link java.beans.PropertyChangeListener}
355    * with the <code>Chart2D</code>.
356    * <p>
357    */

358   public static final String JavaDoc PROPERTY_GRID_COLOR = "chart.gridColor";
359
360   /**
361    * The bean property <code>constant</code> identifying a change of the paint
362    * labels flag.
363    * <p>
364    * Use this constant to register a {@link java.beans.PropertyChangeListener}
365    * with the <code>Chart2D</code>.
366    * <p>
367    */

368   public static final String JavaDoc PROPERTY_PAINTLABELS = "chart.paintLabels";
369
370   /**
371    * Generated serial version UID.
372    */

373   private static final long serialVersionUID = 3978425840633852978L;
374
375   /** Constant describing the x axis (needed for scaling). * */
376   public static final int X = 1;
377
378   /** Constant describing the x and y axis (needed for scaling). * */
379   public static final int X_Y = 3;
380
381   /** Constant describing the y axis (needed for scaling). * */
382   public static final int Y = 2;
383
384   /** The x axis insance. * */
385   private AAxis m_axisX;
386
387   /** The y axis insance. * */
388   private AAxis m_axisY;
389
390   /** The grid color. * */
391   private Color JavaDoc m_gridcolor = Color.lightGray;
392
393   /**
394    * The internal label painter for this chart.
395    */

396   private ILabelPainter m_labelPainter;
397
398   /** Internal Thread that manages adaptive painting speed. */
399   private Painter m_painter = new Painter();
400
401   /**
402    * Flag that decides wether labels for traces are painted below the chart.
403    */

404   private boolean m_paintLabels = true;
405
406   /** Flag for showing coordinates as tool tips. */
407   private boolean m_toolTipCoords = true;
408
409   /**
410    * The internal <code>TreeSetGreedy</code> use to store the different
411    * <code>ITrace2d</code> instanes to paint.
412    */

413   private TreeSetGreedy m_traces = new TreeSetGreedy();
414
415   /**
416    * <p>
417    * An internal counter that is increased for every bound property change event
418    * received from traces.
419    * </p>
420    * <p>
421    * It is reset whenever the painting Thread triggers a repaint and used to
422    * calculate the new time it will sleep until starting the next paint
423    * operation.
424    * </p>
425    */

426   // private boolean m_updateScaling = true;
427
private List JavaDoc m_unscaledPoints = new LinkedList JavaDoc();
428
429   /**
430    * The end x pixel coordinate of the chart.
431    */

432   private int m_xChartEnd;
433
434   /**
435    * The start x coordinate of the chart.
436    */

437   private int m_xChartStart;
438
439   /** The current maximum x value for all points in all traces. */
440   private double m_xmax;
441
442   /**
443    * The maximum x value for all points in all traces of the previous paint
444    * operation.
445    */

446   private double m_xmaxold;
447
448   /** The current minimum x value for all points in all traces. */
449   private double m_xmin;
450
451   /**
452    * The minimum x value for all points in all traces of the previous paint
453    * operation.
454    */

455   private double m_xminold;
456
457   /**
458    * The y coordinate of the upper edge of the chart's display area in px.
459    * <p>
460    *
461    * The px coordinates in awt / swing start from top and increase towards the
462    * bottom.
463    * <p>
464    *
465    */

466   private final int m_yChartEnd = 20;
467
468   /**
469    * The start y coordinate of the chart.
470    */

471   private int m_yChartStart;
472
473   /** The current maximum y value for all points in all traces. */
474   private double m_ymax;
475
476   /**
477    * The maximum y value for all points in all traces of the previous paint
478    * operation.
479    */

480   private double m_ymaxold;
481
482   /** The current minimum y value for all points in all traces. */
483   private double m_ymin;
484
485   /**
486    * The minimum y value for all points in all traces of the previous paint
487    * operation.
488    */

489   private double m_yminold;
490
491   /**
492    * Creates a new chart.
493    * <p>
494    */

495   public Chart2D() {
496     this.setAxisX(new AxisLinear());
497     this.setAxisY(new AxisLinear());
498     this.setLabelPainter(new LabelPainterDefault());
499
500     Font JavaDoc dflt = this.getFont();
501     if (dflt != null) {
502       this.setFont(new Font JavaDoc(dflt.getFontName(), dflt.getStyle(), 10));
503     }
504     this.getBackground();
505     this.setBackground(Color.white);
506     // one initial call to paint for side effect computations
507
// potentially needed from outside (m_XstartChart...):
508
this.repaint();
509     // don't continue when application stops.
510
this.m_painter.setDaemon(true);
511     this.m_painter.start();
512
513     // set a custom cursor:
514
this.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
515
516     // turn on tooltip coordinates at mouse cursor:
517
this.setToolTipCoords(true);
518   }
519
520   /**
521    * Adds the trace to this chart. It will be painted (if it's
522    * {@link ITrace2D#isVisible()} returns true) in this chart.
523    * <p>
524    * This method will trigger a {@link java.beans.PropertyChangeEvent} being
525    * fired on all instances registered by
526    * {@link javax.swing.JComponent#addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)}
527    * (registered with <code>String</code> argument
528    * {@link #PROPERTY_ADD_REMOVE_TRACE}).
529    * <p>
530    *
531    * @see Chart2D#PROPERTY_ADD_REMOVE_TRACE
532    */

533   public final void addTrace(final ITrace2D points) {
534     if (Chart2D.DEBUG_THREADING) {
535       System.out.println("trace.addTrace, 0 locks");
536     }
537     synchronized (this) {
538       if (Chart2D.DEBUG_THREADING) {
539         System.out.println("trace.addTrace, 1 lock");
540       }
541       synchronized (points) {
542         if (Chart2D.DEBUG_THREADING) {
543           System.out.println("trace.addTrace, 2 locks");
544         }
545
546         points.setRenderer(this);
547         this.m_traces.add(points);
548         // listen to bound changes and more
549
points.addPropertyChangeListener(ITrace2D.PROPERTY_MAX_X, this);
550         points.addPropertyChangeListener(ITrace2D.PROPERTY_MIN_X, this);
551         points.addPropertyChangeListener(ITrace2D.PROPERTY_MAX_Y, this);
552         points.addPropertyChangeListener(ITrace2D.PROPERTY_MIN_Y, this);
553         // These are repaint candidates:
554
points.addPropertyChangeListener(ITrace2D.PROPERTY_COLOR, this);
555         points.addPropertyChangeListener(ITrace2D.PROPERTY_STROKE, this);
556         points.addPropertyChangeListener(ITrace2D.PROPERTY_VISIBLE, this);
557         points.addPropertyChangeListener(ITrace2D.PROPERTY_ZINDEX, this);
558         points.addPropertyChangeListener(ITrace2D.PROPERTY_PAINTERS, this);
559         // listen to newly added points
560
// this is needed for scaling at point level.
561
// else every bound change would force to rescale all traces!
562
points.addPropertyChangeListener(ITrace2D.PROPERTY_TRACEPOINT, this);
563
564         // for static traces (all points added) we won't get events.
565
// so update here:
566
boolean change = false;
567         double maxX = points.getMaxX();
568         if (maxX > this.m_xmax) {
569           this.m_xmax = maxX;
570           change = true;
571         }
572         double maxY = points.getMaxY();
573         if (maxY > this.m_ymax) {
574           this.m_ymax = maxY;
575           change = true;
576         }
577         double minX = points.getMinX();
578         if (minX < this.m_xmin) {
579           this.m_xmin = minX;
580           change = true;
581         }
582         double minY = points.getMinY();
583         if (minY < this.m_ymin) {
584           this.m_ymin = minY;
585           change = true;
586         }
587
588         // initial scaling:
589
// if a change in bounds was recorded here, scaling
590
// will be done by the paint method. If not, new traces
591
// (that could contain points already) have to be scaled here.
592
if (!change) {
593           this.scaleTrace(points, Chart2D.X_Y);
594         }
595         // special case: first trace added:
596
if (this.m_traces.size() == 1) {
597           this.m_ymin = minY;
598           this.m_ymax = maxY;
599           this.m_xmin = minX;
600           this.m_xmax = maxX;
601         }
602
603       }
604       if (Chart2D.DEBUG_THREADING) {
605         System.out.println("trace.addTrace, left 1 lock: 1 remaining");
606       }
607     }
608     if (Chart2D.DEBUG_THREADING) {
609       System.out.println("trace.addTrace, left 1 lock: 0 remaining");
610     }
611     // A deadlock occurs if a listener triggers paint.
612
// This was the case with ChartPanel.
613
SwingUtilities.invokeLater(new Runnable JavaDoc() {
614       public void run() {
615         Chart2D.this.firePropertyChange(Chart2D.PROPERTY_ADD_REMOVE_TRACE, null, points);
616       }
617     });
618   }
619
620   /**
621    * @see javax.swing.JComponent#createToolTip()
622    */

623   public JToolTip JavaDoc createToolTip() {
624     /*
625      * If desired return here a HTMLToolTip that transforms the text given to
626      * setTipText into a View (with BasicHTML) and sets it as the
627      * putClientProperty BasicHtml.html of itself.
628      */

629     JToolTip JavaDoc result = super.createToolTip();
630     return result;
631   }
632
633   /**
634    * Destroys the chart by stopping the internal painter thread.
635    * <p>
636    * This method is only of interest if you have an application that dynamically
637    * adds and removes charts. So if you use the same Chart2D object(s) during
638    * the applications lifetime there is no need to use this method.
639    * <p>
640    */

641   public void destroy() {
642     if (Chart2D.DEBUG_THREADING) {
643       System.out.println("destroy, 0 locks");
644     }
645     synchronized (this) {
646       if (Chart2D.DEBUG_THREADING) {
647         System.out.println("destroy, 1 lock");
648       }
649       this.m_axisX = null;
650       this.m_axisY = null;
651       this.m_traces = null;
652       this.m_unscaledPoints = null;
653       this.m_painter.interrupt();
654       this.m_painter = null;
655     }
656   }
657
658   /**
659    * Cleanup when this instance is dropped.
660    * <p>
661    * The internal painter thread is stoppped.
662    * <p>
663    *
664    * @see java.lang.Object#finalize()
665    * @throws Throwable
666    * if a finalizer of a superclass fails.
667    */

668   public void finalize() throws Throwable JavaDoc {
669     super.finalize();
670     this.destroy();
671   }
672
673   /**
674    * Searches for the maximum x value of all contained ITraces.
675    * <p>
676    * This method is triggered when a trace fired a property change for property
677    * <code>{@link ITrace2D#PROPERTY_MAX_X}</code> with a value lower than the
678    * internal stored maximum x.
679    * <p>
680    * Performance breakdown is avoided because all <code>ITrace2D</code>
681    * implementations cache their max and min values.
682    * <p>
683    * Note that the <code>Chart2D</code> itself does not use this value for
684    * painting. It uses {@link AAxis#getRange()} which itself accesses this value
685    * by accessors and additionally filters the value by it's assigned internal
686    * {@link ARangePolicy}.
687    * <p>
688    *
689    * @return the maximum x value of all traces.
690    */

691   protected final double findMaxX() {
692     double max = -Double.MAX_VALUE, tmp;
693     Iterator JavaDoc it = this.m_traces.iterator();
694     ITrace2D trace;
695     while (it.hasNext()) {
696       trace = (ITrace2D) it.next();
697       if (trace.isVisible()) {
698         tmp = trace.getMaxX();
699         if (tmp > max) {
700           max = tmp;
701         }
702       }
703     }
704     if (max == -Double.MAX_VALUE) {
705       max = 10;
706     }
707     return max;
708   }
709
710   /**
711    * Searches for the maximum y value of all contained ITraces.
712    * <p>
713    * This method is triggered when a trace fired a property change for property
714    * <code>{@link ITrace2D#PROPERTY_MAX_Y}</code> with a value lower than the
715    * internal stored maximum x.
716    * <p>
717    * Note that the <code>Chart2D</code> itself does not use this value for
718    * painting. It uses {@link AAxis#getRange()} which itself accesses this value
719    * by accessors and additionally filters the value by it's assigned internal
720    * {@link ARangePolicy}.
721    * <p>
722    *
723    * @return the maximum y value of all traces.
724    */

725   protected final double findMaxY() {
726     double max = -Double.MAX_VALUE, tmp;
727     Iterator JavaDoc it = this.m_traces.iterator();
728     ITrace2D trace;
729     while (it.hasNext()) {
730       trace = (ITrace2D) it.next();
731       if (trace.isVisible()) {
732         tmp = trace.getMaxY();
733         if (tmp > max) {
734           max = tmp;
735         }
736       }
737     }
738     if (max == -Double.MAX_VALUE) {
739       max = 10;
740     }
741     return max;
742   }
743
744   /**
745    * Searches for the minimum x value of all contained ITraces.
746    * <p>
747    * This method is triggered when a trace fired a property change for property
748    * <code>{@link ITrace2D#PROPERTY_MIN_Y}</code> with a value lower than the
749    * internal stored maximum x.
750    * <p>
751    * Note that the <code>Chart2D</code> itself does not use this value for
752    * painting. It uses {@link AAxis#getRange()} which itself accesses this value
753    * by accessors and additionally filters the value by it's assigned internal
754    * {@link ARangePolicy}.
755    * <p>
756    *
757    * @return the minimum x value of all traces.
758    */

759
760   protected final double findMinX() {
761     double min = Double.MAX_VALUE, tmp;
762     Iterator JavaDoc it = this.m_traces.iterator();
763     ITrace2D trace;
764     while (it.hasNext()) {
765       trace = (ITrace2D) it.next();
766       if (trace.isVisible()) {
767         tmp = trace.getMinX();
768         if (tmp < min) {
769           min = tmp;
770         }
771       }
772     }
773     if (min == Double.MAX_VALUE) {
774       min = 0;
775     }
776     return min;
777   }
778
779   /**
780    * Searches for the minimum y value of all contained ITraces.
781    * <p>
782    * This method is triggered when a trace fired a property change for property
783    * <code>{@link ITrace2D#PROPERTY_MIN_Y}</code> with a value lower than the
784    * internal stored maximum x.
785    * <p>
786    * Note that the <code>Chart2D</code> itself does not use this value for
787    * painting. It uses {@link AAxis#getRange()} which itself accesses this value
788    * by accessors and additionally filters the value by it's assigned internal
789    * {@link ARangePolicy}.
790    * <p>
791    *
792    * @return the minimum y value of all traces.
793    */

794
795   protected final double findMinY() {
796     double min = Double.MAX_VALUE, tmp;
797     Iterator JavaDoc it = this.m_traces.iterator();
798     ITrace2D trace;
799     while (it.hasNext()) {
800       trace = (ITrace2D) it.next();
801       if (trace.isVisible()) {
802         tmp = trace.getMinY();
803         if (tmp < min) {
804           min = tmp;
805         }
806       }
807     }
808     if (min == Double.MAX_VALUE) {
809       min = 0;
810     }
811     return min;
812   }
813
814   /**
815    * Returns the axis for the x dimension.
816    * <p>
817    *
818    * @return the axis for the x dimension.
819    */

820   public final IAxis getAxisX() {
821     return this.m_axisX;
822   }
823
824   /**
825    * Returns the axis for the y dimension.
826    * <p>
827    *
828    * @return the axis for the y dimension.
829    */

830   public final IAxis getAxisY() {
831     return this.m_axisY;
832   }
833
834   /**
835    * Returns the color of the grid.
836    * <p>
837    *
838    * @return the color of the grid.
839    */

840   public final Color JavaDoc getGridColor() {
841     return this.m_gridcolor;
842   }
843
844   /**
845    * Returns the painter for the labels.
846    * <p>
847    *
848    * @return Returns the painter for the labels.
849    */

850   public ILabelPainter getLabelPainter() {
851     return this.m_labelPainter;
852   }
853
854   /**
855    * Returns the maximum x-value of all contained <code>{@link ITrace2D}</code>
856    * instances.
857    * <p>
858    *
859    * @return the maximum x-value of all contained <code>{@link ITrace2D}</code>
860    * instances.
861    */

862   public final double getMaxX() {
863     return this.m_xmax;
864   }
865
866   /**
867    * Returns the maximum y-value of all contained <code>{@link ITrace2D}</code>
868    * instances.
869    * <p>
870    *
871    * @return the maximum y-value of all contained <code>{@link ITrace2D}</code>
872    * instances.
873    */

874   public final double getMaxY() {
875     return this.m_ymax;
876   }
877
878   /**
879    * Returns the minimum x-value of all contained <code>{@link ITrace2D}</code>
880    * instances.
881    * <p>
882    *
883    * @return the minimum x-value of all contained <code>{@link ITrace2D}</code>
884    * instances.
885    * @see #getOffsetX()
886    */

887   public final double getMinX() {
888     return this.m_xmin;
889   }
890
891   /**
892    * Returns the minimum y-value of all contained <code>{@link ITrace2D}</code>
893    * instances.
894    * <p>
895    *
896    * @return the minimum y-value of all contained <code>{@link ITrace2D}</code>
897    * instances.
898    */

899   public final double getMinY() {
900     return this.m_ymin;
901   }
902
903   /**
904    * Returns the minimum x-value displayed by this chart.
905    * <p>
906    * This value is not neccessarily the minimum x-value of all internal
907    * <code>{@link ITrace2D}</code> instances. Rounding or forced bounds on the
908    * internal <code>X - {@link AAxis}</code> may have an effect on this value.
909    * <p>
910    *
911    * @return the minimum x-value displayed by this chart.
912    * @see #getMinX()
913    */

914   public final double getOffsetX() {
915     return this.m_axisX.getRange().getMin();
916   }
917
918   /**
919    * @see javax.swing.JComponent#getToolTipText(java.awt.event.MouseEvent)
920    */

921   public String JavaDoc getToolTipText(final MouseEvent JavaDoc event) {
922     if (this.m_toolTipCoords) {
923       TracePoint2D tracePoint = this.translateMousePosition(event);
924       StringBuffer JavaDoc result = new StringBuffer JavaDoc("X: ");
925       result.append(this.getAxisX().getFormatter().format(tracePoint.getX())).append(" ");
926       result.append("Y: ");
927       result.append(this.getAxisY().getFormatter().format(tracePoint.getY()));
928       return result.toString();
929     } else {
930       return super.getToolTipText(event);
931     }
932   }
933
934   /**
935    * Returns the internal set of traces that are currently rendered by this
936    * instance.
937    * <p>
938    * Caution: the original internal set is returned. Modifications on the
939    * returned set could cause problems.
940    * <p>
941    *
942    * @return the internal set of traces that are currently rendered by this
943    * instance.
944    */

945   public final Set JavaDoc getTraces() {
946     return this.m_traces;
947   }
948
949   /**
950    * Returns the x coordinate of the chart's right edge in px.
951    * <p>
952    *
953    * @return the x coordinate of the chart's right edge in px.
954    */

955   protected final int getXChartEnd() {
956     return this.m_xChartEnd;
957   }
958
959   /**
960    * Returns the x coordinate of the chart's left edge in px.
961    * <p>
962    *
963    * @return Returns the x coordinate of the chart's left edge in px.
964    */

965   public int getXChartStart() {
966     return this.m_xChartStart;
967   }
968
969   /**
970    * Returns the y coordinate of the upper edge of the chart's display area in
971    * px.
972    * <p>
973    * Pixel coordinates in awt / swing start from top and increase towards the
974    * bottom.
975    * <p>
976    *
977    * @return The y coordinate of the upper edge of the chart's display area in
978    * px.
979    */

980   protected final int getYChartEnd() {
981     return this.m_yChartEnd;
982   }
983
984   /**
985    * Returns the x coordinate of the chart's lower edge in px.
986    * <p>
987    *
988    * Pixel coordinates in awt / swing start from top and increase towards the
989    * bottom.
990    * <p>
991    *
992    * @return Returns the y coordinate of the chart's lower edge in px.
993    */

994   public int getYChartStart() {
995     return this.m_yChartStart;
996   }
997
998   /**
999    * Interpolates (linear) the two neighbouring points.
1000   * <p>
1001   * Calling this method only makes sense if argument visible is a visible point
1002   * and argument invisible is an invisible point.
1003   * <p>
1004   * Visibility is determined only by their internally normalized coordinates
1005   * that are withen [0.0,1.0] for visible points.
1006   * <p>
1007   *
1008   * @param visible
1009   * the visible point.
1010   * @param invisible
1011   * the invisible point.
1012   * @return the interpolation towards the exceeded bound.
1013   */

1014  private TracePoint2D interpolateVisible(final TracePoint2D invisible, final TracePoint2D visible) {
1015
1016    /*
1017     * Interpolation is done by the two point form:
1018     *
1019     * (y - y1)/(x - x1) = (y2 - y1)/(x2 - x1)
1020     *
1021     * solved to the missing value.
1022     */

1023    // interpolate
1024
double xInterpolate;
1025    double yInterpolate;
1026    // find the bound that has been exceeded:
1027
if (invisible.m_scaledX > 1.0) {
1028      // right x bound
1029
xInterpolate = 1.0;
1030      yInterpolate = (visible.m_scaledY - invisible.m_scaledY)
1031          / (visible.m_scaledX - invisible.m_scaledX) * (1.0 - invisible.m_scaledX)
1032          + invisible.m_scaledY;
1033    } else if (invisible.m_scaledX < 0.0) {
1034      // left x bound
1035
xInterpolate = 0.0;
1036      yInterpolate = (visible.m_scaledY - invisible.m_scaledY)
1037          / (visible.m_scaledX - invisible.m_scaledX) * -invisible.m_scaledX + invisible.m_scaledY;
1038    } else if (invisible.m_scaledY > 1.0) {
1039      // upper y bound, checked
1040
yInterpolate = 1.0;
1041      xInterpolate = (1.0 - invisible.m_scaledY) * (visible.m_scaledX - invisible.m_scaledX)
1042          / (visible.m_scaledY - invisible.m_scaledY) + invisible.m_scaledX;
1043    } else {
1044      // lower y bound
1045
yInterpolate = 0.0;
1046      xInterpolate = -invisible.m_scaledY * (visible.m_scaledX - invisible.m_scaledX)
1047          / (visible.m_scaledY - invisible.m_scaledY) + invisible.m_scaledX;
1048    }
1049    // TODO: do we have to compute and set the unscaled real values too?
1050
TracePoint2D result = new TracePoint2D(0.0, 0.0);
1051    result.m_scaledOnce = true;
1052    result.m_scaledX = xInterpolate;
1053    result.m_scaledY = yInterpolate;
1054    return result;
1055  }
1056
1057  /**
1058   * Returns true if the bounds of all {@link TracePoint2D} instances of all
1059   * internal {@link ITrace2D} instances have changed since all points have been
1060   * normalized to a value between 0 and 1.
1061   * <p>
1062   *
1063   * @return true if the bounds of all {@link TracePoint2D} instances of all
1064   * internal {@link ITrace2D} instances have changed since all points
1065   * have been normalized to a value between 0 and 1.
1066   */

1067  protected final boolean isDirtyScaling() {
1068    return isDirtyScaling(Chart2D.X) || isDirtyScaling(Chart2D.Y);
1069  }
1070
1071  /**
1072   * Returns true if the bounds in the given dimension of all
1073   * {@link TracePoint2D} instances of all internal {@link ITrace2D} instances
1074   * have changed since all points have been normalized to a value between 0 and
1075   * 1.
1076   * <p>
1077   *
1078   * @param axis
1079   * one of {@link Chart2D#X} or {@link Chart2D#Y}.
1080   * @return true if the bounds of all {@link TracePoint2D} instances of all
1081   * internal {@link ITrace2D} instances have changed since all points
1082   * have been normalized to a value between 0 and 1.
1083   */

1084  protected final boolean isDirtyScaling(final int axis) {
1085    boolean result = false;
1086    if (axis == Chart2D.Y) {
1087      result = (this.m_ymax != this.m_ymaxold) || (this.m_ymin != this.m_yminold);
1088
1089    } else if (axis == Chart2D.X) {
1090      result = (this.m_xmax != this.m_xmaxold) || (this.m_xmin != this.m_xminold);
1091
1092    }
1093    return result;
1094  }
1095
1096  /**
1097   * <p>
1098   * Returns true if labels for each chart are painted below it, false else.
1099   * </p>
1100   *
1101   * @return Returns if labels are painted.
1102   */

1103  public final boolean isPaintLabels() {
1104    return this.m_paintLabels;
1105  }
1106
1107  /**
1108   * Returns true if chart coordinates are drawn as tool tips.
1109   * <p>
1110   *
1111   * @return true if chart coordinates are drawn as tool tips.
1112   */

1113  public final boolean isToolTipCoords() {
1114    return this.m_toolTipCoords;
1115  }
1116
1117  /**
1118   * Returns true if the given point is in the visible drawing area of the
1119   * Chart2D.
1120   * <p>
1121   * If the point is null false will be returned.
1122   * <p>
1123   * This only works if the point argument has been scaled already.
1124   * <p>
1125   *
1126   * @param point
1127   * the point to test.
1128   * @return true if the given point is in the visible drawing area of the
1129   * Chart2D.
1130   */

1131  private boolean isVisible(final TracePoint2D point) {
1132    if (point == null) {
1133      return false;
1134    }
1135    return !(point.m_scaledX > 1.0 || point.m_scaledX < 0.0 || point.m_scaledY > 1.0 || point.m_scaledY < 0.0);
1136  }
1137
1138  /**
1139   * Returns an <code>Iterator</code> over the contained {@link ITrace2D}
1140   * instances.
1141   *
1142   * @return an <code>Iterator</code> over the contained {@link ITrace2D}
1143   * instances.
1144   */

1145  public final Iterator JavaDoc iterator() {
1146    return this.m_traces.iterator();
1147  }
1148
1149  /**
1150   * <p>
1151   * A basic rule of a JComponent is: <br>
1152   * <b>Never invoke this method directly. </b> <br>
1153   * See the description of
1154   * {@link javax.swing.JComponent#paint(java.awt.Graphics)} for details.
1155   * </p>
1156   * <p>
1157   * If you do invoke this method you may encounter performance issues,
1158   * flickering UI and even deadlocks.
1159   * </p>
1160   *
1161   * @param g
1162   * the graphics context to use.
1163   */

1164  public synchronized void paint(final Graphics JavaDoc g) {
1165    if (Chart2D.DEBUG_THREADING) {
1166      System.out.println("paint, 1 lock");
1167    }
1168    super.paint(g);
1169    // Some operations (e.g. stroke) need Graphics2d
1170
Graphics2D JavaDoc g2d = (Graphics2D JavaDoc) g;
1171    // will be used in several iterations.
1172
ITrace2D tmpdata;
1173    Iterator JavaDoc traceIt;
1174    ITracePainter tracePainter;
1175
1176    // update the scaling
1177
this.updateScaling(false);
1178    Dimension JavaDoc d = this.getSize();
1179    int width = (int) d.getWidth();
1180    int height = (int) d.getHeight();
1181    // finding the font- dimensions in px
1182
FontMetrics JavaDoc fontdim = g.getFontMetrics();
1183    int fontwidth = fontdim.charWidth('0');
1184    int fontheight = fontdim.getHeight();
1185    int yLabelMax = (this.m_axisY.getFormatter().getMaxAmountChars()) * fontwidth;
1186    this.m_xChartStart = yLabelMax;
1187    // painting trace labels
1188
int labelheight = this.paintTraceLables(g2d);
1189
1190    // finding startpoint of coordinate System.
1191
// -4 is for showing colons of x - labels that are below the baseline
1192
this.m_yChartStart = height - fontheight - labelheight - 4;
1193
1194    int xLabelMax = (this.m_axisX.getFormatter().getMaxAmountChars()) * fontwidth;
1195    this.m_xChartEnd = width - xLabelMax;
1196    int rangex = this.m_xChartEnd - this.m_xChartStart;
1197    int rangey = this.m_yChartStart - this.m_yChartEnd;
1198
1199    this.paintCoordinateSystem(g2d);
1200
1201    // paint Traces.
1202
int tmpx, oldtmpx, tmpy, oldtmpy;
1203    TracePoint2D oldpoint = null, newpoint = null, tmppt = null;
1204    traceIt = this.m_traces.iterator();
1205    Stroke JavaDoc backupStroke = g2d.getStroke();
1206    int count = 0;
1207    Iterator JavaDoc itTracePainters;
1208    while (traceIt.hasNext()) {
1209      count++;
1210      tmpdata = (ITrace2D) traceIt.next();
1211      if (tmpdata.isVisible()) {
1212
1213        g2d.setStroke(tmpdata.getStroke());
1214        g2d.setColor(tmpdata.getColor());
1215        itTracePainters = tmpdata.getTracePainters().iterator();
1216        synchronized (tmpdata) {
1217          if (Chart2D.DEBUG_THREADING) {
1218            System.out.println("paint, 2 locks");
1219          }
1220          while (itTracePainters.hasNext()) {
1221            tracePainter = (ITracePainter) itTracePainters.next();
1222            tracePainter.startPaintIteration();
1223            Iterator JavaDoc pointIt = tmpdata.iterator();
1224            boolean newpointVisible, oldpointVisible;
1225            // searching the first valid point, done as a wrapping loop to cope
1226
// with zero points.
1227
while (pointIt.hasNext()) {
1228              oldpoint = newpoint;
1229              newpoint = (TracePoint2D) pointIt.next();
1230
1231              newpointVisible = isVisible(newpoint);
1232              oldpointVisible = isVisible(oldpoint);
1233              if (newpointVisible || oldpointVisible) {
1234                tmpx = this.m_xChartStart + (int) (newpoint.m_scaledX * rangex);
1235                tmpy = this.m_yChartStart - (int) (newpoint.m_scaledY * rangey);
1236                while (pointIt.hasNext()) {
1237                  oldpoint = newpoint;
1238                  oldtmpx = tmpx;
1239                  oldtmpy = tmpy;
1240                  newpoint = (TracePoint2D) pointIt.next();
1241                  newpointVisible = isVisible(newpoint);
1242                  oldpointVisible = isVisible(oldpoint);
1243                  if (!newpointVisible && !oldpointVisible) {
1244                    // nothing to paint...
1245
continue;
1246                  } else if (newpointVisible && !oldpointVisible) {
1247                    // entering the visible bounds: interpolate from old point
1248
// to
1249
// new point
1250
oldpoint = interpolateVisible(oldpoint, newpoint);
1251                    tmpx = this.m_xChartStart + (int) (newpoint.m_scaledX * rangex);
1252                    tmpy = this.m_yChartStart - (int) (newpoint.m_scaledY * rangey);
1253                    oldtmpx = this.m_xChartStart + (int) (oldpoint.m_scaledX * rangex);
1254                    oldtmpy = this.m_yChartStart - (int) (oldpoint.m_scaledY * rangey);
1255                    tracePainter.paintPoint(oldtmpx, oldtmpy, tmpx, tmpy, g2d);
1256
1257                  } else if (!newpointVisible && oldpointVisible) {
1258                    // leaving the visible bounds:
1259
tmppt = (TracePoint2D) newpoint.clone();
1260                    newpoint = interpolateVisible(newpoint, oldpoint);
1261                    tmpx = this.m_xChartStart + (int) (newpoint.m_scaledX * rangex);
1262                    tmpy = this.m_yChartStart - (int) (newpoint.m_scaledY * rangey);
1263
1264                    tracePainter.paintPoint(oldtmpx, oldtmpy, tmpx, tmpy, g2d);
1265                    tracePainter.discontinue();
1266                    // restore for next loop start:
1267
newpoint = tmppt;
1268
1269                  } else {
1270                    // staying in the visible bounds: just paint
1271
tmpx = this.m_xChartStart + (int) (newpoint.m_scaledX * rangex);
1272                    tmpy = this.m_yChartStart - (int) (newpoint.m_scaledY * rangey);
1273
1274                    tracePainter.paintPoint(oldtmpx, oldtmpy, tmpx, tmpy, g2d);
1275                  }
1276                }
1277              }
1278            }
1279            tracePainter.endPaintIteration();
1280          }
1281
1282        }
1283        if (Chart2D.DEBUG_THREADING) {
1284
1285          System.out.println("paint, left lock");
1286        }
1287      }
1288    }
1289    g2d.setStroke(backupStroke);
1290  }
1291
1292  /**
1293   * Paints the axis, the scales and the labels for the chart.
1294   * <p>
1295   * <b>Caution</b> This is highly coupled code and only factored out for
1296   * better overview. This method may only be called by {@link #paint(Graphics)}
1297   * and the order of this invocation there must not be changed.
1298   * <p>
1299   *
1300   * @param g2d
1301   * the graphics context to use.
1302   */

1303  private void paintCoordinateSystem(final Graphics2D JavaDoc g2d) {
1304    ILabelPainter labelPainter = this.getLabelPainter();
1305    int rangex = this.m_xChartEnd - this.m_xChartStart;
1306    int rangey = this.m_yChartStart - this.m_yChartEnd;
1307    // finding the font- dimensions in px
1308
FontMetrics JavaDoc fontdim = g2d.getFontMetrics();
1309    int fontheight = fontdim.getHeight();
1310
1311    // drawing the axis, scale and labels.
1312
g2d.setColor(this.getForeground());
1313    g2d.drawLine(this.m_xChartStart, this.m_yChartStart, this.m_xChartEnd, this.m_yChartStart);
1314    g2d.drawLine(this.m_xChartStart, this.m_yChartStart, this.m_xChartStart, this.m_yChartEnd);
1315    // drawing the labels.
1316
int tmp;
1317    LabeledValue[] labels;
1318    if (this.m_axisX.isPaintScale()) {
1319      // first for x- angle.
1320
tmp = 0;
1321      labels = this.m_axisX.getScaleValues();
1322      for (int i = 0; i < labels.length; i++) {
1323        tmp = this.m_xChartStart + (int) (labels[i].getValue() * rangex);
1324        labelPainter.paintXTick(tmp, this.m_yChartStart, labels[i].isMajorTick(), g2d);
1325        labelPainter.paintXLabel(tmp, this.m_yChartStart + fontheight, labels[i].getLabel(), g2d);
1326        if (this.m_axisX.isPaintGrid()) {
1327          if ((i != 0) || (tmp != this.m_xChartStart)) {
1328            g2d.setColor(this.m_gridcolor);
1329            g2d.drawLine(tmp, this.m_yChartStart - 1, tmp, this.m_yChartEnd);
1330            g2d.setColor(this.getForeground());
1331
1332          }
1333        }
1334      }
1335      // unit-labeling
1336
g2d.drawString(this.m_axisX.getFormatter().getUnit().getUnitName(), this.m_xChartEnd - 20,
1337          this.m_yChartStart - 5);
1338    }
1339    if (this.m_axisY.isPaintScale()) {
1340      // then for y- angle.
1341
labels = this.m_axisY.getScaleValues();
1342      for (int i = 0; i < labels.length; i++) {
1343        tmp = this.m_yChartStart - (int) (labels[i].getValue() * rangey);
1344        labelPainter.paintYTick(this.m_xChartStart, tmp, labels[i].isMajorTick(), g2d);
1345        labelPainter.paintYLabel(2, tmp, labels[i].getLabel(), g2d);
1346        if (this.m_axisY.isPaintGrid()) {
1347          if ((i != 0) || (tmp != this.m_yChartStart)) {
1348            g2d.setColor(this.m_gridcolor);
1349            g2d.drawLine(this.m_xChartStart + 1, tmp, this.m_xChartEnd, tmp);
1350            g2d.setColor(this.getForeground());
1351          }
1352        }
1353      }
1354      // unit-labeling
1355
g2d.drawString(this.m_axisY.getFormatter().getUnit().getUnitName(), this.m_xChartStart,
1356          this.m_yChartEnd - 5);
1357    }
1358
1359  }
1360
1361  /**
1362   * Internally paints the labels for the traces below the chart.
1363   * <p>
1364   *
1365   * @param g2d
1366   * the graphic context to use.
1367   * @return the amount of vertical (y) px used for the labels.
1368   */

1369
1370  private int paintTraceLables(final Graphics2D JavaDoc g2d) {
1371    int labelheight = 0;
1372    Dimension JavaDoc d = this.getSize();
1373    if (this.m_paintLabels) {
1374      ITrace2D trace;
1375      Iterator JavaDoc traceIt = this.m_traces.iterator();
1376      int xtmpos = this.m_xChartStart;
1377      int ytmpos = (int) d.getHeight() - 2;
1378      int remwidth = (int) d.getWidth() - this.m_xChartStart;
1379      int allwidth = remwidth;
1380      int lblwidth = 0;
1381      String JavaDoc tmplabel;
1382      boolean crlfdone = false;
1383      // finding the font- dimensions in px
1384
FontMetrics JavaDoc fontdim = g2d.getFontMetrics();
1385      // includes leading space
1386
int fontheight = fontdim.getHeight();
1387
1388      if (traceIt.hasNext()) {
1389        labelheight += fontheight;
1390      }
1391      while (traceIt.hasNext()) {
1392        trace = (ITrace2D) traceIt.next();
1393        if (trace.isVisible()) {
1394          tmplabel = trace.getLable();
1395          lblwidth = fontdim.stringWidth(tmplabel) + 10;
1396          // conditional linebreak.
1397
// crlfdone avoids never doing linebreak if all
1398
// labels.length()>allwidth
1399
if (lblwidth > remwidth) {
1400            if (!(lblwidth > allwidth) || (!crlfdone)) {
1401              ytmpos -= fontheight;
1402              xtmpos = this.m_xChartStart;
1403              labelheight += fontheight;
1404              crlfdone = true;
1405              remwidth = (int) d.getWidth() - this.m_xChartStart;
1406            } else {
1407              crlfdone = false;
1408            }
1409          }
1410          remwidth -= lblwidth;
1411          g2d.setColor(trace.getColor());
1412          g2d.drawString(tmplabel, xtmpos, ytmpos);
1413          xtmpos += lblwidth;
1414        }
1415      }
1416    }
1417    return labelheight;
1418  }
1419
1420  /**
1421   * <p>
1422   * Called from
1423   * {@link info.monitorenter.gui.chart.traces.ATrace2D#setZIndex(Integer)} to
1424   * show that property {@link ITrace2D#PROPERTY_ZINDEX} has changed.
1425   * </p>
1426   * <p>
1427   * This class adds itself as a {@link PropertyChangeListener} to the
1428   * {@link info.monitorenter.gui.chart.traces.ATrace2D} in method
1429   * {@link Chart2D#addTrace(ITrace2D)}.
1430   * </p>
1431   * <p>
1432   * Also used for properties <code>{@link ITrace2D#PROPERTY_MAX_X}</code>,
1433   * <code>{@link ITrace2D#PROPERTY_MAX_Y}</code>.
1434   * <code>{@link ITrace2D#PROPERTY_MIN_X}</code> and
1435   * <code>{@link ITrace2D#PROPERTY_MIN_Y}</code> to adapt to bound changes.
1436   * </p>
1437   *
1438   * @param evt
1439   * the property change event that was fired.
1440   * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
1441   */

1442  public void propertyChange(final PropertyChangeEvent JavaDoc evt) {
1443    if (Chart2D.DEBUG_THREADING) {
1444      System.out.println("propertyChange 0 locks");
1445    }
1446    synchronized (this) {
1447      if (Chart2D.DEBUG_THREADING) {
1448        System.out.println("propertyChange 1 lock");
1449      }
1450      String JavaDoc property = evt.getPropertyName();
1451      double value;
1452      // first the bound changes:
1453
if (property.equals(ITrace2D.PROPERTY_MAX_X)) {
1454        if (Chart2D.DEBUG_THREADING) {
1455          System.out.println("pc-Xmax");
1456        }
1457        value = ((Double JavaDoc) evt.getNewValue()).doubleValue();
1458        if (value > this.m_xmax) {
1459          ITrace2D trace = (ITrace2D) evt.getSource();
1460          if (trace.isVisible()) {
1461            this.m_xmax = value;
1462          }
1463        } else if (value < this.m_xmax) {
1464          this.m_xmax = this.findMaxX();
1465        }
1466      } else if (property.equals(ITrace2D.PROPERTY_MIN_X)) {
1467        if (Chart2D.DEBUG_THREADING) {
1468          System.out.println("pc-Xmin");
1469        }
1470        value = ((Double JavaDoc) evt.getNewValue()).doubleValue();
1471        if (value < this.m_xmin) {
1472          ITrace2D trace = (ITrace2D) evt.getSource();
1473          if (trace.isVisible()) {
1474            this.m_xmin = value;
1475          }
1476        } else if (value > this.m_xmin) {
1477          this.m_xmin = this.findMinX();
1478        }
1479      } else if (property.equals(ITrace2D.PROPERTY_MAX_Y)) {
1480        if (Chart2D.DEBUG_THREADING) {
1481          System.out.println("pc-Ymax");
1482        }
1483        value = ((Double JavaDoc) evt.getNewValue()).doubleValue();
1484        if (value > this.m_ymax) {
1485          ITrace2D trace = (ITrace2D) evt.getSource();
1486          if (trace.isVisible()) {
1487            this.m_ymax = value;
1488          }
1489        } else if (value < this.m_ymax) {
1490          this.m_ymax = this.findMaxY();
1491        }
1492      } else if (property.equals(ITrace2D.PROPERTY_MIN_Y)) {
1493        if (Chart2D.DEBUG_THREADING) {
1494          System.out.println("pc-Ymin");
1495        }
1496        value = ((Double JavaDoc) evt.getNewValue()).doubleValue();
1497        if (value < this.m_ymin) {
1498          ITrace2D trace = (ITrace2D) evt.getSource();
1499          if (trace.isVisible()) {
1500            this.m_ymin = value;
1501          }
1502        } else if (value > this.m_ymin) {
1503          this.m_ymin = this.findMinY();
1504        }
1505      } else if (property.equals(ARangePolicy.PROPERTY_RANGE)) {
1506        this.updateScaling(true);
1507        this.repaint(200);
1508      } else if (property.equals(ARangePolicy.PROPERTY_RANGE_MAX)) {
1509        // TODO: Maybe be more precise for this property change: detect if this
1510
// range change has an effect on getMin/getMax of an axis, detect which
1511
// axis changed.
1512
this.updateScaling(true);
1513        this.repaint(200);
1514      } else if (property.equals(ARangePolicy.PROPERTY_RANGE_MIN)) {
1515        // TODO: Maybe be more precise for this property change: detect if this
1516
// range change has an effect on getMin/getMax of an axis, detect which
1517
// axis changed.
1518
this.updateScaling(true);
1519        this.repaint(200);
1520      } else if (property.equals(ITrace2D.PROPERTY_TRACEPOINT)) {
1521        // now points added or removed -> rescale!
1522
if (Chart2D.DEBUG_THREADING) {
1523          System.out.println("pc-tp");
1524        }
1525        TracePoint2D oldPt = (TracePoint2D) evt.getOldValue();
1526        TracePoint2D newPt = (TracePoint2D) evt.getNewValue();
1527        // added or removed?
1528
// we only care about added points (rescaling is our task)
1529
if (oldPt == null) {
1530          this.m_unscaledPoints.add(newPt);
1531        }
1532      } else if (property.equals(ITrace2D.PROPERTY_VISIBLE)) {
1533        boolean visible = ((Boolean JavaDoc) evt.getNewValue()).booleanValue();
1534        if (visible) {
1535          // invisible traces don't count for max and min, so
1536
// expensive search has to be started:
1537
this.findMaxX();
1538          this.findMaxY();
1539          this.findMinX();
1540          this.findMinY();
1541        }
1542      } else if (property.equals(ITrace2D.PROPERTY_STROKE)) {
1543        // TODO: perhaps react more fine grained for the following events:
1544
// just repaint the trace without all the paint code (scaling,
1545
// axis,...).
1546
// But: These property changes are triggered by humans and occur
1547
// very seldom. Huge work non-l&f performance improvement.
1548
this.repaint(200);
1549      } else if (property.equals(ITrace2D.PROPERTY_PAINTERS)) {
1550        this.repaint(200);
1551      } else if (property.equals(ITrace2D.PROPERTY_COLOR)) {
1552        this.repaint(200);
1553      }
1554    }
1555  }
1556
1557  /**
1558   * <p>
1559   * Removes the given instance from this <code>Chart2D</code> if it is
1560   * contained.
1561   * </p>
1562   * <p>
1563   * This method will trigger a {@link java.beans.PropertyChangeEvent} being
1564   * fired on all instances registered by
1565   * {@link javax.swing.JComponent#addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)}
1566   * (registered with <code>String</code> argument
1567   * {@link #PROPERTY_ADD_REMOVE_TRACE}).
1568   * </p>
1569   *
1570   * @param points
1571   * the trace to remove.
1572   * @see Chart2D#PROPERTY_ADD_REMOVE_TRACE
1573   */

1574  public final void removeTrace(final ITrace2D points) {
1575    if (Chart2D.DEBUG_THREADING) {
1576      System.out.println("removeTrace, 0 locks");
1577    }
1578    synchronized (this) {
1579      if (Chart2D.DEBUG_THREADING) {
1580        System.out.println("removeTrace, 1 lock");
1581      }
1582      this.m_traces.remove(points);
1583      // all properties chart listens for
1584
points.removePropertyChangeListener(ITrace2D.PROPERTY_MAX_X, this);
1585      points.removePropertyChangeListener(ITrace2D.PROPERTY_MIN_X, this);
1586      points.removePropertyChangeListener(ITrace2D.PROPERTY_MAX_Y, this);
1587      points.removePropertyChangeListener(ITrace2D.PROPERTY_MIN_Y, this);
1588      points.removePropertyChangeListener(ITrace2D.PROPERTY_TRACEPOINT, this);
1589
1590      // update bounds:
1591
this.m_xmax = this.findMaxX();
1592      this.m_xmin = this.findMinX();
1593      this.m_ymax = this.findMaxY();
1594      this.m_ymin = this.findMinY();
1595
1596      this.updateScaling(false);
1597      this.repaint(200);
1598    }
1599    this.firePropertyChange(Chart2D.PROPERTY_ADD_REMOVE_TRACE, points, null);
1600  }
1601
1602  /**
1603   * <p>
1604   * Internally rescales all <code>{@link TracePoint2D}</code> instances by
1605   * using the bounds provided by the internal <code>{@link AAxis}</code>.
1606   * </p>
1607   *
1608   * @param axis
1609   * one of the values <code>{@link #X}</code>,
1610   * <code>{@link #Y}</code> or <code>{@link #X_Y}</code> to decide
1611   * in which dimension scaling should be performed.
1612   */

1613  protected final void scaleAll(final int axis) {
1614    Iterator JavaDoc it = this.m_traces.iterator();
1615    while (it.hasNext()) {
1616      this.scaleTrace((ITrace2D) it.next(), axis);
1617    }
1618  }
1619
1620  /**
1621   * <p>
1622   * Internally rescales the given <code>{@link TracePoint2D}</code> in both
1623   * dimensions by using the bounds provided by the internal
1624   * <code>{@link AAxis}</code> instances.
1625   * </p>
1626   *
1627   * @param point
1628   * the point to scale (between 0.0 and 1.0) according to the internal
1629   * bounds.
1630   *
1631   * @param axis
1632   * one of {@link Chart2D#X}, {@link Chart2D#Y}, {@link Chart2D#X_Y}.
1633   */

1634  private final void scalePoint(final TracePoint2D point, final int axis) {
1635    if (axis == Chart2D.X_Y) {
1636      point.m_scaledX = this.m_axisX.getScaledValue(point.getX());
1637      point.m_scaledY = this.m_axisY.getScaledValue(point.getY());
1638    } else if (axis == Chart2D.X) {
1639      point.m_scaledX = this.m_axisX.getScaledValue(point.getX());
1640
1641    } else if (axis == Chart2D.Y) {
1642      point.m_scaledY = this.m_axisY.getScaledValue(point.getY());
1643
1644    } else {
1645      throw new IllegalArgumentException JavaDoc("Illegal argument, use constants.");
1646    }
1647    if (Chart2D.DEBUG_SCALING) {
1648      // This is ok for fixed viewports that zoom!
1649
if ((point.m_scaledX > 1.0) || (point.m_scaledX < 0.0) || (point.m_scaledY > 1.0)
1650          || (point.m_scaledY < 0.0)) {
1651        System.out.println("Scaled Point " + point + " to [" + point.m_scaledX + ","
1652            + point.m_scaledY + "]");
1653      }
1654    }
1655  }
1656
1657  /**
1658   * <p>
1659   * Internally rescales all <code>{@link TracePoint2D}</code> instances of
1660   * the trace by using the bounds provided by the internal
1661   * <code>{@link AAxis}</code>.
1662   * </p>
1663   *
1664   * @param axis
1665   * one of the values <code>{@link #X}</code>,
1666   * <code>{@link #Y}</code> or <code>{@link #X_Y}</code> to decide
1667   * in which dimension scaling should be performed.
1668   * @param trace
1669   * the trace to rescale.
1670   */

1671  private final void scaleTrace(final ITrace2D trace, final int axis) {
1672    TracePoint2D tmp;
1673    if (trace.isVisible()) {
1674      Iterator JavaDoc it = trace.iterator();
1675      while (it.hasNext()) {
1676        tmp = (TracePoint2D) it.next();
1677        this.scalePoint(tmp, axis);
1678      }
1679    }
1680  }
1681
1682  /**
1683   * <p>
1684   * Set the x axis to use.
1685   * </p>
1686   *
1687   * @param axisX
1688   * The axisX to set.
1689   */

1690  public void setAxisX(final AAxis axisX) {
1691    IAxis old = this.m_axisX;
1692    this.m_axisX = axisX;
1693    // constructor will register the accessor to the axis:
1694
axisX.new XDataAccessor(this);
1695    this.updateScaling(true);
1696    this.firePropertyChange(Chart2D.PROPERTY_AXIS_X, old, this.m_axisX);
1697    repaint(200);
1698
1699  }
1700
1701  /**
1702   * <p>
1703   * Set the y axis to use.
1704   * </p>
1705   *
1706   * @param axisY
1707   * The axisY to set.
1708   */

1709  public void setAxisY(final AAxis axisY) {
1710    IAxis old = this.m_axisY;
1711    this.m_axisY = axisY;
1712    // constructor will register the accessor to the axis:
1713
axisY.new YDataAccessor(this);
1714    this.updateScaling(true);
1715    this.firePropertyChange(Chart2D.PROPERTY_AXIS_Y, old, this.m_axisY);
1716    repaint(200);
1717  }
1718
1719  /**
1720   * @see java.awt.Component#setBackground(java.awt.Color)
1721   */

1722  public void setBackground(final Color JavaDoc bg) {
1723    Color JavaDoc old = this.getBackground();
1724    super.setBackground(bg);
1725    this.firePropertyChange(Chart2D.PROPERTY_BACKGROUND_COLOR, old, bg);
1726  }
1727
1728  /**
1729   * @see java.awt.Component#setForeground(java.awt.Color)
1730   */

1731  public void setForeground(final Color JavaDoc fg) {
1732    Color JavaDoc old = this.getForeground();
1733    super.setForeground(fg);
1734    this.firePropertyChange(Chart2D.PROPERTY_FOREGROUND_COLOR, old, fg);
1735  }
1736
1737  /**
1738   * <p>
1739   * Set the grid color to use.
1740   * </p>
1741   *
1742   * @param gridclr
1743   * the grid color to use.
1744   */

1745  public final void setGridColor(final Color JavaDoc gridclr) {
1746    if (gridclr != null) {
1747      Color JavaDoc old = this.m_gridcolor;
1748      this.m_gridcolor = gridclr;
1749      if (!old.equals(this.m_gridcolor)) {
1750        this.firePropertyChange(Chart2D.PROPERTY_GRID_COLOR, old, this.m_gridcolor);
1751      }
1752      this.repaint(200);
1753    }
1754  }
1755
1756  /**
1757   * Sets the label painter.
1758   * <p>
1759   *
1760   * @param labelPainter
1761   * The labelPainter to set.
1762   */

1763  public synchronized void setLabelPainter(final ILabelPainter labelPainter) {
1764    this.m_labelPainter = labelPainter;
1765  }
1766
1767  /**
1768   * <p>
1769   * Decide wether labels for each chart are painted below it. If set to true
1770   * this will be done, else labels will be ommited.
1771   * </p>
1772   *
1773   * @param paintLabels
1774   * the value for paintLabels to set.
1775   */

1776  public void setPaintLabels(final boolean paintLabels) {
1777    final boolean change = this.m_paintLabels != paintLabels;
1778    this.m_paintLabels = paintLabels;
1779    if (change) {
1780      this.firePropertyChange(Chart2D.PROPERTY_PAINTLABELS, new Boolean JavaDoc(!paintLabels), new Boolean JavaDoc(
1781          paintLabels));
1782      this.repaint(200);
1783    }
1784  }
1785
1786  /**
1787   * Set wether this component should display the chart coordinates as a tool
1788   * tip.
1789   * <p>
1790   *
1791   * This turns on tool tip support (like
1792   * {@link javax.swing.JComponent#setToolTipText(java.lang.String)}) if
1793   * neccessary.
1794   * <p>
1795   *
1796   * @param toolTipCoords
1797   * The toolTipCoords to set.
1798   */

1799  public final void setToolTipCoords(final boolean toolTipCoords) {
1800    if (toolTipCoords) {
1801      // this turns on tooltips.
1802
if (this.getToolTipText() == null) {
1803        this.setToolTipText("turnOn");
1804      }
1805    } else {
1806      // this is the hidden "unregister for tooltips trick".
1807
this.setToolTipText(null);
1808    }
1809    this.m_toolTipCoords = toolTipCoords;
1810  }
1811
1812  /**
1813   * Returns a BufferedImage of the Chart2D's graphics that may be written to a
1814   * file or OutputStream by using:
1815   * {@link javax.imageio.ImageIO#write(java.awt.image.RenderedImage, java.lang.String, java.io.File)}.
1816   * <p>
1817   *
1818   * @return a BufferedImage of the Chart2D's graphics that may be written to a
1819   * file or OutputStream.
1820   * @since 1.03 - please download versions equal or greater than
1821   * jchart2d-1.03.jar.
1822   */

1823  public BufferedImage JavaDoc snapShot() {
1824    BufferedImage JavaDoc img;
1825    img = new BufferedImage JavaDoc(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_RGB);
1826    Graphics JavaDoc g = img.getGraphics();
1827    // paint is synchronized:
1828
this.paint(g);
1829    return img;
1830  }
1831
1832  /**
1833   * @see java.lang.Object#toString()
1834   */

1835  public String JavaDoc toString() {
1836    if (Chart2D.DEBUG_THREADING) {
1837      System.out.println("toString(), " + Thread.currentThread().getName() + ", 0 locks");
1838    }
1839    synchronized (this) {
1840      return super.toString();
1841    }
1842  }
1843
1844  /**
1845   * Returns the translation of the mouse event coordinates of the given mouse
1846   * event to the value within the chart.
1847   * <p>
1848   * Note that the mouse event has to be an event fired on this component!
1849   * <p>
1850   *
1851   * @param mouseEvent
1852   * a mouse event that has been fired on this component.
1853   * @return the translation of the mouse event coordinates of the given mouse
1854   * event to the value within the chart or null if no calculations
1855   * could be performed as the chart was not painted before.
1856   * @throws IllegalArgumentException
1857   * if the given mouse event does not belong to this component.
1858   */

1859  public TracePoint2D translateMousePosition(final MouseEvent JavaDoc mouseEvent)
1860      throws IllegalArgumentException JavaDoc {
1861    if (mouseEvent.getSource() != this) {
1862      throw new IllegalArgumentException JavaDoc(
1863          "The given mouse event does not belong to this chart but to: " + mouseEvent.getSource());
1864    }
1865    TracePoint2D result = null;
1866    double valueX = this.m_axisX.translateMousePosition(mouseEvent);
1867    double valueY = this.m_axisY.translateMousePosition(mouseEvent);
1868    result = new TracePoint2D(valueX, valueY);
1869
1870    return result;
1871  }
1872
1873  /**
1874   * Compares wether the bounds since last invocation have changed and
1875   * conditionally rescales the internal <code>{@link TracePoint2D}</code>
1876   * instances.
1877   * <p>
1878   * Must only be called from <code>{@link #paint(Graphics)}</code>.
1879   * <p>
1880   * The old values for the bounds are set to the actual values afterwards to
1881   * allow detection of future changes again.
1882   * <p>
1883   * The force argument allows to enforce rescaling even if no change of data
1884   * bounds took place since the last scaling. This is useful if e.g. the view
1885   * upon the data is changed by a constraint (e.g.
1886   * {@link info.monitorenter.gui.chart.rangepolicies.RangePolicyFixedViewport}).
1887   * <p>
1888   *
1889   * @param force
1890   * if true no detection of changes of the data bounds as described
1891   * above are performed: Rescaling is done unconditional.
1892   */

1893  private void updateScaling(final boolean force) {
1894
1895    if (this.m_axisX != null && this.m_axisY != null) {
1896      this.m_axisX.initPaintIteration();
1897      this.m_axisY.initPaintIteration();
1898      boolean xChanged = isDirtyScaling(Chart2D.X);
1899      boolean yChanged = isDirtyScaling(Chart2D.Y);
1900      if (force || (xChanged && yChanged)) {
1901        this.scaleAll(Chart2D.X_Y);
1902      } else if (xChanged) {
1903        scaleAll(Chart2D.X);
1904      } else if (yChanged) {
1905        scaleAll(Chart2D.Y);
1906      }
1907
1908      // scale new point - regardless wether bounds have changed or not:
1909
Iterator JavaDoc itNewPoints = this.m_unscaledPoints.iterator();
1910      TracePoint2D newPoint;
1911      while (itNewPoints.hasNext()) {
1912        newPoint = (TracePoint2D) itNewPoints.next();
1913        this.scalePoint(newPoint, Chart2D.X_Y);
1914        itNewPoints.remove();
1915      }
1916      // reset equality
1917
this.m_xmaxold = this.m_xmax;
1918      this.m_ymaxold = this.m_ymax;
1919      this.m_xminold = this.m_xmin;
1920      this.m_yminold = this.m_ymin;
1921    }
1922
1923  }
1924}
1925
Popular Tags