こんにちは、放浪軍師です。最近ブログ更新頻度が高いね!やったぜ!
Blazor WebAssembly で グラフを表示したい
さて、現在目標としている燻製監視アプリですが、温度の推移をグラフで表示したいなぁと思っていまして、フロントで採用予定の Blazor WebAssembly でグラフを書く方法について調べてみましたので紹介したいと思います。
Blazorise と ChartJs.Blazor
Blazor WebAssembly でのグラフ表示は現在、Blazorise.Charts と ChartJs.Blazor の2種類が主流なようですが、このうち ChartJs.Blazor はちょっとごたごたしていてメンテナーが不在の状態が続いているようです。そのため、Blazorise.Charts を使用したいと思います。
Blazorise
Blazorise は Blazor 上に構築されたコンポーネントライブラリです。GitHub での活動も活発に行われている様子で安心ですね。
今回使うグラフは Blazorise のうちの一部の機能という事になります。公式ページを見ながら早速やってみましょう。
環境
Microsoft Visual Studio Community 2019 Version 16.8.2
Blazorise.Charts 0.9.3.6
Microsoft.AspNetCore.Components.WebAssembly 5.0.5
GitHub
作成したサンプルです。
注意点
Blazorise の最新版は現在 .NET5 でしか動きません。プロジェクトを作成する際には .NET5 を必ず選ぶようにしましょう。(1時間ハマる)
NugetPackge をインストール
プロジェクトを作成したら以下をインストールします。
wwwroot/index.html
<!-- Blazorise.Charts -->
以下の部分を追加します。
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <title>BlazoriseChartsTry</title> <base href="/" /> <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" /> <link href="css/app.css" rel="stylesheet" /> <link href="BlazoriseChartsTry.styles.css" rel="stylesheet" /> </head> <body> <div id="app">Loading...</div> <div id="blazor-error-ui"> An unhandled error has occurred. <a href="" class="reload">Reload</a> <a class="dismiss">🗙</a> </div> <script src="_framework/blazor.webassembly.js"></script> <!-- Blazorise.Charts --> <script src="https://cdn.jsdelivr.net/npm/chart.js@2.8.0"></script> <script src="_content/Blazorise.Charts/blazorise.charts.js"></script> </body> </html>
Program.cs
//Blazorise.Charts
以下の部分を追加します。
using Blazorise; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.Extensions.DependencyInjection; using System; using System.Net.Http; using System.Threading.Tasks; namespace BlazoriseChartsTry { public class Program { public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add<App>("#app"); builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); //Blazorise.Charts builder.Services.AddBlazorise(options => { options.ChangeTextOnKeyPress = true; }).AddEmptyProviders(); await builder.Build().RunAsync(); } } }
※ちなみに公式ページではこの部分は古い形で紹介されているので注意が必要です。(1時間ハマる) blazorise.com
_Imports.razor
@using Blazorise.Charts
を追加します。これにより他の .razor で using しなくても使えるようになるようですね。
Views/LineChartPage.razor
基本的に公式そのままなのですが一点。buttonに関しては公式の<Button Clicked="@(async () => await HandleRedraw())">Redraw</Button>
は使えなかったので変更しています。理由は謎です。
@page "/LineChartPage" <h3>LineChartPage</h3> <button @onclick="@(async () => await HandleRedraw())">Redraw</button> <LineChart @ref="lineChart" TItem="double" /> @code{ LineChart<double> lineChart; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { await HandleRedraw(); } } async Task HandleRedraw() { await lineChart.Clear(); await lineChart.AddLabelsDatasetsAndUpdate(Labels, GetLineChartDataset()); } LineChartDataset<double> GetLineChartDataset() { return new LineChartDataset<double> { Label = "# of randoms", Data = RandomizeData(), BackgroundColor = backgroundColors, BorderColor = borderColors, Fill = true, PointRadius = 2, BorderDash = new List<int> { } }; } string[] Labels = { "Red", "Blue", "Yellow", "Green", "Purple", "Orange" }; List<string> backgroundColors = new List<string> { ChartColor.FromRgba(255, 99, 132, 0.2f), ChartColor.FromRgba(54, 162, 235, 0.2f), ChartColor.FromRgba(255, 206, 86, 0.2f), ChartColor.FromRgba(75, 192, 192, 0.2f), ChartColor.FromRgba(153, 102, 255, 0.2f), ChartColor.FromRgba(255, 159, 64, 0.2f) }; List<string> borderColors = new List<string> { ChartColor.FromRgba(255, 99, 132, 1f), ChartColor.FromRgba(54, 162, 235, 1f), ChartColor.FromRgba(255, 206, 86, 1f), ChartColor.FromRgba(75, 192, 192, 1f), ChartColor.FromRgba(153, 102, 255, 1f), ChartColor.FromRgba(255, 159, 64, 1f) }; List<double> RandomizeData() { var r = new Random(DateTime.Now.Millisecond); return new List<double> { r.Next(3, 50) * r.NextDouble(), r.Next(3, 50) * r.NextDouble(), r.Next(3, 50) * r.NextDouble(), r.Next(3, 50) * r.NextDouble(), r.Next(3, 50) * r.NextDouble(), r.Next(3, 50) * r.NextDouble() }; } }
これによりこんな感じでグラフが書けます。
うにょうにょ動いて結構いい感じなんですが、これX軸が等間隔なんですよね。
Views/ScatterChartPage.razor
X,Yを指定するグラフは以下のように書きます。
@page "/ScatterChartPage" <h3>ScatterChartPage</h3> <p>https://github.com/stsrki/Blazorise/discussions/2127</p> <button @onclick="@(async () => await HandleRedraw(chart,GetChartDataset))">Redraw</button> <Chart @ref="chart" TItem="Plot" OptionsObject="@options" /> @code { Chart<Plot> chart; public struct Plot { public Plot(double x, double y) { X = x; Y = y; } public double X { get; set; } public double Y { get; set; } } object options = new { Scales = new { XAxes = new[] { new { Type = "linear", Position = "bottom" } } } }; string[] Labels = { "Red", "Blue", "Yellow", "Green", "Purple", "Orange" }; List<string> backgroundColors = new List<string> { ChartColor.FromRgba(255, 99, 132, 0.2f), ChartColor.FromRgba(54, 162, 235, 0.2f), ChartColor.FromRgba(255, 206, 86, 0.2f), ChartColor.FromRgba(75, 192, 192, 0.2f), ChartColor.FromRgba(153, 102, 255, 0.2f), ChartColor.FromRgba(255, 159, 64, 0.2f) }; List<string> borderColors = new List<string> { ChartColor.FromRgba(255, 99, 132, 1f), ChartColor.FromRgba(54, 162, 235, 1f), ChartColor.FromRgba(255, 206, 86, 1f), ChartColor.FromRgba(75, 192, 192, 1f), ChartColor.FromRgba(153, 102, 255, 1f), ChartColor.FromRgba(255, 159, 64, 1f) }; bool isAlreadyInitialised; Random random = new Random(DateTime.Now.Millisecond); protected override async Task OnAfterRenderAsync(bool firstRender) { if (!isAlreadyInitialised) { isAlreadyInitialised = true; await Task.WhenAll( HandleRedraw(chart, GetChartDataset)); } } async Task HandleRedraw<TDataSet, TItem, TOptions, TModel>(Blazorise.Charts.BaseChart<TDataSet, TItem, TOptions, TModel> chart, Func<TDataSet> getDataSet) where TDataSet : ChartDataset<TItem> where TOptions : ChartOptions where TModel : ChartModel { await chart.Clear(); await chart.AddLabelsDatasetsAndUpdate(Labels, getDataSet()); } async Task SetDataAndUpdate<TDataSet, TItem, TOptions, TModel>(Blazorise.Charts.BaseChart<TDataSet, TItem, TOptions, TModel> chart, Func<List<TItem>> items) where TDataSet : ChartDataset<TItem> where TOptions : ChartOptions where TModel : ChartModel { await chart.SetData(0, items()); await chart.Update(); } LineChartDataset<Plot> GetChartDataset() { return new LineChartDataset<Plot> { Type = "scatter", Label = "Scatter Dataset", Data = RandomizeData(), BackgroundColor = backgroundColors, BorderColor = borderColors, ShowLine = false }; } List<Plot> RandomizeData() { return new List<Plot> { new Plot(random.Next( 3, 50 ) * random.Next(), random.Next( 3, 50 ) * random.Next()), new Plot(random.Next( 3, 50 ) * random.Next(), random.Next( 3, 50 ) * random.Next()), new Plot(random.Next( 3, 50 ) * random.Next(), random.Next( 3, 50 ) * random.Next()), new Plot(random.Next( 3, 50 ) * random.Next(), random.Next( 3, 50 ) * random.Next()), new Plot(random.Next( 3, 50 ) * random.Next(), random.Next( 3, 50 ) * random.Next()), new Plot(random.Next( 3, 50 ) * random.Next(), random.Next( 3, 50 ) * random.Next()), }; } }
XYプロット用の構造体Plot
を作成してTItemで指定し、OptionsObject で描画条件を入れます。ちなみにこの方法は公式には載っていなくて、以下 discussions で紹介されています。
(実は今回わからない事があったので、つたない英語ですが質問していたりします。メンテナーさんがすぐに答えてくれました。Thank you so much!!!)
こんな感じのグラフになります。よいではないかー!!!
まとめ
結構簡単にいい感じのグラフが描けるようです。しかも線グラフ以外にも円グラフやらなんやらも行ける様子。ガンガン使っていきましょう!