         displacement (subtract-vec pos camera-pos)
         r (scaled-distance-from-camera state pos)
         projected-r (project-displacement r)]
-      (add-vec view-center
-            (vec-clamp-to-bounds (scalar-multiply displacement (/ projected-r r)) view-center))))
+    (add-vec view-center
+             (vec-clamp-to-bounds (scalar-multiply displacement (/ projected-r r)) view-center))))
 
 (defn project-radius [t]
   (+ min-radius (* (- max-radius min-radius)

    (project-radius r)))

(defn sync-graph! [state]
  (let [{:keys [svg nodes]} state
        _view-position (fn [d] (view-position state (:pos d)))
        view-x (comp :x _view-position)
        view-y (comp :y _view-position)
        _view-radius (fn [d] (view-radius state (:pos d)))]
    (-> svg
        (.selectAll ".node")
        (.data (apply array nodes))
        (.attr "cx" view-x)
        (.attr "cy" view-y)

  {:title title
   :pos (math-vec x y)})

(defn link
  "Source and target are indexes into the list of nodes."
  [source target]
  {:source source :target target})

(defn load-graph [json-file callback]
  (.json d3 json-file
         (fn [raw-data]
           ;; Running do-layout! modifies the link data to contain full nodes
           ;;  rather than indices. Hence this ugly ordering.
           (let [data-links (js->clj (aget raw-data "links"))
                 links (mapv (fn [data-link]
                               (link (data-link "source") (data-link "target")))
                             data-links)]
             (do-layout! raw-data)
             (let [data-nodes (js->clj (aget raw-data "nodes"))
                   nodes (mapv (fn [data-node]
                                 (node (data-node "name")
                                       (data-node "x")
                                       (data-node "y"))) data-nodes)]
               (callback {:nodes nodes :links links}))))))

;; Reactive

