///|
/// Bind local artist data without changing the locator's owning axis.
pub fn Axis::bind_data_interval(
self : Axis,
provider : () -> (Double, Double),
) -> Unit {
self.data_interval_provider = Some(provider)
}
///|
pub fn Axis::data_interval(self : Axis) -> @core.Limits {
match self.data_interval_provider {
Some(provider) => {
let (lower, upper) = provider()
@core.limits(lower, upper)
}
None => @core.limits(1.0 / 0.0, -1.0 / 0.0)
}
}
///|
/// Rebind after setting a shared locator so its last explicit owner supplies data.
pub fn Axis::bind_locator_data(self : Axis, minor~ : Bool = false) -> Unit {
if minor && self.tickers.minor_locator_kind == Date {
self.tickers.minor_date_view_provider = Some(() => {
self.view_interval_checked()
})
return
}
if !minor && self.tickers.major_locator_kind == Date {
self.tickers.major_date_view_provider = Some(() => {
self.view_interval_checked()
})
return
}
if (minor && self.tickers.minor_locator_kind != Numeric) ||
(!minor && self.tickers.major_locator_kind != Numeric) {
return
}
let provider = fn() {
let values = self.data_interval()
(values.lower(), values.upper())
}
if minor {
match self.tickers.minor_locator {
Some(@ticker.MinorLocator::NamedLogit(value)) =>
self.bind_logit_context(value)
Some(@ticker.MinorLocator::NamedAsinh(value)) =>
value.bind_view_interval(() => self.view_interval_checked())
Some(@ticker.MinorLocator::NamedSymLog(value)) =>
value.bind_view_interval(() => self.view_interval_checked())
Some(@ticker.MinorLocator::NamedLog(value)) =>
self.bind_log_context(value)
Some(@ticker.MinorLocator::NamedAutoMinor(value)) =>
self.bind_auto_minor_context(value)
Some(@ticker.MinorLocator::Index(value)) =>
value.bind_data_interval(provider)
Some(@ticker.MinorLocator::NamedMaxN(value)) =>
self.bind_maxn_context(value)
Some(@ticker.MinorLocator::NamedAuto(value)) =>
self.bind_maxn_context(value.as_maxn_locator())
_ => ()
}
} else {
match self.tickers.locator {
Some(@ticker.Locator::NamedLogit(value)) => self.bind_logit_context(value)
Some(@ticker.Locator::NamedAsinh(value)) =>
value.bind_view_interval(() => self.view_interval_checked())
Some(@ticker.Locator::NamedSymLog(value)) =>
value.bind_view_interval(() => self.view_interval_checked())
Some(@ticker.Locator::NamedLog(value)) => self.bind_log_context(value)
Some(@ticker.Locator::Index(value)) => value.bind_data_interval(provider)
Some(@ticker.Locator::NamedMaxN(value)) => self.bind_maxn_context(value)
Some(@ticker.Locator::NamedAuto(value)) =>
self.bind_maxn_context(value.as_maxn_locator())
_ => ()
}
}
}
///|
/// New named numeric locators preserve their direct errors in checked rendering.
pub fn Axis::tick_values_checked(
self : Axis,
automatic : @core.Limits,
) -> Array[Double] raise @core.DataError {
if self.tickers.major_locator_kind == Category {
return self.tickers.category_locator_units
.map(units => units.positions())
.unwrap_or([])
}
if self.tickers.major_locator_kind == Date {
return self.date_locator_tick_values_checked(
self.limits.unwrap_or(automatic),
false,
)
}
if (
self.tickers.named_formatter is Some(_) ||
self.tickers.minor_named_formatter is Some(_)
) &&
self.tickers.fixed_ticks is None &&
self.numeric_tickers_active() &&
self.tickers.locator is Some(@ticker.Locator::Fixed(values)) {
return values.copy()
}
if self.tickers.fixed_ticks is None &&
self.tickers.major_locator_kind == Numeric {
if self.tickers.locator
is Some(
@ticker.Locator::NamedLogit(_)
| @ticker.Locator::NamedAsinh(_)
| @ticker.Locator::NamedSymLog(_)
| @ticker.Locator::NamedLog(_)
| @ticker.Locator::Null(_)
| @ticker.Locator::NamedLinear(_)
| @ticker.Locator::Index(_)
| @ticker.Locator::NamedFixed(_)
| @ticker.Locator::NamedMaxN(_)
| @ticker.Locator::NamedAuto(_)
) {
let limits = self.limits.unwrap_or(automatic)
let previous = self.tick_layout_view
if previous is None && !self.has_view_interval_provider() {
self.tick_layout_view = Some((limits.lower(), limits.upper()))
}
defer {
self.tick_layout_view = previous
}
return self.tickers.locator
.unwrap()
.tick_values_for_axis(limits.lower(), limits.upper())
}
}
self.tick_values(automatic)
}
///|
pub fn Axis::minor_tick_values_checked(
self : Axis,
automatic : @core.Limits,
) -> Array[Double] raise @core.DataError {
if self.tickers.minor_locator_kind == Date {
let limits = self.limits.unwrap_or(automatic)
let ticks = self.date_locator_tick_values_checked(limits, true)
if !self.remove_minor_overlap {
return ticks
}
let major = self.major_tick_view_checked().to_array()
return self.filter_numeric_minor_ticks(ticks, major)
}
if self.tickers.minor_locator_kind == Numeric {
if self.tickers.minor_locator is Some(@ticker.MinorLocator::Null(value)) {
let ticks = value.call()
if self.remove_minor_overlap {
// Direct Axis getters still read majors/view; rendering skips Null earlier.
let major = self.major_tick_view_checked().to_array()
return self.filter_numeric_minor_ticks(ticks, major)
}
return ticks
}
let typed_formatter = self.tickers.minor_formatter_kind != Numeric
let raw_named_fixed = (
self.scale.is_logit() ||
self.tickers.minor_named_formatter is Some(_) ||
typed_formatter
) &&
self.tickers.minor_locator is Some(@ticker.MinorLocator::NamedFixed(_))
let raw_legacy_fixed = (
self.tickers.minor_named_formatter is Some(_) || typed_formatter
) &&
self.tickers.minor_locator is Some(@ticker.MinorLocator::Fixed(_))
if self.tickers.minor_locator
is Some(
@ticker.MinorLocator::NamedAutoMinor(_)
| @ticker.MinorLocator::NamedLog(_)
| @ticker.MinorLocator::NamedLogit(_)
| @ticker.MinorLocator::NamedAsinh(_)
| @ticker.MinorLocator::NamedSymLog(_)
) ||
raw_named_fixed ||
raw_legacy_fixed {
let limits = self.limits.unwrap_or(automatic)
let previous = self.tick_layout_view
if previous is None && !self.has_view_interval_provider() {
self.tick_layout_view = Some((limits.lower(), limits.upper()))
}
defer {
self.tick_layout_view = previous
}
// Source minor.call reads its own owner first. Axis overlap reads major again.
let ticks = match self.tickers.minor_locator.unwrap() {
@ticker.MinorLocator::Fixed(values) => values.copy()
@ticker.MinorLocator::NamedFixed(value) => {
let locations = value.tick_values(0.0, 0.0)
if typed_formatter {
locations.to_array()
} else {
locations
.view(0, 1, Int32::min(100000, locations.length()))
.to_array()
}
}
@ticker.MinorLocator::NamedAutoMinor(value) => value.call()
@ticker.MinorLocator::NamedLog(value) => value.call()
@ticker.MinorLocator::NamedLogit(value) => value.call()
@ticker.MinorLocator::NamedAsinh(value) => value.call()
@ticker.MinorLocator::NamedSymLog(value) => value.call()
_ => []
}
if !self.remove_minor_overlap {
return ticks
}
let major = self.major_tick_view_checked().to_array()
return self.filter_numeric_minor_ticks(ticks, major)
}
if self.tickers.minor_locator
is Some(
@ticker.MinorLocator::NamedLinear(_)
| @ticker.MinorLocator::Index(_)
| @ticker.MinorLocator::NamedFixed(_)
| @ticker.MinorLocator::NamedMaxN(_)
| @ticker.MinorLocator::NamedAuto(_)
) {
let limits = self.limits.unwrap_or(automatic)
let previous = self.tick_layout_view
if previous is None && !self.has_view_interval_provider() {
self.tick_layout_view = Some((limits.lower(), limits.upper()))
}
defer {
self.tick_layout_view = previous
}
let major = self.tick_values_checked(automatic)
let ticks = self.tickers.minor_locator
.unwrap()
.tick_values_for_axis(major, limits.lower(), limits.upper())
return ticks
}
}
self.minor_tick_values(automatic)
}
///|
fn Axis::bind_auto_minor_context(
self : Axis,
value : @ticker.AutoMinorLocator,
) -> Unit {
value.bind_scale(() => self.numeric_scale_name())
value.bind_axis_name(() => if self.tick_horizontal { "x" } else { "y" })
value.bind_major_ticks(() => self.major_tick_view_checked())
value.bind_view_interval(() => self.view_interval_checked())
}
///|
fn Axis::numeric_scale_name(self : Axis) -> String {
self.coordinate_scale_name()
}
///|
/// Obtain majors in source order while preserving borrowed integer storage.
fn Axis::major_tick_view_checked(
self : Axis,
) -> @ticker.TickValues raise @core.DataError {
if self.tickers.major_locator_kind != Numeric {
let (lower, upper) = self.view_interval_checked()
return @ticker.TickValues::from_array(
self.tick_values_checked(@core.limits(lower, upper)),
)
}
if self.tickers.fixed_ticks is Some(values) {
return @ticker.TickValues::from_array(values.copy())
}
if self.tickers.major_locator_kind == Numeric {
match self.tickers.locator {
Some(@ticker.Locator::Fixed(values)) if self.tickers.named_formatter
is Some(_) ||
self.tickers.minor_named_formatter is Some(_) =>
return @ticker.TickValues::from_array(values.copy())
Some(@ticker.Locator::NamedFixed(value)) =>
return value.tick_values(0.0, 0.0)
Some(@ticker.Locator::Null(_)) =>
return @ticker.TickValues::from_array([])
Some(locator) if locator
is (@ticker.Locator::NamedLogit(_)
| @ticker.Locator::NamedAsinh(_)
| @ticker.Locator::NamedSymLog(_)
| @ticker.Locator::NamedLog(_)
| @ticker.Locator::Index(_)
| @ticker.Locator::NamedMaxN(_)
| @ticker.Locator::NamedAuto(_)) =>
return locator.tick_view_for_axis(0.0, 0.0)
Some(@ticker.Locator::NamedLinear(value)) => {
let (lower, upper) = self.view_interval_checked()
return value.tick_values(lower, upper)
}
_ => ()
}
}
let (lower, upper) = self.view_interval_checked()
@ticker.TickValues::from_array(
self.tick_values_checked(@core.limits(lower, upper)),
)
}
///|
// Date objects share ticker state, while checked conversion/selection errors
// remain visible to the same Axis and SVG pipeline as named numeric errors.
fn Axis::date_locator_tick_values_checked(
self : Axis,
limits : @core.Limits,
minor : Bool,
) -> Array[Double] raise @core.DataError {
let previous = self.tick_layout_view
if previous is None && !self.has_view_interval_provider() {
self.tick_layout_view = Some((limits.lower(), limits.upper()))
}
defer {
self.tick_layout_view = previous
}
let provider = if minor {
self.tickers.minor_date_view_provider
} else {
self.tickers.major_date_view_provider
}
let limits = match provider {
Some(provider) => {
let (lower, upper) = provider()
@core.limits(lower, upper)
}
None => limits
}
let locator = if minor {
self.tickers.minor_date_locator
} else {
self.tickers.date_locator
}
if locator is Some(locator) {
return locator.tick_values_checked(limits.lower(), limits.upper()) catch {
@date.DateError::InvalidDate(message~)
| @date.DateError::InvalidFormat(message~) =>
raise @core.DataError::InvalidTicks(message~)
}
}
if !minor && self.tickers.automatic_date_locator is Some(locator) {
return locator.tick_values(limits.lower(), limits.upper()) catch {
@date.DateError::InvalidDate(message~)
| @date.DateError::InvalidFormat(message~) =>
raise @core.DataError::InvalidTicks(message~)
}
}
[]
}
///|
fn Axis::filter_numeric_minor_ticks(
self : Axis,
ticks : Array[Double],
major : Array[Double],
) -> Array[Double] raise @core.DataError {
let mask = self.numeric_minor_selection_mask(ticks, major)
let selected : Array[Double] = []
for index, tick in ticks {
if mask[index] {
selected.push(tick)
}
}
selected
}
///|
fn Axis::numeric_minor_selection_mask(
self : Axis,
ticks : Array[Double],
major : Array[Double],
) -> Array[Bool] raise @core.DataError {
// Axis.get_minorticklocs captures one transform after both locator queries.
// Function callbacks run on minors, then majors, before the late view read.
let transform = self.minor_overlap_transform()
let transformed_ticks = ticks.map(transform)
let transformed_major = major.map(transform)
let (lower, upper) = self.view_interval_checked()
let lo = transform(lower)
let hi = transform(upper)
let tolerance = Double::abs(hi - lo) * 1.0e-5
transformed_ticks.map(transformed => {
!transformed_major.any(other => {
transformed == other ||
(
!other.is_nan() &&
!other.is_inf() &&
Double::abs(transformed - other) <= tolerance
)
})
})
}
///|
/// Rendering skips each NullLocator tick group before asking for labels or offsets.
pub fn Axis::tick_group_is_null(self : Axis, minor~ : Bool = false) -> Bool {
if minor {
self.tickers.minor_locator_kind == Numeric &&
self.tickers.minor_locator is Some(@ticker.MinorLocator::Null(_))
} else {
self.tickers.major_locator_kind == Numeric &&
self.tickers.locator is Some(@ticker.Locator::Null(_))
}
}
///|
// Source LogTransform uses nonpositive="clip" for tick overlap coordinates.
fn Axis::minor_overlap_transform(self : Axis) -> (Double) -> Double {
if self.function_scale is Some(_) {
return self.coordinate_transform()
}
let scale = self.scale
fn(value) {
if scale.is_log() && value <= 0.0 {
-1000.0
} else {
scale.transform(value)
}
}
}