Okay this is a genuine re-frame question (unless its actually a reagent question): Why does this not re-render (explanation of expectation follows):
(defn filterable-plot
[table lookup]
(let [choices (vector-of-choices-producing-fn)
selection (rf/subscribe [:forms/selection])]
(fn []
(let [filterfn (if (first @selection)
(by-selection-filtering-fn lookup (first @selection))
(constantly true))]
[com/h-box
:children
[[vega/vega-chart
:spec (plots/some-plot (filter filterfn table))]
[com/multi-select :src (at)
:choices choices
:model selection
:on-change #(rf/dispatch [:forms/set-form-data :selection %])]]]))))
My intention is to have the chart just re-render (should be performant enough for what I do and spares me mutation of the data set via the Vega viewer API).
I figured this ought to be what happens on subscription deref, but it doesn't.
In principle the whole setup "works" - when I navigate somewhere else and then go back to the panel the plot is rendered with the filtered data set. I.e. if I force a "re-render".Here is my vega-chart component in case that's relevant:
(defn vega-chart
[& {:keys [width height id spec on-change]}]
(r/create-class
{:component-did-mount
(fn [_]
(-> (vegaEmbed (str "#" id) (clj->js spec))
(.then #(when on-change
(on-change (.-view %))))))
:component-did-update
(fn [_]
(-> (vegaEmbed (str "#" id) (clj->js spec))
(.then #(when on-change
(on-change (.-view %))))))
:reagent-render
(fn []
[:div {:id id
:style {:width width
:height height}}])}))Do you want for the chart to re-render only if the value of selection changes, and not the values of table and lookup?
Does the :component-did-update function get called when the value of selection changes?
What is plots/some-plot?
plots/some-plot returns a plain data vega-spec and does not contain any subscriptions
table and lookup don't change
I put a log statement in the update fn to check and it does run
So the update function does run, and I assume also with new data.
That leaves vegaEmbed as the only potential culprit so far - are you sure calling that function again on the same element but with a new spec is allowed?
BTW try to avoid using IDs with React. Most of the time, you can use a ref and provide the target node directly. Of course, vegaEmbed has to support it, but my default assumption is that if a function can accept a string ID, it can also accept the target element itself.
Oh, BTW x2 - how does that even work? You don't provide :id there.
Oh goodness, probably not! I kind of forgot that the rest of the world is not functional. So I'll either have to mutate the thing after all or tear it down completely and recreate it
I trimmed down html and css attributes for readability, the id slipped through. Come to think of it that was kind of foolish because the id is essential for the embed as you point out. I'll see if their API allows for embedding via noda ref
thanks!
BTW I myself use Vega in one of my projects.
I require this: ["react-vega" :refer [Vega]], and then it's this:
(ns ...
(:require [reagent.core :as r]
["react" :as react]
["react-vega" :refer [Vega]]
["vega" :as js-vega]
["vega-dataflow" :refer [Transform tupleid ingest]]
["vega-tooltip" :refer [Handler]]
["vega-util" :refer [Warn error inherits]]))
(defn scheme [n]
(js->clj (js-vega/scheme (name n))))
(def -vega-chart (r/adapt-react-class Vega))
(def default-config {:padding 5
:axis {:labelFont :inherit
:titleFont :inherit
:titleFontSize 13
:titleFontWeight 500}
:legend {:labelFont :inherit
:titleFont :inherit}
:title {:font :inherit
:subtitleFont :inherit}
:text {:font :inherit}})
(defn chart [{:keys [init-width init-height init-size renderer]
:or {renderer :svg}}]
(let [;; The width reacts to the viewport size changes,
;; the height stays the same.
width (r/atom (or init-width init-size 300))
height (or init-height init-size 300)
observer (atom nil)
ref (react/createRef)]
(r/create-class
{:display-name
"vega-chart"
:reagent-render
(fn [{:keys [spec data signal-handlers]}]
[:div {:style {:width "100%"
:display :inline-block}
:class "vega-chart-container"
:ref ref}
[-vega-chart (cond-> {:spec (assoc spec
:$schema ""
:config default-config)
:class-name "vega-chart"
;; Turning to JS manually to avoid Reagent converting cebab case to camel case.
:data (clj->js data)
:actions false
:width @width
:height height
:renderer renderer
:tooltip (.-call (Handler.))
:on-error js/console.error
:log-level Warn}
(seq signal-handlers)
(assoc :signal-listeners signal-handlers))]])
:component-did-mount
(fn [_]
(when-let [node (.-current ref)]
(reset! observer
(doto (js/ResizeObserver.
(fn [entries _observer]
(let [^js e (aget entries 0)]
(reset! width (-> e .-contentRect .-width)))))
(.observe node)))))
:component-will-unmount
(fn [_]
(when-let [^js obs @observer]
(when-let [node (.-current ref)]
(.unobserve obs node)
(reset! observer nil))))})))
It also tracks width changes.Sorry to bother you. I just tried copying the code you posted verbatim to start from there (except removing the schema assoc because I am using vega-lite). Did this as a dummy example:
[vega/chart
{:spec
{:$schema ""
:description "A simple bar chart with embedded data."
:mark "bar"
:encoding {:x {:field "a" :type "nominal" :axis {:labelAngle 0}}
:y {:field "b" :type "quantitative"}}}
:data [{:a "A" :b 28}
{:a "B" :b 55}
{:a "C" :b 43}
{:a "D" :b 91}
{:a "E" :b 81}
{:a "F" :b 53}
{:a "G" :b 19}
{:a "H" :b 87}
{:a "I" :b 52}]}]
And it throws Error: Unrecognized data set: 0 which I find quite confusing.
Prefixing the data with {:values [...]} made no difference. So I am a bit confused about what the intended usage is.Well, I didn't use vega-lite so maybe that's the problem. :)
Nevermind, I should also use the VegaLite component then
Mh, doesn't make a difference though. But in principle thats how you call it, right? With spec and data in the map like that?
It doesn't seem to make that much of a difference honestly:
export default function VegaLite(props: VegaLiteProps) {
return <Vega {...props} mode="vega-lite" />;
}
The configs in your code also seem like they should applyIn the usages of vega/chart in that project, the :spec argument has keys like :data, :scales, :marks.
Vega and Vega-Lite have different data formats and different capabilities.
The :data key in the spec describes datums to be expected. Although I think it could also provide the data itself, but I don't use it for that.
The spec and data I have in my example are valid in principle, I have been using them before with vegaEmbed. Inlining data definitely works in both Vega and Vega Lite, in the spec under the data key with {"values": [...]} Weirdly, that react component so far seems to neither accept the data under the data key nor in the spec, although their test code suggests differently. I'll search around some more
An abridged and slightly changed actual usage. Haven't tested it though.
[vega/chart {:spec {:data [{:name :dots}]
:scales [{:name :x
:type :linear
:domain {:fields [{:data :dots, :field :x}]}
:range :width
:zero false}
{:name :y
:type :linear
:domain {:fields [{:data :dots, :field :y}]}
:range :height
:zero false}]
:marks [{:type :symbol
:name :dots-symbol
:from {:data :dots}
:encode {:enter {:size {:value 150}}
:update {:x {:scale :x, :field :x}
:y {:scale :y, :field :y}
:shape {:value :circle}
:stroke {:value :black}
:fill {:value :white}
:fillOpacity {:value 0}}}}]}
:data {:dots [{:x 1 :y 1
:x 1 :y 2
:x 2 :y 1
:x 2 :y 2}]}}]aaaj
Okay, this works with Vega Lite now:
[vega/chart {:spec {:description "A simple bar chart with embedded data."
:data {:name "test"}
:mark "bar"
:encoding {:x {:field "a" :type "nominal" :axis {:labelAngle 0}}
:y {:field "b" :type "quantitative"}}}
:data {:test [{:a "A" :b 28}
{:a "B" :b 55}
{:a "C" :b 43}
{:a "D" :b 91}
{:a "E" :b 81}
{:a "F" :b 53}
{:a "G" :b 19}
{:a "H" :b 87}
{:a "I" :b 52}]}}]
Kind of annoying ever so slight a deviation from the way specs normally look in VL :DJust in case - you can turn those strings into keywords. Personally, I prefer it that way. And IDE keyword search and highlighting can sometimes be helpful.
Yeah already ended up doing that. Thanks so much! This replaced one weird layout problem that I had with another. But the updates now work properly, so that's nice! I am kind of regretting I took on building this frontend. 😄