はじめに
Excel関数とピボットを駆使して、ExcelでWBS(タスク一覧)からガントチャートを作ってみたものの、通常は「1タスク=1行」になるため、タスクが増えると縦に長い表になってしまうなぁと。
「チームごと」や「担当者ごと」にまとめて、スケジュールが空いている期間は同じ行に詰め込みたい(行を圧縮したい) けれど、VBA(マクロ)を使いたくない。。。
ということで、Power Queryだけで「行を圧縮するガントチャート」を作る汎用的なM言語コードを作成しました。
実現できること
-
行の圧縮: チームや担当者単位などを行見出しにして、集計セルに開発フェーズなどの文言を集約するには、
/区切りで1セルに詰め込みます(★ここが今回のポイント)。 - 期間の可変: 列見出しは「日次」「週次(月曜始まり)」「月次」を引数で切り替えられます。
-
マクロ不要:
.xlsxのまま保存でき、更新ボタン一つで反映されます。 - 関数化: 汎用的な関数として定義して、ピボットの行見出しを引数で与えて、集計するコードをいじらなくてもすむようにしています。
完成イメージ
これは条件付き書式でフェーズの変化を示しています。
素のPowerQueryだとこんな感じ。
作り方
今回は処理を2つの関数(クエリ)に分けました。
-
fxPrepWBS: 日付を展開し、カレンダー情報(日・週・月)を付与する(ETL層) -
fxPivotGantt: 指定した粒度で行列を入れ替えて表示する(Presentation層)
手順1. WBSテーブルの準備
ExcelシートにWBSを用意し、テーブル化(Ctrl+T)しておきます。
列名は以下を想定していますが、違っていても関数の引数で吸収可能です。
- チーム
- 開発フェーズ
- 開始日付
- 終了日付
| チーム | 開発フェーズ | 担当者 | タスク | 開始日付 | 終了日付 |
|---|---|---|---|---|---|
| 3.フロント | 基本設計 | 鈴木 | 画面A | 2025/11/7 | 2025/12/1 |
| 3.フロント | 詳細設計 | 鈴木 | 画面A | 2025/12/2 | 2026/1/15 |
| 3.フロント | 実装 | 鈴木 | 画面A | 2026/1/20 | 2026/5/1 |
| 3.フロント | テスト | 鈴木 | 画面A | 2026/5/2 | 2026/6/30 |
| 3.フロント | 基本設計 | 佐藤 | 画面B | 2025/11/13 | 2025/12/12 |
| 3.フロント | 詳細設計 | 佐藤 | 画面B | 2025/12/13 | 2026/1/20 |
| 3.フロント | 実装 | 佐藤 | 画面B | 2026/1/21 | 2026/6/30 |
| 4.バックエンド | 基本設計 | 田中 | バックエンドA/B | 2025/11/1 | 2026/1/31 |
| 4.バックエンド | 詳細設計 | 田中 | バックエンドA/B | 2026/1/22 | 2026/3/15 |
| 4.バックエンド | 実装 | 田中 | バックエンドA | 2026/3/7 | 2026/6/30 |
| 4.バックエンド | 実装 | 伊藤 | バックエンドB | 2026/3/1 | 2026/5/15 |
| 4.バックエンド | テスト | 伊藤 | バックエンドB | 2026/5/16 | 2026/6/30 |
| 5.インフラチーム | 基本設計 | 渡辺 | DB/インフラ | 2025/11/1 | 2025/11/30 |
| 5.インフラチーム | 詳細設計 | 渡辺 | DB/インフラ | 2025/12/1 | 2025/12/31 |
| 5.インフラチーム | 実装 | 渡辺 | DB/インフラ | 2026/1/1 | 2026/1/31 |
| 5.インフラチーム | テスト | 渡辺 | DB/インフラ | 2026/2/1 | 2026/3/1 |
| 1.PM | マネジメント | 山田 | マネジメント | 2025/11/1 | 2026/6/30 |
| 1.PM | マネジメント | 加藤 | マネジメント | 2025/11/1 | 2025/12/25 |
| 2.アーキテクト | 技術支援 | 佐々木 | 技術支援 | 2026/1/1 | 2026/2/28 |
手順2. 関数 fxPrepWBS の作成
「データの取得」>「その他のデータソースから」>「空のクエリ」を作成し、詳細エディタに以下を貼り付けます。クエリ名は fxPrepWBS としてください。
- 週次ロジック: 202511-1 のようなラベルを作成する際、月曜日に週が始まる前提で計算しています。そのため、「その週の月曜日が何月か」を基準に計算して、月をまたぐ週を分けています。
-
2025/10/27(月)は202510-4、2025/11/01(土)は202511-1、2025/12/01(月)は202512-1。
‐ WBSの列名で「チーム」「開発フェーズ」「開始日付」「終了日付」を想定してハードコーディングしているので、適宜修正してください。
‐ 「開始日付」「終了日付」は日付型を想定していますが、日時型の場合はtype dateのところをtype datetimeに適宜修正してください。
(SourceTable as table) as table =>
let
// 1. 型変換(★修正箇所)
Types = Table.TransformColumnTypes(SourceTable,{
{"チーム", type text}, {"開発フェーズ", type text},
{"開始日付", type date}, {"終了日付", type date}
}),
// 2. 日付展開(1行1日)(★修正箇所)
AddedDates = Table.AddColumn(Types, "DateKey", each
List.Dates([開始日付], Duration.Days([終了日付]-[開始日付])+1, #duration(1,0,0,0))
),
Expanded = Table.ExpandListColumn(AddedDates, "DateKey"),
TypeDate = Table.TransformColumnTypes(Expanded,{{"DateKey", type date}}),
// --- 各種期間情報の付与 ---
// 3. 日次情報
AddDayLabel = Table.AddColumn(TypeDate, "DayLabel", each Date.ToText([DateKey], "yy/MM/dd"), type text),
// 4. 月次情報
AddMonthKey = Table.AddColumn(AddDayLabel, "MonthKey", each Date.StartOfMonth([DateKey]), type date),
AddMonthLabel = Table.AddColumn(AddMonthKey, "MonthLabel", each Date.ToText([DateKey], "yyyy/MM"), type text),
// 5. 週次情報(月曜始まり・月曜基準の月度)
// 例: 11/1(土)が含まれる週の月曜が10月なら、それは10月度第N週とする
AddWeekKey = Table.AddColumn(AddMonthLabel, "WeekKey", each Date.StartOfWeek([DateKey], Day.Monday), type date),
AddWeekLabel = Table.AddColumn(AddWeekKey, "WeekLabel", each
let
Monday = Date.StartOfWeek([DateKey], Day.Monday),
YYYYMM = Date.ToText(Monday, "yyyyMM"),
WeekNum = Number.IntegerDivide(Date.Day(Monday) - 1, 7) + 1
in
YYYYMM & "-" & Text.From(WeekNum),
type text
)
in
AddWeekLabel
手順3. 関数 fxPivotGantt の作成
同様に「空のクエリ」を作成し、詳細エディタに以下を貼り付けます。クエリ名は fxPivotGantt としてください。
(PreparedTable as table, RowHeaders as list, TimeScale as text, ValueColumn as text) as table =>
let
// 1. モードに応じて使用する列名(キーとラベル)を決定
TargetColumns =
if TimeScale = "Day" then [Key="DateKey", Label="DayLabel"]
else if TimeScale = "Week" then [Key="WeekKey", Label="WeekLabel"]
else if TimeScale = "Month" then [Key="MonthKey", Label="MonthLabel"]
else error "TimeScaleは 'Day', 'Week', 'Month' のいずれかを指定してください",
KeyCol = TargetColumns[Key],
LabelCol = TargetColumns[Label],
// 2. 必要な列だけを選択(行見出し + 時間キー + 時間ラベル + 集計対象列)
SelectCols = Table.SelectColumns(PreparedTable, RowHeaders & {KeyCol, LabelCol, ValueColumn}),
// 3. 集計対象列を固定名(TempTarget)に変更(動的対応のため)
RenamedTable = Table.RenameColumns(SelectCols, {{ValueColumn, "TempTarget"}}),
// 4. 重複削除
DistinctRows = Table.Distinct(RenamedTable),
// 5. グループ化(行見出し + 時間単位 で値を結合)(★今回のポイント)
GroupKeys = RowHeaders & {KeyCol, LabelCol},
Grouped = Table.Group(DistinctRows, GroupKeys, {
{"Val", each Text.Combine(List.Distinct([TempTarget]), "/"), type text}
}),
// 6. ソート(時系列順を保証)
Sorted = Table.Sort(Grouped, {{KeyCol, Order.Ascending}}),
// 7. ピボット用のカラムリスト作成
PivotHeaders = List.Distinct(Table.Column(Sorted, LabelCol)),
// 8. ピボット実行前の列整理(キー列を削除して行圧縮させる)
PrePivotTable = Table.SelectColumns(Sorted, RowHeaders & {LabelCol, "Val"}),
// 9. ピボット実行
Pivoted = Table.Pivot(
PrePivotTable,
PivotHeaders,
LabelCol,
"Val"
)
in
Pivoted
使い方
準備ができたら、新しいクエリを作成してこんな感じで関数を呼び出すだけです。
let
PreparedWBS = fxPrepWBS(Excel.CurrentWorkbook(){[Name="T_WBS"]}[Content]),
Result = fxPivotGantt(PreparedWBS, {"チーム", "担当者"}, "Month", "開発フェーズ")
in
Result
1. 準備(データ加工)
fxPrepWBS に通します。
= fxPrepWBS(Excel.CurrentWorkbook(){[Name="T_WBS"]}[Content])
2. ガントチャート生成
fxPivotGantt を使い、好きな切り口で出力します。
例A:チーム単位 × 月次(セルには「開発フェーズ」を表示)
= fxPivotGantt(PreparedWBS, {"チーム"}, "Month", "開発フェーズ")
例B:担当者単位 × 日次(セルには「タスク」を表示)
= fxPivotGantt(PreparedWBS, {"担当者"}, "Day", "タスク")
例C:チーム・担当者 × 週次
= fxPivotGantt(PreparedWBS, {"チーム", "担当者"}, "Week", "開発フェーズ")
例D:チーム・担当者 × 月次を条件付き書式で表現
何も書式を指定しないとPower Queryのデフォルトの書式になりますが、条件付き書式を使うとこんな感じにできます。

技術的な特徴・工夫した点
今回のPower Query(M言語)の実装において、技術的に重要ないくつかのポイントを説明します。
1. ETL層とPresentation層の分離
処理を2つの関数に分割したアーキテクチャを採用しています。
-
fxPrepWBS(ETL層):-
List.Datesを用いて、期間(開始~終了)を1日1行のレコードに増幅する処理を行います。 - 計算コストが高い処理ですが、最初に1回だけ実行して中間テーブルとして保持します。
-
-
fxPivotGantt(Presentation層):- 展開済みのテーブルに対して、フィルタリングとピボットのみを行います。
- これにより、同じデータソースから「日次」「週次」「月次」など複数のビューを作成する際、重い展開処理を再計算せずに済み、パフォーマンスを確保しています。
2. 動的なグルーピング(リストによる列指定)
M言語の Table.Group は通常、固定された列名を指定する必要がありますが、汎用性を高めるためにリスト操作を活用してます。
// 5. グループ化(行見出し + 時間単位 で値を結合)(★今回のポイント)
GroupKeys = RowHeaders & {KeyCol, LabelCol},
Grouped = Table.Group(DistinctRows, GroupKeys, {
{"Val", each Text.Combine(List.Distinct([TempTarget]), "/"), type text}
}),







