├── README.md ├── project.clj ├── resources └── public │ ├── css │ └── style.css │ └── index.html └── src └── simplecomponent └── core.cljs /README.md: -------------------------------------------------------------------------------- 1 | # simplecomponent 2 | 3 | A simple compoenent to use Reagent/Re-frame with d3.js 4 | 5 | see http://zachcp.github.io/simplecomponent/ 6 | 7 | ## Overview 8 | 9 | If you want to use reagent with D3.js you need to use lifecycle methods. 10 | These can be very non-intuitive to use, somewhat defeating the otherwise 11 | easy-to-use [Reagent](https://holmsand.github.io/reagent/). 12 | 13 | This project follows the state-management pattern of [re-frame](https://github.com/Day8/re-frame). 14 | Its worth reading the documentationon that project because 15 | 16 | 1. it will get you up and running 17 | 2. it has great sections for more advanced topics. 18 | 19 | I also ended up using these following as references: 20 | 21 | 1. jszakmeister's [clojurescript, D3, and Reagent](http://www.szakmeister.net/blog/2015/nov/26/clojurescript-d3-and-reagent) 22 | 2. nils blum-oests [post on the same topic](http://nils-blum-oeste.net/clojurescripts-reagent-using-props-in-lifecycle-hooks/) 23 | 3. the discussion in [this thread](https://groups.google.com/forum/#!searchin/reagent-project/component-did-update/reagent-project/bDIiKdeDqj8/FdiaKRDJFcsJ) 24 | 25 | 26 | ## Setup 27 | 28 | To get an interactive development environment run: 29 | 30 | lein figwheel 31 | 32 | and open your browser at [localhost:3449](http://localhost:3449/). 33 | This will auto compile and send all changes to the browser without the 34 | need to reload. After the compilation process is complete, you will 35 | get a Browser Connected REPL. An easy way to try it is: 36 | 37 | (js/alert "Am I connected?") 38 | 39 | and you should see an alert in the browser window. 40 | 41 | To clean all compiled files: 42 | 43 | lein clean 44 | 45 | To create a production build run: 46 | 47 | lein cljsbuild once min 48 | 49 | And open your browser in `resources/public/index.html`. You will not 50 | get live reloading, nor a REPL. 51 | 52 | ## License 53 | 54 | Copyright © 2014 FIXME 55 | 56 | Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version. 57 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject simplecomponent "0.1.0-SNAPSHOT" 2 | :description "FIXME: write this!" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | 7 | :dependencies [[org.clojure/clojure "1.7.0"] 8 | [org.clojure/clojurescript "1.7.170"] 9 | [org.clojure/core.async "0.2.374"] 10 | [reagent "0.5.0"] 11 | [re-frame "0.6.0"] 12 | [cljsjs/d3 "3.5.7-1"] 13 | ] 14 | 15 | :plugins [[lein-cljsbuild "1.1.1"] 16 | [lein-figwheel "0.5.0-1"]] 17 | 18 | :source-paths ["src"] 19 | 20 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target"] 21 | 22 | :cljsbuild {:builds 23 | [{:id "dev" 24 | :source-paths ["src"] 25 | 26 | :figwheel {:on-jsload "simplecomponent.core/on-js-reload"} 27 | 28 | :compiler {:main simplecomponent.core 29 | :asset-path "js/compiled/out" 30 | :output-to "resources/public/js/compiled/simplecomponent.js" 31 | :output-dir "resources/public/js/compiled/out" 32 | :source-map-timestamp true}} 33 | ;; This next build is an compressed minified build for 34 | ;; production. You can build this with: 35 | ;; lein cljsbuild once min 36 | {:id "min" 37 | :source-paths ["src"] 38 | :compiler {:output-to "resources/public/js/compiled/simplecomponent.js" 39 | :main simplecomponent.core 40 | :optimizations :advanced 41 | :pretty-print false}}]} 42 | 43 | :figwheel {;; :http-server-root "public" ;; default and assumes "resources" 44 | ;; :server-port 3449 ;; default 45 | ;; :server-ip "127.0.0.1" 46 | 47 | :css-dirs ["resources/public/css"] ;; watch and update CSS 48 | 49 | ;; Start an nREPL server into the running figwheel process 50 | ;; :nrepl-port 7888 51 | 52 | ;; Server Ring Handler (optional) 53 | ;; if you want to embed a ring handler into the figwheel http-kit 54 | ;; server, this is for simple ring servers, if this 55 | ;; doesn't work for you just run your own server :) 56 | ;; :ring-handler hello_world.server/handler 57 | 58 | ;; To be able to open files in your editor from the heads up display 59 | ;; you will need to put a script on your path. 60 | ;; that script will have to take a file path and a line number 61 | ;; ie. in ~/bin/myfile-opener 62 | ;; #! /bin/sh 63 | ;; emacsclient -n +$2 $1 64 | ;; 65 | ;; :open-file-command "myfile-opener" 66 | 67 | ;; if you want to disable the REPL 68 | ;; :repl false 69 | 70 | ;; to configure a different figwheel logfile path 71 | ;; :server-logfile "tmp/logs/figwheel-logfile.log" 72 | }) 73 | -------------------------------------------------------------------------------- /resources/public/css/style.css: -------------------------------------------------------------------------------- 1 | /* some style */ 2 | 3 | -------------------------------------------------------------------------------- /resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |

Figwheel template

13 |

Checkout your developer console.

14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/simplecomponent/core.cljs: -------------------------------------------------------------------------------- 1 | (ns simplecomponent.core 2 | (:require-macros [reagent.ratom :refer [reaction]]) 3 | (:require [reagent.core :as reagent :refer [atom]] 4 | [re-frame.core :refer [register-handler 5 | path 6 | register-sub 7 | dispatch 8 | dispatch-sync 9 | subscribe]] 10 | [cljsjs.d3])) 11 | 12 | (enable-console-print!) 13 | (println "Edits to this text should show up in your developer console.") 14 | 15 | (def app-state {:circles [{:name "circle 1" 16 | :x 10 17 | :y 10 18 | :r 10 19 | :color "black"} 20 | {:name "circle 2" 21 | :x 35 22 | :y 35 23 | :r 15 24 | :color "red"} 25 | {:name "circle 3" 26 | :x 100 27 | :y 100 28 | :r 30 29 | :color "blue"}]}) 30 | 31 | ;; define your app data so that it doesn't get over-written on reload 32 | ;;---- Event handlers----------- 33 | (register-handler 34 | :initialize-db 35 | (fn 36 | [_ _] 37 | app-state)) 38 | 39 | (register-handler 40 | :update 41 | (fn 42 | [db [_ idx param val]] 43 | (println "idx " idx "param " param "val " val) 44 | (assoc-in db [:circles idx param ] val))) 45 | 46 | ;;---- Subscription handlers----------- 47 | (register-sub 48 | :circles 49 | (fn 50 | [db _] 51 | (reaction (:circles @db)))) 52 | 53 | (defn d3-inner [data] 54 | (reagent/create-class 55 | {:reagent-render (fn [] [:div [:svg {:width 400 :height 800}]]) 56 | 57 | :component-did-mount (fn [] 58 | (let [d3data (clj->js data)] 59 | (.. js/d3 60 | (select "svg") 61 | (selectAll "circle") 62 | (data d3data) 63 | enter 64 | (append "circle") 65 | (attr "cx" (fn [d] (.-x d))) 66 | (attr "cy" (fn [d] (.-y d))) 67 | (attr "r" (fn [d] (.-r d))) 68 | (attr "fill" (fn [d] (.-color d)))))) 69 | 70 | :component-did-update (fn [this] 71 | (let [[_ data] (reagent/argv this) 72 | d3data (clj->js data)] 73 | (.. js/d3 74 | (selectAll "circle") 75 | (data d3data) 76 | (attr "cx" (fn [d] (.-x d))) 77 | (attr "cy" (fn [d] (.-y d))) 78 | (attr "r" (fn [d] (.-r d))))) 79 | )})) 80 | 81 | 82 | (defn slider [param idx value] 83 | [:input {:type "range" 84 | :value value 85 | :min 0 86 | :max 500 87 | :style {:width "100%"} 88 | :on-change #(dispatch [:update idx param (-> % .-target .-value)])}]) 89 | 90 | (defn sliders [data] 91 | [:div (for [[idx d] (map-indexed vector data)] 92 | ^{:key (str "slider-" idx)} 93 | [:div 94 | [:h3 (:name d)] 95 | "x " (:x d) (slider :x idx (:x d)) 96 | "y " (:y d) (slider :y idx (:y d)) 97 | "r " (:r d) (slider :r idx (:r d))])]) 98 | 99 | (defn app [] 100 | (let [data (subscribe [:circles])] 101 | (fn [] 102 | [:div {:class "container"} 103 | [:div {:class "row"} 104 | [:div {:class "col-md-5"} 105 | [d3-inner @data]] 106 | [:div {:class "col-md-5"} 107 | [sliders @data]]]] 108 | ))) 109 | 110 | (let [] 111 | (dispatch-sync [:initialize-db]) 112 | (reagent/render-component [app] 113 | (. js/document (getElementById "app")))) 114 | 115 | (defn on-js-reload [] 116 | ;; optionally touch your app-state to force rerendering depending on 117 | ;; your application 118 | ;; (swap! app-state update-in [:__figwheel_counter] inc) 119 | ) 120 | --------------------------------------------------------------------------------