Help us understand the problem. What is going on with this article?

Clojure + d3.js でWebアプリ

More than 3 years have passed since last update.

はじめに

みんな大好き、ClojureでWebアプリのお話です。
Clojrueのテンプレートとかよくわからなくても、HTMLじゃなくてJSONを返すようにすれば、JSと組み合わせてカッコイイサイトが簡単にできちゃいますね!
今回は、はやりのd3.jsを使ってみました。

注:Leiningenプロジェクトの作成とかd3.jsの設置といった基本的な説明は省きますm(_ _)m

環境

  • clojure 1.6.0
  • Leiningen 2.4.2
  • Java 1.8.0

基本構成

hoge-app
├── project.clj ①依存ライブラリ
├── resources ④静的リソースを配置
   └── public
       └── assets
           └── css
               └── hogehoge.css
       ├── demo
       │   └── hogehoge.html ⑤JSONを受け取るd3
       └── index.html
└── src
    └── hoge_app
        ├── core.clj ②ルーティングとか
        └── hogehoge.clj ③マップを返すクラス

解説

①使用するライブラリなど

project.clj
 :dependencies [[org.clojure/clojure "1.6.0"]
                 [ring/ring-core "1.3.0"]
                 [ring/ring-jetty-adapter "1.3.0"]
                 [compojure "1.1.8"]
                 [cheshire "5.3.1"]]
 :dev-dependencies [[ring/ring-devel "1.3.0"]]
 :plugins [[lein-ring "0.8.11"]]
 :main hoge-app.core
 :ring {:handler hoge-app.core/app}
ring
RubyのRackに相当するWebアプリケーションライブラリセット
ring-jetty-adapter
leinコマンドからringサーバーを起動できる
compojure
ring対応のルーティングライブラリ
cheshire
JSONエンコーダー

②ルーティング、JSON変換、エントリポイントなど

core.clj
(ns hoge-app.core
  (:use [compojure.core]
        [ring.util.response]
        [ring.middleware.content-type]
        [ring.middleware.params])
  (:require [ring.adapter.jetty :as jetty]
            [cheshire.core :as json]
            [compojure.route :as route]
            [compojure.handler :as handler]
            [hoge-app.hogehoge :as hogehoge]))

(defn json-response [data & [status]]
  {:status (or status 200)
   :headers {"Content-Type" "application/json"}
   :body (json/generate-string data)})

(defroutes api-routes
  (GET "/demo/d3/hogehoge" []
       (json-response (hogehoge/return-map))) ;---①
  (route/resources "/") ;---②
  (route/files "/demo/" {:root "demo"}) ;---③
  (route/not-found "Page not found"))

(defn wrap-dir-index
  [handler]
  (fn [req]
    (handler (update-in req [:uri] #(if (= "/" %) "/index.html" %)))))

(def app (-> api-routes
             wrap-params
             wrap-content-type
             wrap-dir-index
             handler/site))

(defn -main
  [& args]
  (let [port (or (first *command-line-args*) 8080)]
    (jetty/run-jetty app {:port port})))
json-response
clojureのマップを受け取って、JSON形式のレスポンスを返す関数
cheshireのgenerate-stringメソッドで、マップをJSONに変換する
api-routes
ルーティングの設定。
①はJSONを得るリクエストの設定。hogehogeクラスのreturn-mapメソッドを"/demo/d3/hogehoge"にルーティング(メソッドの戻り値は上記のjson-responseでJSON化)。
②は静的リソースのルート。
③で/demo/以下のファイルアクセスを可能にする
wrap-dir-index
"/"でindex.htmlを表示できるようにする(今回の主題とはあまり関係ない…)
app
threadマクロで上記関数群をまとめる
-main
エントリポイント

③マップを返すhogehogeクラス

hogehoge.clj
(ns hoge-app.hogehoge)

(def sample-data [["hoge" [["1/1/1" 10 ]
                           ["2/2/2" 20 ]]
                  ["piyo" [["3/3/3" 30]
                           ["4/4/4" 40 ]]])

(defn make-map-seq [data]
  (map #(array-map :age (get % 0) :power (get % 1)) data))

(defn merge-to-map [data]
  (apply merge (map #(array-map (keyword (get % 0)) (make-map-seq (get % 1))) data)))

(def hogehoge-map (merge-to-map sample-data))

(defn return-map []
  hogehoge-map)

ここはただの例です。本題からちょっと脱線します!!
最終的にマップを返せば良い。
ここでは、
1. 多次元配列のsample-dataを、
2. make-map-seqでマップ配列に変換して、
3. merge-to-mapで最終的な形式のマップに統合している
(この辺、もっとスマートなコードにしたいので、親切な方アドバイスいただけないでしょうか…)
最終的には、

{ :hoge [{:age "1/1/1" :power 10} {:age "2/2/2" :power 20}]
  :piyo [{:age "3/3/3" :power 30} {:age "4/4/4" :power 40}]
}

といった形のマップになる

④静的リソースの設置

②のルーティングに従って、resources/public下にhtml,js,css等を設置する
d3データ表示用のHTMLは、resources/public/demo下に設置する

⑤d3にJSONを渡し、グラフを描画する

グラフを描画…と言っても、d3の解説まで行うのは面倒なので、d3によるグラフ描画の詳細はソースをご参照ください。
https://github.com/circularuins/circularuins/blob/master/resources/public/demo/sentou_d3.html
ここでは、肝心のd3にレスポンスのJSONを渡すところだけ解説します。
…といっても超簡単です。
以下のルーティングで設定した、JSONを取得するクエリ、

core.clj
(defroutes api-routes
  (GET "/demo/d3/hogehoge" []
       (json-response (hogehoge/return-map)))

この"/demo/d3/hogehoge"を、d3.jsonメソッドに渡してやるだけです。

hogehoge.html
<head>
  <link rel="stylesheet" type="text/css" href="../assets/css/hogehoge.css">
  <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>

  <script type="text/javascript">

    d3.json("/demo/d3/hogehoge", function(error, data) {
      // 以下でdataを処理
    }

  </script>

</body>

渡されたJSONデータはd3側でdataオブジェクトとして利用可能になります。

おしまい

以上でおしまいです。
わざわざclojureでやる意味あるの?って聞かれたら……ですが、rubyとかと遜色ない手軽さでWebアプリ書けますよ、ってことと、表示の部分はJSに任せちゃえばいいよね、って話でした。

グラフのデモは、
http://warm-fortress-5777.herokuapp.com/demo/show
こちらになります。

参考
http://stefan.arentz.ca/clojure-angularjs-recipes-demo.html

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away