アプリでグラフを描くためのちょっと面白いアプローチ

#wpdev_jp #win8dev_jp

Twitter に MSDN フォーラムの質問が出ていた。こちら。

Windows 8 ストアアプリで画面に多数の直線を描く方法

もったいないなーって思ったのは、「本当はグラフを描きたかったわ」けで「多数の直線を描くこと」ではなかったんですよね?まぁ、最終的には多数の直線で書くわけですが。

グラフを描くのは結構大変

この質問は、画面上にサインカーブを描きたいというもので、結論としてはこう。

  • 直線をつないで書くことになります
  • 計算が必要なので、コード側で書くことになります

フォーラムにも書いてありますが、昔ながらのやり方で行くと結構なコード量にはなります。

  • オブジェクトを生成
  • ポイントを設定
  • オブジェクトのプロパティを設定
  • 親オブジェクトに座標を合わせて設定
  • 始点と終点に対して行う

こうなりますね。

XAMLを使えば簡単になる!

ただし、これってうまくやるとすごくコードの量を減らすことができます。今回のなら全部で実質10行以内でできます。それはなぜか?

XAMLは描画オブジェクトを簡潔に設置できる

からです。ようはね?全部コードで書いちゃうから長くなっちゃうんですよ。簡単にできるものは簡単にやっておいたほういいんです。

ではどうやるのか?

  1. まずはXAMLでパスを書いておく

    image

  2. 実行してからロジックでポイントをずらす

    image

こうすればいいんです。パスのポイントいじるところでちょっとコツが要りますがw

サンプルコード

紙面(^^;) の都合でポイントは荒くしています。実質コードはたぶん10行にも満たないでしょう。

MainPage.xaml

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Viewbox Width="800" Height="400">
        <Canvas Height="200" Width="360">
            <Path x:Name="myline"
                     Data="M-180,0 L-180,0 L-150,0 L-120,0 L-90,0 L-60,0 L-30,0 L0,0 L30,0  L60,0 L90,0 L120,0 L150,0 L180,0"
                    Stroke="White" StrokeThickness="7" Margin="180,100,0,0" />
        </Canvas>
    </Viewbox>
</Grid>

デザイン時の画面はこんな感じ。Blend でパスとみると頂点が既にあることがわかります。

image

MainPage.xaml.cs

void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    // パスの各点のコレクションを取得します
    PathFigure _pf = (myline.Data as PathGeometry).Figures[0] as PathFigure;
    // パスの各点の1つ1つを取り出します。
    foreach(LineSegment _ls in _pf.Segments )
    {
        // 取り出した点をいったん保存し
        Point org = _ls.Point;
        // Y座標だけをSIN関数で計算して設定しなおします
        _ls.Point = new Point(org.X, Math.Sin(org.X * Math.PI / 180 ) *100 );
    }
}

荒いですが、そこはご勘弁。きれいに細かくしたかったら、XAMLのポイントを増やせばOK。

image

もう一つの隠されたポイント

簡素化できたポイントはまだいくつかあります。実は画面に収めるための処理を計算式にまったく入れてないんです。あくまでもポイントの移動は論理座標系で行い、できたグラフを画面にうまく収めるところは、すべてXAML側で行います。

■ まずパスの X座標を-180~180の論理座標にした

  • こうすることで、計算式が簡単になります。

しかし画面は整数座標系です。-180とか言われてもはみ出ます。

  • その分はPathのMarginで平行移動することで簡単に対応できます。
  • この部分をロジックに入れなくていいんです。

さらに、グラフのサイズが小さくなってしまう部分は、

  • Canvas を ViewBox で拡大することで対応。線の太さは調整が必要ですけどね。

まとめ

ポイントが不定数の場合は、ロジックで行わなければなりませんが、それでも 基本のパスさえ置いておけば、ポイントの追加はシンプルなコードで書くことができます。置けるものは初めにXAMLで置いておいて、後から表示させたり、移動させたり、すれば処理は簡潔にできます。

今回のものは同様にパスアニメーションにも応用できるかもしれませんね。

なお、画面の部分は、PathオブジェクトのDataの埋め込み以外は、すべてBlend でデザインしています。ですからこれも「Blen道」!

おまけ

ポイントを5度間隔で設定してみた。きれいですね。

image

その時のPathオブジェクトのXAMLのコード。

<Path x:Name="myline"
        Data="M-180,0 L-180,0 L-175,0 L-170,0 L-165,0 L-160,0 L-155,0
        L-150,0 L-145,0 L-140,0 L-135,0 L-130,0 L-125,0 L-120,0 L-115,0 L-110,0 L-105,0 L-100,0 L-95,0
         L-90,0 L-85,0 L-80,0 L-75,0 L-70,0 L-65,0 L-60,0 L-55,0 L-50,0 L-45,0 L-40,0 L-35,0
        L-30,0 L-25,0 L-20,0 L-15,0 L-10,0 L-5,0 L0,0 L5,0 L10,0 L15,0 L20,0 L25,0
         L30,0 L35,0 L40,0 L45,0 L50,0 L55,0 L60,0 L65,0 L70,0 L75,0 L80,0 L85,0
        L90,0 L95,0 L100,0 L105,0 L110,0 L115,0 L120,0 L125,0 L130,0 L135,0 L140,0 L145,0
        L150,0 L155,0 L160,0 L165,0 L170,0 L175,0 L180,0"
        Stroke="White" StrokeThickness="7" Margin="180,100,0,0" />

あ、ちなみに、このデータはどうやって作るかって?もちろんEXCELですよ。

image

ちなみに、SINの計算データもあらかじめ計算して、(100倍するなどして)整数化して配列に入れておくと処理が速くなります。よくやる手です。