Smoothed Line & Anti-aliasing

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
weihang
Posts: 3
Joined: Wed Mar 21, 2007 6:12 am

Smoothed Line & Anti-aliasing

Post by weihang » Wed Mar 21, 2007 7:33 am

Hello all,

As far as I know, there are quite a few people (including myself) looking for a renderer that renders a smooth line through all data points in a dataset. Instead of waiting for a complete solution, I decided to implement my own smoothed line renderer.

I did some search on google and found some relevant information, such as Bezier Curve, Spline, etc. After some trials and errors, I manage to implement the renderer using Bezier interpolation. My renderer extends from XYLineAndShapeRenderer and overrides the drawPrimaryLine() function. Previously, the function simply translates the coordinates of two data points of a line segment to coordiantes in the rendering space. In my renderer, the function divides the line into a continuous set of shorter lines. The coordinates of these lines are calculated using Bezier interpolation.

If you look at the images below, you'll see that my solution is by no means perfect. The smoothed lines don't seem like they have been anti-aliased!!! How can I get around with this problem?

Code: Select all

... drawPrimaryLine(...) {

	// ...
	// translate coordinates
	// ...

	lines[] = getBezierCurve(...)

	// for each line in lines[]
	// render the line to output device
}

Code: Select all

	public static Point2D.Double[] getBezierCurve(
			Point2D.Double point0,
			Point2D.Double point1,
			Point2D.Double point2,
			Point2D.Double point3,
			double smooth,
			int steps) {
		Point2D.Double control1 = new Point2D.Double();
		Point2D.Double control2 = new Point2D.Double();
		
		getControlPoints(point0, point1, point2, point3, control1, control2, smooth);
		
		Point2D.Double C = new Point2D.Double(
				3 * (control1.x - point1.x), 3 * (control1.y - point1.y));
		Point2D.Double B = new Point2D.Double(
				3 * (control2.x - control1.x) - C.x, 3 * (control2.y - control1.y) - C.y);
		Point2D.Double A = new Point2D.Double(
				point2.x - point1.x - C.x - B.x, point2.y - point1.y - C.y - B.y);
		
		Point2D.Double[] res = new Point2D.Double[steps + 1];
		double stepSize = 1.0 / steps;
		double step = stepSize;
	
		res[0] = point1;
		for (int i = 1; i < steps; i++) {
			res[i] = new Point2D.Double(
					A.x * Math.pow(step, 3) + B.x * Math.pow(step, 2) + C.x * step + point1.x,
					A.y * Math.pow(step, 3) + B.y * Math.pow(step, 2) + C.y * step + point1.y);
			System.out.println(step + " : " + res[i]);
			step += stepSize;
		}
		res[steps] = point2;
		
		return res;
	}

Code: Select all

	public static void getControlPoints(
			Point2D.Double point0,
			Point2D.Double point1,
			Point2D.Double point2,
			Point2D.Double point3,
			Point2D.Double control1,
			Point2D.Double control2,
			double smooth) {
		
		if (point0 == null) point0 = point1; //new Point2D.Double(0, 0);
		if (point3 == null) point3 = point2; //new Point2D.Double(0, 0);
		
		Point2D.Double c1 = new Point2D.Double(
				(point0.x + point1.x) / 2.0, (point0.y + point1.y) / 2.0);
		Point2D.Double c2 = new Point2D.Double(
				(point1.x + point2.x) / 2.0, (point1.y + point2.y) / 2.0);
		Point2D.Double c3 = new Point2D.Double(
				(point2.x + point3.x) / 2.0, (point2.y + point3.y) / 2.0);
		
		double len1 = point1.distance(point0);
		double len2 = point2.distance(point1);
		double len3 = point3.distance(point2);
		
		double k1 = len1 / (len1 + len2);
		double k2 = len2 / (len2 + len3);
		
		Point2D.Double m1 = new Point2D.Double(
				c1.x + (c2.x - c1.x) * k1, c1.y + (c2.y - c1.y) * k1);
		Point2D.Double m2 = new Point2D.Double(
				c2.x + (c3.x - c2.x) * k2, c2.y + (c3.y - c2.y) * k2);
		
		control1.setLocation(new Point2D.Double(
				m1.x + (c2.x - m1.x) * smooth + point1.x - m1.x,
				m1.y + (c2.y - m1.y) * smooth + point1.y - m1.y));
		control2.setLocation(new Point2D.Double(
				m2.x + (c2.x - m2.x) * smooth + point2.x - m2.x,
				m2.y + (c2.y - m2.y) * smooth + point2.y - m2.y));
	}

weihang
Posts: 3
Joined: Wed Mar 21, 2007 6:12 am

Post by weihang » Wed Mar 21, 2007 7:35 am

Straight
Image

Smooth
Image

Smooth
Image

Smooth (200 data points)
Image

pstng
Posts: 3
Joined: Wed Mar 21, 2007 7:56 pm

antialiasing

Post by pstng » Wed Mar 21, 2007 8:17 pm

I tink that is fou have 200 points in a 400 pixel chart you don't need to smooth the line! If you do, the bezier curves are about a few pixels long, so make this strange effect.

Another thing is the way you calculate the control points. I've posted the class I use, that makes a new dataset instead of changing the renderer (and I think that your idea is better). Iv'e taken the control points formula from the PHP PEAR Image_Graph class. They calculate the control points after looking not only the two points do draw the curve, but also the following ones. Maybe, if this is possible in your class, that could improve the line quality.

And now, one question: Could you post the whole class code, please? I'm really interested on it.

weihang
Posts: 3
Joined: Wed Mar 21, 2007 6:12 am

Re: antialiasing

Post by weihang » Thu Mar 22, 2007 2:05 am

pstng wrote:I tink that is fou have 200 points in a 400 pixel chart you don't need to smooth the line! If you do, the bezier curves are about a few pixels long, so make this strange effect.

Another thing is the way you calculate the control points. I've posted the class I use, that makes a new dataset instead of changing the renderer (and I think that your idea is better). Iv'e taken the control points formula from the PHP PEAR Image_Graph class. They calculate the control points after looking not only the two points do draw the curve, but also the following ones. Maybe, if this is possible in your class, that could improve the line quality.

And now, one question: Could you post the whole class code, please? I'm really interested on it.
Yes. You're right. The line looks smooth with 200 points. Thanks for you input.

I tried the control points formula from the Image_Graph library as well. It produces a slightly curvier line. I'd say not much difference, really. I wanted to try the Image_Graph library to see how well the smooth line is with 200 points. However, I couldn't get the Apache and PHP set up on my Windows and Mac machines.

Code: Select all

import java.awt.BasicStroke;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.BufferedReader;
import java.io.FileReader;
import java.text.SimpleDateFormat;

import javax.swing.JPanel;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.SegmentedTimeline;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.entity.EntityCollection;
import org.jfree.chart.plot.CrosshairState;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRendererState;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.time.Day;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.xy.XYDataset;
import org.jfree.ui.ApplicationFrame;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.RefineryUtilities;
import org.jfree.ui.StrokeSample;

import com.tictactec.ta.lib.Core;
import com.tictactec.ta.lib.MInteger;


class SimpleChart extends ApplicationFrame {

    public SimpleChart(String s) {
        super(s);
        JFreeChart jfreechart = createChart();
        ChartPanel chartpanel = new ChartPanel(jfreechart);
        chartpanel.setPreferredSize(new Dimension(500, 270));
        setContentPane(chartpanel);
    }
    
	public static void getControlPoints(
			Point2D.Double point0,
			Point2D.Double point1,
			Point2D.Double point2,
			Point2D.Double point3,
			Point2D.Double control1,
			Point2D.Double control2,
			double smooth) {
		// Reference: http://www.antigrain.com/research/bezier_interpolation/
		
		if (point0 == null) point0 = point1; //new Point2D.Double(0, 0);
		if (point3 == null) point3 = point2; //new Point2D.Double(0, 0);
		
		Point2D.Double c1 = new Point2D.Double(
				(point0.x + point1.x) / 2.0, (point0.y + point1.y) / 2.0);
		Point2D.Double c2 = new Point2D.Double(
				(point1.x + point2.x) / 2.0, (point1.y + point2.y) / 2.0);
		Point2D.Double c3 = new Point2D.Double(
				(point2.x + point3.x) / 2.0, (point2.y + point3.y) / 2.0);
		
		double len1 = point1.distance(point0);
		double len2 = point2.distance(point1);
		double len3 = point3.distance(point2);
		
		double k1 = len1 / (len1 + len2);
		double k2 = len2 / (len2 + len3);
		
		Point2D.Double m1 = new Point2D.Double(
				c1.x + (c2.x - c1.x) * k1, c1.y + (c2.y - c1.y) * k1);
		Point2D.Double m2 = new Point2D.Double(
				c2.x + (c3.x - c2.x) * k2, c2.y + (c3.y - c2.y) * k2);
		
		control1.setLocation(new Point2D.Double(
				m1.x + (c2.x - m1.x) * smooth + point1.x - m1.x,
				m1.y + (c2.y - m1.y) * smooth + point1.y - m1.y));
		control2.setLocation(new Point2D.Double(
				m2.x + (c2.x - m2.x) * smooth + point2.x - m2.x,
				m2.y + (c2.y - m2.y) * smooth + point2.y - m2.y));
	}
	
	public static Point2D.Double[] getBezierCurve(
			Point2D.Double point0,
			Point2D.Double point1,
			Point2D.Double point2,
			Point2D.Double point3,
			double smooth,
			int steps) {
		Point2D.Double control1 = new Point2D.Double();
		Point2D.Double control2 = new Point2D.Double();
		
		getControlPoints(point0, point1, point2, point3, control1, control2, smooth);
		
		Point2D.Double C = new Point2D.Double(
				3 * (control1.x - point1.x), 3 * (control1.y - point1.y));
		Point2D.Double B = new Point2D.Double(
				3 * (control2.x - control1.x) - C.x, 3 * (control2.y - control1.y) - C.y);
		Point2D.Double A = new Point2D.Double(
				point2.x - point1.x - C.x - B.x, point2.y - point1.y - C.y - B.y);

		Point2D.Double[] res = new Point2D.Double[steps + 1];
		double stepSize = 1.0 / steps;
		double step = stepSize;
	
		res[0] = point1;
		for (int i = 1; i < steps; i++) {
			res[i] = new Point2D.Double(
					A.x * Math.pow(step, 3) + B.x * Math.pow(step, 2) + C.x * step + point1.x,
					A.y * Math.pow(step, 3) + B.y * Math.pow(step, 2) + C.y * step + point1.y);
			System.out.println(step + " : " + res[i]);
			step += stepSize;
		}
		res[steps] = point2;
		
		return res;
	}

    private static XYDataset createDataset()
    {
        TimeSeriesCollection timeseriescollection = new TimeSeriesCollection();
        TimeSeries timeseries = new TimeSeries("Price", org.jfree.data.time.Day.class);
        TimeSeries timeseries1 = new TimeSeries("Price", org.jfree.data.time.Day.class);

        float[] data = new float[200];
        Day[] days = new Day[200];
        int idx = 0;
        int max = 20;
        
        try {
        	BufferedReader reader = new BufferedReader(new FileReader("../fiex/data/PLUS-CA"));
        	String line = null;
        	SimpleDateFormat format1 = new SimpleDateFormat("yyyyMMdd");
        	
        	while ((line = reader.readLine()) != null) {
        		String fields[] = line.split(",");
        		
        		days[idx] = new Day(format1.parse(fields[2]));
        		//timeseries.add(new Day(format1.parse(fields[2])), 1 + Math.log10(Float.parseFloat(fields[6])));
	        	timeseries.add(new Day(format1.parse(fields[2])), Float.parseFloat(fields[6]));
	//        	data[idx++] = (float) (1.0D + Math.log10(Float.parseFloat(fields[6])));
	        	data[idx++] = Float.parseFloat(fields[6]);
	        	if (idx == max) break;
        	}
        	
        	reader.close();
        } catch (Exception e) {
        	e.printStackTrace();
        } finally {
        }
        Core core = new Core();
        MInteger m1 = new MInteger(), m2 = new MInteger();
        double[] out = new double[200];
        core.sma(0, idx-1, data, 7, m1, m2, out);
        
        for (int ix = 0; ix < idx; ix++) {
//        	System.out.println(out[ix]);
        	
        	if (days[ix+6] == null) break;
        	
        	timeseries1.add(days[ix+6], out[ix]);
        }

        //timeseriescollection.addSeries(timeseries);
        timeseriescollection.addSeries(timeseries1);
        return timeseriescollection;
    }

    private static JFreeChart createChart() {
        XYDataset xydataset = createDataset();
        JFreeChart jfreechart = ChartFactory.createTimeSeriesChart("", "Date", "Price", xydataset, true, true, false);
        
        XYPlot xyplot = jfreechart.getXYPlot();

        ((DateAxis) xyplot.getDomainAxis()).setTimeline(SegmentedTimeline.newMondayThroughFridayTimeline());
        
        NumberAxis numberaxis = (NumberAxis)xyplot.getRangeAxis();
        numberaxis.setAutoRangeIncludesZero(false);
        
//        jfreechart.setAntiAlias(true);
        
        xyplot.setRenderer(new XYLineAndShapeRenderer() {
        	protected void drawPrimaryLine(XYItemRendererState state,
        		Graphics2D g2,
        		XYPlot plot,
        		XYDataset dataset,
        		int pass,
        		int series,
        		int item,
        		ValueAxis domainAxis,
        		ValueAxis rangeAxis,
        		Rectangle2D dataArea) {
        		
                if (item == 0) {
                    return;
                }
        		
                // get the data point...
                double x1 = dataset.getXValue(series, item);
                double y1 = dataset.getYValue(series, item);
                if (Double.isNaN(y1) || Double.isNaN(x1)) {
                    return;
                }

                double x0 = dataset.getXValue(series, item - 1);
                double y0 = dataset.getYValue(series, item - 1);
                if (Double.isNaN(y0) || Double.isNaN(x0)) {
                    return;
                }

                RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
                RectangleEdge yAxisLocation = plot.getRangeAxisEdge();

                double transX0 = domainAxis.valueToJava2D(x0, dataArea, xAxisLocation);
                double transY0 = rangeAxis.valueToJava2D(y0, dataArea, yAxisLocation);

                double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
                double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);

                // only draw if we have good values
                if (Double.isNaN(transX0) || Double.isNaN(transY0)
                    || Double.isNaN(transX1) || Double.isNaN(transY1)) {
                    return;
                }
                
        		Point2D.Double point0 = new Point2D.Double();
        		Point2D.Double point1 = new Point2D.Double();
        		Point2D.Double point2 = new Point2D.Double();
        		Point2D.Double point3 = new Point2D.Double();
        		
        		if (item == 1) {
        			point0 = null;
        		} else {
                    point0.x = domainAxis.valueToJava2D(dataset.getXValue(series, item - 2), dataArea, xAxisLocation);
                    point0.y = rangeAxis.valueToJava2D(dataset.getYValue(series, item - 2), dataArea, yAxisLocation);
        		}
        		
        		point1.x = transX0;
    			point1.y = transY0;
    			
    			point2.x = transX1;
    			point2.y = transY1;
    			
    			if ((item + 1) == dataset.getItemCount(series)) {
        			point3 = null;
        		} else {
        			point3.x = domainAxis.valueToJava2D(dataset.getXValue(series, item + 1), dataArea, xAxisLocation);
                    point3.y = rangeAxis.valueToJava2D(dataset.getYValue(series, item + 1), dataArea, yAxisLocation);
        		}

    			int steps = ((int) ((point2.x - point1.x) / 0.2) < 30) ? (int) ((point2.x - point1.x) / 0.2) : 30;
    			
    			Point2D.Double[] points = getBezierCurve(point0, point1, point2, point3, 1, steps);
    			
    			for (int i = 1; i < points.length; i++) {
    				transX0 = points[i - 1].x;
    				transY0 = points[i - 1].y;
    				transX1 = points[i].x;
    				transY1 = points[i].y;
    				
	                PlotOrientation orientation = plot.getOrientation();
	                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)) {
	                    drawFirstPassShape(g2, pass, series, item, state.workingLine);
	                }
    			}
        	}
        	
        	 protected void drawSecondaryPass(Graphics2D g2, XYPlot plot, 
        		XYDataset dataset,
                int pass, int series, int item,
                ValueAxis domainAxis, 
                Rectangle2D dataArea,
                ValueAxis rangeAxis, 
                CrosshairState crosshairState,
                EntityCollection entities) {
        		//super.drawSecondaryPass(g2, plot, dataset, pass, series, item, domainAxis, dataArea, rangeAxis, crosshairState, entities);
        	 }
        });

        xyplot.getRenderer().setStroke(new BasicStroke(1.1f));
        System.out.println(jfreechart.getAntiAlias());
        return jfreechart;
    }

    public static JPanel createDemoPanel() {
        JFreeChart jfreechart = createChart();
        return new ChartPanel(jfreechart);
    }

    public static void main(String args[]) {
        SimpleChart chart = new SimpleChart("Simple Chart");
        chart.pack();
        RefineryUtilities.centerFrameOnScreen(chart);
        chart.setVisible(true);
    }
}

pstng
Posts: 3
Joined: Wed Mar 21, 2007 7:56 pm

Thanks a lot!

Post by pstng » Thu Mar 22, 2007 12:39 pm

Thanks a lot, Weihang.
I've used your class and works perfectly. I have remade your example in order to make easier to use it.
Just copy the class XYSmoothLineAndShapeRenderer and once you have the XYPlot, use the code:

Code: Select all

XYPlot xyplot = jfreechart.getXYPlot();
.
.
.
.
xyplot.setRenderer(new XYSmoothLineAndShapeRenderer());
Here, the XYSmoothLineAndShapeRenderer.java class code:

Code: Select all

import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;

import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.entity.EntityCollection;
import org.jfree.chart.plot.CrosshairState;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRendererState;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.xy.XYDataset;
import org.jfree.ui.RectangleEdge;

public class XYSmoothLineAndShapeRenderer extends XYLineAndShapeRenderer{
	
	protected void drawPrimaryLine(XYItemRendererState state,
            Graphics2D g2,
            XYPlot plot,
            XYDataset dataset,
            int pass,
            int series,
            int item,
            ValueAxis domainAxis,
            ValueAxis rangeAxis,
            Rectangle2D dataArea) {
            
              if (item == 0) {
                  return;
              }
            
              // get the data point...
              double x1 = dataset.getXValue(series, item);
              double y1 = dataset.getYValue(series, item);
              if (Double.isNaN(y1) || Double.isNaN(x1)) {
                  return;
              }

              double x0 = dataset.getXValue(series, item - 1);
              double y0 = dataset.getYValue(series, item - 1);
              if (Double.isNaN(y0) || Double.isNaN(x0)) {
                  return;
              }

              RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
              RectangleEdge yAxisLocation = plot.getRangeAxisEdge();

              double transX0 = domainAxis.valueToJava2D(x0, dataArea, xAxisLocation);
              double transY0 = rangeAxis.valueToJava2D(y0, dataArea, yAxisLocation);

              double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
              double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);

              // only draw if we have good values
              if (Double.isNaN(transX0) || Double.isNaN(transY0)
                  || Double.isNaN(transX1) || Double.isNaN(transY1)) {
                  return;
              }
             
            Point2D.Double point0 = new Point2D.Double();
            Point2D.Double point1 = new Point2D.Double();
            Point2D.Double point2 = new Point2D.Double();
            Point2D.Double point3 = new Point2D.Double();
            
            if (item == 1) {
               point0 = null;
            } else {
                  point0.x = domainAxis.valueToJava2D(dataset.getXValue(series, item - 2), dataArea, xAxisLocation);
                  point0.y = rangeAxis.valueToJava2D(dataset.getYValue(series, item - 2), dataArea, yAxisLocation);
            }
            
            point1.x = transX0;
           point1.y = transY0;
           
           point2.x = transX1;
           point2.y = transY1;
           
           if ((item + 1) == dataset.getItemCount(series)) {
               point3 = null;
            } else {
               point3.x = domainAxis.valueToJava2D(dataset.getXValue(series, item + 1), dataArea, xAxisLocation);
                  point3.y = rangeAxis.valueToJava2D(dataset.getYValue(series, item + 1), dataArea, yAxisLocation);
            }

           int steps = ((int) ((point2.x - point1.x) / 0.2) < 30) ? (int) ((point2.x - point1.x) / 0.2) : 30;
           
           Point2D.Double[] points = getBezierCurve(point0, point1, point2, point3, 1, steps);
           
           for (int i = 1; i < points.length; i++) {
              transX0 = points[i - 1].x;
              transY0 = points[i - 1].y;
              transX1 = points[i].x;
              transY1 = points[i].y;
              
                 PlotOrientation orientation = plot.getOrientation();
                 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)) {
                     drawFirstPassShape(g2, pass, series, item, state.workingLine);
                 }
           }
         }
         
          protected void drawSecondaryPass(Graphics2D g2, XYPlot plot,
            XYDataset dataset,
              int pass, int series, int item,
              ValueAxis domainAxis,
              Rectangle2D dataArea,
              ValueAxis rangeAxis,
              CrosshairState crosshairState,
              EntityCollection entities) {
            //super.drawSecondaryPass(g2, plot, dataset, pass, series, item, domainAxis, dataArea, rangeAxis, crosshairState, entities);
          }
      
	
	   public static void getControlPoints(
	         Point2D.Double point0,
	         Point2D.Double point1,
	         Point2D.Double point2,
	         Point2D.Double point3,
	         Point2D.Double control1,
	         Point2D.Double control2,
	         double smooth) {
	      // Reference: http://www.antigrain.com/research/bezier_interpolation/
	      
	      if (point0 == null) point0 = point1; //new Point2D.Double(0, 0);
	      if (point3 == null) point3 = point2; //new Point2D.Double(0, 0);
	      
	      Point2D.Double c1 = new Point2D.Double(
	            (point0.x + point1.x) / 2.0, (point0.y + point1.y) / 2.0);
	      Point2D.Double c2 = new Point2D.Double(
	            (point1.x + point2.x) / 2.0, (point1.y + point2.y) / 2.0);
	      Point2D.Double c3 = new Point2D.Double(
	            (point2.x + point3.x) / 2.0, (point2.y + point3.y) / 2.0);
	      
	      double len1 = point1.distance(point0);
	      double len2 = point2.distance(point1);
	      double len3 = point3.distance(point2);
	      
	      double k1 = len1 / (len1 + len2);
	      double k2 = len2 / (len2 + len3);
	      
	      Point2D.Double m1 = new Point2D.Double(
	            c1.x + (c2.x - c1.x) * k1, c1.y + (c2.y - c1.y) * k1);
	      Point2D.Double m2 = new Point2D.Double(
	            c2.x + (c3.x - c2.x) * k2, c2.y + (c3.y - c2.y) * k2);
	      
	      control1.setLocation(new Point2D.Double(
	            m1.x + (c2.x - m1.x) * smooth + point1.x - m1.x,
	            m1.y + (c2.y - m1.y) * smooth + point1.y - m1.y));
	      control2.setLocation(new Point2D.Double(
	            m2.x + (c2.x - m2.x) * smooth + point2.x - m2.x,
	            m2.y + (c2.y - m2.y) * smooth + point2.y - m2.y));
	   }
	   
	   public static Point2D.Double[] getBezierCurve(
	         Point2D.Double point0,
	         Point2D.Double point1,
	         Point2D.Double point2,
	         Point2D.Double point3,
	         double smooth,
	         int steps) {
	      Point2D.Double control1 = new Point2D.Double();
	      Point2D.Double control2 = new Point2D.Double();
	      
	      getControlPoints(point0, point1, point2, point3, control1, control2, smooth);
	      
	      Point2D.Double C = new Point2D.Double(
	            3 * (control1.x - point1.x), 3 * (control1.y - point1.y));
	      Point2D.Double B = new Point2D.Double(
	            3 * (control2.x - control1.x) - C.x, 3 * (control2.y - control1.y) - C.y);
	      Point2D.Double A = new Point2D.Double(
	            point2.x - point1.x - C.x - B.x, point2.y - point1.y - C.y - B.y);

	      Point2D.Double[] res = new Point2D.Double[steps + 1];
	      double stepSize = 1.0 / steps;
	      double step = stepSize;
	   
	      res[0] = point1;
	      for (int i = 1; i < steps; i++) {
	         res[i] = new Point2D.Double(
	               A.x * Math.pow(step, 3) + B.x * Math.pow(step, 2) + C.x * step + point1.x,
	               A.y * Math.pow(step, 3) + B.y * Math.pow(step, 2) + C.y * step + point1.y);
	         //System.out.println(step + " : " + res[i]);
	         step += stepSize;
	      }
	      res[steps] = point2;
	      
	      return res;
	   }

}
I hope that the example will be useful to the other users who were asking for that.

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 » Thu Mar 22, 2007 4:27 pm

This looks really interesting. I haven't studied the implementation in detail, but I wondered if anyone has experimented with building up an entire path for the series using GeneralPath.curveTo(), then drawing the path once the last item in the series is reached. The XYLineAndShapeRenderer does this with line segments when the drawSeriesLineAsPath attribute is set to true...maybe the same thing can be done, but taking advantage of that curveTo() method. Or am I going off track?
David Gilbert
JFreeChart Project Leader

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

clark24
Posts: 2
Joined: Wed Jul 31, 2013 7:33 am
antibot: No, of course not.

Re: Smoothed Line & Anti-aliasing

Post by clark24 » Wed Jul 31, 2013 8:03 am

I had the same problem and I just went to drawing lines using sprites at every length/space. It looks great and I have more control over the line by the sprite I use and I can even apply effects and actions to the line.

Locked