• category: swing folder: CalendarHeatmapList title: JListでウィークカレンダーを作成してヒートマップを表示する tags: [JList, LocalDate, ListModel, Calendar, Icon] author: aterai pubdate: 2018-03-05T17:08:51+09:00 description: JListを垂直方向ニュースペーパー・スタイルレイアウトに設定してウィークカレンダーを作成し、これにヒートマップを表示します。 image: https://drive.google.com/uc?id=1QjS8lhibD62Bltz4Mn3LXAUhBk1b4U1fmg hreflang:
       href: https://java-swing-tips.blogspot.com/2018/03/create-week-calendar-in-jlist-and.html
       lang: en

概要

JListを垂直方向ニュースペーパー・スタイルレイアウトに設定してウィークカレンダーを作成し、これにヒートマップを表示します。

サンプルコード

JList<Contribution> weekList = new JList<Contribution>() {
  @Override public void updateUI() {
    setCellRenderer(null);
    super.updateUI();
    setLayoutOrientation(JList.VERTICAL_WRAP);
    setVisibleRowCount(DayOfWeek.values().length); // ensure 7 rows in the list
    setFixedCellWidth(size.width);
    setFixedCellHeight(size.height);
    setCellRenderer(new ContributionListRenderer());
    getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
    setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
  }
};
//...
private class ContributionListRenderer implements ListCellRenderer<Contribution> {
  private final Icon emptyIcon = new ColorIcon(Color.WHITE);
  private final ListCellRenderer<? super Contribution> renderer
    = new DefaultListCellRenderer();
  @Override public Component getListCellRendererComponent(
      JList<? extends Contribution> list, Contribution value, int index,
      boolean isSelected, boolean cellHasFocus) {
    JLabel l = (JLabel) renderer.getListCellRendererComponent(
        list, null, index, isSelected, cellHasFocus);
    if (value.date.isAfter(currentLocalDate)) {
      l.setIcon(emptyIcon);
      l.setToolTipText(null);
    } else {
      l.setIcon(activityIcons.get(value.activity));
      String actTxt = value.activity == 0 ? "No" : Objects.toString(value.activity);
      l.setToolTipText(actTxt + " contribution on " + value.date.toString());
    }
    return l;
  }
}
View in GitHub: Java, Kotlin

解説

上記のサンプルでは、セルが垂直方向の次に水平方向の順で並ぶ「ニュースペーパー・スタイル」レイアウトのJListを使用して、GitHubで表示されているContribution activity風のヒートマップカレンダーを作成しています。

  • 1列で1週間になるよう、JList.VERTICAL_WRAPスタイルでの行数をJList#setVisibleRowCount(7)で指定
  • WeekFields#getFirstDayOfWeek()メソッドでLocaleに応じた週の最初の曜日を取得して、JList0行目を設定
    public static final int WEEK_VIEW = 27;
    //...
    WeekFields weekFields = WeekFields.of(Locale.getDefault());
    int dow = today.get(weekFields.dayOfWeek()) - 1;
    startDate = today.minusWeeks(WEEK_VIEW - 1).minusDays(dow);
    
  • 表示される週は、約半年分の27週に設定
    • JListindex = 0となるセルの日付は、27週から今週の1週間分を引いた26週前をtoday.minusWeeks(26)で取得し、さらに今日が週の何番目かを引いて求めている
      • 週番号をtoday.get(weekFields.dayOfWeek())で取得すると最初の曜日が1から始まるので、これを-1して最初の曜日がJList0行目からになるよう変更
  • ヒートマップは、テキストを空にして色アイコンを設定したJLabelListCellRenderer#getListCellRendererComponent(...)メソッドで返すことで表示
    • 各日付の色は4段階のランダムで生成したダミー
    • ツールチップテキストも、このListCellRendererJLabelに設定
  • 行ヘッダ
    • セルサイズが同じJListを適用
    • このJListのフォントサイズをセルの高さを基準に変更
  • 列ヘッダ
    • GridBagLayoutを使用するJPanelを適用
    • セルと幅が同じで高さが0のダミーコンポーネントBox.createHorizontalStrut(CELL_SIZE.width)を作成し、ガイドグリッドとして27週分追加
    • 前の週と月が異なる列にgridwidth=3の幅を占有するJLabelで月名を表示
      • 月内の週が4列、5列になる場合でも月名JLabel3列分のみ使用し、空列はガイドグリッドで幅を確保
      • 27 - 3 + 1列目以降は3列確保できないので、月が変わっても月名JLabelを表示しない

参考リンク

コメント