blob: 58ef51de239490d34d0a2514f350850485911732 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015 Domjan Sansovic and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License_Identifier: EPL-2.0
*
* Contributors:
* Domjan Sansovic - initial API and implementation
*******************************************************************************/
package org.eclipse.ease.modules.charting;
import java.io.File;
import java.util.Collection;
import java.util.HashSet;
import org.eclipse.core.resources.IFile;
import org.eclipse.ease.modules.AbstractScriptModule;
import org.eclipse.ease.modules.ScriptParameter;
import org.eclipse.ease.modules.WrapToScript;
import org.eclipse.ease.modules.charting.views.Chart;
import org.eclipse.ease.modules.charting.views.ChartView;
import org.eclipse.ease.modules.platform.UIModule;
import org.eclipse.ease.tools.ResourceTools;
import org.eclipse.nebula.visualization.xygraph.figures.Annotation;
import org.eclipse.nebula.visualization.xygraph.figures.Annotation.CursorLineStyle;
import org.eclipse.nebula.visualization.xygraph.figures.Axis;
import org.eclipse.nebula.visualization.xygraph.figures.Trace;
import org.eclipse.nebula.visualization.xygraph.figures.XYGraph;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbenchPage;
/**
* Provide functions to display and populate line charts.
*/
public class ChartingModule extends AbstractScriptModule {
public static final String MODULE_NAME = "Charting";
private static int fFigureIterator = 1;
private Chart fChart = null;
private XYGraph fXYGraph = null;
private Trace fCurrentPlot = null;
private final Collection<Annotation> fUserMarkers = new HashSet<>();
/**
* Opens a new view with an empty figure. <i>figureId</i> is used as a view and chart title. If a view with the same <i>figureId</i> already exists, it will
* be activated. The last activated figure will be used for all further commands of this modules.
*
* @param figureId
* name of the figure to be created
* @return new or activated chart
* @throws Throwable
* when the view could not be initialized
*/
@WrapToScript
public Chart figure(@ScriptParameter(defaultValue = ScriptParameter.NULL) final String figureId) throws Throwable {
fChart = null;
final String secondaryId = (figureId == null) ? "Figure " + Integer.toString(fFigureIterator++) : figureId;
final ChartView view = (ChartView) UIModule.showView(ChartView.VIEW_ID, secondaryId, IWorkbenchPage.VIEW_ACTIVATE);
view.setViewName(secondaryId);
fChart = view.getChart();
fXYGraph = fChart.setPlotTitle(secondaryId);
return fChart;
}
/**
* Create a new chart on a given parent composite.
*
* @param parent
* composite to create chart into
* @param figureId
* name of the figure to be created
* @return new chart instance
*/
@WrapToScript
public Chart createChart(Composite parent, @ScriptParameter(defaultValue = ScriptParameter.NULL) final String figureId) {
final String secondaryId = (figureId == null) ? "Figure " + Integer.toString(fFigureIterator++) : figureId;
Display.getDefault().syncExec(() -> {
fChart = new Chart(parent, SWT.NONE);
fXYGraph = fChart.setPlotTitle(secondaryId);
});
return fChart;
}
/**
* Series will be created with the name seriesName. If the name is not given then Series will have the name "Series id" where id is the current number of
* the Series Iterator. Series Iterator start at 1 and with every new Series is incremented by one. If Series is already created with seriesName then that
* series will be set as currently active Series and on that Series methods will be performed. If there is no active Figure then Figure will be created and
* activated.
*
* @param seriesName
* Name of this series. Default is <code>null</code> if no seriesName is given, in that case the name is "Series id" as explained above
* @param format
* default is "", if number is written inside format like "f#25" point size will be set(in this case 25), please write format correctly, if for
* example 2 colors will be written like "rg" then the last one will be taken, in this case g or green, so please set line style, point size,
* color and Marker Type only once. Used matlab syntax to define plot format:
* <table>
* <tr>
* <th>Specifier</th>
* <th>LineStyle</th>
* </tr>
* <tr>
* <td>'-'</td>
* <td>Solid line (default)</td>
* </tr>
* <tr>
* <td>'--'</td>
* <td>Dashed line</td>
* </tr>
* <tr>
* <td>':'</td>
* <td>Dotted line</td>
* </tr>
* <tr>
* <td>'-.'</td>
* <td>Dash-dot line</td>
* </tr>
* <tr>
* <td>'#'</td>
* <td>No line</td>
* </tr>
* </table>
*
* <table>
* <tr>
* <th>Specifier</th>
* <th>Color</th>
* </tr>
* <tr>
* <td>r</td>
* <td>Red</td>
* </tr>
* <tr>
* <td>g</td>
* <td>Green</td>
* </tr>
* <tr>
* <td>b</td>
* <td>Blue</td>
* </tr>
* <tr>
* <td>c</td>
* <td>Cyan</td>
* </tr>
* <tr>
* <td>m</td>
* <td>Magenta</td>
* </tr>
* <tr>
* <td>y</td>
* <td>Yellow</td>
* </tr>
* <tr>
* <td>k</td>
* <td>Black</td>
* </tr>
* <tr>
* <td>w</td>
* <td>White</td>
* </tr>
* </table>
*
* <table>
* <tr>
* <th>Specifier</th>
* <th>Marker Type</th>
* </tr>
* <tr>
* <td>'+'</td>
* <td>Plus sign</td>
* </tr>
* <tr>
* <td>'o'</td>
* <td>Circle</td>
* </tr>
* <tr>
* <td>'p'</td>
* <td>Point</td>
* </tr>
* <tr>
* <td>'x'</td>
* <td>Cross</td>
* </tr>
* <tr>
* <td>'s'</td>
* <td>Square</td>
* </tr>
* <tr>
* <td>'f'</td>
* <td>Filled Square</td>
* </tr>
* <tr>
* <td>'d'</td>
* <td>Diamond</td>
* </tr>
* <tr>
* <td>'v'</td>
* <td>Downward-pointing triangle</td>
* </tr>
* </table>
*
* @return series as Trace type to set different properties for this series
* @throws Throwable
* if the series could not be initialized
*/
@WrapToScript
public Trace series(@ScriptParameter(defaultValue = ScriptParameter.NULL) final String seriesName, @ScriptParameter(defaultValue = "") final String format)
throws Throwable {
return getChart().series(seriesName, format);
}
private Chart getChart() throws Throwable {
if (fChart == null)
figure(null);
return fChart;
}
/**
* Add (x,y) point to the last Series that is set with method series(seriesName,format). If there is no active figure and series then both will be created
* and activated.
*
* @param x
* x coordinate of this point
* @param y
* y coordinate of this point
* @return series as Trace type to set different properties for this series
* @throws Throwable
* when the view could not be initialized
*/
@WrapToScript
public Trace plotPoint(final double x, final double y) throws Throwable {
fCurrentPlot = getChart().plot(x, y);
return fCurrentPlot;
}
/**
* Plot array of points (x[],y[]) on the last series that is set with method series(seriesName, format). If there is no active Figure and Series then both
* will be created and activated.
*
* @param x
* array of x coordinates
* @param y
* array of y coordinates
* @return series as Trace type to set different properties for this series
* @throws Throwable
* when the view could not be initialized
*/
@WrapToScript
public Trace plot(final double[] x, final double[] y) throws Throwable {
fCurrentPlot = getChart().plot(x, y);
return fCurrentPlot;
}
/**
* Sets the title of the active chart.
*
* @param chartTitle
* title to be set
* @return {@link XYGraph} object or <code>null</code> if there is no active graph
*/
@WrapToScript(alias = "title")
public XYGraph setPlotTitle(final String chartTitle) {
if (fChart != null)
return fChart.setPlotTitle(chartTitle);
return null;
}
/**
* Set X axis name.
*
* @param label
* label to be used for X axis
* @return {@link Axis} object or <code>null</code> if there is no active graph
*/
@WrapToScript(alias = "xlabel")
public Axis setXLabel(final String label) {
if (fChart != null)
return fChart.setXLabel(label);
return null;
}
/**
* Set Y axis name.
*
* @param label
* label to be used for Y axis
* @return {@link Axis} object or <code>null</code> if there is no active graph
*/
@WrapToScript(alias = "ylabel")
public Axis setYLabel(final String label) {
if (fChart != null)
return fChart.setYLabel(label);
return null;
}
/**
* Set lower and upper limit of the X axis and the Y axis.
*
* @param xRange
* Range from x Axis to be set, format is [xmin, xmax]
* @param yRange
* Range from Y Axis to be set, format is [ymin, ymax]
*/
@WrapToScript(alias = "axis")
public void setAxisRange(final double[] xRange, final double[] yRange) {
if (fChart != null)
fChart.setAxisRange(xRange, yRange);
}
/**
* Activates or deactivates drawing of a background grid.
*
* @param showGrid
* if <code>true</code> a grid will be displayed
*/
@WrapToScript
public void showGrid(final boolean showGrid) {
if (fChart != null)
fChart.showGrid(showGrid);
}
/**
* Activates or deactivates auto scaling whenever the cart is updated. An auto scale may also be triggered by double clicking right into the chart area.
*
* @param performAutoScale
* if <code>true</code> auto scaling will be enabled
*/
@WrapToScript
public void setAutoScale(final boolean performAutoScale) {
if (fChart != null)
fChart.setAutoScale(performAutoScale);
}
/**
* Clears the active chart.
*/
@WrapToScript
public void clear() {
Display.getDefault().syncExec(() -> {
if (fXYGraph != null) {
for (final Annotation marker : fUserMarkers)
fXYGraph.removeAnnotation(marker);
// fXYGraph.removeAll();
}
});
if (fChart != null)
fChart.clear();
fCurrentPlot = null;
}
/**
* Export the current figure as png file. When no <i>location</i> is provided, a popup dialog will ask the user for the target location.
*
* @param location
* where to store image to. Accepts strings, {@link IFile} and {@link File} instances
* @param overwrite
* Overwrite flag, if <code>true</code> file will be overwritten without question
* @throws Throwable
* when the view could not be initialized
*/
@WrapToScript
public void exportGraph(@ScriptParameter(defaultValue = ScriptParameter.NULL) final Object location,
@ScriptParameter(defaultValue = "false") final boolean overwrite) throws Throwable {
if (fChart != null) {
final Object resolvedLocation = (location != null) ? ResourceTools.resolve(location, getScriptEngine().getExecutedFile()) : null;
getChart().export(resolvedLocation, overwrite);
}
}
/**
* Deletes a given series. If the series does not exists this method does nothing.
*
* @param seriesName
* name of series to be deleted
* @throws Throwable
* when the view could not be initialized
*/
@WrapToScript
public void removeSeries(final String seriesName) throws Throwable {
getChart().removeSeries(seriesName);
}
/**
* Add a marker to the chart. A marker is a text label that points to a grid location. By default the marker will point to the data point that was added
* last. If positions are provided always <i>xPosition</i> and <i>yPosition</i> need to be provided. The returned marker can be further customized in the UI
* thread.
*
* @param text
* text to display
* @param xPosition
* if set, this will be the X position the marker points to
* @param yPosition
* if set, this will be the Y position the marker points to
* @return marker implementation
*/
@WrapToScript
public Annotation addMarker(String text, @ScriptParameter(defaultValue = ScriptParameter.NULL) Double xPosition,
@ScriptParameter(defaultValue = ScriptParameter.NULL) Double yPosition) {
if (fCurrentPlot != null) {
final Annotation annotation = new Annotation(text, fCurrentPlot);
annotation.setEnabled(true);
annotation.setCursorLineStyle(CursorLineStyle.NONE);
Display.getDefault().asyncExec(() -> {
fXYGraph.addAnnotation(annotation);
// we need to schedule a fresh UI Job as it seems XY Chart needs to render the annotation first. Only after that it can be moved.
Display.getDefault().asyncExec(() -> {
if ((xPosition != null) && (yPosition != null))
annotation.setValues(xPosition, yPosition);
});
});
fUserMarkers.add(annotation);
return annotation;
}
return null;
}
}