XYLineChart- smooth line, XYSplineRenderer not working
XYLineChart- smooth line, XYSplineRenderer not working
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.
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.
Re: XYLineChart - smooth line
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:
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.
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);
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.
Re: XYLineChart- smooth line, XYSplineRenderer not working
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.
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;
}
}
Re: XYLineChart- smooth line, XYSplineRenderer not working
Hi,
Thanks for the reply and the solution. I will test it out and let you know how it goes.
Thanks.
Thanks for the reply and the solution. I will test it out and let you know how it goes.
Thanks.
Re: XYLineChart- smooth line, XYSplineRenderer not working
@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:
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.
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).
What do you reckon is causing this and how do I avoid this strange rendering of the "smooth" lines ?
Thanks.
Re: XYLineChart- smooth line, XYSplineRenderer not working
I plotted those data points and it looks fine to me. Please post a self-contained runnable example showing what you describe.
Re: XYLineChart- smooth line, XYSplineRenderer not working
@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:
XYCardinalSplineRenderer.java
SmoothLineChartDemo.java
PlotTheChart.java
The Chart that it generates with the aforementioned values is below:
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:
And here is the code used by me: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).
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;
}
}
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;
}
}
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;
}
}
Re: XYLineChart- smooth line, XYSplineRenderer not working
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.
Re: XYLineChart- smooth line, XYSplineRenderer not working
How do I do that:
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.reduce the tension closer to 0 to have less of a curve
Re: XYLineChart- smooth line, XYSplineRenderer not working
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.
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.
Re: XYLineChart- smooth line, XYSplineRenderer not working
@remiohead:
Thanks, it works when I reduce the tension to 0.05 from 0.5. Thanks for the solution.
Thanks, it works when I reduce the tension to 0.05 from 0.5. Thanks for the solution.