Functions to render charts as svg strings by using graal's js engine. A bundle is built by yarn build-static-viz which has charting library. This namespace has some wrapper functions to invoke those functions. Interop is very strange, as the jvm datastructures, not just serialized versions are used. This is why we have the toJSArray and toJSMap functions to turn Clojure's normal datastructures into js native structures.

(ns metabase.channel.render.js.svg
  (:require
   [clojure.string :as str]
   [metabase.channel.render.js.engine :as js.engine]
   [metabase.channel.render.style :as style]
   [metabase.config :as config]
   [metabase.public-settings :as public-settings]
   [metabase.util.json :as json])
  (:import
   (java.io ByteArrayInputStream ByteArrayOutputStream)
   (java.nio.charset StandardCharsets)
   (org.apache.batik.anim.dom SAXSVGDocumentFactory SVGOMDocument)
   (org.apache.batik.transcoder TranscoderInput TranscoderOutput)
   (org.apache.batik.transcoder.image PNGTranscoder)
   (org.graalvm.polyglot Context)
   (org.w3c.dom Element Node)))
(set! *warn-on-reflection* true)

the bundle path goes through webpack. Changes require a yarn build-static-viz

(def ^:private bundle-path
  "frontend_client/app/dist/lib-static-viz.bundle.js")

the interface file does not go through webpack. Feel free to quickly change as needed and then re-require this namespace to redef the context.

(def ^:private interface-path
  "frontend_shared/static_viz_interface.js")
(defn- load-viz-bundle [^Context context]
  ;; make sure people don't try to load the static viz bundle as a side-effect of loading namespaces, because it might
  ;; not have been built! If it's not built, we want to be able to give people a meaningful error (see the fixture
  ;; in [[metabase.channel.render.js.svg-test]]) rather than have the test runner fail to start with a meaningless
  ;; compilation error.
  (when config/tests-available?
    ((requiring-resolve 'mb.hawk.init/assert-tests-are-not-initializing) "(mt/id ...) or (data/id ...)"))
  (doto context
    (js.engine/load-resource bundle-path)
    (js.engine/load-resource interface-path)))

Delay containing a graal js context. It has the chart bundle and the above src-api in its environment suitable for creating charts.

(def ^:private static-viz-context-delay
  (delay (load-viz-bundle (js.engine/context))))

Returns a static viz context. In dev mode, this will be a new context each time. In prod or test modes, it will return the derefed contents of static-viz-context-delay.

(defn- context
  ^Context []
  (if config/is-dev?
    (load-viz-bundle (js.engine/context))
    @static-viz-context-delay))

Mutate in place the elements of the svg document. Remove the fill=transparent attribute in favor of fill-opacity=0.0. Our svg image renderer only understands the latter. Mutation is unfortunately necessary as the underlying tree of nodes is inherently mutable

(defn- post-process
  [^SVGOMDocument svg-document & post-fns]
  (loop [s [(.getDocumentElement svg-document)]]
    (when-let [^Node node (peek s)]
      (let [s' (let [nodelist (.getChildNodes node)
                     length   (.getLength nodelist)]
                 (apply conj (pop s)
                        ;; reverse the nodes for the stack so it goes down first child first
                        (map #(.item nodelist %) (reverse (range length)))))]
        (reduce (fn [node f] (f node)) node post-fns)
        (recur s'))))
  svg-document)

The batik svg renderer does not understand fill="transparent" so we must change that to fill-opacity="0.0". Previously was just doing a string replacement but now is a proper tree walk fix.

(defn- fix-fill
  [^Node node]
  (letfn [(element? [x] (instance? Element x))]
    (if (and (element? node)
             (.hasAttribute ^Element node "fill")
             (= (.getAttribute ^Element node "fill") "transparent"))
      (doto ^Element node
        (.removeAttribute "fill")
        (.setAttribute "fill-opacity" "0.0"))
      node)))

The echarts library (whose output we get via the :javascript_visualization multimethod) adds a