title: JFreeChart X axis labels are going out of chart area
body: <p>I have a <code>JFreeChart</code> chart with <code>DateAxis</code> 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:</p>
  
  <pre><code>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 );
         }
      }
  </code></pre>
  
  <p>Here is the current screenshot; problem can be seen on the last label:</p>
  
  <p><a href="https://i.stack.imgur.com/SA9PD.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/SA9PD.png" alt="date labels clipped"></a></p>
  
  <p>What causes that last label to be rendered outside of the current chart area? Also, how can I prevent it?</p>
  
  <h1>UPDATE</h1>
  
  <p>Here is a more comprehensive example with screenshots and all the details.</p>
  
  <p>According to @trashgod's comments, I've updated to the latest JFreeChart Engine 
  <s>(jfreechart-1.0.19.jar and jcommon-1.0.23.jar)</s>
  (jfreechart-1.6.0-snapshot.jar).</p>
  
  <p>Consider this example (which deeply relies on @trashgod's suggestions - thank you very much):</p>
  
  <pre><code>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);
              }
          });
      }
  }
  </code></pre>
  
  <p>Please note that I've changed the preferred diagram size to <strong>1529 x 538</strong> (I will need to generate a PNG with this size), and also I have introduced a new static variable called <em>lot_of_values</em>. Initially, it is set to <em>false</em>, here is a screenshot of this:</p>
  
  <p><a href="https://i.stack.imgur.com/SWnXJ.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/SWnXJ.png" alt="enter image description here"></a></p>
  
  <p>However if I change <em>lot_of_values</em> to <em>true</em> (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 <em>lot_of_values=true</em>:</p>
  
  <p><a href="https://i.stack.imgur.com/KS5uI.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/KS5uI.png" alt="enter image description here"></a></p>
  
  <h1>UPDATE2</h1>
  
  <p>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)</p>
  
  <p>Consider the following screenshot:
  <a href="https://i.stack.imgur.com/dFyky.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/dFyky.png" alt="enter image description here"></a></p>
  
  <p>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.</p>
  
  <p>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.</p>
  
  <p>Let's see a proof-of-concept for this. I added three lines to the dataset:</p>
  
  <pre><code>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
  </code></pre>
  
  <p>And now the result:
  <a href="https://i.stack.imgur.com/DpIpu.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/DpIpu.png" alt="enter image description here"></a></p>
  
  <p>This can be achieved also by modifying the axis's <em>maximum date</em> by calling:</p>
  
  <pre><code>timeAxis.setMaximumDate(new Date(119,7,29,4,36));
  </code></pre>
  
  <p>Now I will go forward to hunt down where this <em>MaximumDate</em> calculated. If someone know, please let me know.</p>