Functions to render charts as svg strings by using graal's js engine. A bundle is built by | (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.delay :as delay] [metabase.util.json :as json]) (:import (java.io ByteArrayInputStream ByteArrayOutputStream) (java.nio.charset StandardCharsets) (java.util.concurrent TimeUnit) (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 | (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 | (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 | (def ^:private static-viz-context-delay
(delay/delay-with-ttl (.toMillis TimeUnit/MINUTES 10)
#(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 | (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 |