Repository URL to install this package:
|
Version:
2.4.3 ▾
|
import { DataRange1d } from "../ranges/data_range1d";
import { logger } from "../../core/logging";
export class RangeManager {
constructor(parent) {
this.parent = parent;
this.invalidate_dataranges = true;
}
get frame() {
return this.parent.frame;
}
update(range_info, options) {
const { x_ranges, y_ranges } = this.frame;
if (range_info == null) {
for (const [, range] of x_ranges) {
range.reset();
}
for (const [, range] of y_ranges) {
range.reset();
}
this.update_dataranges();
}
else {
const range_info_iter = [];
for (const [name, range] of x_ranges) {
range_info_iter.push([range, range_info.xrs.get(name)]);
}
for (const [name, range] of y_ranges) {
range_info_iter.push([range, range_info.yrs.get(name)]);
}
if (options?.scrolling) {
this._update_ranges_together(range_info_iter); // apply interval bounds while keeping aspect
}
this._update_ranges_individually(range_info_iter, options);
}
}
reset() {
this.update(null);
}
_update_dataranges(frame) {
// Update any DataRange1ds here
const bounds = new Map();
const log_bounds = new Map();
let calculate_log_bounds = false;
for (const [, xr] of frame.x_ranges) {
if (xr instanceof DataRange1d && xr.scale_hint == "log")
calculate_log_bounds = true;
}
for (const [, yr] of frame.y_ranges) {
if (yr instanceof DataRange1d && yr.scale_hint == "log")
calculate_log_bounds = true;
}
for (const renderer of this.parent.model.data_renderers) {
const renderer_view = this.parent.renderer_view(renderer);
if (renderer_view == null)
continue;
const bds = renderer_view.glyph_view.bounds();
if (bds != null)
bounds.set(renderer, bds);
if (calculate_log_bounds) {
const log_bds = renderer_view.glyph_view.log_bounds();
if (log_bds != null)
log_bounds.set(renderer, log_bds);
}
}
let follow_enabled = false;
let has_bounds = false;
//const {width, height} = frame.bbox
const width = frame.x_target.span;
const height = frame.y_target.span;
let r;
if (this.parent.model.match_aspect !== false && width != 0 && height != 0)
r = (1 / this.parent.model.aspect_scale) * (width / height);
for (const [, xr] of frame.x_ranges) {
if (xr instanceof DataRange1d) {
const bounds_to_use = xr.scale_hint == "log" ? log_bounds : bounds;
xr.update(bounds_to_use, 0, this.parent.model, r);
if (xr.follow) {
follow_enabled = true;
}
}
if (xr.bounds != null)
has_bounds = true;
}
for (const [, yr] of frame.y_ranges) {
if (yr instanceof DataRange1d) {
const bounds_to_use = yr.scale_hint == "log" ? log_bounds : bounds;
yr.update(bounds_to_use, 1, this.parent.model, r);
if (yr.follow) {
follow_enabled = true;
}
}
if (yr.bounds != null)
has_bounds = true;
}
if (follow_enabled && has_bounds) {
logger.warn("Follow enabled so bounds are unset.");
for (const [, xr] of frame.x_ranges) {
xr.bounds = null;
}
for (const [, yr] of frame.y_ranges) {
yr.bounds = null;
}
}
}
update_dataranges() {
this._update_dataranges(this.frame);
for (const renderer of this.parent.model.renderers) {
const { coordinates } = renderer;
if (coordinates != null)
this._update_dataranges(coordinates);
}
if (this.compute_initial() != null)
this.invalidate_dataranges = false;
}
compute_initial() {
// check for good values for ranges before setting initial range
let good_vals = true;
const { x_ranges, y_ranges } = this.frame;
const xrs = new Map();
const yrs = new Map();
for (const [name, range] of x_ranges) {
const { start, end } = range;
if (start == null || end == null || isNaN(start + end)) {
good_vals = false;
break;
}
xrs.set(name, { start, end });
}
if (good_vals) {
for (const [name, range] of y_ranges) {
const { start, end } = range;
if (start == null || end == null || isNaN(start + end)) {
good_vals = false;
break;
}
yrs.set(name, { start, end });
}
}
if (good_vals)
return { xrs, yrs };
else {
logger.warn("could not set initial ranges");
return null;
}
}
_update_ranges_together(range_info_iter) {
// Get weight needed to scale the diff of the range to honor interval limits
let weight = 1.0;
for (const [rng, range_info] of range_info_iter) {
weight = Math.min(weight, this._get_weight_to_constrain_interval(rng, range_info));
}
// Apply shared weight to all ranges
if (weight < 1) {
for (const [rng, range_info] of range_info_iter) {
range_info.start = weight * range_info.start + (1 - weight) * rng.start;
range_info.end = weight * range_info.end + (1 - weight) * rng.end;
}
}
}
_update_ranges_individually(range_info_iter, options) {
const panning = !!options?.panning;
const scrolling = !!options?.scrolling;
let hit_bound = false;
for (const [rng, range_info] of range_info_iter) {
// Limit range interval first. Note that for scroll events,
// the interval has already been limited for all ranges simultaneously
if (!scrolling) {
const weight = this._get_weight_to_constrain_interval(rng, range_info);
if (weight < 1) {
range_info.start = weight * range_info.start + (1 - weight) * rng.start;
range_info.end = weight * range_info.end + (1 - weight) * rng.end;
}
}
// Prevent range from going outside limits
// Also ensure that range keeps the same delta when panning/scrolling
if (rng.bounds != null && rng.bounds != "auto") { // check `auto` for type-checking purpose
const [min, max] = rng.bounds;
const new_interval = Math.abs(range_info.end - range_info.start);
if (rng.is_reversed) {
if (min != null) {
if (min > range_info.end) {
hit_bound = true;
range_info.end = min;
if (panning || scrolling) {
range_info.start = min + new_interval;
}
}
}
if (max != null) {
if (max < range_info.start) {
hit_bound = true;
range_info.start = max;
if (panning || scrolling) {
range_info.end = max - new_interval;
}
}
}
}
else {
if (min != null) {
if (min > range_info.start) {
hit_bound = true;
range_info.start = min;
if (panning || scrolling) {
range_info.end = min + new_interval;
}
}
}
if (max != null) {
if (max < range_info.end) {
hit_bound = true;
range_info.end = max;
if (panning || scrolling) {
range_info.start = max - new_interval;
}
}
}
}
}
}
// Cancel the event when hitting a bound while scrolling. This ensures that
// the scroll-zoom tool maintains its focus position. Setting `maintain_focus`
// to false results in a more "gliding" behavior, allowing one to
// zoom out more smoothly, at the cost of losing the focus position.
if (scrolling && hit_bound && options?.maintain_focus)
return;
for (const [rng, range_info] of range_info_iter) {
rng.have_updated_interactively = true;
if (rng.start != range_info.start || rng.end != range_info.end)
rng.setv(range_info);
}
}
_get_weight_to_constrain_interval(rng, range_info) {
// Get the weight by which a range-update can be applied
// to still honor the interval limits (including the implicit
// max interval imposed by the bounds)
const { min_interval } = rng;
let { max_interval } = rng;
// Express bounds as a max_interval. By doing this, the application of
// bounds and interval limits can be applied independent from each-other.
if (rng.bounds != null && rng.bounds != "auto") { // check `auto` for type-checking purpose
const [min, max] = rng.bounds;
if (min != null && max != null) {
const max_interval2 = Math.abs(max - min);
max_interval = max_interval != null ? Math.min(max_interval, max_interval2) : max_interval2;
}
}
let weight = 1.0;
if (min_interval != null || max_interval != null) {
const old_interval = Math.abs(rng.end - rng.start);
const new_interval = Math.abs(range_info.end - range_info.start);
if (min_interval != null && min_interval > 0 && new_interval < min_interval) {
weight = (old_interval - min_interval) / (old_interval - new_interval);
}
if (max_interval != null && max_interval > 0 && new_interval > max_interval) {
weight = (max_interval - old_interval) / (new_interval - old_interval);
}
weight = Math.max(0.0, Math.min(1.0, weight));
}
return weight;
}
}
RangeManager.__name__ = "RangeManager";
//# sourceMappingURL=range_manager.js.map