///|
fn empty_data_interval() -> @core.Limits {
@core.limits(1.0 / 0.0, -1.0 / 0.0)
}
///|
fn merge_data_interval(
old : @core.Limits,
fresh : @core.Limits,
ignore : Bool,
) -> @core.Limits {
if ignore {
fresh
} else {
@core.limits(
Double::min(old.lower(), fresh.lower()),
Double::max(old.upper(), fresh.upper()),
)
}
}
///|
fn Axes::update_artist_data_limits(self : Axes, artist : Artist) -> Unit {
let (update_x, update_y) = match artist {
Artist::Text(_, _, _, _)
| Artist::Annotation(_)
| Artist::Table(_)
| Artist::Pie(_, _, _, _) => return
Artist::HLine(_, _) | Artist::HSpan(_, _, _) => (false, true)
Artist::VLine(_, _) | Artist::VSpan(_, _, _) => (true, false)
_ => (true, true)
}
let artists = [to_render_artist(artist)]
let x = @svg.artist_data_interval(artists, true)
let y = @svg.artist_data_interval(artists, false)
// Line paths consume ignore even if every nonempty vertex is nonfinite.
// Collection/image dataLim updates instead reject fully empty/nonfinite Bbox.
let consume = match artist {
Artist::Line(points, _) => !points.is_empty()
Artist::HLine(_, _) | Artist::VLine(_, _) | Artist::AxLine(_, _, _) => true
_ => x.lower() <= x.upper() || y.lower() <= y.upper()
}
if !consume {
return
}
let state = self.state.val
let ignore = state.ignore_existing_data_limits
if update_x {
state.raw_x_data = merge_data_interval(state.raw_x_data, x, ignore)
let value = @svg.artist_minpos(artists, true)
state.x_data_minpos = if ignore {
value
} else {
Double::min(state.x_data_minpos, value)
}
}
if update_y {
state.raw_y_data = merge_data_interval(state.raw_y_data, y, ignore)
let value = @svg.artist_minpos(artists, false)
state.y_data_minpos = if ignore {
value
} else {
Double::min(state.y_data_minpos, value)
}
}
state.ignore_existing_data_limits = false
}
///|
/// Rebuild cached data limits from the currently attached geometry.
/// This does not request a view update or enable autoscaling.
/// Optionally skip hidden registered Lines before querying their pending data.
pub fn Axes::relim(self : Axes, visible_only~ : Bool = false) -> Unit {
try! self.relim_checked(visible_only~)
}
///|
/// Rebuild data limits in artist order, retaining a merged prefix on failure.
pub fn Axes::relim_checked(
self : Axes,
visible_only~ : Bool = false,
) -> Unit raise @core.DataError {
let state = self.state.val
state.raw_x_data = empty_data_interval()
state.raw_y_data = empty_data_interval()
// Bbox.set_points(null) leaves its existing minimum-positive metadata.
// The next successful ignore update resets only its selected coordinates.
state.ignore_existing_data_limits = true
for artist in state.artists {
let mut skip = false
if artist is Artist::Line(points, _) {
for line in state.line_originals {
if physical_equal(points, line.points) {
if visible_only && !line.get_visible() {
skip = true
} else {
ignore(line.points_snapshot_checked())
}
break
}
}
}
if skip {
continue
}
self.update_artist_data_limits(artist)
}
}
///|
/// Cached raw data limits, independent of viewport and displayed scale.
pub fn Axes::data_limits(
self : Axes,
horizontal~ : Bool = true,
) -> @core.Limits {
if horizontal {
self.state.val.raw_x_data
} else {
self.state.val.raw_y_data
}
}
///|
/// Cached minimum positive raw coordinate, including interior artist points.
pub fn Axes::data_minpos(self : Axes, horizontal~ : Bool = true) -> Double {
if horizontal {
self.state.val.x_data_minpos
} else {
self.state.val.y_data_minpos
}
}
///|
pub fn Axes::ignore_existing_data_limits(self : Axes) -> Bool {
self.state.val.ignore_existing_data_limits
}
///|
/// Extend cached data limits without requesting or enabling a view update.
/// A paired vertex mask is used after the source's any-finite early test.
pub fn Axes::update_datalim(
self : Axes,
points : Array[@core.Point],
update_x~ : Bool = true,
update_y~ : Bool = true,
) -> Unit {
let mut any_finite = false
for point in points {
let x = point.x()
let y = point.y()
if (!x.is_nan() && !x.is_inf()) || (!y.is_nan() && !y.is_inf()) {
any_finite = true
}
}
if !any_finite {
return
}
let artists = [@render.Artist::Line(points, @core.style("#000000"))]
let state = self.state.val
let ignore = state.ignore_existing_data_limits
if update_x {
state.raw_x_data = merge_data_interval(
state.raw_x_data,
@svg.artist_data_interval(artists, true),
ignore,
)
let fresh = @svg.artist_minpos(artists, true)
state.x_data_minpos = if ignore {
fresh
} else {
Double::min(state.x_data_minpos, fresh)
}
}
if update_y {
state.raw_y_data = merge_data_interval(
state.raw_y_data,
@svg.artist_data_interval(artists, false),
ignore,
)
let fresh = @svg.artist_minpos(artists, false)
state.y_data_minpos = if ignore {
fresh
} else {
Double::min(state.y_data_minpos, fresh)
}
}
state.ignore_existing_data_limits = false
}