/*
 * This code is part of Project Waterloo from King's College London
 * 
 *
 * Copyright King's College London 2012-
 *
 * @author Malcolm Lidierth, King's College London  [Contact]
 *
 * Project Waterloo 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 3 of the License, or
 * (at your option) any later version.
 *
 * Project Waterloo 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.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 *
 */
package kcl.waterloo.graphics.plots2D;

import java.awt.*;
import java.awt.geom.Area;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.Iterator;
import kcl.waterloo.defaults.Colors;
import kcl.waterloo.defaults.GJDefaults;
import kcl.waterloo.common.deploy.AbstractDeployableGraphics2D;
import kcl.waterloo.graphics.GJAbstractGraph;
import kcl.waterloo.graphics.GJGraph;
import kcl.waterloo.graphics.GJGraphContainer;
import kcl.waterloo.graphics.GJUtilities;
import kcl.waterloo.marker.GJMarker;
import kcl.waterloo.marker.ShapeUtils;
import kcl.waterloo.math.ArrayMath;
import kcl.waterloo.plotmodel2D.GJCyclicArrayList;
import kcl.waterloo.plotmodel2D.GJDataModel;
import kcl.waterloo.plotmodel2D.GJFillableInterface;
import kcl.waterloo.swing.GCFrame;
import kcl.waterloo.xml.GJEncoder;
import org.jdesktop.swingx.painter.effects.AreaEffect;
import org.jdesktop.swingx.painter.effects.GlowPathEffect;

/**
 * Abstract superclass for the individual plot types. GJAbstractPlot defines
 * methods and properties common to each of them. Some subclasses will override
 * the methods in the abstract class.
 *
 * Most plot sub-classes support multiplexed data. For these sub-classes,
 * isMultiplexible returns true and properties such as the Marker, EdgeColor,
 * Fill etc should be an array of values that will be cycled through during
 * plotting.
 *
 * 

* Required methods are defined in the GJPlotInterface interface. Among these, * subclasses must implement the following methods: *

* a public createInstance method that calls the constructor

*

* paintPlot

* * In addition, if multiplexed plots are not supported <@code isMultiplexible> * should be overridden to return <@code false>. * *

* This code is part of the Waterloo Scientific Graphics package from King's * College London

* * @author Malcolm Lidierth, King's College London [Contact] * * Revisions: 18.05.2013 Now implements GJFastPlotInterface */ public abstract class GJAbstractPlot extends GJAbstractPlotModelImpl implements GJFillableInterface, GJFastPlotInterface { /** * The parent of this plot. Null for a topPlot */ private GJPlotInterface parentPlot = null; /** * Flag to set the visibility of the plot. Set false to prevent this plot * being painted. It may be useful to set this to false while updating the * data or visual model for a plot rather than include conditional * statements in the paintPlot method. */ boolean visible = true; /** * True if this plot has been selected (by mouse or programmatically). */ boolean selected = false; /** * A SwingX area effect to apply to this plot e.g. a shadow. */ private AreaEffect effect; /** * For a topPlot only, specifies another GJFillableInterface - the area * returned by the getAreaFill method of this plot will be painted. */ private GJFillableInterface areaFill = null; /** * Paint to use for filling the getAreaFill region. */ private Paint areaPaint = Colors.getColor("AREAFILL"); private static GlowPathEffect glow = (GlowPathEffect) GJDefaults.getMap().get("GJAbstractPlot.selection"); private AlphaComposite fillComposite = null; /** * Default protected constructor. This will be called implicitly during * subclass construction. */ protected GJAbstractPlot() { //setPlotList(null); } /** * Creates a new plot from an existing plot. *
    *
  1. The data and visual model of the source is copied by reference and * shared with the destination
  2. *
  3. A new screen data array is created
  4. *
  5. All parentPlot references are set to null
  6. *
* * @param p */ protected GJAbstractPlot(GJAbstractPlot p) { super(); initNewInstance(); removePropertyChangeListener(p); setVisualModel(p.getVisualModel()); GJDataModel model = p.getDataModel(); setDataModel(model); model.getXData().addPropertyChangeListener(this); model.getYData().addPropertyChangeListener(this); model.addPropertyChangeListener(this); setScreenDataArray(new ArrayList()); addPropertyChangeListener(this); parentPlot = null; } /** * Factory method for initializing a plot. Call this from the * {@code createInstance} method in the specific plot class passing in an * instance of the plot created using its private constructor i.e. call * {@code super.createInstance} from the {@code createInstance} method of * each concrete subclass. Example: *

*
class myPlot extends GJAbstractPlot {
public GJPlotInterface * createInstance(){
return super.createInstance(new myPlot());
* }
....
}
* }

* * @param p * @return a GJPlotInterface */ protected static GJPlotInterface createInstance(GJPlotInterface p) { p.setDataModel(GJDataModel.createInstance()); //((GJAbstractPlot) p).setPlotList(new ArrayList()); String symbol = (String) GJDefaults.getMap().get("GJAbstractPlot.markerSymbol"); double markerSize = ((Double) GJDefaults.getMap().get("GJAbstractPlot.markerSize")).doubleValue(); p.getVisualModel().setMarkerArray(new GJCyclicArrayList(GJMarker.getMarker(symbol, markerSize))); p.getVisualModel().setDynamicMarkerSize(new GJCyclicArrayList()); Paint edgeColor = (Paint) GJDefaults.getMap().get("GJAbstractPlot.edgeColor"); p.getVisualModel().setEdgeColor(new GJCyclicArrayList(edgeColor)); Float edgeWidth = (Float) GJDefaults.getMap().get("GJAbstractPlot.edgeStrokeWidth"); GJCyclicArrayList edgeStroke = new GJCyclicArrayList(new BasicStroke(edgeWidth)); p.getVisualModel().setEdgeStroke(edgeStroke); Paint lineColor = (Paint) GJDefaults.getMap().get("GJAbstractPlot.lineColor"); p.getVisualModel().setLineColor(new GJCyclicArrayList(lineColor)); float lineWidth = ((Float) GJDefaults.getMap().get("GJAbstractPlot.lineStrokeWidth")); GJCyclicArrayList lineStroke = new GJCyclicArrayList(GJUtilities.makeStroke(lineWidth, (String) GJDefaults.getMap().get("GJAbstractPlot.lineStyle"))); p.getVisualModel().setLineStroke(lineStroke); Paint fill = (Paint) GJDefaults.getMap().get("GJAbstractPlot.fill"); p.getVisualModel().setFill(new GJCyclicArrayList(fill)); p.setAntialiasing(((Boolean) GJDefaults.getMap().get("GJAbstractPlot.antiAliasing"))); p.addPropertyChangeListener(p); return p; } @Override public boolean isMultiplexible() { return true; } /** * Returns true if this is a top plot, i.e. a plot that is not a descendant * of another plot * * @return true/false */ @Override public final boolean isTopPlot() { return (parentPlot == null); } @Override public final void setVisible(boolean flag) { visible = flag; } @Override public final boolean isVisible() { return visible; } /** * Returns true if this plot is visible and in a host container that returns * isVisible==true. * * @return the flag */ public final boolean isShowing() { Container c = (Container) getParentGraph(); return c != null && c.isShowing() && isVisible(); } @Override public final void setSelected(boolean flag) { boolean old = selected; if (!isTopPlot()) { getParentPlot().setSelected(flag); } selected = flag; for (GJPlotInterface p : getPlotList()) { p.setSelectionFlag(flag); } getPCS().firePropertyChange("selected", old, isSelected()); } @Override public final void setSelectionFlag(boolean flag) { selected = flag; for (GJPlotInterface p : getPlotList()) { p.setSelectionFlag(flag); } } @Override public final boolean isSelected() { return selected; } @Override public final GJPlotInterface plus(GJPlotInterface p2) { return this.add(p2); } @Override public final GJPlotInterface add(GJPlotInterface plot) { if (this.getNode().contains(plot) || plot.getNode().contains(this)) { throw (new IllegalArgumentException("Plots are already in the same plot hierarchy")); } if (plot.getParentPlot() != null) { plot.getParentPlot().getPlotList().remove(plot); } plot.setParentPlot(this); getPlotList().add(plot); if (getParentGraph() != null) { ((Component) getParentGraph()).repaint(); } return this; } @Override public final GJPlotInterface getParentPlot() { return parentPlot; } @Override public final void setParentPlot(GJPlotInterface p) { parentPlot = p; } @Override public final boolean intersects(Point2D p) { return intersects(p.getX(), p.getY()); } @Override public final boolean intersects(double x, double y) { for (Iterator it = getScreenDataArray().iterator(); it.hasNext();) { if (it.next().intersects(x - 5, y - 5, 10, 10)) { return true; } } boolean flag; for (Iterator it = getPlotList().iterator(); it.hasNext();) { GJPlotInterface p = it.next(); flag = p.intersects(x, y); if (flag) { return true; } } return false; } @Override public final ArrayList findPlotBelow(Point2D p) { return findPlotBelow(p.getX(), p.getY()); } @Override public final ArrayList findPlotBelow(double x, double y) { ArrayList arr = new ArrayList(); boolean flag; for (GJPlotInterface p : getPlotList()) { flag = p.intersects(x, y); if (flag) { arr.add(p); } } for (Iterator it = getScreenDataArray().iterator(); it.hasNext();) { if (it.next().intersects(x - 5, y - 5, 10, 10)) { arr.add(this); } } return arr; } @Override @Deprecated public final ArrayList getPlots() { return getPlotList(); } @Override @SuppressWarnings("deprecation") public final ArrayList getNode() { ArrayList list = new ArrayList(); // TODO: Remove this in future release /** * Back-compatibility code */ /** * This code will run when de-serializing XML files saved before the 1.1 * release and will move the relevant plot list from the data model to * the plot instance. */ if (getDataModel().getPlotList() != null && getDataModel().getPlotList().size() > 0) { for (GJPlotInterface p : getDataModel().getPlotList()) { p.setParentPlot(this); } getPlotList().addAll(getDataModel().getPlotList()); getDataModel().setPlotList(null); } /** * End of back-compatibility code */ list.add(this); if (getPlotList() != null) { for (GJPlotInterface p : getPlotList()) { list.addAll(p.getNode()); } } return list; } /** * getDataRange returns the range on the X and Y axes that can potentially * be filled by the data points given the current contents of the XData and * YData properties. * * Note that this is not necessarily the same as the range of the data * contained in the XData and YData properties (although it often will be * for a simple Cartesian plot). * * getVisualRange can be used instead to assess the required axes limits * needed to render the data given e.g. the set marker size etc. However, * getVisualRange can only be used to update the display: it requires that * data have already been rendered to screen at least once. * * @return a Rectangle2D instance */ @Override public Rectangle2D getDataRange() { if (this.getXDataValues() != null && this.getYDataValues() != null) { double[] x = ArrayMath.minmax(this.getXDataValues()); double[] y = ArrayMath.minmax(this.getYDataValues()); Rectangle2D r = new Rectangle.Double(x[0], y[0], x[1] - x[0], y[1] - y[0]); Rectangle2D r2; for (GJPlotInterface p : getPlotList()) { r2 = p.getDataRange(); r = r.createUnion(r2); } return r; } else { return new Rectangle.Double(0, 0, 0, 0); } } @Override public GJPlotInterface getTopPlot() { GJPlotInterface p = this; while (!p.isTopPlot()) { p = p.getParentPlot(); } return p; } @Override public final Rectangle2D getVisualRange() { if (getParentGraph() == null) { return new Rectangle.Double(0, 0, 0, 0); } else { if (!isTopPlot()) { return getTopPlot().getVisualRange(); } if (getScreenDataArray().size() > 0) { Rectangle2D bounds = getScreenDataArray().get(0).getBounds2D(); for (GJPlotInterface p : getNode()) { if (p.getScreenDataArray().size() > 0) { for (Shape s : p.getScreenDataArrayAsCopy()) { bounds = bounds.createUnion(s.getBounds2D()); } } } return getParentGraph().convertPixelsToPosition(bounds); } else { return getParentGraph().getAxesBounds(); } } } /** * Entry point for plotting called from the graph methods. * * @param g2 */ @Override public final void paintPlotEntry(Graphics2D g2) { double[] x = getXDataValues(); if (x != null && x.length > 0 && isVisible()) { Graphics2D g = (Graphics2D) g2.create(); setupGraphics(g); paintPlot(g); paintSelected(g); g.dispose(); } } /** * Note that this method is overloaded by all concrete sub-classes which * should call this super-class method. * * @param g2 */ @Override public void paintPlot(Graphics2D g2) { if (g2 instanceof AbstractDeployableGraphics2D) { ((AbstractDeployableGraphics2D) g2).setPaintObject(this); } paintChildPlots(g2); if (isTopPlot() && getAreaFill() != null) { Composite composite = null; if (getFillComposite() != null) { composite = g2.getComposite(); g2.setComposite(getFillComposite()); } paintAreaFill(g2); paintChildPlots(g2); if (composite != null) { g2.setComposite(composite); } } if (getEffect() != null) { drawEffect(g2); } } /** * Paints the child plots * * @param g2 Graphics2D object */ final void paintChildPlots(Graphics2D g2) { for (GJPlotInterface p : new ArrayList(getPlotList())) { Graphics2D g = (Graphics2D) g2.create(); ((GJAbstractPlot) p).setupGraphics(g); p.paintPlot(g2); ((GJAbstractPlot) p).paintSelected(g); g.dispose(); } } /** * Paints the area returned by the areaFill getFillable call. */ final void paintAreaFill(Graphics2D g2) { if (getAreaFill() != null) { Shape s = getFillable(); if (s != null) { Composite composite = null; if (getAreaFill().getFillComposite() != null) { composite = g2.getComposite(); g2.setComposite(getAreaFill().getFillComposite()); } g2.setPaint(getAreaFill().getAreaPaint()); g2.fill(s); if (composite != null) { g2.setComposite(composite); } } } } @Override public void plotRedraw() { Graphics2D g = (Graphics2D) ((Component) getParentGraph()).getGraphics(); setupGraphics(g); paintPlot(g); } @Override public void plotRedraw(Graphics2D g) { paintPlot(g); } @Override public boolean plotUpdate() { plotRedraw(); return false; } @Override public boolean plotUpdate(Graphics2D g) { plotRedraw(g); return false; } /** * getFillable provides the Area instance to fill. This method can be * overridden in custom-classes to return a different Area instance. * * @return the Area to fill when painting the plot. */ @Override public Shape getFillable() { if (getAreaFill() != null) { if (getAreaFill() instanceof GJPlotInterface) { return localFill(); } else if (getAreaFill() instanceof GJFill) { if (((GJFill) getAreaFill()).getOrientation().equals(GJFill.ORIENTATION.ARBITRARY)) { return ((GJFill) getAreaFill()).getPixelArea(); } else { return getAreaFill().getFillable(); } } } return null; } private Shape localFill() { final Area area0 = new Area(); final double ymin = getParentGraph().getYMin(); ArrayList p2 = ((GJPlotInterface) getAreaFill()).getNode(); for (GJPlotInterface plot : p2) { for (Shape s : plot.getScreenDataArray()) { area0.add(new Area(ShapeUtils.getFromX(getParentGraph(), s, ymin))); } } p2 = getNode(); final Area area1 = new Area(); for (GJPlotInterface plot : p2) { for (Shape s : plot.getScreenDataArray()) { area1.add(new Area(ShapeUtils.getFromX(getParentGraph(), s, ymin))); } } Rectangle2D r0 = area0.getBounds2D(); Rectangle2D r1 = area1.getBounds2D(); r0 = r0.createIntersection(r1); double w = this.getMarker(0).getPath().getBounds2D().getWidth(); r1 = new Rectangle2D.Double(Math.ceil(r0.getMinX() + w / 2) + 1, ymin, Math.floor(r0.getWidth() - w) - 2, r0.getMaxY() - getParentGraph().getYMin()); final Area area3 = (Area) area0.clone(); area3.subtract(area1); area1.subtract(area0); area3.add(area1); area3.intersect(new Area(r1)); return area3; } /** * Paints the selected plots. * * @param g2 */ private void paintSelected(Graphics2D g2) { if (isSelected()) { if (getXDataValues().length < 500) { long t = System.currentTimeMillis(); Composite old = g2.getComposite(); g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f)); for (Shape thisPath : getScreenDataArray()) { if (System.currentTimeMillis() - t > 200L) { ((GJAbstractGraph) getParentGraph()).setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); } try { glow.apply(g2, thisPath, 20, 20); } catch (IllegalArgumentException ex) { } } ((GJAbstractGraph) getParentGraph()).setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); g2.setComposite(old); } } } @Override public final void setAntialiasing(boolean flag) { if (flag) { setRenderHintState(RenderingHints.VALUE_ANTIALIAS_ON); } else { setRenderHintState(RenderingHints.VALUE_ANTIALIAS_OFF); } } /** * Called by paintPlot method for subclasses to set up default graphics * mode. This is only called on a TopLevel plot. * * @param g2 Graphics 2D object */ private void setupGraphics(Graphics2D g2) { g2.setComposite(AlphaComposite.getInstance(getCompositeMode(), getAlpha())); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, getRenderHintState()); g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, getTextHintState()); } @Override public void saveAsXML(String fileName) { GJEncoder.save(fileName, this); } @Override public GCFrame createFrame() { final GCFrame f = new GCFrame(); final GJPlotInterface plot = this; GJAbstractGraph graph = GJGraph.createInstance(); GJGraphContainer gr = GJGraphContainer.createInstance(graph); f.add(gr); f.setSize(500, 500); graph.add(plot); graph.autoScale(); return f; } public boolean isOnEDT() { return EventQueue.isDispatchThread(); } @Override public void propertyChange(PropertyChangeEvent pce) { } @Override public String toString() { return toString(false, ""); } public String toString(boolean flag) { return toString(flag, ""); } protected String toString(boolean flag, String tab) { StringBuilder s = new StringBuilder(); Formatter f = new Formatter(s); String clss = getClass().toString().replace("kcl.waterloo.graphics.plots2D.", ""); clss = clss.replace("class", ""); f.format(tab + "Waterloo graphics %s plot [@%x]:\n", clss, hashCode()); if (getParentGraph() == null) { f.format(tab + "Parent Graph: null\n"); } else { f.format(tab + "Parent Graph: [@%x]\n", getParentGraph().hashCode()); } if (getParentPlot() == null) { f.format(tab + "Parent plot: null\n"); } else { f.format(tab + "Parent Plot: [@%x]\n", getParentPlot().hashCode()); } f.format(tab + "Is top plot:\t" + isTopPlot() + "\n"); f.format(tab + "Child plots:\t" + (getPlots().size() > 0) + "\n"); f.format(tab + "Multiplexed:\t" + isMultiplexed() + "\n"); f.format(tab + "Alpha:\t" + getAlpha() + "\n"); f.format(tab + "Data (untransformed):\n"); f.format(tab + "x:\t"); printData(f, getXData().getRawDataValues()); f.format(tab + "y:\t"); printData(f, getYData().getRawDataValues()); f.format(tab + stringSupplement()); if (flag && getPlotList().size() > 0) { for (GJPlotInterface p : getPlotList()) { f.format("\n" + tab + "----Child-----\n"); f.format(tab + ((GJAbstractPlot) p).toString(true, tab)); tab = tab + "\t"; } } return f.toString(); } /** * Adds a supplement to the {@code fromString} returned by the toString() * method. Subclasses can override this method to add information to the end * of the toString() output. * * @return the supplementary {@code fromString} */ protected String stringSupplement() { return ""; } protected static void printData(Formatter f, double[] in) { final int N = 10; if (in.length == 1) { f.format("%5.2f%n", in[0]); return; } for (int k = 0; k < Math.min(N, in.length); k++) { f.format("%5.2f", in[k]); if (k < N) { f.format(", "); } } if (in.length > N) { f.format("%5.2f...%n", in[N]); } else { f.format("%n"); } } @Override public void setEffect(AreaEffect effect) { this.effect = effect; } @Override public AreaEffect getEffect() { return effect; } protected final void drawEffect(final Graphics2D g) { if (effect != null) { for (Shape s : getScreenDataArrayAsCopy()) { effect.apply(g, s, (int) s.getBounds().getWidth(), (int) s.getBounds().getHeight()); } } } /** * @return the areaFill */ public GJFillableInterface getAreaFill() { return areaFill; } /** * @param areaFill the areaFill to set */ public void setAreaFill(GJFillableInterface areaFill) { if (areaPaint != null) { areaFill.setAreaPaint(areaPaint); } this.areaFill = areaFill; } @Override public Paint getAreaPaint() { if (getAreaFill() != null) { return getAreaFill().getAreaPaint(); } else { return areaPaint; } } @Override public void setAreaPaint(Paint p) { if (getAreaFill() != null) { getAreaFill().setAreaPaint(p); } areaPaint = p; } /** * @return the composite */ @Override public AlphaComposite getFillComposite() { return fillComposite; } /** * @param composite the composite to set */ @Override public void setFillComposite(AlphaComposite composite) { this.fillComposite = composite; } @Override public float getFillAlpha() { if (fillComposite != null) { return fillComposite.getAlpha(); } else { return this.getAlpha(); } } @Override public void setFillAlpha(float alpha) { if (fillComposite != null) { fillComposite = fillComposite.derive(alpha); } else { fillComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha); } } }

Ads help maintain this website.