SlideShare ist ein Scribd-Unternehmen logo
1 von 36
Downloaden Sie, um offline zu lesen
Reactive data visualisations with Om
Anna Pawlicka
Data Engineer
@AnnaPawlicka
Saturday, 28 June 14
Technologies
Saturday, 28 June 14
D3 (Data-Driven Documents)
[to visualise data]
• Data bound to DOM
• Interactive - transformations driven by data
• Huge community
• Higher level libraries available
Saturday, 28 June 14
Leaflet.js & Dimple.js
[higher level libraries]
• Open-source Java-Script libraries
• Interactive
• Simple API
• Access to underlying D3 functions
Saturday, 28 June 14
Facebook’s React
[interface components]
• Solves complex UI rendering
• Declarative framework
• No to “two-way data binding”
• Re-renders the entire UI
Saturday, 28 June 14
U can’t touch this
[a.k.a. Virtual DOM]
• Developer describes the document tree
• React :
• Maintains virtual DOM
• Diffs between previous and next renders of a UI
• Less code
• Shorter time to update
Saturday, 28 June 14
Om Nom Nom Nom
[because we prefer Clojure]
• Entire state of the UI in a single piece of data
• Immutable data structures = Reference equality check
• No need to worry about optimisation
• Snapshottable
• Free undo
Saturday, 28 June 14
Component life cycle protocols
IWillMount
IRenderState
IShouldUpdateIInitState
IRender
Saturday, 28 June 14
Liberator & core.async
[component interaction]
• Provide API to access external components (e.g. database):
(defresource hello-world
:available-media-types ["text/plain"]
:allowed-methods [:get]
:handle-ok (fn [_] "Hello, world.”))
• Send/receive messages between components using core.async channels:
(let [ch (chan)]
(go (while true
(let [v (<! ch)]
(prn "Vader: " v))))
(go (>! ch "No, I am your father")
(<! (timeout 5000))
(>! ch "Search your feelings; you know it to be true!")))
Saturday, 28 June 14
Pretty charts
Saturday, 28 June 14
device_id | type | timestamp | value
------------------------------------------+------------------------+---------------------------------
8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 00:00:00+0000 | 8
8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 00:05:00+0000 | 46
8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 00:10:00+0000 | 23
8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 00:15:00+0000 | 20
8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 00:20:00+0000 | 67
8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 00:25:00+0000 | 70
8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 00:30:00+0000 | 10
8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 00:35:00+0000 | 42
8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 00:40:00+0000 | 95
8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 00:45:00+0000 | 16
8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 00:50:00+0000 | 79
8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 00:55:00+0000 | 33
8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 01:00:00+0000 | 45
8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 01:05:00+0000 | 85
8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 01:10:00+0000 | 32
8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 01:15:00+0000 | 7
8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 01:20:00+0000 | 92
8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 01:25:00+0000 | 15
8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 01:30:00+0000 | 9
8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 01:35:00+0000 | 73
Saturday, 28 June 14
Chart & API
Saturday, 28 June 14
(defresource measurements-resource [id type ctx]
:allowed-methods #{:get}
:available-media-types ["application/edn"]
:handle-ok (partial retrieve-measurements id type))
(defresource devices-resource [_]
:allowed-methods #{:get}
:known-content-type? #{"application/edn"}
:available-media-types #{"application/edn"}
:handle-ok retrieve-devices)
(defroutes app-routes
(ANY "/devices/" [] devices-resource)
(ANY "/device/:id/type/:type/measurements/" [id type] (measurements-resource
id type))
(route/not-found "Not Found"))
(def app
(handler/site app-routes))
Saturday, 28 June 14
(def app-model
(atom {:devices {:all []}
:chart {:data []}}))
(om/root measurements-chart app-model
{:target (.getElementById js/document "app")
:shared {:url "http://localhost:3000/"}})
Saturday, 28 June 14
(defn measurements-chart [cursor owner]
(reify
om/IInitState
(init-state [_]
{:chans {:event-chan (chan (sliding-buffer 1))}})
om/IRenderState
(render-state [_ {:keys [chans]}]
(dom/div nil
(om/build device-form (:devices cursor)
{:init-state chans})
(om/build chart/chart-figure
(:chart cursor)
{:init-state chans
:opts {:event-fn get-measurements
:chart {:div {:id "chart"
:width "100%" :height 600}
:bounds {:x "5%" :y "15%"
:width "80%" :height "50%"}
:x-axis "timestamp"
:y-axis "value"
:plot js/dimple.plot.line}}})))))
Initialise core.async
channel
Saturday, 28 June 14
(defn measurements-chart [cursor owner]
(reify
om/IInitState
(init-state [_]
{:chans {:event-chan (chan (sliding-buffer 1))}})
om/IRenderState
(render-state [_ {:keys [chans]}]
(dom/div nil
(om/build device-form (:devices cursor)
{:init-state chans})
(om/build chart/chart-figure
(:chart cursor)
{:init-state chans
:opts {:event-fn get-measurements
:chart {:div {:id "chart"
:width "100%" :height 600}
:bounds {:x "5%" :y "15%"
:width "80%" :height "50%"}
:x-axis "timestamp"
:y-axis "value"
:plot js/dimple.plot.line}}})))))
This is how you construct
components
Triggered on arrival of a
new message
Saturday, 28 June 14
(defn device-form
[cursor owner]
(reify
om/IWillMount
(will-mount [_]
(let [host (:url (om/get-shared owner))
url (str host "devices/")]
(GET url {:handler #(om/update! cursor [:all] %)})))
om/IRenderState
(render-state [_ {:keys [event-chan]}]
(let [devices (:all cursor)]
(dom/div nil
(dom/table nil
(dom/thead nil (dom/tr nil
(dom/th nil "Select")
(dom/th nil "ID")
(dom/th nil "Type")
(dom/th nil "Description")
(dom/th nil "Unit")))
(apply dom/tbody nil
(om/build-all (form-row event-chan)
devices))))))))
Sequence of components
Saturday, 28 June 14
(defn form-row [event-chan]
(fn [the-item owner]
(om/component
(let [{:keys [id type description unit]} the-item]
(dom/tr nil
(dom/td nil
(dom/input #js {:type "radio"
:name "type"
:value name
:onChange
(fn [e]
(put! event-chan
{:id id
:type type}))}))
(dom/td nil id)
(dom/td nil type)
(dom/td nil description)
(dom/td nil unit))))))
Send message down the
queue
Saturday, 28 June 14
(defn chart-figure [cursor owner {:keys [chart] :as opts}]
(reify
om/IWillMount
(will-mount [_]
(let [event-chan (om/get-state owner [:event-chan])
event-fn (:event-fn opts)]
(go (while true
(let [v (<! event-chan)]
(event-fn cursor owner v))))))
om/IRender
(render [_]
(let [{:keys [id width height]} (:div chart)]
(dom/div #js {:id id :width width :height height})))
om/IDidUpdate
(did-update [_ _ _]
(let [n (.getElementById js/document "chart")]
(while (.hasChildNodes n)
(.removeChild n (.-lastChild n))))
(when (:data cursor)
(draw-chart cursor chart)))))
Reads the message from
the queue
Saturday, 28 June 14
(defn get-measurements [cursor owner message]
(let [host (:url (om/get-shared owner))
{:keys [id type]} message
url (str host "device/" id "/type/" type "/
measurements/")]
(GET url {:handler #(om/update! cursor [:data] %)})))
Saturday, 28 June 14
(defn draw-chart [cursor {:keys [div bounds x-axis y-axis plot]}]
(let [{:keys [id width height]} div
Chart (.-chart js/dimple)
svg (.newSvg js/dimple (str "#" id) width height)
data (get-in cursor [:data])
dimple-chart (.setBounds (Chart. svg) (:x bounds) (:y bounds)
(:width bounds) (:height bounds))
x (.addCategoryAxis dimple-chart "x" x-axis)
y (.addMeasureAxis dimple-chart "y" y-axis)
s (.addSeries dimple-chart nil plot (clj->js [x y]))]
(aset s "data" (clj->js data))
(.addLegend dimple-chart "5%" "10%" "20%" "10%" "right")
(.draw dimple-chart)))
Saturday, 28 June 14
Last.fm chart
Saturday, 28 June 14
(def app-model
(atom {:username-box {:username ""}
:chart {:data []}}))
(om/root lastfm-chart app-model
{:target (.getElementById js/document "app")
:shared {:api-root
"http://ws.audioscrobbler.com/2.0/"}})
Saturday, 28 June 14
(defn lastfm-chart [cursor owner]
(reify
om/IInitState
(init-state [_]
{:chans {:event-chan (chan (sliding-buffer 1))}})
om/IRenderState
(render-state [_ {:keys [chans]}]
(dom/div nil
(dom/div #js {:className "container"}
(dom/h3 nil "Last.fm chart")
(om/build forms/input-box
(:username-box cursor)
{:init-state chans})
(dom/div #js {:className "well" :style #js {:width "100%" :height 600}}
(om/build chart/chart-figure
(:chart cursor)
{:init-state chans
:opts {:event-fn get-all-artists
:chart {:div {:id "chart"
:width "100%" :height 600}
:bounds {:x "5%" :y "15%"
:width "80%" :height "50%"}
:x-axis "name"
:y-axis "playcount"
:plot js/dimple.plot.bar}}})))))))
Username input and chart
components
Saturday, 28 June 14
(defn get-all-artists [cursor owner username]
(let [api-root (:api-root (om/get-shared owner))
url (str api-root
"?method=user.gettopartists&user="
username "&api_key="
api-key "&format=json")]
(GET url {:handler #(om/update! cursor [:data]
(get-in % ["topartists" "artist"]))})))
Saturday, 28 June 14
(defn send-value [owner event-chan]
(let [value (om/get-state owner :value)]
(put! event-chan value)))
(defn input-box [cursor owner]
(reify
om/IRenderState
(render-state [_ {:keys [event-chan]}]
(dom/div #js {:className "form-inline" :role "form"}
(dom/div #js {:className "form-group"}
(dom/input
#js {:type "text"
:className "form-control"
:style #js {:width "100%"}
:onChange (fn [e]
(om/set-state! owner :value
(.-value (.-target e))))
:onKeyPress (fn [e]
(when (= (.-keyCode e) 13)
(send-value owner event-chan)))}))
(dom/button #js {:type "button" :className "btn btn-primary"
:onClick (fn [e]
(send-value owner event-chan)} "Go")))))
Saturday, 28 June 14
Interactive maps
Saturday, 28 June 14
Leaflet map & geocoding
Saturday, 28 June 14
(def app-model
(atom
{:map {:leaflet-map nil
:map {:lat 50.06297958283694 :lng 19.94705200195313}}
:panel {:coordinates nil}}))
(om/root geocoded-map app-model {:target (. js/document (getElementById "app"))})
Saturday, 28 June 14
(defn geocoded-map
[cursor owner]
(reify
om/IInitState
(init-state [_]
{:chans {:event-chan (chan (sliding-buffer 1))
:pin-chan (chan (sliding-buffer 1))}})
om/IRenderState
(render-state [_ {:keys [chans]}]
(dom/div nil
(om/build map-component (:map cursor) {:init-state chans})
(om/build panel-component (:panel cursor) {:init-state chans})))))
Saturday, 28 June 14
(defn map-component [cursor owner]
(reify
om/IWillMount
(will-mount [_]
(let [event-chan (om/get-state owner [:event-chan])]
(go (while true
(let [v (<! event-chan)]
(pan-to-postcode cursor owner v))))))
om/IRender
(render [this]
(dom/div #js {:id "map"}))
om/IDidMount
(did-mount [this]
(let [node (om/get-node owner)
{:keys [leaflet-map] :as map} (create-map (:map cursor) node)
loc {:lng (get-in cursor [:map :lng])
:lat (get-in cursor [:map :lat])}]
(.on leaflet-map "click" (fn [e]
(let [latlng (.-latlng e)]
(drop-pin owner leaflet-map latlng))))
(.panTo leaflet-map (clj->js loc))
(om/update! cursor :leaflet-map leaflet-map)))))
Creates map and stores it in
app state
Saturday, 28 June 14
(defn pan-to-postcode [cursor owner postcode]
(let [postcode (.toUpperCase (string/replace postcode #"[s]+" ""))
url (str geocoding-api-root postcode)]
(GET url {:handler
(fn [body]
(let [map (:leaflet-map @cursor)
{:keys [lat lng]} (location-from-response body)]
(.panTo map (clj->js {:lat (js/parseFloat lat)
:lng (js/parseFloat lng)}))))})))
(defn drop-pin [owner map latlng]
(let [marker (-> (.addTo (.marker js/L (clj->js latlng)) map))
pin-chan (om/get-state owner [:pin-chan])]
(put! pin-chan {:action :put :coordinates latlng})
(.on marker "click" (fn [e] (.removeLayer map marker)
(put! pin-chan {:action :remove})))))
Saturday, 28 June 14
(defn panel-component [cursor owner]
(reify
om/IWillMount
(will-mount [_]
(let [pin-chan (om/get-state owner [:pin-chan])]
(go (while true
(let [{:keys [action coordinates]} (<! pin-chan)]
(if (= action :put)
(om/update! cursor [:coordinates] coordinates)
(om/update! cursor [:coordinates] nil)))))))
om/IRender
(render [_]
(let [event-chan (om/get-state owner [:event-chan])]
(dom/div #js {:id "panel"}
(dom/h3 nil "Postcode lookup")
(om/build forms/input-box cursor
{:init-state {:event-chan event-chan}})
(om/build coordinates-component (:coordinates cursor)))))))
Saturday, 28 June 14
(defn coordinates-component [cursor owner]
(om/component
(dom/section nil
(dom/h3 nil "Coordinates")
(dom/p nil "(Click anywhere on a map)")
(when cursor
(dom/div nil
(dom/label nil (str "Lat: " (.-lat cursor)))
(dom/label nil (str "Lng: " (.-lng cursor))))))))
Saturday, 28 June 14
Summary
• You can leverage all of JavaScript and ClojureScript functionality
and combine them with Om
• Fast rendering and interactivity
• Immutability = efficiency
• Sane application structure
• Reusability
Saturday, 28 June 14
Thank you!
Saturday, 28 June 14

Weitere ähnliche Inhalte

Was ist angesagt?

rx.js make async programming simpler
rx.js make async programming simplerrx.js make async programming simpler
rx.js make async programming simpler
Alexander Mostovenko
 
How to build a html5 websites.v1
How to build a html5 websites.v1How to build a html5 websites.v1
How to build a html5 websites.v1
Bitla Software
 
Bindings: the zen of montage
Bindings: the zen of montageBindings: the zen of montage
Bindings: the zen of montage
Kris Kowal
 

Was ist angesagt? (19)

Reactive Collections
Reactive CollectionsReactive Collections
Reactive Collections
 
JQuery Flot
JQuery FlotJQuery Flot
JQuery Flot
 
A Computational View of MapReduce
A Computational View of MapReduceA Computational View of MapReduce
A Computational View of MapReduce
 
Functional streams with Kafka - A comparison between Akka-streams and FS2
Functional streams with Kafka - A comparison between Akka-streams and FS2Functional streams with Kafka - A comparison between Akka-streams and FS2
Functional streams with Kafka - A comparison between Akka-streams and FS2
 
Clojutre Real Life (2012 ClojuTRE Retro Edition)
Clojutre Real Life (2012 ClojuTRE Retro Edition)Clojutre Real Life (2012 ClojuTRE Retro Edition)
Clojutre Real Life (2012 ClojuTRE Retro Edition)
 
Programming the cloud with Skywriting
Programming the cloud with SkywritingProgramming the cloud with Skywriting
Programming the cloud with Skywriting
 
rx.js make async programming simpler
rx.js make async programming simplerrx.js make async programming simpler
rx.js make async programming simpler
 
Nu program language on Shibuya.lisp#5 LT
Nu program language on  Shibuya.lisp#5 LTNu program language on  Shibuya.lisp#5 LT
Nu program language on Shibuya.lisp#5 LT
 
Yavorsky
YavorskyYavorsky
Yavorsky
 
Functional Programming from OO perspective (Sayeret Lambda lecture)
Functional Programming from OO perspective (Sayeret Lambda lecture)Functional Programming from OO perspective (Sayeret Lambda lecture)
Functional Programming from OO perspective (Sayeret Lambda lecture)
 
Micro and moblile: Java on the Raspberry Pi
Micro and moblile: Java on the Raspberry PiMicro and moblile: Java on the Raspberry Pi
Micro and moblile: Java on the Raspberry Pi
 
Add Some Fun to Your Functional Programming With RXJS
Add Some Fun to Your Functional Programming With RXJSAdd Some Fun to Your Functional Programming With RXJS
Add Some Fun to Your Functional Programming With RXJS
 
Data Love Conference - Window Functions for Database Analytics
Data Love Conference - Window Functions for Database AnalyticsData Love Conference - Window Functions for Database Analytics
Data Love Conference - Window Functions for Database Analytics
 
How to build a html5 websites.v1
How to build a html5 websites.v1How to build a html5 websites.v1
How to build a html5 websites.v1
 
Bindings: the zen of montage
Bindings: the zen of montageBindings: the zen of montage
Bindings: the zen of montage
 
Deep learning study 3
Deep learning study 3Deep learning study 3
Deep learning study 3
 
The Ring programming language version 1.5 book - Part 8 of 31
The Ring programming language version 1.5 book - Part 8 of 31The Ring programming language version 1.5 book - Part 8 of 31
The Ring programming language version 1.5 book - Part 8 of 31
 
mobl
moblmobl
mobl
 
The Ring programming language version 1.7 book - Part 48 of 196
The Ring programming language version 1.7 book - Part 48 of 196The Ring programming language version 1.7 book - Part 48 of 196
The Ring programming language version 1.7 book - Part 48 of 196
 

Andere mochten auch (7)

Optimizing AngularJS Application
Optimizing AngularJS ApplicationOptimizing AngularJS Application
Optimizing AngularJS Application
 
Habits of Effective Designers
Habits of Effective DesignersHabits of Effective Designers
Habits of Effective Designers
 
Habits of Effective Designers - Handout
Habits of Effective Designers - HandoutHabits of Effective Designers - Handout
Habits of Effective Designers - Handout
 
Art of-presentations
Art of-presentationsArt of-presentations
Art of-presentations
 
Mapping with Adobe CC
Mapping with Adobe CCMapping with Adobe CC
Mapping with Adobe CC
 
Intro to Adobe Illustrator
Intro to Adobe IllustratorIntro to Adobe Illustrator
Intro to Adobe Illustrator
 
Javascript Best Practices
Javascript Best PracticesJavascript Best Practices
Javascript Best Practices
 

Ähnlich wie Reactive data visualisations with Om

MongoDB for Time Series Data: Analyzing Time Series Data Using the Aggregatio...
MongoDB for Time Series Data: Analyzing Time Series Data Using the Aggregatio...MongoDB for Time Series Data: Analyzing Time Series Data Using the Aggregatio...
MongoDB for Time Series Data: Analyzing Time Series Data Using the Aggregatio...
MongoDB
 
Big Data for each one of us
Big Data for each one of usBig Data for each one of us
Big Data for each one of us
OSCON Byrum
 

Ähnlich wie Reactive data visualisations with Om (20)

Om nom nom nom
Om nom nom nomOm nom nom nom
Om nom nom nom
 
MongoDB for Time Series Data: Analyzing Time Series Data Using the Aggregatio...
MongoDB for Time Series Data: Analyzing Time Series Data Using the Aggregatio...MongoDB for Time Series Data: Analyzing Time Series Data Using the Aggregatio...
MongoDB for Time Series Data: Analyzing Time Series Data Using the Aggregatio...
 
Web components with java by Haijian Wang
Web components with java by Haijian WangWeb components with java by Haijian Wang
Web components with java by Haijian Wang
 
Svcc 2013-d3
Svcc 2013-d3Svcc 2013-d3
Svcc 2013-d3
 
SVCC 2013 D3.js Presentation (10/05/2013)
SVCC 2013 D3.js Presentation (10/05/2013)SVCC 2013 D3.js Presentation (10/05/2013)
SVCC 2013 D3.js Presentation (10/05/2013)
 
Big Data for each one of us
Big Data for each one of usBig Data for each one of us
Big Data for each one of us
 
PyCon SG x Jublia - Building a simple-to-use Database Management tool
PyCon SG x Jublia - Building a simple-to-use Database Management toolPyCon SG x Jublia - Building a simple-to-use Database Management tool
PyCon SG x Jublia - Building a simple-to-use Database Management tool
 
Visualization of Big Data in Web Apps
Visualization of Big Data in Web AppsVisualization of Big Data in Web Apps
Visualization of Big Data in Web Apps
 
Reactive programming every day
Reactive programming every dayReactive programming every day
Reactive programming every day
 
Monitoring Your ISP Using InfluxDB Cloud and Raspberry Pi
Monitoring Your ISP Using InfluxDB Cloud and Raspberry PiMonitoring Your ISP Using InfluxDB Cloud and Raspberry Pi
Monitoring Your ISP Using InfluxDB Cloud and Raspberry Pi
 
Compose Async with RxJS
Compose Async with RxJSCompose Async with RxJS
Compose Async with RxJS
 
SVGD3Angular2React
SVGD3Angular2ReactSVGD3Angular2React
SVGD3Angular2React
 
Interactively querying Google Analytics reports from R using ganalytics
Interactively querying Google Analytics reports from R using ganalyticsInteractively querying Google Analytics reports from R using ganalytics
Interactively querying Google Analytics reports from R using ganalytics
 
React table tutorial project setup, use table, and usefilter
React table tutorial project setup, use table, and usefilterReact table tutorial project setup, use table, and usefilter
React table tutorial project setup, use table, and usefilter
 
ESRI Dev Meetup: Building Distributed JavaScript Map Widgets
ESRI Dev Meetup: Building Distributed JavaScript Map WidgetsESRI Dev Meetup: Building Distributed JavaScript Map Widgets
ESRI Dev Meetup: Building Distributed JavaScript Map Widgets
 
Architecture for scalable Angular applications
Architecture for scalable Angular applicationsArchitecture for scalable Angular applications
Architecture for scalable Angular applications
 
JSinSA - JS Visualisation APIs
JSinSA - JS Visualisation APIsJSinSA - JS Visualisation APIs
JSinSA - JS Visualisation APIs
 
Couchbas for dummies
Couchbas for dummiesCouchbas for dummies
Couchbas for dummies
 
D3.js 30-minute intro
D3.js   30-minute introD3.js   30-minute intro
D3.js 30-minute intro
 
Having fun with graphs, a short introduction to D3.js
Having fun with graphs, a short introduction to D3.jsHaving fun with graphs, a short introduction to D3.js
Having fun with graphs, a short introduction to D3.js
 

Kürzlich hochgeladen

Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
panagenda
 
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Victor Rentea
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
WSO2
 

Kürzlich hochgeladen (20)

WSO2's API Vision: Unifying Control, Empowering Developers
WSO2's API Vision: Unifying Control, Empowering DevelopersWSO2's API Vision: Unifying Control, Empowering Developers
WSO2's API Vision: Unifying Control, Empowering Developers
 
Elevate Developer Efficiency & build GenAI Application with Amazon Q​
Elevate Developer Efficiency & build GenAI Application with Amazon Q​Elevate Developer Efficiency & build GenAI Application with Amazon Q​
Elevate Developer Efficiency & build GenAI Application with Amazon Q​
 
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of Terraform
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
 
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, AdobeApidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
 
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
 
Mcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot Model
Mcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot ModelMcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot Model
Mcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot Model
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
CNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In PakistanCNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In Pakistan
 
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ..."I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
 
[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf
 
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWEREMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
 
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a Fresher
 
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
 

Reactive data visualisations with Om

  • 1. Reactive data visualisations with Om Anna Pawlicka Data Engineer @AnnaPawlicka Saturday, 28 June 14
  • 3. D3 (Data-Driven Documents) [to visualise data] • Data bound to DOM • Interactive - transformations driven by data • Huge community • Higher level libraries available Saturday, 28 June 14
  • 4. Leaflet.js & Dimple.js [higher level libraries] • Open-source Java-Script libraries • Interactive • Simple API • Access to underlying D3 functions Saturday, 28 June 14
  • 5. Facebook’s React [interface components] • Solves complex UI rendering • Declarative framework • No to “two-way data binding” • Re-renders the entire UI Saturday, 28 June 14
  • 6. U can’t touch this [a.k.a. Virtual DOM] • Developer describes the document tree • React : • Maintains virtual DOM • Diffs between previous and next renders of a UI • Less code • Shorter time to update Saturday, 28 June 14
  • 7. Om Nom Nom Nom [because we prefer Clojure] • Entire state of the UI in a single piece of data • Immutable data structures = Reference equality check • No need to worry about optimisation • Snapshottable • Free undo Saturday, 28 June 14
  • 8. Component life cycle protocols IWillMount IRenderState IShouldUpdateIInitState IRender Saturday, 28 June 14
  • 9. Liberator & core.async [component interaction] • Provide API to access external components (e.g. database): (defresource hello-world :available-media-types ["text/plain"] :allowed-methods [:get] :handle-ok (fn [_] "Hello, world.”)) • Send/receive messages between components using core.async channels: (let [ch (chan)] (go (while true (let [v (<! ch)] (prn "Vader: " v)))) (go (>! ch "No, I am your father") (<! (timeout 5000)) (>! ch "Search your feelings; you know it to be true!"))) Saturday, 28 June 14
  • 11. device_id | type | timestamp | value ------------------------------------------+------------------------+--------------------------------- 8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 00:00:00+0000 | 8 8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 00:05:00+0000 | 46 8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 00:10:00+0000 | 23 8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 00:15:00+0000 | 20 8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 00:20:00+0000 | 67 8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 00:25:00+0000 | 70 8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 00:30:00+0000 | 10 8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 00:35:00+0000 | 42 8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 00:40:00+0000 | 95 8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 00:45:00+0000 | 16 8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 00:50:00+0000 | 79 8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 00:55:00+0000 | 33 8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 01:00:00+0000 | 45 8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 01:05:00+0000 | 85 8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 01:10:00+0000 | 32 8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 01:15:00+0000 | 7 8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 01:20:00+0000 | 92 8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 01:25:00+0000 | 15 8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 01:30:00+0000 | 9 8c077c2c3eac472d153886244e7b8aa6cad6a7e7 | electricityConsumption | 2014-01-01 01:35:00+0000 | 73 Saturday, 28 June 14
  • 12. Chart & API Saturday, 28 June 14
  • 13. (defresource measurements-resource [id type ctx] :allowed-methods #{:get} :available-media-types ["application/edn"] :handle-ok (partial retrieve-measurements id type)) (defresource devices-resource [_] :allowed-methods #{:get} :known-content-type? #{"application/edn"} :available-media-types #{"application/edn"} :handle-ok retrieve-devices) (defroutes app-routes (ANY "/devices/" [] devices-resource) (ANY "/device/:id/type/:type/measurements/" [id type] (measurements-resource id type)) (route/not-found "Not Found")) (def app (handler/site app-routes)) Saturday, 28 June 14
  • 14. (def app-model (atom {:devices {:all []} :chart {:data []}})) (om/root measurements-chart app-model {:target (.getElementById js/document "app") :shared {:url "http://localhost:3000/"}}) Saturday, 28 June 14
  • 15. (defn measurements-chart [cursor owner] (reify om/IInitState (init-state [_] {:chans {:event-chan (chan (sliding-buffer 1))}}) om/IRenderState (render-state [_ {:keys [chans]}] (dom/div nil (om/build device-form (:devices cursor) {:init-state chans}) (om/build chart/chart-figure (:chart cursor) {:init-state chans :opts {:event-fn get-measurements :chart {:div {:id "chart" :width "100%" :height 600} :bounds {:x "5%" :y "15%" :width "80%" :height "50%"} :x-axis "timestamp" :y-axis "value" :plot js/dimple.plot.line}}}))))) Initialise core.async channel Saturday, 28 June 14
  • 16. (defn measurements-chart [cursor owner] (reify om/IInitState (init-state [_] {:chans {:event-chan (chan (sliding-buffer 1))}}) om/IRenderState (render-state [_ {:keys [chans]}] (dom/div nil (om/build device-form (:devices cursor) {:init-state chans}) (om/build chart/chart-figure (:chart cursor) {:init-state chans :opts {:event-fn get-measurements :chart {:div {:id "chart" :width "100%" :height 600} :bounds {:x "5%" :y "15%" :width "80%" :height "50%"} :x-axis "timestamp" :y-axis "value" :plot js/dimple.plot.line}}}))))) This is how you construct components Triggered on arrival of a new message Saturday, 28 June 14
  • 17. (defn device-form [cursor owner] (reify om/IWillMount (will-mount [_] (let [host (:url (om/get-shared owner)) url (str host "devices/")] (GET url {:handler #(om/update! cursor [:all] %)}))) om/IRenderState (render-state [_ {:keys [event-chan]}] (let [devices (:all cursor)] (dom/div nil (dom/table nil (dom/thead nil (dom/tr nil (dom/th nil "Select") (dom/th nil "ID") (dom/th nil "Type") (dom/th nil "Description") (dom/th nil "Unit"))) (apply dom/tbody nil (om/build-all (form-row event-chan) devices)))))))) Sequence of components Saturday, 28 June 14
  • 18. (defn form-row [event-chan] (fn [the-item owner] (om/component (let [{:keys [id type description unit]} the-item] (dom/tr nil (dom/td nil (dom/input #js {:type "radio" :name "type" :value name :onChange (fn [e] (put! event-chan {:id id :type type}))})) (dom/td nil id) (dom/td nil type) (dom/td nil description) (dom/td nil unit)))))) Send message down the queue Saturday, 28 June 14
  • 19. (defn chart-figure [cursor owner {:keys [chart] :as opts}] (reify om/IWillMount (will-mount [_] (let [event-chan (om/get-state owner [:event-chan]) event-fn (:event-fn opts)] (go (while true (let [v (<! event-chan)] (event-fn cursor owner v)))))) om/IRender (render [_] (let [{:keys [id width height]} (:div chart)] (dom/div #js {:id id :width width :height height}))) om/IDidUpdate (did-update [_ _ _] (let [n (.getElementById js/document "chart")] (while (.hasChildNodes n) (.removeChild n (.-lastChild n)))) (when (:data cursor) (draw-chart cursor chart))))) Reads the message from the queue Saturday, 28 June 14
  • 20. (defn get-measurements [cursor owner message] (let [host (:url (om/get-shared owner)) {:keys [id type]} message url (str host "device/" id "/type/" type "/ measurements/")] (GET url {:handler #(om/update! cursor [:data] %)}))) Saturday, 28 June 14
  • 21. (defn draw-chart [cursor {:keys [div bounds x-axis y-axis plot]}] (let [{:keys [id width height]} div Chart (.-chart js/dimple) svg (.newSvg js/dimple (str "#" id) width height) data (get-in cursor [:data]) dimple-chart (.setBounds (Chart. svg) (:x bounds) (:y bounds) (:width bounds) (:height bounds)) x (.addCategoryAxis dimple-chart "x" x-axis) y (.addMeasureAxis dimple-chart "y" y-axis) s (.addSeries dimple-chart nil plot (clj->js [x y]))] (aset s "data" (clj->js data)) (.addLegend dimple-chart "5%" "10%" "20%" "10%" "right") (.draw dimple-chart))) Saturday, 28 June 14
  • 23. (def app-model (atom {:username-box {:username ""} :chart {:data []}})) (om/root lastfm-chart app-model {:target (.getElementById js/document "app") :shared {:api-root "http://ws.audioscrobbler.com/2.0/"}}) Saturday, 28 June 14
  • 24. (defn lastfm-chart [cursor owner] (reify om/IInitState (init-state [_] {:chans {:event-chan (chan (sliding-buffer 1))}}) om/IRenderState (render-state [_ {:keys [chans]}] (dom/div nil (dom/div #js {:className "container"} (dom/h3 nil "Last.fm chart") (om/build forms/input-box (:username-box cursor) {:init-state chans}) (dom/div #js {:className "well" :style #js {:width "100%" :height 600}} (om/build chart/chart-figure (:chart cursor) {:init-state chans :opts {:event-fn get-all-artists :chart {:div {:id "chart" :width "100%" :height 600} :bounds {:x "5%" :y "15%" :width "80%" :height "50%"} :x-axis "name" :y-axis "playcount" :plot js/dimple.plot.bar}}}))))))) Username input and chart components Saturday, 28 June 14
  • 25. (defn get-all-artists [cursor owner username] (let [api-root (:api-root (om/get-shared owner)) url (str api-root "?method=user.gettopartists&user=" username "&api_key=" api-key "&format=json")] (GET url {:handler #(om/update! cursor [:data] (get-in % ["topartists" "artist"]))}))) Saturday, 28 June 14
  • 26. (defn send-value [owner event-chan] (let [value (om/get-state owner :value)] (put! event-chan value))) (defn input-box [cursor owner] (reify om/IRenderState (render-state [_ {:keys [event-chan]}] (dom/div #js {:className "form-inline" :role "form"} (dom/div #js {:className "form-group"} (dom/input #js {:type "text" :className "form-control" :style #js {:width "100%"} :onChange (fn [e] (om/set-state! owner :value (.-value (.-target e)))) :onKeyPress (fn [e] (when (= (.-keyCode e) 13) (send-value owner event-chan)))})) (dom/button #js {:type "button" :className "btn btn-primary" :onClick (fn [e] (send-value owner event-chan)} "Go"))))) Saturday, 28 June 14
  • 28. Leaflet map & geocoding Saturday, 28 June 14
  • 29. (def app-model (atom {:map {:leaflet-map nil :map {:lat 50.06297958283694 :lng 19.94705200195313}} :panel {:coordinates nil}})) (om/root geocoded-map app-model {:target (. js/document (getElementById "app"))}) Saturday, 28 June 14
  • 30. (defn geocoded-map [cursor owner] (reify om/IInitState (init-state [_] {:chans {:event-chan (chan (sliding-buffer 1)) :pin-chan (chan (sliding-buffer 1))}}) om/IRenderState (render-state [_ {:keys [chans]}] (dom/div nil (om/build map-component (:map cursor) {:init-state chans}) (om/build panel-component (:panel cursor) {:init-state chans}))))) Saturday, 28 June 14
  • 31. (defn map-component [cursor owner] (reify om/IWillMount (will-mount [_] (let [event-chan (om/get-state owner [:event-chan])] (go (while true (let [v (<! event-chan)] (pan-to-postcode cursor owner v)))))) om/IRender (render [this] (dom/div #js {:id "map"})) om/IDidMount (did-mount [this] (let [node (om/get-node owner) {:keys [leaflet-map] :as map} (create-map (:map cursor) node) loc {:lng (get-in cursor [:map :lng]) :lat (get-in cursor [:map :lat])}] (.on leaflet-map "click" (fn [e] (let [latlng (.-latlng e)] (drop-pin owner leaflet-map latlng)))) (.panTo leaflet-map (clj->js loc)) (om/update! cursor :leaflet-map leaflet-map))))) Creates map and stores it in app state Saturday, 28 June 14
  • 32. (defn pan-to-postcode [cursor owner postcode] (let [postcode (.toUpperCase (string/replace postcode #"[s]+" "")) url (str geocoding-api-root postcode)] (GET url {:handler (fn [body] (let [map (:leaflet-map @cursor) {:keys [lat lng]} (location-from-response body)] (.panTo map (clj->js {:lat (js/parseFloat lat) :lng (js/parseFloat lng)}))))}))) (defn drop-pin [owner map latlng] (let [marker (-> (.addTo (.marker js/L (clj->js latlng)) map)) pin-chan (om/get-state owner [:pin-chan])] (put! pin-chan {:action :put :coordinates latlng}) (.on marker "click" (fn [e] (.removeLayer map marker) (put! pin-chan {:action :remove}))))) Saturday, 28 June 14
  • 33. (defn panel-component [cursor owner] (reify om/IWillMount (will-mount [_] (let [pin-chan (om/get-state owner [:pin-chan])] (go (while true (let [{:keys [action coordinates]} (<! pin-chan)] (if (= action :put) (om/update! cursor [:coordinates] coordinates) (om/update! cursor [:coordinates] nil))))))) om/IRender (render [_] (let [event-chan (om/get-state owner [:event-chan])] (dom/div #js {:id "panel"} (dom/h3 nil "Postcode lookup") (om/build forms/input-box cursor {:init-state {:event-chan event-chan}}) (om/build coordinates-component (:coordinates cursor))))))) Saturday, 28 June 14
  • 34. (defn coordinates-component [cursor owner] (om/component (dom/section nil (dom/h3 nil "Coordinates") (dom/p nil "(Click anywhere on a map)") (when cursor (dom/div nil (dom/label nil (str "Lat: " (.-lat cursor))) (dom/label nil (str "Lng: " (.-lng cursor)))))))) Saturday, 28 June 14
  • 35. Summary • You can leverage all of JavaScript and ClojureScript functionality and combine them with Om • Fast rendering and interactivity • Immutability = efficiency • Sane application structure • Reusability Saturday, 28 June 14