High Performance Apps
High Performance Apps
Is it possible,
to use JFreeChart in high performance applications
which about 10 tausend points per chart ?
to use JFreeChart in high performance applications
which about 10 tausend points per chart ?
Hi,
I do have the same problem, but need to be able to show much more datapoints. I did look at the jfreechart code myself, and came up with some adjustments for XY charts. I do not know what type of chart you want to use. But in case of XY charts the following might help:
In StandardXYItemRenderer the drawItem method is responsible for drawing the lines of a chart. This method creates lines for every point there is, so if multiple datapoints are translated to the same position on the screen that position is used multiple times. I changed to code so that it only draws a point if its position is 1 or 2 pixels away from the previously drawn point.
The same thing goes for creating the ellipses (the addEntity call at the end of the method) (these are created every time, wether they are displayed or not).
With these adjustments it takes about 200 ms to do the drawing of a chart with 10000 datapoints on a 3 ghz machine running linux.
I do not know yet if these adjustments introduce some problems, perhaps David can comment on that?
Alexander
I do have the same problem, but need to be able to show much more datapoints. I did look at the jfreechart code myself, and came up with some adjustments for XY charts. I do not know what type of chart you want to use. But in case of XY charts the following might help:
In StandardXYItemRenderer the drawItem method is responsible for drawing the lines of a chart. This method creates lines for every point there is, so if multiple datapoints are translated to the same position on the screen that position is used multiple times. I changed to code so that it only draws a point if its position is 1 or 2 pixels away from the previously drawn point.
The same thing goes for creating the ellipses (the addEntity call at the end of the method) (these are created every time, wether they are displayed or not).
With these adjustments it takes about 200 ms to do the drawing of a chart with 10000 datapoints on a 3 ghz machine running linux.
I do not know yet if these adjustments introduce some problems, perhaps David can comment on that?
Alexander
Well,
I have build my own extension for JFreeChart based on XML
and a JBoss J2EE environment (Servlet / EJB).
So all charts can be generated by sending XML to my servlet
which parse and analyse the xml and sends the generated image back.
My problem now is, that parsing xml takes a lot of time.
(Performance: 2 second on Redhat Linux 2GH 512MB )
Have you any ideas,
how I can improve this bad performance ?
I have build my own extension for JFreeChart based on XML
and a JBoss J2EE environment (Servlet / EJB).
So all charts can be generated by sending XML to my servlet
which parse and analyse the xml and sends the generated image back.
My problem now is, that parsing xml takes a lot of time.
(Performance: 2 second on Redhat Linux 2GH 512MB )
Have you any ideas,
how I can improve this bad performance ?
-
- JFreeChart Project Leader
- Posts: 11734
- Joined: Fri Mar 14, 2003 10:29 am
- antibot: No, of course not.
- Contact:
I haven't spent much time on optimising the code, but I think the approach you are using makes sense, especially given that the drawing operations are the most expensive part of the code. Also, your approach is "self adjusting" when the user zooms in on part of the chart.Alexander wrote:In StandardXYItemRenderer the drawItem method is responsible for drawing the lines of a chart. This method creates lines for every point there is, so if multiple datapoints are translated to the same position on the screen that position is used multiple times. I changed to code so that it only draws a point if its position is 1 or 2 pixels away from the previously drawn point.
The same thing goes for creating the ellipses (the addEntity call at the end of the method) (these are created every time, wether they are displayed or not).
With these adjustments it takes about 200 ms to do the drawing of a chart with 10000 datapoints on a 3 ghz machine running linux.
I do not know yet if these adjustments introduce some problems, perhaps David can comment on that?
I am going to do some work (probably after 1.0.0 is done) on other optimisations - for example, with subranges on ordered datasets, you don't need to look at values outside the visible range. I'm sure there are lots of ways to make the code a little (or even a lot) faster, suggestions are welcome.
David Gilbert
JFreeChart Project Leader
Read my blog
Support JFree via the Github sponsorship program
JFreeChart Project Leader


-
- JFreeChart Project Leader
- Posts: 11734
- Joined: Fri Mar 14, 2003 10:29 am
- antibot: No, of course not.
- Contact:
Great, I'll be interested to look at your code. There is some overlap between StandardXYItemRenderer and the newer XYLineAndShapeRenderer - you might find it easier to work with the latter.
Other performance suggestions: (1) you could take a look at the FastScatterPlot class which uses data directly from an array (rather than via a dataset interface) to go a little faster. (2) don't use antialiasing, it slows drawing down quite a lot. (3) methods like drawPolyline() can be faster than the draw(Shape) method.
When I do get around to working on performance issues, I'd like to have some benchmarks I can run to get some objective measures of speed. I'm not sure how to go about that yet...
Other performance suggestions: (1) you could take a look at the FastScatterPlot class which uses data directly from an array (rather than via a dataset interface) to go a little faster. (2) don't use antialiasing, it slows drawing down quite a lot. (3) methods like drawPolyline() can be faster than the draw(Shape) method.
When I do get around to working on performance issues, I'd like to have some benchmarks I can run to get some objective measures of speed. I'm not sure how to go about that yet...
David Gilbert
JFreeChart Project Leader
Read my blog
Support JFree via the Github sponsorship program
JFreeChart Project Leader


Hi, thx for the hints.
Looking at the LineAndShape renderer I see that you use 2 separate passes for drawing the shapes and the lines, is there a reason to do it that way?
Doing the drawing/creating of lines and shapes in one step makes much more sense, now it has to calculate the position for every point twice, it also assigns alot variables twice. But most of all, this way it is very difficult to use my code from StandardXYItemRenderer where I check if a line has to be drawn, because only if a line is drawn the shape has to be drawn. And with two passes I have to check everything twice.
Something else that applies to both the line/shape renderer and the standard renderer: A lot of variables are used for every item, but they get assigned also every step. Is there a reason for that? For example the stroke and paint for the lines/shapes, the orientation etc.
For now I will change the standard renderer code, that is what I used before. If I got that going I will post the code for the drawItem method.
Alexander
Looking at the LineAndShape renderer I see that you use 2 separate passes for drawing the shapes and the lines, is there a reason to do it that way?
Doing the drawing/creating of lines and shapes in one step makes much more sense, now it has to calculate the position for every point twice, it also assigns alot variables twice. But most of all, this way it is very difficult to use my code from StandardXYItemRenderer where I check if a line has to be drawn, because only if a line is drawn the shape has to be drawn. And with two passes I have to check everything twice.
Something else that applies to both the line/shape renderer and the standard renderer: A lot of variables are used for every item, but they get assigned also every step. Is there a reason for that? For example the stroke and paint for the lines/shapes, the orientation etc.
For now I will change the standard renderer code, that is what I used before. If I got that going I will post the code for the drawItem method.
Alexander
Hi, thought this might be of help.
I was having trouble with dreadfully slow rendering and with some hints I came up with the following. It's probably not the best code but it works for me 99.9% of the time so far.
I re-wrote the render() and drawItem() methods in a subclass of XYPlot and XYItemRenderer respectively.
render() was re-written to pass an entire series to drawItem() instead of 1 point at a time.
drawItem() then takes that series, finds the first and last item that would appear on-screen and only work with the points in between. If there's less than 1000 points to be displayed it will display all the points. If more than 1000 points need to be displayed it will display every Nth point where N = number of points displayed / 1000. It also makes sure that the final point in the subset is actually displayed.
Here's my render() method:
and here's the drawItem() method:
This code isn't cleaned up (as seen by the commented out parts of things I had also tried) but like I said, it has sped up the drawing of the graph immensly for me.
I was having trouble with dreadfully slow rendering and with some hints I came up with the following. It's probably not the best code but it works for me 99.9% of the time so far.
I re-wrote the render() and drawItem() methods in a subclass of XYPlot and XYItemRenderer respectively.
render() was re-written to pass an entire series to drawItem() instead of 1 point at a time.
drawItem() then takes that series, finds the first and last item that would appear on-screen and only work with the points in between. If there's less than 1000 points to be displayed it will display all the points. If more than 1000 points need to be displayed it will display every Nth point where N = number of points displayed / 1000. It also makes sure that the final point in the subset is actually displayed.
Here's my render() method:
Code: Select all
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);
FastXYItemRenderer renderer = (FastXYItemRenderer)getRenderer(index);
if (renderer == null) {
renderer = (FastXYItemRenderer)getRenderer();
}
XYItemRendererState state = renderer.initialise(
g2, dataArea, this, dataset, info
);
int passCount = renderer.getPassCount();
for (int pass = 0; pass < passCount; pass++) {
int seriesCount = dataset.getSeriesCount();
for (int series = 0; series < seriesCount; series++) {
int itemCount = dataset.getItemCount(series);
renderer.drawItem(
g2, state, dataArea, info,
this, xAxis, yAxis, dataset, series, crosshairState, pass
);
}
}
}
return foundData;
}
Code: Select all
public void drawItem(Graphics2D g2,
XYItemRendererState state,
Rectangle2D dataArea,
PlotRenderingInfo info,
FastXYPlot plot,
ValueAxis domainAxis,
ValueAxis rangeAxis,
XYDataset dataset,
int series,
CrosshairState crosshairState,
int pass) {
// setup for collecting optional entity info...
Shape entityArea = null;
EntityCollection entities = null;
if (info != null) {
entities = info.getOwner().getEntityCollection();
}
PlotOrientation orientation = plot.getOrientation();
Paint paint = getItemPaint(series, 0);
Stroke seriesStroke = getItemStroke(series, 0);
g2.setPaint(paint);
g2.setStroke(seriesStroke);
//long start = System.currentTimeMillis();
// Transform all data Points
RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
long lowerBound = (long)domainAxis.getLowerBound();
long upperBound = (long)domainAxis.getUpperBound();
TimeSeries _series = ((TimeSeriesCollection)dataset).getSeries(series);
List _collection = _series.getItems();
int firstItem = Collections.binarySearch(_collection,new TimeSeriesDataItem(new Second(new Date(lowerBound)),0));
//System.out.println(new Date(lowerBound) + " ---> " + firstItem + "/" + _series.getItemCount());
if (firstItem < 0)
firstItem = -(firstItem - 1);
int lastItem = Collections.binarySearch(_collection,new TimeSeriesDataItem(new Second(new Date(upperBound)),0));
//System.out.println(new Date(upperBound) + " ---> " + lastItem + "/" + _series.getItemCount());
if (lastItem < 0)
lastItem = -(lastItem - 1);
if (lastItem >= _series.getItemCount())
lastItem = _series.getItemCount() - 1;
int step = (lastItem-firstItem)/1000;
if (step == 0)
step = 1;
if (lastItem == firstItem || lastItem < firstItem)
return;
int[] x = new int[(lastItem-firstItem)/step+1];
int[] y = new int[(lastItem-firstItem)/step+1];
double x1 = 0, y1 = 0;
for (int item = firstItem; item <= lastItem;item+=step)
{
x1 = dataset.getXValue(series, item);
y1 = dataset.getYValue(series, item);
if (Double.isNaN(x1) || Double.isNaN(y1)) {
return;
}
x[(item-firstItem)/step] = (int)domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
y[(item-firstItem)/step] = (int)rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
}
x[x.length-1] = (int)domainAxis.valueToJava2D(dataset.getXValue(series,lastItem), dataArea, xAxisLocation);
y[y.length-1] = (int)rangeAxis.valueToJava2D(dataset.getYValue(series,lastItem), dataArea, yAxisLocation);
//System.out.println("1) Calculation Time: " + (double)(System.currentTimeMillis()-start)/1000 + " s for " + x.length + " points instead of " + (lastItem - firstItem + 1));
//start = System.currentTimeMillis();
/*int[] newx = new int[lastItem - firstItem + 1];
int[] newy = new int[lastItem - firstItem + 1];
for (int item = 0; item < dataset.getItemCount(series);item++)
{
if (item >= firstItem && item <= lastItem)
{
newx[item-firstItem] = x[item];
newy[item-firstItem] = y[item];
}
}*/
//System.out.println("2) Total points Found: " + newx.length + " Calculation Time: " + (System.currentTimeMillis()-start));
//start = System.currentTimeMillis();
if (getPlotLines()) {
// only draw if we have good values
if (orientation == PlotOrientation.HORIZONTAL)
{
g2.drawPolyline (y,x,x.length);
//state.workingLine.setLine(transY0, transX0, transY1, transX1);
}
else if (orientation == PlotOrientation.VERTICAL) {
g2.drawPolyline (x,y,x.length);
//state.workingLine.setLine(transX0, transY0, transX1, transY1);
}
//System.out.println("3) Total points Displayed: " + x.length + " Drawing Time: " + (System.currentTimeMillis() - start));
/*if (state.workingLine.intersects(dataArea)) {
g2.draw(state.workingLine);
}*/
}
}
Hi,
Thx for the code, will look into it a little more. Looks like I will have to change it so it doesn't use a TimeSeries anymore.
I also tried to draw everything in at once using drawPolyline, but this gave some strange behaviour. If I resized my screen to become larger, at one point (something about 800 pixels width) it would take about 15 seconds to redrawn the chart, instead of 100 msec for a chart with about 50000 data points. Did you also have that problem?
Drawing everything at once using LIne2D and draw(Shape) or drawLine doesn't increase the speed very much. The most time is taken bij the addEntity(..) method. And I do need that method, because it has to be possible to show/hide the markers and the tooltips.
This is my solution so far. I only changed the drawItem method of XYLineAndShapeRenderer. It now uses one pass instead of two. Points get drawn if there is more then 2 pixels between them and lines get drawn if the start and end point are in the rectangle . Shapes only get drawn if a line is drawn.
The check for the points being in the rectangle is not ok, if zoomed in, so that the start and end point are outside the reactange, no line is drawn. Someone know of some simple solution?
Thx for the code, will look into it a little more. Looks like I will have to change it so it doesn't use a TimeSeries anymore.
I also tried to draw everything in at once using drawPolyline, but this gave some strange behaviour. If I resized my screen to become larger, at one point (something about 800 pixels width) it would take about 15 seconds to redrawn the chart, instead of 100 msec for a chart with about 50000 data points. Did you also have that problem?
Drawing everything at once using LIne2D and draw(Shape) or drawLine doesn't increase the speed very much. The most time is taken bij the addEntity(..) method. And I do need that method, because it has to be possible to show/hide the markers and the tooltips.
This is my solution so far. I only changed the drawItem method of XYLineAndShapeRenderer. It now uses one pass instead of two. Points get drawn if there is more then 2 pixels between them and lines get drawn if the start and end point are in the rectangle . Shapes only get drawn if a line is drawn.
The check for the points being in the rectangle is not ok, if zoomed in, so that the start and end point are outside the reactange, no line is drawn. Someone know of some simple solution?
Code: Select all
/**
* 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) {
if (!getItemVisible(series, item)) {
return;
}
// Initialize everything only once.
if (item == 0) {
entities = null;
if (info != null) {
entities = info.getOwner().getEntityCollection();
}
orientation = plot.getOrientation();
Paint paint = getItemPaint(series, item);
Stroke seriesStroke = getItemStroke(series, item);
g2.setPaint(paint);
g2.setStroke(seriesStroke);
xAxisLocation = plot.getDomainAxisEdge();
yAxisLocation = plot.getRangeAxisEdge();
previousDrawnItem = 0;
}
// Get the needed datapoints before doing anything else
double x1 = dataset.getXValue(series, item);
double y1 = dataset.getYValue(series, item);
if (Double.isNaN(x1) || Double.isNaN(y1)) {
return;
}
double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
double x0 = dataset.getXValue(series, item - previousDrawnItem);
double y0 = dataset.getYValue(series, item - previousDrawnItem);
if (Double.isNaN(x0) && Double.isNaN(y0)) {
return;
}
double transX0 = domainAxis.valueToJava2D(x0, dataArea, xAxisLocation);
double transY0 = rangeAxis.valueToJava2D(y0, dataArea, yAxisLocation);
if (Double.isNaN(transX0) || Double.isNaN(transY0) || Double.isNaN(transX1) || Double.isNaN(transY1)) {
return;
}
// Check if the start and end point are in the dataArea.
// This is not OK, if zoomed in and transXY0 and transXY1 are outside the dataArea the line isn't shown.
if (dataArea.contains(transX0, transY0) || dataArea.contains(transX1, transY1)) {
// Check if the distance between the start and end point is larger than 2 pixels.
// The first shape has to be created, so also execute if previousDrawnItem == 0
if ((transX1 - transX0 > 2 || transX1 - transX0 < -2 || transY1 - transY0 > 2 || transY1 - transY0 < -2) || previousDrawnItem == 0) {
// Reset counter for the previous drawn item.
previousDrawnItem = 1;
// Boolean to indicate wether the shape has to be drawn.
boolean drawShape = false;
if (getItemLineVisible(series, item)) {
drawShape = drawActualItem(item, state, dataset, series, g2, transX1, transY1, transY0, transX0, dataArea, drawShape);
}
if (drawShape) {
// setup for collecting optional entity info...
Shape entityArea = null;
drawShape(g2, dataArea, entityArea, transY1, transX1, y1, x1, dataset, series, item, crosshairState);
}
} else {
// Increase counter for the previous drawn item.
previousDrawnItem++;
}
}
}
private boolean drawActualItem(int item, XYItemRendererState state, XYDataset dataset, int series, Graphics2D g2, double transX1, double transY1, double transY0, double transX0, Rectangle2D dataArea, boolean drawShape) {
if (item == 0) {
if (this.drawSeriesLineAsPath) {
State s = (State) state;
s.seriesPath.reset();
s.lastPointGood = false;
}
}
if (this.drawSeriesLineAsPath) {
int count = dataset.getItemCount(series);
drawSeriesLineAsPath(state, g2, transX1, transY1, series, item, count);
} else if (item != 0) {
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)) {
drawShape = true;
g2.draw(state.workingLine);
}
}
return drawShape;
}
private void drawShape(Graphics2D g2, Rectangle2D dataArea, Shape entityArea, double transY1, double transX1, double y1, double x1, XYDataset dataset, int series, int item, CrosshairState crosshairState) {
if (getItemShapeVisible(series, item)) {
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)) {
if (getItemShapeFilled(series, item)) {
g2.fill(shape);
} else {
g2.draw(shape);
}
}
entityArea = shape;
}
// draw the item label if there is one...
if (isItemLabelVisible(series, item)) {
drawItemLabel(
g2, orientation, dataset, series, item, transX1, transY1,
(y1 < 0.0)
);
}
updateCrosshairValues(
crosshairState, x1, y1, transX1, transY1, orientation
);
// add an entity for the item...
if (entities != null) {
addEntity(
entities, entityArea, dataset, series, item, transX1, transY1
);
}
}
private void drawSeriesLineAsPath(XYItemRendererState state, Graphics2D g2, double transX1, double transY1, int series, int item, int count) {
State s = (State) state;
// update path to reflect latest point
if (!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 == count - 1) {
// draw path
g2.setStroke(getSeriesStroke(series));
g2.setPaint(getSeriesPaint(series));
g2.draw(s.seriesPath);
}
}
Based on Alexander's code I modifed drawItem() of StandardXYItemRenderer to not draw unnecessary lines. With these changes drawing large TimeSeries is now much faster. Drawing a 129600 item (90 days worth of per/minute data) used to take ~5 seconds and now it is a sub-second operation.
NOTE: In the orginal post I forgot to paste in everthing after
NOTE: Entity optimization added in 2nd edit.
NOTE: Further entity optimization (as suggested by Alexander) added in 3rd edit. Also initialized bAddEntity to false instead of true.
NOTE: fixed bug; removed chance of array index out of bound exception
Code: Select all
/**
* A counter to prevent unnecessary Graphics2D.draw() events in drawItem()
*/
private int previousDrawnItem = 0;
/**
* 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) {
if (!getItemVisible(series, item)) {
return;
}
// 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)) {
return;
}
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 (item == 0) {
if (this.drawSeriesLineAsPath) {
State s = (State) state;
s.seriesPath.reset();
s.lastPointGood = false;
}
previousDrawnItem = 0;
}
if (this.drawSeriesLineAsPath) {
State s = (State) state;
// update path to reflect latest point
if (!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) {
// draw path
g2.setStroke(getSeriesStroke(series));
g2.setPaint(getSeriesPaint(series));
g2.draw(s.seriesPath);
}
}
else if (item != 0) {
// get the previous data point...
int idx = item - previousDrawnItem;
if(idx < 0)
{
//there exists some confusion; do not draw anything and reset state
previousDrawnItem = 0;
}
else
{
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((transX1 - transX0 > 2 || transX1 - transX0 < -2 || transY1 - transY0 > 2
|| transY1 - transY0 < -2)
|| 0==previousDrawnItem)
{
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;
}
}
}
}
}
}
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)
);
}
}
// draw the item label if there is one...
if (isItemLabelVisible(series, item)) {
double xx = transX1;
double yy = transY1;
if (orientation == PlotOrientation.HORIZONTAL) {
xx = transY1;
yy = transX1;
}
drawItemLabel(
g2, orientation, dataset, series, item, xx, yy, (y1 < 0.0)
);
}
updateCrosshairValues(
crosshairState, x1, y1, transX1, transY1, orientation
);
// add an entity for the item...
if (entities != null && bAddEntity) {
addEntity(
entities, entityArea, dataset, series, item, transX1, transY1
);
}
}
NOTE: In the orginal post I forgot to paste in everthing after
Code: Select all
if (getPlotShapes()) {
NOTE: Further entity optimization (as suggested by Alexander) added in 3rd edit. Also initialized bAddEntity to false instead of true.
NOTE: fixed bug; removed chance of array index out of bound exception
Last edited by gumshoe on Wed Jun 14, 2006 5:05 pm, edited 6 times in total.