Skip to content

Commit 94f0ef2

Browse files
committed
improve scatter_geo
- add all projection types enum variants - add rotation field to Projection - replicate scatter_geo example 1-to-1 in examples/maps/src/main.rs Signed-off-by: Andrei Gherghescu <[email protected]>
1 parent 5e9d82f commit 94f0ef2

File tree

6 files changed

+545
-54
lines changed

6 files changed

+545
-54
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
99
- [[#281]((https://github.com/plotly/plotly.rs/pull/xxx))] Update to askama 0.13.0
1010
- [[#287]](https://github.com/plotly/plotly.rs/pull/287) Added functionality for callbacks (using wasm)
1111
- [[#289]](https://github.com/plotly/plotly.rs/pull/289) Fixes Kaleido static export for MacOS targets by removing `--disable-gpu` flag for MacOS
12-
- [[#290]](https://github.com/plotly/plotly.rs/pull/289) Remove `--disable-gpu` flag for Kaleido static-image generation for all targets.
12+
- [[#291]](https://github.com/plotly/plotly.rs/pull/291) Remove `--disable-gpu` flag for Kaleido static-image generation for all targets.
13+
- [[#299]](https://github.com/plotly/plotly.rs/pull/299) Added customdata field to HeatMap
14+
- [[#303]](https://github.com/plotly/plotly.rs/pull/303) Split layout mod.rs into modules
15+
- [[#304]](https://github.com/plotly/plotly.rs/pull/304) Refactored examples to allow fo generation of full html files
1316

1417
### Fixed
1518
- [[#284](https://github.com/plotly/plotly.rs/pull/284)] Allow plotly package to be compiled for android
19+
- [[#298](https://github.com/plotly/plotly.rs/pull/298)] Added support for layout axis scaleratio
20+
- [[#301](https://github.com/plotly/plotly.rs/pull/301)] Added ScatterGeo trace and LayoutGeo support
1621

1722
## [0.12.1] - 2025-01-02
1823
### Fixed

examples/maps/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@ edition = "2021"
66

77
[dependencies]
88
plotly = { path = "../../plotly" }
9+
csv = "1.3"
10+
reqwest = { version = "0.11", features = ["blocking"] }
11+

examples/maps/src/main.rs

Lines changed: 110 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
#![allow(dead_code)]
22

33
use plotly::{
4-
common::Marker,
5-
layout::{Center, DragMode, Mapbox, MapboxStyle, Margin},
6-
DensityMapbox, Layout, Plot, ScatterMapbox,
4+
color::Rgb,
5+
common::{Line, Marker, Mode},
6+
layout::{
7+
Axis, Center, DragMode, LayoutGeo, Mapbox, MapboxStyle, Margin, Projection, Rotation,
8+
},
9+
DensityMapbox, Layout, Plot, ScatterGeo, ScatterMapbox,
710
};
811

912
fn scatter_mapbox(show: bool, file_name: &str) {
@@ -30,6 +33,109 @@ fn scatter_mapbox(show: bool, file_name: &str) {
3033
}
3134
}
3235

36+
/// Reproduce the Earth from https://plotly.com/javascript/lines-on-maps/#lines-on-an-orthographic-map
37+
fn scatter_geo(show: bool, file_name: &str) {
38+
use csv;
39+
use reqwest;
40+
41+
// Download and parse the CSV
42+
let url = "https://raw.githubusercontent.com/plotly/datasets/master/globe_contours.csv";
43+
let req = reqwest::blocking::get(url).unwrap().text().unwrap();
44+
let mut rdr = csv::Reader::from_reader(req.as_bytes());
45+
let headers = rdr.headers().unwrap().clone();
46+
let mut rows = vec![];
47+
for result in rdr.records() {
48+
let record = result.unwrap();
49+
rows.push(record);
50+
}
51+
52+
// Color scale
53+
let scl = [
54+
"rgb(213,62,79)",
55+
"rgb(244,109,67)",
56+
"rgb(253,174,97)",
57+
"rgb(254,224,139)",
58+
"rgb(255,255,191)",
59+
"rgb(230,245,152)",
60+
"rgb(171,221,164)",
61+
"rgb(102,194,165)",
62+
"rgb(50,136,189)",
63+
];
64+
65+
// Unpack lat/lon columns
66+
let mut all_lats: Vec<Vec<f64>> = vec![];
67+
let mut all_lons: Vec<Vec<f64>> = vec![];
68+
for i in 0..scl.len() {
69+
let lat_head = format!("lat-{}", i + 1);
70+
let lon_head = format!("lon-{}", i + 1);
71+
let lat: Vec<f64> = rows
72+
.iter()
73+
.map(|row| {
74+
row.get(headers.iter().position(|h| h == lat_head).unwrap())
75+
.unwrap()
76+
.parse()
77+
.unwrap_or(f64::NAN)
78+
})
79+
.collect();
80+
let lon: Vec<f64> = rows
81+
.iter()
82+
.map(|row| {
83+
row.get(headers.iter().position(|h| h == lon_head).unwrap())
84+
.unwrap()
85+
.parse()
86+
.unwrap_or(f64::NAN)
87+
})
88+
.collect();
89+
all_lats.push(lat);
90+
all_lons.push(lon);
91+
}
92+
93+
// Build traces
94+
let mut plot = Plot::new();
95+
for i in 0..scl.len() {
96+
let trace = ScatterGeo::new(all_lats[i].clone(), all_lons[i].clone())
97+
.mode(Mode::Lines)
98+
.line(Line::new().width(2.0).color(scl[i]));
99+
plot.add_trace(trace);
100+
}
101+
102+
let layout = Layout::new()
103+
.drag_mode(DragMode::Zoom)
104+
.margin(Margin::new().top(0).left(0).bottom(0).right(0))
105+
.geo(
106+
LayoutGeo::new()
107+
.showocean(true)
108+
.showlakes(true)
109+
.showcountries(true)
110+
.showland(true)
111+
.oceancolor(Rgb::new(0, 255, 255))
112+
.lakecolor(Rgb::new(0, 255, 255))
113+
.landcolor(Rgb::new(230, 145, 56))
114+
.lataxis(
115+
Axis::new()
116+
.show_grid(true)
117+
.grid_color(Rgb::new(102, 102, 102)),
118+
)
119+
.lonaxis(
120+
Axis::new()
121+
.show_grid(true)
122+
.grid_color(Rgb::new(102, 102, 102)),
123+
)
124+
.projection(
125+
Projection::new()
126+
.projection_type(plotly::layout::ProjectionType::Orthographic)
127+
.rotation(Rotation::new().lon(-100.0).lat(40.0)),
128+
),
129+
);
130+
131+
plot.set_layout(layout);
132+
133+
let path = write_example_to_html(&plot, file_name);
134+
if show {
135+
plot.show_html(path);
136+
}
137+
}
138+
33139
fn density_mapbox(show: bool, file_name: &str) {
34140
let trace = DensityMapbox::new(vec![45.5017], vec![-73.5673], vec![0.75]).zauto(true);
35141

@@ -63,5 +169,6 @@ fn write_example_to_html(plot: &Plot, name: &str) -> String {
63169
fn main() {
64170
// Change false to true on any of these lines to display the example.
65171
scatter_mapbox(false, "scatter_mapbox");
172+
scatter_geo(false, "scatter_geo");
66173
density_mapbox(false, "density_mapbox");
67174
}

plotly/src/layout/geo.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
use plotly_derive::FieldSetter;
2+
use serde::Serialize;
3+
4+
use crate::color::Color;
5+
use crate::layout::{Axis, Center, Projection};
6+
7+
#[derive(Serialize, Clone, Debug, FieldSetter)]
8+
9+
pub struct LayoutGeo {
10+
/// Sets the latitude and longitude of the center of the map.
11+
center: Option<Center>,
12+
/// Sets the domain within which the mapbox will be drawn.
13+
/// Sets the zoom level of the map.
14+
zoom: Option<u8>,
15+
/// Sets the projection of the map
16+
#[field_setter(default = "Projection::new().projection_type(ProjectionType::Orthographic)")]
17+
projection: Option<Projection>,
18+
/// If to show the ocean or not
19+
#[field_setter(default = "Some(true)")]
20+
showocean: Option<bool>,
21+
/// Sets the color of the ocean
22+
#[field_setter(default = "'rgb(0, 255, 255)'")]
23+
oceancolor: Option<Box<dyn Color>>,
24+
/// If to show the land or not
25+
showland: Option<bool>,
26+
/// Sets the color of the land
27+
landcolor: Option<Box<dyn Color>>,
28+
/// If to show lakes or not
29+
showlakes: Option<bool>,
30+
/// Sets the color of the lakes
31+
lakecolor: Option<Box<dyn Color>>,
32+
/// If to show countries (borders) or not
33+
showcountries: Option<bool>,
34+
/// Configures the longitude axis
35+
lonaxis: Option<Axis>,
36+
/// Configures the latitude axis
37+
lataxis: Option<Axis>,
38+
// Sets the coastline stroke width (in px).
39+
#[field_setter(default = "Some(1)")]
40+
coastlinewidth: Option<u8>,
41+
}
42+
43+
impl LayoutGeo {
44+
pub fn new() -> Self {
45+
Default::default()
46+
}
47+
}
48+
49+
#[cfg(test)]
50+
mod tests {
51+
use serde_json::{json, to_value};
52+
53+
use super::*;
54+
use crate::color::Rgb;
55+
use crate::layout::{Axis, Center, Projection, ProjectionType, Rotation};
56+
57+
#[test]
58+
fn serialize_layout_geo() {
59+
let geo = LayoutGeo::new()
60+
.center(Center::new(10.0, 20.0))
61+
.zoom(5)
62+
.projection(
63+
Projection::new()
64+
.projection_type(ProjectionType::Mercator)
65+
.rotation(Rotation::new().lat(1.0).lon(2.0).roll(4.0)),
66+
)
67+
.showocean(true)
68+
.oceancolor(Rgb::new(0, 255, 255))
69+
.showland(true)
70+
.landcolor(Rgb::new(100, 200, 100))
71+
.showlakes(false)
72+
.lakecolor(Rgb::new(50, 50, 200))
73+
.showcountries(true)
74+
.lonaxis(Axis::new().title("Longitude"))
75+
.lataxis(Axis::new().title("Latitude"))
76+
.coastlinewidth(2);
77+
78+
let expected = json!({
79+
"center": {"lat": 10.0, "lon": 20.0},
80+
"zoom": 5,
81+
"projection": {"type": "mercator", "rotation": {"lat": 1.0, "lon": 2.0, "roll": 4.0}},
82+
"showocean": true,
83+
"oceancolor": "rgb(0, 255, 255)",
84+
"showland": true,
85+
"landcolor": "rgb(100, 200, 100)",
86+
"showlakes": false,
87+
"lakecolor": "rgb(50, 50, 200)",
88+
"showcountries": true,
89+
"lataxis": { "title": { "text": "Latitude" } },
90+
"lonaxis": { "title": { "text": "Longitude" } },
91+
"coastlinewidth": 2
92+
});
93+
assert_eq!(to_value(geo).unwrap(), expected);
94+
}
95+
}

plotly/src/layout/mod.rs

Lines changed: 6 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub mod update_menu;
1313

1414
mod annotation;
1515
mod axis;
16+
mod geo;
1617
mod grid;
1718
mod legend;
1819
mod mapbox;
@@ -27,6 +28,7 @@ pub use self::axis::{
2728
RangeMode, RangeSelector, RangeSlider, RangeSliderYAxis, SelectorButton, SelectorStep,
2829
SliderRangeMode, StepMode, TicksDirection, TicksPosition,
2930
};
31+
pub use self::geo::LayoutGeo;
3032
pub use self::grid::{GridDomain, GridPattern, GridXSide, GridYSide, LayoutGrid, RowOrder};
3133
pub use self::legend::{Legend, TraceOrder};
3234
pub use self::mapbox::{Center, Mapbox, MapboxStyle};
@@ -35,6 +37,7 @@ pub use self::modes::{
3537
};
3638
pub use self::scene::{
3739
Camera, CameraCenter, DragMode, DragMode3D, HoverMode, LayoutScene, Projection, ProjectionType,
40+
Rotation,
3841
};
3942
pub use self::shape::{
4043
ActiveShape, DrawDirection, FillRule, NewShape, Shape, ShapeLayer, ShapeLine, ShapeSizeMode,
@@ -137,41 +140,6 @@ pub enum SelectDirection {
137140
Any,
138141
}
139142

140-
#[derive(Serialize, Clone, Debug, FieldSetter)]
141-
pub struct Geo {
142-
/// Sets the zoom level of the map.
143-
zoom: Option<u8>,
144-
/// Sets the projection of the map
145-
#[field_setter(default = "Projection::new().projection_type(ProjectionType::Orthographic)")]
146-
projection: Option<Projection>,
147-
/// If to show the ocean or not
148-
#[field_setter(default = "Some(true)")]
149-
showocean: Option<bool>,
150-
/// Sets the color of the ocean
151-
#[field_setter(default = "'rgb(0, 255, 255)'")]
152-
oceancolor: Option<Box<dyn Color>>,
153-
/// If to show the land or not
154-
showland: Option<bool>,
155-
/// Sets the color of the land
156-
landcolor: Option<Box<dyn Color>>,
157-
/// If to show lakes or not
158-
showlakes: Option<bool>,
159-
/// Sets the color of the lakes
160-
lakecolor: Option<Box<dyn Color>>,
161-
/// If to show countries (borders) or not
162-
showcountries: Option<bool>,
163-
/// Configures the longitude axis
164-
lonaxis: Option<Axis>,
165-
/// Configures the latitude axis
166-
lataxis: Option<Axis>,
167-
}
168-
169-
impl Geo {
170-
pub fn new() -> Self {
171-
Default::default()
172-
}
173-
}
174-
175143
#[serde_with::skip_serializing_none]
176144
#[derive(Serialize, Debug, Clone, FieldSetter)]
177145
pub struct Template {
@@ -309,7 +277,10 @@ pub struct LayoutFields {
309277
y_axis8: Option<Box<Axis>>,
310278
#[serde(rename = "zaxis8")]
311279
z_axis8: Option<Box<Axis>>,
280+
// ternary: Option<LayoutTernary>,
312281
scene: Option<LayoutScene>,
282+
geo: Option<LayoutGeo>,
283+
// polar: Option<LayoutPolar>,
313284
annotations: Option<Vec<Annotation>>,
314285
shapes: Option<Vec<Shape>>,
315286
#[serde(rename = "newshape")]

0 commit comments

Comments
 (0)