package com.positive.charts.plot;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
import com.positive.charts.axis.AxisLocation;
import com.positive.charts.block.RectangleInsets;
import com.positive.charts.common.RectangleEdge;
import com.positive.charts.data.general.DatasetGroup;
import com.positive.charts.event.AxisChangeEvent;
import com.positive.charts.event.AxisChangeListener;
import com.positive.charts.event.ChartChangeEventType;
import com.positive.charts.event.DatasetChangeEvent;
import com.positive.charts.event.DatasetChangeListener;
import com.positive.charts.event.MarkerChangeEvent;
import com.positive.charts.event.MarkerChangeListener;
import com.positive.charts.event.PlotChangeEvent;
import com.positive.charts.event.PlotChangeListener;
import com.positive.charts.legend.LegendItemSource;
import com.positive.charts.util.DrawingAssets;
import com.positive.charts.util.Stroke;
import com.positive.colorchecker.StaticColorChecker;
/**
* The base class for all plots in JFreeChart. The
* {@link org.jfree.chart.JFreeChart} class delegates the drawing of axes and
* data to the plot. This base class provides facilities common to most plot
* types.
*/
public abstract class Plot implements AxisChangeListener,
DatasetChangeListener, MarkerChangeListener, LegendItemSource {
/** For serialization. */
private static final long serialVersionUID = -8831571430103671324L;
/** Useful constant representing zero. */
public static final Number ZERO = new Integer(0);
/** The default insets. */
public static final RectangleInsets DEFAULT_INSETS = new RectangleInsets(4,
8, 4, 8);
/** The default foreground alpha transparency. */
public static final int DEFAULT_FOREGROUND_ALPHA = 0xFF; // opaque
/** The default background alpha transparency. */
public static final int DEFAULT_BACKGROUND_ALPHA = 0xFF; // opaque
/** The minimum width at which the plot should be drawn. */
public static final int MINIMUM_WIDTH_TO_DRAW = 10;
/** The minimum height at which the plot should be drawn. */
public static final int MINIMUM_HEIGHT_TO_DRAW = 10;
/**
* Resolves a domain axis location for a given plot orientation.
*
* @param location
* the location (<code>null</code> not permitted).
* @param orientation
* the orientation (<code>null</code> not permitted).
*
* @return The edge (never <code>null</code>).
*/
public static RectangleEdge resolveDomainAxisLocation(
final AxisLocation location, final PlotOrientation orientation) {
if (location == null) {
throw new IllegalArgumentException("Null 'location' argument.");
}
if (orientation == null) {
throw new IllegalArgumentException("Null 'orientation' argument.");
}
RectangleEdge result = null;
if (location == AxisLocation.TOP_OR_RIGHT) {
if (orientation == PlotOrientation.HORIZONTAL) {
result = RectangleEdge.RIGHT;
} else if (orientation == PlotOrientation.VERTICAL) {
result = RectangleEdge.TOP;
}
} else if (location == AxisLocation.TOP_OR_LEFT) {
if (orientation == PlotOrientation.HORIZONTAL) {
result = RectangleEdge.LEFT;
} else if (orientation == PlotOrientation.VERTICAL) {
result = RectangleEdge.TOP;
}
} else if (location == AxisLocation.BOTTOM_OR_RIGHT) {
if (orientation == PlotOrientation.HORIZONTAL) {
result = RectangleEdge.RIGHT;
} else if (orientation == PlotOrientation.VERTICAL) {
result = RectangleEdge.BOTTOM;
}
} else if (location == AxisLocation.BOTTOM_OR_LEFT) {
if (orientation == PlotOrientation.HORIZONTAL) {
result = RectangleEdge.LEFT;
} else if (orientation == PlotOrientation.VERTICAL) {
result = RectangleEdge.BOTTOM;
}
}
// the above should cover all the options...
if (result == null) {
throw new IllegalStateException("resolveDomainAxisLocation()");
}
return result;
}
/**
* Resolves a range axis location for a given plot orientation.
*
* @param location
* the location (<code>null</code> not permitted).
* @param orientation
* the orientation (<code>null</code> not permitted).
*
* @return The edge (never <code>null</code>).
*/
public static RectangleEdge resolveRangeAxisLocation(
final AxisLocation location, final PlotOrientation orientation) {
if (location == null) {
throw new IllegalArgumentException("Null 'location' argument.");
}
if (orientation == null) {
throw new IllegalArgumentException("Null 'orientation' argument.");
}
RectangleEdge result = null;
if (location == AxisLocation.TOP_OR_RIGHT) {
if (orientation == PlotOrientation.HORIZONTAL) {
result = RectangleEdge.TOP;
} else if (orientation == PlotOrientation.VERTICAL) {
result = RectangleEdge.RIGHT;
}
} else if (location == AxisLocation.TOP_OR_LEFT) {
if (orientation == PlotOrientation.HORIZONTAL) {
result = RectangleEdge.TOP;
} else if (orientation == PlotOrientation.VERTICAL) {
result = RectangleEdge.LEFT;
}
} else if (location == AxisLocation.BOTTOM_OR_RIGHT) {
if (orientation == PlotOrientation.HORIZONTAL) {
result = RectangleEdge.BOTTOM;
} else if (orientation == PlotOrientation.VERTICAL) {
result = RectangleEdge.RIGHT;
}
} else if (location == AxisLocation.BOTTOM_OR_LEFT) {
if (orientation == PlotOrientation.HORIZONTAL) {
result = RectangleEdge.BOTTOM;
} else if (orientation == PlotOrientation.VERTICAL) {
result = RectangleEdge.LEFT;
}
}
// the above should cover all the options...
if (result == null) {
throw new IllegalStateException("resolveRangeAxisLocation()");
}
return result;
}
private final DrawingAssets drawingAssets;
/** The parent plot (<code>null</code> if this is the root plot). */
private Plot parent;
/** The dataset group (to be used for thread synchronisation). */
private DatasetGroup datasetGroup;
/** The message to display if no data is available. */
private String noDataMessage;
/** Amount of blank space around the plot area. */
private RectangleInsets insets;
/** The alpha value used to draw the background image. */
private float backgroundImageAlpha = 0.5f;
/** The alpha-transparency for the plot. */
private int foregroundAlpha;
/** The alpha transparency for the background paint. */
private float backgroundAlpha;
/** The drawing supplier. */
private DrawingSupplier drawingSupplier;
/** Storage for registered change listeners. */
private transient final ListenerList listenerList;
private Color backgroundPaint = StaticColorChecker.dublicateColor(SWT.COLOR_WHITE);
//Display.getDefault().getSystemColor(
// SWT.COLOR_WHITE);
private Color outlineColor = StaticColorChecker.dublicateColor(SWT.COLOR_GRAY);
//Display.getDefault().getSystemColor(
// SWT.COLOR_GRAY);
public static String DEFAULT_LEGEND_ITEM_LINE_COLOR = "AbstractCategoryItemRenderer.defaultLegendItemLineColor";
public static final String COLOR_SUB_LABEL = "Plot.subLabelColor";
/**
* An overriding color to use for drawing all series. If set, all series
* will be drawn using this color.
*/
public static final String COLOR_SERIES_OVERRIDE = "Plot.overrideSeriesColor";
/**
* A color to use for drawing series by default, if drawing supplier is not
* set.
*
* @see #getDrawingSupplier()
*/
public static final String COLOR_SERIES_BASE = "Plot.baseSeriesColor";
public static final String COLOR_SERIES_FILL_OVERRIDE = "Plot.overrideFillSeriesColor";
public static final String COLOR_SERIES_FILL_BASE = "Plot.baseFillSeriesColor";
public static final String COLOR_SERIES_OUTLINE_OVERRIDE = "Plot.overrideOutlineSeriesColor";
public static final String COLOR_SERIES_OUTLINE_BASE = "Plot.baseOutlineSeriesColor";
public static final String COLOR_ITEM_LABEL_BASE = "Plot.baseItemLabelColor";
public static final String COLOR_ITEM_LABEL_OVERRIDE = "Plot.overrideItemLabelColor";
/** A base stroke style to use with all series. */
public static final String STROKE_SERIES_BASE = "Plot.baseSeriesStroke";
public static final String STROKE_SERIES_OVERRIDE = "Plot.overrideSeriesStroke";
public static final String STROKE_SERIES_OUTLINE_BASE = "Plot.baseOutlineSeriesStroke";
public static final String STROKE_SERIES_OUTLINE_OVERRIDE = "Plot.overrideOutlineSeriesStroke";
/** Color of a plot's border */
public static final String COLOR_OUTLINE = "Plot.outlineColor";
/** Color of a plot's background */
public static final String COLOR_BACKGROUND = "Plot.backgroundColor";
/** Color of a domain tick band */
public static final String COLOR_DOMAIN_TICK_BAND = "Plot.domainTickColor";
/** Color of a range tick band */
public static final String COLOR_RANGE_TICK_BAND = "Plot.rangeTickColor";
/** Color of a range tick band */
public static final String COLOR_TICK_LABEL = "Plot.tickLabelColor";
/**
* Creates a new plot.
*/
protected Plot() {
this.parent = null;
this.insets = DEFAULT_INSETS;
this.backgroundAlpha = DEFAULT_BACKGROUND_ALPHA;
this.foregroundAlpha = DEFAULT_FOREGROUND_ALPHA;
this.noDataMessage = null;
this.drawingSupplier = new DefaultDrawingSupplier(null);
this.drawingAssets = new DrawingAssets();
this.listenerList = new ListenerList();
}
/**
* Registers an object for notification of changes to the plot.
*
* @param listener
* the object to be registered.
*/
public void addChangeListener(final PlotChangeListener listener) {
this.listenerList.add(listener);
}
/**
* Sets the foreground color to a supplied value, if it's not
* <code>null</code>
*
* @param gc
* @param colorName
*/
protected void applyForegroundColor(final GC gc, final String colorName) {
final Color color = this.getColor(colorName);
if (color != null) {
gc.setForeground(color);
}
}
/**
* Sets a currently used stroke to supplied value, if it's not
* <code>null</code>
*
* @param gc
* @param strokeName
*/
protected void applyStroke(final GC gc, final String strokeName) {
final Stroke stroke = this.getStroke(strokeName);
if (stroke != null) {
stroke.set(gc);
}
}
/**
* Receives notification of a change to one of the plot's axes.
*
* @param event
* information about the event (not used here).
*/
public void axisChanged(final AxisChangeEvent event) {
this.notifyListeners(new PlotChangeEvent(this));
}
/**
* Receives notification of a change to the plot's dataset.
* <P>
* The plot reacts by passing on a plot change event to all registered
* listeners.
*
* @param event
* information about the event (not used here).
*/
public void datasetChanged(final DatasetChangeEvent event) {
final PlotChangeEvent newEvent = new PlotChangeEvent(this);
newEvent.setType(ChartChangeEventType.DATASET_UPDATED);
this.notifyListeners(newEvent);
}
/**
* Diposes a plot and all drawing assets.
*/
public void dispose() {
this.drawingSupplier.dispose();
this.drawingAssets.dispose();
}
/**
* Draws the plot within the specified area. The anchor is a point on the
* chart that is specified externally (for instance, it may be the last
* point of the last mouse click performed by the user) - plots can use or
* ignore this value as they see fit. <br>
* <br>
* Subclasses need to provide an implementation of this method, obviously.
*
* @param gc
* the graphics device.
* @param area
* the plot area.
* @param anchor
* the anchor point (<code>null</code> permitted).
* @param parentState
* the parent state (if any).
* @param info
* carries back plot rendering info.
*/
public abstract void draw(GC gc, Rectangle area, Point anchor,
PlotState parentState, PlotRenderingInfo info);
/**
* Draws the plot background (the background color and/or image).
* <P>
* This method will be called during the chart drawing process and is
* declared public so that it can be accessed by the renderers used by
* certain subclasses. You shouldn't need to call this method directly.
*
* @param gc
* the graphics device.
* @param area
* the area within which the plot should be drawn.
*/
public void drawBackground(final GC gc, final Rectangle area) {
this.fillBackground(gc, area);
}
/**
* Draws the plot outline. This method will be called during the chart
* drawing process and is declared public so that it can be accessed by the
* renderers used by certain subclasses. You shouldn't need to call this
* method directly.
*
* @param gc
* the graphics device.
* @param area
* the area within which the plot should be drawn.
*/
public void drawOutline(final GC gc, final Rectangle area) {
final Color outlineColor = this.outlineColor;
if (outlineColor == null) {
this.outlineColor = this.drawingAssets.getColor(COLOR_OUTLINE);
}
if (outlineColor != null) {
gc.setForeground(outlineColor);
gc.drawRectangle(area);
}
}
/**
* Fills the specified area with the background paint.
*
* @param gc
* the graphics device.
* @param area
* the area.
*/
protected void fillBackground(final GC gc, final Rectangle area) {
if (this.backgroundPaint != null) {
gc.setBackground(this.backgroundPaint);
gc.fillRectangle(area);
}
// g2.setComposite(originalComposite);
// Color backgroundColor = drawingAssets.getColor(COLOR_BACKGROUND);
// if (backgroundColor != null) {
// gc.setBackground(backgroundColor);
// gc.fillRectangle(area);
// }
}
/**
* Sends a {@link PlotChangeEvent} to all registered listeners.
*
* @since 1.0.10
*/
protected void fireChangeEvent() {
this.notifyListeners(new PlotChangeEvent(this));
}
/**
* Returns the alpha transparency of the plot area background.
*
* @return The alpha transparency.
*/
public float getBackgroundAlpha() {
return this.backgroundAlpha;
}
/**
* Returns the alpha transparency used to draw the background image. This is
* a value in the range 0.0f to 1.0f, where 0.0f is fully transparent and
* 1.0f is fully opaque.
*
* @return The alpha transparency.
*
* @see #setBackgroundImageAlpha(float)
*/
public float getBackgroundImageAlpha() {
return this.backgroundImageAlpha;
}
public Color getBackgroundPaint() {
return this.backgroundPaint;
}
/**
* Returns a color registered with this plot.
*
* @see DrawingAssets#getColor(String)
*/
public Color getColor(final String key) {
return this.drawingAssets.getColor(key);
}
/**
* Returns the dataset group for the plot (not currently used).
*
* @return The dataset group.
*/
public DatasetGroup getDatasetGroup() {
return this.datasetGroup;
}
/**
* Returns the drawing assets from the topmost parent plot.
*
* @return The drawing assets.
*/
public DrawingAssets getDrawingAssets() {
final Plot p = this.getParent();
if (p != null) {
return p.getDrawingAssets();
} else {
return this.drawingAssets;
}
}
/**
* Returns the drawing supplier for the plot.
*
* @return The drawing supplier (possibly <code>null</code>).
*/
public DrawingSupplier getDrawingSupplier() {
final Plot p = this.getParent();
if (p != null) {
return p.getDrawingSupplier();
} else {
return this.drawingSupplier;
}
}
/**
* Returns the alpha-transparency for the plot foreground.
*
* @return The alpha-transparency.
*/
public int getForegroundAlpha() {
return this.foregroundAlpha;
}
/**
* Returns the insets for the plot area.
*
* @return The insets (never <code>null</code>).
*/
public RectangleInsets getInsets() {
return this.insets;
}
/**
* Returns the string that is displayed when the dataset is empty or
* <code>null</code>.
*
* @return The 'no data' message (<code>null</code> possible).
*/
public String getNoDataMessage() {
return this.noDataMessage;
}
/**
* Returns the parent plot (or <code>null</code> if this plot is not part of
* a combined plot).
*
* @return The parent plot.
*/
public Plot getParent() {
return this.parent;
}
/**
* Returns a short string describing the plot type.
* <P>
* Note: this gets used in the chart property editing user interface, but
* there needs to be a better mechanism for identifying the plot type.
*
* @return A short string describing the plot type.
*/
public abstract String getPlotType();
/**
* Adjusts the supplied x-value.
*
* @param x
* the x-value.
* @param w1
* width 1.
* @param w2
* width 2.
* @param edge
* the edge (left or right).
*
* @return The adjusted x-value.
*/
protected double getRectX(final double x, final double w1, final double w2,
final RectangleEdge edge) {
double result = x;
if (edge == RectangleEdge.LEFT) {
result = result + w1;
} else if (edge == RectangleEdge.RIGHT) {
result = result + w2;
}
return result;
}
/**
* Adjusts the supplied y-value.
*
* @param y
* the x-value.
* @param h1
* height 1.
* @param h2
* height 2.
* @param edge
* the edge (top or bottom).
*
* @return The adjusted y-value.
*/
protected double getRectY(final double y, final double h1, final double h2,
final RectangleEdge edge) {
double result = y;
if (edge == RectangleEdge.TOP) {
result = result + h1;
} else if (edge == RectangleEdge.BOTTOM) {
result = result + h2;
}
return result;
}
/**
* Returns the root plot.
*
* @return The root plot.
*/
public Plot getRootPlot() {
final Plot p = this.getParent();
if (p == null) {
return this;
} else {
return p.getRootPlot();
}
}
/**
* Returns a named stroke style registered with a plot.
*
* @param key
* the stroke name
* @return the stroke, or <code>null</code>
*
* @see DrawingAssets#getStroke(String)
*/
public Stroke getStroke(final String key) {
return this.drawingAssets.getStroke(key);
}
/**
* Handles a 'click' on the plot. Since the plot does not maintain any
* information about where it has been drawn, the plot rendering info is
* supplied as an argument.
*
* @param x
* the x coordinate (in Java2D space).
* @param y
* the y coordinate (in Java2D space).
* @param info
* an object containing information about the dimensions of the
* plot.
*/
public void handleClick(final int x, final int y,
final PlotRenderingInfo info) {
// provides a 'no action' default
}
/**
* Returns true if this plot is part of a combined plot structure.
*
* @return <code>true</code> if this plot is part of a combined plot
* structure.
*/
public boolean isSubplot() {
return (this.getParent() != null);
}
/**
* Receives notification of a change to a marker that is assigned to the
* plot.
*
* @param event
* the event.
*/
public void markerChanged(final MarkerChangeEvent event) {
this.notifyListeners(new PlotChangeEvent(this));
}
/**
* Notifies all registered listeners that the plot has been modified.
*
* @param event
* information about the change event.
*/
public void notifyListeners(final PlotChangeEvent event) {
final Object[] listeners = this.listenerList.getListeners();
for (int i = 0; i < listeners.length; i++) {
final PlotChangeListener listener = (PlotChangeListener) listeners[i];
listener.plotChanged(event);
}
}
/**
* Unregisters an object for notification of changes to the plot.
*
* @param listener
* the object to be unregistered.
*/
public void removeChangeListener(final PlotChangeListener listener) {
this.listenerList.remove(listener);
}
/**
* Sets the alpha transparency of the plot area background, and notifies
* registered listeners that the plot has been modified.
*
* @param alpha
* the new alpha value.
*/
public void setBackgroundAlpha(final float alpha) {
if (this.backgroundAlpha != alpha) {
this.backgroundAlpha = alpha;
this.notifyListeners(new PlotChangeEvent(this));
}
}
/**
* Sets the alpha transparency used when drawing the background image.
*
* @param alpha
* the alpha transparency (in the range 0.0f to 1.0f, where 0.0f
* is fully transparent, and 1.0f is fully opaque).
*
* @throws IllegalArgumentException
* if <code>alpha</code> is not within the specified range.
*
* @see #getBackgroundImageAlpha()
*/
public void setBackgroundImageAlpha(final float alpha) {
if ((alpha < 0.0f) || (alpha > 1.0f)) {
throw new IllegalArgumentException(
"The 'alpha' value must be in the range 0.0f to 1.0f.");
}
if (this.backgroundImageAlpha != alpha) {
this.backgroundImageAlpha = alpha;
this.notifyListeners(new PlotChangeEvent(this));
}
}
public void setBackgroundPaint(final Color backgroundPaint) {
this.backgroundPaint = backgroundPaint;
}
/**
* Sets a color property and notifies the plot's listeners, if necessary. A
* plot becomes an owner of the color, so it should not be disposed.
*
* @param key
* an identifier for color
* @param color
* a color
*/
public void setColor(final String key, final Color color) {
final Color oldColor = this.drawingAssets.getColor(key);
this.drawingAssets.setColor(key, color);
if ((color != null) && color.equals(oldColor)) {
return;
}
this.notifyListeners(new PlotChangeEvent(this));
}
/**
* Sets the dataset group (not currently used).
*
* @param group
* the dataset group (<code>null</code> permitted).
*/
protected void setDatasetGroup(final DatasetGroup group) {
this.datasetGroup = group;
}
/**
* Sets the drawing supplier for the plot. The drawing supplier is
* responsible for supplying a limitless (possibly repeating) sequence of
* <code>Paint</code>, <code>Stroke</code> and <code>Shape</code> objects
* that the plot's renderer(s) can use to populate its (their) tables.
*
* @param supplier
* the new supplier.
*/
public void setDrawingSupplier(final DrawingSupplier supplier) {
this.drawingSupplier = supplier;
this.notifyListeners(new PlotChangeEvent(this));
}
/**
* Sets the alpha-transparency for the plot.
*
* @param alpha
* the new alpha transparency.
*/
public void setForegroundAlpha(final int alpha) {
if (this.foregroundAlpha != alpha) {
this.foregroundAlpha = alpha;
this.notifyListeners(new PlotChangeEvent(this));
}
}
/**
* Sets the insets for the plot and sends a {@link PlotChangeEvent} to all
* registered listeners.
*
* @param insets
* the new insets (<code>null</code> not permitted).
*/
public void setInsets(final RectangleInsets insets) {
this.setInsets(insets, true);
}
/**
* Sets the insets for the plot and, if requested, and sends a
* {@link PlotChangeEvent} to all registered listeners.
*
* @param insets
* the new insets (<code>null</code> not permitted).
* @param notify
* a flag that controls whether the registered listeners are
* notified.
*/
public void setInsets(final RectangleInsets insets, final boolean notify) {
if (insets == null) {
throw new IllegalArgumentException("Null 'insets' argument.");
}
if (!this.insets.equals(insets)) {
this.insets = insets;
if (notify) {
this.notifyListeners(new PlotChangeEvent(this));
}
}
}
/**
* Sets the message that is displayed when the dataset is empty or null.
*
* @param message
* the message (null permitted).
*/
public void setNoDataMessage(final String message) {
this.noDataMessage = message;
}
/**
* Sets the parent plot.
*
* @param parent
* the parent plot.
*/
public void setParent(final Plot parent) {
this.parent = parent;
}
public void setStroke(final String key, final Stroke stroke) {
final Stroke oldStroke = this.drawingAssets.getStroke(key);
this.drawingAssets.setStroke(key, stroke);
if ((stroke != null) && stroke.equals(oldStroke)) {
return;
}
this.notifyListeners(new PlotChangeEvent(this));
}
/**
* Performs a zoom on the plot. Subclasses should override if zooming is
* appropriate for the type of plot.
*
* @param percent
* the zoom percentage.
*/
public void zoom(final double percent) {
// do nothing by default.
}
}