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);
}
}

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;
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:

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);
}
}
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.