Plots with large values (10^15+)

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
Fluxim
Posts: 4
Joined: Tue Oct 22, 2013 1:56 pm
antibot: No, of course not.

Plots with large values (10^15+)

Post by Fluxim » Tue Nov 12, 2013 3:02 pm

Hello everybody!

We are using JFreeChart in our software to plot scientific data which can be small (0-1000) but also reach values of 1e23 or more.
The Y axis only seems to display ticks until 1e13 correctly. With values of 1e14 the display seems to break and with 1e15 the ticks disappear completely.
I have created a small example which demonstrates the problem:

Code: Select all

public class JFreeTest extends JFrame {
   
   public JFreeTest(String title, double upperValue) {
      super(title);
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

      DefaultXYDataset dataset = new DefaultXYDataset();
      dataset.addSeries("s1", new double[][] { { 0, 1 }, { 0, upperValue } });

      JFreeChart chart = ChartFactory.createXYLineChart(title, null, null, dataset, PlotOrientation.VERTICAL, false, true, false);
      // XYPlot plot = (XYPlot) chart.getPlot();
      // ((NumberAxis) plot.getRangeAxis()).setNumberFormatOverride(new DecimalFormat("0.####E0"));

      ChartPanel chartPanel = new ChartPanel(chart);
      chartPanel.setPreferredSize(new Dimension(400, 300));
      add(chartPanel);
      pack();
      setLocationRelativeTo(null);
      setVisible(true);
   }

   public static void main(String[] args) {
      new JFreeTest("ok", 1e13);
      new JFreeTest("strange", 1e14);
      new JFreeTest("bad", 1e15);
   }
}
Essentially this code generates 3 plots with curves reaching different max values. The resulting plots look like this:
Image
Of course we normally also set the scientific format override, but this doesn't make any difference here. (see commented code)

Our next step was to have a look at NumberAxis.createStandardTickUnits() and replace the it with a more generic method. This code creates ticks for values between 1e-100 and 1e100:

Code: Select all

TickUnits units = new TickUnits();

DecimalFormat c3 = new DecimalFormat("0.000");
DecimalFormat c2 = new DecimalFormat("0.00");
DecimalFormat c1 = new DecimalFormat("0.0");
DecimalFormat c0 = new DecimalFormat("####");
DecimalFormat e = new DecimalFormat("0.######E0");
DecimalFormat e1 = new DecimalFormat("0.0#####E0");

for (int i = -100; i <= 100; i++) {
   double d1 = Math.pow(10, i);
   if (d1 == 0.001) {
      units.add(new NumberTickUnit(d1, c3, 2));
   } else if (d1 == 0.01) {
      units.add(new NumberTickUnit(d1, c2, 2));
   } else if (d1 == 0.1) {
      units.add(new NumberTickUnit(d1, c1, 2));
   } else if (d1 >= 1 && d1 <= 1000) {
      units.add(new NumberTickUnit(d1, c0, 2));
   } else {
      units.add(new NumberTickUnit(d1, e, 2));
   }

   double d25 = Math.pow(10, i) * 25.0;
   if (d25 == 0.025) {
      units.add(new NumberTickUnit(d25, c3, 5));
   } else if (d25 == 0.25) {
      units.add(new NumberTickUnit(d25, c2, 5));
   } else if (d25 == 2.5) {
      units.add(new NumberTickUnit(d25, c1, 5));
   } else if (d25 >= 25 && d25 <= 2500) {
      units.add(new NumberTickUnit(d25, c0, 5));
   } else {
      units.add(new NumberTickUnit(d25, e1, 5));
   }

   double d5 = Math.pow(10, i) * 5;
   if (d5 == 0.005) {
      units.add(new NumberTickUnit(d5, c3, 5));
   } else if (d5 == 0.05) {
      units.add(new NumberTickUnit(d5, c2, 5));
   } else if (d5 == 0.5) {
      units.add(new NumberTickUnit(d5, c1, 5));
   } else if (d5 >= 5 && d5 <= 5000) {
      units.add(new NumberTickUnit(d5, c0, 5));
   } else {
      units.add(new NumberTickUnit(d5, e, 5));
   }
}
return units;
This seems to solve the first problem.

However when we have for example data points between 1e21 and 1e22, we have to set setAutoRangeIncludesZero() to false in the axis in order to be able to see any change in the curve. If we do that, the Y axis is cut off in the middle, but only for values >= 1e17 !
The plot is drawn correctly after a re-size, zoom, or simple click in the axis region. This is how the plot looks before and after clicking:
Image
The whole code:

Code: Select all

public class JFreeTest extends JFrame {

   public JFreeTest(String title, double upperValue) {
      super(title);
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

      DefaultXYDataset dataset = new DefaultXYDataset();
      dataset.addSeries("s1", new double[][] { { 0, 1 }, { upperValue / 2, upperValue } });

      JFreeChart chart = ChartFactory.createXYLineChart(title, null, null, dataset, PlotOrientation.VERTICAL, false, true, false);
      XYPlot plot = (XYPlot) chart.getPlot();
      ((NumberAxis) plot.getRangeAxis()).setStandardTickUnits(creatTickUnits());
      ((NumberAxis) plot.getRangeAxis()).setAutoRangeIncludesZero(false);

      ChartPanel chartPanel = new ChartPanel(chart);
      chartPanel.setPreferredSize(new Dimension(400, 300));
      add(chartPanel);
      pack();
      setLocationRelativeTo(null);
      setVisible(true);
   }

   private static TickUnitSource creatTickUnits() {

      TickUnits units = new TickUnits();
      DecimalFormat c3 = new DecimalFormat("0.000");
      DecimalFormat c2 = new DecimalFormat("0.00");
      DecimalFormat c1 = new DecimalFormat("0.0");
      DecimalFormat c0 = new DecimalFormat("####");
      DecimalFormat e = new DecimalFormat("0.######E0");
      DecimalFormat e1 = new DecimalFormat("0.0#####E0");

      for (int i = -100; i <= 100; i++) {
         double d1 = Math.pow(10, i);
         if (d1 == 0.001) {
            units.add(new NumberTickUnit(d1, c3, 2));
         } else if (d1 == 0.01) {
            units.add(new NumberTickUnit(d1, c2, 2));
         } else if (d1 == 0.1) {
            units.add(new NumberTickUnit(d1, c1, 2));
         } else if (d1 >= 1 && d1 <= 1000) {
            units.add(new NumberTickUnit(d1, c0, 2));
         } else {
            units.add(new NumberTickUnit(d1, e, 2));
         }

         double d25 = Math.pow(10, i) * 25.0;
         if (d25 == 0.025) {
            units.add(new NumberTickUnit(d25, c3, 5));
         } else if (d25 == 0.25) {
            units.add(new NumberTickUnit(d25, c2, 5));
         } else if (d25 == 2.5) {
            units.add(new NumberTickUnit(d25, c1, 5));
         } else if (d25 >= 25 && d25 <= 2500) {
            units.add(new NumberTickUnit(d25, c0, 5));
         } else {
            units.add(new NumberTickUnit(d25, e1, 5));
         }

         double d5 = Math.pow(10, i) * 5;
         if (d5 == 0.005) {
            units.add(new NumberTickUnit(d5, c3, 5));
         } else if (d5 == 0.05) {
            units.add(new NumberTickUnit(d5, c2, 5));
         } else if (d5 == 0.5) {
            units.add(new NumberTickUnit(d5, c1, 5));
         } else if (d5 >= 5 && d5 <= 5000) {
            units.add(new NumberTickUnit(d5, c0, 5));
         } else {
            units.add(new NumberTickUnit(d5, e, 5));
         }
      }
      return units;
   }

   public static void main(String[] args) {
      new JFreeTest("ok", 1e13);
      new JFreeTest("bad", 1e17);
   }
}
Is there a better way to plot scientific values? Did we miss something, or is it a bug in JFreeChart?

We are struggling with this problem for a long time and any help is highly appreciated! :)
Thank you!

Edit: Used versions are jfreechart-1.0.16 and jcommon-1.0.21.

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

Re: Plots with large values (10^15+)

Post by david.gilbert » Mon Nov 18, 2013 9:38 am

Hi,

This is a bug in JFreeChart but I'm not sure of the right fix for it. It is coming because the auto tick selection is starting with the current tick size (which is 1.0 by default) then trying to extrapolate the next best guess while searching for the right tick size. As a workaround, you can set the tick size to something much closer to your data range before the chart is first painted, then the auto-tick selection will work correctly the first time. In your example I added this:

Code: Select all

      NumberAxis yAxis = (NumberAxis) plot.getRangeAxis();
      yAxis.setTickUnit(new NumberTickUnit(upperValue / 2), false, false);
...right before:

Code: Select all

      ((NumberAxis) plot.getRangeAxis()).setStandardTickUnits(creatTickUnits());
      ((NumberAxis) plot.getRangeAxis()).setAutoRangeIncludesZero(false);
      
      ChartPanel chartPanel = new ChartPanel(chart);
      chartPanel.setPreferredSize(new Dimension(400, 300));
David Gilbert
JFreeChart Project Leader

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

Fluxim
Posts: 4
Joined: Tue Oct 22, 2013 1:56 pm
antibot: No, of course not.

Re: Plots with large values (10^15+)

Post by Fluxim » Mon Nov 18, 2013 4:45 pm

This seems to solve the problem.
Thank you very much!

Gxb0514
Posts: 10
Joined: Mon Dec 02, 2013 2:30 am
antibot: No, of course not.

Re: Plots with large values (10^15+)

Post by Gxb0514 » Tue Dec 03, 2013 3:02 am

Hi everyone

This was a great threat as I too have run into a similar issue. I am using Jfreechart version 1.0.16. I am trying to plot scatter chart with data in which the domain axis (X-axis) can have very large values; larger than 2^32, thus exceeding the capacity of float primitives. At first I was planning to use the FastScatterPlot implementation, however I notices that as a constructor it takes a float[][] matrix which will not support my requirements. I actually looked over the source code for fastScatterPlot and tried to change the floats to double and while the data seemed to plot ok, it broke the x-axis labels. So now I am using a standard ScatterPlot with a XYdataset containing a single XYSeries which can accept type double primitives. Can you see anything I can do better than this? Will I run into issue with trying to plot data across a very large domain axis?

PS: I purchased the developers manual and have access to the source code of the examples now. They are very useful however, unfortunately, the 4 ScatterPlotDemo examples included (under the statistical charts folder) make a call to a SampleXYDataset2() function which is not included in the source code so I am not sure I am using the best data input objects for ScatterPlot.

PSS. On a bit of a tangent, back on issues with the examples, under the markers folder there is a MarkerDemo1 that shows how to use markers to include drawing a circle around a data point. However I cannot get the call to
CircleDrawer cd = new CircleDrawer(Color.red, new BasicStroke(1.0f), null);
To work. I am using the JCommon distribution that came with jfreechart (1.0.20) but it does not appear to include the drawable library that this class is supposed to be in. am I doing something stupid?

Thanks all.

Locked