Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Plotly.rs

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.

API Docs

This book is intended to be a recipe index, which closely follows the plotly.js examples, and is complemented by the API documentation.

Contributing

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.

License

Plotly.rs is distributed under the terms of the MIT license.

See LICENSE

Getting Started

To start using plotly.rs in your project add the following to your Cargo.toml:

[dependencies]
plotly = "0.13"

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):

line_and_scatter_plot

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.

Saving Plots with Kaleido (legacy)

To add the ability to save plots in the following formats: png, jpeg, webp, svg, pdf and eps, you can use the kaleido feature. This feature depends on plotly/Kaleido: 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.13", features = ["kaleido"] }

For static image export using WebDriver and headless browsers, you can use the plotly_static feature. This feature supports the same formats as Kaleido (png, jpeg, webp, svg, pdf) but uses WebDriver for the static export process. To enable static export, add the following to your Cargo.toml:

[dependencies]
plotly = { version = "0.13", features = ["static_export_default"] }

The static_export_default feature includes Chrome WebDriver support with automatic download. For Firefox support, use static_export_geckodriver instead. See the Static Image Export chapter for a detailed usage example.

WebAssembly Support

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>
    }
}
}

Fundamentals

Functionality that applies to the library as a whole is described in the next sections.

Core Features

  • Jupyter Support: Interactive plotting in Jupyter notebooks
  • ndarray Support: Integration with the ndarray crate for numerical computing
  • Shapes: Adding shapes and annotations to plots
  • Themes: Customizing plot appearance with themes
  • Static Image Export: Exporting plots to static images (PNG, JPEG, SVG, PDF) using WebDriver

Jupyter Support

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!

Installation

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.

Usage

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 Support

To 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,
  • and also multiple traces can be created with the Scatter::to_traces method.

The full source code for the examples below can be found here.

ndarray Traces

The 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;
}

Single Trace

#![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")));
}
}

Multiple Traces

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:

Shapes

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::rng;
use rand_distr::{Distribution, Normal};
}

The to_inline_html method is used to produce the html plot displayed in this page.

Filled Area Chart

#![allow(unused)]
fn main() {
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);

    let mut plot = Plot::new();
    plot.add_trace(trace1);
    plot.add_trace(trace2);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Vertical and Horizontal Lines Positioned Relative to Axes

#![allow(unused)]
fn main() {
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",
            "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);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Lines Positioned Relative to the Plot and to the Axes

#![allow(unused)]
fn main() {
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",
            "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);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Creating Tangent Lines with Shapes

#![allow(unused)]
fn main() {
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();

    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);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Rectangles Positioned Relative to the Axes

#![allow(unused)]
fn main() {
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);
    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);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Rectangle Positioned Relative to the Plot and to the Axes

#![allow(unused)]
fn main() {
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",
            "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);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Highlighting Time Series Regions with Rectangle Shapes

#![allow(unused)]
fn main() {
fn highlighting_time_series_regions_with_rectangle_shapes(show: bool, file_name: &str) {
    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);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Circles Positioned Relative to the Axes

#![allow(unused)]
fn main() {
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);

    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);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Highlighting Clusters of Scatter Points with Circle Shapes

#![allow(unused)]
fn main() {
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()
        .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);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Venn Diagram with Circle Shapes

#![allow(unused)]
fn main() {
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.])
            .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);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Adding Shapes to Subplots

#![allow(unused)]
fn main() {
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])
            .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);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

SVG Paths

#![allow(unused)]
fn main() {
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.])
            .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);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Themes

The complete source code for the following examples can also be found here.

Use different theme templates

Similar to Plotly Python templates, plotly.rs provides several built-in themes that can be applied to your plots.

#![allow(unused)]
fn main() {
use plotly::{
    common::{Marker, Mode, Title},
    layout::{Layout, BuiltinTheme},
    Plot, Scatter,
};
}

The to_inline_html method is used to produce the html plot displayed in this page.

Default Theme (Plotly)

Plotly White Theme

Plotly Dark Theme

Seaborn Theme

Matplotlib Theme

Plotnine Theme

Available Themes

The following built-in themes are available in plotly.rs:

  • BuiltinTheme::Default - Default Plotly theme
  • BuiltinTheme::PlotlyWhite - Clean white background theme
  • BuiltinTheme::PlotlyDark - Dark theme
  • BuiltinTheme::Seaborn - Seaborn-style theme
  • BuiltinTheme::SeabornWhitegrid - Seaborn with white grid
  • BuiltinTheme::SeabornDark - Dark Seaborn theme
  • BuiltinTheme::Matplotlib - Matplotlib-style theme
  • BuiltinTheme::Plotnine - Plotnine-style theme

Using Themes

To apply a theme to your plot, use the template() method on the layout:

#![allow(unused)]
fn main() {
let theme = BuiltinTheme::Seaborn;
let layout = Layout::new().template(theme.build());
plot.set_layout(layout);
}

The example above uses real Gapminder 2007 data showing the relationship between GDP per capita, life expectancy, and population size across different continents, with marker sizes representing population and colors representing continents.

Static Image Export

The plotly crate provides static image export functionality through the plotly_static crate, which uses WebDriver and headless browsers to render plots as static images.

Overview

Static image export allows you to convert Plotly plots into various image formats (PNG, JPEG, WEBP, SVG, PDF) for use in reports, web applications, or any scenario where you need static images.

Feature Flags

The static export functionality is controlled by feature flags in the main plotly crate:

Required Features (choose one):

  • static_export_chromedriver: Uses Chrome/Chromium for rendering (requires chromedriver)
  • static_export_geckodriver: Uses Firefox for rendering (requires geckodriver)

Optional Features:

  • static_export_wd_download: Automatically downloads WebDriver binaries at build time
  • static_export_default: Convenience feature that includes chromedriver + downloader

Cargo.toml Configuration Examples:

# Basic usage with manual Chromedriver installation
[dependencies]
plotly = { version = "0.13", features = ["static_export_chromedriver"] }

# With automatic Chromedriver download
[dependencies]
plotly = { version = "0.13", features = ["static_export_chromedriver", "static_export_wd_download"] }

# Recommended: Default configuration with Chromedriver + auto-download
[dependencies]
plotly = { version = "0.13", features = ["static_export_default"] }

Prerequisites

  1. WebDriver Installation: You need either chromedriver or geckodriver installed

    • Chrome: Download from https://chromedriver.chromium.org/
    • Firefox: Download from https://github.com/mozilla/geckodriver/releases
    • Or use the static_export_wd_download feature for automatic download
  2. Browser Installation: You need Chrome/Chromium or Firefox installed

  3. Environment Variables (optional):

    • Set WEBDRIVER_PATH to specify custom WebDriver binary location (should point to the full executable path)
    • Set BROWSER_PATH to specify custom browser binary location (should point to the full executable path)
    export WEBDRIVER_PATH=/path/to/chromedriver
    export BROWSER_PATH=/path/to/chrome
    

Basic Usage

Simple Export

#![allow(unused)]
fn main() {
use plotly::{Plot, Scatter};
use plotly::plotly_static::ImageFormat;

let mut plot = Plot::new();
plot.add_trace(Scatter::new(vec![1, 2, 3], vec![4, 5, 6]));

// Export to PNG file
plot.write_image("my_plot", ImageFormat::PNG, 800, 600, 1.0)
    .expect("Failed to export plot");
}

Efficient Exporter Reuse

For better performance when exporting multiple plots, reuse a single StaticExporter:

#![allow(unused)]
fn main() {
use plotly::{Plot, Scatter};
use plotly::plotly_static::{StaticExporterBuilder, ImageFormat};

let mut plot1 = Plot::new();
plot1.add_trace(Scatter::new(vec![1, 2, 3], vec![4, 5, 6]));

let mut plot2 = Plot::new();
plot2.add_trace(Scatter::new(vec![2, 3, 4], vec![5, 6, 7]));

// Create a single exporter to reuse
let mut exporter = StaticExporterBuilder::default()
    .build()
    .expect("Failed to create StaticExporter");

// Export multiple plots using the same exporter
plot1.write_image_with_exporter(&mut exporter, "plot1", ImageFormat::PNG, 800, 600, 1.0)
    .expect("Failed to export plot1");
plot2.write_image_with_exporter(&mut exporter, "plot2", ImageFormat::JPEG, 800, 600, 1.0)
    .expect("Failed to export plot2");
}

Supported Formats

Raster Formats

  • PNG: Portable Network Graphics, lossless compression
  • JPEG: Joint Photographic Experts Group, lossy compression (smaller files)
  • WEBP: Google's image format

Vector Formats

  • SVG: Scalable Vector Graphics
  • PDF: Portable Document Format

Deprecated

  • EPS: Encapsulated PostScript (will be removed in version 0.14.0)

String Export

For web applications or APIs, you can export to strings:

#![allow(unused)]
fn main() {
use plotly::{Plot, Scatter};
use plotly::plotly_static::{StaticExporterBuilder, ImageFormat};

let mut plot = Plot::new();
plot.add_trace(Scatter::new(vec![1, 2, 3], vec![4, 5, 6]));

let mut exporter = StaticExporterBuilder::default()
    .build()
    .expect("Failed to create StaticExporter");

// Get base64 data (useful for embedding in HTML)
let base64_data = plot.to_base64_with_exporter(&mut exporter, ImageFormat::PNG, 400, 300, 1.0)
    .expect("Failed to export plot");

// Get SVG data (vector format, scalable)
let svg_data = plot.to_svg_with_exporter(&mut exporter, 400, 300, 1.0)
    .expect("Failed to export plot");
}

Advanced Configuration

Custom WebDriver Configuration

#![allow(unused)]
fn main() {
use plotly::plotly_static::StaticExporterBuilder;

let mut exporter = StaticExporterBuilder::default()
    .webdriver_port(4445)  // Use different port for parallel operations
    .spawn_webdriver(true) // Explicitly spawn WebDriver
    .offline_mode(true)    // Use bundled JavaScript (no internet required)
    .webdriver_browser_caps(vec![
        "--headless".to_string(),
        "--no-sandbox".to_string(),
        "--disable-gpu".to_string(),
        "--disable-dev-shm-usage".to_string(),
    ])
    .build()
    .expect("Failed to create StaticExporter");
}

Parallel Usage

For parallel operations (tests, etc.), use unique ports:

#![allow(unused)]
fn main() {
use plotly::plotly_static::StaticExporterBuilder;
use std::sync::atomic::{AtomicU32, Ordering};

// Generate unique ports for parallel usage
static PORT_COUNTER: AtomicU32 = AtomicU32::new(4444);

fn get_unique_port() -> u32 {
    PORT_COUNTER.fetch_add(1, Ordering::SeqCst)
}

// Each thread/process should use a unique port
let mut exporter = StaticExporterBuilder::default()
    .webdriver_port(get_unique_port())
    .build()
    .expect("Failed to build StaticExporter");
}

Logging Support

Enable logging for debugging and monitoring:

#![allow(unused)]
fn main() {
use plotly::plotly_static::StaticExporterBuilder;

// Initialize logging (typically done once at the start of your application)
env_logger::init();

// Set log level via environment variable
// RUST_LOG=debug cargo run

let mut exporter = StaticExporterBuilder::default()
    .build()
    .expect("Failed to create StaticExporter");
}

Performance Considerations

  • Exporter Reuse: Create a single StaticExporter and reuse it for multiple plots
  • Parallel Usage: Use unique ports for parallel operations (tests, etc.)
  • Resource Management: The exporter automatically manages WebDriver lifecycle

Complete Example

See the static export example for a complete working example that demonstrates:

  • Multiple export formats
  • Exporter reuse
  • String export
  • Logging
  • Error handling

To run the example:

cd examples/static_export
cargo run

NOTE Set RUST_LOG=debug to see detailed WebDriver operations and troubleshooting information.

Recipes

Most of the recipes presented here have been adapted from the official documentation for plotly.js and plotly.py. Contributions of interesting plots that showcase the capabilities of the library are most welcome. For more information on the process please see the contributing guidelines.

Basic Charts

The source code for the following examples can also be found here.

KindLink
Scatter PlotsScatter Plots
Line ChartsLine Charts
Bar ChartsBar Charts
Pie ChartsPie Charts
Sankey DiagramsSankey Diagrams

Scatter Plots

The following imports have been used to produce the plots below:

#![allow(unused)]
fn main() {
use ndarray::Array;
use plotly::common::{
    ColorScale, ColorScalePalette, DashType, Fill, Font, Line, LineShape, Marker, Mode, Title,
};
use plotly::layout::{Axis, BarMode, Layout, Legend, TicksDirection};
use plotly::{Bar, color::{NamedColor, Rgb, Rgba}, Plot, Scatter};
use rand_distr::{Distribution, Normal, Uniform};
}

The to_inline_html method is used to produce the html plot displayed in this page.

Simple Scatter Plot

#![allow(unused)]
fn main() {
fn simple_scatter_plot(show: bool, file_name: &str) {
    let n: usize = 100;
    let t: Vec<f64> = Array::linspace(0., 10., n).into_raw_vec_and_offset().0;
    let y: Vec<f64> = t.iter().map(|x| x.sin()).collect();

    let trace = Scatter::new(t, y).mode(Mode::Markers);
    let mut plot = Plot::new();
    plot.add_trace(trace);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Line and Scatter Plots

#![allow(unused)]
fn main() {
fn line_and_scatter_plots(show: bool, file_name: &str) {
    let n: usize = 100;
    let mut rng = rand::rng();
    let random_x: Vec<f64> = Array::linspace(0., 1., n).into_raw_vec_and_offset().0;
    let random_y0: Vec<f64> = Normal::new(5., 1.)
        .unwrap()
        .sample_iter(&mut rng)
        .take(n)
        .collect();
    let random_y1: Vec<f64> = Normal::new(0., 1.)
        .unwrap()
        .sample_iter(&mut rng)
        .take(n)
        .collect();
    let random_y2: Vec<f64> = Normal::new(-5., 1.)
        .unwrap()
        .sample_iter(&mut rng)
        .take(n)
        .collect();

    let trace1 = Scatter::new(random_x.clone(), random_y0)
        .mode(Mode::Markers)
        .name("markers");
    let trace2 = Scatter::new(random_x.clone(), random_y1)
        .mode(Mode::LinesMarkers)
        .name("linex+markers");
    let trace3 = Scatter::new(random_x, random_y2)
        .mode(Mode::Lines)
        .name("lines");

    let mut plot = Plot::new();
    plot.add_trace(trace1);
    plot.add_trace(trace2);
    plot.add_trace(trace3);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Bubble Scatter Plots

#![allow(unused)]
fn main() {
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(
            Marker::new()
                .size_array(vec![40, 60, 80, 100])
                .color_array(vec![
                    NamedColor::Red,
                    NamedColor::Blue,
                    NamedColor::Cyan,
                    NamedColor::OrangeRed,
                ]),
        );
    let mut plot = Plot::new();
    plot.add_trace(trace1);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Data Labels Hover

#![allow(unused)]
fn main() {
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")
        .marker(Marker::new().size(12));
    let trace2 = Scatter::new(vec![1.5, 2.5, 3.5, 4.5, 5.5], vec![4, 1, 7, 1, 4])
        .mode(Mode::Markers)
        .name("Team B")
        .marker(Marker::new().size(12));

    let mut plot = Plot::new();
    plot.add_trace(trace1);
    plot.add_trace(trace2);

    let layout = Layout::new()
        .title("Data Labels Hover")
        .x_axis(Axis::new().title("x").range(vec![0.75, 5.25]))
        .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_html(path);
    }
}
}

Data Labels on the Plot

#![allow(unused)]
fn main() {
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")
        .marker(Marker::new().size(12))
        .text_array(vec!["A-1", "A-2", "A-3", "A-4", "A-5"]);
    let trace2 = Scatter::new(vec![1.5, 2.5, 3.5, 4.5, 5.5], vec![4, 1, 7, 1, 4])
        .mode(Mode::Markers)
        .name("Team B")
        .text_array(vec!["B-a", "B-b", "B-c", "B-d", "B-e"])
        .marker(Marker::new().size(12));

    let mut plot = Plot::new();
    plot.add_trace(trace1);
    plot.add_trace(trace2);

    let layout = Layout::new()
        .title("Data Labels on the Plot")
        .x_axis(Axis::new().range(vec![0.75, 5.25]))
        .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_html(path);
    }
}
}

Colored and Styled Scatter Plot

#![allow(unused)]
fn main() {
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")
        .text_array(vec!["United States", "Canada"])
        .marker(
            Marker::new()
                .color(Rgb::new(164, 194, 244))
                .size(12)
                .line(Line::new().color(NamedColor::White).width(0.5)),
        );
    let trace2 = Scatter::new(
        vec![
            39317, 37236, 35650, 30066, 29570, 27159, 23557, 21046, 18007,
        ],
        vec![33, 20, 13, 19, 27, 19, 49, 44, 38],
    )
    .mode(Mode::Markers)
    .name("Europe")
    .text_array(vec![
        "Germany",
        "Britain",
        "France",
        "Spain",
        "Italy",
        "Czech Rep.",
        "Greece",
        "Poland",
    ])
    .marker(Marker::new().color(Rgb::new(255, 217, 102)).size(12));
    let trace3 = Scatter::new(
        vec![42952, 37037, 33106, 17478, 9813, 5253, 4692, 3899],
        vec![23, 42, 54, 89, 14, 99, 93, 70],
    )
    .mode(Mode::Markers)
    .name("Asia/Pacific")
    .text_array(vec![
        "Australia",
        "Japan",
        "South Korea",
        "Malaysia",
        "China",
        "Indonesia",
        "Philippines",
        "India",
    ])
    .marker(Marker::new().color(Rgb::new(234, 153, 153)).size(12));
    let trace4 = Scatter::new(
        vec![19097, 18601, 15595, 13546, 12026, 7434, 5419],
        vec![43, 47, 56, 80, 86, 93, 80],
    )
    .mode(Mode::Markers)
    .name("Latin America")
    .text_array(vec![
        "Chile",
        "Argentina",
        "Mexico",
        "Venezuela",
        "Venezuela",
        "El Salvador",
        "Bolivia",
    ])
    .marker(Marker::new().color(Rgb::new(142, 124, 195)).size(12));

    let layout = Layout::new()
        .title("Quarter 1 Growth")
        .x_axis(
            Axis::new()
                .title("GDP per Capita")
                .show_grid(false)
                .zero_line(false),
        )
        .y_axis(Axis::new().title("Percent").show_line(false));
    let mut plot = Plot::new();
    plot.add_trace(trace1);
    plot.add_trace(trace2);
    plot.add_trace(trace3);
    plot.add_trace(trace4);
    plot.set_layout(layout);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Large Data Sets

#![allow(unused)]
fn main() {
fn large_data_sets(show: bool, file_name: &str) {
    let n: usize = 100_000;
    let mut rng = rand::rng();
    let r: Vec<f64> = Uniform::new(0., 1.)
        .unwrap()
        .sample_iter(&mut rng)
        .take(n)
        .collect();
    let theta: Vec<f64> = Normal::new(0., 2. * std::f64::consts::PI)
        .unwrap()
        .sample_iter(&mut rng)
        .take(n)
        .collect();

    let x: Vec<f64> = r
        .iter()
        .zip(theta.iter())
        .map(|args| args.0 * args.1.cos())
        .collect();
    let y: Vec<f64> = r
        .iter()
        .zip(theta.iter())
        .map(|args| args.0 * args.1.sin())
        .collect();
    let trace = Scatter::new(x, y)
        .web_gl_mode(true)
        .mode(Mode::Markers)
        .marker(
            Marker::new()
                .color_scale(ColorScale::Palette(ColorScalePalette::Viridis))
                .line(Line::new().width(1.)),
        );
    let mut plot = Plot::new();
    plot.add_trace(trace);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Line Charts

The following imports have been used to produce the plots below:

#![allow(unused)]
fn main() {
use ndarray::Array;
use plotly::common::{
    ColorScale, ColorScalePalette, DashType, Fill, Font, Line, LineShape, Marker, Mode, Title,
};
use plotly::layout::{Axis, BarMode, Layout, Legend, TicksDirection};
use plotly::{Bar, color::{NamedColor, Rgb, Rgba}, Plot, Scatter};
use rand_distr::{Distribution, Normal, Uniform};
}

The to_inline_html method is used to produce the html plot displayed in this page.

Adding Names to Line and Scatter Plot

#![allow(unused)]
fn main() {
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");
    let trace2 = Scatter::new(vec![2, 3, 4, 5], vec![16, 5, 11, 9])
        .mode(Mode::Lines)
        .name("Lines");
    let trace3 = Scatter::new(vec![1, 2, 3, 4], vec![12, 9, 15, 12])
        .mode(Mode::LinesMarkers)
        .name("Scatter + Lines");

    let layout = Layout::new().title("Adding Names to Line and Scatter Plot");
    let mut plot = Plot::new();
    plot.add_trace(trace1);
    plot.add_trace(trace2);
    plot.add_trace(trace3);
    plot.set_layout(layout);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Line and Scatter Styling

#![allow(unused)]
fn main() {
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")
        .marker(Marker::new().color(Rgb::new(219, 64, 82)).size(12));
    let trace2 = Scatter::new(vec![2, 3, 4, 5], vec![16, 5, 11, 9])
        .mode(Mode::Lines)
        .name("trace2")
        .line(Line::new().color(Rgb::new(55, 128, 191)).width(3.0));
    let trace3 = Scatter::new(vec![1, 2, 3, 4], vec![12, 9, 15, 12])
        .mode(Mode::LinesMarkers)
        .name("trace3")
        .marker(Marker::new().color(Rgb::new(128, 0, 128)).size(12))
        .line(Line::new().color(Rgb::new(128, 0, 128)).width(1.0));

    let layout = Layout::new().title("Line and Scatter Styling");
    let mut plot = Plot::new();
    plot.add_trace(trace1);
    plot.add_trace(trace2);
    plot.add_trace(trace3);
    plot.set_layout(layout);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Styling Line Plot

#![allow(unused)]
fn main() {
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")
        .line(Line::new().color(Rgb::new(219, 64, 82)).width(3.0));
    let trace2 = Scatter::new(vec![1, 2, 3, 4], vec![12, 9, 15, 12])
        .mode(Mode::LinesMarkers)
        .name("Blue")
        .line(Line::new().color(Rgb::new(55, 128, 191)).width(1.0));

    let layout = Layout::new()
        .title("Styling Line Plot")
        .width(500)
        .height(500);
    let mut plot = Plot::new();
    plot.add_trace(trace1);
    plot.add_trace(trace2);
    plot.set_layout(layout);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Line Shape Options for Interpolation

#![allow(unused)]
fn main() {
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")
        .line(Line::new().shape(LineShape::Linear));
    let trace2 = Scatter::new(vec![1, 2, 3, 4, 5], vec![6, 8, 7, 8, 6])
        .mode(Mode::LinesMarkers)
        .name("spline")
        .line(Line::new().shape(LineShape::Spline));
    let trace3 = Scatter::new(vec![1, 2, 3, 4, 5], vec![11, 13, 12, 13, 11])
        .mode(Mode::LinesMarkers)
        .name("vhv")
        .line(Line::new().shape(LineShape::Vhv));
    let trace4 = Scatter::new(vec![1, 2, 3, 4, 5], vec![16, 18, 17, 18, 16])
        .mode(Mode::LinesMarkers)
        .name("hvh")
        .line(Line::new().shape(LineShape::Hvh));
    let trace5 = Scatter::new(vec![1, 2, 3, 4, 5], vec![21, 23, 22, 23, 21])
        .mode(Mode::LinesMarkers)
        .name("vh")
        .line(Line::new().shape(LineShape::Vh));
    let trace6 = Scatter::new(vec![1, 2, 3, 4, 5], vec![26, 28, 27, 28, 26])
        .mode(Mode::LinesMarkers)
        .name("hv")
        .line(Line::new().shape(LineShape::Hv));

    let mut plot = Plot::new();
    let layout = Layout::new().legend(
        Legend::new()
            .y(0.5)
            .trace_order(TraceOrder::Reversed)
            .font(Font::new().size(16)),
    );
    plot.set_layout(layout);
    plot.add_trace(trace1);
    plot.add_trace(trace2);
    plot.add_trace(trace3);
    plot.add_trace(trace4);
    plot.add_trace(trace5);
    plot.add_trace(trace6);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Line Dash

#![allow(unused)]
fn main() {
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")
        .line(Line::new().dash(DashType::Solid));
    let trace2 = Scatter::new(vec![1, 2, 3, 4, 5], vec![6, 8, 7, 8, 6])
        .mode(Mode::LinesMarkers)
        .name("dashdot")
        .line(Line::new().dash(DashType::DashDot));
    let trace3 = Scatter::new(vec![1, 2, 3, 4, 5], vec![11, 13, 12, 13, 11])
        .mode(Mode::LinesMarkers)
        .name("dash")
        .line(Line::new().dash(DashType::Dash));
    let trace4 = Scatter::new(vec![1, 2, 3, 4, 5], vec![16, 18, 17, 18, 16])
        .mode(Mode::LinesMarkers)
        .name("dot")
        .line(Line::new().dash(DashType::Dot));
    let trace5 = Scatter::new(vec![1, 2, 3, 4, 5], vec![21, 23, 22, 23, 21])
        .mode(Mode::LinesMarkers)
        .name("longdash")
        .line(Line::new().dash(DashType::LongDash));
    let trace6 = Scatter::new(vec![1, 2, 3, 4, 5], vec![26, 28, 27, 28, 26])
        .mode(Mode::LinesMarkers)
        .name("longdashdot")
        .line(Line::new().dash(DashType::LongDashDot));

    let mut plot = Plot::new();
    let layout = Layout::new()
        .legend(
            Legend::new()
                .y(0.5)
                .trace_order(TraceOrder::Reversed)
                .font(Font::new().size(16)),
        )
        .x_axis(Axis::new().range(vec![0.95, 5.05]).auto_range(false))
        .y_axis(Axis::new().range(vec![0.0, 28.5]).auto_range(false));
    plot.set_layout(layout);
    plot.add_trace(trace1);
    plot.add_trace(trace2);
    plot.add_trace(trace3);
    plot.add_trace(trace4);
    plot.add_trace(trace5);
    plot.add_trace(trace6);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Filled Lines

#![allow(unused)]
fn main() {
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,
    ];
    let x2 = (1..=10).map(|iv| iv as f64).collect::<Vec<f64>>();
    let trace1 = Scatter::new(
        x1.clone(),
        vec![
            2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0,
            2.0, 1.0, 0.0,
        ],
    )
    .fill(Fill::ToZeroX)
    .fill_color(Rgba::new(0, 100, 80, 0.2))
    .line(Line::new().color(NamedColor::Transparent))
    .name("Fair")
    .show_legend(false);
    let trace2 = Scatter::new(
        x1.clone(),
        vec![
            5.5, 3.0, 5.5, 8.0, 6.0, 3.0, 8.0, 5.0, 6.0, 5.5, 4.75, 5.0, 4.0, 7.0, 2.0, 4.0, 7.0,
            4.4, 2.0, 4.5,
        ],
    )
    .fill(Fill::ToZeroX)
    .fill_color(Rgba::new(0, 176, 246, 0.2))
    .line(Line::new().color(NamedColor::Transparent))
    .name("Premium")
    .show_legend(false);
    let trace3 = Scatter::new(
        x1,
        vec![
            11.0, 9.0, 7.0, 5.0, 3.0, 1.0, 3.0, 5.0, 3.0, 1.0, -1.0, 1.0, 3.0, 1.0, -0.5, 1.0, 3.0,
            5.0, 7.0, 9.0,
        ],
    )
    .fill(Fill::ToZeroX)
    .fill_color(Rgba::new(231, 107, 243, 0.2))
    .line(Line::new().color(NamedColor::Transparent))
    .name("Fair")
    .show_legend(false);
    let trace4 = Scatter::new(
        x2.clone(),
        vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0],
    )
    .line(Line::new().color(Rgb::new(0, 100, 80)))
    .name("Fair");
    let trace5 = Scatter::new(
        x2.clone(),
        vec![5.0, 2.5, 5.0, 7.5, 5.0, 2.5, 7.5, 4.5, 5.5, 5.0],
    )
    .line(Line::new().color(Rgb::new(0, 176, 246)))
    .name("Premium");
    let trace6 = Scatter::new(x2, vec![10.0, 8.0, 6.0, 4.0, 2.0, 0.0, 2.0, 4.0, 2.0, 0.0])
        .line(Line::new().color(Rgb::new(231, 107, 243)))
        .name("Ideal");

    let layout = Layout::new()
        .paper_background_color(Rgb::new(255, 255, 255))
        .plot_background_color(Rgb::new(229, 229, 229))
        .x_axis(
            Axis::new()
                .grid_color(Rgb::new(255, 255, 255))
                .range(vec![1.0, 10.0])
                .show_grid(true)
                .show_line(false)
                .show_tick_labels(true)
                .tick_color(Rgb::new(127, 127, 127))
                .ticks(TicksDirection::Outside)
                .zero_line(false),
        )
        .y_axis(
            Axis::new()
                .grid_color(Rgb::new(255, 255, 255))
                .show_grid(true)
                .show_line(false)
                .show_tick_labels(true)
                .tick_color(Rgb::new(127, 127, 127))
                .ticks(TicksDirection::Outside)
                .zero_line(false),
        );

    let mut plot = Plot::new();
    plot.set_layout(layout);
    plot.add_trace(trace1);
    plot.add_trace(trace2);
    plot.add_trace(trace3);
    plot.add_trace(trace4);
    plot.add_trace(trace5);
    plot.add_trace(trace6);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Setting Lower or Upper Bounds on Axis

This example demonstrates how to set partial axis ranges using both the new AxisRange API and the backward-compatible vector syntax. The x-axis uses both the new AxisRange::upper() method and the traditional vec![None, Some(value)] syntax to set only an upper bound, while the y-axis uses only the vec![Some(value), None] syntax to set a lower bound.

#![allow(unused)]
fn main() {
fn set_lower_or_upper_bound_on_axis(show: bool, file_name: &str) {
    use std::fs::File;
    use std::io::BufReader;

    // Read the iris dataset
    let file = File::open("assets/iris.csv").expect("Failed to open iris.csv");
    let reader = BufReader::new(file);
    let mut csv_reader = csv::Reader::from_reader(reader);

    // Parse the data
    let mut sepal_width = Vec::new();
    let mut sepal_length = Vec::new();
    let mut species = Vec::new();

    for result in csv_reader.records() {
        let record = result.expect("Failed to read CSV record");
        sepal_width.push(record[1].parse::<f64>().unwrap());
        sepal_length.push(record[0].parse::<f64>().unwrap());
        species.push(record[4].to_string());
    }

    // Create separate traces for each species
    let mut traces = Vec::new();
    let unique_species: Vec<String> = species
        .iter()
        .cloned()
        .collect::<std::collections::HashSet<_>>()
        .into_iter()
        .collect();

    for (i, species_name) in unique_species.iter().enumerate() {
        let mut x = Vec::new();
        let mut y = Vec::new();

        for (j, s) in species.iter().enumerate() {
            if s == species_name {
                x.push(sepal_width[j]);
                y.push(sepal_length[j]);
            }
        }

        let trace = Scatter::new(x, y)
            .name(species_name)
            .mode(plotly::common::Mode::Markers)
            .x_axis(format!("x{}", i + 1))
            .y_axis(format!("y{}", i + 1));
        traces.push(trace);
    }

    let mut plot = Plot::new();
    for trace in traces {
        plot.add_trace(trace);
    }

    // Create layout with subplots
    let mut layout = Layout::new()
        .title("Iris Dataset - Subplots by Species")
        .grid(
            LayoutGrid::new()
                .rows(1)
                .columns(3)
                .pattern(plotly::layout::GridPattern::Independent),
        );

    // Set x-axis range for all subplots: [None, 4.5]
    layout = layout
        .x_axis(
            Axis::new()
                .title("sepal_width")
                // Can be set using a vec! of two optional values
                .range(vec![None, Some(4.5)]),
        )
        .x_axis2(
            Axis::new()
                .title("sepal_width")
                // Or can be set using AxisRange::upper(4.5)
                .range(AxisRange::upper(4.5)),
        )
        .x_axis3(
            Axis::new()
                .title("sepal_width")
                // Or can be set using AxisRange::upper(4.5)
                .range(AxisRange::upper(4.5)),
        );

    // Set y-axis range for all subplots: [3, None]
    layout = layout
        .y_axis(
            Axis::new()
                .title("sepal_length")
                .range(vec![Some(3.0), None]),
        )
        .y_axis2(
            Axis::new()
                .title("sepal_length")
                .range(vec![Some(3.0), None]),
        )
        .y_axis3(
            Axis::new()
                .title("sepal_length")
                .range(vec![Some(3.0), None]),
        );

    plot.set_layout(layout);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Bar Charts

The following imports have been used to produce the plots below:

#![allow(unused)]
fn main() {
use ndarray::Array;
use plotly::common::{
    ColorScale, ColorScalePalette, DashType, Fill, Font, Line, LineShape, Marker, Mode, Title,
};
use plotly::layout::{Axis, BarMode, Layout, Legend, TicksDirection};
use plotly::{Bar, color::{NamedColor, Rgb, Rgba}, Plot, Scatter};
use rand_distr::{Distribution, Normal, Uniform};
}

The to_inline_html method is used to produce the html plot displayed in this page.

Basic Bar Chart

#![allow(unused)]
fn main() {
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_html(path);
    }
}
}

Grouped Bar Chart

#![allow(unused)]
fn main() {
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");

    let animals2 = vec!["giraffes", "orangutans", "monkeys"];
    let trace2 = Bar::new(animals2, vec![12, 18, 29]).name("LA Zoo");

    let layout = Layout::new().bar_mode(BarMode::Group);

    let mut plot = Plot::new();
    plot.add_trace(trace1);
    plot.add_trace(trace2);
    plot.set_layout(layout);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Stacked Bar Chart

#![allow(unused)]
fn main() {
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");

    let animals2 = vec!["giraffes", "orangutans", "monkeys"];
    let trace2 = Bar::new(animals2, vec![12, 18, 29]).name("LA Zoo");

    let layout = Layout::new().bar_mode(BarMode::Stack);

    let mut plot = Plot::new();
    plot.add_trace(trace1);
    plot.add_trace(trace2);
    plot.set_layout(layout);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Pie Charts

The following imports have been used to produce the plots below:

#![allow(unused)]
fn main() {
use plotly::common::{Domain, Font, HoverInfo, Orientation};
use plotly::layout::{
        Annotation, Layout, LayoutGrid},
use plotly::layout::Layout;
use plotly::{Pie, Plot};
}

The to_inline_html method is used to produce the html plot displayed in this page.

Basic Pie Chart

#![allow(unused)]
fn main() {
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_html(path);
    }
}
}
#![allow(unused)]
fn main() {
fn basic_pie_chart_labels(show: bool, file_name: &str) {
    let labels = ["giraffes", "giraffes", "orangutans", "monkeys"];
    let t = Pie::<u32>::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_html(path);
    }
}
}

Grouped Pie Chart

#![allow(unused)]
fn main() {
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];
    let labels = vec![
        "US",
        "China",
        "European Union",
        "Russian Federation",
        "Brazil",
        "India",
        "Rest of World",
    ];
    let t = Pie::new(values)
        .labels(labels)
        .name("GHG Emissions")
        .hover_info(HoverInfo::All)
        .text("GHG")
        .hole(0.4)
        .domain(Domain::new().column(0));
    plot.add_trace(t);

    let values = vec![27, 11, 25, 8, 1, 3, 25];
    let labels = vec![
        "US",
        "China",
        "European Union",
        "Russian Federation",
        "Brazil",
        "India",
        "Rest of World",
    ];

    let t = Pie::new(values)
        .labels(labels)
        .name("CO2 Emissions")
        .hover_info(HoverInfo::All)
        .text("CO2")
        .text_position(plotly::common::Position::Inside)
        .hole(0.4)
        .domain(Domain::new().column(1));
    plot.add_trace(t);

    let layout = Layout::new()
        .title("Global Emissions 1990-2011")
        .height(400)
        .width(600)
        .annotations(vec![
            Annotation::new()
                .font(Font::new().size(20))
                .show_arrow(false)
                .text("GHG")
                .x(0.17)
                .y(0.5),
            Annotation::new()
                .font(Font::new().size(20))
                .show_arrow(false)
                .text("CO2")
                .x(0.82)
                .y(0.5),
        ])
        .show_legend(false)
        .grid(
            LayoutGrid::new()
                .columns(2)
                .rows(1)
                .pattern(plotly::layout::GridPattern::Independent),
        );
    plot.set_layout(layout);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Pie Chart Text Control

#![allow(unused)]
fn main() {
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)
        .labels(labels)
        .automargin(true)
        .show_legend(true)
        .text_position(plotly::common::Position::Outside)
        .name("Costs")
        .text_info("label+percent");
    let mut plot = Plot::new();
    plot.add_trace(t);

    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_html(path);
    }
}
}

Sankey Diagrams

The following imports have been used to produce the plots below:

#![allow(unused)]
fn main() {
use ndarray::Array;
use plotly::common::{
    ColorScale, ColorScalePalette, DashType, Fill, Font, Line, LineShape, Marker, Mode, Title,
};
use plotly::layout::{Axis, BarMode, Layout, Legend, TicksDirection};
use plotly::Sankey;
use rand_distr::{Distribution, Normal, Uniform};
}

The to_inline_html method is used to produce the html plot displayed in this page.

Constructing a basic Sankey diagram

#![allow(unused)]
fn main() {
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)
        .node(
            Node::new()
                .pad(15)
                .thickness(30)
                .line(SankeyLine::new().color(NamedColor::Black).width(0.5))
                .label(vec!["A1", "A2", "B1", "B2", "C1", "C2"])
                .color_array(vec![
                    NamedColor::Blue,
                    NamedColor::Blue,
                    NamedColor::Blue,
                    NamedColor::Blue,
                    NamedColor::Blue,
                    NamedColor::Blue,
                ]),
        )
        .link(
            Link::new()
                .value(vec![8, 4, 2, 8, 4, 2])
                .source(vec![0, 1, 0, 2, 3, 3])
                .target(vec![2, 3, 3, 4, 4, 5]),
        );

    let layout = Layout::new()
        .title("Basic Sankey")
        .font(Font::new().size(10));

    let mut plot = Plot::new();
    plot.add_trace(trace);
    plot.set_layout(layout);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Skankey diagram with defined node position

#![allow(unused)]
fn main() {
fn custom_node_sankey_diagram(show: bool, file_name: &str) {
    // https://plotly.com/javascript/sankey-diagram/#basic-sankey-diagram
    let trace = Sankey::new()
        .orientation(Orientation::Horizontal)
        .arrangement(plotly::sankey::Arrangement::Snap)
        .node(
            Node::new()
                .pad(15)
                .thickness(30)
                .line(SankeyLine::new().color(NamedColor::Black).width(0.5))
                .label(vec!["A", "B", "C", "D", "E", "F"])
                .x(vec![0.2, 0.1, 0.5, 0.7, 0.3, 0.5])
                .y(vec![0.2, 0.1, 0.5, 0.7, 0.3, 0.5])
                .pad(20),
        )
        .link(
            Link::new()
                .source(vec![0, 0, 1, 2, 5, 4, 3, 5])
                .target(vec![5, 3, 4, 3, 0, 2, 2, 3])
                .value(vec![1, 2, 1, 1, 1, 1, 1, 2]),
        );

    let layout = Layout::new()
        .title("Define Node Position")
        .font(Font::new().size(10));

    let mut plot = Plot::new();
    plot.add_trace(trace);
    plot.set_layout(layout);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Statistical Charts

The complete source code for the following examples can also be found here.

KindLink
Error BarsScatter Plots
Box PlotsLine Charts
HistogramsScatter Plots

Error Bars

The following imports have been used to produce the plots below:

#![allow(unused)]
fn main() {
use ndarray::Array;
use plotly::box_plot::{BoxMean, BoxPoints};
use plotly::common::{ErrorData, ErrorType, Line, Marker, Mode, Orientation, Title};
use plotly::histogram::{Bins, Cumulative, HistFunc, HistNorm};
use plotly::layout::{Axis, BarMode, BoxMode, Layout, Margin};
use plotly::{Bar, BoxPlot, Histogram, Plot, color::{NamedColor, Rgb, Rgba}, Scatter};
use rand_distr::{Distribution, Normal, Uniform};

}

The to_inline_html method is used to produce the html plot displayed in this page.

Basic Symmetric Error Bars

#![allow(unused)]
fn main() {
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]));

    let mut plot = Plot::new();
    plot.add_trace(trace1);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Asymmetric Error Bars

#![allow(unused)]
fn main() {
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(
            ErrorData::new(ErrorType::Data)
                .array(vec![0.1, 0.2, 0.1, 0.1])
                .array_minus(vec![0.2, 0.4, 1., 0.2]),
        );

    let mut plot = Plot::new();
    plot.add_trace(trace1);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Error Bars as a Percentage of the Y Value

#![allow(unused)]
fn main() {
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));

    let mut plot = Plot::new();
    plot.add_trace(trace1);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Asymmetric Error Bars with a Constant Offset

#![allow(unused)]
fn main() {
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(
            ErrorData::new(ErrorType::Percent)
                .symmetric(false)
                .value(15.)
                .value_minus(25.),
        );

    let mut plot = Plot::new();
    plot.add_trace(trace1);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Horizontal Error Bars

#![allow(unused)]
fn main() {
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.));

    let mut plot = Plot::new();
    plot.add_trace(trace1);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Bar Chart with Error Bars

#![allow(unused)]
fn main() {
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])
        .error_y(ErrorData::new(ErrorType::Data).array(vec![0.5, 1., 2.]));

    let mut plot = Plot::new();
    plot.add_trace(trace_c);
    plot.add_trace(trace_e);

    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_html(path);
    }
}
}

Colored and Styled Error Bars

#![allow(unused)]
fn main() {
fn colored_and_styled_error_bars(show: bool, file_name: &str) {
    let x_theo: Vec<f64> = Array::linspace(-4., 4., 100).into_raw_vec_and_offset().0;
    let sincx: Vec<f64> = x_theo
        .iter()
        .map(|x| (x * std::f64::consts::PI).sin() / (*x * std::f64::consts::PI))
        .collect();
    let x = vec![
        -3.8, -3.03, -1.91, -1.46, -0.89, -0.24, -0.0, 0.41, 0.89, 1.01, 1.91, 2.28, 2.79, 3.56,
    ];
    let y = vec![
        -0.02, 0.04, -0.01, -0.27, 0.36, 0.75, 1.03, 0.65, 0.28, 0.02, -0.11, 0.16, 0.04, -0.15,
    ];

    let trace1 = Scatter::new(x_theo, sincx).name("sinc(x)");
    let trace2 = Scatter::new(x, y)
        .mode(Mode::Markers)
        .name("measured")
        .error_y(
            ErrorData::new(ErrorType::Constant)
                .value(0.1)
                .color(NamedColor::Purple)
                .thickness(1.5)
                .width(3),
        )
        .error_x(
            ErrorData::new(ErrorType::Constant)
                .value(0.2)
                .color(NamedColor::Purple)
                .thickness(1.5)
                .width(3),
        )
        .marker(Marker::new().color(NamedColor::Purple).size(8));

    let mut plot = Plot::new();
    plot.add_trace(trace1);
    plot.add_trace(trace2);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Box Plots

The following imports have been used to produce the plots below:

#![allow(unused)]
fn main() {
use ndarray::Array;
use plotly::box_plot::{BoxMean, BoxPoints};
use plotly::common::{ErrorData, ErrorType, Line, Marker, Mode, Orientation, Title};
use plotly::histogram::{Bins, Cumulative, HistFunc, HistNorm};
use plotly::layout::{Axis, BarMode, BoxMode, Layout, Margin};
use plotly::{Bar, BoxPlot, Histogram, color::{NamedColor, Rgb, Rgba}, Scatter};
use rand_distr::{Distribution, Normal, Uniform};

}

The to_inline_html method is used to produce the html plot displayed in this page.

Basic Box Plot

#![allow(unused)]
fn main() {
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();
    let n = 50;

    let mut y0 = Vec::with_capacity(n);
    let mut y1 = Vec::with_capacity(n);

    for _ in 0..n {
        y0.push(uniform1.sample(&mut rng));
        y1.push(uniform2.sample(&mut rng));
    }

    let trace1 = BoxPlot::<f64, f64>::new(y0);
    let trace2 = BoxPlot::<f64, f64>::new(y1);
    let mut plot = Plot::new();
    plot.add_trace(trace1);
    plot.add_trace(trace2);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Box Plot that Displays the Underlying Data

#![allow(unused)]
fn main() {
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)
        .point_pos(-1.8);
    let mut plot = Plot::new();
    plot.add_trace(trace1);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Horizontal Box Plot

#![allow(unused)]
fn main() {
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",
    ];

    let trace = BoxPlot::new_xy(
        vec![1, 2, 3, 4, 4, 4, 8, 9, 10, 2, 3, 3, 3, 3, 5, 6, 6, 7],
        x.clone(),
    )
    .orientation(Orientation::Horizontal);

    let mut plot = Plot::new();
    plot.add_trace(trace);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Grouped Box Plot

#![allow(unused)]
fn main() {
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",
    ];

    let trace1 = BoxPlot::new_xy(
        x.clone(),
        vec![0.2, 0.2, 0.6, 1.0, 0.5, 0.4, 0.2, 0.7, 0.9, 0.1, 0.5, 0.3],
    );
    let trace2 = BoxPlot::new_xy(
        x.clone(),
        vec![0.6, 0.7, 0.3, 0.6, 0.0, 0.5, 0.7, 0.9, 0.5, 0.8, 0.7, 0.2],
    );
    let trace3 = BoxPlot::new_xy(
        x.clone(),
        vec![0.1, 0.3, 0.1, 0.9, 0.6, 0.6, 0.9, 1.0, 0.3, 0.6, 0.8, 0.5],
    );

    let mut plot = Plot::new();
    plot.add_trace(trace1);
    plot.add_trace(trace2);
    plot.add_trace(trace3);

    let layout = Layout::new()
        .y_axis(Axis::new().title("normalized moisture").zero_line(false))
        .box_mode(BoxMode::Group);

    plot.set_layout(layout);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Box Plot Styling Outliers

#![allow(unused)]
fn main() {
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,
    ];
    let trace1 = BoxPlot::new(y.clone())
        .name("All Points")
        .jitter(0.3)
        .point_pos(-1.8)
        .marker(Marker::new().color(Rgb::new(7, 40, 89)))
        .box_points(BoxPoints::All);
    let trace2 = BoxPlot::new(y.clone())
        .name("Only Whiskers")
        .marker(Marker::new().color(Rgb::new(9, 56, 125)))
        .box_points(BoxPoints::False);
    let trace3 = BoxPlot::new(y.clone())
        .name("Suspected Outlier")
        .marker(
            Marker::new()
                .color(Rgb::new(8, 81, 156))
                .outlier_color(Rgba::new(219, 64, 82, 0.6))
                .line(
                    Line::new()
                        .outlier_color(Rgba::new(219, 64, 82, 1.0))
                        .outlier_width(2),
                ),
        )
        .box_points(BoxPoints::SuspectedOutliers);
    let trace4 = BoxPlot::new(y)
        .name("Whiskers and Outliers")
        .marker(Marker::new().color(Rgb::new(107, 174, 214)))
        .box_points(BoxPoints::Outliers);

    let layout = Layout::new().title("Box Plot Styling Outliers");

    let mut plot = Plot::new();
    plot.set_layout(layout);
    plot.add_trace(trace1);
    plot.add_trace(trace2);
    plot.add_trace(trace3);
    plot.add_trace(trace4);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Box Plot Styling Mean and Standard Deviation

#![allow(unused)]
fn main() {
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,
    ];

    let trace1 = BoxPlot::new(y.clone())
        .name("Only Mean")
        .marker(Marker::new().color(Rgb::new(8, 81, 156)))
        .box_mean(BoxMean::True);
    let trace2 = BoxPlot::new(y)
        .name("Mean and Standard Deviation")
        .marker(Marker::new().color(Rgb::new(8, 81, 156)))
        .box_mean(BoxMean::StandardDeviation);
    let layout = Layout::new().title("Box Plot Styling Mean and Standard Deviation");

    let mut plot = Plot::new();
    plot.set_layout(layout);
    plot.add_trace(trace1);
    plot.add_trace(trace2);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Grouped Horizontal Box Plot

#![allow(unused)]
fn main() {
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",
    ];

    let trace1 = BoxPlot::new_xy(
        vec![0.2, 0.2, 0.6, 1.0, 0.5, 0.4, 0.2, 0.7, 0.9, 0.1, 0.5, 0.3],
        x.clone(),
    )
    .name("Kale")
    .marker(Marker::new().color("3D9970"))
    .box_mean(BoxMean::False)
    .orientation(Orientation::Horizontal);
    let trace2 = BoxPlot::new_xy(
        vec![0.6, 0.7, 0.3, 0.6, 0.0, 0.5, 0.7, 0.9, 0.5, 0.8, 0.7, 0.2],
        x.clone(),
    )
    .name("Radishes")
    .marker(Marker::new().color("FF4136"))
    .box_mean(BoxMean::False)
    .orientation(Orientation::Horizontal);
    let trace3 = BoxPlot::new_xy(
        vec![0.1, 0.3, 0.1, 0.9, 0.6, 0.6, 0.9, 1.0, 0.3, 0.6, 0.8, 0.5],
        x.clone(),
    )
    .name("Carrots")
    .marker(Marker::new().color("FF851B"))
    .box_mean(BoxMean::False)
    .orientation(Orientation::Horizontal);

    let mut plot = Plot::new();
    plot.add_trace(trace1);
    plot.add_trace(trace2);
    plot.add_trace(trace3);

    let layout = Layout::new()
        .title("Grouped Horizontal Box Plot")
        .x_axis(Axis::new().title("normalized moisture").zero_line(false))
        .box_mode(BoxMode::Group);

    plot.set_layout(layout);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Fully Styled Box Plot

#![allow(unused)]
fn main() {
fn fully_styled_box_plot(show: bool, file_name: &str) {
    let rnd_sample = |num, mul| -> Vec<f64> {
        let mut v: Vec<f64> = Vec::with_capacity(num);
        let mut rng = rand::rng();
        let uniform = Uniform::new(0.0, mul).unwrap();
        for _ in 0..num {
            v.push(uniform.sample(&mut rng));
        }
        v
    };

    let x_data = [
        "Carmelo<br>Anthony",
        "Dwyane<br>Wade",
        "Deron<br>Williams",
        "Brook<br>Lopez",
        "Damian<br>Lillard",
        "David<br>West",
        "Blake<br>Griffin",
        "David<br>Lee",
        "Demar<br>Derozan",
    ];
    let y_data = vec![
        rnd_sample(30, 10.0),
        rnd_sample(30, 20.0),
        rnd_sample(30, 25.0),
        rnd_sample(30, 40.0),
        rnd_sample(30, 45.0),
        rnd_sample(30, 30.0),
        rnd_sample(30, 20.0),
        rnd_sample(30, 15.0),
        rnd_sample(30, 43.0),
    ];

    let mut plot = Plot::new();
    let layout = Layout::new()
        .title("Points Scored by the Top 9 Scoring NBA Players in 2012")
        .y_axis(
            Axis::new()
                .auto_range(true)
                .show_grid(true)
                .zero_line(true)
                .dtick(5.0)
                .grid_color(Rgb::new(255, 255, 255))
                .grid_width(1)
                .zero_line_color(Rgb::new(255, 255, 255))
                .zero_line_width(2),
        )
        .margin(Margin::new().left(40).right(30).bottom(80).top(100))
        .paper_background_color(Rgb::new(243, 243, 243))
        .plot_background_color(Rgb::new(243, 243, 243))
        .show_legend(false);
    plot.set_layout(layout);

    for index in 0..x_data.len() {
        let trace = BoxPlot::new(y_data[index].clone())
            .name(x_data[index])
            .box_points(BoxPoints::All)
            .jitter(0.5)
            .whisker_width(0.2)
            .marker(Marker::new().size(6))
            .line(Line::new().width(2.0));
        plot.add_trace(trace);
    }

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Histograms

The following imports have been used to produce the plots below:

#![allow(unused)]
fn main() {
use ndarray::Array;
use plotly::box_plot::{BoxMean, BoxPoints};
use plotly::common::{ErrorData, ErrorType, Line, Marker, Mode, Orientation, Title};
use plotly::histogram::{Bins, Cumulative, HistFunc, HistNorm};
use plotly::layout::{Axis, BarMode, BoxMode, Layout, Margin};
use plotly::{Bar, BoxPlot, Histogram, Plot, color::{NamedColor, Rgb, Rgba}, Scatter};
use rand_distr::{Distribution, Normal, Uniform};

}

The to_inline_html method is used to produce the html plot displayed in this page.

Basic Histogram

#![allow(unused)]
fn main() {
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_html(path);
    }
}
}

Horizontal Histogram

#![allow(unused)]
fn main() {
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")
        .marker(Marker::new().color(NamedColor::Pink));
    let mut plot = Plot::new();

    plot.add_trace(trace);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Overlaid Histogram

#![allow(unused)]
fn main() {
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")
        .opacity(0.5)
        .marker(Marker::new().color(NamedColor::Green));

    let samples2 = sample_normal_distribution(500, 0.0, 1.0);
    let trace2 = Histogram::new(samples2)
        .name("trace 2")
        .opacity(0.6)
        .marker(Marker::new().color(NamedColor::Red));

    let mut plot = Plot::new();
    plot.add_trace(trace1);
    plot.add_trace(trace2);

    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_html(path);
    }
}
}

Stacked Histograms

#![allow(unused)]
fn main() {
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")
        .opacity(0.5)
        .marker(Marker::new().color(NamedColor::Green));

    let samples2 = sample_normal_distribution(500, 0.0, 1.0);
    let trace2 = Histogram::new(samples2)
        .name("trace 2")
        .opacity(0.6)
        .marker(Marker::new().color(NamedColor::Red));

    let mut plot = Plot::new();
    plot.add_trace(trace1);
    plot.add_trace(trace2);

    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_html(path);
    }
}
}

Colored and Styled Histograms

#![allow(unused)]
fn main() {
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);
    let y1 = sample_uniform_distribution(n, 0.0, 1.0);
    let y2 = sample_uniform_distribution(n, 0.0, 2.0);

    let trace1 = Histogram::new_xy(x1, y1)
        .name("control")
        .hist_func(HistFunc::Count)
        .marker(
            Marker::new()
                .color(Rgba::new(255, 100, 102, 0.7))
                .line(Line::new().color(Rgba::new(255, 100, 102, 1.0)).width(1.0)),
        )
        .opacity(0.5)
        .auto_bin_x(false)
        .x_bins(Bins::new(0.5, 2.8, 0.06));
    let trace2 = Histogram::new_xy(x2, y2)
        .name("experimental")
        .hist_func(HistFunc::Count)
        .marker(
            Marker::new()
                .color(Rgba::new(100, 200, 102, 0.7))
                .line(Line::new().color(Rgba::new(100, 200, 102, 1.0)).width(1.0)),
        )
        .opacity(0.75)
        .auto_bin_x(false)
        .x_bins(Bins::new(-3.2, 4.0, 0.06));
    let layout = Layout::new()
        .title("Colored and Styled Histograms")
        .x_axis(Axis::new().title("Value"))
        .y_axis(Axis::new().title("Count"))
        .bar_mode(BarMode::Overlay)
        .bar_gap(0.05)
        .bar_group_gap(0.2);

    let mut plot = Plot::new();
    plot.set_layout(layout);
    plot.add_trace(trace1);
    plot.add_trace(trace2);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Cumulative Histogram

#![allow(unused)]
fn main() {
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)
        .cumulative(Cumulative::new().enabled(true))
        .marker(Marker::new().color(NamedColor::BurlyWood));
    let mut plot = Plot::new();
    plot.add_trace(trace);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Normalized Histogram

#![allow(unused)]
fn main() {
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)
        .hist_norm(HistNorm::Probability)
        .marker(Marker::new().color(NamedColor::SeaGreen));
    let mut plot = Plot::new();
    plot.add_trace(trace);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Specify Binning Function

#![allow(unused)]
fn main() {
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"];

    let trace1 = Histogram::new_xy(x.clone(), y.clone())
        .name("count")
        .hist_func(HistFunc::Count);
    let trace2 = Histogram::new_xy(x.clone(), y.clone())
        .name("sum")
        .hist_func(HistFunc::Sum);

    let mut plot = Plot::new();
    plot.add_trace(trace1);
    plot.add_trace(trace2);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Scientific Charts

The source code for the following examples can also be found here.

KindLink
Contour PlotsContour Plots
HeatmapsHeatmaps

Contour Plots

The following imports have been used to produce the plots below:

#![allow(unused)]
fn main() {
use plotly::common::{ColorScale, ColorScalePalette, Title};
use plotly::contour::Contours;
use plotly::{Contour, HeatMap, Layout, Plot};
use std::f64::consts::PI;
}

The to_inline_html method is used to produce the html plot displayed in this page.

Simple Contour Plot

#![allow(unused)]
fn main() {
fn simple_contour_plot(show: bool, file_name: &str) {
    let n = 200;
    let mut x = Vec::<f64>::new();
    let mut y = Vec::<f64>::new();
    let mut z: Vec<Vec<f64>> = Vec::new();

    for index in 0..n {
        let value = -2.0 * PI + 4.0 * PI * (index as f64) / (n as f64);
        x.push(value);
        y.push(value);
    }

    y.iter().take(n).for_each(|y| {
        let mut row = Vec::<f64>::new();
        x.iter().take(n).for_each(|x| {
            let radius_squared = x.powf(2.0) + y.powf(2.0);
            let zv = x.sin() * y.cos() * radius_squared.sin() / (radius_squared + 1.0).log10();
            row.push(zv);
        });
        z.push(row);
    });

    let trace = Contour::new(x, y, z);
    let mut plot = Plot::new();

    plot.add_trace(trace);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Colorscale for Contour Plot

#![allow(unused)]
fn main() {
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],
        vec![2.5, 3.125, 5., 8.125, 12.5],
        vec![0.625, 1.25, 3.125, 6.25, 10.625],
        vec![0.0, 0.625, 2.5, 5.625, 10.0],
    ];
    let trace = Contour::new_z(z).color_scale(ColorScale::Palette(ColorScalePalette::Jet));

    let layout = Layout::new().title("Colorscale for Contour Plot");
    let mut plot = Plot::new();
    plot.set_layout(layout);
    plot.add_trace(trace);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Customizing Size and Range of a Contour Plot Contours

#![allow(unused)]
fn main() {
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],
        vec![2.5, 3.125, 5., 8.125, 12.5],
        vec![0.625, 1.25, 3.125, 6.25, 10.625],
        vec![0.0, 0.625, 2.5, 5.625, 10.0],
    ];
    let trace = Contour::new_z(z)
        .color_scale(ColorScale::Palette(ColorScalePalette::Jet))
        .auto_contour(false)
        .contours(Contours::new().start(0.0).end(8.0).size(2.0));

    let layout = Layout::new().title("Customizing Size and Range of Contours");
    let mut plot = Plot::new();
    plot.set_layout(layout);
    plot.add_trace(trace);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Customizing Spacing Between X and Y Ticks

#![allow(unused)]
fn main() {
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],
        vec![2.5, 3.125, 5., 8.125, 12.5],
        vec![0.625, 1.25, 3.125, 6.25, 10.625],
        vec![0.0, 0.625, 2.5, 5.625, 10.0],
    ];
    let trace = Contour::new_z(z)
        .color_scale(ColorScale::Palette(ColorScalePalette::Jet))
        .dx(10.0)
        .x0(5.0)
        .dy(10.0)
        .y0(10.0);

    let layout = Layout::new().title("Customizing Size and Range of Contours");
    let mut plot = Plot::new();
    plot.set_layout(layout);
    plot.add_trace(trace);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Heatmaps

The following imports have been used to produce the plots below:

#![allow(unused)]
fn main() {
use plotly::common::{ColorScale, ColorScalePalette, Title};
use plotly::contour::Contours;
use plotly::{Contour, HeatMap, Layout, Plot};
use std::f64::consts::PI;
}

The to_inline_html method is used to produce the html plot displayed in this page.

Basic Heatmap

#![allow(unused)]
fn main() {
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_html(path);
    }
}
}

Financial Charts

The source code for the following examples can also be found here.

KindLink
Time Series and Date AxesTime Series and Date Axes
Candlestick ChartsCandlestick Charts
OHLC ChartsOHLC Charts
RangebreaksRangebreaks

Time Series and Date Axes

The following imports have been used to produce the plots below:

#![allow(unused)]
fn main() {
use plotly::common::{TickFormatStop, Title};
use plotly::layout::{Axis, RangeSelector, RangeSlider, SelectorButton, SelectorStep, StepMode};
use plotly::{Candlestick, Layout, Ohlc, Plot, Scatter};
use serde::Deserialize;
use std::env;
use std::path::PathBuf;
}

The to_inline_html method is used to produce the html plot displayed in this page.

Time Series Plot with Custom Date Range

#![allow(unused)]
fn main() {
fn time_series_plot_with_custom_date_range(show: bool, file_name: &str) {
    let data = load_apple_data();
    let date: Vec<String> = data.iter().map(|d| d.date.clone()).collect();
    let high: Vec<f64> = data.iter().map(|d| d.high).collect();

    let trace = Scatter::new(date, high);

    let mut plot = Plot::new();
    plot.add_trace(trace);

    let layout = Layout::new()
        .x_axis(Axis::new().range(vec!["2016-07-01", "2016-12-31"]))
        .title("Manually Set Date Range");
    plot.set_layout(layout);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Time Series with Range Slider

#![allow(unused)]
fn main() {
fn time_series_with_range_slider(show: bool, file_name: &str) {
    let data = load_apple_data();
    let date: Vec<String> = data.iter().map(|d| d.date.clone()).collect();
    let high: Vec<f64> = data.iter().map(|d| d.high).collect();

    let trace = Scatter::new(date, high);

    let mut plot = Plot::new();
    plot.add_trace(trace);

    let layout = Layout::new()
        .x_axis(Axis::new().range_slider(RangeSlider::new().visible(true)))
        .title("Manually Set Date Range");
    plot.set_layout(layout);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Time Series with Range Selector Buttons

#![allow(unused)]
fn main() {
fn time_series_with_range_selector_buttons(show: bool, file_name: &str) {
    let data = load_apple_data();
    let date: Vec<String> = data.iter().map(|d| d.date.clone()).collect();
    let high: Vec<f64> = data.iter().map(|d| d.high).collect();

    let trace = Scatter::new(date, high);

    let mut plot = Plot::new();
    plot.add_trace(trace);

    let layout = Layout::new().x_axis(
        Axis::new()
            .range_slider(RangeSlider::new().visible(true))
            .range_selector(RangeSelector::new().buttons(vec![
                        SelectorButton::new()
                            .count(1)
                            .label("1m")
                            .step(SelectorStep::Month)
                            .step_mode(StepMode::Backward),
                        SelectorButton::new()
                            .count(6)
                            .label("6m")
                            .step(SelectorStep::Month)
                            .step_mode(StepMode::Backward),
                        SelectorButton::new()
                            .count(1)
                            .label("YTD")
                            .step(SelectorStep::Year)
                            .step_mode(StepMode::ToDate),
                        SelectorButton::new()
                            .count(1)
                            .label("1y")
                            .step(SelectorStep::Year)
                            .step_mode(StepMode::Backward),
                        SelectorButton::new().step(SelectorStep::All),
                    ])),
    );
    plot.set_layout(layout);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Customizing Tick Label Formatting by Zoom Level

#![allow(unused)]
fn main() {
fn customizing_tick_label_formatting_by_zoom_level(show: bool, file_name: &str) {
    let data = load_apple_data();
    let date: Vec<String> = data.iter().map(|d| d.date.clone()).collect();
    let high: Vec<f64> = data.iter().map(|d| d.high).collect();

    let trace = Scatter::new(date, high);

    let mut plot = Plot::new();
    plot.add_trace(trace);

    let layout = Layout::new().x_axis(
        Axis::new()
            .range_slider(RangeSlider::new().visible(true))
            .tick_format_stops(vec![
                TickFormatStop::new()
                    .dtick_range(vec![0, 1000])
                    .value("%H:%M:%S.%L ms"),
                TickFormatStop::new()
                    .dtick_range(vec![1000, 60000])
                    .value("%H:%M:%S s"),
                TickFormatStop::new()
                    .dtick_range(vec![60000, 3600000])
                    .value("%H:%M m"),
                TickFormatStop::new()
                    .dtick_range(vec![3600000, 86400000])
                    .value("%H:%M h"),
                TickFormatStop::new()
                    .dtick_range(vec![86400000, 604800000])
                    .value("%e. %b d"),
                TickFormatStop::new()
                    .dtick_range(vec!["M1", "M12"])
                    .value("%b '%y M"),
            ]),
    );
    plot.set_layout(layout);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Candlestick Charts

The following imports have been used to produce the plots below:

#![allow(unused)]
fn main() {
use plotly::common::{TickFormatStop, Title};
use plotly::layout::{Axis, RangeSelector, RangeSlider, SelectorButton, SelectorStep, StepMode};
use plotly::{Candlestick, Layout, Ohlc, Plot, Scatter};
use serde::Deserialize;
use std::env;
use std::path::PathBuf;
}

The to_inline_html method is used to produce the html plot displayed in this page.

Simple Candlestick Chart

#![allow(unused)]
fn main() {
fn simple_candlestick_chart(show: bool, file_name: &str) {
    let x = vec![
        "2017-01-04",
        "2017-01-05",
        "2017-01-06",
        "2017-01-09",
        "2017-01-10",
        "2017-01-11",
        "2017-01-12",
        "2017-01-13",
        "2017-01-17",
        "2017-01-18",
        "2017-01-19",
        "2017-01-20",
        "2017-01-23",
        "2017-01-24",
        "2017-01-25",
        "2017-01-26",
        "2017-01-27",
        "2017-01-30",
        "2017-01-31",
        "2017-02-01",
        "2017-02-02",
        "2017-02-03",
        "2017-02-06",
        "2017-02-07",
        "2017-02-08",
        "2017-02-09",
        "2017-02-10",
        "2017-02-13",
        "2017-02-14",
        "2017-02-15",
    ];
    let open = vec![
        115.849998, 115.919998, 116.779999, 117.949997, 118.769997, 118.739998, 118.900002,
        119.110001, 118.339996, 120.0, 119.400002, 120.449997, 120.0, 119.550003, 120.419998,
        121.669998, 122.139999, 120.93, 121.150002, 127.029999, 127.980003, 128.309998, 129.130005,
        130.539993, 131.350006, 131.649994, 132.460007, 133.080002, 133.470001, 135.520004,
    ];
    let high = vec![
        116.510002, 116.860001, 118.160004, 119.43, 119.379997, 119.93, 119.300003, 119.620003,
        120.239998, 120.5, 120.089996, 120.449997, 120.809998, 120.099998, 122.099998, 122.440002,
        122.349998, 121.629997, 121.389999, 130.490005, 129.389999, 129.190002, 130.5, 132.089996,
        132.220001, 132.449997, 132.940002, 133.820007, 135.089996, 136.270004,
    ];
    let low = vec![
        115.75, 115.809998, 116.470001, 117.940002, 118.300003, 118.599998, 118.209999, 118.809998,
        118.220001, 119.709999, 119.370003, 119.730003, 119.769997, 119.5, 120.279999, 121.599998,
        121.599998, 120.660004, 120.620003, 127.010002, 127.779999, 128.160004, 128.899994,
        130.449997, 131.220001, 131.119995, 132.050003, 132.75, 133.25, 134.619995,
    ];
    let close = vec![
        116.019997, 116.610001, 117.910004, 118.989998, 119.110001, 119.75, 119.25, 119.040001,
        120.0, 119.989998, 119.779999, 120.0, 120.080002, 119.970001, 121.879997, 121.940002,
        121.949997, 121.629997, 121.349998, 128.75, 128.529999, 129.080002, 130.289993, 131.529999,
        132.039993, 132.419998, 132.119995, 133.289993, 135.020004, 135.509995,
    ];

    let trace1 = Candlestick::new(x, open, high, low, close);

    let mut plot = Plot::new();
    plot.add_trace(trace1);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

OHLC Charts

The following imports have been used to produce the plots below:

#![allow(unused)]
fn main() {
use plotly::common::{TickFormatStop, Title};
use plotly::layout::{Axis, RangeSelector, RangeSlider, SelectorButton, SelectorStep, StepMode};
use plotly::{Candlestick, Layout, Ohlc, Plot, Scatter};
use serde::Deserialize;
use std::env;
use std::path::PathBuf;
}

The to_inline_html method is used to produce the html plot displayed in this page.

Simple OHLC Chart

#![allow(unused)]
fn main() {
fn simple_ohlc_chart(show: bool, file_name: &str) {
    let x = vec![
        "2017-01-04",
        "2017-01-05",
        "2017-01-06",
        "2017-01-09",
        "2017-01-10",
        "2017-01-11",
        "2017-01-12",
        "2017-01-13",
        "2017-01-17",
        "2017-01-18",
        "2017-01-19",
        "2017-01-20",
        "2017-01-23",
        "2017-01-24",
        "2017-01-25",
        "2017-01-26",
        "2017-01-27",
        "2017-01-30",
        "2017-01-31",
        "2017-02-01",
        "2017-02-02",
        "2017-02-03",
        "2017-02-06",
        "2017-02-07",
        "2017-02-08",
        "2017-02-09",
        "2017-02-10",
        "2017-02-13",
        "2017-02-14",
        "2017-02-15",
    ];
    let open = vec![
        115.849998, 115.919998, 116.779999, 117.949997, 118.769997, 118.739998, 118.900002,
        119.110001, 118.339996, 120.0, 119.400002, 120.449997, 120.0, 119.550003, 120.419998,
        121.669998, 122.139999, 120.93, 121.150002, 127.029999, 127.980003, 128.309998, 129.130005,
        130.539993, 131.350006, 131.649994, 132.460007, 133.080002, 133.470001, 135.520004,
    ];
    let high = vec![
        116.510002, 116.860001, 118.160004, 119.43, 119.379997, 119.93, 119.300003, 119.620003,
        120.239998, 120.5, 120.089996, 120.449997, 120.809998, 120.099998, 122.099998, 122.440002,
        122.349998, 121.629997, 121.389999, 130.490005, 129.389999, 129.190002, 130.5, 132.089996,
        132.220001, 132.449997, 132.940002, 133.820007, 135.089996, 136.270004,
    ];
    let low = vec![
        115.75, 115.809998, 116.470001, 117.940002, 118.300003, 118.599998, 118.209999, 118.809998,
        118.220001, 119.709999, 119.370003, 119.730003, 119.769997, 119.5, 120.279999, 121.599998,
        121.599998, 120.660004, 120.620003, 127.010002, 127.779999, 128.160004, 128.899994,
        130.449997, 131.220001, 131.119995, 132.050003, 132.75, 133.25, 134.619995,
    ];
    let close = vec![
        116.019997, 116.610001, 117.910004, 118.989998, 119.110001, 119.75, 119.25, 119.040001,
        120.0, 119.989998, 119.779999, 120.0, 120.080002, 119.970001, 121.879997, 121.940002,
        121.949997, 121.629997, 121.349998, 128.75, 128.529999, 129.080002, 130.289993, 131.529999,
        132.039993, 132.419998, 132.119995, 133.289993, 135.020004, 135.509995,
    ];

    let trace1 = Ohlc::new(x, open, high, low, close);

    let mut plot = Plot::new();
    plot.add_trace(trace1);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Rangebreaks

The following imports have been used to produce the plots below:

#![allow(unused)]
fn main() {
use plotly::common::{Mode, Title};
use plotly::layout::{Axis, RangeBreak};
use plotly::{Layout, Plot, Scatter};
use chrono::{DateTime, Duration};
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;
}

The to_inline_html method is used to produce the html plot displayed in this page.

Series with Weekend and Holiday Gaps

#![allow(unused)]
fn main() {
fn series_with_gaps_for_weekends_and_holidays(show: bool, file_name: &str) {
    let data = load_apple_data();

    // Filter data for the specific date range as in the Python example
    let filtered_data: Vec<&FinData> = data
        .iter()
        .filter(|d| d.date.as_str() >= "2015-12-01" && d.date.as_str() <= "2016-01-15")
        .collect();

    let date: Vec<String> = filtered_data.iter().map(|d| d.date.clone()).collect();
    let high: Vec<f64> = filtered_data.iter().map(|d| d.high).collect();

    let trace = Scatter::new(date, high).mode(plotly::common::Mode::Markers);

    let mut plot = Plot::new();
    plot.add_trace(trace);

    let layout = Layout::new()
        .title("Series with Weekend and Holiday Gaps")
        .x_axis(
            Axis::new()
                .range(vec!["2015-12-01", "2016-01-15"])
                .title("Date"),
        )
        .y_axis(Axis::new().title("Price"));
    plot.set_layout(layout);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Hiding Weekend and Holiday Gaps with Rangebreaks

#![allow(unused)]
fn main() {
fn hiding_weekends_and_holidays_with_rangebreaks(show: bool, file_name: &str) {
    let data = load_apple_data();

    // Filter data for the specific date range as in the Python example
    let filtered_data: Vec<&FinData> = data
        .iter()
        .filter(|d| d.date.as_str() >= "2015-12-01" && d.date.as_str() <= "2016-01-15")
        .collect();

    let date: Vec<String> = filtered_data.iter().map(|d| d.date.clone()).collect();
    let high: Vec<f64> = filtered_data.iter().map(|d| d.high).collect();

    let trace = Scatter::new(date, high).mode(plotly::common::Mode::Markers);

    let mut plot = Plot::new();
    plot.add_trace(trace);

    let layout = Layout::new()
        .title("Hide Weekend and Holiday Gaps with rangebreaks")
        .x_axis(
            Axis::new()
                .range(vec!["2015-12-01", "2016-01-15"])
                .title("Date")
                .range_breaks(vec![
                    plotly::layout::RangeBreak::new()
                        .bounds("sat", "mon"), // hide weekends
                    plotly::layout::RangeBreak::new()
                        .values(vec!["2015-12-25", "2016-01-01"]), // hide Christmas and New Year's
                ]),
        )
        .y_axis(Axis::new().title("Price"));
    plot.set_layout(layout);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Series with Non-Business Hours Gaps

#![allow(unused)]
fn main() {
fn series_with_non_business_hours_gaps(show: bool, file_name: &str) {
    use chrono::NaiveDateTime;
    use chrono::Timelike;
    let (dates, all_values) = generate_business_hours_data();
    let mut values = Vec::with_capacity(all_values.len());

    for (date_str, v) in dates.iter().zip(all_values.iter()) {
        // Parse the date string to extract hour
        // Format is "2020-03-02 09:00:00"
        if let Ok(datetime) = NaiveDateTime::parse_from_str(date_str, "%Y-%m-%d %H:%M:%S") {
            let hour = datetime.hour();
            if (9..17).contains(&hour) {
                values.push(*v);
            } else {
                values.push(f64::NAN);
            }
        } else {
            values.push(f64::NAN);
        }
    }

    let trace = Scatter::new(dates, values).mode(plotly::common::Mode::Markers);
    let mut plot = Plot::new();
    plot.add_trace(trace);
    let layout = Layout::new()
        .title("Series with Non-Business Hour Gaps")
        .x_axis(Axis::new().title("Time").tick_format("%b %d, %Y %H:%M"))
        .y_axis(Axis::new().title("Value"));
    plot.set_layout(layout);
    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Hiding Non-Business Hours Gaps with Rangebreaks

#![allow(unused)]
fn main() {
fn hiding_non_business_hours_with_rangebreaks(show: bool, file_name: &str) {
    let (dates, values) = generate_business_hours_data();
    let trace = Scatter::new(dates, values).mode(plotly::common::Mode::Markers);
    let mut plot = Plot::new();
    plot.add_trace(trace);
    let layout = Layout::new()
        .title("Hide Non-Business Hour Gaps with rangebreaks")
        .x_axis(
            Axis::new()
                .title("Time")
                .tick_format("%b %d, %Y %H:%M")
                .range_breaks(vec![
                    plotly::layout::RangeBreak::new()
                        .bounds(17, 9)
                        .pattern("hour"), // hide hours outside of 9am-5pm
                ]),
        )
        .y_axis(Axis::new().title("Value"));
    plot.set_layout(layout);
    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

3D Charts

The complete source code for the following examples can also be found here.

KindLink
Scatter3DScatter 3D Charts

Scatter 3D Charts

The following imports have been used to produce the plots below:

#![allow(unused)]
fn main() {
use ndarray::Array;
use plotly::common::{
    ColorScale, ColorScalePalette, DashType, Fill, Font, Line, LineShape, Marker, Mode, Title,
};
use plotly::layout::{Axis, BarMode, Layout, Legend, TicksDirection};
use plotly::Sankey;
use rand_distr::{Distribution, Normal, Uniform};
}

The to_inline_html method is used to produce the html plot displayed in this page.

Constructing a basic Scatter 3D plot

#![allow(unused)]
fn main() {
fn simple_scatter3d_plot(show: bool, file_name: &str) {
    let n: usize = 100;
    let t: Vec<f64> = Array::linspace(0., 10., n).into_raw_vec_and_offset().0;
    let y: Vec<f64> = t.iter().map(|x| x.sin()).collect();
    let z: Vec<f64> = t.iter().map(|x| x.cos()).collect();

    let trace = Scatter3D::new(t, y, z).mode(Mode::Markers);
    let mut plot = Plot::new();
    plot.add_trace(trace);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Subplots

The complete source code for the following examples can also be found here.

KindLink
SubplotsScatter Plots
Multiple AxesLine Charts

Subplots

The following imports have been used to produce the plots below:

#![allow(unused)]
fn main() {
use plotly::common::{Font, Side, Title};
use plotly::layout::{Axis, GridPattern, Layout, LayoutGrid, Legend, RowOrder};
use plotly::{Plot, Rgb, Scatter};
}

The to_inline_html method is used to produce the html plot displayed in this page.

Simple Subplot

#![allow(unused)]
fn main() {
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")
        .x_axis("x2")
        .y_axis("y2");

    let mut plot = Plot::new();
    plot.add_trace(trace1);
    plot.add_trace(trace2);

    let layout = Layout::new().grid(
        LayoutGrid::new()
            .rows(1)
            .columns(2)
            .pattern(GridPattern::Independent),
    );
    plot.set_layout(layout);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Subplots with Multiple Traces

#![allow(unused)]
fn main() {
fn subplots_with_multiple_traces(show: bool, file_name: &str) {
    // Create multiple traces for the first subplot (left side)
    let trace1 = Scatter::new(vec![1, 2, 3, 4], vec![10, 11, 12, 13])
        .name("Line 1")
        .mode(plotly::common::Mode::LinesMarkers);

    let trace2 = Scatter::new(vec![1, 2, 3, 4], vec![8, 9, 10, 11])
        .name("Line 2")
        .mode(plotly::common::Mode::LinesMarkers);

    let trace3 = Scatter::new(vec![1, 2, 3, 4], vec![12, 13, 14, 15])
        .name("Line 3")
        .mode(plotly::common::Mode::LinesMarkers);

    // Create traces for the second subplot (right side)
    let trace4 = Scatter::new(vec![1, 2, 3, 4], vec![20, 25, 30, 35])
        .name("Dots 1")
        .x_axis("x2")
        .y_axis("y2")
        .mode(plotly::common::Mode::Markers);

    let trace5 = Scatter::new(vec![1, 2, 3, 4], vec![15, 20, 25, 30])
        .name("Dots 2")
        .x_axis("x2")
        .y_axis("y2")
        .mode(plotly::common::Mode::Markers);

    let mut plot = Plot::new();
    // Add traces to first subplot (default axes)
    plot.add_trace(trace1);
    plot.add_trace(trace2);
    plot.add_trace(trace3);
    // Add traces to second subplot (x2, y2 axes)
    plot.add_trace(trace4);
    plot.add_trace(trace5);

    let layout = Layout::new().title("Subplots with Multiple Traces").grid(
        LayoutGrid::new()
            .rows(1)
            .columns(2)
            .pattern(GridPattern::Independent),
    );
    plot.set_layout(layout);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Custom Sized Subplot

#![allow(unused)]
fn main() {
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")
        .x_axis("x2")
        .y_axis("y2");

    let mut plot = Plot::new();
    plot.add_trace(trace1);
    plot.add_trace(trace2);

    let layout = Layout::new()
        .x_axis(Axis::new().domain(&[0., 0.7]))
        .y_axis2(Axis::new().anchor("x2"))
        .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_html(path);
    }
}
}

Multiple Subplots

#![allow(unused)]
fn main() {
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")
        .x_axis("x2")
        .y_axis("y2");
    let trace3 = Scatter::new(vec![300, 400, 500], vec![600, 700, 800])
        .x_axis("x3")
        .y_axis("y3");
    let trace4 = Scatter::new(vec![4000, 5000, 6000], vec![7000, 8000, 9000])
        .x_axis("x4")
        .y_axis("y4");

    let mut plot = Plot::new();
    plot.add_trace(trace1);
    plot.add_trace(trace2);
    plot.add_trace(trace3);
    plot.add_trace(trace4);

    let layout = Layout::new().grid(
        LayoutGrid::new()
            .rows(2)
            .columns(2)
            .pattern(GridPattern::Independent),
    );
    plot.set_layout(layout);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Stacked Subplots

#![allow(unused)]
fn main() {
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")
        .x_axis("x2")
        .y_axis("y2");
    let trace3 = Scatter::new(vec![3, 4, 5], vec![1000, 1100, 1200])
        .x_axis("x3")
        .y_axis("y3");

    let mut plot = Plot::new();
    plot.add_trace(trace1);
    plot.add_trace(trace2);
    plot.add_trace(trace3);

    let layout = Layout::new().grid(
        LayoutGrid::new()
            .rows(3)
            .columns(1)
            .pattern(GridPattern::Independent)
            .row_order(RowOrder::BottomToTop),
    );
    plot.set_layout(layout);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Stacked Subplots with Shared X Axis

#![allow(unused)]
fn main() {
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")
        .y_axis("y2");
    let trace3 = Scatter::new(vec![3, 4, 5], vec![1000, 1100, 1200]).y_axis("y3");

    let mut plot = Plot::new();
    plot.add_trace(trace1);
    plot.add_trace(trace2);
    plot.add_trace(trace3);

    let layout = Layout::new()
        .y_axis(Axis::new().domain(&[0., 0.33]))
        .legend(Legend::new().trace_order(TraceOrder::Reversed))
        .y_axis2(Axis::new().domain(&[0.33, 0.66]))
        .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_html(path);
    }
}
}

Multiple Custom Sized Subplots

#![allow(unused)]
fn main() {
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)")
        .x_axis("x2")
        .y_axis("y2");
    let trace3 = Scatter::new(vec![1, 2], vec![1, 2])
        .name("(1,2,2)")
        .x_axis("x3")
        .y_axis("y3");
    let trace4 = Scatter::new(vec![1, 2], vec![1, 2])
        .name("{(2,1), (2,2)}")
        .x_axis("x4")
        .y_axis("y4");

    let mut plot = Plot::new();
    plot.add_trace(trace1);
    plot.add_trace(trace2);
    plot.add_trace(trace3);
    plot.add_trace(trace4);

    let layout = Layout::new()
        .title("Multiple Custom Sized Subplots")
        .x_axis(Axis::new().domain(&[0., 0.45]).anchor("y1"))
        .y_axis(Axis::new().domain(&[0.5, 1.]).anchor("x1"))
        .x_axis2(Axis::new().domain(&[0.55, 1.]).anchor("y2"))
        .y_axis2(Axis::new().domain(&[0.8, 1.]).anchor("x2"))
        .x_axis3(Axis::new().domain(&[0.55, 1.]).anchor("y3"))
        .y_axis3(Axis::new().domain(&[0.5, 0.75]).anchor("x3"))
        .x_axis4(Axis::new().domain(&[0., 1.]).anchor("y4"))
        .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_html(path);
    }
}
}

Multiple Axes

The following imports have been used to produce the plots below:

#![allow(unused)]
fn main() {
use plotly::common::{Font, AxisSide, Title};
use plotly::layout::{Axis, GridPattern, Layout, LayoutGrid, Legend, RowOrder};
use plotly::{Plot, Rgb, Scatter};
}

The to_inline_html method is used to produce the html plot displayed in this page.

Two Y Axes

#![allow(unused)]
fn main() {
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")
        .y_axis("y2");

    let mut plot = Plot::new();
    plot.add_trace(trace1);
    plot.add_trace(trace2);

    let layout = Layout::new()
        .title("Double Y Axis Example")
        .y_axis(Axis::new().title("yaxis title"))
        .y_axis2(
            Axis::new()
                .title(Title::from("yaxis2 title").font(Font::new().color(Rgb::new(148, 103, 189))))
                .tick_font(Font::new().color(Rgb::new(148, 103, 189)))
                .overlaying("y")
                .side(AxisSide::Right),
        );
    plot.set_layout(layout);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Multiple Axes

#![allow(unused)]
fn main() {
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")
        .y_axis("y2");
    let trace3 = Scatter::new(vec![4, 5, 6], vec![40_000, 50_000, 60_000]).y_axis("y3");
    let trace4 = Scatter::new(vec![5, 6, 7], vec![400_000, 500_000, 600_000]).y_axis("y4");

    let mut plot = Plot::new();
    plot.add_trace(trace1);
    plot.add_trace(trace2);
    plot.add_trace(trace3);
    plot.add_trace(trace4);

    let layout = Layout::new()
        .title("multiple y-axes example")
        .width(800)
        .x_axis(Axis::new().domain(&[0.3, 0.7]))
        .y_axis(
            Axis::new()
                .title(Title::from("yaxis title").font(Font::new().color("#1f77b4")))
                .tick_font(Font::new().color("#1f77b4")),
        )
        .y_axis2(
            Axis::new()
                .title(Title::from("yaxis2 title").font(Font::new().color("#ff7f0e")))
                .tick_font(Font::new().color("#ff7f0e"))
                .anchor("free")
                .overlaying("y")
                .side(AxisSide::Left)
                .position(0.15),
        )
        .y_axis3(
            Axis::new()
                .title(Title::from("yaxis3 title").font(Font::new().color("#d62728")))
                .tick_font(Font::new().color("#d62728"))
                .anchor("x")
                .overlaying("y")
                .side(AxisSide::Right),
        )
        .y_axis4(
            Axis::new()
                .title(Title::from("yaxis4 title").font(Font::new().color("#9467bd")))
                .tick_font(Font::new().color("#9467bd"))
                .anchor("free")
                .overlaying("y")
                .side(AxisSide::Right)
                .position(0.85),
        );
    plot.set_layout(layout);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Custom Controls

This section covers interactive controls that can be added to plots to modify data or layout attributes dynamically.

Dropdown Menus

Dropdown menus, implemented as an UpdateMenu in Plotly.rs, allow users to modify a plot's data or layout attributes by selecting an option from a list.

You can use a dropdown menu to switch between different sets of data. In this example, we show two different bar charts and use a dropdown to select which one is visible.

#![allow(unused)]
fn main() {
/// Display a bar chart with an associated dropdown selector to show 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(
        BarType::new(vec!["Giraffes", "Orangutans", "Monkeys"], vec![20, 14, 23]).name("Animals"),
    );
    plot.add_trace(
        BarType::new(
            vec!["Little Grebes", "Nuthatches", "Firecrests", "Goldfinches"],
            vec![8, 23, 17, 2],
        )
        .name("Birds")
        .visible(Visible::False),
    );
    let buttons = vec![
        ButtonBuilder::new()
            .label("Animals")
            .push_restyle(BarType::modify_visible(vec![Visible::True, Visible::False]))
            .build()
            .unwrap(),
        ButtonBuilder::new()
            .label("Birds")
            .push_restyle(BarType::modify_visible(vec![Visible::False, Visible::True]))
            .build()
            .unwrap(),
    ];
    plot.set_layout(Layout::new().update_menus(vec![UpdateMenu::new().y(0.8).buttons(buttons)]));

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Update menus can also modify layout attributes. Here, we use buttons (a variation of a dropdown) to change the bar_mode of a bar chart, toggling between grouped and stacked bars.

#![allow(unused)]
fn main() {
/// Display a bar chart, with buttons to toggle between stacked or grouped
/// display modes.
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(
        BarType::new(vec!["giraffes", "orangutans", "monkeys"], vec![20, 14, 23]).name("Africa"),
    );
    plot.add_trace(
        BarType::new(vec!["giraffes", "orangutans", "monkeys"], vec![30, 8, 15]).name("Australia"),
    );
    let buttons = vec![("Group", BarMode::Group), ("Stack", BarMode::Stack)]
        .into_iter()
        .map(|(label, bar_mode)| {
            ButtonBuilder::new()
                .label(label)
                .push_relayout(Layout::modify_bar_mode(bar_mode))
                .build()
                .unwrap()
        })
        .collect_vec();

    plot.set_layout(Layout::new().update_menus(vec![UpdateMenu::new()
            .x(0.1)
            .x_anchor(Anchor::Left)
            .y(1.2)
            .y_anchor(Anchor::Top)
            .ty(UpdateMenuType::Buttons)
            .direction(UpdateMenuDirection::Right)
            .buttons(buttons)]));

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

You can dynamically change the colorscale of a heatmap or contour plot.

#![allow(unused)]
fn main() {
/// Display a heat map, with buttons to allow for toggling of different
/// colorscales.
fn heat_map_with_modifiable_colorscale(show: bool, file_name: &str) {
    type HeatMapType = HeatMap<f64, f64, Vec<f64>>;
    let gauss = |v: i32| (-v as f64 * v as f64 / 200.0).exp();
    let z = (-30..30)
        .map(|x| (-30..30).map(|y| gauss(x) * gauss(y)).collect_vec())
        .collect_vec();
    let trace = HeatMapType::new_z(z).color_scale(ColorScalePalette::Viridis.into());
    let mut plot = Plot::new();
    plot.add_trace(trace);

    let buttons = IntoIterator::into_iter([
        ("Viridis", ColorScalePalette::Viridis),
        ("Portland", ColorScalePalette::Portland),
        ("Blackbody", ColorScalePalette::Blackbody),
    ])
    .map(|(label, palette)| {
        ButtonBuilder::new()
            .label(label)
            .push_restyle(HeatMapType::modify_all_color_scale(palette.into()))
            .build()
            .unwrap()
    })
    .collect_vec();

    plot.set_layout(Layout::new().update_menus(vec![UpdateMenu::new()
            .ty(UpdateMenuType::Buttons)
            .y(0.8)
            .buttons(buttons)]));

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Sliders

Sliders provide interactive controls to animate through different data states or parameters. They are different from range sliders and are typically used for animating through time series data or changing plot parameters dynamically.

Data sliders support various customization options including:

  • Positioning: Control x, y coordinates and anchors
  • Styling: Background color, border color, width, and font
  • Behavior: Active step highlighting, step execution control
  • Dimensions: Length, width, and orientation (horizontal/vertical)
  • Steps: Multiple steps with different data states or parameters

Customizing a Simple Data Slider

Sliders are perfect for stepping through sequential data, like time series. This example creates a slider to show the population of different animals for four consecutive years. It shows how to use the slider API to customize slider features.

#![allow(unused)]
fn main() {
/// Display a bar chart with a data slider to animate through different years,
/// showing how to customize the slider.
fn bar_chart_with_slider_customization(show: bool, file_name: &str) {
    type BarType = Bar<&'static str, i32>;
    let mut plot = Plot::new();
    plot.add_trace(
        BarType::new(vec!["Giraffes", "Orangutans", "Monkeys"], vec![20, 14, 23])
            .name("2019")
            .visible(Visible::True),
    );
    plot.add_trace(
        BarType::new(vec!["Giraffes", "Orangutans", "Monkeys"], vec![25, 18, 28])
            .name("2020")
            .visible(Visible::False),
    );
    plot.add_trace(
        BarType::new(vec!["Giraffes", "Orangutans", "Monkeys"], vec![22, 16, 25])
            .name("2021")
            .visible(Visible::False),
    );
    plot.add_trace(
        BarType::new(vec!["Giraffes", "Orangutans", "Monkeys"], vec![28, 20, 30])
            .name("2022")
            .visible(Visible::False),
    );
    let slider_steps = vec![
        SliderStepBuilder::new()
            .label("2019")
            .value("2019")
            .push_restyle(BarType::modify_visible(vec![
                Visible::True,
                Visible::False,
                Visible::False,
                Visible::False,
            ]))
            .build()
            .unwrap(),
        SliderStepBuilder::new()
            .label("2020")
            .value("2020")
            .push_restyle(BarType::modify_visible(vec![
                Visible::False,
                Visible::True,
                Visible::False,
                Visible::False,
            ]))
            .build()
            .unwrap(),
        SliderStepBuilder::new()
            .label("2021")
            .value("2021")
            .push_restyle(BarType::modify_visible(vec![
                Visible::False,
                Visible::False,
                Visible::True,
                Visible::False,
            ]))
            .build()
            .unwrap(),
        SliderStepBuilder::new()
            .label("2022")
            .value("2022")
            .push_restyle(BarType::modify_visible(vec![
                Visible::False,
                Visible::False,
                Visible::False,
                Visible::True,
            ]))
            .build()
            .unwrap(),
    ];

    plot.set_layout(
        Layout::new()
            .title("Animal Population by Year (Custom Slider Fields)")
            .sliders(vec![Slider::new()
                .active(0)
                .steps(slider_steps)
                .x(0.2)
                .x_anchor(Anchor::Left)
                .y(-0.6)
                .y_anchor(Anchor::Bottom)
                .length(0.8)
                .background_color("#f8fafc")
                .border_color("#bec8d9")
                .border_width(2)
                .tick_color("#e74c3c")
                .tick_length(15)
                .tick_width(4)
                .current_value(
                    SliderCurrentValue::new()
                        .prefix("Year: ")
                        .suffix(" (selected)")
                        .visible(true)
                        .x_anchor(SliderCurrentValueXAnchor::Center)
                        .font(Font::new().size(16).color("#2c3e50"))
                        .offset(5),
                )]),
    );
    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Slider for Parameter Control

Sliders aren't just for data; they can also be used to control parameters in a function. Here, a slider modifies the frequency of a sinusoidal wave, updating the plot in real-time.

#![allow(unused)]
fn main() {
// Sinusoidal Wave with Slider Control
fn sinusoidal_slider_example(show: bool, file_name: &str) {
    use ndarray::Array;

    let mut plot = Plot::new();
    let num_steps = 51; // 0..=50 for frequencies 0.0 to 5.0 in 0.1 steps

    // Add traces, one for each slider step (frequency parameter)
    for step in 0..=num_steps {
        let frequency = step as f64 / 10.0; // 0.0, 0.1, ..., 5.0
        let x: Vec<f64> = Array::linspace(0.0, 10.0, 1001).into_raw_vec().to_vec();
        let y: Vec<f64> = x.iter().map(|&x_val| (frequency * x_val).sin()).collect();
        let trace = Scatter::new(x, y)
            .visible(if step == 10 {
                Visible::True
            } else {
                Visible::False
            }) // Make 10th trace visible
            .line(plotly::common::Line::new().color("#00CED1").width(6.0))
            .name(format!("ν = {frequency:.1}"));
        plot.add_trace(trace);
    }

    // Create slider steps
    let mut steps = Vec::new();
    for i in 0..num_steps {
        let frequency = i as f64 / 10.0;
        let mut visible = vec![Visible::False; num_steps];
        visible[i] = Visible::True;
        let step = SliderStepBuilder::new()
            .label(format!("step-{i}"))
            .value(format!("{frequency:.1}"))
            .push_restyle(Scatter::<f64, f64>::modify_visible(visible))
            .push_relayout(Layout::modify_title(format!(
                "Slider switched to step: {i}"
            )))
            .build()
            .unwrap();
        steps.push(step);
    }
    let layout = Layout::new()
        .title(Title::with_text("Simple Slider Control"))
        .sliders(vec![Slider::new()
            .active(10)
            .pad(Pad::new(50, 0, 0))
            .steps(steps)]);
    plot.set_layout(layout);
    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Advanced Slider: GDP vs. Life Expectancy

This example, based on the Python Plotly Gapminder dataset, demonstrates a more complex use case. A slider is used to animate a bubble chart showing the relationship between GDP per capita and life expectancy across different continents over several decades. See https://plotly.com/python/sliders/

#![allow(unused)]
fn main() {
// GDP per Capita/Life Expectancy Slider (matches second plot in https://plotly.com/python/sliders/)
fn gdp_life_expectancy_slider_example(show: bool, file_name: &str) {
    let data = load_gapminder_data();

    // Get unique years and sort them
    let years: Vec<i32> = data
        .iter()
        .map(|d| d.year)
        .collect::<std::collections::HashSet<_>>()
        .into_iter()
        .sorted()
        .collect();

    // Create color mapping for continents to match the Python plotly example
    let continent_colors = HashMap::from([
        ("Asia".to_string(), "rgb(99, 110, 250)"),
        ("Europe".to_string(), "rgb(239, 85, 59)"),
        ("Africa".to_string(), "rgb(0, 204, 150)"),
        ("Americas".to_string(), "rgb(171, 99, 250)"),
        ("Oceania".to_string(), "rgb(255, 161, 90)"),
    ]);
    let continents: Vec<String> = continent_colors.keys().cloned().sorted().collect();

    let mut plot = Plot::new();

    // Create a trace for each continent for each year
    for &year in &years {
        for continent in &continents {
            let records: Vec<&GapminderData> = data
                .iter()
                .filter(|d| d.continent == *continent && d.year == year)
                .collect();

            if !records.is_empty() {
                let x: Vec<f64> = records.iter().map(|r| r.gdp_per_cap).collect();
                let y: Vec<f64> = records.iter().map(|r| r.life_exp).collect();
                let size: Vec<f64> = records.iter().map(|r| r.pop).collect();
                let hover: Vec<String> = records.iter().map(|r| r.country.clone()).collect();

                let trace = Scatter::new(x, y)
                    .name(continent)
                    .mode(Mode::Markers)
                    .hover_text_array(hover)
                    .visible(if year == years[0] {
                        Visible::True
                    } else {
                        Visible::False
                    })
                    .marker(
                        plotly::common::Marker::new()
                            .color(*continent_colors.get(continent).unwrap())
                            .size_array(size.into_iter().map(|s| s as usize).collect())
                            .size_mode(plotly::common::SizeMode::Area)
                            .size_ref(200000)
                            .size_min(4),
                    );
                plot.add_trace(trace);
            }
        }
    }

    // Create slider steps for each year
    let steps: Vec<SliderStep> = years
        .iter()
        .enumerate()
        .map(|(i, &year)| {
            let mut visible = vec![Visible::False; years.len() * continents.len()];
            let start = i * continents.len();
            let end = start + continents.len();
            visible[start..end].fill(Visible::True);

            SliderStepBuilder::new()
                .label(year.to_string())
                .value(year)
                .push_restyle(Scatter::<f64, f64>::modify_visible(visible))
                .push_relayout(Layout::modify_title(format!(
                    "GDP vs. Life Expectancy ({year})"
                )))
                .build()
                .unwrap()
        })
        .collect();

    let layout = Layout::new()
        .title(Title::with_text(format!(
            "GDP vs. Life Expectancy ({})",
            years[0]
        )))
        .x_axis(
            Axis::new()
                .title(Title::with_text("gdpPercap"))
                .type_(plotly::layout::AxisType::Log),
        )
        .y_axis(
            Axis::new()
                .title(Title::with_text("lifeExp"))
                .range(vec![30.0, 85.0]), // Fixed range for Life Expectancy
        )
        .sliders(vec![Slider::new().active(0).steps(steps).current_value(
            SliderCurrentValue::new()
                .visible(true)
                .prefix("Year: ")
                .x_anchor(SliderCurrentValueXAnchor::Right)
                .font(Font::new().size(20).color("rgb(102, 102, 102)")),
        )]);
    plot.set_layout(layout);
    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}

Animations

Animations in Plotly.rs allow you to create dynamic, interactive visualizations that can play through different data states over time.

GDP vs. Life Expectancy Animation

This example demonstrates an animation based on the Gapminder dataset, showing the relationship between GDP per capita and life expectancy across different continents over several decades. The animation is based on the JavaScript example https://plotly.com/javascript/gapminder-example/ and shows how to create buttons and sliders that interact with the animation mechanism.

#![allow(unused)]
fn main() {
// GDP per Capita/Life Expectancy Animation (animated version of the slider
// example)
fn gdp_life_expectancy_animation_example(show: bool, file_name: &str) {
    use plotly::{
        common::Font,
        common::Pad,
        common::Title,
        layout::Axis,
        layout::{
            update_menu::{ButtonBuilder, UpdateMenu, UpdateMenuDirection, UpdateMenuType},
            Animation, AnimationMode, Frame, FrameSettings, Slider, SliderCurrentValue,
            SliderCurrentValueXAnchor, SliderStepBuilder, TransitionSettings,
        },
        Layout, Plot, Scatter,
    };

    let data = load_gapminder_data();

    // Get unique years and sort them
    let years: Vec<i32> = data
        .iter()
        .map(|d| d.year)
        .collect::<std::collections::HashSet<_>>()
        .into_iter()
        .sorted()
        .collect();

    // Create color mapping for continents to match the Python plotly example
    let continent_colors = HashMap::from([
        ("Asia".to_string(), "rgb(99, 110, 250)"),
        ("Europe".to_string(), "rgb(239, 85, 59)"),
        ("Africa".to_string(), "rgb(0, 204, 150)"),
        ("Americas".to_string(), "rgb(171, 99, 250)"),
        ("Oceania".to_string(), "rgb(255, 161, 90)"),
    ]);
    let continents: Vec<String> = continent_colors.keys().cloned().sorted().collect();

    let mut plot = Plot::new();
    let mut initial_traces = Vec::new();

    for (frame_index, &year) in years.iter().enumerate() {
        let mut frame_traces = plotly::Traces::new();

        for continent in &continents {
            let records: Vec<&GapminderData> = data
                .iter()
                .filter(|d| d.continent == *continent && d.year == year)
                .collect();

            if !records.is_empty() {
                let x: Vec<f64> = records.iter().map(|r| r.gdp_per_cap).collect();
                let y: Vec<f64> = records.iter().map(|r| r.life_exp).collect();
                let size: Vec<f64> = records.iter().map(|r| r.pop).collect();
                let hover: Vec<String> = records.iter().map(|r| r.country.clone()).collect();

                let trace = Scatter::new(x, y)
                    .name(continent)
                    .mode(Mode::Markers)
                    .hover_text_array(hover)
                    .marker(
                        plotly::common::Marker::new()
                            .color(*continent_colors.get(continent).unwrap())
                            .size_array(size.into_iter().map(|s| s as usize).collect())
                            .size_mode(plotly::common::SizeMode::Area)
                            .size_ref(200000)
                            .size_min(4),
                    );

                frame_traces.push(trace.clone());

                // Store traces from first year for initial plot
                if frame_index == 0 {
                    initial_traces.push(trace);
                }
            }
        }

        // Create layout for this frame
        let frame_layout = Layout::new()
            .title(Title::with_text(format!(
                "GDP vs. Life Expectancy ({year})"
            )))
            .x_axis(
                Axis::new()
                    .title(Title::with_text("gdpPercap"))
                    .type_(plotly::layout::AxisType::Log),
            )
            .y_axis(
                Axis::new()
                    .title(Title::with_text("lifeExp"))
                    .range(vec![30.0, 85.0]), // Fixed range for Life Expectancy
            );

        // Add frame with all traces for this year
        plot.add_frame(
            Frame::new()
                .name(format!("frame{frame_index}"))
                .data(frame_traces)
                .layout(frame_layout),
        );
    }

    // Add initial traces to the plot (all traces from first year)
    for trace in initial_traces {
        plot.add_trace(trace);
    }

    // Create animation configuration for playing all frames
    let play_animation = Animation::all_frames().options(
        AnimationOptions::new()
            .mode(AnimationMode::Immediate)
            .frame(FrameSettings::new().duration(500).redraw(false))
            .transition(TransitionSettings::new().duration(300))
            .fromcurrent(true),
    );

    let play_button = ButtonBuilder::new()
        .label("Play")
        .animation(play_animation)
        .build()
        .unwrap();

    let pause_animation = Animation::pause();

    let pause_button = ButtonBuilder::new()
        .label("Pause")
        .animation(pause_animation)
        .build()
        .unwrap();

    let updatemenu = UpdateMenu::new()
        .ty(UpdateMenuType::Buttons)
        .direction(UpdateMenuDirection::Right)
        .buttons(vec![play_button, pause_button])
        .x(0.1)
        .y(1.15)
        .show_active(true)
        .visible(true);

    // Create slider steps for each year
    let mut slider_steps = Vec::new();
    for (i, &year) in years.iter().enumerate() {
        let frame_animation = Animation::frames(vec![format!("frame{}", i)]).options(
            AnimationOptions::new()
                .mode(AnimationMode::Immediate)
                .frame(FrameSettings::new().duration(300).redraw(false))
                .transition(TransitionSettings::new().duration(300)),
        );
        let step = SliderStepBuilder::new()
            .label(year.to_string())
            .value(year)
            .animation(frame_animation)
            .build()
            .unwrap();
        slider_steps.push(step);
    }

    let slider = Slider::new()
        .pad(Pad::new(55, 0, 130))
        .current_value(
            SliderCurrentValue::new()
                .visible(true)
                .prefix("Year: ")
                .x_anchor(SliderCurrentValueXAnchor::Right)
                .font(Font::new().size(20).color("rgb(102, 102, 102)")),
        )
        .steps(slider_steps);

    // Set the layout with initial title, buttons, and slider
    let layout = Layout::new()
        .title(Title::with_text(format!(
            "GDP vs. Life Expectancy ({}) - Click 'Play' to animate",
            years[0]
        )))
        .x_axis(
            Axis::new()
                .title(Title::with_text("gdpPercap"))
                .type_(plotly::layout::AxisType::Log),
        )
        .y_axis(
            Axis::new()
                .title(Title::with_text("lifeExp"))
                .range(vec![30.0, 85.0]), // Fixed range for Life Expectancy
        )
        .update_menus(vec![updatemenu])
        .sliders(vec![slider]);

    plot.set_layout(layout);

    let path = write_example_to_html(&plot, file_name);
    if show {
        plot.show_html(path);
    }
}
}