UNPKG

@bokeh/bokehjs

Version:

Interactive, novel data visualization

650 lines 18.1 kB
import { ScreenArray } from "../types"; import { equals } from "./eq"; import { map } from "./arrayable"; import { isPlainObject } from "./types"; const { min, max, round } = Math; export function empty() { return { x0: Infinity, y0: Infinity, x1: -Infinity, y1: -Infinity, }; } export function full() { return { x0: -Infinity, y0: -Infinity, x1: Infinity, y1: Infinity, }; } export function x_range(x0, x1) { return { x0, y0: -Infinity, x1, y1: Infinity, }; } export function y_range(y0, y1) { return { x0: -Infinity, y0, x1: Infinity, y1, }; } export function positive_x() { return x_range(Number.MIN_VALUE, Infinity); } export function negative_x() { return x_range(-Infinity, -Number.MIN_VALUE); } export function positive_y() { return y_range(Number.MIN_VALUE, Infinity); } export function negative_y() { return y_range(-Infinity, -Number.MIN_VALUE); } function _min(a, b) { if (isNaN(a)) { return b; } else if (isNaN(b)) { return a; } else { return min(a, b); } } function _max(a, b) { if (isNaN(a)) { return b; } else if (isNaN(b)) { return a; } else { return max(a, b); } } export function union(a, b) { return { x0: _min(a.x0, b.x0), x1: _max(a.x1, b.x1), y0: _min(a.y0, b.y0), y1: _max(a.y1, b.y1), }; } export function isXY(obj) { return isPlainObject(obj) && "x" in obj && "y" in obj; } export class BBox { static __name__ = "BBox"; x0; y0; x1; y1; constructor(box, correct = false) { if (box == null) { this.x0 = 0; this.y0 = 0; this.x1 = 0; this.y1 = 0; } else if ("x0" in box) { const { x0, y0, x1, y1 } = box; if (!isFinite(x0 + y0 + x1 + y1)) { this.x0 = NaN; this.y0 = NaN; this.x1 = NaN; this.y1 = NaN; } else { if (!(x0 <= x1 && y0 <= y1)) { throw new Error(`invalid bbox {x0: ${x0}, y0: ${y0}, x1: ${x1}, y1: ${y1}}`); } this.x0 = x0; this.y0 = y0; this.x1 = x1; this.y1 = y1; } } else if ("x" in box) { const { x, y, width, height, origin = "top_left" } = box; if (!(width >= 0 && height >= 0)) { throw new Error(`invalid bbox {x: ${x}, y: ${y}, width: ${width}, height: ${height}}`); } const base_origin = (() => { switch (origin) { case "left": return "center_left"; case "right": return "center_right"; case "top": return "top_center"; case "bottom": return "bottom_center"; case "center": return "center_center"; default: return origin; } })(); const [y_align, x_align] = base_origin.split("_", 2); const y_coeff = (() => { switch (y_align) { case "top": return 0.0; case "center": return 0.5; case "bottom": return 1.0; } })(); const x_coeff = (() => { switch (x_align) { case "left": return 0.0; case "center": return 0.5; case "right": return 1.0; } })(); const d_width = x_coeff * width; const d_height = y_coeff * height; const x0 = x - d_width; const y0 = y - d_height; const x1 = x0 + width; const y1 = y0 + height; this.x0 = x0; this.y0 = y0; this.x1 = x1; this.y1 = y1; } else { let left, right; let top, bottom; if ("width" in box) { if ("left" in box) { left = box.left; right = left + box.width; } else if ("right" in box) { right = box.right; left = right - box.width; } else { const w2 = box.width / 2; left = box.hcenter - w2; right = box.hcenter + w2; } } else { left = box.left; right = box.right; } if ("height" in box) { if ("top" in box) { top = box.top; bottom = top + box.height; } else if ("bottom" in box) { bottom = box.bottom; top = bottom - box.height; } else { const h2 = box.height / 2; top = box.vcenter - h2; bottom = box.vcenter + h2; } } else { top = box.top; bottom = box.bottom; } if (left > right || top > bottom) { if (correct) { if (left > right) { left = right; } if (top > bottom) { top = bottom; } } else { throw new Error(`invalid bbox {left: ${left}, top: ${top}, right: ${right}, bottom: ${bottom}}`); } } this.x0 = left; this.y0 = top; this.x1 = right; this.y1 = bottom; } } static from_lrtb({ left, right, top, bottom }) { return new BBox({ x0: min(left, right), y0: min(top, bottom), x1: max(left, right), y1: max(top, bottom), }); } static from_rect({ x0, y0, x1, y1 }) { return new BBox({ x0: min(x0, x1), y0: min(y0, y1), x1: max(x0, x1), y1: max(y0, y1), }); } static empty() { return new BBox({ x0: 0, y0: 0, x1: 0, y1: 0 }); } static invalid() { return new BBox({ x0: NaN, y0: NaN, x1: NaN, y1: NaN }); } clone() { return new BBox(this); } equals(that) { return this.x0 == that.x0 && this.y0 == that.y0 && this.x1 == that.x1 && this.y1 == that.y1; } [equals](that, cmp) { return cmp.eq(this.x0, that.x0) && cmp.eq(this.y0, that.y0) && cmp.eq(this.x1, that.x1) && cmp.eq(this.y1, that.y1); } toString() { return `BBox({left: ${this.left}, top: ${this.top}, width: ${this.width}, height: ${this.height}})`; } get is_valid() { const { x0, x1, y0, y1 } = this; return isFinite(x0 + x1 + y0 + y1); } get is_empty() { const { x0, x1, y0, y1 } = this; return x0 == 0 && x1 == 0 && y0 == 0 && y1 == 0; } get left() { return this.x0; } get top() { return this.y0; } get right() { return this.x1; } get bottom() { return this.y1; } get p0() { return { x: this.x0, y: this.y0 }; } get p1() { return { x: this.x1, y: this.y1 }; } get x() { return this.x0; } get y() { return this.y0; } get width() { return this.x1 - this.x0; } get height() { return this.y1 - this.y0; } get size() { return { width: this.width, height: this.height }; } get rect() { const { x0, y0, x1, y1 } = this; return { p0: { x: x0, y: y0 }, p1: { x: x1, y: y0 }, p2: { x: x1, y: y1 }, p3: { x: x0, y: y1 }, }; } get box() { const { x, y, width, height } = this; return { x, y, width, height }; } get lrtb() { const { left, right, top, bottom } = this; return { left, right, top, bottom }; } get args() { const { x, y, width, height } = this; return [x, y, width, height]; } get x_range() { return { start: this.x0, end: this.x1 }; } get y_range() { return { start: this.y0, end: this.y1 }; } get h_range() { return this.x_range; } get v_range() { return this.y_range; } get ranges() { return [this.x_range, this.y_range]; } get aspect() { return this.width / this.height; } get x_center() { return (this.left + this.right) / 2; } get y_center() { return (this.top + this.bottom) / 2; } get hcenter() { return this.x_center; } get vcenter() { return this.y_center; } get area() { return this.width * this.height; } resolve(symbol) { switch (symbol) { case "top_left": return this.top_left; case "top_center": return this.top_center; case "top_right": return this.top_right; case "center_left": return this.center_left; case "center_center": return this.center_center; case "center_right": return this.center_right; case "bottom_left": return this.bottom_left; case "bottom_center": return this.bottom_center; case "bottom_right": return this.bottom_right; case "center": return this.center; case "top": return this.top; case "left": return this.left; case "right": return this.right; case "bottom": return this.bottom; case "width": return this.width; case "height": return this.height; default: return { x: NaN, y: NaN }; } } get top_left() { return { x: this.left, y: this.top }; } get top_center() { return { x: this.hcenter, y: this.top }; } get top_right() { return { x: this.right, y: this.top }; } get center_left() { return { x: this.left, y: this.vcenter }; } get center_center() { return { x: this.hcenter, y: this.vcenter }; } get center_right() { return { x: this.right, y: this.vcenter }; } get bottom_left() { return { x: this.left, y: this.bottom }; } get bottom_center() { return { x: this.hcenter, y: this.bottom }; } get bottom_right() { return { x: this.right, y: this.bottom }; } get center() { return { x: this.hcenter, y: this.vcenter }; } round() { return new BBox({ x0: round(this.x0), x1: round(this.x1), y0: round(this.y0), y1: round(this.y1), }); } relative() { const { width, height } = this; return new BBox({ x: 0, y: 0, width, height }); } relative_to(to) { const { x, y, width, height } = this; return new BBox({ x: x - to.x, y: y - to.y, width, height }); } translate(tx, ty) { const { x, y, width, height } = this; return new BBox({ x: tx + x, y: ty + y, width, height }); } scale(factor) { return new BBox({ x0: this.x0 * factor, x1: this.x1 * factor, y0: this.y0 * factor, y1: this.y1 * factor, }); } relativize(x, y) { return [x - this.x, y - this.y]; } contains(x, y) { return this.x0 <= x && x <= this.x1 && this.y0 <= y && y <= this.y1; } clip(x, y) { if (x < this.x0) { x = this.x0; } else if (x > this.x1) { x = this.x1; } if (y < this.y0) { y = this.y0; } else if (y > this.y1) { y = this.y1; } return [x, y]; } grow_by(size) { return new BBox({ left: this.left - size, right: this.right + size, top: this.top - size, bottom: this.bottom + size, }); } shrink_by(size) { return new BBox({ left: this.left + size, right: this.right - size, top: this.top + size, bottom: this.bottom - size, }, true); } union(that) { return new BBox({ x0: min(this.x0, that.x0), y0: min(this.y0, that.y0), x1: max(this.x1, that.x1), y1: max(this.y1, that.y1), }); } intersection(that) { if (!this.intersects(that)) { return null; } else { return new BBox({ x0: max(this.x0, that.x0), y0: max(this.y0, that.y0), x1: min(this.x1, that.x1), y1: min(this.y1, that.y1), }); } } intersects(that) { return !(that.x1 < this.x0 || that.x0 > this.x1 || that.y1 < this.y0 || that.y0 > this.y1); } _x_percent; get x_percent() { const self = this; return this._x_percent ?? (this._x_percent = { compute(x) { return self.left + x * self.width; }, invert(sx) { return (sx - self.left) / self.width; }, v_compute(xs) { const { left, width } = self; return new ScreenArray(map(xs, (x) => left + x * width)); }, v_invert(sxs) { const { left, width } = self; return map(sxs, (sx) => (sx - left) / width); }, get source_range() { return self.x_range; }, get target_range() { return self.x_range; }, }); } _y_percent; get y_percent() { const self = this; return this._y_percent ?? (this._y_percent = { compute(y) { return self.top + y * self.height; }, invert(sy) { return (sy - self.top) / self.height; }, v_compute(ys) { const { top, height } = self; return new ScreenArray(map(ys, (y) => top + y * height)); }, v_invert(sys) { const { top, height } = self; return map(sys, (sy) => (sy - top) / height); }, get source_range() { return self.y_range; }, get target_range() { return self.y_range; }, }); } _x_screen; get x_screen() { const self = this; return this._x_screen ?? (this._x_screen = { compute(x) { return self.left + x; }, invert(sx) { return sx - self.left; }, v_compute(xs) { const { left } = self; return new ScreenArray(map(xs, (x) => left + x)); }, v_invert(sxs) { const { left } = self; return map(sxs, (sx) => sx - left); }, get source_range() { return self.x_range; }, get target_range() { return self.x_range; }, }); } _y_screen; get y_screen() { const self = this; return this._y_screen ?? (this._y_screen = { compute(y) { return self.top + y; }, invert(sy) { return sy - self.top; }, v_compute(ys) { const { top } = self; return new ScreenArray(map(ys, (y) => top + y)); }, v_invert(sys) { const { top } = self; return map(sys, (sy) => sy - top); }, get source_range() { return self.y_range; }, get target_range() { return self.y_range; }, }); } _x_view; get x_view() { const self = this; return this._x_view ?? (this._x_view = { compute(x) { return self.left + x; }, invert(sx) { return sx - self.left; }, v_compute(xs) { const { left } = self; return new ScreenArray(map(xs, (x) => left + x)); }, v_invert(sxs) { const { left } = self; return map(sxs, (sx) => sx - left); }, get source_range() { return self.x_range; }, get target_range() { return self.x_range; }, }); } _y_view; get y_view() { const self = this; return this._y_view ?? (this._y_view = { compute(y) { return self.bottom - y; }, invert(sy) { return self.bottom - sy; }, v_compute(ys) { const { bottom } = self; return new ScreenArray(map(ys, (y) => bottom - y)); }, v_invert(sys) { const { bottom } = self; return map(sys, (sy) => bottom - sy); }, get source_range() { return self.y_range; }, get target_range() { return { start: self.bottom, end: self.top }; }, }); } get xview() { return this.x_view; } get yview() { return this.y_view; } } //# sourceMappingURL=bbox.js.map