///|
/// Logarithmic locator with ordered parameter updates and live axis context.
pub struct LogLocator {
priv mut base : Double
priv mut subs : LogSubs
priv mut numticks : LogNumTicks
priv mut tick_space_provider : (() -> LogTickSpace raise @core.DataError)?
priv mut view_interval_provider : (() -> (Double, Double) raise @core.DataError)?
priv mut minpos_provider : (() -> Double raise @core.DataError)?
priv mut shared_minpos_provider : (() -> Array[Double] raise @core.DataError)?
priv diagnostics : Array[String]
}
///|
fn log_normalize_subs(subs : LogSubs?) -> LogSubs raise @core.DataError {
match subs {
None => Text("auto")
Some(Text(value)) => {
if value != "auto" && value != "all" {
raise @core.DataError::InvalidTicks(
message="log subs must be all, auto, or a sequence of floats",
)
}
Text(value)
}
Some(Values(value)) =>
if value.is_int64() {
Values(TickValues::from_array(value.to_array()))
} else {
Values(value)
}
}
}
///|
/// Omitted subs use [1]; explicit None selects auto. Numeric values are not validated.
pub fn LogLocator::new(
base~ : Double = 10.0,
subs? : LogSubs?,
numticks~ : LogNumTicks? = None,
) -> LogLocator raise @core.DataError {
let numticks = match numticks {
Some(value) => value
None =>
if @config.classic_mode() {
Integer(@bigint.BigInt::from_int(15))
} else {
Text("auto")
}
}
let subs = match subs {
None => LogSubs::Values(TickValues::from_array([1.0]))
Some(value) => log_normalize_subs(value)
}
{
base,
subs,
numticks,
tick_space_provider: None,
view_interval_provider: None,
minpos_provider: None,
shared_minpos_provider: None,
diagnostics: [],
}
}
///|
pub fn LogLocator::base(self : LogLocator) -> Double {
self.base
}
///|
pub fn LogLocator::subs(self : LogLocator) -> LogSubs {
self.subs
}
///|
pub fn LogLocator::numticks(self : LogLocator) -> LogNumTicks {
self.numticks
}
///|
/// Assign the raw public numticks slot without validation or classic recapture.
pub fn LogLocator::set_numticks(self : LogLocator, value : LogNumTicks) -> Unit {
self.numticks = value
}
///|
/// Preserve source order and partial state: base, then subs, then numticks.
pub fn LogLocator::set_params(
self : LogLocator,
base? : Double?,
subs? : LogSubs?,
numticks? : LogNumTicks?,
) -> Unit raise @core.DataError {
if base is Some(Some(value)) {
self.base = value
}
if subs is Some(Some(value)) {
self.subs = log_normalize_subs(Some(value))
}
if numticks is Some(Some(value)) {
self.numticks = value
}
}
///|
pub fn LogLocator::copy(self : LogLocator) -> LogLocator {
{
base: self.base,
subs: self.subs,
numticks: self.numticks,
tick_space_provider: self.tick_space_provider,
view_interval_provider: self.view_interval_provider,
minpos_provider: self.minpos_provider,
shared_minpos_provider: self.shared_minpos_provider,
diagnostics: self.diagnostics.copy(),
}
}
///|
pub fn LogLocator::warnings(self : LogLocator) -> Array[String] {
self.diagnostics.copy()
}
///|
pub fn LogLocator::bind_tick_space(
self : LogLocator,
provider : () -> LogTickSpace raise @core.DataError,
) -> Unit {
self.tick_space_provider = Some(provider)
}
///|
pub fn LogLocator::bind_view_interval(
self : LogLocator,
provider : () -> (Double, Double) raise @core.DataError,
) -> Unit {
self.view_interval_provider = Some(provider)
}
///|
pub fn LogLocator::bind_minpos(
self : LogLocator,
provider : () -> Double raise @core.DataError,
) -> Unit {
self.minpos_provider = Some(provider)
}
///|
pub fn LogLocator::bind_shared_minpos(
self : LogLocator,
provider : () -> Array[Double] raise @core.DataError,
) -> Unit {
self.shared_minpos_provider = Some(provider)
}
///|
pub fn LogLocator::tick_space_provider(
self : LogLocator,
) -> (() -> LogTickSpace raise @core.DataError)? {
self.tick_space_provider
}
///|
pub fn LogLocator::view_interval_provider(
self : LogLocator,
) -> (() -> (Double, Double) raise @core.DataError)? {
self.view_interval_provider
}
///|
pub fn LogLocator::minpos_provider(
self : LogLocator,
) -> (() -> Double raise @core.DataError)? {
self.minpos_provider
}
///|
pub fn LogLocator::shared_minpos_provider(
self : LogLocator,
) -> (() -> Array[Double] raise @core.DataError)? {
self.shared_minpos_provider
}
///|
fn LogLocator::is_bound(self : LogLocator) -> Bool {
self.tick_space_provider is Some(_) ||
self.view_interval_provider is Some(_) ||
self.minpos_provider is Some(_) ||
self.shared_minpos_provider is Some(_)
}
///|
pub fn LogLocator::tick_values(
self : LogLocator,
lower : Double,
upper : Double,
) -> Array[Double] raise @core.DataError {
self.diagnostics.clear()
named_log_locator_ticks(self, lower, upper)
}
///|
pub fn LogLocator::call(
self : LogLocator,
) -> Array[Double] raise @core.DataError {
self.diagnostics.clear()
let (lower, upper) = match self.view_interval_provider {
Some(provider) => provider()
None =>
raise @core.DataError::InvalidTicks(
message="log locator is not bound to a view interval",
)
}
self.tick_values(lower, upper)
}
///|
/// Positive finite ranges consult every shared minpos, even when already nonsingular.
pub fn LogLocator::nonsingular(
self : LogLocator,
lower : Double,
upper : Double,
) -> (Double, Double) raise @core.DataError {
self.diagnostics.clear()
let mut first = lower
let mut last = upper
if first > last {
let previous = first
first = last
last = previous
}
if first.is_nan() || first.is_inf() || last.is_nan() || last.is_inf() {
return (1.0, 10.0)
}
if last <= 0.0 {
self.diagnostics.push(
"Data has no positive values, and therefore cannot be log-scaled.",
)
return (1.0, 10.0)
}
let minpos = match self.shared_minpos_provider {
Some(provider) => provider()
None =>
raise @core.DataError::InvalidTicks(
message="log locator is not bound to shared minimum positive values",
)
}
if minpos.is_empty() {
raise @core.DataError::InvalidTicks(
message="log locator shared minimum positive values are empty",
)
}
let mut minimum = minpos[0]
// Python min keeps the first NaN and ignores later NaNs via strict comparison.
for index in 1.. (Double, Double) raise @core.DataError {
let (first, last) = self.nonsingular(lower, upper)
if @config.axes_autolimit_mode() == "round_numbers" {
(
log_decade_less_equal(first, self.base),
log_decade_greater_equal(last, self.base),
)
} else {
(first, last)
}
}