001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
006 *
007 * Project Info: http://www.jfree.org/jfreechart/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
022 * USA.
023 *
024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025 * in the United States and other countries.]
026 *
027 * -------------------
028 * ChartComposite.java
029 * -------------------
030 * (C) Copyright 2006, 2007, by Henry Proudhon and Contributors.
031 *
032 * Original Author: Henry Proudhon (henry.proudhon AT ensmp.fr);
033 * Contributor(s): David Gilbert (for Object Refinery Limited);
034 * Cedric Chabanois (cchabanois AT no-log.org);
035 * Christoph Beck;
036 *
037 * Changes
038 * -------
039 * 19-Jun-2006 : New class (HP);
040 * 06-Nov-2006 : Added accessor methods for zoomInFactor and zoomOutFactor (DG);
041 * 28-Nov-2006 : Added support for trace lines (HP);
042 * 30-Nov-2006 : Improved zoom box handling (HP);
043 * 06-Dec-2006 : Added (simplified) tool tip support (HP);
044 * 11-Dec-2006 : Fixed popup menu location by fgiust, bug 1612770 (HP);
045 * 31-Jan-2007 : Fixed some issues with the trace lines, fixed cross hair not
046 * being drawn, added getter and setter methods for the trace
047 * lines (HP);
048 * 07-Apr-2007 : Changed this.redraw() into canvas.redraw() to fix redraw
049 * problems (HP);
050 * 19-May-2007 : Small fix in paintControl to check for null charts, bug
051 * 1719260 (HP);
052 * 19-May-2007 : Corrected bug with scaling when the drawing region is larger
053 * than maximum draw width/height (HP);
054 * 23-May-2007 : Added some dispose call to free SWT resources, patch sent by
055 * Cédric Chabanois (CC);
056 * 06-Jun-2007 : Fixed minor issues with tooltips. bug reported and fix
057 * proposed by Christoph Beck, bug 1726404 (HP);
058 * 22-Oct-2007 : Added addChartMouseListener and removeChartMouseListener
059 * methods as suggested by Christoph Beck, bug 1742002 (HP);
060 * 22-Oct-2007 : Fixed bug in zooming with multiple plots (HP);
061 * 22-Oct-2007 : Check for null zoom point when restoring auto range and domain
062 * bounds (HP);
063 * 22-Oct-2007 : Pass mouse moved events to listening ChartMouseListeners (HP);
064 * 22-Oct-2007 : Refactored class, now implements PaintListener, MouseListener,
065 * MouseMoveListener. Made the chart field be private again and
066 * added new method addSWTListener to allow custom behavior.
067 * 14-Nov-2007 : Create canvas with SWT.DOUBLE_BUFFER, added
068 * getChartRenderingInfo(), is/setDomainZoomable() and
069 * is/setRangeZoomable() as per feature request (DG);
070 */
071
072 package org.jfree.experimental.chart.swt;
073
074 import java.awt.Graphics;
075 import java.awt.Point;
076 import java.awt.RenderingHints;
077 import java.awt.geom.Point2D;
078 import java.awt.geom.Rectangle2D;
079 import java.awt.print.PageFormat;
080 import java.awt.print.Printable;
081 import java.awt.print.PrinterException;
082 import java.awt.print.PrinterJob;
083 import java.io.File;
084 import java.io.IOException;
085 import java.util.ResourceBundle;
086
087 import javax.swing.event.EventListenerList;
088
089 import org.eclipse.swt.SWT;
090 import org.eclipse.swt.events.ControlListener;
091 import org.eclipse.swt.events.DisposeListener;
092 import org.eclipse.swt.events.FocusListener;
093 import org.eclipse.swt.events.HelpListener;
094 import org.eclipse.swt.events.KeyListener;
095 import org.eclipse.swt.events.MouseEvent;
096 import org.eclipse.swt.events.MouseListener;
097 import org.eclipse.swt.events.MouseMoveListener;
098 import org.eclipse.swt.events.MouseTrackListener;
099 import org.eclipse.swt.events.PaintEvent;
100 import org.eclipse.swt.events.PaintListener;
101 import org.eclipse.swt.events.SelectionEvent;
102 import org.eclipse.swt.events.SelectionListener;
103 import org.eclipse.swt.events.TraverseListener;
104 import org.eclipse.swt.graphics.GC;
105 import org.eclipse.swt.graphics.Rectangle;
106 import org.eclipse.swt.internal.SWTEventListener;
107 import org.eclipse.swt.layout.FillLayout;
108 import org.eclipse.swt.widgets.Canvas;
109 import org.eclipse.swt.widgets.Composite;
110 import org.eclipse.swt.widgets.Event;
111 import org.eclipse.swt.widgets.FileDialog;
112 import org.eclipse.swt.widgets.Menu;
113 import org.eclipse.swt.widgets.MenuItem;
114 import org.eclipse.swt.widgets.MessageBox;
115 import org.jfree.chart.ChartMouseEvent;
116 import org.jfree.chart.ChartMouseListener;
117 import org.jfree.chart.ChartRenderingInfo;
118 import org.jfree.chart.ChartUtilities;
119 import org.jfree.chart.JFreeChart;
120 import org.jfree.chart.entity.ChartEntity;
121 import org.jfree.chart.entity.EntityCollection;
122 import org.jfree.chart.event.ChartChangeEvent;
123 import org.jfree.chart.event.ChartChangeListener;
124 import org.jfree.chart.event.ChartProgressEvent;
125 import org.jfree.chart.event.ChartProgressListener;
126 import org.jfree.chart.plot.Plot;
127 import org.jfree.chart.plot.PlotOrientation;
128 import org.jfree.chart.plot.PlotRenderingInfo;
129 import org.jfree.chart.plot.ValueAxisPlot;
130 import org.jfree.chart.plot.Zoomable;
131 import org.jfree.experimental.chart.swt.editor.SWTChartEditor;
132 import org.jfree.experimental.swt.SWTGraphics2D;
133 import org.jfree.experimental.swt.SWTUtils;
134
135 /**
136 * A SWT GUI composite for displaying a {@link JFreeChart} object.
137 * <p>
138 * The composite listens to the chart to receive notification of changes to any
139 * component of the chart. The chart is redrawn automatically whenever this
140 * notification is received.
141 */
142 public class ChartComposite extends Composite implements ChartChangeListener,
143 ChartProgressListener,
144 PaintListener,
145 SelectionListener,
146 MouseListener,
147 MouseMoveListener,
148 Printable
149 {
150 /** Default setting for buffer usage. */
151 public static final boolean DEFAULT_BUFFER_USED = false;
152
153 /** The default panel width. */
154 public static final int DEFAULT_WIDTH = 680;
155
156 /** The default panel height. */
157 public static final int DEFAULT_HEIGHT = 420;
158
159 /** The default limit below which chart scaling kicks in. */
160 public static final int DEFAULT_MINIMUM_DRAW_WIDTH = 300;
161
162 /** The default limit below which chart scaling kicks in. */
163 public static final int DEFAULT_MINIMUM_DRAW_HEIGHT = 200;
164
165 /** The default limit below which chart scaling kicks in. */
166 public static final int DEFAULT_MAXIMUM_DRAW_WIDTH = 800;
167
168 /** The default limit below which chart scaling kicks in. */
169 public static final int DEFAULT_MAXIMUM_DRAW_HEIGHT = 600;
170
171 /** The minimum size required to perform a zoom on a rectangle */
172 public static final int DEFAULT_ZOOM_TRIGGER_DISTANCE = 10;
173
174 /** Properties action command. */
175 public static final String PROPERTIES_COMMAND = "PROPERTIES";
176
177 /** Save action command. */
178 public static final String SAVE_COMMAND = "SAVE";
179
180 /** Print action command. */
181 public static final String PRINT_COMMAND = "PRINT";
182
183 /** Zoom in (both axes) action command. */
184 public static final String ZOOM_IN_BOTH_COMMAND = "ZOOM_IN_BOTH";
185
186 /** Zoom in (domain axis only) action command. */
187 public static final String ZOOM_IN_DOMAIN_COMMAND = "ZOOM_IN_DOMAIN";
188
189 /** Zoom in (range axis only) action command. */
190 public static final String ZOOM_IN_RANGE_COMMAND = "ZOOM_IN_RANGE";
191
192 /** Zoom out (both axes) action command. */
193 public static final String ZOOM_OUT_BOTH_COMMAND = "ZOOM_OUT_BOTH";
194
195 /** Zoom out (domain axis only) action command. */
196 public static final String ZOOM_OUT_DOMAIN_COMMAND = "ZOOM_DOMAIN_BOTH";
197
198 /** Zoom out (range axis only) action command. */
199 public static final String ZOOM_OUT_RANGE_COMMAND = "ZOOM_RANGE_BOTH";
200
201 /** Zoom reset (both axes) action command. */
202 public static final String ZOOM_RESET_BOTH_COMMAND = "ZOOM_RESET_BOTH";
203
204 /** Zoom reset (domain axis only) action command. */
205 public static final String ZOOM_RESET_DOMAIN_COMMAND = "ZOOM_RESET_DOMAIN";
206
207 /** Zoom reset (range axis only) action command. */
208 public static final String ZOOM_RESET_RANGE_COMMAND = "ZOOM_RESET_RANGE";
209
210 /** The chart that is displayed in the panel. */
211 private JFreeChart chart;
212
213 /** The canvas to display the chart. */
214 private Canvas canvas;
215
216 /** Storage for registered (chart) mouse listeners. */
217 private EventListenerList chartMouseListeners;
218
219 /** A flag that controls whether or not the off-screen buffer is used. */
220 private boolean useBuffer;
221
222 /** A flag that indicates that the buffer should be refreshed. */
223 private boolean refreshBuffer;
224
225 /** A flag that indicates that the tooltips should be displayed. */
226 private boolean displayToolTips;
227
228 /** A buffer for the rendered chart. */
229 private org.eclipse.swt.graphics.Image chartBuffer;
230
231 /** The height of the chart buffer. */
232 private int chartBufferHeight;
233
234 /** The width of the chart buffer. */
235 private int chartBufferWidth;
236
237 /**
238 * The minimum width for drawing a chart (uses scaling for smaller widths).
239 */
240 private int minimumDrawWidth;
241
242 /**
243 * The minimum height for drawing a chart (uses scaling for smaller
244 * heights).
245 */
246 private int minimumDrawHeight;
247
248 /**
249 * The maximum width for drawing a chart (uses scaling for bigger
250 * widths).
251 */
252 private int maximumDrawWidth;
253
254 /**
255 * The maximum height for drawing a chart (uses scaling for bigger
256 * heights).
257 */
258 private int maximumDrawHeight;
259
260 /** The popup menu for the frame. */
261 private Menu popup;
262
263 /** The drawing info collected the last time the chart was drawn. */
264 private ChartRenderingInfo info;
265
266 /** The chart anchor point. */
267 private Point2D anchor;
268
269 /** The scale factor used to draw the chart. */
270 private double scaleX;
271
272 /** The scale factor used to draw the chart. */
273 private double scaleY;
274
275 /** The plot orientation. */
276 private PlotOrientation orientation = PlotOrientation.VERTICAL;
277
278 /** A flag that controls whether or not domain zooming is enabled. */
279 private boolean domainZoomable = false;
280
281 /** A flag that controls whether or not range zooming is enabled. */
282 private boolean rangeZoomable = false;
283
284 /**
285 * The zoom rectangle starting point (selected by the user with a mouse
286 * click). This is a point on the screen, not the chart (which may have
287 * been scaled up or down to fit the panel).
288 */
289 private org.eclipse.swt.graphics.Point zoomPoint = null;
290
291 /** The zoom rectangle (selected by the user with the mouse). */
292 private transient Rectangle zoomRectangle = null;
293
294 /** Controls if the zoom rectangle is drawn as an outline or filled. */
295 //TODO private boolean fillZoomRectangle = true;
296
297 /** The minimum distance required to drag the mouse to trigger a zoom. */
298 private int zoomTriggerDistance;
299
300 /** A flag that controls whether or not horizontal tracing is enabled. */
301 private boolean horizontalAxisTrace = false;
302
303 /** A flag that controls whether or not vertical tracing is enabled. */
304 private boolean verticalAxisTrace = false;
305
306 /** A vertical trace line. */
307 private transient int verticalTraceLineX;
308
309 /** A horizontal trace line. */
310 private transient int horizontalTraceLineY;
311
312 /** Menu item for zooming in on a chart (both axes). */
313 private MenuItem zoomInBothMenuItem;
314
315 /** Menu item for zooming in on a chart (domain axis). */
316 private MenuItem zoomInDomainMenuItem;
317
318 /** Menu item for zooming in on a chart (range axis). */
319 private MenuItem zoomInRangeMenuItem;
320
321 /** Menu item for zooming out on a chart. */
322 private MenuItem zoomOutBothMenuItem;
323
324 /** Menu item for zooming out on a chart (domain axis). */
325 private MenuItem zoomOutDomainMenuItem;
326
327 /** Menu item for zooming out on a chart (range axis). */
328 private MenuItem zoomOutRangeMenuItem;
329
330 /** Menu item for resetting the zoom (both axes). */
331 private MenuItem zoomResetBothMenuItem;
332
333 /** Menu item for resetting the zoom (domain axis only). */
334 private MenuItem zoomResetDomainMenuItem;
335
336 /** Menu item for resetting the zoom (range axis only). */
337 private MenuItem zoomResetRangeMenuItem;
338
339 /** A flag that controls whether or not file extensions are enforced. */
340 private boolean enforceFileExtensions;
341
342 /** The factor used to zoom in on an axis range. */
343 private double zoomInFactor = 0.5;
344
345 /** The factor used to zoom out on an axis range. */
346 private double zoomOutFactor = 2.0;
347
348 /** The resourceBundle for the localization. */
349 protected static ResourceBundle localizationResources
350 = ResourceBundle.getBundle("org.jfree.chart.LocalizationBundle");
351
352 /**
353 * Create a new chart composite with a default FillLayout.
354 * This way, when drawn, the chart will fill all the space.
355 * @param comp The parent.
356 * @param style The style of the composite.
357 */
358 public ChartComposite(Composite comp, int style) {
359 this(comp,
360 style,
361 null,
362 DEFAULT_WIDTH,
363 DEFAULT_HEIGHT,
364 DEFAULT_MINIMUM_DRAW_WIDTH,
365 DEFAULT_MINIMUM_DRAW_HEIGHT,
366 DEFAULT_MAXIMUM_DRAW_WIDTH,
367 DEFAULT_MAXIMUM_DRAW_HEIGHT,
368 DEFAULT_BUFFER_USED,
369 true, // properties
370 true, // save
371 true, // print
372 true, // zoom
373 true // tooltips
374 );
375 }
376
377 /**
378 * Constructs a panel that displays the specified chart.
379 *
380 * @param comp The parent.
381 * @param style The style of the composite.
382 * @param chart the chart.
383 */
384 public ChartComposite(Composite comp, int style, JFreeChart chart) {
385 this(
386 comp,
387 style,
388 chart,
389 DEFAULT_WIDTH,
390 DEFAULT_HEIGHT,
391 DEFAULT_MINIMUM_DRAW_WIDTH,
392 DEFAULT_MINIMUM_DRAW_HEIGHT,
393 DEFAULT_MAXIMUM_DRAW_WIDTH,
394 DEFAULT_MAXIMUM_DRAW_HEIGHT,
395 DEFAULT_BUFFER_USED,
396 true, // properties
397 true, // save
398 true, // print
399 true, // zoom
400 true // tooltips
401 );
402 }
403
404 /**
405 * Constructs a panel containing a chart.
406 *
407 * @param comp The parent.
408 * @param style The style of the composite.
409 * @param chart the chart.
410 * @param useBuffer a flag controlling whether or not an off-screen buffer
411 * is used.
412 */
413 public ChartComposite(Composite comp, int style, JFreeChart chart,
414 boolean useBuffer) {
415
416 this(comp, style, chart,
417 DEFAULT_WIDTH,
418 DEFAULT_HEIGHT,
419 DEFAULT_MINIMUM_DRAW_WIDTH,
420 DEFAULT_MINIMUM_DRAW_HEIGHT,
421 DEFAULT_MAXIMUM_DRAW_WIDTH,
422 DEFAULT_MAXIMUM_DRAW_HEIGHT,
423 useBuffer,
424 true, // properties
425 true, // save
426 true, // print
427 true, // zoom
428 true // tooltips
429 );
430 }
431
432 /**
433 * Constructs a JFreeChart panel.
434 *
435 * @param comp The parent.
436 * @param style The style of the composite.
437 * @param chart the chart.
438 * @param properties a flag indicating whether or not the chart property
439 * editor should be available via the popup menu.
440 * @param save a flag indicating whether or not save options should be
441 * available via the popup menu.
442 * @param print a flag indicating whether or not the print option
443 * should be available via the popup menu.
444 * @param zoom a flag indicating whether or not zoom options should
445 * be added to the popup menu.
446 * @param tooltips a flag indicating whether or not tooltips should be
447 * enabled for the chart.
448 */
449 public ChartComposite(
450 Composite comp,
451 int style,
452 JFreeChart chart,
453 boolean properties,
454 boolean save,
455 boolean print,
456 boolean zoom,
457 boolean tooltips) {
458 this(
459 comp,
460 style,
461 chart,
462 DEFAULT_WIDTH,
463 DEFAULT_HEIGHT,
464 DEFAULT_MINIMUM_DRAW_WIDTH,
465 DEFAULT_MINIMUM_DRAW_HEIGHT,
466 DEFAULT_MAXIMUM_DRAW_WIDTH,
467 DEFAULT_MAXIMUM_DRAW_HEIGHT,
468 DEFAULT_BUFFER_USED,
469 properties,
470 save,
471 print,
472 zoom,
473 tooltips
474 );
475 }
476
477 /**
478 * Constructs a JFreeChart panel.
479 *
480 * @param comp The parent.
481 * @param style The style of the composite.
482 * @param jfreechart the chart.
483 * @param width the preferred width of the panel.
484 * @param height the preferred height of the panel.
485 * @param minimumDrawW the minimum drawing width.
486 * @param minimumDrawH the minimum drawing height.
487 * @param maximumDrawW the maximum drawing width.
488 * @param maximumDrawH the maximum drawing height.
489 * @param usingBuffer a flag that indicates whether to use the off-screen
490 * buffer to improve performance (at the expense of
491 * memory).
492 * @param properties a flag indicating whether or not the chart property
493 * editor should be available via the popup menu.
494 * @param save a flag indicating whether or not save options should be
495 * available via the popup menu.
496 * @param print a flag indicating whether or not the print option
497 * should be available via the popup menu.
498 * @param zoom a flag indicating whether or not zoom options should be
499 * added to the popup menu.
500 * @param tooltips a flag indicating whether or not tooltips should be
501 * enabled for the chart.
502 */
503 public ChartComposite(Composite comp,
504 int style,
505 JFreeChart jfreechart,
506 int width,
507 int height,
508 int minimumDrawW,
509 int minimumDrawH,
510 int maximumDrawW,
511 int maximumDrawH,
512 boolean usingBuffer,
513 boolean properties,
514 boolean save,
515 boolean print,
516 boolean zoom,
517 boolean tooltips) {
518 super(comp, style);
519 this.setChart(jfreechart);
520 this.chartMouseListeners = new EventListenerList();
521 this.setLayout(new FillLayout());
522 this.info = new ChartRenderingInfo();
523 this.useBuffer = usingBuffer;
524 this.refreshBuffer = false;
525 this.minimumDrawWidth = minimumDrawW;
526 this.minimumDrawHeight = minimumDrawH;
527 this.maximumDrawWidth = maximumDrawW;
528 this.maximumDrawHeight = maximumDrawH;
529 this.zoomTriggerDistance = DEFAULT_ZOOM_TRIGGER_DISTANCE;
530 this.setDisplayToolTips(tooltips);
531 // create the canvas and add the required listeners
532 this.canvas = new Canvas(this, SWT.DOUBLE_BUFFERED | SWT.NO_BACKGROUND);
533 this.canvas.addPaintListener(this);
534 this.canvas.addMouseListener(this);
535 this.canvas.addMouseMoveListener(this);
536
537 if (this.chart != null) {
538 this.chart.addChangeListener(this);
539 Plot plot = this.chart.getPlot();
540 this.domainZoomable = false;
541 this.rangeZoomable = false;
542 if (plot instanceof Zoomable) {
543 Zoomable z = (Zoomable) plot;
544 this.domainZoomable = z.isDomainZoomable();
545 this.rangeZoomable = z.isRangeZoomable();
546 this.orientation = z.getOrientation();
547 }
548 }
549
550 // set up popup menu...
551 this.popup = null;
552 if (properties || save || print || zoom)
553 this.popup = createPopupMenu(properties, save, print, zoom);
554
555 this.enforceFileExtensions = true;
556 }
557
558 /**
559 * Returns the X scale factor for the chart. This will be 1.0 if no
560 * scaling has been used.
561 *
562 * @return The scale factor.
563 */
564 public double getScaleX() {
565 return this.scaleX;
566 }
567
568 /**
569 * Returns the Y scale factory for the chart. This will be 1.0 if no
570 * scaling has been used.
571 *
572 * @return The scale factor.
573 */
574 public double getScaleY() {
575 return this.scaleY;
576 }
577
578 /**
579 * Returns the anchor point.
580 *
581 * @return The anchor point (possibly <code>null</code>).
582 */
583 public Point2D getAnchor() {
584 return this.anchor;
585 }
586
587 /**
588 * Sets the anchor point. This method is provided for the use of
589 * subclasses, not end users.
590 *
591 * @param anchor the anchor point (<code>null</code> permitted).
592 */
593 protected void setAnchor(Point2D anchor) {
594 this.anchor = anchor;
595 }
596
597 /**
598 * Returns the chart contained in the panel.
599 *
600 * @return The chart (possibly <code>null</code>).
601 */
602 public JFreeChart getChart() {
603 return this.chart;
604 }
605
606 /**
607 * Sets the chart that is displayed in the panel.
608 *
609 * @param chart the chart (<code>null</code> permitted).
610 */
611 public void setChart(JFreeChart chart) {
612 // stop listening for changes to the existing chart
613 if (this.chart != null) {
614 this.chart.removeChangeListener(this);
615 this.chart.removeProgressListener(this);
616 }
617
618 // add the new chart
619 this.chart = chart;
620 if (chart != null) {
621 this.chart.addChangeListener(this);
622 this.chart.addProgressListener(this);
623 Plot plot = chart.getPlot();
624 this.domainZoomable = false;
625 this.rangeZoomable = false;
626 if (plot instanceof Zoomable) {
627 Zoomable z = (Zoomable) plot;
628 this.domainZoomable = z.isDomainZoomable();
629 this.rangeZoomable = z.isRangeZoomable();
630 this.orientation = z.getOrientation();
631 }
632 }
633 else {
634 this.domainZoomable = false;
635 this.rangeZoomable = false;
636 }
637 if (this.useBuffer) {
638 this.refreshBuffer = true;
639 }
640 }
641
642 /**
643 * Returns the chart rendering info from the most recent chart redraw.
644 *
645 * @return The chart rendering info (possibly <code>null</code>).
646 */
647 public ChartRenderingInfo getChartRenderingInfo() {
648 return this.info;
649 }
650
651 /**
652 * Returns the flag that determines whether or not zooming is enabled for
653 * the domain axis.
654 *
655 * @return A boolean.
656 */
657 public boolean isDomainZoomable() {
658 return this.domainZoomable;
659 }
660
661 /**
662 * Sets the flag that controls whether or not zooming is enable for the
663 * domain axis. A check is made to ensure that the current plot supports
664 * zooming for the domain values.
665 *
666 * @param flag <code>true</code> enables zooming if possible.
667 */
668 public void setDomainZoomable(boolean flag) {
669 if (flag) {
670 Plot plot = this.chart.getPlot();
671 if (plot instanceof Zoomable) {
672 Zoomable z = (Zoomable) plot;
673 this.domainZoomable = flag && (z.isDomainZoomable());
674 }
675 }
676 else {
677 this.domainZoomable = false;
678 }
679 }
680
681 /**
682 * Returns the flag that determines whether or not zooming is enabled for
683 * the range axis.
684 *
685 * @return A boolean.
686 */
687 public boolean isRangeZoomable() {
688 return this.rangeZoomable;
689 }
690
691 /**
692 * A flag that controls mouse-based zooming on the vertical axis.
693 *
694 * @param flag <code>true</code> enables zooming.
695 */
696 public void setRangeZoomable(boolean flag) {
697 if (flag) {
698 Plot plot = this.chart.getPlot();
699 if (plot instanceof Zoomable) {
700 Zoomable z = (Zoomable) plot;
701 this.rangeZoomable = flag && (z.isRangeZoomable());
702 }
703 }
704 else {
705 this.rangeZoomable = false;
706 }
707 }
708
709 /**
710 * Returns the zoom in factor.
711 *
712 * @return The zoom in factor.
713 *
714 * @see #setZoomInFactor(double)
715 */
716 public double getZoomInFactor() {
717 return this.zoomInFactor;
718 }
719
720 /**
721 * Sets the zoom in factor.
722 *
723 * @param factor the factor.
724 *
725 * @see #getZoomInFactor()
726 */
727 public void setZoomInFactor(double factor) {
728 this.zoomInFactor = factor;
729 }
730
731 /**
732 * Returns the zoom out factor.
733 *
734 * @return The zoom out factor.
735 *
736 * @see #setZoomOutFactor(double)
737 */
738 public double getZoomOutFactor() {
739 return this.zoomOutFactor;
740 }
741
742 /**
743 * Sets the zoom out factor.
744 *
745 * @param factor the factor.
746 *
747 * @see #getZoomOutFactor()
748 */
749 public void setZoomOutFactor(double factor) {
750 this.zoomOutFactor = factor;
751 }
752
753 /**
754 * Displays a dialog that allows the user to edit the properties for the
755 * current chart.
756 */
757 private void attemptEditChartProperties() {
758 SWTChartEditor editor = new SWTChartEditor(this.canvas.getDisplay(),
759 this.chart);
760 //ChartEditorManager.getChartEditor(canvas.getDisplay(), this.chart);
761 editor.open();
762 }
763
764 /**
765 * Returns <code>true</code> if file extensions should be enforced, and
766 * <code>false</code> otherwise.
767 *
768 * @return The flag.
769 */
770 public boolean isEnforceFileExtensions() {
771 return this.enforceFileExtensions;
772 }
773
774 /**
775 * Sets a flag that controls whether or not file extensions are enforced.
776 *
777 * @param enforce the new flag value.
778 */
779 public void setEnforceFileExtensions(boolean enforce) {
780 this.enforceFileExtensions = enforce;
781 }
782
783 /**
784 * Opens a file chooser and gives the user an opportunity to save the chart
785 * in PNG format.
786 *
787 * @throws IOException if there is an I/O error.
788 */
789 public void doSaveAs() throws IOException {
790 FileDialog fileDialog = new FileDialog(this.canvas.getShell(),
791 SWT.SAVE);
792 String[] extensions = { "*.png" };
793 fileDialog.setFilterExtensions(extensions);
794 String filename = fileDialog.open();
795 if (filename != null) {
796 if (isEnforceFileExtensions()) {
797 if (!filename.endsWith(".png")) {
798 filename = filename + ".png";
799 }
800 }
801 //TODO replace getSize by getBounds ?
802 ChartUtilities.saveChartAsPNG(new File(filename), this.chart,
803 this.canvas.getSize().x, this.canvas.getSize().y);
804 }
805 }
806
807 /**
808 * Returns a point based on (x, y) but constrained to be within the bounds
809 * of the given rectangle. This method could be moved to JCommon.
810 *
811 * @param x the x-coordinate.
812 * @param y the y-coordinate.
813 * @param area the rectangle (<code>null</code> not permitted).
814 *
815 * @return A point within the rectangle.
816 */
817 private org.eclipse.swt.graphics.Point getPointInRectangle(int x, int y,
818 Rectangle area) {
819 x = Math.max(area.x, Math.min(x, area.x + area.width));
820 y = Math.max(area.y, Math.min(y, area.y + area.height));
821 return new org.eclipse.swt.graphics.Point(x, y);
822 }
823
824 /**
825 * Zooms in on an anchor point (specified in screen coordinate space).
826 *
827 * @param x the x value (in screen coordinates).
828 * @param y the y value (in screen coordinates).
829 */
830 public void zoomInBoth(double x, double y) {
831 zoomInDomain(x, y);
832 zoomInRange(x, y);
833 }
834
835 /**
836 * Decreases the length of the domain axis, centered about the given
837 * coordinate on the screen. The length of the domain axis is reduced
838 * by the value of {@link #getZoomInFactor()}.
839 *
840 * @param x the x coordinate (in screen coordinates).
841 * @param y the y-coordinate (in screen coordinates).
842 */
843 public void zoomInDomain(double x, double y) {
844 Plot p = this.chart.getPlot();
845 if (p instanceof Zoomable)
846 {
847 Zoomable plot = (Zoomable) p;
848 plot.zoomDomainAxes(this.zoomInFactor, this.info.getPlotInfo(),
849 translateScreenToJava2D(new Point((int) x, (int) y)));
850 }
851 }
852
853 /**
854 * Decreases the length of the range axis, centered about the given
855 * coordinate on the screen. The length of the range axis is reduced by
856 * the value of {@link #getZoomInFactor()}.
857 *
858 * @param x the x-coordinate (in screen coordinates).
859 * @param y the y coordinate (in screen coordinates).
860 */
861 public void zoomInRange(double x, double y) {
862 Plot p = this.chart.getPlot();
863 if (p instanceof Zoomable) {
864 Zoomable z = (Zoomable) p;
865 z.zoomRangeAxes(this.zoomInFactor, this.info.getPlotInfo(),
866 translateScreenToJava2D(new Point((int) x, (int) y)));
867 }
868 }
869
870 /**
871 * Zooms out on an anchor point (specified in screen coordinate space).
872 *
873 * @param x the x value (in screen coordinates).
874 * @param y the y value (in screen coordinates).
875 */
876 public void zoomOutBoth(double x, double y) {
877 zoomOutDomain(x, y);
878 zoomOutRange(x, y);
879 }
880
881 /**
882 * Increases the length of the domain axis, centered about the given
883 * coordinate on the screen. The length of the domain axis is increased
884 * by the value of {@link #getZoomOutFactor()}.
885 *
886 * @param x the x coordinate (in screen coordinates).
887 * @param y the y-coordinate (in screen coordinates).
888 */
889 public void zoomOutDomain(double x, double y) {
890 Plot p = this.chart.getPlot();
891 if (p instanceof Zoomable) {
892 Zoomable z = (Zoomable) p;
893 z.zoomDomainAxes(this.zoomOutFactor, this.info.getPlotInfo(),
894 translateScreenToJava2D(new Point((int) x, (int) y)));
895 }
896 }
897
898 /**
899 * Increases the length the range axis, centered about the given
900 * coordinate on the screen. The length of the range axis is increased
901 * by the value of {@link #getZoomOutFactor()}.
902 *
903 * @param x the x coordinate (in screen coordinates).
904 * @param y the y-coordinate (in screen coordinates).
905 */
906 public void zoomOutRange(double x, double y) {
907 Plot p = this.chart.getPlot();
908 if (p instanceof Zoomable) {
909 Zoomable z = (Zoomable) p;
910 z.zoomRangeAxes(this.zoomOutFactor, this.info.getPlotInfo(),
911 translateScreenToJava2D(new Point((int) x, (int) y)));
912 }
913 }
914
915 /**
916 * Zooms in on a selected region.
917 *
918 * @param selection the selected region.
919 */
920 public void zoom(Rectangle selection) {
921
922 // get the origin of the zoom selection in the Java2D space used for
923 // drawing the chart (that is, before any scaling to fit the panel)
924 Point2D selectOrigin = translateScreenToJava2D(
925 new Point(selection.x, selection.y));
926 PlotRenderingInfo plotInfo = this.info.getPlotInfo();
927 Rectangle scaledDataArea = getScreenDataArea(
928 (selection.x + selection.width / 2),
929 (selection.y + selection.height/2));
930 if ((selection.height > 0) && (selection.width > 0)) {
931
932 double hLower = (selection.x - scaledDataArea.x)
933 / (double) scaledDataArea.width;
934 double hUpper = (selection.x + selection.width - scaledDataArea.x)
935 / (double) scaledDataArea.width;
936 double vLower = (scaledDataArea.y + scaledDataArea.height
937 - selection.y - selection.height)
938 / (double) scaledDataArea.height;
939 double vUpper = (scaledDataArea.y + scaledDataArea.height
940 - selection.y) / (double) scaledDataArea.height;
941 Plot p = this.chart.getPlot();
942 if (p instanceof Zoomable) {
943 Zoomable z = (Zoomable) p;
944 if (z.getOrientation() == PlotOrientation.HORIZONTAL) {
945 z.zoomDomainAxes(vLower, vUpper, plotInfo, selectOrigin);
946 z.zoomRangeAxes(hLower, hUpper, plotInfo, selectOrigin);
947 }
948 else {
949 z.zoomDomainAxes(hLower, hUpper, plotInfo, selectOrigin);
950 z.zoomRangeAxes(vLower, vUpper, plotInfo, selectOrigin);
951 }
952 }
953
954 }
955
956 }
957
958 /**
959 * Receives notification of changes to the chart, and redraws the chart.
960 *
961 * @param event details of the chart change event.
962 */
963 public void chartChanged(ChartChangeEvent event) {
964 this.refreshBuffer = true;
965 Plot plot = this.chart.getPlot();
966 if (plot instanceof Zoomable) {
967 Zoomable z = (Zoomable) plot;
968 this.orientation = z.getOrientation();
969 }
970 this.canvas.redraw();
971 }
972
973 /**
974 * Forces a redraw of the canvas by invoking a new PaintEvent.
975 */
976 public void forceRedraw() {
977 Event ev = new Event();
978 ev.gc = new GC(this.canvas);
979 ev.x = 0;
980 ev.y = 0;
981 ev.width = this.canvas.getBounds().width;
982 ev.height = this.canvas.getBounds().height;
983 ev.count = 0;
984 this.canvas.notifyListeners(SWT.Paint, ev);
985 ev.gc.dispose();
986 }
987
988 /**
989 * Adds a listener to the list of objects listening for chart mouse events.
990 *
991 * @param listener the listener (<code>null</code> not permitted).
992 */
993 public void addChartMouseListener(ChartMouseListener listener) {
994 this.chartMouseListeners.add(ChartMouseListener.class, listener);
995 }
996
997 /**
998 * Removes a listener from the list of objects listening for chart mouse
999 * events.
1000 *
1001 * @param listener the listener.
1002 */
1003 public void removeChartMouseListener(ChartMouseListener listener) {
1004 this.chartMouseListeners.remove(ChartMouseListener.class, listener);
1005 }
1006
1007 /**
1008 * Receives notification of a chart progress event.
1009 *
1010 * @param event the event.
1011 */
1012 public void chartProgress(ChartProgressEvent event) {
1013 // does nothing - override if necessary
1014 }
1015
1016 /**
1017 * Restores the auto-range calculation on both axes.
1018 */
1019 public void restoreAutoBounds() {
1020 restoreAutoDomainBounds();
1021 restoreAutoRangeBounds();
1022 }
1023
1024 /**
1025 * Restores the auto-range calculation on the domain axis.
1026 */
1027 public void restoreAutoDomainBounds() {
1028 Plot p = this.chart.getPlot();
1029 if (p instanceof Zoomable) {
1030 Zoomable z = (Zoomable) p;
1031 // we need to guard against this.zoomPoint being null
1032 org.eclipse.swt.graphics.Point zp =
1033 (this.zoomPoint != null ? this.zoomPoint :
1034 new org.eclipse.swt.graphics.Point(0,0));
1035 z.zoomDomainAxes(0.0, this.info.getPlotInfo(),
1036 SWTUtils.toAwtPoint(zp));
1037 }
1038 }
1039
1040 /**
1041 * Restores the auto-range calculation on the range axis.
1042 */
1043 public void restoreAutoRangeBounds() {
1044 Plot p = this.chart.getPlot();
1045 if (p instanceof ValueAxisPlot) {
1046 Zoomable z = (Zoomable) p;
1047 // we need to guard against this.zoomPoint being null
1048 org.eclipse.swt.graphics.Point zp =
1049 (this.zoomPoint != null ? this.zoomPoint :
1050 new org.eclipse.swt.graphics.Point(0,0));
1051 z.zoomRangeAxes(0.0, this.info.getPlotInfo(),
1052 SWTUtils.toAwtPoint(zp));
1053 }
1054 }
1055
1056 /**
1057 * Applies any scaling that is in effect for the chart drawing to the
1058 * given rectangle.
1059 *
1060 * @param rect the rectangle.
1061 *
1062 * @return A new scaled rectangle.
1063 */
1064 public Rectangle scale(Rectangle2D rect) {
1065 Rectangle insets = this.getClientArea();
1066 int x = (int) Math.round(rect.getX() * getScaleX()) + insets.x;
1067 int y = (int) Math.round(rect.getY() * this.getScaleY()) + insets.y;
1068 int w = (int) Math.round(rect.getWidth() * this.getScaleX());
1069 int h = (int) Math.round(rect.getHeight() * this.getScaleY());
1070 return new Rectangle(x, y, w, h);
1071 }
1072
1073 /**
1074 * Returns the data area for the chart (the area inside the axes) with the
1075 * current scaling applied (that is, the area as it appears on screen).
1076 *
1077 * @return The scaled data area.
1078 */
1079 public Rectangle getScreenDataArea() {
1080 Rectangle2D dataArea = this.info.getPlotInfo().getDataArea();
1081 Rectangle clientArea = this.getClientArea();
1082 int x = (int) (dataArea.getX() * this.scaleX + clientArea.x);
1083 int y = (int) (dataArea.getY() * this.scaleY + clientArea.y);
1084 int w = (int) (dataArea.getWidth() * this.scaleX);
1085 int h = (int) (dataArea.getHeight() * this.scaleY);
1086 return new Rectangle(x, y, w, h);
1087 }
1088
1089 /**
1090 * Returns the data area (the area inside the axes) for the plot or subplot,
1091 * with the current scaling applied.
1092 *
1093 * @param x the x-coordinate (for subplot selection).
1094 * @param y the y-coordinate (for subplot selection).
1095 *
1096 * @return The scaled data area.
1097 */
1098 public Rectangle getScreenDataArea(int x, int y) {
1099 PlotRenderingInfo plotInfo = this.info.getPlotInfo();
1100 Rectangle result;
1101 if (plotInfo.getSubplotCount() == 0)
1102 result = getScreenDataArea();
1103 else {
1104 // get the origin of the zoom selection in the Java2D space used for
1105 // drawing the chart (that is, before any scaling to fit the panel)
1106 Point2D selectOrigin = translateScreenToJava2D(new Point(x, y));
1107 int subplotIndex = plotInfo.getSubplotIndex(selectOrigin);
1108 if (subplotIndex == -1) {
1109 return null;
1110 }
1111 result = scale(plotInfo.getSubplotInfo(subplotIndex).getDataArea());
1112 }
1113 return result;
1114 }
1115
1116 /**
1117 * Translates a Java2D point on the chart to a screen location.
1118 *
1119 * @param java2DPoint the Java2D point.
1120 *
1121 * @return The screen location.
1122 */
1123 public Point translateJava2DToScreen(Point2D java2DPoint) {
1124 Rectangle insets = this.getClientArea();
1125 int x = (int) (java2DPoint.getX() * this.scaleX + insets.x);
1126 int y = (int) (java2DPoint.getY() * this.scaleY + insets.y);
1127 return new Point(x, y);
1128 }
1129
1130 /**
1131 * Translates a screen location to a Java SWT point.
1132 *
1133 * @param screenPoint the screen location.
1134 *
1135 * @return The Java2D coordinates.
1136 */
1137 public Point translateScreenToJavaSWT(Point screenPoint) {
1138 Rectangle insets = this.getClientArea();
1139 int x = (int) ((screenPoint.x - insets.x) / this.scaleX);
1140 int y = (int) ((screenPoint.y - insets.y) / this.scaleY);
1141 return new Point(x, y);
1142 }
1143
1144 /**
1145 * Translates a screen location to a Java2D point.
1146 *
1147 * @param screenPoint the screen location.
1148 *
1149 * @return The Java2D coordinates.
1150 */
1151 public Point2D translateScreenToJava2D(Point screenPoint) {
1152 Rectangle insets = this.getClientArea();
1153 int x = (int) ((screenPoint.x - insets.x) / this.scaleX);
1154 int y = (int) ((screenPoint.y - insets.y) / this.scaleY);
1155 return new Point2D.Double(x, y);
1156 }
1157
1158 /**
1159 * Returns the flag that controls whether or not a horizontal axis trace
1160 * line is drawn over the plot area at the current mouse location.
1161 *
1162 * @return A boolean.
1163 */
1164 public boolean getHorizontalAxisTrace() {
1165 return this.horizontalAxisTrace;
1166 }
1167
1168 /**
1169 * A flag that controls trace lines on the horizontal axis.
1170 *
1171 * @param flag <code>true</code> enables trace lines for the mouse
1172 * pointer on the horizontal axis.
1173 */
1174 public void setHorizontalAxisTrace(boolean flag) {
1175 this.horizontalAxisTrace = flag;
1176 }
1177
1178 /**
1179 * Returns the flag that controls whether or not a vertical axis trace
1180 * line is drawn over the plot area at the current mouse location.
1181 *
1182 * @return A boolean.
1183 */
1184 public boolean getVerticalAxisTrace() {
1185 return this.verticalAxisTrace;
1186 }
1187
1188 /**
1189 * A flag that controls trace lines on the vertical axis.
1190 *
1191 * @param flag <code>true</code> enables trace lines for the mouse
1192 * pointer on the vertical axis.
1193 */
1194 public void setVerticalAxisTrace(boolean flag) {
1195 this.verticalAxisTrace = flag;
1196 }
1197
1198 /**
1199 * @param displayToolTips the displayToolTips to set
1200 */
1201 public void setDisplayToolTips( boolean displayToolTips ) {
1202 this.displayToolTips = displayToolTips;
1203 }
1204
1205 /**
1206 * Returns a string for the tooltip.
1207 *
1208 * @param e the mouse event.
1209 *
1210 * @return A tool tip or <code>null</code> if no tooltip is available.
1211 */
1212 public String getToolTipText(org.eclipse.swt.events.MouseEvent e) {
1213 String result = null;
1214 if (this.info != null) {
1215 EntityCollection entities = this.info.getEntityCollection();
1216 if (entities != null) {
1217 Rectangle insets = getClientArea();
1218 ChartEntity entity = entities.getEntity(
1219 (int) ((e.x - insets.x) / this.scaleX),
1220 (int) ((e.y - insets.y) / this.scaleY));
1221 if (entity != null) {
1222 result = entity.getToolTipText();
1223 }
1224 }
1225 }
1226 return result;
1227
1228 }
1229
1230 /**
1231 * The idea is to modify the zooming options depending on the type of chart
1232 * being displayed by the panel.
1233 *
1234 * @param x horizontal position of the popup.
1235 * @param y vertical position of the popup.
1236 */
1237 protected void displayPopupMenu(int x, int y) {
1238 if (this.popup != null) {
1239 // go through each zoom menu item and decide whether or not to
1240 // enable it...
1241 Plot plot = this.chart.getPlot();
1242 boolean isDomainZoomable = false;
1243 boolean isRangeZoomable = false;
1244 if (plot instanceof Zoomable) {
1245 Zoomable z = (Zoomable) plot;
1246 isDomainZoomable = z.isDomainZoomable();
1247 isRangeZoomable = z.isRangeZoomable();
1248 }
1249 if (this.zoomInDomainMenuItem != null) {
1250 this.zoomInDomainMenuItem.setEnabled(isDomainZoomable);
1251 }
1252 if (this.zoomOutDomainMenuItem != null) {
1253 this.zoomOutDomainMenuItem.setEnabled(isDomainZoomable);
1254 }
1255 if (this.zoomResetDomainMenuItem != null) {
1256 this.zoomResetDomainMenuItem.setEnabled(isDomainZoomable);
1257 }
1258
1259 if (this.zoomInRangeMenuItem != null) {
1260 this.zoomInRangeMenuItem.setEnabled(isRangeZoomable);
1261 }
1262 if (this.zoomOutRangeMenuItem != null) {
1263 this.zoomOutRangeMenuItem.setEnabled(isRangeZoomable);
1264 }
1265
1266 if (this.zoomResetRangeMenuItem != null) {
1267 this.zoomResetRangeMenuItem.setEnabled(isRangeZoomable);
1268 }
1269
1270 if (this.zoomInBothMenuItem != null) {
1271 this.zoomInBothMenuItem.setEnabled(
1272 isDomainZoomable & isRangeZoomable
1273 );
1274 }
1275 if (this.zoomOutBothMenuItem != null) {
1276 this.zoomOutBothMenuItem.setEnabled(isDomainZoomable
1277 & isRangeZoomable);
1278 }
1279 if (this.zoomResetBothMenuItem != null) {
1280 this.zoomResetBothMenuItem.setEnabled(isDomainZoomable
1281 & isRangeZoomable);
1282 }
1283
1284 this.popup.setLocation(x, y);
1285 this.popup.setVisible(true);
1286 }
1287
1288 }
1289
1290 /**
1291 * Creates a print job for the chart.
1292 */
1293 public void createChartPrintJob() {
1294 //FIXME try to replace swing print stuff by swt
1295 PrinterJob job = PrinterJob.getPrinterJob();
1296 PageFormat pf = job.defaultPage();
1297 PageFormat pf2 = job.pageDialog(pf);
1298 if (pf2 != pf) {
1299 job.setPrintable(this, pf2);
1300 if (job.printDialog()) {
1301 try {
1302 job.print();
1303 }
1304 catch (PrinterException e) {
1305 MessageBox messageBox = new MessageBox(
1306 this.canvas.getShell(), SWT.OK | SWT.ICON_ERROR );
1307 messageBox.setMessage( e.getMessage() );
1308 messageBox.open();
1309 }
1310 }
1311 }
1312 }
1313
1314 /**
1315 * Creates a popup menu for the canvas.
1316 *
1317 * @param properties include a menu item for the chart property editor.
1318 * @param save include a menu item for saving the chart.
1319 * @param print include a menu item for printing the chart.
1320 * @param zoom include menu items for zooming.
1321 *
1322 * @return The popup menu.
1323 */
1324 protected Menu createPopupMenu(boolean properties, boolean save,
1325 boolean print, boolean zoom) {
1326
1327 Menu result = new Menu(this);
1328 boolean separator = false;
1329
1330 if (properties) {
1331 MenuItem propertiesItem = new MenuItem(result, SWT.PUSH);
1332 propertiesItem.setText(localizationResources.getString(
1333 "Properties..."));
1334 propertiesItem.setData(PROPERTIES_COMMAND);
1335 propertiesItem.addSelectionListener(this);
1336 separator = true;
1337 }
1338 if (save) {
1339 if (separator) {
1340 new MenuItem(result, SWT.SEPARATOR);
1341 separator = false;
1342 }
1343 MenuItem saveItem = new MenuItem(result, SWT.NONE);
1344 saveItem.setText(localizationResources.getString("Save_as..."));
1345 saveItem.setData(SAVE_COMMAND);
1346 saveItem.addSelectionListener(this);
1347 separator = true;
1348 }
1349 if (print) {
1350 if (separator) {
1351 new MenuItem(result, SWT.SEPARATOR);
1352 separator = false;
1353 }
1354 MenuItem printItem = new MenuItem(result, SWT.NONE);
1355 printItem.setText(localizationResources.getString("Print..."));
1356 printItem.setData(PRINT_COMMAND);
1357 printItem.addSelectionListener(this);
1358 separator = true;
1359 }
1360 if (zoom) {
1361 if (separator) {
1362 new MenuItem(result, SWT.SEPARATOR);
1363 separator = false;
1364 }
1365
1366 Menu zoomInMenu = new Menu(result);
1367 MenuItem zoomInMenuItem = new MenuItem(result, SWT.CASCADE);
1368 zoomInMenuItem.setText(localizationResources.getString("Zoom_In"));
1369 zoomInMenuItem.setMenu(zoomInMenu);
1370
1371 this.zoomInBothMenuItem = new MenuItem(zoomInMenu, SWT.PUSH);
1372 this.zoomInBothMenuItem.setText(localizationResources.getString(
1373 "All_Axes"));
1374 this.zoomInBothMenuItem.setData(ZOOM_IN_BOTH_COMMAND);
1375 this.zoomInBothMenuItem.addSelectionListener(this);
1376
1377 new MenuItem(zoomInMenu, SWT.SEPARATOR);
1378
1379 this.zoomInDomainMenuItem = new MenuItem(zoomInMenu, SWT.PUSH);
1380 this.zoomInDomainMenuItem.setText(localizationResources.getString(
1381 "Domain_Axis"));
1382 this.zoomInDomainMenuItem.setData(ZOOM_IN_DOMAIN_COMMAND);
1383 this.zoomInDomainMenuItem.addSelectionListener(this);
1384
1385 this.zoomInRangeMenuItem = new MenuItem(zoomInMenu, SWT.PUSH);
1386 this.zoomInRangeMenuItem.setText(localizationResources.getString(
1387 "Range_Axis"));
1388 this.zoomInRangeMenuItem.setData(ZOOM_IN_RANGE_COMMAND);
1389 this.zoomInRangeMenuItem.addSelectionListener(this);
1390
1391 Menu zoomOutMenu = new Menu(result);
1392 MenuItem zoomOutMenuItem = new MenuItem(result, SWT.CASCADE);
1393 zoomOutMenuItem.setText(localizationResources.getString(
1394 "Zoom_Out"));
1395 zoomOutMenuItem.setMenu(zoomOutMenu);
1396
1397 this.zoomOutBothMenuItem = new MenuItem(zoomOutMenu, SWT.PUSH);
1398 this.zoomOutBothMenuItem.setText(localizationResources.getString(
1399 "All_Axes"));
1400 this.zoomOutBothMenuItem.setData(ZOOM_OUT_BOTH_COMMAND);
1401 this.zoomOutBothMenuItem.addSelectionListener(this);
1402
1403 new MenuItem(zoomOutMenu, SWT.SEPARATOR);
1404
1405 this.zoomOutDomainMenuItem = new MenuItem(zoomOutMenu, SWT.PUSH);
1406 this.zoomOutDomainMenuItem.setText(localizationResources.getString(
1407 "Domain_Axis"));
1408 this.zoomOutDomainMenuItem.setData(ZOOM_OUT_DOMAIN_COMMAND);
1409 this.zoomOutDomainMenuItem.addSelectionListener(this);
1410
1411 this.zoomOutRangeMenuItem = new MenuItem(zoomOutMenu, SWT.PUSH);
1412 this.zoomOutRangeMenuItem.setText(
1413 localizationResources.getString("Range_Axis"));
1414 this.zoomOutRangeMenuItem.setData(ZOOM_OUT_RANGE_COMMAND);
1415 this.zoomOutRangeMenuItem.addSelectionListener(this);
1416
1417 Menu autoRangeMenu = new Menu(result);
1418 MenuItem autoRangeMenuItem = new MenuItem(result, SWT.CASCADE);
1419 autoRangeMenuItem.setText(localizationResources.getString(
1420 "Auto_Range"));
1421 autoRangeMenuItem.setMenu(autoRangeMenu);
1422
1423 this.zoomResetBothMenuItem = new MenuItem(autoRangeMenu, SWT.PUSH);
1424 this.zoomResetBothMenuItem.setText(localizationResources.getString(
1425 "All_Axes"));
1426 this.zoomResetBothMenuItem.setData(ZOOM_RESET_BOTH_COMMAND);
1427 this.zoomResetBothMenuItem.addSelectionListener(this);
1428
1429 new MenuItem(autoRangeMenu, SWT.SEPARATOR);
1430
1431 this.zoomResetDomainMenuItem = new MenuItem(autoRangeMenu,
1432 SWT.PUSH);
1433 this.zoomResetDomainMenuItem.setText(
1434 localizationResources.getString("Domain_Axis"));
1435 this.zoomResetDomainMenuItem.setData(ZOOM_RESET_DOMAIN_COMMAND);
1436 this.zoomResetDomainMenuItem.addSelectionListener(this);
1437
1438 this.zoomResetRangeMenuItem = new MenuItem(autoRangeMenu, SWT.PUSH);
1439 this.zoomResetRangeMenuItem.setText(
1440 localizationResources.getString("Range_Axis"));
1441 this.zoomResetRangeMenuItem.setData(ZOOM_RESET_RANGE_COMMAND);
1442 this.zoomResetRangeMenuItem.addSelectionListener(this);
1443 }
1444
1445 return result;
1446 }
1447
1448 /**
1449 * Handles action events generated by the popup menu.
1450 *
1451 * @see org.eclipse.swt.events.SelectionListener#widgetDefaultSelected(
1452 * org.eclipse.swt.events.SelectionEvent)
1453 */
1454 public void widgetDefaultSelected(SelectionEvent e) {
1455 widgetSelected(e);
1456 }
1457
1458 /**
1459 * Handles action events generated by the popup menu.
1460 *
1461 * @see org.eclipse.swt.events.SelectionListener#widgetSelected(
1462 * org.eclipse.swt.events.SelectionEvent)
1463 */
1464 public void widgetSelected(SelectionEvent e) {
1465 String command = (String) ((MenuItem) e.getSource()).getData();
1466 if (command.equals(PROPERTIES_COMMAND)) {
1467 attemptEditChartProperties();
1468 }
1469 else if (command.equals(SAVE_COMMAND)) {
1470 try {
1471 doSaveAs();
1472 }
1473 catch (IOException ex) {
1474 ex.printStackTrace();
1475 }
1476 }
1477 else if (command.equals(PRINT_COMMAND)) {
1478 createChartPrintJob();
1479 }
1480 /* in the next zoomPoint.x and y replace by e.x and y for now.
1481 * this helps to handle the mouse events and besides,
1482 * those values are unused AFAIK. */
1483 else if (command.equals(ZOOM_IN_BOTH_COMMAND)) {
1484 zoomInBoth( e.x, e.y );
1485 }
1486 else if (command.equals(ZOOM_IN_DOMAIN_COMMAND)) {
1487 zoomInDomain( e.x, e.y );
1488 }
1489 else if (command.equals(ZOOM_IN_RANGE_COMMAND)) {
1490 zoomInRange( e.x, e.y );
1491 }
1492 else if (command.equals(ZOOM_OUT_BOTH_COMMAND)) {
1493 zoomOutBoth( e.x, e.y );
1494 }
1495 else if (command.equals(ZOOM_OUT_DOMAIN_COMMAND)) {
1496 zoomOutDomain( e.x, e.y );
1497 }
1498 else if (command.equals(ZOOM_OUT_RANGE_COMMAND)) {
1499 zoomOutRange( e.x, e.y );
1500 }
1501 else if (command.equals(ZOOM_RESET_BOTH_COMMAND)) {
1502 restoreAutoBounds();
1503 }
1504 else if (command.equals(ZOOM_RESET_DOMAIN_COMMAND)) {
1505 restoreAutoDomainBounds();
1506 }
1507 else if (command.equals(ZOOM_RESET_RANGE_COMMAND)) {
1508 restoreAutoRangeBounds();
1509 }
1510 this.forceRedraw();
1511 }
1512
1513 public int print(Graphics graphics, PageFormat pageFormat, int pageIndex)
1514 throws PrinterException {
1515 if (pageIndex != 0) {
1516 return NO_SUCH_PAGE;
1517 }
1518 /*
1519 CairoImage image = new CairoImage(
1520 this.getBounds().width, this.getBounds().height );
1521 Graphics2D g2 = image.createGraphics2D();
1522 double x = pageFormat.getImageableX();
1523 double y = pageFormat.getImageableY();
1524 double w = pageFormat.getImageableWidth();
1525 double h = pageFormat.getImageableHeight();
1526 this.chart.draw(
1527 g2, new Rectangle2D.Double(x, y, w, h), this.anchor, null
1528 );
1529 */
1530 return PAGE_EXISTS;
1531 }
1532
1533 /**
1534 * Hook an SWT listener on the canvas where the chart is drawn.
1535 * The purpose of this method is to allow some degree of customization.
1536 * @param listener The SWT listener to attach to the canvas.
1537 */
1538 public void addSWTListener(SWTEventListener listener) {
1539 if (listener instanceof ControlListener) {
1540 this.canvas.addControlListener((ControlListener) listener);
1541 } else if (listener instanceof DisposeListener) {
1542 this.canvas.addDisposeListener((DisposeListener) listener);
1543 // } else if (listener instanceof DragDetectListener) {
1544 // this.canvas.addDragDetectListener((DragDetectListener) listener);
1545 } else if (listener instanceof FocusListener) {
1546 this.canvas.addFocusListener((FocusListener) listener);
1547 } else if (listener instanceof HelpListener) {
1548 this.canvas.addHelpListener((HelpListener) listener);
1549 } else if (listener instanceof KeyListener) {
1550 this.canvas.addKeyListener((KeyListener) listener);
1551 // } else if (listener instanceof MenuDetectListener) {
1552 // this.canvas.addMenuDetectListener((MenuDetectListener) listener);
1553 } else if (listener instanceof MouseListener) {
1554 this.canvas.addMouseListener((MouseListener) listener);
1555 } else if (listener instanceof MouseMoveListener) {
1556 this.canvas.addMouseMoveListener((MouseMoveListener) listener);
1557 } else if (listener instanceof MouseTrackListener) {
1558 this.canvas.addMouseTrackListener((MouseTrackListener) listener);
1559 // } else if (listener instanceof MouseWheelListener) {
1560 // this.canvas.addMouseWheelListener((MouseWheelListener) listener);
1561 } else if (listener instanceof PaintListener) {
1562 this.canvas.addPaintListener((PaintListener) listener);
1563 } else if (listener instanceof TraverseListener) {
1564 this.canvas.addTraverseListener((TraverseListener) listener);
1565 }
1566 }
1567
1568 /* (non-Javadoc)
1569 * @see org.eclipse.swt.events.MouseListener#mouseDoubleClick(
1570 * org.eclipse.swt.events.MouseEvent)
1571 */
1572 public void mouseDoubleClick(MouseEvent event) {
1573 // do nothing, override if necessary
1574 }
1575
1576 /* (non-Javadoc)
1577 * @see org.eclipse.swt.events.MouseListener#mouseDown(
1578 * org.eclipse.swt.events.MouseEvent)
1579 */
1580 public void mouseDown(MouseEvent event) {
1581
1582 Rectangle scaledDataArea = getScreenDataArea(event.x, event.y);
1583 if (scaledDataArea == null) return;
1584 this.zoomPoint = getPointInRectangle(event.x, event.y, scaledDataArea);
1585 int x = (int) ((event.x - getClientArea().x) / this.scaleX);
1586 int y = (int) ((event.y - getClientArea().y) / this.scaleY);
1587
1588 this.anchor = new Point2D.Double(x, y);
1589 this.chart.setNotify(true); // force a redraw
1590 this.canvas.redraw();
1591
1592 // new entity code
1593 ChartEntity entity = null;
1594 if (this.info != null) {
1595 EntityCollection entities = this.info.getEntityCollection();
1596 if (entities != null) {
1597 entity = entities.getEntity(x, y);
1598 }
1599 }
1600
1601 Object[] listeners = this.chartMouseListeners.getListeners(
1602 ChartMouseListener.class);
1603 if (listeners.length == 0) {
1604 return;
1605 }
1606
1607 // pass mouse down event if some ChartMouseListener are listening
1608 java.awt.event.MouseEvent mouseEvent = SWTUtils.toAwtMouseEvent(event);
1609 ChartMouseEvent chartEvent = new ChartMouseEvent(getChart(),
1610 mouseEvent, entity);
1611 for (int i = listeners.length - 1; i >= 0; i -= 1) {
1612 ((ChartMouseListener) listeners[i]).chartMouseClicked(chartEvent);
1613 }
1614 }
1615
1616 /* (non-Javadoc)
1617 * @see org.eclipse.swt.events.MouseListener#mouseUp(
1618 * org.eclipse.swt.events.MouseEvent)
1619 */
1620 public void mouseUp(MouseEvent event) {
1621
1622 boolean hZoom, vZoom;
1623 if (this.zoomRectangle == null) {
1624 Rectangle screenDataArea = getScreenDataArea(event.x, event.y);
1625 if (screenDataArea != null) {
1626 this.zoomPoint = getPointInRectangle(event.x, event.y,
1627 screenDataArea);
1628 }
1629 if (this.popup != null && event.button == 3) {
1630 org.eclipse.swt.graphics.Point pt = this.canvas.toDisplay(
1631 event.x, event.y);
1632 displayPopupMenu(pt.x, pt.y);
1633 }
1634 }
1635 else {
1636 hZoom = false;
1637 vZoom = false;
1638 if (this.orientation == PlotOrientation.HORIZONTAL) {
1639 hZoom = this.rangeZoomable;
1640 vZoom = this.domainZoomable;
1641 }
1642 else {
1643 hZoom = this.domainZoomable;
1644 vZoom = this.rangeZoomable;
1645 }
1646 boolean zoomTrigger1 = hZoom && Math.abs(this.zoomRectangle.width)
1647 >= this.zoomTriggerDistance;
1648 boolean zoomTrigger2 = vZoom
1649 && Math.abs(this.zoomRectangle.height)
1650 >= this.zoomTriggerDistance;
1651 if (zoomTrigger1 || zoomTrigger2) {
1652 // if the box has been drawn backwards, restore the auto bounds
1653 if ((hZoom && (this.zoomRectangle.x + this.zoomRectangle.width
1654 < this.zoomPoint.x)) || (vZoom && (this.zoomRectangle.y
1655 + this.zoomRectangle.height < this.zoomPoint.y)))
1656 restoreAutoBounds();
1657 else {
1658 zoom(this.zoomRectangle);
1659 }
1660 this.canvas.redraw();
1661 }
1662 }
1663 this.zoomPoint = null;
1664 this.zoomRectangle = null;
1665 }
1666
1667 /* (non-Javadoc)
1668 * @see org.eclipse.swt.events.MouseMoveListener#mouseMove(
1669 * org.eclipse.swt.events.MouseEvent)
1670 */
1671 public void mouseMove(MouseEvent event) {
1672
1673 // handle axis trace
1674 if (this.horizontalAxisTrace || this.verticalAxisTrace) {
1675 this.horizontalTraceLineY = event.y;
1676 this.verticalTraceLineX = event.x;
1677 this.canvas.redraw();
1678 }
1679
1680 // handle tool tips in a simple way
1681 if (this.displayToolTips) {
1682 String s = getToolTipText(event);
1683 if (s == null && this.canvas.getToolTipText() != null
1684 || s!=null && !s.equals(this.canvas.getToolTipText()))
1685 this.canvas.setToolTipText(s);
1686 }
1687
1688 // handle zoom box
1689 boolean hZoom, vZoom;
1690 if (this.zoomPoint != null) {
1691 Rectangle scaledDataArea = getScreenDataArea(this.zoomPoint.x,
1692 this.zoomPoint.y);
1693 org.eclipse.swt.graphics.Point movingPoint
1694 = getPointInRectangle(event.x, event.y, scaledDataArea);
1695 if (this.orientation == PlotOrientation.HORIZONTAL) {
1696 hZoom = this.rangeZoomable;
1697 vZoom = this.domainZoomable;
1698 }
1699 else {
1700 hZoom = this.domainZoomable;
1701 vZoom = this.rangeZoomable;
1702 }
1703 if (hZoom && vZoom) {
1704 // selected rectangle shouldn't extend outside the data area...
1705 this.zoomRectangle = new Rectangle(this.zoomPoint.x,
1706 this.zoomPoint.y, movingPoint.x - this.zoomPoint.x,
1707 movingPoint.y - this.zoomPoint.y);
1708 }
1709 else if (hZoom) {
1710 this.zoomRectangle = new Rectangle(this.zoomPoint.x,
1711 scaledDataArea.y, movingPoint.x - this.zoomPoint.x,
1712 scaledDataArea.height);
1713 }
1714 else if (vZoom) {
1715 int ymax = Math.max(movingPoint.y, scaledDataArea.y);
1716 this.zoomRectangle = new Rectangle(
1717 scaledDataArea.x, this.zoomPoint.y,
1718 scaledDataArea.width, ymax - this.zoomPoint.y);
1719 }
1720 this.canvas.redraw();
1721 }
1722
1723 // new entity code
1724 ChartEntity entity = null;
1725 int x = (int) ((event.x - getClientArea().x) / this.scaleX);
1726 int y = (int) ((event.y - getClientArea().y) / this.scaleY);
1727
1728 if (this.info != null) {
1729 EntityCollection entities = this.info.getEntityCollection();
1730 if (entities != null) {
1731 entity = entities.getEntity(x, y);
1732 }
1733 }
1734
1735 Object[] listeners = this.chartMouseListeners.getListeners(
1736 ChartMouseListener.class);
1737 if (listeners.length == 0) {
1738 return;
1739 }
1740
1741 // pass mouse move event if some ChartMouseListener are listening
1742 java.awt.event.MouseEvent mouseEvent = SWTUtils.toAwtMouseEvent(event);
1743 ChartMouseEvent chartEvent = new ChartMouseEvent(getChart(),
1744 mouseEvent, entity);
1745 for (int i = listeners.length - 1; i >= 0; i -= 1) {
1746 ((ChartMouseListener) listeners[i]).chartMouseMoved(chartEvent);
1747 }
1748 }
1749
1750 /* (non-Javadoc)
1751 * @see org.eclipse.swt.events.PaintListener#paintComponent(
1752 * org.eclipse.swt.events.PaintEvent)
1753 */
1754 public void paintControl(PaintEvent e) {
1755 // first determine the size of the chart rendering area...
1756 // TODO workout insets for SWT
1757 Rectangle available = getBounds();
1758 // skip if chart is null
1759 if (this.chart == null) {
1760 this.canvas.drawBackground(e.gc, available.x, available.y,
1761 available.width, available.height);
1762 return;
1763 }
1764 SWTGraphics2D sg2 = new SWTGraphics2D(e.gc);
1765
1766 // work out if scaling is required...
1767 boolean scale = false;
1768 int drawWidth = available.width;
1769 int drawHeight = available.height;
1770 if ( drawWidth == 0.0 || drawHeight == 0.0 ) return;
1771 this.scaleX = 1.0;
1772 this.scaleY = 1.0;
1773 if (drawWidth < this.minimumDrawWidth) {
1774 this.scaleX = (double) drawWidth / this.minimumDrawWidth;
1775 drawWidth = this.minimumDrawWidth;
1776 scale = true;
1777 }
1778 else if (drawWidth > this.maximumDrawWidth) {
1779 this.scaleX = (double) drawWidth / this.maximumDrawWidth;
1780 drawWidth = this.maximumDrawWidth;
1781 scale = true;
1782 }
1783 if (drawHeight < this.minimumDrawHeight) {
1784 this.scaleY = (double) drawHeight / this.minimumDrawHeight;
1785 drawHeight = this.minimumDrawHeight;
1786 scale = true;
1787 }
1788 else if (drawHeight > this.maximumDrawHeight) {
1789 this.scaleY = (double) drawHeight / this.maximumDrawHeight;
1790 drawHeight = this.maximumDrawHeight;
1791 scale = true;
1792 }
1793 // are we using the chart buffer?
1794 if (this.useBuffer) {
1795 //SwtGraphics2D sg2 = new SwtGraphics2D( e.gc );
1796 this.chartBuffer = (org.eclipse.swt.graphics.Image)
1797 this.canvas.getData("double-buffer-image");
1798 // do we need to fill the buffer?
1799 if (this.chartBuffer == null
1800 || this.chartBufferWidth != available.width
1801 || this.chartBufferHeight != available.height ) {
1802 this.chartBufferWidth = available.width;
1803 this.chartBufferHeight = available.height;
1804 if (this.chartBuffer != null) {
1805 this.chartBuffer.dispose();
1806 }
1807 this.chartBuffer = new org.eclipse.swt.graphics.Image(
1808 getDisplay(), this.chartBufferWidth,
1809 this.chartBufferHeight);
1810 this.refreshBuffer = true;
1811 }
1812
1813 // do we need to redraw the buffer?
1814 if (this.refreshBuffer) {
1815 // Performs the actual drawing here ...
1816 GC gci = new GC(this.chartBuffer);
1817 // anti-aliasing
1818 if (this.chart.getAntiAlias()) {
1819 gci.setAntialias(SWT.ON);
1820 }
1821 if (this.chart.getTextAntiAlias()
1822 == RenderingHints.KEY_TEXT_ANTIALIASING) {
1823 gci.setTextAntialias(SWT.ON);
1824 }
1825 SWTGraphics2D sg2d = new SWTGraphics2D(gci);
1826 if (scale) {
1827 sg2d.scale(this.scaleX, this.scaleY);
1828 this.chart.draw(sg2d, new Rectangle2D.Double(0, 0,
1829 drawWidth, drawHeight), getAnchor(), this.info);
1830 }
1831 else {
1832 this.chart.draw(sg2d, new Rectangle2D.Double(0, 0,
1833 drawWidth, drawHeight), getAnchor(), this.info);
1834 }
1835 this.canvas.setData("double-buffer-image", this.chartBuffer);
1836 sg2d.dispose();
1837 gci.dispose();
1838 this.refreshBuffer = false;
1839 }
1840
1841 // zap the buffer onto the canvas...
1842 sg2.drawImage(this.chartBuffer, 0, 0);
1843 }
1844 // or redrawing the chart every time...
1845 else {
1846 if (this.chart.getAntiAlias()) {
1847 e.gc.setAntialias(SWT.ON);
1848 }
1849 if (this.chart.getTextAntiAlias()
1850 == RenderingHints.KEY_TEXT_ANTIALIASING) {
1851 e.gc.setTextAntialias(SWT.ON);
1852 }
1853 this.chart.draw(sg2, new Rectangle2D.Double(0, 0,
1854 getBounds().width, getBounds().height), getAnchor(),
1855 this.info);
1856 }
1857 Rectangle area = getScreenDataArea();
1858 // TODO see if we need to apply some line color and style to the
1859 // axis traces
1860 if (this.verticalAxisTrace && area.x < this.verticalTraceLineX
1861 && area.x + area.width > this.verticalTraceLineX) {
1862 e.gc.drawLine(this.verticalTraceLineX, area.y,
1863 this.verticalTraceLineX, area.y + area.height);
1864 }
1865 if (this.horizontalAxisTrace && area.y < this.horizontalTraceLineY
1866 && area.y + area.height > this.horizontalTraceLineY) {
1867 e.gc.drawLine(area.x, this.horizontalTraceLineY,
1868 area.x + area.width, this.horizontalTraceLineY);
1869 }
1870 this.verticalTraceLineX = 0;
1871 this.horizontalTraceLineY = 0;
1872 if (this.zoomRectangle != null) {
1873 e.gc.drawRectangle(this.zoomRectangle);
1874 }
1875 sg2.dispose();
1876 }
1877
1878 }