vega / vl-convert

Utilities for converting Vega-Lite specs from the command line and Python
BSD 3-Clause "New" or "Revised" License
98 stars 12 forks source link

SVG and PNG image export support #8

Closed jonmmease closed 1 year ago

jonmmease commented 1 year ago

Overview

This PR adds support for exporting Vega(-Lite) chart specifications to SVG and PNG static images. This support is added to the vl-convert-rs Rust library, the vl-convert-python Python library, and the vl-convert CLI application.

How it works

deno_core to deno_runtime

The first change this PR makes is adding a dependency on the deno_runtime crate. This is a wrapper around deno_core that adds a variety of extensions for increased browser/node.js support. This is needed to provide support for functions like fetch, which are required by Vega in order to evaluate specifications for image generation.

Custom test width measurement for SVG export

The Vega JavaScript library supports exporting chart specifications to SVG images, and this conversion works in Deno. However, there is a subtle complication. In order to properly position text within the exported SVG, Vega needs to compute the width of text fragments (at a particular font size, in a particular font, etc.). When running in Node.js, these calculations are done using node canvas, which does not work in Deno. When node canvas is not available, Vega falls back to a rough heuristic for text measurement that results in poor text placement results.

For example, here is the stacked horizontal bar chart example from the Vega-Lite gallery.

visualization

And here is what the exported image looks like without node canvas. vis

Notice how much extra space is added between the y-axis label and the y-axis tick labels due to inaccurate text measurements.

This PR works around this by overriding the text width calculation function using a custom Rust function. This custom Rust function uses the usvg crate (part of the resvg project) to compute the width of text fragments. With this customization, we regain accurate text placement in the SVG results produced by Vega. Here is the above example as generated by this PR:

vis

PNG export

The Vega JavaScript library supports exporting chart specifications directly to PNG images. When running in Node.js, this functionality relies on node canvas, which is not available in Deno.

This PR generates PNG images by first exporting charts to SVG as described above, then converting the SVG image to a PNG image using the resvg crate.

CLI changes

Now that additional conversions are supported, the CLI app has been updated to use subcommands, one per conversion format: vl2vg, vl2svg, vl2png, vg2svg, vg2png.

Display the documentation for the top-level vl-convert command

$ vl-convert --help

vl-convert: A utility for converting Vega-Lite specifications

Usage: vl-convert <COMMAND>

Commands:
  vl2vg   Convert a Vega-Lite specification to a Vega specification
  vl2svg  Convert a Vega-Lite specification to an SVG image
  vl2png  Convert a Vega-Lite specification to an PNG image
  vg2svg  Convert a Vega specification to an SVG image
  vg2png  Convert a Vega specification to an PNG image
  help    Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help information
  -V, --version  Print version information

Various conversion formats are handled by the subcommands listed above. Documentation for each subcommands is displayed using the --help flag.

vl2vg

Convert a Vega-Lite JSON specification to a Vega JSON specification

$ vl-convert vl2vg --help 

Convert a Vega-Lite specification to a Vega specification

Usage: vl-convert vl2vg [OPTIONS] --input <INPUT> --output <OUTPUT>

Options:
  -i, --input <INPUT>            Path to input Vega-Lite file
  -o, --output <OUTPUT>          Path to output Vega file to be created
  -v, --vl-version <VL_VERSION>  Vega-Lite Version. One of 4.17, 5.0, 5.1, 5.2, 5.3, 5.4, 5.5 [default: 5.5]
  -p, --pretty                   Pretty-print JSON in output file
  -h, --help                     Print help information

For example, convert a Vega-Lite specification file named in.vl.json into a Vega specification file named out.vg.json. Perform the conversion using version 5.5 of the Vega-Lite JavaScript library and pretty-print the resulting JSON.

$ vl-convert vl2vg -i ./in.vl.json -o ./out.vg.json --vl-version 5.5 --pretty

vl2svg

Convert a Vega-Lite specification to an SVG image

$ vl-convert vl2svg --help 

Convert a Vega-Lite specification to an SVG image

Usage: vl-convert vl2svg [OPTIONS] --input <INPUT> --output <OUTPUT>

Options:
  -i, --input <INPUT>            Path to input Vega-Lite file
  -o, --output <OUTPUT>          Path to output SVG file to be created
  -v, --vl-version <VL_VERSION>  Vega-Lite Version. One of 4.17, 5.0, 5.1, 5.2, 5.3, 5.4, 5.5 [default: 5.5]
  -h, --help                     Print help information

For example, convert a Vega-Lite specification file named in.vl.json into an SVG file named out.svg. Perform the conversion using version 5.5 of the Vega-Lite JavaScript library.

$ vl-convert vl2svg -i ./in.vl.json -o ./out.svg --vl-version 5.5

vl2png

Convert a Vega-Lite specification to an PNG image

$ vl-convert vl2png --help

Convert a Vega-Lite specification to an PNG image

Usage: vl-convert vl2png [OPTIONS] --input <INPUT> --output <OUTPUT>

Options:
  -i, --input <INPUT>            Path to input Vega-Lite file
  -o, --output <OUTPUT>          Path to output PNG file to be created
  -v, --vl-version <VL_VERSION>  Vega-Lite Version. One of 4.17, 5.0, 5.1, 5.2, 5.3, 5.4, 5.5 [default: 5.5]
  -s, --scale <SCALE>            Image scale factor [default: 1.0]
  -h, --help                     Print help information

For example, convert a Vega-Lite specification file named in.vl.json into a PNG file named out.png with a scale factor of 2. Perform the conversion using version 5.5 of the Vega-Lite JavaScript library.

$ vl-convert vl2png -i ./in.vl.json -o ./out.png --vl-version 5.5 --scale 2

vg2svg

Convert a Vega specification to an SVG image

$ vl-convert vg2svg --help

Convert a Vega specification to an SVG image

Usage: vl-convert vg2svg --input <INPUT> --output <OUTPUT>

Options:
  -i, --input <INPUT>    Path to input Vega file
  -o, --output <OUTPUT>  Path to output SVG file to be created
  -h, --help             Print help information

For example, convert a Vega specification file named in.vg.json into an SVG file named out.svg.

$ vl-convert vg2svg -i ./in.vg.json -o ./out.svg

vg2png

$ vl-convert vg2png --help

Convert a Vega specification to an PNG image

Usage: vl-convert vg2png [OPTIONS] --input <INPUT> --output <OUTPUT>

Options:
  -i, --input <INPUT>    Path to input Vega file
  -o, --output <OUTPUT>  Path to output PNG file to be created
  -s, --scale <SCALE>    Image scale factor [default: 1.0]
  -h, --help             Print help information

For example, convert a Vega specification file named in.vg.json into a PNG file named out.png with a scale factor of 2.

$ vl-convert vg2png -i ./in.vg.json -o ./out.png --scale 2