I'd very much like to know as well. Thanks.adrsingle wrote:does the last version of Jfreechart has these classes
To Those Who Use JFreeChart for Dynamic Plotting/Large Sets
Re: Jfreechart - 1.0.3
FastXYPlot
I wrote a new version from the FastXYPlot renderFast method. My problem was that I have datasets that have lots of one pixel wide peaks and the previous version seemed to not draw them. This version isn't perfect but it seems to work relatively fast.
EDIT: Made a little change so that the last item is also rendered.
Code: Select all
/**
* Performs fast rendering. Skips overlapping pixels
*/
private void renderFast(ValueAxis xAxis, ValueAxis yAxis, XYDataset dataset,
int series, XYItemRenderer renderer, XYItemRendererState state,
int pass, Graphics2D g2, Rectangle2D dataArea,
PlotRenderingInfo info, CrosshairState crosshairState) {
// Contains the x-values for the current series
int count = dataset.getItemCount(series);
double[] xValues = new double[count];
for (int item = 0; item < count; item++)
xValues[item] = dataset.getXValue(series, item);
int firstItem = findItemIndex(xValues, xAxis.getLowerBound(), count);
int lastItem = findItemIndex(xValues, xAxis.getUpperBound(), count);
RectangleEdge domainEdge = getDomainAxisEdge();
RectangleEdge rangeEdge = getDomainAxisEdge();
double xAxisUpperBound = xAxis.getUpperBound();
double yAxisUpperBound = yAxis.getUpperBound();
int maxX = (int) Math.ceil(xAxis.valueToJava2D(xAxisUpperBound,dataArea,domainEdge)) + 1;
int maxY = (int) Math.ceil(yAxis.valueToJava2D(yAxisUpperBound,dataArea,rangeEdge)) + 1;
boolean drawnArray[][] = new boolean[maxX][maxY];
for (int item = firstItem; item <= lastItem; item++) {
double xValue = dataset.getXValue(series, item);
double yValue = dataset.getYValue(series,item);
double xCoordinate = xAxis.valueToJava2D(xValue, dataArea, domainEdge);
double yCoordinate = yAxis.valueToJava2D(yValue,dataArea,rangeEdge);
int roundedX = (int) Math.round(xCoordinate);
int roundedY = (int) Math.round(yCoordinate);
// This happens if we are doing double zooming, i.e. zooming
// before the previous zoom result has been drawn.
if (roundedX >= 0 && roundedY >= 0 && roundedX < maxX && roundedY < maxY) {
if (drawnArray[roundedX][roundedY] == true)
continue;
drawnArray[roundedX][roundedY] = true;
}
renderer.drawItem(g2, state, dataArea, info, this, xAxis,
yAxis, dataset, series, item, crosshairState, pass);
}
}
private int findItemIndex(double[] values, double value, int maxCount) {
int itemIndex = Arrays.binarySearch(values, value);
if (itemIndex < 0)
itemIndex = -itemIndex - 1;
itemIndex = Math.max(0, Math.min(itemIndex, maxCount - 1));
return itemIndex;
}
EDIT: Made a little change so that the last item is also rendered.
-
- Posts: 6
- Joined: Wed Dec 27, 2006 1:03 am
Here is another (much simplified) version. Renders my 250,000 data points in under a second. In fact, the speed is nearly constant, no matter what the size of the dataset, or how much data is displayed when zoomed in or out. That's simply because it samples the data points when their size exceeds that of the data area. As before, this will work with any renderer.
Code: Select all
package com.jsystemtrader.chart;
import org.jfree.chart.axis.*;
import org.jfree.chart.plot.*;
import org.jfree.chart.renderer.xy.*;
import org.jfree.data.general.*;
import org.jfree.data.xy.*;
import java.awt.*;
import java.awt.geom.*;
import java.util.*;
class FastXYPlot extends XYPlot {
public FastXYPlot(XYDataset dataset, ValueAxis domainAxis, ValueAxis rangeAxis, XYItemRenderer renderer) {
super(dataset, domainAxis, rangeAxis, renderer);
}
private void renderFast(ValueAxis xAxis, ValueAxis yAxis, XYDataset dataset, int series, XYItemRenderer renderer,
XYItemRendererState state, int pass, Graphics2D g2, Rectangle2D dataArea,
PlotRenderingInfo info, CrosshairState crosshairState) {
int count = dataset.getItemCount(series);
double[] xValues = new double[count];
for (int item = 0; item < count; item++) {
xValues[item] = dataset.getXValue(series, item);
}
int firstItem = findItemIndex(xValues, xAxis.getLowerBound(), count);
int lastItem = findItemIndex(xValues, xAxis.getUpperBound(), count);
double width = Math.max(dataArea.getWidth(), 1);
int itemsPerPixel = (int) Math.max(1, (lastItem - firstItem) / width);
for (int item = firstItem; item < lastItem; item += itemsPerPixel) {
renderer.drawItem(g2, state, dataArea, info, this, xAxis, yAxis, dataset, series, item, crosshairState, pass);
}
}
public boolean render(Graphics2D g2, Rectangle2D dataArea, int index, PlotRenderingInfo info,
CrosshairState crosshairState) {
boolean foundData = false;
XYDataset dataset = getDataset(index);
if (!DatasetUtilities.isEmptyOrNull(dataset)) {
foundData = true;
ValueAxis xAxis = getDomainAxisForDataset(index);
ValueAxis yAxis = getRangeAxisForDataset(index);
XYItemRenderer renderer = getRenderer(index);
if (renderer == null) {
renderer = getRenderer();
}
XYItemRendererState state = renderer.initialise(g2, dataArea, this, dataset, info);
int passCount = renderer.getPassCount();
SeriesRenderingOrder seriesOrder = getSeriesRenderingOrder();
if (seriesOrder == SeriesRenderingOrder.REVERSE) {
//render series in reverse order
for (int pass = 0; pass < passCount; pass++) {
int seriesCount = dataset.getSeriesCount();
for (int series = seriesCount - 1; series >= 0; series--) {
renderFast(xAxis, yAxis, dataset, series, renderer, state, pass, g2, dataArea, info,
crosshairState);
}
}
} else {
//render series in forward order
for (int pass = 0; pass < passCount; pass++) {
int seriesCount = dataset.getSeriesCount();
for (int series = 0; series < seriesCount; series++) {
renderFast(xAxis, yAxis, dataset, series, renderer, state, pass, g2, dataArea, info,
crosshairState);
}
}
}
}
return foundData;
}
private int findItemIndex(double[] values, double value, int maxCount) {
int itemIndex = Arrays.binarySearch(values, value);
if (itemIndex < 0)
itemIndex = -itemIndex - 1;
itemIndex = Math.max(0, Math.min(itemIndex, maxCount));
return itemIndex;
}
}
-
- Posts: 6
- Joined: Wed Dec 27, 2006 1:03 am
I just noticed that version 1.0.6 has been released with the performance improvments, so I downloaded and tried it. Nice job with the state.getProcessVisibleItemsOnly() in XYPlot, DG! There is a very noticeble speed up when zoomed-in. However, it still takes an eternity when zoomed out: about 50 seconds to render my data set. As I mentioned above, it takes less than a second with the FastXYPlot which I posted. Granted, it's a hack, but I believe it could be turned into a real thing which could be a permanent part of JFreeChart.
The key idea is very simple: since there are only so many pixels in the data area, you can't display a large data set without the pixel overlaping. As it is in the latest release, XYPlot still calls renderer.drawItem() for every single visible item, even if the items overlap (that is, rendered in exatly same pixels). What I suggest can be done is that only the non-overlapping items can be calculated in advance, and then passed to the renderer. That way, the resulting plot would look exactly the same as if rendered with XYPlot in release 1.0.6, but it would take no more than a second or so to render, no matter what the size of the dataset and no matter what the zoom is.
The key idea is very simple: since there are only so many pixels in the data area, you can't display a large data set without the pixel overlaping. As it is in the latest release, XYPlot still calls renderer.drawItem() for every single visible item, even if the items overlap (that is, rendered in exatly same pixels). What I suggest can be done is that only the non-overlapping items can be calculated in advance, and then passed to the renderer. That way, the resulting plot would look exactly the same as if rendered with XYPlot in release 1.0.6, but it would take no more than a second or so to render, no matter what the size of the dataset and no matter what the zoom is.
-
- Posts: 6
- Joined: Wed Dec 27, 2006 1:03 am
Here is the latest and greatest version that I have. It takes advantage of JFreeChart 1.0.6 improvements and also borrows from SamiLakka's ideas. It renders much more precisely, not distinguishable from a standard XYPlot, but much much faster. Hope this makes it into one of the next releases.
Code: Select all
package com.jsystemtrader.chart;
import org.jfree.chart.axis.*;
import org.jfree.chart.plot.*;
import org.jfree.chart.renderer.*;
import org.jfree.chart.renderer.xy.*;
import org.jfree.data.general.*;
import org.jfree.data.xy.*;
import org.jfree.ui.*;
import java.awt.*;
import java.awt.geom.*;
import java.util.*;
/**
* Performs fast rendering of large datasets in nearly constant time.
*/
class FastXYPlot extends XYPlot {
private final HashSet<Integer> renderedPixels = new HashSet<Integer>();
public FastXYPlot(XYDataset dataset, ValueAxis domainAxis, ValueAxis rangeAxis, XYItemRenderer renderer) {
super(dataset, domainAxis, rangeAxis, renderer);
}
/**
* Determines if the item is to be rendered in the area of the plot where one of the previous
* items has already been rendered.
*/
private boolean hasRendered(XYDataset dataset, ValueAxis xAxis, ValueAxis yAxis, RectangleEdge domainEdge,
RectangleEdge rangeEdge, Rectangle2D dataArea, int series, int item) {
boolean hasRendered = true;
int width = (int) dataArea.getWidth();
double xValue = dataset.getXValue(series, item);
double yValue = dataset.getYValue(series, item);
int x = (int) xAxis.valueToJava2D(xValue, dataArea, domainEdge);
int y = (int) yAxis.valueToJava2D(yValue, dataArea, rangeEdge);
int itemKey = x + width * y;
if (!renderedPixels.contains(itemKey)) {
renderedPixels.add(itemKey);
hasRendered = false;
}
return hasRendered;
}
@Override
public boolean render(Graphics2D g2,
Rectangle2D dataArea,
int index,
PlotRenderingInfo info,
CrosshairState crosshairState) {
boolean foundData = false;
XYDataset dataset = getDataset(index);
if (!DatasetUtilities.isEmptyOrNull(dataset)) {
foundData = true;
ValueAxis xAxis = getDomainAxisForDataset(index);
ValueAxis yAxis = getRangeAxisForDataset(index);
XYItemRenderer renderer = getRenderer(index);
if (renderer == null) {
renderer = getRenderer();
if (renderer == null) { // no default renderer available
return foundData;
}
}
XYItemRendererState state = renderer.initialise(g2, dataArea, this,
dataset, info);
int passCount = renderer.getPassCount();
renderedPixels.clear();
RectangleEdge domainEdge = getDomainAxisEdge();
RectangleEdge rangeEdge = getDomainAxisEdge();
SeriesRenderingOrder seriesOrder = getSeriesRenderingOrder();
if (seriesOrder == SeriesRenderingOrder.REVERSE) {
//render series in reverse order
for (int pass = 0; pass < passCount; pass++) {
int seriesCount = dataset.getSeriesCount();
for (int series = seriesCount - 1; series >= 0; series--) {
int firstItem = 0;
int lastItem = dataset.getItemCount(series) - 1;
if (lastItem == -1) {
continue;
}
if (state.getProcessVisibleItemsOnly()) {
int[] itemBounds = RendererUtilities.findLiveItems(
dataset, series, xAxis.getLowerBound(),
xAxis.getUpperBound());
firstItem = itemBounds[0];
lastItem = itemBounds[1];
}
for (int item = firstItem; item <= lastItem; item++) {
if (!hasRendered(dataset, xAxis, yAxis, domainEdge, rangeEdge, dataArea, series, item)) {
renderer.drawItem(g2, state, dataArea, info,
this, xAxis, yAxis, dataset, series, item,
crosshairState, pass);
}
}
}
}
} else {
//render series in forward order
for (int pass = 0; pass < passCount; pass++) {
int seriesCount = dataset.getSeriesCount();
for (int series = 0; series < seriesCount; series++) {
int firstItem = 0;
int lastItem = dataset.getItemCount(series) - 1;
if (state.getProcessVisibleItemsOnly()) {
int[] itemBounds = RendererUtilities.findLiveItems(
dataset, series, xAxis.getLowerBound(),
xAxis.getUpperBound());
firstItem = itemBounds[0];
lastItem = itemBounds[1];
}
for (int item = firstItem; item <= lastItem; item++) {
if (!hasRendered(dataset, xAxis, yAxis, domainEdge, rangeEdge, dataArea, series, item)) {
renderer.drawItem(g2, state, dataArea, info,
this, xAxis, yAxis, dataset, series, item,
crosshairState, pass);
}
}
}
}
}
}
return foundData;
}
}
Good to see others having, and solving, problems with massive data sets. My requirements are for 500K datasets in XYPlots drawn in ~5 seconds on a P4 3GHz box. I achieved this in 2005 (1.0.1 days), with the help of the folks in this thread: http://www.jfree.org/phpBB2/viewtopic.p ... 0&start=30.
We modified drawItem() in StandardXYItemRenderer to do just what you mentioned; only execute a g2.draw() if the draw would not be over top of a previous one. Sadly these efforts have not made it into any subsequent release (even 1.2.0-pre1). I just upgraded to 1.0.6 and re-implemented the changes to StandardXYItemRenderer.
The changes are minor and I did not extend the class on account of visibility constraints.
For an overview, here is the diff to revision 1 of https://jfreechart.svn.sourceforge.net/ ... jfreechart
I have not tried your solution nonlinear5, I expect to soon though.
Comments?
We modified drawItem() in StandardXYItemRenderer to do just what you mentioned; only execute a g2.draw() if the draw would not be over top of a previous one. Sadly these efforts have not made it into any subsequent release (even 1.2.0-pre1). I just upgraded to 1.0.6 and re-implemented the changes to StandardXYItemRenderer.
The changes are minor and I did not extend the class on account of visibility constraints.
Code: Select all
package org.jfree.chart.renderer.xy;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import org.jfree.chart.LegendItem;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.entity.EntityCollection;
import org.jfree.chart.event.RendererChangeEvent;
import org.jfree.chart.labels.XYToolTipGenerator;
import org.jfree.chart.plot.CrosshairState;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.PlotRenderingInfo;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.urls.XYURLGenerator;
import org.jfree.data.xy.XYDataset;
import org.jfree.io.SerialUtilities;
import org.jfree.ui.RectangleEdge;
import org.jfree.util.BooleanList;
import org.jfree.util.BooleanUtilities;
import org.jfree.util.ObjectUtilities;
import org.jfree.util.PublicCloneable;
import org.jfree.util.ShapeUtilities;
import org.jfree.util.UnitType;
/**
* Standard item renderer for an {@link XYPlot}. This class can draw (a)
* shapes at each point, or (b) lines between points, or (c) both shapes and
* lines.
* <P>
* This renderer has been retained for historical reasons and, in general, you
* should use the {@link XYLineAndShapeRenderer} class instead.
*/
public class StandardXYItemRenderer extends AbstractXYItemRenderer
implements XYItemRenderer, Cloneable, PublicCloneable, Serializable {
/** For serialization. */
private static final long serialVersionUID = -3271351259436865995L;
/** Constant for the type of rendering (shapes only). */
public static final int SHAPES = 1;
/** Constant for the type of rendering (lines only). */
public static final int LINES = 2;
/** Constant for the type of rendering (shapes and lines). */
public static final int SHAPES_AND_LINES = SHAPES | LINES;
/** Constant for the type of rendering (images only). */
public static final int IMAGES = 4;
/** Constant for the type of rendering (discontinuous lines). */
public static final int DISCONTINUOUS = 8;
/** Constant for the type of rendering (discontinuous lines). */
public static final int DISCONTINUOUS_LINES = LINES | DISCONTINUOUS;
/** A flag indicating whether or not shapes are drawn at each XY point. */
private boolean baseShapesVisible;
/** A flag indicating whether or not lines are drawn between XY points. */
private boolean plotLines;
/** A flag indicating whether or not images are drawn between XY points. */
private boolean plotImages;
/** A flag controlling whether or not discontinuous lines are used. */
private boolean plotDiscontinuous;
/** Specifies how the gap threshold value is interpreted. */
private UnitType gapThresholdType = UnitType.RELATIVE;
/** Threshold for deciding when to discontinue a line. */
private double gapThreshold = 1.0;
/** A flag that controls whether or not shapes are filled for ALL series. */
private Boolean shapesFilled;
/**
* A table of flags that control (per series) whether or not shapes are
* filled.
*/
private BooleanList seriesShapesFilled;
/** The default value returned by the getShapeFilled() method. */
private boolean baseShapesFilled;
/**
* A flag that controls whether or not each series is drawn as a single
* path.
*/
private boolean drawSeriesLineAsPath;
/**
* The shape that is used to represent a line in the legend.
* This should never be set to <code>null</code>.
*/
private transient Shape legendLine;
/** A counter to prevent unnecessary Graphics2D.draw() events in drawItem() */
private int previousDrawnItem;
private int skippedDrawItems;
/**
* Constructs a new renderer.
*/
public StandardXYItemRenderer() {
this(LINES, null);
}
/**
* Constructs a new renderer. To specify the type of renderer, use one of
* the constants: {@link #SHAPES}, {@link #LINES} or
* {@link #SHAPES_AND_LINES}.
*
* @param type the type.
*/
public StandardXYItemRenderer(int type) {
this(type, null);
}
/**
* Constructs a new renderer. To specify the type of renderer, use one of
* the constants: {@link #SHAPES}, {@link #LINES} or
* {@link #SHAPES_AND_LINES}.
*
* @param type the type of renderer.
* @param toolTipGenerator the item label generator (<code>null</code>
* permitted).
*/
public StandardXYItemRenderer(int type,
XYToolTipGenerator toolTipGenerator) {
this(type, toolTipGenerator, null);
}
/**
* Constructs a new renderer. To specify the type of renderer, use one of
* the constants: {@link #SHAPES}, {@link #LINES} or
* {@link #SHAPES_AND_LINES}.
*
* @param type the type of renderer.
* @param toolTipGenerator the item label generator (<code>null</code>
* permitted).
* @param urlGenerator the URL generator.
*/
public StandardXYItemRenderer(int type,
XYToolTipGenerator toolTipGenerator,
XYURLGenerator urlGenerator) {
super();
setBaseToolTipGenerator(toolTipGenerator);
setURLGenerator(urlGenerator);
if ((type & SHAPES) != 0) {
this.baseShapesVisible = true;
}
if ((type & LINES) != 0) {
this.plotLines = true;
}
if ((type & IMAGES) != 0) {
this.plotImages = true;
}
if ((type & DISCONTINUOUS) != 0) {
this.plotDiscontinuous = true;
}
this.shapesFilled = null;
this.seriesShapesFilled = new BooleanList();
this.baseShapesFilled = true;
this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
this.drawSeriesLineAsPath = false;
}
/**
* Returns true if shapes are being plotted by the renderer.
*
* @return <code>true</code> if shapes are being plotted by the renderer.
*
* @see #setBaseShapesVisible
*/
public boolean getBaseShapesVisible() {
return this.baseShapesVisible;
}
/**
* Sets the flag that controls whether or not a shape is plotted at each
* data point.
*
* @param flag the flag.
*
* @see #getBaseShapesVisible
*/
public void setBaseShapesVisible(boolean flag) {
if (this.baseShapesVisible != flag) {
this.baseShapesVisible = flag;
notifyListeners(new RendererChangeEvent(this));
}
}
// SHAPES FILLED
/**
* Returns the flag used to control whether or not the shape for an item is
* filled.
* <p>
* The default implementation passes control to the
* <code>getSeriesShapesFilled</code> method. You can override this method
* if you require different behaviour.
*
* @param series the series index (zero-based).
* @param item the item index (zero-based).
*
* @return A boolean.
*
* @see #getSeriesShapesFilled(int)
*/
public boolean getItemShapeFilled(int series, int item) {
// return the overall setting, if there is one...
if (this.shapesFilled != null) {
return this.shapesFilled.booleanValue();
}
// otherwise look up the paint table
Boolean flag = this.seriesShapesFilled.getBoolean(series);
if (flag != null) {
return flag.booleanValue();
}
else {
return this.baseShapesFilled;
}
}
/**
* Returns the override flag that controls whether or not shapes are filled
* for ALL series.
*
* @return The flag (possibly <code>null</code>).
*
* @since 1.0.5
*/
public Boolean getShapesFilled() {
return this.shapesFilled;
}
/**
* Sets the 'shapes filled' for ALL series.
*
* @param filled the flag.
*
* @see #setShapesFilled(Boolean)
*/
public void setShapesFilled(boolean filled) {
// here we use BooleanUtilities to remain compatible with JDKs < 1.4
setShapesFilled(BooleanUtilities.valueOf(filled));
}
/**
* Sets the override flag that controls whether or not shapes are filled
* for ALL series and sends a {@link RendererChangeEvent} to all registered
* listeners.
*
* @param filled the flag (<code>null</code> permitted).
*
* @see #setShapesFilled(boolean)
*/
public void setShapesFilled(Boolean filled) {
this.shapesFilled = filled;
fireChangeEvent();
}
/**
* Returns the flag used to control whether or not the shapes for a series
* are filled.
*
* @param series the series index (zero-based).
*
* @return A boolean.
*/
public Boolean getSeriesShapesFilled(int series) {
return this.seriesShapesFilled.getBoolean(series);
}
/**
* Sets the 'shapes filled' flag for a series.
*
* @param series the series index (zero-based).
* @param flag the flag.
*
* @see #getSeriesShapesFilled(int)
*/
public void setSeriesShapesFilled(int series, Boolean flag) {
this.seriesShapesFilled.setBoolean(series, flag);
fireChangeEvent();
}
/**
* Returns the base 'shape filled' attribute.
*
* @return The base flag.
*
* @see #setBaseShapesFilled(boolean)
*/
public boolean getBaseShapesFilled() {
return this.baseShapesFilled;
}
/**
* Sets the base 'shapes filled' flag.
*
* @param flag the flag.
*
* @see #getBaseShapesFilled()
*/
public void setBaseShapesFilled(boolean flag) {
this.baseShapesFilled = flag;
}
/**
* Returns true if lines are being plotted by the renderer.
*
* @return <code>true</code> if lines are being plotted by the renderer.
*
* @see #setPlotLines(boolean)
*/
public boolean getPlotLines() {
return this.plotLines;
}
/**
* Sets the flag that controls whether or not a line is plotted between
* each data point.
*
* @param flag the flag.
*
* @see #getPlotLines()
*/
public void setPlotLines(boolean flag) {
if (this.plotLines != flag) {
this.plotLines = flag;
notifyListeners(new RendererChangeEvent(this));
}
}
/**
* Returns the gap threshold type (relative or absolute).
*
* @return The type.
*
* @see #setGapThresholdType(UnitType)
*/
public UnitType getGapThresholdType() {
return this.gapThresholdType;
}
/**
* Sets the gap threshold type.
*
* @param thresholdType the type (<code>null</code> not permitted).
*
* @see #getGapThresholdType()
*/
public void setGapThresholdType(UnitType thresholdType) {
if (thresholdType == null) {
throw new IllegalArgumentException(
"Null 'thresholdType' argument.");
}
this.gapThresholdType = thresholdType;
notifyListeners(new RendererChangeEvent(this));
}
/**
* Returns the gap threshold for discontinuous lines.
*
* @return The gap threshold.
*
* @see #setGapThreshold(double)
*/
public double getGapThreshold() {
return this.gapThreshold;
}
/**
* Sets the gap threshold for discontinuous lines.
*
* @param t the threshold.
*
* @see #getGapThreshold()
*/
public void setGapThreshold(double t) {
this.gapThreshold = t;
notifyListeners(new RendererChangeEvent(this));
}
/**
* Returns true if images are being plotted by the renderer.
*
* @return <code>true</code> if images are being plotted by the renderer.
*
* @see #setPlotImages(boolean)
*/
public boolean getPlotImages() {
return this.plotImages;
}
/**
* Sets the flag that controls whether or not an image is drawn at each
* data point.
*
* @param flag the flag.
*
* @see #getPlotImages()
*/
public void setPlotImages(boolean flag) {
if (this.plotImages != flag) {
this.plotImages = flag;
notifyListeners(new RendererChangeEvent(this));
}
}
/**
* Returns a flag that controls whether or not the renderer shows
* discontinuous lines.
*
* @return <code>true</code> if lines should be discontinuous.
*/
public boolean getPlotDiscontinuous() {
return this.plotDiscontinuous;
}
/**
* Sets the flag that controls whether or not the renderer shows
* discontinuous lines, and sends a {@link RendererChangeEvent} to all
* registered listeners.
*
* @param flag the new flag value.
*
* @since 1.0.5
*/
public void setPlotDiscontinuous(boolean flag) {
if (this.plotDiscontinuous != flag) {
this.plotDiscontinuous = flag;
fireChangeEvent();
}
}
/**
* Returns a flag that controls whether or not each series is drawn as a
* single path.
*
* @return A boolean.
*
* @see #setDrawSeriesLineAsPath(boolean)
*/
public boolean getDrawSeriesLineAsPath() {
return this.drawSeriesLineAsPath;
}
/**
* Sets the flag that controls whether or not each series is drawn as a
* single path.
*
* @param flag the flag.
*
* @see #getDrawSeriesLineAsPath()
*/
public void setDrawSeriesLineAsPath(boolean flag) {
this.drawSeriesLineAsPath = flag;
}
/**
* Returns the shape used to represent a line in the legend.
*
* @return The legend line (never <code>null</code>).
*
* @see #setLegendLine(Shape)
*/
public Shape getLegendLine() {
return this.legendLine;
}
/**
* Sets the shape used as a line in each legend item and sends a
* {@link RendererChangeEvent} to all registered listeners.
*
* @param line the line (<code>null</code> not permitted).
*
* @see #getLegendLine()
*/
public void setLegendLine(Shape line) {
if (line == null) {
throw new IllegalArgumentException("Null 'line' argument.");
}
this.legendLine = line;
notifyListeners(new RendererChangeEvent(this));
}
/**
* Returns a legend item for a series.
*
* @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) {
XYPlot plot = getPlot();
if (plot == null) {
return null;
}
LegendItem result = null;
XYDataset dataset = plot.getDataset(datasetIndex);
if (dataset != null) {
if (getItemVisible(series, 0)) {
String label = getLegendItemLabelGenerator().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 = lookupSeriesShape(series);
boolean shapeFilled = getItemShapeFilled(series, 0);
Paint paint = lookupSeriesPaint(series);
Paint linePaint = paint;
Stroke lineStroke = lookupSeriesStroke(series);
result = new LegendItem(label, description, toolTipText,
urlText, this.baseShapesVisible, shape, shapeFilled,
paint, !shapeFilled, paint, lineStroke,
this.plotLines, this.legendLine, lineStroke, linePaint);
result.setDataset(dataset);
result.setDatasetIndex(datasetIndex);
result.setSeriesKey(dataset.getSeriesKey(series));
result.setSeriesIndex(series);
}
}
return result;
}
/**
* Records the state for the renderer. This is used to preserve state
* information between calls to the drawItem() method for a single chart
* drawing.
*/
public static class State extends XYItemRendererState {
/** The path for the current series. */
public GeneralPath seriesPath;
/** The series index. */
private int seriesIndex;
/**
* A flag that indicates if the last (x, y) point was 'good'
* (non-null).
*/
private boolean lastPointGood;
/**
* Creates a new state instance.
*
* @param info the plot rendering info.
*/
public State(PlotRenderingInfo info) {
super(info);
}
/**
* Returns a flag that indicates if the last point drawn (in the
* current series) was 'good' (non-null).
*
* @return A boolean.
*/
public boolean isLastPointGood() {
return this.lastPointGood;
}
/**
* Sets a flag that indicates if the last point drawn (in the current
* series) was 'good' (non-null).
*
* @param good the flag.
*/
public void setLastPointGood(boolean good) {
this.lastPointGood = good;
}
/**
* Returns the series index for the current path.
*
* @return The series index for the current path.
*/
public int getSeriesIndex() {
return this.seriesIndex;
}
/**
* Sets the series index for the current path.
*
* @param index the index.
*/
public void setSeriesIndex(int index) {
this.seriesIndex = index;
}
}
/**
* Initialises the renderer.
* <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.
*/
public XYItemRendererState initialise(Graphics2D g2,
Rectangle2D dataArea,
XYPlot plot,
XYDataset data,
PlotRenderingInfo info) {
State state = new State(info);
state.seriesPath = new GeneralPath();
state.seriesIndex = -1;
return state;
}
/**
* Draws the visual representation of a single data item.
*
* @param g2 the graphics device.
* @param state the renderer state.
* @param dataArea the area within which the data is being drawn.
* @param info collects information about the drawing.
* @param plot the plot (can be used to obtain standard color information
* etc).
* @param domainAxis the domain axis.
* @param rangeAxis the range axis.
* @param dataset the dataset.
* @param series the series index (zero-based).
* @param item the item index (zero-based).
* @param crosshairState crosshair information for the plot
* (<code>null</code> permitted).
* @param pass the pass index.
*/
public void drawItem(Graphics2D g2,
XYItemRendererState state,
Rectangle2D dataArea,
PlotRenderingInfo info,
XYPlot plot,
ValueAxis domainAxis,
ValueAxis rangeAxis,
XYDataset dataset,
int series,
int item,
CrosshairState crosshairState,
int pass) {
boolean itemVisible = getItemVisible(series, item);
// setup for collecting optional entity info...
boolean bAddEntity=false;
Shape entityArea = null;
EntityCollection entities = null;
if (info != null) {
entities = info.getOwner().getEntityCollection();
}
PlotOrientation orientation = plot.getOrientation();
Paint paint = getItemPaint(series, item);
Stroke seriesStroke = getItemStroke(series, item);
g2.setPaint(paint);
g2.setStroke(seriesStroke);
// get the data point...
double x1 = dataset.getXValue(series, item);
double y1 = dataset.getYValue(series, item);
if (Double.isNaN(x1) || Double.isNaN(y1)) {
itemVisible = false;
}
RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
if (getPlotLines()) {
if (this.drawSeriesLineAsPath) {
State s = (State) state;
if (s.getSeriesIndex() != series) {
// we are starting a new series path
s.seriesPath.reset();
s.lastPointGood = false;
s.setSeriesIndex(series);
}
if(0 == item) {
this.previousDrawnItem = 0;
}
// update path to reflect latest point
if (itemVisible && !Double.isNaN(transX1)
&& !Double.isNaN(transY1)) {
float x = (float) transX1;
float y = (float) transY1;
if (orientation == PlotOrientation.HORIZONTAL) {
x = (float) transY1;
y = (float) transX1;
}
if (s.isLastPointGood()) {
// TODO: check threshold
s.seriesPath.lineTo(x, y);
}
else {
s.seriesPath.moveTo(x, y);
}
s.setLastPointGood(true);
}
else {
s.setLastPointGood(false);
}
if (item == dataset.getItemCount(series) - 1) {
if (s.seriesIndex == series) {
// draw path
g2.setStroke(lookupSeriesStroke(series));
g2.setPaint(lookupSeriesPaint(series));
g2.draw(s.seriesPath);
}
}
}
else if (item != 0 && itemVisible) {
// get the previous data point...
int idx = item - this.previousDrawnItem;
if(idx < 0)
{
//there exists some confusion; do not draw anything and reset state
this.previousDrawnItem = 0;
}
else
{
// get the previous data point...
double x0 = dataset.getXValue(series, idx);
double y0 = dataset.getYValue(series, idx);
if (!Double.isNaN(x0) && !Double.isNaN(y0)) {
boolean drawLine = true;
if (getPlotDiscontinuous()) {
// only draw a line if the gap between the current and
// previous data point is within the threshold
int numX = dataset.getItemCount(series);
double minX = dataset.getXValue(series, 0);
double maxX = dataset.getXValue(series, numX - 1);
if (this.gapThresholdType == UnitType.ABSOLUTE) {
drawLine = Math.abs(x1 - x0) <= this.gapThreshold;
}
else {
drawLine = Math.abs(x1 - x0) <= ((maxX - minX)
/ numX * getGapThreshold());
}
}
if (drawLine) {
double transX0 = domainAxis.valueToJava2D(x0, dataArea,
xAxisLocation);
double transY0 = rangeAxis.valueToJava2D(y0, dataArea,
yAxisLocation);
// only draw if we have good values
if (Double.isNaN(transX0) || Double.isNaN(transY0)
|| Double.isNaN(transX1) || Double.isNaN(transY1)) {
return;
}
// Only draw line if it is more than a pixel away from the previous one
if((0 == this.previousDrawnItem || transX1 - transX0 > 2 || transX1 - transX0 < -2 || transY1 - transY0 > 2
|| transY1 - transY0 < -2)) {
this.previousDrawnItem = 1;
if (orientation == PlotOrientation.HORIZONTAL) {
state.workingLine.setLine(transY0, transX0,
transY1, transX1);
}
else if (orientation == PlotOrientation.VERTICAL) {
state.workingLine.setLine(transX0, transY0,
transX1, transY1);
}
if (state.workingLine.intersects(dataArea)) {
g2.draw(state.workingLine);
}
}
else {
//Increase counter for the previous drawn item.
previousDrawnItem++;
bAddEntity=false;
this.skippedDrawItems++;
}
}
}
}
}
}
// we needed to get this far even for invisible items, to ensure that
// seriesPath updates happened, but now there is nothing more we need
// to do for non-visible items...
if (!itemVisible) {
return;
}
if (getBaseShapesVisible()) {
Shape shape = getItemShape(series, item);
if (orientation == PlotOrientation.HORIZONTAL) {
shape = ShapeUtilities.createTranslatedShape(shape, transY1,
transX1);
}
else if (orientation == PlotOrientation.VERTICAL) {
shape = ShapeUtilities.createTranslatedShape(shape, transX1,
transY1);
}
if (shape.intersects(dataArea)) {
bAddEntity=true;
if (getItemShapeFilled(series, item)) {
g2.fill(shape);
}
else {
g2.draw(shape);
}
}
entityArea = shape;
}
if (getPlotImages()) {
Image image = getImage(plot, series, item, transX1, transY1);
if (image != null) {
Point hotspot = getImageHotspot(plot, series, item, transX1,
transY1, image);
g2.drawImage(image, (int) (transX1 - hotspot.getX()),
(int) (transY1 - hotspot.getY()), null);
entityArea = new Rectangle2D.Double(transX1 - hotspot.getX(),
transY1 - hotspot.getY(), image.getWidth(null),
image.getHeight(null));
}
}
double xx = transX1;
double yy = transY1;
if (orientation == PlotOrientation.HORIZONTAL) {
xx = transY1;
yy = transX1;
}
// draw the item label if there is one...
if (isItemLabelVisible(series, item)) {
drawItemLabel(g2, orientation, dataset, series, item, xx, yy,
(y1 < 0.0));
}
int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex,
rangeAxisIndex, transX1, transY1, orientation);
// add an entity for the item...
if (entities != null && dataArea.contains(xx, yy) && bAddEntity) {
addEntity(entities, entityArea, dataset, series, item, xx, yy);
}
}
/**
* Tests this renderer for equality with another object.
*
* @param obj the object (<code>null</code> permitted).
*
* @return A boolean.
*/
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof StandardXYItemRenderer)) {
return false;
}
StandardXYItemRenderer that = (StandardXYItemRenderer) obj;
if (this.baseShapesVisible != that.baseShapesVisible) {
return false;
}
if (this.plotLines != that.plotLines) {
return false;
}
if (this.plotImages != that.plotImages) {
return false;
}
if (this.plotDiscontinuous != that.plotDiscontinuous) {
return false;
}
if (this.gapThresholdType != that.gapThresholdType) {
return false;
}
if (this.gapThreshold != that.gapThreshold) {
return false;
}
if (!ObjectUtilities.equal(this.shapesFilled, that.shapesFilled)) {
return false;
}
if (!this.seriesShapesFilled.equals(that.seriesShapesFilled)) {
return false;
}
if (this.baseShapesFilled != that.baseShapesFilled) {
return false;
}
if (this.drawSeriesLineAsPath != that.drawSeriesLineAsPath) {
return false;
}
if (!ShapeUtilities.equal(this.legendLine, that.legendLine)) {
return false;
}
return super.equals(obj);
}
/**
* Returns a clone of the renderer.
*
* @return A clone.
*
* @throws CloneNotSupportedException if the renderer cannot be cloned.
*/
public Object clone() throws CloneNotSupportedException {
StandardXYItemRenderer clone = (StandardXYItemRenderer) super.clone();
clone.seriesShapesFilled
= (BooleanList) this.seriesShapesFilled.clone();
clone.legendLine = ShapeUtilities.clone(this.legendLine);
return clone;
}
////////////////////////////////////////////////////////////////////////////
// PROTECTED METHODS
// These provide the opportunity to subclass the standard renderer and
// create custom effects.
////////////////////////////////////////////////////////////////////////////
/**
* Returns the image used to draw a single data item.
*
* @param plot the plot (can be used to obtain standard color information
* etc).
* @param series the series index.
* @param item the item index.
* @param x the x value of the item.
* @param y the y value of the item.
*
* @return The image.
*
* @see #getPlotImages()
*/
protected Image getImage(Plot plot, int series, int item,
double x, double y) {
// this method must be overridden if you want to display images
return null;
}
/**
* Returns the hotspot of the image used to draw a single data item.
* The hotspot is the point relative to the top left of the image
* that should indicate the data item. The default is the center of the
* image.
*
* @param plot the plot (can be used to obtain standard color information
* etc).
* @param image the image (can be used to get size information about the
* image)
* @param series the series index
* @param item the item index
* @param x the x value of the item
* @param y the y value of the item
*
* @return The hotspot used to draw the data item.
*/
protected Point getImageHotspot(Plot plot, int series, int item,
double x, double y, Image image) {
int height = image.getHeight(null);
int width = image.getWidth(null);
return new Point(width / 2, height / 2);
}
protected void finalize() throws Throwable
{
System.out.println("skipped draw items: "+this.skippedDrawItems);
}
/**
* Provides serialization support.
*
* @param stream the input stream.
*
* @throws IOException if there is an I/O error.
* @throws ClassNotFoundException if there is a classpath problem.
*/
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException {
stream.defaultReadObject();
this.legendLine = SerialUtilities.readShape(stream);
}
/**
* Provides serialization support.
*
* @param stream the output stream.
*
* @throws IOException if there is an I/O error.
*/
private void writeObject(ObjectOutputStream stream) throws IOException {
stream.defaultWriteObject();
SerialUtilities.writeShape(this.legendLine, stream);
}
}
Code: Select all
Index: E:/work/eclipse3.2workspace/JFreeChart/source/org/jfree/chart/renderer/xy/StandardXYItemRenderer.java
===================================================================
--- E:/work/eclipse3.2workspace/JFreeChart/source/org/jfree/chart/renderer/xy/StandardXYItemRenderer.java (revision 1)
+++ E:/work/eclipse3.2workspace/JFreeChart/source/org/jfree/chart/renderer/xy/StandardXYItemRenderer.java (working copy)
@@ -218,6 +218,10 @@
*/
private transient Shape legendLine;
+ /** A counter to prevent unnecessary Graphics2D.draw() events in drawItem() */
+ private int previousDrawnItem;
+ private int skippedDrawItems;
+
/**
* Constructs a new renderer.
*/
@@ -788,6 +792,7 @@
boolean itemVisible = getItemVisible(series, item);
// setup for collecting optional entity info...
+ boolean bAddEntity=false;
Shape entityArea = null;
EntityCollection entities = null;
if (info != null) {
@@ -821,6 +826,9 @@
s.lastPointGood = false;
s.setSeriesIndex(series);
}
+ if(0 == item) {
+ this.previousDrawnItem = 0;
+ }
// update path to reflect latest point
if (itemVisible && !Double.isNaN(transX1)
@@ -852,53 +860,73 @@
}
}
}
-
else if (item != 0 && itemVisible) {
- // get the previous data point...
- double x0 = dataset.getXValue(series, item - 1);
- double y0 = dataset.getYValue(series, item - 1);
- if (!Double.isNaN(x0) && !Double.isNaN(y0)) {
- boolean drawLine = true;
- if (getPlotDiscontinuous()) {
- // only draw a line if the gap between the current and
- // previous data point is within the threshold
- int numX = dataset.getItemCount(series);
- double minX = dataset.getXValue(series, 0);
- double maxX = dataset.getXValue(series, numX - 1);
- if (this.gapThresholdType == UnitType.ABSOLUTE) {
- drawLine = Math.abs(x1 - x0) <= this.gapThreshold;
- }
- else {
- drawLine = Math.abs(x1 - x0) <= ((maxX - minX)
- / numX * getGapThreshold());
- }
- }
- if (drawLine) {
- double transX0 = domainAxis.valueToJava2D(x0, dataArea,
- xAxisLocation);
- double transY0 = rangeAxis.valueToJava2D(y0, dataArea,
- yAxisLocation);
-
- // only draw if we have good values
- if (Double.isNaN(transX0) || Double.isNaN(transY0)
- || Double.isNaN(transX1) || Double.isNaN(transY1)) {
- return;
- }
-
- if (orientation == PlotOrientation.HORIZONTAL) {
- state.workingLine.setLine(transY0, transX0,
- transY1, transX1);
- }
- else if (orientation == PlotOrientation.VERTICAL) {
- state.workingLine.setLine(transX0, transY0,
- transX1, transY1);
- }
-
- if (state.workingLine.intersects(dataArea)) {
- g2.draw(state.workingLine);
- }
- }
- }
+ // get the previous data point...
+ int idx = item - this.previousDrawnItem;
+ if(idx < 0)
+ {
+ //there exists some confusion; do not draw anything and reset state
+ this.previousDrawnItem = 0;
+ }
+ else
+ {
+ // get the previous data point...
+ double x0 = dataset.getXValue(series, idx);
+ double y0 = dataset.getYValue(series, idx);
+ if (!Double.isNaN(x0) && !Double.isNaN(y0)) {
+ boolean drawLine = true;
+ if (getPlotDiscontinuous()) {
+ // only draw a line if the gap between the current and
+ // previous data point is within the threshold
+ int numX = dataset.getItemCount(series);
+ double minX = dataset.getXValue(series, 0);
+ double maxX = dataset.getXValue(series, numX - 1);
+ if (this.gapThresholdType == UnitType.ABSOLUTE) {
+ drawLine = Math.abs(x1 - x0) <= this.gapThreshold;
+ }
+ else {
+ drawLine = Math.abs(x1 - x0) <= ((maxX - minX)
+ / numX * getGapThreshold());
+ }
+ }
+ if (drawLine) {
+ double transX0 = domainAxis.valueToJava2D(x0, dataArea,
+ xAxisLocation);
+ double transY0 = rangeAxis.valueToJava2D(y0, dataArea,
+ yAxisLocation);
+
+ // only draw if we have good values
+ if (Double.isNaN(transX0) || Double.isNaN(transY0)
+ || Double.isNaN(transX1) || Double.isNaN(transY1)) {
+ return;
+ }
+
+ // Only draw line if it is more than a pixel away from the previous one
+ if((0 == this.previousDrawnItem || transX1 - transX0 > 2 || transX1 - transX0 < -2 || transY1 - transY0 > 2
+ || transY1 - transY0 < -2)) {
+ this.previousDrawnItem = 1;
+ if (orientation == PlotOrientation.HORIZONTAL) {
+ state.workingLine.setLine(transY0, transX0,
+ transY1, transX1);
+ }
+ else if (orientation == PlotOrientation.VERTICAL) {
+ state.workingLine.setLine(transX0, transY0,
+ transX1, transY1);
+ }
+
+ if (state.workingLine.intersects(dataArea)) {
+ g2.draw(state.workingLine);
+ }
+ }
+ else {
+ //Increase counter for the previous drawn item.
+ previousDrawnItem++;
+ bAddEntity=false;
+ this.skippedDrawItems++;
+ }
+ }
+ }
+ }
}
}
@@ -921,6 +949,7 @@
transY1);
}
if (shape.intersects(dataArea)) {
+ bAddEntity=true;
if (getItemShapeFilled(series, item)) {
g2.fill(shape);
}
@@ -965,7 +994,7 @@
rangeAxisIndex, transX1, transY1, orientation);
// add an entity for the item...
- if (entities != null && dataArea.contains(xx, yy)) {
+ if (entities != null && dataArea.contains(xx, yy) && bAddEntity) {
addEntity(entities, entityArea, dataset, series, item, xx, yy);
}
@@ -1090,6 +1119,10 @@
return new Point(width / 2, height / 2);
}
+ protected void finalize() throws Throwable
+ {
+ System.out.println("skipped draw items: "+this.skippedDrawItems);
+ }
/**
* Provides serialization support.
Comments?
Just tried the FastXYPlot and am liking it. My completely scientific and reproducible tests (thank you second hand) show it to be as fast or faster for drawing with no zoom and much faster when drawing zoomed. This is great for my application as much zooming in and dragging around once zoomed is done. In addition it seems to be a more elegant solution.
I will play with for a while and hope to end up using it. Great work, thanks for posting it.
I will play with for a while and hope to end up using it. Great work, thanks for posting it.
A simple workaround for the high Jfree mem use in XYPlots
Thanks for the post. There are some good ideas. I have one case with ioGAS where we plot 5 or 10 million points on the screen, AND I DON'T NEED TOOLTIPS. It used to work with a two year old version of JFreeChart, but not now (out of mem ex)!
There is something fishy about the entity collection code now I think. My simple workaround is to override render in a subclass of XYPlot and pass null as the 'info' object. (and also elsewhere make sure I don't set a tooltiprenderer).
I didn't have time to merge in your changes today, but should I need further speed and mem improvements I will.
Thanks again
Rob Wall.
===example overriddedn method to pass null as info object
final public boolean render(Graphics2D g2, Rectangle2D dataArea, int index, PlotRenderingInfo info,
CrosshairState crosshairState)
{
boolean ret = super.render(g2, dataArea, index, m_wantTooltips ? info : null, crosshairState);
return ret;
}
There is something fishy about the entity collection code now I think. My simple workaround is to override render in a subclass of XYPlot and pass null as the 'info' object. (and also elsewhere make sure I don't set a tooltiprenderer).
I didn't have time to merge in your changes today, but should I need further speed and mem improvements I will.
Thanks again
Rob Wall.
===example overriddedn method to pass null as info object
final public boolean render(Graphics2D g2, Rectangle2D dataArea, int index, PlotRenderingInfo info,
CrosshairState crosshairState)
{
boolean ret = super.render(g2, dataArea, index, m_wantTooltips ? info : null, crosshairState);
return ret;
}
forgot
I forgot to say, this gives at least a 10 fold decrease in memory used, according to my debug outputs.
-
- JFreeChart Project Leader
- Posts: 11734
- Joined: Fri Mar 14, 2003 10:29 am
- antibot: No, of course not.
- Contact:
Re: A simple workaround for the high Jfree mem use in XYPlot
It's always been fishy. The tooltip for *every* item is precomputed, along with the hotspot region - it's analogous to an HTML image map. It doesn't work well for large datasets.robwall wrote:There is something fishy about the entity collection code now I think.
Most likely the renderer you used in the old version of JFreeChart didn't support tooltip generation at all, and it's been added in the interim.
Your workaround is good.
David Gilbert
JFreeChart Project Leader
Read my blog
Support JFree via the Github sponsorship program
JFreeChart Project Leader


fishyness
Dave,
Thanks for the response. That was the odd thing! The old version did somehow do tooltips and it was quicker?!?
(I think is is the ver, I copied this from JFreeChart.java
" * $Id: JFreeChart.java,v 1.18 2002/10/18 14:37:03 mungady Exp $")
I would investigate further but now I have a workaround its not worth my time, or yours I expect! Thanks for the response anyway.
You know you have done a good job when people replace a few or implement your interfaces. You know you havn't when they just throw it all away ...
Cheers
Rob
Thanks for the response. That was the odd thing! The old version did somehow do tooltips and it was quicker?!?
(I think is is the ver, I copied this from JFreeChart.java
" * $Id: JFreeChart.java,v 1.18 2002/10/18 14:37:03 mungady Exp $")
I would investigate further but now I have a workaround its not worth my time, or yours I expect! Thanks for the response anyway.
You know you have done a good job when people replace a few or implement your interfaces. You know you havn't when they just throw it all away ...
Cheers
Rob
Fat XYPlot not working with multiple series. Bug???
The snippet below plots two XYSeriesCollection on the same graph, and with a different renderer for each. But, if I replace the XYPlot with FastXYPlot, then it will only display both collections if they are plotted with the same renderer. If I try to use two different renderers, only one is drawn. Is this a bug or have I misunderstood fastXPPlot??
Cheers,
Arwel
The snippet below plots two XYSeriesCollection on the same graph, and with a different renderer for each. But, if I replace the XYPlot with FastXYPlot, then it will only display both collections if they are plotted with the same renderer. If I try to use two different renderers, only one is drawn. Is this a bug or have I misunderstood fastXPPlot??
Cheers,
Arwel
Code: Select all
public ChartPanel mkPointsAndLinesSemilogChart(XYSeriesCollection pointsColl, XYSeriesCollection linesColl, String name) {
NumberAxis xAxis = new NumberAxis("Qz");
LogarithmicAxis yAxis = new LogarithmicAxis("Ref");
yAxis.setStrictValuesFlag(false);
//Make the symbols plot....
XYLineAndShapeRenderer rendererPoints = new XYLineAndShapeRenderer();
rendererPoints.setShapesVisible(true);
rendererPoints.setShapesFilled(false);
rendererPoints.setLinesVisible(false);
//Make the lines plot....
XYLineAndShapeRenderer rendererLines = new XYLineAndShapeRenderer();
rendererLines.setLinesVisible(true);
rendererLines.setShapesVisible(false);
XYPlot plot = new XYPlot(pointsColl, xAxis, yAxis, rendererPoints);
plot.setBackgroundPaint(Color.white);
plot.setDomainGridlinesVisible(false);
plot.setRangeGridlinesVisible(false);
plot.setDataset(1,linesColl);
plot.setRenderer(1,rendererLines);
JFreeChart chart = new JFreeChart("Reflectivity", plot);
chart.removeLegend();
chart.setAntiAlias(false);
final ChartPanel panel = new ChartPanel(chart);
ch = chart;
return panel;
}