Vega 解説記事です。今まではVegaでのグラフの書き方を説明していましたが、今回はWebアプリに組み込んでみます。
完成イメージは以下です。
- データを渡してバーチャートを描画
- バーチャートのバーをクリックすると、設定されている値を取得しテキストボックスに設定
- テキストボックスに入った値を更新し、Updateボタンをクリックするとバーチャートのグラフを更新
Vega グラフとHTMLフォームのイベントを捕捉し、相互に更新します。
react-vega というライブラリもありますが、今回は素のVega View API を利用して実現します。
barchartSpec.ts
Vega Specを定義し返却します。Specの内容はVega解説〜離散データ〜を流用したものです。
import * as vega from "vega";
export interface BarChartValue {
category: string
amount: number
}
export const barchartSpec = (values: BarChartValue[]): vega.Spec => {
return {
"$schema": "https://vega.github.io/schema/vega/v5.json",
"width": 400,
"height": 200,
"padding": 5,
"data": [
{
"name": "table",
"values": values
}
],
"scales": [
{
"name": "xscale",
"type": "band",
"domain": { "data": "table", "field": "category" },
"range": "width",
"padding": 0.2,
"round": true
},
{
"name": "yscale",
"domain": { "data": "table", "field": "amount" },
"range": "height"
}
],
"axes": [
{ "orient": "bottom", "scale": "xscale" },
{ "orient": "left", "scale": "yscale" }
],
"marks": [
{
"name": "layer_0",
"type": "rect",
"from": { "data": "table" },
"encode": {
"update": {
"x": { "scale": "xscale", "field": "category" },
"width": { "scale": "xscale", "band": 1 },
"y": { "scale": "yscale", "field": "amount" },
"y2": { "scale": "yscale", "value": 0 },
"fill": { "value": "steelblue" }
}
}
}
]
}
}
BarChart.tsx
今回はこのファイルがメインの処理になります。
- specを取得し、View APIを利用して描画します
const view = new vega.View(vega.parse(spec), { renderer: 'canvas', container: '#view', hover: true }) view.runAsync()
-
view
に clickイベントを登録しますview.addEventListener('click', handleVegaClickEvent)
- クリックされたItemのデータを取得し、
category
とamount
の状態を更新しますconst handleVegaClickEvent = (_: vega.ScenegraphEvent, item?: vega.Item | null) => { if (item) { setCategory(item.datum.category) setAmount(item.datum.amount) } }
- ボタンを押した時に、categoryが一致するデータを探し入力された情報(amount)に変更します
const handleClick = () => { if (view) { view.change('table', vega.changeset().modify((datum: any) => { return datum.category === category }, 'amount', amount)).run() } }
全体は以下
import { ChangeEvent, useEffect, useState } from "react"
import * as vega from "vega"
import { BarChartValue, barchartSpec } from "./barchartSpec"
export function BarChart({ values }: { values: BarChartValue[] }) {
const [category, setCategory] = useState<string>()
const [amount, setAmount] = useState<number>(0)
const [view, setView] = useState<vega.View>()
const spec = barchartSpec(values)
useEffect(() => {
const view = new vega.View(vega.parse(spec), {
renderer: 'canvas',
container: '#view',
hover: true
})
view.addEventListener('click', handleVegaClickEvent)
view.runAsync()
setView(view)
}, [])
const handleVegaClickEvent = (_: vega.ScenegraphEvent, item?: vega.Item | null) => {
if (item) {
setCategory(item.datum.category)
setAmount(item.datum.amount)
}
}
const handleChangeEvent = (e: ChangeEvent<HTMLInputElement>) => {
setAmount(parseInt(e.target.value))
}
const handleClick = () => {
if (view) {
view.change('table', vega.changeset().modify((datum: any) => {
return datum.category === category
}, 'amount', amount)).run()
}
}
return (
<>
<div id="view"></div>
<div>
<div>{category}</div>
<input type="number" onChange={handleChangeEvent} value={amount}></input>
<button onClick={handleClick}>Update</button>
</div>
</>
)
}
App.tsx
データを定義し、上で作成したコンポーネントを呼び出しています。
import './App.css'
import { BarChart } from './BarChart'
function App() {
const values = [
{ "category": "A", "amount": 28 },
{ "category": "B", "amount": 55 },
{ "category": "C", "amount": 43 }
]
return (
<>
<BarChart values={values} />
</>
)
}
export default App
上記の実装で以下のように動きます。
参考
過去のVega解説記事は以下です。