///|
fn line_drawing_colored_style(
style : @core.Style,
color : String,
stroke : @core.LineStroke,
) -> @core.Style {
@core.style(
color,
width=style.width(),
radius=style.radius(),
fill=style.fill(),
font_size=style.font_size(),
marker=style.marker(),
line_alpha=None,
line_stroke=stroke,
line_gapcolor=style.line_gapcolor(),
)
}
///|
fn line_drawing_foreground_style(
style : @core.Style,
source_color : Bool,
) -> @core.Style raise @core.DataError {
if source_color {
line_drawing_colored_style(
style,
line_color_rgba(style.color(), alpha=style.line_alpha()).to_css(),
style.line_stroke(),
)
} else {
style
}
}
///|
fn to_render_artist(artist : Artist) -> @render.Artist {
match artist {
Artist::Line(points, style) => @render.Artist::Line(points, style)
Artist::Scatter(points, style) => @render.Artist::Scatter(points, style)
Artist::PathCollection(collection) =>
@render.Artist::PathCollection(collection.points(), collection.styles())
Artist::Bar(points, style) => @render.Artist::Bar(points, style)
Artist::BarH(points, style) => @render.Artist::BarH(points, style)
Artist::FillBetween(upper, lower, style) =>
@render.Artist::FillBetween(upper, lower, style)
Artist::Polygon(points, style) => @render.Artist::Polygon(points, style)
Artist::CompoundPolygon(polygons, style) =>
@render.Artist::CompoundPolygon(polygons, style)
Artist::GouraudTriangle(points, colors) =>
@render.Artist::GouraudTriangle(points, colors)
Artist::HLine(value, style) => @render.Artist::HLine(value, style)
Artist::VLine(value, style) => @render.Artist::VLine(value, style)
Artist::AxLine(point, direction, style) =>
@render.Artist::AxLine(point, direction, style)
Artist::HSpan(lower, upper, style) =>
@render.Artist::HSpan(lower, upper, style)
Artist::VSpan(lower, upper, style) =>
@render.Artist::VSpan(lower, upper, style)
Artist::Text(x, y, value, style) => @render.Artist::Text(x, y, value, style)
Artist::Annotation(value) =>
@render.Artist::Annotation(
@render.annotation(
value.x,
value.y,
value.text_x,
value.text_y,
value.value,
value.xycoords,
value.textcoords,
value.connector,
value.arrow,
value.arrowcolor,
value.linewidth,
value.head_length,
value.head_width,
value.style,
),
)
Artist::ErrorBar(points, yerr, xerr, style) =>
@render.Artist::ErrorBar(points, yerr, xerr, style)
Artist::Image(data, vmin, vmax, cmap) =>
@render.Artist::Image(data, vmin, vmax, cmap)
Artist::ImageNorm(data, norm, cmap, origin, extent, alpha, interpolation) =>
@render.Artist::ImageNorm(
data,
norm,
cmap,
match origin {
ImageOrigin::Upper => @render.ImageOrigin::Upper
ImageOrigin::Lower => @render.ImageOrigin::Lower
},
extent,
alpha,
match interpolation {
ImageInterpolation::Nearest => @render.ImageInterpolation::Nearest
ImageInterpolation::NoInterpolation =>
@render.ImageInterpolation::NoInterpolation
ImageInterpolation::Bilinear => @render.ImageInterpolation::Bilinear
ImageInterpolation::Bicubic => @render.ImageInterpolation::Bicubic
ImageInterpolation::Spline16 => @render.ImageInterpolation::Spline16
ImageInterpolation::Spline36 => @render.ImageInterpolation::Spline36
ImageInterpolation::Hanning => @render.ImageInterpolation::Hanning
ImageInterpolation::Hamming => @render.ImageInterpolation::Hamming
ImageInterpolation::Hermite => @render.ImageInterpolation::Hermite
ImageInterpolation::Kaiser => @render.ImageInterpolation::Kaiser
ImageInterpolation::Quadric => @render.ImageInterpolation::Quadric
ImageInterpolation::Catrom => @render.ImageInterpolation::Catrom
ImageInterpolation::Gaussian => @render.ImageInterpolation::Gaussian
ImageInterpolation::Bessel => @render.ImageInterpolation::Bessel
ImageInterpolation::Mitchell => @render.ImageInterpolation::Mitchell
ImageInterpolation::Sinc => @render.ImageInterpolation::Sinc
ImageInterpolation::Lanczos => @render.ImageInterpolation::Lanczos
ImageInterpolation::Blackman => @render.ImageInterpolation::Blackman
ImageInterpolation::Auto => @render.ImageInterpolation::Auto
ImageInterpolation::Antialiased =>
@render.ImageInterpolation::Antialiased
},
)
Artist::ColorImage(data, origin, extent, alpha, interpolation) =>
@render.Artist::ColorImage(
data,
match origin {
ImageOrigin::Upper => @render.ImageOrigin::Upper
ImageOrigin::Lower => @render.ImageOrigin::Lower
},
extent,
alpha,
match interpolation {
ImageInterpolation::Nearest => @render.ImageInterpolation::Nearest
ImageInterpolation::NoInterpolation =>
@render.ImageInterpolation::NoInterpolation
ImageInterpolation::Bilinear => @render.ImageInterpolation::Bilinear
ImageInterpolation::Bicubic => @render.ImageInterpolation::Bicubic
ImageInterpolation::Spline16 => @render.ImageInterpolation::Spline16
ImageInterpolation::Spline36 => @render.ImageInterpolation::Spline36
ImageInterpolation::Hanning => @render.ImageInterpolation::Hanning
ImageInterpolation::Hamming => @render.ImageInterpolation::Hamming
ImageInterpolation::Hermite => @render.ImageInterpolation::Hermite
ImageInterpolation::Kaiser => @render.ImageInterpolation::Kaiser
ImageInterpolation::Quadric => @render.ImageInterpolation::Quadric
ImageInterpolation::Catrom => @render.ImageInterpolation::Catrom
ImageInterpolation::Gaussian => @render.ImageInterpolation::Gaussian
ImageInterpolation::Bessel => @render.ImageInterpolation::Bessel
ImageInterpolation::Mitchell => @render.ImageInterpolation::Mitchell
ImageInterpolation::Sinc => @render.ImageInterpolation::Sinc
ImageInterpolation::Lanczos => @render.ImageInterpolation::Lanczos
ImageInterpolation::Blackman => @render.ImageInterpolation::Blackman
ImageInterpolation::Auto => @render.ImageInterpolation::Auto
ImageInterpolation::Antialiased =>
@render.ImageInterpolation::Antialiased
},
)
Artist::Table(table) => {
let cells = Array(capacity=table.cells().length())
for cell in table.cells() {
cells.push(
@render.table_cell(
cell.row(),
cell.column(),
cell.text(),
cell.facecolor(),
cell.edgecolor(),
cell.align(),
cell.visible_edges(),
),
)
}
@render.Artist::Table(
@render.table(
cells,
table.rows(),
table.columns(),
table.x(),
table.y(),
table.width(),
table.height(),
table.column_widths(),
),
)
}
Artist::Pie(values, labels, colors, start_angle) =>
@render.Artist::Pie(values, labels, colors, start_angle)
Artist::BoxPlot(stats, horizontal, style) =>
@render.Artist::BoxPlot(stats, horizontal, style)
Artist::Quiver(points, vectors, style) =>
@render.Artist::Quiver(points, vectors, style)
Artist::Violin(
stats,
positions,
widths,
horizontal,
side,
showmeans,
showextrema,
showmedians,
body_style,
line_style,
opacity
) =>
@render.Artist::Violin(
stats.map(stat => {
@render.violin_stats(
stat.coords(),
stat.vals(),
stat.mean(),
stat.median(),
stat.min(),
stat.max(),
stat.quantiles(),
)
}),
positions,
widths,
horizontal,
match side {
ViolinSide::Both => @render.ViolinSide::Both
ViolinSide::Low => @render.ViolinSide::Low
ViolinSide::High => @render.ViolinSide::High
},
showmeans,
showextrema,
showmedians,
body_style,
line_style,
opacity,
)
}
}
///|
fn to_render_legend_kind(kind : LegendKind) -> @render.LegendKind {
match kind {
LegendKind::Line => @render.LegendKind::Line
LegendKind::Marker => @render.LegendKind::Marker
LegendKind::Patch => @render.LegendKind::Patch
}
}
///|
fn to_render_legend(entry : LegendEntry) -> @render.LegendEntry {
@render.legend_entry(
entry.label(),
entry.color(),
to_render_legend_kind(entry.kind()),
)
}
///|
fn to_render_secondary_axis(axis : SecondaryAxis) -> @render.SecondaryAxis {
let location = match axis.alignment() {
SecondaryAxisAlignment::Top => @render.SecondaryLocation::Top
SecondaryAxisAlignment::Bottom => @render.SecondaryLocation::Bottom
SecondaryAxisAlignment::Left => @render.SecondaryLocation::Left
SecondaryAxisAlignment::Right => @render.SecondaryLocation::Right
}
@render.secondary_axis(
location,
axis.forward(),
axis.inverse(),
axis.label(),
axis.tick_count(),
axis.ticks(),
axis.locator(),
axis.formatter(),
named_formatter=axis.named_formatter(),
minor_named_formatter=axis.minor_named_formatter(),
formatter_provider=Some(fn(minor) {
if minor {
axis.minor_named_formatter()
} else {
axis.named_formatter()
}
}),
legacy_formatter_provider=Some(() => axis.formatter()),
null_group_provider=Some(fn(minor) { axis.tick_group_is_null(minor~) }),
major_formatter_locations_provider=Some(() => {
axis.formatter_tick_locations_checked()
}),
minor_formatter_locations_provider=Some(() => {
axis.formatter_tick_locations_checked(minor=true)
}),
tick_label_size=axis.tick_label_size(),
draw_order=Some(axis.draw_order),
minor_locator=axis.minor_locator(),
minor_ticks_provider=Some(() => axis.minor_tick_values_checked()),
synchronize_view_provider=Some(() => axis.synchronize_formatter_frame()),
prepare_draw_provider=Some(() => axis.prepare_draw()),
positioned_aspect_provider=Some((left, top, width, height) => {
axis.positioned_aspect(left, top, width, height)
}),
drawing_context_provider=Some(() => {
let (left, top, right, bottom) = axis.drawing_bounds()
let alignment = match axis.alignment() {
SecondaryAxisAlignment::Top => @render.SecondaryLocation::Top
SecondaryAxisAlignment::Bottom => @render.SecondaryLocation::Bottom
SecondaryAxisAlignment::Left => @render.SecondaryLocation::Left
SecondaryAxisAlignment::Right => @render.SecondaryLocation::Right
}
@render.SecondaryDrawingContext::new(left, top, right, bottom, alignment)
}),
locator_provider=Some(() => axis.locator()),
minor_locator_provider=Some(() => axis.minor_locator()),
fixed_ticks_provider=Some(() => axis.ticks()),
placement_provider=Some(() => {
let view = axis.numeric_axis.stored_view_limits()
@render.SecondaryPlacement::new(
view.lower(),
view.upper(),
axis.numeric_axis.coordinate_transform(),
)
}),
formatter_view_provider=Some(() => {
let view = axis.numeric_axis.stored_view_limits()
(view.lower(), view.upper())
}),
)
}
///|
fn to_render_spine(spine : Spine) -> @render.Spine {
let side = match spine.side() {
SpineSide::Left => @render.SpineSide::Left
SpineSide::Right => @render.SpineSide::Right
SpineSide::Bottom => @render.SpineSide::Bottom
SpineSide::Top => @render.SpineSide::Top
}
let position = match spine.position() {
SpinePosition::Axes => @render.SpinePosition::Axes
SpinePosition::Data(value) => @render.SpinePosition::Data(value)
SpinePosition::Outward(value) => @render.SpinePosition::Outward(value)
}
@render.spine(
side,
spine.visible(),
spine.color(),
spine.linewidth(),
position,
spine.bounds(),
)
}
///|
fn Figure::to_render_figure(
self : Figure,
extra? : @ref.Ref[AxesData],
capture_points~ : Bool = false,
) -> @render.Figure {
let states = self.axes.copy()
if extra is Some(state) {
states.push(state)
}
let panels = Array(capacity=states.length())
for state_ref in states {
let state = state_ref.val
// Pin this frame's stable Line identities, without touching invalid data.
// A later clear/new attachment must not redirect an old IR completion.
let lines = state.line_originals.copy()
let artists = Array(capacity=state.artists.length())
for artist in state.artists {
artists.push(to_render_artist(artist))
}
let source_legend = state.legend_entries()
let legend = Array(capacity=source_legend.length())
for entry in source_legend {
legend.push(to_render_legend(entry))
}
let secondary_x = Array(capacity=state.secondary_x.length())
for axis in state.secondary_x {
secondary_x.push(to_render_secondary_axis(axis))
}
let secondary_y = Array(capacity=state.secondary_y.length())
for axis in state.secondary_y {
secondary_y.push(to_render_secondary_axis(axis))
}
let spines = Array(capacity=state.spines.length())
for index in 0.. SpineSide::Left
1 => SpineSide::Right
2 => SpineSide::Bottom
_ => SpineSide::Top
}
let handle = { axes: { figure: self, state: state_ref, }, side, }
spines.push(to_render_spine(handle))
}
let axes = { figure: self, state: state_ref, }
let rect = axes.rect()
let aspect = state.data_aspect
let box_aspect = state.box_aspect
panels.push(
@render.panel_with_sharing(
rect,
artists,
state.title,
legend,
state.explicit_bounds,
state.x_axis,
state.y_axis,
state.share_x_group,
state.share_y_group,
false,
secondary_x,
secondary_y,
legend_position=state.legend_position,
show_x_tick_labels=state.show_x_tick_labels,
show_y_tick_labels=state.show_y_tick_labels,
frame_visible=state.frame_visible,
x_margin=state.x_margin,
y_margin=state.y_margin,
spines~,
aspect?,
anchor=try! state.anchor.coordinates(),
adjustable=state.adjustable,
box_aspect?,
line_provider=Some(points => {
for line in lines {
if physical_equal(points, line.points) {
if !line.get_visible() {
return Some(
@render.line_drawing_context([], line.style, () => ()),
)
}
let current = line.points_snapshot_checked()
let style = line.style
let stroke = style.line_stroke()
let source_color = line.source_color
if current.is_empty() || stroke.linestyle() == "None" {
return Some(
@render.line_drawing_context(current, style, () => {
line.mark_drawn()
}),
)
}
let hit_points = if capture_points && style.width() > 0.0 {
let points = Array(capacity=line.cache.xs.length())
for index in 0.. 0.0 &&
(
stroke.linestyle() == "--" ||
stroke.linestyle() == "-." ||
stroke.linestyle() == ":"
) &&
style.line_gapcolor() is Some(gapcolor) {
// Resolve gap color before inverse's possible empty-list error.
let gap = line_color_rgba(gapcolor, alpha=style.line_alpha()).to_css()
guard stroke.dash_sequence() is Some(dashes) else {
raise @core.DataError::InvalidTicks(
message="TypeError: 'NoneType' object is not subscriptable",
)
}
let (offset, inverse) = line_gapcolor_inverse(
stroke.dash_offset(),
dashes,
scaled_as_list=line.scaled_dashes_as_list,
)
let first_style = line_drawing_colored_style(
style,
gap,
@core.line_stroke(
stroke.linestyle(),
offset~,
dashes=Some(inverse),
),
)
return Some(
@render.line_drawing_context(
current,
first_style,
() => line.mark_drawn(),
foreground=Some(() => {
line_drawing_foreground_style(style, source_color)
}),
hit_points~,
),
)
}
return Some(
@render.line_drawing_context(
current,
line_drawing_foreground_style(style, source_color),
() => line.mark_drawn(),
hit_points~,
),
)
}
}
None
}),
),
)
}
let subfigures = []
for data_ref in self.subfigures {
append_render_subfigures(self, data_ref, subfigures)
}
@render.figure_with_subfigures(
self.width,
self.height,
self.margin,
panels,
self.colorbars,
subfigures,
self.title,
)
}
///|
/// Flatten a nested SubFigure tree into absolute display rectangles for the
/// backend-neutral render model. SubplotSpec keeps the parent geometry chain,
/// so every descendant can resolve its current rectangle independently.
fn append_render_subfigures(
figure : Figure,
data_ref : @ref.Ref[SubFigureData],
result : Array[@render.SubFigure],
) -> Unit {
let data = data_ref.val
result.push(@render.subfigure(figure.subplot_rect(data.spec), data.title))
for child in data.subfigures {
append_render_subfigures(figure, child, result)
}
}
///|
/// Render a figure with the default SVG backend.
pub fn Figure::to_svg(self : Figure) -> String {
try! self.to_svg_checked()
}
///|
/// Render SVG, reporting aspect-resolution failures through DataError.
/// Datalim views are resolved for all panels before any are committed.
/// The non-throwing to_svg() convenience aborts if this checked call fails.
///
/// ```mbt check
/// test {
/// let fig = @scene.figure(width=400, height=200)
/// let ax = try! fig.add_axes(0.0, 0.0, 1.0, 1.0)
/// try! ax.plot([0.0, 1.0], [0.0, 1.0])
/// try! ax.set_xlim(0.0, 1.0)
/// try! ax.set_ylim(0.0, 1.0)
/// try! ax.set_aspect(1.0, adjustable="datalim")
/// let svg = try! fig.to_svg_checked()
/// assert_true(svg.contains("points=\"100,200 300,0\""))
/// }
/// ```
pub fn Figure::to_svg_checked(self : Figure) -> String raise @core.DataError {
self.apply_position_aspects()
let (document, positions) = @svg.render_with_positions_checked(
self.to_render_figure(),
)
for index, (left, top, width, height) in positions {
self.axes[index].val.active_position = Some(
@gridspec.rect(left, top, width, height),
)
}
document
}
///|
/// Render once and capture in-panel Line/scatter datum anchors for interaction.
/// Point indices refer to the unfiltered source order, before Line step expansion.
/// Anchors do not describe marker pixel coverage; hit tolerance is caller-chosen.
pub fn Figure::to_drawing(
self : Figure,
) -> @drawing.Drawing raise @core.DataError {
self.apply_position_aspects()
let (drawing, positions) = @svg.render_drawing_with_positions_checked(
self.to_render_figure(capture_points=true),
)
for index, (left, top, width, height) in positions {
self.axes[index].val.active_position = Some(
@gridspec.rect(left, top, width, height),
)
}
drawing
}