Fork me on GitHub
#reagent
<
2016-08-17
>
sandbags09:08:35

I’m interested in whether anyone here has experience of drawing text using SVG from a reagent app? The problem I am facing is knowing, in advance, the width and height of the text to position it correctly. Most solutions I have found use a “hidden render” technique that depends on creating a DOM node and measuring it. That doesn’t appear to be open to me here. Any suggestions?

sandbags09:08:48

(might be a bit slow to respond, just getting off a train)

sandbags09:08:38

wondering if this might be too specific a question for this group, bit of an edge case

timothypratley18:08:48

@sandbags: I like using SVG from reagent, and text... but I tend to just center it over a point and adjust the font size. What are your constraints in terms of positioning? it sounds like an interesting problem.

timothypratley18:08:17

If you just want to get the width/height I think you could do that with :ref

timothypratley18:08:53

(see discussion about a page up)

sandbags18:08:07

@timothypratley: I’m drawing a spider diagram and I wanted to be able to position the axis labels based on the text centre

sandbags18:08:22

in my specific case i know what the labels are so I can just fix it

sandbags18:08:38

but the abstract-loving little guy inside of me isn’t happy with this solution

sandbags18:08:37

I’ve found generating SVG in reagent a treat

sandbags18:08:56

and once i did a little remedial trig drawing the spider diagram was simple enough

sandbags18:08:39

but when it came to positioning the axis labels… i’m having to fall back to hand estimation

sandbags18:08:06

Okay refs look interesting

sandbags19:08:17

does reagent support refs?

sandbags19:08:27

the first google hit is an issue that seems to be saying not

timothypratley19:08:55

^^ I found this helpful

sandbags19:08:01

many thanks

sandbags19:08:48

okay what do i need to read to understand that? 🙂

sandbags19:08:20

I should mention i have only passing knowledge of react and the underpinnings for using it in CLJS

sandbags19:08:45

i don’t know what that snippet is doing, or how 🙂

timothypratley19:08:58

Hmmm what part is the most confusing? 🙂

sandbags19:08:13

A possible response here is “Matt go learn react"

sandbags19:08:23

well in the first place i have no idea what “foo” does

timothypratley19:08:30

no no no, just trying to find the best resource to direct you to.

sandbags19:08:38

what is a better name for this function?

sandbags19:08:56

i don’t know what is being achieved by this function

sandbags19:08:21

oh my train is coming in, but i’m interesting so i hope you’ll still be around later and i can ask a few more questions

timothypratley19:08:45

foo is a component (wow that doesnt help much huh? lol) yup, i'll try to make a more detailed answer later today

timothypratley19:08:55

<-- this is the best first stop

timothypratley19:08:14

Understanding components have 3 forms for declaration -- if you get that, you should be good with the refs example.

pesterhazy19:08:27

@sandbags: I've updated the gist with a bit more context

sandbags20:08:27

@pesterhazy: thanks, thats very useful. Btw is there a significance to the use of ! in the variable name !ref ?

sandbags20:08:39

i’m wondering if i’ve missed some clojurism

sandbags20:08:21

@timothypratley: ah, i’ve not really tackled reframe yet (just skimmed some of the docs) but it seems there is some good stuff here

sandbags20:08:01

I do like the honesty with which the author lies and distorts

gadfly36120:08:11

@sandbags ! usually means you are mutating something. Think swap! in relation to an atom or something of that nature

sandbags20:08:26

but isn’t that, by definition, in use of a ref or atom?

sandbags20:08:38

or is this marking that it is an atom

sandbags20:08:47

i’ve not seen it used before, perhaps it’s a good practice

sandbags21:08:37

although i’m not clear since in principle the places where either can be mutated are fairly delineated and, at other times, their value can be relied upon… or so I think

gadfly36121:08:55

@sandbags sorry i didnt actually look at the code snippet. I dont know the prefix use of ! In that context. I wouldn't use it on an atom itself.

sandbags21:08:33

if it’s being used as a kind of flag to the reader “Hey this is an atom” I guess that could be useful although in practice you see the @ so...

sandbags21:08:48

perhaps it has some other significance

gadfly36121:08:50

Yeah, i havent seen the prefix bang, but perhaps i just havent noticed. Ive only ever used it at the end to signal that i am doing a mutation

gadfly36121:08:53

Hopefully someone else knows, sorry to add noise haha 😁

sandbags21:08:49

@timothypratley: that was very useful thank you and @pesterhazy’s gist is now relatively clearer. I presume I am interested in the componentDidMount react lifecycle method

sandbags21:08:13

looking at the source of create-class the callback is :component-did-mount (fn [this]) where i assume this is the DOM node of the mounted component?

sandbags21:08:52

or maybe some reagent data structure containing a reference to it

sandbags21:08:09

hrmm… reagent.impl.component is a bit gnarly

pesterhazy21:08:47

yeah the leading ! is a somewhat idiosyncratic convention some people use to indicate reference types (mainly atoms)

pesterhazy21:08:13

yes, this is the component

sandbags21:08:18

@pesterhazy: when you say “the component” do you mean the DOM node representing the component? Or the reagent data structure that you pass to create-class?

sandbags21:08:39

(or something else i guess)

gadfly36121:08:46

@sandbags: you may find (reagent/dom-node this) useful. (Keep your eyes on this issue tho ... react may do away with findDOMNode https://github.com/reagent-project/reagent/issues/251)

pesterhazy21:08:56

as @gadfly361 says, it's not the DOM node but the internal React representation of the component, I think

sandbags21:08:07

I am trying to grok the example @gadfly361 just posted (thank you @gadfly361)

sandbags21:08:10

so if you pass :ref #() as an attribute of some markup your component generates, that function will be called at some point in the render process passing in the DOM node reference that you can stash away for later use, am I understanding that right?

pesterhazy21:08:35

that's my assumption, yes

gadfly36121:08:38

I still use dom-node at the moment, haven't made the switch to :ref

sandbags21:08:46

do you need to use a form-3 component for that?

sandbags21:08:52

or you can just use :ref on any hiccup?

sandbags21:08:02

i mean, i guess i can test this quite easily myself...

pesterhazy21:08:12

if you're in doubt, try prn'ing the data structure that is passed to the ref attributes and see what you get

pesterhazy21:08:23

you don't need a form-3 component, no

pesterhazy21:08:53

though at some point you'll want to do something with the ref, and a lifecycle method is probably the best place for that

sandbags21:08:01

hrmm… perhaps i did something wrong, i added a :ref (fn [x] ..) but it doesn’t seem to have been called

sandbags21:08:29

no, sorry, i am an idiot

sandbags21:08:58

thank you @timothypratley, @pesterhazy, and @gadfly361 I’m not there yet but at least I know how I will get there

pesterhazy21:08:58

what does it give you?

sandbags21:08:07

Inside ref: #object[SVGSVGElement [object SVGSVGElement]]

pesterhazy21:08:30

that looks like a dom node 🙂

sandbags21:08:34

indeed 🙂

sandbags21:08:03

i wonder how fast this will all work, my hope is that it will be essentially instantaneous and you won’t see the text “snap” into place

sandbags21:08:12

my next task is PDF generation… I suspect it won’t be as much fun… it’s not even a day job!

sandbags21:08:08

@pesterhazy: looking again at your gist is your !ref a clojure atom or a reagent atom?

sandbags21:08:46

i’m guessing from what i read in the reframe docs you want to use a clojure atom as you don’t want it entangled with the tracking thing

pesterhazy21:08:02

clojure atom, as you don't need to keep track of updates to it

pesterhazy21:08:48

good luck with this!

pesterhazy21:08:12

reagent is severely lacking in documentation, we should start putting up stuff like this on the wiki

pesterhazy21:08:53

there's especially too little information out there on how to use the lifecycle methods in reagent

sandbags21:08:17

yes, it actually looks pretty straightforward but I would have struggled to infer any of this without your help

pesterhazy21:08:40

yeah, reading the reagent source is not a walk in the park

pesterhazy21:08:04

it's pretty awesome otherwise though 🙂

gadfly36121:08:14

I think re-frame docs cover pretty well +1 to link @timothypratley shared earlier

sandbags21:08:06

indeed, but i wouldn’t necessarily ever have thought to head to re-frame

pesterhazy21:08:34

re-frame docs cover a lot of missing surface area, but not all (e.g. lifecycle methods -- what parameters do they receive exactly?)

sandbags21:08:35

hrmm… trying to wrap my head around this. I write my component render function and put a :ref fn in to get access to the DOM node, after it’s mounted I get the width & height of the bounding box but then I want to go back and amend the :x and :y values passed into the component but… mental model breaks

gadfly36121:08:26

We should make it more prominent, but this is in the reagent wiki: https://github.com/reagent-project/reagent/wiki/Creating-Components

sandbags21:08:09

anyone see where i’ve made a mistake here?

timothypratley21:08:28

did-mount will happen before render

sandbags21:08:41

where am i passing the values from the outer function into the component itself?

timothypratley21:08:02

the args to the render fn... you are doing that part right.

timothypratley21:08:25

they don't actually pass through as such

gadfly36121:08:33

forgot reagent/create-class

timothypratley21:08:42

they are 'available' at creation, and 'available' at render.

sandbags21:08:54

yes, stupid

sandbags21:08:57

no create-class

gadfly36121:08:11

i do it all the time 🙂

timothypratley21:08:27

note that did mount fn takes a 'this' arg

timothypratley21:08:38

and you can call reagent/dom-node on it

timothypratley21:08:45

for the bounding box part

sandbags21:08:57

oh so i don’t need to stash it at all

sandbags21:08:21

although i do need to record that i’ve sampled the width & height

timothypratley21:08:29

well... it depends what you want the bounding box of

timothypratley21:08:34

I was just confused

timothypratley21:08:41

if you want the bounding box of the text

timothypratley21:08:52

then you should call bounding box in the ref fn

sandbags21:08:58

what i can’t get my head around though, is how i - having obtained the bounding box - go back and tell the component it has a different :x and :y

sandbags21:08:14

there’s also something about this form-3 function that isn’t right in my head… how do the values for :x, :y, and :label arrive at the reagent-render function if they go nowhere in my axis-label-component function...

timothypratley21:08:58

I'm thinking something like: (let [text-width (reagent/atom 0)] (fn [] [:text {:x @text-width :ref (fn [cmp] (reset! text-width ....)))} "oh my gawd"]))

sandbags21:08:05

@timothypratley: i wasn’t sure if the node was fully rendered until after didmount

timothypratley21:08:07

but I haven't tested it.

timothypratley21:08:42

(if that works, consider setting a style "hidden" when the text-width is 0 (or nil)

sandbags22:08:06

Can you explain because I am pretty confused about this: I pass in values x, y, label to my component function axis-label-component but these values go nowhere because they are shadowed in the reagent-render function. How do these values end up getting passed to the reagent-render function at all?

timothypratley22:08:43

🙂 It's weird huh?

sandbags22:08:43

i’m looking at the example from the re-frame page and i see the same thing

sandbags22:08:49

i do not understand how this happening

timothypratley22:08:56

Let's talk about form 2 first:

timothypratley22:08:24

(defn a [x] (fn b [x] [:div "foo"]))

timothypratley22:08:48

a returns a function, that has hiccup in it

timothypratley22:08:06

say I have this code:

timothypratley22:08:17

and 'a' doesn't exist in the DOM

timothypratley22:08:32

a is going to get called

timothypratley22:08:39

with 1 as the argument

timothypratley22:08:58

but only once, when it gets added to the DOM

sandbags22:08:01

hang on, no it’s not

timothypratley22:08:11

b is going to get called lots of times

sandbags22:08:13

when you setup the component

timothypratley22:08:24

to render (if something changed that it depends on.

sandbags22:08:43

okay you’ve lost me

sandbags22:08:54

how does a depend on the DOM… you’re calling a surely

timothypratley22:08:09

I don't call a at all

sandbags22:08:11

when you setup another (containing) component

timothypratley22:08:13

a is in a vector

sandbags22:08:17

okay then i have something about face

sandbags22:08:58

maybe I am doing it completely wrong, here’s the actual code

sandbags22:08:31

I just replaced my use of [:text] directly within the containing component with a call to my axis-label-component component function

sandbags22:08:36

was that wrong?

timothypratley22:08:43

it's not 'wrong' but it's not the reactive way 😛 you should use a vector instead. [axis-label-component ....]

timothypratley22:08:04

functions get called immediately

timothypratley22:08:15

being in a vector is the way reagent can do things only if something changes.

sandbags22:08:07

okay back in business

sandbags22:08:27

I didn’t fully appreciate the vector thing as it (mostly) seemed to work using functions and I thought once I used a vector with arguments and nothing happened

sandbags22:08:12

okay so this is how the arguments end up in the render function

sandbags22:08:18

because it’s not actually a function call

sandbags22:08:24

that solves that

sandbags22:08:43

so reagent calls the component function with the arguments and, then, calls the render function with the same arguments (at some point)

sandbags22:08:16

a little confusing but i think i get there 😉

sandbags22:08:56

so this in the component-did-mount function looks like something else than what i see in the ref function

sandbags22:08:24

#object[Constructor [object Object]]

sandbags22:08:34

i hope this is the component where i can get at the x and y properties

timothypratley22:08:55

yeah, the this that context is the reagent component, not the element. calling reagent/dom-node on it will give you the element though if you want it.

timothypratley22:08:42

but I don't think you need to use the mount path at all for what you are doing.

sandbags22:08:16

assuming the dom node is fully rendered in the :ref fn i guess not

sandbags22:08:31

okay so i have measured my dom node width

sandbags22:08:33

now i have to change the x and y values of the text component to the adjusted values (and set visibility back from hidden)… but the only way I know of to get a component to update is to have it depend on a ratom

sandbags22:08:56

so i guess i actually do want my component to create a reagent atom rather than a clojure atom

sandbags22:08:01

in the setup

sandbags22:08:22

and have :x and :y in the component depend on the ratom

sandbags22:08:39

so it gets re-rendered when the atom is updated using the measured width

sandbags22:08:46

is that the right way of handling this? or did i miss something?

sandbags22:08:06

well blow me if it doesn’t actually work!!

sandbags22:08:45

comments on style or otherwise welcome

sandbags22:08:18

obv i can get rid of component-did-mount

sandbags22:08:40

i suppose i could use one atom with a map for width & visibility

sandbags22:08:46

not sure how expensive those ratoms are

sandbags22:08:20

and thanks @timothypratley for suggesting the visibility trick, mightn’t have thought of that

timothypratley22:08:50

nice! seeing you only specify the render fn, you don't need the create-class form at all (a function that returns a function will work here)

sandbags22:08:26

ah, yes indeed

sandbags22:08:55

thanks again to all three of you… i feel i have moved from unconscious incompetence to conscious incompetence in one evening

timothypratley22:08:38

https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor <-- setting this to 'middle' will center it on the x coord, which in hindsight seems to be what (- x (/ (:width @dom-data) 2)) does.

sandbags23:08:43

oh well, learning opportunity

sandbags23:08:52

plus i would need to do this anyway

sandbags23:08:14

since eventually i have to rotate the text and then inset it based on the height of the bounding box

sandbags23:08:56

of course i guess i should check there’s nothing in the SVG standard that will take care of that for me!