Browse Source

Graph view enhancements (#3453)

Graph view enhancements
Tienson Qin 3 years ago
parent
commit
dc34f027b6

+ 7 - 0
externs.js

@@ -120,6 +120,13 @@ dummy.commit = function() {};
 dummy.raw = function() {};
 dummy.onHeadersReceived = function() {};
 dummy.responseHeaders = function() {};
+dummy.velocityDecay = function() {};
+dummy.velocityDecay = function() {};
+dummy.updatePosition = function() {};
+dummy.getNodesObjects = function() {};
+dummy.getEdgesObjects = function() {};
+dummy.alphaTarget = function() {};
+dummy.restart = function() {};
 
 /**
  * @typedef {{

+ 1 - 1
package.json

@@ -93,7 +93,7 @@
         "jszip": "3.5.0",
         "mldoc": "1.2.5",
         "path": "0.12.7",
-        "pixi-graph-fork": "0.1.6",
+        "pixi-graph-fork": "0.2.0",
         "pixi.js": "6.2.0",
         "posthog-js": "1.10.2",
         "react": "17.0.2",

+ 2 - 2
src/main/frontend/components/page.cljs

@@ -582,7 +582,7 @@
     (global-graph-inner graph settings theme)))
 
 (rum/defc page-graph-inner < rum/static
-  [graph dark?]
+  [page graph dark?]
   [:div.sidebar-item.flex-col
    (graph/graph-2d {:nodes (:nodes graph)
                     :links (:links graph)
@@ -605,7 +605,7 @@
                 (graph-handler/build-block-graph (uuid page) theme)
                 (graph-handler/build-page-graph page theme))]
     (when (seq (:nodes graph))
-      (page-graph-inner graph dark?))))
+      (page-graph-inner page graph dark?))))
 
 (defn- sort-pages-by
   [by-item desc? pages]

+ 2 - 2
src/main/frontend/components/sidebar.cljs

@@ -336,8 +336,8 @@
              (ui/loading (t :loading))]]
 
            :else
-           [:div.pb-24 {:class (if global-graph-pages? "" (util/hiccup->class "max-w-7xl.mx-auto"))
-                        :style {:margin-bottom (if global-graph-pages? 0 120)}}
+           [:div {:class (if global-graph-pages? "" (util/hiccup->class "max-w-7xl.mx-auto.pb-24"))
+                  :style {:margin-bottom (if global-graph-pages? 0 120)}}
             main-content])]]])))
 
 (rum/defc footer

+ 6 - 11
src/main/frontend/extensions/graph.cljs

@@ -6,13 +6,6 @@
             [goog.object :as gobj]
             [rum.core :as rum]))
 
-(defn- highlight-node!
-  [^js graph node]
-  (.resetNodeStyle graph node
-                   (bean/->js {:color "#6366F1"
-                               :border {:width 2
-                                        :color "#6366F1"}})))
-
 (defn- highlight-neighbours!
   [^js graph node focus-nodes dark?]
   (.forEachNeighbor
@@ -61,14 +54,16 @@
 (rum/defcs graph-2d <
   (rum/local nil :ref)
   {:did-update pixi/render!
+   :should-update (fn [old-state new-state]
+                    (not= (select-keys (first (:rum/args old-state))
+                                       [:nodes :links :dark?])
+                          (select-keys (first (:rum/args new-state))
+                                       [:nodes :links :dark?])))
    :will-unmount (fn [state]
-                   (when-let [graph (:graph state)]
-                     (.destroy graph))
                    (reset! pixi/*graph-instance nil)
                    state)}
   [state opts]
-  [:div.graph {:style {:height "100vh"}
-               :ref (fn [value]
+  [:div.graph {:ref (fn [value]
                       (let [ref (get state :ref)]
                         (when (and ref value)
                           (reset! ref value))))}])

+ 4 - 0
src/main/frontend/extensions/graph.css

@@ -7,3 +7,7 @@
     position: relative;
     z-index: 4;
 }
+
+.graph {
+    height: calc(100vh - 100px) !important;
+}

+ 107 - 51
src/main/frontend/extensions/graph/pixi.cljs

@@ -7,6 +7,9 @@
             ["graphology" :as graphology]
             ["pixi-graph-fork" :as Pixi-Graph]))
 
+(defonce *graph-instance (atom nil))
+(defonce *simulation (atom nil))
+
 (def Graph (gobj/get graphology "Graph"))
 
 (defonce colors
@@ -38,7 +41,6 @@
                    :type     (.-TEXT (.-TextType Pixi-Graph))
                    :fontSize 12
                    :color (if dark? "rgba(255, 255, 255, 0.8)" "rgba(0, 0, 0, 0.8)")
-                   ;                  :backgroundColor "rgba(255, 255, 255, 0.5)"
                    :padding  4}}
    :edge {:width 1
           :color (if dark? "#094b5a" "#cccccc")}})
@@ -46,15 +48,14 @@
 (defn default-hover-style
   [dark?]
   {:node {:color  "#6366F1"
-          :border {:width 2
-                   :color "#6366F1"}
           :label  {:backgroundColor "rgba(238, 238, 238, 1)"
                    :color           "#333333"}}
    :edge {:color "#A5B4FC"}})
 
 (defn layout!
   [nodes links]
-  (let [simulation (forceSimulation nodes)]
+  (let [nodes-count (count nodes)
+        simulation (forceSimulation nodes)]
     (-> simulation
         (.force "link"
                 (-> (forceLink)
@@ -63,19 +64,19 @@
                     (.links links)))
         (.force "charge"
                 (-> (forceManyBody)
-                    (.distanceMax 4000)
+                    (.distanceMax (if (> nodes-count 500) 4000 600))
                     (.theta 0.5)
                     (.strength -600)))
         (.force "collision"
                 (-> (forceCollide)
-                    (.radius (+ 8 18))))
+                    (.radius (+ 8 18))
+                    (.iterations 2)))
         (.force "x" (-> (forceX 0) (.strength 0.02)))
         (.force "y" (-> (forceY 0) (.strength 0.02)))
         (.force "center" (forceCenter))
-        (.tick 3)
-        (.stop))))
-
-(defonce *graph-instance (atom nil))
+        (.velocityDecay 0.8))
+    (reset! *simulation simulation)
+    simulation))
 
 (defn- clear-nodes!
   [graph]
@@ -83,63 +84,118 @@
                 (fn [node]
                   (.dropNode graph node))))
 
+;; (defn- clear-edges!
+;;   [graph]
+;;   (.forEachEdge graph
+;;                 (fn [edge]
+;;                   (.dropEdge graph edge))))
+
 (defn destroy-instance!
   []
   (when-let [instance (:pixi @*graph-instance)]
     (.destroy instance)
-    (reset! *graph-instance nil)))
+    (reset! *graph-instance nil)
+    (reset! *simulation nil)))
 
 (defonce *dark? (atom nil))
 
+(defn- update-position!
+  [node obj]
+  (.updatePosition node #js {:x (.-x obj)
+                             :y (.-y obj)}))
+
+(defn- tick!
+  [pixi graph nodes-js links-js]
+  (fn []
+    (let [nodes-objects (.getNodesObjects pixi)
+          edges-objects (.getEdgesObjects pixi)]
+      (doseq [node nodes-js]
+        (when-let [node-object (.get nodes-objects (.-id node))]
+          (update-position! node-object node)))
+      (doseq [edge links-js]
+        (when-let [edge-object (.get edges-objects (str (.-index edge)))]
+          (.updatePosition edge-object
+                           #js {:x (.-x (.-source edge))
+                                :y (.-y (.-source edge))}
+                           #js {:x (.-x (.-target edge))
+                                :y (.-y (.-target edge))}))))))
+
+(defn- set-up-listeners!
+  [pixi-graph]
+  (when pixi-graph
+    ;; drag start
+    (let [*dragging? (atom false)
+          nodes (.getNodesObjects pixi-graph)
+          on-drag-end (fn [node event]
+                        (.stopPropagation event)
+                        (when-let [s @*simulation]
+                          (when-not (.-active event)
+                            (.alphaTarget s 0)))
+                        (reset! *dragging? false))]
+      (.on pixi-graph "nodeMousedown"
+           (fn [event node-key]
+             (when-let [node (.get nodes node-key)]
+               (when-let [s @*simulation]
+                 (when-not (.-active event)
+                   (-> (.alphaTarget s 0.3)
+                       (.restart))
+                   (js/setTimeout #(.alphaTarget s 0) 2000))
+                 (reset! *dragging? true)))))
+
+      (.on pixi-graph "nodeMouseup"
+           (fn [event node-key]
+             (when-let [node (.get nodes node-key)]
+               (on-drag-end node event))))
+
+      (.on pixi-graph "nodeMousemove"
+           (fn [event node-key]
+             (when-let [node (.get nodes node-key)]
+               (when @*dragging?
+                 (update-position! node event))))))))
+
 (defn render!
   [state]
-  (let [dark? (:dark? (first (:rum/args state)))]
-    (when (and (some? @*dark?) (not= @*dark? dark?))
-      (destroy-instance!))
-    (reset! *dark? dark?))
-
   (try
-    (let [old-instance         @*graph-instance
-          {:keys [graph pixi]} old-instance]
-      (when (and graph pixi)
-            (clear-nodes! graph))
-      (let [{:keys [nodes links style hover-style height register-handlers-fn dark?]} (first (:rum/args state))
-            style                                                                     (or style (default-style dark?))
-            hover-style                                                               (or hover-style (default-hover-style dark?))
-            graph                                                                     (or graph (Graph.))
-            nodes-set                                                                 (set (map :id nodes))
-            links                                                                     (->>
-                                                                                        (filter
-                                                                                          (fn [link]
-                                                                                            (and (nodes-set (:source link)) (nodes-set (:target link))))
-                                                                                          links)
-                                                                                        (distinct))
-            nodes                                                                     (remove nil? nodes)
-            links                                                                     (remove (fn [{:keys [source target]}] (or (nil? source) (nil? target))) links)
-            nodes-js                                                                  (bean/->js nodes)
-            links-js                                                                  (bean/->js links)]
-        (layout! nodes-js links-js)
+    (when @*graph-instance
+      (clear-nodes! (:graph @*graph-instance))
+      (destroy-instance!))
+    (let [{:keys [nodes links style hover-style height register-handlers-fn dark?]} (first (:rum/args state))
+          style                                                                     (or style (default-style dark?))
+          hover-style                                                               (or hover-style (default-hover-style dark?))
+          graph                                                                     (Graph.)
+          nodes-set                                                                 (set (map :id nodes))
+          links                                                                     (->>
+                                                                                     (filter
+                                                                                      (fn [link]
+                                                                                        (and (nodes-set (:source link)) (nodes-set (:target link))))
+                                                                                      links)
+                                                                                     (distinct))
+          nodes                                                                     (remove nil? nodes)
+          links                                                                     (remove (fn [{:keys [source target]}] (or (nil? source) (nil? target))) links)
+          nodes-js                                                                  (bean/->js nodes)
+          links-js                                                                  (bean/->js links)]
+      (let [simulation (layout! nodes-js links-js)]
         (doseq [node nodes-js]
           (.addNode graph (.-id node) node))
         (doseq [link links-js]
           (let [source (.-id (.-source link))
                 target (.-id (.-target link))]
             (.addEdge graph source target link)))
-        (if pixi
-          (.resetView pixi)
-          (when-let [container-ref (:ref state)]
-            (let [pixi-graph (new (.-PixiGraph Pixi-Graph)
-                                  (bean/->js
-                                   {:container  @container-ref
-                                    :graph      graph
-                                    :style      style
-                                    :hoverStyle hover-style
-                                    :height     height}))]
-              (reset! *graph-instance
-                      {:graph graph
-                       :pixi  pixi-graph})
-              (when register-handlers-fn
-                (register-handlers-fn pixi-graph)))))))
+        (when-let [container-ref (:ref state)]
+          (let [pixi-graph (new (.-PixiGraph Pixi-Graph)
+                                (bean/->js
+                                 {:container  @container-ref
+                                  :graph      graph
+                                  :style      style
+                                  :hoverStyle hover-style
+                                  :height     height}))]
+            (reset! *graph-instance
+                    {:graph graph
+                     :pixi  pixi-graph})
+            (when register-handlers-fn
+              (register-handlers-fn pixi-graph))
+            (set-up-listeners! pixi-graph)
+            (.on simulation "tick" (tick! pixi-graph graph nodes-js links-js))))))
     (catch js/Error e
       (js/console.error e)))
   state)