From 03a5dc44482e0571341fffa499c6548f3e1023c5 Mon Sep 17 00:00:00 2001 From: Joel Natividad <1980690+jqnatividad@users.noreply.github.com> Date: Sat, 17 Oct 2026 11:50:02 -0400 Subject: [PATCH 01/17] Remove deprecated kaleido backend and EPS image format Both were deprecated in 0.13.0 with removal announced for 0.15.0. - Drop the plotly_kaleido crate from the workspace, the `kaleido` and `kaleido_download` features, and the kaleido-gated Plot methods. - Drop `ImageFormat::EPS` from plotly_static and the `#![allow(deprecated)]` that was waiting on 0.15.0. - Remove the kaleido example, its CI matrix entries, the publish_plotly_kaleido workflow and the release publish step; run the semver check against `static_export_chromedriver` instead. - Remove kaleido/EPS sections from README and the book, and point the install snippets at 0.15. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/build.yml | 13 +- .github/workflows/publish_plotly_kaleido.yml | 16 - .github/workflows/release.yml | 2 - Cargo.toml | 2 +- README.md | 63 +-- docs/book/src/fundamentals/jupyter_support.md | 2 +- .../src/fundamentals/static_image_export.md | 9 +- docs/book/src/getting_started.md | 17 +- examples/Cargo.toml | 1 - examples/kaleido/Cargo.toml | 11 - examples/kaleido/src/main.rs | 31 -- examples/static_export/README.md | 2 +- examples/static_export/src/bin/sync.rs | 2 +- plotly/Cargo.toml | 7 - plotly/src/lib.rs | 32 -- plotly/src/plot.rs | 109 +--- plotly_kaleido/.gitignore | 2 - plotly_kaleido/Cargo.toml | 34 -- plotly_kaleido/README.md | 7 - plotly_kaleido/build.rs | 148 ------ plotly_kaleido/kaleido/placeholder.md | 1 - plotly_kaleido/src/lib.rs | 479 ------------------ plotly_static/src/lib.rs | 33 +- 23 files changed, 31 insertions(+), 992 deletions(-) delete mode 100644 .github/workflows/publish_plotly_kaleido.yml delete mode 100644 examples/kaleido/Cargo.toml delete mode 100644 examples/kaleido/src/main.rs delete mode 100644 plotly_kaleido/.gitignore delete mode 100644 plotly_kaleido/Cargo.toml delete mode 100644 plotly_kaleido/README.md delete mode 100644 plotly_kaleido/build.rs delete mode 100644 plotly_kaleido/kaleido/placeholder.md delete mode 100644 plotly_kaleido/src/lib.rs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a9431a21..76611eb0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -51,7 +51,7 @@ jobs: # lint the main library workspace for non-wasm target - run: cargo clippy --features all -- -D warnings -A deprecated # lint the non-wasm examples - - run: cd ${{ github.workspace }}/examples && cargo clippy --workspace --exclude "wasm*" --exclude "kaleido" -- -D warnings + - run: cd ${{ github.workspace }}/examples && cargo clippy --workspace --exclude "wasm*" -- -D warnings # lint the plotly library for wasm target - run: cargo clippy --package plotly --target wasm32-unknown-unknown -- -D warnings # lint the wasm examples @@ -67,7 +67,7 @@ jobs: with: package: plotly feature-group: only-explicit-features - features: kaleido + features: static_export_chromedriver rust-toolchain: stable release-type: minor @@ -135,7 +135,7 @@ jobs: # Run tests on Ubuntu with Chrome - name: Run tests (${{ matrix.os }} - Chrome) if: matrix.os == 'ubuntu-latest' && matrix.browser == 'chrome' - run: cargo test --workspace --features ${{ matrix.features }} --exclude plotly_kaleido + run: cargo test --workspace --features ${{ matrix.features }} # Install xvfb for Firefox WebGL support - name: Install xvfb @@ -157,12 +157,12 @@ jobs: echo "BROWSER_PATH: $BROWSER_PATH" echo "RUST_LOG: $RUST_LOG" - xvfb-run -s "-screen 0 1920x1080x24" cargo test --workspace --features ${{ matrix.features }} --exclude plotly_kaleido + xvfb-run -s "-screen 0 1920x1080x24" cargo test --workspace --features ${{ matrix.features }} # Run tests on macOS with Chrome - name: Run tests (${{ matrix.os }} - Chrome) if: matrix.os == 'macos-latest' && matrix.browser == 'chrome' - run: cargo test --workspace --features ${{ matrix.features }} --exclude plotly_kaleido + run: cargo test --workspace --features ${{ matrix.features }} # Run tests on Windows with Chrome WebDriver - name: Run tests (${{ matrix.os }} - Chrome) @@ -180,7 +180,7 @@ jobs: Write-Host "WEBDRIVER_PATH: $env:WEBDRIVER_PATH" Write-Host "BROWSER_PATH: $env:BROWSER_PATH" - cargo test --workspace --features ${{ matrix.features }} --exclude plotly_kaleido + cargo test --workspace --features ${{ matrix.features }} - name: Upload static image(s) artifact uses: actions/upload-artifact@v4 @@ -218,7 +218,6 @@ jobs: customization/multiple_plots_example, financial_charts, images, - kaleido, maps, ndarray, scientific_charts, diff --git a/.github/workflows/publish_plotly_kaleido.yml b/.github/workflows/publish_plotly_kaleido.yml deleted file mode 100644 index 6c68c821..00000000 --- a/.github/workflows/publish_plotly_kaleido.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Publish plotly-kaleido - -on: - workflow_dispatch: - -jobs: - create-crates-io-release: - name: Deploy to crates.io - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - run: cargo login ${{ env.CRATES_IO_TOKEN }} - env: - CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} - - run: cargo publish --allow-dirty -p plotly_kaleido \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e32ec3a8..df2f88ab 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,8 +22,6 @@ jobs: install-chromedriver: true - run: cargo publish --allow-dirty -p plotly_derive - run: sleep 10 - - run: cargo publish --allow-dirty -p plotly_kaleido - - run: sleep 10 - run: cargo publish --allow-dirty -p plotly # plotly_static is not part of the same ecosystem yet so it doesn't use the same TOKEN # - run: sleep 10 diff --git a/Cargo.toml b/Cargo.toml index 4d31d6d4..955935f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] resolver = "2" -members = ["plotly", "plotly_derive", "plotly_kaleido", "plotly_static"] +members = ["plotly", "plotly_derive", "plotly_static"] diff --git a/README.md b/README.md index ad9fb845..21d7ff98 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,6 @@ * [Basic Usage](#basic-usage) * [Exporting a single Interactive Plot](#exporting-a-single-interactive-plot) * [Exporting Static Images with plotly_static (Recommended)](#exporting-static-images-with-plotly_static-recommended) - * [Exporting Static Images with Kaleido (legacy)](#exporting-static-images-with-kaleido-legacy) * [Usage Within a WASM Environment](#usage-within-a-wasm-environment) * [Timeseries Downsampling](#timeseries-downsampling) * [Crate Feature Flags](#crate-feature-flags) @@ -63,7 +62,7 @@ Add this to your `Cargo.toml`: ```toml [dependencies] -plotly = "0.14" +plotly = "0.15" ``` ## Exporting a single Interactive Plot @@ -103,7 +102,7 @@ The recommended way to export static images is using the `plotly_static` backend ```toml [dependencies] -plotly = { version = "0.14", features = ["static_export_default"] } +plotly = { version = "0.15", features = ["static_export_default"] } ``` This supports PNG, JPEG, WEBP, SVG, and PDF formats: @@ -122,56 +121,15 @@ let svg_string = plot.to_svg(800, 600, 1.0)?; **Note:** This feature requires a WebDriver-compatible browser (Chrome or Firefox) as well as a Webdriver (chromedriver/geckodriver) to be available on the system. -The above example uses the legacy API that is backwards compatible with the Kaleido API. However, for more efficient workflows a `StaticExporter` object can be built and reused between calls to `write_image`. +The above example uses the one-shot `Plot` API, which creates a new exporter for every call. However, for more efficient workflows a `StaticExporter` object can be built and reused between calls to `write_image`. More specifically, enabling any of the `plotly` features `static_export_chromedriver`, `static_export_geckodriver`, or `static_export_default` gives access to both the synchronous `StaticExporter` and the asynchronous `AsyncStaticExporter` (available via `plotly::plotly_static`). For exporter reuse and up-to-date sync/async usage patterns, please see the dedicated example in `examples/static_export`, which demonstrates both synchronous and asynchronous exporters and how to reuse a single exporter instance across multiple exports. For further details see [`plotly_static` crate documentation](https://docs.rs/plotly_static/). -## Exporting Static Images with Kaleido (legacy) - -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.14", features = ["kaleido", "kaleido_download"] } -``` - -Alternatively, enable only the `kaleido` feature and manually install Kaleido. -```toml -# Cargo.toml - -[dependencies] -plotly = { version = "0.14", features = ["kaleido"] } -``` - -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: - -```rust -use plotly::{ImageFormat, Plot}; - -let mut plot = Plot::new(); -let trace = Scatter::new(vec![0, 1, 2], vec![2, 1, 0]); -plot.add_trace(trace); - -plot.write_image("out.png", ImageFormat::PNG, 800, 600, 1.0); -``` - -### 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 runs on the same machine where it has been 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`). - ## Usage Within a WASM Environment -`Plotly.rs` can be used with a WASM-based frontend framework. Note that the `kaleido` and `plotly_static` static export features are not supported in WASM environments and will throw a compilation error if used. +`Plotly.rs` can be used with a WASM-based frontend framework. Note that the `plotly_static` static export features are not supported in WASM environments and will throw a compilation error if used. The needed dependencies are automatically enabled for `wasm32` targets at compile time and there is no longer a need for the custom `wasm` flag in this crate. @@ -184,7 +142,7 @@ First, make sure that you have the Plotly JavaScript library in your base HTML t - + @@ -263,17 +221,6 @@ When the feature is enabled, users can still opt in for the CDN version by using Note that when using `Plot::to_inline_html()`, it is assumed that the `plotly.js` library is already in scope within the HTML file, so enabling this feature flag will have no effect. -### `kaleido` (legacy) - -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` (legacy) - -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. - # Contributing * If you've spotted a bug or would like to see a new feature, please submit an issue on the [issue tracker](https://github.com/plotly/plotly.rs/issues). diff --git a/docs/book/src/fundamentals/jupyter_support.md b/docs/book/src/fundamentals/jupyter_support.md index 52927e54..937a7373 100644 --- a/docs/book/src/fundamentals/jupyter_support.md +++ b/docs/book/src/fundamentals/jupyter_support.md @@ -46,7 +46,7 @@ display a plot: **Cell 1** ``` -:dep plotly = "0.14" +:dep plotly = "0.15" ``` **Cell 2** diff --git a/docs/book/src/fundamentals/static_image_export.md b/docs/book/src/fundamentals/static_image_export.md index 931fa777..a3642ae1 100644 --- a/docs/book/src/fundamentals/static_image_export.md +++ b/docs/book/src/fundamentals/static_image_export.md @@ -23,15 +23,15 @@ The static export functionality is controlled by feature flags in the main `plot ```toml # Basic usage with manual Chromedriver installation [dependencies] -plotly = { version = "0.14", features = ["static_export_chromedriver"] } +plotly = { version = "0.15", features = ["static_export_chromedriver"] } # With automatic Chromedriver download [dependencies] -plotly = { version = "0.14", features = ["static_export_chromedriver", "static_export_wd_download"] } +plotly = { version = "0.15", features = ["static_export_chromedriver", "static_export_wd_download"] } # Recommended: Default configuration with Chromedriver + auto-download [dependencies] -plotly = { version = "0.14", features = ["static_export_default"] } +plotly = { version = "0.15", features = ["static_export_default"] } ``` > Enabling any of the static export features in `plotly` (`static_export_chromedriver`, `static_export_geckodriver`, or `static_export_default`) enables both APIs from `plotly_static`: the sync `StaticExporter` and the async `AsyncStaticExporter` (reachable as `plotly::plotly_static::AsyncStaticExporter`). Prefer the async API inside async code. @@ -110,9 +110,6 @@ exporter.close(); - **SVG**: Scalable Vector Graphics - **PDF**: Portable Document Format -### Deprecated -- **EPS**: Encapsulated PostScript (will be removed in version 0.15.0) - ## String Export For web applications or APIs, you can export to strings: diff --git a/docs/book/src/getting_started.md b/docs/book/src/getting_started.md index 5fa95ddc..9cd802f6 100644 --- a/docs/book/src/getting_started.md +++ b/docs/book/src/getting_started.md @@ -22,7 +22,7 @@ To start using [plotly.rs](https://github.com/plotly/plotly.rs) in your project ```toml [dependencies] -plotly = "0.14" +plotly = "0.15" ``` [Plotly.rs](https://github.com/plotly/plotly.rs) is ultimately a thin wrapper around the `plotly.js` library. The main job of this library is to provide `structs` and `enums` which get serialized to `json` and passed to the `plotly.js` library to actually do the heavy lifting. As such, if you are familiar with `plotly.js` or its derivatives (e.g. the equivalent Python library), then you should find [`plotly.rs`](https://github.com/plotly/plotly.rs) intuitive to use. @@ -83,7 +83,7 @@ plot.show_image(ImageFormat::PNG, 1280, 900); will display in the browser the rasterised plot; 1280 pixels wide and 900 pixels tall, in png format. -Once a satisfactory result is achieved, and assuming the [`kaleido`](#saving-plots) feature is enabled, the plot can be saved using the following: +Once a satisfactory result is achieved, and assuming one of the [static export](#static-image-export-with-webdriver-recommended) features is enabled, the plot can be saved using the following: ```rust plot.write_image("/home/user/plot_name.ext", ImageFormat::PNG, 1280, 900, 1.0); @@ -91,22 +91,13 @@ plot.write_image("/home/user/plot_name.ext", ImageFormat::PNG, 1280, 900, 1.0); The extension in the file-name path is optional as the appropriate extension (`ImageFormat::PNG`) will be included. Note that in all functions that save files to disk, both relative and absolute paths are supported. -## Saving Plots with Kaleido (legacy) - -To add the ability to save plots in the following formats: png, jpeg, webp, svg, pdf and eps, you can use the `kaleido` feature. This feature depends on [plotly/Kaleido](https://github.com/plotly/Kaleido): a cross-platform open source library for generating static images. All the necessary binaries have been included with `plotly_kaleido` for `Linux`, `Windows` and `MacOS`. Previous versions of [plotly.rs](https://github.com/plotly/plotly.rs) used the `orca` feature, however, this has been deprecated as it provided the same functionality but required additional installation steps. To enable the `kaleido` feature add the following to your `Cargo.toml`: - -```toml -[dependencies] -plotly = { version = "0.14", features = ["kaleido"] } -``` - ## Static Image Export with WebDriver (recommended) -For static image export using WebDriver and headless browsers, you can use the `plotly_static` feature. This feature supports the same formats as Kaleido (png, jpeg, webp, svg, pdf) but uses WebDriver for the static export process. To enable static export, add the following to your `Cargo.toml`: +For static image export using WebDriver and headless browsers, you can use the `plotly_static` feature. It supports png, jpeg, webp, svg and pdf, and uses WebDriver for the static export process. To enable static export, add the following to your `Cargo.toml`: ```toml [dependencies] -plotly = { version = "0.14", features = ["static_export_default"] } +plotly = { version = "0.15", features = ["static_export_default"] } ``` The `static_export_default` feature includes Chrome WebDriver support with automatic download. For Firefox support, use `static_export_geckodriver` instead. See the [Static Image Export](../fundamentals/static_image_export.md) chapter for a detailed usage example. diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 17dcd263..d82d5a5a 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -9,7 +9,6 @@ members = [ "downsampling", "financial_charts", "images", - "kaleido", "maps", "ndarray", "plotly_utils", diff --git a/examples/kaleido/Cargo.toml b/examples/kaleido/Cargo.toml deleted file mode 100644 index 5dc65140..00000000 --- a/examples/kaleido/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "kaleido" -version = "0.1.0" -authors = [ - "Michael Freeborn ", - "Andrei Gherghescu andrei-ng@protonmail.com", -] -edition = "2021" - -[dependencies] -plotly = { path = "../../plotly", features = ["kaleido", "kaleido_download"] } diff --git a/examples/kaleido/src/main.rs b/examples/kaleido/src/main.rs deleted file mode 100644 index de855bf7..00000000 --- a/examples/kaleido/src/main.rs +++ /dev/null @@ -1,31 +0,0 @@ -use plotly::{ImageFormat, Plot, Scatter}; - -fn main() { - let mut plot = Plot::new(); - let trace = Scatter::new(vec![0, 1, 2], vec![2, 1, 0]); - plot.add_trace(trace); - - // Adjust these arguments to set the width and height of the - // output image. - let width = 800; - let height = 600; - let scale = 1.0; - - std::fs::create_dir_all("./output").unwrap(); - let filename = "./output/inline_image".to_string(); - - // The image will be saved to format!("output/image.{image_format}") relative to - // the current working directory. - #[allow(deprecated)] - { - 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); - println!("SVG plot string: {svg_string}"); - } -} diff --git a/examples/static_export/README.md b/examples/static_export/README.md index 12c4ef27..e09825f3 100644 --- a/examples/static_export/README.md +++ b/examples/static_export/README.md @@ -4,7 +4,7 @@ This example demonstrates how to use the `plotly_static` crate for exporting Plo The `plotly_static` provides a interface for converting Plotly plots into various static image formats (PNG, JPEG, WEBP, SVG, PDF) using WebDriver and headless browsers. -In this example it is shown how to use the `StaticExporter` with the old style Kaleido API and also with the new style API. Using the former API is fine for one time static exports, but that API will crate an instance of the `StaticExporter` for each `write_image` call. The new style API is recommended for performance as the same instance of the `StaticExporter` can be reused across multiple exports. +In this example it is shown how to use the `StaticExporter` with the one-shot `Plot::write_image` API (the style of the former Kaleido backend) and also with the new style API. Using the former API is fine for one time static exports, but that API will crate an instance of the `StaticExporter` for each `write_image` call. The new style API is recommended for performance as the same instance of the `StaticExporter` can be reused across multiple exports. When any of the `plotly` static export features are enabled (`static_export_chromedriver`, `static_export_geckodriver`, or `static_export_default`), both `StaticExporter` (sync) and `AsyncStaticExporter` (async) are available via `plotly::plotly_static`. This example includes separate `sync` and `async` bins demonstrating both. Refer to the [`plotly_static` API Documentation](https://docs.rs/plotly_static/) a more detailed description. diff --git a/examples/static_export/src/bin/sync.rs b/examples/static_export/src/bin/sync.rs index 8cb93a98..4be47620 100644 --- a/examples/static_export/src/bin/sync.rs +++ b/examples/static_export/src/bin/sync.rs @@ -19,7 +19,7 @@ fn main() -> Result<(), Box> { std::fs::create_dir_all("./output").unwrap(); - info!("Exporting multiple plots using a Kaleido style API ..."); + info!("Exporting multiple plots using the one-shot Plot::write_image API ..."); plot1.write_image("./output/plot1_legacy_api", ImageFormat::PNG, 800, 600, 1.0)?; plot2.write_image( "./output/plot2_legacy_api", diff --git a/plotly/Cargo.toml b/plotly/Cargo.toml index 723576fd..ebf8e24f 100644 --- a/plotly/Cargo.toml +++ b/plotly/Cargo.toml @@ -49,12 +49,6 @@ all = [ # This is used for enabling extra debugging messages and debugging functionality debug = ["plotly_static?/debug"] -# DEPRECATED: kaleido feature will be removed in version 0.15.0. Use `static_export_*` features instead. -kaleido = ["plotly_kaleido"] -# DEPRECATED: kaleido_download feature will be removed in version 0.15.0. Use `static_export_wd_download` instead. -kaleido_download = ["plotly_kaleido/download"] - - [dependencies] askama = { version = "0.16.0", features = ["serde_json"] } dyn-clone = "1" @@ -62,7 +56,6 @@ erased-serde = "0.4" image = { version = "0.25", optional = true } plotly_derive = { version = "0.14", path = "../plotly_derive" } plotly_static = { version = "0.1", path = "../plotly_static", optional = true } -plotly_kaleido = { version = "0.13", path = "../plotly_kaleido", optional = true } ndarray = { version = "0.17", optional = true } once_cell = "1" serde = { version = "1.0", features = ["derive"] } diff --git a/plotly/src/lib.rs b/plotly/src/lib.rs index d40a09d0..62e4fce4 100644 --- a/plotly/src/lib.rs +++ b/plotly/src/lib.rs @@ -1,41 +1,11 @@ //! # Plotly.rs //! //! A plotting library for Rust powered by [Plotly.js](https://plot.ly/javascript/). -//! -//! ## Feature Deprecation Notice -//! -//! The `kaleido` and `kaleido_download` features are deprecated since version -//! 0.13.0 and will be removed in version 0.15.0. Please migrate to the -//! `plotly_static` and `static_export_*` features instead. #![recursion_limit = "256"] // lets us use a large serde_json::json! macro for testing crate::layout::Axis extern crate askama; extern crate rand; extern crate serde; -#[cfg(feature = "kaleido")] -#[deprecated( - since = "0.13.0", - note = "kaleido feature is deprecated and will be removed in version 0.15.0. Use plotly_static feature instead" -)] -const _KALEIDO_DEPRECATED: () = (); - -#[cfg(feature = "kaleido_download")] -#[deprecated( - since = "0.13.0", - note = "kaleido_download feature is deprecated and will be removed in version 0.15.0. Use plotly_static_download feature instead" -)] -const _KALEIDO_DOWNLOAD_DEPRECATED: () = (); - -#[cfg(all(feature = "kaleido", target_family = "wasm"))] -compile_error!( - r#"The "kaleido" feature is not available on "wasm" targets. Please compile without this feature for the wasm target family."# -); - -#[cfg(all(feature = "kaleido", feature = "plotly_static"))] -compile_error!( - r#"The "kaleido" feature and "plotly_static" are conflictings. Please select only one of them."# -); - #[cfg(feature = "plotly_ndarray")] pub mod ndarray; #[cfg(feature = "plotly_ndarray")] @@ -75,8 +45,6 @@ pub use traces::{ pub trait Restyle: serde::Serialize {} pub trait Relayout {} -#[cfg(feature = "kaleido")] -pub use plotly_kaleido::ImageFormat; #[cfg(feature = "plotly_static")] pub use plotly_static::{self, ImageFormat}; diff --git a/plotly/src/plot.rs b/plotly/src/plot.rs index 32cd770b..351b9b9f 100644 --- a/plotly/src/plot.rs +++ b/plotly/src/plot.rs @@ -5,8 +5,6 @@ use std::{fs::File, io::Write, path::Path}; use askama::Template; use dyn_clone::DynClone; use erased_serde::Serialize as ErasedSerialize; -#[cfg(feature = "kaleido")] -use plotly_kaleido::ImageFormat; #[cfg(feature = "plotly_static")] use plotly_static::ImageFormat; use rand::{ @@ -27,7 +25,7 @@ struct PlotTemplate<'a> { js_scripts: &'a str, } -#[cfg(any(feature = "kaleido", feature = "plotly_static"))] +#[cfg(feature = "plotly_static")] #[derive(Template)] #[template(path = "static_plot.html", escape = "none")] #[cfg(all(not(target_family = "wasm"), not(target_os = "android")))] @@ -55,14 +53,13 @@ struct JupyterNotebookPlotTemplate<'a> { #[cfg(all(not(target_family = "wasm"), not(target_os = "android")))] const DEFAULT_HTML_APP_NOT_FOUND: &str = r#"Could not find default application for HTML files. -Consider using the `to_html` method obtain a string representation instead. If using the `kaleido` or `plotly_static` feature the +Consider using the `to_html` method obtain a string representation instead. If using the `plotly_static` feature the `write_image` method can be used to produce a static image in one of the following formats: - ImageFormat::PNG - ImageFormat::JPEG - ImageFormat::WEBP - ImageFormat::SVG - ImageFormat::PDF -- ImageFormat::EPS // will be removed in version 0.15.0 Used as follows: let plot = Plot::new(); @@ -318,7 +315,7 @@ impl Plot { /// Display the fully rendered `Plot` as a static image of the given format /// in the default system browser. #[cfg(all(not(target_family = "wasm"), not(target_os = "android")))] - #[cfg(any(feature = "kaleido", feature = "plotly_static"))] + #[cfg(feature = "plotly_static")] pub fn show_image(&self, format: ImageFormat, width: usize, height: usize) { use std::env; @@ -433,100 +430,6 @@ impl Plot { self.lab_display(); } - /// Convert the `Plot` to a static image of the given image format and save - /// at the given location using kaleido. - /// - /// This function is deprecated since version 0.13.0. The kaleido-based - /// implementation will be removed in version 0.15.0. Use - /// `plotly_static` feature instead for static image export functionality. - #[deprecated( - since = "0.13.0", - note = "kaleido-based implementation is deprecated. Use plotly_static feature instead. The kaleido implementation will be removed in version 0.15.0" - )] - #[cfg(feature = "kaleido")] - pub fn write_image>( - &self, - filename: P, - format: ImageFormat, - width: usize, - height: usize, - scale: f64, - ) { - let kaleido = plotly_kaleido::Kaleido::new(); - kaleido - .save( - filename.as_ref(), - &serde_json::to_value(self).unwrap(), - format, - width, - height, - scale, - ) - .unwrap_or_else(|_| panic!("failed to export plot to {:?}", filename.as_ref())); - } - - /// Convert the `Plot` to a static image and return the image as a `base64` - /// String using kaleido. Supported formats are [ImageFormat::JPEG], - /// [ImageFormat::PNG] and [ImageFormat::WEBP] - /// - /// This function is deprecated since version 0.13.0. The kaleido-based - /// implementation will be removed in version 0.15.0. Use - /// `plotly_static` feature instead for static image export functionality. - #[deprecated( - since = "0.13.0", - note = "kaleido-based implementation is deprecated. Use plotly_static feature instead. The kaleido implementation will be removed in version 0.15.0" - )] - #[cfg(feature = "kaleido")] - pub fn to_base64( - &self, - format: ImageFormat, - width: usize, - height: usize, - scale: f64, - ) -> String { - match format { - ImageFormat::JPEG | ImageFormat::PNG | ImageFormat::WEBP => { - let kaleido = plotly_kaleido::Kaleido::new(); - kaleido - .image_to_string( - &serde_json::to_value(self).unwrap(), - format, - width, - height, - scale, - ) - .unwrap_or_else(|_| panic!("Kaleido failed to generate image")) - } - _ => { - eprintln!("Cannot generate base64 string for ImageFormat:{format}. Allowed formats are JPEG, PNG, WEBP"); - String::default() - } - } - } - - /// Convert the `Plot` to SVG and return it as a String using kaleido. - /// - /// This function is deprecated since version 0.13.0. The kaleido-based - /// implementation will be removed in version 0.15.0. Use - /// `plotly_static` feature instead for static image export functionality. - #[deprecated( - since = "0.13.0", - note = "kaleido-based implementation is deprecated. Use plotly_static feature instead. The kaleido implementation will be removed in version 0.15.0" - )] - #[cfg(feature = "kaleido")] - pub fn to_svg(&self, width: usize, height: usize, scale: f64) -> String { - let kaleido = plotly_kaleido::Kaleido::new(); - kaleido - .image_to_string( - &serde_json::to_value(self).unwrap(), - ImageFormat::SVG, - width, - height, - scale, - ) - .unwrap_or_else(|_| panic!("Kaleido failed to generate image")) - } - /// Convert the `Plot` to a static image of the given image format and save /// at the given location. /// @@ -699,7 +602,7 @@ impl Plot { } #[cfg(all(not(target_family = "wasm"), not(target_os = "android")))] - #[cfg(any(feature = "kaleido", feature = "plotly_static"))] + #[cfg(feature = "plotly_static")] pub fn render_static(&self, format: &ImageFormat, width: usize, height: usize) -> String { let tmpl = StaticPlotTemplate { plot: self, @@ -839,12 +742,10 @@ impl PartialEq for Plot { mod tests { use std::path::PathBuf; - #[cfg(feature = "kaleido")] - use plotly_kaleido::ImageFormat; #[cfg(feature = "plotly_static")] use plotly_static::ImageFormat; use serde_json::{json, to_value}; - #[cfg(any(feature = "kaleido", feature = "plotly_static"))] + #[cfg(feature = "plotly_static")] use {base64::engine::general_purpose, base64::Engine}; use super::*; diff --git a/plotly_kaleido/.gitignore b/plotly_kaleido/.gitignore deleted file mode 100644 index f3c022cf..00000000 --- a/plotly_kaleido/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -examples/downloader.rs -kaleido/* \ No newline at end of file diff --git a/plotly_kaleido/Cargo.toml b/plotly_kaleido/Cargo.toml deleted file mode 100644 index a935e3d7..00000000 --- a/plotly_kaleido/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "plotly_kaleido" -version = "0.13.6" -description = "Additional output format support for plotly using Kaleido" -authors = [ - "Ioannis Giagkiozis ", - "Andrei Gherghescu andrei-ng@protonmail.com", -] -license = "MIT" -readme = "README.md" -workspace = ".." -homepage = "https://github.com/plotly/plotly.rs" -documentation = "https://docs.rs/plotly_kaleido" -repository = "https://github.com/plotly/plotly.rs" -edition = "2021" -keywords = ["plot", "chart", "plotly", "ndarray"] - -exclude = ["target/*", "kaleido/*", "examples/*"] - -[features] -download = [] - -[dependencies] -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -dunce = "1.0" -base64 = "0.23" - -[dev-dependencies] -plotly_kaleido = { path = ".", features = ["download"] } - -[build-dependencies] -zip = "8.0" -directories = ">=4, <7" diff --git a/plotly_kaleido/README.md b/plotly_kaleido/README.md deleted file mode 100644 index 0d0eae99..00000000 --- a/plotly_kaleido/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# plotly_kaleido - -This is an internal crate which implements the `kaleido` feature for [Plotly.rs](https://github.com/plotly/plotly.rs). - -The `kaleido` feature enables `Plot` conversion to the following output formats: `png`, `jpeg`, `webp`, `svg`, `pdf` and `eps`. - -See [examples/](https://github.com/plotly/plotly.rs/tree/main/examples/kaleido) for usage demonstrations. \ No newline at end of file diff --git a/plotly_kaleido/build.rs b/plotly_kaleido/build.rs deleted file mode 100644 index b0df880f..00000000 --- a/plotly_kaleido/build.rs +++ /dev/null @@ -1,148 +0,0 @@ -extern crate directories; -extern crate zip; - -use std::env; -use std::fs; -use std::io; -use std::io::Result; -use std::path::{Path, PathBuf}; -use std::process::Command; - -use directories::ProjectDirs; - -#[cfg(all(target_arch = "x86_64", target_os = "linux"))] -const KALEIDO_URL: &str = - "https://github.com/plotly/Kaleido/releases/download/v0.2.1/kaleido_linux_x64.zip"; - -#[cfg(all(target_arch = "aarch64", target_os = "linux"))] -const KALEIDO_URL: &str = - "https://github.com/plotly/Kaleido/releases/download/v0.2.1/kaleido_linux_arm64.zip"; - -#[cfg(target_os = "windows")] -const KALEIDO_URL: &str = - "https://github.com/plotly/Kaleido/releases/download/v0.2.1/kaleido_win_x64.zip"; - -#[cfg(all(target_arch = "x86_64", target_os = "macos"))] -const KALEIDO_URL: &str = - "https://github.com/plotly/Kaleido/releases/download/v0.2.1/kaleido_mac_x64.zip"; - -#[cfg(all(target_arch = "aarch64", target_os = "macos"))] -const KALEIDO_URL: &str = - "https://github.com/plotly/Kaleido/releases/download/v0.2.1/kaleido_mac_arm64.zip"; - -#[cfg(any(target_os = "linux", target_os = "macos"))] -const KALEIDO_BIN: &str = "kaleido"; - -#[cfg(target_os = "windows")] -const KALEIDO_BIN: &str = "kaleido.exe"; - -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(); - - for i in 0..archive.len() { - let mut file = archive.by_index(i).unwrap(); - let outpath = file.mangled_name(); - let outpath = p.join(outpath); - println!("outpath: {outpath:?}"); - - { - let comment = file.comment(); - if !comment.is_empty() { - println!("File {i} comment: {comment}"); - } - } - - if (file.name()).ends_with('/') { - println!( - "File {} extracted to \"{}\"", - i, - outpath.as_path().display() - ); - fs::create_dir_all(&outpath).unwrap(); - } else { - println!( - "File {} extracted to \"{}\" ({} bytes)", - i, - outpath.as_path().display(), - file.size() - ); - if let Some(p) = outpath.parent() { - if !p.exists() { - fs::create_dir_all(p).unwrap(); - } - } - let mut outfile = fs::File::create(&outpath).unwrap(); - io::copy(&mut file, &mut outfile).unwrap(); - } - - // Get and Set permissions - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - - if let Some(mode) = file.unix_mode() { - fs::set_permissions(&outpath, fs::Permissions::from_mode(mode)).unwrap(); - } - } - } - - fs::remove_file(zip_file)?; - Ok(()) -} - -fn main() -> Result<()> { - 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 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}"); - } - Ok(()) -} diff --git a/plotly_kaleido/kaleido/placeholder.md b/plotly_kaleido/kaleido/placeholder.md deleted file mode 100644 index a141c566..00000000 --- a/plotly_kaleido/kaleido/placeholder.md +++ /dev/null @@ -1 +0,0 @@ -placeholder for plotly/Kaleido diff --git a/plotly_kaleido/src/lib.rs b/plotly_kaleido/src/lib.rs deleted file mode 100644 index 6f18b114..00000000 --- a/plotly_kaleido/src/lib.rs +++ /dev/null @@ -1,479 +0,0 @@ -//! # Plotly Kaleido -//! Plotly Kaleido implements the `kaleido` feature for [Plotly.rs](https://github.com/plotly/plotly.rs) -//! -//! The `kaleido` feature enables `Plot` conversion to the following output -//! formats: png, jpeg, webp, svg, pdf and eps. It has the added benefit over -//! the `orca` feature in that there is no need for an additional installation -//! for the feature to function properly. The Kaleido precompiled binaries are -//! packaged redistributed from the original repository [plotly/Kaleido](https://github.com/plotly/Kaleido). -//! -//! Note that [plotly/Kaleido](https://github.com/plotly/Kaleido) is still in pre-release and as such the `kaleido` -//! feature should be considered in pre-release mode as well. - -#![cfg(not(target_family = "wasm"))] -use std::fs::File; -use std::io::prelude::*; -use std::io::BufReader; -use std::path::{Path, PathBuf}; -use std::process::{Command, Stdio}; - -use base64::{engine::general_purpose, Engine as _}; -use serde::{Deserialize, Serialize}; -use serde_json::Value; - -/// Image format for static image export using Kaleido. -/// -/// This enum defines all the image formats that can be exported from Plotly -/// plots using the Kaleido engine. Kaleido supports all these formats natively. -#[derive(Debug, Clone)] -#[allow(deprecated)] -pub enum ImageFormat { - /// Portable Network Graphics format - PNG, - /// Joint Photographic Experts Group format - JPEG, - /// WebP format (Google's image format) - WEBP, - /// Scalable Vector Graphics format - SVG, - /// Portable Document Format - PDF, - /// Encapsulated PostScript format (deprecated) - /// - /// This format is deprecated since version 0.13.0 and will be removed in - /// version 0.15.0. Use SVG or PDF instead for vector graphics. EPS is - /// not supported in the open source version. - #[deprecated( - since = "0.13.0", - note = "Use SVG or PDF instead. EPS variant will be removed in version 0.15.0" - )] - EPS, -} - -impl std::fmt::Display for ImageFormat { - /// Converts the ImageFormat to its lowercase string representation. - /// - /// # Examples - /// - /// ```rust - /// use plotly_kaleido::ImageFormat; - /// - /// assert_eq!(ImageFormat::PNG.to_string(), "png"); - /// assert_eq!(ImageFormat::SVG.to_string(), "svg"); - /// assert_eq!(ImageFormat::PDF.to_string(), "pdf"); - /// assert_eq!(ImageFormat::EPS.to_string(), "eps"); - /// ``` - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Self::PNG => "png", - Self::JPEG => "jpeg", - Self::WEBP => "webp", - Self::SVG => "svg", - Self::PDF => "pdf", - #[allow(deprecated)] - Self::EPS => "eps", - } - ) - } -} - -impl serde::Serialize for ImageFormat { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(&self.to_string()) - } -} - -#[allow(dead_code)] -#[derive(Deserialize, Debug)] -struct KaleidoResult { - code: i32, - message: Option, - #[serde(rename = "pdfBgColor")] - pdf_background_color: Option, - format: Option, - result: Option, - width: Option, - height: Option, - scale: Option, - version: Option, -} - -impl KaleidoResult { - fn from(result: &str) -> KaleidoResult { - serde_json::from_str(result).unwrap() - } -} - -#[derive(Serialize)] -struct PlotData<'a> { - format: ImageFormat, - width: usize, - height: usize, - scale: f64, - // TODO: it would be great if this could be a plotly::Plot, but with the current workspace set - // up, that would be a cyclic dependency. - data: &'a Value, -} - -impl<'a> PlotData<'a> { - fn new( - data: &'a Value, - format: ImageFormat, - width: usize, - height: usize, - scale: f64, - ) -> PlotData<'a> { - PlotData { - format, - width, - height, - scale, - data, - } - } - - fn to_json(&self) -> String { - serde_json::to_string(self).unwrap() - } -} - -#[derive(Default)] -pub struct Kaleido { - cmd_path: PathBuf, -} - -impl Kaleido { - const KALEIDO_PATH_ENV: &str = "KALEIDO_PATH"; - - pub fn new() -> Kaleido { - use std::env; - - let path = match env::var(Self::KALEIDO_PATH_ENV) { - Ok(runtime_env) => runtime_env, - Err(runtime_env_err) => match option_env!("KALEIDO_COMPILE_TIME_DLD_PATH") { - Some(compile_time_path) => compile_time_path.to_string(), - None => { - println!("{}: {}", Self::KALEIDO_PATH_ENV, runtime_env_err); - println!("Use `kaleido_download` feature to automatically download, install and use Kaleido when targeting applications that run on the host machine."); - println!("Use `{}` environment variable when targeting applications intended to run on different machines. Manually install Kaleido on the target machine and point {} to the installation location.", Self::KALEIDO_PATH_ENV, Self::KALEIDO_PATH_ENV - ); - std::process::exit(1); - } - }, - }; - - let path = match Kaleido::binary_path(&path) { - Ok(kaleido_path) => kaleido_path, - Err(msg) => panic!("Failed tu use Kaleido binary at {path} due to {msg}"), - }; - - Kaleido { cmd_path: path } - } - - fn binary_path(dld_path: &str) -> Result { - let mut p = PathBuf::from(dld_path); - p = Self::os_binary_path(p); - if !p.exists() { - return Err("could not find kaleido executable in path"); - } - Ok(p) - } - - #[cfg(any(target_os = "linux", target_os = "macos"))] - fn os_binary_path(path: PathBuf) -> PathBuf { - match path.join("kaleido").canonicalize() { - Ok(v) => v, - Err(e) => { - println!( - "Failed to find Kaleido binary at '{}': {e}", - path.to_string_lossy() - ); - panic!("{e}"); - } - } - } - - #[cfg(target_os = "windows")] - fn os_binary_path(path: PathBuf) -> PathBuf { - path.join("kaleido.cmd") - } - - /// Generate a static image from a Plotly graph and save it to a file - pub fn save( - &self, - dst: &Path, - plotly_data: &Value, - format: ImageFormat, - width: usize, - height: usize, - scale: f64, - ) -> Result<(), Box> { - let mut dst = PathBuf::from(dst); - dst.set_extension(format.to_string()); - - let image_data = self.convert(plotly_data, format.clone(), width, height, scale)?; - #[allow(deprecated)] - let data = match format { - ImageFormat::SVG | ImageFormat::EPS => image_data.as_bytes(), - _ => &general_purpose::STANDARD.decode(image_data).unwrap(), - }; - let mut file = File::create(dst.as_path())?; - file.write_all(data)?; - file.flush()?; - - Ok(()) - } - - /// Generate a static image from a Plotly graph and return it as a String - /// The output may be base64 encoded or a plain text depending on the image - /// format provided as argument. SVG and EPS are returned in plain text - /// while JPEG, PNG, WEBP will be returned as a base64 encoded string. - pub fn image_to_string( - &self, - plotly_data: &Value, - format: ImageFormat, - width: usize, - height: usize, - scale: f64, - ) -> Result> { - let image_data = self.convert(plotly_data, format.clone(), width, height, scale)?; - Ok(image_data) - } - - /// Convert the Plotly graph to a static image using Kaleido and return the - /// result as a String - pub fn convert( - &self, - plotly_data: &Value, - format: ImageFormat, - width: usize, - height: usize, - scale: f64, - ) -> Result> { - let p = self.cmd_path.to_str().unwrap(); - let format_str = format.to_string(); - - // Removed flag 'disable-gpu' as it causes issues on MacOS and other platforms - // see Kaleido issue #323 - let cmd_args = vec![ - "plotly", - "--allow-file-access-from-files", - "--disable-breakpad", - "--disable-dev-shm-usage", - "--disable-software-rasterizer", - "--single-process", - "--no-sandbox", - ]; - - #[allow(clippy::zombie_processes)] - let mut process = Command::new(p) - .current_dir(self.cmd_path.parent().unwrap()) - .args(cmd_args) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap_or_else(|_| { - panic!( - "{}", - format!( - "failed to spawn Kaleido binary at {}", - self.cmd_path.to_string_lossy() - ) - .to_string() - ) - }); - { - let plot_data = PlotData::new(plotly_data, format, width, height, scale).to_json(); - let mut process_stdin = process.stdin.take().unwrap(); - process_stdin - .write_all(plot_data.as_bytes()) - .expect("couldn't write to Kaleido stdin"); - process_stdin.flush()?; - } - - let output_lines = BufReader::new(process.stdout.take().unwrap()).lines(); - for line in output_lines.map_while(Result::ok) { - let res = KaleidoResult::from(line.as_str()); - if let Some(image_data) = res.result { - // TODO: this should be refactored - // The assumption is that KaleidoResult contains a single image. - // We should end the loop on the first valid one. - // If that is not the case, prior implementation would have returned the last - // valid image - return Ok(image_data); - } - } - - // Don't eat up Kaleido/Chromium errors but show them in the terminal - println!("Kaleido failed to generate static image for format: {format_str}."); - println!("Kaleido stderr output:"); - let stderr = process.stderr.take().unwrap(); - let stderr_lines = BufReader::new(stderr).lines(); - for line in stderr_lines { - let line = line.unwrap(); - eprintln!("{line}"); - } - - Ok(String::default()) - } -} - -#[cfg(test)] -mod tests { - use std::path::PathBuf; - - use serde_json::{json, to_value}; - - use super::*; - - fn create_test_plot() -> Value { - to_value(json!({ - "data": [ - { - "name": "Surface", - "type": "surface", - "x": [ - 1.0, - 2.0, - 3.0 - ], - "y": [ - 4.0, - 5.0, - 6.0 - ], - "z": [ - [ - 1.0, - 2.0, - 3.0 - ], - [ - 4.0, - 5.0, - 6.0 - ], - [ - 7.0, - 8.0, - 9.0 - ] - ] - } - ], - "layout": {} - })) - .unwrap() - } - - #[test] - fn can_find_kaleido_executable() { - let _k = Kaleido::new(); - } - - #[test] - fn plot_data_to_json() { - let test_plot = create_test_plot(); - let kaleido_data = PlotData::new(&test_plot, ImageFormat::PNG, 400, 500, 1.); - let expected = json!({ - "data": test_plot, - "format": "png", - "width": 400, - "height": 500, - "scale": 1.0 - }); - - assert_eq!(to_value(kaleido_data).unwrap(), expected); - } - - // For MacOS failures, see issue #241 and upstream https://github.com/plotly/Kaleido/issues/323 is resolved - #[test] - fn save_png() { - let test_plot = create_test_plot(); - let k = Kaleido::new(); - let dst = PathBuf::from("example.png"); - let r = k.save(dst.as_path(), &test_plot, ImageFormat::PNG, 1200, 900, 4.5); - assert!(r.is_ok()); - assert!(dst.exists()); - let metadata = std::fs::metadata(&dst).expect("Could not retrieve file metadata"); - let file_size = metadata.len(); - assert!(file_size > 0,); - assert!(std::fs::remove_file(dst.as_path()).is_ok()); - } - - #[test] - fn save_jpeg() { - let test_plot = create_test_plot(); - let k = Kaleido::new(); - let dst = PathBuf::from("example.jpeg"); - let r = k.save(dst.as_path(), &test_plot, ImageFormat::JPEG, 1200, 900, 4.5); - assert!(r.is_ok()); - assert!(dst.exists()); - let metadata = std::fs::metadata(&dst).expect("Could not retrieve file metadata"); - let file_size = metadata.len(); - assert!(file_size > 0,); - assert!(std::fs::remove_file(dst.as_path()).is_ok()); - } - - #[test] - fn save_webp() { - let test_plot = create_test_plot(); - let k = Kaleido::new(); - let dst = PathBuf::from("example.webp"); - let r = k.save(dst.as_path(), &test_plot, ImageFormat::WEBP, 1200, 900, 4.5); - assert!(r.is_ok()); - assert!(dst.exists()); - let metadata = std::fs::metadata(&dst).expect("Could not retrieve file metadata"); - let file_size = metadata.len(); - assert!(file_size > 0,); - assert!(std::fs::remove_file(dst.as_path()).is_ok()); - } - - #[test] - fn save_svg() { - let test_plot = create_test_plot(); - let k = Kaleido::new(); - let dst = PathBuf::from("example.svg"); - let r = k.save(dst.as_path(), &test_plot, ImageFormat::SVG, 1200, 900, 4.5); - assert!(r.is_ok()); - assert!(dst.exists()); - let metadata = std::fs::metadata(&dst).expect("Could not retrieve file metadata"); - let file_size = metadata.len(); - assert!(file_size > 0,); - assert!(std::fs::remove_file(dst.as_path()).is_ok()); - } - - #[test] - fn save_pdf() { - let test_plot = create_test_plot(); - let k = Kaleido::new(); - let dst = PathBuf::from("example.pdf"); - let r = k.save(dst.as_path(), &test_plot, ImageFormat::PDF, 1200, 900, 4.5); - assert!(r.is_ok()); - assert!(dst.exists()); - let metadata = std::fs::metadata(&dst).expect("Could not retrieve file metadata"); - let file_size = metadata.len(); - assert!(file_size > 0,); - assert!(std::fs::remove_file(dst.as_path()).is_ok()); - } - - // Kaleido generates empty eps files - #[test] - #[ignore] - fn save_eps() { - let test_plot = create_test_plot(); - let k = Kaleido::new(); - let dst = PathBuf::from("example.eps"); - #[allow(deprecated)] - let r = k.save(dst.as_path(), &test_plot, ImageFormat::EPS, 1200, 900, 4.5); - assert!(r.is_ok()); - assert!(std::fs::remove_file(dst.as_path()).is_ok()); - } -} diff --git a/plotly_static/src/lib.rs b/plotly_static/src/lib.rs index 347cdc1c..af28fa6b 100644 --- a/plotly_static/src/lib.rs +++ b/plotly_static/src/lib.rs @@ -262,6 +262,9 @@ //! //! ## Comparison with Kaleido //! +//! `plotly_static` replaces the Kaleido-based `plotly_kaleido` backend, which +//! was removed in plotly.rs 0.15.0. +//! //! - **No custom Chromium/Chrome external dependency**: Uses standard WebDriver //! instead of proprietary Kaleido //! - **Better Browser Support**: Works with any WebDriver-compatible browser: @@ -273,15 +276,13 @@ //! //! - Requires a WebDriver-compatible browser //! - PDF export uses browser JavaScript `html2pdf` (not native Plotly PDF) -//! - EPS is no longer supported and will be removed +//! - EPS is not supported (removed in 0.2.0); use SVG or PDF instead //! - Slightly slower than Kaleido //! //! ## License //! //! MIT License - see LICENSE file for details. -// TODO: remove this once version 0.15.0 is out -#![allow(deprecated)] use std::fs::File; use std::io::prelude::*; use std::path::{Path, PathBuf}; @@ -322,14 +323,6 @@ const DRIVER_FEATURE_REQUIRED: &str = "Static image export at runtime requires e /// - **SVG**: Scalable Vector Graphics format (vector-based, scalable) /// - **PDF**: Portable Document Format (implemented via browser JS) /// -/// # Deprecated Formats -/// -/// - **EPS**: Encapsulated PostScript format (deprecated since 0.13.0, will be -/// removed in 0.15.0) -/// - Use SVG or PDF instead for vector graphics -/// - EPS is not supported in the open source version and in versions prior to -/// 0.13.0 has been generating empty images. -/// /// # Examples /// /// ```rust @@ -339,7 +332,6 @@ const DRIVER_FEATURE_REQUIRED: &str = "Static image export at runtime requires e /// assert_eq!(format.to_string(), "png"); /// ``` #[derive(Debug, Clone, Serialize)] -#[allow(deprecated)] pub enum ImageFormat { /// Portable Network Graphics format PNG, @@ -351,16 +343,6 @@ pub enum ImageFormat { SVG, /// Portable Document Format (implemented via browser JS) PDF, - /// Encapsulated PostScript format (deprecated) - /// - /// This format is deprecated since version 0.13.0 and will be removed in - /// version 0.15.0. Use SVG or PDF instead for vector graphics. EPS is - /// not supported in the open source Plotly ecosystem version. - #[deprecated( - since = "0.13.0", - note = "Use SVG or PDF instead. EPS variant will be removed in version 0.15.0" - )] - EPS, } impl std::fmt::Display for ImageFormat { @@ -383,8 +365,6 @@ impl std::fmt::Display for ImageFormat { Self::WEBP => "webp", Self::SVG => "svg", Self::PDF => "pdf", - #[allow(deprecated)] - Self::EPS => "eps", } ) } @@ -1128,11 +1108,6 @@ impl AsyncStaticExporter { ImageFormat::PNG | ImageFormat::JPEG | ImageFormat::WEBP | ImageFormat::PDF => { common::extract_encoded(result, &plot.format) } - #[allow(deprecated)] - ImageFormat::EPS => { - error!("EPS format is deprecated. Use SVG or PDF instead."); - common::extract_encoded(result, &plot.format) - } } } From f1d5d067cef092ad65d7185fac2a947108833801 Mon Sep 17 00:00:00 2001 From: Joel Natividad <1980690+jqnatividad@users.noreply.github.com> Date: Sat, 17 Oct 2026 11:52:21 -0400 Subject: [PATCH 02/17] Bump dependencies and drop unused zip build-dependency - plotly_static: remove the `zip` build-dependency, which build.rs never used; bump `dirs` 6 -> 7. - plotly_derive: bump `darling` 0.23 -> 0.24 together with `syn` 2 -> 3, since darling 0.24 is built on syn 3. No macro code changes needed. - examples: bump reqwest 0.11 -> 0.13, env_logger 0.10 -> 0.11, itertools 0.12 -> 0.15, ndarray 0.15 -> 0.17 and rand/rand_chacha 0.8/0.3 -> 0.10, adapting to `RngExt::random` and dropping the deprecated `into_raw_vec`. zip 8.x is still pulled in transitively by webdriver-downloader 0.17 (`zip ^8`) until ik1ne/webdriver-downloader#67 is released. Co-Authored-By: Claude Opus 5.5 --- examples/custom_controls/Cargo.toml | 4 ++-- examples/custom_controls/src/main.rs | 2 +- .../customization/consistent_static_format_export/Cargo.toml | 2 +- examples/financial_charts/Cargo.toml | 4 ++-- examples/financial_charts/src/main.rs | 4 ++-- examples/maps/Cargo.toml | 2 +- plotly_derive/Cargo.toml | 4 ++-- plotly_static/Cargo.toml | 3 +-- 8 files changed, 12 insertions(+), 13 deletions(-) diff --git a/examples/custom_controls/Cargo.toml b/examples/custom_controls/Cargo.toml index d0d4da93..b3abdeee 100644 --- a/examples/custom_controls/Cargo.toml +++ b/examples/custom_controls/Cargo.toml @@ -5,11 +5,11 @@ authors = ["Michael Freeborn "] edition = "2021" [dependencies] -itertools = "0.12" +itertools = "0.15" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" csv = "1.3" -ndarray = "0.15" +ndarray = "0.17" plotly = { path = "../../plotly" } plotly_utils = { path = "../plotly_utils" } \ No newline at end of file diff --git a/examples/custom_controls/src/main.rs b/examples/custom_controls/src/main.rs index 1643b69d..ed2c7b53 100644 --- a/examples/custom_controls/src/main.rs +++ b/examples/custom_controls/src/main.rs @@ -271,7 +271,7 @@ fn sinusoidal_slider_example(show: bool, file_name: &str) { // Add traces, one for each slider step (frequency parameter) for step in 0..=num_steps { let frequency = step as f64 / 10.0; // 0.0, 0.1, ..., 5.0 - let x: Vec = Array::linspace(0.0, 10.0, 1001).into_raw_vec().to_vec(); + let x: Vec = Array::linspace(0.0, 10.0, 1001).to_vec(); let y: Vec = x.iter().map(|&x_val| (frequency * x_val).sin()).collect(); let trace = Scatter::new(x, y) .visible(if step == 10 { diff --git a/examples/customization/consistent_static_format_export/Cargo.toml b/examples/customization/consistent_static_format_export/Cargo.toml index 937c6512..70cf9d14 100644 --- a/examples/customization/consistent_static_format_export/Cargo.toml +++ b/examples/customization/consistent_static_format_export/Cargo.toml @@ -7,5 +7,5 @@ description = "This example demonstrates exporting a plot to SVG, PNG, and PDF a [dependencies] plotly = { path = "../../../plotly", features = ["static_export_default"] } -env_logger = "0.10" +env_logger = "0.11" log = "0.4" diff --git a/examples/financial_charts/Cargo.toml b/examples/financial_charts/Cargo.toml index 0ceab75f..e432edc6 100644 --- a/examples/financial_charts/Cargo.toml +++ b/examples/financial_charts/Cargo.toml @@ -10,5 +10,5 @@ plotly = { path = "../../plotly" } plotly_utils = { path = "../plotly_utils" } serde = "1.0" chrono = "0.4" -rand = "0.8" -rand_chacha = "0.3" +rand = "0.10" +rand_chacha = "0.10" diff --git a/examples/financial_charts/src/main.rs b/examples/financial_charts/src/main.rs index 2e25c92d..664cb686 100644 --- a/examples/financial_charts/src/main.rs +++ b/examples/financial_charts/src/main.rs @@ -453,7 +453,7 @@ fn hiding_weekends_and_holidays_with_rangebreaks(show: bool, file_name: &str) { // Helper to generate random walk data for all hours in a week fn generate_business_hours_data() -> (Vec, Vec) { - use rand::Rng; + use rand::RngExt; use rand::SeedableRng; use rand_chacha::ChaCha8Rng; @@ -467,7 +467,7 @@ fn generate_business_hours_data() -> (Vec, Vec) { for hour in 0..24 { let current_date = start_date + Duration::days(day) + Duration::hours(hour); dates.push(current_date.format("%Y-%m-%d %H:%M:%S").to_string()); - current_value += (rng.gen::() - 0.5) * 2.0; + current_value += (rng.random::() - 0.5) * 2.0; values.push(current_value); } } diff --git a/examples/maps/Cargo.toml b/examples/maps/Cargo.toml index cb3b2b26..465b22c3 100644 --- a/examples/maps/Cargo.toml +++ b/examples/maps/Cargo.toml @@ -8,6 +8,6 @@ edition = "2021" plotly = { path = "../../plotly" } plotly_utils = { path = "../plotly_utils" } csv = "1.3" -reqwest = { version = "0.11", features = ["blocking"] } +reqwest = { version = "0.13", features = ["blocking"] } serde_json = "1" diff --git a/plotly_derive/Cargo.toml b/plotly_derive/Cargo.toml index cba1e240..eb44f25f 100644 --- a/plotly_derive/Cargo.toml +++ b/plotly_derive/Cargo.toml @@ -12,9 +12,9 @@ keywords = ["plot", "chart", "plotly"] [dependencies] quote = "1" -syn = "2" +syn = "3" proc-macro2 = "1" -darling = "0.23" +darling = "0.24" [lib] proc-macro = true diff --git a/plotly_static/Cargo.toml b/plotly_static/Cargo.toml index 953e21fd..4af39c39 100644 --- a/plotly_static/Cargo.toml +++ b/plotly_static/Cargo.toml @@ -40,8 +40,7 @@ clap = { version = "4.0", features = ["derive"] } [build-dependencies] tokio = { version = "1", features = ["full"] } anyhow = "1.0" -dirs = "6.0" -zip = "8.0" +dirs = "7.0" webdriver-downloader = "0.17" # Needed for docs.rs to build the documentation From 5d68616cb679eed96648d09f3cd6faa11c3134c8 Mon Sep 17 00:00:00 2001 From: Joel Natividad <1980690+jqnatividad@users.noreply.github.com> Date: Sat, 17 Oct 2026 11:54:21 -0400 Subject: [PATCH 03/17] Release 0.15.0 - plotly 0.14.1 -> 0.15.0, plotly_derive 0.14.1 -> 0.15.0 - plotly_static 0.1.0 -> 0.2.0 (EPS removal is breaking; also carries the plotly.js 3.7.0 bundle) - Promote [Unreleased] to [0.15.0] in the CHANGELOG, add Removed and Security sections, and drop duplicated Funnel/Waterfall entries. Last release on plotly.js 3.x; plotly.js 4.x follows in 0.16.0 (#437). Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 19 +++++++++++++++++-- plotly/Cargo.toml | 6 +++--- plotly_derive/Cargo.toml | 2 +- plotly_static/Cargo.toml | 2 +- plotly_static/README.md | 2 +- plotly_static/src/lib.rs | 2 +- 6 files changed, 24 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6913daa1..109074b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +## [0.15.0] - 2026-10-17 + +This is the last release bundling plotly.js 3.x; the upgrade to plotly.js 4.x is planned for 0.16.0 ([#437](https://github.com/plotly/plotly.rs/issues/437)). + ### Added - [[#406](https://github.com/plotly/plotly.rs/issues/406)] Expose `plotly.js` 3.1–3.6 attributes @@ -13,8 +17,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - [[#412](https://github.com/plotly/plotly.rs/pull/412)] Add `Violin` trace type with box, mean line, KDE span, and split/grouped support - [[#414](https://github.com/plotly/plotly.rs/issues/414)] Add `DensityMap` (MapLibre `map` subplot) trace type — density heatmaps with full color-scale and hover support - [[#417](https://github.com/plotly/plotly.rs/issues/417)] Add `ScatterMap` (MapLibre `map` subplot) trace type — the modern counterpart to `ScatterMapbox` -- Add `Funnel` trace type (bar-like, cartesian) with `Connector` and `FunnelHoverInfo` options, plus the layout-level `funnelmode`/`funnelgap`/`funnelgroupgap` attributes and a `FunnelMode` enum -- Add `Waterfall` trace type (bar-like, cartesian) with a typed `Measure` enum (`absolute`/`relative`/`total`), per-class `increasing`/`decreasing`/`totals` styling via `MeasureStyle`, a `Connector` with `ConnectorMode`, and a `WaterfallHoverInfo` enum. The layout-level `waterfallmode`/`waterfallgap`/`waterfallgroupgap` attributes and the `WaterfallMode` enum already existed - [[#418](https://github.com/plotly/plotly.rs/issues/418)] Add native point clustering to `ScatterMap` via a `Cluster` option - [[#421](https://github.com/plotly/plotly.rs/pull/421)] Backfill trace attributes for trace types to bring them to parity with plotly.js 3.7 - [[#422](https://github.com/plotly/plotly.rs/issues/422)] Add `Indicator`, `Histogram2d`, `Icicle` trace types @@ -26,6 +28,19 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - [[#406](https://github.com/plotly/plotly.rs/issues/406)] Upgrade bundled plotly.js from 3.0.1 to 3.6.0 - [[#419](https://github.com/plotly/plotly.rs/issues/419)] Upgrade bundled plotly.js from 3.6.0 to 3.7.0 - [[#422](https://github.com/plotly/plotly.rs/issues/422)] Add layout-level `uirevision` to preserve UI state (zoom/pan/selection) across re-renders (complements the trace-level `uirevision` added in #421) +- `plotly_static` bumped to 0.2.0 (breaking: `ImageFormat::EPS` removed) +- Bump `darling` to 0.24 and `syn` to 3 in `plotly_derive`, and `dirs` to 7 in `plotly_static` +- Bump example dependencies: `reqwest` 0.13, `env_logger` 0.11, `itertools` 0.15, `ndarray` 0.17, `rand`/`rand_chacha` 0.10 + +### Removed + +- The deprecated Kaleido static export backend: the `plotly_kaleido` crate is no longer part of the workspace, and the `kaleido` and `kaleido_download` features are gone. Use the `static_export_*` features (`plotly_static`) instead +- The deprecated `ImageFormat::EPS` variant. Use `ImageFormat::SVG` or `ImageFormat::PDF` for vector output + +### Security + +- Remove the unused `zip` build-dependency from `plotly_static`; with `plotly_kaleido` gone, no workspace crate depends on `zip` directly +- Known issue: `zip` 8.x is still pulled in transitively by `webdriver-downloader` 0.17 (used at build time to fetch chromedriver/geckodriver) and is flagged by RUSTSEC-2026-0367/0368/0369. Those advisories concern `ZipArchive::extract()`, which `webdriver-downloader` does not call (it reads the driver entry with `by_name`). It will be resolved once [ik1ne/webdriver-downloader#67](https://github.com/ik1ne/webdriver-downloader/pull/67) is released ## [0.14.1] - 2026-02-15 diff --git a/plotly/Cargo.toml b/plotly/Cargo.toml index ebf8e24f..ecfbd2a8 100644 --- a/plotly/Cargo.toml +++ b/plotly/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "plotly" -version = "0.14.1" +version = "0.15.0" description = "A plotting library powered by Plotly.js" authors = [ "Ioannis Giagkiozis ", @@ -54,8 +54,8 @@ askama = { version = "0.16.0", features = ["serde_json"] } dyn-clone = "1" erased-serde = "0.4" image = { version = "0.25", optional = true } -plotly_derive = { version = "0.14", path = "../plotly_derive" } -plotly_static = { version = "0.1", path = "../plotly_static", optional = true } +plotly_derive = { version = "0.15", path = "../plotly_derive" } +plotly_static = { version = "0.2", path = "../plotly_static", optional = true } ndarray = { version = "0.17", optional = true } once_cell = "1" serde = { version = "1.0", features = ["derive"] } diff --git a/plotly_derive/Cargo.toml b/plotly_derive/Cargo.toml index eb44f25f..b824095d 100644 --- a/plotly_derive/Cargo.toml +++ b/plotly_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "plotly_derive" -version = "0.14.1" +version = "0.15.0" description = "Internal proc macro crate for Plotly-rs." authors = ["Ioannis Giagkiozis "] license = "MIT" diff --git a/plotly_static/Cargo.toml b/plotly_static/Cargo.toml index 4af39c39..087c2096 100644 --- a/plotly_static/Cargo.toml +++ b/plotly_static/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "plotly_static" -version = "0.1.0" +version = "0.2.0" description = "Export Plotly graphs to static images using WebDriver" authors = ["Andrei Gherghescu andrei-ng@protonmail.com"] license = "MIT" diff --git a/plotly_static/README.md b/plotly_static/README.md index eb10e334..89624bf0 100644 --- a/plotly_static/README.md +++ b/plotly_static/README.md @@ -57,7 +57,7 @@ Add to your `Cargo.toml`: ```toml [dependencies] -plotly_static = { version = "0.1", features = ["chromedriver", "webdriver_download"] } +plotly_static = { version = "0.2", features = ["chromedriver", "webdriver_download"] } serde_json = "1.0" ``` diff --git a/plotly_static/src/lib.rs b/plotly_static/src/lib.rs index af28fa6b..cff40726 100644 --- a/plotly_static/src/lib.rs +++ b/plotly_static/src/lib.rs @@ -76,7 +76,7 @@ //! //! ```toml //! [dependencies] -//! plotly_static = { version = "0.1", features = ["chromedriver", "webdriver_download"] } +//! plotly_static = { version = "0.2", features = ["chromedriver", "webdriver_download"] } //! ``` //! //! ## Async Support From 16b30fb3b419cf94be9b1598a1027faebe3f0d73 Mon Sep 17 00:00:00 2001 From: Joel Natividad <1980690+jqnatividad@users.noreply.github.com> Date: Sat, 17 Oct 2026 11:57:08 -0400 Subject: [PATCH 04/17] ci: let cargo-semver-checks infer the release type A pinned `release-type: minor` is applied verbatim, without the 0.y.z promotion, so the intentional removals in 0.15.0 (kaleido features and methods, `ImageFormat::EPS`) would fail as major-level changes. Inferring from the version bump treats 0.14.1 -> 0.15.0 as major, while unchanged versions during development still default to a minor-level check. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 76611eb0..8ebaac0b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -69,7 +69,6 @@ jobs: feature-group: only-explicit-features features: static_export_chromedriver rust-toolchain: stable - release-type: minor test: name: Tests From 48b387b58ad86635093ff737d1e33ad13b22e253 Mon Sep 17 00:00:00 2001 From: Joel Natividad <1980690+jqnatividad@users.noreply.github.com> Date: Sat, 17 Oct 2026 12:01:21 -0400 Subject: [PATCH 05/17] examples: fix off-by-one in sinusoidal slider traces The trace loop used `0..=num_steps`, creating 52 traces (frequencies 0.0-5.1) while each slider step's visibility mask has 51 entries, so step 0 also showed the stray 5.1 curve. Use `0..num_steps` to match. Co-Authored-By: Claude Opus 5.5 --- examples/custom_controls/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/custom_controls/src/main.rs b/examples/custom_controls/src/main.rs index ed2c7b53..71051475 100644 --- a/examples/custom_controls/src/main.rs +++ b/examples/custom_controls/src/main.rs @@ -269,7 +269,7 @@ fn sinusoidal_slider_example(show: bool, file_name: &str) { let num_steps = 51; // 0..=50 for frequencies 0.0 to 5.0 in 0.1 steps // Add traces, one for each slider step (frequency parameter) - for step in 0..=num_steps { + for step in 0..num_steps { let frequency = step as f64 / 10.0; // 0.0, 0.1, ..., 5.0 let x: Vec = Array::linspace(0.0, 10.0, 1001).to_vec(); let y: Vec = x.iter().map(|&x_val| (frequency * x_val).sin()).collect(); From ea7c2bf3b887544c27e8bf0a4d3158564300b368 Mon Sep 17 00:00:00 2001 From: Joel Natividad <1980690+jqnatividad@users.noreply.github.com> Date: Sat, 17 Oct 2026 12:04:25 -0400 Subject: [PATCH 06/17] style: apply rustfmt from nightly 2026-10-16 The current nightly rustfmt re-wraps comments under the repo's `wrap_comments` setting, which fails the Rustfmt CI job. Formatting only; no code changes. Co-Authored-By: Claude Opus 5.5 --- examples/static_export/src/bin/sync.rs | 4 ++-- plotly/src/common/mod.rs | 4 ++-- plotly/src/layout/polar.rs | 4 ++-- plotly/src/lib.rs | 3 ++- plotly/src/plot.rs | 17 ++++++++++------- plotly/src/traces/violin.rs | 3 ++- plotly_derive/src/field_setter.rs | 4 ++-- plotly_derive/src/lib.rs | 3 ++- plotly_static/src/lib.rs | 25 ++++++++++++++----------- plotly_static/src/template.rs | 3 ++- plotly_static/src/webdriver/launch.rs | 3 ++- 11 files changed, 42 insertions(+), 31 deletions(-) diff --git a/examples/static_export/src/bin/sync.rs b/examples/static_export/src/bin/sync.rs index 4be47620..2b7877dd 100644 --- a/examples/static_export/src/bin/sync.rs +++ b/examples/static_export/src/bin/sync.rs @@ -42,8 +42,8 @@ fn main() -> Result<(), Box> { .expect("Failed to create StaticExporter"); info!("Exporting multiple plots using a single StaticExporter..."); - // Export all plots using the same exporter (new unified naming via extension - // trait) + // Export all plots using the same exporter (new unified naming via + // extension trait) exporter.write_image( &plot1, "./output/plot1_new_api", diff --git a/plotly/src/common/mod.rs b/plotly/src/common/mod.rs index 5b0e8375..2639285e 100644 --- a/plotly/src/common/mod.rs +++ b/plotly/src/common/mod.rs @@ -1177,8 +1177,8 @@ mod tests { #[test] fn serialize_direction() { - // TODO: I think `Direction` would be better as a struct, with `fillcolor` and - // `line` attributes + // TODO: I think `Direction` would be better as a struct, with + // `fillcolor` and `line` attributes let inc = Direction::Increasing { line: Line::new() }; let expected = json!({"line": {}}); assert_eq!(to_value(inc).unwrap(), expected); diff --git a/plotly/src/layout/polar.rs b/plotly/src/layout/polar.rs index d8e44ea8..4fc9d380 100644 --- a/plotly/src/layout/polar.rs +++ b/plotly/src/layout/polar.rs @@ -552,8 +552,8 @@ mod tests { use crate::Layout; // The focus of the test is serialization, so we test all options for - // [`LayoutPolar`], even though some of those options would normally be mutually - // exclusive. + // [`LayoutPolar`], even though some of those options would normally be + // mutually exclusive. #[test] fn serialize_layout_polar() { let ticks = PolarAxisTicks::new() diff --git a/plotly/src/lib.rs b/plotly/src/lib.rs index 62e4fce4..4a63c1ac 100644 --- a/plotly/src/lib.rs +++ b/plotly/src/lib.rs @@ -1,7 +1,8 @@ //! # Plotly.rs //! //! A plotting library for Rust powered by [Plotly.js](https://plot.ly/javascript/). -#![recursion_limit = "256"] // lets us use a large serde_json::json! macro for testing crate::layout::Axis +#![recursion_limit = "256"] // lets us use a large serde_json::json! macro for + // testing crate::layout::Axis extern crate askama; extern crate rand; extern crate serde; diff --git a/plotly/src/plot.rs b/plotly/src/plot.rs index 351b9b9f..0bb50a44 100644 --- a/plotly/src/plot.rs +++ b/plotly/src/plot.rs @@ -295,7 +295,8 @@ impl Plot { file.flush().unwrap(); } - // Hand off the job of opening the browser to an OS-specific implementation. + // Hand off the job of opening the browser to an OS-specific + // implementation. Plot::show_with_default_app(temp_path); } @@ -308,7 +309,8 @@ impl Plot { pub fn show_html + std::clone::Clone>(&self, filename: P) { let path = filename.as_ref().to_str().unwrap(); self.write_html(filename.clone()); - // Hand off the job of opening the browser to an OS-specific implementation. + // Hand off the job of opening the browser to an OS-specific + // implementation. Plot::show_with_default_app(path); } @@ -339,7 +341,8 @@ impl Plot { file.flush().unwrap(); } - // Hand off the job of opening the browser to an OS-specific implementation. + // Hand off the job of opening the browser to an OS-specific + // implementation. Plot::show_with_default_app(temp_path); } @@ -642,8 +645,8 @@ impl Plot { /// This is used when the `plotly_embed_js` feature is enabled, providing /// self-contained HTML files that don't require external CDN resources. pub fn offline_js_sources() -> String { - // Note that since 'tex-mml-chtml' conflicts with 'tex-svg' when generating - // Latex Titles we no longer include it. + // Note that since 'tex-mml-chtml' conflicts with 'tex-svg' when + // generating Latex Titles we no longer include it. let local_tex_svg_js = include_str!("../resource/tex-svg-3.2.2.js"); let local_plotly_js = include_str!("../resource/plotly.min.js"); @@ -667,8 +670,8 @@ impl Plot { /// disabled, providing smaller HTML files that rely on external /// resources. pub fn online_cdn_js() -> String { - // Note that since 'tex-mml-chtml' conflicts with 'tex-svg' when generating - // Latex Titles we no longer include it. + // Note that since 'tex-mml-chtml' conflicts with 'tex-svg' when + // generating Latex Titles we no longer include it. r##" "## diff --git a/plotly/src/traces/violin.rs b/plotly/src/traces/violin.rs index 3d49357b..daf2146a 100644 --- a/plotly/src/traces/violin.rs +++ b/plotly/src/traces/violin.rs @@ -324,7 +324,8 @@ mod tests { let numeric = Violin::new(vec![1, 2, 3]).span(vec![0., 10.]); assert_eq!(to_value(numeric).unwrap()["span"], json!([0.0, 10.0])); - // Date/string span for date-valued axes (plotly.js types `span` as `any`). + // Date/string span for date-valued axes (plotly.js types `span` as + // `any`). let dated = Violin::new(vec![1, 2, 3]) .span_mode(SpanMode::Manual) .span(vec!["2020-01-01", "2020-12-31"]); diff --git a/plotly_derive/src/field_setter.rs b/plotly_derive/src/field_setter.rs index bcc87554..429b0113 100644 --- a/plotly_derive/src/field_setter.rs +++ b/plotly_derive/src/field_setter.rs @@ -509,8 +509,8 @@ impl FieldReceiver { _ => quote![], }; - // Optional matrix setter for Dim<_> when #[field_setter(with_matrix)] is - // present + // Optional matrix setter for Dim<_> when #[field_setter(with_matrix)] + // is present let matrix_setter = if self.with_matrix { let matrix_ident = Ident::new( &format!("{field_ident}_matrix"), diff --git a/plotly_derive/src/lib.rs b/plotly_derive/src/lib.rs index e5111a73..93c879ab 100644 --- a/plotly_derive/src/lib.rs +++ b/plotly_derive/src/lib.rs @@ -30,7 +30,8 @@ pub fn layout_structs(_attr: TokenStream, item: TokenStream) -> TokenStream { #vis struct #template_struct_name #fields }; - // Generate Layout (with template field and #[field_setter(kind = "layout")]) + // Generate Layout (with template field and #[field_setter(kind = + // "layout")]) let layout_struct_name = format_ident!("Layout"); let mut layout_fields = fields.clone(); // Add the template field diff --git a/plotly_static/src/lib.rs b/plotly_static/src/lib.rs index cff40726..2a9e92fc 100644 --- a/plotly_static/src/lib.rs +++ b/plotly_static/src/lib.rs @@ -1040,14 +1040,15 @@ impl AsyncStaticExporter { new_client }; - // For offline mode, write HTML to file to avoid data URI size limits since JS - // libraries are embedded in the file + // For offline mode, write HTML to file to avoid data URI size limits + // since JS libraries are embedded in the file let url = if self.offline_mode { let temp_file = template::to_file(html_content) .with_context(|| "Failed to create temporary HTML file")?; format!("file://{}", temp_file.to_string_lossy()) } else { - // For online mode, use data URI (smaller size since JS is loaded from CDN) + // For online mode, use data URI (smaller size since JS is loaded + // from CDN) format!("data:text/html,{}", encode(html_content)) }; @@ -1118,7 +1119,8 @@ impl AsyncStaticExporter { } #[cfg(any(feature = "chromedriver", feature = "geckodriver"))] { - // Define browser capabilities (copied to avoid reordering existing code) + // Define browser capabilities (copied to avoid reordering existing + // code) let mut caps = JsonMap::new(); let mut browser_opts = JsonMap::new(); let browser_args = self.webdriver_browser_caps.clone(); @@ -1348,8 +1350,8 @@ mod tests { PORT_COUNTER.fetch_add(1, Ordering::SeqCst) } - // In CI which may run on slow machines, we run a different strategy to generate - // the unique port. + // In CI which may run on slow machines, we run a different strategy to + // generate the unique port. #[cfg(feature = "debug")] fn get_unique_port() -> u32 { static PORT_COUNTER: AtomicU32 = AtomicU32::new(4844); @@ -1644,8 +1646,8 @@ mod tests { assert!(std::fs::remove_file(dst1.as_path()).is_ok()); exporter1.close(); - // Create second exporter on the same port - this should connect to existing - // WebDriver process (but create a new session) + // Create second exporter on the same port - this should connect to + // existing WebDriver process (but create a new session) let mut exporter2 = StaticExporterBuilder::default() .spawn_webdriver(true) .webdriver_port(test_port) @@ -1662,15 +1664,16 @@ mod tests { assert!(std::fs::remove_file(dst2.as_path()).is_ok()); exporter2.close(); - // Create third exporter on the same port - should also connect to existing - // WebDriver process + // Create third exporter on the same port - should also connect to + // existing WebDriver process let mut exporter3 = StaticExporterBuilder::default() .spawn_webdriver(true) .webdriver_port(test_port) .build() .unwrap(); - // Export third image using another new session on the same WebDriver process + // Export third image using another new session on the same WebDriver + // process let dst3 = PathBuf::from("process_reuse_3.png"); exporter3 .write_fig(dst3.as_path(), &test_plot, ImageFormat::PNG, 800, 600, 1.0) diff --git a/plotly_static/src/template.rs b/plotly_static/src/template.rs index 32e0b1cc..a3d9c1c0 100644 --- a/plotly_static/src/template.rs +++ b/plotly_static/src/template.rs @@ -256,7 +256,8 @@ pub(crate) fn to_file(data: &str) -> Result { fn offline_js_sources() -> String { // tex-mml-chtml conflicts with tex-svg when generating Latex Titles - // let local_tex_mml_js = include_str!("../resource/tex-mml-chtml-3.2.0.js"); + // let local_tex_mml_js = + // include_str!("../resource/tex-mml-chtml-3.2.0.js"); let local_plotly_js = include_str!("../resource/plotly.min.js"); let local_tex_svg_js = include_str!("../resource/tex-svg-3.2.2.js"); let local_html2pdf_js = include_str!("../resource/html2pdf.bundle.min.js"); diff --git a/plotly_static/src/webdriver/launch.rs b/plotly_static/src/webdriver/launch.rs index 600ce1c9..070b2299 100644 --- a/plotly_static/src/webdriver/launch.rs +++ b/plotly_static/src/webdriver/launch.rs @@ -172,7 +172,8 @@ impl WebDriver { } fn spawn_process(&self, driver_path: &Option, port: u32) -> Result { - let driver_path = driver_path.as_ref().unwrap(); // Safe unwrap since we validated above + let driver_path = driver_path.as_ref().unwrap(); // Safe unwrap since we + // validated above let mut command = Self::create_command(driver_path, port); Self::log_command(&command); From a21da1f14717728b7f9fbc84fa99118eab52a8fd Mon Sep 17 00:00:00 2001 From: Joel Natividad <1980690+jqnatividad@users.noreply.github.com> Date: Sat, 17 Oct 2026 13:16:10 -0400 Subject: [PATCH 07/17] Fix pedantic doc/lazy-static lints and add_frames duplication Address clippy::missing_panics_doc, missing_errors_doc, doc_markdown, non_std_lazy_statics and cargo::redundant_homepage: - Add `# Errors` / `# Panics` sections to public fallible APIs, including the wasm-only bindings and callbacks. - Backtick identifiers in docs and fix unbalanced backticks; add clippy.toml whitelisting WebDriver, MapLibre, GeoJSON, MathJax and ModeBar as product names. - Switch theme statics to std::sync::LazyLock and drop once_cell. - Remove `homepage` fields duplicating `repository`. Also fix Plot::add_frames adding every frame twice when the plot had no frames yet, with a regression test. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 6 ++ clippy.toml | 1 + plotly/Cargo.toml | 2 - plotly/src/bindings.rs | 10 +++ plotly/src/callbacks.rs | 6 ++ plotly/src/common/color.rs | 6 +- plotly/src/configuration.rs | 6 +- plotly/src/export.rs | 33 ++++++--- plotly/src/layout/animation.rs | 6 +- plotly/src/layout/axis.rs | 2 +- plotly/src/layout/mod.rs | 10 +-- plotly/src/layout/polar.rs | 19 ++++-- plotly/src/layout/scene.rs | 4 +- plotly/src/layout/slider.rs | 10 ++- plotly/src/layout/themes.rs | 18 ++--- plotly/src/layout/update_menu.rs | 8 +++ plotly/src/plot.rs | 102 ++++++++++++++++++++++++---- plotly/src/traces/density_mapbox.rs | 2 +- plotly/src/traces/image.rs | 2 +- plotly/src/traces/mesh3d.rs | 2 +- plotly/src/traces/pie.rs | 10 +-- plotly/src/traces/sankey.rs | 2 +- plotly/src/traces/scatter3d.rs | 4 +- plotly/src/traces/scatter_geo.rs | 2 +- plotly/src/traces/scatter_map.rs | 2 +- plotly/src/traces/scatter_mapbox.rs | 2 +- plotly_derive/Cargo.toml | 1 - plotly_derive/src/lib.rs | 15 ++++ plotly_static/Cargo.toml | 1 - plotly_static/src/lib.rs | 58 +++++++++++++--- plotly_static/src/webdriver.rs | 2 +- 31 files changed, 266 insertions(+), 88 deletions(-) create mode 100644 clippy.toml diff --git a/CHANGELOG.md b/CHANGELOG.md index 109074b1..18ba1191 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,12 +31,18 @@ This is the last release bundling plotly.js 3.x; the upgrade to plotly.js 4.x is - `plotly_static` bumped to 0.2.0 (breaking: `ImageFormat::EPS` removed) - Bump `darling` to 0.24 and `syn` to 3 in `plotly_derive`, and `dirs` to 7 in `plotly_static` - Bump example dependencies: `reqwest` 0.13, `env_logger` 0.11, `itertools` 0.15, `ndarray` 0.17, `rand`/`rand_chacha` 0.10 +- Theme statics in `plotly::layout::themes` (`DEFAULT`, `PLOTLY_WHITE`, `PLOTLY_DARK`, …) are now `std::sync::LazyLock