I have a JFreeChart
chart with DateAxis
as domain. It looks very nice, however the last axis label sometimes goes out of the chart area. Here is the sample code to reproduce:
public class LineChart_AWT extends ApplicationFrame {
public LineChart_AWT( String applicationTitle , String chartTitle ) {
super(applicationTitle);
ValueAxis timeAxis = new DateAxis("");
NumberAxis valueAxis = new NumberAxis("Number");
((DateAxis)timeAxis).setDateFormatOverride(new SimpleDateFormat("YYYY-MM-dd HH:mm"));
XYPlot plot = new XYPlot(createDataset(), timeAxis, valueAxis, null);
XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer(true, false);
plot.setRenderer(renderer);
plot.getRangeAxis().setAutoRange(true);
((NumberAxis)plot.getRangeAxis()).setAutoRangeIncludesZero(false);
JFreeChart lineChart = new JFreeChart(chartTitle, plot);
plot.setBackgroundPaint(Color.lightGray);
plot.setDomainGridlinesVisible(true);
plot.setRangeGridlinesVisible(true);
plot.setDomainGridlinePaint(Color.white);
plot.setRangeGridlinePaint(Color.white);
lineChart.setBackgroundPaint(Color.white);
ChartPanel chartPanel = new ChartPanel( lineChart );
chartPanel.setPreferredSize( new java.awt.Dimension( 560 , 367 ) );
setContentPane( chartPanel );
}
private TimeSeriesCollection createDataset( ) {
TimeSeries typeA = new TimeSeries("TypeA");
TimeSeries typeB = new TimeSeries("TypeB");
TimeSeriesCollection collection = new TimeSeriesCollection();
collection.addSeries(typeA);
collection.addSeries(typeB);
typeA = collection.getSeries("TypeA");
typeA.add(new Hour(8, new Day()), 1.0);
typeA.add(new Hour(10, new Day()), 1.0);
typeA.add(new Hour(11, new Day()), 1.0);
typeA.add(new Hour(13, new Day()), 1.0);
typeA.add(new Hour(16, new Day()), 2.0);
typeA.add(new Hour(18, new Day()), 2.0);
typeB.add(new Hour(8, new Day()), 1.0);
typeB.add(new Hour(19, new Day()), 2.0);
typeB.add(new Hour(20, new Day()), 5.0);
return collection;
}
public static void main( String[ ] args ) {
LineChart_AWT chart = new LineChart_AWT(
"X-axis demo" ,
"X-axis labels are truncated");
chart.pack( );
RefineryUtilities.centerFrameOnScreen( chart );
chart.setVisible( true );
}
}
Here is the current screenshot; problem can be seen on the last label:
What causes that last label to be rendered outside of the current chart area? Also, how can I prevent it?
UPDATE
Here is a more comprehensive example with screenshots and all the details.
According to @trashgod's comments, I've updated to the latest JFreeChart Engine
(jfreechart-1.0.19.jar and jcommon-1.0.23.jar)
(jfreechart-1.6.0-snapshot.jar).
Consider this example (which deeply relies on @trashgod's suggestions - thank you very much):
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.text.SimpleDateFormat;
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.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.ui.ApplicationFrame;
import org.jfree.data.time.Minute;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
/**
* @see https://stackoverflow.com/a/57637615/230513
* @see https://stackoverflow.com/a/57544811/230513
*/
public class TimeChart extends ApplicationFrame {
private static boolean lot_of_values = false;
public TimeChart(String applicationTitle, String chartTitle) {
super(applicationTitle);
DateAxis timeAxis = new DateAxis("Timestamp");
timeAxis.setUpperMargin(DateAxis.DEFAULT_UPPER_MARGIN /* * 2*/); // UPDATED
timeAxis.setLowerMargin(DateAxis.DEFAULT_LOWER_MARGIN /* * 2*/); // UPDATED
timeAxis.setDateFormatOverride(new SimpleDateFormat("YYYY-MM-dd HH:mm"));
NumberAxis numberAxis = new NumberAxis("Number");
numberAxis.setAutoRangeIncludesZero(false);
XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer(true, false);
XYPlot plot = new XYPlot(createDataset(), timeAxis, numberAxis, renderer);
plot.setBackgroundPaint(Color.lightGray);
plot.setDomainGridlinePaint(Color.white);
plot.setRangeGridlinePaint(Color.white);
JFreeChart lineChart = new JFreeChart(chartTitle, plot);
lineChart.setBackgroundPaint(Color.white);
ChartPanel chartPanel = new ChartPanel(lineChart) {
@Override
public Dimension getPreferredSize() {
return new Dimension(1529 , 538);
}
};
add(chartPanel);
}
private TimeSeriesCollection createDataset() {
TimeSeries typeA = new TimeSeries("Temperatures");
TimeSeriesCollection collection = new TimeSeriesCollection();
collection.addSeries(typeA);
if (lot_of_values) {
typeA.add(Minute.parseMinute("2019-08-25 00:00"), 26.68);
typeA.add(Minute.parseMinute("2019-08-25 01:00"), 26.75);
typeA.add(Minute.parseMinute("2019-08-25 02:00"), 25.95);
typeA.add(Minute.parseMinute("2019-08-25 03:00"), 25.47);
typeA.add(Minute.parseMinute("2019-08-25 04:00"), 25.19);
typeA.add(Minute.parseMinute("2019-08-25 05:00"), 24.65);
typeA.add(Minute.parseMinute("2019-08-25 06:00"), 24.61);
typeA.add(Minute.parseMinute("2019-08-25 07:00"), 25.58);
typeA.add(Minute.parseMinute("2019-08-25 08:00"), 26.43);
typeA.add(Minute.parseMinute("2019-08-25 09:00"), 26.96);
typeA.add(Minute.parseMinute("2019-08-25 10:00"), 27.81);
typeA.add(Minute.parseMinute("2019-08-25 11:00"), 28.69);
typeA.add(Minute.parseMinute("2019-08-25 12:00"), 29.39);
typeA.add(Minute.parseMinute("2019-08-25 13:00"), 29.89);
typeA.add(Minute.parseMinute("2019-08-25 14:00"), 30.32);
typeA.add(Minute.parseMinute("2019-08-25 15:00"), 30.69);
typeA.add(Minute.parseMinute("2019-08-25 16:00"), 30.83);
typeA.add(Minute.parseMinute("2019-08-25 17:00"), 30.85);
typeA.add(Minute.parseMinute("2019-08-25 18:00"), 30.64);
typeA.add(Minute.parseMinute("2019-08-25 19:00"), 30.04);
typeA.add(Minute.parseMinute("2019-08-25 20:00"), 29.51);
typeA.add(Minute.parseMinute("2019-08-25 21:00"), 28.63);
typeA.add(Minute.parseMinute("2019-08-25 22:00"), 28.48);
typeA.add(Minute.parseMinute("2019-08-25 23:00"), 27.15);
typeA.add(Minute.parseMinute("2019-08-26 00:00"), 27.3);
typeA.add(Minute.parseMinute("2019-08-26 01:00"), 27.05);
typeA.add(Minute.parseMinute("2019-08-26 02:00"), 26.84);
typeA.add(Minute.parseMinute("2019-08-26 03:00"), 26.47);
typeA.add(Minute.parseMinute("2019-08-26 04:00"), 26.34);
typeA.add(Minute.parseMinute("2019-08-26 05:00"), 25.95);
typeA.add(Minute.parseMinute("2019-08-26 06:00"), 26.46);
typeA.add(Minute.parseMinute("2019-08-26 07:00"), 26.75);
typeA.add(Minute.parseMinute("2019-08-26 08:00"), 26.94);
typeA.add(Minute.parseMinute("2019-08-26 09:00"), 27.05);
typeA.add(Minute.parseMinute("2019-08-26 10:00"), 27.35);
typeA.add(Minute.parseMinute("2019-08-26 11:00"), 27.67);
typeA.add(Minute.parseMinute("2019-08-26 12:00"), 28.12);
typeA.add(Minute.parseMinute("2019-08-26 13:00"), 28.41);
typeA.add(Minute.parseMinute("2019-08-26 14:00"), 28.67);
typeA.add(Minute.parseMinute("2019-08-26 15:00"), 28.99);
typeA.add(Minute.parseMinute("2019-08-26 16:00"), 28.99);
typeA.add(Minute.parseMinute("2019-08-26 17:00"), 29.02);
typeA.add(Minute.parseMinute("2019-08-26 18:00"), 29.02);
typeA.add(Minute.parseMinute("2019-08-26 19:00"), 28.43);
typeA.add(Minute.parseMinute("2019-08-26 20:00"), 27.87);
typeA.add(Minute.parseMinute("2019-08-26 21:00"), 27.2);
typeA.add(Minute.parseMinute("2019-08-26 22:00"), 26.88);
typeA.add(Minute.parseMinute("2019-08-26 23:00"), 26.31);
typeA.add(Minute.parseMinute("2019-08-27 00:00"), 26.02);
typeA.add(Minute.parseMinute("2019-08-27 01:00"), 25.51);
typeA.add(Minute.parseMinute("2019-08-27 02:00"), 25.12);
typeA.add(Minute.parseMinute("2019-08-27 03:00"), 25.11);
typeA.add(Minute.parseMinute("2019-08-27 04:00"), 24.97);
typeA.add(Minute.parseMinute("2019-08-27 05:00"), 24.85);
typeA.add(Minute.parseMinute("2019-08-27 06:00"), 24.73);
typeA.add(Minute.parseMinute("2019-08-27 07:00"), 25.04);
typeA.add(Minute.parseMinute("2019-08-27 08:00"), 25.68);
typeA.add(Minute.parseMinute("2019-08-27 09:00"), 26.22);
typeA.add(Minute.parseMinute("2019-08-27 10:00"), 26.69);
typeA.add(Minute.parseMinute("2019-08-27 11:00"), 27.3);
typeA.add(Minute.parseMinute("2019-08-27 12:00"), 27.84);
typeA.add(Minute.parseMinute("2019-08-27 13:00"), 28.26);
typeA.add(Minute.parseMinute("2019-08-27 14:00"), 28.6);
typeA.add(Minute.parseMinute("2019-08-27 15:00"), 29.03);
typeA.add(Minute.parseMinute("2019-08-27 16:00"), 29.38);
typeA.add(Minute.parseMinute("2019-08-27 17:00"), 29.62);
typeA.add(Minute.parseMinute("2019-08-27 18:00"), 29.47);
typeA.add(Minute.parseMinute("2019-08-27 19:00"), 29.01);
typeA.add(Minute.parseMinute("2019-08-27 20:00"), 28.31);
typeA.add(Minute.parseMinute("2019-08-27 21:00"), 27.69);
typeA.add(Minute.parseMinute("2019-08-27 22:00"), 26.93);
typeA.add(Minute.parseMinute("2019-08-27 23:00"), 26.37);
}
typeA.add(Minute.parseMinute("2019-08-28 00:00"), 26.12);
typeA.add(Minute.parseMinute("2019-08-28 01:00"), 25.77);
typeA.add(Minute.parseMinute("2019-08-28 02:00"), 25.42);
typeA.add(Minute.parseMinute("2019-08-28 03:00"), 25.0);
typeA.add(Minute.parseMinute("2019-08-28 04:00"), 24.57);
typeA.add(Minute.parseMinute("2019-08-28 05:00"), 24.23);
typeA.add(Minute.parseMinute("2019-08-28 06:00"), 24.38);
typeA.add(Minute.parseMinute("2019-08-28 07:00"), 24.99);
typeA.add(Minute.parseMinute("2019-08-28 08:00"), 25.86);
typeA.add(Minute.parseMinute("2019-08-28 09:00"), 26.53);
typeA.add(Minute.parseMinute("2019-08-28 10:00"), 27.32);
typeA.add(Minute.parseMinute("2019-08-28 11:00"), 27.95);
typeA.add(Minute.parseMinute("2019-08-28 12:00"), 28.64);
typeA.add(Minute.parseMinute("2019-08-28 13:00"), 29.38);
typeA.add(Minute.parseMinute("2019-08-28 14:00"), 29.74);
typeA.add(Minute.parseMinute("2019-08-28 15:00"), 30.13);
typeA.add(Minute.parseMinute("2019-08-28 16:00"), 30.42);
typeA.add(Minute.parseMinute("2019-08-28 17:00"), 30.48);
typeA.add(Minute.parseMinute("2019-08-28 18:00"), 30.14);
typeA.add(Minute.parseMinute("2019-08-28 19:00"), 29.41);
typeA.add(Minute.parseMinute("2019-08-28 20:00"), 28.47);
typeA.add(Minute.parseMinute("2019-08-28 21:00"), 28.05);
return collection;
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
TimeChart chart = new TimeChart(
"Date axis demo",
"Date axis labels are visible");
chart.pack();
chart.setVisible(true);
}
});
}
}
Please note that I've changed the preferred diagram size to 1529 x 538 (I will need to generate a PNG with this size), and also I have introduced a new static variable called lot_of_values. Initially, it is set to false, here is a screenshot of this:
However if I change lot_of_values to true (which will add more data to the collection - you can see in the source), the last label of the domain axis will be cut. Here is the screenshot with lot_of_values=true:
UPDATE2
I have digged myself into JFreeChart's sources and I'm on the way of solving the problem. (also I had to remove some lines from the source above to fit into the 30k characters limit)
Consider the following screenshot:
I think margin values are applied before and after the chart's current data plotting and not to the current range ticks. That's why the last tick label can be cut.
It won't be a problem if the data would fill up until the last tick (currently 2019-08-29 00:00) because in that case the margin would allow that value to be printed correctly.
Let's see a proof-of-concept for this. I added three lines to the dataset:
typeA.add(Minute.parseMinute("2019-08-28 21:00"), 28.05); //original line
typeA.add(Minute.parseMinute("2019-08-28 22:00"), 28.05); //new line
typeA.add(Minute.parseMinute("2019-08-28 23:00"), 28.05); //new line
typeA.add(Minute.parseMinute("2019-08-29 00:00"), 28.05); //new line
This can be achieved also by modifying the axis's maximum date by calling:
timeAxis.setMaximumDate(new Date(119,7,29,4,36));
Now I will go forward to hunt down where this MaximumDate calculated. If someone know, please let me know.