use std::collections::HashMap;
use super::codec::impl_codec;
#[cfg(feature = "document-read")]
use super::codec::{Decode, Reader};
#[cfg(feature = "document-write")]
use super::codec::{Encode, Writer};
#[cfg(feature = "document-read")]
use super::DocumentError;
use crate::layout::Axis as LayoutAxis;
use crate::layout::{CellId, Extent, Inset, Placement, Track};
use crate::plot::chrome::axis::{Axis, AxisPlacement, PolarRing};
use crate::plot::chrome::legend::{
AestheticSource, BinSpacing, ColorbarSpec, Legend, LegendBody, LegendKey, LegendKeySpec,
StackBody,
};
use crate::plot::geom::Channel;
use crate::plot::projection::{
ChromeStrategy, CustomProjection, PolarEdgeStyle, PolarProjection, Projection,
};
use crate::scales::chrome::{Anchor, AxisSide, LegendSide};
impl_codec! {
enum LayoutAxis {
0 => Width,
1 => Height,
}
newtype CellId;
enum Extent {
0 => Sum { px, inches, percent },
1 => Min(a, b),
2 => Max(a, b),
3 => TrackOf { grid, axis, track, span },
}
enum Track {
0 => Fixed(extent),
1 => Fr(share),
2 => Auto,
}
struct Inset { left, right, top, bottom, width, height }
struct Placement { row, col, row_span, col_span, inset }
}
impl_codec! {
enum ChromeStrategy {
0 => PatchSlots,
1 => InsidePanel,
}
enum PolarEdgeStyle {
0 => Geodesic,
1 => Chord,
}
struct CustomProjection { outline, x_major, x_minor, y_major, y_minor, x_channel, y_channel }
enum Projection {
0 => Cartesian,
1 => Polar(p),
2 => Custom(p),
}
}
#[cfg(feature = "document-write")]
impl Encode for PolarProjection {
fn encode(&self, w: &mut Writer) {
self.angle_channel().encode(w);
self.radius_channel().encode(w);
self.theta_start().encode(w);
self.theta_end().encode(w);
self.inner_radius_frac().encode(w);
self.outer_radius_frac().encode(w);
self.edge_style().encode(w);
self.theta_break_fracs().to_vec().encode(w);
self.is_fit_to_bbox().encode(w);
}
}
#[cfg(feature = "document-read")]
impl Decode for PolarProjection {
fn decode(r: &mut Reader<'_>) -> Result<Self, DocumentError> {
let angle = String::decode(r)?;
let radius = String::decode(r)?;
let theta_start = f64::decode(r)?;
let theta_end = f64::decode(r)?;
let inner = f64::decode(r)?;
let outer = f64::decode(r)?;
let edges = PolarEdgeStyle::decode(r)?;
let breaks = Vec::<f64>::decode(r)?;
let fit = bool::decode(r)?;
Ok(PolarProjection::full_circle()
.channels(angle, radius)
.theta_range(theta_start, theta_end)
.inner_radius(inner)
.outer_radius(outer)
.edges(edges)
.theta_breaks(breaks)
.fit_to_bbox(fit))
}
}
impl_codec! {
enum AxisSide {
0 => Left,
1 => Right,
2 => Bottom,
3 => Top,
}
enum PolarRing {
0 => Outer,
1 => Inner,
}
enum AxisPlacement {
0 => Cartesian(side),
1 => PolarRadius { theta_frac },
2 => PolarAngular(ring),
}
}
#[cfg(feature = "document-write")]
impl Encode for Axis {
fn encode(&self, w: &mut Writer) {
self.scale_name().map(str::to_string).encode(w);
self.placement().encode(w);
self.title_ref().map(str::to_string).encode(w);
}
}
#[cfg(feature = "document-read")]
impl Decode for Axis {
fn decode(r: &mut Reader<'_>) -> Result<Self, DocumentError> {
let scale_name = Option::<String>::decode(r)?;
let placement = AxisPlacement::decode(r)?;
let title = Option::<String>::decode(r)?;
let axis = match (scale_name, &title) {
(Some(name), _) => Axis::rail(name, placement),
(None, Some(t)) => Axis::title_only(t.clone(), placement),
(None, None) => Axis::title_only(String::new(), placement),
};
Ok(match title {
Some(t) => axis.title(t),
None => axis,
})
}
}
impl_codec! {
enum Anchor {
0 => TopLeft,
1 => TopCenter,
2 => TopRight,
3 => CenterLeft,
4 => Center,
5 => CenterRight,
6 => BottomLeft,
7 => BottomCenter,
8 => BottomRight,
}
enum LegendSide {
0 => Left,
1 => Right,
2 => Top,
3 => Bottom,
4 => InPanel { anchor, inset_pt },
}
enum LegendKey {
0 => Point,
1 => Line,
2 => Rect,
3 => Text,
}
enum AestheticSource {
0 => Scaled(scale_name),
1 => Fixed(value),
}
struct LegendKeySpec { kind, bindings }
struct StackBody { keys, binned }
struct ColorbarSpec { samples, stepped, bindings }
enum LegendBody {
0 => Stack(body),
1 => Colorbar(spec),
}
enum BinSpacing {
0 => Proportional,
1 => Equal,
}
struct Legend {
side,
title,
domain_scale,
body,
open_lower,
open_upper,
bin_spacing,
theme_variant,
merge,
}
}
impl_codec! {
enum Channel {
0 => Constant(v),
1 => Data(col),
2 => RawConstant(v),
3 => RawData(col),
}
}
#[cfg(any(feature = "document-read", feature = "document-write"))]
pub(crate) struct GeomParts {
pub(crate) kind: String,
pub(crate) keys: Option<crate::scales::value::DataColumn>,
pub(crate) channels: HashMap<String, Channel>,
}
#[cfg(feature = "document-write")]
impl Encode for GeomParts {
fn encode(&self, w: &mut Writer) {
self.kind.encode(w);
self.keys.encode(w);
self.channels.encode(w);
}
}
#[cfg(feature = "document-read")]
impl Decode for GeomParts {
fn decode(r: &mut Reader<'_>) -> Result<Self, DocumentError> {
Ok(GeomParts {
kind: String::decode(r)?,
keys: Option::decode(r)?,
channels: HashMap::decode(r)?,
})
}
}
#[cfg(feature = "document-read")]
impl GeomParts {
pub(crate) fn build(self, r: &Reader<'_>) -> Result<Box<dyn crate::plot::Geom>, DocumentError> {
let factory =
r.ctx()
.geom_factory(&self.kind)
.ok_or_else(|| DocumentError::UnknownGeom {
kind: self.kind.clone(),
})?;
Ok(factory(self.keys, self.channels))
}
}
#[cfg(feature = "document-write")]
pub(crate) fn geom_parts(g: &dyn crate::plot::Geom) -> Option<GeomParts> {
let kind = g.kind()?.to_string();
let state = g.state();
Some(GeomParts {
kind,
keys: match &state.keys {
crate::plot::Keys::Explicit(col) => Some(col.clone()),
crate::plot::Keys::Positional(_) => None,
},
channels: state.channels.clone(),
})
}
impl_codec! {
struct Span { rows, cols }
enum ElementTemplate {
0 => NamedPatch(id),
1 => Spacer,
2 => Composition(nested),
}
struct PlacementTemplate { row, col, span, element }
struct CompositionTemplate {
id,
rows,
cols,
widths,
heights,
aspect,
margin,
padding,
placements,
}
struct CompositionChrome {
title,
subtitle,
caption,
axis_titles,
legends,
next_legend_id,
}
}
use crate::composition::Span;
use crate::plot::composition::{
CompositionChrome, CompositionTemplate, ElementTemplate, PlacementTemplate,
};
#[cfg(feature = "document-write")]
impl Encode for Plot {
fn encode(&self, w: &mut Writer) {
self.patch_id().to_string().encode(w);
let mut bindings: Vec<(&str, &str)> = self.bindings().collect();
bindings.sort_unstable_by_key(|(channel, _)| *channel);
w.varint(bindings.len() as u64);
for (channel, scale) in bindings {
channel.encode(w);
scale.encode(w);
}
self.title_ref().map(str::to_string).encode(w);
self.subtitle_ref().map(str::to_string).encode(w);
self.caption_ref().map(str::to_string).encode(w);
let strips: [Option<String>; 4] = [
AxisSide::Left,
AxisSide::Right,
AxisSide::Bottom,
AxisSide::Top,
]
.map(|side| self.strip_at(side).map(str::to_string));
strips.encode(w);
self.axes().to_vec().encode(w);
self.legends().to_vec().encode(w);
self.projection_ref().encode(w);
self.is_clipped().encode(w);
self.tracks_identity().encode(w);
self.aspect_ratio_ref().encode(w);
self.aspect_mode_ref().encode(w);
self.theme_override_ref().cloned().encode(w);
let parts: Vec<GeomParts> = self.geoms().filter_map(|(_, g)| geom_parts(g)).collect();
parts.encode(w);
}
}
#[cfg(feature = "document-read")]
pub(crate) fn decode_plot(
r: &mut Reader<'_>,
composition: &crate::composition::Composition,
) -> Result<Plot, DocumentError> {
let patch_id = String::decode(r)?;
let mut plot = Plot::try_new(composition, &patch_id).map_err(|e| DocumentError::Invalid {
what: "plot patch binding",
why: e.to_string(),
})?;
let bindings = r.count()?;
for _ in 0..bindings {
let channel = String::decode(r)?;
let scale = String::decode(r)?;
plot.set_binding(channel, scale);
}
if let Some(t) = Option::<String>::decode(r)? {
plot.set_title(t);
}
if let Some(t) = Option::<String>::decode(r)? {
plot = plot.subtitle(t);
}
if let Some(t) = Option::<String>::decode(r)? {
plot = plot.caption(t);
}
let strips = <[Option<String>; 4]>::decode(r)?;
for (side, text) in [
AxisSide::Left,
AxisSide::Right,
AxisSide::Bottom,
AxisSide::Top,
]
.into_iter()
.zip(strips)
{
plot.set_strip(side, text);
}
for axis in Vec::<Axis>::decode(r)? {
plot.add_axis(axis);
}
for legend in Vec::<Legend>::decode(r)? {
plot.add_legend(legend);
}
plot = plot.projection(Projection::decode(r)?);
plot = plot.clip(bool::decode(r)?);
plot = plot.track_identity(bool::decode(r)?);
if let Some(ratio) = Option::<f64>::decode(r)? {
plot = plot.aspect_ratio(ratio);
}
plot = plot.aspect_mode(crate::plot::AspectMode::decode(r)?);
plot.set_theme_override(Option::decode(r)?);
let parts = Vec::<GeomParts>::decode(r)?;
let geoms: Vec<Box<dyn crate::plot::Geom>> = parts
.into_iter()
.map(|p| p.build(r))
.collect::<Result<_, _>>()?;
for geom in geoms {
plot.add_boxed_geom(geom);
}
Ok(plot)
}
impl_codec! {
enum AspectMode {
0 => Panel,
1 => Range,
}
}
use crate::plot::{AspectMode, Plot};