/* * Copyright 2003-2004, Franz-Josef Elmer, All rights reserved * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This program 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. See the * GNU Lesser General Public License for more details * (http://www.gnu.org/copyleft/lesser.html). * * You should have received a copy of the GNU Lesser General Public License * along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package jcckit.plot; import java.util.Vector; import jcckit.data.DataCurve; import jcckit.data.DataEvent; import jcckit.data.DataListener; import jcckit.data.DataPlot; import jcckit.data.DataPoint; import jcckit.graphic.ClippingShape; import jcckit.graphic.GraphPoint; import jcckit.graphic.GraphicalComposite; import jcckit.graphic.GraphicalElement; import jcckit.transformation.Transformation; import jcckit.util.ConfigParameters; import jcckit.util.Factory; /** * A plot is determined by a {@link CoordinateSystem}, {@link Curve Curves}, * an optional annotation layer and an optional {@link Legend}. When rendered * these components are draw in this order. *

* Registrated {@link PlotListener PlotListeners} will be informed * when the plot changes. *

* A {@link DataPlot} can be connected with a Plot instance. * This is done with the method {@link #connect connect()} which registrates * this Plot instance as * a {@link DataListener} at the connected DataPlot. * After an received {@link DataEvent DataEvents} has been handled * the registrated PlotListeners will receive a * {@link PlotEvent} of the type {@link PlotEventType#DATA_PLOT_CHANGED}. * * @author Franz-Josef Elmer */ public class Plot implements DataListener { /** Configuration parameter key. */ public static final String COORDINATE_SYSTEM_KEY = "coordinateSystem", CURVE_FACTORY_KEY = "curveFactory", LEGEND_VISIBLE_KEY = "legendVisible", LEGEND_KEY = "legend", INITIAL_HINT_FOR_NEXT_CURVE_KEY = "initialHintForNextCurve"; private final Vector _plotListeners = new Vector(); private DataPlot _dataPlot; private final CurveFactory _curveFactory; private final Vector _curves = new Vector(); private final Vector _nextCurveHints = new Vector(); private final Hint _initialHintForNextCurve; private final Legend _legend; private final boolean _legendVisibility; private GraphicalElement _coordinateSystemView; private ClippingShape _clippingShape; private Transformation _transformation; private GraphicalElement _annotation; private GraphicalComposite _legendView = new GraphicalComposite(null); /** * Creates an instance from the specified configuration parameters. * * * * * * * * * * * * * * * * * * *
Key & Default ValueTypeMandatoryDescription
coordinateSystem = {@link CartesianCoordinateSystem}ConfigParametersnoDefinition of the {@link CoordinateSystem}.
curveFactory = {@link SimpleCurveFactory}ConfigParametersnoDefinition of the {@link CurveFactory}.
initialHintForNextCurve = nullConfigParametersnoDefinition of the initial {@link Hint} which is needed by some * {@link SymbolFactory SymbolFactories} like {@link BarFactory}. *
legend = default values of {@link Legend}ConfigParametersnoConfiguration parameters of a {@link Legend}.
legendVisible = truebooleannoIf true the {@link Legend} will be created.
*/ public Plot(ConfigParameters config) { CoordinateSystem coordinateSystem = (CoordinateSystem) Factory.create( config.getNode(COORDINATE_SYSTEM_KEY), CartesianCoordinateSystem.class.getName()); setCoordinateSystem(coordinateSystem); _curveFactory = (CurveFactory) Factory.create( config.getNode(CURVE_FACTORY_KEY), SimpleCurveFactory.class.getName()); _initialHintForNextCurve = (Hint) Factory.createOrGet( config.getNode(INITIAL_HINT_FOR_NEXT_CURVE_KEY), null); _legend = new Legend(config.getNode(LEGEND_KEY)); _legendVisibility = config.getBoolean(LEGEND_VISIBLE_KEY, true); } /** * Sets the coordinate system. All curves will be regenerated and a * {@link PlotEvent} of type {@link PlotEventType#COODINATE_SYSTEM_CHANGED} * will be fired. * * @param coordinateSystem New coordinate system. */ public void setCoordinateSystem(CoordinateSystem coordinateSystem) { _coordinateSystemView = coordinateSystem.getView(); _clippingShape = coordinateSystem.getClippingShape(); _transformation = coordinateSystem.getTransformation(); if (_dataPlot != null) { generateCurves(_dataPlot); } notifyListeners( new PlotEvent(this, PlotEventType.COODINATE_SYSTEM_CHANGED, null)); } /** * Adds the specified {@link PlotListener}. Does nothing if * already added. */ public void addPlotListener(PlotListener listener) { if (!_plotListeners.contains(listener)) { _plotListeners.addElement(listener); } } /** * Removes the specfied {@link PlotListener}. Does nothing if * already removed. */ public void removePlotListener(PlotListener listener) { _plotListeners.removeElement(listener); } /** * Sends all registrated {@link PlotListener PlotListeners} * the specified event. */ protected void notifyListeners(PlotEvent event) { for (int i = 0, n = _plotListeners.size(); i < n; i++) { ((PlotListener) _plotListeners.elementAt(i)).plotChanged(event); } } /** * Connect the specified {@link DataPlot} with this instance. *

* If this Plot instance is already connected with a * DataPlot the connection will be released and a * {@link PlotEvent} of the type {@link PlotEventType#DATA_PLOT_DISCONNECTED} * will be sent to all registrated {@link PlotListener PlotListeners}. *

* It registers itself at dataPlot and * all its {@link DataCurve DataCurves}. *

* Finally all curves will be generated and a PlotEvent * of the type {@link PlotEventType#DATA_PLOT_CONNECTED} will be transmitted. * @param dataPlot Data to be connected with this plot instance. * Can be null in order to disconnect this instance from * any DataPlot. */ public void connect(DataPlot dataPlot) { if (_dataPlot != null) { _dataPlot.removeDataListener(this); notifyListeners(new PlotEvent(this, PlotEventType.DATA_PLOT_DISCONNECTED, _dataPlot)); } _dataPlot = dataPlot; if (_dataPlot != null) { _dataPlot.addDataListener(this); generateCurves(_dataPlot); notifyListeners(new PlotEvent(this, PlotEventType.DATA_PLOT_CONNECTED, _dataPlot)); } } /** * Transforms a point from device-independent coordinates into * data coordinates. * @param point Point in device-independent coordinates. * @return transform point. */ public DataPoint transform(GraphPoint point) { return _transformation.transformToData(point); } /** * Creates a graphical representation of the complete plot. * @return GraphicalComposite containing the views of the * coordinate system, the curves, and optionally the legend (in this order). */ public GraphicalComposite getCompletePlot() { GraphicalComposite result = new GraphicalComposite(null); result.addElement(_coordinateSystemView); GraphicalElement[] curves = getCurves(); for (int i = 0; i < curves.length; i++) { result.addElement(curves[i]); } if (_annotation != null) { result.addElement(_annotation); } if (_legendVisibility) { result.addElement(getLegend()); } return result; } /** Returns the view of the coordinate system. */ public GraphicalElement getCoordinateSystem() { return _coordinateSystemView; } /** Returns the graphical representations of all curves. */ public GraphicalElement[] getCurves() { synchronized (_curves) { GraphicalElement[] curves = new GraphicalElement[_curves.size()]; for (int i = 0; i < curves.length; i++) { curves[i] = ((Curve) _curves.elementAt(i)).getView(); } return curves; } } /** * Returns the annotation layer. * @return null if no annotation layer. */ public GraphicalElement getAnnotation() { return _annotation; } /** * Sets the annotation layer. * @param annotation Any kind of graphics which will be drawn on the * top of the curves but may be covered by the legend. * Can be null. */ public void setAnnotation(GraphicalElement annotation) { _annotation = annotation; } /** Returns true if the legend is visible. */ public boolean isLegendVisible() { return _legendVisibility; } /** Returns the graphical representations of the legend. */ public GraphicalElement getLegend() { return _legendView; } /** * Handles the received {@link DataEvent} and notifies * {@link PlotListener PlotListeners} by an event of the type * {@link PlotEventType#DATA_CURVE_CHANGED} or * {@link PlotEventType#DATA_PLOT_CHANGED}. The following table shows what * this method does: * * * * * * * *
Source of eventAll hints for the next curve are null?ActionType of sent {@link PlotEvent}
{@link DataCurve}YesRecreate changed curve. * DATA_CURVE_CHANGED
{@link DataCurve}NoRecreate changed curve * and all curves with large curve index. * DATA_PLOT_CHANGED
{@link DataPlot}-Recreate all curves * and {@link Legend} view. * DATA_PLOT_CHANGED
*/ public void dataChanged(DataEvent event) { Integer index = new Integer(0); PlotEventType type = PlotEventType.DATA_PLOT_CHANGED; synchronized (_curves) { int numberOfCurves = _curves.size(); if (event.getContainer() instanceof DataCurve && numberOfCurves == _dataPlot.getNumberOfElements()) { DataCurve curve = (DataCurve) event.getContainer(); index = new Integer(curve.getContainer().getIndexOf(curve)); type = PlotEventType.DATA_CURVE_CHANGED; fillCurve(index.intValue(), curve); if (index.intValue() < numberOfCurves - 1) { Vector curveHints = (Vector) _nextCurveHints.elementAt(index.intValue()); for (int i = 0, n = curveHints.size(); i < n; i++) { if (curveHints.elementAt(i) != null) { type = PlotEventType.DATA_PLOT_CHANGED; for (int j = index.intValue()+1; j < numberOfCurves; j++) { fillCurve(j, (DataCurve) _dataPlot.getElement(j)); } break; } } } } else { generateCurves(_dataPlot); } } notifyListeners(new PlotEvent(Plot.this, type, index)); } /** * Generates all curves based on the specified data. * In addition the legend view is created. */ private void generateCurves(DataPlot dataPlot) { synchronized (_curves) { _legendView = new GraphicalComposite(null); _legendView.addElement(_legend.getBox()); _curves.setSize(0); _nextCurveHints.setSize(0); for (int i = 0, n = dataPlot.getNumberOfElements(); i < n; i++) { Curve curve = _curveFactory.create(i, n, _clippingShape, _legend); _curves.addElement(curve); _nextCurveHints.addElement(new Vector()); DataCurve dataCurve = (DataCurve) dataPlot.getElement(i); _legendView.addElement(curve.getLegendSymbol()); _legendView.addElement( _legend.createCurveTitle(i, n, dataCurve.getTitle())); fillCurve(i, dataCurve); } } } private void fillCurve(int curveIndex, DataCurve dataCurve) { Vector curveHints = (Vector) _nextCurveHints.elementAt(curveIndex); Curve curve = (Curve) _curves.elementAt(curveIndex); curve.removeAllPoints(); for (int i = 0, n = dataCurve.getNumberOfElements(); i < n; i++) { setHintForNextCurve(curveHints, i, curve.addPoint(_transformation.transformToGraph( (DataPoint) dataCurve.getElement(i)), getHintForNextCurve(curveIndex - 1, i))); } } private Hint getHintForNextCurve(int curveIndex, int pointIndex) { Hint result = _initialHintForNextCurve; if (curveIndex >= 0) { Vector curveHints = (Vector) _nextCurveHints.elementAt(curveIndex); result = pointIndex < curveHints.size() ? (Hint) curveHints.elementAt(pointIndex) : getHintForNextCurve(curveIndex - 1, pointIndex); } return result; } private void setHintForNextCurve(Vector curveHints, int pointIndex, Hint hint) { while (curveHints.size() <= pointIndex) { curveHints.addElement(_initialHintForNextCurve); } curveHints.setElementAt(hint, pointIndex); } }