Chartコントロールを使ってWindows Phoneに棒グラフを表示する

2012年2月20日(月)
PROJECT KySS

Windows Phone 縦向きのページの作成(DataInPage.xaml)

VS2010メニューの「プロジェクト(P)/新しい項目の追加(W)」と選択して、「Windows Phone 縦向きのページ」を選択します。「名前(N)」には「DataInPage.xaml」指定し、[追加(A)]ボタンをクリックします。

コントロールの配置とXAMLの編集

PageTitleという名前を持つTextBlockのTextプロパティに「血圧入力」と指定します。

ツールボックスから、TextBlockコントロールを3個、TextBoxコントロールを3個、Buttonコントロールを2個配置します(図10)。

図10:各種コントロールを配置した(クリックで拡大)

ichiranButtonは最初の状態では、IsEnabledのチェックを外して、使用不可としておきます。データが1件でも記録されれば、[データ一覧]ボタンは使用可能となります。

書き出されるXAMLコードは省略します。

ソリューションエクスプローラー内のMainPage.xamlを展開して表示される、MainPage.xaml.vbをダブルクリックして、リスト2のコードを記述します。

ロジックコードを記述する

リスト2  (MainPage.xaml.vb)

Option Strict On
Imports System.Windows.Documents
Imports System.Windows.Input
Imports System.Windows.Media
Imports System.Windows.Media.Animation

Imports Microsoft.Phone.Controls
Imports System.Xml.Linq
Imports System.Collections.ObjectModel
Imports System.IO

仮想ファイルシステムの作成および使用するための型が含まれている、System.IO.IsolatedStorage名前空間をインポートします。分離ストレージによって、安全なクライアント側のストレージが提供されます。

Imports System.IO.IsolatedStorage

BloodPressureという名前のクラス内に、文字列の型の「記録日」と数値型の「最高血圧、最低血圧」プロパティを定義しておきます。

Public Class BloodPressure
  Property 記録日 As String
  Property 最高血圧 As Integer
  Property 最低血圧 As Integer
End Class

Partial Public Class MainPage
  Inherits PhoneApplicationPage
  ' コンストラクター
  Public Sub New()
    InitializeComponent()
 
    ' ListBox コントロールのデータ コンテキストをサンプル データに設定します
    DataContext = App.ViewModel
  End Sub
 
  ' ViewModel Items のデータを読み込みます

XML要素を表すXElementクラス型のメンバ変数xmldocを宣言します。

Dim xmldoc As XElement

1ページに表示するデータ件数を4件とし、変数numberに格納しておきます。

  Dim number As Integer = 4
 
  Dim no1 As Integer = 0
  Dim no2 As Integer = 0
  Dim Index As Integer = 0
  Dim myIndex As Integer = 0

PivotItemクラス型のメンバ変数myPivotItem2を宣言します。

  Dim myPivotItem2 As PivotItem

新しいPivotItemクラスのインスタンスmyPivoをメンバ変数として宣言します。PivotItemクラスは、ピボットコントロール内の項目のコンテナを表すクラスです。

  Dim myPivo As New PivotItem

表示するデータの開始インデックスを変数startPosに格納します。

  Dim startPos As Integer = 0

表示するデータの最終インデックスを変数endPosに格納します。

  Dim endPos As Integer = 3

BloodPressureクラス型の新しいObservableCollectionのインスタンス、myBloodPressureListをメンバ変数として宣言します。ObservableCollectioクラスは、項目が追加、削除された時、またはリスト全体が更新された時に通知する動的なデータコレクションを表すクラスです。

  Dim myBloodPressureList As New ObservableCollection(Of BloodPressure)
 
  Private Sub MainPage_Loaded(ByVal sender As Object, ByVal e As RoutedEventArgs) Handles Me.Loaded
    If Not App.ViewModel.IsDataLoaded Then
      App.ViewModel.LoadData()
    End If
  End Sub

ページがアクティブになった時の処理

変数storageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。

Path.CombineでRecordBloodPressureというフォルダとBloodPressure.xmlというXMLファイルを連結し、変数filePathに格納しておきます。

RecordBloodPressureフォルダ内にBloodPressure.xmlが存在する場合は、IsolatedStorageFileクラスのOpenFileメソッドでRecordBloodPressureフォルダ内のBloodPressure.xml ファイルを、指定したファイルアクセスを使用して指定したモードで開きます。開いたファイルをStreamReaderで読み込みます。ReadToEndメソッドでファイルの最後まで読み取り、変数readXmldoc変数に格納しておきます。読み込んだXMLテキストをParseメソッドでXElementとして読み込みます。

最初の4件のデータと、PivotItemのHeaderとなる「年,月,日~年,月,日」までを表示する処理である、FirstShowプロシージャを実行します。

RecordBloodPressureフォルダ内にBloodPressure.xmlが存在しない場合は、以下の処理を行います。RecordBloodPressureフォルダが存在しない場合は、CreateDirectoryメソッドで、フォルダを作成します。Visual Basic の埋め込み式を用いて、XML宣言とルート要素であるだけのXMLを作成します。IsolatedStorageFileクラスのCreateFileメソッドで、RecordBloodPressureフォルダ内にBloodPressure.xmlファイルを作成します。

RecordBloodPressureフォルダ内にBloodPressure.xmlが存在する場合は、新しいStreamWriter 生成し、IsolatedStorageFile.OpenFileメソッドで、指定したファイルアクセスを使用して指定したモードでファイルを開き、初期化します。Writeメソッドでルート要素だけのXMLを、ストリームに書き込み、データを入力するDataInPage.xamlに遷移します。血圧のデータが、XMLファイルに1件も記録されていない場合は、ルート要素だけのXMLファイルを作成して、データ入力ページに遷移するということです。

  Protected Overrides Sub OnNavigatedTo(e As System.Windows.Navigation.NavigationEventArgs)
    Dim storage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
    Dim filePath As String = Path.Combine("RecordBloodPressure", "BloodPressure.xml")
    If storage.FileExists(filePath) = True Then
      Using myStream As IsolatedStorageFileStream = storage.OpenFile(filePath, FileMode.Open, FileAccess.Read)
        Using reader As StreamReader = New StreamReader(myStream, System.Text.Encoding.UTF8)
          Dim readXmldoc As String = reader.ReadToEnd
          xmldoc = XElement.Parse(readXmldoc)
        End Using
      End Using
      FirstShow()
    Else
      If storage.DirectoryExists("RecordBloodPressure") = False Then
          storage.CreateDirectory("RecordBloodPressure")
      End If
      If storage.FileExists(filePath) = False Then
        Dim xmldoc As XDocument = <?xml version="1.0" encoding="utf-8"?>
                                  <血圧記録></血圧記録>
        Using stream As IsolatedStorageFileStream = storage.CreateFile(filePath)
        End Using
        If storage.FileExists(filePath) = True Then
          Using writer As StreamWriter = New StreamWriter(storage.OpenFile(filePath, FileMode.Open, FileAccess.Write))
            writer.Flush()
            writer.Write(xmldoc.ToString)
          End Using
          NavigationService.Navigate(New Uri("/DataInPage.xaml", UriKind.Relative))
        End If
      End If
    End If
    MyBase.OnNavigatedTo(e)
  End Sub

最初の4件のデータと、PivotItemのHeaderとなる「年,月,日~年,月,日」までを表示する処理

BloodPressure.xmlの要素を選択するクエリを定義します。要素の子要素を選択するqueryCountクエリを定義します。

Countプロパティで要素の個数を取得し、個数が0か0より小さい場合は、入力ページであるDataInPage.xamlに遷移し、処理を抜けます。

要素の個数が4より小さい場合は、最終インデックスを格納するendPos変数に要素の個数から-1した値を格納します。-1しているのは、Countプロパティは1から始まるのに対し、XMLの要素の個数は0から始まるためです。4より大きい場合は、3を格納します。

変数day1にメンバ変数startPosに該当する要素の子要素の値を格納します。最初の日付が格納されます。変数day2にメンバ変数endPosに該当する要素の子要素の値を格納します。開始日から、最大で3日後の日付が格納されます。

新しいPivotItemのインスタンスmyPivotItemを作成し、Headerプロパティに変数day1とday2の値を指定します。

新しいmCharts.Windows.QuickCharts.SerialChartのインスタンス、mainChartを作成します。

棒グラフを表す、新しいAmCharts.Windows.QuickCharts.ColumnGraphのインスタンス、chartを作成します。同じく、棒グラフを表す、新しいAmCharts.Windows.QuickCharts.ColumnGraphのインスタンス、chart2を作成します。

chartの棒グラフのTitleプロパティに「最高血圧」、Brushプロパティに青色、ValueMemberPathプロパティに「最高血圧」と指定します。

ValueMemberPathプロパティは、このグラフのデータソースの保持値にメンバへのパスを設定するプロパティです。同様にchart2のTitleプロパティに「最低血圧」、Brushプロパティに黄色、ValueMemberPathプロパティに「最低血圧」と指定します。最高血圧のグラフの色が青色、最低血圧のグラフの色が黄色で表示されます。

SerialChartのオブジェクトmainChartのAxisForegroundプロパティに赤色、PlotAreaBackgroundプロパティにグレーを指定します。

AxisForegroundプロパティは、グラフの軸のフォアグラウンドカラーを設定します。デフォルトは黒です。PlotAreaBackgroundプロパティは、プロットエリア(軸の内側の領域)の背景として使用するブラシを設定します。デフォルトは白です。CategoryValueMemberPathプロパティに「記録日」と指定します。CategoryValueMemberPathプロパティは、データソース内のカテゴリの値を保持するプロパティへのパスを設定します。DataSourceプロパティにBloodPressureクラス型のObservableCollectionであるmyBloodPressureListを戻り値とするDataプロパティを指定します。

SerialChartにAddメソッドでchartとchart2を追加します。

を選択していたクエリのTakeメソッドに4を指定して、4個の要素内を反復処理する変数resultに、各要素を格納しながら、myBloodPressureListオブジェクトにAddメソッドで、BloodPressureクラスの、各プロパティに要素の子要素の値を指定して、追加していきます。

PivotItemのオブジェクトmyPivotItemのContentプロパティにmainChartオブジェクトを指定します。Pivot1にAddメソッドで棒グラフの設定のされた、myPivotItemを追加します。

最終の日付となるPivotのHeaderを作成します。

増分が4で、3から要素の個数から-1した分だけ繰り返し処理を行います。PivotItemクラスのインスタンスmyPivotItem2オブジェクトを作成します。要素の個数から-1した個数を4で除算して余りを求め、その余りを変数amariに格納します。

繰り返し変数iの値に4を加算した値が、要素の個数から-1した値と同じか、それより大きい場合はiの値に要素の個数から-1した値を代入し、変数numberに0を代入します。変数lastday1にインデックスから変数amari分を差し引いた位置の要素の子要素の値を格納します。変数lastday2にインデックス位置の要素の子要素の値を格納します。

PivotItemクラスのインスタンスmyPivotItem3オブジェクトを生成します。Headerプロパティにlastday1とlastday2の値を指定します。PivotのAddメソッドでHeaderに日付の設定されたmyPivotItem3を追加します。

繰り返し変数iの値に4を加算した値が、要素の個数から-1した値より小さい場合は、変数dya3にi+1のインデックスに位置する要素の子要素の値を格納します。変数dya4にi+numberのインデックスに位置する要素の子要素の値を格納します。

myPivotItem2オブジェクトのHeaderプロパティにday3とday4の値を指定します。PivotのAddメソッドでHeaderに日付の設定されたmyPivotItem2を追加します。

  Private Sub FirstShow()
    Dim query = From c In xmldoc.Descendants("情報") Select c
    Dim queryCount = From c In xmldoc.Descendants("情報") Select c.Element("記録日")
    If queryCount.Count <= 0 Then
      NavigationService.Navigate(New Uri("/DataInPage.xaml", UriKind.Relative))
      Exit Sub
    End If
 
    If query.Count < 4 Then
        endPos = query.Count - 1
    Else
        endPos = 3
    End If
    Dim day1 = xmldoc.Descendants("情報")(startPos).Element("記録日").Value
    Dim day2 = xmldoc.Descendants("情報")(endPos).Element("記録日").Value
    Dim myPivotItem As New PivotItem
 
    myPivotItem.Header = day1 & "から" & day2
 
    Dim mainChart As New AmCharts.Windows.QuickCharts.SerialChart
    Dim chart As New AmCharts.Windows.QuickCharts.ColumnGraph
    Dim chart2 As New AmCharts.Windows.QuickCharts.ColumnGraph
 
    chart.Title = "最高血圧"
    chart.Brush = New SolidColorBrush(Colors.Blue)
    chart.ValueMemberPath = "最高血圧"
 
    chart2.Title = "最低血圧"
    chart2.Brush = New SolidColorBrush(Colors.Yellow)
    chart2.ValueMemberPath = "最低血圧"
 
    mainChart.AxisForeground = New SolidColorBrush(Colors.Red)
    mainChart.PlotAreaBackground = New SolidColorBrush(Colors.Gray)
 
    mainChart.CategoryValueMemberPath = "記録日"
    mainChart.DataSource = Data
    mainChart.Graphs.Add(chart)
    mainChart.Graphs.Add(chart2)
    myBloodPressureList.Clear()
    For Each result In query.Take(4)
        With myBloodPressureList
            .Add(New BloodPressure With {.記録日 = result.Element("記録日").Value, .最高血圧 = CInt(result.Element("最高血圧").Value), .最低血圧 = CInt(result.Element("最低血圧").Value)})
        End With
        myPivotItem.Content = mainChart
    Next
    Pivot1.Items.Add(myPivotItem)
 
    For i = 3 To query.Count - 1 Step number
        myPivotItem2 = New PivotItem
        Dim amari As Integer = (query.Count - 1) Mod 4
        If i + number >= query.Count - 1 Then
            i = query.Count - 1
            number = 0
            Dim lastday1 = xmldoc.Descendants("情報")((i + number) - amari).Element("記録日").Value
            Dim lastday2 = xmldoc.Descendants("情報")(i + number).Element("記録日").Value
            Dim myPivotItem3 As New PivotItem
            myPivotItem3.Header = lastday1 & "から" & lastday2
            Pivot1.Items.Add(myPivotItem3)
            Exit For
        Else
            Dim day3 = xmldoc.Descendants("情報")(i + 1).Element("記録日").Value
            Dim day4 = xmldoc.Descendants("情報")(i + number).Element("記録日").Value
            myPivotItem2.Header = day3 & "から" & day4
 
        End If
        Pivot1.Items.Add(myPivotItem2)
    Next
  End Sub

AddHandlerステートメントで、動的にピボットアイテムのコンテンツをロードしたり、変更する、イベントハンドラを呼び出す処理

 
  Private Sub DataShow()
      AddHandler Pivot1.LoadingPivotItem, AddressOf Me.pivot1_LoadingPivotItem
  End Sub

ピボットアイテムのコンテンツがロードされた時の処理

Pivotの選択されたインデックスを変数Indexに格納しておきます。Indexの値が0かそれより大きい場合の処理です。要素を選択するクエリを定義します。要素の個数を4で除算し、余りを変数amariに格納しておきます。変数no1にIndexに4を乗算した値を格納します。変数no2に変数no1に3を加算した値を格納します。これらの値は後ほどFor~Nextステートメントで使用されます。データの開始と最後を指定する変数です。余りが0より大きくかつ変数no2の値が要素の個数から-1した値より大きいか等しい場合は、no1にはIndexに4を乗算した値が、no2には要素の個数から-1した値が格納されます。

PivotクラスのmyPivot変数を宣言し、senderオブジェクトが保持しているPivotの情報を取得します。Pivotがフリックされない場合は処理を抜けます。Pivotがフリックされた場合は、以下の処理を行います。

Dispatcher.BeginInvokeで非同期処理を行います。

反復変数iを使って、変数no1~no2の処理を繰り返します。

新しいmCharts.Windows.QuickCharts.SerialChartのインスタンス、mainChartを作成します。

棒グラフを表す、新しいAmCharts.Windows.QuickCharts.ColumnGraphのインスタンス、chartを作成します。同じく、棒グラフを表す、新しいAmCharts.Windows.QuickCharts.ColumnGraphのインスタンス、chart2を作成します。

chartの棒グラフのTitleプロパティに「最高血圧」、Brushプロパティに青色、ValueMemberPathプロパティに「最高血圧」と指定します。ValueMemberPathプロパティは、このグラフのデータソースの保持値にメンバへのパスを設定するプロパティです。同様にchart2のTitleプロパティに「最低血圧」、Brushプロパティに黄色、ValueMemberPathプロパティに「最低血圧」と指定します。最高血圧のグラフの色が青色、最低血圧のグラフの色が黄色で表示されます。

SerialChartのオブジェクトmainChartのAxisForegroundプロパティに赤色、PlotAreaBackgroundプロパティにグレーを指定します。

AxisForegroundプロパティは、グラフの軸のフォアグラウンドカラーを設定します。デフォルトは黒です。PlotAreaBackgroundプロパティは、プロットエリア(軸の内側の領域)の背景として使用するブラシを設定します。デフォルトは白です。CategoryValueMemberPathプロパティに「記録日」と指定します。CategoryValueMemberPathプロパティは、データソース内のカテゴリの値を保持するプロパティへのパスを設定します。

DataSourceプロパティにBloodPressureクラス型のObservableCollectionであるmyBloodPressureListを戻り値とするDataプロパティを指定します。

SerialChartにAddメソッドでchartとchart2を追加します。

myBloodPressureListオブジェクトにAddメソッドで、BloodPressureクラスの、各プロパティに変数iに該当する要素の子要素の、各値を指定して、追加していきます。最高血圧と最低血圧の値はCInt関数で数値に変換しています。

PivotItemのインスタンスのContentプロパティにmainChartオブジェクトを指定します。

 
  Private Sub pivot1_LoadingPivotItem(sender As Object, e As PivotItemEventArgs)
    Index = Pivot1.SelectedIndex
    If Index >= 0 Then
      Dim query = From c In xmldoc.Descendants("情報") Select c
      Dim amari As Integer = (query.Count) Mod 4
      no1 = Index * 4
      no2 = no1 + 3
      If amari > 0 AndAlso no2 >= query.Count - 1 Then
 
        no1 = Index * 4
        no2 = query.Count - 1
 
      End If
    End If
 
    Dim myPivot As Pivot = DirectCast(sender, Pivot)
    If e.Item Is myPivot.Items(Index) = False Then Exit Sub
    If e.Item Is myPivot.Items(Index) = True Then
        Dispatcher.BeginInvoke(Sub()
            For i As Integer = no1 To no2
                Dim mainChart As New AmCharts.Windows.QuickCharts.SerialChart
                Dim chart As New AmCharts.Windows.QuickCharts.ColumnGraph
                Dim chart2 As New AmCharts.Windows.QuickCharts.ColumnGraph
 
                  chart.Title = "最高血圧"
                  chart.Brush = New SolidColorBrush(Colors.Blue)
                  chart.ValueMemberPath = "最高血圧"
 
                  chart2.Title = "最低血圧"
                  chart2.Brush = New SolidColorBrush(Colors.Yellow)
                  chart2.ValueMemberPath = "最低血圧"
                  mainChart.AxisForeground = New SolidColorBrush(Colors.Red)
                  mainChart.PlotAreaBackground = New SolidColorBrush(Colors.Gray)
                  mainChart.CategoryValueMemberPath = "記録日"
 
                  mainChart.Graphs.Add(chart)
                  mainChart.Graphs.Add(chart2)
                  mainChart.DataSource = Data
                  
            With myBloodPressureList
  
              .Add(New BloodPressure With {.記録日 = xmldoc.Descendants("情報")(i).Element("記録日").Value, .最高血圧 = CInt(xmldoc.Descendants("情報")(i).Element("最高血圧").Value), .最低血圧 = CInt(xmldoc.Descendants("情報")(i).Element("最低血圧").Value)})
            End With
                          e.Item.Content = mainChart
                Next
                  End Sub)
    End If
  End Sub

BloodPressureクラス型のObservalCollectionであるDataというプロパティを定義します。myBloodPressureListを戻り値とします。

  Public ReadOnly Property Data() As ObservableCollection(Of BloodPressure)
    Get
      Return myBloodPressureList
    End Get
  End Property

Pivotの現在選択されている項目が変更された場合に発生する処理

RemoveHandlerステートメントで、pivot1_LoadingPivotItemイベントハンドラを削除します。myBloodPressureListオブジェクトをクリアして、動的にピボットアイテムのコンテンツをロードするイベントハンドラを実行しているDataShowプロシージャを実行します。イベントハンドラの削除とmyBloodPressureListオブジェクトのクリアを指定しておかないと、フリックした場合にデータが重複して表示されますので、注意してください。

 
  Private Sub Pivot1_SelectionChanged(sender As Object, e As System.Windows.Controls.SelectionChangedEventArgs) Handles Pivot1.SelectionChanged
    RemoveHandler Pivot1.LoadingPivotItem, AddressOf Me.pivot1_LoadingPivotItem
    myBloodPressureList.Clear()
    DataShow()
  End Sub

アプリケーションバーメニューの「データ入力」をタップした場合の処理

DataInPage.xamlに遷移します。

  Private Sub DataRecordPage(sender As Object, e As EventArgs)
    NavigationService.Navigate(New Uri("/DataInPage.xaml", UriKind.Relative))
  End Sub

アプリケーションバーメニューの「最後のデータに移動」をタップした場合の処理

入力されているデータから要素を選択するクエリを定義します。

要素の個数から-1した値から、Math.Floorを使って、最も小さい整数を求め変数myIndexに格納します。Pivotの選択されたインデックスにmyIndexの値を指定します。これで、一番最後のページに移動します。

  Private Sub LastDataRecordPage(sender As Object, e As EventArgs)
    Dim query = From c In xmldoc.Descendants("情報") Select c
    myIndex = CInt(System.Math.Floor((query.Count - 1) / 4))
    Pivot1.SelectedIndex = myIndex
  End Sub
End Class
  • 「Chartコントロールを使ってWindows Phoneに棒グラフを表示する」サンプルプログラム

四国のSOHO。薬師寺国安(VBプログラマ)と、薬師寺聖(デザイナ、エンジニア)によるコラボレーション・ユニット。1997年6月、Dynamic HTMLとDirectAnimationの普及を目的として結成。共同開発やユニット名義での執筆活動を行う。XMLおよび.NETに関する著書や連載多数。最新刊は「Silverlight実践プログラミング」両名とも、Microsoft MVP for Development Platforms - Client App Dev (Oct 2003-Sep 2012)。http://www.PROJECTKySS.NET/

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています