コンテンツにスキップ

plotly.jsのVue用パッケージを公開しています

昔懐かしのメキシカンハットをPlotlyで表示しています。マウスでぐるぐる動きます。

PythonRのデータ可視化で知られているPlotlyですが、 そのJavaScriptの部分(plotly.js)をVueアプリから使いやすいようにVueコンポーネント化しました。PythonやR無しでPlotlyのチャートを表示できます。AstroのSSGページにも対応しました。

PlotlyのVueコンポーネント化の背景

Section titled “PlotlyのVueコンポーネント化の背景”

PlotlyJupyterLab(以前のJupyter Notebook)Dashを使ってデータを可視化するPython(あるいはR)のライブラリです。ブラウザ上ではplotly.jsがグラフを表示します。

Plotlyのデータ可視化機能を備えた本格的なWebアプリケーションを作るために、フロントエンドのフレームワーク用にコンポーネントがあると便利です。React用は公式からnpmパッケージが出ているのでそれを使えばよいですが、Vue用ではTypeScriptに対応しているものが無さそうでしたので、自作してnpmに公開しました。

PlotlyのVueコンポーネントの使い方

Section titled “PlotlyのVueコンポーネントの使い方”

ターミナル上で、練習用のVueプロジェクトを作って使い方を説明します。

Terminal window
cd <作業フォルダー>
npm create vite@latest -- plotly-practice -t vue-ts
ターミナル画面の詳細

npm@yamakox/vue3-plotly パッケージをインストールします。

Terminal window
cd plotly-practice
npm install --save @yamakox/vue3-plotly plotly.js-dist-min

TypeScriptでは、Plotlyの型定義やVueコンポーネントの型ヘルパーモジュールもインストールしておきます。

Terminal window
npm install --save-dev @types/plotly.js-dist-min
npm install --save-dev vue-component-type-helpers

Visual Studio CodeやCursorなどのコードエディタでplotly-practiceを開きます。 ファイル一覧からApp.vueを選択し、以下の内容で置き換えてください。

App.vue
<script setup lang="ts">
import Plot from '@yamakox/vue3-plotly'
import Plotly from 'plotly.js-dist-min'
const x = Array.from({ length: 21 }, (_, i) => 2 * Math.PI * i / 20)
const data: Plotly.Data[] = [
{ x: x, y: x.map(x => Math.sin(x)), type: 'scatter', mode: 'lines+markers' }
]
const layout: Partial<Plotly.Layout> = {
title: { text: '波形の表示', font: { size: 20 } },
width: 600, height: 400, showlegend: false,
}
</script>
<template>
<h1>Plotlyの練習</h1>
<plot ref="plot1" :data="data" :layout="layout" />
</template>

ターミナルでnpm run devを実行してブラウザでhttp://localhost:5173を開くと、Plotlyの練習画面が表示されます。

Plotlyの練習画面

ブラウザは開いたままの状態でApp.vue波形の追加ボタンを追加します。

App.vue
<script setup lang="ts">
import Plot from '@yamakox/vue3-plotly'
import Plotly from 'plotly.js-dist-min'
import { useTemplateRef } from 'vue'
import type { ComponentExposed } from 'vue-component-type-helpers'
const plot1 = useTemplateRef<ComponentExposed<typeof Plot>|null>('plot1')
const x = Array.from({ length: 21 }, (_, i) => 2 * Math.PI * i / 20)
const x = Array.from({ length: 101 }, (_, i) => 2 * Math.PI * i / 100)
const data: Plotly.Data[] = [
{ x: x, y: x.map(x => Math.sin(x)), type: 'scatter', mode: 'lines+markers' }
{ x: x, y: x.map(x => Math.sin(x)), type: 'scatter', mode: 'lines' }
]
let n = 1
const layout: Partial<Plotly.Layout> = {
title: { text: '波形の表示', font: { size: 20 } },
width: 600, height: 400, showlegend: false,
}
function addTrace() {
n += 1
plot1.value?.addTraces([
{ x: x, y: x.map(x => Math.sin(n * x)), type: 'scatter', mode: 'lines' }
])
}
</script>
<template>
<h1>Plotlyの練習</h1>
<plot ref="plot1" :data="data" :layout="layout" />
<button @click="addTrace">波形の追加</button>
</template>

Viteの高速なHMR(Hot Module Replacement)のおかげで、実行した状態のままで動作を変更できるのはとても助かります。

App.vueを上書き保存すると、ブラウザに波形の追加ボタンが表示されます。2回クリックすると波形が3つ表示できていれば正常に動作しています。

「波形の追加」ボタンの詳細

ソースコードの入力中には、Plotコンポーネントのメソッドの型を確認することができます。

AstroのSSG/SSRページでの使い方(client:onlyが必要)

Section titled “AstroのSSG/SSRページでの使い方(client:onlyが必要)”

本記事の先頭に表示しているメキシカンハットはPlotコンポーネントで実装しています。

本記事はAstro Starlightで作成していますが、アイランドアーキテクチャによってVueコンポーネントをクライアントアイランドで表示しています。

Astroはビルド時にコンポーネントをHTMLとCSSのみへレンダリングするため、 クライアントサイドのJavaScriptを使うPlotlyでは client: ディレクティブを指定することが必須です。

Plotコンポーネントの場合、client:loadではAstroのビルド時にエラーが発生するため、 client:only=“vue” を指定して、サーバー側でのレンダリングを行わないようにします。

メキシカンハットのサンプルコード
mexican-hat.mdx
---
title: "タイトル"
description: "説明"
---
import Plot from '@yamakox/vue3-plotly'
export function getData() {
const size = 1
const zscale = 0.8
const data = [
{ x: [], y: [], z: [], type: 'scatter3d', mode: 'markers', marker: {size: size, color: 'rgb( 0, 0, 255)'} },
{ x: [], y: [], z: [], type: 'scatter3d', mode: 'markers', marker: {size: size, color: 'rgb(255, 0, 0)'} },
{ x: [], y: [], z: [], type: 'scatter3d', mode: 'markers', marker: {size: size, color: 'rgb(255, 0, 255)'} },
{ x: [], y: [], z: [], type: 'scatter3d', mode: 'markers', marker: {size: size, color: 'rgb( 0, 255, 0)'} },
{ x: [], y: [], z: [], type: 'scatter3d', mode: 'markers', marker: {size: size, color: 'rgb( 0, 255, 255)'} },
{ x: [], y: [], z: [], type: 'scatter3d', mode: 'markers', marker: {size: size, color: 'rgb(255, 255, 0)'} },
{ x: [], y: [], z: [], type: 'scatter3d', mode: 'markers', marker: {size: size, color: 'rgb(255, 255, 255)'} },
]
const range = (start, stop, step) => Array.from(
{ length: Math.ceil((stop - start) / step) },
(_, i) => start + i * step,
)
range(-180, 180, 6).forEach((x) => {
range(-180, 180, 6).forEach((y) => {
let r = Math.PI * Math.sqrt(x * x + y * y) / 180
let z = 100 * Math.cos(r) - 30 * Math.cos(3 * r)
let zz = Math.trunc((z + 100) * 0.035) + 1
let color = 0
if ([1, 3, 5, 7].includes(zz)) color |= 1
if ([2, 3].includes(zz) || zz >= 6) color |= 2
if (zz >= 4) color |= 4
if (color > 0) {
let trace = data[color - 1]
trace.x.push(x)
trace.y.push(y)
trace.z.push(z * zscale)
}
})
})
return data
}
export const layout = {
width: 400, height: 300, showlegend: false,
margin: { t: 0, r: 0, b: 0, l: 0 },
paper_bgcolor: 'black',
hovermode: false,
scene: {
xaxis: {visible: false},
yaxis: {visible: false},
zaxis: {visible: false},
camera: {
eye: {x: .9, y: 1.3, z: .4},
center: {x: 0, y: 0.1, z: -0.2},
up: {x: .2, y: 0, z: 1},
},
dragmode: 'orbit',
},
}
<Plot client:only="vue" data={getData()} layout={layout} />
global.css(一部抜粋)
.plot-container.plotly * {
/*
* @layer starlight.contentの以下の定義が
* Plotlyのボタンやチャートのツールチップなどに適用されてしまうため、これを無効化する。
*
* .sl-markdown-content
* :not(a, strong, em, del, span, input, code, br)
* + :not(a, strong, em, del, span, input, code, br, :where(.not-content *)) {
* margin-top: var(--sl-content-gap-y);
* }
*/
margin-top: 0;
}

当初はclient:onlyを指定しても、ビルド時にimport Plot from '@yamakox/vue3-plotly'の中でplotly.jsのクライアント側JavaScriptが呼び出されてしまってエラーが起きていたのですが、ブラウザがコンポーネントをマウントしたタイミングでplotly.jsをインポートするように修正したところ、正常にビルドできるようになり、ブラウザでグラフを表示できるようになりました✨