Power Appsで縮尺可変ガントチャート2

## 緒言
以前[こちら](https://qiita.com/meccha__eeyan/items/8a77ecc0362bdc1d5a48)で記事化したものの改良版。
動作イメージは↓

https://x.com/meccha__eeyan/status/1877002938033263042

## 作り方

### 1.データ用意
[前回](https://qiita.com/meccha__eeyan/items/8a77ecc0362bdc1d5a48)同様。バケット(`Bucket`)で分類された開始日(`StartDate`)と納期(`DueDate`)を持つタスクリストをデモデータ`PlannerPlus`とする。
| Title | Bucket | StartDate | DueDate |
| --- | --- | --- | --- |
| ベンチプレス | 胸トレ | 2024/12/09 | 2024/12/13 |
| ダンベルフライ | 胸トレ | 2024/12/11 | 2024/12/14 |
| プッシュアップ | 胸トレ | 2024/12/03 | 2024/12/17 |
| レッグプレス | 足トレ | 2024/12/16 | 2024/12/27 |
| カーフレイズ | 足トレ | 2024/12/23 | 2024/12/27 |
| スクワット | 足トレ | 2024/12/08 | 2025/01/10 |
| デッドリフト | 背中トレ | 2025/01/01 | 2025/01/09 |
| ラットプルダウン | 背中トレ | 2025/01/06 | 2025/01/16 |

```diff_java
ClearCollect(ColPlannerPlus,AddColumns(PlannerPlus,RowIndex,0));
ForAll(
    Sequence(CountRows(ColPlannerPlus)) As i,
    Patch(ColPlannerPlus,Index(ColPlannerPlus,i.Value),{RowIndex:i.Value})
);
UpdateContext({
    _minDate:DateAdd(Today(),-30,TimeUnit.Days),
    _dateDuration:60,
    _step:1
})
```
`ColPlannerPlus`：`PlannerPlus`に行インデックス列を追加したコレクション
`_minDate`：Today基準の30日前。
`_dateDuration`：ガントチャートの列数。
`_step`：ガントチャート1列ごとの間隔(日)。初期1日とする。

### 2.画面設計
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3670355/c6ee1b44-ceb3-aeb0-4b15-90af3a5ab44f.png)
主なコントロールは下記の6つ
`GalleryMain2`：ガントチャートのメインギャラリー
`GalleryDate2`：日付ラベル用のギャラリー
`RadioGroupStep`：ガントチャートの列間隔(日)の変更
`IconBack`：チャートを左へ移動
`IconNext`：チャートを右へ移動
`ButtonReset`：チャートをリセット

### 3.コード
<details><summary>コピペ用</summary>

```diff_java
- ContainerGantt2:
    Control: GroupContainer
    Variant: manualLayoutContainer
    Properties:
      DropShadow: =DropShadow.Bold
      Height: =461
      RadiusBottomLeft: =10
      RadiusBottomRight: =10
      RadiusTopLeft: =10
      RadiusTopRight: =10
      Width: =1113
      X: =150
      Y: =85
    Children:
    - GalleryMain2:
        Control: Gallery
        Variant: BrowseLayout_Vertical_OneTextVariant_ver5.0
        Properties:
          Items: =ColPlannerPlus
          DelayItemLoading: =true
          Height: =Parent.Height-Self.Y
          Layout: =Layout.Vertical
          LoadingSpinner: =LoadingSpinner.Data
          TemplatePadding: =0
          TemplateSize: =60
          Width: =Parent.Width
          Y: =67
        Children:
        - RectangleGridH2:
            Control: Rectangle
            Properties:
              OnSelect: =Select(Parent)
              Height: =2
              Visible: =LabelBucket2.Text<>""
              Width: =Parent.Width
        - LabelID2:
            Control: Label
            Properties:
              OnSelect: =Select(Parent)
              Text: =ThisItem.ID
              Height: =18
              PaddingBottom: =0
              PaddingRight: =0
              PaddingTop: =0
              Size: =12
              Visible: =false
              Width: =86
              X: =108
        - LabelTitle2:
            Control: Label
            Properties:
              OnSelect: =Select(Parent)
              Text: =ThisItem.タイトル
              Fill: =App.Theme.Colors.Lighter80
              Height: =Parent.TemplateHeight
              PaddingBottom: =0
              PaddingRight: =0
              PaddingTop: =0
              Width: =194
              X: =108
        - LabelBucket2:
            Control: Label
            Properties:
              OnSelect: =Select(Parent)
              Text: =If(ThisItem.RowIndex>1&&Index(ColPlannerPlus,ThisItem.RowIndex-1).Bucket=ThisItem.Bucket,"",ThisItem.Bucket)
              Color: =
              Fill: =RGBA(255, 255, 255, 1)
              FontWeight: =FontWeight.Bold
              Height: =Parent.TemplateHeight
              PaddingBottom: =0
              PaddingLeft: =15
              PaddingRight: =0
              PaddingTop: =0
              Width: =108
        - GallerySub2:
            Control: Gallery
            Variant: BrowseLayout_Horizontal_TwoTextOneImageVariant_ver5.0
            Properties:
              Items: =Sequence(_dateDuration,1,_step)
              DelayItemLoading: =true
              Height: =60
              LoadingSpinner: =LoadingSpinner.Data
              ShowScrollbar: =false
              TemplatePadding: =0
              TemplateSize: =IfError(Self.Width/_dateDuration,1)
              Width: =Parent.Width-302
              X: =302
            Children:
            - RectangleSchedule2:
                Control: Rectangle
                Properties:
                  OnSelect: =Select(Parent)
                  BorderColor: =RGBA(40, 134, 222, 1)
                  BorderStyle: =BorderStyle.None
                  Fill: =RGBA(40, 134, 222, 1)
                  Height: =Parent.TemplateHeight*0.6
                  Visible: |-
                    =With({rec:LookUp(ColPlannerPlus,ID=Value(LabelID2.Text))},
                        And(
                            DateDiff(_minDate,rec.StartDate,TimeUnit.Days)<=ThisItem.Value-1,
                            DateDiff(_minDate,rec.DueDate,TimeUnit.Days)>=ThisItem.Value-1
                        )
                    )
                  Width: =Parent.TemplateWidth
                  Y: =Parent.TemplateHeight/2-Self.Height/2
            - RectangleToday2:
                Control: Rectangle
                Properties:
                  OnSelect: =Select(Parent)
                  BorderStyle: =BorderStyle.None
                  Fill: =RGBA(215, 58, 60, 1)
                  Height: =60
                  Visible: =And(DateAdd(_minDate,ThisItem.Value-_step)<=Today(),DateAdd(_minDate,ThisItem.Value)>Today())
                  Width: =4
                  X: =Parent.TemplateWidth/2-Self.Width/2
            - RectangleGridV2:
                Control: Rectangle
                Properties:
                  OnSelect: =Select(Parent)
                  BorderStyle: =BorderStyle.None
                  Fill: =RGBA(106, 122, 127, 1)
                  Height: =60
                  Width: =1
    - GalleryDate2:
        Control: Gallery
        Variant: BrowseLayout_Horizontal_TwoTextOneImageVariant_ver5.0
        Properties:
          Items: =Sequence(_dateDuration,1,_step)
          DelayItemLoading: =true
          Height: =30
          LoadingSpinner: =LoadingSpinner.Data
          ShowScrollbar: =false
          TemplatePadding: =0
          TemplateSize: =IfError(Self.Width/_dateDuration,1)
          Width: =Parent.Width-GallerySub2.X
          X: =GallerySub2.X
          Y: =35
        Children:
        - TextCanvasDate2:
            Control: Text
            Properties:
              Text: =Text(DateAdd(_minDate,ThisItem.Value-1),"m/d")
              Wrap: =false
              Height: =Parent.Height
              Visible: =IfError(Mod(ThisItem.Value,Int(_dateDuration*_step/10)),2)=1
              Width: =50
    - RadioGroupStep:
        Control: Radio
        Variant: pcfdataset
        Properties:
          OnChange: =UpdateContext({_step:Self.Selected.value})
          DefaultSelectedItems: =[{text:"1d",value:1}]
          Items: =[{text:"1d",value:1},{text:"3d",value:3},{text:"7d",value:7}]
          Layout: ='RadioGroupCanvas.Layout'.Horizontal
          Height: =40
          Width: =200
          X: =62
        Children:
        - TextInput1_1:
            Control: PowerApps_CoreControls_RadioGroupCanvasTemplate_dataField
            Variant: textualColumn
            Properties:
              FieldDisplayName: ="text"
              FieldName: ="text"
              FieldType: ="s"
              Order: =1
    - IconBack:
        Control: Classic/Icon
        Variant: BackArrow
        Properties:
          OnSelect: =UpdateContext({_minDate:DateAdd(_minDate,-15*_step,TimeUnit.Days)})
          Height: =35
          Icon: =Icon.BackArrow
          PaddingBottom: =3
          PaddingLeft: =3
          PaddingRight: =3
          PaddingTop: =3
          Width: =35
          X: =302
    - IconNext:
        Control: Classic/Icon
        Variant: BackArrow
        Properties:
          OnSelect: =UpdateContext({_minDate:DateAdd(_minDate,15*_step,TimeUnit.Days)})
          Height: =35
          Icon: =Icon.NextArrow
          PaddingBottom: =3
          PaddingLeft: =3
          PaddingRight: =3
          PaddingTop: =3
          Width: =35
          X: =1078
    - RectangleHeadBorder:
        Control: Rectangle
        Properties:
          OnSelect: =
          BorderThickness: =2
          Height: =3
          Visible: =LabelBucket2.Text<>""
          Width: =Parent.Width
          Y: =67
    - ButtonReset:
        Control: Button
        Properties:
          OnSelect: |-
            =ClearCollect(ColPlannerPlus,AddColumns(PlannerPlus,RowIndex,0));
            ForAll(
                Sequence(CountRows(ColPlannerPlus)) As i,
                Patch(ColPlannerPlus,Index(ColPlannerPlus,i.Value),{RowIndex:i.Value})
            );
            UpdateContext({
                _minDate:DateAdd(Today(),-30,TimeUnit.Days),
                _dateDuration:60,
                _step:1
            })
          Text: ="リセット"
          X: =659
          Y: =6

```
</details>

:::note warn
Mod演算時にゼロ割エラーが出ることがあったのでIfErrorで回避
:::

## 結言
1年くらいのタイムスパンのある100件近くのタスクでも比較的サクッと表示できて快適！