【问题标题】:JFreeChart X axis labels are going out of chart areaJFreeChart X 轴标签超出图表区域
【发布时间】:2019-12-29 04:31:41
【问题描述】:

我有一个JFreeChart 图表,以DateAxis 作为域。它看起来很漂亮,但是最后一个轴标签有时会超出图表区域。这是要重现的示例代码:

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

这是当前的屏幕截图;问题可以在最后一个标签上看到:

是什么导致最后一个标签呈现在当前图表区域之外?还有,如何预防?

更新

这是一个更全面的示例,包含屏幕截图和所有详细信息。

根据@trashgod 的cmets,我已经更新到最新的JFreeChart Engine (jfreechart-1.0.19.jar 和 jcommon-1.0.23.jar) (jfreechart-1.6.0-snapshot.jar)。

考虑这个例子(非常依赖@trashgod 的建议——非常感谢):

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

请注意,我已将首选图表大小更改为 1529 x 538(我将需要生成具有此大小的 PNG),并且我还引入了一个名为 lot_of_values。最初,它被设置为 false,这是一个截图:

但是,如果我将 lot_of_values 更改为 true(这将向集合添加更多数据 - 您可以在源代码中看到),域轴的最后一个标签将被剪掉。这是 lot_of_values=true 的屏幕截图:

更新2

我已经深入研究了 JFreeChart 的资源,并且正在解决这个问题。 (我还必须从上面的源代码中删除一些行以适应 30k 字符的限制)

考虑以下屏幕截图:

我认为在图表的当前数据绘制之前和之后应用边距值,而不是当前范围刻度。这就是为什么最后一个刻度标签可以被剪掉的原因。

如果数据在最后一个刻度(当前为 2019 年 8 月 29 日 00:00)之前填满,那将不是问题,因为在这种情况下,边距将允许正确打印该值。

让我们看一个概念验证。我在数据集中添加了三行:

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

现在结果:

这也可以通过调用修改轴的最大日期来实现:

timeAxis.setMaximumDate(new Date(119,7,29,4,36));

现在我将继续寻找这个 MaximumDate 计算的位置。如果有人知道,请告诉我。

【问题讨论】:

    标签: java swing jfreechart


    【解决方案1】:

    该效果是人为减小图表的首选大小而显式增加日期轴刻度标签大小而导致的伪影。请注意,省略对setPreferredSize() 的调用会消除这种影响。或者,您可以按照here 的建议设置轴边距以进行补偿。下面的示例将默认上下边距加倍,从刻度间隔的 10% 到 20%。

    timeAxis.setUpperMargin(DateAxis.DEFAULT_UPPER_MARGIN * 2);
    timeAxis.setLowerMargin(DateAxis.DEFAULT_LOWER_MARGIN * 2);
    

    更准确地说:它是针对这些标签长度的最终解决方案,还是只是针对当前情况的特定破解?

    DateAxis 使用标签的计算大小使标签在其刻度/网格线上居中。因为字体大小因平台而异,而标签大小因格式和语言环境而异,所以对于给定的封闭组件大小,总有一些值的组合可能会剪裁标签。随着组件大小的调整,显示的标签数量将发生变化以优化显示。只要您允许图表随着大小的变化而调整,讨论here,用户不会有任何麻烦。调整示例的框架大小或使用这些内置的controls 来查看效果。

    我不想硬连线数据集;图表必须在所有记录计数方面看起来都不错——即使我只有 1 条记录,或者我有 100 条记录。

    为此,将用户引导至适合您的用例的interactive features:此example 使用组合框侦听器来切换setVerticalTickLabels();您可以保持用户的偏好,如here 所示。这个example 提供了一个缩放控件的工具栏。 here 引用的示例将平移与 setMouseWheelEnabled() 结合使用。

    另一方面,不要忽视提到的其他问题 here,因为它们是常见的陷阱,会使其他问题难以隔离。

    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.chart.ui.UIUtils;
    import org.jfree.data.time.Day;
    import org.jfree.data.time.Hour;
    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 {
    
        public TimeChart(String applicationTitle, String chartTitle) {
            super(applicationTitle);
    
            DateAxis timeAxis = new DateAxis("Timestamp");
            timeAxis.setUpperMargin(DateAxis.DEFAULT_UPPER_MARGIN * 2);
            timeAxis.setLowerMargin(DateAxis.DEFAULT_LOWER_MARGIN * 2);
            timeAxis.setDateFormatOverride(new SimpleDateFormat("YYYY-MM-dd HH:mm"));
            NumberAxis numberAxis = new NumberAxis("Number");
            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(560 , 367);
                }
            };
            add(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) {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    TimeChart chart = new TimeChart(
                        "Date axis demo",
                        "Date axis labels are visible");
                    chart.pack();
                    UIUtils.centerFrameOnScreen(chart);
                    chart.setVisible(true);
                }
            });
        }
    }
    

    【讨论】:

    • 谢谢。为我省略 setPreferredSize() 并不能解决问题。 '20:00' 的最后一个 0 仍在屏幕之外。
    • 我使用 setPreferredSize() 来通知图表何时调整其父窗口的大小。这是一种不好的方法吗?
    • 如果图表上的标签不合适,我会完全接受。这比有一个被切成两半的标签要好。
    • 是的,字体大小因平台而异;您可能需要调整边距;另外,调整框架大小以查看效果;注意将图表面板添加到框架默认边框布局中心的效果。
    • 我现在不太明白。当调整窗口大小时,有时会变得更好有时更糟。即使在减少时,图表标签也会更好,因为在某一点上,JFreechart 隐藏了一些标签,然后其余的有空间。将图表面板添加到“框架默认边框布局中心”是什么意思?有没有办法为域轴标签提供独立于平台的解决方案? IE。如果没有空间,则减少渲染。我接受设置边距,但它需要根据标签的宽度进行计算才能真正独立于平台。
    【解决方案2】:

    我已经成功调查并解决了这个问题。

    当 JFreeChart 决定其轴上的默认刻度时,它会计算标签的宽度并检查它们是否适合刻度之间,然后增加刻度直到标签适合。

    这很好但是在此过程中 JFreeChart 不会检查最后一个标签是否适合图表的绘图区域。

    要克服这种情况,您将有两个任务:

    1. 检查您的最后一个标签是否被剪掉
    2. 如果它被切割校正轴的范围

    我是这样做的,尽量做到最小化,同时完全不接触 JFreeChart 的源代码:

    import java.awt.Font;
    import java.awt.FontMetrics;
    import java.awt.Graphics2D;
    import java.awt.font.FontRenderContext;
    import java.awt.font.LineMetrics;
    import java.awt.geom.Rectangle2D;
    import java.text.DateFormat;
    import java.util.Date;
    import java.util.Locale;
    import java.util.TimeZone;
    
    import org.jfree.chart.axis.AxisState;
    import org.jfree.chart.axis.DateAxis;
    import org.jfree.chart.axis.DateTickUnit;
    import org.jfree.chart.plot.PlotRenderingInfo;
    import org.jfree.chart.ui.RectangleEdge;
    import org.jfree.chart.ui.RectangleInsets;
    import org.jfree.data.time.DateRange;
    
    public class CorrectedDateAxis extends DateAxis {
    
        /** For serialization. */
        private static final long serialVersionUID = 0L;
    
    
        /**
         * Creates a date axis with no label.
         */
        public CorrectedDateAxis() {
            super(null);
        }
    
        /**
         * Creates a date axis with the specified label.
         *
         * @param label  the axis label ({@code null} permitted).
         */
        public CorrectedDateAxis(String label) {
            super(label);
        }
    
        /**
         * Creates a date axis.
         *
         * @param label  the axis label ({@code null} permitted).
         * @param zone  the time zone.
         * @param locale  the locale ({@code null} not permitted).
         */
        public CorrectedDateAxis(String label, TimeZone zone, Locale locale) {
            super(label, zone, locale);
        }
    
        /**
         * Estimates the maximum width of the tick labels, assuming the specified
         * tick unit is used.
         * <P>
         * Rather than computing the string bounds of every tick on the axis, we
         * just look at two values: the lower bound and the upper bound for the
         * axis.  These two values will usually be representative.
         *
         * @param g2  the graphics device.
         * @param unit  the tick unit to use for calculation.
         *
         * @return The estimated maximum width of the tick labels.
         */
        private double estimateMaximumTickLabelWidth(Graphics2D g2, 
                DateTickUnit unit) {
    
            RectangleInsets tickLabelInsets = getTickLabelInsets();
            double result = tickLabelInsets.getLeft() + tickLabelInsets.getRight();
    
            Font tickLabelFont = getTickLabelFont();
            FontRenderContext frc = g2.getFontRenderContext();
            LineMetrics lm = tickLabelFont.getLineMetrics("ABCxyz", frc);
            if (isVerticalTickLabels()) {
                // all tick labels have the same width (equal to the height of
                // the font)...
                result += lm.getHeight();
            }
            else {
                // look at lower and upper bounds...
                DateRange range = (DateRange) getRange();
                Date lower = range.getLowerDate();
                Date upper = range.getUpperDate();
                String lowerStr, upperStr;
                DateFormat formatter = getDateFormatOverride();
                if (formatter != null) {
                    lowerStr = formatter.format(lower);
                    upperStr = formatter.format(upper);
                }
                else {
                    lowerStr = unit.dateToString(lower);
                    upperStr = unit.dateToString(upper);
                }
                FontMetrics fm = g2.getFontMetrics(tickLabelFont);
                double w1 = fm.stringWidth(lowerStr);
                double w2 = fm.stringWidth(upperStr);
                result += Math.max(w1, w2);
            }
    
            return result;
        }   
    
        @Override
        public AxisState draw(Graphics2D g2, double cursor, Rectangle2D plotArea, Rectangle2D dataArea, RectangleEdge edge,
                PlotRenderingInfo plotState) {
    
            double labelWidth = estimateMaximumTickLabelWidth(g2, getTickUnit());
    
            double lastLabelPosition = dateToJava2D(calculateHighestVisibleTickValue(getTickUnit()),
                    plotArea, edge);
    
            if (lastLabelPosition + labelWidth / 2 > plotArea.getMaxX()) {
                double plottingWidthCorrection = plotArea.getX() + (lastLabelPosition + labelWidth / 2) - plotArea.getMaxX();
    
                // Calculate and set the new corrected maximum date
                setMaximumDate(new Date((long)(getMaximumDate().getTime() + java2DToValue(plottingWidthCorrection, plotArea, edge) - getMinimumDate().getTime())));
            }
    
            return super.draw(g2, cursor, plotArea, dataArea, edge, plotState);
        }
    }
    

    这是对 DateAxis 类的覆盖,它完成了上述两项任务。

    注意,这个子类包含许多从 JFreeChart 的 DateAxis 类复制的代码,因为它们已将 estimateMaximumTickLabelWidth 定义为私有,因此子类无法访问它。

    你可以修改原来的 DateAxis 类,将这个函数定义为受保护的,这样你就可以在这个子类中跳过这个函数。

    CorrectedDateAxis 进入图片并更正 DateAxis 的范围时的样子如下:

    最后没有错误的标签!

    【讨论】:

    • 我已经习惯了这段代码显示我的最后一个标签,但我的 x 轴是自定义的。开始标签之前的边距不再存在。是否有任何解决方案可以在 x 轴的起点给出边距。我已经尝试过 setUpperMargin() 和 setLowerMargin()。
    • 如果您的 x 轴是自定义的,您可以覆盖 draw 函数并计算实际区域的最佳值,就像我对 DateAxis 所做的那样。我认为 setUpperMarginsetLowerMargin 不适用于此代码,因为我的 draw 函数没有考虑到这些,应该不难不过应用它们。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-09
    • 1970-01-01
    • 2011-01-30
    • 2018-02-10
    相关资源
    最近更新 更多