Created
December 1, 2017 16:29
-
-
Save duncanjbrown/b9901e2bb2c26921dd45d1690ca86f87 to your computer and use it in GitHub Desktop.
Basic circle packing with ClojureScript and d3.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns bubble.core | |
(:require [d3 :as d3])) | |
(enable-console-print!) | |
(.format d3 "d") ; we will use integers for our data | |
; set up a color sequence. Later on we will pass numbers to the | |
; 'color' function defined here and it will give us back colours one | |
; by one. | |
; CLJS uses the - sign to access properties instead of invoke methods | |
; removing the minus sign -> method not found | |
(def color (.scaleOrdinal d3 (.-schemeCategory20c d3))) | |
(defn annotate-data-for-circle-packing | |
"Take in some appropriately-stuctured data and decorate it | |
with D3 metadata about how it should be displayed" | |
[data] | |
; we must prepare our data for packing by passing it through d3's | |
; hierarchy function. This code will attach a value to the structure by | |
; summing all the .value fields, and it'll sort the members in descending | |
; order. I think this means they will be put on the screen in descending order, | |
; but what that means for packing I don't know. I couldn't get this to do | |
; much when I changed it around | |
(let [heirarchy-data (-> d3 | |
(.hierarchy data) | |
(.sum #(.-value %)) | |
(.sort #(- (.-value %2) (.-value %1)))) | |
; we build up a function which will accept our hierarchy as data | |
; that function will return the heirarchy deeply decorated with x, y and r values for | |
; position and radius | |
packing-function (-> d3 | |
(.pack) | |
(.size #js [460, 460]) | |
(.padding 1.5))] | |
(packing-function heirarchy-data))) | |
; we build ourselves an array of elements inside the svg and store them as a value. | |
; that value has members for each member of the data structure. | |
(defn get-visualisation-elements | |
[svg data] | |
(-> svg | |
(.selectAll "circle") ; address its circle elements | |
(.data (.-children data)) ; hand the data to the SVG | |
(.enter) ; returns placeholder nodes for each datum not already represented by a circle | |
(.append "g") ; create a 'g' (group) element | |
(.attr "transform" #(str "translate(" (.-x %) "," (.-y %) ")")))) | |
; the following two functions take the set of elements we'll work on and stick things in them | |
; they are a bit like iterators, in that methods called on these functions will be applied to | |
; each data point in turn. | |
; it is possible to assign anonymous functions to properties on these elements, which will | |
; recieve the current data point as arguments. | |
(defn apply-titles | |
"Assign a title to each visualised element" | |
[vis-elements] | |
(-> vis-elements | |
(.append "title") | |
(.attr "x" #(.-x %)) | |
(.attr "y" #(.-y %)) | |
(.text #(.-name (.-data %))))) | |
(defn draw-circles | |
"Draw a circle for each visualised element" | |
[vis-elements] | |
(-> vis-elements | |
(.append "circle") | |
(.style "fill" #(color %2)) | |
(.attr "stroke" "grey") | |
(.attr "r" #(.-r %)))) ; this is the circle's radius assigned by applying packfn | |
(defn draw-labels | |
"Put labels on visualised elements" | |
[vis-elements] | |
(-> vis-elements | |
(.append "text") | |
(.attr "fill" "black") | |
(.text #(.-name (.-data %))))) | |
; define some dummy data | |
; note that the d3-hierarchy plugin which packs the circles for us | |
; expects to recieve things in this structure (children: [{} {} {}]) | |
; I believe it is possible to recursively nest these. | |
(def array-data | |
(clj->js {:children [ | |
{:name "Parsley" :value 10} | |
{:name "Basil" :value 20} | |
{:name "Sage", :value 30}]})) | |
(defn main | |
[] | |
(let [svg-element (.select d3 "svg") | |
data (annotate-data-for-circle-packing array-data) | |
vis-elements (get-visualisation-elements svg-element data)] | |
(do | |
(apply-titles vis-elements) | |
(draw-circles vis-elements) | |
(draw-labels vis-elements)))) | |
; unary + in JS will convert strings, bools, null(!) to integers | |
; https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Unary_plus | |
(defn on-js-reload [] | |
;; optionally touch your app-state to force rerendering depending on | |
;; your application | |
;; (swap! app-state update-in [:__figwheel_counter] inc) | |
(main)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment