【问题标题】:Is there a workaround to make DateAxis draw tick from the first day of the week?是否有一种解决方法可以使 DateAxis 从一周的第一天开始绘制刻度?
【发布时间】:2012-06-16 00:33:26
【问题描述】:

我在创建需要带有周周期的轴的图表时遇到了这个问题。

当我用new DateTickUnit(DateTickUnitType.Day, 7)设置DateAxis的刻度单位时,它每7天显示一次刻度线。但是,刻度线的日期并非从一周的第一天开始。您可以在屏幕截图中观察此行为。

青色线05-01 w18(May. 1, Week 18)低于w18(Week 18)刻度线,这是因为刻度线w18的日期实际上是5月2日,也就是星期三。

这个使图表看起来不正确,因为人们倾向于认为每个刻度都应该是一周的开始。

查看源代码后,我发现DateAxis 不支持这样的行为周(反正没有周类型)。

我无法创建另一个DateTickUnitType,因为私有构造函数和DateAxis 中的correctTickDateForPosition() 方法也是私有的。我试图覆盖nextStandardDate(),但没有得到满意的结果。

如何让DateAxis 总是从一周的第一天开始绘制刻度线?


这是截图和示例代码

需要 JFreeChart/JCommon

public class Main extends ApplicationFrame {

  public Main (String title) throws ParseException {
    super(title);
    JPanel chartPanel = createDemoPanel();
    chartPanel.setPreferredSize(new java.awt.Dimension(800, 600));
    setContentPane(chartPanel);
  }

  private static JFreeChart createChart(XYDataset dataset) {
    JFreeChart chart = ChartFactory.createXYLineChart("Week period in DateAxis", "X", "Y", 
        dataset, PlotOrientation.VERTICAL, true, true, false);

    DateAxis x = new DateAxis("X");
    x.setTickUnit(new DateTickUnit(DateTickUnitType.MONTH, 1, new SimpleDateFormat("MMM.")));

    DateAxis y = new DateAxis("Y");
    y.setTickUnit(new DateTickUnit(DateTickUnitType.DAY, 7, new SimpleDateFormat("MM-dd 'W'w.")));

    XYPlot plot = chart.getXYPlot();
    plot.setDomainAxis(x);
    plot.setRangeAxis(y);
    plot.setDomainGridlinesVisible(true);
    plot.setRangeGridlinesVisible(true);
    XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) plot.getRenderer();
    renderer.setBaseShapesVisible(true);
    renderer.setBaseItemLabelGenerator(new StandardXYItemLabelGenerator("{2}", 
        new SimpleDateFormat("MMM."), new SimpleDateFormat("MM-dd 'w'w")));
    renderer.setBaseItemLabelsVisible(true);
    return chart;
  }

  private static XYDataset createDataset() throws ParseException {
    SimpleDateFormat df = new SimpleDateFormat("yyyy/MM/dd");

    String[] allDate = new String[]{
        "2012/03/27", "2012/04/03", "2012/04/10", 
        "2012/04/17", "2012/04/24", "2012/05/01"};

    String dateY1 = "2012/01/15";
    String dateY2 = "2012/02/15";
    String dateY3 = "2012/03/15";
    String dateY4 = "2012/04/15";
    String dateY5 = "2012/05/15";
    String dateY6 = "2012/06/15";

    XYSeriesCollection dataset = new XYSeriesCollection();
    for (String date : allDate) {
      XYSeries series = new XYSeries(date);

      series.add(df.parse(dateY1).getTime(), df.parse(date).getTime());
      series.add(df.parse(dateY2).getTime(), df.parse(date).getTime());
      series.add(df.parse(dateY3).getTime(), df.parse(date).getTime());
      series.add(df.parse(dateY4).getTime(), df.parse(date).getTime());
      series.add(df.parse(dateY5).getTime(), df.parse(date).getTime());
      series.add(df.parse(dateY6).getTime(), df.parse(date).getTime());
      dataset.addSeries(series);
    }
    return dataset;
  }

  public static JPanel createDemoPanel() throws ParseException {
    JFreeChart chart = createChart(createDataset());
    return new ChartPanel(chart);
  }

  public static void main(String[] args) throws ParseException {
    Main demo = new Main("Test");
    demo.pack();
    RefineryUtilities.centerFrameOnScreen(demo);
    demo.setVisible(true);
  }
}

【问题讨论】:

    标签: java jfreechart


    【解决方案1】:

    这是支持周期间的DateAxis 的我的版本(仅)。

    public static class WeeklyDateAxis extends DateAxis {
    
      private static final long serialVersionUID = 1L;
      private Locale locale;
    
      public WeeklyDateAxis(String label, TimeZone zone, Locale locale) {
        super(label, zone, locale);
        this.locale = locale;
      }
    
      @Override
      protected Date previousStandardDate(Date date, DateTickUnit unit) {
        Calendar cal = Calendar.getInstance(getTimeZone(), locale);
        cal.setTime(date);
        resetFieldBelowDay(cal);
    
        cal.set(Calendar.DAY_OF_WEEK, cal.getFirstDayOfWeek());
        return cal.getTime();
      }
    
      @Override
      protected Date nextStandardDate(Date date, DateTickUnit unit) {
        Date previous = previousStandardDate(date, unit);
        Calendar cal = Calendar.getInstance(getTimeZone(), locale);
        cal.setTime(previous);
        resetFieldBelowDay(cal);
    
        cal.add(Calendar.WEEK_OF_YEAR, 1);
        return cal.getTime();
      }
    
      private void resetFieldBelowDay(Calendar cal) {
        cal.clear(Calendar.MILLISECOND);
        cal.clear(Calendar.SECOND);
        cal.clear(Calendar.MINUTE);
        cal.set(Calendar.HOUR_OF_DAY, 0);
      }
    }
    

    解释:

    DateAxis计算tick时,调用时会从最小值开始

    nextStandardDate(getMinimumDate(), unit);
    

    ,其中getMinimumDate() 是图表的边缘值。然后它将继续使用日期的最后一个刻度作为输入来计算下一个日期,直到达到最高可用日期。

    所以我在previousStandardDate() 中将时间设置为一周的第一天,然后每次计算下一个刻度时,我都会在previousStandardDate() 的结果中添加一周。

    resetFieldBelowDay() 部分只是为了去除一些可能导致线条与刻度线不对齐的噪声数据。

    这是我用来验证结果的另一个数据集,您可以简单地将其替换为我提供的相关示例代码。

    private static XYDataset createDataset() throws ParseException {
      SimpleDateFormat df = new SimpleDateFormat("yyyy/MM/dd");
    
      String[][] allDate = new String[][] {
          {"2012/03/25", "2012/03/29", "2012/03/28", "2012/03/27", "2012/03/27", "2012/03/27"},
          {"2012/04/01", "2012/04/02", "2012/04/06", "2012/04/06", "2012/04/06", "2012/04/06"},
          {"2012/04/11", "2012/04/12", "2012/04/08", "2012/04/10", "2012/04/10", "2012/04/10"},
          {"2012/04/15", "2012/04/14", "2012/04/15", "2012/04/18", "2012/04/19", "2012/04/19"},
          {"2012/05/01", "2012/05/02", "2012/05/08", "2012/05/04", "2012/05/04", "2012/05/04"},
          {"2012/05/12", "2012/05/12", "2012/05/18", "2012/05/14", "2012/05/14", "2012/05/14"},
          {"2012/05/22", "2012/05/22", "2012/05/28", "2012/05/28", "2012/05/28", "2012/05/30"},
      };
    
      String dateY1 = "2012/01/15";
      String dateY2 = "2012/02/15";
      String dateY3 = "2012/03/15";
      String dateY4 = "2012/04/15";
      String dateY5 = "2012/05/15";
      String dateY6 = "2012/06/15";
    
      XYSeriesCollection dataset = new XYSeriesCollection();
      for (String[] dateOfOneSeries : allDate) {
        XYSeries series = new XYSeries(dateOfOneSeries[0]);
    
        series.add(df.parse(dateY1).getTime(), df.parse(dateOfOneSeries[0]).getTime());
        series.add(df.parse(dateY2).getTime(), df.parse(dateOfOneSeries[1]).getTime());
        series.add(df.parse(dateY3).getTime(), df.parse(dateOfOneSeries[2]).getTime());
        series.add(df.parse(dateY4).getTime(), df.parse(dateOfOneSeries[3]).getTime());
        series.add(df.parse(dateY5).getTime(), df.parse(dateOfOneSeries[4]).getTime());
        series.add(df.parse(dateY6).getTime(), df.parse(dateOfOneSeries[5]).getTime());
        dataset.addSeries(series);
      }
      return dataset;
    }
    

    结果:

    【讨论】:

      【解决方案2】:

      通过覆盖DateAxis 中的previousStandardDate,我设法为刻度线调整了星期几。

      在您的示例中,数据显示为星期二,因此在下面的代码中,Calendar 的第一天被设置为星期二,这使数据行与刻度线对齐。

      DateAxis y = new DateAxis("Y") {
        @Override
        protected Date previousStandardDate(Date date, DateTickUnit unit) {
          Date prevDate = super.previousStandardDate(date, unit);
      
          Calendar calendar = Calendar.getInstance();
          calendar.setTime(prevDate);
      
          int showDayOfWeek = Calendar.TUESDAY;
          calendar.setFirstDayOfWeek(showDayOfWeek);
          if (showDayOfWeek != calendar.get(Calendar.DAY_OF_WEEK)) {
            calendar.set(Calendar.DAY_OF_WEEK, showDayOfWeek);
          }
      
          return calendar.getTime();
        }
      };
      

      [编辑:] 我刚刚意识到您实际上并不想对齐刻度线,而是在一周的实际第一天显示它们。解决方案是

      DateAxis y = new DateAxis("Y") {    
        @Override
        protected Date previousStandardDate(Date date, DateTickUnit unit) {
          Date prevDate = super.previousStandardDate(date, unit);
      
          Calendar calendar = Calendar.getInstance(TimeZone.getDefault());
          calendar.setTime(prevDate);
      
          int firstDayOfWeek = calendar.getFirstDayOfWeek();
          if (firstDayOfWeek != calendar.get(Calendar.DAY_OF_WEEK)) {
            calendar.set(Calendar.DAY_OF_WEEK, firstDayOfWeek);
          }
      
          return calendar.getTime();
        }
      };
      

      【讨论】:

      • 你的回答和我一开始的差不多,除了我覆盖了nextStandardDate()。不幸的是,这种方式只适用于我提供的数据集。如果您稍微调整数据,您会发现有时刻度线会超出图表但您的回答启发了我,并帮助我修复了自己的版本。
      • 顺便说一句,你不必再getFirstDayOfWeek()然后setFirstDayOfWeek()
      猜你喜欢
      • 1970-01-01
      • 2018-07-25
      • 2014-02-09
      • 1970-01-01
      • 2018-08-02
      • 2017-12-23
      • 1970-01-01
      • 1970-01-01
      • 2016-05-18
      相关资源
      最近更新 更多