shadcn/uiとDuckDB Wasmで軽量で直感的なデータ可視化を実践
こちらは、公式アドベントカレンダー2024_A IT技術関連トピック Day.19 の記事です。
公式アドベントカレンダー2024_B 仕事術・キャリア・体験記も毎日記事を公開していますので、ぜひあわせてご覧下さい。
★Day18のアドベントカレンダー記事
「SQL応用編 ~ JOINを駆使して脱初心者 ~」(鳥羽千明)
動機
前々回の記事では、DuckDB WasmとMonacoEditorを使って軽量な分析環境を作成してみました。
前回の記事ではshadcn/uiの導入を行いました。
今回の目標は、クエリ結果をさらに直感的かつ操作性よく扱える環境を用意することです。
データをテーブルで表示するだけではなく、そのテーブルで列の表示・非表示や検索での絞り込み、列ごとのソートができるDataTableを作成します。
また、取得データをバーチャートで可視化することで、簡易的なダッシュボードのような操作感を得られるようにします。
下図は今回の拡張後の画面例です。
左側のMonaco EditorでSQLクエリを入力・実行し、右上のエリアで結果を DataTable形式で表示、その下には簡易的なバーチャートを配置しています。
動作の様子は動画をご覧ください。
コードはこちらで公開しています。
今回取り込んだCSVはこちらで公開しています。
DataTableとは
この記事でのDataTableは、データを表形式で表示し、列の表示・非表示、検索による絞り込み、列ごとのソートなどの機能を提供するコンポーネントです。
DataTableのドキュメントはありますが、DataTableは完成したコンポーネントとして提供されているわけではなく、開発者側で構築する必要があります。
これは意図してそうなっているようで、完成したUIコンポーネントを提供するよりは、 @tanstack/react-tableというヘッドレス(見た目のない)テーブルロジックライブラリを使用し、shadcn/uiで提供されるTable、Button、DropDownMenu、Inputといった部品を開発者側で組み合わせることで、柔軟性を失わないようにしているようです。
この必要な要素を組み合わせて独自のテーブルを作る思想はDataTableのIntroductionにも記されています。
DataTableの導入
インストール
# テーブルが未追加であれば
bunx --bun shadcn@latest add table
bun add @tanstack/react-table
DataTableの作成
@tanstack/react-tableに渡すために、クエリ結果(Output)用の型を用意します。
CSVやクエリ結果は事前に知ることはできないため、unknownにしておきます。
type OutputRow = Record<string, unknown>;
ソート、グローバルフィルタ、カラムの表示/非表示、ページネーションを使いたいため、@tanstack/react-tableに実装されているロジックを引いてきます。
function DataTable({ columns, data }: DataTableProps) {
const [sorting, setSorting] = React.useState<SortingState>([]);
const [globalFilter, setGlobalFilter] = React.useState("");
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[],
);
const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({});
const table = useReactTable<OutputRow>({
data,
columns,
state: {
sorting,
globalFilter,
columnFilters,
columnVisibility,
},
initialState: {
pagination: {
pageSize: 8,
},
},
onSortingChange: setSorting,
onGlobalFilterChange: setGlobalFilter,
onColumnFiltersChange: setColumnFilters,
onColumnVisibilityChange: setColumnVisibility,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
globalFilterFn: globalFilterFn,
});
return (
略
returnの中では、shadcn/uiのTable、Button、DropDownMenu、Inputを組み合わせて自前で実装します。
インポートするCSVのプレビューコンポーネント(画像左下)はそのままに、右上のクエリ結果の部分をDataTableに差し替えました。
これでユーザーにフィルタ、ソート、列の表示/非表示切り替え、ページネーションを提供できました。
導入後のクエリ結果のテーブルは以下のようになります。
チャートの導入
shadcn/uiはデフォルトで様々なチャートを提供してくださっています。
最終的には全部引いてきた上でそれらをユーザーが自由に設定可能にしたいのですが、まず今回はバーチャートを使ってみました。
チャートのインストール
bunx --bun shadcn@latest add chart
した上で、CSSファイルに
@layer base {
:root {
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
}
.dark {
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}
を追記します。
公式のBar Chart - Multipleを参考にクエリ結果の1列目の項目をx軸に使用して、2列目以降をバーに割り当てる形で実装しました。
const keys = Object.keys(data[0]);
const xKey = keys[0];
const yKeys = keys.slice(1);
const dynamicChartConfig: Record<string, { label: string; color: string }> =
{};
yKeys.forEach((key, index) => {
dynamicChartConfig[key] = {
label: key,
color: `hsl(var(--chart-${index + 1}))`,
};
});
導入後のチャート以下のようになります。
おわりに
今回の実装で、テーブルによるデータ整形機能や、グラフによる可視化機能を追加し、簡易的なダッシュボードとしての形を整えることができました。
一方で、まだ改善の余地がかなりあります。
UIの改善:エディタ、テーブル、グラフの比率が固定値になってしまっているため、ユーザーが自由に比率や表示/非表示を切り替えられるようにする。
CSVダウンロード:クエリやテーブルでの絞り込み、並び替え後にその状態でのダウンロードを可能にする。
より多様なチャートへの対応:現在は棒グラフのみを扱っていますが、折れ線グラフ、円グラフ、ヒートマップなど、分析対象や表現したい軸に合わせて様々なチャートを選べるようにしていく。
柔軟な軸選択UI:今は特定の形式(1列目をx軸)での可視化に限られていますが、ユーザーがどの列をx軸、どの列をy軸に使うかを選べるUIを導入して、分析の幅を広げる。
等々...
DuckDB Wasmはとても有用な技術と考えています。
ブラウザ上で動作するため、サーバーサイドのセットアップが不要でデータベース操作できるのは技術的に非常に面白く、様々な使い道があると思います。
この記事が誰かのお役に立てば幸いです。
★SHIFTグループ公式アドベントカレンダー2024【A】 IT技術関連トピック Day20は「軽い気持ちで1on1を支援するツールを作ったら、奥が深くていっぱい考えた話」(森川 知雄)
お問合せはお気軽に
SHIFTについて(コーポレートサイト)
SHIFTのサービスについて(サービスサイト)
SHIFTの導入事例
お役立ち資料はこちら
SHIFTの採用情報はこちら