Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/scripts/build-book-examples.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ BOOK_EXAMPLES=(
"basic_charts"
"custom_controls"
"financial_charts"
"maps"
"scientific_charts"
"shapes"
"static_export"
Expand Down
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a

### Added

- [[#412](https://github.com/plotly/plotly.rs/pull/412)] Add `Violin` trace type with box, mean line, KDE span, and split/grouped support
- [[#406](https://github.com/plotly/plotly.rs/issues/406)] Expose `plotly.js` 3.1–3.6 attributes
- [[#410](https://github.com/plotly/plotly.rs/pull/410)] Add `Choropleth` (geo subplot) and `ChoroplethMap` (MapLibre `map` subplot) trace types, with a `LocationMode` enum and a dedicated `choropleth::Marker`; add the MapLibre `map` subplot via `LayoutMap`/`MapStyle`/`MapBounds`
- [[#410](https://github.com/plotly/plotly.rs/pull/410)] Add `LayoutGeo` options: `fitbounds` (`GeoFitBounds`), `resolution` (`GeoResolution`, 1:110M/1:50M base-layer detail), and `bgcolor`
- [[#412](https://github.com/plotly/plotly.rs/pull/412)] Add `Violin` trace type with box, mean line, KDE span, and split/grouped support

### Changed

Expand Down
2 changes: 2 additions & 0 deletions docs/book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
- [Rangebreaks](./recipes/financial_charts/rangebreaks.md)
- [3D Charts](./recipes/3dcharts.md)
- [Scatter 3D](./recipes/3dcharts/3dcharts.md)
- [Maps](./recipes/maps.md)
- [Choropleth Maps](./recipes/maps/choropleth_maps.md)
- [Subplots](./recipes/subplots.md)
- [Subplots](./recipes/subplots/subplots.md)
- [Multiple Axes](./recipes/subplots/multiple_axes.md)
Expand Down
7 changes: 7 additions & 0 deletions docs/book/src/recipes/maps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Maps

The source code for the following examples can also be found [here](https://github.com/plotly/plotly.rs/tree/main/examples/maps).

Kind | Link
:---|:----:
Choropleth Maps | [Choropleth Maps](./maps/choropleth_maps.md)
47 changes: 47 additions & 0 deletions docs/book/src/recipes/maps/choropleth_maps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Choropleth Maps

Choropleth maps color geographic regions (countries, states, custom GeoJSON
areas) according to a data value. Two trace types are available:

- [`Choropleth`](https://docs.rs/plotly/latest/plotly/struct.Choropleth.html) —
drawn on the built-in `geo` subplot
([`LayoutGeo`](https://docs.rs/plotly/latest/plotly/layout/struct.LayoutGeo.html)).
Regions are matched by `location_mode` (ISO-3 codes, USA state codes, country
names, or a GeoJSON id).
- [`ChoroplethMap`](https://docs.rs/plotly/latest/plotly/struct.ChoroplethMap.html) —
drawn on the MapLibre `map` subplot
([`LayoutMap`](https://docs.rs/plotly/latest/plotly/layout/struct.LayoutMap.html)).
Regions are always matched against a GeoJSON feature collection via
`feature_id_key`.

The following imports are used in the examples below:

```rust,no_run
use plotly::{
choropleth::{LocationMode, Marker as ChoroplethMarker},
color::Rgb,
common::{ColorBar, ColorScale, ColorScalePalette, Line},
layout::{Center, DragMode, GeoResolution, LayoutGeo, LayoutMap, MapStyle},
Choropleth, ChoroplethMap, Configuration, Layout, Plot,
};
```

The `to_inline_html` method is used to produce the html plots displayed in this
page. The rendered maps require an internet connection (the MapLibre basemap and,
for the second example, the remote GeoJSON are fetched in the browser).

## Choropleth on a geo subplot

```rust,no_run
{{#include ../../../../../examples/maps/src/main.rs:choropleth}}
```

{{#include ../../../../../examples/maps/output/inline_choropleth.html}}

## Choropleth on a MapLibre map subplot

```rust,no_run
{{#include ../../../../../examples/maps/src/main.rs:choropleth_map}}
```

{{#include ../../../../../examples/maps/output/inline_choropleth_map.html}}
6 changes: 2 additions & 4 deletions examples/financial_charts/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -381,10 +381,8 @@ fn hiding_weekends_and_holidays_with_rangebreaks(show: bool, file_name: &str) {
.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
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"));
Expand Down
1 change: 1 addition & 0 deletions examples/maps/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ plotly = { path = "../../plotly" }
plotly_utils = { path = "../plotly_utils" }
csv = "1.3"
reqwest = { version = "0.11", features = ["blocking"] }
serde_json = "1"

153 changes: 132 additions & 21 deletions examples/maps/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
#![allow(dead_code)]

use plotly::{
choropleth::{LocationMode, Marker as ChoroplethMarker},
color::Rgb,
common::{Line, Marker, Mode},
layout::{Axis, Center, DragMode, LayoutGeo, Mapbox, MapboxStyle, Projection, Rotation},
Configuration, DensityMapbox, Layout, Plot, ScatterGeo, ScatterMapbox,
common::{ColorBar, ColorScale, ColorScalePalette, Line, Marker, Mode},
layout::{
Axis, Center, DragMode, GeoResolution, LayoutGeo, LayoutMap, MapStyle, Mapbox, MapboxStyle,
Projection, Rotation,
},
Choropleth, ChoroplethMap, Configuration, DensityMapbox, Layout, Plot, ScatterGeo,
ScatterMapbox,
};
use plotly_utils::write_example_to_html;
use plotly_utils::{write_example_to_html, write_example_to_html_with_inline_config};

fn scatter_mapbox(show: bool, file_name: &str) {
let trace = ScatterMapbox::new(vec![45.5017], vec![-73.5673])
Expand Down Expand Up @@ -35,15 +40,38 @@ fn scatter_geo(show: bool, file_name: &str) {
use csv;
use reqwest;

// Download and parse the CSV
// Download and parse the CSV. If the fetch fails (e.g. no network during a
// book/CI build), warn and skip this example rather than panicking.
let url = "https://raw.githubusercontent.com/plotly/datasets/master/globe_contours.csv";
let req = reqwest::blocking::get(url).unwrap().text().unwrap();
let req = match reqwest::blocking::get(url)
.and_then(|resp| resp.error_for_status())
.and_then(|resp| resp.text())
{
Ok(body) => body,
Err(err) => {
eprintln!("warning: skipping scatter_geo example; failed to fetch {url}: {err}");
return;
}
};
let mut rdr = csv::Reader::from_reader(req.as_bytes());
let headers = rdr.headers().unwrap().clone();
let headers = match rdr.headers() {
Ok(headers) => headers.clone(),
Err(err) => {
eprintln!("warning: skipping scatter_geo example; failed to read CSV headers: {err}");
return;
}
};
let mut rows = vec![];
for result in rdr.records() {
let record = result.unwrap();
rows.push(record);
match result {
Ok(record) => rows.push(record),
Err(err) => {
eprintln!(
"warning: skipping scatter_geo example; failed to parse CSV record: {err}"
);
return;
}
}
}

// Color scale
Expand All @@ -65,23 +93,26 @@ fn scatter_geo(show: bool, file_name: &str) {
for i in 0..scl.len() {
let lat_head = format!("lat-{}", i + 1);
let lon_head = format!("lon-{}", i + 1);
let (lat_idx, lon_idx) = match (
headers.iter().position(|h| h == lat_head),
headers.iter().position(|h| h == lon_head),
) {
(Some(lat_idx), Some(lon_idx)) => (lat_idx, lon_idx),
_ => {
eprintln!(
"warning: skipping scatter_geo example; missing expected columns \
{lat_head}/{lon_head}"
);
return;
}
};
let lat: Vec<f64> = rows
.iter()
.map(|row| {
row.get(headers.iter().position(|h| h == lat_head).unwrap())
.unwrap()
.parse()
.unwrap_or(f64::NAN)
})
.map(|row| row.get(lat_idx).unwrap_or("").parse().unwrap_or(f64::NAN))
.collect();
let lon: Vec<f64> = rows
.iter()
.map(|row| {
row.get(headers.iter().position(|h| h == lon_head).unwrap())
.unwrap()
.parse()
.unwrap_or(f64::NAN)
})
.map(|row| row.get(lon_idx).unwrap_or("").parse().unwrap_or(f64::NAN))
.collect();
all_lats.push(lat);
all_lons.push(lon);
Expand Down Expand Up @@ -152,9 +183,89 @@ fn density_mapbox(show: bool, file_name: &str) {
}
}

/// Classic choropleth on the `geo` subplot, coloring countries by value using
/// ISO-3 country codes.
// ANCHOR: choropleth
fn choropleth(show: bool, file_name: &str) {
let trace = Choropleth::new(
vec![
"USA", "CAN", "MEX", "BRA", "ARG", "FRA", "DEU", "CHN", "IND", "AUS",
],
vec![10.0, 8.0, 6.0, 7.0, 4.0, 9.0, 9.5, 12.0, 11.0, 5.0],
)
.location_mode(LocationMode::Iso3)
.color_scale(ColorScale::Palette(ColorScalePalette::Viridis))
.color_bar(ColorBar::new().title("Score"))
.marker(ChoroplethMarker::new().line(Line::new().width(0.5).color(Rgb::new(80, 80, 80))));

let layout = Layout::new().drag_mode(DragMode::Zoom).geo(
LayoutGeo::new()
.showcountries(true)
.showland(true)
.resolution(GeoResolution::OneOverFiftyMillion),
);

let mut plot = Plot::new();
plot.add_trace(trace);
plot.set_layout(layout);
plot.set_configuration(Configuration::default().responsive(true).fill_frame(true));

let path = write_example_to_html_with_inline_config(
&plot,
file_name,
Some(Configuration::default().scroll_zoom(false)), // book friendly intline HTML
);
if show {
plot.show_html(path);
}
}
// ANCHOR_END: choropleth

/// Choropleth on the MapLibre `map` subplot. Regions are matched against a
/// GeoJSON feature collection (referenced here by URL) via `feature_id_key`.
// ANCHOR: choropleth_map
fn choropleth_map(show: bool, file_name: &str) {
let geojson_url =
"https://raw.githubusercontent.com/python-visualization/folium/main/tests/us-states.json";

let trace = ChoroplethMap::new(
vec!["AL", "AK", "AZ", "CA", "NY", "TX"],
vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
)
.geojson(serde_json::json!(geojson_url))
.feature_id_key("id")
.color_scale(ColorScale::Palette(ColorScalePalette::Bluered))
.show_scale(true)
.marker(ChoroplethMarker::new().opacity(0.7));

let layout = Layout::new().drag_mode(DragMode::Zoom).map(
LayoutMap::new()
.style(MapStyle::CartoPositron)
.center(Center::new(38.0, -96.0))
.zoom(3.0),
);

let mut plot = Plot::new();
plot.add_trace(trace);
plot.set_layout(layout);
plot.set_configuration(Configuration::default().responsive(true).fill_frame(true));

let path = write_example_to_html_with_inline_config(
&plot,
file_name,
Some(Configuration::default().scroll_zoom(false)), // book friendly intline HTML
);
if show {
plot.show_html(path);
}
}
// ANCHOR_END: choropleth_map

fn main() {
// Change false to true on any of these lines to display the example.
scatter_mapbox(false, "scatter_mapbox");
scatter_geo(false, "scatter_geo");
density_mapbox(false, "density_mapbox");
choropleth(false, "choropleth");
choropleth_map(false, "choropleth_map");
}
47 changes: 39 additions & 8 deletions examples/plotly_utils/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use plotly::Plot;
use plotly::{Configuration, Plot};

/// Write a plot to HTML files for documentation and display
///
Expand All @@ -11,17 +11,48 @@ use plotly::Plot;
/// * `plot` - The plot to write to HTML
/// * `name` - The base name for the HTML files (without extension)
///
/// # Returns
///
/// The path to the standalone HTML file
/// # Returns the path to the standalone HTML file
pub fn write_example_to_html(plot: &Plot, name: &str) -> String {
write_example_to_html_with_inline_config(plot, name, None)
}

/// Write a plot to HTML files for documentation and display
///
/// This function creates both an inline HTML file (for mdbook inclusion) and
/// a standalone HTML file (for direct viewing). The inline file is prefixed
/// with "inline_" and both files are placed in the "./output" directory.
/// The plot for inline HTML display can be configured with the provided
/// `inline_config` when provided, otherwise the standalone inherited plot
/// configuration is used.
///
/// # Arguments
///
/// * `plot` - The plot to write to HTML
/// * `name` - The base name for the HTML files (without extension)
/// * `inline_config` - The configuration to use for the inline HTML file
///
/// # Returns the path to the standalone HTML file
pub fn write_example_to_html_with_inline_config(
plot: &Plot,
name: &str,
inline_config: Option<Configuration>,
) -> String {
std::fs::create_dir_all("./output").unwrap();

let standalone_config = plot.configuration().clone();
let inline_config = inline_config.unwrap_or(standalone_config.clone());

// Write inline HTML
let html = plot.to_inline_html(Some(name));
let path = format!("./output/inline_{name}.html");
std::fs::write(path, html).unwrap();
let mut inline_plot = plot.clone();
inline_plot.set_configuration(inline_config);
let html = inline_plot.to_inline_html(Some(name));
let inline_path = format!("./output/inline_{name}.html");
std::fs::write(inline_path, html).unwrap();

// Write standalone HTML
let mut standalone_plot = plot.clone();
standalone_plot.set_configuration(standalone_config);
let path = format!("./output/{name}.html");
plot.write_html(&path);
standalone_plot.write_html(&path);
path
}
2 changes: 2 additions & 0 deletions plotly/src/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,8 @@ pub enum PlotType {
Bar,
Box,
Candlestick,
Choropleth,
ChoroplethMap,
Contour,
HeatMap,
Histogram,
Expand Down
Loading
Loading