blob: 99df342d26d867491da3259ab134f3153e4e09de [file] [log] [blame]
package org.eclipse.stem.ui.reports.views;
/*******************************************************************************
* Copyright (c) 2007 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.birt.chart.device.IDeviceRenderer;
import org.eclipse.birt.chart.exception.ChartException;
import org.eclipse.birt.chart.factory.Generator;
import org.eclipse.birt.chart.model.Chart;
import org.eclipse.birt.chart.model.ChartWithAxes;
import org.eclipse.birt.chart.model.attribute.Anchor;
import org.eclipse.birt.chart.model.attribute.AxisType;
import org.eclipse.birt.chart.model.attribute.Bounds;
import org.eclipse.birt.chart.model.attribute.ColorDefinition;
import org.eclipse.birt.chart.model.attribute.IntersectionType;
import org.eclipse.birt.chart.model.attribute.JavaNumberFormatSpecifier;
import org.eclipse.birt.chart.model.attribute.LineAttributes;
import org.eclipse.birt.chart.model.attribute.LineStyle;
import org.eclipse.birt.chart.model.attribute.Marker;
import org.eclipse.birt.chart.model.attribute.NumberFormatSpecifier;
import org.eclipse.birt.chart.model.attribute.Position;
import org.eclipse.birt.chart.model.attribute.TickStyle;
import org.eclipse.birt.chart.model.attribute.impl.BoundsImpl;
import org.eclipse.birt.chart.model.attribute.impl.ColorDefinitionImpl;
import org.eclipse.birt.chart.model.attribute.impl.JavaNumberFormatSpecifierImpl;
import org.eclipse.birt.chart.model.attribute.impl.NumberFormatSpecifierImpl;
import org.eclipse.birt.chart.model.component.Axis;
import org.eclipse.birt.chart.model.component.Series;
import org.eclipse.birt.chart.model.component.impl.SeriesImpl;
import org.eclipse.birt.chart.model.data.NumberDataSet;
import org.eclipse.birt.chart.model.data.SeriesDefinition;
import org.eclipse.birt.chart.model.data.impl.NumberDataElementImpl;
import org.eclipse.birt.chart.model.data.impl.NumberDataSetImpl;
import org.eclipse.birt.chart.model.data.impl.SeriesDefinitionImpl;
import org.eclipse.birt.chart.model.impl.ChartWithAxesImpl;
import org.eclipse.birt.chart.model.layout.Legend;
import org.eclipse.birt.chart.model.layout.Plot;
import org.eclipse.birt.chart.model.type.LineSeries;
import org.eclipse.birt.chart.model.type.ScatterSeries;
import org.eclipse.birt.chart.model.type.impl.ScatterSeriesImpl;
import org.eclipse.birt.chart.util.PluginSettings;
import org.eclipse.emf.edit.provider.IItemPropertyDescriptor;
import org.eclipse.emf.edit.provider.ItemPropertyDescriptor;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.stem.core.model.Decorator;
import org.eclipse.stem.core.model.STEMTime;
import org.eclipse.stem.definitions.adapters.relativevalue.history.RelativeValueHistoryProvider;
import org.eclipse.stem.definitions.adapters.relativevalue.history.RelativeValueHistoryProviderAdapter;
import org.eclipse.stem.jobs.simulation.ISimulation;
import org.eclipse.stem.ui.adapters.color.STEMColor;
import org.eclipse.stem.ui.preferences.VisualizationPreferencePage;
import org.eclipse.stem.ui.reports.Activator;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.IWorkbenchActionConstants;
/**
* TimeSeriesCanvas is a subclass of {@link Canvas} suitable for chart drawings.
*/
public class TimeSeriesCanvas extends Canvas {
protected IDeviceRenderer idr = null;
/**
* This is the {@link Chart} that plots the relative values.
*/
protected Chart cm = null;
/**
* The provider of relative values.
*/
private RelativeValueHistoryProvider rvhp;
private ReportControl control = null;
/**
* some extra colors
*/
protected static final ColorDefinition[] colorDefault = {
ColorDefinitionImpl.BLUE(),
ColorDefinitionImpl.BLACK(),
ColorDefinitionImpl.GREY(),
ColorDefinitionImpl.CYAN(),
ColorDefinitionImpl.ORANGE()
};
/**
* used to index the line series so we can step through default colors
* when a user custom color is not yet assigned
*/
protected int seriesCount = 0;
/**
* Log of zero is negative infinity so for each location we will cut off the minimum value
* of log(y) at 0.1/POPULATION for display purposes only
*/
private double minLogScaleValue = 1.0;
/**
* once time > DEFAULT_AUTOAXIS_THRESHOLD we start to autoreset the time axis
* tick marks and scale so we don't have too many tick marks
*/
private static final int DEFAULT_AUTOAXIS_THRESHOLD = 5;
/**
* the max value for the time (x) axis
*/
private int maxTimeAxisValue = DEFAULT_AUTOAXIS_THRESHOLD;
/**
* the minimum number of time axis ticks
*/
private static final int MIN_TICKS = 5;
/**
* the maximum number of time axis ticks after time=DEFAULT_AUTOAXIS_THRESHOLD;
*/
private static final int MAX_TICKS = 10;
/**
* used to set the autoaxis tick
*/
private static final int TICK_TRIGGER = MAX_TICKS/MIN_TICKS;
/**
* This is a map of properties of the label updated by the selected
* {@link Decorator} whose relative value might be plotted (context menu allows us to toggle the individual
* properties to plot
*/
protected Map<ItemPropertyDescriptor,Boolean> propertiesToPlot = new HashMap<ItemPropertyDescriptor,Boolean>();
/**
* TODO this is temporary code and should be in user preferences
* we want to hide a few of the possible line-series on start up
*/
private static final String[] HIDE_ON_STARTUP = {"IR","IF","Incidence","Births","Deaths","Population Count", "Disease Deaths"};
private static final Set<String> defaultHideSet = new HashSet<String>();
/**
* this is a map of the DataSeries object (keyed by property name)
*/
protected final Map<String,DataSeries> dataSeriesMap = new HashMap<String,DataSeries>();
/**
* These are the cycle numbers that match the relative values that will be
* plotted
*
* @see #relativeValues
*/
private final List<Integer> cycleNumbers = new ArrayList<Integer>();
/**
* Chart generator instance (reference to a singleton)
*/
Generator gr;
Axis yAxisPrimary;
Axis xAxisPrimary;
/**
* A context menu for this view
*/
Menu popUpMenu = null;
// Init a Context Menu Manager
final MenuManager contextMenuManager = new MenuManager();
/**
* set y axis to a linear scale
*/
private LinearScaleAction linearTimeAction;
/**
* set y axis to a log scale
*/
private LogScaleAction logTimeAction;
protected boolean useLinearTimeScale = true;
/**
* show the legend (true by default)
*/
private LegendViewAction viewLegend;
/**
* Clear the properties from the time series
*/
private ClearAction clearAction;
List<DisplayableProperty> displayableProperties;
/**
* hide the legend
*/
private LegendHideAction hideLegend;
protected boolean showLegend = true;
protected Legend legend = null;
private static final String defaultKey = "";
Image imgChart = null;
/**
* Constructor.
*
* @param parent
* the SWT parent of the {@link Widget}
*/
public TimeSeriesCanvas(final Composite parent) {
super(parent, SWT.DOUBLE_BUFFERED | SWT.BORDER);
// init
for (int i = 0; i < HIDE_ON_STARTUP.length; i ++) {
defaultHideSet.add(HIDE_ON_STARTUP[i]);
}
gr = Generator.instance();
try {
idr = PluginSettings.instance().getDevice("dv.SWT"); //$NON-NLS-1$
} catch (final ChartException pex) {
Activator.logError("Problem initializing chart", pex); //$NON-NLS-1$
return;
}
control = (ReportControl) parent;
rvhp = control.rvhp;
cm = createSimpleLineChart(dataSeriesMap, cycleNumbers, Messages
.getString("CC.title")); //$NON-NLS-1$
resetData();
addPaintListener(new PaintListener() {
public void paintControl(final PaintEvent pe) {
final Composite source = (Composite) pe.getSource();
final org.eclipse.swt.graphics.Rectangle d = source
.getClientArea();
if(imgChart != null) imgChart.dispose();
imgChart = new Image(source.getDisplay(), d);
idr.setProperty(IDeviceRenderer.GRAPHICS_CONTEXT, new GC(
imgChart));
final Bounds bounds = BoundsImpl.create(d.x, d.y, d.width,
d.height);
bounds.scale(72d / idr.getDisplayServer().getDpiResolution());
// BOUNDS MUST BE SPECIFIED IN POINTS
try {
gr.render(idr, gr.build(idr.getDisplayServer(), cm, bounds,
null, null, null));
pe.gc.drawImage(imgChart, d.x, d.y);
} catch (final ChartException ce) {
Activator.logError("Problem rendering chart", ce); //$NON-NLS-1$
}
} // paintControl
} // PaintListener
);
//Create a context menu for the canvas
createContextMenu(this);
} // TimeSeriesCanvas
/**
* The method which gets the {@link TimeSeriesCanvas}' reports list, and
* draws it on the {@link TimeSeriesCanvas}.
*
*/
public synchronized void draw() {
// Has a relative value provider been provided?
if (rvhp != null) {
double maxY = Double.MIN_VALUE;
double minY = Double.MAX_VALUE;
// Yes
// build up the set of properties to plot
// First we disable all series so that we only turn on the ones that are enabled.
for(DataSeries d: dataSeriesMap.values())
d.hide();
List<ItemPropertyDescriptor> itemList = new ArrayList<ItemPropertyDescriptor>();
itemList.addAll(propertiesToPlot.keySet());
Collections.sort(itemList, new Comparator<ItemPropertyDescriptor>() {
public int compare(ItemPropertyDescriptor o1,
ItemPropertyDescriptor o2) {
return o1.getDisplayName(o1).compareTo(o2.getDisplayName(o2));
}
});
List<ItemPropertyDescriptor> displayedPropertyList = new ArrayList<ItemPropertyDescriptor>();
for(ItemPropertyDescriptor property: itemList){
boolean visible = propertiesToPlot.get(property).booleanValue();
displayedPropertyList.add(property);
if(visible) {
if(dataSeriesMap.containsKey(property.getDisplayName(property))) {
DataSeries series = dataSeriesMap.get(property.getDisplayName(property));
// remove it
if(!series.isVisible()) {
series.show();
}
}
} else {
if(dataSeriesMap.containsKey(property.getDisplayName(property))) {
DataSeries series = dataSeriesMap.get(property.getDisplayName(property));
// remove it
if(series.isVisible()) {
series.hide();
}
}
}// visible?
}// all displayedProperties
/////////////////////////////////////////////////////////////
// Log(0.0) is negative infinity so for display purposes only
// we set the minimum axis value at 0.1/POPULATION
double denom = rvhp.getDenominator(null);
if(denom <=0.0) denom = 1.0;
minLogScaleValue = 0.1/denom;
/////////////////////////////////////////////////////////////
// clear
resetData();
boolean setCycles = false;
// Get the values for the property to be plotted
int maxPoints = 0;
STEMTime[] time = rvhp.getAllHistoricTimeValues();
cycleNumbers.clear();
cycleNumbers.add(Integer.valueOf(0));
for (int i = 0; i < displayedPropertyList.size(); i++) {
ItemPropertyDescriptor property = displayedPropertyList.get(i);
String propertyName = property.getDisplayName(property);
//TESTING
//System.out.println("looking for "+property.getDisplayName(property));
final double[] doubleValues = rvhp.getHistoricInstances(property,time);
// Any values?
if (doubleValues.length > 0) {
if (maxPoints < doubleValues.length) {
maxPoints = doubleValues.length;
}
// this might be a new display
// so we have to fill any empty data set
if (!dataSeriesMap.containsKey(property.getDisplayName(property))) {
DataSeries series = new DataSeries(propertyName, seriesCount);
// we are out of data (properties) so we need to fill in
// zeros for any unused lines
// fill in the empty arrays
if(doubleValues.length < maxPoints) {
for(int ii = doubleValues.length; ii < maxPoints-1; ii ++) {
// pad with zeros
series.addValue(new Double(0.0));
}
}
dataSeriesMap.put(propertyName, series);
}
final int earliestCycleNumber = rvhp.getEarliestCycleNumber();
DataSeries series = dataSeriesMap.get(property.getDisplayName(property));
series.setColorDefs(property.getDisplayName(property));
for (int cycleNumber = 0; cycleNumber < doubleValues.length; cycleNumber++) {
Double value;
double displayValue = doubleValues[cycleNumber];
if (displayValue <= minLogScaleValue && !useLinearTimeScale) {
// Log(0.0) is negative infinity so for display purposes only
// we set the minimum axis value at 0.1/POPULATION
displayValue = minLogScaleValue;
}
if (useLinearTimeScale) {
value = new Double(displayValue);
} else {
value = new Double(Math.log(displayValue));
}
if(maxY < displayValue && series.isVisible()) {
maxY = displayValue;
}
if(minY > displayValue && series.isVisible()) {
minY = displayValue;
}
series.addValue(value);
if (!setCycles) {
this.cycleNumbers.add(Integer.valueOf(
earliestCycleNumber + cycleNumber + 1));
/*
* We don't want to add x (time) axis ticks ad infinitum so we we need to dynamically
* change the scale. This will toggle between 5-10 ticks every time the max time doubles
* past an initial value of maxTimeAxisValue = DEFAULT_AUTOAXIS_THRESHOLD (= 25)
*/
if(this.cycleNumbers.size()>TICK_TRIGGER*maxTimeAxisValue) {
maxTimeAxisValue = this.cycleNumbers.size()+1 ;
int stepX = maxTimeAxisValue / MIN_TICKS;
xAxisPrimary.getScale().setStep(stepX);
}
}
} // for cycleNumber
//Activator.logInformation(" "+series.propertyName+"cycles = "+this.cycleNumbers.size()+" datasize = "+series.getDataSize());
setCycles = true; // we set them only once
} else {
resetData();
}
} // for i properties
// Set the new min/max but only for linear scale
if(useLinearTimeScale) {
if(maxY == Double.MIN_VALUE) {
// No plots
yAxisPrimary.getScale().setMax(NumberDataElementImpl.create(1.0));
yAxisPrimary.getScale().setMin(NumberDataElementImpl.create(0.0));
yAxisPrimary.getScale().setStep(0.25);
setYAxisNumberFormatFromMaxY(1.0);
} else if(maxY-minY != 0.0) {
yAxisPrimary.getScale().setMax(NumberDataElementImpl.create(maxY));
yAxisPrimary.getScale().setMin(NumberDataElementImpl.create(0.0));
yAxisPrimary.getScale().setStep((maxY-minY)/MIN_TICKS);
setYAxisNumberFormatFromMaxY(maxY);
} else if(maxY == minY && maxY > 0.0) {
yAxisPrimary.getScale().setMax(NumberDataElementImpl.create(maxY));
yAxisPrimary.getScale().setMin(NumberDataElementImpl.create(0.0));
yAxisPrimary.getScale().setStep((maxY)/MIN_TICKS);
setYAxisNumberFormatFromMaxY(maxY);
} else {
yAxisPrimary.getScale().setMax(NumberDataElementImpl.create(1.0));
yAxisPrimary.getScale().setMin(NumberDataElementImpl.create(0.0));
yAxisPrimary.getScale().setStep(0.25);
setYAxisNumberFormatFromMaxY(1.0);
}
} else setYAxisLogNumberFormat();
} // if a relative value provider has been provided
else {
// No
// Need to clear everything
resetData();
}
// Check each data series in the map to make sure they have enough Y values in them
for(DataSeries ds:this.dataSeriesMap.values()) {
if(ds.relativeValues.size() < this.cycleNumbers.size()) {
ds.relativeValues.clear();
for(int n=0;n<this.cycleNumbers.size();++n) ds.addValue(new Double(0.0));
}
}
if (!this.isDisposed()) {
redraw();
}
} // paintControl
private void setYAxisLogNumberFormat() {
NumberFormatSpecifier jnfs = null;
jnfs = NumberFormatSpecifierImpl.create();
yAxisPrimary.setFormatSpecifier(jnfs);
}
/**
* Create the view's context menu and add the action handlers to it.
*/
private void createContextMenu(final Composite parent) {
// ---------------------------------------------------------------------
linearTimeAction = new LinearScaleAction();
logTimeAction = new LogScaleAction();
contextMenuManager.add(linearTimeAction);
contextMenuManager.add(logTimeAction);
// Place Holder for Menu Additions
contextMenuManager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
// ---------------------------------------------------------------------
viewLegend = new LegendViewAction();
hideLegend = new LegendHideAction();
contextMenuManager.add(viewLegend);
contextMenuManager.add(hideLegend);
// ---------------------------------------------------------------------
// Place Holder for Menu Additions
contextMenuManager.add(new Separator(
IWorkbenchActionConstants.MB_ADDITIONS));
popUpMenu = contextMenuManager.createContextMenu(parent);
// Set the context menu for the viewer
parent.setMenu(popUpMenu);
} // createContextMenu
/**
* Update the view's context menu and add the action handlers to it.
*/
private void updateContextMenu(final Composite parent) {
popUpMenu.dispose();
// Init a Context Menu Manager
contextMenuManager.removeAll();
// ---------------------------------------------------------------------
if(linearTimeAction==null) linearTimeAction = new LinearScaleAction();
if(logTimeAction==null) logTimeAction = new LogScaleAction();
contextMenuManager.add(linearTimeAction);
contextMenuManager.add(logTimeAction);
contextMenuManager.update();
// ---------------------------------------------------------------------
// Place Holder for Menu Additions
contextMenuManager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
// ---------------------------------------------------------------------
viewLegend = new LegendViewAction();
hideLegend = new LegendHideAction();
contextMenuManager.add(viewLegend);
contextMenuManager.add(hideLegend);
// ---------------------------------------------------------------------
// Place Holder for Menu Additions
contextMenuManager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
// ---------------------------------------------------------------------
// add the displayable properties
if(displayableProperties == null) {
displayableProperties = new ArrayList<DisplayableProperty>();
}
else {
displayableProperties.clear();
}
if(rvhp!= null) {
//List<IItemPropertyDescriptor> properties = rvhp.getProperties();
contextMenuManager.update();
Iterator<ItemPropertyDescriptor> iter = propertiesToPlot.keySet().iterator();
//for(int i = 0; i < properties.size(); i ++) {
while((iter!=null)&&(iter.hasNext())) {
ItemPropertyDescriptor nextProp = iter.next();
DisplayableProperty property = new DisplayableProperty(nextProp);
displayableProperties.add(property);
contextMenuManager.add(property);
}// for all properties
contextMenuManager.updateAll(true);
}// rvhp not null
// ---------------------------------------------------------------------
// Place Holder for Menu Additions
contextMenuManager.add(new Separator(
IWorkbenchActionConstants.MB_ADDITIONS));
// ---------------------------------------------------------------------
clearAction = new ClearAction();
contextMenuManager.add(clearAction);
contextMenuManager.update();
contextMenuManager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
final Menu popUpMenu = contextMenuManager.createContextMenu(parent);
// Set the context menu for the viewer
parent.setMenu(popUpMenu);
} // updateContextMenu
/**
* @param dataSeriesMap the {@link Map} that will contain the relative values
* (0.0-1.0) to plot
* @param cycleNumbers
* the {@link List} of simulation cycle numbers that match the
* relative values
* @param seriesIdentifier
* the title of the chart
* @return a <code>Chart</code>
*/
public final Chart createSimpleLineChart(
final Map<String, DataSeries> dataSeriesMap,
final List<Integer> cycleNumbers, final String seriesIdentifier) {
final ChartWithAxes chartWithAxes = ChartWithAxesImpl.create();
// Plot
chartWithAxes.getBlock().setBackground(ColorDefinitionImpl.create(50, 50,50));
final Plot p = chartWithAxes.getPlot();
p.getClientArea().setBackground( ColorDefinitionImpl.create(0, 0, 0));
// Title
// cwaLine.getTitle( ).getLabel( ).getCaption( ).setValue( "Line Chart"
// );//$NON-NLS-1$
chartWithAxes.getTitle().setVisible(false);
// Legend
legend = chartWithAxes.getLegend();
final LineAttributes lia = legend.getOutline();
legend.getText().getFont().setSize(8);
lia.setStyle(LineStyle.SOLID_LITERAL);
legend.getInsets().set(10, 5, 0, 0);
legend.getOutline().setVisible(false);
legend.setAnchor(Anchor.NORTH_LITERAL);
legend.setPosition(Position.BELOW_LITERAL);
legend.getText().setColor(ColorDefinitionImpl.create(255, 255, 255));
legend.getOutline().setColor(ColorDefinitionImpl.create(255, 255, 255));
// /////////
// X-Axis
xAxisPrimary = chartWithAxes.getPrimaryBaseAxes()[0];
xAxisPrimary.getMajorGrid().setTickStyle(TickStyle.BELOW_LITERAL);
xAxisPrimary.getOrigin().setType(IntersectionType.VALUE_LITERAL);
xAxisPrimary.getTitle().setVisible(false);
xAxisPrimary.getTitle().getCaption().getFont().setSize(9);
xAxisPrimary.getTitle().getCaption().setColor(ColorDefinitionImpl.create(255, 255, 255));
xAxisPrimary.getLabel().getCaption().setColor(ColorDefinitionImpl.create(255, 255, 255));
xAxisPrimary.setType(AxisType.LINEAR_LITERAL);
final Series seCategory = SeriesImpl.create();
// new colors
seCategory.getLabel().getCaption().setColor(ColorDefinitionImpl.create(255, 255, 255));
seCategory.getLabel().getOutline().setColor(ColorDefinitionImpl.create(255, 255, 255));
//
final NumberDataSet categoryValues = NumberDataSetImpl
.create(cycleNumbers);
seCategory.setDataSet(categoryValues);
final SeriesDefinition sdX = SeriesDefinitionImpl.create();
xAxisPrimary.getSeriesDefinitions().add(sdX);
sdX.getSeries().add(seCategory);
// ////////
// Y-Axis
this.yAxisPrimary = chartWithAxes.getPrimaryOrthogonalAxis(xAxisPrimary);
yAxisPrimary.getMajorGrid().setTickStyle(TickStyle.LEFT_LITERAL);
// end Y-Series
xAxisPrimary.getScale().setMin(NumberDataElementImpl.create(0.0));
xAxisPrimary.getScale().setStep(1);
//xAxisPrimary.getScale().setMax(NumberDataElementImpl.create(TICK_TRIGGER*DEFAULT_AUTOAXIS_THRESHOLD));
//int numTicks = TICK_TRIGGER*DEFAULT_AUTOAXIS_THRESHOLD/MIN_TICKS;
//
xAxisPrimary.getLabel().getCaption().getFont().setSize(9);
// xAxisPrimary.getLabel().getCaption().setColor(foreGround);
setTimeLabel();
yAxisPrimary.getScale().setMin(NumberDataElementImpl.create(0.0));
yAxisPrimary.getScale().setMax(NumberDataElementImpl.create(1.0));
yAxisPrimary.getScale().setStep(0.25);
yAxisPrimary.getLabel().getCaption().getFont().setSize(9);
yAxisPrimary.getLabel().getCaption().setColor(ColorDefinitionImpl.create(255, 255, 255));
yAxisPrimary.getTitle().getCaption().setColor(ColorDefinitionImpl.create(255, 255, 255));
setYAxisNumberFormatFromMaxY(1.0);
// yAxisPrimary.getMajorGrid().getLineAttributes().setColor(foreGround);
// for now get ready to create only one line - we have no data yet.
// we will add more lines as we need them
// handle null
if(!dataSeriesMap.containsKey(defaultKey)) {
DataSeries series = new DataSeries(defaultKey, seriesCount);
dataSeriesMap.put(defaultKey, series);
}
return chartWithAxes;
} // createSimpleLineChart
private void setYAxisNumberFormatFromMaxY(double maxY) {
JavaNumberFormatSpecifier jnfs = null;
if(maxY < 0.001)
jnfs = JavaNumberFormatSpecifierImpl.create("0.####E0");
else
jnfs = JavaNumberFormatSpecifierImpl.create("0.#####");
yAxisPrimary.setFormatSpecifier(jnfs);
}
void setTimeLabel() {
String val = "";
if (control != null) {
final ISimulation sim = control.simulation;
if (sim != null) {
final long timeDelta = sim.getScenario().getSequencer()
.getTimeDelta();
if (timeDelta < STEMTime.Units.MINUTE.getMilliseconds()) {
val = "secs";
xAxisPrimary.getTitle().setVisible(true);
xAxisPrimary.getTitle().getCaption().setValue(val);
return;
} else if (timeDelta < STEMTime.Units.HOUR.getMilliseconds()) {
val = "mins";
xAxisPrimary.getTitle().setVisible(true);
xAxisPrimary.getTitle().getCaption().setValue(val);
return;
} else if (timeDelta < STEMTime.Units.DAY.getMilliseconds()) {
val = "hrs";
xAxisPrimary.getTitle().setVisible(true);
xAxisPrimary.getTitle().getCaption().setValue(val);
return;
} else if (timeDelta <= STEMTime.Units.WEEK.getMilliseconds()) {
val = "days";
xAxisPrimary.getTitle().setVisible(true);
xAxisPrimary.getTitle().getCaption().setValue(val);
return;
} else if (timeDelta < 4 * STEMTime.Units.WEEK
.getMilliseconds()) {
val = "weeks";
xAxisPrimary.getTitle().setVisible(true);
xAxisPrimary.getTitle().getCaption().setValue(val);
return;
} else if (timeDelta < STEMTime.Units.YEAR.getMilliseconds()) {
val = "months";
xAxisPrimary.getTitle().setVisible(true);
xAxisPrimary.getTitle().getCaption().setValue(val);
return;
} else {
val = "years";
xAxisPrimary.getTitle().setVisible(true);
xAxisPrimary.getTitle().getCaption().setValue(val);
return;
}
} // sim ! null
}// control ! null
} // setTimeLabel()
/**
* @param rvhp
* @param propertiesToPlotList
*/
public synchronized void setDataSourceAndRedraw(
final RelativeValueHistoryProviderAdapter rvhp,
final List<IItemPropertyDescriptor> propertiesToPlotList) {
if(rvhp!=this.rvhp) {
this.rvhp= rvhp;
removeDataSeries();
this.propertiesToPlot.clear();
this.propertiesToPlot = addProperties(propertiesToPlotList);
}
updateContextMenu(this);
draw();
} // setDataSourceAndRedraw
/**
* Filters a list of ItemPropertyDescriptors to show preferred properties if
* at least one exists. If not, just returns the entire list.
*
* @return filteredList<ItemPropertyDescriptor>
*/
protected List<ItemPropertyDescriptor> filterPreferredProperties(
final List<ItemPropertyDescriptor> fullList) {
final List<ItemPropertyDescriptor> propertyList = new ArrayList<ItemPropertyDescriptor>();
// Are any of the labels in the preference set?
final Set<String> prefSet = VisualizationPreferencePage
.getPreferenceSet();
for (int i = 0; i < fullList.size(); i++) {
final ItemPropertyDescriptor property = fullList.get(i);
if (prefSet.contains(property.getDisplayName(property))) {
propertyList.add(property);
}
}
// if we didn't find any in the preferences then just add them all
if (propertyList.size() == 0) {
propertyList.addAll(fullList);
}
return propertyList;
}
/**
* Filters a list of ItemPropertyDescriptors to show preferred properties if
* at least one exists. If not, just returns the entire list.
*
* @return filteredList<ItemPropertyDescriptor>
*/
protected Map<ItemPropertyDescriptor,Boolean> addProperties(final List<IItemPropertyDescriptor> propertiesToPlotList) {
for (int i = 0; i < propertiesToPlotList.size(); i++) {
final ItemPropertyDescriptor property = (ItemPropertyDescriptor)propertiesToPlotList.get(i);
if(!propertiesToPlot.containsKey(property)) {
boolean visible = true;
if(defaultHideSet.contains(property.getDisplayName(property))) visible = false;
Boolean bObj = Boolean.valueOf(visible); //default visibility
this.propertiesToPlot.put(property,bObj);
}
}
return this.propertiesToPlot;
}
/**
* Disposes the Color objects
*/
@Override
public void dispose() {
super.dispose();
}
protected void resetData() {
clearData();
// for now get ready to create only one line - we have no data yet.
// we will add more lines as we need them
if(!dataSeriesMap.containsKey(defaultKey)) {
DataSeries series = new DataSeries(defaultKey, seriesCount);
dataSeriesMap.put(defaultKey, series);
}
// handle null
cycleNumbers.add(Integer.valueOf(0));
}
/**
* clearsAllData
*/
private void clearData() {
Iterator<String> iter = dataSeriesMap.keySet().iterator();
while((iter!=null)&&(iter.hasNext())) {
String key = iter.next();
DataSeries series = dataSeriesMap.get(key);
series.relativeValues.clear();
series.addValue(new Double(0.0));
xAxisPrimary.getScale().setMin(NumberDataElementImpl.create(0.0));
maxTimeAxisValue = DEFAULT_AUTOAXIS_THRESHOLD;
//xAxisPrimary.getScale().setMax(NumberDataElementImpl.create(TICK_TRIGGER*DEFAULT_AUTOAXIS_THRESHOLD));
//int numTicks = TICK_TRIGGER*DEFAULT_AUTOAXIS_THRESHOLD/MIN_TICKS;
xAxisPrimary.getScale().setStep(1);
}
cycleNumbers.clear();
}
/**
* removeData is called when switching between decorators. For example, when switching from display of a disease to dispaly of a population,
* the properties are different and the lineSeries, propetries, and axis labels must be removed. SEE also: DataSeries.removeLineSeries()
*/
private void removeDataSeries() {
if(displayableProperties!=null) displayableProperties.clear();
Iterator<ItemPropertyDescriptor> iter = propertiesToPlot.keySet().iterator();
while((iter!=null)&&(iter.hasNext())) {
ItemPropertyDescriptor propertyToRemove = iter.next();
String key = propertyToRemove.getDisplayName(propertyToRemove);
DataSeries series = dataSeriesMap.get(key);
//series.hide();
if(series != null) {
series.removeLineSeries(key);
//series.lineSeries.setVisible(false);
dataSeriesMap.remove(key);
series = null;
} // if series not null
}
propertiesToPlot.clear();
}//removeDataSeries
/**
* toggle the scale from logarithmic to linear
*/
void toggleAxisScale() {
if (useLinearTimeScale) { //Switch to logarithmic scale
logTimeAction.setChecked(true);
linearTimeAction.setChecked(false);
//Just using the following axis type, to move to log scale, didn't work
//yAxisPrimary.setType(AxisType.LOGARITHMIC_LITERAL);
yAxisPrimary.getScale().setMin(null);
yAxisPrimary.getScale().setMax(null);
yAxisPrimary.getScale().unsetStep();
yAxisPrimary.getScale().unsetStepNumber();
}
else { //Switch to linear scale
logTimeAction.setChecked(false);
linearTimeAction.setChecked(true);
//yAxisPrimary.setType(AxisType.LINEAR_LITERAL);
yAxisPrimary.getScale().setMin(NumberDataElementImpl.create(0.0));
yAxisPrimary.getScale().setMax(NumberDataElementImpl.create(1.0));
yAxisPrimary.getScale().setStep(0.25);
}
useLinearTimeScale = !useLinearTimeScale;
this.draw();
}
/**
* toggle the scale from logarithmic to linear
*/
void toggleLegend() {
if (showLegend) { //Switch to hide
viewLegend.setChecked(false);
hideLegend.setChecked(true);
legend.setVisible(false);
}
else { //Switch to view
viewLegend.setChecked(true);
hideLegend.setChecked(false);
legend.setVisible(true);
}
showLegend = !showLegend;
this.draw();
}
/**
* switch to linear plot
*
*/
protected class LinearScaleAction extends Action {
public LinearScaleAction()
{
super(Messages.getString("ContextMenu.LinearTimeScale"), IAction.AS_CHECK_BOX);
setChecked(useLinearTimeScale);
}
/**
* @see org.eclipse.jface.action.Action#getText()
*/
@Override
public String getText() {
return Messages.getString("ContextMenu.LinearTimeScale");
}
/**
* @see org.eclipse.jface.action.Action#run()
*/
@Override
public void run() {
if (useLinearTimeScale) {
setChecked(true);
//Nothing to do. It's already linear-time.
}
else {
toggleAxisScale();
}
}
} //LinearScaleAction
/**
* switch to semi-log plot (log scale on y axis)
*
*/
class LogScaleAction extends Action {
public LogScaleAction()
{
super(Messages.getString("ContextMenu.LogTimeScale"), IAction.AS_CHECK_BOX);
setChecked(!useLinearTimeScale);
}
/**
* @see org.eclipse.jface.action.Action#getText()
*/
@Override
public String getText() {
return Messages.getString("ContextMenu.LogTimeScale");
}
/**
* @see org.eclipse.jface.action.Action#run()
*/
@Override
public void run() {
if (!useLinearTimeScale) {
setChecked(true);
//Nothing to do. It's already log-time.
}
else {
toggleAxisScale();
}
}
}//LogScaleAction
/**
* Action to show the legend
*/
protected class LegendViewAction extends Action {
public LegendViewAction()
{
super(Messages.getString("ContextMenu.ShowLegend"), IAction.AS_CHECK_BOX);
setChecked(showLegend);
}
/**
* @see org.eclipse.jface.action.Action#getText()
*/
@Override
public String getText() {
return Messages.getString("ContextMenu.ShowLegend");
}
/**
* @see org.eclipse.jface.action.Action#run()
*/
@Override
public void run() {
if (showLegend) {
setChecked(true);
//Nothing to do. It's already linear-time.
}
else {
toggleLegend();
}
}
} //LegendViewAction
/**
* Action to hide the legend
*/
class LegendHideAction extends Action {
public LegendHideAction()
{
super(Messages.getString("ContextMenu.HideLegend"), IAction.AS_CHECK_BOX);
setChecked(!showLegend);
}
/**
* @see org.eclipse.jface.action.Action#getText()
*/
@Override
public String getText() {
return Messages.getString("ContextMenu.HideLegend");
}
/**
* @see org.eclipse.jface.action.Action#run()
*/
@Override
public void run() {
if (!showLegend) {
setChecked(true);
//Nothing to do. It's already log-time.
}
else {
toggleLegend();
}
}
}//LegendHideAction
/**
* DisplayableProperty
*
*/
protected class DisplayableProperty extends Action
{
ItemPropertyDescriptor property = null;
public DisplayableProperty(ItemPropertyDescriptor property)
{
super(property.getDisplayName(property), IAction.AS_CHECK_BOX);
this.property = property;
if(propertiesToPlot.containsKey(property)) {
setChecked(propertiesToPlot.get(property).booleanValue());
} else {
propertiesToPlot.put(property,new Boolean(true));
setChecked(propertiesToPlot.get(property).booleanValue());
}
}
/**
* @see org.eclipse.jface.action.Action#getText()
*/
@Override
public String getText() {
return property.getDisplayName(property);
}
/**
* Toggle the state
* @see org.eclipse.jface.action.Action#run()
*/
@Override
public void run() {
boolean state = !propertiesToPlot.get(property).booleanValue();
propertiesToPlot.put(property, Boolean.valueOf(state));
setChecked(state);
draw();
}
public ItemPropertyDescriptor getProperty() {
return property;
}
}// DisplayableProperty
/**
* Action to show the legend
*/
protected class ClearAction extends Action {
public ClearAction()
{
super(Messages.getString("ContextMenu.Clear"), IAction.AS_PUSH_BUTTON);
}
/**
* @see org.eclipse.jface.action.Action#getText()
*/
@Override
public String getText() {
return Messages.getString("ContextMenu.Clear");
}
/**
* @see org.eclipse.jface.action.Action#run()
*/
@SuppressWarnings("boxing")
@Override
public void run() {
for(ItemPropertyDescriptor ipd:propertiesToPlot.keySet()) {
boolean state = !propertiesToPlot.get(ipd).booleanValue();
propertiesToPlot.put(ipd, false);
for(DisplayableProperty dp:displayableProperties)
if(dp.getProperty().equals(ipd)) {dp.setChecked(false);break;}
draw();
}
}
} //ClearAction
protected class DataSeries
{
public String propertyName = "";
public List<Double> relativeValues = new ArrayList<Double>();
public LineSeries lineSeries = null;
private boolean visible = true;
private SeriesDefinition sdY = null;
private int seriesIndex = 0;
public boolean isVisible() {
return visible;
}
/**
*
* @param propertyName
* @param index
*/
public DataSeries(String propertyName, int index) {
this.propertyName = propertyName;
this.seriesIndex = index;
seriesCount ++;
relativeValues = new ArrayList<Double>();
relativeValues.add(new Double(0.0));
addLineSeries(propertyName);
}
public void addValue(Double val) {
if(relativeValues==null) relativeValues = new ArrayList<Double>();
relativeValues.add(val);
}
/**
* @param propertyName
*
*/
@SuppressWarnings("cast")
public void addLineSeries(final String propertyName) {
final NumberDataSet orthoValues = NumberDataSetImpl
.create(relativeValues);
if(lineSeries == null) lineSeries = (ScatterSeries) ScatterSeriesImpl.create();
lineSeries.setDataSet(orthoValues);
lineSeries.getLineAttributes().setVisible(true);
// Assign the line color
// based on selected property. Default is Blue
setColorDefs(propertyName);
// replaces deprecated code: lineSeries.getMarker().setVisible(false);
if (!lineSeries.getMarkers().isEmpty()) {
Marker marker = (Marker)lineSeries.getMarkers().get(0);
marker.setVisible(false);
}
// the series def
sdY = SeriesDefinitionImpl.create();
//sdY.getSeriesPalette().update(-2);
sdY.getSeries().add(lineSeries);
yAxisPrimary.getSeriesDefinitions().add(sdY);
setTimeLabel();
return;
}
/**
*
* @param propertyName
*/
public void removeLineSeries(final String propertyName) {
//visible = false;
seriesIndex = 0;
// ?? relativeValues.clear();
if(lineSeries!=null) lineSeries.getLineAttributes().setVisible(false);
if(yAxisPrimary !=null) yAxisPrimary.getSeriesDefinitions().remove(sdY);
if(sdY != null) sdY.getSeries().remove(lineSeries);
lineSeries = null;
sdY = null;
return;
}
/**
* in response to user action temporarily remove the line series from the graph
*/
public void hide() {
lineSeries.setVisible(false);
visible = false;
}// hide
/**
* in response to user action add back the line series to the graph
*/
public void show() {
lineSeries.setVisible(true);
visible = true;
}// show
/**
* Sets the colors for a n array of LineSeries given the property to Plot
* for each. Try to set color from the preferences (if specified for that
* property) otherwise sets line color to blue.
* @param propertyName
*
*/
public void setColorDefs(String propertyName) {
// the default line color
// if possible get color from preferences
final Map<String, STEMColor> colorMap = VisualizationPreferencePage.getColorMapping();
// pick a default color
int colorIndex = seriesIndex % colorDefault.length;
ColorDefinition color = colorDefault[colorIndex];
// Check for a prefix match
for (String key : colorMap.keySet()) {
if((propertyName.startsWith(key))&&(key.length()>=1)) {
// might be right
String tail = propertyName.substring(key.length(), propertyName.length());
if((tail==null)||(tail.length()==0)) {
final STEMColor c = colorMap.get(key);
color = ColorDefinitionImpl.create((int)(c.getRed() * 255.0), (int)(c.getGreen() * 255.0), (int)(c.getBlue() * 255.0));
break;
}
boolean hit = true;
for (char c : tail.toCharArray()) {
if (!Character.isDigit(c)) {
hit = false;
break;
}
}// all characters in tail
if(hit){
final STEMColor c = colorMap.get(key);
color = ColorDefinitionImpl.create((int)(c.getRed() * 255.0), (int)(c.getGreen() * 255.0), (int)(c.getBlue() * 255.0));
break;
}
}
}
// if (colorMap.containsKey(propertyName)) {
// final Color c = colorMap.get(propertyName);
// color = ColorDefinitionImpl.create(c.getRed(), c.getGreen(), c.getBlue());
// }
this.lineSeries.setSeriesIdentifier(propertyName);
this.lineSeries.getLineAttributes().setColor(color);
return;
}// getColorDef
/**
* length of the series
* @return number of data points
*/
public int getDataSize() {
return relativeValues.size();
}
// Accessors
public String getPropertyName() {
return propertyName;
}
public void setPropertyName(String propertyName) {
this.propertyName = propertyName;
}
public List<Double> getRelativeValues() {
return relativeValues;
}
public void setRelativeValues(List<Double> relativeValues) {
this.relativeValues = relativeValues;
}
public LineSeries getLineSeries() {
return lineSeries;
}
public void setLineSeries(LineSeries lineSeries) {
this.lineSeries = lineSeries;
}
}// DataSeries
} // TimeSeriesCanvas