XYLineChart- smooth line, XYSplineRenderer not working

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
AjitPS
Posts: 49
Joined: Tue Oct 22, 2013 10:08 am
antibot: No, of course not.
Location: UK

XYLineChart- smooth line, XYSplineRenderer not working

Post by AjitPS » Wed Feb 26, 2014 4:08 pm

Hi,
Is there any way to have a smooth line connecting the x, y points for an XYSeries in JFreeChart's XYLineChart ?

If I have multiple x, y points with gradually decreasing y-axis values, e.g., (1.0,450.0) , (2.0,360.0), (3.0,290.0), (4.0,228.0), (5.0,189.0), (6.0,112.0), (7.0,82.0), (8.0,70.0), etc., can the line joining them be shown as a smooth line ?

If anyone has any suggestions about how to implement this, please do let me know. Thanks.
Last edited by AjitPS on Thu Feb 27, 2014 5:42 pm, edited 1 time in total.

AjitPS
Posts: 49
Joined: Tue Oct 22, 2013 10:08 am
antibot: No, of course not.
Location: UK

Re: XYLineChart - smooth line

Post by AjitPS » Thu Feb 27, 2014 3:19 pm

I had read that the XYSplineRenderer can be used to accomplish this and tried using it but it did not work for me. I had used:

Code: Select all

final XYSplineRenderer rend = new XYSplineRenderer();
rend.setPrecision(2); // Precision: the number of line segments between 2 points [default: 5]

rend.setSeriesItemLabelsVisible(0, false);
rend.setSeriesShapesVisible(0, true);
rend.setSeriesPaint(0, Color.red);
rend.setSeriesStroke(0, new BasicStroke(1.5f));

plot.setRenderer(rend);
With the XYSplineRenderer, the line with the points on it did not have a smooth slope. Instead, the slope abruptly went up and down between 2 points in some cases for no apparent reason.

If anyone has any other ideas about how to achieve smooth lines in an XYLineChart or on how to correctly use the XYSplineRenderer, please let me know.
Thanks.

remiohead
Posts: 201
Joined: Fri Oct 02, 2009 3:53 pm
antibot: No, of course not.

Re: XYLineChart- smooth line, XYSplineRenderer not working

Post by remiohead » Thu Feb 27, 2014 11:05 pm

Here is an alternative based on this Stackoverflow answer http://stackoverflow.com/a/15528789

I put this together very quickly so I cannot vouch for correctness nor have I tested it for more than a few minutes.

Code: Select all

import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.List;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.PlotRenderingInfo;
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;

import com.google.common.collect.Lists;

public class XYCardinalSplineRenderer extends XYLineAndShapeRenderer {

	private static final long serialVersionUID = -5155759408423090131L;
	
	private final List<Point2D> points =
			Lists.newArrayList();

	public XYCardinalSplineRenderer() {
		super();
	} 
	
	@Override
    public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea,
            XYPlot plot, XYDataset data, PlotRenderingInfo info) {

        State state = (State) super.initialise(g2, dataArea, plot, data, info);
        state.setProcessVisibleItemsOnly(false);
        this.points.clear();
        setDrawSeriesLineAsPath(true);
        return state;
    }
	
	@Override
	protected void drawPrimaryLineAsPath(XYItemRendererState state,
            Graphics2D g2, XYPlot plot,
            XYDataset dataset,
            int pass,
            int series,
            int item,
            ValueAxis domainAxis,
            ValueAxis rangeAxis,
            Rectangle2D dataArea) {


		RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
		RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
		
		// get the data point...
		double x1 = dataset.getXValue(series, item);
		double y1 = dataset.getYValue(series, item);
		double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
		double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
		
		State s = (State) state;
		// update path to reflect latest point
		if (!Double.isNaN(transX1) && !Double.isNaN(transY1)) {
			this.points.add(new Point2D.Double(transX1, transY1));
		}
		// if this is the last item, draw the path ...
		if (item == s.getLastItemIndex()) {
			List<Point2D> pts = this.getCurvePoints();
			s.seriesPath.moveTo(
					pts.get(0).getX(), 
					pts.get(0).getY());
			
			for(int i = 1; i < pts.size(); i++) {
				final Point2D p = pts.get(i);
				s.seriesPath.lineTo(p.getX(), p.getY());
			}
			
			 // draw path
            drawFirstPassShape(g2, pass, series, item, s.seriesPath);
            this.points.clear();
		}
	}
	
	private List<Point2D> getCurvePoints() {
		List<Point2D> pts = this.points;
		pts.add(0, (Point2D)pts.get(0).clone());
		pts.add((Point2D)pts.get(pts.size()-1).clone());
		
		double tension = 0.5;
		int segments = 16;
		
		List<Point2D> result = Lists.newArrayList();
		
		for(int i = 1; i < pts.size()-2; i++) {
			for(int t = 0; t <= segments; t++) {
				
				double t1x = (pts.get(i+1).getX() - pts.get(i-1).getX()) * tension;
				double t2x = (pts.get(i+2).getX() - pts.get(i).getX()) * tension;
				
				double t1y = (pts.get(i+1).getY() - pts.get(i-1).getY()) * tension;
				double t2y = (pts.get(i+2).getY() - pts.get(i).getY()) * tension;
				
				double st = (double)t/(double)segments;
				
				double c1 =   2 * Math.pow(st, 3)  - 3 * Math.pow(st, 2) + 1; 
				double c2 = -(2 * Math.pow(st, 3)) + 3 * Math.pow(st, 2); 
				double c3 =       Math.pow(st, 3)  - 2 * Math.pow(st, 2) + st; 
				double c4 =       Math.pow(st, 3)  -     Math.pow(st, 2);
				
				double x = c1 * pts.get(i).getX()  + c2 * pts.get(i+1).getX() + c3 * t1x + c4 * t2x;
	            double y = c1 * pts.get(i).getY()  + c2 * pts.get(i+1).getY() + c3 * t1y + c4 * t2y;
	            
	            result.add(new Point2D.Double(x, y));
			}
		}
		
		return result;
	}
}

AjitPS
Posts: 49
Joined: Tue Oct 22, 2013 10:08 am
antibot: No, of course not.
Location: UK

Re: XYLineChart- smooth line, XYSplineRenderer not working

Post by AjitPS » Mon Mar 03, 2014 4:19 pm

Hi,
Thanks for the reply and the solution. I will test it out and let you know how it goes.
Thanks.

AjitPS
Posts: 49
Joined: Tue Oct 22, 2013 10:08 am
antibot: No, of course not.
Location: UK

Re: XYLineChart- smooth line, XYSplineRenderer not working

Post by AjitPS » Mon Mar 03, 2014 4:33 pm

@remiohead, I just tested out the XYCardinalSplineRendderer code posted by you. However, the problem still persists. For one of the examples, it plotted a smooth line between the points (just like JFreeChart's XYSplineRenderer). However, for another dataset the lines went haywire between certain points.

In the dataset that it didn't work with, the x, y values were:

Code: Select all

(1.0, 60.22), (2.0, 38.79), (3.0, 27.91), (4.0, 22.78), (5.0, 19.69), (6.0, 17.58), (7.0, 16.01), (8.0, 14.81), (9.0, 13.84) and (10.0, 13.03).
The plotted line went smoothly upto (4.0, 22.78) and then strangely went downwards below 0,0 and upwards again before going to (5.0, 19.69).
What do you reckon is causing this and how do I avoid this strange rendering of the "smooth" lines ?
Thanks.

remiohead
Posts: 201
Joined: Fri Oct 02, 2009 3:53 pm
antibot: No, of course not.

Re: XYLineChart- smooth line, XYSplineRenderer not working

Post by remiohead » Mon Mar 03, 2014 8:15 pm

I plotted those data points and it looks fine to me. Please post a self-contained runnable example showing what you describe.

AjitPS
Posts: 49
Joined: Tue Oct 22, 2013 10:08 am
antibot: No, of course not.
Location: UK

Re: XYLineChart- smooth line, XYSplineRenderer not working

Post by AjitPS » Tue Mar 04, 2014 12:09 pm

@remiohead:
Hi,
I have included some sample data for which the smooth lines go haywire along with my code. I have used the XYCardinalSplineRenderer that you posted but have made one change to it. Instead of using google.collections.Lists, I have used a java ArrayList. This is because I tried to manually download the google guava jar yesterday for using the Lists collection but the download kept failing. Here is the sample dataset:
Series 1: (1.0,335.8359), (2.0,61.1282), (3.0,11.9879), (4.0,6.5319), (5.0,4.7309), (6.0,3.8591), (7.0,3.3475), (8.0,3.0107), (9.0,2.7715), (10.0,2.5922).

Series 2: (1.0,179.0455), (2.0,10.6659), (3.0,5.492), (4.0,4.0155), (5.0,3.3248), (6.0,2.9225), (7.0,2.6575), (8.0,2.4686), (9.0,2.3265), (10.0,2.2151).

Series 3: (1.0,39.2073), (2.0,7.3499), (3.0,4.5072), (4.0,3.5111), (5.0,3.0009), (6.0,2.688), (7.0,2.4748), (8.0,2.3192), (9.0,2.2001), (10.0,2.1055).

Series 4: (1.0,22.757), (2.0,6.2665), (3.0,4.1198), (4.0,3.2969), (5.0,2.8575), (6.0,2.5814), (7.0,2.3902), (8.0,2.2491), (9.0,2.1401), (10.0,2.053).

Series 5: (1.0,17.2905), (2.0,5.7367), (3.0,3.9135), (4.0,3.1785), (5.0,2.7766), (6.0,2.5204), (7.0,2.3414), (8.0,2.2084), (9.0,2.1051), (10.0,2.0223).
And here is the code used by me:
XYCardinalSplineRenderer.java

Code: Select all

package smooth;

//import com.google.common.collect.Lists;
import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.PlotRenderingInfo;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRendererState;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.ui.RectangleEdge;
import org.jfree.data.xy.XYDataset;

/*
 * @author remiohead
 */
public class XYCardinalSplineRenderer extends XYLineAndShapeRenderer {

   private static final long serialVersionUID = -5155759408423090131L;

//   private final List<Point2D> points = Lists.newArrayList();
   private final List<Point2D> points = new ArrayList<>();

   public XYCardinalSplineRenderer() {
      super();
   } 
   
   @Override
    public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea,
            XYPlot plot, XYDataset data, PlotRenderingInfo info) {

        State state = (State) super.initialise(g2, dataArea, plot, data, info);
        state.setProcessVisibleItemsOnly(false);
        this.points.clear();
        setDrawSeriesLineAsPath(true);
        return state;
    }
   
   @Override
   protected void drawPrimaryLineAsPath(XYItemRendererState state,
            Graphics2D g2, XYPlot plot,
            XYDataset dataset,
            int pass,
            int series,
            int item,
            ValueAxis domainAxis,
            ValueAxis rangeAxis,
            Rectangle2D dataArea) {


      RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
      RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
      
      // get the data point...
      double x1 = dataset.getXValue(series, item);
      double y1 = dataset.getYValue(series, item);
      double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
      double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
      
      State s = (State) state;
      // update path to reflect latest point
      if (!Double.isNaN(transX1) && !Double.isNaN(transY1)) {
         this.points.add(new Point2D.Double(transX1, transY1));
      }
      // if this is the last item, draw the path ...
      if (item == s.getLastItemIndex()) {
         List<Point2D> pts = this.getCurvePoints();
         s.seriesPath.moveTo(
               pts.get(0).getX(), 
               pts.get(0).getY());
         
         for(int i = 1; i < pts.size(); i++) {
            final Point2D p = pts.get(i);
            s.seriesPath.lineTo(p.getX(), p.getY());
         }
         
          // draw path
            drawFirstPassShape(g2, pass, series, item, s.seriesPath);
            this.points.clear();
      }
   }
   
   private List<Point2D> getCurvePoints() {
      List<Point2D> pts = this.points;
      pts.add(0, (Point2D)pts.get(0).clone());
      pts.add((Point2D)pts.get(pts.size()-1).clone());
      
      double tension = 0.5;
      int segments = 16;

//      List<Point2D> result = Lists.newArrayList();
      List<Point2D> result = new ArrayList<>();

      for(int i = 1; i < pts.size()-2; i++) {
         for(int t = 0; t <= segments; t++) {
            
            double t1x = (pts.get(i+1).getX() - pts.get(i-1).getX()) * tension;
            double t2x = (pts.get(i+2).getX() - pts.get(i).getX()) * tension;
            
            double t1y = (pts.get(i+1).getY() - pts.get(i-1).getY()) * tension;
            double t2y = (pts.get(i+2).getY() - pts.get(i).getY()) * tension;
            
            double st = (double)t/(double)segments;
            
            double c1 =   2 * Math.pow(st, 3)  - 3 * Math.pow(st, 2) + 1; 
            double c2 = -(2 * Math.pow(st, 3)) + 3 * Math.pow(st, 2); 
            double c3 =       Math.pow(st, 3)  - 2 * Math.pow(st, 2) + st; 
            double c4 =       Math.pow(st, 3)  -     Math.pow(st, 2);
            
            double x = c1 * pts.get(i).getX()  + c2 * pts.get(i+1).getX() + c3 * t1x + c4 * t2x;
               double y = c1 * pts.get(i).getY()  + c2 * pts.get(i+1).getY() + c3 * t1y + c4 * t2y;
               
               result.add(new Point2D.Double(x, y));
         }
      }
      
      return result;
   }
}
SmoothLineChartDemo.java

Code: Select all

package smooth;

import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.ui.ApplicationFrame;
import org.jfree.chart.ui.RefineryUtilities;

/*
 * @author Ajit Singh
 */
public class SmoothLineChartDemo extends ApplicationFrame {
    
   public SmoothLineChartDemo(String title) {
        super(title);
        ChartPanel chartPanel = (ChartPanel) makeDemoPanel();
        chartPanel.setPreferredSize(new java.awt.Dimension(1150, 620));
        setContentPane(chartPanel);
   }

    public static void main(String[] args)
    {
     SmoothLineChartDemo demo= new SmoothLineChartDemo("SplineRendering Test");
     demo.pack();
     RefineryUtilities.centerFrameOnScreen(demo);
     demo.setVisible(true);
    }

    private ChartPanel makeDemoPanel()
    {
     double[][] dataset= getSampleDataset();

     PlotTheChart ptc= new PlotTheChart();
     JFreeChart chart= ptc.makeChart(dataset);

     ChartPanel panel= new ChartPanel(chart);
     panel.setFillZoomRectangle(true);
     panel.setMouseWheelEnabled(true);
     return panel;
    }

    private double[][] getSampleDataset() {
        double[][] data= new double[5][10];
        // Sample series data.
        double[] seriesOneData= { 335.8359, 61.1282, 11.9879, 6.5319, 4.7309, 3.8591, 3.3475, 3.0107, 2.7715, 2.5922 };
        double[] seriesTwoData= { 179.0455, 10.6659, 5.492, 4.0155, 3.3248, 2.9225, 2.6575, 2.4686, 2.3265, 2.2151 };
        double[] seriesThreeData= { 39.2073, 7.3499, 4.5072, 3.5111, 3.0009, 2.688, 2.4748, 2.3192, 2.2001, 2.1055 };
        double[] seriesFourData= { 22.757, 6.2665, 4.1198, 3.2969, 2.8575, 2.5814, 2.3902, 2.2491, 2.1401, 2.053 };
        double[] seriesFiveData= { 17.2905, 5.7367, 3.9135, 3.1785, 2.7766, 2.5204, 2.3414, 2.2084, 2.1051, 2.0223 };

        for(int i=0; i< 5; i++)
           {
            for(int j=0; j< 10; j++)
               {
                switch(i) {
                 case 0: data[i][j]= seriesOneData[j];
                         break;

                 case 1: data[i][j]= seriesTwoData[j];
                         break;

                 case 2: data[i][j]= seriesThreeData[j];
                         break;

                 case 3: data[i][j]= seriesFourData[j];
                         break;

                 case 4: data[i][j]= seriesFiveData[j];
                         break;
                }
               }
           }
        return data;
    }
}
PlotTheChart.java

Code: Select all

package smooth;

import java.awt.Color;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

/*
 * @author Ajit Singh
 */
public class PlotTheChart {

  private JFreeChart demoChart;

  public JFreeChart makeChart(double[][] yData)
  {
   // Creating all the Series.
   final XYSeriesCollection chartDataset = makeAllTheSeries(yData);

   // Create the XYLine Chart.
   demoChart = ChartFactory.createXYLineChart("DemoChart", "x-axis", "y-axis", chartDataset);

   demoChart.setBackgroundPaint(Color.WHITE);
   XYPlot demoChartPlot= (XYPlot) demoChart.getPlot();
   demoChartPlot.setDomainGridlinePaint(Color.BLACK);
   demoChartPlot.setRangeGridlinePaint(Color.BLACK); 

   try {
   // Using the CardinalSplineRenderer.
   final XYCardinalSplineRenderer csRenderer = new XYCardinalSplineRenderer();
   csRenderer.setDrawOutlines(true);

   // Series 1.
   csRenderer.setSeriesShapesVisible(0, false);
   csRenderer.setSeriesPaint(0, Color.red);

   // Series 2.
   csRenderer.setSeriesShapesVisible(1, false);
   csRenderer.setSeriesPaint(1, Color.green);

   // Series 3.
   csRenderer.setSeriesShapesVisible(2, false);
   csRenderer.setSeriesPaint(2, Color.orange);
   
   // Series 4.
   csRenderer.setSeriesShapesVisible(3, false);
   csRenderer.setSeriesPaint(3, Color.magenta);

   // Series 5.
   csRenderer.setSeriesShapesVisible(4, false);
   csRenderer.setSeriesPaint(4, Color.blue);
   
   demoChartPlot.setDomainCrosshairVisible(true);
   demoChartPlot.setRangeCrosshairVisible(true);
   // Add the Renderer to the Plot.
   demoChartPlot.setRenderer(csRenderer);

   demoChartPlot.setDomainPannable(true);
   demoChartPlot.setRangePannable(true);

   final NumberAxis yAxis = (NumberAxis) demoChartPlot.getRangeAxis();
   yAxis.setStandardTickUnits(NumberAxis.createStandardTickUnits());
   // Set the visible y-axis range from 0.0 to 10.0
   yAxis.setLowerBound(0.0);
   yAxis.setUpperBound(10.0);
   yAxis.setTickLabelsVisible(true);
  }
  catch(Exception ex) {
        ex.printStackTrace();
       }
   
  return demoChart;
 }

  private XYSeriesCollection makeAllTheSeries(double[][] yData)
  { // Creating all the Series.
   final XYSeries s1 = new XYSeries("Series 1");
   final XYSeries s2 = new XYSeries("Series 2");
   final XYSeries s3 = new XYSeries("Series 3");
   final XYSeries s4 = new XYSeries("Series 4");
   final XYSeries s5 = new XYSeries("Series 5");
   double[] xData= {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0};

   for(int i=0; i<5; i++)
      {
       for(int j=0; j< 10; j++)
          {
           switch(i) {
                 case 0: s1.add(xData[j], yData[i][j]);
                         break;

                 case 1: s2.add(xData[j], yData[i][j]);
                         break;

                 case 2: s3.add(xData[j], yData[i][j]);
                         break;

                 case 3: s4.add(xData[j], yData[i][j]);
                         break;

                 case 4: s5.add(xData[j], yData[i][j]);
                         break;
           }
          }
      }

   // Display all the Series Values.
   System.out.println("\n Series 1: ");
   for(int k=0; k< s1.getItemCount(); k++) {
       System.out.print("("+ s1.getX(k) +","+ s1.getY(k) +"), ");
      }

   System.out.println("\n Series 2: ");
   for(int k=0; k< s2.getItemCount(); k++) {
       System.out.print("("+ s2.getX(k) +","+ s2.getY(k) +"), ");
      }

   System.out.println("\n Series 3: ");
   for(int k=0; k< s3.getItemCount(); k++) {
       System.out.print("("+ s3.getX(k) +","+ s3.getY(k) +"), ");
      }

   System.out.println("\n Series 4: ");
   for(int k=0; k< s4.getItemCount(); k++) {
       System.out.print("("+ s4.getX(k) +","+ s4.getY(k) +"), ");
      }

   System.out.println("\n Series 5: ");
   for(int k=0; k< s5.getItemCount(); k++) {
       System.out.print("("+ s5.getX(k) +","+ s5.getY(k) +"), ");
      }

   final XYSeriesCollection chartDataset = new XYSeriesCollection();
   // Adding all the XY Series to the chart Dataset.
   chartDataset.addSeries(s1);
   chartDataset.addSeries(s2);
   chartDataset.addSeries(s3);
   chartDataset.addSeries(s4);
   chartDataset.addSeries(s5);
   
   return chartDataset;
 }
}
The Chart that it generates with the aforementioned values is below:
Image

remiohead
Posts: 201
Joined: Fri Oct 02, 2009 3:53 pm
antibot: No, of course not.

Re: XYLineChart- smooth line, XYSplineRenderer not working

Post by remiohead » Tue Mar 04, 2014 3:05 pm

It's not a problem. So far as I can tell, this is the correct result for these data points. You can reduce the tension closer to 0 to have less of a curve. Otherwise you'll need to implement a different type of spline.

AjitPS
Posts: 49
Joined: Tue Oct 22, 2013 10:08 am
antibot: No, of course not.
Location: UK

Re: XYLineChart- smooth line, XYSplineRenderer not working

Post by AjitPS » Tue Mar 04, 2014 3:19 pm

How do I do that:
reduce the tension closer to 0 to have less of a curve
What I find strange is that in Series 2 (the green one) the curve goes below 0 and then comes back up between points 2.0 and 3.0 on the x-axis while the x, y points in the Series for those 2 points are: (2.0,10.6659), (3.0,5.492). I expected it to smoothly go from 10.6659 to 5.492. I don't understand why the plotted curve goes from 10.6659 to below 0 and then comes back up to 5.492.

remiohead
Posts: 201
Joined: Fri Oct 02, 2009 3:53 pm
antibot: No, of course not.

Re: XYLineChart- smooth line, XYSplineRenderer not working

Post by remiohead » Tue Mar 04, 2014 6:35 pm

You have two points, p1 and p2, the points before and after (p0 and p3) are used as control points for constructing the interpolation between p1 and p2. If you want more details you can follow the link from my original reply.

There is a variable called tension which is set to a default value of 0.5. Adjust this lower to have less of a curve.

AjitPS
Posts: 49
Joined: Tue Oct 22, 2013 10:08 am
antibot: No, of course not.
Location: UK

Re: XYLineChart- smooth line, XYSplineRenderer not working

Post by AjitPS » Wed Mar 05, 2014 1:48 pm

@remiohead:
Thanks, it works when I reduce the tension to 0.05 from 0.5. Thanks for the solution.

Locked