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

## 緒言
[こちら](https://qiita.com/meccha__eeyan/items/de666b3e70b357996eca)や[こちら](https://qiita.com/meccha__eeyan/items/8286b77b00bdc46df21e)の記事でタスク管理について書いてきましたが、上司やプロジェクトリーダーが見たいのはガントチャート。全体感を見たかったり、直近の細かい予定を見たかったりで縮尺可変が望ましい！ということでチャレンジしてみました。PlannerにもScheduleビューが用意されてるけど、ちょっと使いにくい。。。

完成イメージは以下

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

## 作り方
### 1. データ用意
バケット(`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 |

このデータを使って以下のコレクションや変数を作成。
```
ClearCollect(ColPlannerPlus,AddColumns(PlannerPlus,RowIndex,0));
ForAll(
    Sequence(CountRows(ColPlannerPlus)) As i,
    Patch(ColPlannerPlus,Index(ColPlannerPlus,i.Value),{RowIndex:i.Value})
);
UpdateContext({_maxDate:Max(ColPlannerPlus,DueDate),_minDate:Min(ColPlannerPlus,StartDate)});
UpdateContext({_dateDuration:DateDiff(_minDate,_maxDate,TimeUnit.Days)})
```
`ColPlannerPlus`：`PlannerPlus`に行インデックス列を追加したコレクション
`_maxDate`：全データ中の日付最大値
`_minDate`：全データ中の日付最小値
`_dateDuration`：`_maxDate`と`minDate`の差（日）

### 2.画面設計
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3670355/8f8dc3ba-e369-7cc4-94a1-6c9c813bbb8f.png)
コンテナ内にパーツを配置。特に重要なパーツは以下の6種類。
`RadioGroupMag`：倍率選択ラジオボタン
`SliderZoom`：Zoom操作スライダー
`SliderScroll`：拡大時のスクロール用スライダー
`RectangleBlind`：目隠し用の図形
`GalleryDate`：日付ラベル用のギャラリー
`GalleryMain`：ガントチャートのメインギャラリー

`GalleryMain`内部に入れ子でギャラリーを下図のように配置。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3670355/6fc04bec-e15c-2468-9613-e64ac51c1ec9.png)
`LabelID`：非表示だが`Text`プロパティで子ギャラリーに親ギャラリーのレコード情報を渡す役割。
`LabelTitle`：タスクタイトル表示
`LabelBucket`：バケット表示
`GallerySub`：ガントチャート表示用ギャラリー

`GallerySub`の内容は今日の目印やスケジュール塗りつぶし用の図形。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3670355/8ff9093e-03e9-6c79-470d-2ae00cb51e11.png)


### 3.ロジック
スライダーやラジオボタンの値に応じて幅や座標を可変にしている。
```
- ContainerGantt:
    Children:
    - GalleryMain:
        Properties:
          Items: =ColPlannerPlus
          Width: =Parent.Width*(1+(SliderZoom.Value-1)/SliderZoom.Max*RadioGroupMag.Selected.value)
        Children:
        - LabelID:
            Properties:
              Text: =ThisItem.ID
              
        - LabelTitle:
            Properties:
              Text: =ThisItem.Title
              
        - LabelBucket:
            Properties:
              Text: |-
                  =If(
                      And(
                          ThisItem.RowIndex>1,
                          Index(ColPlannerPlus,ThisItem.RowIndex-1).Bucket=ThisItem.Bucket
                      ),
                      "",
                      ThisItem.Bucket
                  )
                  
        - GallerySub:
            Properties:
              Items: =Sequence(_dateDuration)
              TemplateSize: =IfError(Self.Width/_dateDuration,10)
              Width: =Parent.Width-(LabelTitle.X+LabelTitle.Width)
              X: =LabelTitle.X+LabelTitle.Width-(SliderScroll.Value-1)*(GalleryMain.Width-ContainerGantt.Width)/SliderScroll.Max
            Children:
            - RectangleSchedule:
                Properties:
                  Visible: |-
                    =With({rec:LookUp(ColPlannerPlus,ID=Value(LabelID.Text))},
                        And(
                            DateDiff(_minDate,rec.StartDate,TimeUnit.Days)<=ThisItem.Value-1,
                            DateDiff(_minDate,rec.DueDate,TimeUnit.Days)>=ThisItem.Value-1
                        )
                    )
                  Width: =Parent.TemplateWidth+1
                  Y: =Parent.TemplateHeight/2-Self.Height/2
                  
            - RectangleToday:
                Properties:
                  Visible: =DateAdd(_minDate,ThisItem.Value-1)=Today()
                  
    - GalleryDate:
        Properties:
          Items: =Sequence(_dateDuration)
          TemplateSize: =IfError(Self.Width/_dateDuration,10)
          Width: =Parent.Width*(1+(SliderZoom.Value-1)/SliderZoom.Max*RadioGroupMag.Selected.value)-(LabelTitle.X+LabelTitle.Width)
          X: =LabelTitle.X+LabelTitle.Width-(SliderScroll.Value-1)*(GalleryMain.Width-ContainerGantt.Width)/SliderScroll.Max
        Children:
        - TextCanvasDate:
            Properties:
              Text: =Text(DateAdd(_minDate,ThisItem.Value-1),"m/d")
              Visible: =Mod(ThisItem.Value,Int(_dateDuration/10))=1

```

## 結言
そんなに苦労せず見やすいガントチャートができた！