Skip to content

Commit 252aeb4

Browse files
committed
expose an async API for plotly_static
Signed-off-by: Andrei Gherghescu <[email protected]>
1 parent ef25443 commit 252aeb4

File tree

16 files changed

+751
-240
lines changed

16 files changed

+751
-240
lines changed

docs/book/src/fundamentals/static_image_export.md

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ plotly = { version = "0.13", features = ["static_export_default"] }
3737
## Prerequisites
3838

3939
1. **WebDriver Installation**: You need either chromedriver or geckodriver installed
40-
- Chrome: Download from https://chromedriver.chromium.org/
41-
- Firefox: Download from https://github.com/mozilla/geckodriver/releases
40+
- Chrome: Download from [https://chromedriver.chromium.org/](https://chromedriver.chromium.org/)
41+
- Firefox: Download from [https://github.com/mozilla/geckodriver/releases](https://github.com/mozilla/geckodriver/releases)
4242
- Or use the `static_export_wd_download` feature for automatic download
4343

4444
2. **Browser Installation**: You need Chrome/Chromium or Firefox installed
@@ -91,6 +91,9 @@ plot1.write_image_with_exporter(&mut exporter, "plot1", ImageFormat::PNG, 800, 6
9191
.expect("Failed to export plot1");
9292
plot2.write_image_with_exporter(&mut exporter, "plot2", ImageFormat::JPEG, 800, 600, 1.0)
9393
.expect("Failed to export plot2");
94+
95+
// Always close the exporter to ensure proper release of WebDriver resources
96+
exporter.close();
9497
```
9598

9699
## Supported Formats
@@ -129,8 +132,13 @@ let base64_data = plot.to_base64_with_exporter(&mut exporter, ImageFormat::PNG,
129132
// Get SVG data (vector format, scalable)
130133
let svg_data = plot.to_svg_with_exporter(&mut exporter, 400, 300, 1.0)
131134
.expect("Failed to export plot");
135+
136+
// Always close the exporter to ensure proper release of WebDriver resources
137+
exporter.close();
132138
```
133139

140+
Always call `close()` on the exporter to ensure proper release of WebDriver resources. Due to the nature of WebDriver implementation, close has to be called as resources cannot be automatically dropped or released.
141+
134142
## Advanced Configuration
135143

136144
### Custom WebDriver Configuration
@@ -150,6 +158,10 @@ let mut exporter = StaticExporterBuilder::default()
150158
])
151159
.build()
152160
.expect("Failed to create StaticExporter");
161+
162+
// Always close the exporter to ensure proper release of WebDriver resources
163+
exporter.close();
164+
153165
```
154166

155167
### Parallel Usage
@@ -172,8 +184,17 @@ let mut exporter = StaticExporterBuilder::default()
172184
.webdriver_port(get_unique_port())
173185
.build()
174186
.expect("Failed to build StaticExporter");
187+
188+
// Always close the exporter to ensure proper release of WebDriver resources
189+
exporter.close();
175190
```
176191

192+
### Async support
193+
194+
`plotly_static` package offers an `async` API which can is exposed in `plotly` as well but only if the user passes an `AsyncStaticExporter` to the `write_image_async`, `to_base64_async` and `to_svg_async` functions. The `AsyncStaticExporter` can be built using the `StaticExportBuilder`'s `build_async` method.
195+
196+
For more details check the [`plotly_static` API Documentation](https://docs.rs/plotly_static/)
197+
177198
## Logging Support
178199

179200
Enable logging for debugging and monitoring:
@@ -190,6 +211,9 @@ env_logger::init();
190211
let mut exporter = StaticExporterBuilder::default()
191212
.build()
192213
.expect("Failed to create StaticExporter");
214+
215+
// Always close the exporter to ensure proper release of WebDriver resources
216+
exporter.close();
193217
```
194218

195219
## Performance Considerations
@@ -200,7 +224,7 @@ let mut exporter = StaticExporterBuilder::default()
200224

201225
## Complete Example
202226

203-
See the [static export example](../../../examples/static_export/) for a complete working example that demonstrates:
227+
See the [static export example](https://github.com/plotly/plotly.rs/tree/main/examples/static_export) for a complete working example that demonstrates:
204228

205229
- Multiple export formats
206230
- Exporter reuse

examples/customization/consistent_static_format_export/src/main.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,9 @@ fn line_and_scatter_plot(
162162
info!(" - {file_name}.pdf");
163163
info!(" - {file_name}.svg");
164164
info!(" - {file_name}.png");
165+
166+
// Always close the exporter to ensure proper release of WebDriver resources
167+
exporter.close();
165168
}
166169

167170
fn read_from_file(file_path: &str) -> Vec<Vec<f64>> {

examples/static_export/Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ authors = ["Andrei Gherghescu [email protected]"]
55
edition = "2021"
66
description = "Example demonstrating static image export using plotly_static with WebDriver"
77
readme = "README.md"
8+
default-run = "sync"
89

910
[dependencies]
1011
plotly = { path = "../../plotly", features = ["static_export_default"] }
11-
env_logger = "0.10"
12-
log = "0.4"
12+
env_logger = "0.11"
13+
log = "0.4"
14+
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }

examples/static_export/README.md

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ The `plotly_static` provides a interface for converting Plotly plots into variou
66

77
In this example it is shown how to use the `StaticExporter` with the old style Kaleido API and also with the new style API. Using the former API is fine for one time static exports, but that API will crate an instance of the `StaticExporter` for each `write_image` call. The new style API is recommended for performance as the same instance of the `StaticExporter` can be reused across multiple exports.
88

9-
See also the `Static Image Export` section in the book for a more detailed description.
9+
There are both a sync and an async StaticExporter version which can be build with `build` and `build_async` methods.
1010

11-
## Overview
11+
Refer to the [`plotly_static` API Documentation](https://docs.rs/plotly_static/) a more detailed description.
1212

13+
## Overview
1314

1415
## Features
1516

17+
- **Async/Sync API**
1618
- **Multiple Export Formats**: PNG, JPEG, SVG, PDF
1719
- **Exporter Reuse (new API)**: Efficient reuse of a single `StaticExporter` instance
1820
- **String Export**: Base64 and SVG string output for web applications
@@ -45,17 +47,32 @@ plotly = { version = "0.13", features = ["static_export_geckodriver"] }
4547
plotly = { version = "0.13", features = ["static_export_chromedriver"] }
4648
```
4749

48-
## Running the Example
50+
## Running the Example(s)
51+
52+
To run the `sync` API example
53+
54+
```bash
55+
# Basic run
56+
cargo run --bin sync
57+
58+
# With debug logging
59+
RUST_LOG=debug cargo run --bin sync
60+
61+
# With custom WebDriver path
62+
WEBDRIVER_PATH=/path/to/chromedriver cargo run --bin sync
63+
```
64+
65+
To run the `async` API example
4966

5067
```bash
5168
# Basic run
52-
cargo run
69+
cargo run --bin async
5370

5471
# With debug logging
55-
RUST_LOG=debug cargo run
72+
RUST_LOG=debug cargo run --bin async
5673

5774
# With custom WebDriver path
58-
WEBDRIVER_PATH=/path/to/chromedriver cargo run
75+
WEBDRIVER_PATH=/path/to/chromedriver cargo run --bin async
5976
```
6077

6178
## Output
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use log::info;
2+
use plotly::plotly_static::{ImageFormat, StaticExporterBuilder};
3+
use plotly::{Plot, Scatter};
4+
5+
#[tokio::main]
6+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
7+
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
8+
9+
// Create some plots
10+
let mut plot1 = Plot::new();
11+
plot1.add_trace(Scatter::new(vec![1, 2, 3, 4], vec![10, 15, 13, 17]).name("trace1"));
12+
13+
let mut plot2 = Plot::new();
14+
plot2.add_trace(Scatter::new(vec![2, 3, 4, 5], vec![16, 5, 11, 9]).name("trace2"));
15+
16+
std::fs::create_dir_all("./output").unwrap();
17+
18+
info!("Creating AsyncStaticExporter with default configuration...");
19+
let mut exporter = StaticExporterBuilder::default()
20+
.webdriver_port(5111)
21+
.build_async()
22+
.expect("Failed to create AsyncStaticExporter");
23+
24+
info!("Exporting multiple plots using a single AsyncStaticExporter...");
25+
plot1
26+
.write_image_async(
27+
&mut exporter,
28+
"./output/plot1_async_api",
29+
ImageFormat::PNG,
30+
800,
31+
600,
32+
1.0,
33+
)
34+
.await?;
35+
plot1
36+
.write_image_async(
37+
&mut exporter,
38+
"./output/plot1_async_api",
39+
ImageFormat::JPEG,
40+
800,
41+
600,
42+
1.0,
43+
)
44+
.await?;
45+
plot2
46+
.write_image_async(
47+
&mut exporter,
48+
"./output/plot2_async_api",
49+
ImageFormat::SVG,
50+
800,
51+
600,
52+
1.0,
53+
)
54+
.await?;
55+
plot2
56+
.write_image_async(
57+
&mut exporter,
58+
"./output/plot2_async_api",
59+
ImageFormat::PDF,
60+
800,
61+
600,
62+
1.0,
63+
)
64+
.await?;
65+
66+
info!("Exporting to base64 and SVG strings with async API...");
67+
let _base64_data = plot1
68+
.to_base64_async(&mut exporter, ImageFormat::PNG, 400, 300, 1.0)
69+
.await?;
70+
let _svg_data = plot1.to_svg_async(&mut exporter, 400, 300, 1.0).await?;
71+
72+
// Always close the exporter to ensure proper release of WebDriver resources
73+
exporter.close().await;
74+
75+
info!("Async exports completed successfully!");
76+
Ok(())
77+
}
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
2828
1.0,
2929
)?;
3030
plot3.write_image("./output/plot3_legacy_api", ImageFormat::SVG, 800, 600, 1.0)?;
31-
plot1.write_image("./output/plot3_legacy_api", ImageFormat::PDF, 800, 600, 1.0)?;
31+
32+
plot1.write_image("./output/plot1_legacy_api", ImageFormat::PDF, 800, 600, 1.0)?;
3233

3334
// Create a single StaticExporter to reuse across all plots
3435
// This is more efficient than creating a new exporter for each plot which
3536
// happens implicitly in the calls above using the old API
3637
info!("Creating StaticExporter with default configuration...");
3738
let mut exporter = StaticExporterBuilder::default()
39+
.webdriver_port(5112)
3840
.build()
3941
.expect("Failed to create StaticExporter");
4042

@@ -108,5 +110,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
108110
.expect("Failed to create custom StaticExporter");
109111
*/
110112

113+
// Always close the exporter to ensure proper release of WebDriver resources
114+
exporter.close();
115+
111116
Ok(())
112117
}

plotly/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ dyn-clone = "1"
5252
erased-serde = "0.4"
5353
image = { version = "0.25", optional = true }
5454
plotly_derive = { version = "0.13", path = "../plotly_derive" }
55-
plotly_static = { version = "0.0", path = "../plotly_static", optional = true }
55+
plotly_static = { version = "0.1", path = "../plotly_static", optional = true }
5656
plotly_kaleido = { version = "0.13", path = "../plotly_kaleido", optional = true }
5757
ndarray = { version = "0.16", optional = true }
5858
once_cell = "1"

plotly/src/layout/rangebreaks.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use serde::Serialize;
44
use crate::private::NumOrString;
55

66
/// Struct representing a rangebreak for Plotly axes.
7-
/// See: https://plotly.com/python/reference/layout/xaxis/#layout-xaxis-rangebreaks
7+
/// See: <https://plotly.com/python/reference/layout/xaxis/#layout-xaxis-rangebreaks>
88
#[derive(Debug, Clone, Serialize, PartialEq, FieldSetter)]
99
pub struct RangeBreak {
1010
/// Sets the lower and upper bounds for this range break, e.g. ["sat",

plotly/src/layout/scene.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ impl Rotation {
357357
pub struct Projection {
358358
#[serde(rename = "type")]
359359
projection_type: Option<ProjectionType>,
360-
/// Sets the rotation of the map projection. See https://plotly.com/python/reference/layout/geo/#layout-geo-projection-rotation
360+
// Sets the rotation of the map projection. See https://plotly.com/python/reference/layout/geo/#layout-geo-projection-rotation
361361
#[serde(rename = "rotation")]
362362
rotation: Option<Rotation>,
363363
}

plotly/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
//!
77
//! The `kaleido` and `kaleido_download` features are deprecated since version
88
//! 0.13.0 and will be removed in version 0.14.0. Please migrate to the
9-
//! `plotly_static` and `plotly_static_download` features instead.
9+
//! `plotly_static` and `static_export_*` features instead.
1010
#![recursion_limit = "256"] // lets us use a large serde_json::json! macro for testing crate::layout::Axis
1111
extern crate askama;
1212
extern crate rand;

0 commit comments

Comments
 (0)