to visualise with style
by Chris Zheng,
I started travelling with just a backpack and a sitar ever since August of last year. It's been an extremely rewarding and life changing experience and I've finally have found a place to call home in Pune.
Career wise, I'm working with Helpshift - an in-app mobile support platform founded by two amazing individuals - and . Helpshift has amazing people and a superb engineering culture. It's a real treat to be able to work here. Coupled with the great weather and amazing food, I consider myself extremely lucky.
One of the things that I have been tasked with is to be able to make sense of a code base that has a substantial amount of functionality. A key requirement for this is to look for functional connectivity within the codebase. Another is to be able to visualise it.
Static Analysis
There have been alot of work done recently to do static analysis of clojure code, the most common use case is to look for unused functionality. One is yagni and the other is sniper. I was quite lucky in that both tools came out at around the same time and so I didn't have to write something myself. In the end, I settled on sniper because it actually does the right thing (in my mind anyway) by using tools.analyser for the analysis. yagni cheats a little bit by loading all the files and does not have as complex a check so the following piece of code will be not analysed correctly
(def blah)
(defn foo []
(let [blah 2]
blah))
yagni will not process the let
form properly and considers foo and blah to be functionally connected, even though they are not. Anyways... having seen this false positive, I started using sniper and even though the library does have it's own quirks, I was able to get the connectivity statistics out using the sniper.snarf namespace.
So that solved the problem for functional connectivity. The next problem was visualisation.
Graphing
These days, every cool kid is using javascript for visualisation. The problem was that I didn't want to mess with the browser so I started digging around for alternatives. I found a terrific project called graphstream and bridges the gap between using graphviz and d3 in terms of complexity, interactivity and usability.
I created a super simple abstraction layer around the library to make for super easy visualisation of connectivity. We've open sourced the project here. The latest version is currently:
[helpshift/gulfstream "0.1.6"]
The premise is pretty simple. For any visualisation, there is:
- a data structure representing the dom,
- a data structure representing the stylesheet
- a data structure representing the graph attributes (title, force, etc.)
We can see a simple example:
(require '[gulfstream.core :as gs])
(def browser (gs/browse {:title "Simple"
:dom {:a {:label "a"}
:b {:label "b"}
[:a :b] {:label "a->b"}}}))
We should see a window popping up as follows:
React Inspired
gulfstream includes a shadow dom, and there is a dom diffing code that was pretty cool to write. Luckily, the dom for graphstream is very simple and so it did not take too long to implement.
Changing the graph
Having the shadow dom in place, We can change elements that we want to display relatively easily. We use a map instead of a key value pair to update our browser:
(browser {:title "Triangle"
:dom {:a {:label "a"}
:b {:label "b"}
:c {:label "c"}
[:a :b] {:label "a->b" :ui.class "sinking"}
[:b :c] {:label "b->c"}
[:c :a] {:label "c->a"}}})
Styling DOM
Note the use of :ui.class
on the [:a :b]
edge. This is exactly the same concept of adding a class in html. We can now style our browser using the following call:
(browser :style [["node#a" {:fill-color "green"
:text-size "20px"
:size "30px"}]
["node#b" {:fill-color "red"
:text-size "20px"
:size "20px"}]
["node" {:fill-color "black"
:size "10px"
:shape "box"
:text-size "10px"}]
["edge" {:fill-color "gray"
:arrow-size "5px, 5px"}]
["edge.sinking"
{:fill-color "blue"
:arrow-size "10px, 10px"}]])
CSS References
For more options, see the css reference for graphstream.