Logarithmic trend line

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
mickish
Posts: 29
Joined: Tue Jan 08, 2008 11:15 pm
Location: Venice, Florida

Logarithmic trend line

Post by mickish » Mon Apr 07, 2008 6:05 pm

I thought you might be interested in a modification to XYLineAnnotation.draw() that plots a proper logarithmic curve between two points on the LogAxis, instead of the default straight line (which does not accurately follow the data).

You can see the story in these three pictures:
  1. A simple arithmetic plot with a proper trend line
  2. An incorrect logarithmic plot where the straight line is only correct at the end points
  3. A correct logarithmic plot. Note that the trend line intersects the data line at the same spots as on the arithmetic plot.

Code: Select all

    public void draw(final Graphics2D g2, final XYPlot plot, final Rectangle2D dataArea,
                     final ValueAxis domainAxis, final ValueAxis rangeAxis,
                     final int rendererIndex, final PlotRenderingInfo info) {

		final PlotOrientation orientation = plot.getOrientation();
		final RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(
			plot.getDomainAxisLocation(), orientation);
		final RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation(
			plot.getRangeAxisLocation(), orientation);
		
		final Shape shape;
		final Shape hotspot;
		if( rangeAxis instanceof LogAxis ) {
			shape = createPolyLine( dataArea, domainAxis, rangeAxis,
				orientation, domainEdge, rangeEdge);
			hotspot = shape;
		} else {
			shape = createSimpleLine( dataArea, domainAxis, rangeAxis,
				orientation, domainEdge, rangeEdge );
			hotspot = ShapeUtilities.createLineRegion((Line2D)shape, 1.0f);
		}
		
		g2.setPaint(this.config.paint);
		g2.setStroke(this.config.stroke);
		g2.draw( shape );
		
		final String toolTip = this.getToolTipText();
		final String url = this.getURL();
		this.addEntity(info, hotspot, rendererIndex, toolTip, url);
    }
    
	//--------------------------------------------------
    
    /**
     * A straight line between two java coordinates.
     */
    public Shape createSimpleLine(final Rectangle2D dataArea,
    		final ValueAxis domainAxis, final ValueAxis rangeAxis,
    		final PlotOrientation orientation,
    		final RectangleEdge domainEdge, final RectangleEdge rangeEdge) {
    	
        final float j2DX1;
        final float j2DX2;
        final float j2DY1;
        final float j2DY2;
        if (orientation == PlotOrientation.VERTICAL) {
            j2DX1 = (float) domainAxis.valueToJava2D(this.config.x1, dataArea,
                    			domainEdge);
            j2DY1 = (float) (rangeAxis.valueToJava2D(this.config.y1, dataArea,
                    			rangeEdge) - this.config.y1offset);
            j2DX2 = (float) domainAxis.valueToJava2D(this.config.x2, dataArea,
                    			domainEdge);
            j2DY2 = (float) (rangeAxis.valueToJava2D(this.config.y2, dataArea,
                    			rangeEdge) - this.config.y2offset);
        }
        else {
        	assert( orientation == PlotOrientation.HORIZONTAL );
            j2DY1 = (float) domainAxis.valueToJava2D(this.config.x1, dataArea,
                    domainEdge);
            j2DX1 = (float) rangeAxis.valueToJava2D(this.config.y1, dataArea,
                    rangeEdge);
            j2DY2 = (float) domainAxis.valueToJava2D(this.config.x2, dataArea,
                    domainEdge);
            j2DX2 = (float) rangeAxis.valueToJava2D(this.config.y2, dataArea,
                    rangeEdge);
        }
        final Line2D line = new Line2D.Float(j2DX1, j2DY1, j2DX2, j2DY2);
        
        return line;
    }
    
	//--------------------------------------------------

    /**
     * Use the slope of the data values to plot explicit points.
     */
    public Shape createPolyLine(final Rectangle2D dataArea,
    		final ValueAxis domainAxis, final ValueAxis rangeAxis,
    		final PlotOrientation orientation,
    		final RectangleEdge domainEdge, final RectangleEdge rangeEdge) {
    	
    	final ArrayList<Float> xList = new ArrayList<Float>();
    	final ArrayList<Float> yList = new ArrayList<Float>();

    	// Convert the incoming y-offsets from pixel to data values.
    	// Specifically, find the desired java2d locations, then convert
    	// back to data values.
    	final double y1offsetValue =
    		this.config.y1 -
	    		rangeAxis.java2DToValue( 
		    		(rangeAxis.valueToJava2D(this.config.y1, dataArea, rangeEdge) -
		    			this.config.y1offset),
		    		dataArea, rangeEdge );
    	final double y2offsetValue =
    		this.config.y2 -
	    		rangeAxis.java2DToValue( 
		    		(rangeAxis.valueToJava2D(this.config.y2, dataArea, rangeEdge) -
		    			this.config.y2offset),
		    		dataArea, rangeEdge );
    		
    	final double y1 = this.config.y1 - y1offsetValue;
    	final double y2 = this.config.y2 - y2offsetValue;
    	
    	final double xDelta = (this.config.x2 - this.config.x1) / 50;
    	final double m =	// slope
    		(y2 - y1) / (this.config.x2 - this.config.x1);
    	final double b =	// y-intercept
    		y1 - ( m * this.config.x1 );
    	for(double x = this.config.x1; x <= this.config.x2; x += xDelta ) {
    		final double y = (m * x) + b;
			float j2DX = (float) domainAxis.valueToJava2D(x, dataArea, domainEdge);
			float j2DY = (float) rangeAxis.valueToJava2D(y, dataArea, rangeEdge);
    		xList.add( j2DX );
    		yList.add( j2DY );
    	}

    	final GeneralPath path = new GeneralPath();
    	path.moveTo( xList.get( 0 ), yList.get( 0 ) );
    	for(int i=1; i<xList.size(); i++) {
    		path.lineTo( xList.get(i), yList.get(i) );
    	}

    	return path;
    }

Locked