thi-ng / geom

2D/3D geometry toolkit for Clojure/Clojurescript

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

CLJS serialization story for SVG

den1k opened this issue · comments

For CLJ hiccup serializes SVG's as in

#?(:clj
   (defn serialize
     ^String [svg] (str xml-preamble (html {:mode :xml} svg))))

I have tried multiple alternatives for CLJS (hiccups, hipo and sablono for react), which AFAIK each miss an :xml mode.
sablono works properly for the area and line plot example, but fails when used with the bar graph.

In Om the following returns a valid react component based on the line-plot example:

(ns geom-test.geom
  (:require
    [om.core :as om :include-macros true]
    [sablono.core :refer-macros [html]]
    [thi.ng.geom.viz.core :as viz]
    [thi.ng.geom.svg.core :as svg]
    [thi.ng.math.core :as m :refer [PI]]))

(defn test-equation
  [t] (let [x (m/mix (- PI) PI t)] [x (* (Math/cos (* 0.5 x)) (Math/sin (* x x x)))]))

(def viz-spec
  {:x-axis (viz/linear-axis
             {:domain [(- PI) PI] :range [50 580] :major (/ PI 2) :minor (/ PI 4) :pos 250})
   :y-axis (viz/linear-axis
             {:domain     [-1 1] :range [250 20] :major 0.2 :minor 0.1 :pos 50
              :label-dist 15 :label {:text-anchor "end"}})
   :grid   {:attribs {:stroke "#caa"}
            :minor-y true}
   :data   [{:values  (map test-equation (m/norm-range 200))
             :attribs {:fill "none" :stroke "#0af"}
             :layout  viz/svg-line-plot}]})

(defn line-plot [_ owner]
  (om/component
    (html
      [:svg {:width 600 :height 320}
       (viz/svg-plot2d-cartesian viz-spec)])))

I'm more of a reagent kinda person, so no 1st hand experience with sablono / om... But am really suprised that the bar graph fails of all the methods (which only uses rects & lines). Do you get an error or it just doesn't produce a DOM fragment? The svg-plot2d-cartesian just produces hiccup vectors with nothing special. However for React components the issue is that attribs like :fill, :stroke etc. need to be nested under the :style key in the :attribs map. So instead of:

:attribs {:fill "none" :stroke "#0af"}

...you'll need to state these as:

:attribs {:style {:fill "none" :stroke "#0af"}}

(I don't know if that's the same in Om, but I guess so...)

For a non-React based example, check out the SVG physics demo here:

Source: https://github.com/thi-ng/demos/blob/master/geom/src/physics_demos/strands.cljs
Live: http://demo.thi.ng/geom/physics/strands.html

This demo uses the hiccup serializer in the domus lib

The line-plot actually renders perfectly, apparently chrome also accepts tag attributes for properties like fill. I got really excited when it worked. I thought: finally a D3 replacement where I could add event handlers in a data transformation step before passing it to sablono.
The bar graph breaks something in sablono's html macro. The grid renders properly. Everything else ends up outside of the svg tag, rendered in spans as react does with strings:
screen shot 2015-10-13 at 9 36 58 pm

I think this is really a shortcoming of sablono and other serializers. The bar graph hiccup looks like this below and serializes fine into an SVG DOM using my domus lib or in CLJ w/ hiccup:

[:g
 nil
 ([:g
   {:stroke "#ccc", :stroke-dasharray "1 1"}
   (([:line {:x1 "50.00", :y1 "280.00", :x2 "50.00", :y2 "20.00"}]
     [:line {:x1 "81.18", :y1 "280.00", :x2 "81.18", :y2 "20.00"}]
     ...)
    ([:line {:x1 "50.00", :y1 "267.00", :x2 "580.00", :y2 "267.00"}]
     [:line {:x1 "50.00", :y1 "241.00", :x2 "580.00", :y2 "241.00"}]
     ...))]
  ([:g
    {:stroke "#0af", :stroke-width "19px"}
    (([:line {:x1 "81.18", :y1 "195.10", :x2 "81.18", :y2 "280.00"}]
      ...))])
  [:g
   {:stroke "black"}
   (([:line {:x1 "50.00", :y1 "280.00", :x2 "50.00", :y2 "290.00"}]
     ...)
    ()
    [:g
     {:stroke "none",
      :fill "black",
      :font-family "Arial, sans-serif",
      :font-size 10,
      :text-anchor "middle"}
     (([:text {:x "50.00", :y "300.00"} 1999]
       ...))]
    [:line {:x1 "50.00", :y1 "280.00", :x2 "580.00", :y2 "280.00"}])]
  [:g
   {:stroke "black"}
   (([:line {:x1 "50.00", :y1 "280.00", :x2 "40.00", :y2 "280.00"}]
     ...)
    ([:line {:x1 "50.00", :y1 "267.00", :x2 "45.00", :y2 "267.00"}]
     ...)
    [:g
     {:stroke "none",
      :fill "black",
      :font-family "Arial, sans-serif",
      :font-size 10,
      :text-anchor "end"}
     (([:text {:x "35.00", :y "280.00"} "0.00"]
       ...))]
    [:line {:x1 "50.00", :y1 "280.00", :x2 "50.00", :y2 "20.00"}])])]

I guess, it's the nested seqs which cause the serialization "hiccup" (pun intended! :) Will have to check if I can switch to mapcat somewhere.

thanks @postspectacular I'm a huge fan of the geom toolkit. Looking forward to use it more & more in the future!

Hi @den1k - just checked in 2 pieces of functionality which now cause no more warnings when using the viz module with Reagent (still not tried with sablono or any other Om related libs).

Seqs of elements in SVG groups now have one level less of nesting and you'll need to insert a call to inject-element-attribs (in the thi.ng.geom.svg.adapter ns). This fn will generate unique IDs for each element in the SVG hiccup structure.

The below is a complete reagent component for reference:

(defn visualization
  "Takes a geom.viz visualization spec map and generates a SVG component.
  The call to inject-element-attribs ensures that all SVG elements have
  an unique :key attribute, required for React.js."
  [spec]
  (->> spec
       (viz/svg-plot2d-cartesian)
       (svgadapt/inject-element-attribs svgadapt/key-attrib-injector)
       (svg/svg {:width 600 :height 600})))

It'd be great if you could test this with any of the other libs.
Latest release version is: [thi.ng/geom "0.0.908"]

Thanks @postspectacular for adding keys! The bar chart now renders properly. I checked the line chart which has extra numbers in it's last svg group node.
It appears that the group should contain svg text nodes but instead it has plain numbers which react renders as spans.

This is the group:

[:g
   {:stroke "none",
    :fill "black",
    :font-family "Arial, sans-serif",
    :font-size 10,
    :text-anchor "middle",
    :key "G__2367"}
   -1
   -0.8
   -0.6000000000000001
   -0.4000000000000001
   -0.20000000000000007
   -5.551115123125783e-17
   0.19999999999999996
   0.39999999999999997
   0.6
   0.8
   1]

Render Output:
om tutorial 2015-11-10 14-23-17

Full output with injected keys:

[:g
 {:key "G__2370"}
 [:g
  {:stroke "#caa", :stroke-dasharray "1 1", :key "G__2319"}
  ([:line
    {:x1 "50.00",
     :y1 "250.00",
     :x2 "50.00",
     :y2 "20.00",
     :key "G__2285"}]
   [:line
    {:x1 "182.50",
     :y1 "250.00",
     :x2 "182.50",
     :y2 "20.00",
     :key "G__2286"}]
   [:line
    {:x1 "315.00",
     :y1 "250.00",
     :x2 "315.00",
     :y2 "20.00",
     :key "G__2287"}]
   [:line
    {:x1 "447.50",
     :y1 "250.00",
     :x2 "447.50",
     :y2 "20.00",
     :key "G__2288"}]
   [:line
    {:x1 "580.00",
     :y1 "250.00",
     :x2 "580.00",
     :y2 "20.00",
     :key "G__2289"}])
  ([:line
    {:x1 "50.00",
     :y1 "238.50",
     :x2 "580.00",
     :y2 "238.50",
     :key "G__2290"}]
   [:line
    {:x1 "50.00",
     :y1 "215.50",
     :x2 "580.00",
     :y2 "215.50",
     :key "G__2291"}]
   [:line
    {:x1 "50.00",
     :y1 "192.50",
     :x2 "580.00",
     :y2 "192.50",
     :key "G__2292"}]
   [:line
    {:x1 "50.00",
     :y1 "181.00",
     :x2 "580.00",
     :y2 "181.00",
     :key "G__2293"}]
   [:line
    {:x1 "50.00",
     :y1 "169.50",
     :x2 "580.00",
     :y2 "169.50",
     :key "G__2294"}]
   [:line
    {:x1 "50.00",
     :y1 "158.00",
     :x2 "580.00",
     :y2 "158.00",
     :key "G__2295"}]
   [:line
    {:x1 "50.00",
     :y1 "146.50",
     :x2 "580.00",
     :y2 "146.50",
     :key "G__2296"}]
   [:line
    {:x1 "50.00",
     :y1 "135.00",
     :x2 "580.00",
     :y2 "135.00",
     :key "G__2297"}]
   [:line
    {:x1 "50.00",
     :y1 "123.50",
     :x2 "580.00",
     :y2 "123.50",
     :key "G__2298"}]
   [:line
    {:x1 "50.00",
     :y1 "112.00",
     :x2 "580.00",
     :y2 "112.00",
     :key "G__2299"}]
   [:line
    {:x1 "50.00",
     :y1 "100.50",
     :x2 "580.00",
     :y2 "100.50",
     :key "G__2300"}]
   [:line
    {:x1 "50.00",
     :y1 "89.00",
     :x2 "580.00",
     :y2 "89.00",
     :key "G__2301"}]
   [:line
    {:x1 "50.00",
     :y1 "77.50",
     :x2 "580.00",
     :y2 "77.50",
     :key "G__2302"}]
   [:line
    {:x1 "50.00",
     :y1 "66.00",
     :x2 "580.00",
     :y2 "66.00",
     :key "G__2303"}]
   [:line
    {:x1 "50.00",
     :y1 "54.50",
     :x2 "580.00",
     :y2 "54.50",
     :key "G__2304"}]
   [:line
    {:x1 "50.00",
     :y1 "43.00",
     :x2 "580.00",
     :y2 "43.00",
     :key "G__2305"}]
   [:line
    {:x1 "50.00",
     :y1 "31.50",
     :x2 "580.00",
     :y2 "31.50",
     :key "G__2306"}]
   [:line
    {:x1 "50.00",
     :y1 "20.00",
     :x2 "580.00",
     :y2 "20.00",
     :key "G__2307"}]
   [:line
    {:x1 "50.00",
     :y1 "250.00",
     :x2 "580.00",
     :y2 "250.00",
     :key "G__2308"}]
   [:line
    {:x1 "50.00",
     :y1 "227.00",
     :x2 "580.00",
     :y2 "227.00",
     :key "G__2309"}]
   [:line
    {:x1 "50.00",
     :y1 "204.00",
     :x2 "580.00",
     :y2 "204.00",
     :key "G__2310"}]
   [:line
    {:x1 "50.00",
     :y1 "181.00",
     :x2 "580.00",
     :y2 "181.00",
     :key "G__2311"}]
   [:line
    {:x1 "50.00",
     :y1 "158.00",
     :x2 "580.00",
     :y2 "158.00",
     :key "G__2312"}]
   [:line
    {:x1 "50.00",
     :y1 "135.00",
     :x2 "580.00",
     :y2 "135.00",
     :key "G__2313"}]
   [:line
    {:x1 "50.00",
     :y1 "112.00",
     :x2 "580.00",
     :y2 "112.00",
     :key "G__2314"}]
   [:line
    {:x1 "50.00",
     :y1 "89.00",
     :x2 "580.00",
     :y2 "89.00",
     :key "G__2315"}]
   [:line
    {:x1 "50.00",
     :y1 "66.00",
     :x2 "580.00",
     :y2 "66.00",
     :key "G__2316"}]
   [:line
    {:x1 "50.00",
     :y1 "43.00",
     :x2 "580.00",
     :y2 "43.00",
     :key "G__2317"}]
   [:line
    {:x1 "50.00",
     :y1 "20.00",
     :x2 "580.00",
     :y2 "20.00",
     :key "G__2318"}])]
 ([:polygon
   {:fill "#0af",
    :points
    "50.00,135.00 52.65,133.25 55.30,132.15 57.95,134.87 60.60,140.39 63.25,143.96 65.90,141.29 68.55,132.58 71.20,122.96 73.85,119.11 76.50,124.66 79.15,137.62 81.80,151.35 84.45,158.32 87.10,154.21 89.75,140.13 92.40,122.00 95.05,107.68 97.70,103.45 100.35,111.46 103.00,129.08 105.65,150.21 108.30,167.65 110.95,175.64 113.60,171.61 116.25,156.65 118.90,134.91 121.55,112.13 124.20,93.97 126.85,84.67 129.50,86.15 132.15,97.89 134.80,117.33 137.45,140.65 140.10,163.61 142.75,182.43 145.40,194.32 148.05,197.82 150.70,192.80 153.35,180.30 156.00,162.20 158.65,140.85 161.30,118.69 163.95,97.97 166.60,80.51 169.25,67.63 171.90,60.05 174.55,57.96 177.20,61.12 179.85,68.92 182.50,80.52 185.15,94.95 187.80,111.23 190.45,128.41 193.10,145.63 195.75,162.19 198.40,177.51 201.05,191.18 203.70,202.93 206.35,212.61 209.00,220.19 211.65,225.72 214.30,229.30 216.95,231.12 219.60,231.35 222.25,230.21 224.90,227.91 227.55,224.66 230.20,220.66 232.85,216.09 235.50,211.11 238.15,205.86 240.80,200.48 243.45,195.07 246.10,189.71 248.75,184.48 251.40,179.44 254.05,174.63 256.70,170.08 259.35,165.81 262.00,161.85 264.65,158.20 267.30,154.86 269.95,151.83 272.60,149.11 275.25,146.68 277.90,144.54 280.55,142.67 283.20,141.05 285.85,139.67 288.50,138.52 291.15,137.57 293.80,136.81 296.45,136.22 299.10,135.77 301.75,135.44 304.40,135.23 307.05,135.10 309.70,135.03 312.35,135.00 315.00,135.00 317.65,135.00 320.30,134.97 322.95,134.90 325.60,134.77 328.25,134.56 330.90,134.23 333.55,133.78 336.20,133.19 338.85,132.43 341.50,131.48 344.15,130.33 346.80,128.95 349.45,127.33 352.10,125.46 354.75,123.32 357.40,120.89 360.05,118.17 362.70,115.14 365.35,111.80 368.00,108.15 370.65,104.19 373.30,99.92 375.95,95.37 378.60,90.56 381.25,85.52 383.90,80.29 386.55,74.93 389.20,69.52 391.85,64.14 394.50,58.89 397.15,53.91 399.80,49.34 402.45,45.34 405.10,42.09 407.75,39.79 410.40,38.65 413.05,38.88 415.70,40.70 418.35,44.28 421.00,49.81 423.65,57.39 426.30,67.07 428.95,78.82 431.60,92.49 434.25,107.81 436.90,124.37 439.55,141.59 442.20,158.77 444.85,175.05 447.50,189.48 450.15,201.08 452.80,208.88 455.45,212.04 458.10,209.95 460.75,202.37 463.40,189.49 466.05,172.03 468.70,151.31 471.35,129.15 474.00,107.80 476.65,89.70 479.30,77.20 481.95,72.18 484.60,75.68 487.25,87.57 489.90,106.39 492.55,129.35 495.20,152.67 497.85,172.11 500.50,183.85 503.15,185.33 505.80,176.03 508.45,157.87 511.10,135.09 513.75,113.35 516.40,98.39 519.05,94.36 521.70,102.35 524.35,119.79 527.00,140.92 529.65,158.54 532.30,166.55 534.95,162.32 537.60,148.00 540.25,129.87 542.90,115.79 545.55,111.68 548.20,118.65 550.85,132.38 553.50,145.34 556.15,150.89 558.80,147.04 561.45,137.42 564.10,128.71 566.75,126.04 569.40,129.61 572.05,135.13 574.70,137.85 577.35,136.75 580.00,135.00 580.00,250.00 50.00,250.00",
    :key "G__2320"}])
 [:g
  {:stroke "black", :key "G__2337"}
  ([:line
    {:x1 "50.00",
     :y1 "250.00",
     :x2 "50.00",
     :y2 "260.00",
     :key "G__2321"}]
   [:line
    {:x1 "182.50",
     :y1 "250.00",
     :x2 "182.50",
     :y2 "260.00",
     :key "G__2322"}]
   [:line
    {:x1 "315.00",
     :y1 "250.00",
     :x2 "315.00",
     :y2 "260.00",
     :key "G__2323"}]
   [:line
    {:x1 "447.50",
     :y1 "250.00",
     :x2 "447.50",
     :y2 "260.00",
     :key "G__2324"}]
   [:line
    {:x1 "580.00",
     :y1 "250.00",
     :x2 "580.00",
     :y2 "260.00",
     :key "G__2325"}])
  ([:line
    {:x1 "116.25",
     :y1 "250.00",
     :x2 "116.25",
     :y2 "255.00",
     :key "G__2326"}]
   [:line
    {:x1 "248.75",
     :y1 "250.00",
     :x2 "248.75",
     :y2 "255.00",
     :key "G__2327"}]
   [:line
    {:x1 "381.25",
     :y1 "250.00",
     :x2 "381.25",
     :y2 "255.00",
     :key "G__2328"}]
   [:line
    {:x1 "513.75",
     :y1 "250.00",
     :x2 "513.75",
     :y2 "255.00",
     :key "G__2329"}])
  [:g
   {:stroke "none",
    :fill "black",
    :font-family "Arial, sans-serif",
    :font-size 10,
    :text-anchor "middle",
    :key "G__2335"}
   [:text {:x "50.00", :y "270.00", :key "G__2330"} "-3.14"]
   [:text {:x "182.50", :y "270.00", :key "G__2331"} "-1.57"]
   [:text {:x "315.00", :y "270.00", :key "G__2332"} "0.00"]
   [:text {:x "447.50", :y "270.00", :key "G__2333"} "1.57"]
   [:text {:x "580.00", :y "270.00", :key "G__2334"} "3.14"]]
  [:line
   {:x1 "50.00",
    :y1 "250.00",
    :x2 "580.00",
    :y2 "250.00",
    :key "G__2336"}]]
 [:g
  {:stroke "black", :key "G__2369"}
  ([:line
    {:x1 "50.00",
     :y1 "250.00",
     :x2 "40.00",
     :y2 "250.00",
     :key "G__2338"}]
   [:line
    {:x1 "50.00",
     :y1 "227.00",
     :x2 "40.00",
     :y2 "227.00",
     :key "G__2339"}]
   [:line
    {:x1 "50.00",
     :y1 "204.00",
     :x2 "40.00",
     :y2 "204.00",
     :key "G__2340"}]
   [:line
    {:x1 "50.00",
     :y1 "181.00",
     :x2 "40.00",
     :y2 "181.00",
     :key "G__2341"}]
   [:line
    {:x1 "50.00",
     :y1 "158.00",
     :x2 "40.00",
     :y2 "158.00",
     :key "G__2342"}]
   [:line
    {:x1 "50.00",
     :y1 "135.00",
     :x2 "40.00",
     :y2 "135.00",
     :key "G__2343"}]
   [:line
    {:x1 "50.00",
     :y1 "112.00",
     :x2 "40.00",
     :y2 "112.00",
     :key "G__2344"}]
   [:line
    {:x1 "50.00",
     :y1 "89.00",
     :x2 "40.00",
     :y2 "89.00",
     :key "G__2345"}]
   [:line
    {:x1 "50.00",
     :y1 "66.00",
     :x2 "40.00",
     :y2 "66.00",
     :key "G__2346"}]
   [:line
    {:x1 "50.00",
     :y1 "43.00",
     :x2 "40.00",
     :y2 "43.00",
     :key "G__2347"}]
   [:line
    {:x1 "50.00",
     :y1 "20.00",
     :x2 "40.00",
     :y2 "20.00",
     :key "G__2348"}])
  ([:line
    {:x1 "50.00",
     :y1 "238.50",
     :x2 "45.00",
     :y2 "238.50",
     :key "G__2349"}]
   [:line
    {:x1 "50.00",
     :y1 "215.50",
     :x2 "45.00",
     :y2 "215.50",
     :key "G__2350"}]
   [:line
    {:x1 "50.00",
     :y1 "192.50",
     :x2 "45.00",
     :y2 "192.50",
     :key "G__2351"}]
   [:line
    {:x1 "50.00",
     :y1 "181.00",
     :x2 "45.00",
     :y2 "181.00",
     :key "G__2352"}]
   [:line
    {:x1 "50.00",
     :y1 "169.50",
     :x2 "45.00",
     :y2 "169.50",
     :key "G__2353"}]
   [:line
    {:x1 "50.00",
     :y1 "158.00",
     :x2 "45.00",
     :y2 "158.00",
     :key "G__2354"}]
   [:line
    {:x1 "50.00",
     :y1 "146.50",
     :x2 "45.00",
     :y2 "146.50",
     :key "G__2355"}]
   [:line
    {:x1 "50.00",
     :y1 "135.00",
     :x2 "45.00",
     :y2 "135.00",
     :key "G__2356"}]
   [:line
    {:x1 "50.00",
     :y1 "123.50",
     :x2 "45.00",
     :y2 "123.50",
     :key "G__2357"}]
   [:line
    {:x1 "50.00",
     :y1 "112.00",
     :x2 "45.00",
     :y2 "112.00",
     :key "G__2358"}]
   [:line
    {:x1 "50.00",
     :y1 "100.50",
     :x2 "45.00",
     :y2 "100.50",
     :key "G__2359"}]
   [:line
    {:x1 "50.00",
     :y1 "89.00",
     :x2 "45.00",
     :y2 "89.00",
     :key "G__2360"}]
   [:line
    {:x1 "50.00",
     :y1 "77.50",
     :x2 "45.00",
     :y2 "77.50",
     :key "G__2361"}]
   [:line
    {:x1 "50.00",
     :y1 "66.00",
     :x2 "45.00",
     :y2 "66.00",
     :key "G__2362"}]
   [:line
    {:x1 "50.00",
     :y1 "54.50",
     :x2 "45.00",
     :y2 "54.50",
     :key "G__2363"}]
   [:line
    {:x1 "50.00",
     :y1 "43.00",
     :x2 "45.00",
     :y2 "43.00",
     :key "G__2364"}]
   [:line
    {:x1 "50.00",
     :y1 "31.50",
     :x2 "45.00",
     :y2 "31.50",
     :key "G__2365"}]
   [:line
    {:x1 "50.00",
     :y1 "20.00",
     :x2 "45.00",
     :y2 "20.00",
     :key "G__2366"}])
  [:g
   {:stroke "none",
    :fill "black",
    :font-family "Arial, sans-serif",
    :font-size 10,
    :text-anchor "middle",
    :key "G__2367"}
   -1
   -0.8
   -0.6000000000000001
   -0.4000000000000001
   -0.20000000000000007
   -5.551115123125783e-17
   0.19999999999999996
   0.39999999999999997
   0.6
   0.8
   1]
  [:line
   {:x1 "50.00",
    :y1 "250.00", 
    :x2 "50.00", 
    :y2 "20.00", 
    :key "G__2368"}]]]

That's weird - in this demo here (using Reagent) it all works perfectly: http://demo.thi.ng/ws-ldn-1/geom-viz/

will take another look tonight after the workshop...

@postspectacular the bar chart works fine, try the line chart.

I don't know what you're seeing with that above linked demo, but I get the same (good & expected) results in Chrome (46.0.2490.86), FF(42.0) and even Safari 6.0.5 (8536.30.1):




Screenshots from Chrome, but worrying that you seem to be seeing something else... I also don't see any React warnings in the console for any of these viz methods. What browser & version you're testing with?

If you want to try, the source for that demo is in this repo: https://github.com/thi-ng/ws-ldn-1

Is there a way to provide a simple example of a working Reagent-based SVG for working with, say, shapes?

I'm trying the following and it's not working out:

(defn svg-component [width body]
  [:div
   [(->> body
         (svgadapt/inject-element-attribs svgadapt/key-attrib-injector)
         (svg/svg {:width width :height width}))]])

(defn simple []
  (svg-component 300 (svg/group {:stroke "blue" :fill "none"} (c/circle 20))))

(defn ^:export on-js-reload []
  (r/render [simple]
            (js/document.getElementById "app")))

I get Error rendering component (in things.core.simple and Uncaught Error: Invalid arity: 1 with pretty useless stack traces.

I've done my due diligence looking around the demo examples and couldn't quite find what I needed.

Hi @Polyrhythm - there're two things wrong with your example:

  1. In svg-component - you don't want to wrap the child of the inner form in a vector, since then your structure would look like this: [:div [[:svg ... ]]] and this causes part of the trouble
  2. The other issue is that you forgot to call svgadapt/all-as-svg before injecting the :key attribs

So corrected, this works without a hitch:

(defn svg-component [width body]
  [:div
   (->> body
        (svgadapt/all-as-svg)
        (svgadapt/inject-element-attribs svgadapt/key-attrib-injector)
        (svg/svg {:width width :height width}))])

(defn simple []
  (svg-component 300 (svg/group {:stroke "blue" :fill "none"} (c/circle 20))))

(defn ^:export main []
  (reagent/render
   [simple]
   (js/document.getElementById "app")))

@postspectacular thank you for the quick and thorough reply. Now I can get started learning this awesome set of tools. 👍