///|
fn Axes::original_position_rect(self : Axes) -> @gridspec.Rect {
self
.rect()
.unwrap_or_else(() => {
@gridspec.rect(
Double::from_int(self.figure.margin),
Double::from_int(self.figure.margin),
Double::from_int(self.figure.width - self.figure.margin * 2),
Double::from_int(self.figure.height - self.figure.margin * 2),
)
})
}
///|
/// Return a normalized Figure rectangle with a lower-left origin.
///
/// The returned Rect stores (left, bottom, width, height), unlike rect(),
/// which returns the allocated display rectangle in pixels from the top left.
/// original=true returns the allocated position. The default active position
/// applies the same data/physical-aspect geometry as SVG rendering.
/// Autoscale margins, shared limits, and image bounds are resolved by the
/// existing renderer. Detached axes or nonpositive Figure dimensions return
/// None. SubFigure axes currently use coordinates of the root Figure.
/// Aspect-resolution failures return None; apply_aspect preserves DataError.
///
/// ```mbt check
/// test {
/// let fig = @scene.figure(width=400, height=200)
/// let ax = try! fig.add_axes(0.25, 0.25, 0.5, 0.5)
/// try! ax.set_xlim(0.0, 1.0)
/// try! ax.set_ylim(0.0, 1.0)
/// ax.set_aspect_equal()
/// assert_eq(ax.get_position(original=true).unwrap().width(), 0.5)
/// assert_eq(ax.get_position().unwrap().width(), 0.25)
/// }
/// ```
pub fn Axes::get_position(
self : Axes,
original~ : Bool = false,
) -> @gridspec.Rect? {
if !self.is_attached() || self.figure.width <= 0 || self.figure.height <= 0 {
return None
}
let rect = if original {
self.original_position_rect()
} else {
self.apply_aspect() catch {
_ => return None
}
self.state.val.active_position.unwrap()
}
let figure_width = Double::from_int(self.figure.width)
let figure_height = Double::from_int(self.figure.height)
Some(
@gridspec.rect(
rect.x() / figure_width,
1.0 - (rect.y() + rect.height()) / figure_height,
rect.width() / figure_width,
rect.height() / figure_height,
),
)
}
///|
/// Set a normalized Figure rectangle (left, bottom, width, height).
///
/// which accepts both, original, or active. GridSpec membership is retained;
/// subplots_adjust restores grid placement. Manual panels keep their new
/// position through subplots_adjust. Zero sizes and positions outside the
/// Figure are allowed. Invalid values leave the state unchanged.
/// Negative sizes, non-finite coordinates, coordinate overflow, invalid which
/// values, and detached handles raise DataError::InvalidReference.
/// As in Matplotlib, an active-only value is overwritten by original and
/// aspect on the next active-position read or draw.
///
/// ```mbt check
/// test {
/// let ax = @scene.figure(width=400, height=200).axes()
/// try! ax.set_position(0.25, 0.25, 0.5, 0.5)
/// let p = ax.get_position(original=true).unwrap()
/// assert_eq((p.x(), p.y(), p.width(), p.height()), (0.25, 0.25, 0.5, 0.5))
/// }
/// ```
pub fn Axes::set_position(
self : Axes,
left : Double,
bottom : Double,
width : Double,
height : Double,
which~ : String = "both",
) -> Unit raise @core.DataError {
if !self.is_attached() {
raise @core.DataError::InvalidReference(
message="axes is detached from its figure",
)
}
if which != "both" && which != "original" && which != "active" {
raise @core.DataError::InvalidReference(
message="position which must be both, original, or active",
)
}
let rect = self.figure.position_rect(left, bottom, width, height) catch {
_ =>
raise @core.DataError::InvalidReference(message="invalid axes position")
}
if which == "both" || which == "original" {
self.state.val.position_override = Some(rect)
}
if which == "both" || which == "active" {
self.state.val.active_position = Some(rect)
}
}
///|
/// Reset active to original, preserving the allocated position.
/// The next active-position read or draw reapplies the aspect constraint.
/// Detached handles raise DataError::InvalidReference.
///
/// ```mbt check
/// test {
/// let ax = try! @scene.figure().add_axes(0.25, 0.25, 0.5, 0.5)
/// try! ax.set_position(0.0, 0.0, 0.25, 0.25, which="active")
/// try! ax.reset_position()
/// assert_eq(ax.get_position().unwrap().x(), 0.25)
/// }
/// ```
pub fn Axes::reset_position(self : Axes) -> Unit raise @core.DataError {
if !self.is_attached() {
raise @core.DataError::InvalidReference(
message="axes is detached from its figure",
)
}
self.state.val.active_position = Some(self.original_position_rect())
}