package com.positive.charts.renderer.xy;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
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.ValueAxis;
import com.positive.charts.block.RectUtils;
import com.positive.charts.data.Range;
import com.positive.charts.data.util.DatasetUtilities;
import com.positive.charts.data.xy.XYDataset;
import com.positive.charts.entity.EntityCollection;
import com.positive.charts.entity.XYItemEntity;
import com.positive.charts.event.RendererChangeEvent;
import com.positive.charts.labels.ItemLabelPosition;
import com.positive.charts.labels.StandardXYSeriesLabelGenerator;
import com.positive.charts.labels.XYItemLabelGenerator;
import com.positive.charts.labels.XYSeriesLabelGenerator;
import com.positive.charts.legend.LegendItem;
import com.positive.charts.legend.LegendItemCollection;
import com.positive.charts.plot.DrawingSupplier;
import com.positive.charts.plot.Plot;
import com.positive.charts.plot.PlotOrientation;
import com.positive.charts.plot.PlotRenderingInfo;
import com.positive.charts.plot.xy.XYPlot;
import com.positive.charts.renderer.AbstractRenderer;
import com.positive.charts.util.DrawingAssets;
import com.positive.charts.util.RectangleUtil;
import com.positive.charts.util.Stroke;
import com.positive.charts.util.TextUtilities;
import com.positive.colorchecker.StaticColorChecker;
/**
* A base class that can be used to create new {@link XYItemRenderer}
* implementations.
*/
public abstract class AbstractXYItemRenderer extends AbstractRenderer implements
XYItemRenderer {
/** The plot. */
protected XYPlot plot;
/** The item label generator for ALL series. */
private XYItemLabelGenerator itemLabelGenerator;
/** A list of item label generators (one per series). */
private final List itemLabelGeneratorList;
/** The base item label generator. */
private XYItemLabelGenerator baseItemLabelGenerator;
private int defaultEntityRadius;
private XYSeriesLabelGenerator legendItemLabelGenerator;
private XYSeriesLabelGenerator legendItemToolTipGenerator;
private XYSeriesLabelGenerator legendItemURLGenerator;
/**
* Creates a renderer where the tooltip generator and the URL generator are
* both <code>null</code>.
*/
protected AbstractXYItemRenderer() {
this.itemLabelGenerator = null;
this.itemLabelGeneratorList = new ArrayList();
this.defaultEntityRadius = 3;
this.legendItemLabelGenerator = new StandardXYSeriesLabelGenerator();
}
/**
* Adds an entity to the collection.
*
* @param entities
* the entity collection being populated.
* @param area
* the entity area (if <code>null</code> a default will be used).
* @param dataset
* the dataset.
* @param series
* the series.
* @param item
* the item.
* @param entityX
* the entity's center x-coordinate in user space (only used if
* <code>area</code> is <code>null</code>).
* @param entityY
* the entity's center y-coordinate in user space (only used if
* <code>area</code> is <code>null</code>).
*/
protected void addEntity(final EntityCollection entities,
final Rectangle area, final XYDataset dataset, final int series,
final int item, final double entityX, final double entityY) {
if (!this.getItemCreateEntity(series, item)) {
return;
}
Rectangle hotspot = area;
if (hotspot == null) {
final double w = this.defaultEntityRadius * 2;
if (this.getPlot().getOrientation() == PlotOrientation.VERTICAL) {
hotspot = RectUtils.doubleRect(entityX
- this.defaultEntityRadius, entityY
- this.defaultEntityRadius, w, w);
} else {
hotspot = RectUtils.doubleRect(entityY
- this.defaultEntityRadius, entityX
- this.defaultEntityRadius, w, w);
}
}
final String tip = null;
// XYToolTipGenerator generator = getToolTipGenerator(series, item);
// if (generator != null) {
// tip = generator.generateToolTip(dataset, series, item);
// }
final String url = null;
final XYItemEntity entity = new XYItemEntity(hotspot, dataset, series,
item, tip, url);
entities.add(entity);
}
/**
* Draws a grid line against the range axis.
*
* @param g2
* the graphics device.
* @param plot
* the plot.
* @param axis
* the value axis.
* @param dataArea
* the area for plotting data (not yet adjusted for any 3D
* effect).
* @param value
* the value at which the grid line should be drawn.
*/
public void drawDomainGridLine(final GC g2, final XYPlot plot,
final ValueAxis axis, final Rectangle dataArea, final double value) {
final Range range = axis.getRange();
if (!range.contains(value)) {
return;
}
final PlotOrientation orientation = plot.getOrientation();
final int v = (int) axis.valueToJava2D(value, dataArea, plot
.getDomainAxisEdge());
final Color color = plot.getDomainGridlinePaint();
final Stroke stroke = plot.getDomainGridlineStroke();
// g2.setPaint(paint != null ? paint : Plot.DEFAULT_OUTLINE_PAINT);
// g2.setStroke(stroke != null ? stroke : Plot.DEFAULT_OUTLINE_STROKE);
if (color != null) {
g2.setForeground(color);
}
if (stroke != null) {
stroke.set(g2);
}
if (orientation == PlotOrientation.HORIZONTAL) {
g2.drawLine(RectangleUtil.getMinX(dataArea), v, RectangleUtil
.getMaxX(dataArea), v);
} else if (orientation == PlotOrientation.VERTICAL) {
g2.drawLine(v, RectangleUtil.getMinY(dataArea), v, RectangleUtil
.getMaxY(dataArea));
}
}
/**
* Draws an item label.
*
* @param g2
* the graphics device.
* @param orientation
* the orientation.
* @param dataset
* the dataset.
* @param series
* the series index (zero-based).
* @param item
* the item index (zero-based).
* @param x
* the x coordinate (in Java2D space).
* @param y
* the y coordinate (in Java2D space).
* @param negative
* indicates a negative value (which affects the item label
* position).
*/
protected void drawItemLabel(final GC g2,
final PlotOrientation orientation, final XYDataset dataset,
final int series, final int item, final double x, final double y,
final boolean negative) {
final XYItemLabelGenerator generator = this.getItemLabelGenerator(
series, item);
if (generator != null) {
final Font labelFont = this.getItemLabelFont(series, item);
// Paint paint = getItemLabelPaint(series, item);
// g2.setPaint(paint);
// Color color = getColor(ITEM_LABEL_COLOR);
// g2.setForeground(color);
g2.setFont(labelFont);
final String label = generator.generateLabel(dataset, series, item);
// get the label position..
ItemLabelPosition position = null;
if (!negative) {
position = this.getPositiveItemLabelPosition(series, item);
} else {
position = this.getNegativeItemLabelPosition(series, item);
}
// work out the label anchor point...
final Point anchorPoint = this.calculateLabelAnchorPoint(position
.getItemLabelAnchor(), (int) x, (int) y, orientation);
TextUtilities.drawRotatedString(label, g2, anchorPoint.x,
anchorPoint.y, position.getTextAnchor(), position
.getAngle(), position.getRotationAnchor());
}
}
/**
* Draws a line perpendicular to the range axis.
*
* @param g2
* the graphics device.
* @param plot
* the plot.
* @param axis
* the value axis.
* @param dataArea
* the area for plotting data (not yet adjusted for any 3D
* effect).
* @param value
* the value at which the grid line should be drawn.
*/
public void drawRangeLine(final GC g2, final XYPlot plot,
final ValueAxis axis, final Rectangle dataArea, final double value) {
final Color color = plot.getDomainGridlinePaint();
final Stroke stroke = plot.getDomainGridlineStroke();
// g2.setPaint(paint != null ? paint : Plot.DEFAULT_OUTLINE_PAINT);
// g2.setStroke(stroke != null ? stroke : Plot.DEFAULT_OUTLINE_STROKE);
if (color != null) {
g2.setForeground(color);
}
if (stroke != null) {
stroke.set(g2);
}
this.drawRangeLine(g2, plot, axis, dataArea, value, null, null);
}
/**
* Draws a line perpendicular to the range axis.
*
* @param gc
* the graphics device.
* @param plot
* the plot.
* @param axis
* the value axis.
* @param dataArea
* the area for plotting data (not yet adjusted for any 3D
* effect).
* @param value
* the value at which the grid line should be drawn.
* @param paint
* the paint.
* @param stroke
* the stroke.
*/
public void drawRangeLine(final GC gc, final XYPlot plot,
final ValueAxis axis, final Rectangle dataArea, final double value,
final Color paint, final Stroke stroke) {
final Range range = axis.getRange();
if (!range.contains(value)) {
return;
}
if (paint != null) {
gc.setForeground(paint);
}
if (stroke != null) {
stroke.set(gc);
}
final PlotOrientation orientation = plot.getOrientation();
final int v = (int) axis.valueToJava2D(value, dataArea, plot
.getRangeAxisEdge());
if (orientation == PlotOrientation.HORIZONTAL) {
gc.drawLine(v, RectangleUtil.getMinY(dataArea), v, RectangleUtil
.getMaxY(dataArea));
} else if (orientation == PlotOrientation.VERTICAL) {
gc.drawLine(RectangleUtil.getMinX(dataArea), v, RectangleUtil
.getMaxX(dataArea), v);
}
}
// ITEM LABEL GENERATOR
/**
* Fills a band between two values on the axis. This can be used to color
* bands between the grid lines.
*
* @param g2
* the graphics device.
* @param plot
* the plot.
* @param axis
* the domain axis.
* @param dataArea
* the data area.
* @param start
* the start value.
* @param end
* the end value.
*/
public void fillDomainGridBand(final GC gc, final XYPlot plot,
final ValueAxis axis, final Rectangle dataArea, final double start,
final double end) {
final int x1 = (int) axis.valueToJava2D(start, dataArea, plot
.getDomainAxisEdge());
final int x2 = (int) axis.valueToJava2D(end, dataArea, plot
.getDomainAxisEdge());
// TODO Take account of plot orientation
final Rectangle band = new Rectangle(x1, RectangleUtil
.getMinY(dataArea), x2 - x1, dataArea.height);
final Color color = plot.getColor(Plot.COLOR_DOMAIN_TICK_BAND);
if (color != null) {
gc.setBackground(color);
gc.fillRectangle(band);
}
}
/**
* Fills a band between two values on the range axis. This can be used to
* color bands between the grid lines.
*
* @param g2
* the graphics device.
* @param plot
* the plot.
* @param axis
* the range axis.
* @param dataArea
* the data area.
* @param start
* the start value.
* @param end
* the end value.
*/
public void fillRangeGridBand(final GC g2, final XYPlot plot,
final ValueAxis axis, final Rectangle dataArea, final double start,
final double end) {
final int y1 = (int) axis.valueToJava2D(start, dataArea, plot
.getRangeAxisEdge());
final int y2 = (int) axis.valueToJava2D(end, dataArea, plot
.getRangeAxisEdge());
// TODO: need to change the next line to take account of the plot
// orientation
final Rectangle band = new Rectangle(RectangleUtil.getMinX(dataArea),
y2, dataArea.width, y1 - y2);
final Color paint = plot.getColor(Plot.COLOR_RANGE_TICK_BAND);
if (paint != null) {
g2.setBackground(paint);
g2.fillRectangle(band);
}
}
/**
* Returns the lower and upper bounds (range) of the x-values in the
* specified dataset.
*
* @param dataset
* the dataset (<code>null</code> permitted).
*
* @return The range (<code>null</code> if the dataset is <code>null</code>
* or empty).
*/
public Range findDomainBounds(final XYDataset dataset) {
if (dataset != null) {
return DatasetUtilities.findDomainBounds(dataset, false);
} else {
return null;
}
}
/**
* Returns the range of values the renderer requires to display all the
* items from the specified dataset.
*
* @param dataset
* the dataset (<code>null</code> permitted).
*
* @return The range (<code>null</code> if the dataset is <code>null</code>
* or empty).
*/
public Range findRangeBounds(final XYDataset dataset) {
if (dataset != null) {
return DatasetUtilities.findRangeBounds(dataset, false);
} else {
return null;
}
}
/**
* Returns the base item label generator.
*
* @return The generator (possibly <code>null</code>).
*/
public XYItemLabelGenerator getBaseItemLabelGenerator() {
return this.baseItemLabelGenerator;
}
/**
* Returns the radius of the circle used for the default entity area when no
* area is specified.
*
* @return A radius.
*/
public int getDefaultEntityRadius() {
return this.defaultEntityRadius;
}
/**
* Returns the drawing assets from the plot.
*
* @return The drawing assets
* @see Plot#getDrawingAssets();
*/
public DrawingAssets getDrawingAssets() {
final Plot p = this.getPlot();
if (p != null) {
return p.getDrawingAssets();
}
return null;
}
/**
* Returns the drawing supplier from the plot.
*
* @return The drawing supplier (possibly <code>null</code>).
*/
public DrawingSupplier getDrawingSupplier() {
final Plot p = this.getPlot();
if (p != null) {
return p.getDrawingSupplier();
}
return null;
}
/**
* Returns the label generator for a data item. This implementation simply
* passes control to the {@link #getSeriesItemLabelGenerator(int)} method.
* If, for some reason, you want a different generator for individual items,
* you can override this method.
*
* @param row
* the row index (zero based).
* @param column
* the column index (zero based).
*
* @return The generator (possibly <code>null</code>).
*/
public XYItemLabelGenerator getItemLabelGenerator(final int row,
final int column) {
return this.getSeriesItemLabelGenerator(row);
}
/**
* Returns a default legend item for the specified series. Subclasses should
* override this method to generate customised items.
*
* @param datasetIndex
* the dataset index (zero-based).
* @param series
* the series index (zero-based).
*
* @return A legend item for the series.
*/
public LegendItem getLegendItem(final int datasetIndex, final int series) {
LegendItem result = null;
final XYPlot xyplot = this.getPlot();
if (xyplot != null) {
final XYDataset dataset = xyplot.getDataset(datasetIndex);
if (dataset != null) {
final String label = this.legendItemLabelGenerator
.generateLabel(dataset, series);
final String description = label;
String toolTipText = null;
if (this.getLegendItemToolTipGenerator() != null) {
toolTipText = this.getLegendItemToolTipGenerator()
.generateLabel(dataset, series);
}
String urlText = null;
if (this.getLegendItemURLGenerator() != null) {
urlText = this.getLegendItemURLGenerator().generateLabel(
dataset, series);
}
final Rectangle shape = this.lookupSeriesShape(series);
final Color paint = this.getSeriesPaint(series);
final Color outlinePaint = this
.lookupSeriesOutlinePaint(series);
final Stroke outlineStroke = this
.lookupSeriesOutlineStroke(series);
result = new LegendItem(label, description, toolTipText,
urlText, shape, paint, outlineStroke, outlinePaint);
result.setSeriesKey(dataset.getSeriesKey(series));
result.setSeriesKey(dataset.getSeriesKey(series));
result.setSeriesIndex(series);
result.setDataset(dataset);
result.setDatasetIndex(datasetIndex);
}
}
return result;
}
/**
* Returns the legend item label generator.
*
* @return The label generator (never <code>null</code>).
*/
public XYSeriesLabelGenerator getLegendItemLabelGenerator() {
return this.legendItemLabelGenerator;
}
/**
* Returns a (possibly empty) collection of legend items for the series that
* this renderer is responsible for drawing.
*
* @return The legend item collection (never <code>null</code>).
*/
public LegendItemCollection getLegendItems() {
if (this.plot == null) {
return new LegendItemCollection();
}
final LegendItemCollection result = new LegendItemCollection();
final int index = this.plot.getIndexOf(this);
final XYDataset dataset = this.plot.getDataset(index);
if (dataset != null) {
final int seriesCount = dataset.getSeriesCount();
for (int i = 0; i < seriesCount; i++) {
if (this.isSeriesVisibleInLegend(i)) {
final LegendItem item = this.getLegendItem(index, i);
if (item != null) {
result.add(item);
}
}
}
}
return result;
}
/**
* Returns the legend item tool tip generator.
*
* @return The tool tip generator (possibly <code>null</code>).
*/
public XYSeriesLabelGenerator getLegendItemToolTipGenerator() {
return this.legendItemToolTipGenerator;
}
/**
* Returns the legend item URL generator.
*
* @return The URL generator (possibly <code>null</code>).
*/
public XYSeriesLabelGenerator getLegendItemURLGenerator() {
return this.legendItemURLGenerator;
}
/**
* Returns the number of passes through the data that the renderer requires
* in order to draw the chart. Most charts will require a single pass, but
* some require two passes.
*
* @return The pass count.
*/
public int getPassCount() {
return 1;
}
/**
* Returns the plot that the renderer is assigned to.
*
* @return The plot.
*/
public XYPlot getPlot() {
return this.plot;
}
/**
* Returns the item label generator for a series.
*
* @param series
* the series index (zero based).
*
* @return The generator (possibly <code>null</code>).
*/
public XYItemLabelGenerator getSeriesItemLabelGenerator(final int series) {
// return the generator for ALL series, if there is one...
if (this.itemLabelGenerator != null) {
return this.itemLabelGenerator;
}
// otherwise look up the generator table
XYItemLabelGenerator generator = (XYItemLabelGenerator) this.itemLabelGeneratorList
.get(series);
if (generator == null) {
generator = this.baseItemLabelGenerator;
}
return generator;
}
// /**
// * Returns a (possibly empty) collection of legend items for the series
// that
// * this renderer is responsible for drawing.
// *
// * @return The legend item collection (never <code>null</code>).
// */
// public LegendItemCollection getLegendItems() {
// if (this.plot == null) {
// return new LegendItemCollection();
// }
// LegendItemCollection result = new LegendItemCollection();
// int index = this.plot.getIndexOf(this);
// XYDataset dataset = this.plot.getDataset(index);
// if (dataset != null) {
// int seriesCount = dataset.getSeriesCount();
// for (int i = 0; i < seriesCount; i++) {
// if (isSeriesVisibleInLegend(i)) {
// LegendItem item = getLegendItem(index, i);
// if (item != null) {
// result.add(item);
// }
// }
// }
//
// }
// return result;
// }
// /**
// * Returns a default legend item for the specified series. Subclasses
// should
// * override this method to generate customised items.
// *
// * @param datasetIndex
// * the dataset index (zero-based).
// * @param series
// * the series index (zero-based).
// *
// * @return A legend item for the series.
// */
// public LegendItem getLegendItem(int datasetIndex, int series) {
// LegendItem result = null;
// XYPlot xyplot = getPlot();
// if (xyplot != null) {
// XYDataset dataset = xyplot.getDataset(datasetIndex);
// if (dataset != null) {
// String label = this.legendItemLabelGenerator.generateLabel(
// dataset, series);
// String description = label;
// String toolTipText = null;
// if (getLegendItemToolTipGenerator() != null) {
// toolTipText = getLegendItemToolTipGenerator()
// .generateLabel(dataset, series);
// }
// String urlText = null;
// if (getLegendItemURLGenerator() != null) {
// urlText = getLegendItemURLGenerator().generateLabel(
// dataset, series);
// }
// Shape shape = getSeriesShape(series);
// Paint paint = getSeriesPaint(series);
// Paint outlinePaint = getSeriesOutlinePaint(series);
// Stroke outlineStroke = getSeriesOutlineStroke(series);
// result = new LegendItem(label, description, toolTipText,
// urlText, shape, paint, outlineStroke, outlinePaint);
// result.setSeriesIndex(series);
// result.setDatasetIndex(datasetIndex);
// }
// }
// return result;
// }
/**
* Initialises the renderer and returns a state object that should be passed
* to all subsequent calls to the drawItem() method.
* <P>
* This method will be called before the first item is rendered, giving the
* renderer an opportunity to initialise any state information it wants to
* maintain. The renderer can do nothing if it chooses.
*
* @param g2
* the graphics device.
* @param dataArea
* the area inside the axes.
* @param plot
* the plot.
* @param data
* the data.
* @param info
* an optional info collection object to return data back to the
* caller.
*
* @return The renderer state (never <code>null</code>).
*/
public XYItemRendererState initialise(final GC g2,
final Rectangle dataArea, final XYPlot plot, final XYDataset data,
final PlotRenderingInfo info) {
final XYItemRendererState state = new XYItemRendererState(info);
return state;
}
private Color lookupSeriesOutlinePaint(final int series) {
return StaticColorChecker.dublicateColor(SWT.COLOR_BLACK);//Display.getCurrent().getSystemColor(SWT.COLOR_BLACK);
}
private Stroke lookupSeriesOutlineStroke(final int series) {
return new Stroke(1);
}
/**
* Sets the base item label generator and sends a
* {@link RendererChangeEvent} to all registered listeners.
*
* @param generator
* the generator (<code>null</code> permitted).
*/
public void setBaseItemLabelGenerator(final XYItemLabelGenerator generator) {
this.baseItemLabelGenerator = generator;
this.notifyListeners(new RendererChangeEvent(this));
}
/**
* Sets the radius of the circle used for the default entity area when no
* area is specified.
*
* @param radius
* the radius.
*/
public void setDefaultEntityRadius(final int radius) {
this.defaultEntityRadius = radius;
}
/**
* Sets the item label generator for ALL series and sends a
* {@link RendererChangeEvent} to all registered listeners.
*
* @param generator
* the generator (<code>null</code> permitted).
*/
public void setItemLabelGenerator(final XYItemLabelGenerator generator) {
this.itemLabelGenerator = generator;
this.notifyListeners(new RendererChangeEvent(this));
}
/**
* Sets the legend item label generator.
*
* @param generator
* the generator (<code>null</code> not permitted).
*/
public void setLegendItemLabelGenerator(
final XYSeriesLabelGenerator generator) {
if (generator == null) {
throw new IllegalArgumentException("Null 'generator' argument.");
}
this.legendItemLabelGenerator = generator;
}
/**
* Sets the legend item tool tip generator.
*
* @param generator
* the generator (<code>null</code> permitted).
*/
public void setLegendItemToolTipGenerator(
final XYSeriesLabelGenerator generator) {
this.legendItemToolTipGenerator = generator;
}
/**
* Sets the plot that the renderer is assigned to.
*
* @param plot
* the plot.
*/
public void setPlot(final XYPlot plot) {
this.plot = plot;
}
/**
* Sets the item label generator for a series and sends a
* {@link RendererChangeEvent} to all registered listeners.
*
* @param series
* the series index (zero based).
* @param generator
* the generator (<code>null</code> permitted).
*/
public void setSeriesItemLabelGenerator(final int series,
final XYItemLabelGenerator generator) {
this.itemLabelGeneratorList.set(series, generator);
this.notifyListeners(new RendererChangeEvent(this));
}
}