///|
/// Location of a secondary coordinate axis.
pub(all) enum SecondaryAxisLocation {
Top
Bottom
Left
Right
Position(Double)
}
///|
/// Decoration side, independent of the requested secondary-axis location.
pub(all) enum SecondaryAxisAlignment {
Top
Bottom
Left
Right
}
///|
/// A coordinate axis whose values are derived from a primary axis.
pub struct SecondaryAxis {
priv numeric_axis : @axis.Axis
priv draw_order : Int32
priv horizontal : Bool
priv figure_bounds : @transforms.BBox
priv original_bounds : @transforms.BBox
priv mut active_bounds : @transforms.BBox
priv mut parent_axis : @axis.Axis?
priv mut parent_scale_name : String?
priv mut ticks_set : Bool
priv mut source_ticks_current : Bool
priv mut legacy_sampling : Bool
priv mut location : SecondaryAxisLocation
priv mut location_locator : SecondaryLocationLocator
priv mut alignment : SecondaryAxisAlignment
priv mut forward : (Double) -> Double
priv mut inverse : (Double) -> Double
mut label : String?
}
///|
fn secondary_axis(
location : SecondaryAxisLocation,
forward : (Double) -> Double,
inverse : (Double) -> Double,
horizontal~ : Bool,
figure_bounds~ : @transforms.BBox,
draw_order~ : Int32 = 0,
) -> SecondaryAxis {
let original_bounds = if horizontal {
@transforms.bbox_from_bounds(0.0, 1.0, 1.0, 0.0001)
} else {
@transforms.bbox_from_bounds(0.0, 1.0, 0.0001, 1.0)
}
let low = secondary_location_position(location) < 0.5
let alignment = if horizontal {
if low {
SecondaryAxisAlignment::Bottom
} else {
Top
}
} else if low {
SecondaryAxisAlignment::Left
} else {
SecondaryAxisAlignment::Right
}
let result : SecondaryAxis = {
numeric_axis: @axis.axis(horizontal~),
draw_order,
horizontal,
figure_bounds,
original_bounds,
active_bounds: original_bounds,
parent_axis: None,
parent_scale_name: None,
ticks_set: false,
source_ticks_current: false,
legacy_sampling: false,
location,
location_locator: {
position: secondary_location_position(location),
transform: None,
},
alignment,
forward,
inverse,
label: None,
}
result.numeric_axis.bind_data_interval(() => (1.0 / 0.0, -1.0 / 0.0))
result.numeric_axis.bind_minpos(() => 1.0 / 0.0)
result.numeric_axis.bind_shared_minpos(() => [1.0 / 0.0])
result.numeric_axis.bind_autoscale_minpos(() => [1.0 / 0.0])
result.numeric_axis.bind_tick_geometry(
() => {
let (origin, length) = result.formatter_geometry()
// The source applies the inverse affine's already-rounded reciprocal.
// Dividing each endpoint by 72 changes the y constructor's tick space.
let inverse_dpi = 1.0 / 72.0
((origin + length) * inverse_dpi - origin * inverse_dpi) * 72.0
},
horizontal,
)
result.numeric_axis.bind_formatter_geometry(() => result.formatter_geometry())
result
}
///|
pub fn SecondaryAxis::location(self : SecondaryAxis) -> SecondaryAxisLocation {
self.location
}
///|
pub fn SecondaryAxis::forward(self : SecondaryAxis) -> (Double) -> Double {
self.forward
}
///|
pub fn SecondaryAxis::inverse(self : SecondaryAxis) -> (Double) -> Double {
self.inverse
}
///|
pub fn SecondaryAxis::set_label(self : SecondaryAxis, value : String) -> Unit {
self.label = Some(value)
}
///|
pub fn SecondaryAxis::label(self : SecondaryAxis) -> String? {
self.label
}
///|
/// Select the legacy parent-grid sampler only while no explicit locator owns ticks.
pub fn SecondaryAxis::set_tick_count(
self : SecondaryAxis,
value : Int32,
) -> Unit {
self.numeric_axis.set_tick_count(value)
if self.numeric_axis.locator() is None {
self.legacy_sampling = true
self.source_ticks_current = false
}
}
///|
pub fn SecondaryAxis::tick_count(self : SecondaryAxis) -> Int32 {
self.numeric_axis.tick_count()
}
///|
pub fn SecondaryAxis::set_ticks(
self : SecondaryAxis,
values : Array[Double],
) -> Unit raise @core.DataError {
for value in values {
if value.is_nan() || value.is_inf() {
raise @core.DataError::InvalidTicks(
message="secondary tick positions must be finite",
)
}
}
self.numeric_axis.set_source_ticks(values)
self.ticks_set = true
self.source_ticks_current = true
self.legacy_sampling = false
}
///|
/// Return a copy of the currently retained explicit set_ticks positions.
pub fn SecondaryAxis::ticks(self : SecondaryAxis) -> Array[Double]? {
if !self.source_ticks_current {
return None
}
match self.numeric_axis.locator() {
Some(@ticker.Locator::NamedFixed(value)) =>
// set_source_ticks owns this buffer; only checked element mutation is
// exposed through its view, so this compatibility projection is valid.
Some(try! value.locations().to_array())
_ => None
}
}
///|
/// Install a locator in the secondary axis' own coordinates and stable context.
pub fn SecondaryAxis::set_locator(
self : SecondaryAxis,
locator : @ticker.Locator,
) -> Unit raise @core.DataError {
match locator {
@ticker.Locator::Fixed(values) =>
for value in values {
if value.is_nan() || value.is_inf() {
raise @core.DataError::InvalidTicks(
message="secondary locator values must be finite",
)
}
}
_ => ()
}
self.numeric_axis.set_locator(locator)
self.source_ticks_current = false
self.legacy_sampling = false
}
///|
pub fn SecondaryAxis::locator(self : SecondaryAxis) -> @ticker.Locator? {
self.numeric_axis.locator()
}
///|
/// Explicit convenience extension restoring the legacy parent-grid sampler.
pub fn SecondaryAxis::clear_locator(self : SecondaryAxis) -> Unit {
self.numeric_axis.clear_locator()
self.source_ticks_current = false
self.legacy_sampling = true
}
///|
pub fn SecondaryAxis::set_formatter(
self : SecondaryAxis,
formatter : @ticker.Formatter,
) -> Unit {
self.numeric_axis.set_formatter(formatter)
}
///|
pub fn SecondaryAxis::formatter(self : SecondaryAxis) -> @ticker.Formatter? {
self.numeric_axis.formatter()
}
///|
/// Clear both major formatter roles and restore explicit plain numeric labels.
pub fn SecondaryAxis::clear_formatter(self : SecondaryAxis) -> Unit {
self.numeric_axis.clear_formatter()
}
///|
/// Add a secondary x axis derived from the primary x coordinate.
pub fn Axes::secondary_xaxis(
self : Axes,
forward : (Double) -> Double,
inverse : (Double) -> Double,
location~ : SecondaryAxisLocation = SecondaryAxisLocation::Top,
transform~ : @transforms.Affine2D? = None,
) -> SecondaryAxis raise @core.DataError {
match location {
SecondaryAxisLocation::Top
| SecondaryAxisLocation::Bottom
| SecondaryAxisLocation::Position(_) => ()
SecondaryAxisLocation::Left | SecondaryAxisLocation::Right =>
raise @core.DataError::InvalidTicks(
message="secondary x axis location must be top or bottom",
)
}
let result = secondary_axis(
location,
forward,
inverse,
horizontal=true,
figure_bounds=@transforms.bbox(
0.0,
0.0,
self.figure.width.to_double(),
self.figure.height.to_double(),
),
draw_order=self.state.val.secondary_x.length() +
self.state.val.secondary_y.length(),
)
result.parent_axis = Some(self.x_axis())
result.set_location(location, transform~)
result.initialize_formatter_view()
self.state.val.secondary_x.push(result)
result
}
///|
/// Add a secondary y axis derived from the primary y coordinate.
pub fn Axes::secondary_yaxis(
self : Axes,
forward : (Double) -> Double,
inverse : (Double) -> Double,
location~ : SecondaryAxisLocation = SecondaryAxisLocation::Right,
transform~ : @transforms.Affine2D? = None,
) -> SecondaryAxis raise @core.DataError {
match location {
SecondaryAxisLocation::Left
| SecondaryAxisLocation::Right
| SecondaryAxisLocation::Position(_) => ()
SecondaryAxisLocation::Top | SecondaryAxisLocation::Bottom =>
raise @core.DataError::InvalidTicks(
message="secondary y axis location must be left or right",
)
}
let result = secondary_axis(
location,
forward,
inverse,
horizontal=false,
figure_bounds=@transforms.bbox(
0.0,
0.0,
self.figure.width.to_double(),
self.figure.height.to_double(),
),
draw_order=self.state.val.secondary_x.length() +
self.state.val.secondary_y.length(),
)
result.parent_axis = Some(self.y_axis())
result.set_location(location, transform~)
result.initialize_formatter_view()
self.state.val.secondary_y.push(result)
result
}
///|
/// Add a secondary x axis to the default axes panel.
pub fn Figure::secondary_xaxis(
self : Figure,
forward : (Double) -> Double,
inverse : (Double) -> Double,
location~ : SecondaryAxisLocation = SecondaryAxisLocation::Top,
transform~ : @transforms.Affine2D? = None,
) -> SecondaryAxis raise @core.DataError {
self.axes().secondary_xaxis(forward, inverse, location~, transform~)
}
///|
/// Add a secondary y axis to the default axes panel.
pub fn Figure::secondary_yaxis(
self : Figure,
forward : (Double) -> Double,
inverse : (Double) -> Double,
location~ : SecondaryAxisLocation = SecondaryAxisLocation::Right,
transform~ : @transforms.Affine2D? = None,
) -> SecondaryAxis raise @core.DataError {
self.axes().secondary_yaxis(forward, inverse, location~, transform~)
}