こんにちは。フロントエンドチームの安井です。今回はLLMと相性のいいReactのChartライブラリを探すために検証を行いました。ReactのChartライブラリといえば数多く種類が存在し、どれを採用するのがいいか迷うところです。下記のサイトはReactのChartライブラリが一覧で整理されており、これだけでも数十個候補になるライブラリがあることがわかります。
https://awesome.cube.dev/?tools=charts&frameworks=react
本記事では数多くあるReactのChartライブラリの中からLLMと相性がいいライブラリを様々な観点から考察することを目的とします。
HighCharts GPTというプロダクトを例に挙げますが、以下のようにLLMから生成した結果をグラフで表示させることを目的とします。
また、HighChartsは非常に優れたChartプロダクトですが、商用利用する際にはライセンスが有料となっているため今回は対象外としています。
https://www.highcharts.com/chat/gpt/
少し検証内容が長くなってしまうので、先に考察結果を紹介します。
オブジェクトベースのデータ構造で、LLMから生成されるデータを直接注入可能なライブラリが相性がいい。
ライブラリ名 | シンプルさ | 拡張性 | LLMとの相性 | データ構造のタイプ |
---|---|---|---|---|
Recharts | ⭐️⭐️⭐️⭐️⭐️ | ⭐️⭐️⭐️⭐️ | ⭐️⭐️⭐️ | オブジェクトベースのデータ構造 |
AG Charts | ⭐️⭐️⭐️⭐️ | ⭐️⭐️ | ⭐️⭐️⭐️⭐️⭐️ | オブジェクトベースのデータ構造 |
Ant Design Charts | ⭐️⭐️⭐️⭐️⭐️ | ⭐️⭐️⭐️ | ⭐️⭐️⭐️⭐️ | オブジェクトベースのデータ構造 |
nivo | ⭐️⭐️ | ⭐️⭐️⭐️⭐️⭐️ | ⭐️⭐️ | オブジェクトベースのデータ構造 |
Unovis | ⭐️⭐️⭐️ | ⭐️⭐️ | ⭐️⭐️⭐️ | オブジェクトベースのデータ構造 |
Chart.js | ⭐️⭐️⭐️ | ⭐️⭐️⭐️ | ⭐️⭐️⭐️⭐️ | 配列ベースのデータ構造 |
apexcharts | ⭐️⭐️⭐️ | ⭐️⭐️⭐️ | ⭐️⭐️⭐️⭐️ | 配列ベースのデータ構造 |
まずは数あるライブラリの中から最終更新日が半年以内のものに絞り込みました。どれだけ魅力的なライブラリでも適切にメンテナンスされていなければ現場で採用するのは難しくなります。その結果以下の7件が該当しました。
*満遍なく検討しましたが、これが全てではありません。
ライブラリ名 | Star数 | 説明 | URL |
---|---|---|---|
Chart.js | 63K | 最もStar数の多いChartライブラリで、HTML5を使用したレスポンシブでモダンなチャートを簡単に実装できます。 | Chart.js |
Recharts | 22K | Reactベースのライブラリで、宣言的にカスタマイズ性高くチャートを構築できます。 | Recharts |
apexcharts | 14K | インタラクティブでモダンなデザインのチャートを提供するライブラリです。多種多様なタイプをサポートしており、簡単にリッチなチャートを作成できます。 | apexcharts |
AG Charts | 59 | 高性能なJavaScriptチャートライブラリで、AG Gridの開発者によって作られました。大量のデータを扱うアプリケーション向けに最適化されており、スムーズなアニメーションとインタラクティブなチャート作成が可能です。 | AG Charts |
nivo | 12K | Reactをベースにした豊富なコンポーネントを提供するライブラリです。SVGとCanvasのレンダリングをサポートしており、大量のデータや複雑なインタラクションを扱うチャートに適しています。 | nivo |
Ant Design Charts | 1.8K | Ant Designシステムに基づいたReactのチャートライブラリで、優れたデザインとUXを提供します。data visualizationを簡単かつ効果的に実装できるように設計されています。 | Ant Design Charts |
Unovis | 1.7K | 強力なdata visualizationとデータ分析のためのJavaScriptライブラリで、複雑なデータセットを扱う際に特化しています。カスタマイズ可能で、多彩なタイプとインタラクティブな機能を備えています。 | Unovis |
候補に上がるライブラリを絞ったので、次に各ライブラリを網羅的に確認しました。
すると、グラフに渡すデータ構造に大きく2つのパターンがあることがわかりました。
「オブジェクトベースのデータ構造」か「配列ベースのデータ構造」に分かれる。
実際にRecharts(オブジェクトベース)とChart.js(配列ベース)を例に挙げてそれぞれ解説をします。
オブジェクトベースのデータ構造は以下の例です。
Labelとそれに紐づくValueがオブジェクトで関連付けられている
(Labelを軸に置いた構造)
const data = [ { label: 'March', pv: 23, uv: 13 }, { label: 'April', pv: 45, uv: 25 }, { label: 'May', pv: 56, uv: 36 }, { label: 'June', pv: 67, uv: 47 }];
https://recharts.org/en-US/examples
ラベルに対して紐づくデータが構造化されている
配列ベースに対してデータの不一致がおきずらい。
データ構造の中にValue値以外の情報を含めずらい
配列ベースのデータ構造で解説しますが、オブジェクトベースのデータ構造ではあくまでLabelに関連するValueを定義することに適しています。
配列ベースのデータ構造は以下の例です。
LabelとValueが別の配列で管理されており、Indexで関連付けをしている
(Valueを軸に置いた構造)
const data = { labels: ["March", "April", "May", "June"], datasets: [ { label: 'pv', data: [23, 45, 56, 67], }, { label: 'uv', data: [13, 25, 36, 47], }, ]};
https://www.chartjs.org/docs/latest/samples/line/line.html
各Valueに対して追加情報を付与できる。
const data = { labels: ["March", "April", "May", "June"], datasets: [ { label: 'pv', data: [23000, 45000, 56000, 67000], // ↓ のような情報 borderColor: "red", backgroundColor: "white", fill: false }, ]}
オブジェクトベースのデータ構造だった場合、それぞれのValueに対して追加情報を付与したい場合はデータ構造の外で定義する必要が生まれます。
データ量が増すほど複雑になる
以下の例のようにデータ量が増えた際にはオブジェクトベースの方がシンプルな構造を保ったまま拡張することができます。
また、配列ベースだとIndexがズレた場合Labelに対してValueが不一致が起きる可能性があります。
オブジェクトベースのデータ構造が複雑化した場合
const data = [ { label: 'March', pv: 23000, uv: 13000, revenue: 2000, newVisitors: 7000, sessions: 15000, bounceRate: 45 }, { label: 'April', pv: 45000, uv: 25000, revenue: 4500, newVisitors: 15000, sessions: 30000, bounceRate: 50 }, { label: 'May', pv: 56000, uv: 36000, revenue: 6000, newVisitors: 20000, sessions: 40000, bounceRate: 55 }, { label: 'June', pv: 67000, uv: 47000, revenue: 8000, newVisitors: 22000, sessions: 50000, bounceRate: 60 }];
配列ベースのデータ構造が複雑化した場合
const data = { labels: ["March", "April", "May", "June"], datasets: [ { label: 'pv', data: [23000, 45000, 56000, 67000], }, { label: 'uv', data: [13000, 25000, 36000, 47000], }, { label: 'revenue', data: [2000, 4000, 6000, 8000], }, { label: 'newVisitors', data: [7000, 15000, 20000, 22000], }, { label: 'sessions', data: [15000, 30000, 40000, 50000], }, { label: 'bounceRate', data: [45, 50, 55, 60], } ]};
これまでの内容から以下のことが考えられます。
トークン数を抑えながら、確実性高くグラフデータを生成するという観点でオブジェクトベースのデータ構造の方がLLMと相性がいいと考えられる。
1st Stepで絞り込んだライブラリをパターンで分けたところ、以下の表のようになりました。
ライブラリ | データ構造のタイプ |
---|---|
Recharts | オブジェクトベースのデータ構造 |
AG Charts | オブジェクトベースのデータ構造 |
Ant Design Charts | オブジェクトベースのデータ構造 |
Unovis | オブジェクトベースのデータ構造 |
nivo | オブジェクトベースのデータ構造 |
Chart.js | 配列ベースのデータ構造 |
apexcharts | 配列ベースのデータ構造 |
では実際に全てのライブラリでLLMからjsonデータを生成し、グラフで表示してみます。
全てのライブラリに対して共通の内容でグラフを表示させてみます。
第1四半期(Q1)の売上は5,000,000ドルです。第2四半期(Q2)の売上は5,500,000ドルです。第3四半期(Q3)の売上は6,000,000ドルです。第4四半期(Q4)では、売上が6,500,000ドルに達しました。
そして、それぞれ私の体感になりますが以下3つの項目でライブラリを比較していきます。
各項目に対して最大⭐️5つで評価します。
system prompt
{ role: "system", content: "You are a helpful assistant designed to output JSON. Return Data Format isdata: { label: {label}, sales: {value} }`.",},
generated data
{ "data": [ { "label": "Q1", "sales": 5000000 }, { "label": "Q2", "sales": 5500000 }, { "label": "Q3", "sales": 6000000 }, { "label": "Q4", "sales": 6500000 } ]}
component.tsx
<LineChart data={data}> <CartesianGrid strokeDasharray="3 3" /> <XAxis dataKey="label" /> <YAxis /> <Tooltip /> <Legend /> <Line type="monotone" dataKey="sales" stroke="#8884d8" activeDot={{ r: 8 }} /></LineChart>
シンプルさ:⭐️⭐️⭐️⭐️⭐️
拡張性:⭐️⭐️⭐️⭐️
LLMとの相性:⭐️⭐️⭐️
データ構造が非常にシンプルで、データ量の増加にも耐えうる構造だと感じました。
拡張性は高く柔軟にカスタマイズ可能ですが、内容によってはその分コード量が増えることが予想されます。
LLMとの相性の観点で気になったこととして、コンポーネント側でX軸、Y軸の変数名(ここではlabelとsales)を指定する必要があります。そのため、LLMから生成される値を事前に知っておく、もしくは一致させる必要があるのは注意点かと思いました。
system prompt
{ role: "system", content: `You are a helpful assistant designed to output JSON. Return Data Format is "data: { title: { text: {title}, }, data: [{ quarter: {quarter}, sales: {value} }], series: [{ type: 'line', xKey: 'quarter', yKey: 'sales' }], }".`,},
generated data
{ "data": { "title": { "text": "Quarterly Sales Data" }, "data": [ { "quarter": "Q1", "sales": 5000000 }, { "quarter": "Q2", "sales": 5500000 }, { "quarter": "Q3", "sales": 6000000 }, { "quarter": "Q4", "sales": 6500000 } ], "series": [ { "type": "line", "xKey": "quarter", "yKey": "sales" } ] }}
component.tsx
<AgChartsReact options={data} />
シンプルさ:⭐️⭐️⭐️⭐️
拡張性:⭐️⭐️
LLMとの相性:⭐️⭐️⭐️⭐️⭐️
AG Chartsを使用して一番メリットに感じた点は、LLMから生成されたデータをそのままコンポーネントに注入できることです。
そのため、LLM側と実装側でデータの調整をすることがなくなります。
その一方、実装側でカスタマイズするには少し不便に感じたことからシンプルにLLMと組み合わせる場合に相性がいいように感じました。
また、AG Chartsの無料枠が充実している利点はありますが、プロダクトとして展開されていることから、よりリッチなコンポーネントを使用する場合に料金が発生してしまいます。
system prompt
{ role: "system", content: "You are a helpful assistant designed to output JSON. Return Data Format is `data: { label: {label}, sales: {value} }`.",},
generated data
{ "data": [ { "label": "Q1", "sales": 5000000 }, { "label": "Q2", "sales": 5500000 }, { "label": "Q3", "sales": 6000000 }, { "label": "Q4", "sales": 6500000 } ]}
component.tsx
const config = { xField: 'label', yField: 'sales', point: { shapeField: 'square', sizeField: 4, }, interaction: { tooltip: { marker: false, }, },};<Line data={data} {...config} />
シンプルさ:⭐️⭐️⭐️⭐️⭐️
拡張性:⭐️⭐️⭐️
LLMとの相性:⭐️⭐️⭐️⭐️
Ant Design ChartsもAG Chartsに似て、LLMから生成されるデータを素直に注入することができます。(上記のconfigデータ自体もLLMに生成させることも可能です。)
また、データ構造も非常にシンプルなためLLMと相性が良く実装することができそうです。
一つ懸念があるとすると、ドキュメントの大半が中国語で書かれていることで実装に詰まった場合で苦戦しそうな予感がしました。
system prompt
{ role: "system", content: `You are a helpful assistant designed to output JSON. Return Data Format is "data: [ { "id": "sales", "color": rgb(147 197 253), "data": [{ x: quarter, y: sales }] } ] ".`,},
generated data
{ "data": [ { "id": "sales", "color": "rgb(147, 197, 253)", "data": [ { "x": "Q1", "y": 5000000 }, { "x": "Q2", "y": 5500000 }, { "x": "Q3", "y": 6000000 }, { "x": "Q4", "y": 6500000 } ] } ]}
component.tsx
<ResponsiveLine data={data} margin={{ top: 50, right: 110, bottom: 50, left: 60 }} xScale={{ type: 'point' }} yScale={{ type: 'linear', min: 'auto', max: 'auto', stacked: true, reverse: false }} yFormat=" >-.2f" axisTop={null} axisRight={null} axisLeft={{ tickSize: 5, tickPadding: 5, tickRotation: 0, legend: 'sales', legendOffset: -40, legendPosition: 'middle', truncateTickAt: 0 }}/>
シンプルさ:⭐️⭐️
拡張性:⭐️⭐️⭐️⭐️⭐️
LLMとの相性:⭐️⭐️
nivoは特に拡張性に富んだライブラリに感じました。その一方で、シンプルな実装をしたい場合は他に比べてコードが肥大化してしまう欠点もあります。
特にコンポーネントのコードを見ていただくとわかるとおり、他のライブラリに比べてもコード量が多いことがわかります。
system prompt
{ role: "system", content: `You are a helpful assistant designed to output JSON. Return Data Format is type Data = { quarter: number; sales: number } "data: { quarter: {quarter}, sales: {value} }". `,},
generated data
{ "data": [ { "quarter": 1, "sales": 5000000 }, { "quarter": 2, "sales": 5500000 }, { "quarter": 3, "sales": 6000000 }, { "quarter": 4, "sales": 6500000 } ]}
component.tsx
<VisXYContainer> <VisLine data={data} x={useCallback(d => d.quarter, [])} y={useCallback(d => d.sales, [])} /> <VisAxis type="x" /> <VisAxis type="y" /></VisXYContainer>
シンプルさ:⭐️⭐️⭐️
拡張性:⭐️⭐️⭐️⭐️
LLMとの相性:⭐️⭐️⭐️
Univosはシンプルなデータ構造でグラフを構築できますが、Recharts同様にコンポーネント側がLLMから生成される変数を知っておく必要がある点がデメリットに感じました。
また使用するデータがnumber型である必要があったため、プロンプトで型を指定する必要がありました。
system prompt
{ role: "system", content: `You are a helpful assistant designed to output JSON. Return Data Format is type quarter = string[] type sales = number[] { 'data': { 'labels': {quarter}, 'datasets': [ { 'label': 'sales', 'data': {sales}, 'borderColor': 'rgb(147, 197, 253)', 'backgroundColor': 'rgba(147, 197, 253, 0.5)' } ] } }`,},
generated data
{ "data": { "labels": ["Q1", "Q2", "Q3", "Q4"], "datasets": [ { "label": "sales", "data": [5000000, 5500000, 6000000, 6500000], "borderColor": "rgb(147, 197, 253)", "backgroundColor": "rgba(147, 197, 253, 0.5)" } ] }}
component.tsx
<Line data={data} />
シンプルさ:⭐️⭐️⭐️
拡張性:⭐️⭐️⭐️
LLMとの相性:⭐️⭐️⭐️⭐️
Chart.jsは配列ベースの構造のため、データ量が少ない場合はシンプルな構造になりますが、データ量が増えるとデータ構造も複雑になっていきます。
しかし、提供されているコンポーネント自体は非常にわかりやすくシンプルなため、複雑なグラフを描画する目的でなければスムーズにLLMと組み合わせ可能に感じました。
生成されるデータを直接注入可能な点も非常に相性が良く感じます。
system prompt
{ role: "system", content: `You are a helpful assistant designed to output JSON. Return Data Format is "data: { options: { chart: { id: {id} }, xaxis: { quarter: {quarter} } }, series: [ { name: "sales", data: {sales} } ] }". `,},
generated data
{ "data": { "options": { "chart": { "id": "sales" }, "xaxis": { "quarter": ["Q1", "Q2", "Q3", "Q4"] } }, "series": [ { "name": "sales", "data": [5000000, 5500000, 6000000, 6500000] } ] }}
component.tsx
<Chart options={data.options} series={data.series} // 🐱 このtpyeを自由に切り替えることで同一データでも見せ方を可変にできる type="line"/>
シンプルさ:⭐️⭐️⭐️
拡張性:⭐️⭐️⭐️
LLMとの相性:⭐️⭐️⭐️⭐️
apexchartsは独特なデータ構造を持っていることから、プロンプトで正確に生成結果を制御する必要があります。
しかし、コンポーネントに対して生成されたデータを素直に注入できることと、同一のデータから複数のtypeに切り替え可能なことは非常に強みに感じました。
ライブラリ名 | シンプルさ | 拡張性 | LLMとの相性 | データ構造のタイプ |
---|---|---|---|---|
Recharts | ⭐️⭐️⭐️⭐️⭐️ | ⭐️⭐️⭐️⭐️ | ⭐️⭐️⭐️ | オブジェクトベースのデータ構造 |
AG Charts | ⭐️⭐️⭐️⭐️ | ⭐️⭐️ | ⭐️⭐️⭐️⭐️⭐️ | オブジェクトベースのデータ構造 |
Ant Design Charts | ⭐️⭐️⭐️⭐️⭐️ | ⭐️⭐️⭐️ | ⭐️⭐️⭐️⭐️ | オブジェクトベースのデータ構造 |
nivo | ⭐️⭐️ | ⭐️⭐️⭐️⭐️⭐️ | ⭐️⭐️ | オブジェクトベースのデータ構造 |
Unovis | ⭐️⭐️⭐️ | ⭐️⭐️ | ⭐️⭐️⭐️ | オブジェクトベースのデータ構造 |
Chart.js | ⭐️⭐️⭐️ | ⭐️⭐️⭐️ | ⭐️⭐️⭐️⭐️ | 配列ベースのデータ構造 |
apexcharts | ⭐️⭐️⭐️ | ⭐️⭐️⭐️ | ⭐️⭐️⭐️⭐️ | 配列ベースのデータ構造 |
今回は数あるReactのChartライブラリから個人の視点でLLMと相性がいいと考えられるものを考察しました。
初めに各ライブラリを比較する中でデータ構造が「オブジェクトベースのデータ構造」と「配列ベースのデータ構造」の二種類に分かれることに着目しました。そして、データ量が複雑化した場合のケースを考慮すると、使用するトークン数と確実性の観点から「オブジェクトベースのデータ構造」がLLMと相性がいいと考えました。
その上で各ライブラリを上記二つに分類し、それぞれLLMからグラフを生成してみました。その結果、ライブラリごとに強みやLLMとの相性が異なり、大きく特徴が分かれる結果になりました。
今回はどのライブラリが一番LLMと相性がいいかを断定することはしませんが、これまでに整理したChartライブラリの特性とLLMとの相性、グラフのUIなどから総合的に判断していただけるといいかと思います。