Biffweb で ApexChart を Hyperscript で表示する
1. Biffweb で Chartを表示したい
今回は、ApexChart Get Started にあるチャート例を Biffweb 上で表示したいと思います。
Creating Your First JavaScript Chart – ApexCharts.js
データはなるべくClojureで処理し、最小限のJSしか書かずに、Hyperscriptでチャートをロードすることを目標としています。
作ったものはここに公開しています:
2. 前準備
2.1. Project 作成
いつものように project を作成します
Create a project | Biff
2.2. ApexCharts を Install
ui.clj
に script タグを追加するだけです。
(defn base [{:keys [::recaptcha] :as ctx} & body]
(apply
biff/base-html
(-> ctx
(merge #:base{:title settings/app-name
:lang "en-US"
:icon "/img/glider.png"
:description (str settings/app-name " Description")
:image "https://clojure.org/images/clojure-logo-120b.png"})
(update :base/head (fn [head]
(concat [[:link {:rel "stylesheet" :href (css-path)}]
+ [:script {:src "https://cdn.jsdelivr.net/npm/apexcharts"}]
[:script {:src (js-path)}]
[:script {:src "https://unpkg.com/htmx.org@1.9.12"}]
[:script {:src "https://unpkg.com/htmx.org@1.9.12/dist/ext/ws.js"}]
[:script {:src "https://unpkg.com/hyperscript.org@0.9.8"}]
(when recaptcha
[:script {:src "https://www.google.com/recaptcha/api.js"
:async "async" :defer "defer"}])]
head))))
body))
2.3. チャート用の namespace を準備
チャートを表示するためのハンドラ関数とルーティングを作成します。
-
src/com/example/chart.clj
(新規作成)
(ns com.example.chart
(:require [com.example.ui :as ui]
[com.biffweb :as biff]))
;; データフェッチ用ハンドラ関数(後で書く)
(defn get-data [])
;; 表示用ハンドラ
(defn chart-page [ctx]
(ui/page
{:base/title (str "drawing charts on biffweb")}
[:h1 {:class "m-4 text-4xl font-extrabold dark:text-white"} "Chart"]))
;; ルーティング
(def module
{:routes ["/chart"
["" {:get chart-page}]
["/sales" {:get get-data}]]})
2.4. routing 設定
/chart
でチャートを表示できるようにルーティングモジュールを追加します。
src/com/example.clj
(ns com.example
(:require [com.biffweb :as biff]
[com.example.email :as email]
[com.example.app :as app]
[com.example.home :as home]
[com.example.middleware :as mid]
[com.example.ui :as ui]
[com.example.worker :as worker]
[com.example.schema :as schema]
[clojure.test :as test]
[clojure.tools.logging :as log]
[clojure.tools.namespace.repl :as tn-repl]
[malli.core :as malc]
[malli.registry :as malr]
[nrepl.cmdline :as nrepl-cmd]
+ [com.example.chart :as chart]
)
(:gen-class))
(def modules
[app/module
(biff/authentication-module {})
home/module
schema/module
worker/module
+ chart/module
])
2.5. 一旦確認
サーバを起動してたあと http://localhost:8080/chart
を確認してください。
clj -M:dev dev
"Chart" と表示されたらOKです。
3. データ
3.1. REPL 起動
テストデータを挿入するためにREPLを使います。お使いのEditorなどでREPLを起動してください。
3.2. テストデータの確認
先程の Creating Your First JavaScript Chart – ApexCharts.js のデータを使います。JSを見るとデータは下記のように定義されています
var options = {
chart: {
type: 'line'
},
series: [{
name: 'sales',
data: [30,40,35,50,49,60,70,91,125]
}],
xaxis: {
categories: [1991,1992,1993,1994,1995,1996,1997, 1998,1999]
}
}
var chart = new ApexCharts(document.querySelector("#chart"), options);
chart.render();
この data
と categories
に合わせて schema と fixture を作ってXTDBへSubmitします。
3.3. schema 作成
(ns com.example.schema)
(def schema
{:user/id :uuid
:user [:map {:closed true}
[:xt/id :user/id]
[:user/email :string]
[:user/joined-at inst?]
[:user/foo {:optional true} :string]
[:user/bar {:optional true} :string]]
:msg/id :uuid
:msg [:map {:closed true}
[:xt/id :msg/id]
[:msg/user :user/id]
[:msg/text :string]
[:msg/sent-at inst?]]
+ :sales/id :uuid
+ :sales [:map {:closed true}
+ [:xt/id :sales/id]
+ [:sales/year :int]
+ [:sales/data :int]]
})
3.4. テストデータ記述
続いて、Trasaction Docmentの形でテストデータを記述します。
[{:db/doc-type :sales
:sales/year 1991
:sales/data 30}
{:db/doc-type :sales
:sales/year 1992
:sales/data 40}
{:db/doc-type :sales
:sales/year 1993
:sales/data 35}
{:db/doc-type :sales
:sales/year 1994
:sales/data 50}
{:db/doc-type :sales
:sales/year 1995
:sales/data 49}
{:db/doc-type :sales
:sales/year 1996
:sales/data 60}
{:db/doc-type :sales
:sales/year 1997
:sales/data 70}
{:db/doc-type :sales
:sales/year 1998
:sales/data 91}
{:db/doc-type :sales
:sales/year 1999
:sales/data 125}]
3.5. テストデータ挿入
dev/repl.clj
をREPLで評価し、コメントの中にある (add-fixtures)
を評価してテストデータをSubmitします。データを確認したい場合は、データ確認用クエリをコピペして貼り付け確認してください。
(comment
(add-fixtures) ;; 評価すると↑の fixture データが入る
;; データ確認用クエリ
(let [{:keys [biff/db] :as ctx} (get-context)]
(q db
'{:find (pull e [*])
:where [[e :sales/data]]}))
)
3.6. ApexChart 形式のデータを作成する関数を書く
ApexChartにデータを渡すときには、こういった形式でデータを渡す必要があります。
chart: {
type: 'bar'
},
series: [{
name: 'sales',
data: [30,40,35,50,49,60,70,91,125]
}],
xaxis: {
categories: [1991,1992,1993,1994,1995,1996,1997, 1998,1999]
}
そのための関数を、もちろんClojureで書きます。ただしJSONの形式で渡す必要があるので、ClojureのHash-mapで作ったあとにJSONに変換します。
(ns com.example.chart
(:require [com.example.ui :as ui]
[com.biffweb :as biff]
+ [cheshire.core :as cheshire]))
+ (defn serialize-sales-data [sales-data]
+ (let [sort-by-year (sort-by :sales/year sales-data)
+ year (map :sales/year sort-by-year)
+ sales (map :sales/data sort-by-year)]
+ (cheshire/generate-string
+ {:chart {:type "bar"}
+ :series [{:name "sales"
+ :data sales}]
+ :xaxis {:categories year}})))
+ (defn get-data
+ [{:keys [biff/db]
+ :as ctx}]
+ (let [data (biff/q db
+ '{:find (pull e [*])
+ :where [[e :sales/year]]})]
+ (serialize-sales-data data)))
cheshire/generate-string
は clojure hash-map を json にシリアライズしてくれる便利ライブラリです。XTDB からクエリしてきた sale data
を JSON に変換しておき、あとでApexChartに渡します。
4. チャート描画
get-data
で得たデータをApexChart で描画するために必要最低限のJSを main.js に書きます。そのための工夫として以下の2つを行っています。
- データは
data-chart-contents
要素に保存する - 書いたJSをページをロードするタイミングで hyperscript で呼び出す
4.1. Hyperscript でページロードのタイミングで描画する
(defn chart-page [ctx]
(ui/page
{:base/title (str "drawing charts on biffweb")}
[:h1 {:class "m-4 text-4xl font-extrabold dark:text-white"} "Chart"]
+ [:div {:class "flex flex-col"}
+ [:p "chart"
+ (let [sales-data (get-data ctx)]
+ [:div {:id "chart"
+ :data-chart-contents sales-data
+ :_ "on load call renderChart(me)"}])]]))
hyperscrip "on load call renderChart(me)"
は、以下の様に振る舞います。
-
div#chart
要素 に対してon load call
というイベントハンドラが適用されます。 -
div#chart
の中で実行されたデータは自分自身が持っている状態です。 -
me
は hyperscript の特別語で自分自身を表します。 - つまり
me
はdata-chart-contents
という属性をもち、その値はXTDBからフェッチしてきたデータということになります。
4.2. renderChart 関数 (JS)
次に Javascript を書きます。JSは、Biffweb のテンプレートに用意されている resources/public/js/main.js
にJSを書けばOKです。
上記で説明したように、自分の属性である data-chart-contents
に保存した sales-data
を 取得してChartに渡すように定義します。
function renderChart(element) {
var options = JSON.parse(element.getAttribute('data-chart-contents'));
var chart = new ApexCharts(document.querySelector("#chart"), options);
// var chart = new ApexCharts(document.getElementById("chart"), options); ByIdでももちろんOK
chart.render();
}
5. 完成
これで完成です。
6. 感想
- Biffwebで書いている限り、とことんClojureで書いて、本当にどうしても必要なところだけJSを書くということができます。
- Hyperscriptになれるのは、ちょっと大変です。
- Hyperscriptもなるべく小さく短く書くことをおすすめします。とにかくなるべくClojureを書いたほうがいいです。
- 今度はHTMXも含めた何かを書きたいと思います。
Discussion