Plotly.Blazorのサンプル
.NET 8のリリースから半年近く経って、Plotly.Blazorのサンプルが増えていた。
Plotly.Blazorのサンプルが増えていたのでプロットしてみていた。とりあえず.NET6でプロットしたい場合にはサンプルのままで問題ない。
だが、縦横比を1:1にするときはどのようにするかが難しかった。とりあえずnugetパッケージをPlotly.Blazor(4.3.0)をインストール。
.NET 8になって、明示的にApp.razorのRoutesに@rendermodeを指定する必要が出ているようだ。以下Blazor serverで実装していく。
それからplotly.jsを追加していく。
<Routes @rendermode=RenderMode.InteractiveServer /> <script src="_content/Plotly.Blazor/plotly-latest.min.js" type="text/javascript"></script> <script src="_content/Plotly.Blazor/plotly-interop.js" type="text/javascript"></script> <script src="_framework/blazor.web.js"></script>
_Imports.razorに以下を追加する。
@using Plotly.Blazor @using Plotly.Blazor.Traces
例えば、Home.razorに追加してみる。
@page "/" @inject IJSRuntime JS <PageTitle>Home</PageTitle> <PlotlyChart @bind-Config="config" @bind-Layout="layout" @bind-Data="data" @ref="chart" style="height: 500px;"/> @code { PlotlyChart? chart; Config config = new Config(); Layout layout = new Layout(); // Using of the interface IList is important for the event callback! IList<ITrace> data = new List<ITrace> { new Scatter { Name = "Sample circle small", Mode = Plotly.Blazor.Traces.ScatterLib.ModeFlag.Lines | Plotly.Blazor.Traces.ScatterLib.ModeFlag.Markers, X = new List<object>(), Y = new List<object>() }, new Scatter { Name = "Sample circle large", Mode = Plotly.Blazor.Traces.ScatterLib.ModeFlag.Lines, X = new List<object>(), Y = new List<object>(), Line = new Plotly.Blazor.Traces.ScatterLib.Line() { Width = 1.85M, Dash = "3 12 5", }, }, new Scatter { Name = "Sample circle", Mode = Plotly.Blazor.Traces.ScatterLib.ModeFlag.Lines, X = new List<object>(), Y = new List<object>(), Line = new Plotly.Blazor.Traces.ScatterLib.Line() { Width = 0.5M, Dash = "dashdot", }, }, }; private void AddData(int count = 360, double radius = 100D, Scatter? scatter = null) { Scatter refToScatter = (Scatter)data[0]; if (scatter != null) refToScatter = scatter; double interval = 360D / (count - 1); for (int i = 0; i < count; i++) { double theta = ((double)i) * interval / 180D * Math.PI; double r = radius; refToScatter.X.Add(r * Math.Cos(theta)); refToScatter.Y.Add(r * Math.Sin(theta)); } } protected override void OnInitialized() { IList<Plotly.Blazor.LayoutLib.YAxis> ys = new List<Plotly.Blazor.LayoutLib.YAxis>(); ys.Add(new Plotly.Blazor.LayoutLib.YAxis() { ScaleAnchor = "x", }); layout.YAxis = ys; layout.AutoSize = true; AddData(count: 31, radius: 100D, scatter: (Scatter)data[0]); AddData(count: 98, radius: 60D, scatter: (Scatter)data[1]); AddData(count: 67, radius: 80D, scatter: (Scatter)data[2]); } }
注意する点として、線幅がDoubleではなくDecimalで定義されている。とりあえず、PlotlyChartのstyleにheightを指定しているが、リサイズしても追従してこない。うまい方法がないかを探しているが、例えば、App.razorの
<script src="_framework/blazor.web.js"></script>
の後くらいに以下のようなコードを挿入して
<script> window.viewportChangeCallback = (dotnetObject) => { window.addEventListener('load', () => { dotnetObject.invokeMethodAsync('OnResize', window.innerWidth, window.innerHeight); }); window.addEventListener('resize', () => { dotnetObject.invokeMethodAsync('OnResize', window.innerWidth, window.innerHeight); }); } </script>
Home.razorのコード部分に以下を追加してやる。引数はとりあえず使用していないが、何かあった時のために渡している。
[JSInvokable] public void OnResize(int width, int height) { chart?.Update(); } protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { await JS.InvokeVoidAsync("window.viewportChangeCallback", DotNetObjectReference.Create(this)); } }
ここまで書いておいてなんだが、このOnResizeの対応は別に書かなくてもConfigのプロパティにResponsiveというのがあるので、これで多くの場合には事足りるようだ。
Config config = new Config() { Responsive = true };
3次元プロットも以下のようになる。
@page "/Chart3D" <PlotlyChart @bind-Config="config" @bind-Layout="layout" @bind-Data="data" @ref="chart" @bind-Style="Style" /> @code { PlotlyChart? chart; Config config = new Config() { Responsive = true }; Layout layout = new Layout(); [Parameter] public string? Style { get; set; } = "height: 900px;"; IList<ITrace> data = new List<ITrace> { new Scatter3D { Name = "Sample helix 3d", Mode = Plotly.Blazor.Traces.Scatter3DLib.ModeFlag.Lines | Plotly.Blazor.Traces.Scatter3DLib.ModeFlag.Markers, X = new List<object>(), Y = new List<object>(), Z = new List<object>(), Marker = new Plotly.Blazor.Traces.Scatter3DLib.Marker() { Size = 1M, } }, }; private void AddData( int count = 360, double radius = 100D, double lead = 10D, Scatter3D? scatter = null) { Scatter3D refToScatter = (Scatter3D)data[0]; if (scatter != null) refToScatter = scatter; double interval = 360D / (count - 1); for (int i = 0; i < count; i++) { double theta = ((double)i) * interval / 180D * Math.PI; double r = radius; refToScatter.X.Add(r * Math.Cos(theta)); refToScatter.Y.Add(r * Math.Sin(theta)); refToScatter.Z.Add(interval / 360 * lead * i); } } protected override void OnInitialized() { IList<Plotly.Blazor.LayoutLib.Scene> scene = new List<Plotly.Blazor.LayoutLib.Scene>(); scene.Add(new Plotly.Blazor.LayoutLib.Scene() { AspectMode = Plotly.Blazor.LayoutLib.SceneLib.AspectModeEnum.Data, AspectRatio = new Plotly.Blazor.LayoutLib.SceneLib.AspectRatio() { X = 1M, Y = 1M, Z = 1M }, } ); layout.Scene = scene; AddData(count: 31, radius: 100D, lead: 400D, scatter: (Scatter3D)data[0]); } }