コルネの進捗や備忘録が記されたなにか

進捗や成果物や備忘録てきななにかを雑に更新していきます。

【Power Apps】あみだくじを作成してみよう!-その2-


スポンサードリンク

はじめに

この記事は前回投稿した、【Power Apps】あみだくじを作成してみよう!-その1-の続きです。
まだご覧になられていない方は是非ご確認ください。

前回はあみだくじ作成に必要な情報を作成しました。
今回はいよいよあみだくじの作成を行っていこうと思います!

あみだくじを作成する

とりあえず全容

まずはとりあえずコピペ用に全容を貼ります。

長いので折りたたんで表示しています。 "data:image/svg+xml,"& EncodeUrl( "<svg viewBox='0 0 "& Self.Width & " " & Self.Height & "' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' > " & Concat( ForAll( Sequence(ParticipantDropdown.Selected.Value, 1), "<path d='M " & Amidakuji.Width * Value / (ParticipantDropdown.Selected.Value + 1) & " 100 L " & Amidakuji.Width * Value / (ParticipantDropdown.Selected.Value + 1) & " 650' stroke='black' fill='transparent' stroke-width='2' />" ), Value ) & Concat( ForAll( Sequence(CountRows(amidakuji)), If( Last(FirstN(amidakuji, Value)).isline, "<path d='M " & Amidakuji.Width * Last(FirstN(amidakuji, Value)).num / (ParticipantDropdown.Selected.Value + 1) & " " & Last(FirstN(amidakuji, Value)).cnt * 30 + 100 & " L " & Amidakuji.Width * (Last(FirstN(amidakuji, Value)).num + 1) / (ParticipantDropdown.Selected.Value + 1) & " " & Last(FirstN(amidakuji, Value)).cnt * 30 + 100 & "' stroke='black' fill='transparent' stroke-width='2' />" ) ), Value ) & Concat( ForAll( Sequence(ParticipantDropdown.Selected.Value) As roopcnt, "<path d='M " & Amidakuji.Width * roopcnt.Value / (ParticipantDropdown.Selected.Value + 1) & " 100 " & "L " & Amidakuji.Width * roopcnt.Value / (ParticipantDropdown.Selected.Value + 1) & " " & First(Filter(route, num = roopcnt.Value)).cnt * 30 + 100 & Concat( ForAll( Sequence(CountRows(Filter(route, num = roopcnt.Value))) As routeNum, " L " & Amidakuji.Width * Last(FirstN(Filter(route, num = roopcnt.Value), routeNum.Value)).change / (ParticipantDropdown.Selected.Value + 1) & " " & Last(FirstN(Filter(route, num = roopcnt.Value), routeNum.Value)).cnt * 30 + 100 & "L " & Amidakuji.Width * Last(FirstN(Filter(route, num = roopcnt.Value), routeNum.Value)).change / (ParticipantDropdown.Selected.Value + 1) & " " & Last(FirstN(Filter(route, num = roopcnt.Value), routeNum.Value + 1)).cnt * 30 + 100 ), Value ) & " L " & Amidakuji.Width * Last(Filter(route, num = roopcnt.Value)).change / (ParticipantDropdown.Selected.Value + 1) & " 650'" & " stroke='none' fill='transparent' stroke-width='1' id='theMotionPath " & roopcnt.Value &"' />" ), Value ) & Concat( ForAll( Sequence(ParticipantDropdown.Selected.Value) As roopcnt, If( isStart, "<g> <rect x='-50' y='-15' width='100' height='30' fill='black'/> <text x='-" & Len(Last(FirstN(ParticipantGallery.AllItems, roopcnt.Value)).ParticipantTextInput.Text) * 10 & "' y='6' font-size='20' fill='white' font-weight='bold' > " & Last(FirstN(ParticipantGallery.AllItems, roopcnt.Value)).ParticipantTextInput.Text & " </text> <animateMotion dur='7s' repeatCount='1' fill='freeze' > <mpath xlink:href='#theMotionPath " & roopcnt.Value &"' /> </animateMotion> </g>", "<g> <rect x='" & Amidakuji.Width * roopcnt.Value / (ParticipantDropdown.Selected.Value + 1) - 50 & "' y='85' width='100' height='30' fill='black'/> <text x='" & Amidakuji.Width * roopcnt.Value / (ParticipantDropdown.Selected.Value + 1) - Len(Last(FirstN(ParticipantGallery.AllItems, roopcnt.Value)).ParticipantTextInput.Text) * 10 & "' y='106' font-size='20' fill='white' font-weight='bold' > " & Last(FirstN(ParticipantGallery.AllItems, roopcnt.Value)).ParticipantTextInput.Text & " </text> </g>" ) ), Value ) & With({s_result:Shuffle(result)}, Concat( ForAll( Sequence(ParticipantDropdown.Selected.Value) As roopcnt, "<g> <rect x='" & Amidakuji.Width * roopcnt.Value / (ParticipantDropdown.Selected.Value + 1) - 50 & "' y='665' width='100' height='30' fill='none'>" & If( isStart, "<animate attributeName='fill' begin='7s' dur='3s' from='none' to='black' repeatCount='1' fill='freeze' />" ) & " </rect> <text x='" & Amidakuji.Width * roopcnt.Value / (ParticipantDropdown.Selected.Value + 1) - Len(Last(FirstN(s_result, roopcnt.Value)).result) * 8 & "' y='687' font-size='20' fill='none' font-weight='bold' > " & Last(FirstN(s_result, roopcnt.Value)).result & If( isStart, "<animate attributeName='fill' begin='7s' dur='5s' from='none' to='white' repeatCount='1' fill='freeze' />" ) & " </text> </g>" ), Value ) ) & "</svg>" )

長い!!
いやー長いですね...
ローコードとは...

ここで注目して欲しいのはこのあみだくじの作成には、SVG という画像フォーマット技術を利用している。ということです。
SVG に関しては下記のリファレンスをご覧ください。(また今度SVG について纏めた記事書きたいと思います。)

developer.mozilla.org

また、コードをよくみてみると同じような塊になっているということがわかるかと思います。

ForAll 関数で繰り返し処理を行いコレクションを作成し、そのコレクションのカラムたちをConcat 関数で文字列として繋げていますね。
これによりそれぞれある要素を作成しているということです。

それでは細かく処理をみていきましょう!

SVGを記載するための準備

Power Apps でSVG を表示するためには、イメージコントロールを利用します。
「メディア」より「画像」を選択して、以下のように設定してみてください。

Image.Image

"data:image/svg+xml,"& 
EncodeUrl(
    "<svg viewBox='0 0 "& Self.Width & " " & Self.Height & "' xmlns='http://www.w3.org/2000/svg' >       
        <circle cx='50' cy='50' r='50' fill='black'/>
    </svg>"
)

これにより、「画像」コントロールの左上隅に黒い丸が表示されたかと思います。

黒い丸を表している箇所が4行目になります。

下記のような記述方法を基本として、SVG を作成していきます。

"data:image/svg+xml,"& 
EncodeUrl(
    "<svg viewBox='0 0 "& Self.Width & " " & Self.Height & "' xmlns='http://www.w3.org/2000/svg' >       
        [ここにSVG 要素を記述する]
    </svg>"
)

「画像」コントロールは画面いっぱいに設定しておいてください。

SVG をより詳しく見たい方はこちらをご確認ください。

developer.mozilla.org

あみだくじの縦線を作成する

まずはあみだくじの縦線を作成します。
縦線は、以下のように作成します。

Image.Image

"data:image/svg+xml,"& 
EncodeUrl(
    "<svg viewBox='0 0 "& Self.Width & " " & Self.Height & "' xmlns='http://www.w3.org/2000/svg' >       
        <path d='M 200 100 L 200 650'
          stroke='black' fill='transparent' stroke-width='2' />
    </svg>"
)

これで縦線が1本表示されるようになったかと思います。

ただ縦線は参加人数分作成したいですね。
そのためには上記のような記載を参加人数分作成したいです。
そこで利用するのが、ForAll 関数です。

ただ、ForAll 関数で得られる結果はコレクションです。
ここに設定する値は文字列です。
そんな問題を解決するのがConcat 関数です。

上記を踏まえて以下のように記載します。

Image.Image

"data:image/svg+xml,"& 
EncodeUrl(
    "<svg viewBox='0 0 "& Self.Width & " " & Self.Height & "' xmlns='http://www.w3.org/2000/svg' > " &
        Concat(
                ForAll(
                    Sequence(ParticipantDropdown.Selected.Value, 1),
                    "<path d='M " & Amidakuji.Width * Value / (ParticipantDropdown.Selected.Value + 1) & " 100 
                     L " &  Amidakuji.Width * Value / (ParticipantDropdown.Selected.Value + 1) & " 650'
                      stroke='black' fill='transparent' stroke-width='2' />"
                ),
                Value
        ) & "
    </svg>"
)

簡単に解説します。

繰り返し処理を行う回数は Sequence(ParticipantDropdown.Selected.Value, 1) 回です。
ParticipantDropdown.Selected.Value では参加人数が選択されていますね。

線の位置はそれぞれx座標の場所が異なりますね。
なのでループごとにx座標の値が等間隔で変化するように、また、参加人数によって線の間隔が変わるように式を作成しています。( Amidakuji.Width * Value / (ParticipantDropdown.Selected.Value + 1) )
ここで Amidakuji.Width は画面の幅を指定しています。

y座標の始点/終点は固定なので、始点:100、終点:650 で固定しています。

ちなみに始点の設定がM、終点の設定がLだと思ってください。

あみだくじの横線を作成する

続いてあみだくじの横線を作成します。

あみだくじの横線要素は以前 amidakuji というコレクションに作成しましたね。 このコレクションには、

アイテム名 役割
num x番目の箇所
cnt y番目の箇所(15分割されている)
isline 線を引くかどうか

が設定されています。

このコレクションに設定されているレコード数だけループ処理を行う必要があります。
なので、 Sequence(CountRows(amidakuji)) により、 amidakuji のレコード数分処理を行います。

上記を踏まえて先ほどの Concat() に続けて、& で繋げて以下のように記載します。

        Concat(
            ForAll(
                Sequence(CountRows(amidakuji)),
                If(
                    Last(FirstN(amidakuji, Value)).isline,
                    "<path d='M " & Amidakuji.Width * Last(FirstN(amidakuji, Value)).num / (ParticipantDropdown.Selected.Value + 1) & " 
                     " & Last(FirstN(amidakuji, Value)).cnt * 30 + 100 & " 
                      L " &  Amidakuji.Width * (Last(FirstN(amidakuji, Value)).num + 1) / (ParticipantDropdown.Selected.Value + 1) & " 
                       " & Last(FirstN(amidakuji, Value)).cnt * 30 + 100 & "'
                        stroke='black' fill='transparent' stroke-width='2' />"
                )
            ),
            Value
        )

現在みるべきレコードは、 Last(FirstN(amidakuji, Value)) で取得しています。

横線を引くのは、 islinetrue のときだけなので、If 関数により判断を行っています。

横線のx座標の式は縦線のx座標の式とほぼ同様です。

[横線の式]
Amidakuji.Width * 
+ Last(FirstN(amidakuji, Value)).num
 / (ParticipantDropdown.Selected.Value + 1)

[縦線の式]
Amidakuji.Width * 
- Value
 / (ParticipantDropdown.Selected.Value + 1)

このように valueLast(FirstN(amidakuji, Value)).num が異なっています。

横線の value はなにを表していたでしょうか?

縦線のときの value は、x本目の縦線ということを表していました。
これを今回の横線の作成の式に当てはめると、 amidakujinum 要素になります。
よって、 Last(FirstN(amidakuji, Value)).num となっています。

始点がx本目の縦線からはじまると、終点はx + 1本目の縦線になりますね。
なので、 Amidakuji.Width * (Last(FirstN(amidakuji, Value)).num + 1) / (ParticipantDropdown.Selected.Value + 1) となります。

最後にy座標ですが、これは、 amidakujicnt 要素に設定されています。
なので Last(FirstN(amidakuji, Value)).cnt * 30 + 100 としています。
* 30 は線の間隔を、 + 100 は一番上の横線が始まるy座標の初期位置をそれぞれ調整しています。

おわり

これで、あみだくじの梯子が作成できたかと思います。

f:id:koruneko:20201108034616p:plain

お疲れさまでした!

次回以降は参加者の名前をあみだくじに表示し、その名前があみだくじに沿って結果まで動くというアニメーションの作成方法を解説したいと思います。


スポンサードリンク