From b3d64dd7a83ce9d1d423583351db1316dc52ad69 Mon Sep 17 00:00:00 2001 From: Daniel Compton Date: Wed, 9 Nov 2016 12:30:11 +1300 Subject: [PATCH] Temp working point --- project.clj | 6 +- src/day8/re_frame/trace.cljs | 234 ++++++++++++ src/day8/re_frame/trace/d3.cljs | 110 ++++++ src/day8/re_frame/trace/log.cljs | 1 + src/day8/re_frame/trace/subvis.cljs | 559 ++++++++++++++++++++++++++++ 5 files changed, 908 insertions(+), 2 deletions(-) create mode 100644 src/day8/re_frame/trace.cljs create mode 100644 src/day8/re_frame/trace/d3.cljs create mode 100644 src/day8/re_frame/trace/log.cljs create mode 100644 src/day8/re_frame/trace/subvis.cljs diff --git a/project.clj b/project.clj index b2b8078..23b4ed8 100644 --- a/project.clj +++ b/project.clj @@ -4,5 +4,7 @@ :license {:name "MIT"} :dependencies [[org.clojure/clojure "1.8.0"] [org.clojure/clojurescript "1.9.227"] - [reagent "0.6.0"]] - ) + [reagent "0.6.0"] + [re-frame "0.8.1-SNAPSHOT"] + [cljsjs/d3 "4.2.2-0"]] + :profiles {:dev {:dependencies [[binaryage/dirac "RELEASE"]]}}) diff --git a/src/day8/re_frame/trace.cljs b/src/day8/re_frame/trace.cljs new file mode 100644 index 0000000..7429e15 --- /dev/null +++ b/src/day8/re_frame/trace.cljs @@ -0,0 +1,234 @@ +(ns day8.re-frame.trace + (:require [day8.re-frame.trace.subvis :as subvis] + [re-frame.tracing :as trace :include-macros true] + [cljs.pprint :as pprint] + [clojure.string :as str] + [reagent.core :as r] + [reagent.interop :refer-macros [$ $!]] + [reagent.impl.util :as util] + [reagent.impl.component :as component] + [reagent.impl.batching :as batch] + [reagent.ratom :as ratom] + [re-frame.utils :as rutils] + [goog.object :as gob] + [re-frame.interop :as interop])) + +(defn comp-name [c] + (let [n (or (component/component-path c) + (some-> c .-constructor util/fun-name))] + (if-not (empty? n) + n + ""))) + + + +(def static-fns + {:render + (fn render [] + (this-as c + (trace/with-trace {:op-type :render + :tags {:component-path (reagent.impl.component/component-path c)} + :operation (last (str/split (reagent.impl.component/component-path c) #" > "))} + (if util/*non-reactive* + (reagent.impl.component/do-render c) + (let [rat ($ c :cljsRatom) + _ (batch/mark-rendered c) + res (if (nil? rat) + (ratom/run-in-reaction #(reagent.impl.component/do-render c) c "cljsRatom" + batch/queue-render reagent.impl.component/rat-opts) + (._run rat false)) + cljs-ratom ($ c :cljsRatom)] ;; actually a reaction + (trace/merge-trace! + {:tags {:reaction (rutils/reagent-id cljs-ratom) + :input-signals (when cljs-ratom + (map rutils/reagent-id (gob/get cljs-ratom "watching" :none)))}}) + res)))))}) + + +(defn monkey-patch-reagent [] + (let [#_#_real-renderer reagent.impl.component/do-render + real-custom-wrapper reagent.impl.component/custom-wrapper + real-next-tick reagent.impl.batching/next-tick + real-schedule reagent.impl.batching/schedule] + + + #_(set! reagent.impl.component/do-render + (fn [c] + (let [name (comp-name c)] + (js/console.log c) + (trace/with-trace {:op-type :render + :tags {:component-path (reagent.impl.component/component-path c)} + :operation (last (str/split name #" > "))} + (real-renderer c) + + )))) + + (set! reagent.impl.component/static-fns static-fns) + + (set! reagent.impl.component/custom-wrapper + (fn [key f] + (case key + :componentWillUnmount + (fn [] (this-as c + (trace/with-trace {:op-type key + :operation (last (str/split (comp-name c) #" > ")) + :tags {:component-path (reagent.impl.component/component-path c)}}) + (.call (real-custom-wrapper key f) c c))) + + (real-custom-wrapper key f)))) + + #_(set! reagent.impl.batching/next-tick (fn [f] + (real-next-tick (fn [] + (trace/with-trace {:op-type :raf} + (f)))))) + + #_(set! reagent.impl.batching/schedule schedule + #_(fn [] + (reagent.impl.batching/do-after-render (fn [] (trace/with-trace {:op-type :raf-end}))) + (real-schedule))) + )) + +(def traces (interop/ratom [])) +(defn log-trace? [trace] + (let [render-type? (= (:type trace) :render)] + (not (and render-type? + (str/includes? (or (get-in trace [:tags :component-path]) "") "day8.re_frame.trace.devtools"))))) + +(defn init-tracing! [] + (re-frame.tracing/register-trace-cb ::cb (fn [new-traces] + (let [new-traces (filter log-trace? new-traces)] + (swap! traces #(reduce conj % new-traces))))) + (monkey-patch-reagent) + ) + +(defn search-input [{:keys [title on-save on-stop]}] + (let [val (r/atom title) + save #(let [v (-> @val str clojure.string/trim)] + (on-save v))] + (fn [props] + [:input (merge props + {:type "text" + :value @val + :auto-focus true + :on-blur save + :on-change #(reset! val (-> % .-target .-value)) + :on-key-down #(case (.-which %) + 13 (save) + nil)})]))) + + +(defn render-traces [] + (let [search (r/atom "") + slower-than-ms (r/atom "") + slower-than-bold (r/atom "")] + (fn [] + (let [slower-than-ms-int (js/parseInt @slower-than-ms) + slower-than-bold-int (js/parseInt @slower-than-bold) + op-filter (when-not (str/blank? @search) + (filter #(str/includes? (str (:operation %) " " (:type %)) @search))) + ms-filter (when-not (str/blank? @slower-than-ms) + (filter #(< slower-than-ms-int (:duration %)))) + transducers (apply comp (remove nil? [ms-filter op-filter])) + showing-traces (sequence transducers @traces) + + filter-msg (if (and (str/blank? @search) (str/blank? @slower-than-ms)) + (str "Filter " (count @traces) " events: ") + (str "Filtering " (count showing-traces) " of " (count @traces) " events:")) + padding {:padding "0px 5px 0px 5px"}] + [:div + {:style {:padding "10px"}} + [:h1 "TRACES"] + [:span filter-msg [:button {:on-click #(do (trace/reset-tracing!) (reset! traces []))} " Clear traces"]] [:br] + [:span "Filter events " [search-input {:on-save #(reset! search %)}]] [:br] + [:span "Filter slower than " [search-input {:on-save #(reset! slower-than-ms %)}] "ms "] [:br] + [:span "Bold slower than " [search-input {:on-save #(reset! slower-than-bold %)}] "ms "] + [:table + {:cell-spacing "0" :width "100%"} + [:thead>tr + [:th "op"] + [:th "event"] + [:th "meta"]] + [:tbody + (doall + (for [{:keys [type id operation tags duration] :as trace} showing-traces] + (let [row-style (merge padding {:border-top (case type :event "1px solid lightgrey" nil)})] + (list [:tr {:key id + :style {:color (case type + :sub/create "green" + :sub/run "red" + :event "blue" + :render "purple" + :re-frame.router/fsm-trigger "red" + nil)}} + [:td {:style row-style} (str type)] + [:td {:style row-style} operation] + [:td + {:style (merge row-style {:font-weight (if (< slower-than-bold-int duration) + "bold" + "")})} + (.toFixed duration 1) " ms"]] + (when true + [:tr {:key (str id "-details")} + [:td {:col-span 3} (with-out-str (pprint/pprint (dissoc tags :query-v :event :duration)))]])))))]]])))) + +(defn resizer-style [draggable-area] + {:position "absolute" :z-index 2 :opacity 0 + :left (str (- (/ draggable-area 2)) "px") :width "10px" :top "0px" :height "100%" :cursor "col-resize"}) + +(def ease-transition "left 0.2s ease-out, top 0.2s ease-out, width 0.2s ease-out, height 0.2s ease-out") + +(defn devtools [] + ;; Add clear button + ;; Filter out different trace types + (let [position (r/atom :right) + size (r/atom 0.5) + showing? (r/atom true) + dragging? (r/atom false) + pin-to-bottom? (r/atom true) + selected-tab (r/atom :subvis) + _ (js/console.log "Rendering devtools") + handle-keys (fn [e] + (let [combo-key? (or (.-ctrlKey e) (.-metaKey e) (.-altKey e)) + tag-name (.-tagName (.-target e)) + key (.-key e) + entering-input? (contains? #{"INPUT" "SELECT" "TEXTAREA"} tag-name)] + (when (and (not entering-input?) combo-key?) + (cond + (and (= key "h") (.-ctrlKey e)) + (do (swap! showing? not) + (.preventDefault e))))))] + (r/create-class + {:component-will-mount #(js/window.addEventListener "keydown" handle-keys) + :component-will-unmount #(js/window.removeEventListener "keydown" handle-keys) + :display-name "devtools outer" + :reagent-render (fn [] + (let [draggable-area 10 + full-width js/window.innerWidth + full-height js/window.innerHeight + left (if @showing? (str (* 100 (- 1 @size)) "%") + (str full-width "px")) + transition (if @showing? + ease-transition + (str ease-transition ", opacity 0.01s linear 0.2s"))] + [:div {:style {:position "fixed" :width "0px" :height "0px" :top "0px" :left "0px" :z-index 99999999}} + [:div {:style {:position "fixed" :z-index 1 :box-shadow "rgba(0, 0, 0, 0.298039) 0px 0px 4px" :background "white" + :left left :top "0px" :width (str (* 100 @size) "%") :height "100%" + :transition transition}} + [:div.resizer {:style (resizer-style draggable-area) + :on-mouse-down #(reset! dragging? true) + :on-mouse-up #(reset! dragging? false) + :on-mouse-move (fn [e] + (when @dragging? + (let [x (.-clientX e) + y (.-clientY e)] + (.preventDefault e) + (reset! size (/ (- full-width x) + full-width)))))}] + [:div {:style {:width "100%" :height "100%" :overflow "auto"}} + [:button {:on-click #(reset! selected-tab :traces)} "Traces"] + [:button {:on-click #(reset! selected-tab :subvis)} "SubVis"] + (case @selected-tab + :traces [render-traces] + :subvis [subvis/render-subvis traces] + [render-traces])]]]))}))) + diff --git a/src/day8/re_frame/trace/d3.cljs b/src/day8/re_frame/trace/d3.cljs new file mode 100644 index 0000000..f0d95ab --- /dev/null +++ b/src/day8/re_frame/trace/d3.cljs @@ -0,0 +1,110 @@ +(ns day8.re-frame.trace.d3 + (:require [reagent.core :as r])) + +(defn component-did-update [{:keys [d3-enter d3-update d3-exit]} ratoms] + (apply d3-enter ratoms) + (apply d3-update ratoms) + (apply d3-exit ratoms)) + +(defn component-did-mount [{:keys [d3-did-mount] :as lifecycle-fns} ratoms] + (apply d3-did-mount ratoms) + (component-did-update lifecycle-fns ratoms)) + +(defn no-op [desc] + (fn [& args] + (println "No-op" desc))) + +(defn create-d3 + "Creates a bridging component from Reagent to D3. Takes a map of + lifecycle functions, and reactive data sources. + + :render-component - Render the outer Reagent component, and a place for your D3 component to mount to (probably an SVG element) + :d3-did-mount - Function called after the component has been rendered, for you to setup anything you need in D3 (e.g. adding or classes) + :d3-enter, :d3-update, :d3-exit - correspond to functions in the D3 general update pattern: https://bl.ocks.org/mbostock/3808218 + " + [{:keys [render-component d3-did-mount d3-enter d3-update d3-exit] + :or {render-component no-op + d3-did-mount (no-op :d3-did-mount) + d3-enter (no-op :d3-enter) + d3-update (no-op :d3-update) + d3-exit (no-op :d3-exit)}} + & ratoms] + (let [lifecycle-fns {:render-component render-component + :d3-did-mount d3-did-mount + :d3-enter d3-enter + :d3-update d3-update + :d3-exit d3-exit}] + (r/create-class + {:reagent-render (fn [] + (doseq [r ratoms] (deref r)) + (apply render-component ratoms)) + :component-did-mount (fn [this] (component-did-mount lifecycle-fns ratoms)) + :component-did-update (fn [this old-argv] (component-did-update lifecycle-fns ratoms))})) + ) + + +;;;; +;;;; +;;; app +; +; +;(def data (r/atom {})) +; +;(defn my-render [ratom] +; (let [width 100 +; height 100] +; [:div +; {:id "barchart"} +; [:svg +; {:width width +; :height height}]])) +; +;(defn bars-did-mount [ratom] +; (-> (js/d3.select "#barchart svg") +; (.append "g") +; (.attr "class" "container") +; (.append "g") +; (.attr "class" "bars"))) +; +;(defn bars-enter [ratom] +; (let [data (get-data ratom)] +; (-> (js/d3.select "#barchart svg .container .bars") +; (.selectAll "rect") +; (.data (clj->js data)) +; .enter +; (.append "rect")))) +; +;(defn bars-update [ratom] +; (let [width (get-width ratom) +; height (get-height ratom) +; data (get-data ratom) +; data-n (count data) +; rect-height (/ height data-n) +; x-scale (-> js/d3 +; .scaleLinear +; (.domain #js [0 5]) +; (.range #js [0 width]))] +; (-> (js/d3.select "#barchart svg .container .bars") +; (.selectAll "rect") +; (.data (clj->js data)) +; (.attr "fill" "green") +; (.attr "x" (x-scale 0)) +; (.attr "y" (fn [_ i] +; (* i rect-height))) +; (.attr "height" (- rect-height 1)) +; (.attr "width" (fn [d] +; (x-scale (aget d "x"))))))) +; +;(defn bars-exit [ratom] +; (let [data (get-data ratom)] +; (-> (js/d3.select "#barchart svg .container .bars") +; (.selectAll "rect") +; (.data (clj->js data)) +; .exit +; .remove))) +; +;(create-d3 data {:reagent-render my-render +; :d3-did-mount bars-did-mount +; :d3-enter bars-enter +; :d3-update bars-update +; :d3-exit bars-exit}) diff --git a/src/day8/re_frame/trace/log.cljs b/src/day8/re_frame/trace/log.cljs new file mode 100644 index 0000000..156a874 --- /dev/null +++ b/src/day8/re_frame/trace/log.cljs @@ -0,0 +1 @@ +(ns day8.re-frame.trace.log) diff --git a/src/day8/re_frame/trace/subvis.cljs b/src/day8/re_frame/trace/subvis.cljs new file mode 100644 index 0000000..7991ba8 --- /dev/null +++ b/src/day8/re_frame/trace/subvis.cljs @@ -0,0 +1,559 @@ +(ns day8.re-frame.trace.subvis + (:require cljsjs.d3 + [day8.re-frame.trace.d3 :as d3t] + [reagent.core :as r] + [re-frame.utils :as rutils] + [re-frame.db :as db] + [goog.object :as gob] + [clojure.set :as set])) + +(def width 400) +(def height 200) + +(def mygraph (r/atom {:nodes [{:id 1 :group 1} + {:id 2 :group 1} + {:id 3 :group 2}] + :links [{:source 1 :target 2 :value 1}]})) + +(defn trace->sub-graph [traces] + (let [disposed (->> traces + (filter #(#{:sub/dispose} (:type %))) + (map #(get-in % [:tags :reaction])) + set) + sub-nodes (->> traces + (filter #(#{:sub/create} (:type %))) + (remove #(contains? disposed (get-in % [:tags :reaction]))) + (remove #(get-in % [:tags :cached?])) + (map (fn [trace] + {:id (get-in trace [:tags :reaction]) + :title (str (:operation trace)) + :group (mod (:id trace) 20) + :r 10 + :data trace}))) + + unmounted-components #{} + view-nodes (->> traces + (filter #(#{:render} (:type %))) + (remove #(contains? unmounted-components (:id %))) ;; todo + (map (fn [trace] + {:id (get-in trace [:tags :reaction]) + :title (:operation trace) + :group (mod (:id trace) 20) + :r 5 + :data trace})) + + ) + + sub-links (->> traces + (filter #(#{:sub/run} (:type %))) + (remove #(contains? disposed (get-in % [:tags :reaction]))) + (mapcat (fn [trace] + (for [input-signal (get-in trace [:tags :input-signals]) + :let [reaction (get-in trace [:tags :reaction])] + :when (every? some? [input-signal reaction])] + {:source input-signal :target reaction :value 1})))) + + view-links (->> traces + (filter #(#{:render} (:type %))) + (remove #(contains? unmounted-components (get-in % [:tags :reaction]))) + (mapcat (fn [trace] + (for [input-signal (get-in trace [:tags :input-signals]) + :let [reaction (get-in trace [:tags :reaction])] + :when (every? some? [input-signal reaction])] + {:source input-signal :target reaction :value 0.5})))) + + app-db {:id (rutils/reagent-id db/app-db) + :title "app-db" + :group 1 + :r 20 + :fx (/ width 2) + :fy 30} + + + all-nodes (concat sub-nodes [app-db] view-nodes) + node-ids (set (map :id all-nodes)) + nodes-links (->> (mapcat (fn [{:keys [source target]}] [source target]) view-links) + set) + missing-nodes (set/difference nodes-links node-ids) ;; These are local ratoms + + view-links (->> view-links + (remove #(get missing-nodes (:source %)))) + ] + + {:nodes all-nodes + :links (concat sub-links view-links)})) + +(defn min-max + "Returns x if it is within min-val and max-val + otherwise returns min-val if x is less than min-val + and max-val if x is greater than max-val. + + Essentially this provides a bounding box/clamp around x." + [min-val x max-val] + (assert (<= min-val max-val)) + (cljs.core/max min-val (cljs.core/min x max-val))) + +(defn force-inner [graph] + (r/create-class + {:reagent-render (fn [] [:div [:svg {:width width :height height}]]) + + :component-did-mount (fn [] + (let [nodes (clj->js (:nodes graph)) + links (clj->js (:links graph)) + color (.scaleOrdinal js/d3 (.-schemeCategory20 js/d3)) + svg (. js/d3 select "svg") + simulation (.. js/d3 + (forceSimulation) + (force "link" (.. js/d3 (forceLink) + (id #(.-id %)) + (distance (constantly 100)))) + (force "charge" (.. js/d3 (forceManyBody) + (strength (constantly -100)))) + (force "center" (. js/d3 forceCenter (/ width 2) (/ height 2)))) + + dragstarted (fn [d] + (when (zero? (.. js/d3 -event -active)) + (.. simulation + (alphaTarget 0.3) + (restart))) + + (set! (.-fx d) (.-x d)) + (set! (.-fy d) (.-y d))) + + dragged (fn [d] + (set! (.-fx d) (.. js/d3 -event -x)) + (set! (.-fy d) (.. js/d3 -event -y))) + + dragended (fn [d] + (when (zero? (.. js/d3 -event -active)) + (.. simulation + (alphaTarget 0.0))) + (set! (.-fx d) nil) + (set! (.-fy d) nil)) + + link (.. (. svg append "g") + (attr "class" "links") + (selectAll "line") + (data links) + (enter) + (append "line") + (attr "stroke-width" (fn [d] (Math/sqrt (.-value d))))) + + node (.. (. svg append "g") + (attr "class" "nodes") + (selectAll "circle") + (data nodes) + (enter) + (append "g") + (attr "class" "node")) + + circle (.. node + (append "circle") + (attr "r" #(or (gob/get % "r" 10))) + (attr "fill" (fn [d] (color (.-group d)))) + (call (.. (. js/d3 drag) + (on "start" dragstarted) + (on "drag" dragged) + (on "end" dragended)))) + + label (.. node + (append "text") + (attr "dy" ".35em") + (text #(gob/get % "title" ""))) + + ticked (fn [] + (.. link + (attr "x1" (fn [d] (.. d -source -x))) + (attr "y1" (fn [d] (.. d -source -y))) + (attr "x2" (fn [d] (.. d -target -x))) + (attr "y2" (fn [d] (.. d -target -y)))) + (.. circle + (attr "cx" (fn [d] (min-max (.. d -r) (.. d -x) (- width (.. d -r))))) + (attr "cy" (fn [d] (min-max (.. d -r) (.. d -y) (- height (.. d -r)))))) + (.. label + (attr "x" #(+ (.-x %) 2 (.-r %))) + (attr "y" #(+ (.-y %)))) + nil) + ] + + (.. simulation + (nodes nodes) + (on "tick" ticked)) + + (.. simulation + (force "link") + (links links)))) + + :component-did-update (fn [this] + #_(let [[_ data] (r/argv this) + d3data (clj->js data) + circles (.. js/d3 + (select "svg") + (selectAll "circle") + (data d3data (fn [d i] (when d (.-name d)))))] + (.. circles + ;(attr "cx" (fn [d] (.-x d))) + ;(attr "cy" (fn [d] (.-y d))) + ;(attr "r" (fn [d] (.-r d))) + enter + (append "circle") + (attr "cx" 200) + (attr "cy" 200) + (attr "r" 500) + (transition) + (attr "cx" (fn [d] (.-x d))) + (attr "cy" (fn [d] (.-y d))) + (attr "r" (fn [d] (.-r d))) + (attr "fill" (fn [d] (.-color d)))) + (.. circles + exit + remove)))})) + + +(defn force-outer [traces-ratom] + (fn [] + (let [trace-graph (trace->sub-graph @traces-ratom)] + [force-inner trace-graph]))) + +(defonce desc (r/atom 1)) + +(defn render-subvis [traces-ratom] + (let [color-a (atom nil) + svg-a (atom nil) + simulation-a (atom nil)] + (fn [] + [:div + {:style {:padding "10px"}} + [:h1 "SUBVIS"] + [force-outer traces-ratom] + [:hr] + [:h2 {:on-click #(swap! desc inc)} "Click"] + [(d3t/create-d3 {:render-component (fn [ratom] + [:div + [:h1 (str "SVG")] + [:svg#d3cmp {:width width :height height}]]) + :d3-did-mount (fn [ratom] + (let [graph (trace->sub-graph @ratom) + nodes (clj->js (:nodes graph)) + links (clj->js (:links graph)) + color (reset! color-a (.scaleOrdinal js/d3 (.-schemeCategory20 js/d3))) + svg (reset! svg-a (. js/d3 select "#d3cmp")) + simulation (reset! simulation-a + (.. js/d3 + (forceSimulation) + (force "link" (.. js/d3 (forceLink) + (id #(.-id %)) + (distance (constantly 100)))) + (force "charge" (.. js/d3 (forceManyBody) + (strength (constantly -100)))) + (force "center" (. js/d3 forceCenter (/ width 2) (/ height 2))))) + + dragstarted (fn [d] + (when (zero? (.. js/d3 -event -active)) + (.. simulation + (alphaTarget 0.3) + (restart))) + + (set! (.-fx d) (.-x d)) + (set! (.-fy d) (.-y d))) + + dragged (fn [d] + (set! (.-fx d) (.. js/d3 -event -x)) + (set! (.-fy d) (.. js/d3 -event -y))) + + dragended (fn [d] + (when (zero? (.. js/d3 -event -active)) + (.. simulation + (alphaTarget 0.0))) + (set! (.-fx d) nil) + (set! (.-fy d) nil)) + + link (.. (. svg append "g") + (attr "class" "links") + (selectAll "line") + (data links) + (enter) + (append "line") + (attr "stroke-width" (fn [d] (Math/sqrt (.-value d)))) + ) + + link-sel (.. svg + (selectAll ".links > line")) + + node (.. (. svg append "g") + (attr "class" "nodes") + (selectAll "circle") + (data nodes) + (enter) + (append "g") + (attr "class" "node")) + + circle (.. node + (append "circle") + (attr "r" #(or (gob/get % "r" 10))) + (attr "fill" (fn [d] (color (.-group d)))) + (call (.. (. js/d3 drag) + (on "start" dragstarted) + (on "drag" dragged) + (on "end" dragended)))) + + circle-sel (.. svg + (selectAll ".node > circle")) + + label (.. node + (append "text") + (attr "dy" ".35em") + (text #(gob/get % "title" ""))) + + label-sel (.. svg + (selectAll ".node > text")) + + ticked (fn [] + (.. link-sel + (attr "x1" (fn [d] (.. d -source -x))) + (attr "y1" (fn [d] (.. d -source -y))) + (attr "x2" (fn [d] (.. d -target -x))) + (attr "y2" (fn [d] (.. d -target -y)))) + (.. circle-sel + (attr "cx" (fn [d] (min-max (.. d -r) (.. d -x) (- width (.. d -r))))) + (attr "cy" (fn [d] (min-max (.. d -r) (.. d -y) (- height (.. d -r)))))) + (.. label-sel + (attr "x" #(+ (.-x %) 2 (.-r %))) + (attr "y" #(+ (.-y %)))) + nil) + ] + + (.. simulation + (nodes nodes) + (on "tick" ticked)) + + (.. simulation + (force "link") + (links links)))) + :d3-enter (fn [ratom] + (let [graph (trace->sub-graph @ratom) + nodes (clj->js (:nodes graph)) + links (clj->js (:links graph)) + + svg @svg-a + color @color-a + simulation @simulation-a + + dragstarted (fn [d] + (when (zero? (.. js/d3 -event -active)) + (.. simulation + (alphaTarget 0.3) + (restart))) + + (set! (.-fx d) (.-x d)) + (set! (.-fy d) (.-y d))) + + dragged (fn [d] + (set! (.-fx d) (.. js/d3 -event -x)) + (set! (.-fy d) (.. js/d3 -event -y))) + + dragended (fn [d] + (when (zero? (.. js/d3 -event -active)) + (.. simulation + (alphaTarget 0.0))) + (set! (.-fx d) nil) + (set! (.-fy d) nil)) + + link (.. (. svg append "g") + (attr "class" "links") + (selectAll "line") + (data links) + (enter) + (append "line") + (attr "stroke-width" (fn [d] (Math/sqrt (.-value d))))) + + node (.. (. svg append "g") + (attr "class" "nodes") + (selectAll "circle") + (data nodes) + (enter) + (append "g") + (attr "class" "node")) + + circle (.. node + (append "circle") + (attr "r" #(or (gob/get % "r" 10))) + (attr "fill" (fn [d] (color (.-group d)))) + (call (.. (. js/d3 drag) + (on "start" dragstarted) + (on "drag" dragged) + (on "end" dragended)))) + + + label (.. node + (append "text") + (attr "dy" ".35em") + (text #(gob/get % "title" ""))) + + ] + + (.. simulation + (force "link") + (links links)) + ) + + + + + (println "D3 did enter"))} + traces-ratom)] + [:hr]]))) + + + + + +;;;;;;;;;;;;;;;;;;;;;;;;;; + + + + + + + + + + + +;(ns todomvc.subvis2 +; (:require [reagent.core :as r])) +; +;(defn prep-parent [parent] +; (-> parent +; (.selectAll "*") +; .remove) +; (.append parent "g")) +; +;(defn draw-circle [parent] +; (let [OUTER_WIDTH 208 +; OUTER_HEIGHT 208 +; parent (-> (js/d3.select parent) +; (.select "svg") +; (.attr "width" OUTER_WIDTH) +; (.attr "height" OUTER_HEIGHT))] +; +; ; Clear everything under the parent node so we can re-render it. +; (prep-parent parent) +; +; (let [top-node (.select parent "g") +; COLOR_CIRCLE "#fdb74a"] +; (.attr top-node +; "transform" +; (str "translate(" +; (/ OUTER_WIDTH 2.0) +; "," +; (/ OUTER_HEIGHT 2.0) +; ")")) +; +; (-> top-node +; (.append "circle") +; (.attr "cx" 0) +; (.attr "cy" 0) +; (.attr "r" 100) +; (.style "stroke" COLOR_CIRCLE))))) +; +;(defn d3-gauge [args] +; (let [dom-node (r/atom nil)] +; (r/create-class +; {:component-did-update +; (fn [this old-argv] +; (let [[_ args] (r/argv this)] +; ;; This is where we get to actually draw the D3 gauge. +; (draw-circle @dom-node))) +; +; :component-did-mount +; (fn [this] +; (let [node (r/dom-node this)] +; ;; This will trigger a re-render of the component. +; (reset! dom-node node))) +; +; :reagent-render +; (fn [args] +; ;; Necessary for Reagent to see that we depend on the dom-node r/atom. +; ;; Note: we don't actually use any of the args here. This is because +; ;; we cannot render D3 at this point. We have to wait for the update. +; @dom-node +; [:div.gauge [:svg]])}))) +; +; +;(def circles (r/atom [{:name "circle 1" +; :x 10 +; :y 10 +; :r 20 +; :color "black"} +; {:name "circle 2" +; :x 35 +; :y 35 +; :r 15 +; :color "red"} +; {:name "circle 3" +; :x 100 +; :y 100 +; :r 30 +; :color "blue"} +; {:name "circle 4" :x 55 :y 55 :r 10 :color "red"}])) +; +;(defn new-circle [] +; {:name (str (gensym "circle")) +; :x (rand-int 400) +; :y (rand-int 400) +; :r (+ 10 (rand-int 20)) +; :color (str "hsl(" (rand-int 360) ", 100%, 50%)")}) +; +;(defn add-new [n] +; (swap! circles conj (new-circle))) +; +;(defn d3-inner [data] +; (r/create-class +; {:reagent-render (fn [] [:div [:svg {:width 400 :height 800}]]) +; +; :component-did-mount (fn [] +; (let [d3data (clj->js data)] +; (.interval js/d3 add-new 1000) +; (.. js/d3 +; (select "svg") +; (selectAll "circle") +; (data d3data (fn [d i] (.-name d))) +; enter +; (append "circle") +; (attr "cx" (fn [d] (.-x d))) +; (attr "cy" (fn [d] (.-y d))) +; (attr "r" (fn [d] (.-r d))) +; (attr "fill" (fn [d] (.-color d)))))) +; +; :component-did-update (fn [this] +; (let [[_ data] (r/argv this) +; d3data (clj->js data) +; circles (.. js/d3 +; (select "svg") +; (selectAll "circle") +; (data d3data (fn [d i] (when d (.-name d)))))] +; (.. circles +; ;(attr "cx" (fn [d] (.-x d))) +; ;(attr "cy" (fn [d] (.-y d))) +; ;(attr "r" (fn [d] (.-r d))) +; enter +; (append "circle") +; (attr "cx" 200) +; (attr "cy" 200) +; (attr "r" 500) +; (transition) +; (attr "cx" (fn [d] (.-x d))) +; (attr "cy" (fn [d] (.-y d))) +; (attr "r" (fn [d] (.-r d))) +; (attr "fill" (fn [d] (.-color d)))) +; (.. circles +; exit +; remove)))})) +; +;(defn outer [] +; (let [data circles #_(subscribe [:circles])] +; (fn [] +; [d3-inner @data]))) +; +;