Why should anyone have to spin up a webserver just to render plots? We have JavaFX. We have webviews. Let’s combine them.
Uses the webview, plus some js scripts, to help communicate when charts are loaded and to emit svg/png/html. Since .snapshot doesn’t work with hidden components, we can’t use it for saving images. We also prefer to have all the exacting image details from Vega....so we just use Vega and tell it to give us an image. Clojure captures the result and spits it to a target file.
We can still (and probably want to) render this to an interactive JFX webview for use in other applications (e.g. plotting stuff). We can do this to. We need to separate this out into a separate Vega component, but for now visuals happen per-plot, creating a new stage and webview each time.
(require '[vegafx.core :as vfx])
(def test-spec {"$schema" "https://vega.github.io/schema/vega-lite/v2.0.json"
"data" {"values" [{"a" "A","b" 100}
{"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}]}
"mark" "bar"
"encoding" {"x" {"field" "a", "type" "ordinal"}
"y" {"field" "b", "type" "quantitative"}}
"background" "white"})
;;These will all render without showing the webpage.
(vfx/vega->image test-spec "the-image")
(vfx/vega->image test-spec "the-image" :format :svg)
(vfx/vega->html test-spec "the-page")
;;you'll get a javafx view of the plot.
(vfx/vega->image test-spec "another-image" :show? true)
;;Just render the plot, no spitting image...
(vfx/vega-lite test-spec :show? true)
(defn test-batch []
(let [colors ["red" "white" "blue"]]
(doseq [c colors]
(let [spec (assoc test-spec "background" c)
tgt (str "./examples/" c)]
(println [:spitting c])
(vfx/vega->image spec tgt)
(vfx/vega->image spec tgt :format :svg)
(vfx/vega->html spec tgt)))))
There is support for vega themes as well (WIP to ensure dark theme compatibility…):
(require '[vegafx [core :as vfx] [config :as config]])
;;you can either bind a global options map
;;in vegafx.core/*options*, or pass :options args to client functions.
(defn theme-batch []
(doseq [t config/themes]
(let [tgt (str "./examples/" t)]
(binding [vfx/*options* (assoc vfx/*options* "theme" t)]
(println [:spitting t])
(vfx/vega->image test-spec tgt)
(vfx/vega->image test-spec tgt :format :svg)
(vfx/vega->html test-spec tgt)))))
This lib was built against JavaFX 8, of which there isn’t an easily acquired OpenJFX lib for all platforms. If you have oracle JDK 8, it includes JavaFX automatically. The only other JDK that does this (for Java 8), is Azul Systems Zulu JDK Community Edition. Ensuring JavaFX is available for other Java 8 JDK’s is an exercise for the reader.
If you are using this with Java 11, it should work, but you’ll need a different profile. Borrowing from fn-fx’s setup, we have an :openjfx11 profile. You can alternately modify project.clj to use whatever your system is running as the default.
If you’re installing a legacy java 8 binary distribution on an LTS platform, many are old and the webview rendering doesn’t work with Vega and other libraries.
For example, Ubuntu 16.06 LTS ships up to openjfx 8u60, and 18.08 has openjfx 8u161 availble. Initial testing shows that at least the JDK bundled with openjdk-8-jdk under ubuntu 16.06 doesn’t render correctly (really nothing renders, and there’s a broader problem with other websites - it’s an old bug from early versions of the webiew).
In general, the ZuluFX distribution of opendjk + openjfx appears to work across platforms. I haven’t tested other distributions to date (or newer opendjk distributions).
Even recent Oracl JREs (tested on W10) exhibit similar problems loading vega scripts. Currently looking for a work around, since this is a large target platform.
I used [sdkman](https://sdkman.io/usage) to grab and install the zulu + openjfx distribution. I had to uninstall the default ubuntu 16.06 openjdk-8-jdk package
sudo apt-get remove openjdk-8-jdk
Then I followed the sdkman install instructions. After installation, I picked from the “java” candidates: 8.0.202-zulufx, via
sdk install java 8.0.202-zulufx
After trying out all the other java 8 JDK’s, the only other OpenJDK alternative that includes OpenJFX8 AND works is the “liberica” JDK by Bellsoft.
sdk install java 8.0.232-librca
This distribution also renders everything correctly, and is a possible option for legacy Java 8 users (aside from Oracl’s Java 8 SE, which I have not tested).
If you’re on Java 11, you can use an alternate profile to grab the dependencies, and not worry about messing with boutique JDK configurations we see.
#use openjfx 11 dependencies
lein with-profile +openjfx11 repl
#install locally with the openjfx deps.
lein with-profile +openjfx11 install
#use whatever JavaFX is provided - typical for Java 8
lein repl
#install assuming JavaFX is provided
lein install
These are cripped from the official vega repo for testing purposes. The difference is I included urls to point at the repo for data, where the repo typically points to a local file in /data/something… Note: these are fully interacive plots (performance is actually quite good). The images emitted are just snapshots of the plot prior to interaction. It may take a moment to pull down the datasets, but shouldn’t be more than a couple of seconds with a good connection.
The specifications live in ./examples/specs. All but the “projection” example works, since it uses nonstandard vega addons with D3 cartographic projections.
(require '[vegafx.example :as ex])
(vfx/vega->image (ex/remote-data (slurp "./examples/specs/country-unemployment.vg.json")) "vega-country-unemployment" :show? true)
(vfx/vega->image (ex/remote-data (slurp "./examples/specs/scatter.vg.json")) "vega-scatter" :show? true)
(vfx/vega->image (ex/remote-data (slurp "./examples/specs/scatter-plot-null-values.vg.json")) "vega-scatter-plot-null-values" :show? true)
For convenience, you can explore the examples using the `vega-example`, `list-examples`, `batch-examples` from the `vegafx.example` namespace.
(require '[vegafx.example :as ex])
(vec (ex/list-examples))
#_["airport-connections"
"annual-temperature"
"arc-diagram"
"area-chart"
"bar-chart"
"barley-trellis-plot"
"beeswarm-plot"
"binned-scatter-plot"
"box-plot"
"brushing-scatter-plots"
"budget-forecasts"
"circle-packing"
"connected-scatter-plot"
"contour-plot"
"county-unemployment"
"crossfilter-flights"
"distortion-comparison"
"donut-chart"
"dorling-cartogram"
"dot-plot"
"earthquakes"
"edge-bundling"
"error-bars"
"falkensee-population"
"force-directed-layout"
"global-development"
"grouped-bar-chart"
"heatmap"
"histogram-null-values"
"histogram"
"horizon-graph"
"hypothetical-outcome-plots"
"interactive-legend"
"job-voyager"
"line-chart"
"loess-regression"
"nested-bar-chart"
"overview-plus-detail"
"parallel-coordinates"
"pi-monte-carlo"
"pie-chart"
"population-pyramid"
"probability-density"
"projections"
"quantile-dot-plot"
"quantile-quantile-plot"
"radial-plot"
"radial-tree-layout"
"regression"
"reorderable-matrix"
"scatter-plot-null-values"
"scatter-plot"
"stacked-area-chart"
"stacked-bar-chart"
"stock-index-chart"
"sunburst"
"timelines"
"top-k-plot-with-others"
"top-k-plot"
"tree-layout"
"treemap"
"u-district-cuisine"
"violin-plot"
"volcano-contours"
"weekly-temperature"
"wheat-and-wages"
"wheat-plot"
"wind-vectors"
"word-cloud"
"world-map"
"zoomable-scatter-plot"
"zoomable-world-map"]
;;These are some complex/interactive examples
(ex/vega-example "arc-diagram")
(ex/vega-example "word-cloud")
(ex/vega-example "world-map")
(ex/vega-example "airport-connections")
;;batch-samples will run them all, and may take a 30s or so depending on download
;;speed since we're grabbing data from the vega github at runtime vs. caching it.
;;You'll see the images spit out in ./examples/vega
(ex/batch-samples)
By default, the rendered plot will have the VegaEmbed actions menu to the right, which shows as a little button with an ellipses (…).
- Clicking on this brings up a context menu for saving as png, svg, viewing source,
and opening the page in the Vega Editor.
- All of these are currently supported, except for saving as SVG isn’t rendering as expected.
- We get a blob:null/, with some B64 encoded text. Wondering if this is supposed to be an svg file....either way it’s not rendering in the webview.
- I “think” viewing in the vega editor requires you to be online (could bundle it in the future…).
- All of these are currently supported, except for saving as SVG isn’t rendering as expected.
The “copy to clipboard” option is somewhat compelling for folks, since it’s trivial for users to leverage the copy-and-paste paradigm to populate things like presentations.
- Future iterations will make this easier, e.g. a context menu on the main plot (regardless
of VegaEmbed controls).
You can override showing these actions by messing with the options. The simplest way is to bind the global options in the `vegafx.core/*options*` map like so:
(binding [vegafx.core/*options* (assoc vegafx.core/*options* "actions" false)]
(vega-example "word-cloud"))
I’ll probably provide a convenience macro in short order. Note: “actions” currently has to be bound to false (e.g. nil won’t work due to JS).
- Prefer .loadContent instead of .load, maybe.
Get vegaembed actions (export, view source, view in editor) working[done]Requires getting popup windows working [described here](https://stackoverflow.com/questions/16370622/webview-not-opening-the-popup-window-in-javafx)If you click on the export button (the elipsis …) and select export to png or svg, you’ll get an errorabout not finding vegaEmbed, which is due to this.Current solution: don’t use the GUI to export, use the vega->image function instead.
- Revisit context menu to provide additional features
- Live screenshots, for interactive analysis (copy/paste)
- Somewhat handled by vegaembed export options.
- Static embedded (or at least cached) vega source inlined during templating.
- Decompose the plot component for easier composition in JavaFX scenes.