     (project-radius r)))
 
 (defn sync-graph! [state]
-  (let [{:keys [svg nodes]} state
+  (let [{:keys [svg nodes links]} 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")
+    (-> svg (.selectAll ".link")
+        (.data (apply array links))
+        (.attr "x1" (fn [link] (view-x (nodes (:source link)))))
+        (.attr "y1" (fn [link] (view-y (nodes (:source link)))))
+        (.attr "x2" (fn [link] (view-x (nodes (:target link)))))
+        (.attr "y2" (fn [link] (view-y (nodes (:target link)))))
+        .enter
+        (.append "line")
+        (.attr "class" "link")
+        (.attr "x1" (fn [link] (view-x (nodes (:source link)))))
+        (.attr "y1" (fn [link] (view-y (nodes (:source link)))))
+        (.attr "x2" (fn [link] (view-x (nodes (:target link)))))
+        (.attr "y2" (fn [link] (view-y (nodes (:target link)))))
+        (.style "stroke" "black")
+        (.style "stroke-width" 3))
+    (-> svg (.selectAll ".node")
         (.data (apply array nodes))
         (.attr "cx" view-x)
         (.attr "cy" view-y)

  {:svg svg
   :view-size (math-vec width height)
   :camera-pos (math-vec 0 0)
   :nodes []})

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

  {: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

