From 41f50cc8156832eef5cf872c555be16520addac6 Mon Sep 17 00:00:00 2001 From: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> Date: Thu, 2 Jan 2025 12:15:38 +0100 Subject: [PATCH 01/83] bump version Signed-off-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> --- plotly/Cargo.toml | 2 +- plotly_kaleido/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plotly/Cargo.toml b/plotly/Cargo.toml index 9f08cdfa..921675e0 100644 --- a/plotly/Cargo.toml +++ b/plotly/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "plotly" -version = "0.12.0" +version = "0.12.1" description = "A plotting library powered by Plotly.js" authors = ["Ioannis Giagkiozis "] license = "MIT" diff --git a/plotly_kaleido/Cargo.toml b/plotly_kaleido/Cargo.toml index 8b2ed365..75b2334a 100644 --- a/plotly_kaleido/Cargo.toml +++ b/plotly_kaleido/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "plotly_kaleido" -version = "0.12.0" +version = "0.12.1" description = "Additional output format support for plotly using Kaleido" authors = [ "Ioannis Giagkiozis ", From 306db4316313c64926ac094e16196e6c8f917244 Mon Sep 17 00:00:00 2001 From: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> Date: Thu, 2 Jan 2025 12:20:25 +0100 Subject: [PATCH 02/83] bump plotly derive version Signed-off-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> --- plotly_derive/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plotly_derive/Cargo.toml b/plotly_derive/Cargo.toml index 65acd533..3d9c7f43 100644 --- a/plotly_derive/Cargo.toml +++ b/plotly_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "plotly_derive" -version = "0.12.0" +version = "0.12.1" description = "Internal proc macro crate for Plotly-rs." authors = ["Ioannis Giagkiozis "] license = "MIT" From 050fbb78538219f5660b172df9db9ef7218d6a24 Mon Sep 17 00:00:00 2001 From: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> Date: Thu, 2 Jan 2025 12:45:25 +0100 Subject: [PATCH 03/83] update changelog with latest patch version Signed-off-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5caf4cc1..ee28bed8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.12.1] - 2025-01-02 +### Fixed +- [[#269](https://github.com/plotly/plotly.rs/pull/269)] Fix publishing to crates.io issue + ## [0.12.0] - 2025-01-02 ### Changed - [[#256](https://github.com/plotly/plotly.rs/pull/256)] Bump Cargo.toml edition to 2021 From 6a8c96c96324fc5ddb41d20f66fa985713e46d3f Mon Sep 17 00:00:00 2001 From: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> Date: Thu, 2 Jan 2025 12:50:48 +0100 Subject: [PATCH 04/83] add separate workflow files for publishing sub-packages Signed-off-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> --- .github/workflows/publish_plotly.yml | 25 ++++++++++++++++++++ .github/workflows/publish_plotly_kaleido.yml | 25 ++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 .github/workflows/publish_plotly.yml create mode 100644 .github/workflows/publish_plotly_kaleido.yml diff --git a/.github/workflows/publish_plotly.yml b/.github/workflows/publish_plotly.yml new file mode 100644 index 00000000..53d56b27 --- /dev/null +++ b/.github/workflows/publish_plotly.yml @@ -0,0 +1,25 @@ +name: Deploy Plotly + +on: + workflow_dispatch: + push: + tags: + - '[0-9]+.[0-9]+.[0-9]+' + +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 + + create-gh-release: + name: Deploy to GH Releases + runs-on: ubuntu-latest + steps: + - uses: softprops/action-gh-release@v1 \ No newline at end of file diff --git a/.github/workflows/publish_plotly_kaleido.yml b/.github/workflows/publish_plotly_kaleido.yml new file mode 100644 index 00000000..162cb33f --- /dev/null +++ b/.github/workflows/publish_plotly_kaleido.yml @@ -0,0 +1,25 @@ +name: Deploy Plotly Kaleido + +on: + workflow_dispatch: + push: + tags: + - '[0-9]+.[0-9]+.[0-9]+' + +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 + + create-gh-release: + name: Deploy to GH Releases + runs-on: ubuntu-latest + steps: + - uses: softprops/action-gh-release@v1 \ No newline at end of file From 8b19db1d1641ffa4aebec97de1cef1525f3bb046 Mon Sep 17 00:00:00 2001 From: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> Date: Thu, 2 Jan 2025 12:55:12 +0100 Subject: [PATCH 05/83] fix workflows for publishing independent packages Signed-off-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> --- .github/workflows/publish_plotly.yml | 6 ------ .github/workflows/publish_plotly_kaleido.yml | 8 +------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/.github/workflows/publish_plotly.yml b/.github/workflows/publish_plotly.yml index 53d56b27..09798a5d 100644 --- a/.github/workflows/publish_plotly.yml +++ b/.github/workflows/publish_plotly.yml @@ -17,9 +17,3 @@ jobs: env: CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} - run: cargo publish --allow-dirty -p plotly - - create-gh-release: - name: Deploy to GH Releases - runs-on: ubuntu-latest - steps: - - uses: softprops/action-gh-release@v1 \ No newline at end of file diff --git a/.github/workflows/publish_plotly_kaleido.yml b/.github/workflows/publish_plotly_kaleido.yml index 162cb33f..e464f8d4 100644 --- a/.github/workflows/publish_plotly_kaleido.yml +++ b/.github/workflows/publish_plotly_kaleido.yml @@ -16,10 +16,4 @@ jobs: - run: cargo login ${{ env.CRATES_IO_TOKEN }} env: CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} - - run: cargo publish --allow-dirty -p plotly_kaleido - - create-gh-release: - name: Deploy to GH Releases - runs-on: ubuntu-latest - steps: - - uses: softprops/action-gh-release@v1 \ No newline at end of file + - run: cargo publish --allow-dirty -p plotly_kaleido \ No newline at end of file From 628f2bea233db5ca0509ab061132f2cb4689311e Mon Sep 17 00:00:00 2001 From: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> Date: Thu, 2 Jan 2025 13:00:08 +0100 Subject: [PATCH 06/83] cleanup workflows for publishing to crates.io Signed-off-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> --- .github/workflows/publish_plotly.yml | 5 +---- .github/workflows/publish_plotly_derive.yml | 16 ++++++++++++++++ .github/workflows/publish_plotly_kaleido.yml | 5 +---- 3 files changed, 18 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/publish_plotly_derive.yml diff --git a/.github/workflows/publish_plotly.yml b/.github/workflows/publish_plotly.yml index 09798a5d..0db2a789 100644 --- a/.github/workflows/publish_plotly.yml +++ b/.github/workflows/publish_plotly.yml @@ -1,10 +1,7 @@ -name: Deploy Plotly +name: Publish plotly on: workflow_dispatch: - push: - tags: - - '[0-9]+.[0-9]+.[0-9]+' jobs: create-crates-io-release: diff --git a/.github/workflows/publish_plotly_derive.yml b/.github/workflows/publish_plotly_derive.yml new file mode 100644 index 00000000..14df2f0f --- /dev/null +++ b/.github/workflows/publish_plotly_derive.yml @@ -0,0 +1,16 @@ +name: Publish plotly-derive + +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_derive \ No newline at end of file diff --git a/.github/workflows/publish_plotly_kaleido.yml b/.github/workflows/publish_plotly_kaleido.yml index e464f8d4..6c68c821 100644 --- a/.github/workflows/publish_plotly_kaleido.yml +++ b/.github/workflows/publish_plotly_kaleido.yml @@ -1,10 +1,7 @@ -name: Deploy Plotly Kaleido +name: Publish plotly-kaleido on: workflow_dispatch: - push: - tags: - - '[0-9]+.[0-9]+.[0-9]+' jobs: create-crates-io-release: From e9cba6826a0ff3bc1c335c5e99a168912197dbb4 Mon Sep 17 00:00:00 2001 From: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> Date: Thu, 2 Jan 2025 13:06:01 +0100 Subject: [PATCH 07/83] cleanup workflows Signed-off-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> --- .github/workflows/book.yml | 4 ++-- .github/workflows/release.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml index 6da4cf71..b08ad106 100644 --- a/.github/workflows/book.yml +++ b/.github/workflows/book.yml @@ -34,11 +34,11 @@ jobs: run: | rm -rf content cp -r gh-pages/content . - - name: Deploy to GitHub Pages + - name: Trigger GitHub Pages Bot run: | git config --global user.name 'github-actions[bot]' git config --global user.email 'github-actions[bot]@users.noreply.github.com' - + git add content if [ "${{ github.ref_type }}" == "tag" ]; then git commit --allow-empty -m "update book for release ${{ github.ref }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e64c3a6e..25ffd779 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -name: Deploy Releases +name: Publish all on: push: From 3fd500f95be8c39514ba6fb4fbb090bc3ba4e863 Mon Sep 17 00:00:00 2001 From: Andrei <8067229+andrei-ng@users.noreply.github.com> Date: Mon, 13 Jan 2025 09:43:04 +0100 Subject: [PATCH 08/83] make scatter trace specific types public (#273) Signed-off-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> --- plotly/src/lib.rs | 3 ++- plotly/src/traces/mod.rs | 4 ++-- plotly_derive/src/field_setter.rs | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/plotly/src/lib.rs b/plotly/src/lib.rs index c9d89c40..9d331d92 100644 --- a/plotly/src/lib.rs +++ b/plotly/src/lib.rs @@ -31,7 +31,8 @@ pub use layout::Layout; pub use plot::{ImageFormat, Plot, Trace}; // Also provide easy access to modules which contain additional trace-specific types pub use traces::{ - box_plot, contour, heat_map, histogram, image, mesh3d, sankey, scatter_mapbox, surface, + box_plot, contour, heat_map, histogram, image, mesh3d, sankey, scatter, scatter3d, + scatter_mapbox, surface, }; // Bring the different trace types into the top-level scope pub use traces::{ diff --git a/plotly/src/traces/mod.rs b/plotly/src/traces/mod.rs index 515072b2..2a2d3042 100644 --- a/plotly/src/traces/mod.rs +++ b/plotly/src/traces/mod.rs @@ -12,8 +12,8 @@ pub mod mesh3d; mod ohlc; pub mod pie; pub mod sankey; -mod scatter; -mod scatter3d; +pub mod scatter; +pub mod scatter3d; pub mod scatter_mapbox; mod scatter_polar; pub mod surface; diff --git a/plotly_derive/src/field_setter.rs b/plotly_derive/src/field_setter.rs index ca13d1d3..426d18fa 100644 --- a/plotly_derive/src/field_setter.rs +++ b/plotly_derive/src/field_setter.rs @@ -213,7 +213,7 @@ impl FieldType { // Not the best implementation but works in practice let (type_str_parts, types) = _type_str_parts(field_ty); - if type_str_parts.first().map_or(false, |t| t != "Option") { + if type_str_parts.first().is_some_and(|t| t != "Option") { return FieldType::NotOption; } @@ -507,7 +507,7 @@ impl FieldReceiver { attr.path() .segments .first() - .map_or(false, |p| p.ident == name) + .is_some_and(|p| p.ident == name) }) .map(|attr| { quote![ From 7c63fbeb713d6ce0bf1a9b13b99d074cccb67770 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 10:23:29 +0100 Subject: [PATCH 09/83] Update directories requirement from >=4, <6 to >=4, <7 (#272) --- updated-dependencies: - dependency-name: directories dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- plotly_kaleido/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plotly_kaleido/Cargo.toml b/plotly_kaleido/Cargo.toml index 75b2334a..0761cc8e 100644 --- a/plotly_kaleido/Cargo.toml +++ b/plotly_kaleido/Cargo.toml @@ -31,4 +31,4 @@ plotly_kaleido = { path = ".", features = ["download"] } [build-dependencies] zip = "2.1" -directories = ">=4, <6" +directories = ">=4, <7" From e80f837fca6d89edfde9b04b70164378d7cd5199 Mon Sep 17 00:00:00 2001 From: Andrei <8067229+andrei-ng@users.noreply.github.com> Date: Sat, 1 Feb 2025 17:00:24 +0100 Subject: [PATCH 10/83] Rework wasm feature and make it work with getrandom 0.3 (#277) * update rand requirement from 0.8 to 0.9 * update rand_distr requirement from 0.4 to 0.5 * update getrandom requirement from 0.2 to 0.3 * update code to work with rand=0.9 and rand_distr=0.5 crates * fix issues with latest getrandom 0.3 and wasm target - reworked the wasm use case and removed the wasm feature flag, putting everything behind the target specific dependencies Signed-off-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .cargo/config.toml | 2 ++ .github/workflows/ci.yml | 10 ++++------ CHANGELOG.md | 4 ++++ docs/book/src/fundamentals/shapes.md | 2 +- examples/3d_charts/Cargo.toml | 2 +- examples/3d_charts/src/main.rs | 4 ++-- examples/basic_charts/Cargo.toml | 4 ++-- examples/basic_charts/src/main.rs | 10 +++++++--- examples/customization/Cargo.toml | 2 +- examples/customization/src/main.rs | 7 ++----- examples/shapes/Cargo.toml | 4 ++-- examples/shapes/src/main.rs | 3 +-- examples/statistical_charts/Cargo.toml | 4 ++-- examples/statistical_charts/src/main.rs | 16 ++++++++-------- examples/wasm-yew-minimal/Cargo.toml | 2 +- plotly/Cargo.toml | 14 +++++++------- plotly/src/bindings.rs | 2 +- plotly/src/lib.rs | 6 +++--- plotly/src/plot.rs | 21 +++++++++++---------- plotly/src/traces/histogram.rs | 2 +- 20 files changed, 63 insertions(+), 58 deletions(-) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..2e07606d --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.wasm32-unknown-unknown] +rustflags = ['--cfg', 'getrandom_backend="wasm_js"'] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a60329ed..73799af2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,12 +37,12 @@ jobs: with: components: clippy targets: wasm32-unknown-unknown - # lint the main library workspace excluding the wasm feature - - run: cargo clippy --features plotly_ndarray,plotly_image,kaleido -- -D warnings - # lint the plotly library with wasm enabled - - run: cargo clippy --package plotly --features wasm --target wasm32-unknown-unknown -- -D warnings + # lint the main library workspace for non-wasm target + - run: cargo clippy --all-features -- -D warnings # lint the non-wasm examples - 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 - run: cd ${{ github.workspace }}/examples && cargo clippy --target wasm32-unknown-unknown --package "wasm*" @@ -83,8 +83,6 @@ jobs: with: components: llvm-tools-preview - uses: taiki-e/install-action@cargo-llvm-cov - # we are skipping anything to do with wasm here - - run: cargo llvm-cov --workspace --features plotly_ndarray,plotly_image,kaleido --lcov --output-path lcov.info - uses: codecov/codecov-action@v3 build_examples: diff --git a/CHANGELOG.md b/CHANGELOG.md index ee28bed8..23f41d97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.13.0] - 2025-02-xx +### Changed +- [[#277](https://github.com/plotly/plotly.rs/pull/277)] Removed `wasm` feature flag and put evrything behind target specific dependencies. Added `.cargo/config.toml` for configuration flags needed by `getrandom` version 0.3 on `wasm` targets. + ## [0.12.1] - 2025-01-02 ### Fixed - [[#269](https://github.com/plotly/plotly.rs/pull/269)] Fix publishing to crates.io issue diff --git a/docs/book/src/fundamentals/shapes.md b/docs/book/src/fundamentals/shapes.md index 4c34d684..04314a53 100644 --- a/docs/book/src/fundamentals/shapes.md +++ b/docs/book/src/fundamentals/shapes.md @@ -12,7 +12,7 @@ use plotly::layout::{ ShapeType, }; use plotly::{Bar, color::NamedColor, Plot, Scatter}; -use rand::thread_rng; +use rand::rng; use rand_distr::{Distribution, Normal}; ``` diff --git a/examples/3d_charts/Cargo.toml b/examples/3d_charts/Cargo.toml index fab9a60d..01f56c64 100644 --- a/examples/3d_charts/Cargo.toml +++ b/examples/3d_charts/Cargo.toml @@ -6,5 +6,5 @@ edition = "2021" [dependencies] ndarray = "0.16" -rand = "0.8" +rand = "0.9" plotly = { path = "../../plotly" } diff --git a/examples/3d_charts/src/main.rs b/examples/3d_charts/src/main.rs index f4b7c03a..23da9718 100644 --- a/examples/3d_charts/src/main.rs +++ b/examples/3d_charts/src/main.rs @@ -218,8 +218,8 @@ fn colorscale_plot(show: bool) -> Plot { let _color: Vec = (0..z.len()).collect(); let _color: Vec = (0..z.len()).map(|x| x as u8).collect(); let _color: Vec = { - let mut rng = rand::thread_rng(); - (0..z.len()).map(|_| rng.gen_range(0..100)).collect() + let mut rng = rand::rng(); + (0..z.len()).map(|_| rng.random_range(0..100)).collect() }; let color_max = color.iter().fold(f64::MIN, |acc, x| acc.max(*x as f64)); diff --git a/examples/basic_charts/Cargo.toml b/examples/basic_charts/Cargo.toml index 54ffb5ee..c89e6adf 100644 --- a/examples/basic_charts/Cargo.toml +++ b/examples/basic_charts/Cargo.toml @@ -7,5 +7,5 @@ edition = "2021" [dependencies] ndarray = "0.16" plotly = { path = "../../plotly" } -rand = "0.8" -rand_distr = "0.4" +rand = "0.9" +rand_distr = "0.5" diff --git a/examples/basic_charts/src/main.rs b/examples/basic_charts/src/main.rs index 0de8a8ca..89c30571 100644 --- a/examples/basic_charts/src/main.rs +++ b/examples/basic_charts/src/main.rs @@ -38,7 +38,7 @@ fn simple_scatter_plot(show: bool) -> Plot { // ANCHOR: line_and_scatter_plots fn line_and_scatter_plots(show: bool) -> Plot { let n: usize = 100; - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); let random_x: Vec = Array::linspace(0., 1., n).into_raw_vec_and_offset().0; let random_y0: Vec = Normal::new(5., 1.) .unwrap() @@ -273,8 +273,12 @@ fn colored_and_styled_scatter_plot(show: bool) -> Plot { // ANCHOR: large_data_sets fn large_data_sets(show: bool) -> Plot { let n: usize = 100_000; - let mut rng = rand::thread_rng(); - let r: Vec = Uniform::new(0., 1.).sample_iter(&mut rng).take(n).collect(); + let mut rng = rand::rng(); + let r: Vec = Uniform::new(0., 1.) + .unwrap() + .sample_iter(&mut rng) + .take(n) + .collect(); let theta: Vec = Normal::new(0., 2. * std::f64::consts::PI) .unwrap() .sample_iter(&mut rng) diff --git a/examples/customization/Cargo.toml b/examples/customization/Cargo.toml index bc794a09..74bc5cd9 100644 --- a/examples/customization/Cargo.toml +++ b/examples/customization/Cargo.toml @@ -6,6 +6,6 @@ edition = "2021" [dependencies] build_html = "2.5.0" -rand = "0.8" +rand = "0.9" ndarray = "0.16" plotly = { path = "../../plotly" } diff --git a/examples/customization/src/main.rs b/examples/customization/src/main.rs index 1988fc7f..c32c0090 100644 --- a/examples/customization/src/main.rs +++ b/examples/customization/src/main.rs @@ -121,14 +121,11 @@ fn write_html(html_data: &str) -> String { use std::env; use std::{fs::File, io::Write}; - use rand::{ - distributions::{Alphanumeric, DistString}, - thread_rng, - }; + use rand::distr::{Alphanumeric, SampleString}; // Set up the temp file with a unique filename. let mut temp = env::temp_dir(); - let mut plot_name = Alphanumeric.sample_string(&mut thread_rng(), 22); + let mut plot_name = Alphanumeric.sample_string(&mut rand::rng(), 22); plot_name.push_str(".html"); plot_name = format!("plotly_{}", plot_name); temp.push(plot_name); diff --git a/examples/shapes/Cargo.toml b/examples/shapes/Cargo.toml index 799fea60..927e0540 100644 --- a/examples/shapes/Cargo.toml +++ b/examples/shapes/Cargo.toml @@ -7,5 +7,5 @@ edition = "2021" [dependencies] ndarray = "0.16" plotly = { path = "../../plotly" } -rand = "0.8" -rand_distr = "0.4" +rand = "0.9" +rand_distr = "0.5" diff --git a/examples/shapes/src/main.rs b/examples/shapes/src/main.rs index a00864b4..c582435b 100644 --- a/examples/shapes/src/main.rs +++ b/examples/shapes/src/main.rs @@ -9,7 +9,6 @@ use plotly::{ }, Bar, Plot, Scatter, }; -use rand::thread_rng; use rand_distr::{num_traits::Float, Distribution, Normal}; // ANCHOR: filled_area_chart @@ -433,7 +432,7 @@ fn circles_positioned_relative_to_the_axes(show: bool) -> Plot { // ANCHOR: highlighting_clusters_of_scatter_points_with_circle_shapes fn highlighting_clusters_of_scatter_points_with_circle_shapes(show: bool) -> Plot { - let mut rng = thread_rng(); + let mut rng = rand::rng(); let x0 = Normal::new(2., 0.45) .unwrap() .sample_iter(&mut rng) diff --git a/examples/statistical_charts/Cargo.toml b/examples/statistical_charts/Cargo.toml index b2b02c20..0dffe48e 100644 --- a/examples/statistical_charts/Cargo.toml +++ b/examples/statistical_charts/Cargo.toml @@ -7,5 +7,5 @@ edition = "2021" [dependencies] ndarray = "0.16" plotly = { path = "../../plotly" } -rand = "0.8" -rand_distr = "0.4" +rand = "0.9" +rand_distr = "0.5" diff --git a/examples/statistical_charts/src/main.rs b/examples/statistical_charts/src/main.rs index 4f82eaef..11b2ea21 100644 --- a/examples/statistical_charts/src/main.rs +++ b/examples/statistical_charts/src/main.rs @@ -170,9 +170,9 @@ fn colored_and_styled_error_bars(show: bool) -> Plot { // Box Plots // ANCHOR: basic_box_plot fn basic_box_plot(show: bool) -> Plot { - let mut rng = rand::thread_rng(); - let uniform1 = Uniform::new(0.0, 1.0); - let uniform2 = Uniform::new(1.0, 2.0); + let mut rng = rand::rng(); + let uniform1 = Uniform::new(0.0, 1.0).unwrap(); + let uniform2 = Uniform::new(1.0, 2.0).unwrap(); let n = 50; let mut y0 = Vec::with_capacity(n); @@ -407,8 +407,8 @@ fn grouped_horizontal_box_plot(show: bool) -> Plot { fn fully_styled_box_plot(show: bool) -> Plot { let rnd_sample = |num, mul| -> Vec { let mut v: Vec = Vec::with_capacity(num); - let mut rng = rand::thread_rng(); - let uniform = Uniform::new(0.0, mul); + let mut rng = rand::rng(); + let uniform = Uniform::new(0.0, mul).unwrap(); for _ in 0..num { v.push(uniform.sample(&mut rng)); } @@ -478,7 +478,7 @@ fn fully_styled_box_plot(show: bool) -> Plot { // Histograms fn sample_normal_distribution(n: usize, mean: f64, std_dev: f64) -> Vec { - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); let dist = Normal::new(mean, std_dev).unwrap(); let mut v = Vec::::with_capacity(n); for _idx in 1..n { @@ -488,8 +488,8 @@ fn sample_normal_distribution(n: usize, mean: f64, std_dev: f64) -> Vec { } fn sample_uniform_distribution(n: usize, lb: f64, ub: f64) -> Vec { - let mut rng = rand::thread_rng(); - let dist = Uniform::new(lb, ub); + let mut rng = rand::rng(); + let dist = Uniform::new(lb, ub).unwrap(); let mut v = Vec::::with_capacity(n); for _idx in 1..n { v.push(dist.sample(&mut rng)); diff --git a/examples/wasm-yew-minimal/Cargo.toml b/examples/wasm-yew-minimal/Cargo.toml index bc4156ba..7a094e75 100644 --- a/examples/wasm-yew-minimal/Cargo.toml +++ b/examples/wasm-yew-minimal/Cargo.toml @@ -8,7 +8,7 @@ authors = [ edition = "2021" [dependencies] -plotly = { path = "../../plotly", features = ["wasm"] } +plotly = { path = "../../plotly" } yew = "0.21" yew-hooks = "0.3" log = "0.4" diff --git a/plotly/Cargo.toml b/plotly/Cargo.toml index 921675e0..576335f3 100644 --- a/plotly/Cargo.toml +++ b/plotly/Cargo.toml @@ -21,7 +21,6 @@ plotly_ndarray = ["ndarray"] plotly_image = ["image"] plotly_embed_js = [] -wasm = ["getrandom", "js-sys", "wasm-bindgen", "wasm-bindgen-futures"] with-axum = ["rinja/with-axum", "rinja_axum"] [dependencies] @@ -29,9 +28,7 @@ rinja = { version = "0.3", features = ["serde_json"] } rinja_axum = { version = "0.3", optional = true } dyn-clone = "1" erased-serde = "0.4" -getrandom = { version = "0.2", features = ["js"], optional = true } image = { version = "0.25", optional = true } -js-sys = { version = "0.3", optional = true } plotly_derive = { version = "0.12", path = "../plotly_derive" } plotly_kaleido = { version = "0.12", path = "../plotly_kaleido", optional = true } ndarray = { version = "0.16", optional = true } @@ -40,9 +37,12 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_repr = "0.1" serde_with = ">=2, <4" -rand = "0.8" -wasm-bindgen = { version = "0.2", optional = true } -wasm-bindgen-futures = { version = "0.4", optional = true } +rand = "0.9" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +getrandom = { version = "0.3", features = ["wasm_js"] } +wasm-bindgen-futures = { version = "0.4" } +wasm-bindgen = { version = "0.2" } [dev-dependencies] csv = "1.1" @@ -51,5 +51,5 @@ itertools = ">=0.10, <0.15" itertools-num = "0.1" ndarray = "0.16" plotly_kaleido = { path = "../plotly_kaleido", features = ["download"] } -rand_distr = "0.4" +rand_distr = "0.5" base64 = "0.22" diff --git a/plotly/src/bindings.rs b/plotly/src/bindings.rs index 938430e3..9b86c05d 100644 --- a/plotly/src/bindings.rs +++ b/plotly/src/bindings.rs @@ -2,8 +2,8 @@ //! context, where it is assumed that a remote copy of the Javascript Plotly //! library is available, (i.e. via a CDN). -use js_sys::Object; use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::js_sys::Object; use crate::Plot; diff --git a/plotly/src/lib.rs b/plotly/src/lib.rs index 9d331d92..a4b0a3a4 100644 --- a/plotly/src/lib.rs +++ b/plotly/src/lib.rs @@ -6,9 +6,9 @@ extern crate rand; extern crate rinja; extern crate serde; -#[cfg(all(feature = "kaleido", feature = "wasm"))] +#[cfg(all(feature = "kaleido", target_family = "wasm"))] compile_error!( - r#"The "kaleido" and "wasm" features are mutually exclusive and cannot be activated at the same time. Please disable one or the other."# + r#"The "kaleido" feature is not available on "wasm" targets. Please compile without this feature for the wasm target family."# ); #[cfg(feature = "plotly_ndarray")] @@ -16,7 +16,7 @@ pub mod ndarray; #[cfg(feature = "plotly_ndarray")] pub use crate::ndarray::ArrayTraces; -#[cfg(feature = "wasm")] +#[cfg(target_family = "wasm")] pub mod bindings; pub mod common; diff --git a/plotly/src/plot.rs b/plotly/src/plot.rs index 6dbe22eb..ecc72934 100644 --- a/plotly/src/plot.rs +++ b/plotly/src/plot.rs @@ -3,8 +3,8 @@ use std::{fs::File, io::Write, path::Path}; use dyn_clone::DynClone; use erased_serde::Serialize as ErasedSerialize; use rand::{ - distributions::{Alphanumeric, DistString}, - thread_rng, + distr::{Alphanumeric, SampleString}, + rng, }; use rinja::Template; use serde::Serialize; @@ -254,7 +254,7 @@ impl Plot { // Set up the temp file with a unique filename. let mut temp = env::temp_dir(); - let mut plot_name = Alphanumeric.sample_string(&mut thread_rng(), 22); + let mut plot_name = Alphanumeric.sample_string(&mut rng(), 22); plot_name.push_str(".html"); plot_name = format!("plotly_{}", plot_name); temp.push(plot_name); @@ -296,7 +296,7 @@ impl Plot { // Set up the temp file with a unique filename. let mut temp = env::temp_dir(); - let mut plot_name = Alphanumeric.sample_string(&mut thread_rng(), 22); + let mut plot_name = Alphanumeric.sample_string(&mut rng(), 22); plot_name.push_str(".html"); plot_name = format!("plotly_{}", plot_name); temp.push(plot_name); @@ -354,13 +354,13 @@ impl Plot { pub fn to_inline_html(&self, plot_div_id: Option<&str>) -> String { let plot_div_id = match plot_div_id { Some(id) => id.to_string(), - None => Alphanumeric.sample_string(&mut thread_rng(), 20), + None => Alphanumeric.sample_string(&mut rng(), 20), }; self.render_inline(&plot_div_id) } fn to_jupyter_notebook_html(&self) -> String { - let plot_div_id = Alphanumeric.sample_string(&mut thread_rng(), 20); + let plot_div_id = Alphanumeric.sample_string(&mut rng(), 20); let tmpl = JupyterNotebookPlotTemplate { plot: self, @@ -534,10 +534,11 @@ impl Plot { serde_json::to_string(self).unwrap() } - #[cfg(feature = "wasm")] + #[cfg(target_family = "wasm")] /// Convert a `Plot` to a native Javasript `js_sys::Object`. - pub fn to_js_object(&self) -> js_sys::Object { - use wasm_bindgen::JsCast; + pub fn to_js_object(&self) -> wasm_bindgen_futures::js_sys::Object { + use wasm_bindgen_futures::js_sys; + use wasm_bindgen_futures::wasm_bindgen::JsCast; // The only reason this could fail is if to_json() produces structurally // incorrect JSON. That would be a bug, and would require fixing in the // to_json()/serialization methods, rather than here @@ -734,7 +735,7 @@ mod tests { #[test] #[ignore] // Don't really want it to try and open a browser window every time we run a test. - #[cfg(not(feature = "wasm"))] + #[cfg(not(target_family = "wasm"))] fn show_image() { let plot = create_test_plot(); plot.show_image(ImageFormat::PNG, 1024, 680); diff --git a/plotly/src/traces/histogram.rs b/plotly/src/traces/histogram.rs index 7b83b884..e1e8c422 100644 --- a/plotly/src/traces/histogram.rs +++ b/plotly/src/traces/histogram.rs @@ -220,7 +220,7 @@ where /// /// fn ndarray_to_traces() { /// let n: usize = 1_250; - /// let mut rng = rand::thread_rng(); + /// let mut rng = rand::rng(); /// let t: Array = Array::range(0., 10., 10. / n as f64); /// let mut ys: Array = Array::zeros((n, 4)); /// let mut count = 0.; From 833af05f024d8759e0fd86afbf97099e317d7539 Mon Sep 17 00:00:00 2001 From: Andrei <8067229+andrei-ng@users.noreply.github.com> Date: Sun, 9 Feb 2025 17:09:23 +0100 Subject: [PATCH 11/83] update readme to remove mention of wasm feature flag (#278) Signed-off-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> --- README.md | 11 ++--------- plotly_kaleido/src/lib.rs | 1 + 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 2d2528ab..f885f889 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ To save a plot as a static image, the `kaleido` feature is required as well as i ### 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 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. @@ -143,14 +143,7 @@ plot.write_image("out.png", ImageFormat::PNG, 800, 600, 1.0); ## Usage Within a Wasm Environment -Using `Plotly.rs` in a Wasm-based frontend framework is possible by enabling the `wasm` feature: - -```toml -# Cargo.toml - -[dependencies] -plotly = { version = "0.12", features = ["wasm"] } -``` +`Plotly.rs` can be used with a Wasm-based frontend framework. The needed dependencies are automatically enabled on `wasm32` targets. Note that the `kaleido` feature is not supported in Wasm enviroments and will throw a compilation error if enabled. First, make sure that you have the Plotly JavaScript library in your base HTML template: diff --git a/plotly_kaleido/src/lib.rs b/plotly_kaleido/src/lib.rs index cddcaf44..276ba124 100644 --- a/plotly_kaleido/src/lib.rs +++ b/plotly_kaleido/src/lib.rs @@ -10,6 +10,7 @@ //! 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; From b4ec4acdb60450690a062a81a250e455d3c4ccbe Mon Sep 17 00:00:00 2001 From: Steve Date: Tue, 18 Feb 2025 20:23:28 +0100 Subject: [PATCH 12/83] use last version of use_effect_with in README.md (#279) --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f885f889..149b3005 100644 --- a/README.md +++ b/README.md @@ -182,11 +182,10 @@ pub fn plot_component() -> Html { }); - use_effect_with_deps(move |_| { - p.run(); - || () - }, (), - ); + use_effect_with((), move |_| { + p.run(); + || () + }); html! { From 989147965e07fd46190b2475274880494c18458c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Sat, 22 Mar 2025 01:11:59 +0100 Subject: [PATCH 13/83] Less copying in template struct creation --- plotly/src/plot.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plotly/src/plot.rs b/plotly/src/plot.rs index ecc72934..8b98471e 100644 --- a/plotly/src/plot.rs +++ b/plotly/src/plot.rs @@ -15,7 +15,7 @@ use crate::{Configuration, Layout}; #[template(path = "plot.html", escape = "none")] struct PlotTemplate<'a> { plot: &'a Plot, - js_scripts: String, + js_scripts: &'a str, } #[derive(Template)] @@ -24,7 +24,7 @@ struct PlotTemplate<'a> { struct StaticPlotTemplate<'a> { plot: &'a Plot, format: ImageFormat, - js_scripts: String, + js_scripts: &'a str, width: usize, height: usize, } @@ -466,7 +466,7 @@ impl Plot { fn render(&self) -> String { let tmpl = PlotTemplate { plot: self, - js_scripts: self.js_scripts.clone(), + js_scripts: &self.js_scripts, }; tmpl.render().unwrap() } @@ -476,7 +476,7 @@ impl Plot { let tmpl = StaticPlotTemplate { plot: self, format, - js_scripts: self.js_scripts.clone(), + js_scripts: &self.js_scripts, width, height, }; From cb35c3704ac4ef076b1859f6ad80ad2e50113430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Thu, 27 Mar 2025 18:00:44 +0100 Subject: [PATCH 14/83] Update to askama 0.13.0 Rinja 0.2 was a fork of askama 0.12. Both projects were [merged again], and new versions of the project will be released under the name askama. The feature `"with-axum"` is not needed anymore. The implementation for web-framework integrations was moved into its own crate. The `askama` crate does not have any problems with feature flag incompatibilities anymore. [merged again]: --- CHANGELOG.md | 3 ++- plotly/Cargo.toml | 5 +---- plotly/src/lib.rs | 2 +- plotly/src/plot.rs | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23f41d97..b5a17392 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.13.0] - 2025-02-xx +## [0.13.0] - 2025-03-xx ### Changed - [[#277](https://github.com/plotly/plotly.rs/pull/277)] Removed `wasm` feature flag and put evrything behind target specific dependencies. Added `.cargo/config.toml` for configuration flags needed by `getrandom` version 0.3 on `wasm` targets. +- [[#281]((https://github.com/plotly/plotly.rs/pull/xxx))] Update to askama 0.13.0 ## [0.12.1] - 2025-01-02 ### Fixed diff --git a/plotly/Cargo.toml b/plotly/Cargo.toml index 576335f3..ed517d9b 100644 --- a/plotly/Cargo.toml +++ b/plotly/Cargo.toml @@ -21,11 +21,8 @@ plotly_ndarray = ["ndarray"] plotly_image = ["image"] plotly_embed_js = [] -with-axum = ["rinja/with-axum", "rinja_axum"] - [dependencies] -rinja = { version = "0.3", features = ["serde_json"] } -rinja_axum = { version = "0.3", optional = true } +askama = { version = "0.13.0", features = ["serde_json"] } dyn-clone = "1" erased-serde = "0.4" image = { version = "0.25", optional = true } diff --git a/plotly/src/lib.rs b/plotly/src/lib.rs index a4b0a3a4..ee082087 100644 --- a/plotly/src/lib.rs +++ b/plotly/src/lib.rs @@ -2,8 +2,8 @@ //! //! 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 +extern crate askama; extern crate rand; -extern crate rinja; extern crate serde; #[cfg(all(feature = "kaleido", target_family = "wasm"))] diff --git a/plotly/src/plot.rs b/plotly/src/plot.rs index 8b98471e..011276f5 100644 --- a/plotly/src/plot.rs +++ b/plotly/src/plot.rs @@ -1,12 +1,12 @@ use std::{fs::File, io::Write, path::Path}; +use askama::Template; use dyn_clone::DynClone; use erased_serde::Serialize as ErasedSerialize; use rand::{ distr::{Alphanumeric, SampleString}, rng, }; -use rinja::Template; use serde::Serialize; use crate::{Configuration, Layout}; From 030125929577116880cf76f43ece37de87bd8a27 Mon Sep 17 00:00:00 2001 From: Marius Kriegerowski Date: Wed, 16 Apr 2025 15:12:47 +0200 Subject: [PATCH 15/83] simplify path to str conversion (#283) * simplify path to str conversion * save data as byte array --------- Signed-off-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> Co-authored-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> --- plotly_kaleido/src/lib.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/plotly_kaleido/src/lib.rs b/plotly_kaleido/src/lib.rs index 276ba124..91f1e09f 100644 --- a/plotly_kaleido/src/lib.rs +++ b/plotly_kaleido/src/lib.rs @@ -146,12 +146,12 @@ impl Kaleido { dst.set_extension(format); let image_data = self.convert(plotly_data, format, width, height, scale)?; - let data: Vec = match format { - "svg" | "eps" => image_data.as_bytes().to_vec(), - _ => general_purpose::STANDARD.decode(image_data).unwrap(), + let data = match format { + "svg" | "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.write_all(data)?; file.flush()?; Ok(()) @@ -183,12 +183,10 @@ impl Kaleido { height: usize, scale: f64, ) -> Result> { - let p = self.cmd_path.as_path(); - let p = p.to_str().unwrap(); - let p = String::from(p); + let p = self.cmd_path.to_str().unwrap(); #[allow(clippy::zombie_processes)] - let mut process = Command::new(p.as_str()) + let mut process = Command::new(p) .current_dir(self.cmd_path.parent().unwrap()) .args([ "plotly", From 96c85916067c0b42dd0a44ba4a00380d131f2be0 Mon Sep 17 00:00:00 2001 From: Andrei NG <8067229+andrei-ng@users.noreply.github.com> Date: Fri, 18 Apr 2025 09:15:07 +0200 Subject: [PATCH 16/83] allow plotly to be compiled for android (#284) - disable show/render functionality when target_os is android Closes #282 Signed-off-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> --- CHANGELOG.md | 3 +++ plotly/src/plot.rs | 12 ++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5a17392..e50cd444 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - [[#277](https://github.com/plotly/plotly.rs/pull/277)] Removed `wasm` feature flag and put evrything behind target specific dependencies. Added `.cargo/config.toml` for configuration flags needed by `getrandom` version 0.3 on `wasm` targets. - [[#281]((https://github.com/plotly/plotly.rs/pull/xxx))] Update to askama 0.13.0 +### Fixed +- [[#284](https://github.com/plotly/plotly.rs/pull/284)] Allow plotly package to be compiled for android + ## [0.12.1] - 2025-01-02 ### Fixed - [[#269](https://github.com/plotly/plotly.rs/pull/269)] Fix publishing to crates.io issue diff --git a/plotly/src/plot.rs b/plotly/src/plot.rs index 011276f5..9f970b92 100644 --- a/plotly/src/plot.rs +++ b/plotly/src/plot.rs @@ -20,7 +20,7 @@ struct PlotTemplate<'a> { #[derive(Template)] #[template(path = "static_plot.html", escape = "none")] -#[cfg(not(target_family = "wasm"))] +#[cfg(all(not(target_family = "wasm"), not(target_os = "android")))] struct StaticPlotTemplate<'a> { plot: &'a Plot, format: ImageFormat, @@ -43,7 +43,7 @@ struct JupyterNotebookPlotTemplate<'a> { plot_div_id: &'a str, } -#[cfg(not(target_family = "wasm"))] +#[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` feature the `write_image` method can be used to produce a static image in one of the following formats: @@ -246,7 +246,7 @@ impl Plot { /// /// The HTML file is saved in a temp file, from which it is read and /// displayed by the browser. - #[cfg(not(target_family = "wasm"))] + #[cfg(all(not(target_family = "wasm"), not(target_os = "android")))] pub fn show(&self) { use std::env; @@ -278,7 +278,7 @@ impl Plot { /// The HTML file is generated and saved in the provided filename as long as /// the path already exists, after the file is saved, it is read and /// displayed by the browser. - #[cfg(not(target_family = "wasm"))] + #[cfg(all(not(target_family = "wasm"), not(target_os = "android")))] pub fn show_html + std::clone::Clone>(&self, filename: P) { let path = filename.as_ref().to_str().unwrap(); self.write_html(filename.clone()); @@ -288,7 +288,7 @@ impl Plot { /// Display the fully rendered `Plot` as a static image of the given format /// in the default system browser. - #[cfg(not(target_family = "wasm"))] + #[cfg(all(not(target_family = "wasm"), not(target_os = "android")))] pub fn show_image(&self, format: ImageFormat, width: usize, height: usize) { use std::env; @@ -471,7 +471,7 @@ impl Plot { tmpl.render().unwrap() } - #[cfg(not(target_family = "wasm"))] + #[cfg(all(not(target_family = "wasm"), not(target_os = "android")))] fn render_static(&self, format: ImageFormat, width: usize, height: usize) -> String { let tmpl = StaticPlotTemplate { plot: self, From b05315f22ac14fc9c7ed38fe6fe90860350145a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Apr 2025 18:35:33 +0300 Subject: [PATCH 17/83] Update askama requirement from 0.13.0 to 0.14.0 (#286) Updates the requirements on [askama](https://github.com/askama-rs/askama) to permit the latest version. - [Release notes](https://github.com/askama-rs/askama/releases) - [Commits](https://github.com/askama-rs/askama/compare/v0.13.0...v0.14.0) --- updated-dependencies: - dependency-name: askama dependency-version: 0.14.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- plotly/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plotly/Cargo.toml b/plotly/Cargo.toml index ed517d9b..e7bbcee3 100644 --- a/plotly/Cargo.toml +++ b/plotly/Cargo.toml @@ -22,7 +22,7 @@ plotly_image = ["image"] plotly_embed_js = [] [dependencies] -askama = { version = "0.13.0", features = ["serde_json"] } +askama = { version = "0.14.0", features = ["serde_json"] } dyn-clone = "1" erased-serde = "0.4" image = { version = "0.25", optional = true } From a7ac37d81595a0ceb1d0bad4522926e139165a43 Mon Sep 17 00:00:00 2001 From: Joaquin Bejar Garcia <99006095+joaquinbejar@users.noreply.github.com> Date: Mon, 12 May 2025 19:21:01 +0200 Subject: [PATCH 18/83] Add macOS-specific fixes and tests for Kaleido compatibility (#289) * add macOS-specific fixes and tests for Kaleido compatibility Introduce macOS-specific arguments for Kaleido to address issue #323. Add new tests to validate image generation (e.g., PNG, JPEG, SVG, PDF) on macOS, ensuring proper functionality. This improves cross-platform support and resolves inconsistencies in the Kaleido backend. * Add macOS-specific test surface creation and adjust imports Introduced a macOS-specific `create_test_surface` function in `plotly_kaleido` and adjusted test-related imports in `plotly`. Ensures compatibility with macOS while keeping non-macOS logic intact. * Refactor imports in plot.rs tests for macOS compatibility issue: #241 --- plotly/src/plot.rs | 31 +++++++- plotly_kaleido/src/lib.rs | 150 +++++++++++++++++++++++++++++++++++--- 2 files changed, 167 insertions(+), 14 deletions(-) diff --git a/plotly/src/plot.rs b/plotly/src/plot.rs index 9f970b92..826f82ba 100644 --- a/plotly/src/plot.rs +++ b/plotly/src/plot.rs @@ -586,9 +586,9 @@ impl PartialEq for Plot { mod tests { use std::path::PathBuf; - #[cfg(feature = "kaleido")] - use base64::{engine::general_purpose, Engine as _}; use serde_json::{json, to_value}; + #[cfg(not(target_os = "macos"))] + use {base64::engine::general_purpose, base64::Engine}; use super::*; use crate::Scatter; @@ -866,4 +866,31 @@ mod tests { const LEN: usize = 10; assert_eq!(expected[..LEN], image_svg[..LEN]); } + + #[cfg(target_os = "macos")] + #[test] + #[cfg(feature = "kaleido")] + fn save_surface_to_png() { + use crate::Surface; + let mut plot = Plot::new(); + let z_matrix = vec![ + vec![1.0, 2.0, 3.0], + vec![4.0, 5.0, 6.0], + vec![7.0, 8.0, 9.0], + ]; + let x_unique = vec![1.0, 2.0, 3.0]; + let y_unique = vec![4.0, 5.0, 6.0]; + let surface = Surface::new(z_matrix) + .x(x_unique) + .y(y_unique) + .name("Surface"); + + plot.add_trace(surface); + let dst = PathBuf::from("example.png"); + plot.write_image("example.png", ImageFormat::PNG, 800, 600, 1.0); + assert!(dst.exists()); + assert!(std::fs::remove_file(&dst).is_ok()); + assert!(!dst.exists()); + assert!(!plot.to_base64(ImageFormat::PNG, 1024, 680, 1.0).is_empty()); + } } diff --git a/plotly_kaleido/src/lib.rs b/plotly_kaleido/src/lib.rs index 91f1e09f..5ad78737 100644 --- a/plotly_kaleido/src/lib.rs +++ b/plotly_kaleido/src/lib.rs @@ -185,20 +185,34 @@ impl Kaleido { ) -> Result> { let p = self.cmd_path.to_str().unwrap(); + #[cfg(not(target_os = "macos"))] + let cmd_args = vec![ + "plotly", + "--disable-gpu", + "--allow-file-access-from-files", + "--disable-breakpad", + "--disable-dev-shm-usage", + "--disable-software-rasterizer", + "--single-process", + "--no-sandbox", + ]; + + // Add Kaleido issue #323 + #[cfg(target_os = "macos")] + 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([ - "plotly", - "--disable-gpu", - "--allow-file-access-from-files", - "--disable-breakpad", - "--disable-dev-shm-usage", - "--disable-software-rasterizer", - "--single-process", - "--disable-gpu", - "--no-sandbox", - ]) + .args(cmd_args) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) @@ -213,7 +227,6 @@ impl Kaleido { .to_string() ) }); - { let plot_data = PlotData::new(plotly_data, format, width, height, scale).to_json(); let mut process_stdin = process.stdin.take().unwrap(); @@ -287,6 +300,47 @@ mod tests { .unwrap() } + #[cfg(target_os = "macos")] + fn create_test_surface() -> 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(); @@ -378,4 +432,76 @@ mod tests { assert!(r.is_ok()); assert!(std::fs::remove_file(dst.as_path()).is_ok()); } + + // Issue #241 workaround until https://github.com/plotly/Kaleido/issues/323 is resolved + #[cfg(target_os = "macos")] + #[test] + fn save_surface_png() { + let test_plot = create_test_surface(); + let k = Kaleido::new(); + let dst = PathBuf::from("example.png"); + let r = k.save(dst.as_path(), &test_plot, "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()); + } + #[cfg(target_os = "macos")] + #[test] + fn save_surface_jpeg() { + let test_plot = create_test_surface(); + let k = Kaleido::new(); + let dst = PathBuf::from("example.jpeg"); + let r = k.save(dst.as_path(), &test_plot, "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()); + } + #[cfg(target_os = "macos")] + #[test] + fn save_surface_webp() { + let test_plot = create_test_surface(); + let k = Kaleido::new(); + let dst = PathBuf::from("example.webp"); + let r = k.save(dst.as_path(), &test_plot, "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()); + } + #[cfg(target_os = "macos")] + #[test] + fn save_surface_svg() { + let test_plot = create_test_surface(); + let k = Kaleido::new(); + let dst = PathBuf::from("example.svg"); + let r = k.save(dst.as_path(), &test_plot, "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()); + } + #[cfg(target_os = "macos")] + #[test] + fn save_surface_pdf() { + let test_plot = create_test_surface(); + let k = Kaleido::new(); + let dst = PathBuf::from("example.pdf"); + let r = k.save(dst.as_path(), &test_plot, "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()); + } } From 1ecb8ac13f7ec3540c67b65011a07cfcaf24e097 Mon Sep 17 00:00:00 2001 From: Andrei NG <8067229+andrei-ng@users.noreply.github.com> Date: Mon, 12 May 2025 20:43:25 +0200 Subject: [PATCH 19/83] remove disable-gpu flag for all targets (#291) - keep only the kaleido unittests added in PR #289 in favor of previous ones Signed-off-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> --- CHANGELOG.md | 4 +- plotly/src/plot.rs | 2 +- plotly_kaleido/src/lib.rs | 151 ++++++-------------------------------- 3 files changed, 28 insertions(+), 129 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e50cd444..724188f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.13.0] - 2025-03-xx +## [0.13.0] - 2025-xx-xx ### Changed - [[#277](https://github.com/plotly/plotly.rs/pull/277)] Removed `wasm` feature flag and put evrything behind target specific dependencies. Added `.cargo/config.toml` for configuration flags needed by `getrandom` version 0.3 on `wasm` targets. - [[#281]((https://github.com/plotly/plotly.rs/pull/xxx))] Update to askama 0.13.0 +- [[#289]](https://github.com/plotly/plotly.rs/pull/289) Fixes Kaleido static export for MacOS targets by removing `--disable-gpu` flag for MacOS +- [[#290]](https://github.com/plotly/plotly.rs/pull/289) Remove `--disable-gpu` flag for Kaleido static-image generation for all targets. ### Fixed - [[#284](https://github.com/plotly/plotly.rs/pull/284)] Allow plotly package to be compiled for android diff --git a/plotly/src/plot.rs b/plotly/src/plot.rs index 826f82ba..67c2a737 100644 --- a/plotly/src/plot.rs +++ b/plotly/src/plot.rs @@ -587,7 +587,7 @@ mod tests { use std::path::PathBuf; use serde_json::{json, to_value}; - #[cfg(not(target_os = "macos"))] + #[cfg(feature = "kaleido")] use {base64::engine::general_purpose, base64::Engine}; use super::*; diff --git a/plotly_kaleido/src/lib.rs b/plotly_kaleido/src/lib.rs index 5ad78737..9b8d9e21 100644 --- a/plotly_kaleido/src/lib.rs +++ b/plotly_kaleido/src/lib.rs @@ -185,20 +185,8 @@ impl Kaleido { ) -> Result> { let p = self.cmd_path.to_str().unwrap(); - #[cfg(not(target_os = "macos"))] - let cmd_args = vec![ - "plotly", - "--disable-gpu", - "--allow-file-access-from-files", - "--disable-breakpad", - "--disable-dev-shm-usage", - "--disable-software-rasterizer", - "--single-process", - "--no-sandbox", - ]; - - // Add Kaleido issue #323 - #[cfg(target_os = "macos")] + // 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", @@ -272,36 +260,6 @@ mod tests { use super::*; fn create_test_plot() -> Value { - to_value(json!({ - "data": [ - { - "type": "scatter", - "x": [1, 2, 3, 4], - "y": [10, 15, 13, 17], - "name": "trace1", - "mode": "markers" - }, - { - "type": "scatter", - "x": [2, 3, 4, 5], - "y": [16, 5, 11, 9], - "name": "trace2", - "mode": "lines" - }, - { - "type": "scatter", - "x": [1, 2, 3, 4], - "y": [12, 9, 15, 12], - "name": "trace3", - } - ], - "layout": {} - })) - .unwrap() - } - - #[cfg(target_os = "macos")] - fn create_test_surface() -> Value { to_value(json!({ "data": [ { @@ -361,8 +319,7 @@ mod tests { assert_eq!(to_value(kaleido_data).unwrap(), expected); } - // This seems to fail unpredictably on MacOs. - #[cfg(not(target_os = "macos"))] + // 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(); @@ -370,11 +327,13 @@ mod tests { let dst = PathBuf::from("example.png"); let r = k.save(dst.as_path(), &test_plot, "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()); } - // This seems to fail unpredictably on MacOs. - #[cfg(not(target_os = "macos"))] #[test] fn save_jpeg() { let test_plot = create_test_plot(); @@ -382,11 +341,13 @@ mod tests { let dst = PathBuf::from("example.jpeg"); let r = k.save(dst.as_path(), &test_plot, "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()); } - // This seems to fail unpredictably on MacOs. - #[cfg(not(target_os = "macos"))] #[test] fn save_webp() { let test_plot = create_test_plot(); @@ -394,11 +355,13 @@ mod tests { let dst = PathBuf::from("example.webp"); let r = k.save(dst.as_path(), &test_plot, "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()); } - // This seems to fail unpredictably on MacOs. - #[cfg(not(target_os = "macos"))] #[test] fn save_svg() { let test_plot = create_test_plot(); @@ -406,11 +369,13 @@ mod tests { let dst = PathBuf::from("example.svg"); let r = k.save(dst.as_path(), &test_plot, "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()); } - // This seems to fail unpredictably on MacOs. - #[cfg(not(target_os = "macos"))] #[test] fn save_pdf() { let test_plot = create_test_plot(); @@ -418,10 +383,14 @@ mod tests { let dst = PathBuf::from("example.pdf"); let r = k.save(dst.as_path(), &test_plot, "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()); } - // This generates empty eps files for some reason + // Kaleido generates empty eps files #[test] #[ignore] fn save_eps() { @@ -432,76 +401,4 @@ mod tests { assert!(r.is_ok()); assert!(std::fs::remove_file(dst.as_path()).is_ok()); } - - // Issue #241 workaround until https://github.com/plotly/Kaleido/issues/323 is resolved - #[cfg(target_os = "macos")] - #[test] - fn save_surface_png() { - let test_plot = create_test_surface(); - let k = Kaleido::new(); - let dst = PathBuf::from("example.png"); - let r = k.save(dst.as_path(), &test_plot, "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()); - } - #[cfg(target_os = "macos")] - #[test] - fn save_surface_jpeg() { - let test_plot = create_test_surface(); - let k = Kaleido::new(); - let dst = PathBuf::from("example.jpeg"); - let r = k.save(dst.as_path(), &test_plot, "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()); - } - #[cfg(target_os = "macos")] - #[test] - fn save_surface_webp() { - let test_plot = create_test_surface(); - let k = Kaleido::new(); - let dst = PathBuf::from("example.webp"); - let r = k.save(dst.as_path(), &test_plot, "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()); - } - #[cfg(target_os = "macos")] - #[test] - fn save_surface_svg() { - let test_plot = create_test_surface(); - let k = Kaleido::new(); - let dst = PathBuf::from("example.svg"); - let r = k.save(dst.as_path(), &test_plot, "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()); - } - #[cfg(target_os = "macos")] - #[test] - fn save_surface_pdf() { - let test_plot = create_test_surface(); - let k = Kaleido::new(); - let dst = PathBuf::from("example.pdf"); - let r = k.save(dst.as_path(), &test_plot, "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()); - } } From 4b563ebfd9baec3868723c10f959ab6ea59b015a Mon Sep 17 00:00:00 2001 From: h4ck4l1 Date: Fri, 25 Apr 2025 21:38:01 +0000 Subject: [PATCH 20/83] Added functionality for callbacks --- examples/wasm-yew-callback-minimal/Cargo.toml | 12 ++ examples/wasm-yew-callback-minimal/README.md | 9 ++ examples/wasm-yew-callback-minimal/index.html | 12 ++ .../wasm-yew-callback-minimal/src/main.rs | 80 ++++++++++ plotly/Cargo.toml | 2 + plotly/src/bindings.rs | 1 + plotly/src/callbacks.rs | 142 ++++++++++++++++++ plotly/src/lib.rs | 3 + 8 files changed, 261 insertions(+) create mode 100644 examples/wasm-yew-callback-minimal/Cargo.toml create mode 100644 examples/wasm-yew-callback-minimal/README.md create mode 100644 examples/wasm-yew-callback-minimal/index.html create mode 100644 examples/wasm-yew-callback-minimal/src/main.rs create mode 100644 plotly/src/callbacks.rs diff --git a/examples/wasm-yew-callback-minimal/Cargo.toml b/examples/wasm-yew-callback-minimal/Cargo.toml new file mode 100644 index 00000000..12f22f6d --- /dev/null +++ b/examples/wasm-yew-callback-minimal/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "wasm-yew-callback-minimal" +version = "0.1.0" +edition = "2024" + +[dependencies] +plotly = { path = "../../plotly" } +yew = "0.21" +yew-hooks = "0.3" +log = "0.4" +wasm-logger = "0.2" +web-sys = { version = "0.3.77"} \ No newline at end of file diff --git a/examples/wasm-yew-callback-minimal/README.md b/examples/wasm-yew-callback-minimal/README.md new file mode 100644 index 00000000..a62a6681 --- /dev/null +++ b/examples/wasm-yew-callback-minimal/README.md @@ -0,0 +1,9 @@ +# Wasm Yew Minimal + +## Prerequisites + +1. Install [Trunk](https://trunkrs.dev/) using `cargo install --locked trunk`. + +## How to Run + +1. Run `trunk serve --open` in this directory to build and serve the application, opening the default web browser automatically. \ No newline at end of file diff --git a/examples/wasm-yew-callback-minimal/index.html b/examples/wasm-yew-callback-minimal/index.html new file mode 100644 index 00000000..88480a2e --- /dev/null +++ b/examples/wasm-yew-callback-minimal/index.html @@ -0,0 +1,12 @@ + + + + + + Plotly Yew + + + + + + \ No newline at end of file diff --git a/examples/wasm-yew-callback-minimal/src/main.rs b/examples/wasm-yew-callback-minimal/src/main.rs new file mode 100644 index 00000000..f1a82ab2 --- /dev/null +++ b/examples/wasm-yew-callback-minimal/src/main.rs @@ -0,0 +1,80 @@ +use plotly::{Plot,common::Mode, Scatter,Histogram}; +use plotly::callbacks::{ClickEvent}; +use web_sys::js_sys::Math; +use yew::prelude::*; + + +#[function_component(App)] +pub fn plot_component() -> Html { + + let x = use_state(|| None::); + let y = use_state(|| None::); + let point_numbers = use_state(|| None::>); + let point_number = use_state(|| None::); + let curve_number = use_state(|| 0usize); + let click_event = use_state(|| ClickEvent::default()); + + let x_clone = x.clone(); + let y_clone = y.clone(); + let curve_clone = curve_number.clone(); + let point_numbers_clone = point_numbers.clone(); + let point_number_clone = point_number.clone(); + let click_event_clone = click_event.clone(); + + let p = yew_hooks::use_async::<_, _, ()>({ + let id = "plot-div"; + let mut fig = Plot::new(); + let xs: Vec = (0..50).map(|i| i as f64).collect(); + let ys: Vec = xs.iter().map(|x| x.sin()).collect(); + fig.add_trace( + Scatter::new(xs.clone(), ys.clone()) + .mode(Mode::Markers) + .name("Sine markers") + ); + let random_values: Vec = (0..100) + .map(|_| Math::random()) + .collect(); + fig.add_trace( + Histogram::new(random_values) + .name("Random histogram") + ); + let layout = plotly::Layout::new().title("Click Event Callback Example in Yew"); + fig.set_layout(layout); + async move { + plotly::bindings::new_plot(id, &fig).await; + plotly::callbacks::bind_click(id, move |event| { + let pt = &event.points[0]; + x_clone.set(pt.x); + y_clone.set(pt.y); + curve_clone.set(pt.curve_number); + point_numbers_clone.set(pt.point_numbers.clone()); + point_number_clone.set(pt.point_number); + click_event_clone.set(event); + }); + Ok(()) + } + }); + // Only on first render + use_effect_with((), move |_| { + p.run(); + }); + + html! { + <> +
+
+

{format!("x: {:?}",*x)}

+

{format!("y: {:?}",*y)}

+

{format!("curveNumber: {:?}",*curve_number)}

+

{format!("pointNumber: {:?}",*point_number)}

+

{format!("pointNumbers: {:?}",*point_numbers)}

+

{format!("ClickEvent: {:?}",*click_event)}

+
+ + } +} + +fn main() { + wasm_logger::init(wasm_logger::Config::default()); + yew::Renderer::::new().render(); +} \ No newline at end of file diff --git a/plotly/Cargo.toml b/plotly/Cargo.toml index e7bbcee3..2340ef10 100644 --- a/plotly/Cargo.toml +++ b/plotly/Cargo.toml @@ -40,6 +40,8 @@ rand = "0.9" getrandom = { version = "0.3", features = ["wasm_js"] } wasm-bindgen-futures = { version = "0.4" } wasm-bindgen = { version = "0.2" } +serde-wasm-bindgen = {version = "0.6.3"} +web-sys = { version = "0.3.77", features = ["Document", "Window", "HtmlElement"]} [dev-dependencies] csv = "1.1" diff --git a/plotly/src/bindings.rs b/plotly/src/bindings.rs index 9b86c05d..40dcf94f 100644 --- a/plotly/src/bindings.rs +++ b/plotly/src/bindings.rs @@ -25,6 +25,7 @@ extern "C" { pub async fn new_plot(id: &str, plot: &Plot) { let plot_obj = &plot.to_js_object(); + // This will only fail if the Rust Plotly library has produced // plotly-incompatible JSON. An error here should have been handled by the // library, rather than down here. diff --git a/plotly/src/callbacks.rs b/plotly/src/callbacks.rs new file mode 100644 index 00000000..5e3b7fcf --- /dev/null +++ b/plotly/src/callbacks.rs @@ -0,0 +1,142 @@ +use serde::{Deserialize, Serialize}; +use wasm_bindgen::prelude::*; +use web_sys::{js_sys::Function, window, HtmlElement}; + +/// Provides utilities for binding Plotly.js click events to Rust closures +/// via `wasm-bindgen`. +/// +/// This module defines a `PlotlyDiv` foreign type for the Plotly `
` element, +/// a high-level `bind_click` function to wire up Rust callbacks, and +/// the `ClickPoint`/`ClickEvent` data structures to deserialize event payloads. + +#[wasm_bindgen] +extern "C" { + + /// A wrapper around the JavaScript `HTMLElement` representing a Plotly `
`. + /// + /// This type extends `web_sys::HtmlElement` and exposes Plotly’s + /// `.on(eventName, callback)` method for attaching event listeners. + + #[wasm_bindgen(extends= HtmlElement, js_name=HTMLElement)] + type PlotlyDiv; + + /// Attach a JavaScript event listener to this Plotly `
`. + /// + /// # Parameters + /// - `event`: The Plotly event name (e.g., `"plotly_click"`). + /// - `cb`: A JS `Function` to invoke when the event fires. + /// + /// # Panics + /// This method assumes the underlying element is indeed a Plotly div + /// and that the Plotly.js library has been loaded on the page. + + #[wasm_bindgen(method,structural,js_name=on)] + fn on(this: &PlotlyDiv, event: &str, cb: &Function); +} + +/// Bind a Rust callback to the Plotly `plotly_click` event on a given `
`. +/// +/// # Type Parameters +/// - `F`: A `'static + FnMut(ClickEvent)` closure type to handle the click data. +/// +/// # Parameters +/// - `div_id`: The DOM `id` attribute of the Plotly `
`. +/// - `cb`: A mutable Rust closure that will be called with a `ClickEvent`. +/// +/// # Details +/// 1. Looks up the element by `div_id`, converts it to `PlotlyDiv`. +/// 2. Wraps a `Closure` that deserializes the JS event +/// into our `ClickEvent` type via `serde_wasm_bindgen`. +/// 3. Calls `plot_div.on("plotly_click", …)` to register the listener. +/// 4. Forgets the closure so it lives for the lifetime of the page. +/// +/// # Example +/// ```ignore +/// bind_click("my-plot", |evt| { +/// web_sys::console::log_1(&format!("{:?}", evt).into()); +/// }); +/// ``` + + +pub fn bind_click(div_id: &str, mut cb: F) +where + F: 'static + FnMut(ClickEvent) +{ + + let plot_div: PlotlyDiv = window().unwrap() + .document().unwrap() + .get_element_by_id(div_id).unwrap() + .unchecked_into(); + let closure = Closure::wrap(Box::new(move |event: JsValue| { + let event: ClickEvent = serde_wasm_bindgen::from_value(event) + .expect("\n Couldn't serialize the event \n"); + cb(event); + }) as Box); + plot_div.on("plotly_click", &closure.as_ref().unchecked_ref()); + closure.forget(); +} + + +/// Represents a single point from a Plotly click event. +/// +/// Fields mirror Plotly’s `event.points[i]` properties, all optional +/// where appropriate: +/// +/// - `curve_number`: The zero-based index of the trace that was clicked. +/// - `point_numbers`: An optional list of indices if multiple points were selected. +/// - `point_number`: The index of the specific point clicked (if singular). +/// - `x`, `y`, `z`: Optional numeric coordinates in data space. +/// - `lat`, `lon`: Optional geographic coordinates (for map plots). +/// +/// # Serialization +/// Uses `serde` with `camelCase` field names to match Plotly’s JS API. + + +#[derive(Debug,Deserialize,Serialize,Default)] +#[serde(rename_all = "camelCase")] +pub struct ClickPoint { + pub curve_number: usize, + pub point_numbers: Option>, + pub point_number: Option, + pub x: Option, + pub y: Option, + pub z: Option, + pub lat: Option, + pub lon: Option +} + + +/// Provide a default single-point vector for `ClickEvent::points`. +/// +/// Returns `vec![ClickPoint::default()]` so deserialization always yields +/// at least one element rather than an empty vector. + +fn default_click_event() -> Vec {vec![ClickPoint::default()]} + + +/// The top-level payload for a Plotly click event. +/// +/// - `points`: A `Vec` containing all clicked points. +/// Defaults to the result of `default_click_event` to ensure +/// `points` is non-empty even if Plotly sends no data. +/// +/// # Serialization +/// Uses `serde` with `camelCase` names and a custom default so you can +/// call `event.points` without worrying about missing values. + +#[derive(Debug,Deserialize,Serialize)] +#[serde(rename_all="camelCase",default)] +pub struct ClickEvent { + #[serde(default="default_click_event")] + pub points: Vec +} + +/// A `Default` implementation yielding an empty `points` vector. +/// +/// Useful when you need a zero-event placeholder (e.g., initial state). + +impl Default for ClickEvent { + fn default() -> Self { + ClickEvent { points: vec![] } + } +} \ No newline at end of file diff --git a/plotly/src/lib.rs b/plotly/src/lib.rs index ee082087..e22a1482 100644 --- a/plotly/src/lib.rs +++ b/plotly/src/lib.rs @@ -19,6 +19,9 @@ pub use crate::ndarray::ArrayTraces; #[cfg(target_family = "wasm")] pub mod bindings; +#[cfg(target_family = "wasm")] +pub mod callbacks; + pub mod common; pub mod configuration; pub mod layout; From 418d0a39a5c6e4222c6834f53a1faaba56f5fc73 Mon Sep 17 00:00:00 2001 From: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> Date: Mon, 12 May 2025 20:19:12 +0200 Subject: [PATCH 21/83] fix fmt & clippy Signed-off-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> --- .../wasm-yew-callback-minimal/src/main.rs | 19 ++--- plotly/src/bindings.rs | 1 - plotly/src/callbacks.rs | 76 +++++++++---------- 3 files changed, 42 insertions(+), 54 deletions(-) diff --git a/examples/wasm-yew-callback-minimal/src/main.rs b/examples/wasm-yew-callback-minimal/src/main.rs index f1a82ab2..e0d6055c 100644 --- a/examples/wasm-yew-callback-minimal/src/main.rs +++ b/examples/wasm-yew-callback-minimal/src/main.rs @@ -1,12 +1,10 @@ -use plotly::{Plot,common::Mode, Scatter,Histogram}; -use plotly::callbacks::{ClickEvent}; +use plotly::callbacks::ClickEvent; +use plotly::{Histogram, Plot, Scatter, common::Mode}; use web_sys::js_sys::Math; use yew::prelude::*; - #[function_component(App)] pub fn plot_component() -> Html { - let x = use_state(|| None::); let y = use_state(|| None::); let point_numbers = use_state(|| None::>); @@ -29,15 +27,10 @@ pub fn plot_component() -> Html { fig.add_trace( Scatter::new(xs.clone(), ys.clone()) .mode(Mode::Markers) - .name("Sine markers") - ); - let random_values: Vec = (0..100) - .map(|_| Math::random()) - .collect(); - fig.add_trace( - Histogram::new(random_values) - .name("Random histogram") + .name("Sine markers"), ); + let random_values: Vec = (0..100).map(|_| Math::random()).collect(); + fig.add_trace(Histogram::new(random_values).name("Random histogram")); let layout = plotly::Layout::new().title("Click Event Callback Example in Yew"); fig.set_layout(layout); async move { @@ -77,4 +70,4 @@ pub fn plot_component() -> Html { fn main() { wasm_logger::init(wasm_logger::Config::default()); yew::Renderer::::new().render(); -} \ No newline at end of file +} diff --git a/plotly/src/bindings.rs b/plotly/src/bindings.rs index 40dcf94f..9b86c05d 100644 --- a/plotly/src/bindings.rs +++ b/plotly/src/bindings.rs @@ -25,7 +25,6 @@ extern "C" { pub async fn new_plot(id: &str, plot: &Plot) { let plot_obj = &plot.to_js_object(); - // This will only fail if the Rust Plotly library has produced // plotly-incompatible JSON. An error here should have been handled by the // library, rather than down here. diff --git a/plotly/src/callbacks.rs b/plotly/src/callbacks.rs index 5e3b7fcf..6f6257bd 100644 --- a/plotly/src/callbacks.rs +++ b/plotly/src/callbacks.rs @@ -5,14 +5,14 @@ use web_sys::{js_sys::Function, window, HtmlElement}; /// Provides utilities for binding Plotly.js click events to Rust closures /// via `wasm-bindgen`. /// -/// This module defines a `PlotlyDiv` foreign type for the Plotly `
` element, -/// a high-level `bind_click` function to wire up Rust callbacks, and +/// This module defines a `PlotlyDiv` foreign type for the Plotly `
` +/// element, a high-level `bind_click` function to wire up Rust callbacks, and /// the `ClickPoint`/`ClickEvent` data structures to deserialize event payloads. - #[wasm_bindgen] extern "C" { - /// A wrapper around the JavaScript `HTMLElement` representing a Plotly `
`. + /// A wrapper around the JavaScript `HTMLElement` representing a Plotly + /// `
`. /// /// This type extends `web_sys::HtmlElement` and exposes Plotly’s /// `.on(eventName, callback)` method for attaching event listeners. @@ -37,17 +37,18 @@ extern "C" { /// Bind a Rust callback to the Plotly `plotly_click` event on a given `
`. /// /// # Type Parameters -/// - `F`: A `'static + FnMut(ClickEvent)` closure type to handle the click data. +/// - `F`: A `'static + FnMut(ClickEvent)` closure type to handle the click +/// data. /// /// # Parameters /// - `div_id`: The DOM `id` attribute of the Plotly `
`. /// - `cb`: A mutable Rust closure that will be called with a `ClickEvent`. /// /// # Details -/// 1. Looks up the element by `div_id`, converts it to `PlotlyDiv`. -/// 2. Wraps a `Closure` that deserializes the JS event -/// into our `ClickEvent` type via `serde_wasm_bindgen`. -/// 3. Calls `plot_div.on("plotly_click", …)` to register the listener. +/// 1. Looks up the element by `div_id`, converts it to `PlotlyDiv`. +/// 2. Wraps a `Closure` that deserializes the JS event into +/// our `ClickEvent` type via `serde_wasm_bindgen`. +/// 3. Calls `plot_div.on("plotly_click", …)` to register the listener. /// 4. Forgets the closure so it lives for the lifetime of the page. /// /// # Example @@ -56,43 +57,41 @@ extern "C" { /// web_sys::console::log_1(&format!("{:?}", evt).into()); /// }); /// ``` - - pub fn bind_click(div_id: &str, mut cb: F) -where - F: 'static + FnMut(ClickEvent) +where + F: 'static + FnMut(ClickEvent), { - - let plot_div: PlotlyDiv = window().unwrap() - .document().unwrap() - .get_element_by_id(div_id).unwrap() + let plot_div: PlotlyDiv = window() + .unwrap() + .document() + .unwrap() + .get_element_by_id(div_id) + .unwrap() .unchecked_into(); let closure = Closure::wrap(Box::new(move |event: JsValue| { - let event: ClickEvent = serde_wasm_bindgen::from_value(event) - .expect("\n Couldn't serialize the event \n"); + let event: ClickEvent = + serde_wasm_bindgen::from_value(event).expect("\n Couldn't serialize the event \n"); cb(event); }) as Box); - plot_div.on("plotly_click", &closure.as_ref().unchecked_ref()); + plot_div.on("plotly_click", closure.as_ref().unchecked_ref()); closure.forget(); } - /// Represents a single point from a Plotly click event. /// /// Fields mirror Plotly’s `event.points[i]` properties, all optional /// where appropriate: /// /// - `curve_number`: The zero-based index of the trace that was clicked. -/// - `point_numbers`: An optional list of indices if multiple points were selected. +/// - `point_numbers`: An optional list of indices if multiple points were +/// selected. /// - `point_number`: The index of the specific point clicked (if singular). /// - `x`, `y`, `z`: Optional numeric coordinates in data space. /// - `lat`, `lon`: Optional geographic coordinates (for map plots). /// /// # Serialization /// Uses `serde` with `camelCase` field names to match Plotly’s JS API. - - -#[derive(Debug,Deserialize,Serialize,Default)] +#[derive(Debug, Deserialize, Serialize, Default)] #[serde(rename_all = "camelCase")] pub struct ClickPoint { pub curve_number: usize, @@ -102,41 +101,38 @@ pub struct ClickPoint { pub y: Option, pub z: Option, pub lat: Option, - pub lon: Option + pub lon: Option, } - /// Provide a default single-point vector for `ClickEvent::points`. /// /// Returns `vec![ClickPoint::default()]` so deserialization always yields /// at least one element rather than an empty vector. - -fn default_click_event() -> Vec {vec![ClickPoint::default()]} - +fn default_click_event() -> Vec { + vec![ClickPoint::default()] +} /// The top-level payload for a Plotly click event. /// -/// - `points`: A `Vec` containing all clicked points. -/// Defaults to the result of `default_click_event` to ensure -/// `points` is non-empty even if Plotly sends no data. +/// - `points`: A `Vec` containing all clicked points. Defaults to +/// the result of `default_click_event` to ensure `points` is non-empty even +/// if Plotly sends no data. /// /// # Serialization /// Uses `serde` with `camelCase` names and a custom default so you can /// call `event.points` without worrying about missing values. - -#[derive(Debug,Deserialize,Serialize)] -#[serde(rename_all="camelCase",default)] +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase", default)] pub struct ClickEvent { - #[serde(default="default_click_event")] - pub points: Vec + #[serde(default = "default_click_event")] + pub points: Vec, } /// A `Default` implementation yielding an empty `points` vector. /// /// Useful when you need a zero-event placeholder (e.g., initial state). - impl Default for ClickEvent { fn default() -> Self { ClickEvent { points: vec![] } } -} \ No newline at end of file +} From 3b66a48b86c7e750ddee8dd9db5b27dc863a8774 Mon Sep 17 00:00:00 2001 From: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> Date: Mon, 12 May 2025 22:28:22 +0200 Subject: [PATCH 22/83] remove unwrap calls - update changelog Signed-off-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- CHANGELOG.md | 1 + .../wasm-yew-callback-minimal/src/main.rs | 14 ++++++++----- plotly/src/callbacks.rs | 21 +++++++++++-------- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 73799af2..98a0a09d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -115,7 +115,7 @@ jobs: strategy: fail-fast: false matrix: - example: [wasm-yew-minimal] + example: [wasm-yew-minimal, wasm-yew-callback-minimal] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 724188f9..e0d69dec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ### Changed - [[#277](https://github.com/plotly/plotly.rs/pull/277)] Removed `wasm` feature flag and put evrything behind target specific dependencies. Added `.cargo/config.toml` for configuration flags needed by `getrandom` version 0.3 on `wasm` targets. - [[#281]((https://github.com/plotly/plotly.rs/pull/xxx))] Update to askama 0.13.0 +- [[#287]](https://github.com/plotly/plotly.rs/pull/287) Added functionality for callbacks (using wasm) - [[#289]](https://github.com/plotly/plotly.rs/pull/289) Fixes Kaleido static export for MacOS targets by removing `--disable-gpu` flag for MacOS - [[#290]](https://github.com/plotly/plotly.rs/pull/289) Remove `--disable-gpu` flag for Kaleido static-image generation for all targets. diff --git a/examples/wasm-yew-callback-minimal/src/main.rs b/examples/wasm-yew-callback-minimal/src/main.rs index e0d6055c..d2e4a04b 100644 --- a/examples/wasm-yew-callback-minimal/src/main.rs +++ b/examples/wasm-yew-callback-minimal/src/main.rs @@ -1,5 +1,5 @@ use plotly::callbacks::ClickEvent; -use plotly::{Histogram, Plot, Scatter, common::Mode}; +use plotly::{Histogram, Plot, Scatter, common::Mode, histogram::Bins}; use web_sys::js_sys::Math; use yew::prelude::*; @@ -23,14 +23,18 @@ pub fn plot_component() -> Html { let id = "plot-div"; let mut fig = Plot::new(); let xs: Vec = (0..50).map(|i| i as f64).collect(); - let ys: Vec = xs.iter().map(|x| x.sin()).collect(); + let ys: Vec = xs.iter().map(|x| x.sin() * 5.0).collect(); fig.add_trace( Scatter::new(xs.clone(), ys.clone()) .mode(Mode::Markers) - .name("Sine markers"), + .name("Sine Wave Markers"), + ); + let random_values: Vec = (0..500).map(|_| Math::random() * 100.0).collect(); + fig.add_trace( + Histogram::new(random_values) + .name("Random Data Histogram") + .x_bins(Bins::new(-1.0, 30.0, 5.0)), ); - let random_values: Vec = (0..100).map(|_| Math::random()).collect(); - fig.add_trace(Histogram::new(random_values).name("Random histogram")); let layout = plotly::Layout::new().title("Click Event Callback Example in Yew"); fig.set_layout(layout); async move { diff --git a/plotly/src/callbacks.rs b/plotly/src/callbacks.rs index 6f6257bd..4a47b562 100644 --- a/plotly/src/callbacks.rs +++ b/plotly/src/callbacks.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; -use web_sys::{js_sys::Function, window, HtmlElement}; +use web_sys::{js_sys::Function, HtmlElement}; /// Provides utilities for binding Plotly.js click events to Rust closures /// via `wasm-bindgen`. @@ -61,22 +61,25 @@ pub fn bind_click(div_id: &str, mut cb: F) where F: 'static + FnMut(ClickEvent), { - let plot_div: PlotlyDiv = window() - .unwrap() - .document() - .unwrap() - .get_element_by_id(div_id) - .unwrap() - .unchecked_into(); let closure = Closure::wrap(Box::new(move |event: JsValue| { let event: ClickEvent = - serde_wasm_bindgen::from_value(event).expect("\n Couldn't serialize the event \n"); + serde_wasm_bindgen::from_value(event).expect("Could not serialize the event"); cb(event); }) as Box); + + let plot_div: PlotlyDiv = get_div(div_id).expect("Could not get Div element by Id"); plot_div.on("plotly_click", closure.as_ref().unchecked_ref()); closure.forget(); } +fn get_div(tag: &str) -> Option { + web_sys::window()? + .document()? + .get_element_by_id(tag)? + .dyn_into() + .ok() +} + /// Represents a single point from a Plotly click event. /// /// Fields mirror Plotly’s `event.points[i]` properties, all optional From e7defa069f462ca871829aab8f46d7409f0e1fb0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 03:12:50 +0000 Subject: [PATCH 23/83] Update zip requirement from 2.1 to 3.0 Updates the requirements on [zip](https://github.com/zip-rs/zip2) to permit the latest version. - [Release notes](https://github.com/zip-rs/zip2/releases) - [Changelog](https://github.com/zip-rs/zip2/blob/master/CHANGELOG.md) - [Commits](https://github.com/zip-rs/zip2/compare/v2.1.0...v3.0.0) --- updated-dependencies: - dependency-name: zip dependency-version: 3.0.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- plotly_kaleido/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plotly_kaleido/Cargo.toml b/plotly_kaleido/Cargo.toml index 0761cc8e..62096a36 100644 --- a/plotly_kaleido/Cargo.toml +++ b/plotly_kaleido/Cargo.toml @@ -30,5 +30,5 @@ base64 = "0.22" plotly_kaleido = { path = ".", features = ["download"] } [build-dependencies] -zip = "2.1" +zip = "3.0" directories = ">=4, <7" From 819fd2f02559eefeb09925eb16158c1067150f24 Mon Sep 17 00:00:00 2001 From: Zhenyi Zhou Date: Tue, 27 May 2025 00:09:10 +0800 Subject: [PATCH 24/83] support layout axis scaleratio --- plotly/src/layout/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plotly/src/layout/mod.rs b/plotly/src/layout/mod.rs index 57d33184..b3af4e60 100644 --- a/plotly/src/layout/mod.rs +++ b/plotly/src/layout/mod.rs @@ -505,6 +505,8 @@ pub struct Axis { #[serde(rename = "scaleanchor")] scale_anchor: Option, + #[serde(rename = "scaleratio")] + scale_ratio: Option, tick0: Option, dtick: Option, From d6326fc6df8f7b1567666384258d34d444b76569 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 May 2025 03:41:57 +0000 Subject: [PATCH 25/83] Update zip requirement from 3.0 to 4.0 Updates the requirements on [zip](https://github.com/zip-rs/zip2) to permit the latest version. - [Release notes](https://github.com/zip-rs/zip2/releases) - [Changelog](https://github.com/zip-rs/zip2/blob/master/CHANGELOG.md) - [Commits](https://github.com/zip-rs/zip2/compare/v3.0.0...v4.0.0) --- updated-dependencies: - dependency-name: zip dependency-version: 4.0.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- plotly_kaleido/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plotly_kaleido/Cargo.toml b/plotly_kaleido/Cargo.toml index 62096a36..7cd015fb 100644 --- a/plotly_kaleido/Cargo.toml +++ b/plotly_kaleido/Cargo.toml @@ -30,5 +30,5 @@ base64 = "0.22" plotly_kaleido = { path = ".", features = ["download"] } [build-dependencies] -zip = "3.0" +zip = "4.0" directories = ">=4, <7" From 3326cb63daa7399da31247d9ceff78c1b1bcfb74 Mon Sep 17 00:00:00 2001 From: Zhenyi Zhou Date: Tue, 27 May 2025 01:38:49 +0800 Subject: [PATCH 26/83] add customdata to HeatMap --- plotly/src/traces/heat_map.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plotly/src/traces/heat_map.rs b/plotly/src/traces/heat_map.rs index 0fdf85c8..cd108283 100644 --- a/plotly/src/traces/heat_map.rs +++ b/plotly/src/traces/heat_map.rs @@ -7,6 +7,7 @@ use crate::{ common::{ Calendar, ColorBar, ColorScale, Dim, HoverInfo, Label, LegendGroupTitle, PlotType, Visible, }, + private::NumOrStringCollection, Trace, }; @@ -71,6 +72,11 @@ where color_scale: Option, #[serde(rename = "connectgaps")] connect_gaps: Option, + /// Assigns extra data each datum. This may be useful when listening to + /// hover, click and selection events. Note that, "scatter" traces also + /// appends customdata items in the markers DOM elements + #[serde(rename = "customdata")] + custom_data: Option, #[serde(rename = "hoverinfo")] hover_info: Option, #[serde(rename = "hoverlabel")] From 9cf057d267224298ecde1f4954cd1e8d8be4c25f Mon Sep 17 00:00:00 2001 From: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> Date: Tue, 3 Jun 2025 20:49:15 +0100 Subject: [PATCH 27/83] refactor examples to generate standalone html Signed-off-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> --- .github/workflows/ci.yml | 6 +- examples/.gitignore | 2 +- examples/3d_charts/README.md | 6 - examples/3d_charts/src/main.rs | 61 +++-- examples/Cargo.toml | 2 +- examples/README.md | 10 +- examples/basic_charts/README.md | 6 - examples/basic_charts/src/main.rs | 237 +++++++++--------- examples/custom_controls/README.md | 6 - examples/custom_controls/src/main.rs | 37 ++- examples/customization/README.md | 2 +- examples/customization/src/main.rs | 59 ++--- examples/financial_charts/README.md | 6 - examples/financial_charts/src/main.rs | 72 +++--- examples/images/README.md | 6 - examples/images/src/main.rs | 55 ++-- examples/kaleido/README.md | 6 - examples/kaleido/src/main.rs | 18 +- examples/maps/README.md | 6 - examples/maps/src/main.rs | 28 ++- examples/ndarray/README.md | 6 - examples/ndarray/src/main.rs | 37 ++- examples/scientific_charts/README.md | 6 - examples/scientific_charts/src/main.rs | 66 +++-- examples/shapes/README.md | 6 - examples/shapes/src/main.rs | 140 +++++------ examples/statistical_charts/README.md | 6 - examples/statistical_charts/src/main.rs | 231 ++++++++--------- examples/subplots/README.md | 6 - examples/subplots/src/main.rs | 118 ++++----- examples/wasm-yew-callback-minimal/README.md | 9 - examples/wasm-yew-minimal/README.md | 9 - examples/wasm-yew/Cargo.toml | 4 + examples/wasm-yew/README.md | 10 + .../basic}/Cargo.toml | 2 +- .../basic}/index.html | 0 .../basic}/src/main.rs | 0 .../callback-example}/Cargo.toml | 2 +- .../callback-example}/index.html | 0 .../callback-example}/src/main.rs | 2 +- plotly/src/plot.rs | 25 +- 41 files changed, 633 insertions(+), 683 deletions(-) delete mode 100644 examples/3d_charts/README.md delete mode 100644 examples/basic_charts/README.md delete mode 100644 examples/custom_controls/README.md delete mode 100644 examples/financial_charts/README.md delete mode 100644 examples/images/README.md delete mode 100644 examples/kaleido/README.md delete mode 100644 examples/maps/README.md delete mode 100644 examples/ndarray/README.md delete mode 100644 examples/scientific_charts/README.md delete mode 100644 examples/shapes/README.md delete mode 100644 examples/statistical_charts/README.md delete mode 100644 examples/subplots/README.md delete mode 100644 examples/wasm-yew-callback-minimal/README.md delete mode 100644 examples/wasm-yew-minimal/README.md create mode 100644 examples/wasm-yew/Cargo.toml create mode 100644 examples/wasm-yew/README.md rename examples/{wasm-yew-minimal => wasm-yew/basic}/Cargo.toml (87%) rename examples/{wasm-yew-callback-minimal => wasm-yew/basic}/index.html (100%) rename examples/{wasm-yew-minimal => wasm-yew/basic}/src/main.rs (100%) rename examples/{wasm-yew-callback-minimal => wasm-yew/callback-example}/Cargo.toml (83%) rename examples/{wasm-yew-minimal => wasm-yew/callback-example}/index.html (100%) rename examples/{wasm-yew-callback-minimal => wasm-yew/callback-example}/src/main.rs (97%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 98a0a09d..14840772 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,7 +44,7 @@ jobs: # lint the plotly library for wasm target - run: cargo clippy --package plotly --target wasm32-unknown-unknown -- -D warnings # lint the wasm examples - - run: cd ${{ github.workspace }}/examples && cargo clippy --target wasm32-unknown-unknown --package "wasm*" + - run: cd ${{ github.workspace }}/examples/wasm-yew && cargo clippy --target wasm32-unknown-unknown --all semver: name: semver @@ -115,11 +115,11 @@ jobs: strategy: fail-fast: false matrix: - example: [wasm-yew-minimal, wasm-yew-callback-minimal] + example: [basic, callback-example] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: targets: wasm32-unknown-unknown - - run: cd ${{ github.workspace }}/examples/${{ matrix.example }} && cargo build --target wasm32-unknown-unknown + - run: cd ${{ github.workspace }}/examples/wasm-yew/${{ matrix.example }} && cargo build --target wasm32-unknown-unknown diff --git a/examples/.gitignore b/examples/.gitignore index 466e2480..9b1960e7 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -1 +1 @@ -out/ \ No newline at end of file +output/ \ No newline at end of file diff --git a/examples/3d_charts/README.md b/examples/3d_charts/README.md deleted file mode 100644 index f5babb6d..00000000 --- a/examples/3d_charts/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# 3D Charts - -## How to Run - -1. Configure which example(s) you want to run by commenting/uncommenting lines in the `main` function, located in `src/main.rs`. -2. Run `cargo run`. \ No newline at end of file diff --git a/examples/3d_charts/src/main.rs b/examples/3d_charts/src/main.rs index 23da9718..3ecd6926 100644 --- a/examples/3d_charts/src/main.rs +++ b/examples/3d_charts/src/main.rs @@ -11,7 +11,7 @@ use rand::Rng; // 3D Scatter Plots // ANCHOR: simple_scatter3d_plot -fn simple_scatter3d_plot(show: bool) -> Plot { +fn simple_scatter3d_plot(show: bool, file_name: &str) { let n: usize = 100; let t: Vec = Array::linspace(0., 10., n).into_raw_vec_and_offset().0; let y: Vec = t.iter().map(|x| x.sin()).collect(); @@ -21,15 +21,15 @@ fn simple_scatter3d_plot(show: bool) -> Plot { let mut plot = Plot::new(); plot.add_trace(trace); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: simple_scatter3d_plot // ANCHOR: customized_scatter3d_plot -fn customized_scatter3d_plot(show: bool) -> Plot { +fn customized_scatter3d_plot(show: bool, file_name: &str) { let n: usize = 100; let t: Vec = Array::linspace(0., 10., n).into_raw_vec_and_offset().0; let y: Vec = t.iter().map(|x| x.sin()).collect(); @@ -114,16 +114,16 @@ fn customized_scatter3d_plot(show: bool) -> Plot { .height(500); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: customized_scatter3d_plot // 3D Line Plots // ANCHOR: simple_line3d_plot -fn simple_line3d_plot(show: bool) -> Plot { +fn simple_line3d_plot(show: bool, file_name: &str) { let n: usize = 100; let t: Vec = Array::linspace(0., 10., n).into_raw_vec_and_offset().0; let y: Vec = t.iter().map(|x| x.sin()).collect(); @@ -133,16 +133,16 @@ fn simple_line3d_plot(show: bool) -> Plot { let mut plot = Plot::new(); plot.add_trace(trace); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: simple_line3d_plot // 3D Surface Plot // ANCHOR: surface_plot -fn surface_plot(show: bool) -> Plot { +fn surface_plot(show: bool, file_name: &str) { let n: usize = 100; let x: Vec = Array::linspace(-10., 10., n).into_raw_vec_and_offset().0; let y: Vec = Array::linspace(-10., 10., n).into_raw_vec_and_offset().0; @@ -159,15 +159,15 @@ fn surface_plot(show: bool) -> Plot { let mut plot = Plot::new(); plot.add_trace(trace); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: surface_plot // ANCHOR: mesh_3d_plot -fn mesh_3d_plot(show: bool) -> Plot { +fn mesh_3d_plot(show: bool, file_name: &str) { let trace = Mesh3D::new( vec![0, 1, 2, 0], vec![0, 0, 1, 2], @@ -182,15 +182,15 @@ fn mesh_3d_plot(show: bool) -> Plot { let mut plot = Plot::new(); plot.add_trace(trace); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: mesh_3d_plot // ANCHOR: colorscale_plot -fn colorscale_plot(show: bool) -> Plot { +fn colorscale_plot(show: bool, file_name: &str) { let mut plot = Plot::new(); let x = (0..100) @@ -252,34 +252,31 @@ fn colorscale_plot(show: bool) -> Plot { plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: colorscale_plot -fn write_example_to_html(plot: Plot, name: &str) { - std::fs::create_dir_all("./out").unwrap(); - let html = plot.to_inline_html(Some(name)); - std::fs::write(format!("./out/{}.html", name), html).unwrap(); +fn write_example_to_html(plot: &Plot, name: &str) -> String { + std::fs::create_dir_all("./output").unwrap(); + let path = format!("./output/{}.html", name); + plot.write_html(&path); + path } fn main() { // Change false to true on any of these lines to display the example. - // Scatter3D Plots - write_example_to_html(simple_scatter3d_plot(false), "simple_scatter3d_plot"); - write_example_to_html(simple_line3d_plot(false), "simple_line3d_plot"); - write_example_to_html( - customized_scatter3d_plot(false), - "customized_scatter3d_plot", - ); - write_example_to_html(colorscale_plot(false), "colorscale_plot"); + simple_scatter3d_plot(false, "simple_scatter3d_plot"); + simple_line3d_plot(false, "simple_line3d_plot"); + customized_scatter3d_plot(false, "customized_scatter3d_plot"); + colorscale_plot(false, "colorscale_plot"); // Surface Plots - write_example_to_html(surface_plot(false), "surface_plot"); + surface_plot(false, "surface_plot"); // Mesh Plots - write_example_to_html(mesh_3d_plot(false), "mesh_3d_plot"); + mesh_3d_plot(false, "mesh_3d_plot"); } diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 1e112635..b65c8592 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -1,4 +1,4 @@ [workspace] members = ["*"] resolver = "2" -exclude = ["jupyter", "target"] +exclude = ["jupyter", "target", "wasm-yew"] diff --git a/examples/README.md b/examples/README.md index 74170ed3..606f4e66 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,5 +1,11 @@ # Examples -This folder contains a multitude of usage examples covering as many features of the library as possible. Instructions on how to run each example can be found in each example's subdirectory. +This folder contains a multitude of usage examples covering as many features of the library as possible. -Pull requests with more examples of different behaviour are always welcome. +To run the basic examples, `cd` into the chosen example folder and run `cargo run`. Upon completion all the generated plots of that example are located in the `output` folder as `html` files and can be open in your browser. + +You can also configure the chosen example to open the resulting plots automatically. To do so, open the `src/main.rs` file, locate the `main` function and change the boolean flag of the called functions from `false` to `true`. This will make the examples open the default browser application and load the generated HTML files. + +For more complex examples, instructions on how to run them can be found in the README of each example's subdirectory. + +Pull requests with more examples of different behavior are always welcome. diff --git a/examples/basic_charts/README.md b/examples/basic_charts/README.md deleted file mode 100644 index e9ae3fc6..00000000 --- a/examples/basic_charts/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Basic Charts - -## How to Run - -1. Configure which example(s) you want to run by commenting/uncommenting lines in the `main` function, located in `src/main.rs`. -2. Run `cargo run`. \ No newline at end of file diff --git a/examples/basic_charts/src/main.rs b/examples/basic_charts/src/main.rs index 89c30571..b3b4fefa 100644 --- a/examples/basic_charts/src/main.rs +++ b/examples/basic_charts/src/main.rs @@ -19,7 +19,7 @@ use rand_distr::{Distribution, Normal, Uniform}; // Scatter Plots // ANCHOR: simple_scatter_plot -fn simple_scatter_plot(show: bool) -> Plot { +fn simple_scatter_plot(show: bool, file_name: &str) { let n: usize = 100; let t: Vec = Array::linspace(0., 10., n).into_raw_vec_and_offset().0; let y: Vec = t.iter().map(|x| x.sin()).collect(); @@ -28,15 +28,15 @@ fn simple_scatter_plot(show: bool) -> Plot { let mut plot = Plot::new(); plot.add_trace(trace); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: simple_scatter_plot // ANCHOR: line_and_scatter_plots -fn line_and_scatter_plots(show: bool) -> Plot { +fn line_and_scatter_plots(show: bool, file_name: &str) { let n: usize = 100; let mut rng = rand::rng(); let random_x: Vec = Array::linspace(0., 1., n).into_raw_vec_and_offset().0; @@ -71,15 +71,15 @@ fn line_and_scatter_plots(show: bool) -> Plot { plot.add_trace(trace2); plot.add_trace(trace3); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: line_and_scatter_plots // ANCHOR: bubble_scatter_plots -fn bubble_scatter_plots(show: bool) -> Plot { +fn bubble_scatter_plots(show: bool, file_name: &str) { let trace1 = Scatter::new(vec![1, 2, 3, 4], vec![10, 11, 12, 13]) .mode(Mode::Markers) .marker( @@ -95,14 +95,14 @@ fn bubble_scatter_plots(show: bool) -> Plot { let mut plot = Plot::new(); plot.add_trace(trace1); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: bubble_scatter_plots -fn polar_scatter_plot(show: bool) -> Plot { +fn polar_scatter_plot(show: bool, file_name: &str) { let n: usize = 400; let theta: Vec = Array::linspace(0., 360., n).into_raw_vec_and_offset().0; let r: Vec = theta @@ -118,14 +118,14 @@ fn polar_scatter_plot(show: bool) -> Plot { let mut plot = Plot::new(); plot.add_trace(trace); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR: data_labels_hover -fn data_labels_hover(show: bool) -> Plot { +fn data_labels_hover(show: bool, file_name: &str) { let trace1 = Scatter::new(vec![1, 2, 3, 4, 5], vec![1, 6, 3, 6, 1]) .mode(Mode::Markers) .name("Team A") @@ -145,15 +145,15 @@ fn data_labels_hover(show: bool) -> Plot { .y_axis(Axis::new().title("y").range(vec![0., 8.])); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: data_labels_hover // ANCHOR: data_labels_on_the_plot -fn data_labels_on_the_plot(show: bool) -> Plot { +fn data_labels_on_the_plot(show: bool, file_name: &str) { let trace1 = Scatter::new(vec![1, 2, 3, 4, 5], vec![1, 6, 3, 6, 1]) .mode(Mode::Markers) .name("Team A") @@ -175,15 +175,15 @@ fn data_labels_on_the_plot(show: bool) -> Plot { .y_axis(Axis::new().range(vec![0., 8.])); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: data_labels_on_the_plot // ANCHOR: colored_and_styled_scatter_plot -fn colored_and_styled_scatter_plot(show: bool) -> Plot { +fn colored_and_styled_scatter_plot(show: bool, file_name: &str) { let trace1 = Scatter::new(vec![52698, 43117], vec![53, 31]) .mode(Mode::Markers) .name("North America") @@ -263,15 +263,15 @@ fn colored_and_styled_scatter_plot(show: bool) -> Plot { plot.add_trace(trace4); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: colored_and_styled_scatter_plot // ANCHOR: large_data_sets -fn large_data_sets(show: bool) -> Plot { +fn large_data_sets(show: bool, file_name: &str) { let n: usize = 100_000; let mut rng = rand::rng(); let r: Vec = Uniform::new(0., 1.) @@ -306,16 +306,16 @@ fn large_data_sets(show: bool) -> Plot { let mut plot = Plot::new(); plot.add_trace(trace); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: large_data_sets // Line Charts // ANCHOR: adding_names_to_line_and_scatter_plot -fn adding_names_to_line_and_scatter_plot(show: bool) -> Plot { +fn adding_names_to_line_and_scatter_plot(show: bool, file_name: &str) { let trace1 = Scatter::new(vec![1, 2, 3, 4], vec![10, 15, 13, 17]) .mode(Mode::Markers) .name("Scatter"); @@ -333,15 +333,15 @@ fn adding_names_to_line_and_scatter_plot(show: bool) -> Plot { plot.add_trace(trace3); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: adding_names_to_line_and_scatter_plot // ANCHOR: line_and_scatter_styling -fn line_and_scatter_styling(show: bool) -> Plot { +fn line_and_scatter_styling(show: bool, file_name: &str) { let trace1 = Scatter::new(vec![1, 2, 3, 4], vec![10, 15, 13, 17]) .mode(Mode::Markers) .name("trace1") @@ -363,15 +363,15 @@ fn line_and_scatter_styling(show: bool) -> Plot { plot.add_trace(trace3); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: line_and_scatter_styling // ANCHOR: styling_line_plot -fn styling_line_plot(show: bool) -> Plot { +fn styling_line_plot(show: bool, file_name: &str) { let trace1 = Scatter::new(vec![1, 2, 3, 4], vec![10, 15, 13, 17]) .mode(Mode::Markers) .name("Red") @@ -390,15 +390,15 @@ fn styling_line_plot(show: bool) -> Plot { plot.add_trace(trace2); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: styling_line_plot // ANCHOR: line_shape_options_for_interpolation -fn line_shape_options_for_interpolation(show: bool) -> Plot { +fn line_shape_options_for_interpolation(show: bool, file_name: &str) { let trace1 = Scatter::new(vec![1, 2, 3, 4, 5], vec![1, 3, 2, 3, 1]) .mode(Mode::LinesMarkers) .name("linear") @@ -439,15 +439,15 @@ fn line_shape_options_for_interpolation(show: bool) -> Plot { plot.add_trace(trace5); plot.add_trace(trace6); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: line_shape_options_for_interpolation // ANCHOR: line_dash -fn line_dash(show: bool) -> Plot { +fn line_dash(show: bool, file_name: &str) { let trace1 = Scatter::new(vec![1, 2, 3, 4, 5], vec![1, 3, 2, 3, 1]) .mode(Mode::LinesMarkers) .name("solid") @@ -491,15 +491,15 @@ fn line_dash(show: bool) -> Plot { plot.add_trace(trace5); plot.add_trace(trace6); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: line_dash // ANCHOR: filled_lines -fn filled_lines(show: bool) -> Plot { +fn filled_lines(show: bool, file_name: &str) { let x1 = vec![ 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, @@ -591,16 +591,16 @@ fn filled_lines(show: bool) -> Plot { plot.add_trace(trace5); plot.add_trace(trace6); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: filled_lines /// Scatter plot showing y axis categories and category ordering. // ANCHOR: categories_scatter_chart -fn categories_scatter_chart(show: bool) -> Plot { +fn categories_scatter_chart(show: bool, file_name: &str) { // Categories are ordered on the y axis from bottom to top. let categories = vec!["Unknown", "Off", "On"]; @@ -639,30 +639,30 @@ fn categories_scatter_chart(show: bool) -> Plot { plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: categories_scatter_chart // Bar Charts // ANCHOR: basic_bar_chart -fn basic_bar_chart(show: bool) -> Plot { +fn basic_bar_chart(show: bool, file_name: &str) { let animals = vec!["giraffes", "orangutans", "monkeys"]; let t = Bar::new(animals, vec![20, 14, 23]); let mut plot = Plot::new(); plot.add_trace(t); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: basic_bar_chart // ANCHOR: grouped_bar_chart -fn grouped_bar_chart(show: bool) -> Plot { +fn grouped_bar_chart(show: bool, file_name: &str) { let animals1 = vec!["giraffes", "orangutans", "monkeys"]; let trace1 = Bar::new(animals1, vec![20, 14, 23]).name("SF Zoo"); @@ -676,15 +676,15 @@ fn grouped_bar_chart(show: bool) -> Plot { plot.add_trace(trace2); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: grouped_bar_chart // ANCHOR: stacked_bar_chart -fn stacked_bar_chart(show: bool) -> Plot { +fn stacked_bar_chart(show: bool, file_name: &str) { let animals1 = vec!["giraffes", "orangutans", "monkeys"]; let trace1 = Bar::new(animals1, vec![20, 14, 23]).name("SF Zoo"); @@ -698,17 +698,17 @@ fn stacked_bar_chart(show: bool) -> Plot { plot.add_trace(trace2); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: stacked_bar_chart /// Graph a bar chart that orders the x axis categories by the total number /// of animals in each category. // ANCHOR: category_order_bar_chart -fn category_order_bar_chart(show: bool) -> Plot { +fn category_order_bar_chart(show: bool, file_name: &str) { let animals1 = vec!["giraffes", "orangutans", "monkeys"]; let trace1 = Bar::new(animals1, vec![10, 14, 23]).name("SF Zoo"); @@ -726,15 +726,15 @@ fn category_order_bar_chart(show: bool) -> Plot { plot.add_trace(trace2); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: category_order_bar_chart // ANCHOR: bar_chart_with_pattern_fills -fn bar_chart_with_pattern_fills(show: bool) -> Plot { +fn bar_chart_with_pattern_fills(show: bool, file_name: &str) { let animals1 = vec!["giraffes", "orangutans", "monkeys"]; let trace1 = Bar::new(animals1, vec![20, 14, 23]).name("SF Zoo").marker( Marker::new().line(Line::new().width(1.0)).pattern( @@ -760,16 +760,16 @@ fn bar_chart_with_pattern_fills(show: bool) -> Plot { plot.add_trace(trace2); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: bar_chart_with_pattern_fills // Sankey Diagrams // ANCHOR: basic_sankey_diagram -fn basic_sankey_diagram(show: bool) -> Plot { +fn basic_sankey_diagram(show: bool, file_name: &str) { // https://plotly.com/javascript/sankey-diagram/#basic-sankey-diagram let trace = Sankey::new() .orientation(Orientation::Horizontal) @@ -803,15 +803,15 @@ fn basic_sankey_diagram(show: bool) -> Plot { plot.add_trace(trace); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: basic_sankey_diagram // ANCHOR: table_chart -fn table_chart(show: bool) -> Plot { +fn table_chart(show: bool, file_name: &str) { let trace = Table::new( Header::new(vec![String::from("col1"), String::from("col2")]), Cells::new(vec![vec![1, 2], vec![2, 3]]), @@ -819,45 +819,45 @@ fn table_chart(show: bool) -> Plot { let mut plot = Plot::new(); plot.add_trace(trace); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: table_chart // Pie Charts // ANCHOR: basic_pie_chart -fn basic_pie_chart(show: bool) -> Plot { +fn basic_pie_chart(show: bool, file_name: &str) { let values = vec![2, 3, 4]; let labels = vec!["giraffes", "orangutans", "monkeys"]; let t = Pie::new(values).labels(labels); let mut plot = Plot::new(); plot.add_trace(t); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: basic_pie_chart // ANCHOR: basic_pie_chart_labels -fn basic_pie_chart_labels(show: bool) -> Plot { +fn basic_pie_chart_labels(show: bool, file_name: &str) { let labels = ["giraffes", "giraffes", "orangutans", "monkeys"]; let t = Pie::::from_labels(&labels); let mut plot = Plot::new(); plot.add_trace(t); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: basic_pie_chart_labels // ANCHOR: pie_chart_text_control -fn pie_chart_text_control(show: bool) -> Plot { +fn pie_chart_text_control(show: bool, file_name: &str) { let values = vec![2, 3, 4, 4]; let labels = vec!["Wages", "Operating expenses", "Cost of sales", "Insurance"]; let t = Pie::new(values) @@ -873,15 +873,15 @@ fn pie_chart_text_control(show: bool) -> Plot { let layout = Layout::new().height(700).width(700).show_legend(true); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: pie_chart_text_control // ANCHOR: grouped_donout_pie_charts -fn grouped_donout_pie_charts(show: bool) -> Plot { +fn grouped_donout_pie_charts(show: bool, file_name: &str) { let mut plot = Plot::new(); let values = vec![16, 15, 12, 6, 5, 4, 42]; @@ -951,70 +951,61 @@ fn grouped_donout_pie_charts(show: bool) -> Plot { ); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: grouped_donout_pie_charts -fn write_example_to_html(plot: Plot, name: &str) { - std::fs::create_dir_all("./out").unwrap(); - let html = plot.to_inline_html(Some(name)); - std::fs::write(format!("./out/{}.html", name), html).unwrap(); +fn write_example_to_html(plot: &Plot, name: &str) -> String { + std::fs::create_dir_all("./output").unwrap(); + let path = format!("./output/{}.html", name); + plot.write_html(&path); + path } fn main() { // Change false to true on any of these lines to display the example. // Scatter Plots - write_example_to_html(simple_scatter_plot(false), "simple_scatter_plot"); - write_example_to_html(line_and_scatter_plots(false), "line_and_scatter_plots"); - write_example_to_html(bubble_scatter_plots(false), "bubble_scatter_plots"); - write_example_to_html(polar_scatter_plot(false), "polar_scatter_plot"); - write_example_to_html(data_labels_hover(false), "data_labels_hover"); - write_example_to_html(data_labels_on_the_plot(false), "data_labels_on_the_plot"); - write_example_to_html( - colored_and_styled_scatter_plot(false), - "colored_and_styled_scatter_plot", - ); - write_example_to_html(large_data_sets(false), "large_data_sets"); - write_example_to_html(categories_scatter_chart(false), "categories_scatter_chart"); + simple_scatter_plot(false, "simple_scatter_plot"); + line_and_scatter_plots(false, "line_and_scatter_plots"); + bubble_scatter_plots(false, "bubble_scatter_plots"); + polar_scatter_plot(false, "polar_scatter_plot"); + data_labels_hover(false, "data_labels_hover"); + data_labels_on_the_plot(false, "data_labels_on_the_plot"); + + colored_and_styled_scatter_plot(false, "colored_and_styled_scatter_plot"); + large_data_sets(false, "large_data_sets"); + categories_scatter_chart(false, "categories_scatter_chart"); // Line Charts - write_example_to_html( - adding_names_to_line_and_scatter_plot(false), - "adding_names_to_line_and_scatter_plot", - ); - write_example_to_html(line_and_scatter_styling(false), "line_and_scatter_styling"); - write_example_to_html(styling_line_plot(false), "styling_line_plot"); - write_example_to_html( - line_shape_options_for_interpolation(false), - "line_shape_options_for_interpolation", - ); - write_example_to_html(line_dash(false), "line_dash"); - write_example_to_html(filled_lines(false), "filled_lines"); + + adding_names_to_line_and_scatter_plot(false, "adding_names_to_line_and_scatter_plot"); + line_and_scatter_styling(false, "line_and_scatter_styling"); + styling_line_plot(false, "styling_line_plot"); + + line_shape_options_for_interpolation(false, "line_shape_options_for_interpolation"); + line_dash(false, "line_dash"); + filled_lines(false, "filled_lines"); // Bar Charts - write_example_to_html(basic_bar_chart(false), "basic_bar_chart"); - write_example_to_html(grouped_bar_chart(false), "grouped_bar_chart"); - write_example_to_html(stacked_bar_chart(false), "stacked_bar_chart"); - write_example_to_html(table_chart(false), "table_chart"); - write_example_to_html(category_order_bar_chart(false), "category_order_bar_chart"); - write_example_to_html( - bar_chart_with_pattern_fills(false), - "bar_chart_with_pattern_fills", - ); + basic_bar_chart(false, "basic_bar_chart"); + grouped_bar_chart(false, "grouped_bar_chart"); + stacked_bar_chart(false, "stacked_bar_chart"); + table_chart(false, "table_chart"); + category_order_bar_chart(false, "category_order_bar_chart"); + + bar_chart_with_pattern_fills(false, "bar_chart_with_pattern_fills"); // Sankey Diagrams - write_example_to_html(basic_sankey_diagram(false), "basic_sankey_diagram"); + basic_sankey_diagram(false, "basic_sankey_diagram"); // Pie Charts - write_example_to_html(basic_pie_chart(false), "basic_pie_chart"); - write_example_to_html(basic_pie_chart_labels(false), "basic_pie_chart_labels"); - write_example_to_html(pie_chart_text_control(false), "pie_chart_text_control"); - write_example_to_html( - grouped_donout_pie_charts(false), - "grouped_donout_pie_charts", - ); + basic_pie_chart(false, "basic_pie_chart"); + basic_pie_chart_labels(false, "basic_pie_chart_labels"); + pie_chart_text_control(false, "pie_chart_text_control"); + + grouped_donout_pie_charts(false, "grouped_donout_pie_charts"); } diff --git a/examples/custom_controls/README.md b/examples/custom_controls/README.md deleted file mode 100644 index 19d72b4d..00000000 --- a/examples/custom_controls/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Custom Controls - -## How to Run - -1. Configure which example(s) you want to run by commenting/uncommenting lines in the `main` function, located in `src/main.rs`. -2. Run `cargo run`. diff --git a/examples/custom_controls/src/main.rs b/examples/custom_controls/src/main.rs index 2f285361..b3e21e50 100644 --- a/examples/custom_controls/src/main.rs +++ b/examples/custom_controls/src/main.rs @@ -12,7 +12,7 @@ use plotly::{ /// Display a bar chart with an associated dropdown selector to show different /// data. -fn bar_plot_with_dropdown_for_different_data() { +fn bar_plot_with_dropdown_for_different_data(show: bool, file_name: &str) { type BarType = Bar<&'static str, i32>; let mut plot = Plot::new(); plot.add_trace( @@ -38,12 +38,15 @@ fn bar_plot_with_dropdown_for_different_data() { ]; plot.set_layout(Layout::new().update_menus(vec![UpdateMenu::new().y(0.8).buttons(buttons)])); - plot.show(); + let path = write_example_to_html(&plot, file_name); + if show { + plot.show_html(path); + } } /// Display a heat map, with buttons to allow for toggling of different /// colorscales. -fn heat_map_with_modifiable_colorscale() { +fn heat_map_with_modifiable_colorscale(show: bool, file_name: &str) { type HeatMapType = HeatMap>; let gauss = |v: i32| (-v as f64 * v as f64 / 200.0).exp(); let z = (-30..30) @@ -71,12 +74,15 @@ fn heat_map_with_modifiable_colorscale() { .y(0.8) .buttons(buttons)])); - plot.show(); + let path = write_example_to_html(&plot, file_name); + if show { + plot.show_html(path); + } } /// Display a bar chart, with buttons to toggle between stacked or grouped /// display maodes. -fn bar_chart_with_modifiable_bar_mode() { +fn bar_chart_with_modifiable_bar_mode(show: bool, file_name: &str) { type BarType = Bar<&'static str, i32>; let mut plot = Plot::new(); plot.add_trace( @@ -104,13 +110,24 @@ fn bar_chart_with_modifiable_bar_mode() { .direction(UpdateMenuDirection::Right) .buttons(buttons)])); - plot.show(); + let path = write_example_to_html(&plot, file_name); + if show { + plot.show_html(path); + } +} +// ANCHOR_END: colorscale_plot + +fn write_example_to_html(plot: &Plot, name: &str) -> String { + std::fs::create_dir_all("./output").unwrap(); + let path = format!("./output/{}.html", name); + plot.write_html(&path); + path } fn main() { - // Uncomment any of these lines to display the example. + // Change false to true on any of these lines to display the example. - // bar_plot_with_dropdown_for_different_data(); - // heat_map_with_modifiable_colorscale(); - // bar_chart_with_modifiable_bar_mode(); + bar_plot_with_dropdown_for_different_data(false, "bar_plot"); + heat_map_with_modifiable_colorscale(false, "heat_map"); + bar_chart_with_modifiable_bar_mode(false, "bar_chart"); } diff --git a/examples/customization/README.md b/examples/customization/README.md index dc1cb4ed..7fdf8df2 100644 --- a/examples/customization/README.md +++ b/examples/customization/README.md @@ -5,4 +5,4 @@ We often get issues/questions regarding customization of the HTML output. In mos This example pacakge contains examples of the most frequent raised questions by users of `plotly-rs`, such as - making the resulting HTML plot responsive on browser window size change - making the resulting HTML fill the entire browser page -- placing multiple plots in the same HTML page using the [`build_html`](https://crates.io/crates/build_html) crate +- placing multiple plots in the same HTML page, e.g., by using the [`build_html`](https://crates.io/crates/build_html) crate diff --git a/examples/customization/src/main.rs b/examples/customization/src/main.rs index c32c0090..456df549 100644 --- a/examples/customization/src/main.rs +++ b/examples/customization/src/main.rs @@ -1,5 +1,8 @@ #![allow(dead_code)] +use std::fs::File; +use std::io::Write; + use build_html::*; use ndarray::Array; use plotly::{ @@ -10,7 +13,7 @@ use plotly::{ }; const DEFAULT_HTML_APP_NOT_FOUND: &str = "Could not find default application for HTML files."; -fn density_mapbox_responsive_autofill() { +fn density_mapbox_responsive_autofill(show: bool, file_name: &str) { let trace = DensityMapbox::new(vec![45.5017], vec![-73.5673], vec![0.75]).zauto(true); let layout = Layout::new() @@ -28,10 +31,13 @@ fn density_mapbox_responsive_autofill() { plot.set_layout(layout); plot.set_configuration(Configuration::default().responsive(true).fill_frame(true)); - plot.show(); + let path = write_example_to_html(&plot, file_name); + if show { + plot.show_html(path); + } } -fn multiple_plots_on_same_html_page() { +fn multiple_plots_on_same_html_page(show: bool, file_name: &str) { let html: String = HtmlPage::new() .with_title("Plotly-rs Multiple Plots") .with_script_link("https://cdn.plot.ly/plotly-latest.min.js") @@ -41,8 +47,15 @@ fn multiple_plots_on_same_html_page() { .with_raw(third_plot()) .to_html_string(); - let file = write_html(&html); - show_with_default_app(&file); + std::fs::create_dir_all("./output").unwrap(); + let path = format!("./output/{}.html", file_name); + let mut file = File::create(&path).unwrap(); + file.write_all(html.as_bytes()) + .expect("failed to write html output"); + file.flush().unwrap(); + if show { + show_with_default_app(&path); + } } fn first_plot() -> String { @@ -117,34 +130,16 @@ fn show_with_default_app(temp_path: &str) { .expect(DEFAULT_HTML_APP_NOT_FOUND); } -fn write_html(html_data: &str) -> String { - use std::env; - use std::{fs::File, io::Write}; - - use rand::distr::{Alphanumeric, SampleString}; - - // Set up the temp file with a unique filename. - let mut temp = env::temp_dir(); - let mut plot_name = Alphanumeric.sample_string(&mut rand::rng(), 22); - plot_name.push_str(".html"); - plot_name = format!("plotly_{}", plot_name); - temp.push(plot_name); - - // Save the rendered plot to the temp file. - let temp_path = temp.to_str().unwrap(); - - { - let mut file = File::create(temp_path).unwrap(); - file.write_all(html_data.as_bytes()) - .expect("failed to write html output"); - file.flush().unwrap(); - } - temp_path.to_string() +fn write_example_to_html(plot: &Plot, name: &str) -> String { + std::fs::create_dir_all("./output").unwrap(); + let path = format!("./output/{}.html", name); + plot.write_html(&path); + path } fn main() { - // Uncomment any of these lines to display the example. - - // density_mapbox_responsive_autofill(); - // multiple_plots_on_same_html_page(); + // Switch the boolean flag to `true` to display the example, otherwise manually + // open the generated file in the `output` folder. + density_mapbox_responsive_autofill(false, "density_mapbox"); + multiple_plots_on_same_html_page(false, "multiple_plots"); } diff --git a/examples/financial_charts/README.md b/examples/financial_charts/README.md deleted file mode 100644 index 196c30dc..00000000 --- a/examples/financial_charts/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Financial Charts - -## How to Run - -1. Configure which example(s) you want to run by commenting/uncommenting lines in the `main` function, located in `src/main.rs`. -2. Run `cargo run`. \ No newline at end of file diff --git a/examples/financial_charts/src/main.rs b/examples/financial_charts/src/main.rs index 8eb4e0a2..745a58e6 100644 --- a/examples/financial_charts/src/main.rs +++ b/examples/financial_charts/src/main.rs @@ -39,7 +39,7 @@ fn load_apple_data() -> Vec { // Time Series and Date Axes // ANCHOR: time_series_plot_with_custom_date_range -fn time_series_plot_with_custom_date_range(show: bool) -> Plot { +fn time_series_plot_with_custom_date_range(show: bool, file_name: &str) { let data = load_apple_data(); let date: Vec = data.iter().map(|d| d.date.clone()).collect(); let high: Vec = data.iter().map(|d| d.high).collect(); @@ -54,15 +54,15 @@ fn time_series_plot_with_custom_date_range(show: bool) -> Plot { .title("Manually Set Date Range"); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: time_series_plot_with_custom_date_range // ANCHOR: time_series_with_range_slider -fn time_series_with_range_slider(show: bool) -> Plot { +fn time_series_with_range_slider(show: bool, file_name: &str) { let data = load_apple_data(); let date: Vec = data.iter().map(|d| d.date.clone()).collect(); let high: Vec = data.iter().map(|d| d.high).collect(); @@ -77,15 +77,15 @@ fn time_series_with_range_slider(show: bool) -> Plot { .title("Manually Set Date Range"); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: time_series_with_range_slider // ANCHOR: time_series_with_range_selector_buttons -fn time_series_with_range_selector_buttons(show: bool) -> Plot { +fn time_series_with_range_selector_buttons(show: bool, file_name: &str) { let data = load_apple_data(); let date: Vec = data.iter().map(|d| d.date.clone()).collect(); let high: Vec = data.iter().map(|d| d.high).collect(); @@ -124,15 +124,15 @@ fn time_series_with_range_selector_buttons(show: bool) -> Plot { ); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: time_series_with_range_selector_buttons // ANCHOR: customizing_tick_label_formatting_by_zoom_level -fn customizing_tick_label_formatting_by_zoom_level(show: bool) -> Plot { +fn customizing_tick_label_formatting_by_zoom_level(show: bool, file_name: &str) { let data = load_apple_data(); let date: Vec = data.iter().map(|d| d.date.clone()).collect(); let high: Vec = data.iter().map(|d| d.high).collect(); @@ -168,16 +168,16 @@ fn customizing_tick_label_formatting_by_zoom_level(show: bool) -> Plot { ); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: customizing_tick_label_formatting_by_zoom_level // Candlestick Charts // ANCHOR: simple_candlestick_chart -fn simple_candlestick_chart(show: bool) -> Plot { +fn simple_candlestick_chart(show: bool, file_name: &str) { let x = vec![ "2017-01-04", "2017-01-05", @@ -240,16 +240,16 @@ fn simple_candlestick_chart(show: bool) -> Plot { let mut plot = Plot::new(); plot.add_trace(trace1); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: simple_candlestick_chart // OHLC Charts // ANCHOR: simple_ohlc_chart -fn simple_ohlc_chart(show: bool) -> Plot { +fn simple_ohlc_chart(show: bool, file_name: &str) { let x = vec![ "2017-01-04", "2017-01-05", @@ -312,43 +312,39 @@ fn simple_ohlc_chart(show: bool) -> Plot { let mut plot = Plot::new(); plot.add_trace(trace1); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: simple_ohlc_chart -fn write_example_to_html(plot: Plot, name: &str) { - std::fs::create_dir_all("./out").unwrap(); - let html = plot.to_inline_html(Some(name)); - std::fs::write(format!("./out/{}.html", name), html).unwrap(); +fn write_example_to_html(plot: &Plot, name: &str) -> String { + std::fs::create_dir_all("./output").unwrap(); + let path = format!("./output/{}.html", name); + plot.write_html(&path); + path } fn main() { // Change false to true on any of these lines to display the example. // Time Series and Date Axes - write_example_to_html( - time_series_plot_with_custom_date_range(false), - "time_series_plot_with_custom_date_range", - ); - write_example_to_html( - time_series_with_range_slider(false), - "time_series_with_range_slider", - ); - write_example_to_html( - time_series_with_range_selector_buttons(false), - "time_series_with_range_selector_buttons", - ); - write_example_to_html( - customizing_tick_label_formatting_by_zoom_level(false), + + time_series_plot_with_custom_date_range(false, "time_series_plot_with_custom_date_range"); + + time_series_with_range_slider(false, "time_series_with_range_slider"); + + time_series_with_range_selector_buttons(false, "time_series_with_range_selector_buttons"); + + customizing_tick_label_formatting_by_zoom_level( + false, "customizing_tick_label_formatting_by_zoom_level", ); // Candlestick Charts - write_example_to_html(simple_candlestick_chart(false), "simple_candlestick_chart"); + simple_candlestick_chart(false, "simple_candlestick_chart"); // OHLC Charts - write_example_to_html(simple_ohlc_chart(false), "simple_ohlc_chart"); + simple_ohlc_chart(false, "simple_ohlc_chart"); } diff --git a/examples/images/README.md b/examples/images/README.md deleted file mode 100644 index 917e0c6c..00000000 --- a/examples/images/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Images - -## How to Run - -1. Configure which example(s) you want to run by commenting/uncommenting lines in the `main` function, located in `src/main.rs`. -2. Run `cargo run`. \ No newline at end of file diff --git a/examples/images/src/main.rs b/examples/images/src/main.rs index 362736aa..3984f7b4 100644 --- a/examples/images/src/main.rs +++ b/examples/images/src/main.rs @@ -3,7 +3,7 @@ use ndarray::arr2; use plotly::{color::Rgb, image::ColorModel, Image, Plot}; -fn basic_image() { +fn basic_image(show: bool, file_name: &str) { let w = Rgb::new(255, 255, 255); let b = Rgb::new(0, 0, 0); let r = Rgb::new(240, 8, 5); @@ -36,30 +36,39 @@ fn basic_image() { let mut plot = Plot::new(); plot.add_trace(trace); - plot.show() + let path = write_example_to_html(&plot, file_name); + if show { + plot.show_html(path); + } } -fn trace_from_image_crate_rgb() { +fn trace_from_image_crate_rgb(show: bool, file_name: &str) { let im = image::open("assets/mario.png").unwrap().into_rgb8(); let trace = Image::new(im).color_model(ColorModel::RGB); let mut plot = Plot::new(); plot.add_trace(trace); - plot.show() + let path = write_example_to_html(&plot, file_name); + if show { + plot.show_html(path); + } } -fn trace_from_image_crate_rgba() { +fn trace_from_image_crate_rgba(show: bool, file_name: &str) { let im = image::open("assets/mario.png").unwrap().into_rgba8(); let trace = Image::new(im).color_model(ColorModel::RGBA); let mut plot = Plot::new(); plot.add_trace(trace); - plot.show() + let path = write_example_to_html(&plot, file_name); + if show { + plot.show_html(path); + } } -fn trace_from_ndarray_rgb() { +fn trace_from_ndarray_rgb(show: bool, file_name: &str) { let pixels = arr2(&[ [(255, 255, 255), (0, 0, 0)], [(0, 0, 0), (255, 255, 255)], @@ -70,10 +79,13 @@ fn trace_from_ndarray_rgb() { let mut plot = Plot::new(); plot.add_trace(trace); - plot.show() + let path = write_example_to_html(&plot, file_name); + if show { + plot.show_html(path); + } } -fn trace_from_ndarray_rgba() { +fn trace_from_ndarray_rgba(show: bool, file_name: &str) { let pixels = arr2(&[ [(255, 255, 255, 1.), (0, 0, 0, 0.25)], [(0, 0, 0, 0.5), (255, 255, 255, 1.)], @@ -84,15 +96,24 @@ fn trace_from_ndarray_rgba() { let mut plot = Plot::new(); plot.add_trace(trace); - plot.show() + let path = write_example_to_html(&plot, file_name); + if show { + plot.show_html(path); + } } -fn main() { - // Uncomment any of these lines to display the example. +fn write_example_to_html(plot: &Plot, name: &str) -> String { + std::fs::create_dir_all("./output").unwrap(); + let path = format!("./output/{}.html", name); + plot.write_html(&path); + path +} - // basic_image(); - // trace_from_image_crate_rgb(); - // trace_from_image_crate_rgba(); - // trace_from_ndarray_rgb(); - // trace_from_ndarray_rgba(); +fn main() { + // Change false to true on any of these lines to display the example. + basic_image(true, "basic_image"); + trace_from_image_crate_rgb(true, "trace_from_image_rgb"); + trace_from_image_crate_rgba(false, "trace_from_image_rgba"); + trace_from_ndarray_rgb(false, "trace_from_ndarray_rgb"); + trace_from_ndarray_rgba(false, "trace_from_ndrarray_rgba"); } diff --git a/examples/kaleido/README.md b/examples/kaleido/README.md deleted file mode 100644 index b006bbbd..00000000 --- a/examples/kaleido/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Shapes - -## How to Run - -1. Configure the settings got the image export in the `main` function, located in `src/main.rs`. -2. Run `cargo run`. \ No newline at end of file diff --git a/examples/kaleido/src/main.rs b/examples/kaleido/src/main.rs index b2d1b827..48886f74 100644 --- a/examples/kaleido/src/main.rs +++ b/examples/kaleido/src/main.rs @@ -7,19 +7,21 @@ fn main() { // Adjust these arguments to set the width and height of the // output image. - let filename = "out"; let width = 800; let height = 600; let scale = 1.0; - // The image will be saved to format!("{filename}.{image_format}") relative to + std::fs::create_dir_all("./output").unwrap(); + let filename = "./output/image".to_string(); + + // The image will be saved to format!("output/image.{image_format}") relative to // the current working directory. - 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); + 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); } diff --git a/examples/maps/README.md b/examples/maps/README.md deleted file mode 100644 index 5fecb44a..00000000 --- a/examples/maps/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Maps - -## How to Run - -1. Configure which example(s) you want to run by commenting/uncommenting lines in the `main` function, located in `src/main.rs`. -2. Run `cargo run`. \ No newline at end of file diff --git a/examples/maps/src/main.rs b/examples/maps/src/main.rs index 1258e6ae..7da047ec 100644 --- a/examples/maps/src/main.rs +++ b/examples/maps/src/main.rs @@ -6,7 +6,7 @@ use plotly::{ DensityMapbox, Layout, Plot, ScatterMapbox, }; -fn scatter_mapbox() { +fn scatter_mapbox(show: bool, file_name: &str) { let trace = ScatterMapbox::new(vec![45.5017], vec![-73.5673]) .marker(Marker::new().size(25).opacity(0.9)); @@ -24,10 +24,13 @@ fn scatter_mapbox() { plot.add_trace(trace); plot.set_layout(layout); - plot.show(); + let path = write_example_to_html(&plot, file_name); + if show { + plot.show_html(path); + } } -fn density_mapbox() { +fn density_mapbox(show: bool, file_name: &str) { let trace = DensityMapbox::new(vec![45.5017], vec![-73.5673], vec![0.75]).zauto(true); let layout = Layout::new() @@ -44,12 +47,21 @@ fn density_mapbox() { plot.add_trace(trace); plot.set_layout(layout); - plot.show(); + let path = write_example_to_html(&plot, file_name); + if show { + plot.show_html(path); + } } -fn main() { - // Uncomment any of these lines to display the example. +fn write_example_to_html(plot: &Plot, name: &str) -> String { + std::fs::create_dir_all("./output").unwrap(); + let path = format!("./output/{}.html", name); + plot.write_html(&path); + path +} - // scatter_mapbox(); - // density_mapbox(); +fn main() { + // Change false to true on any of these lines to display the example. + scatter_mapbox(false, "scatter_mapbox"); + density_mapbox(false, "density_mapbox"); } diff --git a/examples/ndarray/README.md b/examples/ndarray/README.md deleted file mode 100644 index 3e212739..00000000 --- a/examples/ndarray/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# ndarray Support - -## How to Run - -1. Configure which example(s) you want to run by commenting/uncommenting lines in the `main` function, located in `src/main.rs`. -2. Run `cargo run`. \ No newline at end of file diff --git a/examples/ndarray/src/main.rs b/examples/ndarray/src/main.rs index 8754b27f..e052040c 100644 --- a/examples/ndarray/src/main.rs +++ b/examples/ndarray/src/main.rs @@ -5,7 +5,7 @@ use plotly::common::Mode; use plotly::ndarray::ArrayTraces; use plotly::{Plot, Scatter}; -fn single_ndarray_trace() { +fn single_ndarray_trace(show: bool, file_name: &str) { let n: usize = 11; let t: Array = Array::range(0., 10., 10. / n as f64); let ys: Array = t.iter().map(|v| (*v).powf(2.)).collect(); @@ -15,10 +15,13 @@ fn single_ndarray_trace() { let mut plot = Plot::new(); plot.add_trace(trace); - plot.show(); + let path = write_example_to_html(&plot, file_name); + if show { + plot.show_html(path); + } } -fn multiple_ndarray_traces_over_columns() { +fn multiple_ndarray_traces_over_columns(show: bool, file_name: &str) { let n: usize = 11; let t: Array = Array::range(0., 10., 10. / n as f64); let mut ys: Array = Array::zeros((11, 11)); @@ -38,10 +41,13 @@ fn multiple_ndarray_traces_over_columns() { let mut plot = Plot::new(); plot.add_traces(traces); - plot.show(); + let path = write_example_to_html(&plot, file_name); + if show { + plot.show_html(path); + } } -fn multiple_ndarray_traces_over_rows() { +fn multiple_ndarray_traces_over_rows(show: bool, file_name: &str) { let n: usize = 11; let t: Array = Array::range(0., 10., 10. / n as f64); let mut ys: Array = Array::zeros((11, 11)); @@ -61,13 +67,22 @@ fn multiple_ndarray_traces_over_rows() { let mut plot = Plot::new(); plot.add_traces(traces); - plot.show(); + let path = write_example_to_html(&plot, file_name); + if show { + plot.show_html(path); + } +} + +fn write_example_to_html(plot: &Plot, name: &str) -> String { + std::fs::create_dir_all("./output").unwrap(); + let path = format!("./output/{}.html", name); + plot.write_html(&path); + path } fn main() { - // Uncomment any of these lines to display the example. - - // single_ndarray_trace(); - // multiple_ndarray_traces_over_columns(); - // multiple_ndarray_traces_over_rows(); + // Change false to true on any of these lines to display the example. + single_ndarray_trace(false, "single_ndarray_trace"); + multiple_ndarray_traces_over_columns(false, "multiple_ndarray_traces_over_columns"); + multiple_ndarray_traces_over_rows(false, "multiple_ndarray_traces_over_rows"); } diff --git a/examples/scientific_charts/README.md b/examples/scientific_charts/README.md deleted file mode 100644 index c2eef8e2..00000000 --- a/examples/scientific_charts/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Scientific Charts - -## How to Run - -1. Configure which example(s) you want to run by commenting/uncommenting lines in the `main` function, located in `src/main.rs`. -2. Run `cargo run`. \ No newline at end of file diff --git a/examples/scientific_charts/src/main.rs b/examples/scientific_charts/src/main.rs index 85d06d4c..b8d00896 100644 --- a/examples/scientific_charts/src/main.rs +++ b/examples/scientific_charts/src/main.rs @@ -8,7 +8,7 @@ use plotly::{Contour, HeatMap, Layout, Plot}; // Contour Plots // ANCHOR: simple_contour_plot -fn simple_contour_plot(show: bool) -> Plot { +fn simple_contour_plot(show: bool, file_name: &str) { let n = 200; let mut x = Vec::::new(); let mut y = Vec::::new(); @@ -35,15 +35,15 @@ fn simple_contour_plot(show: bool) -> Plot { plot.add_trace(trace); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: simple_contour_plot // ANCHOR: colorscale_for_contour_plot -fn colorscale_for_contour_plot(show: bool) -> Plot { +fn colorscale_for_contour_plot(show: bool, file_name: &str) { let z = vec![ vec![10.0, 10.625, 12.5, 15.625, 20.0], vec![5.625, 6.25, 8.125, 11.25, 15.625], @@ -58,15 +58,15 @@ fn colorscale_for_contour_plot(show: bool) -> Plot { plot.set_layout(layout); plot.add_trace(trace); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: colorscale_for_contour_plot // ANCHOR: customizing_size_and_range_of_a_contour_plots_contours -fn customizing_size_and_range_of_a_contour_plots_contours(show: bool) -> Plot { +fn customizing_size_and_range_of_a_contour_plots_contours(show: bool, file_name: &str) { let z = vec![ vec![10.0, 10.625, 12.5, 15.625, 20.0], vec![5.625, 6.25, 8.125, 11.25, 15.625], @@ -84,15 +84,15 @@ fn customizing_size_and_range_of_a_contour_plots_contours(show: bool) -> Plot { plot.set_layout(layout); plot.add_trace(trace); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: customizing_size_and_range_of_a_contour_plots_contours // ANCHOR: customizing_spacing_between_x_and_y_ticks -fn customizing_spacing_between_x_and_y_ticks(show: bool) -> Plot { +fn customizing_spacing_between_x_and_y_ticks(show: bool, file_name: &str) { let z = vec![ vec![10.0, 10.625, 12.5, 15.625, 20.0], vec![5.625, 6.25, 8.125, 11.25, 15.625], @@ -112,30 +112,30 @@ fn customizing_spacing_between_x_and_y_ticks(show: bool) -> Plot { plot.set_layout(layout); plot.add_trace(trace); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: customizing_spacing_between_x_and_y_ticks // Heatmaps // ANCHOR: basic_heat_map -fn basic_heat_map(show: bool) -> Plot { +fn basic_heat_map(show: bool, file_name: &str) { let z = vec![vec![1, 20, 30], vec![20, 1, 60], vec![30, 60, 1]]; let trace = HeatMap::new_z(z).zmin(1.0).zmax(60.0); let mut plot = Plot::new(); plot.add_trace(trace); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: basic_heat_map // ANCHOR: customized_heat_map -fn customized_heat_map(show: bool) -> Plot { +fn customized_heat_map(show: bool, file_name: &str) { let x = (0..100).map(|x| x as f64).collect::>(); let y = (0..100).map(|y| y as f64).collect::>(); let z: Vec> = y @@ -169,38 +169,32 @@ fn customized_heat_map(show: bool) -> Plot { plot.set_layout(layout); plot.add_trace(trace); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: customized_heat_map -fn write_example_to_html(plot: Plot, name: &str) { - std::fs::create_dir_all("./out").unwrap(); - let html = plot.to_inline_html(Some(name)); - std::fs::write(format!("./out/{}.html", name), html).unwrap(); +fn write_example_to_html(plot: &Plot, name: &str) -> String { + std::fs::create_dir_all("./output").unwrap(); + let path = format!("./output/{}.html", name); + plot.write_html(&path); + path } fn main() { // Change false to true on any of these lines to display the example. - // Contour Plots - write_example_to_html(simple_contour_plot(false), "simple_contour_plot"); - write_example_to_html( - colorscale_for_contour_plot(false), - "colorscale_for_contour_plot", - ); - write_example_to_html( - customizing_size_and_range_of_a_contour_plots_contours(false), + simple_contour_plot(false, "simple_contour_plot"); + colorscale_for_contour_plot(false, "colorscale_for_contour_plot"); + customizing_size_and_range_of_a_contour_plots_contours( + false, "customizing_size_and_range_of_a_contour_plots_contours", ); - write_example_to_html( - customizing_spacing_between_x_and_y_ticks(false), - "customizing_spacing_between_x_and_y_ticks", - ); + customizing_spacing_between_x_and_y_ticks(false, "customizing_spacing_between_x_and_y_ticks"); // Heatmaps - write_example_to_html(basic_heat_map(false), "basic_heat_map"); - write_example_to_html(customized_heat_map(false), "customized_heat_map"); + basic_heat_map(false, "basic_heat_map"); + customized_heat_map(false, "customized_heat_map"); } diff --git a/examples/shapes/README.md b/examples/shapes/README.md deleted file mode 100644 index f924de7d..00000000 --- a/examples/shapes/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Shapes - -## How to Run - -1. Configure which example(s) you want to run by commenting/uncommenting lines in the `main` function, located in `src/main.rs`. -2. Run `cargo run`. \ No newline at end of file diff --git a/examples/shapes/src/main.rs b/examples/shapes/src/main.rs index c582435b..21e05095 100644 --- a/examples/shapes/src/main.rs +++ b/examples/shapes/src/main.rs @@ -12,7 +12,7 @@ use plotly::{ use rand_distr::{num_traits::Float, Distribution, Normal}; // ANCHOR: filled_area_chart -fn filled_area_chart(show: bool) -> Plot { +fn filled_area_chart(show: bool, file_name: &str) { let trace1 = Scatter::new(vec![0, 1, 2, 0], vec![0, 2, 0, 0]).fill(Fill::ToSelf); let trace2 = Scatter::new(vec![3, 3, 5, 5, 3], vec![0.5, 1.5, 1.5, 0.5, 0.5]).fill(Fill::ToSelf); @@ -21,15 +21,15 @@ fn filled_area_chart(show: bool) -> Plot { plot.add_trace(trace1); plot.add_trace(trace2); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: filled_area_chart // ANCHOR: vertical_and_horizontal_lines_positioned_relative_to_axes -fn vertical_and_horizontal_lines_positioned_relative_to_axes(show: bool) -> Plot { +fn vertical_and_horizontal_lines_positioned_relative_to_axes(show: bool, file_name: &str) { let trace = Scatter::new(vec![2.0, 3.5, 6.0], vec![1.0, 1.5, 1.0]) .text_array(vec![ "Vertical Line", @@ -87,15 +87,15 @@ fn vertical_and_horizontal_lines_positioned_relative_to_axes(show: bool) -> Plot plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: vertical_and_horizontal_lines_positioned_relative_to_axes // ANCHOR: lines_positioned_relative_to_the_plot_and_to_the_axes -fn lines_positioned_relative_to_the_plot_and_to_the_axes(show: bool) -> Plot { +fn lines_positioned_relative_to_the_plot_and_to_the_axes(show: bool, file_name: &str) { let trace = Scatter::new(vec![2.0, 6.0], vec![1.0, 1.0]) .text_array(vec![ "Line positioned relative to the plot", @@ -135,15 +135,15 @@ fn lines_positioned_relative_to_the_plot_and_to_the_axes(show: bool) -> Plot { plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: lines_positioned_relative_to_the_plot_and_to_the_axes // ANCHOR: creating_tangent_lines_with_shapes -fn creating_tangent_lines_with_shapes(show: bool) -> Plot { +fn creating_tangent_lines_with_shapes(show: bool, file_name: &str) { let x0 = Array::linspace(1.0, 3.0, 200).into_raw_vec_and_offset().0; let y0 = x0.iter().map(|v| *v * (v.powf(2.)).sin() + 1.).collect(); @@ -195,15 +195,15 @@ fn creating_tangent_lines_with_shapes(show: bool) -> Plot { plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: creating_tangent_lines_with_shapes // ANCHOR: rectangles_positioned_relative_to_the_axes -fn rectangles_positioned_relative_to_the_axes(show: bool) -> Plot { +fn rectangles_positioned_relative_to_the_axes(show: bool, file_name: &str) { let trace = Scatter::new(vec![1.5, 4.5], vec![0.75, 0.75]) .text_array(vec!["Unfilled Rectangle", "Filled Rectangle"]) .mode(Mode::Text); @@ -241,15 +241,15 @@ fn rectangles_positioned_relative_to_the_axes(show: bool) -> Plot { plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: rectangles_positioned_relative_to_the_axes // ANCHOR: rectangle_positioned_relative_to_the_plot_and_to_the_axes -fn rectangle_positioned_relative_to_the_plot_and_to_the_axes(show: bool) -> Plot { +fn rectangle_positioned_relative_to_the_plot_and_to_the_axes(show: bool, file_name: &str) { let trace = Scatter::new(vec![1.5, 3.], vec![2.5, 2.5]) .text_array(vec![ "Rectangle reference to the plot", @@ -292,15 +292,15 @@ fn rectangle_positioned_relative_to_the_plot_and_to_the_axes(show: bool) -> Plot plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: rectangle_positioned_relative_to_the_plot_and_to_the_axes // ANCHOR: highlighting_time_series_regions_with_rectangle_shapes -fn highlighting_time_series_regions_with_rectangle_shapes(show: bool) -> Plot { +fn highlighting_time_series_regions_with_rectangle_shapes(show: bool, file_name: &str) { let x = vec![ "2015-02-01", "2015-02-02", @@ -374,15 +374,15 @@ fn highlighting_time_series_regions_with_rectangle_shapes(show: bool) -> Plot { plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: highlighting_time_series_regions_with_rectangle_shapes // ANCHOR: circles_positioned_relative_to_the_axes -fn circles_positioned_relative_to_the_axes(show: bool) -> Plot { +fn circles_positioned_relative_to_the_axes(show: bool, file_name: &str) { let trace = Scatter::new(vec![1.5, 3.5], vec![0.75, 2.5]) .text_array(vec!["Unfilled Circle", "Filled Circle"]) .mode(Mode::Text); @@ -423,15 +423,15 @@ fn circles_positioned_relative_to_the_axes(show: bool) -> Plot { plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: circles_positioned_relative_to_the_axes // ANCHOR: highlighting_clusters_of_scatter_points_with_circle_shapes -fn highlighting_clusters_of_scatter_points_with_circle_shapes(show: bool) -> Plot { +fn highlighting_clusters_of_scatter_points_with_circle_shapes(show: bool, file_name: &str) { let mut rng = rand::rng(); let x0 = Normal::new(2., 0.45) .unwrap() @@ -540,15 +540,15 @@ fn highlighting_clusters_of_scatter_points_with_circle_shapes(show: bool) -> Plo plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: highlighting_clusters_of_scatter_points_with_circle_shapes // ANCHOR: venn_diagram_with_circle_shapes -fn venn_diagram_with_circle_shapes(show: bool) -> Plot { +fn venn_diagram_with_circle_shapes(show: bool, file_name: &str) { let mut plot = Plot::new(); plot.add_trace( Scatter::new(vec![1., 1.75, 2.5], vec![1., 1., 1.]) @@ -611,15 +611,15 @@ fn venn_diagram_with_circle_shapes(show: bool) -> Plot { plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: venn_diagram_with_circle_shapes // ANCHOR: adding_shapes_to_subplots -fn adding_shapes_to_subplots(show: bool) -> Plot { +fn adding_shapes_to_subplots(show: bool, file_name: &str) { let mut plot = Plot::new(); plot.add_trace( Scatter::new(vec![2, 6], vec![1, 1]) @@ -702,15 +702,15 @@ fn adding_shapes_to_subplots(show: bool) -> Plot { plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: adding_shapes_to_subplots // ANCHOR: svg_paths -fn svg_paths(show: bool) -> Plot { +fn svg_paths(show: bool, file_name: &str) { let mut plot = Plot::new(); plot.add_trace( Scatter::new(vec![2, 1, 8, 8], vec![0.25, 9., 2., 6.]) @@ -765,62 +765,58 @@ fn svg_paths(show: bool) -> Plot { plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: svg_paths -fn write_example_to_html(plot: Plot, name: &str) { - std::fs::create_dir_all("./out").unwrap(); - let html = plot.to_inline_html(Some(name)); - std::fs::write(format!("./out/{}.html", name), html).unwrap(); +fn write_example_to_html(plot: &Plot, name: &str) -> String { + std::fs::create_dir_all("./output").unwrap(); + let path = format!("./output/{}.html", name); + plot.write_html(&path); + path } fn main() { // Change false to true on any of these lines to display the example. - write_example_to_html(filled_area_chart(false), "filled_area_chart"); - write_example_to_html( - vertical_and_horizontal_lines_positioned_relative_to_axes(false), + filled_area_chart(false, "filled_area_chart"); + + vertical_and_horizontal_lines_positioned_relative_to_axes( + false, "vertical_and_horizontal_lines_positioned_relative_to_axes", ); - write_example_to_html( - lines_positioned_relative_to_the_plot_and_to_the_axes(false), + + lines_positioned_relative_to_the_plot_and_to_the_axes( + false, "lines_positioned_relative_to_the_plot_and_to_the_axes", ); - write_example_to_html( - creating_tangent_lines_with_shapes(false), - "creating_tangent_lines_with_shapes", - ); - write_example_to_html( - rectangles_positioned_relative_to_the_axes(false), - "rectangles_positioned_relative_to_the_axes", - ); - write_example_to_html( - rectangle_positioned_relative_to_the_plot_and_to_the_axes(false), + + creating_tangent_lines_with_shapes(true, "creating_tangent_lines_with_shapes"); + + rectangles_positioned_relative_to_the_axes(true, "rectangles_positioned_relative_to_the_axes"); + + rectangle_positioned_relative_to_the_plot_and_to_the_axes( + false, "rectangle_positioned_relative_to_the_plot_and_to_the_axes", ); - write_example_to_html( - highlighting_time_series_regions_with_rectangle_shapes(false), + + highlighting_time_series_regions_with_rectangle_shapes( + false, "highlighting_time_series_regions_with_rectangle_shapes", ); - write_example_to_html( - circles_positioned_relative_to_the_axes(false), - "circles_positioned_relative_to_the_axes", - ); - write_example_to_html( - highlighting_clusters_of_scatter_points_with_circle_shapes(false), + + circles_positioned_relative_to_the_axes(false, "circles_positioned_relative_to_the_axes"); + + highlighting_clusters_of_scatter_points_with_circle_shapes( + false, "highlighting_clusters_of_scatter_points_with_circle_shapes", ); - write_example_to_html( - venn_diagram_with_circle_shapes(false), - "venn_diagram_with_circle_shapes", - ); - write_example_to_html( - adding_shapes_to_subplots(false), - "adding_shapes_to_subplots", - ); - write_example_to_html(svg_paths(false), "svg_paths"); + + venn_diagram_with_circle_shapes(false, "venn_diagram_with_circle_shapes"); + + adding_shapes_to_subplots(false, "adding_shapes_to_subplots"); + svg_paths(false, "svg_paths"); } diff --git a/examples/statistical_charts/README.md b/examples/statistical_charts/README.md deleted file mode 100644 index 2dd9ad5a..00000000 --- a/examples/statistical_charts/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Statistical Charts - -## How to Run - -1. Configure which example(s) you want to run by commenting/uncommenting lines in the `main` function, located in `src/main.rs`. -2. Run `cargo run`. \ No newline at end of file diff --git a/examples/statistical_charts/src/main.rs b/examples/statistical_charts/src/main.rs index 11b2ea21..e79e44e8 100644 --- a/examples/statistical_charts/src/main.rs +++ b/examples/statistical_charts/src/main.rs @@ -13,7 +13,7 @@ use rand_distr::{Distribution, Normal, Uniform}; // Error Bars // ANCHOR: basic_symmetric_error_bars -fn basic_symmetric_error_bars(show: bool) -> Plot { +fn basic_symmetric_error_bars(show: bool, file_name: &str) { let trace1 = Scatter::new(vec![0, 1, 2], vec![6, 10, 2]) .name("trace1") .error_y(ErrorData::new(ErrorType::Data).array(vec![1.0, 2.0, 3.0])); @@ -21,15 +21,15 @@ fn basic_symmetric_error_bars(show: bool) -> Plot { let mut plot = Plot::new(); plot.add_trace(trace1); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: basic_symmetric_error_bars // ANCHOR: asymmetric_error_bars -fn asymmetric_error_bars(show: bool) -> Plot { +fn asymmetric_error_bars(show: bool, file_name: &str) { let trace1 = Scatter::new(vec![1, 2, 3, 4], vec![2, 1, 3, 4]) .name("trace1") .error_y( @@ -41,15 +41,15 @@ fn asymmetric_error_bars(show: bool) -> Plot { let mut plot = Plot::new(); plot.add_trace(trace1); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: asymmetric_error_bars // ANCHOR: error_bars_as_a_percentage_of_the_y_value -fn error_bars_as_a_percentage_of_the_y_value(show: bool) -> Plot { +fn error_bars_as_a_percentage_of_the_y_value(show: bool, file_name: &str) { let trace1 = Scatter::new(vec![0, 1, 2], vec![6, 10, 2]) .name("trace1") .error_y(ErrorData::new(ErrorType::Percent).value(50.).visible(true)); @@ -57,15 +57,15 @@ fn error_bars_as_a_percentage_of_the_y_value(show: bool) -> Plot { let mut plot = Plot::new(); plot.add_trace(trace1); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: error_bars_as_a_percentage_of_the_y_value // ANCHOR: asymmetric_error_bars_with_a_constant_offset -fn asymmetric_error_bars_with_a_constant_offset(show: bool) -> Plot { +fn asymmetric_error_bars_with_a_constant_offset(show: bool, file_name: &str) { let trace1 = Scatter::new(vec![1, 2, 3, 4], vec![2, 1, 3, 4]) .name("trace1") .error_y( @@ -78,15 +78,15 @@ fn asymmetric_error_bars_with_a_constant_offset(show: bool) -> Plot { let mut plot = Plot::new(); plot.add_trace(trace1); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: asymmetric_error_bars_with_a_constant_offset // ANCHOR: horizontal_error_bars -fn horizontal_error_bars(show: bool) -> Plot { +fn horizontal_error_bars(show: bool, file_name: &str) { let trace1 = Scatter::new(vec![1, 2, 3, 4], vec![2, 1, 3, 4]) .name("trace1") .error_x(ErrorData::new(ErrorType::Percent).value(10.)); @@ -94,15 +94,15 @@ fn horizontal_error_bars(show: bool) -> Plot { let mut plot = Plot::new(); plot.add_trace(trace1); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: horizontal_error_bars // ANCHOR: bar_chart_with_error_bars -fn bar_chart_with_error_bars(show: bool) -> Plot { +fn bar_chart_with_error_bars(show: bool, file_name: &str) { let trace_c = Bar::new(vec!["Trial 1", "Trial 2", "Trial 3"], vec![3, 6, 4]) .error_y(ErrorData::new(ErrorType::Data).array(vec![1., 0.5, 1.5])); let trace_e = Bar::new(vec!["Trial 1", "Trial 2", "Trial 3"], vec![4, 7, 3]) @@ -115,15 +115,15 @@ fn bar_chart_with_error_bars(show: bool) -> Plot { let layout = Layout::new().bar_mode(BarMode::Group); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: bar_chart_with_error_bars // ANCHOR: colored_and_styled_error_bars -fn colored_and_styled_error_bars(show: bool) -> Plot { +fn colored_and_styled_error_bars(show: bool, file_name: &str) { let x_theo: Vec = Array::linspace(-4., 4., 100).into_raw_vec_and_offset().0; let sincx: Vec = x_theo .iter() @@ -160,16 +160,16 @@ fn colored_and_styled_error_bars(show: bool) -> Plot { plot.add_trace(trace1); plot.add_trace(trace2); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: colored_and_styled_error_bars // Box Plots // ANCHOR: basic_box_plot -fn basic_box_plot(show: bool) -> Plot { +fn basic_box_plot(show: bool, file_name: &str) { let mut rng = rand::rng(); let uniform1 = Uniform::new(0.0, 1.0).unwrap(); let uniform2 = Uniform::new(1.0, 2.0).unwrap(); @@ -189,15 +189,15 @@ fn basic_box_plot(show: bool) -> Plot { plot.add_trace(trace1); plot.add_trace(trace2); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: basic_box_plot // ANCHOR: box_plot_that_displays_the_underlying_data -fn box_plot_that_displays_the_underlying_data(show: bool) -> Plot { +fn box_plot_that_displays_the_underlying_data(show: bool, file_name: &str) { let trace1 = BoxPlot::new(vec![0, 1, 1, 2, 3, 5, 8, 13, 21]) .box_points(BoxPoints::All) .jitter(0.3) @@ -205,15 +205,15 @@ fn box_plot_that_displays_the_underlying_data(show: bool) -> Plot { let mut plot = Plot::new(); plot.add_trace(trace1); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: box_plot_that_displays_the_underlying_data // ANCHOR: horizontal_box_plot -fn horizontal_box_plot(show: bool) -> Plot { +fn horizontal_box_plot(show: bool, file_name: &str) { let x = vec![ "Set 1", "Set 1", "Set 1", "Set 1", "Set 1", "Set 1", "Set 1", "Set 1", "Set 1", "Set 2", "Set 2", "Set 2", "Set 2", "Set 2", "Set 2", "Set 2", "Set 2", "Set 2", @@ -228,15 +228,15 @@ fn horizontal_box_plot(show: bool) -> Plot { let mut plot = Plot::new(); plot.add_trace(trace); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: horizontal_box_plot // ANCHOR: grouped_box_plot -fn grouped_box_plot(show: bool) -> Plot { +fn grouped_box_plot(show: bool, file_name: &str) { let x = vec![ "day 1", "day 1", "day 1", "day 1", "day 1", "day 1", "day 2", "day 2", "day 2", "day 2", "day 2", "day 2", @@ -266,15 +266,15 @@ fn grouped_box_plot(show: bool) -> Plot { plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: grouped_box_plot // ANCHOR: box_plot_styling_outliers -fn box_plot_styling_outliers(show: bool) -> Plot { +fn box_plot_styling_outliers(show: bool, file_name: &str) { let y = vec![ 0.75, 5.25, 5.5, 6.0, 6.2, 6.6, 6.80, 7.0, 7.2, 7.5, 7.5, 7.75, 8.15, 8.15, 8.65, 8.93, 9.2, 9.5, 10.0, 10.25, 11.5, 12.0, 16.0, 20.90, 22.3, 23.25, @@ -316,15 +316,15 @@ fn box_plot_styling_outliers(show: bool) -> Plot { plot.add_trace(trace3); plot.add_trace(trace4); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: box_plot_styling_outliers // ANCHOR: box_plot_styling_mean_and_standard_deviation -fn box_plot_styling_mean_and_standard_deviation(show: bool) -> Plot { +fn box_plot_styling_mean_and_standard_deviation(show: bool, file_name: &str) { let y = vec![ 2.37, 2.16, 4.82, 1.73, 1.04, 0.23, 1.32, 2.91, 0.11, 4.51, 0.51, 3.75, 1.35, 2.98, 4.50, 0.18, 4.66, 1.30, 2.06, 1.19, @@ -345,15 +345,15 @@ fn box_plot_styling_mean_and_standard_deviation(show: bool) -> Plot { plot.add_trace(trace1); plot.add_trace(trace2); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: box_plot_styling_mean_and_standard_deviation // ANCHOR: grouped_horizontal_box_plot -fn grouped_horizontal_box_plot(show: bool) -> Plot { +fn grouped_horizontal_box_plot(show: bool, file_name: &str) { let x = vec![ "day 1", "day 1", "day 1", "day 1", "day 1", "day 1", "day 2", "day 2", "day 2", "day 2", "day 2", "day 2", @@ -396,15 +396,15 @@ fn grouped_horizontal_box_plot(show: bool) -> Plot { plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: grouped_horizontal_box_plot // ANCHOR: fully_styled_box_plot -fn fully_styled_box_plot(show: bool) -> Plot { +fn fully_styled_box_plot(show: bool, file_name: &str) { let rnd_sample = |num, mul| -> Vec { let mut v: Vec = Vec::with_capacity(num); let mut rng = rand::rng(); @@ -469,10 +469,10 @@ fn fully_styled_box_plot(show: bool) -> Plot { plot.add_trace(trace); } + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: fully_styled_box_plot @@ -498,21 +498,21 @@ fn sample_uniform_distribution(n: usize, lb: f64, ub: f64) -> Vec { } // ANCHOR: basic_histogram -fn basic_histogram(show: bool) -> Plot { +fn basic_histogram(show: bool, file_name: &str) { let samples = sample_normal_distribution(10_000, 0.0, 1.0); let trace = Histogram::new(samples).name("h"); let mut plot = Plot::new(); plot.add_trace(trace); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: basic_histogram // ANCHOR: horizontal_histogram -fn horizontal_histogram(show: bool) -> Plot { +fn horizontal_histogram(show: bool, file_name: &str) { let samples = sample_normal_distribution(10_000, 0.0, 1.0); let trace = Histogram::new_vertical(samples) .name("h") @@ -521,15 +521,15 @@ fn horizontal_histogram(show: bool) -> Plot { plot.add_trace(trace); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: horizontal_histogram // ANCHOR: overlaid_histogram -fn overlaid_histogram(show: bool) -> Plot { +fn overlaid_histogram(show: bool, file_name: &str) { let samples1 = sample_normal_distribution(500, 0.0, 1.0); let trace1 = Histogram::new(samples1) .name("trace 1") @@ -549,15 +549,15 @@ fn overlaid_histogram(show: bool) -> Plot { let layout = Layout::new().bar_mode(BarMode::Overlay); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: overlaid_histogram // ANCHOR: stacked_histograms -fn stacked_histograms(show: bool) -> Plot { +fn stacked_histograms(show: bool, file_name: &str) { let samples1 = sample_normal_distribution(500, 0.0, 1.0); let trace1 = Histogram::new(samples1) .name("trace 1") @@ -577,15 +577,15 @@ fn stacked_histograms(show: bool) -> Plot { let layout = Layout::new().bar_mode(BarMode::Stack); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: stacked_histograms // ANCHOR: colored_and_styled_histograms -fn colored_and_styled_histograms(show: bool) -> Plot { +fn colored_and_styled_histograms(show: bool, file_name: &str) { let n = 500; let x1 = sample_uniform_distribution(n, 0.0, 5.0); let x2 = sample_uniform_distribution(n, 0.0, 10.0); @@ -627,15 +627,15 @@ fn colored_and_styled_histograms(show: bool) -> Plot { plot.add_trace(trace1); plot.add_trace(trace2); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: colored_and_styled_histograms // ANCHOR: cumulative_histogram -fn cumulative_histogram(show: bool) -> Plot { +fn cumulative_histogram(show: bool, file_name: &str) { let n = 500; let x = sample_uniform_distribution(n, 0.0, 1.0); let trace = Histogram::new(x) @@ -644,15 +644,15 @@ fn cumulative_histogram(show: bool) -> Plot { let mut plot = Plot::new(); plot.add_trace(trace); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: cumulative_histogram // ANCHOR: normalized_histogram -fn normalized_histogram(show: bool) -> Plot { +fn normalized_histogram(show: bool, file_name: &str) { let n = 500; let x = sample_uniform_distribution(n, 0.0, 1.0); let trace = Histogram::new(x) @@ -661,15 +661,15 @@ fn normalized_histogram(show: bool) -> Plot { let mut plot = Plot::new(); plot.add_trace(trace); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: normalized_histogram // ANCHOR: specify_binning_function -fn specify_binning_function(show: bool) -> Plot { +fn specify_binning_function(show: bool, file_name: &str) { let x = vec!["Apples", "Apples", "Apples", "Oranges", "Bananas"]; let y = vec!["5", "10", "3", "10", "5"]; @@ -684,78 +684,65 @@ fn specify_binning_function(show: bool) -> Plot { plot.add_trace(trace1); plot.add_trace(trace2); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: specify_binning_function -fn write_example_to_html(plot: Plot, name: &str) { - std::fs::create_dir_all("./out").unwrap(); - let html = plot.to_inline_html(Some(name)); - std::fs::write(format!("./out/{}.html", name), html).unwrap(); +fn write_example_to_html(plot: &Plot, name: &str) -> String { + std::fs::create_dir_all("./output").unwrap(); + let path = format!("./output/{}.html", name); + plot.write_html(&path); + path } fn main() { // Change false to true on any of these lines to display the example. // Error Bars - write_example_to_html( - basic_symmetric_error_bars(false), - "basic_symmetric_error_bars", - ); - write_example_to_html(asymmetric_error_bars(false), "asymmetric_error_bars"); - write_example_to_html( - error_bars_as_a_percentage_of_the_y_value(false), - "error_bars_as_a_percentage_of_the_y_value", - ); - write_example_to_html( - asymmetric_error_bars_with_a_constant_offset(false), + + basic_symmetric_error_bars(false, "basic_symmetric_error_bars"); + asymmetric_error_bars(false, "asymmetric_error_bars"); + + error_bars_as_a_percentage_of_the_y_value(false, "error_bars_as_a_percentage_of_the_y_value"); + + asymmetric_error_bars_with_a_constant_offset( + false, "asymmetric_error_bars_with_a_constant_offset", ); - write_example_to_html(horizontal_error_bars(false), "horizontal_error_bars"); - write_example_to_html( - bar_chart_with_error_bars(false), - "bar_chart_with_error_bars", - ); - write_example_to_html( - colored_and_styled_error_bars(false), - "colored_and_styled_error_bars", - ); + horizontal_error_bars(false, "horizontal_error_bars"); + + bar_chart_with_error_bars(false, "bar_chart_with_error_bars"); + + colored_and_styled_error_bars(false, "colored_and_styled_error_bars"); // Box Plots - write_example_to_html(basic_box_plot(false), "basic_box_plot"); - write_example_to_html( - box_plot_that_displays_the_underlying_data(false), - "box_plot_that_displays_the_underlying_data", - ); - write_example_to_html(horizontal_box_plot(false), "horizontal_box_plot"); - write_example_to_html(grouped_box_plot(false), "grouped_box_plot"); - write_example_to_html( - box_plot_styling_outliers(false), - "box_plot_styling_outliers", - ); - write_example_to_html( - box_plot_styling_mean_and_standard_deviation(false), + basic_box_plot(false, "basic_box_plot"); + + box_plot_that_displays_the_underlying_data(false, "box_plot_that_displays_the_underlying_data"); + horizontal_box_plot(false, "horizontal_box_plot"); + grouped_box_plot(false, "grouped_box_plot"); + + box_plot_styling_outliers(false, "box_plot_styling_outliers"); + + box_plot_styling_mean_and_standard_deviation( + false, "box_plot_styling_mean_and_standard_deviation", ); - write_example_to_html( - grouped_horizontal_box_plot(false), - "grouped_horizontal_box_plot", - ); - write_example_to_html(fully_styled_box_plot(false), "fully_styled_box_plot"); + + grouped_horizontal_box_plot(false, "grouped_horizontal_box_plot"); + fully_styled_box_plot(false, "fully_styled_box_plot"); // Histograms - write_example_to_html(basic_histogram(false), "basic_histogram"); - write_example_to_html(horizontal_histogram(false), "horizontal_histogram"); - write_example_to_html(overlaid_histogram(false), "overlaid_histogram"); - write_example_to_html(stacked_histograms(false), "stacked_histograms"); - write_example_to_html( - colored_and_styled_histograms(false), - "colored_and_styled_histograms", - ); - write_example_to_html(cumulative_histogram(false), "cumulative_histogram"); - write_example_to_html(normalized_histogram(false), "normalized_histogram"); - write_example_to_html(specify_binning_function(false), "specify_binning_function"); + basic_histogram(false, "basic_histogram"); + horizontal_histogram(false, "horizontal_histogram"); + overlaid_histogram(false, "overlaid_histogram"); + stacked_histograms(false, "stacked_histograms"); + + colored_and_styled_histograms(false, "colored_and_styled_histograms"); + cumulative_histogram(false, "cumulative_histogram"); + normalized_histogram(false, "normalized_histogram"); + specify_binning_function(false, "specify_binning_function"); } diff --git a/examples/subplots/README.md b/examples/subplots/README.md deleted file mode 100644 index 52ef9cc9..00000000 --- a/examples/subplots/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Subplots - -## How to Run - -1. Configure which example(s) you want to run by commenting/uncommenting lines in the `main` function, located in `src/main.rs`. -2. Run `cargo run`. \ No newline at end of file diff --git a/examples/subplots/src/main.rs b/examples/subplots/src/main.rs index 7806c48a..eb4b2b85 100644 --- a/examples/subplots/src/main.rs +++ b/examples/subplots/src/main.rs @@ -9,7 +9,7 @@ use plotly::{color::Rgb, Plot, Scatter}; // Subplots // ANCHOR: simple_subplot -fn simple_subplot(show: bool) -> Plot { +fn simple_subplot(show: bool, file_name: &str) { let trace1 = Scatter::new(vec![1, 2, 3], vec![4, 5, 6]).name("trace1"); let trace2 = Scatter::new(vec![20, 30, 40], vec![50, 60, 70]) .name("trace2") @@ -28,15 +28,15 @@ fn simple_subplot(show: bool) -> Plot { ); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: simple_subplot // ANCHOR: simple_subplot_matches_x_axis -fn simple_subplot_matches_x_axis(show: bool) -> Plot { +fn simple_subplot_matches_x_axis(show: bool, file_name: &str) { let trace1 = Scatter::new(vec![1, 2, 3], vec![4, 5, 6]).name("trace1"); let trace2 = Scatter::new(vec![20, 30, 40], vec![50, 60, 70]) .name("trace2") @@ -55,15 +55,15 @@ fn simple_subplot_matches_x_axis(show: bool) -> Plot { ); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: simple_subplot_matches_x_axis // ANCHOR: simple_subplot_matches_y_axis -fn simple_subplot_matches_y_axis(show: bool) -> Plot { +fn simple_subplot_matches_y_axis(show: bool, file_name: &str) { let trace1 = Scatter::new(vec![1, 2, 3], vec![4, 5, 6]).name("trace1"); let trace2 = Scatter::new(vec![20, 30, 40], vec![50, 60, 70]) .name("trace2") @@ -82,15 +82,15 @@ fn simple_subplot_matches_y_axis(show: bool) -> Plot { ); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: simple_subplot_matches_y_axis // ANCHOR: custom_sized_subplot -fn custom_sized_subplot(show: bool) -> Plot { +fn custom_sized_subplot(show: bool, file_name: &str) { let trace1 = Scatter::new(vec![1, 2, 3], vec![4, 5, 6]).name("trace1"); let trace2 = Scatter::new(vec![20, 30, 40], vec![50, 60, 70]) .name("trace2") @@ -107,15 +107,15 @@ fn custom_sized_subplot(show: bool) -> Plot { .x_axis2(Axis::new().domain(&[0.8, 1.])); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: custom_sized_subplot // ANCHOR: multiple_subplots -fn multiple_subplots(show: bool) -> Plot { +fn multiple_subplots(show: bool, file_name: &str) { let trace1 = Scatter::new(vec![1, 2, 3], vec![4, 5, 6]).name("trace1"); let trace2 = Scatter::new(vec![20, 30, 40], vec![50, 60, 70]) .name("trace2") @@ -142,15 +142,15 @@ fn multiple_subplots(show: bool) -> Plot { ); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: multiple_subplots // ANCHOR: stacked_subplots -fn stacked_subplots(show: bool) -> Plot { +fn stacked_subplots(show: bool, file_name: &str) { let trace1 = Scatter::new(vec![0, 1, 2], vec![10, 11, 12]).name("trace1"); let trace2 = Scatter::new(vec![2, 3, 4], vec![100, 110, 120]) .name("trace2") @@ -174,15 +174,15 @@ fn stacked_subplots(show: bool) -> Plot { ); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: stacked_subplots // ANCHOR: stacked_subplots_with_shared_x_axis -fn stacked_subplots_with_shared_x_axis(show: bool) -> Plot { +fn stacked_subplots_with_shared_x_axis(show: bool, file_name: &str) { let trace1 = Scatter::new(vec![0, 1, 2], vec![10, 11, 12]).name("trace1"); let trace2 = Scatter::new(vec![2, 3, 4], vec![100, 110, 120]) .name("trace2") @@ -201,15 +201,15 @@ fn stacked_subplots_with_shared_x_axis(show: bool) -> Plot { .y_axis3(Axis::new().domain(&[0.66, 1.])); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: stacked_subplots_with_shared_x_axis // ANCHOR: multiple_custom_sized_subplots -fn multiple_custom_sized_subplots(show: bool) -> Plot { +fn multiple_custom_sized_subplots(show: bool, file_name: &str) { let trace1 = Scatter::new(vec![1, 2], vec![1, 2]).name("(1,1)"); let trace2 = Scatter::new(vec![1, 2], vec![1, 2]) .name("(1,2,1)") @@ -242,16 +242,16 @@ fn multiple_custom_sized_subplots(show: bool) -> Plot { .y_axis4(Axis::new().domain(&[0., 0.45]).anchor("x4")); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: multiple_custom_sized_subplots // Multiple Axes // ANCHOR: two_y_axes -fn two_y_axes(show: bool) -> Plot { +fn two_y_axes(show: bool, file_name: &str) { let trace1 = Scatter::new(vec![1, 2, 3], vec![40, 50, 60]).name("trace1"); let trace2 = Scatter::new(vec![2, 3, 4], vec![4, 5, 6]) .name("trace2") @@ -273,15 +273,15 @@ fn two_y_axes(show: bool) -> Plot { ); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: two_y_axes // ANCHOR: multiple_axes -fn multiple_axes(show: bool) -> Plot { +fn multiple_axes(show: bool, file_name: &str) { let trace1 = Scatter::new(vec![1, 2, 3], vec![4, 5, 6]).name("trace1"); let trace2 = Scatter::new(vec![2, 3, 4], vec![40, 50, 60]) .name("trace2") @@ -332,15 +332,15 @@ fn multiple_axes(show: bool) -> Plot { ); plot.set_layout(layout); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: multiple_axes // ANCHOR: many_subplots_with_titles -fn many_subplots_with_titles(show: bool) -> Plot { +fn many_subplots_with_titles(show: bool, file_name: &str) { let trace1 = Scatter::new(vec![1, 2], vec![4, 5]); let number_of_plots = 10; @@ -378,49 +378,39 @@ fn many_subplots_with_titles(show: bool) -> Plot { plot.set_layout(layout); plot.set_configuration(Configuration::new().responsive(true)); + let path = write_example_to_html(&plot, file_name); if show { - plot.show(); + plot.show_html(path); } - plot } // ANCHOR_END: many_subplots_with_titles -fn write_example_to_html(plot: Plot, name: &str) { - std::fs::create_dir_all("./out").unwrap(); - let html = plot.to_inline_html(Some(name)); - std::fs::write(format!("./out/{}.html", name), html).unwrap(); +fn write_example_to_html(plot: &Plot, name: &str) -> String { + std::fs::create_dir_all("./output").unwrap(); + let path = format!("./output/{}.html", name); + plot.write_html(&path); + path } fn main() { // Change false to true on any of these lines to display the example. - // Subplots - write_example_to_html(simple_subplot(false), "simple_subplot"); - write_example_to_html( - simple_subplot_matches_x_axis(false), - "simple_subplot_matches_x_axis", - ); - write_example_to_html( - simple_subplot_matches_y_axis(false), - "simple_subplot_matches_y_axis", - ); - write_example_to_html(custom_sized_subplot(false), "custom_sized_subplot"); - write_example_to_html(multiple_subplots(false), "multiple_subplots"); - write_example_to_html(stacked_subplots(false), "stacked_subplots"); - write_example_to_html( - stacked_subplots_with_shared_x_axis(false), - "stacked_subplots_with_shared_x_axis", - ); - write_example_to_html( - multiple_custom_sized_subplots(false), - "multiple_custom_sized_subplots", - ); - write_example_to_html( - many_subplots_with_titles(false), - "many_subplots_with_titles", - ); + simple_subplot(false, "simple_subplot"); + + simple_subplot_matches_x_axis(false, "simple_subplot_matches_x_axis"); + + simple_subplot_matches_y_axis(false, "simple_subplot_matches_y_axis"); + custom_sized_subplot(false, "custom_sized_subplot"); + multiple_subplots(false, "multiple_subplots"); + stacked_subplots(false, "stacked_subplots"); + + stacked_subplots_with_shared_x_axis(false, "stacked_subplots_with_shared_x_axis"); + + multiple_custom_sized_subplots(false, "multiple_custom_sized_subplots"); + + many_subplots_with_titles(false, "many_subplots_with_titles"); // Multiple Axes - write_example_to_html(two_y_axes(false), "two_y_axes"); - write_example_to_html(multiple_axes(false), "multiple_axes"); + two_y_axes(false, "two_y_axes"); + multiple_axes(false, "multiple_axes"); } diff --git a/examples/wasm-yew-callback-minimal/README.md b/examples/wasm-yew-callback-minimal/README.md deleted file mode 100644 index a62a6681..00000000 --- a/examples/wasm-yew-callback-minimal/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Wasm Yew Minimal - -## Prerequisites - -1. Install [Trunk](https://trunkrs.dev/) using `cargo install --locked trunk`. - -## How to Run - -1. Run `trunk serve --open` in this directory to build and serve the application, opening the default web browser automatically. \ No newline at end of file diff --git a/examples/wasm-yew-minimal/README.md b/examples/wasm-yew-minimal/README.md deleted file mode 100644 index a62a6681..00000000 --- a/examples/wasm-yew-minimal/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Wasm Yew Minimal - -## Prerequisites - -1. Install [Trunk](https://trunkrs.dev/) using `cargo install --locked trunk`. - -## How to Run - -1. Run `trunk serve --open` in this directory to build and serve the application, opening the default web browser automatically. \ No newline at end of file diff --git a/examples/wasm-yew/Cargo.toml b/examples/wasm-yew/Cargo.toml new file mode 100644 index 00000000..8ced70a8 --- /dev/null +++ b/examples/wasm-yew/Cargo.toml @@ -0,0 +1,4 @@ +[workspace] +members = ["*"] +resolver = "2" +exclude = ["target"] diff --git a/examples/wasm-yew/README.md b/examples/wasm-yew/README.md new file mode 100644 index 00000000..e4757d4c --- /dev/null +++ b/examples/wasm-yew/README.md @@ -0,0 +1,10 @@ +# Wasm Yew Examples + +## Prerequisites + +1. Install [Trunk](https://trunkrs.dev/) using `cargo install --locked trunk`. + +## How to Run + +1. `cd` into one of the examples and run `trunk serve --open` to build and serve the application example, which will also open the default web browser automatically. + diff --git a/examples/wasm-yew-minimal/Cargo.toml b/examples/wasm-yew/basic/Cargo.toml similarity index 87% rename from examples/wasm-yew-minimal/Cargo.toml rename to examples/wasm-yew/basic/Cargo.toml index 7a094e75..1fa328d2 100644 --- a/examples/wasm-yew-minimal/Cargo.toml +++ b/examples/wasm-yew/basic/Cargo.toml @@ -8,7 +8,7 @@ authors = [ edition = "2021" [dependencies] -plotly = { path = "../../plotly" } +plotly = { path = "../../../plotly" } yew = "0.21" yew-hooks = "0.3" log = "0.4" diff --git a/examples/wasm-yew-callback-minimal/index.html b/examples/wasm-yew/basic/index.html similarity index 100% rename from examples/wasm-yew-callback-minimal/index.html rename to examples/wasm-yew/basic/index.html diff --git a/examples/wasm-yew-minimal/src/main.rs b/examples/wasm-yew/basic/src/main.rs similarity index 100% rename from examples/wasm-yew-minimal/src/main.rs rename to examples/wasm-yew/basic/src/main.rs diff --git a/examples/wasm-yew-callback-minimal/Cargo.toml b/examples/wasm-yew/callback-example/Cargo.toml similarity index 83% rename from examples/wasm-yew-callback-minimal/Cargo.toml rename to examples/wasm-yew/callback-example/Cargo.toml index 12f22f6d..c967a896 100644 --- a/examples/wasm-yew-callback-minimal/Cargo.toml +++ b/examples/wasm-yew/callback-example/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] -plotly = { path = "../../plotly" } +plotly = { path = "../../../plotly" } yew = "0.21" yew-hooks = "0.3" log = "0.4" diff --git a/examples/wasm-yew-minimal/index.html b/examples/wasm-yew/callback-example/index.html similarity index 100% rename from examples/wasm-yew-minimal/index.html rename to examples/wasm-yew/callback-example/index.html diff --git a/examples/wasm-yew-callback-minimal/src/main.rs b/examples/wasm-yew/callback-example/src/main.rs similarity index 97% rename from examples/wasm-yew-callback-minimal/src/main.rs rename to examples/wasm-yew/callback-example/src/main.rs index d2e4a04b..8ba13133 100644 --- a/examples/wasm-yew-callback-minimal/src/main.rs +++ b/examples/wasm-yew/callback-example/src/main.rs @@ -10,7 +10,7 @@ pub fn plot_component() -> Html { let point_numbers = use_state(|| None::>); let point_number = use_state(|| None::); let curve_number = use_state(|| 0usize); - let click_event = use_state(|| ClickEvent::default()); + let click_event = use_state(ClickEvent::default); let x_clone = x.clone(); let y_clone = y.clone(); diff --git a/plotly/src/plot.rs b/plotly/src/plot.rs index 67c2a737..f2ab4362 100644 --- a/plotly/src/plot.rs +++ b/plotly/src/plot.rs @@ -500,32 +500,39 @@ impl Plot { } fn offline_js_sources() -> String { - let local_plotly_js = include_str!("../templates/plotly.min.js"); - let local_tex_mml_js = include_str!("../templates/tex-mml-chtml-3.2.0.js"); + // tex-mml-chtml conflicts with tex-svg when generating Latex Titles + // let local_tex_mml_js = include_str!("../templates/tex-mml-chtml-3.2.0.js"); let local_tex_svg_js = include_str!("../templates/tex-svg-3.2.2.js"); + let local_plotly_js = include_str!("../templates/plotly.min.js"); + format!( "\n \n \n", - local_plotly_js, local_tex_mml_js, local_tex_svg_js + \n + ", + local_plotly_js, local_tex_svg_js, "" ) .to_string() } fn online_cdn_js() -> String { - r##" - - + // tex-mml-chtml conflicts with tex-svg when generating Latex Titles + // r##" + // + // + // "## + r##" + "## .to_string() } From d8cf5154c991fb798ba90002f0544b8b727ba1b7 Mon Sep 17 00:00:00 2001 From: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> Date: Tue, 3 Jun 2025 23:02:45 +0200 Subject: [PATCH 28/83] split layout into multiple modules Signed-off-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> --- plotly/src/layout/annotation.rs | 420 ++++++++++++++++++++++++++++++++ plotly/src/layout/mod.rs | 395 +----------------------------- 2 files changed, 424 insertions(+), 391 deletions(-) create mode 100644 plotly/src/layout/annotation.rs diff --git a/plotly/src/layout/annotation.rs b/plotly/src/layout/annotation.rs new file mode 100644 index 00000000..6ef0bb5d --- /dev/null +++ b/plotly/src/layout/annotation.rs @@ -0,0 +1,420 @@ +use plotly_derive::FieldSetter; +use serde::{Serialize, Serializer}; + +use crate::color::Color; +use crate::common::{Anchor, Font, Label}; +use crate::private::NumOrString; + +#[derive(Serialize, Debug, Clone)] +#[serde(rename_all = "lowercase")] +pub enum VAlign { + Top, + Middle, + Bottom, +} + +#[derive(Serialize, Debug, Clone)] +#[serde(rename_all = "lowercase")] +pub enum HAlign { + Left, + Center, + Right, +} + +#[derive(Serialize, Debug, Clone)] +#[serde(rename_all = "lowercase")] +pub enum ArrowSide { + End, + Start, + #[serde(rename = "end+start")] + StartEnd, + None, +} + +#[derive(Debug, Clone)] +pub enum ClickToShow { + False, + OnOff, + OnOut, +} + +impl Serialize for ClickToShow { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match *self { + Self::False => serializer.serialize_bool(false), + Self::OnOff => serializer.serialize_str("onoff"), + Self::OnOut => serializer.serialize_str("onout"), + } + } +} + +#[serde_with::skip_serializing_none] +#[derive(Serialize, Debug, Clone, FieldSetter)] +pub struct Annotation { + /// Determines whether or not this annotation is visible. + visible: Option, + /// Sets the text associated with this annotation. Plotly uses a subset of + /// HTML tags to do things like newline (
), bold (), italics + /// (), hyperlinks (). Tags , , + /// are also supported. + text: Option, + /// Sets the angle at which the `text` is drawn with respect to the + /// horizontal. + #[serde(rename = "textangle")] + text_angle: Option, + /// Sets the annotation text font. + font: Option, + /// Sets an explicit width for the text box. null (default) lets the text + /// set the box width. Wider text will be clipped. There is no automatic + /// wrapping; use
to start a new line. + width: Option, + /// Sets an explicit height for the text box. null (default) lets the text + /// set the box height. Taller text will be clipped. + height: Option, + /// Sets the opacity of the annotation (text + arrow). + opacity: Option, + /// Sets the horizontal alignment of the `text` within the box. Has an + /// effect only if `text` spans two or more lines (i.e. `text` contains + /// one or more
HTML tags) or if an explicit width is set to + /// override the text width. + align: Option, + /// Sets the vertical alignment of the `text` within the box. Has an effect + /// only if an explicit height is set to override the text height. + valign: Option, + /// Sets the background color of the annotation. + #[serde(rename = "bgcolor")] + background_color: Option>, + /// Sets the color of the border enclosing the annotation `text`. + #[serde(rename = "bordercolor")] + border_color: Option>, + /// Sets the padding (in px) between the `text` and the enclosing border. + #[serde(rename = "borderpad")] + border_pad: Option, + /// Sets the width (in px) of the border enclosing the annotation `text`. + #[serde(rename = "borderwidth")] + border_width: Option, + /// Determines whether or not the annotation is drawn with an arrow. If + /// "True", `text` is placed near the arrow's tail. If "False", `text` + /// lines up with the `x` and `y` provided. + #[serde(rename = "showarrow")] + show_arrow: Option, + /// Sets the color of the annotation arrow. + #[serde(rename = "arrowcolor")] + arrow_color: Option>, + /// Sets the end annotation arrow head style. Integer between or equal to 0 + /// and 8. + #[serde(rename = "arrowhead")] + arrow_head: Option, + /// Sets the start annotation arrow head style. Integer between or equal to + /// 0 and 8. + #[serde(rename = "startarrowhead")] + start_arrow_head: Option, + /// Sets the annotation arrow head position. + #[serde(rename = "arrowside")] + arrow_side: Option, + /// Sets the size of the end annotation arrow head, relative to + /// `arrowwidth`. A value of 1 (default) gives a head about 3x as wide + /// as the line. + #[serde(rename = "arrowsize")] + arrow_size: Option, + /// Sets the size of the start annotation arrow head, relative to + /// `arrowwidth`. A value of 1 (default) gives a head about 3x as wide + /// as the line. + #[serde(rename = "startarrowsize")] + start_arrow_size: Option, + /// Sets the width (in px) of annotation arrow line. + #[serde(rename = "arrowwidth")] + arrow_width: Option, + /// Sets a distance, in pixels, to move the end arrowhead away from the + /// position it is pointing at, for example to point at the edge of a + /// marker independent of zoom. Note that this shortens the arrow from + /// the `ax` / `ay` vector, in contrast to `xshift` / `yshift` which + /// moves everything by this amount. + #[serde(rename = "standoff")] + stand_off: Option, + /// Sets a distance, in pixels, to move the start arrowhead away from the + /// position it is pointing at, for example to point at the edge of a + /// marker independent of zoom. Note that this shortens the arrow from + /// the `ax` / `ay` vector, in contrast to `xshift` / `yshift` + /// which moves everything by this amount. + #[serde(rename = "startstandoff")] + start_stand_off: Option, + /// Sets the x component of the arrow tail about the arrow head. If `axref` + /// is `pixel`, a positive (negative) component corresponds to an arrow + /// pointing from right to left (left to right). If `axref` is an axis, + /// this is an absolute value on that axis, like `x`, NOT a + /// relative value. + ax: Option, + /// Sets the y component of the arrow tail about the arrow head. If `ayref` + /// is `pixel`, a positive (negative) component corresponds to an arrow + /// pointing from bottom to top (top to bottom). If `ayref` is an axis, + /// this is an absolute value on that axis, like `y`, NOT a + /// relative value. + ay: Option, + /// Indicates in what terms the tail of the annotation (ax,ay) is specified. + /// If `pixel`, `ax` is a relative offset in pixels from `x`. If set to + /// an x axis id (e.g. "x" or "x2"), `ax` is specified in the same terms + /// as that axis. This is useful for trendline annotations which + /// should continue to indicate the correct trend when zoomed. + #[serde(rename = "axref")] + ax_ref: Option, + /// Indicates in what terms the tail of the annotation (ax,ay) is specified. + /// If `pixel`, `ay` is a relative offset in pixels from `y`. If set to + /// a y axis id (e.g. "y" or "y2"), `ay` is specified in the same terms + /// as that axis. This is useful for trendline annotations which + /// should continue to indicate the correct trend when zoomed. + #[serde(rename = "ayref")] + ay_ref: Option, + /// Sets the annotation's x coordinate axis. If set to an x axis id (e.g. + /// "x" or "x2"), the `x` position refers to an x coordinate If set to + /// "paper", the `x` position refers to the distance from the left side + /// of the plotting area in normalized coordinates where 0 (1) + /// corresponds to the left (right) side. + #[serde(rename = "xref")] + x_ref: Option, + /// Sets the annotation's x position. If the axis `type` is "log", then you + /// must take the log of your desired range. If the axis `type` is + /// "date", it should be date strings, like date data, though Date + /// objects and unix milliseconds will be accepted and converted to strings. + /// If the axis `type` is "category", it should be numbers, using the scale + /// where each category is assigned a serial number from zero in the + /// order it appears. + x: Option, + /// Sets the text box's horizontal position anchor This anchor binds the `x` + /// position to the "left", "center" or "right" of the annotation. For + /// example, if `x` is set to 1, `xref` to "paper" and `xanchor` to + /// "right" then the right-most portion of the annotation lines up with + /// the right-most edge of the plotting area. If "auto", the anchor is + /// equivalent to "center" for data-referenced annotations or if there + /// is an arrow, whereas for paper-referenced with no arrow, the anchor + /// picked corresponds to the closest side. + #[serde(rename = "xanchor")] + x_anchor: Option, + /// Shifts the position of the whole annotation and arrow to the right + /// (positive) or left (negative) by this many pixels. + #[serde(rename = "xshift")] + x_shift: Option, + /// Sets the annotation's y coordinate axis. If set to an y axis id (e.g. + /// "y" or "y2"), the `y` position refers to an y coordinate If set to + /// "paper", the `y` position refers to the distance from the bottom of + /// the plotting area in normalized coordinates where 0 (1) corresponds + /// to the bottom (top). + #[serde(rename = "yref")] + y_ref: Option, + /// Sets the annotation's y position. If the axis `type` is "log", then you + /// must take the log of your desired range. If the axis `type` is + /// "date", it should be date strings, like date data, though Date + /// objects and unix milliseconds will be accepted and converted to strings. + /// If the axis `type` is "category", it should be numbers, using the + /// scale where each category is assigned a serial number from zero in + /// the order it appears. + y: Option, + /// Sets the text box's vertical position anchor This anchor binds the `y` + /// position to the "top", "middle" or "bottom" of the annotation. For + /// example, if `y` is set to 1, `yref` to "paper" and `yanchor` to + /// "top" then the top-most portion of the annotation lines up with the + /// top-most edge of the plotting area. If "auto", the anchor is equivalent + /// to "middle" for data-referenced annotations or if there is an arrow, + /// whereas for paper-referenced with no arrow, the anchor picked + /// corresponds to the closest side. + #[serde(rename = "yanchor")] + y_anchor: Option, + /// Shifts the position of the whole annotation and arrow up (positive) or + /// down (negative) by this many pixels. + #[serde(rename = "yshift")] + y_shift: Option, + /// Makes this annotation respond to clicks on the plot. If you click a data + /// point that exactly matches the `x` and `y` values of this + /// annotation, and it is hidden (visible: false), it will appear. In + /// "onoff" mode, you must click the same point again to make it disappear, + /// so if you click multiple points, you can show multiple annotations. + /// In "onout" mode, a click anywhere else in the plot (on another data + /// point or not) will hide this annotation. If you need to show/hide + /// this annotation in response to different `x` or `y` values, you can set + /// `xclick` and/or `yclick`. This is useful for example to label the side + /// of a bar. To label markers though, `standoff` is preferred over + /// `xclick` and `yclick`. + #[serde(rename = "clicktoshow")] + click_to_show: Option, + /// Toggle this annotation when clicking a data point whose `x` value is + /// `xclick` rather than the annotation's `x` value. + #[serde(rename = "xclick")] + x_click: Option, + /// Toggle this annotation when clicking a data point whose `y` value is + /// `yclick` rather than the annotation's `y` value. + #[serde(rename = "yclick")] + y_click: Option, + /// Sets text to appear when hovering over this annotation. If omitted or + /// blank, no hover label will appear. + #[serde(rename = "hovertext")] + hover_text: Option, + /// Label displayed on mouse hover. + #[serde(rename = "hoverlabel")] + hover_label: Option
diff --git a/plotly/templates/plot.html b/plotly/templates/plot.html index 12198857..528bead5 100644 --- a/plotly/templates/plot.html +++ b/plotly/templates/plot.html @@ -3,12 +3,11 @@ + {{js_scripts}}
- {{js_scripts}} -
-
- + img_element.setAttribute("src", data_url); + +
+ + From 30b0367a49143abab27f8fc27f45885e616240a2 Mon Sep 17 00:00:00 2001 From: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> Date: Thu, 19 Jun 2025 18:44:15 +0100 Subject: [PATCH 43/83] adding extra themes Signed-off-by: Andrei Gherghescu <8067229+andrei-ng@users.noreply.github.com> --- examples/themes/Cargo.toml | 10 + examples/themes/assets/gapminder2007.csv | 143 ++++++++++++ examples/themes/src/main.rs | 154 +++++++++++++ plotly/src/layout/themes.rs | 278 ++++++++++++++++++++++- 4 files changed, 583 insertions(+), 2 deletions(-) create mode 100644 examples/themes/Cargo.toml create mode 100644 examples/themes/assets/gapminder2007.csv create mode 100644 examples/themes/src/main.rs diff --git a/examples/themes/Cargo.toml b/examples/themes/Cargo.toml new file mode 100644 index 00000000..421b4dbf --- /dev/null +++ b/examples/themes/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "themes" +version = "0.1.0" +authors = ["Andrei Gherghescu andrei-ng@protonmail.com"] +edition = "2021" + +[dependencies] +ndarray = "0.16" +csv = "1.1" +plotly = { path = "../../plotly" } diff --git a/examples/themes/assets/gapminder2007.csv b/examples/themes/assets/gapminder2007.csv new file mode 100644 index 00000000..044ac396 --- /dev/null +++ b/examples/themes/assets/gapminder2007.csv @@ -0,0 +1,143 @@ +country,pop,continent,lifeExp,gdpPercap +Afghanistan,31889923.0,Asia,43.828,974.5803384 +Albania,3600523.0,Europe,76.423,5937.029525999999 +Algeria,33333216.0,Africa,72.301,6223.367465 +Angola,12420476.0,Africa,42.731,4797.231267 +Argentina,40301927.0,Americas,75.32,12779.379640000001 +Australia,20434176.0,Oceania,81.235,34435.367439999995 +Austria,8199783.0,Europe,79.829,36126.4927 +Bahrain,708573.0,Asia,75.635,29796.048339999998 +Bangladesh,150448339.0,Asia,64.062,1391.253792 +Belgium,10392226.0,Europe,79.441,33692.60508 +Benin,8078314.0,Africa,56.728,1441.284873 +Bolivia,9119152.0,Americas,65.554,3822.1370840000004 +Bosnia and Herzegovina,4552198.0,Europe,74.852,7446.298803 +Botswana,1639131.0,Africa,50.728,12569.851770000001 +Brazil,190010647.0,Americas,72.39,9065.800825 +Bulgaria,7322858.0,Europe,73.005,10680.79282 +Burkina Faso,14326203.0,Africa,52.295,1217.032994 +Burundi,8390505.0,Africa,49.58,430.07069160000003 +Cambodia,14131858.0,Asia,59.723,1713.7786859999999 +Cameroon,17696293.0,Africa,50.43,2042.0952399999999 +Canada,33390141.0,Americas,80.653,36319.235010000004 +Central African Republic,4369038.0,Africa,44.74100000000001,706.016537 +Chad,10238807.0,Africa,50.651,1704.0637239999999 +Chile,16284741.0,Americas,78.553,13171.63885 +China,1318683096.0,Asia,72.961,4959.1148539999995 +Colombia,44227550.0,Americas,72.889,7006.580419 +Comoros,710960.0,Africa,65.152,986.1478792000001 +"Congo, Dem. Rep.",64606759.0,Africa,46.461999999999996,277.55185869999997 +"Congo, Rep.",3800610.0,Africa,55.321999999999996,3632.557798 +Costa Rica,4133884.0,Americas,78.782,9645.06142 +Cote d'Ivoire,18013409.0,Africa,48.328,1544.750112 +Croatia,4493312.0,Europe,75.748,14619.222719999998 +Cuba,11416987.0,Americas,78.273,8948.102923 +Czech Republic,10228744.0,Europe,76.486,22833.30851 +Denmark,5468120.0,Europe,78.332,35278.41874 +Djibouti,496374.0,Africa,54.791000000000004,2082.4815670000003 +Dominican Republic,9319622.0,Americas,72.235,6025.374752000001 +Ecuador,13755680.0,Americas,74.994,6873.262326000001 +Egypt,80264543.0,Africa,71.33800000000001,5581.180998 +El Salvador,6939688.0,Americas,71.878,5728.353514 +Equatorial Guinea,551201.0,Africa,51.57899999999999,12154.08975 +Eritrea,4906585.0,Africa,58.04,641.3695236000001 +Ethiopia,76511887.0,Africa,52.946999999999996,690.8055759 +Finland,5238460.0,Europe,79.313,33207.0844 +France,61083916.0,Europe,80.657,30470.0167 +Gabon,1454867.0,Africa,56.735,13206.48452 +Gambia,1688359.0,Africa,59.448,752.7497265 +Germany,82400996.0,Europe,79.406,32170.37442 +Ghana,22873338.0,Africa,60.022,1327.60891 +Greece,10706290.0,Europe,79.483,27538.41188 +Guatemala,12572928.0,Americas,70.259,5186.050003 +Guinea,9947814.0,Africa,56.007,942.6542111 +Guinea-Bissau,1472041.0,Africa,46.388000000000005,579.2317429999999 +Haiti,8502814.0,Americas,60.916000000000004,1201.637154 +Honduras,7483763.0,Americas,70.19800000000001,3548.3308460000003 +"Hong Kong, China",6980412.0,Asia,82.208,39724.97867 +Hungary,9956108.0,Europe,73.33800000000001,18008.94444 +Iceland,301931.0,Europe,81.757,36180.789189999996 +India,1110396331.0,Asia,64.69800000000001,2452.210407 +Indonesia,223547000.0,Asia,70.65,3540.6515640000002 +Iran,69453570.0,Asia,70.964,11605.71449 +Iraq,27499638.0,Asia,59.545,4471.061906 +Ireland,4109086.0,Europe,78.885,40675.99635 +Israel,6426679.0,Asia,80.745,25523.2771 +Italy,58147733.0,Europe,80.546,28569.7197 +Jamaica,2780132.0,Americas,72.567,7320.880262000001 +Japan,127467972.0,Asia,82.603,31656.06806 +Jordan,6053193.0,Asia,72.535,4519.461171 +Kenya,35610177.0,Africa,54.11,1463.249282 +"Korea, Dem. Rep.",23301725.0,Asia,67.297,1593.06548 +"Korea, Rep.",49044790.0,Asia,78.623,23348.139730000003 +Kuwait,2505559.0,Asia,77.58800000000001,47306.98978 +Lebanon,3921278.0,Asia,71.993,10461.05868 +Lesotho,2012649.0,Africa,42.592,1569.331442 +Liberia,3193942.0,Africa,45.678000000000004,414.5073415 +Libya,6036914.0,Africa,73.952,12057.49928 +Madagascar,19167654.0,Africa,59.443000000000005,1044.770126 +Malawi,13327079.0,Africa,48.303000000000004,759.3499101 +Malaysia,24821286.0,Asia,74.241,12451.6558 +Mali,12031795.0,Africa,54.467,1042.581557 +Mauritania,3270065.0,Africa,64.164,1803.1514960000002 +Mauritius,1250882.0,Africa,72.801,10956.99112 +Mexico,108700891.0,Americas,76.195,11977.57496 +Mongolia,2874127.0,Asia,66.803,3095.7722710000003 +Montenegro,684736.0,Europe,74.543,9253.896111 +Morocco,33757175.0,Africa,71.164,3820.17523 +Mozambique,19951656.0,Africa,42.082,823.6856205 +Myanmar,47761980.0,Asia,62.068999999999996,944.0 +Namibia,2055080.0,Africa,52.906000000000006,4811.060429 +Nepal,28901790.0,Asia,63.785,1091.359778 +Netherlands,16570613.0,Europe,79.762,36797.93332 +New Zealand,4115771.0,Oceania,80.204,25185.00911 +Nicaragua,5675356.0,Americas,72.899,2749.320965 +Niger,12894865.0,Africa,56.867,619.6768923999999 +Nigeria,135031164.0,Africa,46.858999999999995,2013.9773050000001 +Norway,4627926.0,Europe,80.196,49357.19017 +Oman,3204897.0,Asia,75.64,22316.19287 +Pakistan,169270617.0,Asia,65.483,2605.94758 +Panama,3242173.0,Americas,75.53699999999999,9809.185636 +Paraguay,6667147.0,Americas,71.752,4172.838464 +Peru,28674757.0,Americas,71.421,7408.905561 +Philippines,91077287.0,Asia,71.688,3190.481016 +Poland,38518241.0,Europe,75.563,15389.924680000002 +Portugal,10642836.0,Europe,78.098,20509.64777 +Puerto Rico,3942491.0,Americas,78.74600000000001,19328.70901 +Reunion,798094.0,Africa,76.442,7670.122558 +Romania,22276056.0,Europe,72.476,10808.47561 +Rwanda,8860588.0,Africa,46.242,863.0884639000001 +Sao Tome and Principe,199579.0,Africa,65.528,1598.435089 +Saudi Arabia,27601038.0,Asia,72.777,21654.83194 +Senegal,12267493.0,Africa,63.062,1712.4721359999999 +Serbia,10150265.0,Europe,74.002,9786.534714 +Sierra Leone,6144562.0,Africa,42.568000000000005,862.5407561000001 +Singapore,4553009.0,Asia,79.972,47143.179639999995 +Slovak Republic,5447502.0,Europe,74.663,18678.31435 +Slovenia,2009245.0,Europe,77.926,25768.25759 +Somalia,9118773.0,Africa,48.159,926.1410683 +South Africa,43997828.0,Africa,49.339,9269.657808 +Spain,40448191.0,Europe,80.941,28821.0637 +Sri Lanka,20378239.0,Asia,72.396,3970.0954070000003 +Sudan,42292929.0,Africa,58.556000000000004,2602.394995 +Swaziland,1133066.0,Africa,39.613,4513.480643 +Sweden,9031088.0,Europe,80.884,33859.74835 +Switzerland,7554661.0,Europe,81.70100000000001,37506.419069999996 +Syria,19314747.0,Asia,74.143,4184.548089 +Taiwan,23174294.0,Asia,78.4,28718.27684 +Tanzania,38139640.0,Africa,52.516999999999996,1107.482182 +Thailand,65068149.0,Asia,70.616,7458.3963269999995 +Togo,5701579.0,Africa,58.42,882.9699437999999 +Trinidad and Tobago,1056608.0,Americas,69.819,18008.50924 +Tunisia,10276158.0,Africa,73.923,7092.923025 +Turkey,71158647.0,Europe,71.777,8458.276384 +Uganda,29170398.0,Africa,51.542,1056.3801210000001 +United Kingdom,60776238.0,Europe,79.425,33203.26128 +United States,301139947.0,Americas,78.242,42951.65309 +Uruguay,3447496.0,Americas,76.384,10611.46299 +Venezuela,26084662.0,Americas,73.747,11415.805690000001 +Vietnam,85262356.0,Asia,74.249,2441.576404 +West Bank and Gaza,4018332.0,Asia,73.422,3025.349798 +"Yemen, Rep.",22211743.0,Asia,62.698,2280.769906 +Zambia,11746035.0,Africa,42.38399999999999,1271.211593 +Zimbabwe,12311143.0,Africa,43.486999999999995,469.70929810000007 diff --git a/examples/themes/src/main.rs b/examples/themes/src/main.rs new file mode 100644 index 00000000..f185e332 --- /dev/null +++ b/examples/themes/src/main.rs @@ -0,0 +1,154 @@ +#![allow(dead_code)] + +use plotly::{ + common::{Marker, Mode, Title}, + layout::Layout, + Plot, Scatter, +}; + +use plotly::layout::themes::BuiltinTheme; + +use std::fs::File; +use std::io::BufReader; + +use csv::ReaderBuilder; + +// Read Gapminder 2007 data from CSV +fn read_gapminder_data_from_csv() -> (Vec, Vec, Vec, Vec, Vec) { + let file = File::open("assets/gapminder2007.csv").expect("Cannot open gapminder2007.csv"); + let mut rdr = ReaderBuilder::new().has_headers(true).from_reader(BufReader::new(file)); + let mut gdp_per_capita = Vec::new(); + let mut life_expectancy = Vec::new(); + let mut population = Vec::new(); + let mut continents = Vec::new(); + let mut countries = Vec::new(); + for result in rdr.records() { + let record = result.expect("CSV record error"); + countries.push(record[0].to_string()); + population.push(record[1].parse::().unwrap_or(0.0)); + continents.push(record[2].to_string()); + life_expectancy.push(record[3].parse::().unwrap_or(0.0)); + gdp_per_capita.push(record[4].parse::().unwrap_or(0.0)); + } + (gdp_per_capita, life_expectancy, population, continents, countries) +} + +fn create_gapminder_scatter_plot(theme: BuiltinTheme, show: bool) { + let (gdp, life_exp, pop, continents, countries) = read_gapminder_data_from_csv(); + + let mut plot = Plot::new(); + let continent_colors = vec![ + ("Asia", "#1f77b4"), + ("Europe", "#ff7f0e"), + ("Africa", "#2ca02c"), + ("Americas", "#d62728"), + ("Oceania", "#9467bd"), + ]; + + for (continent, color) in &continent_colors { + let indices: Vec = continents + .iter() + .enumerate() + .filter(|(_, c)| c == continent) + .map(|(idx, _)| idx) + .collect(); + if indices.is_empty() { + continue; + } + let continent_gdp: Vec = indices.iter().map(|&idx| gdp[idx]).collect(); + let continent_life: Vec = indices.iter().map(|&idx| life_exp[idx]).collect(); + let continent_pop: Vec = indices.iter().map(|&idx| pop[idx]).collect(); + let continent_countries: Vec = indices.iter().map(|&idx| countries[idx].clone()).collect(); + let trace = Scatter::new(continent_gdp, continent_life) + .mode(Mode::Markers) + .name(continent.to_string()) + .text_array(continent_countries) + .marker( + Marker::new() + .size_array( + continent_pop + .iter() + .map(|&p| ((p / 1_000_000.0).min(60.0)) as usize) + .collect(), + ) + .color(*color) + .opacity(0.6) + .line(plotly::common::Line::new().width(1.0).color("white")), + ); + plot.add_trace(trace); + } + + let theme_template = theme.build(); + plot.set_layout( + Layout::new() + .template(theme_template) + .title(Title::from(format!( + "Gapminder 2007: '{}' theme", + theme_name(theme) + ))) + .x_axis( + plotly::layout::Axis::new() + .title("GDP per capita (log scale)") + .type_(plotly::layout::AxisType::Log), + ) + .y_axis(plotly::layout::Axis::new().title("Life Expectancy")) + .width(800) + .height(600) + .show_legend(true), + ); + + let path = write_example_to_html( + &plot, + &format!("gapminder_{}", theme_name(theme).to_lowercase()), + ); + if show { + plot.show_html(path); + } +} + +fn theme_name(theme: BuiltinTheme) -> &'static str { + match theme { + BuiltinTheme::Default => "plotly", + BuiltinTheme::PlotlyWhite => "plotly_white", + BuiltinTheme::PlotlyDark => "plotly_dark", + BuiltinTheme::Seaborn => "seaborn", + BuiltinTheme::SeabornWhitegrid => "seaborn_whitegrid", + BuiltinTheme::SeabornDark => "seaborn_dark", + BuiltinTheme::Matplotlib => "matplotlib", + BuiltinTheme::Plotnine => "plotnine", + } +} + +fn write_example_to_html(plot: &Plot, name: &str) -> String { + std::fs::create_dir_all("./output").unwrap(); + // Write inline HTML + let html = plot.to_inline_html(Some(name)); + let path = format!("./output/inline_{}.html", name); + std::fs::write(path, html).unwrap(); + // Write standalone HTML + let path = format!("./output/{}.html", name); + plot.write_html(&path); + path +} + +fn main() { + // Create Gapminder-style plots with different themes, matching the Plotly documentation + // Based on: https://plotly.com/python/templates/ + + // Create plots for each theme + let themes = vec![ + BuiltinTheme::Default, + BuiltinTheme::PlotlyWhite, + BuiltinTheme::PlotlyDark, + BuiltinTheme::Seaborn, + BuiltinTheme::Matplotlib, + BuiltinTheme::Plotnine, + ]; + + for theme in themes { + create_gapminder_scatter_plot(theme, false); + } + + // Show one example (the default theme) + create_gapminder_scatter_plot(BuiltinTheme::Default, true); +} diff --git a/plotly/src/layout/themes.rs b/plotly/src/layout/themes.rs index 6d010295..ec08c056 100644 --- a/plotly/src/layout/themes.rs +++ b/plotly/src/layout/themes.rs @@ -2,7 +2,7 @@ use once_cell::sync::Lazy; use crate::{ common::{ColorBar, ColorScale, ColorScaleElement, Font, Label, Title}, - layout::{Axis, ColorAxis, HoverMode, LayoutColorScale, LayoutTemplate, Template}, + layout::{Axis, ColorAxis, HoverMode, LayoutColorScale, LayoutTemplate, Template, TicksDirection}, }; pub static DEFAULT: Lazy