Skip to content
/ vegafx Public

A simple little static-site viewer using javafx that renders vega specs.

License

Notifications You must be signed in to change notification settings

joinr/vegafx

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

26 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

VEGAFX (a very simple renderer for vega/lite specs)

Purpose

Why should anyone have to spin up a webserver just to render plots? We have JavaFX. We have webviews. Let’s combine them.

Headless rendering

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.

Optional Visual Rendering as a Webview Component

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.

Usage

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

JavaFX Versions

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.

Outdated Binary Distributions And Webview

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

Java SE 1.8.0_221 (e.g. recent)

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.

SDKMan

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

JavaFX 11 / OpenJFX 11

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

Vega Examples

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)

Exporting From the GUI Using VegaEmbed Controls

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…).

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

Todo

  • Prefer .loadContent instead of .load, maybe.
  • Get vegaembed actions (export, view source, view in editor) working [done]
  • 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.

About

A simple little static-site viewer using javafx that renders vega specs.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published