Plotly is a data visualization library that enables users to create interactive, publication ready charts and dashboards in Python, R and JavaScript. It is widely used for exploratory data analysis, business reporting and web‑based visualisations.The Plotly website is available at the following url:
https://plotly.com/python/Github repo with source code for the demo
My Github repo with the demo source code in this article is available here:https://github.com/toreaurstadboss/RustWithPlotly2dDemo
Screenshot of the demo
Shown below is a screenshot running the demo. A scatter graph with 3 data series are shown. A color scale with palette Viridis is shown. The color scale also controls the color of the points depending on the point's y value.
Setting up the Plotly crate
Cargo add Plotly adds the Plotly crate. Your Rust application can then star using Plotly.
Please note that Plotly crate can let you output pictures, which then can be shown in a console application or a web site for example.
Rust manifest file
The Rust manifest file - that is Cargo.toml file - looks like this :Cargo.toml
[package]
name = "plotly_plotdemo2d"
version = "0.1.0"
edition = "2026"
[dependencies]
plotly = "0.14.1"
rand = "0.10.2"
Helper methods for creating the 2d scatter plot
The following helper methods sets up shared look of axis and series (called 'traces' in Plotly) font sizes and injects HTML script for a shuffle button to regenerate 2d scatter plots with 3 series with randomized y-values. Also, setting up page background style and overall container layout in the HTML is also shown below.main.rs
fn make_scatter_series(series_name: &str) -> Box<Scatter<i32, i32>> {
let xpoints: Vec<i32> = (1..=10).collect();
let ypoints: Vec<i32> = (1..=10)
.map(|_| rand::random_range(-80..=80))
.collect();
let serieslabels: Vec<String> = xpoints
.iter()
.zip(&ypoints)
.map(|(x, y)| format!("({}, {})", x, y))
.collect();
let seriesmarker = series_marker(series_name == "Series 1", &ypoints);
let seriestrace = Scatter::new(xpoints, ypoints)
.mode(Mode::LinesMarkersText)
.name(series_name)
.text_array(serieslabels)
.text_position(Position::TopCenter)
.text_font(trace_font())
.hover_template("(%{x}, %{y})<extra></extra>")
.marker(seriesmarker);
seriestrace
}
/* Helper methods for drawing the 2d scatter plot */
fn axis_font() -> Font {
Font::new()
.family("Aptos, Segoe UI, Arial, sans-serif")
.size(16)
.color("#F8FAFC")
}
fn trace_font() -> Font {
Font::new()
.family("Aptos, Segoe UI, Arial, sans-serif")
.size(11)
.color("#E2E8F0")
}
fn series_marker(show_scale: bool, ypoints: &[i32]) -> Marker {
let marker = Marker::new()
.size(10)
.color_array(ypoints.to_vec())
.color_scale(ColorScale::Palette(ColorScalePalette::Viridis))
.show_scale(show_scale);
if show_scale {
marker.color_bar(ColorBar::new().title("Intensity (Y value)"))
} else {
marker
}
}
fn apply_page_background(output_file: &PathBuf) {
let html = fs::read_to_string(output_file).expect("failed to read generated Plotly HTML");
let styled_html = html
.replacen("height:100%; width:100%;", "height:100%; width:50%; margin:0 auto;", 1)
.replacen(
"<body>",
&format!(r#"<body style="{}">"#, PAGE_BACKGROUND_STYLE),
1,
);
fs::write(output_file, styled_html).expect("failed to rewrite Plotly HTML with page styling");
}
fn inject_shuffle_button_and_script(output_file: &PathBuf) {
let cargo_root = env!("CARGO_MANIFEST_DIR");
let shuffle_block = std::fs::read_to_string(Path::new(cargo_root).join("assets/shuffle_2dscatter_plot.html")).expect("failed to read shuffle block HTML");
let html = fs::read_to_string(output_file).expect("failed to read generated Plotly HTML");
let updated_html = html.replacen("</body>", &format!("{}\n</body>", shuffle_block), 1);
fs::write(output_file, updated_html).expect("failed to inject shuffle button and script");
}
Main program
The plot is written to html using the plotly::Plot method write_html, this writes the plot into a html file with script references to the Plotly Js library and SVG export Js lib. The code checks if target OS is windows and if so, spawns a new process using std::Command::new("cmd") with arguments pased in to start the file. This will show a dialog box of which browser to open the file and if that is already configured, opens the generated html file into the configured web browser to handle html files in the OS.main.rs
use plotly::{
common::{Anchor, ColorBar, ColorScale, ColorScalePalette, Font, Marker, Mode, Orientation, Position, Title},
layout::{Axis, Legend},
Layout, Plot, Scatter,
};
use std::{fs, path::PathBuf, path::Path, process::Command};
const DTICK_SIZE: f64 = 10.0;
const DTICK_SIZE_X_AXIS: f64 = 1.0;
const OUTPUT_FILE_NAME: &str = "2dscatter.html";
const PAGE_BACKGROUND_STYLE: &str =
r#"margin:0; min-height:100vh; background: linear-gradient(135deg, #081120 0%, #132238 45%, #050816 100%); font-family: 'Aptos', 'Segoe UI', Arial, sans-serif;"#;
fn main() {
println!("Generating a 2d scatter plot using Plotly and Rust - Demo");
let grid_color: &str = "rgba(148, 163, 184, 0.35)";
let mut plot = Plot::new();
for i in 1..=3 {
let series_trace = make_scatter_series(&format!("Series {}", i));
plot.add_trace(series_trace);
}
plot.set_layout(
Layout::new()
.title(
Title::with_text("2D scatter plot with Plotly and Rust").font(
Font::new()
.family("Aptos, Segoe UI, Arial, sans-serif")
.size(24)
.color("#F8FAFC")
))
.font(
Font::new()
.family("Aptos, Segoe UI, Arial, sans-serif")
.size(14)
.color("#E2E8F0"))
.legend(
Legend::new()
.orientation(Orientation::Horizontal)
.x(0.0)
.x_anchor(Anchor::Left)
.y(-0.22)
.y_anchor(Anchor::Top),
)
.x_axis(
Axis::new()
.title(Title::with_text("X Axis").font(axis_font()))
.dtick(DTICK_SIZE_X_AXIS)
.show_grid(true)
.grid_color(grid_color))
.y_axis(
Axis::new()
.title(Title::with_text("Y Axis").font(axis_font()))
.dtick(DTICK_SIZE)
.show_grid(true)
.grid_color(grid_color))
.paper_background_color("rgba(0, 0, 0, 0)")
.plot_background_color("rgba(15, 23, 42, 0.82)"));
let output_file = PathBuf::from(OUTPUT_FILE_NAME);
plot.write_html(&output_file);
apply_page_background(&output_file);
inject_shuffle_button_and_script(&output_file);
#[cfg(target_os = "windows")]
{
println!("Opening 2d scatter plot..");
let _ = Command::new("cmd")
.args(["/C", "start", "", output_file.to_str().unwrap()])
.spawn();
}
}
Shuffle button html static file
To provide generating sample data, a static html file containing a little bit Javascript is used to provide some dynamic reload capability. Randomized data is generated. We use Javascript and not Rust to regenerate the series for the Plotly plot showing the 3 data series in the scatter x,y plot.Shuffle_2d_Scatter_plot.html
<button
id="shuffle-button"
style="
position: fixed;
top: 16px;
left: 16px;
z-index: 1000;
padding: 10px 14px;
border: 0;
border-radius: 10px;
background: #38bdf8;
color: #081120;
font:
600 14px "Aptos",
"Segoe UI",
Arial,
sans-serif;
cursor: pointer;
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.35);
"
>
Shuffle series
</button>
<script>
const graphDiv = document.getElementById("plotly-html-element");
function buildSeries(seriesName, showScale) {
const xValues = Array.from({ length: 10 }, (_, index) => index + 1);
const yValues = Array.from(
{ length: 10 },
() => Math.floor(Math.random() * 161) - 80,
);
return {
x: xValues,
y: yValues,
mode: "lines+markers+text",
name: seriesName,
text: xValues.map((xValue, index) => `(${xValue}, ${yValues[index]})`),
textposition: "top center",
hovertemplate: "(%{x}, %{y})<extra></extra>",
textfont: {
family: "Aptos, Segoe UI, Arial, sans-serif",
size: 11,
color: "#E2E8F0",
},
marker: {
size: 10,
color: yValues,
colorscale: "Viridis",
showscale: showScale,
colorbar : {
title: {
text: "Intensity (Y-value)",
}
},
},
};
}
function shuffleSeries() {
Plotly.react(
graphDiv,
[
buildSeries("Series 1", true),
buildSeries("Series 2", false),
buildSeries("Series 3", false),
],
graphDiv.layout,
);
}
document
.getElementById("shuffle-button")
.addEventListener("click", shuffleSeries);
</script>