re-frame

Schmoho 2025-03-13T21:42:49.912379Z

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

Schmoho 2025-03-13T21:44:37.928869Z

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

p-himik 2025-03-13T22:13:31.121309Z

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?

Schmoho 2025-03-13T23:18:26.084059Z

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

p-himik 2025-03-13T23:22:20.245889Z

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.

p-himik 2025-03-13T23:22:59.854869Z

Oh, BTW x2 - how does that even work? You don't provide :id there.

Schmoho 2025-03-14T00:08:00.456029Z

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

Schmoho 2025-03-14T00:08:02.687379Z

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

Schmoho 2025-03-14T00:09:47.332799Z

thanks!

p-himik 2025-03-14T09:25:43.574899Z

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.

❤️ 1
Schmoho 2025-03-15T17:05:44.905789Z

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.

p-himik 2025-03-15T17:08:39.833339Z

Well, I didn't use vega-lite so maybe that's the problem. :)

Schmoho 2025-03-15T17:08:44.199689Z

Nevermind, I should also use the VegaLite component then

Schmoho 2025-03-15T17:10:28.345779Z

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?

Schmoho 2025-03-15T17:12:07.934639Z

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 apply

p-himik 2025-03-15T17:15:08.951509Z

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

p-himik 2025-03-15T17:15:43.446039Z

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.

Schmoho 2025-03-15T17:18:58.702209Z

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

p-himik 2025-03-15T17:19:59.693849Z

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}]}}]

Schmoho 2025-03-15T17:21:43.535699Z

aaaj

Schmoho 2025-03-15T17:41:06.687599Z

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 :D

p-himik 2025-03-15T18:05:07.333189Z

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

Schmoho 2025-03-15T18:10:29.047369Z

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

😄 1