   (swap-state-animated! current-state assoc
                         :projection :fisheye
                         :camera-pos (v/copy pos)))
+
+(defn select-and-zoom-to-node! [current-state node]
+  (swap-state-animated! current-state assoc
+                        :projection :fisheye
+                        :camera-pos (v/copy (:pos node))
+                        :selected-node node))
 
 ;; Layout
 

   :camera-pos (v/zero)
   :nodes []
   :edges []
   :projection :fisheye ; Can be :fisheye or :zoom-out
   ; Subject is part of state because it is needed to render the graph.
   :animation-subject animation-subject})

(defn swap-state! [current-state f & args]
  (apply swap! current-state f args)
  (sync-graph! @current-state))

(defn swap-state-animated! [current-state f & args]
  (apply swap! current-state f args)
  (sync-graph! @current-state :animate? true))

(defn update-state! [current-state k f & args]
  (apply swap! current-state update k f args)
  (sync-graph! @current-state))

(defn move-camera! [current-state d]
  (update-state! current-state :camera-pos v/add
                 (-> d v/copy (v/multiply
                                (/ (scroll-scale-for-state @current-state))))))

(defn set-projection! [current-state projection]
  (swap-state-animated! current-state assoc :projection projection))

(def label-font-size 24)
(def hitbox-size 96)

(def deselection-distance 0.4)

(def background-color "#EDF0F2")
(def link-color "#C9CBCB")
(def node-color "#777A7A")
(def node-label-color node-color)
(def selected-node-color "#FE9F51")

(def label-y 42)
(def label-y-transform (str "translate(0," label-y ")"))
(def label-visible-threshold 1.5)
