Skip to content

Instantly share code, notes, and snippets.

@duncanjbrown
Created December 1, 2017 16:29
Show Gist options
  • Save duncanjbrown/b9901e2bb2c26921dd45d1690ca86f87 to your computer and use it in GitHub Desktop.
Save duncanjbrown/b9901e2bb2c26921dd45d1690ca86f87 to your computer and use it in GitHub Desktop.
Basic circle packing with ClojureScript and d3.js
(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