4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Vega解説 〜Reactアプリ上でVegaグラフを表示し、グラフのイベントを捕まえる〜

Last updated at Posted at 2023-12-02

Vega 解説記事です。今まではVegaでのグラフの書き方を説明していましたが、今回はWebアプリに組み込んでみます。

完成イメージは以下です。

  1. データを渡してバーチャートを描画
  2. バーチャートのバーをクリックすると、設定されている値を取得しテキストボックスに設定
  3. テキストボックスに入った値を更新し、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のデータを取得し、categoryamount の状態を更新します
    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

上記の実装で以下のように動きます。

chrome-capture-2023-11-30.gif

参考

過去のVega解説記事は以下です。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?