過去1カ月の気温を表示するWebページを作る
本記事では、気象庁のWebページから取得した過去1カ月の最高気温・最低気温をサーバーのWeb APIで提供し、それをSpreadJSで表示するように実装していきます(図6)。気温データそのものに加え、クライアント側で気温データの最大値・最小値・平均値を集計して表示します。
過去1カ月の最高気温・最低気温は、気象庁の「過去の気象データ・ダウンロード」から、2022年7月の札幌のデータをCSVファイルでダウンロードします。今回は実装を単純にするため、CSVファイルに対して手動で内容の整形とヘッダー付加を行い、図7の状態でプロジェクトに追加します。
以下では、このデータをWebページ上に表示する実装を、サーバー側(ASP.NET CoreのWeb API)とクライアント側(SpreadJS)とに分けて順に説明していきます。
サーバー側の実装
サーバー側でCSVファイルを読み出せるように、NuGet パッケージ マネージャーで「CsvHelper」パッケージを検索してインストールします。
Controllers配下にC#のファイルを生成して、Web API処理をリスト2の通り実装します。
// 気温Web APIのコントローラー ...(1) [Route("api/[controller]")] [ApiController] public class TemperatureController : ControllerBase { // GET時の処理 ...(2) [HttpGet] public object Get() { // CSVファイルを読み込むReaderを生成 ...(3) using (var reader = new StreamReader("data.csv")) { // CSV内容を読み込むReaderを生成 ...(4) using (var csv = new CsvReader(reader, new CsvConfiguration(CultureInfo.InvariantCulture))) { // CSVファイルからレコードを取得して返却 ...(5) var records = csv.GetRecords<TemperatureData>(); return records.ToList(); } } } }
クラスに付与した(1)の[Route("api/[controller]")]と[ApiController]属性により、「api/Temperature」というパスに対応するWeb APIであることを指定します。Getメソッド(2)に付与した[HttpGet]属性は、HTTP GET時の処理であることを表します。
Getメソッド内では(3)および(4)でCSV内容を読み込むCsvReaderを生成し、(5)でCSV内容をレコードのリストにして返却します。ASP.NET Coreの処理により、リスト内容がJSON文字列に変換されてAPIの戻り値になります。なお(5)で型設定されるTemperatureDataは、日時、最高気温、最低気温のプロパティを含むクラスです。詳細はサンプルコードを参照してください。
クライアント側の実装
ClientAppフォルダーでリスト3を実行して、プロジェクトにSpreadJSを追加します。
npm install @grapecity/spread-sheets # ...SpreadJS本体 npm install @grapecity/spread-sheets-angular # ...Angularモジュール npm install @grapecity/spread-sheets-resources-ja # ...日本語リソース
SpreadJSのCSSが参照されるよう、angular.jsonファイルにリスト4の通り記述します。
"styles": [ "node_modules/bootstrap/dist/css/bootstrap.min.css", // ↓この行を追加 "node_modules/@grapecity/spread-sheets/styles/gc.spread.sheets.excel2016darkGray.css", "src/styles.css" ],
app.module.tsのimports内に、リスト5の通りSpreadJSのモジュール(SpreadSheetsModule)を追加してインポートします。
@NgModule({ imports: [ SpreadSheetsModule, (略) })
次に、スプレッドシートを表示するSpreadSampleコンポーネントを、Angular CLIのngコマンド(リスト6)で追加します。ngコマンドの次の「g」は生成(generate)の意味で、--moduleオプションはコンポーネントを追加するモジュール(ここではappモジュール)を指定します。コマンドによりSpreadSampleコンポーネントのファイルがsrc/app/spread-sampleフォルダーに生成されます。
ng g component SpreadSample --module app
生成されたspread-sampleフォルダー配下のファイルを編集していきます。コンポーネントの表示内容に対応するテンプレート(htmlファイル)には、リスト7の通り記述します。
<gc-spread-sheets [hostStyle]="hostStyle" (workbookInitialized)="workbookInit($event)"> <!--(1)--> <gc-worksheet name="気温" [dataSource]="temperatures" [autoGenerateColumns]="false"> <!--(2)--> <gc-column headerText="日時" dataField="date" width="200"> </gc-column> <!--(3)--> <gc-column headerText="最高気温" dataField="maxTemperature" width="100" formatter="#.0"></gc-column> <gc-column headerText="最低気温" dataField="minTemperature" width="100" formatter="#.0"></gc-column> </gc-worksheet> </gc-spread-sheets>
まずスプレッドシート全体を表す<gc-spread-sheets>コンポーネント(1)を記述します。[hostStyle]には後述する表示サイズのオブジェクトhostStyleを指定します。(workbookInitialized)はスプレッドシート初期化時のイベントハンドラーメソッドで、後ほど実装するworkbookInitメソッドを呼ぶようにします。
<gc-spread-sheets>内に、1つのワークシートに対応した<gc-worksheet>コンポーネント(2)を記述します。name属性はワークシートの名前、dataSourceは後述するデータ変数temperaturesを指定します。[autoGenerateColumns]にはfalseを設定して、データに合わせて自動で列が生成されないようにします。
<gc-worksheet>内には(3)の通り、データを表示する列に対応した<gc-column>コンポーネントを、列の数だけ記述します。dataFieldはデータの属性名、headerTextはヘッダー部の文言、widthは列の幅指定、formatterはデータの表示フォーマットです。formatterの"#.0"は、数字を小数第一位まで表示することを表します。
リスト7に対応するコンポーネントの実装は、リスト8の通りです。
// SpreadJSをインポート ...(1) import '@grapecity/spread-sheets-resources-ja'; import * as GC from '@grapecity/spread-sheets'; // カルチャ設定 ...(2) GC.Spread.Common.CultureManager.culture('ja-jp'); // ライセンスキー設定 ...(3) GC.Spread.Sheets.LicenseKey = '<ライセンスキー>'; @Component({ selector: 'app-spread-sample', templateUrl: './spread-sample.component.html', styleUrls: ['./spread-sample.component.css'] }) export class SpreadSampleComponent { // スプレッドシートの表示サイズ ...(4) hostStyle = { width: '100%', height: '500px' }; // 受信した気温データを格納する変数 ...(5) temperatures: Temperature[] = []; // SpreadJSのスプレッドシートを保持する変数 ...(6) spread: GC.Spread.Sheets.Workbook | undefined; // コンストラクター(依存性注入でHttpClient変数httpを受け取る) ...(7) constructor(private http: HttpClient) { } // ワークブック初期化後の処理 ...(8) workbookInit(args: any) { // スプレッドシートのオブジェクトを取得して保持 ...(9) this.spread = args.spread; // Web APIからデータ取得 ...(10) this.http.get<Temperature[]>('/api/Temperature').subscribe({ // 取得時の処理 next: result => { // データを変数に保持 ...(11) this.temperatures = result; // ワークシートを装飾 ...(12) this.decorateWorksheet(); }, // エラー時の処理 error: (e) => console.error(e) }); } (略) }
(1)で、SpreadJSと日本語リソースのパッケージをインポートします。(2)はスプレッドシートで日本語を使用するカルチャの設定、(3)はライセンスキーの設定です。ライセンスキーを設定しないとトライアル版として動作し、スプレッドシート内にトライアル版である旨の透かしが表示されます。
コンポーネント内には、(4)~(6)のプロパティを設定します。各プロパティの内容は表2の通りです。
No. | 名前 | 意味 |
---|---|---|
(4) | hostStyle | スプレッドシートのスタイル(ここでは幅と高さ) |
(5) | temperatures | スプレッドシートに表示する気温データ |
(6) | spread | Webページで表示するSpreadJSのスプレッドシート |
コンストラクター(7)では、HTTP通信を行うAngularのHttpClientオブジェクトhttpを依存性注入で受け取ります。ワークブック初期化後に実行されるworkbookInit(8)では、まず(9)でイベント引数からスプレッドシートのオブジェクトを取得して保持した後、(10)のhttp.getメソッドでWeb API(/api/Temperature)からデータを取得して、(11)で取得データをtemperatures変数に格納します。(12)については後述します。
追加したSpreadSampleコンポーネントを表示できるようにするため、パス(ルート)を設定するapp.module.tsのRouteModuleに、リスト9の内容を追加します。
RouterModule.forRoot([ (略) { path: 'spread-sample', component: SpreadSampleComponent } ])
リスト9で追加したパスを、nav-menuコンポーネントにリスト10の通り設定します。
<li class="nav-item" [routerLinkActive]="['link-active']"> <a class="nav-link text-dark" [routerLink]="['/spread-sample']" >SpreadJS</a> </li>
スプレッドシートのヘッダー行に集計行を追加
次に、スプレッドシートのヘッダー行に集計行を追加します。リスト8(13)のdecorateWorksheetメソッドを、リスト11の通り実装します。
decorateWorksheet() { // ワークシートを取得 ...(1) const sheet = this.spread!.getActiveSheet(); // ヘッダーが4行になるよう設定 ...(2) sheet.setRowCount(4, GC.Spread.Sheets.SheetArea.colHeader); // 最大値ヘッダー行のスタイルを定義 ...(3) const style1 = new GC.Spread.Sheets.Style() style1.backColor = '#ffe6d5'; style1.foreColor = '#ff0000'; style1.formatter = "#.0"; // 最大値ヘッダー行に文字を指定 ...(4) sheet.setValue(0, 0, '最大値', GC.Spread.Sheets.SheetArea.colHeader); // 最大値ヘッダー行にスタイルを指定 ...(5) sheet.setStyle(0, -1, style1, GC.Spread.Sheets.SheetArea.colHeader); // 最大値ヘッダー行に数式を指定 ...(6) // 2列目:「=MAX(<シート名>!B:B)」、3列目:「=MAX(<シート名>!C:C)」 sheet.setFormula(0, 1, `=MAX(${sheet.name()}!B:B`, GC.Spread.Sheets.SheetArea.colHeader); sheet.setFormula(0, 2, `=MAX(${sheet.name()}!C:C`, GC.Spread.Sheets.SheetArea.colHeader); (以下略:最小値と平均値の行をヘッダーに設定) }
(1)で処理対象のワークシートを取得します。「this.spread!」は、spread変数がnullである可能性を無視してプロパティを取得するTypeScriptの記法です。(2)ではsheet.setRowCountメソッドで、ヘッダー行が4行になるように指定しています。
(3)でGC.Spread.Sheets.StyleクラスのオブジェクトにbackColor(背景色)、foreColor(文字色)、formatter(表示フォーマット)を設定します。
(4)のsetValueメソッドでセルの値を、(5)のsetStyleメソッドでセルのスタイルを、(6)のsetFormulaメソッドでセルの数式を、それぞれヘッダー行に設定します。各メソッドの第1引数は行番号、第2引数は列番号、第3引数は設定値です。第4引数にはGC.Spread.Sheets.SheetArea.colHeaderを設定して、ヘッダー行に対する処理であることを表します。(5)の第2引数(列番号)「-1」は、すべての列への指定を意味します。
まとめ
本記事では、グレープシティのJavaScriptスプレッドシート部品SpreadJSを、クライアント側にAngular、サーバー側にASP.NET Coreの環境で利用する方法を説明しました。SpreadJSのExcelライクなスプレッドシート機能により、Web APIから取得したデータをクライアント側で集計して表示できます。