///|
/// A handle to one independent plotting panel in a figure.
pub struct Axes {
figure : Figure
state : @ref.Ref[AxesData]
}
///|
/// Return the figure owning these axes.
pub fn Axes::figure(self : Axes) -> Figure {
self.figure
}
///|
/// Return the allocated display rectangle in pixels, with a top-left origin.
/// This includes manual position updates but does not apply aspect shrinking.
/// The implicit default panel returns None until laid out or positioned.
pub fn Axes::rect(self : Axes) -> @gridspec.Rect? {
if self.state.val.position_override is Some(rect) {
return Some(rect)
}
match self.state.val.subplot_spec {
Some(spec) => Some(self.figure.subplot_rect(spec))
None => self.state.val.rect
}
}
///|
/// Return this panel's x-axis object.
pub fn Axes::x_axis(self : Axes) -> @axis.Axis {
self.state.val.x_axis
}
///|
/// Return this panel's y-axis object.
pub fn Axes::y_axis(self : Axes) -> @axis.Axis {
self.state.val.y_axis
}
///|
/// Set an equal data aspect for this axes panel.
///
/// The SVG backend keeps one data unit equally sized on both axes and places
/// the resulting plot rectangle using the panel's anchor.
/// share=true also sets the direct x/y shared siblings to aspect 1.
pub fn Axes::set_aspect_equal(self : Axes, share~ : Bool = false) -> Unit {
self.apply_data_aspect(Some(1.0), share)
}
///|
/// Restore automatic aspect handling for this axes panel.
/// share=true also restores the direct x/y shared siblings.
pub fn Axes::set_aspect_auto(self : Axes, share~ : Bool = false) -> Unit {
self.apply_data_aspect(None, share)
}
///|
/// Return whether this axes panel uses equal data scaling.
pub fn Axes::aspect_equal(self : Axes) -> Bool {
self.state.val.data_aspect == Some(1.0)
}
///|
/// Hide the frame, ticks, labels, grid, and secondary axes for this panel.
pub fn Axes::set_axis_off(self : Axes) -> Unit {
self.state.val.frame_visible = false
}
///|
/// Show the frame, ticks, labels, grid, and secondary axes for this panel.
pub fn Axes::set_axis_on(self : Axes) -> Unit {
self.state.val.frame_visible = true
}
///|
/// Return whether this panel's primary axis decorations are visible.
pub fn Axes::axis_visible(self : Axes) -> Bool {
self.state.val.frame_visible
}
///|
fn validate_margin(value : Double) -> Unit raise @core.DataError {
if value <= -0.5 {
raise @core.DataError::InvalidTicks(
message="axes margins must be greater than -0.5",
)
}
}
///|
/// Set the automatic x-axis margin as a fraction of the data span.
pub fn Axes::set_x_margin(
self : Axes,
value : Double,
) -> Unit raise @core.DataError {
validate_margin(value)
self.state.val.x_margin = value
self.state.val.x_axis.request_autoscale()
}
///|
/// Set the automatic y-axis margin as a fraction of the data span.
pub fn Axes::set_y_margin(
self : Axes,
value : Double,
) -> Unit raise @core.DataError {
validate_margin(value)
self.state.val.y_margin = value
self.state.val.y_axis.request_autoscale()
}
///|
/// Set both automatic axis margins.
pub fn Axes::set_margins(
self : Axes,
x : Double,
y : Double,
) -> Unit raise @core.DataError {
validate_margin(x)
validate_margin(y)
self.state.val.tight = Some(true)
self.state.val.x_margin = x
self.state.val.y_margin = y
self.state.val.x_axis.request_autoscale()
self.state.val.y_axis.request_autoscale()
}
///|
/// Return the automatic margins as an `(x, y)` pair.
pub fn Axes::margins(self : Axes) -> (Double, Double) {
(self.state.val.x_margin, self.state.val.y_margin)
}
///|
/// Return the automatic x-axis margin.
pub fn Axes::x_margin(self : Axes) -> Double {
self.state.val.x_margin
}
///|
/// Return the automatic y-axis margin.
pub fn Axes::y_margin(self : Axes) -> Double {
self.state.val.y_margin
}
///|
/// Set the x-axis tick label side for this panel.
pub fn Axes::set_x_tick_position(
self : Axes,
position : @axis.TickPosition,
) -> Unit {
self.state.val.x_axis.set_tick_position(position)
}
///|
/// Set the y-axis tick label side for this panel.
pub fn Axes::set_y_tick_position(
self : Axes,
position : @axis.TickPosition,
) -> Unit {
self.state.val.y_axis.set_tick_position(position)
}
///|
/// Configure major or minor grid lines for this axes panel.
///
/// `which` accepts `"both"`, `"x"`, `"y"`, `"major"`, or `"minor"`.
/// `axis` selects `"both"`, `"x"`, or `"y"` with the default or major/minor mode.
/// The legacy `which="x"`/`"y"` selectors require the default `axis="both"`.
/// Shared axes propagate the corresponding setting to their linked panels.
pub fn Axes::grid(
self : Axes,
visible~ : Bool = true,
which~ : String = "both",
color~ : String = "#e5e7eb",
linewidth~ : Double = 1.0,
axis~ : String = "both",
) -> Unit raise @core.DataError {
if which != "both" &&
which != "x" &&
which != "y" &&
which != "major" &&
which != "minor" {
raise @core.DataError::InvalidTicks(
message="grid which must be 'both', 'x', 'y', 'major', or 'minor'",
)
}
if axis != "both" && axis != "x" && axis != "y" {
raise @core.DataError::InvalidTicks(
message="grid axis must be 'both', 'x', or 'y'",
)
}
if (which == "x" || which == "y") && axis != "both" {
raise @core.DataError::InvalidTicks(
message="grid axis is only valid with 'both', 'major', or 'minor' which",
)
}
if which == "major" || which == "minor" {
if which == "minor" {
if axis == "both" || axis == "x" {
for state in self.x_group_states() {
state.val.x_axis.set_minor_grid(visible, color, linewidth)
}
}
if axis == "both" || axis == "y" {
for state in self.y_group_states() {
state.val.y_axis.set_minor_grid(visible, color, linewidth)
}
}
} else {
if axis == "both" || axis == "x" {
for state in self.x_group_states() {
state.val.x_axis.set_grid(visible, color, linewidth)
}
}
if axis == "both" || axis == "y" {
for state in self.y_group_states() {
state.val.y_axis.set_grid(visible, color, linewidth)
}
}
}
return
}
if (which == "both" && axis != "y") || which == "x" {
for state in self.x_group_states() {
state.val.x_axis.set_grid(visible, color, linewidth)
}
}
if (which == "both" && axis != "x") || which == "y" {
for state in self.y_group_states() {
state.val.y_axis.set_grid(visible, color, linewidth)
}
}
}
///|
/// Return this panel's artists in insertion order.
pub fn Axes::artists(self : Axes) -> Array[Artist] {
let state = self.state.val
let artists = state.artists
let result = Array(capacity=artists.length())
for artist in artists {
match artist {
Artist::Line(points, style) => {
let mut current_style = style
for line in state.line_originals {
if physical_equal(points, line.points) {
current_style = line.style
break
}
}
// Appearance is a current immutable snapshot; this query must not
// trigger conversion of pending original data.
result.push(Artist::Line(copy_points(points), current_style))
}
_ => result.push(copy_artist(artist))
}
}
result
}
///|
/// Set this panel's title.
pub fn Axes::set_title(self : Axes, title : String) -> Unit {
self.state.val.title = Some(title)
}
///|
/// Return this panel's optional title.
pub fn Axes::title(self : Axes) -> String? {
self.state.val.title
}
///|
/// Set this panel's x-axis label.
pub fn Axes::set_x_label(self : Axes, label : String) -> Unit {
self.state.val.x_axis.set_label(label)
}
///|
/// Set this panel's y-axis label.
pub fn Axes::set_y_label(self : Axes, label : String) -> Unit {
self.state.val.y_axis.set_label(label)
}
///|
/// Return explicit x limits, or a resolved datalim view after aspect application.
pub fn Axes::x_limits(self : Axes) -> @core.Limits? {
if self.state.val.adjustable == "datalim" &&
self.state.val.data_aspect is Some(_) {
self.state.val.x_axis.current_view_limits()
} else {
self.state.val.x_axis.limits()
}
}
///|
/// Return explicit y limits, or a resolved datalim view after aspect application.
pub fn Axes::y_limits(self : Axes) -> @core.Limits? {
if self.state.val.adjustable == "datalim" &&
self.state.val.data_aspect is Some(_) {
self.state.val.y_axis.current_view_limits()
} else {
self.state.val.y_axis.limits()
}
}
///|
/// Set explicit limits for this panel.
pub fn Axes::set_bounds(
self : Axes,
limits : @core.Bounds,
) -> Unit raise @core.DataError {
if !limits.is_finite() {
raise @core.DataError::NonFiniteLimit(axis="both")
}
self.state.val.explicit_bounds = Some(limits)
self.set_x_limits(limits.xmin(), limits.xmax())
self.set_y_limits(limits.ymin(), limits.ymax())
}
///|
/// Set the x-axis limits through this axes handle.
pub fn Axes::set_x_limits(
self : Axes,
lower : Double,
upper : Double,
) -> Unit raise @core.DataError {
if !@core.limits(lower, upper).is_finite() {
raise @core.DataError::NonFiniteLimit(axis="x")
}
for state in self.x_group_states() {
state.val.x_axis.set_limits(lower, upper)
}
}
///|
/// Set the y-axis limits through this axes handle.
pub fn Axes::set_y_limits(
self : Axes,
lower : Double,
upper : Double,
) -> Unit raise @core.DataError {
if !@core.limits(lower, upper).is_finite() {
raise @core.DataError::NonFiniteLimit(axis="y")
}
for state in self.y_group_states() {
state.val.y_axis.set_limits(lower, upper)
}
}
///|
/// Set fixed x-axis tick positions through this axes handle.
pub fn Axes::set_x_ticks(
self : Axes,
values : Array[Double],
) -> Unit raise @core.DataError {
for state in self.x_group_states() {
state.val.x_axis.set_ticks(values)
}
}
///|
/// Set fixed y-axis tick positions through this axes handle.
pub fn Axes::set_y_ticks(
self : Axes,
values : Array[Double],
) -> Unit raise @core.DataError {
for state in self.y_group_states() {
state.val.y_axis.set_ticks(values)
}
}
///|
/// Set the numeric major locator through this axes handle.
pub fn Axes::set_x_locator(
self : Axes,
locator : @ticker.Locator,
) -> Unit raise @core.DataError {
for state in self.x_group_states() {
state.val.x_axis.set_locator(locator)
}
self.state.val.x_axis.bind_locator_data()
}
///|
/// Set the numeric major locator through this axes handle.
pub fn Axes::set_y_locator(
self : Axes,
locator : @ticker.Locator,
) -> Unit raise @core.DataError {
for state in self.y_group_states() {
state.val.y_axis.set_locator(locator)
}
self.state.val.y_axis.bind_locator_data()
}
///|
/// Set the numeric formatter through this axes handle.
pub fn Axes::set_x_formatter(
self : Axes,
formatter : @ticker.Formatter,
) -> Unit {
for state in self.x_group_states() {
state.val.x_axis.set_formatter(formatter)
}
}
///|
/// Set the numeric formatter through this axes handle.
pub fn Axes::set_y_formatter(
self : Axes,
formatter : @ticker.Formatter,
) -> Unit {
for state in self.y_group_states() {
state.val.y_axis.set_formatter(formatter)
}
}
///|
/// Set the minor locator through this axes handle.
pub fn Axes::set_x_minor_locator(
self : Axes,
locator : @ticker.MinorLocator,
) -> Unit raise @core.DataError {
for state in self.x_group_states() {
state.val.x_axis.set_minor_locator(locator)
}
self.state.val.x_axis.bind_locator_data(minor=true)
}
///|
/// Set the minor locator through this axes handle.
pub fn Axes::set_y_minor_locator(
self : Axes,
locator : @ticker.MinorLocator,
) -> Unit raise @core.DataError {
for state in self.y_group_states() {
state.val.y_axis.set_minor_locator(locator)
}
self.state.val.y_axis.bind_locator_data(minor=true)
}
///|
/// Set fixed x-axis tick labels through this axes handle.
pub fn Axes::set_x_tick_labels(
self : Axes,
labels : Array[String],
) -> Unit raise @core.DataError {
for state in self.x_group_states() {
state.val.x_axis.set_tick_labels(labels)
}
}
///|
/// Set fixed y-axis tick labels through this axes handle.
pub fn Axes::set_y_tick_labels(
self : Axes,
labels : Array[String],
) -> Unit raise @core.DataError {
for state in self.y_group_states() {
state.val.y_axis.set_tick_labels(labels)
}
}
///|
/// Clear this axes' artists and presentation state while retaining its panel
/// rectangle and shared-axis membership.
pub fn Axes::clear(self : Axes) -> Unit {
try! self.clear_checked()
}
///|
/// Add a line plot through the axes view.
pub fn Axes::plot(
self : Axes,
xs : Array[Double],
ys : Array[Double],
color~ : String = "",
label~ : String = "",
) -> Unit raise @core.DataError {
let values = @core.series(xs, ys)
self.line(values.points(), color~, label~)
}
///|
/// Add explicit line points through the axes view.
pub fn Axes::line(
self : Axes,
points : Array[@core.Point],
color~ : String = "",
label~ : String = "",
) -> Unit {
self.attach_line_with_originals(
points,
LineData::Numeric(points.map(point => point.x())),
LineData::Numeric(points.map(point => point.y())),
color~,
label~,
)
}
///|
/// Add scatter points through the axes view.
pub fn Axes::scatter(
self : Axes,
points : Array[@core.Point],
color~ : String = "",
label~ : String = "",
) -> Unit {
let resolved_color = self.resolve_color(color)
self.push_artist(
Artist::Scatter(
copy_points(points),
@core.style(resolved_color, width=0.0, radius=3.5),
),
)
self.add_auto_legend(label, resolved_color, LegendKind::Marker)
}
///|
/// Add bars through the axes view.
pub fn Axes::bar(
self : Axes,
points : Array[@core.Point],
color~ : String = "",
label~ : String = "",
) -> Unit {
let resolved_color = self.resolve_color(color)
let start = self.state.val.artists.length()
self.push_artist(
Artist::Bar(
copy_points(points),
@core.style(resolved_color, width=0.0, fill=resolved_color),
),
)
self.add_auto_legend(label, resolved_color, LegendKind::Patch)
self.add_container(ContainerKind::Bar, start, label)
}