package org.swtchart.ext;
import org.eclipse.jface.preference.PreferenceDialog;
import org.eclipse.jface.preference.PreferenceManager;
import org.eclipse.jface.preference.PreferenceNode;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.swtchart.Chart;
import org.swtchart.IAxis;
import org.swtchart.Range;
import org.swtchart.IAxis.Direction;
import org.swtchart.ext.internal.SelectionRectangle;
import org.swtchart.ext.internal.properties.AxisPage;
import org.swtchart.ext.internal.properties.AxisTickPage;
import org.swtchart.ext.internal.properties.ChartPage;
import org.swtchart.ext.internal.properties.GridPage;
import org.swtchart.ext.internal.properties.LegendPage;
import org.swtchart.ext.internal.properties.PropertiesResources;
import org.swtchart.ext.internal.properties.SeriesLabelPage;
import org.swtchart.ext.internal.properties.SeriesPage;
/**
* An interactive chart which provides the following abilities.
* <ul>
* <li>scroll with arrow keys</li>
* <li>zoom in and out with ctrl + arrow up/down keys</li>
* <li>context menus for adjusting axis range and zooming in/out.</li>
* <li>file selector dialog to save chart to image file.</li>
* <li>properties dialog to configure the chart settings</li>
* </ul>
*/
public class InteractiveChart extends Chart implements PaintListener {
/** the filter extensions */
private static final String[] EXTENSIONS = new String[] { "*.jpeg",
"*.jpg", "*.png" };
/** the selection rectangle for zoom in/out */
protected SelectionRectangle selection;
/** the clicked time in milliseconds */
private long clickedTime;
/** the resources created with properties dialog */
private PropertiesResources resources;
/**
* Mouse move listener implementation showing the current coordinates
* of the mouse cursor as data points.
* @author nbraun
*
*/
private class DataPilot implements MouseMoveListener
{
@Override
public void mouseMove(MouseEvent e) {
IAxis axis = getAxisSet().getYAxis(0);
if (axis != null)
{
double y = axis.getDataCoordinate(e.y);
axis = getAxisSet().getXAxis(0);
if (axis != null)
{
double x = axis.getDataCoordinate(e.x);
String strToolTip;
strToolTip = String.format("%.2f %.2f", x, y);
getPlotArea().setToolTipText(strToolTip);
}
}
}
}
/** The data pilot enabling tool-tip text with the current coordinates */
private DataPilot dataPilot = null;
/**
* Constructor.
*
* @param parent
* the parent composite
* @param style
* the style
*/
public InteractiveChart(Composite parent, int style) {
super(parent, style);
init();
}
/**
* Initializes.
*/
private void init() {
selection = new SelectionRectangle();
resources = new PropertiesResources();
Composite plot = getPlotArea();
plot.addListener(SWT.Resize, this);
plot.addListener(SWT.MouseMove, this);
plot.addListener(SWT.MouseDown, this);
plot.addListener(SWT.MouseUp, this);
plot.addListener(SWT.MouseWheel, this);
plot.addListener(SWT.KeyDown, this);
plot.addPaintListener(this);
createMenuItems();
}
/**
* Creates menu items.
*/
private void createMenuItems() {
Menu menu = new Menu(getPlotArea());
getPlotArea().setMenu(menu);
// adjust axis range menu group
MenuItem menuItem = new MenuItem(menu, SWT.CASCADE);
menuItem.setText(Messages.ADJUST_AXIS_RANGE_GROUP);
Menu adjustAxisRangeMenu = new Menu(menuItem);
menuItem.setMenu(adjustAxisRangeMenu);
// adjust axis range
menuItem = new MenuItem(adjustAxisRangeMenu, SWT.PUSH);
menuItem.setText(Messages.ADJUST_AXIS_RANGE);
menuItem.addListener(SWT.Selection, this);
// adjust X axis range
menuItem = new MenuItem(adjustAxisRangeMenu, SWT.PUSH);
menuItem.setText(Messages.ADJUST_X_AXIS_RANGE);
menuItem.addListener(SWT.Selection, this);
// adjust Y axis range
menuItem = new MenuItem(adjustAxisRangeMenu, SWT.PUSH);
menuItem.setText(Messages.ADJUST_Y_AXIS_RANGE);
menuItem.addListener(SWT.Selection, this);
menuItem = new MenuItem(menu, SWT.SEPARATOR);
// zoom in menu group
menuItem = new MenuItem(menu, SWT.CASCADE);
menuItem.setText(Messages.ZOOMIN_GROUP);
Menu zoomInMenu = new Menu(menuItem);
menuItem.setMenu(zoomInMenu);
// zoom in both axes
menuItem = new MenuItem(zoomInMenu, SWT.PUSH);
menuItem.setText(Messages.ZOOMIN);
menuItem.addListener(SWT.Selection, this);
// zoom in X axis
menuItem = new MenuItem(zoomInMenu, SWT.PUSH);
menuItem.setText(Messages.ZOOMIN_X);
menuItem.addListener(SWT.Selection, this);
// zoom in Y axis
menuItem = new MenuItem(zoomInMenu, SWT.PUSH);
menuItem.setText(Messages.ZOOMIN_Y);
menuItem.addListener(SWT.Selection, this);
// zoom out menu group
menuItem = new MenuItem(menu, SWT.CASCADE);
menuItem.setText(Messages.ZOOMOUT_GROUP);
Menu zoomOutMenu = new Menu(menuItem);
menuItem.setMenu(zoomOutMenu);
// zoom out both axes
menuItem = new MenuItem(zoomOutMenu, SWT.PUSH);
menuItem.setText(Messages.ZOOMOUT);
menuItem.addListener(SWT.Selection, this);
// zoom out X axis
menuItem = new MenuItem(zoomOutMenu, SWT.PUSH);
menuItem.setText(Messages.ZOOMOUT_X);
menuItem.addListener(SWT.Selection, this);
// zoom out Y axis
menuItem = new MenuItem(zoomOutMenu, SWT.PUSH);
menuItem.setText(Messages.ZOOMOUT_Y);
menuItem.addListener(SWT.Selection, this);
menuItem = new MenuItem(menu, SWT.SEPARATOR);
// save as
menuItem = new MenuItem(menu, SWT.PUSH);
menuItem.setText(Messages.SAVE_AS);
menuItem.addListener(SWT.Selection, this);
menuItem = new MenuItem(menu, SWT.SEPARATOR);
// properties
menuItem = new MenuItem(menu, SWT.PUSH);
menuItem.setText(Messages.PROPERTIES);
menuItem.addListener(SWT.Selection, this);
}
/*
* @see PaintListener#paintControl(PaintEvent)
*/
public void paintControl(PaintEvent e) {
selection.draw(e.gc);
}
/*
* @see Listener#handleEvent(Event)
*/
@Override
public void handleEvent(Event event) {
super.handleEvent(event);
switch (event.type) {
case SWT.MouseMove:
handleMouseMoveEvent(event);
break;
case SWT.MouseDown:
handleMouseDownEvent(event);
break;
case SWT.MouseUp:
handleMouseUpEvent(event);
break;
case SWT.MouseWheel:
handleMouseWheel(event);
break;
case SWT.KeyDown:
handleKeyDownEvent(event);
break;
case SWT.Selection:
handleSelectionEvent(event);
break;
default:
break;
}
}
/*
* @see Chart#dispose()
*/
@Override
public void dispose() {
super.dispose();
resources.dispose();
}
/**
* Handles mouse move event.
*
* @param event
* the mouse move event
*/
private void handleMouseMoveEvent(Event event) {
if (!selection.isDisposed()) {
selection.setEndPoint(event.x, event.y);
redraw();
}
}
/**
* Handles the mouse down event.
*
* @param event
* the mouse down event
*/
private void handleMouseDownEvent(Event event) {
if (event.button == 1) {
selection.setStartPoint(event.x, event.y);
clickedTime = System.currentTimeMillis();
}
}
/**
* Handles the mouse up event.
*
* @param event
* the mouse up event
*/
private void handleMouseUpEvent(Event event) {
if (event.button == 1 && System.currentTimeMillis() - clickedTime > 100) {
for (IAxis axis : getAxisSet().getAxes()) {
Point range = null;
if ((getOrientation() == SWT.HORIZONTAL && axis.getDirection() == Direction.X)
|| (getOrientation() == SWT.VERTICAL && axis
.getDirection() == Direction.Y)) {
range = selection.getHorizontalRange();
} else {
range = selection.getVerticalRange();
}
if (range != null && range.x != range.y) {
setRange(range, axis);
}
}
}
selection.dispose();
redraw();
}
/**
* Handles mouse wheel event.
*
* @param event
* the mouse wheel event
*/
private void handleMouseWheel(Event event) {
for (IAxis axis : getAxes(SWT.HORIZONTAL)) {
double coordinate = axis.getDataCoordinate(event.x);
if (event.count > 0) {
axis.zoomIn(coordinate);
} else {
axis.zoomOut(coordinate);
}
}
for (IAxis axis : getAxes(SWT.VERTICAL)) {
double coordinate = axis.getDataCoordinate(event.y);
if (event.count > 0) {
axis.zoomIn(coordinate);
} else {
axis.zoomOut(coordinate);
}
}
redraw();
}
/**
* Handles the key down event.
*
* @param event
* the key down event
*/
private void handleKeyDownEvent(Event event) {
if (event.keyCode == SWT.ARROW_DOWN) {
if (event.stateMask == SWT.CTRL) {
getAxisSet().zoomOut();
} else {
for (IAxis axis : getAxes(SWT.VERTICAL)) {
axis.scrollDown();
}
}
redraw();
} else if (event.keyCode == SWT.ARROW_UP) {
if (event.stateMask == SWT.CTRL) {
getAxisSet().zoomIn();
} else {
for (IAxis axis : getAxes(SWT.VERTICAL)) {
axis.scrollUp();
}
}
redraw();
} else if (event.keyCode == SWT.ARROW_LEFT) {
for (IAxis axis : getAxes(SWT.HORIZONTAL)) {
axis.scrollDown();
}
redraw();
} else if (event.keyCode == SWT.ARROW_RIGHT) {
for (IAxis axis : getAxes(SWT.HORIZONTAL)) {
axis.scrollUp();
}
redraw();
}
}
/**
* Gets the axes for given orientation.
*
* @param orientation
* the orientation
* @return the axes
*/
private IAxis[] getAxes(int orientation) {
IAxis[] axes;
if (getOrientation() == orientation) {
axes = getAxisSet().getXAxes();
} else {
axes = getAxisSet().getYAxes();
}
return axes;
}
/**
* Handles the selection event.
*
* @param event
* the event
*/
private void handleSelectionEvent(Event event) {
if (!(event.widget instanceof MenuItem)) {
return;
}
MenuItem menuItem = (MenuItem) event.widget;
if (menuItem.getText().equals(Messages.ADJUST_AXIS_RANGE)) {
getAxisSet().adjustRange();
} else if (menuItem.getText().equals(Messages.ADJUST_X_AXIS_RANGE)) {
for (IAxis axis : getAxisSet().getXAxes()) {
axis.adjustRange();
}
} else if (menuItem.getText().equals(Messages.ADJUST_Y_AXIS_RANGE)) {
for (IAxis axis : getAxisSet().getYAxes()) {
axis.adjustRange();
}
} else if (menuItem.getText().equals(Messages.ZOOMIN)) {
getAxisSet().zoomIn();
} else if (menuItem.getText().equals(Messages.ZOOMIN_X)) {
for (IAxis axis : getAxisSet().getXAxes()) {
axis.zoomIn();
}
} else if (menuItem.getText().equals(Messages.ZOOMIN_Y)) {
for (IAxis axis : getAxisSet().getYAxes()) {
axis.zoomIn();
}
} else if (menuItem.getText().equals(Messages.ZOOMOUT)) {
getAxisSet().zoomOut();
} else if (menuItem.getText().equals(Messages.ZOOMOUT_X)) {
for (IAxis axis : getAxisSet().getXAxes()) {
axis.zoomOut();
}
} else if (menuItem.getText().equals(Messages.ZOOMOUT_Y)) {
for (IAxis axis : getAxisSet().getYAxes()) {
axis.zoomOut();
}
} else if (menuItem.getText().equals(Messages.SAVE_AS)) {
openSaveAsDialog();
} else if (menuItem.getText().equals(Messages.PROPERTIES)) {
openPropertiesDialog();
}
redraw();
}
/**
* Opens the Save As dialog.
*/
private void openSaveAsDialog() {
FileDialog dialog = new FileDialog(getShell(), SWT.SAVE);
dialog.setText(Messages.SAVE_AS_DIALOG_TITLE);
dialog.setFilterExtensions(EXTENSIONS);
String filename = dialog.open();
if (filename == null) {
return;
}
int format;
if (filename.endsWith(".jpg") || filename.endsWith(".jpeg")) {
format = SWT.IMAGE_JPEG;
} else if (filename.endsWith(".png")) {
format = SWT.IMAGE_PNG;
} else {
format = SWT.IMAGE_UNDEFINED;
}
if (format != SWT.IMAGE_UNDEFINED) {
save(filename, format);
}
}
/**
* Opens the properties dialog.
*/
private void openPropertiesDialog() {
PreferenceManager manager = new PreferenceManager();
final String chartTitle = "Chart";
PreferenceNode chartNode = new PreferenceNode(chartTitle);
chartNode.setPage(new ChartPage(this, resources, chartTitle));
manager.addToRoot(chartNode);
final String legendTitle = "Legend";
PreferenceNode legendNode = new PreferenceNode(legendTitle);
legendNode.setPage(new LegendPage(this, resources, legendTitle));
manager.addTo(chartTitle, legendNode);
final String xAxisTitle = "X Axis";
PreferenceNode xAxisNode = new PreferenceNode(xAxisTitle);
xAxisNode
.setPage(new AxisPage(this, resources, Direction.X, xAxisTitle));
manager.addTo(chartTitle, xAxisNode);
final String gridTitle = "Grid";
PreferenceNode xGridNode = new PreferenceNode(gridTitle);
xGridNode
.setPage(new GridPage(this, resources, Direction.X, gridTitle));
manager.addTo(chartTitle + "." + xAxisTitle, xGridNode);
final String tickTitle = "Tick";
PreferenceNode xTickNode = new PreferenceNode(tickTitle);
xTickNode.setPage(new AxisTickPage(this, resources, Direction.X,
tickTitle));
manager.addTo(chartTitle + "." + xAxisTitle, xTickNode);
final String yAxisTitle = "Y Axis";
PreferenceNode yAxisNode = new PreferenceNode(yAxisTitle);
yAxisNode
.setPage(new AxisPage(this, resources, Direction.Y, yAxisTitle));
manager.addTo(chartTitle, yAxisNode);
PreferenceNode yGridNode = new PreferenceNode(gridTitle);
yGridNode
.setPage(new GridPage(this, resources, Direction.Y, gridTitle));
manager.addTo(chartTitle + "." + yAxisTitle, yGridNode);
PreferenceNode yTickNode = new PreferenceNode(tickTitle);
yTickNode.setPage(new AxisTickPage(this, resources, Direction.Y,
tickTitle));
manager.addTo(chartTitle + "." + yAxisTitle, yTickNode);
final String seriesTitle = "Series";
PreferenceNode plotNode = new PreferenceNode(seriesTitle);
plotNode.setPage(new SeriesPage(this, resources, seriesTitle));
manager.addTo(chartTitle, plotNode);
final String labelTitle = "Label";
PreferenceNode labelNode = new PreferenceNode(labelTitle);
labelNode.setPage(new SeriesLabelPage(this, resources, labelTitle));
manager.addTo(chartTitle + "." + seriesTitle, labelNode);
PreferenceDialog dialog = new PreferenceDialog(getShell(), manager);
dialog.create();
dialog.getShell().setText("Properties");
dialog.getTreeViewer().expandAll();
dialog.open();
}
/**
* Sets the axis range.
*
* @param range
* the axis range in pixels
* @param axis
* the axis to set range
*/
private void setRange(Point range, IAxis axis) {
if (range == null) {
return;
}
double min = axis.getDataCoordinate(range.x);
double max = axis.getDataCoordinate(range.y);
axis.setRange(new Range(min, max));
}
/**
* When the data pilot is enabled, the mouse cursor will be extended with a
* tool-tip showing the current x,y coordinates that the mouse is pointing
* to.
* @param enable true to enable mouse tool-tips, false to disable
* @return whether the data pilot was enabled or not, before calling this
* function.
*/
public boolean enableDataPilot(boolean enable)
{
boolean b = (dataPilot != null);
if ((b == false) && enable)
{
dataPilot = new DataPilot();
getPlotArea().addMouseMoveListener(dataPilot);
}
else if ((b==true) && (enable == false))
{
getPlotArea().removeMouseMoveListener(dataPilot);
dataPilot = null;
} /** if ... else */
return b;
}
}