過去1カ月の気温をスプレッドシートに表示するWebページを作る
本記事でSpreadJSに表示する題材は、過去1カ月の最高気温・最低気温です。これらをサーバー側のWeb APIで提供し、それをSpreadJSで表示するように実装していきます(図4)。
このサンプルでは最高気温・最低気温そのもの以外に、SpreadJSのスプレッドシート機能を利用して、クライアント側で最大値・最小値・平均値を集計して表示します。なお、同じ題材をAngularで実現したサンプルを過去記事で説明しているので参考にしてください。
表示するデータは、気象庁の「過去の気象データ・ダウンロード」から、2022年8月の東京のデータをCSVファイルでダウンロードして利用します。ダウンロードしたCSVファイルは事前に内容の整形とヘッダー付加を行い、図5の状態にしておきます。
以下では、このデータをWebページ上に表示する実装を、サーバー側(ASP.NET Coreで実装するWeb API)とクライアント側(React、SpreadJS)とに分けて説明していきます。
サーバー側の実装
サーバー側には、図5のCSVファイルを読みだして、戻り値として返却するWeb APIを実装します。このサーバー側の実装内容は、クライアント側にAngularを利用した過去記事のサンプルと同じものです。
まず、サーバー側でCSVファイルを読み出せるように、NuGet パッケージ マネージャーで「CsvHelper」パッケージを検索してインストールします。
次に、Web APIのクラスが配置されるControllers配下にC#のファイルを生成して、Web API処理をリスト1の通り実装します。
// 気温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)で内容をレコードのリストにして返却します。ASP.NET Coreの処理により、リスト内容がJSON文字列に変換されてAPIの戻り値として返却されます。
リスト1(5)のcsv.GetRecordsメソッドで型設定されるTemperatureDataは、日時、最高気温、最低気温のプロパティを含むクラスです(リスト2)。csv.GetRecordsメソッドは、クラスのプロパティ名(date, maxTemperature, minTemperature)を識別して、同じ名前のヘッダーを持つCSVの列からデータを各プロパティに設定します。
public class TemperatureData { // 日時 public DateTime date { get; set; } // 最高気温 public double maxTemperature { get; set; } // 最低気温 public double minTemperature { get; set; } }
クライアント側の実装
次に、クライアント側の実装を行っていきます。ReactプロジェクトにSpreadJSを追加する方法が公式ドキュメントで案内されているので、その手順に従って進めます。最初にClientAppフォルダーでリスト3のコマンドを実行して、プロジェクトにSpreadJSを追加します。
npm install @grapecity/spread-sheets-react # ...React用モジュール npm install @grapecity/spread-sheets-resources-ja # ...日本語リソース
Webページ表示時、最初に実行されるindex.jsに、リスト4の通り記述します。(1)でSpreadJSのCSSとJavaScriptをインポートします。(2)はカルチャの設定で、日本語(ja-jp)を設定します。(3)はライセンスキーの設定です。ライセンスキーを設定しない場合は、トライアル版である旨の透かしがスプレッドシートに表示されます。
// SpreadJSをインポート ...(1) import'@grapecity/spread-sheets/styles/gc.spread.sheets.excel2016darkGray.css'; import * as GC from '@grapecity/spread-sheets'; // カルチャ設定 ...(2) GC.Spread.Common.CultureManager.culture('ja-jp'); // ライセンスキー設定 ...(3) GC.Spread.Sheets.LicenseKey = '<ライセンスキー>';
次に、スプレッドシートを表示するSpreadSampleコンポーネントに対応するファイルSpreadSample.jsを追加して、リスト5の通り実装します。
// SpreadJSをインポート ...(1) import { SpreadSheets, Worksheet, Column } from '@grapecity/spread-sheets-react'; import '@grapecity/spread-sheets-resources-ja/dist/gc.spread.sheets.resources.ja.min.js'; export class SpreadSample extends Component { // コンストラクター ...(2) constructor(props) { super(props); // スプレッドシートの表示サイズ ...(3) this.hostStyle = { width: '100%', height: '500px' }; // 状態を保持 ...(4) this.state = { temperatures: [] }; // workbookInitメソッドをthisにバインド ...(5) this.workbookInit = this.workbookInit.bind(this); } // 表示内容 ...(6) render() { return ( // SpreadSheets:スプレッドシート全体を表す ...(7) <SpreadSheets hostStyle={this.hostStyle} workbookInitialized={this.workbookInit}> {/*WorkSheet:ワークシートを表す ...(8)*/} <Worksheet name='気温' dataSource={this.state.temperatures} autoGenerateColumns={false}> {/*Column:ワークシートの1列を表す ...(9)*/} <Column headerText='日時' dataField='date' width={200}></Column> <Column headerText='最高気温' dataField='maxTemperature' width={100} formatter='#.0'></Column> <Column headerText='最低気温' dataField='minTemperature' width={100} formatter='#.0'></Column> </Worksheet> </SpreadSheets> ); } // ワークブック初期化後の処理 ...(10) async workbookInit(spread) { // Web APIからデータ取得 ...(11) const response = await fetch('/api/Temperature'); const data = await response.json(); // データをstateに保持 ...(12) this.setState({ temperatures: data }); // ワークシートを装飾 ...(13) this.decorateWorksheet(spread); } (略:後述) }
まず(1)で、SpreadJSと日本語リソースのパッケージをインポートします。コンストラクター(2)では、(3)でスプレッドシートの表示サイズ、(4)ではコンポーネントの状態(state)を記述します。このコンポーネントでは、スプレッドシートに表示する温度データ配列temperaturesを状態に持ちます(初期値は空配列です)。(5)は後述するworkbookInitメソッド内において、thisでコンポーネントを参照するための記述です。
表示内容を記述するrenderメソッド(6)では、まずスプレッドシート全体を表す<SpreadSheets>コンポーネント(7)を記述します。スプレッドシートの表示スタイルを表すhostStyleには(3)のサイズ指定を反映します。また、workbookInitializedはスプレッドシート初期化時のイベントハンドラーメソッドで、後ほど実装するworkbookInitメソッドを呼ぶようにします。
<SpreadSheets>内に、1つのワークシートに対応した<Worksheet>コンポーネント(8)を記述します。nameはワークシートの名前、dataSourceは(4)で状態(state)に定義したtemperatures変数を指定します。autoGenerateColumnsにはfalseを設定して、データに合わせて自動で列が生成されないようにします。
<Worksheet>内には(9)の通り、データを表示する列に対応した<Column>コンポーネントを、列の数だけ記述します。dataFieldはデータの属性名、headerTextはヘッダー部の文言、widthは列の幅指定、formatterはデータの表示フォーマットです。formatterの"#.0"は、数字を小数第一位まで表示することを表します。
ワークブック初期化後に実行されるworkbookInitメソッド(10)では、(11)のfetchメソッドでWeb API(/api/Temperature)からデータを取得して、取得したデータを(12)のsetStateメソッドで状態(state)のtemperaturesに格納します。(13)については後述します。
ここまでで追加したSpreadSampleコンポーネントを表示できるようにするため、パス(ルート)を設定するAppRoute.jsに、リスト6の内容を追加して、SpreadSampleコンポーネントに「/spread-sample」のパスを割り当てます。
const AppRoutes = [ (略) { path: '/spread-sample', element: <SpreadSample /> } ];
リスト6で追加したパスを、画面上部のページ切替リンクに対応するNavMenuコンポーネントのrenderメソッド内に、リスト7の通り設定します。
<NavItem> <NavLink tag={Link} className="text-dark" to="/spread-sample">SpreadJS</NavLink> </NavItem>
スプレッドシートのヘッダー行に集計行を追加
次に、スプレッドシートのヘッダー行に集計行を追加します。リスト5(13)のdecorateWorksheetメソッドを、リスト8の通り実装します。
decorateWorksheet(spread) { // ワークシートを取得 ...(1) const sheet = 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); (以下略:最小値と平均値の行をヘッダーに設定) }
引数に渡されるSpreadJSのオブジェクトspeadのgetActiveSheetメソッド(1)で、処理対象のワークシートを取得します。(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」は、すべての列への指定を意味します。
以上の実装により、図4のスプレッドシートをWebブラウザーに表示できます。Web APIから取得できるのは最高気温と最低気温の生データだけですが、SpreadJSのスプレッドシート機能により、最大値・最小値・平均値をクライアント側の処理により表示できます。
まとめ
本記事では、グレープシティのJavaScriptスプレッドシート部品SpreadJSを、クライアント側にReact、サーバー側にASP.NET Coreの環境で利用する方法を説明しました。SpreadJSが提供するスプレッドシート、ワークシート、列のReactコンポーネントにより、SpreadJSの機能をReactの一部のように透過的に利用できます。また、SpreadJSのExcelライクなスプレッドシート機能を生かして、取得したデータをクライアント側で集計して表示できます。