Fork me on GitHub
#re-frame
<
2022-06-07
>
dumrat16:06:47

Is it a good idea to store large [:div]s in db? I just wanna subscribe and show in different places.

dpsutton16:06:26

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

👍 1
p-himik16:06:59

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.

👍 1
p-himik16:06:58

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.

👍 1
🍝 1
Nundrum21:06:40

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.

Nundrum21:06:47

Do I need to make an [:svg ] in :reagent-render ? And if so, how do I reference it from :component-did-mount?

Nundrum22:06:08

(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                                                         
       })))                                                                          
                                                                                     

sansarip22:06:17

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

Nundrum22:06:44

I'm using d3 outside of that block. But it looks like at the time I do the .getElementByID it's nil.

Nundrum22:06:33

Ah I see what you mean. I replaced the getElementById with (-> d3 (.select "#svg-graph"))

👆 1
Nundrum22:06:57

Lots of other errors now, joy.

Nundrum22:06:40

There's not an "items" in any of the example code or data. Or in my code. That's puzzling.

Nundrum22:06:25

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.

sansarip22:06:33

The error says that it’s line 70 of your views.cljs file

Nundrum22:06:40

Ok, figured that out. The atom wasn't getting derefed.

Nundrum22:06:20

Now I just don't get anything in the page. No errors, but no graph either.

Nundrum22:06:59

I think this is where it becomes a re-frame question again. Is component-did-mount the wrong place to put the d3 code?

Nundrum22:06:22

Will it be the same code block in component-did-update?

sansarip22:06:29

Are you using your graph-outer component? e.g. [:> graph-outer …] ?

sansarip22:06:04

re-frame is the wrong place for this period, haha. Your questions are entirely reagent related.

Nundrum22:06:05

Yes:

(defn app []                                   
  [:<>                                         
   [:div {:id "main"} [graph-outer][sidebar]]  
   [cmdline]                                   
   ]                                           
  )                                            
The sidebar and cmdline components render.

Nundrum22:06:50

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.

Nundrum22:06:07

But I'll hop over there in a bit and ask.

sansarip22:06:27

Yes, they’re reagent. You’re not using re-frame at all in the code you’ve demonstrated 😄

1
sansarip22:06:57

If you put something else in your div like "Hello, World", does that render?

sansarip23:06:54

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.

Nundrum23:06:16

So yeah, I added a [:p "stuff here"] and it shows up.

sansarip23:06:40

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

p-himik23:06:48

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.

p-himik00:06:07

@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)))

Nundrum19:06:36

@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.

p-himik19:06:41

It's attached to the <svg> node.

p-himik19:06:22

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. ;)

Nundrum19:06:16

Yes, :set hls is nice, but I'm still not seeing how the two get connected.

Nundrum19:06:16

I guess I would have expected it in the [:svg] not in the surrounding let

p-himik19:06:37

I have no clue what you mean.

Nundrum19:06:29

I see...

(defn d3-graph...
   (let [^js ref (react/createRef)
     ...
     ...
     (r/create-class
       ...
       :reagen-render (fn [] [:svg {:ref ref})

Nundrum19:06:02

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?

p-himik19:06:42

What docs are you reading exacty?

Nundrum19:06:11

I'm reading the code you pasted in above 🙂

Nundrum19:06:32

I've been trying to follow through it.

p-himik19:06:20

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.

Nundrum19:06:21

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.

Nundrum19:06:05

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

sansarip19:06:21

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

sansarip19:06:59

there is behind the scenes React magic that goes on that sets the ref, it’s not explicit in the code

Nundrum19:06:03

So what is createref returning? Sorry, I'm still puzzled.

Nundrum19:06:46

Hmmm...pondering...pondering...maybe retiuclating splines, too.

p-himik19:06:17

@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.

p-himik19:06:48

> So what is createref returning? Refs are plain objects with a single field: https://reactjs.org/docs/refs-and-the-dom.html#accessing-refs

👆 1
p-himik19:06:41

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.

Nundrum19:06:52

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.

p-himik19:06:06

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.

👆 1
Nundrum19:06:19

Would it? JavaFX seems so much easier 😄

Nundrum19:06:14

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.

p-himik19:06:52

> 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.

sansarip19:06:44

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 💥 🧠

Nundrum19:06:06

I don't think that's true because I've done so much with Java interop that hasn't been this difficult.

p-himik19:06:20

Do you have prior experience with Java and its ecosystem?

Nundrum19:06:02

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.

Nundrum19:06:21

Yeah, but my Java experience was twelve years ago, maybe longer 😉

p-himik19:06:06

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.

Nundrum19:06:29

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?

p-himik20:06:17

> good docs That's what MDN is. And I habitually read a lot of code. Programming is my main hobby, basically.

Nundrum20:06:59

Mine too, but I've been burned by JS so many times that I'm just depressed.

p-himik20:06:02

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.

👆 1
Nundrum20:06:10

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.

p-himik20:06:18

Becoming an expert in any area takes a lot of time. But you don't need to be an expert here - after all, I'm definitely not a JS expert and yet I was able to write that code above. ;)