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]);
}
}

