Shoeisha Technology Media

CodeZine(コードジン)

特集ページ一覧

「Xuni」のXamarin.Forms向けコントロールセットを使ってデータを可視化してみた

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2017/06/01 14:00

 Xuni(ズーニー)は、Xamarin.Forms向けのさまざまなコントロールを提供するライブラリです。Xamarin.Formsは標準で用意されるコントロールが少ないため、Xamarin.AndroidやXamarin.iOSを利用して独自のコントロールを作成するシーンが多いですが、Xuniが提供するコントロールを使うと、独自のコントロールを作成することなく、豊富な機能を持ったAndroid・iOS両対応アプリケーションを開発することができます。今回は、気象庁が提供する気象データを、Xuniに含まれるコントロールの一つ「FlexGrid」を使って、さまざまな方法で可視化してみました。

必要な環境

Windowsの場合

  • Windows 7以上
  • Visual Studio 2015またはVisual Studio 2017
  • Xamarin

macOSの場合

  • OS X Mountain Lion以上
  • Xamarin StudioまたはVisual Studio for Mac

 筆者の環境の都合上、本記事ではmacOS SierraとXamarin Studio 6.3を使用して解説します。

Xuniとは

 「Xuni(ズーニー)」は、モバイルアプリケーション向けのコントロールセットで、チャート、ゲージ、グリッド、カレンダーなどの高機能なコントロールが含まれます。

 今回紹介するのは、Xamarin.Forms向けのライブラリですが、Xuniは他にも次のようなプラットフォームに対応しています。

Xamarin、Xamarin.Formsとは

 今更説明は不要と思いますが、Xamarinとは、Android・iOS・Windowsなど向けのアプリケーションを共通の言語(C#やF#)で開発できるクロスプラットフォーム開発ツールです。

 AndroidのAPIをC#から呼び出せるようにした「Xamarin.Android」、同じくiOSのAPIをC#から呼び出せるようにした「Xamarin.iOS」を中心に、.NETの技術を利用してクロスプラットフォームアプリケーションを開発するためのライブラリや各種ツールキットを提供しています。

 Xamarin.Formsは、XAMLやデータバインディングなどの技術を活用して、画面もクロスプラットフォームで共通化できるフレームワークです。

 プラットフォーム固有の機能をフルに活用したアプリの開発には向きませんが、Android・iOSに加え Universal Windows Platform(UWP)やmacOSにも対応が始まっており、Xamarinの中でも最も注目度の高い要素になっています。

Xuniのインストール

 XuniはNuGetパッケージマネージャからインストールしますが、NuGetパッケージ公開情報に説明があるように、日本語版のパッケージは http://nuget.c1.grapecity.com/nuget/ から提供されており、Visual StudioまたはXamarin StudioのNuGetパッケージマネージャのソースに、このURLを追加する必要があります。

 一方、グレープシティのサイトからXuniのトライアル版をダウンロードすることができます(要ユーザー登録)。

 このトライアル版には、全てのプラットフォーム向けのサンプルプログラムが含まれているので、まずはこちらをダウンロードしてインストールすることをお勧めします。

 macOSでは、インストールすると「書類」のディレクトリの中に「XuniJp」というディレクトリが作成され、各プラットフォーム向けのサンプルプログラムが格納されます。Xamarin.Forms向けはXamarin/Fomrsの中です。

図1 macOSにインストールされたXuniのサンプルプログラム群
図1 macOSにインストールされたXuniのサンプルプログラム群

 Xamarin/NugetPackagesディレクトリには、このサンプルが動作可能なNuGetパッケージ群が格納されています。オンラインのNuGetパッケージで正常に動作しないようであれば、このディレクトリをNuGetのソースに設定するとよいでしょう。図2はXamarin StudioでこのディレクトリをNuGetソースに設定した画面です。

図2 Xamarin StudioでのNuGetソース設定画面
図2 Xamarin StudioでのNuGetソース設定画面

FlexGridのサンプルを動かしてみよう

 それでは実際に、Xuniのインストーラに含まれるFlexGridのサンプルをAndroidとiOSそれぞれで動かしてみましょう。

 /Users/<ユーザー名>/Documents/XuniJp/Xamarin/Samples/Forms/FlexGrid101/FlexGrid101.slnをXamarin Studioで開きます。

Androidでサンプルを動かしてみる

 ソリューション内の「FlexGrid101.Android」をスタートアッププロジェクトにしてAndroid端末またはエミュレータで実行します。

 サンプルが起動すると、メニューの画面になり(図3の左画像)、更に一番上の「Getting Started」をタップするとFlexGridの標準的な使用例が表示されます。

 メニューの他の項目では、FlexGridのさまざまな機能が確認できますので、一通り動かしてみましょう。

図3 FlexGridサンプルをAndroidでの実行画面
図3 FlexGridサンプルをAndroidでの実行画面

iOSでサンプルを動かしてみる

 ソリューション内の「FlexGrid101.iOS」をスタートアッププロジェクトにしてiPhone端末またはiOSシミュレータで実行します。

 iPhone端末またはiOSシミュレータの言語設定を「日本語」にすると、図4のように、アプリケーションも日本語表示されます(以降の画像は言語設定「英語」を使用しているため、Android/iOSともに英語<"FlexGridの基本機能"→"Getting Started">となります)。

図4 FlexGridサンプルをiPhoneでの実行画面
図4 FlexGridサンプルをiPhoneでの実行画面

 プロジェクトのビルド時に「'Icon-xxxx.png' not found on disk (should be at '…/FlexGrid101.iOS/Resources/Icon-xxxx.png') (FlexGrid101.iOS)」というエラーが出る場合は、FlexGrid101.iOS/ResourcesディレクトリにあるIcon-xxxx.pngファイル群をFlexGrid101.iOS/Resources/Images.xcassets/AppIcons.appiconsetディレクトリにコピーします。

 iOSシミュレータでの実行時、「"FlexGrid 101" Needs to Be Updated」と表示される場合は、FlexGrid101.iOSプロジェクトの設定→iOS Buildで、「サポートされるアーキテクチャ」を「x84_64」に変更します(図5参照)。

図5 iOSプロジェクトの設定画面
図5 iOSプロジェクトの設定画面

 iOSでもAndroidと同じ機能が使用できます。Xamarin.Forms向けのコンポーネントなので、AndroidやiOS固有のコードは一切必要なく、共通なコードのみでAndroid/iOSで動作可能なグリッドを使用できます。

気象庁の「最新の気象データ」をグリッドに表示してみよう(1)

 ではここからは、実際のデータを使用して、FlexGridのさまざまな機能を紹介します。

 使用するデータは、気象庁が提供している「最新の気象データ」のCSVです。

 このデータは、気温・降水量・風速などがCSV形式でリアルタイムに提供されており、利用規約に基いて使用できます。

最新の最高気温CSVをグリッドに表示してみよう

 最新の最高気温のCSVは、次のURLで取得できます。

 http://www.data.jma.go.jp/obd/stats/data/mdrr/tem_rct/alltable/mxtemsadext00_rct.csv

 これで得られるCSVデータを、FlexGridに表示してみましょう。

 FlexGrid101.slnのソリューションを改造して、気象庁のCSVデータを表示できるようにします。

CSVデータ用のクラスを作る

 まず、CSVデータの1行を示すクラスを、FlexGrid101.XamarinプロジェクトにHighestTemperature.csという名称で作成します。

 元のCSVは項目数が多いので、サンプルで使用する項目だけをプロパティとして用意します。

 作成するHighestTemperatureは次のようになります。

// FlexGrid101.Xamarin プロジェクトに作成する
public class HighestTemperature
{
    public string PlaceNo { get; set; } // 観測所番号
    public string Pref { get; set; } // 都道府県
    public string Point { get; set; } // 地点
    public int NowYear { get; set; } // 現在時刻(年)
    public int NowMonth { get; set; } // 現在時刻(月)
    public int NowDay { get; set; } // 現在時刻(日)
    public int NowHour { get; set; } // 現在時刻(時)
    public int NowMinute { get; set; } // 現在時刻(分)
    public float TodayHighestTemperature { get; set; } // 今日の最高気温(℃)
    public float DiffToAverageYear{ get; set; } // 平年差(℃)
}

CSV読み込みライブラリを追加する

 次にCSVデータの読み込みは、CsvHelperというオープンソースライブラリを使用して行います。

 そのために、FlexGrid101.XamarinFlexGrid101.AndroidFlexGrid101.iOSそれぞれのプロジェクトのメニュー→プロジェクト→NuGetパッケージの追加で、「csvhelper」で検索し、表示される「CsvHelper」をプロジェクトに追加します。

図6 FlexGrid101.XamarinプロジェクトへのCsvHelperライブラリの追加
図6 FlexGrid101.XamarinプロジェクトへのCsvHelperライブラリの追加

CSVのURLからデータを取得する

 まず通信を行う権限をAndroidプロジェクトに付与します。

 FlexGrid101.Androidプロジェクトの設定 → Android Applicationで「必要なアクセス許可」の項目群から「Internet」にチェックを入れます。

 また、FlexGrid101.iOSプロジェクトの設定 → iOS Buildで、コードセットの「cjk」にチェックを入れます。これはShift-JISのCSVファイルを取り扱うのに必要な設定です。

図7 FlexGrid101.iOSプロジェクトのコードセットの設定
図7 FlexGrid101.iOSプロジェクトのコードセットの設定

 次に、CSVファイルをダウンロードして読み込む処理をHighestTemperatureクラスに次のように実装します。

public static async Task<IList<HighestTemperature>> Read()
{
    var client = new HttpClient();
    var records = new List<HighestTemperature>();
    var encoding = Encoding.GetEncoding("Shift-jis");

    using (var stream = await client.GetStreamAsync(
        @"http://www.data.jma.go.jp/obd/stats/data/mdrr/tem_rct/alltable/mxtemsadext00_rct.csv"))
    using (var csvReader = new CsvReader(new StreamReader(stream, encoding)))
    {
        while (csvReader.Read())
        {
            var record = new HighestTemperature();

            record.PlaceNo = csvReader.GetField<string>(0); // 観測所番号
            record.Pref = csvReader.GetField<string>(1); // 都道府県
            record.Point = csvReader.GetField<string>(2);
            record.NowYear = csvReader.GetField<int>(4); // 現在時刻(年)
            record.NowMonth = csvReader.GetField<int>(5); // 現在時刻(月)
            record.NowDay = csvReader.GetField<int>(6); // 現在時刻(日)
            record.NowHour = csvReader.GetField<int>(7); // 現在時刻(時)
            record.NowMinute = csvReader.GetField<int>(8); // 現在時刻(分)
            record.TodayHighestTemperature = csvReader.GetField<float>(9); // 今日の最高気温(℃)

            var diff = csvReader.GetField<float?>(14); // // 平年差(℃)
            record.DiffToAverageYear = diff ?? 0f;
            records.Add(record);
        }
    }
    return records;
}

 このコードは、HttpClientを使用して気象データCSVをダウンロードし、CsvHelperを使用してデータをパースする処理です。元CSVは項目数が多いので本サンプルに必要な項目のみを取り出しています。尚、取り出しは項目の並び順に依存しているため、データの仕様が変わるとこのコードは動作しなくなる可能性があることに注意してください。

FlexGridにデータを表示する

 上記までで取り出したデータをFlexGridに設定して表示させます。

 Getting Startedの画面を改造して使用します。

 GettingStarted.xaml.csの既存のコードを全て削除し、次のように記述します。

public partial class GettingStarted : ContentPage
{
    private XuniCollectionView<HighestTemperature> _collView;

    public GettingStarted()
    {
        InitializeComponent();
        this.Title = AppResources.GettingStartedTitle;

        InitializeData();
    }

    private async void InitializeData()
    {
        var data = await HighestTemperature.Read();
        _collView = new XuniCollectionView<HighestTemperature>(data);
        grid.ItemsSource = _collView;
    }
}

 InitializeDataメソッドで、CSVからデータを取得し、グリッドのItemsSourceに設定することでデータを表示させています。

 これをAndroid,iOSそれぞれで実行すると、次のようになります。

図8 Androidでの実行結果
図8 Androidでの実行結果
図9 iOSでの実行結果
図9 iOSでの実行結果

気象庁の「最新の気象データ」をグリッドに表示してみよう(2)

データを気温の低い順に並び替えてみよう

 次に、データの並び替えをしてみましょう。FlexGrid標準の機能でヘッダー列をタップすれば並び替え順を昇順/降順で切り替えることができますが、コードからも行えます。同時に先頭列の固定と、列幅の自動調整もしてみましょう。

 GettingStarted.xaml.csInitializeDataに次のように追記します。

async void InitializeData()
{
    var data = await HighestTemperature.Read();
    _collView = new XuniCollectionView<HighestTemperature>(data);
    grid.ItemsSource = _collView;

    // 追記ここから
    grid.FrozenColumns = 2; // 先頭列の固定
    await _collView.SortAsync(x => x.TodayHighestTemperature,
        SortDirection.Ascending); // 最高気温の昇順にソート
    grid.AutoSizeColumns(0, grid.Columns.Count - 1); // 列幅自動調整
}

 先頭列の固定はXAMLにFrozenColumns="2"と記述することもできます。

 このコードを実行すると、次の図のようになります。

図10 データの並び替えと先頭列固定、列幅自動調整の実行結果(iOS)
図10 データの並び替えと先頭列固定、列幅自動調整の実行結果(iOS)

平年より気温が高い地域のセルを着色してみよう

 次は、セルの背景色を値に応じて変えてみましょう。

 先ほどと同じくGettingStarted.xaml.csInitializeDataに次のように追記します。

public partial class GettingStarted : ContentPage
{
    <省略>

    async void InitializeData()
    {
        var data = await HighestTemperature.Read();
        _collView = new XuniCollectionView<HighestTemperature>(data);
        grid.ItemsSource = _collView;

        grid.FrozenColumns = 2;
        await _collView.SortAsync(x => x.TodayHighestTemperature, SortDirection.Ascending);
        grid.AutoSizeColumns(0, grid.Columns.Count - 1);

// 追記ここから
        grid.CellFactory = new CustomCellFactory();
    }
}

class CustomCellFactory : GridCellFactory
{
    public override GridCellView CreateCell(GridCellType cellType, GridCellRange range)
    {
        var cell = base.CreateCell(cellType, range);
        if (cellType == GridCellType.Cell && range.Column == 8)
        {
            var cellValue = Grid[range.Row, range.Column] as float?;
            if (cellValue.HasValue)
            {
                cell.BackgroundColor = cellValue <= 0.0 ? Color.Aqua : Color.Pink;
            }
        }
        return cell;
    }

    public override void BindCellContent(GridCellType cellType, GridCellRange range, View cellContent)
    {
        var label = cellContent as Label;
        if (label != null && cellType == GridCellType.Cell && range.Column == 8)
        {
            var cellValue = Grid[range.Row, range.Column] as float?;
            if (cellValue.HasValue)
            {
                label.BackgroundColor = cellValue <= 0.0 ? Color.Aqua : Color.Pink;
            }
        }
    }    
}

 セルをカスタマイズする方法のひとつはGridCellFactoryを拡張することです。

 ここではGridCellFactoryを拡張してCustomCellFactoryクラスを作成し、列インデックスが8(平年差)の時に、その値が0以下だったらセル背景色を水色に、0より大きかったらピンク色に設定します。

 そしてCustomCellFactoryクラスをFlexGridのCellFactoryプロパティに設定します。

 このコードを実行すると次の図のようになります。

図11 セルの着色結果(Android)
図11 セルの着色結果(Android)

気象庁の「最新の気象データ」をグリッドに表示してみよう(3)

地域でグループ化してみよう

 更に、行の値を使ってグループ化を行い、グリッドを展開できるようにしてみましょう。

 InitializeDataを次のように修正します。

async void InitializeData()
{
    var data = await HighestTemperature.Read();
    _collView = new XuniCollectionView<HighestTemperature>(data);
    grid.ItemsSource = _collView;
    grid.FrozenColumns = 2;
    await _collView.SortAsync(x => x.TodayHighestTemperature, SortDirection.Ascending);
    grid.AutoSizeColumns(0, grid.Columns.Count - 1);

    // 追記ここから
    await _collView.GroupAsync(x => x.Pref);
    //grid.CellFactory = new CustomCellFactory();
}

 _collView.GroupAsync(x => x.Pref)でデータの地域を対象にグループ化を行っています(グループ化とCellFactoryは併用できない?ようなので、ここではコメントアウトしています)。

 このコードを実行すると、次のようになります。

図12 グループ化の結果(iOS)
図12 グループ化の結果(iOS)

気温の高低を画像で表してみよう

 最後に、気温の値を数値ではなく画像で表してみましょう。

 FlexGridには様々なGauge(ゲージ)が用意されているので、これを使用すると用意にグラフィカルな表現が可能になります。

 ゲージの設定はXAMLで行ってみましょう。

 まず、InitializeDataを次のように修正します。

async void InitializeData()
{
    var data = await HighestTemperature.Read();
    _collView = new XuniCollectionView<HighestTemperature>(data);
    grid.ItemsSource = _collView;

    // 以下をコメントアウト
    //grid.FrozenColumns = 2;
    //await _collView.SortAsync(x => x.TodayHighestTemperature, SortDirection.Ascending);
    //grid.AutoSizeColumns(0, grid.Columns.Count - 1);
    //await _collView.GroupAsync(x => x.Pref);
    //grid.CellFactory = new CustomCellFactory();
}

 grid.ItemsSource = _collView;より下の処理をコメントアウトします。

 次に、GettingStarted.xamlを次のように修正します。

<?xml version="1.0" encoding="utf-8"?>
<ContentPage
  xmlns="http://xamarin.com/schemas/2014/forms"
  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  x:Class="FlexGrid101.GettingStarted"
  x:Name="page"
  xmlns:gauge="clr-namespace:Xuni.Forms.Gauge;assembly=Xuni.Forms.Gauge"  ←追記
  xmlns:xuni="clr-namespace:Xuni.Forms.FlexGrid;assembly=Xuni.Forms.FlexGrid">

    <中略>

    <!-- FlexGridの定義を以下のように修正 -->
    <xuni:FlexGrid x:Name="grid" Grid.Row="1" AutoGenerateColumns="false">
      <xuni:FlexGrid.Columns>
        <xuni:GridColumn Binding="Pref" Header="都道府県" Width="*" />
        <xuni:GridColumn Binding="Point" Header="地点" Width="*" />
        <xuni:GridColumn Binding="TodayHighestTemperature" Header="最高気温" Width="*">
          <xuni:GridColumn.CellTemplate>
            <DataTemplate>
              <gauge:XuniLinearGauge IsAnimated="False" Value="{Binding TodayHighestTemperature}"
                Min="0" Max="30" ShowText="Value" ShowRanges="False" WidthRequest="50" HeightRequest="50">
                <gauge:XuniLinearGauge.Ranges>
                  <gauge:GaugeRange Min="0" Max="10" Color="Aqua" />
                  <gauge:GaugeRange Min="10" Max="20" Color="Green" />
                  <gauge:GaugeRange Min="20" Max="30" Color="Fuchsia" />
                </gauge:XuniLinearGauge.Ranges>
              </gauge:XuniLinearGauge>
            </DataTemplate>
          </xuni:GridColumn.CellTemplate>
        </xuni:GridColumn>
      </xuni:FlexGrid.Columns>
    </xuni:FlexGrid>
  </Grid>
</ContentPage>

 まず、<xuni:FlexGrid.Columns><xuni:GridColumn>を3つ定義します。これとAutoGenerateColumns="false"を設定することによって、グリッドには定義した3列しか表示されないようになります。

 そして、3つ目の<xuni:GridColumn><gauge:XuniLinearGauge>を使用して、セル内にゲージを表示します。Min="0" Max="30"がゲージの最小・最大値を示し、<gauge:XuniLinearGauge.Ranges>の設定で、「最高気温が0〜10度は水色、10〜20度は緑、20〜30度はピンク」と定義しています。

 このコードを実行すると、次のようになります。

図13 ゲージを使った表現(Android)
図13 ゲージを使った表現(Android)

まとめ

 このように、FlexGridを使用すると、業務アプリケーションでよく見られる表形式の画面を容易に作成することができます。

 Xamarin.Formsに対応しているおかげで、共通のコードでAndroid・iOSで動作可能ですし、XAMLやデータバインディングもフルに活用することができます。

 本記事で作成したサンプルプログラムは こちらからダウンロードでき、Visual Studio for Mac/Visual Studio 2015、2017で動作します。

 Xuniは、FlexGridコントロール以外にも、カレンダー・チャート・オートコンプリート対応入力コントロール群などが含まれています。この記事でインストールしたサンプルプログラムには、これらのサンプルも含まれていますので、是非試してみてください。

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • 奥山 裕紳(オクヤマ ヒロノブ)

     Microsoft MVP for Visual Studio and Development Technologies  ネットやコミュニティでは amay077(あめい) という名前で活動しています。  XamarinやC#ネタをブログに投稿したり、勉強会で発表したりしています。  愛知...

All contents copyright © 2005-2019 Shoeisha Co., Ltd. All rights reserved. ver.1.5