Skip to content

Commit

Permalink
allow users to set Kaleido path via envionment variable (#262)
Browse files Browse the repository at this point in the history
* allow users to set Kaleido path via envionment variable

 - introduced a new feature to allow users to download Kaleido at
   compile time when the applications are targeted for the host machine
 - this can be overriden by the runtime environment variable

* add no-sanbox arg to Kaleido process
 - something fishy is happening in the CI, without this argument
   empty files are generated because of chromium security issues
 - print stderr of Kaleido to console

Signed-off-by: Andrei Gherghescu <[email protected]>
  • Loading branch information
andrei-ng authored Dec 21, 2024
1 parent ea8d95c commit b6155ab
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 94 deletions.
41 changes: 31 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
* [Introduction](#introduction)
* [Basic Usage](#basic-usage)
* [Exporting an Interactive Plot](#exporting-an-interactive-plot)
* [Exporting a Static Image](#exporting-a-static-image)
* [Exporting Static Images with Kaleido](#exporting-static-images-with-kaleido)
* [Usage Within a Wasm Environment](#usage-within-a-wasm-environment)
* [Crate Feature Flags](#crate-feature-flags)
* [Contributing](#contributing)
Expand Down Expand Up @@ -96,18 +96,38 @@ If you only want to view the plot in the browser quickly, use the `Plot.show()`
plot.show(); // The default web browser will open, displaying an interactive plot
```

## Exporting a Static Image
## Exporting Static Images with Kaleido

To save a plot as a static image, the `kaleido` feature is required:
To save a plot as a static image, the `kaleido` feature is required as well as installing an **external dependency**.

### Kaleido external dependency

When developing applications for your host, enabling both `kaleido` and `kaleido_download` features will ensure that the `kaleido` binary is downloaded for your system's architecture at compile time. After download, it is unpacked into a specific path, e.g., on Linux this is `/home/USERNAME/.config/kaleido`. With these two features enabled, static images can be exported as described in the next section as long as the application run on the same host where where this crate was compiled on.

When the applications developed with `plotly.rs` are intended for other targets or when the user wants to control where the `kaleido` binary is installed then Kaleido must be manually downloaded and installed. Setting the environment variable `KALEIDO_PATH=/path/installed/kaleido/` will ensure that applications that were built with the `kaleido` feature enabled can locate the `kaleido` executable and use it to generate static images.

Kaleido binaries are available on Github [release page](https://github.com/plotly/Kaleido/releases). It currently supports Linux(`x86_64`), Windows(`x86_64`) and MacOS(`x86_64`/`aarch64`).

## Exporting a Static Images

Enable the `kaleido` feature and opt in for automatic downloading of the `kaleido` binaries by doing the following

```toml
# Cargo.toml

[dependencies]
plotly = { version = "0.11", features = ["kaleido", "kaleido_download"] }
```

Alternatively, enable only the `kaleido` feature and manually install Kaleido.
```toml
# Cargo.toml

[dependencies]
plotly = { version = "0.11", features = ["kaleido"] }
```

With this feature enabled, plots can be saved as any of `png`, `jpeg`, `webp`, `svg`, `pdf` and `eps`. Note that the plot will be a static image, i.e. they will be non-interactive.
With the feature enabled, plots can be saved as any of `png`, `jpeg`, `webp`, `svg`, `pdf` and `eps`. Note that the plot will be a static image, i.e. they will be non-interactive.

Exporting a simple plot looks like this:

Expand All @@ -121,12 +141,6 @@ plot.add_trace(trace);
plot.write_image("out.png", ImageFormat::PNG, 800, 600, 1.0);
```

### _Kaleido dependency_

On your host, when building this project with the `kaleido` feature enabled the Kaleido binary is downloaded automatically for your system's architecture at compile time from the official Kaleido [release page](https://github.com/plotly/Kaleido/releases). This library currently supports `x86_64` on Linux and Windows, and both `x86_64` and `aarch64` on macOS.

When building application for other targets that depend on this feature, the `Kaleido` binary will need to be installed manually on the target machine. Currently, the location where the binary is expected is hardcoded depending on the target OS. E.g., on Linux this defaults to `~/.config/kaleido`. This is defined in source code [here](https://github.com/plotly/plotly.rs/blob/1405731b5121c1343b491e307222a21ef4becc5e/plotly_kaleido/src/lib.rs#L89)

## Usage Within a Wasm Environment

Using `Plotly.rs` in a Wasm-based frontend framework is possible by enabling the `wasm` feature:
Expand Down Expand Up @@ -198,6 +212,13 @@ The following feature flags are available:

Adds plot save functionality to the following formats: `png`, `jpeg`, `webp`, `svg`, `pdf` and `eps`.

Requires `Kaleido` to have been previously installed on the host machine. See the following feature flag and [Kaleido external dependency](#kaleido-external-dependency).

### `kaleido_download`

Enable download and install of Kaleido binary at build time from [Kaleido releases](https://github.com/plotly/Kaleido/releases/) on the host machine.
See [Kaleido external dependency](#kaleido-external-dependency) for more details.

### `plotly_image`

Adds trait implementations so that `image::RgbImage` and `image::RgbaImage` can be used more directly with the `plotly::Image` trace.
Expand Down
7 changes: 5 additions & 2 deletions examples/kaleido/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
[package]
name = "kaleido"
version = "0.1.0"
authors = ["Michael Freeborn <[email protected]>"]
authors = [
"Michael Freeborn <[email protected]>",
"Andrei Gherghescu [email protected]",
]
edition = "2021"

[dependencies]
plotly = { path = "../../plotly", features = ["kaleido"] }
plotly = { path = "../../plotly", features = ["kaleido", "kaleido_download"] }
12 changes: 9 additions & 3 deletions examples/kaleido/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,21 @@ fn main() {
let trace = Scatter::new(vec![0, 1, 2], vec![2, 1, 0]);
plot.add_trace(trace);

// Adjust these arguments to set the image format, width and height of the
// Adjust these arguments to set the width and height of the
// output image.
let filename = "out";
let image_format = ImageFormat::PNG;
let width = 800;
let height = 600;
let scale = 1.0;

// The image will be saved to format!("{filename}.{image_format}") relative to
// the current working directory.
plot.write_image(filename, image_format, width, height, scale);
plot.write_image(filename, ImageFormat::EPS, width, height, scale);
plot.write_image(filename, ImageFormat::JPEG, width, height, scale);
plot.write_image(filename, ImageFormat::PDF, width, height, scale);
plot.write_image(filename, ImageFormat::PNG, width, height, scale);
plot.write_image(filename, ImageFormat::SVG, width, height, scale);
plot.write_image(filename, ImageFormat::WEBP, width, height, scale);

let _svg_string = plot.to_svg(width, height, scale);
}
8 changes: 6 additions & 2 deletions plotly/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ exclude = ["target/*"]

[features]
kaleido = ["plotly_kaleido"]
kaleido_download = ["plotly_kaleido/download"]

plotly_ndarray = ["ndarray"]
plotly_image = ["image"]
# Embed JavaScript into library and templates for offline use
plotly_embed_js = []

wasm = ["getrandom", "js-sys", "wasm-bindgen", "wasm-bindgen-futures"]
with-axum = ["rinja/with-axum", "rinja_axum"]

Expand Down Expand Up @@ -48,6 +50,8 @@ image = "0.25"
itertools = ">=0.10, <0.14"
itertools-num = "0.1"
ndarray = "0.16"
plotly_kaleido = { version = "0.11", path = "../plotly_kaleido" }
plotly_kaleido = { version = "0.11", path = "../plotly_kaleido", features = [
"download",
] }
rand_distr = "0.4"
base64 = "0.22"
14 changes: 7 additions & 7 deletions plotly/src/plot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,7 @@ mod tests {
assert!(!dst.exists());
}

#[cfg(target_os = "linux")]
#[cfg(not(target_os = "macos"))]
#[test]
#[cfg(feature = "kaleido")]
fn test_save_to_png() {
Expand All @@ -761,7 +761,7 @@ mod tests {
assert!(!dst.exists());
}

#[cfg(target_os = "linux")]
#[cfg(not(target_os = "macos"))]
#[test]
#[cfg(feature = "kaleido")]
fn test_save_to_jpeg() {
Expand All @@ -773,7 +773,7 @@ mod tests {
assert!(!dst.exists());
}

#[cfg(target_os = "linux")]
#[cfg(not(target_os = "macos"))]
#[test]
#[cfg(feature = "kaleido")]
fn test_save_to_svg() {
Expand All @@ -797,7 +797,7 @@ mod tests {
assert!(!dst.exists());
}

#[cfg(target_os = "linux")]
#[cfg(not(target_os = "macos"))]
#[test]
#[cfg(feature = "kaleido")]
fn test_save_to_pdf() {
Expand All @@ -809,7 +809,7 @@ mod tests {
assert!(!dst.exists());
}

#[cfg(target_os = "linux")]
#[cfg(not(target_os = "macos"))]
#[test]
#[cfg(feature = "kaleido")]
fn test_save_to_webp() {
Expand All @@ -821,8 +821,8 @@ mod tests {
assert!(!dst.exists());
}

#[cfg(target_os = "linux")]
#[test]
#[cfg(not(target_os = "macos"))]
#[cfg(feature = "kaleido")]
fn test_image_to_base64() {
let plot = create_test_plot();
Expand All @@ -849,8 +849,8 @@ mod tests {
assert!(image_base64.is_empty());
}

#[cfg(target_os = "linux")]
#[test]
#[cfg(not(target_os = "macos"))]
#[cfg(feature = "kaleido")]
fn test_image_to_svg_string() {
let plot = create_test_plot();
Expand Down
14 changes: 11 additions & 3 deletions plotly_kaleido/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
name = "plotly_kaleido"
version = "0.11.0"
description = "Additional output format support for plotly using Kaleido"
authors = ["Ioannis Giagkiozis <[email protected]>"]
authors = [
"Ioannis Giagkiozis <[email protected]>",
"Andrei Gherghescu [email protected]",
]
license = "MIT"
readme = "README.md"
workspace = ".."
Expand All @@ -14,12 +17,17 @@ keywords = ["plot", "chart", "plotly", "ndarray"]

exclude = ["target/*", "kaleido/*", "examples/*"]

[features]
download = []

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
base64 = "0.22"
dunce = "1.0"
directories = ">=4, <6"
base64 = "0.22"

[dev-dependencies]
plotly_kaleido = { version = "0.11", path = ".", features = ["download"] }

[build-dependencies]
zip = "2.1"
Expand Down
83 changes: 51 additions & 32 deletions plotly_kaleido/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,12 @@ const KALEIDO_URL: &str =
const KALEIDO_URL: &str =
"https://github.com/plotly/Kaleido/releases/download/v0.2.1/kaleido_mac_arm64.zip";

#[cfg(target_os = "linux")]
#[cfg(any(target_os = "linux", target_os = "macos"))]
const KALEIDO_BIN: &str = "kaleido";

#[cfg(target_os = "windows")]
const KALEIDO_BIN: &str = "kaleido.exe";

#[cfg(target_os = "macos")]
const KALEIDO_BIN: &str = "kaleido";

fn extract_zip(p: &Path, zip_file: &Path) -> Result<()> {
let file = fs::File::open(zip_file).unwrap();
let mut archive = zip::ZipArchive::new(file).unwrap();
Expand Down Expand Up @@ -95,35 +92,57 @@ fn extract_zip(p: &Path, zip_file: &Path) -> Result<()> {
}

fn main() -> Result<()> {
let project_dirs = ProjectDirs::from("org", "plotly", "kaleido")
.expect("Could not create plotly_kaleido config directory.");
let dst: PathBuf = project_dirs.config_dir().into();
if cfg!(feature = "download") {
let project_dirs = ProjectDirs::from("org", "plotly", "kaleido")
.expect("Could not create Kaleido config directory path.");
let dst: PathBuf = project_dirs.config_dir().into();

let kaleido_binary = dst.join("bin").join(KALEIDO_BIN);

println!("cargo:rerun-if-changed=src/lib.rs");
println!(
"cargo::rerun-if-changed={}",
kaleido_binary.to_string_lossy()
);

println!(
"cargo:rustc-env=KALEIDO_COMPILE_TIME_DLD_PATH={}",
dst.to_string_lossy()
);

if kaleido_binary.exists() {
return Ok(());
}

let kaleido_binary = dst.join("bin").join(KALEIDO_BIN);
if kaleido_binary.exists() {
return Ok(());
let msg = format!(
"Downloaded Plotly Kaleido from {KALEIDO_URL} to '{}'",
dst.to_string_lossy()
);
println!("cargo::warning={msg}");

let p = PathBuf::from(env::var("OUT_DIR").unwrap());
let kaleido_zip_file = p.join("kaleido.zip");

let mut cmd = Command::new("cargo")
.args(["install", "ruget"])
.spawn()
.unwrap();
cmd.wait()?;

let mut cmd = Command::new("ruget")
.args([
KALEIDO_URL,
"-o",
kaleido_zip_file.as_path().to_str().unwrap(),
])
.spawn()
.unwrap();
cmd.wait()?;

extract_zip(&dst, &kaleido_zip_file)?;
} else {
let msg = "'download' feature disabled. Please install Kaleido manually and make the environment variable 'KALEIDO_PATH' point to it.".to_string();
println!("cargo::warning={msg}");
}

let p = PathBuf::from(env::var("OUT_DIR").unwrap());
let kaleido_zip_file = p.join("kaleido.zip");

let mut cmd = Command::new("cargo")
.args(["install", "ruget"])
.spawn()
.unwrap();
cmd.wait()?;

let mut cmd = Command::new("ruget")
.args([
KALEIDO_URL,
"-o",
kaleido_zip_file.as_path().to_str().unwrap(),
])
.spawn()
.unwrap();
cmd.wait()?;

extract_zip(&dst, &kaleido_zip_file)?;
println!("cargo:rerun-if-changed=src/lib.rs");
Ok(())
}
Loading

0 comments on commit b6155ab

Please sign in to comment.