I would like to draw two different customCVGseries
using a single function; but, of course, this code (striped to a minimum) only returns the last one:
(defn make-label-with-line [x y key]
^{:key key}
[:> rvis/CustomSVGSeries {:onValueMouseOver (fn [] (reset! mouse-over? true))
:onValueMouseOut (fn [] (reset! mouse-over? false))
:data [{:x x :y y
:customComponent (fn [_ position-in-pixels]
(if (and @middle-button-pressed? @mouse-over?)
(reset! pos (calculate-xy position-in-pixels)))
(let [[delta-x delta-y] @pos]
(r/as-element [:g
[:text
[:tspan {:x delta-x :y (+ delta-y 18)} "Hidrógeno "]
[:tspan {:x delta-x :y (+ delta-y 36)} "Alfa"]]])))}]}]
^{:key (str key "line")}
[:> rvis/CustomSVGSeries {:data [{:x x :y y
:customComponent (fn []
(let [[delta-x delta-y] @pos]
(r/as-element [:g
[:polyline {:points [0 0 0 delta-y delta-x delta-y]
:stroke "black" :fill "none"}]])))}]}])
I tried wrapping both in a :div
, and even in a vector ([
and ]
), but I get errors (I can copy them if they are useful).
I need them to be two elements, and not one, because I need that only the first be aware of :onValueMouseOver
and :onValueMouseOut
events: I need them to 'drag' the label (:text
) over the graph, and the polyline
can be too big and stretch over a big portion of the graph, and capture unwanted events.
In this screenshot I show the area captured by those events when I use the following working code:
one customSVGseries is "too" big
(r/as-element [:g
[:polyline {:points [0 0 0 inc-y inc-x inc-y]
:stroke "black" :fill "none"}]
[:text
[:tspan {:x delta-x :y (+ delta-y 18)} "Hidrógeno "]
[:tspan {:x delta-x :y (+ delta-y 36)} "Alfa"]]])
I even thought that using two line
s (instead of a polyline
) the "area" would more "limited"; I mean, that the user should put the mouse exactly over the line
s to trigger the events. But I was wrong: the area subjected to the events is the same.
(r/as-element [:g
[:line {:x1 0 :y1 0 :x2 0 :y2 inc-y :stroke "black"}]
[:line {:x1 0 :y1 inc-y :x2 inc-x :y2 inc-y :stroke "black"}]
[:text
[:tspan {:x delta-x :y (+ delta-y 18)} "Hidrógeno "]
[:tspan {:x delta-x :y (+ delta-y 36)} "Alfa"]]])
I was thinking in using two functions (one for the text
and one for the polyline
); but there should be a better way! :) What bothers me most is that I must be missing something obvious... :/
I tried the solution proposed by Eugene Pakhomov, but now neither series show up in the graph; I get no errors either (it's as if they were commented-out...). I copy the full function in case I'm missing something obvious:
(let [mouse-over? (atom false)
pos (atom [0 18])]
(defn crear-etiqueta [x y key position]
(if-not (= position [0 18]) (reset! pos position))
[:<>
^{:key key}
[:> rvis/CustomSVGSeries {:onValueMouseOver (fn [d] (reset! mouse-over? true))
:onValueMouseOut (fn [d] (if-not @button-cen-pressed? (reset! mouse-over? false)))
:data [{:x x :y y
:customComponent (fn [_ position-in-pixels]
(if (and @button-cen-pressed? @mouse-over?)
(reset! pos (calcular-xy-etiqueta position-in-pixels)))
(let [[inc-x inc-y] @pos]
(r/as-element [:g {:className "etiqueta"}
[:text
[:tspan {:x inc-x :y (+ inc-y 0)} "Hidrógeno "]
[:tspan {:x inc-x :y (+ inc-y 18)} "Alfa"]]])))}]}]
^{:key (str key "line")}
[:> rvis/CustomSVGSeries {:data [{:x x :y y
:customComponent (fn []
(let [[inc-x inc-y] @pos]
(r/as-element [:g {:className "etiqueta"}
[:polyline {:points [0 (if (< inc-y 5) -10 5) 0 inc-y inc-x inc-y]
:stroke "black" :fill "none"}]])))}]}]
]))
I'm more confused. Reading about the use of []
and ()
here, I called crear-etiqueta
like this: [crear-etiqueta 100 100 "key" [0 0]]
... ¡But that was even worst! I even tried the simplest case, and didn't work:
(defn test-component [x y]
^{:key key}
[:> rvis/CustomSVGSeries {:data [{:x x :y y :customComponent "square" :size 30}]}])
(defn line-chart []
[:div
[:> rvis/FlexibleXYPlot
[...]
[test-component 176 550]]])
But if I change [test-component 176 550]
with (test-component 176 550)
, it works.
Please excuse my wanderings; I realize I'm still learning.
The solution of Eugene Pakhomov certainly works... at least when the function for creating the two elements is called "simply". Now I have another problem:
The function should be called over a collection of items, each one having this form:
{:etiqueta-1 {:y 6071.758666687525, :x 176.60089063427614, :texto ["176.6"], :pos [0 18], :mouse-over? false}}
So I tried to insert them like this:
(into [:> rvis/FlexibleXYPlot {...}]
(doall (for [[id {:keys [x y texto]}] (:etiquetas (get @perfiles @perfil-activo))]
(crear-etiqueta id x y texto [@perfil-activo :etiquetas id])))
But this doesn' work. It shows nothing. I updated the repo to show this.
Whenever you need to return multiple elements from a single Reagent component, use React fragments. In Reagent, it means wrapping those multiple elements in a single [:<> ...]
.
But seems like you're out of luck when it comes to react-vis
and React fragments - the library doesn't actually render the children (elements created with rvis/CustomSVGSeries
) directly but rather it extracts all the information from them and then constructs what it needs to based on that information. React fragments aren't series themselves, and react-vis
doesn't go inside fragments.
What you can do, however, is to make your series-creating function return a simple vector of series (no need for the :key
metadata), and into
that vector inside the Hiccup vector that creates rvis/FlexibleXYPlot
element:
(into [:> rvis/FlexibleXYPlot {...}]
(create-vector-of-series))