Easy d3 tooltips in Clojure reagent
Table of Contents
Working with d3js I found myself in need of a tooltip to explicate data as it is explored in a chart. The best way to use d3 with Reagent (react.js) is to use reagent to handle the DOM manipulations and d3 to handle more elaborate visualizaion calculations. We don’t follow the d3 model of adding an event to every dom element to trigger a tooltip item to be created, which duplication would behave poorly with React state maintenance. We instead create a single invisible tooltip object on the page and simply move it, change its text, and make it visible in response to our mouse-over events.
;; our action namespace
(require '(reagent.core :as r))
(def TOOLTIP (r/atom nil))
(defn show-tooltip
"Update the tooltip atom with `text` and location according to the event location (just off-mouse)."
[event text]
(let [x (.-pageX event)
y (.-pageY event)]
(swap! TOOLTIP assoc
:show? true
:x x
:y (- y 28)
:text text)))
(defn hide-tooltip
"Set the tooltip to be hidden"
[&_]
(swap! TOOLTIP assoc :show? false))
(defn mount-tooltip
"Mount the tooltip which displays based on the `TOOLTIP` atom"
[]
(let [t @TOOLTIP
o (if (:show? t) 0.9 0.0)] ;; when the element changes to show, become mostly opaque
[:div.tooltip
{:style {:opacity o
:left (str (:x t) "px")
:top (str (:y t) "px")}} ;; Dynamic styles here, the static ones below
(:text t)]))
(defn time-series
"A time-series of time by fairytale"
[]
(r/create-class
{ ;; :component-did-mount
;; :component-did-update
:display-name "my-example"
:reagent-render (fn []
(tel/curate-teleography) ;; necessary for subscription here
[:div.tooltip-example
[mount-tooltip]
;; other stuff
])}))
;; elements on the page
[:div.something {:on-mouse-over #(show-tooltip % msg)
:on-mouse-out hide-tooltip}]
And finally our tooltip styling, using Garden. In this application we were using back-end Garden to generate this into a style.css file, but these days I often favor using Garden on the front-end.
(def tooltip-style
[:body
[:rect {:stroke-style "solid"
:stroke "transparent"
:stroke-width 1}
[:&:hover {:stroke-style "solid"
:stroke "black"
}]]
[:.tooltip
{:position "absolute"
:text-align "center"
:transition-property "opacity"
:transition-duration "0.5s"
:padding (px 5)
:font-size (px 12)
:font-weight 600
:font-family "sans-serif"
:background "lightsteelblue"
:border (px 0)
:border-radius (px 8)
:pointer-events "none"
:opacity 1
}
]
[:.goog-tooltip {:background-color "#2f4f4f"
:color "#F5f5f5"
:padding (px 3)}]])