Day 27:Blazor x Chart.js

Chart.js是一款open source的图表制作library,支援多种图表,包括Pie chart、Bar chart、line chart...等等,也有动画效果,可以制作出精美的报表。

今天我们要建立一个Blazor WebAssembly专案,这个专案会串接一个新冠肺炎的Api,并使用Chart.js呈现近60天的确诊人数折线图,那我们就开始吧!

接下来主要分3部分进行:

  1. 串接Api的Service
  2. 设定Chart.js和程序码
  3. 在index.razor取得资料和图表并显示在页面上

串接Api的Service

Api部份,我们可以用https://covid19api.com/ 这个网站的Api来取得资料,一般资料是free的,如果有更多资料的需求再订阅付费,我们可以到他的文件看一下有哪些Api可用。

首先建立Blazor WebAssembly专案,取消勾选Asp.net core hosted
https://ithelp.ithome.com.tw/upload/images/20201011/20130058S3osauA09r.jpg

建立完专案後,新增Service和Model资料夹,Model中加入json要对应的类别,Service资料夹加入COVID19Service。
COVID19Service有两个方法:
GetCountriesAsync:取得index.razor的国家下拉选单资料
GetCountrySummaryAsync:传入CountryCode,取得某国的确诊、死亡等等人数

public class COVID19Service : ICOVID19Service
    {
        private readonly HttpClient httpClient;

        private string baseurl { get; set; } = "https://api.covid19api.com";

        public COVID19Service(HttpClient httpClient)
        {
            this.httpClient = httpClient;
        }

        /// <summary>
        /// 取得国家清单
        /// </summary>
        /// <returns></returns>
        public async Task<List<CountryModel>> GetCountriesAsync()
        {
            List<CountryModel> countries = new List<CountryModel>();
            var response = await httpClient.GetAsync($"{baseurl}/countries");

            if (response.IsSuccessStatusCode)
            {
                var contentString = await response.Content.ReadAsStringAsync();
                countries = JsonConvert.DeserializeObject<List<CountryModel>>(contentString);
            }

            return countries;
        }

        /// <summary>
        /// 取得确诊、死亡等等人数资料
        /// </summary>
        /// <param name="countryCode"></param>
        /// <returns></returns>
        public async Task<List<CountrySummary>> GetCountrySummaryAsync(string countryCode)
        {
            List<CountrySummary> countrySummary = new List<CountrySummary>();

            var respones = await httpClient.GetAsync($"{baseurl}/country/{countryCode}");
            if (respones.IsSuccessStatusCode)
            {
                var contentString = await respones.Content.ReadAsStringAsync();
                countrySummary = JsonConvert.DeserializeObject<List<CountrySummary>>(contentString);
            }

            return countrySummary.TakeLast(60).ToList();
        }
    }

在Program.cs注册COVID19Service

builder.Services.AddScoped<ICOVID19Service, COVID19Service>();

设定Chart.js和绘制图表程序码

使用cdn引入chart.js。可从https://cdnjs.com/libraries/Chart.js 取得cdn连结
再来把cdn script拉进wwwroot/index.html

<!DOCTYPE html>
<html>
//略....
<body>
    <app>Loading...</app>

    <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>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
    <script src="script/MyChart.js"></script>
</body>
</html>

另外还拉了一个MyChart.js进去,这是我们自己建立的js档,待会会在里面写产生图表的程序码

接下来到index.razor,这边增加一个canvas标签,产生的图表会放到这个canvas内

<canvas id="myChart" width="800" height="300"></canvas>

在MyChart.js建立图表资料

function DrawLineChart(summaryListlist) {
    //建立图表资料
    var datachart = {
        labels: [],
        datasets: [{
            label: '确诊人数',
            data: [],
            borderColor: '#FF5376',
            fill: false
        }]
    };
    
    //将日期与人数push到labels和data阵列中
    for (var i = 0; i < summaryListlist.length; i++) {
        datachart.labels.push(summaryListlist[i].shortDate);
        datachart.datasets[0].data.push(summaryListlist[i].confirmed);
    }
    
    //绘制图表
    var ctx = document.getElementById("myChart").getContext("2d");
    var chart = new Chart(ctx, {
        type: 'line', //图表类型
        data: datachart,
    })
}

chart.js的官方文件还有许多设定可以套用,让我们的图表可以更美观更友善

在index.razor取得资料和图表并显示在页面上

在画面上会准备一个国家的下拉选单给user选取要看哪一国的资料,所以下拉选单部分会透过上述建立的COVID19Service取的国家资料後在塞到Select标签中

@page "/"
@inject ICOVID19Service COVID19Service

<h2>COVID19 Chart</h2>
<hr />

@if (countries != null)
{
    <div class="form-group">
        <label for="countrySelect" class="font-weight-bold">国家</label>
        <select @onchange="ChangeCountryAsync" class="form-control" id="countrySelect">
            <option>--请选择--</option>

            @foreach (var item in countries)
            {
                <option value="@item.Slug" selected="@(item.Slug == DefaultCountry)">@item.Country</option>
            }
        </select>
    </div>

    //显示chart的canvas标签
    <canvas id="myChart" width="800" height="300"></canvas>
}
else
{
    //还在读取资料时,显示loading gif
    <img src="https://media.giphy.com/media/3oEjI6SIIHBdRxXI40/giphy.gif" />
}

@code{
public string DefaultCountry { get; set; } = "taiwan";
public List<CountryModel> countries;

protected override async Task OnInitializedAsync()
    {
        countries = await COVID19Service.GetCountriesAsync();   
    }   
}

在一进入index.razor,我希望显示的是台湾的确诊资料,因此在OnInitializedAsync方法取得下拉选单然後用一个预设为taiwan的DefaultCountry属性,让下拉选单预设选到taiwan

传入countryCode取确诊资料并透过IJSRuntime物件绘制图表

@inject IJSRuntime js

@code{
private async Task BuildChartAsync(string selectedValue)
    {        
        summaryList = await COVID19Service.GetCountrySummaryAsync(selectedValue);
        summaryList.ForEach(x => x.ShortDate = $"{x.Date.Month}/{x.Date.Day}");

        await js.InvokeVoidAsync("DrawLineChart", summaryList);
    }
}

在OnInitializedAsync也加入BuildChartAsync

protected override async Task OnInitializedAsync()
    {
        countries = await COVID19Service.GetCountriesAsync();
        BuildChartAsync(DefaultCountry);
    }

选取国家时,处理onchange事件的ChangeCountryAsync方法中,使用ChangeEventArgs参数取得选择的value,在传入刚刚建立的BuildChartAsync方法

public async Task ChangeCountryAsync(ChangeEventArgs e)
    {
        if (e.Value != null)
        {
            await BuildChartAsync(e.Value.ToString());
        }
    }

最後完成的结果

程序码可参考:https://github.com/CircleLin/COVID19_Chart


<<:  Day 28 铁人赛章节回顾

>>:  [Day. 26] Codeigniter 页面

Day03:浅谈 Git 和 GitHub

Git Git 是一个开源的分布式版本控制系统, 允许我们跟踪档案异动, 最初目的是为更好地管理 L...

分散式资料库:一致性协定

基於讯息传递的一致性协定,只能保证资料的「最终一致性」,都无法处理「拜占庭将军问题(Byzantin...

【Day23】SwiftUI Essentials - SwiftUI 基础

在这边先声明,这篇是是WWDC大会的部分翻译,我找到的也是别人翻译以及笔记的 文章,只是我觉得很适合...

Tcl语言和你 SAY HELLO!!

第七天 各位点进来的朋友,你们好阿 小的不才只能做这个系列的文章,但还是希望分享给点进来的朋友,知道...

[Day 21] 资料标注 (2/2) — 各种标注方法

子曰:『工欲善其事,必先利其器。 前言 昨天提到依照 Ground truth 改变的速度会让不同...