Document not found (404)
+This URL is invalid, sorry. Please use the navigation bar or search to continue.
+ +diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index 2e07606d..00000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[target.wasm32-unknown-unknown] -rustflags = ['--cfg', 'getrandom_backend="wasm_js"'] diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index e69de29b..00000000 diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 83bcd989..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,7 +0,0 @@ -version: 2 - -updates: - - package-ecosystem: cargo - directory: / - schedule: - interval: daily diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml deleted file mode 100644 index b08ad106..00000000 --- a/.github/workflows/book.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Book - -on: - workflow_dispatch: - push: - tags: - - '[0-9]+.[0-9]+.[0-9]+' - -env: - RUST_BACKTRACE: full - -jobs: - build: - name: Build and deploy book - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - run: cargo install mdbook --no-default-features --features search --vers "^0.4" --locked --quiet - - name: Build examples - run: | - cd ${{ github.workspace }}/examples/basic_charts && cargo run - cd ${{ github.workspace }}/examples/statistical_charts && cargo run - cd ${{ github.workspace }}/examples/scientific_charts && cargo run - cd ${{ github.workspace }}/examples/financial_charts && cargo run - cd ${{ github.workspace }}/examples/3d_charts && cargo run - cd ${{ github.workspace }}/examples/subplots && cargo run - cd ${{ github.workspace }}/examples/shapes && cargo run - - run: mdbook build docs/book - - name: Checkout gh-pages branch - run: | - git fetch origin gh-pages:gh-pages - git checkout gh-pages - - name: Overwrite book content - run: | - rm -rf content - cp -r gh-pages/content . - - 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 }}" - else - git commit --allow-empty -m 'update book from commit ${{ github.sha }}' - fi - git push origin gh-pages \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index c52e2db9..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,144 +0,0 @@ -name: CI - -on: - workflow_dispatch: - pull_request: - branches: [ main ] - push: - branches: [ main ] - -# Cancel any in-flight jobs for the same PR/branch so there's only one active -# at a time -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -env: - RUST_BACKTRACE: full - -jobs: - rustfmt: - name: Rustfmt - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@nightly - with: - components: rustfmt - - run: cargo fmt --all -- --check - - run: cd ${{ github.workspace }}/examples && cargo fmt --all -- --check - - clippy: - name: Clippy - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - with: - components: clippy - targets: wasm32-unknown-unknown - # 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/wasm-yew && cargo clippy --target wasm32-unknown-unknown --all - - semver: - name: semver - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Check semver - uses: obi1kenobi/cargo-semver-checks-action@v2 - with: - package: plotly - feature-group: only-explicit-features - features: kaleido - rust-toolchain: stable - release-type: minor - - test: - name: Tests - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - run: cargo test --features plotly_ndarray,plotly_image,kaleido - - if: ${{ matrix.os == 'windows-latest' }} - run: gci -recurse -filter "*example*" - - code-coverage: - name: Code Coverage - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - with: - components: llvm-tools-preview - - uses: taiki-e/install-action@cargo-llvm-cov - - uses: codecov/codecov-action@v3 - - build_examples: - name: Build Examples - strategy: - fail-fast: false - matrix: - example: # missing jupyter - [ - 3d_charts, - basic_charts, - custom_controls, - financial_charts, - images, - kaleido, - maps, - ndarray, - scientific_charts, - shapes, - subplots, - ] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - run: cd ${{ github.workspace }}/examples/${{ matrix.example }} && cargo build - - build_wasm_examples: - name: Build Wasm Examples - strategy: - fail-fast: false - matrix: - 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/wasm-yew/${{ matrix.example }} && cargo build --target wasm32-unknown-unknown - - build_book: - name: Build Book - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - run: cargo install mdbook --no-default-features --features search --vers "^0.4" --locked --quiet - - name: Build examples to generate needed html files - run: | - cd ${{ github.workspace }}/examples/basic_charts && cargo run - cd ${{ github.workspace }}/examples/statistical_charts && cargo run - cd ${{ github.workspace }}/examples/scientific_charts && cargo run - cd ${{ github.workspace }}/examples/financial_charts && cargo run - cd ${{ github.workspace }}/examples/3d_charts && cargo run - cd ${{ github.workspace }}/examples/subplots && cargo run - cd ${{ github.workspace }}/examples/shapes && cargo run - - name: Build book - run: mdbook build docs/book \ No newline at end of file diff --git a/.github/workflows/publish_plotly.yml b/.github/workflows/publish_plotly.yml deleted file mode 100644 index 0db2a789..00000000 --- a/.github/workflows/publish_plotly.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Publish plotly - -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 diff --git a/.github/workflows/publish_plotly_derive.yml b/.github/workflows/publish_plotly_derive.yml deleted file mode 100644 index 14df2f0f..00000000 --- a/.github/workflows/publish_plotly_derive.yml +++ /dev/null @@ -1,16 +0,0 @@ -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 deleted file mode 100644 index 6c68c821..00000000 --- a/.github/workflows/publish_plotly_kaleido.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Publish plotly-kaleido - -on: - workflow_dispatch: - -jobs: - create-crates-io-release: - name: Deploy to crates.io - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - run: cargo login ${{ env.CRATES_IO_TOKEN }} - env: - CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} - - run: cargo publish --allow-dirty -p plotly_kaleido \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 25ffd779..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Publish all - -on: - 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_derive - - run: sleep 10 - - run: cargo publish --allow-dirty -p plotly_kaleido - - run: sleep 10 - - 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/.gitignore b/.gitignore deleted file mode 100644 index 62bcbed4..00000000 --- a/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -target/ -.idea -Cargo.lock -gh-pages/ -Untitled* -.ipynb_checkpoints/ -.DS_Store -.vscode -dist/ -out.* \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 397f35ff..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,273 +0,0 @@ -# Changelog -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-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 -- [[#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 -- [[#291]](https://github.com/plotly/plotly.rs/pull/291) Remove `--disable-gpu` flag for Kaleido static-image generation for all targets. -- [[#299]](https://github.com/plotly/plotly.rs/pull/299) Added customdata field to HeatMap -- [[#303]](https://github.com/plotly/plotly.rs/pull/303) Split layout mod.rs into modules -- [[#304]](https://github.com/plotly/plotly.rs/pull/304) Refactored examples to allow fo generation of full html files - -### Fixed -- [[#284](https://github.com/plotly/plotly.rs/pull/284)] Allow plotly package to be compiled for android -- [[#298](https://github.com/plotly/plotly.rs/pull/298)] Added support for layout axis scaleratio -- [[#301](https://github.com/plotly/plotly.rs/pull/301)] Added ScatterGeo trace and LayoutGeo support - -## [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 -- [[#261](https://github.com/plotly/plotly.rs/pull/261)] Updated code of conduct - -### Fixed -- [[#265](https://github.com/plotly/plotly.rs/pull/265)] Add Pie Chart trace -- [[#264](https://github.com/plotly/plotly.rs/issues/264)] Derive Deserialize on NamedColor, Rgb and Rgba -- [[#216](https://github.com/plotly/plotly.rs/issues/216)] Opt out of downloading Kaleido binaries and allow users to set Kaleido path via environment variable -- [[#259](https://github.com/plotly/plotly.rs/issues/259)] Mesh3d::new() has wrong signature -- [[#175](https://github.com/plotly/plotly.rs/issues/175)] Put multiple subplots in the same html - added an example using `build_html` crate. -- [[#228](https://github.com/plotly/plotly.rs/issues/228)] Redraw function seems to be broken - added example on generating responsive plots. - -## [0.11.0] - 2024-12-06 -### Changed -- [[#251](https://github.com/plotly/plotly.rs/pull/251)] Expose image data as String with `to_base64` and `to_svg` using Kaleido -- [[#245](https://github.com/plotly/plotly.rs/pull/245)] Change Contours size to be `f64` instead of `usize` -- [[#243](https://github.com/plotly/plotly.rs/pull/243)] Made `plotly_embed_js` embed all JS scripts when enabled. - Renamed `use_cdn_plotly` to `use_cdn_js`. - -### Fixed -- [[#248](https://github.com/plotly/plotly.rs/issues/248)] Book recipes do not render graphs -- [[#247](https://github.com/plotly/plotly.rs/issues/247)] Add function to export image (with Kaleido) as a b64 string -- [[#246](https://github.com/plotly/plotly.rs/pull/246)] Expose pattern fill api for histograms and bar charts -- [[#244](https://github.com/plotly/plotly.rs/pull/244)] Fix swapped x and y in the examples. -- [[#242](https://github.com/plotly/plotly.rs/issues/242)] Disable request for tex-svg.js file -- [[#237](https://github.com/plotly/plotly.rs/issues/237)] Add Categorical Axis ordering. - -## [0.10.0] - 2024-09-16 -### Added -- [[#231](https://github.com/plotly/plotly.rs/pull/231)] Added new `plotly_embed_js` feature to reduce binary sizes by not embedding `plotly.min.js` in the library unless explicitly enabled via the feature flag. Deprecates `use_local_plotly` in favor of explicit opt-in via the feature flag and introduce method `use_cdn_plotly` to allow users to use CDN version even behind the `plotly_embed_js` feature flag. - -### Fixed -- [[#230](https://github.com/plotly/plotly.rs/pull/230)] Make Bar chart `width` and `offset` use `f64` values. - -## [0.9.1] - 2024-09-06 -### Added -- [[#217](https://github.com/plotly/plotly.rs/pull/217)] Added show_html(filename) method to bypass situations where accessing default `/tmp` is not possible, e.g., with in SNAP Firefox -- [[#227](https://github.com/plotly/plotly.rs/pull/227)] Switch from HTML template render from `askama` to `rinja` - -### Fixed -- [[#232](https://github.com/plotly/plotly.rs/pull/232)] Generalize OS detection in the context of opening HTML pages with default app via `xdg-open` -- [[#233](https://github.com/plotly/plotly.rs/pull/233)] Fix mdBook code examples - -## [0.9.0] - 2024-06-29 -### Added -- [[#153](https://github.com/plotly/plotly.rs/pull/153)] Added `LayoutScene`. -- [[#154](https://github.com/plotly/plotly.rs/pull/154)] Improve ergonomics of `Title` and `LegendGroupTitle` structs: `new` method now takes no arguments as per other structs, whilst a new `with_text()` constructor is added for convenience. Where other structs contain a `Title` (and `LegendGroupTitle`), users can now call the `title()` (and `legend_group_title()`) method with anything that `impl`s `Into
Plotly for Rust
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
This URL is invalid, sorry. Please use the navigation bar or search to continue.
+ +As of version 0.7.0
, Plotly.rs has native support for the EvCxR Jupyter Kernel.
Once you've installed the required packages you'll be able to run all the examples shown here as well as all the recipes in Jupyter Lab!
+It is assumed that an installation of the Anaconda Python distribution is already present in the system. If that is not the case you can follow these instructions to get up and running with Anaconda
.
conda install -c plotly plotly=4.9.0
+conda install jupyterlab "ipywidgets=7.5"
+
+optionally (or instead of jupyterlab
) you can also install Jupyter Notebook:
conda install notebook
+
+Although there are alternative methods to enable support for the EvCxR Jupyter Kernel, we have elected to keep the requirements consistent with what those of other languages, e.g. Julia, Python and R. This way users know what to expect; and also the folks at Plotly have done already most of the heavy lifting to create an extension for Jupyter Lab that works very well.
+Run the following to install the Plotly Jupyter Lab extension:
+jupyter labextension install jupyterlab-plotly@4.9.0
+
+Once this step is complete to make sure the installation so far was successful, run the following command:
+jupyter lab
+
+Open a Python 3
kernel copy/paste the following code in a cell and run it:
import plotly.graph_objects as go
+fig = go.Figure(data=go.Bar(x=['a', 'b', 'c'], y=[11, 22, 33]))
+fig.show()
+
+You should see the following figure:
+ + +Next you need to install the EvCxR Jupyter Kernel. Note that EvCxR requires CMake as it has to compile ZMQ. If CMake is already installed on your system and is in your path (to test that simply run cmake --version
if that returns a version you're good to go) then continue to the next steps.
In a command line execute the following commands:
+cargo install evcxr_jupyter
+evcxr_jupyter --install
+
+If you're not familiar with the EvCxR kernel it would be good that you at least glance over the EvCxR Jupyter Tour.
+Launch Jupyter Lab:
+jupyter lab
+
+create a new notebook and select the Rust
kernel. Then create the following three cells and execute them in order:
:dep ndarray = "0.15.6"
+:dep plotly = { version = ">=0.7.0" }
+
++#![allow(unused)] +fn main() { +extern crate ndarray; +extern crate plotly; +extern crate rand_distr; +}
+#![allow(unused)] +fn main() { +use ndarray::Array; +use plotly::common::Mode; +use plotly::layout::{Layout}; +use plotly::{Plot, Scatter}; +use rand_distr::{num_traits::Float, Distribution}; +}
Now we're ready to start plotting!
++#![allow(unused)] +fn main() { +let x0 = Array::linspace(1.0, 3.0, 200).into_raw_vec(); +let y0 = x0.iter().map(|v| *v * (v.powf(2.)).sin() + 1.).collect(); + +let trace = Scatter::new(x0, y0); +let mut plot = Plot::new(); +plot.add_trace(trace); + +let layout = Layout::new().height(525); +plot.set_layout(layout); + +plot.lab_display(); +format!("EVCXR_BEGIN_CONTENT application/vnd.plotly.v1+json\n{}\nEVCXR_END_CONTENT", plot.to_json()) +}
For Jupyter Lab there are two ways to display a plot in the EvCxR
kernel, either have the plot object be in the last line without a semicolon or directly invoke the Plot::lab_display
method on it; both have the same result. You can also find an example notebook here that will periodically be updated with examples.
The process for Jupyter Notebook is very much the same with one exception; the Plot::notebook_display
method must be used to display the plot. You can find an example notebook here
ndarray
SupportTo enable ndarray support in Plotly.rs add the following feature to your Cargo.toml
file:
[dependencies]
+plotly = { version = ">=0.7.0", features = ["plotly_ndarray"] }
+
+This extends the Plotly.rs API in two ways:
+Scatter
traces can now be created using the Scatter::from_ndarray
constructor,Scatter::to_traces
method.The full source code for the examples below can be found here.
+ndarray
TracesThe following imports have been used to produce the plots below:
++#![allow(unused)] +fn main() { +use plotly::common::{Mode}; +use plotly::{Plot, Scatter}; +use ndarray::{Array, Ix1, Ix2}; +use plotly::ndarray::ArrayTraces; +}
+ + +#![allow(unused)] +fn main() { +fn single_ndarray_trace(show: bool) { + let n: usize = 11; + let t: Array<f64, Ix1> = Array::range(0., 10., 10. / n as f64); + let ys: Array<f64, Ix1> = t.iter().map(|v| (*v).powf(2.)).collect(); + + let trace = Scatter::from_array(t, ys).mode(Mode::LinesMarkers); + + let mut plot = Plot::new(); + plot.add_trace(trace); + if show { + plot.show(); + } + println!("{}", plot.to_inline_html(Some("single_ndarray_trace"))); +} +}
To display a 2D
array (Array<_, Ix2>
) you can use the Scatter::to_traces
method. The first argument of the method represents the common axis for the traces (x
axis) whilst the second argument contains a collection of traces. At this point it should be noted that there is some ambiguity when passing a 2D
array; namely are the traces arranged along the columns or the rows of the matrix? This ambiguity is resolved by the third argument of the Scatter::to_traces
method. If that argument is set to ArrayTraces::OverColumns
then the library assumes that every column represents an individual trace, alternatively if this is set to ArrayTraces::OverRows
the assumption is that every row represents a trace.
To illustrate this distinction consider the following examples:
++ + +#![allow(unused)] +fn main() { +fn multiple_ndarray_traces_over_columns(show: bool) { + let n: usize = 11; + let t: Array<f64, Ix1> = Array::range(0., 10., 10. / n as f64); + let mut ys: Array<f64, Ix2> = Array::zeros((11, 11)); + let mut count = 0.; + for mut row in ys.columns_mut() { + for index in 0..row.len() { + row[index] = count + (index as f64).powf(2.); + } + count += 1.; + } + + let traces = + Scatter::default() + .mode(Mode::LinesMarkers) + .to_traces(t, ys, ArrayTraces::OverColumns); + + let mut plot = Plot::new(); + plot.add_traces(traces); + if show { + plot.show(); + } + println!("{}", plot.to_inline_html(Some("multiple_ndarray_traces_over_columns"))); +} +}
Replacing ArrayTraces::OverColumns
with ArrayTraces::OverRows
results in the following:
The following imports have been used to produce the plots below:
++#![allow(unused)] +fn main() { +use ndarray::Array; +use plotly::common::{ + Fill, Font, Mode, +}; +use plotly::layout::{ + Axis, GridPattern, Layout, LayoutGrid, Margin, Shape, ShapeLayer, ShapeLine, + ShapeType, +}; +use plotly::{Bar, color::NamedColor, Plot, Scatter}; +use rand::thread_rng; +use rand_distr::{Distribution, Normal}; +}
The to_inline_html
method is used to produce the html plot displayed in this page.
+ + +#![allow(unused)] +fn main() { +fn filled_area_chart(show: bool) -> Plot { + 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); + + let mut plot = Plot::new(); + plot.add_trace(trace1); + plot.add_trace(trace2); + + if show { + plot.show(); + } + plot +} +}
+ + +#![allow(unused)] +fn main() { +fn vertical_and_horizontal_lines_positioned_relative_to_axes(show: bool) -> Plot { + let trace = Scatter::new(vec![2.0, 3.5, 6.0], vec![1.0, 1.5, 1.0]) + .text_array(vec![ + "Vertical Line", + "Horizontal Dashed Line", + "Diagonal dotted Line", + ]) + .mode(Mode::Text); + + let mut plot = Plot::new(); + plot.add_trace(trace); + + let mut layout = Layout::new() + .x_axis(Axis::new().range(vec![0.0, 7.0])) + .y_axis(Axis::new().range(vec![0.0, 2.5])); + + layout.add_shape( + Shape::new() + .shape_type(ShapeType::Line) + .x0(1) + .y0(0) + .x1(1) + .y1(2) + .line(ShapeLine::new().color(NamedColor::RoyalBlue).width(3.)), + ); + + layout.add_shape( + Shape::new() + .shape_type(ShapeType::Line) + .x0(2) + .y0(2) + .x1(5) + .y1(2) + .line( + ShapeLine::new() + .color(NamedColor::LightSeaGreen) + .width(3.) + .dash(DashType::DashDot), + ), + ); + + layout.add_shape( + Shape::new() + .shape_type(ShapeType::Line) + .x0(4) + .y0(0) + .x1(6) + .y1(2) + .line( + ShapeLine::new() + .color(NamedColor::MediumPurple) + .width(3.) + .dash(DashType::Dot), + ), + ); + + plot.set_layout(layout); + + if show { + plot.show(); + } + plot +} +}
+ + +#![allow(unused)] +fn main() { +fn lines_positioned_relative_to_the_plot_and_to_the_axes(show: bool) -> Plot { + let trace = Scatter::new(vec![2.0, 6.0], vec![1.0, 1.0]) + .text_array(vec![ + "Line positioned relative to the plot", + "Line positioned relative to the axes", + ]) + .mode(Mode::Text); + + let mut plot = Plot::new(); + plot.add_trace(trace); + + let mut layout = Layout::new() + .x_axis(Axis::new().range(vec![0.0, 8.0])) + .y_axis(Axis::new().range(vec![0.0, 2.])); + + layout.add_shape( + Shape::new() + .x_ref("x") + .y_ref("y") + .shape_type(ShapeType::Line) + .x0(4) + .y0(0) + .x1(8) + .y1(1) + .line(ShapeLine::new().color(NamedColor::LightSeaGreen).width(3.)), + ); + layout.add_shape( + Shape::new() + .x_ref("paper") + .y_ref("paper") + .shape_type(ShapeType::Line) + .x0(0.0) + .y0(0.0) + .x1(0.5) + .y1(0.5) + .line(ShapeLine::new().color(NamedColor::DarkOrange).width(3.)), + ); + + plot.set_layout(layout); + + if show { + plot.show(); + } + plot +} +}
+ + +#![allow(unused)] +fn main() { +fn creating_tangent_lines_with_shapes(show: bool) -> Plot { + 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(); + + let trace = Scatter::new(x0, y0); + let mut plot = Plot::new(); + plot.add_trace(trace); + + let mut layout = + Layout::new().title("$f(x)=x\\sin(x^2)+1\\\\ f\'(x)=\\sin(x^2)+2x^2\\cos(x^2)$"); + + layout.add_shape( + Shape::new() + .x_ref("x") + .y_ref("y") + .opacity(0.7) + .shape_type(ShapeType::Line) + .x0(1.) + .y0(2.30756) + .x1(1.75) + .y1(2.30756) + .line(ShapeLine::new().color(NamedColor::Crimson).width(2.5)), + ); + + layout.add_shape( + Shape::new() + .x_ref("x") + .y_ref("y") + .opacity(0.7) + .shape_type(ShapeType::Line) + .x0(2.5) + .y0(3.80796) + .x1(3.05) + .y1(3.80796) + .line(ShapeLine::new().color(NamedColor::Crimson).width(2.5)), + ); + + layout.add_shape( + Shape::new() + .x_ref("x") + .y_ref("y") + .opacity(0.7) + .shape_type(ShapeType::Line) + .x0(1.90) + .y0(-1.1827) + .x1(2.5) + .y1(-1.1827) + .line(ShapeLine::new().color(NamedColor::Crimson).width(2.5)), + ); + + plot.set_layout(layout); + + if show { + plot.show(); + } + plot +} +}
+ + +#![allow(unused)] +fn main() { +fn rectangles_positioned_relative_to_the_axes(show: bool) -> Plot { + let trace = Scatter::new(vec![1.5, 4.5], vec![0.75, 0.75]) + .text_array(vec!["Unfilled Rectangle", "Filled Rectangle"]) + .mode(Mode::Text); + let mut plot = Plot::new(); + plot.add_trace(trace); + + let mut layout = Layout::new() + .x_axis(Axis::new().range(vec![0.0, 7.0]).show_grid(false)) + .y_axis(Axis::new().range(vec![0.0, 3.5])); + + layout.add_shape( + Shape::new() + .x_ref("x") + .y_ref("y") + .shape_type(ShapeType::Rect) + .x0(1.) + .y0(1.) + .x1(2.) + .y1(3.) + .line(ShapeLine::new().color(NamedColor::RoyalBlue)), + ); + + layout.add_shape( + Shape::new() + .x_ref("x") + .y_ref("y") + .shape_type(ShapeType::Rect) + .x0(3.) + .y0(1.) + .x1(6.) + .y1(2.) + .line(ShapeLine::new().color(NamedColor::RoyalBlue).width(2.)) + .fill_color(NamedColor::LightSkyBlue), + ); + + plot.set_layout(layout); + + if show { + plot.show(); + } + plot +} +}
+ + +#![allow(unused)] +fn main() { +fn rectangle_positioned_relative_to_the_plot_and_to_the_axes(show: bool) -> Plot { + let trace = Scatter::new(vec![1.5, 3.], vec![2.5, 2.5]) + .text_array(vec![ + "Rectangle reference to the plot", + "Rectangle reference to the axes", + ]) + .mode(Mode::Text); + + let mut plot = Plot::new(); + plot.add_trace(trace); + + let mut layout = Layout::new() + .x_axis(Axis::new().range(vec![0.0, 4.0]).show_grid(false)) + .y_axis(Axis::new().range(vec![0.0, 4.0])); + + layout.add_shape( + Shape::new() + .x_ref("x") + .y_ref("y") + .shape_type(ShapeType::Rect) + .x0(2.5) + .y0(0.0) + .x1(3.5) + .y1(2.0) + .line(ShapeLine::new().color(NamedColor::RoyalBlue).width(3.)) + .fill_color(NamedColor::LightSkyBlue), + ); + + layout.add_shape( + Shape::new() + .x_ref("paper") + .y_ref("paper") + .shape_type(ShapeType::Rect) + .x0(0.25) + .y0(0.0) + .x1(0.5) + .y1(0.5) + .line(ShapeLine::new().color(NamedColor::LightSeaGreen).width(3.)) + .fill_color(NamedColor::PaleTurquoise), + ); + + plot.set_layout(layout); + + if show { + plot.show(); + } + plot +} +}
+ + +#![allow(unused)] +fn main() { +fn highlighting_time_series_regions_with_rectangle_shapes(show: bool) -> Plot { + let x = vec![ + "2015-02-01", + "2015-02-02", + "2015-02-03", + "2015-02-04", + "2015-02-05", + "2015-02-06", + "2015-02-07", + "2015-02-08", + "2015-02-09", + "2015-02-10", + "2015-02-11", + "2015-02-12", + "2015-02-13", + "2015-02-14", + "2015-02-15", + "2015-02-16", + "2015-02-17", + "2015-02-18", + "2015-02-19", + "2015-02-20", + "2015-02-21", + "2015-02-22", + "2015-02-23", + "2015-02-24", + "2015-02-25", + "2015-02-26", + "2015-02-27", + "2015-02-28", + ]; + let y = vec![ + -14, -17, -8, -4, -7, -10, -12, -14, -12, -7, -11, -7, -18, -14, -14, -16, -13, -7, -8, + -14, -8, -3, -9, -9, -4, -13, -9, -6, + ]; + + let trace = Scatter::new(x, y).mode(Mode::Lines).name("temperature"); + let mut plot = Plot::new(); + plot.add_trace(trace); + + let mut layout = Layout::new(); + + layout.add_shape( + Shape::new() + .x_ref("x") + .y_ref("paper") + .shape_type(ShapeType::Rect) + .x0("2015-02-04") + .y0(0) + .x1("2015-02-06") + .y1(1) + .fill_color(NamedColor::LightSalmon) + .opacity(0.5) + .layer(ShapeLayer::Below) + .line(ShapeLine::new().width(0.)), + ); + + layout.add_shape( + Shape::new() + .x_ref("x") + .y_ref("paper") + .shape_type(ShapeType::Rect) + .x0("2015-02-20") + .y0(0) + .x1("2015-02-22") + .y1(1) + .fill_color(NamedColor::LightSalmon) + .opacity(0.5) + .layer(ShapeLayer::Below) + .line(ShapeLine::new().width(0.)), + ); + + plot.set_layout(layout); + + if show { + plot.show(); + } + plot +} +}
+ + +#![allow(unused)] +fn main() { +fn circles_positioned_relative_to_the_axes(show: bool) -> Plot { + let trace = Scatter::new(vec![1.5, 3.5], vec![0.75, 2.5]) + .text_array(vec!["Unfilled Circle", "Filled Circle"]) + .mode(Mode::Text); + + let mut plot = Plot::new(); + plot.add_trace(trace); + + let mut layout = Layout::new() + .x_axis(Axis::new().range(vec![0.0, 4.5]).zero_line(false)) + .y_axis(Axis::new().range(vec![0.0, 4.5])) + .width(800) + .height(800); + + layout.add_shape( + Shape::new() + .x_ref("x") + .y_ref("y") + .shape_type(ShapeType::Circle) + .x0(1) + .y0(1) + .x1(3) + .y1(3) + .line(ShapeLine::new().color(NamedColor::LightSeaGreen)), + ); + + layout.add_shape( + Shape::new() + .x_ref("x") + .y_ref("y") + .shape_type(ShapeType::Circle) + .x0(3) + .y0(3) + .x1(4) + .y1(4) + .line(ShapeLine::new().color(NamedColor::LightSeaGreen)) + .fill_color(NamedColor::PaleTurquoise), + ); + + plot.set_layout(layout); + + if show { + plot.show(); + } + plot +} +}
+ + +#![allow(unused)] +fn main() { +fn highlighting_clusters_of_scatter_points_with_circle_shapes(show: bool) -> Plot { + let mut rng = thread_rng(); + let x0 = Normal::new(2., 0.45) + .unwrap() + .sample_iter(&mut rng) + .take(300) + .collect::<Vec<f64>>(); + let y0 = Normal::new(2., 0.45) + .unwrap() + .sample_iter(&mut rng) + .take(300) + .collect::<Vec<f64>>(); + let x1 = Normal::new(6., 0.4) + .unwrap() + .sample_iter(&mut rng) + .take(300) + .collect::<Vec<f64>>(); + let y1 = Normal::new(6., 0.4) + .unwrap() + .sample_iter(&mut rng) + .take(300) + .collect::<Vec<f64>>(); + let x2 = Normal::new(4., 0.3) + .unwrap() + .sample_iter(&mut rng) + .take(300) + .collect::<Vec<f64>>(); + let y2 = Normal::new(4., 0.3) + .unwrap() + .sample_iter(&mut rng) + .take(300) + .collect::<Vec<f64>>(); + + let x0min = x0.iter().copied().fold(f64::NAN, f64::min); + let x0max = x0.iter().copied().fold(f64::NAN, f64::max); + let y0min = y0.iter().copied().fold(f64::NAN, f64::min); + let y0max = y0.iter().copied().fold(f64::NAN, f64::max); + + let x1min = x1.iter().copied().fold(f64::NAN, f64::min); + let x1max = x1.iter().copied().fold(f64::NAN, f64::max); + let y1min = y1.iter().copied().fold(f64::NAN, f64::min); + + let x2min = x2.iter().copied().fold(f64::NAN, f64::min); + let x2max = x2.iter().copied().fold(f64::NAN, f64::max); + let y2min = y2.iter().copied().fold(f64::NAN, f64::min); + + let mut plot = Plot::new(); + plot.add_trace(Scatter::new(x0, y0.clone()).mode(Mode::Markers)); + plot.add_trace(Scatter::new(x1.clone(), y1).mode(Mode::Markers)); + plot.add_trace(Scatter::new(x2, y2).mode(Mode::Markers)); + plot.add_trace(Scatter::new(x1, y0).mode(Mode::Markers)); + + let mut layout = Layout::new().show_legend(false); + + layout.add_shape( + Shape::new() + .x_ref("x") + .y_ref("y") + .shape_type(ShapeType::Circle) + .x0(x0min) + .y0(y0min) + .x1(x0max) + .y1(y0max) + .opacity(0.2) + .fill_color(NamedColor::Blue) + .line(ShapeLine::new().color(NamedColor::Blue)), + ); + layout.add_shape( + Shape::new() + .x_ref("x") + .y_ref("y") + .shape_type(ShapeType::Circle) + .x0(x1min) + .y0(y1min) + .x1(x1max) + .y1(x1max) + .opacity(0.2) + .fill_color(NamedColor::Orange) + .line(ShapeLine::new().color(NamedColor::Orange)), + ); + layout.add_shape( + Shape::new() + .x_ref("x") + .y_ref("y") + .shape_type(ShapeType::Circle) + .x0(x2min) + .y0(y2min) + .x1(x2max) + .y1(x2max) + .opacity(0.2) + .fill_color(NamedColor::Green) + .line(ShapeLine::new().color(NamedColor::Green)), + ); + layout.add_shape( + Shape::new() + .x_ref("x") + .y_ref("y") + .shape_type(ShapeType::Circle) + .x0(x1min) + .y0(y0min) + .x1(x1max) + .y1(x0max) + .opacity(0.2) + .fill_color(NamedColor::Red) + .line(ShapeLine::new().color(NamedColor::Red)), + ); + + plot.set_layout(layout); + + if show { + plot.show(); + } + plot +} +}
+ + +#![allow(unused)] +fn main() { +fn venn_diagram_with_circle_shapes(show: bool) -> Plot { + let mut plot = Plot::new(); + plot.add_trace( + Scatter::new(vec![1., 1.75, 2.5], vec![1., 1., 1.]) + .text_array(vec!["$A$", "$A+B$", "$B$"]) + .mode(Mode::Text) + .text_font( + Font::new() + .color(NamedColor::Black) + .size(18) + .family("Arial"), + ), + ); + + let mut layout = Layout::new() + .x_axis( + Axis::new() + .zero_line(false) + .show_grid(false) + .show_tick_labels(false), + ) + .y_axis( + Axis::new() + .zero_line(false) + .show_grid(false) + .show_tick_labels(false), + ) + .margin(Margin::new().left(20).right(20).bottom(100)) + .height(600) + .width(800) + .plot_background_color(NamedColor::White); + + layout.add_shape( + Shape::new() + .x_ref("x") + .y_ref("y") + .shape_type(ShapeType::Circle) + .x0(0) + .y0(0) + .x1(2) + .y1(2) + .opacity(0.3) + .layer(ShapeLayer::Below) + .fill_color(NamedColor::Blue) + .line(ShapeLine::new().color(NamedColor::Blue)), + ); + layout.add_shape( + Shape::new() + .x_ref("x") + .y_ref("y") + .shape_type(ShapeType::Circle) + .x0(1.5) + .y0(0.) + .x1(3.5) + .y1(2.) + .opacity(0.3) + .layer(ShapeLayer::Below) + .fill_color(NamedColor::Gray) + .line(ShapeLine::new().color(NamedColor::Gray)), + ); + + plot.set_layout(layout); + + if show { + plot.show(); + } + plot +} +}
+ + +#![allow(unused)] +fn main() { +fn adding_shapes_to_subplots(show: bool) -> Plot { + let mut plot = Plot::new(); + plot.add_trace( + Scatter::new(vec![2, 6], vec![1, 1]) + .x_axis("x1") + .y_axis("y1"), + ); + plot.add_trace( + Bar::new(vec![1, 2, 3], vec![4, 5, 6]) + .x_axis("x2") + .y_axis("y2"), + ); + plot.add_trace( + Scatter::new(vec![10, 20], vec![40, 50]) + .x_axis("x3") + .y_axis("y3"), + ); + plot.add_trace( + Bar::new(vec![11, 13, 15], vec![8, 11, 20]) + .x_axis("x4") + .y_axis("y4"), + ); + + let mut layout = Layout::new() + .grid( + LayoutGrid::new() + .rows(2) + .columns(2) + .pattern(GridPattern::Independent), + ) + .x_axis(Axis::new().domain(&[0.0, 0.48]).anchor("x1")) + .y_axis(Axis::new().domain(&[0.52, 1.]).anchor("y1")) + .x_axis2(Axis::new().domain(&[0.52, 1.0]).anchor("x2")) + .y_axis2(Axis::new().domain(&[0.5, 1.]).anchor("y2")) + .x_axis3(Axis::new().domain(&[0.0, 0.48]).anchor("x3")) + .y_axis3(Axis::new().domain(&[0.0, 0.48]).anchor("y3")) + .x_axis4(Axis::new().domain(&[0.52, 1.0]).anchor("x4")) + .y_axis4(Axis::new().domain(&[0.0, 0.48]).anchor("y4")); + + layout.add_shape( + Shape::new() + .x_ref("x1") + .y_ref("y1") + .shape_type(ShapeType::Line) + .x0(3) + .y0(0.5) + .x1(5) + .y1(0.8) + .line(ShapeLine::new().width(3.)), + ); + layout.add_shape( + Shape::new() + .x_ref("x2") + .y_ref("y2") + .shape_type(ShapeType::Rect) + .x0(4) + .y0(2) + .x1(5) + .y1(6), + ); + layout.add_shape( + Shape::new() + .x_ref("x3") + .y_ref("y3") + .shape_type(ShapeType::Rect) + .x0(10) + .y0(20) + .x1(15) + .y1(30), + ); + layout.add_shape( + Shape::new() + .x_ref("x4") + .y_ref("y4") + .shape_type(ShapeType::Circle) + .x0(5) + .y0(12) + .x1(10) + .y1(18), + ); + + plot.set_layout(layout); + + if show { + plot.show(); + } + plot +} +}
+ + + +#![allow(unused)] +fn main() { +fn svg_paths(show: bool) -> Plot { + let mut plot = Plot::new(); + plot.add_trace( + Scatter::new(vec![2, 1, 8, 8], vec![0.25, 9., 2., 6.]) + .text_array(vec![ + "Filled Triangle", + "Filled Polygon", + "Quadratic Bezier Curves", + "Cubic Bezier Curves", + ]) + .mode(Mode::Text), + ); + + let mut layout = Layout::new() + .x_axis( + Axis::new() + .domain(&[0.05, 0.95]) + .range(vec![0., 9.]) + .zero_line(false), + ) + .y_axis( + Axis::new() + .domain(&[0.05, 0.95]) + .range(vec![0, 11]) + .zero_line(false), + ); + layout.add_shape( + Shape::new() + .shape_type(ShapeType::Path) + .path("M 4,4 Q 6,0 8,4") + .line(ShapeLine::new().color(NamedColor::RoyalBlue)), + ); + layout.add_shape( + Shape::new() + .shape_type(ShapeType::Path) + .path("M 1,4 C 2,8 6,4 8,8") + .line(ShapeLine::new().color(NamedColor::MediumPurple)), + ); + layout.add_shape( + Shape::new() + .shape_type(ShapeType::Path) + .path("M 1 1 L 1 3 L 4 1 Z") + .fill_color(NamedColor::LightPink) + .line(ShapeLine::new().color(NamedColor::Crimson)), + ); + layout.add_shape( + Shape::new() + .shape_type(ShapeType::Path) + .path("M 3,7 L2,8 L2,9 L3,10, L4,10 L5,9 L5,8 L4,7 Z") + .fill_color(NamedColor::PaleTurquoise) + .line(ShapeLine::new().color(NamedColor::LightSeaGreen)), + ); + + plot.set_layout(layout); + + if show { + plot.show(); + } + plot +} +}
To start using plotly.rs in your project add the following to your Cargo.toml
:
[dependencies]
+plotly = "0.12"
+
+Plotly.rs is ultimately a thin wrapper around the plotly.js
library. The main job of this library is to provide structs
and enums
which get serialized to json
and passed to the plotly.js
library to actually do the heavy lifting. As such, if you are familiar with plotly.js
or its derivatives (e.g. the equivalent Python library), then you should find plotly.rs
intuitive to use.
A Plot
struct contains one or more Trace
objects which describe the structure of data to be displayed. Optional Layout
and Configuration
structs can be used to specify the layout and config of the plot, respectively.
The builder pattern is used extensively throughout the library, which means you only need to specify the attributes and details you desire. Any attributes that are not set will fall back to the default value used by plotly.js
.
All available traces (e.g. Scatter
, Bar
, Histogram
, etc), the Layout
, Configuration
and Plot
have been hoisted in the plotly
namespace so that they can be imported simply using the following:
+#![allow(unused)] +fn main() { +use plotly::{Plot, Layout, Scatter}; +}
The aforementioned components can be combined to produce as simple plot as follows:
++use plotly::common::Mode; +use plotly::{Plot, Scatter}; + +fn line_and_scatter_plot() { + let trace1 = Scatter::new(vec![1, 2, 3, 4], vec![10, 15, 13, 17]) + .name("trace1") + .mode(Mode::Markers); + let trace2 = Scatter::new(vec![2, 3, 4, 5], vec![16, 5, 11, 9]) + .name("trace2") + .mode(Mode::Lines); + let trace3 = Scatter::new(vec![1, 2, 3, 4], vec![12, 9, 15, 12]).name("trace3"); + + let mut plot = Plot::new(); + plot.add_trace(trace1); + plot.add_trace(trace2); + plot.add_trace(trace3); + plot.show(); +} + +fn main() -> std::io::Result<()> { + line_and_scatter_plot(); + Ok(()) +}
which results in the following figure (displayed here as a static png file):
+The above code will generate an interactive html
page of the Plot
and display it in the default browser. The html
for the plot is stored in the platform specific temporary directory. To save the html
result, you can do so quite simply:
+#![allow(unused)] +fn main() { +plot.write_html("/home/user/line_and_scatter_plot.html"); +}
It is often the case that plots are produced to be included in a document and a different format for the plot is desirable (e.g. png, jpeg, etc). Given that the html
version of the plot is composed of vector graphics, the display when converted to a non-vector format (e.g. png) is not guaranteed to be identical to the one displayed in html
. This means that some fine tuning may be required to get to the desired output. To support that iterative workflow, Plot
has a show_image()
method which will display the rasterised output to the target format, for example:
+#![allow(unused)] +fn main() { +plot.show_image(ImageFormat::PNG, 1280, 900); +}
will display in the browser the rasterised plot; 1280 pixels wide and 900 pixels tall, in png format.
+Once a satisfactory result is achieved, and assuming the kaleido
feature is enabled, the plot can be saved using the following:
+#![allow(unused)] +fn main() { +plot.write_image("/home/user/plot_name.ext", ImageFormat::PNG, 1280, 900, 1.0); +}
The extension in the file-name path is optional as the appropriate extension (ImageFormat::PNG
) will be included. Note that in all functions that save files to disk, both relative and absolute paths are supported.
To add the ability to save plots in the following formats: png, jpeg, webp, svg, pdf and eps, you can use the kaleido
feature. This feature depends on plotly/Kaleido: a cross-platform open source library for generating static images. All the necessary binaries have been included with plotly_kaleido
for Linux
, Windows
and MacOS
. Previous versions of plotly.rs used the orca
feature, however, this has been deprecated as it provided the same functionality but required additional installation steps. To enable the kaleido
feature add the following to your Cargo.toml
:
[dependencies]
+plotly = { version = "0.12", features = ["kaleido"] }
+
+As of v0.8.0, plotly.rs can now be used in a Wasm
environment by enabling the wasm
feature in your Cargo.toml
:
[dependencies]
+plotly = { version = ">=0.8.0" features = ["wasm"] }
+
+The wasm
feature exposes rudimentary bindings to the plotly.js
library, which can then be used in a wasm
environment such as the Yew
frontend framework.
To make a very simple Plot
component might look something like:
+ +#![allow(unused)] +fn main() { +use yew::prelude::*; + +#[derive(Properties, PartialEq)] +pub struct PlotProps { + pub id: String, + pub plot: plotly::Plot, + pub class: Option<Classes>, +} + +#[function_component(Plot)] +pub fn plot(props: &PlotProps) -> Html { + let PlotProps { id, plot, class } = props; + + let p = yew_hooks::use_async::<_, _, ()>({ + let id = id.clone(); + let plot = plot.clone(); + async move { + plotly::bindings::new_plot(&id, &plot).await; + Ok(()) + } + }); + + { + let id = id.clone(); + let plot = plot.clone(); + use_effect_with_deps( + move |(_, _)| { + p.run(); + || () + }, + (id, plot), + ); + } + + html! { + <div id={id.clone()} class={class.clone()}></div> + } +} +}
Plotly.rs is a plotting library powered by Plotly.js. The aim is to bring over to Rust all the functionality that Python
users have come to rely on with the added benefit of type safety and speed.
Plotly.rs is free and open source. You can find the source on GitHub. Issues and feature requests can be posted on the issue tracker.
+This book is intended to be a recipe index, which closely follows the plotly.js examples, and is complemented by the API documentation.
+Contributions are always welcomed, no matter how large or small. Refer to the contributing guidelines for further pointers, and, if in doubt, open an issue.
+Plotly.rs is distributed under the terms of the MIT license.
+See LICENSE
+ +