/***************************************************************************** * Limpet - the Lightweight InforMation ProcEssing Toolkit * http://limpet.info * * (C) 2015-2016, Deep Blue C Technologies Ltd * * This library is free software; you can redistribute it and/or * modify it under the terms of the Eclipse Public License v1.0 * (http://www.eclipse.org/legal/epl-v10.html) * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *****************************************************************************/ package info.limpet.ui.xy_plot; import info.limpet.ICollection; import info.limpet.IQuantityCollection; import info.limpet.IStoreItem; import info.limpet.ITemporalQuantityCollection; import info.limpet.data.impl.samples.TemporalLocation; import info.limpet.data.operations.CollectionComplianceTests; import info.limpet.data.operations.CollectionComplianceTests.TimePeriod; import info.limpet.ui.PlottingHelpers; import info.limpet.ui.core_view.CoreAnalysisView; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.Iterator; import java.util.List; import javax.measure.Measurable; import javax.measure.quantity.Quantity; import javax.measure.unit.Unit; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Composite; import org.swtchart.Chart; import org.swtchart.IAxis; import org.swtchart.IAxis.Position; import org.swtchart.ILineSeries; import org.swtchart.ILineSeries.PlotSymbolType; import org.swtchart.ISeries; import org.swtchart.ISeries.SeriesType; import org.swtchart.ext.InteractiveChart; /** * display analysis overview of selection * * @author ian * */ public class XyPlotView extends CoreAnalysisView { private static final int MAX_SIZE = 10000; /** * The ID of the view as specified by the extension. */ public static final String ID = "info.limpet.ui.XyPlotView"; private final CollectionComplianceTests aTests = new CollectionComplianceTests(); private Chart chart; public XyPlotView() { super(ID, "XY plot view"); } /** * This is a callback that will allow us to create the viewer and initialize it. */ public void createPartControl(Composite parent) { makeActions(); contributeToActionBars(); // create a chart chart = new InteractiveChart(parent, SWT.NONE); // set titles chart.getAxisSet().getXAxis(0).getTitle().setText("Value"); chart.getAxisSet().getYAxis(0).getTitle().setText("Value"); chart.getTitle().setVisible(false); // adjust the axis range chart.getAxisSet().adjustRange(); // register as selection listener setupListener(); } @Override public void display(List<IStoreItem> res) { if (res.size() == 0) { chart.setVisible(false); } else { // they're all the same type - check the first one Iterator<IStoreItem> iter = res.iterator(); ICollection first = (ICollection) iter.next(); // sort out what type of data this is. if (first.isQuantity()) { if (aTests.allTemporalOrSingleton(res)) { showTemporalQuantity(res); } else { showQuantity(res); } chart.setVisible(true); } else { // exception - show locations if (aTests.allLocation(res)) { showLocations(res); chart.setVisible(true); } else { chart.setVisible(false); } } } } @SuppressWarnings("unchecked") private void showQuantity(List<IStoreItem> res) { Iterator<IStoreItem> iter = res.iterator(); clearGraph(); Unit<Quantity> existingUnits = null; // get the longest collection length (used for plotting singletons) int longestColl = aTests.getLongestCollectionLength(res); while (iter.hasNext()) { ICollection coll = (ICollection) iter.next(); if (coll.isQuantity() && coll.getValuesCount() >= 1 && coll.getValuesCount() < MAX_SIZE) { IQuantityCollection<Quantity> thisQ = (IQuantityCollection<Quantity>) coll; final Unit<Quantity> theseUnits = thisQ.getUnits(); String seriesName = seriesNameFor(thisQ, theseUnits); // do we need to create this series ISeries match = chart.getSeriesSet().getSeries(seriesName); if (match != null) { continue; } ILineSeries newSeries = (ILineSeries) chart.getSeriesSet().createSeries(SeriesType.LINE, seriesName); final PlotSymbolType theSym; // if it's a singleton, show the symbol // markers if (thisQ.getValuesCount() > 100 || thisQ.getValuesCount() == 1) { theSym = PlotSymbolType.NONE; } else { theSym = PlotSymbolType.CIRCLE; } newSeries.setSymbolType(theSym); newSeries.setLineColor(PlottingHelpers.colorFor(seriesName)); newSeries.setSymbolColor(PlottingHelpers.colorFor(seriesName)); final double[] yData; if (coll.getValuesCount() == 1) { // singleton = insert it's value at every point yData = new double[longestColl]; Measurable<Quantity> thisValue = thisQ.getValues().iterator().next(); double dVal = thisValue.doubleValue(thisQ.getUnits()); for (int i = 0; i < longestColl; i++) { yData[i] = dVal; } } else { yData = new double[thisQ.getValuesCount()]; Iterator<?> values = thisQ.getValues().iterator(); int ctr = 0; while (values.hasNext()) { Measurable<Quantity> tQ = (Measurable<Quantity>) values.next(); yData[ctr++] = tQ.doubleValue(thisQ.getUnits()); } } // ok, do we have existing data? if (existingUnits != null && !existingUnits.equals(theseUnits)) { // create second Y axis int axisId = chart.getAxisSet().createYAxis(); // set the properties of second Y axis IAxis yAxis2 = chart.getAxisSet().getYAxis(axisId); yAxis2.getTitle().setText(theseUnits.toString()); yAxis2.setPosition(Position.Secondary); newSeries.setYAxisId(axisId); } else { chart.getAxisSet().getYAxes()[0].getTitle().setText( theseUnits.toString()); existingUnits = theseUnits; } // newSeries.setXSeries(xData); newSeries.setYSeries(yData); chart.getAxisSet().getXAxis(0).getTitle().setText("Count"); // adjust the axis range chart.getAxisSet().adjustRange(); IAxis xAxis = chart.getAxisSet().getXAxis(0); xAxis.enableCategory(false); chart.redraw(); } } } private String seriesNameFor(IQuantityCollection<Quantity> thisQ, final Unit<Quantity> theseUnits) { String seriesName = thisQ.getName() + " (" + theseUnits + ")"; return seriesName; } @SuppressWarnings("unchecked") private void showTemporalQuantity(List<IStoreItem> res) { Iterator<IStoreItem> iter = res.iterator(); clearGraph(); Unit<Quantity> existingUnits = null; // get the outer time period (used for plotting singletons) List<ICollection> safeColl = new ArrayList<ICollection>(); safeColl.addAll((Collection<? extends ICollection>) res); TimePeriod outerPeriod = aTests.getBoundingTime(safeColl); while (iter.hasNext()) { ICollection coll = (ICollection) iter.next(); if (coll.isQuantity() && coll.getValuesCount() >= 1 && coll.getValuesCount() < MAX_SIZE) { IQuantityCollection<Quantity> thisQ = (IQuantityCollection<Quantity>) coll; final Unit<Quantity> theseUnits = thisQ.getUnits(); String seriesName = seriesNameFor(thisQ, theseUnits); // do we need to create this series ISeries match = chart.getSeriesSet().getSeries(seriesName); if (match != null) { continue; } ILineSeries newSeries = (ILineSeries) chart.getSeriesSet().createSeries(SeriesType.LINE, seriesName); newSeries.setLineColor(PlottingHelpers.colorFor(seriesName)); final Date[] xTimeData; final double[] yData; if (coll.isTemporal()) { // must be temporal ITemporalQuantityCollection<Quantity> thisTQ = (ITemporalQuantityCollection<Quantity>) coll; xTimeData = new Date[thisQ.getValuesCount()]; yData = new double[thisQ.getValuesCount()]; Iterator<?> values = thisTQ.getValues().iterator(); Iterator<Long> times = thisTQ.getTimes().iterator(); int ctr = 0; while (values.hasNext()) { Measurable<Quantity> tQ = (Measurable<Quantity>) values.next(); long t = times.next(); xTimeData[ctr] = new Date(t); yData[ctr++] = tQ.doubleValue(thisQ.getUnits()); } } else { // non temporal, include it as a marker line // must be non temporal xTimeData = new Date[2]; yData = new double[2]; // get the singleton value Measurable<Quantity> theValue = thisQ.getValues().iterator().next(); // create the marker line xTimeData[0] = new Date(outerPeriod.getStartTime()); yData[0] = theValue.doubleValue(thisQ.getUnits()); xTimeData[1] = new Date(outerPeriod.getEndTime()); yData[1] = theValue.doubleValue(thisQ.getUnits()); } newSeries.setXDateSeries(xTimeData); newSeries.setYSeries(yData); // ok, do we have existing data, in different units? if (existingUnits != null && !existingUnits.equals(theseUnits)) { // create second Y axis int axisId = chart.getAxisSet().createYAxis(); // set the properties of second Y axis IAxis yAxis2 = chart.getAxisSet().getYAxis(axisId); yAxis2.getTitle().setText(theseUnits.toString()); yAxis2.setPosition(Position.Secondary); newSeries.setYAxisId(axisId); } else { chart.getAxisSet().getYAxes()[0].getTitle().setText( theseUnits.toString()); existingUnits = theseUnits; } // if it's a monster line, or just a singleton value, we won't plot // markers if (thisQ.getValuesCount() > 90 || thisQ.getValuesCount() == 1) { newSeries.setSymbolType(PlotSymbolType.NONE); } else { newSeries.setSymbolType(PlotSymbolType.CROSS); } chart.getAxisSet().getXAxis(0).getTitle().setText("Time"); // adjust the axis range chart.getAxisSet().adjustRange(); IAxis xAxis = chart.getAxisSet().getXAxis(0); xAxis.enableCategory(false); chart.redraw(); } } } private void clearGraph() { // clear the graph ISeries[] series = chart.getSeriesSet().getSeries(); for (int i = 0; i < series.length; i++) { ISeries iSeries = series[i]; chart.getSeriesSet().deleteSeries(iSeries.getId()); // and clear any series IAxis[] yA = chart.getAxisSet().getYAxes(); for (int j = 1; j < yA.length; j++) { IAxis iAxis = yA[j]; chart.getAxisSet().deleteYAxis(iAxis.getId()); } } } private void showLocations(List<IStoreItem> res) { Iterator<IStoreItem> iter = res.iterator(); // clear the graph ISeries[] series = chart.getSeriesSet().getSeries(); for (int i = 0; i < series.length; i++) { ISeries iSeries = series[i]; chart.getSeriesSet().deleteSeries(iSeries.getId()); } while (iter.hasNext()) { ICollection coll = (ICollection) iter.next(); if (!coll.isQuantity() && coll.getValuesCount() >= 1 && coll.getValuesCount() < MAX_SIZE) { final List<Point2D> values; if (coll.isTemporal()) { TemporalLocation loc = (TemporalLocation) coll; values = loc.getValues(); } else { info.limpet.data.impl.samples.StockTypes.NonTemporal.Location loc = (info.limpet.data.impl.samples.StockTypes.NonTemporal.Location) coll; values = loc.getValues(); } String seriesName = coll.getName(); ILineSeries newSeries = (ILineSeries) chart.getSeriesSet().createSeries(SeriesType.LINE, seriesName); newSeries.setSymbolType(PlotSymbolType.NONE); newSeries.setLineColor(PlottingHelpers.colorFor(seriesName)); double[] xData = new double[values.size()]; double[] yData = new double[values.size()]; Iterator<Point2D> vIter = values.iterator(); int ctr = 0; while (vIter.hasNext()) { Point2D geom = vIter.next(); xData[ctr] = geom.getX(); yData[ctr++] = geom.getY(); } // clear the axis labels chart.getAxisSet().getXAxis(0).getTitle().setText(""); chart.getAxisSet().getYAxis(0).getTitle().setText(""); newSeries.setXSeries(xData); newSeries.setYSeries(yData); newSeries.setSymbolType(PlotSymbolType.CROSS); // adjust the axis range chart.getAxisSet().adjustRange(); chart.redraw(); } } } @Override public void setFocus() { chart.setFocus(); } @Override protected boolean appliesToMe(List<IStoreItem> res, CollectionComplianceTests tests) { return tests.allCollections(res) && tests.allQuantity(res) || tests.allNonQuantity(res); } @Override protected String getTextForClipboard() { return "Pending"; } @SuppressWarnings("unchecked") @Override protected void datasetDataChanged(IStoreItem subject) { final String name; ICollection coll = (ICollection) subject; if (coll.isQuantity()) { IQuantityCollection<Quantity> cq = (IQuantityCollection<Quantity>) coll; Unit<Quantity> units = cq.getUnits(); name = seriesNameFor(cq, units); } else { name = coll.getName(); } ISeries match = chart.getSeriesSet().getSeries(name); if (match != null) { chart.getSeriesSet().deleteSeries(name); } else { // clear all of the series ISeries[] allSeries = chart.getSeriesSet().getSeries(); for (int i = 0; i < allSeries.length; i++) { ISeries iSeries = allSeries[i]; chart.getSeriesSet().deleteSeries(iSeries.getId()); } } } }