Power Apps コンテナ内にSVGで棒グラフ・ドーナツグラフを作る方法|コードで動くチャートの実装

Power Apps のネイティブグラフコントロールはデザインのカスタマイズに限界があります。SVGコードを Image コントロールで表示すれば、棒グラフもドーナツグラフも自分で設計できます。コードが動くグラフなので、データが変われば見た目も自動で変わります。

なぜ SVG を使うのか

Power Apps のネイティブグラフコントロール(Chart コントロール)は手軽ですが、色・サイズ・フォント・レイアウトの自由度が低いです。デザインを統一したいダッシュボードで使うと、KPIカードとの見た目の差が出てしまいます。かといって PowerBI を埋め込むのはライセンスの問題があります。

SVG はテキストベースのベクターグラフィック形式です。Power Apps の Image コントロールは SVG コードを直接表示できるので、コード内にコレクションや変数の値を差し込めば動的なグラフになります。ファイルサイズも軽く、拡大しても画質が落ちません。私がダッシュボードにSVGグラフを採用したのは、デザインの自由度とパフォーマンスの両方を満たせるからです。

コンテナ内にSVGグラフを組み込む方法はコンテナクラスターの実装事例の一つです。クラスター全体の設計思想はPower Apps コンテナ完全ガイドでまとめています。

Image コントロールで SVG を表示する基本

Image コントロールの Image プロパティに次のパターンで SVG を渡します。

"data:image/svg+xml," & EncodeUrl("SVGコード")

EncodeUrl で SVG コードを URL エンコードすることで、ブラウザが SVG を画像として認識します。SVGコードの中に Power Apps の式(コレクション参照・変数参照)を文字列として埋め込めるのが重要なポイントです。例えば棒の長さを変数で指定したい場合、SVGコード内の width 属性に & で変数を連結します。

"data:image/svg+xml," & EncodeUrl(
    "" &
    "" &
    ""
)

varBarWidth の値が変わると棒の幅が自動で変わります。この仕組みを理解しておくと、棒グラフもドーナツグラフも同じ発想で作れます。

棒グラフの実装

複数フェーズの件数を棒グラフで表示する場合、コレクション colPhaseCount に Phase(フェーズ名)と Count(件数)の列が入っているとします。各フェーズの棒の長さは最大値に対する相対値で計算します。

// 各フェーズの棒幅の計算式(最大値に対する比率×グラフ最大幅)
LookUp(colPhaseCount, Phase = "荷役中").Count / Max(colPhaseCount, Count) * 160

Max(colPhaseCount, Count) でコレクション内の最大件数を取得し、各フェーズの件数をその最大値で割って比率を出します。160 はグラフの最大幅(ピクセル)です。件数が最も多いフェーズが幅160になり、他のフェーズはその比率に応じた長さになります。

これを各フェーズ分 SVG の rect タグで並べるとグラフになります。フェーズが3つあれば rect を3行書きます。y 座標は各行の高さ×インデックスで計算します。

"data:image/svg+xml," & EncodeUrl(
    "" &
    "" &
    "" &
    "" &
    ""
)

rx='4' は棒の角丸です。コンテナの BorderRadius と統一しておくとデザインに一体感が出ます。

ドーナツグラフの実装

ドーナツグラフはSVGの circle タグと stroke-dasharray・stroke-dashoffset の組み合わせで作ります。少し数学的な話になりますが、仕組みを一度理解すれば応用できます。

半径 38px の円の円周は 2×π×38 ≈ 238.8 です。stroke-dasharray="238.8" を設定すると、円周全体が1本の線として扱われます。そこに stroke-dashoffset で「この長さだけ線をずらす」と指定することで、円の一部だけを表示できます。

"data:image/svg+xml," & EncodeUrl(
    "" &
    "" &
    "" &
    "" & Text(varCompletedRate * 100, "0") & "%" &
    ""
)

stroke-dashoffset に 238.8×(1-完了率) を設定することで、完了率が100%なら offset=0(全部表示)、0%なら offset=238.8(全部隠れる)となります。transform='rotate(-90 50 50)' で開始位置を12時方向に合わせています。SVGのデフォルトは3時方向から始まるので、-90度回転させています。

varCompletedRate は 0〜1 の小数で持っておくと計算式がシンプルになります。完了件数÷総件数で出します。ゼロ除算の対処は If 式で同様に対応してください。

不可視ラベルで中間値を保持するパターン

SVGのImage式の中に長い計算式を直接書くと、EncodeUrl の中でエラーが出たときにどこが悪いかわかりにくくなります。そこで、中間値をラベルに保持しておいてImage式からそのラベルを参照する方法が有効です。

Visible=false のラベル(例:lblCalcBarWidth)を画面に置いて、Text プロパティに計算式を書きます。

// lblCalcBarWidth の Text プロパティ
Text(LookUp(colPhaseCount, Phase = "荷役中").Count / Max(colPhaseCount, Count) * 160, "0")

Image コントロールの Image 式ではラベルのテキストを参照します。

// Image式内での参照
"width='" & lblCalcBarWidth.Text & "'"

これでImage式がすっきりし、計算結果をラベルで目視確認しながらデバッグできます。私もSVGグラフを初めて作ったとき、EncodeUrl の中でエラーが出て何時間も悩みました。それ以来、複雑な計算はすべて不可視ラベルに出してから Image 式で使うようにしています。

まとめ

SVGグラフは最初のとっつきにくさがありますが、仕組みを理解すれば自由度が高くパフォーマンスも良い選択肢です。棒グラフはデータの最大値に対する比率で棒幅を計算する、ドーナツグラフは stroke-dashoffset で円弧を切り取る。この2パターンを覚えておけば、ダッシュボードのグラフ表現の幅が大きく広がります。デバッグは不可視ラベルを使いながら進めると格段にやりやすくなります。

Xでフォローしよう