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