High Performance Apps

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Klaus K.

High Performance Apps

Post by Klaus K. » Mon Mar 14, 2005 10:37 am

Is it possible,
to use JFreeChart in high performance applications
which about 10 tausend points per chart ?

Alexander
Posts: 24
Joined: Thu Feb 17, 2005 3:59 pm

Post by Alexander » Mon Mar 14, 2005 11:04 am

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

angel
Posts: 899
Joined: Thu Jan 15, 2004 12:07 am
Location: Germany - Palatinate

Post by angel » Mon Mar 14, 2005 12:19 pm

I am using a TimeSeriesChart with several thousands points and have a good response time. It depends on the chart type and how you retrieve the data (database, file, ..)

Klaus K.

Post by Klaus K. » Mon Mar 14, 2005 12:44 pm

thank your for your help.

Klaus K.

Post by Klaus K. » Mon Mar 14, 2005 12:57 pm

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 ?

Alexander
Posts: 24
Joined: Thu Feb 17, 2005 3:59 pm

Post by Alexander » Mon Mar 14, 2005 1:02 pm

Hi,

This is kinda off topic for this forum, but are you using sax or dom? Sax is faster as dom. Besides that, what parser do you use? Might be some parser that is faster, but I don't remember which ones are fast/slow.

Also take a look at the properties of the parser, enable/disable validation etc.

david.gilbert
JFreeChart Project Leader
Posts: 11734
Joined: Fri Mar 14, 2003 10:29 am
antibot: No, of course not.
Contact:

Post by david.gilbert » Mon Mar 14, 2005 8:00 pm

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 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.

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

:idea: Read my blog
:idea: Support JFree via the Github sponsorship program

Alexander
Posts: 24
Joined: Thu Feb 17, 2005 3:59 pm

Post by Alexander » Mon Mar 14, 2005 8:15 pm

Great to hear that!

I will finalize my code some time this week, if you are interested I'm willing to post it here so you can use it.

I really need a very fast chart component, so if you have any more suggestions I'd like to hear it. I don't mind hacking in the jfreechart code.

Alexander

david.gilbert
JFreeChart Project Leader
Posts: 11734
Joined: Fri Mar 14, 2003 10:29 am
antibot: No, of course not.
Contact:

Post by david.gilbert » Mon Mar 14, 2005 10:20 pm

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...
David Gilbert
JFreeChart Project Leader

:idea: Read my blog
:idea: Support JFree via the Github sponsorship program

Alexander
Posts: 24
Joined: Thu Feb 17, 2005 3:59 pm

Post by Alexander » Tue Mar 15, 2005 9:51 am

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

TheOnys
Posts: 4
Joined: Mon Nov 15, 2004 11:32 pm

Post by TheOnys » Tue Mar 15, 2005 8:22 pm

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:

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;
    }
and here's the drawItem() method:

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);
	    }*/
        }
    }
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.

Alexander
Posts: 24
Joined: Thu Feb 17, 2005 3:59 pm

Post by Alexander » Wed Mar 16, 2005 10:14 am

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?

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);
        }
    }

gumshoe
Posts: 80
Joined: Mon Jul 12, 2004 6:06 pm

Post by gumshoe » Wed Mar 23, 2005 12:42 am

Alexander, do you mind posting you changes to to drawItem in StandardXYItemRenderer?

Thanks.

gumshoe
Posts: 80
Joined: Mon Jul 12, 2004 6:06 pm

Post by gumshoe » Wed Mar 23, 2005 2:32 am

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.

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: 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
Last edited by gumshoe on Wed Jun 14, 2006 5:05 pm, edited 6 times in total.

Alexander
Posts: 24
Joined: Thu Feb 17, 2005 3:59 pm

Post by Alexander » Wed Mar 23, 2005 10:01 am

You left out the drawing of the entities completely? You could, for a start, use a boolean, so that an entity only is created if a line is drawn (like I do in the XYLineAndShapeRenderer).

Alexander

Locked