This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-06-07
Channels
- # announcements (2)
- # asami (2)
- # babashka (15)
- # babashka-sci-dev (31)
- # beginners (130)
- # boot (4)
- # cider (5)
- # circleci (12)
- # clj-kondo (10)
- # cljs-dev (8)
- # clojure (7)
- # clojure-czech (14)
- # clojure-europe (19)
- # clojure-france (5)
- # clojure-uk (2)
- # clojured (23)
- # clojurescript (11)
- # conjure (8)
- # datomic (5)
- # emacs (1)
- # etaoin (8)
- # events (2)
- # fulcro (10)
- # graalvm (18)
- # gratitude (1)
- # holy-lambda (16)
- # honeysql (4)
- # introduce-yourself (1)
- # jobs (2)
- # kaocha (3)
- # london-clojurians (1)
- # lsp (53)
- # off-topic (16)
- # other-languages (2)
- # pathom (4)
- # pedestal (3)
- # podcasts (1)
- # portal (10)
- # re-frame (69)
- # reitit (2)
- # shadow-cljs (11)
- # vim (7)
- # xtdb (29)
Is it a good idea to store large [:div]
s in db? I just wanna subscribe and show in different places.
isn’t this the idea of a component? ie store in the db the data required to render that div and then have multiple places render that component by subscribing to the data
App-db is for your app's state. What "app's state" means is up to you. If you store just a Hiccup vector in the DB and not some stateful component itself, I don't see anything wrong with that.
As long as that Hiccup vector itself doesn't depend on anything else, like ratoms. Because otherwise it would become a good bowl of spaghetti.
I didn't want to come back here hoping for help again, but here I am... I just absolutely can't get this to work with re-frame: https://observablehq.com/@d3/modifying-a-force-directed-graph?collection=@d3/d3-force Or here's a cljs example: https://github.com/scotthaleen/cljs-d3-force-direct-graph/blob/master/src/haleen/fd/graph/core.cljs I've been through the "form-3" component doc a dozen times now, but just can't figure out what goes where.
Do I need to make an [:svg ]
in :reagent-render
? And if so, how do I reference it from :component-did-mount
?
(defn graph-outer []
(let []
(reagent/create-class
{:reagent-render (fn [] [:div [:svg {:id "svg-graph" :width 500 :height 500}]])
:component-did-mount
(fn [this old-argv]
(let [svg (-> js/document (.getElementById "svg-graph"))
links (-> svg
(.append "g")
(.attr "class" "links")
(.selectAll "line")
(.data (.-links graph))
(.enter)
(.append "line")
(.attr "stroke-width" (fn [d] (Math/sqrt (.-value d))))
)
nodes (doto
(-> svg
(.append "g")
(.attr "class" "nodes")
(.selectAll "circle")
(.data (.-nodes graph))
(.enter)
(.append "circle")
(.attr "r" 5)
(.attr "fill" (fn [d] (color (.-gropu d))))
)
(.append "title")
(.text (fn [d] (.-id d))))]
[links nodes]
))
;:componet-did-update
})))
You appear to not be using d3 at all in your code :thinking_face: I’d expect your code to error out because many of those properties like .enter
.attr
.enter
etc. do not exist on HTML element nodes.
In the cljsjs d3 file you linked, you can see here that they’re using d3 to select (presumably) a node instead of just .getElementById
https://github.com/scotthaleen/cljs-d3-force-direct-graph/blob/master/src/haleen/fd/graph/core.cljs#L17-L21
I'm using d3 outside of that block. But it looks like at the time I do the .getElementByID it's nil.
Ah I see what you mean. I replaced the getElementById with (-> d3 (.select "#svg-graph"))
There's not an "items" in any of the example code or data. Or in my code. That's puzzling.
I think data.js is this: https://github.com/d3/d3-selection/blob/main/src/selection/data.js
But there isn't an items
there either.
I think this is where it becomes a re-frame question again. Is component-did-mount
the wrong place to put the d3 code?
re-frame is the wrong place for this period, haha. Your questions are entirely reagent related.
Yes:
(defn app []
[:<>
[:div {:id "main"} [graph-outer][sidebar]]
[cmdline]
]
)
The sidebar and cmdline components render.Are they reagent questions? I feel like this has a lot to do with re-frame's loop being at odds with D3's animation loop.
Yes, they’re reagent. You’re not using re-frame at all in the code you’ve demonstrated 😄
and component-did-mount
is used for code that you want to be executed once, after the component is mounted (like initialization code). component-did-update
runs whenever the component’s state/properties change. These are react lifecycle methods btw, not exclusive to reagent.
Right, so then the svg probably is being rendered; your d3 code in your component-did-mount
is probably just not populating it with what you expect. Nonetheless it should be there in your browser and you should be able to find it with your browsers devtools or do a document.getElementById
from the browser’s console to verify.
If you’re not already, I’d recommend that you experiment in your browser-connected Cljs REPL with d3, targeting that svg, until you see the expected results displayed in the browser. Then plop what worked into your component-did-mount
I have already mentioned it before - do not use getElementById
with React. Especially when you don't have a good grasp on React components' life cycle.
Use React refs instead.
@U02UHTG2YH5 The amount of interop could probably be reduced, but I don't know how D3 manages its data to do that.
(ns app.core
(:require [clojure.browser.dom]
[reagent.core :as r]
[reagent.dom]
["react" :as react]
["d3" :as d3]))
(defn d3-graph [#_{:keys [width height graph]}]
(let [^js ref (react/createRef)
simulation (atom nil)
link (atom nil)
node (atom nil)
tick! (fn []
(.. ^js @node
(attr "cx" #(.-x ^js %))
(attr "cy" #(.-y ^js %)))
(.. ^js @link
(attr "x1" #(.. ^js % -source -x))
(attr "y1" #(.. ^js % -source -y))
(attr "x2" #(.. ^js % -target -x))
(attr "y2" #(.. ^js % -target -y))))
get-id #(.-id ^js %)
color (.scaleOrdinal d3 (.-schemeTableau10 d3))
set-new-data! (fn [{:keys [nodes links]}]
(let [old (js/Map. (.. ^js @node (data) (map (fn [d] #js [(get-id d) d]))))
nodes (.map (clj->js nodes) #(js/Object.assign (or (.get old (get-id %)) #js {}) %))
links (clj->js links)]
(doto ^js @simulation
(.nodes nodes)
(.. (force "link") (links links))
(.. (alpha 1) (restart)))
(swap! node (fn [^js node]
(.. node
(data nodes get-id)
(join (fn [^js enter]
(.. enter
(append "circle")
(attr "r" 8)
(attr "fill" #(color (get-id %)))))))))
(swap! link (fn [^js link]
(.. link
(data links (fn [^js d]
(str (.. d -source -id) \tab (.. d -target -id))))
(join "line"))))))]
(r/create-class
{:display-name
"d3-graph"
:component-did-mount
(fn [this]
(when-let [svg-node (.-current ref)]
(let [{:keys [width height graph]} (r/props this)
svg (.. d3
(select svg-node)
(attr "width" width)
(attr "height" height)
(attr "viewBox" #js [(/ width -2) (/ height -2) width height]))]
(reset! link (.. svg
(append "g")
(attr "stroke" "#000")
(attr "stroke-width" 1.5)
(selectAll "line")))
(reset! node (.. svg
(append "g")
(attr "stroke" "#fff")
(attr "stroke-width" 1.5)
(selectAll "circle")))
(reset! simulation (.. d3
(forceSimulation)
(force "charge" (.. d3 (forceManyBody) (strength -1000)))
(force "link" (.. d3 (forceLink) (id get-id) (distance 200)))
(force "x" (.forceX d3))
(force "y" (.forceY d3))
(on "tick" tick!)))
(when graph
(set-new-data! graph)))))
:component-did-update
(fn [this [_ old-props]]
(let [{:keys [graph]} (r/props this)]
(when (not= graph (:graph old-props))
(set-new-data! graph))))
:component-will-unmount
(fn [_this]
(when-let [^js sim @simulation]
(.stop sim)))
:reagent-render
(fn []
[:svg {:ref ref}])})))
(defn radio-button [{:keys [label checked? on-change]}]
(r/with-let [id (gensym "radio-button-")]
[:<>
[:input {:id id
:type :radio
:checked checked?
:on-change (fn [_evt]
(on-change))}]
[:label {:for id}
label]]))
(defn app-view []
(r/with-let [graph1 {:nodes [{:id "a"} {:id "b"} {:id "c"}]
:links []}
graph2 (assoc graph1 :links [{:source "a" :target "b"}
{:source "b" :target "c"}
{:source "c" :target "a"}])
graph3 {:nodes [{:id "a"} {:id "b"}]
:links [{:source "a" :target "b"}]}
current-data (r/atom graph1)]
[:div
[:fieldset
[radio-button {:label "Graph 1"
:checked? (= @current-data graph1)
:on-change #(reset! current-data graph1)}]
[radio-button {:label "Graph 2"
:checked? (= @current-data graph2)
:on-change #(reset! current-data graph2)}]
[radio-button {:label "Graph 3"
:checked? (= @current-data graph3)
:on-change #(reset! current-data graph3)}]]
[d3-graph {:width 400
:height 400
:graph @current-data}]]))
(defn ^:export ^:dev/after-load main []
(reagent.dom/render [app-view] (clojure.browser.dom/get-element :app)))
@U2FRKM4TW Yes, I remember the warning to not use that function now. I'm so far in the deep end that I missed the rescue flotation device 😉 What is react/createRef? I read the docs from React and it seems attached to a dom element, but I can't tell from this code what it's getting attached to.
In that :reagent-render
, it's [:svg {:ref ref}]
.
If you aren't using an IDE that highlights all usages of the symbol under your text cursor, I definitely suggest getting one. ;)
I see...
(defn d3-graph...
(let [^js ref (react/createRef)
...
...
(r/create-class
...
:reagen-render (fn [] [:svg {:ref ref})
It's not obvious to me know ref
knows it's connected to the svg. Or are the react docs just misleading and the ref is just some sort of uuid?
Yeah, but you said: > are the react docs just misleading So I'm trying to understand what you're reading exactly that you find misleading.
I don't understand what ref
is at the time createRef
executes. I see it's set as the ref
attribute on the svg
but I don't understand otherwise how the two are associated.
The React docs make it look like there is some magic in the constructor for a component: https://reactjs.org/docs/refs-and-the-dom.html#creating-refs
when [:svg {:ref ref}]
is mounted, the value of ref
will also update accordingly and ref.current
will point to the SVG element node.
At the time of createRef
, ref.current
won’t yet be referencing anything
there is behind the scenes React magic that goes on that sets the ref, it’s not explicit in the code
@U02UHTG2YH5 It seems to me like you're lacking a lot of crucial information on how it all works together. At this point, it's impossible for me to gauge what you're lacking exactly, so I'd definitely suggest going through all Reagent documentation, while also referring to React docs whenever you see some React concepts being mentioned that you don't understand. Don't just try to guess. It's all documented. With numerous examples, right in the Reagent repo.
> So what is createref returning? Refs are plain objects with a single field: https://reactjs.org/docs/refs-and-the-dom.html#accessing-refs
Oh, I suppose, I should restate it as "opaque object with a current
field", because we don't know what else might potentially be there at some point.
Yeah, I'm afraid that without stopping everything to start over and learn JS and React, this is all kind of pointless. I was hoping that Clojurescript would do for JS was Clojure did for Java.
It's not about CLJS at all. You're using Reagent, which is a relatively leaky abstraction. But you also want to use D3, which does not directly work with React, so you have to actually rely on some leaks in the Reagent abstractions. It would be exactly the same thing in Clojure if you were to use, say, JavaFX.
And frankly I've had a hard time groking D3 in plain JS as well. I was able to get some things working, but just couldn't get over the hump to integrate it in my cljs project.
> JavaFX seems so much easier Maybe because you've already had some prior experience with it. But conceptually it's exactly the same - you would have to rely on interop a lot, and you'd have to know the underlying functionality very well.
That’s all subjective, haha. I agree that the frontend can be a tricky world to navigate. But the grander point here is that no matter the abstraction, nothing is magic and everything will take varying degrees of understanding. I think the difficulty here is also compounded because you’re working with so many different technologies that you don’t have a good grasp on all at the same time 💥 🧠
I don't think that's true because I've done so much with Java interop that hasn't been this difficult.
Yes, and it's hard to find a good explanation for most of it. Starting at ground zero is mind-numbing, and I just keep checking out every time I try to follow tutorials or learning paths with JS. I can't find one that's not geared to total newbies.
It's still an experience, and Java along with its ecosystem don't change that much for the experience to just stop being relevant. It's hard for me to suggest any JS learning material though - I myself have never studied it, I mostly used an still use MDN.
Right. I don't know what I don't know here. Where did you learn it all if not from some good docs or a tutorial?
> good docs That's what MDN is. And I habitually read a lot of code. Programming is my main hobby, basically.
Start small. :) Trying to handle Reagent + React + D3 + JS all at once when you know none of them is a recipe for disaster. You don't learn how to ride a bicycle by juggling flaming axes while riding a unicycle in a circus.
lol well I don't know what to say I've been through this: http://reagent-project.github.io/ So simple! I'm afraid that I would need a year to become a JS expert to make coming back to this worthwhile.