@bokeh/bokehjs
Version:
Interactive, novel data visualization
820 lines • 33.5 kB
JavaScript
import { Annotation, AnnotationView } from "./annotation";
import { Model } from "../../model";
import { AreaVisuals } from "./area_visuals";
import { auto_ranged } from "../ranges/data_range1d";
import * as mixins from "../../core/property_mixins";
import { CoordinateUnits } from "../../core/enums";
import { min as amin } from "../../core/util/array";
import { BBox, empty } from "../../core/util/bbox";
import { Signal } from "../../core/signaling";
import { isNumber } from "../../core/util/types";
import { clamp } from "../../core/util/math";
import { assert } from "../../core/util/assert";
import { values } from "../../core/util/object";
import { BorderRadius } from "../common/kinds";
import * as Box from "../common/box_kinds";
import { round_rect } from "../common/painting";
import * as resolve from "../common/resolve";
import { Node } from "../coordinates/node";
import { Coordinate } from "../coordinates/coordinate";
import { FactorLike, FactorRange } from "../ranges/factor_range";
import { Or, Float, Ref } from "../../core/kinds";
const CoordinateLike = Or(Float, FactorLike, Ref(Coordinate));
export const EDGE_TOLERANCE = 2.5;
const { abs } = Math;
export class BoxInteractionHandles extends Model {
static __name__ = "BoxInteractionHandles";
constructor(attrs) {
super(attrs);
}
static {
this.define(({ Ref, Nullable }) => ({
all: [Ref(AreaVisuals)],
move: [Nullable(Ref(AreaVisuals)), null],
resize: [Nullable(Ref(AreaVisuals)), null],
sides: [Nullable(Ref(AreaVisuals)), null],
corners: [Nullable(Ref(AreaVisuals)), null],
left: [Nullable(Ref(AreaVisuals)), null],
right: [Nullable(Ref(AreaVisuals)), null],
top: [Nullable(Ref(AreaVisuals)), null],
bottom: [Nullable(Ref(AreaVisuals)), null],
top_left: [Nullable(Ref(AreaVisuals)), null],
top_right: [Nullable(Ref(AreaVisuals)), null],
bottom_left: [Nullable(Ref(AreaVisuals)), null],
bottom_right: [Nullable(Ref(AreaVisuals)), null],
}));
}
}
const DEFAULT_HANDLES = () => {
return new BoxInteractionHandles({
all: new AreaVisuals({
fill_color: "white",
fill_alpha: 1.0,
line_color: "black",
line_alpha: 1.0,
hover_fill_color: "lightgray",
hover_fill_alpha: 1.0,
}),
});
};
export class BoxAnnotationView extends AnnotationView {
static __name__ = "BoxAnnotationView";
_bbox = new BBox();
get bbox() {
return this._bbox;
}
_handles;
_handles_views = {};
initialize() {
super.initialize();
this._update_handles();
}
_update_handles() {
const { editable, use_handles, handles } = this.model;
if (editable && use_handles) {
const { movable, resizable } = this;
const common = {
visible: true,
resizable: "none",
left_units: "canvas",
right_units: "canvas",
top_units: "canvas",
bottom_units: "canvas",
level: this.model.level,
is_handle: true,
};
function attrs_of(source) {
return {
...mixins.attrs_of(source, "", mixins.Line, true),
...mixins.attrs_of(source, "", mixins.Fill, true),
...mixins.attrs_of(source, "", mixins.Hatch, true),
...mixins.attrs_of(source, "hover_", mixins.Line, true),
...mixins.attrs_of(source, "hover_", mixins.Fill, true),
...mixins.attrs_of(source, "hover_", mixins.Hatch, true),
};
}
const h = handles;
const attrs = {
area: attrs_of(h.move ?? h.all),
left: attrs_of(h.left ?? h.sides ?? h.resize ?? h.all),
right: attrs_of(h.right ?? h.sides ?? h.resize ?? h.all),
top: attrs_of(h.top ?? h.sides ?? h.resize ?? h.all),
bottom: attrs_of(h.bottom ?? h.sides ?? h.resize ?? h.all),
top_left: attrs_of(h.top_left ?? h.corners ?? h.resize ?? h.all),
top_right: attrs_of(h.top_right ?? h.corners ?? h.resize ?? h.all),
bottom_left: attrs_of(h.bottom_left ?? h.corners ?? h.resize ?? h.all),
bottom_right: attrs_of(h.bottom_right ?? h.corners ?? h.resize ?? h.all),
};
const { tl_cursor, tr_cursor, bl_cursor, br_cursor, ew_cursor, ns_cursor, } = this.model;
this._handles = {
area: movable ? new BoxAnnotation({ ...common, ...attrs.area, in_cursor: "move", movable: this.model.movable }) : null,
left: resizable.left ? new BoxAnnotation({ ...common, ...attrs.left, in_cursor: ew_cursor }) : null,
right: resizable.right ? new BoxAnnotation({ ...common, ...attrs.right, in_cursor: ew_cursor }) : null,
top: resizable.top ? new BoxAnnotation({ ...common, ...attrs.top, in_cursor: ns_cursor }) : null,
bottom: resizable.bottom ? new BoxAnnotation({ ...common, ...attrs.bottom, in_cursor: ns_cursor }) : null,
top_left: resizable.top_left ? new BoxAnnotation({ ...common, ...attrs.top_left, in_cursor: tl_cursor }) : null,
top_right: resizable.top_right ? new BoxAnnotation({ ...common, ...attrs.top_right, in_cursor: tr_cursor }) : null,
bottom_left: resizable.bottom_left ? new BoxAnnotation({ ...common, ...attrs.bottom_left, in_cursor: bl_cursor }) : null,
bottom_right: resizable.bottom_right ? new BoxAnnotation({ ...common, ...attrs.bottom_right, in_cursor: br_cursor }) : null,
};
}
else {
this._handles = {
area: null,
left: null,
right: null,
top: null,
bottom: null,
top_left: null,
top_right: null,
bottom_left: null,
bottom_right: null,
};
}
}
get computed_renderers() {
return [...super.computed_renderers, ...values(this._handles).filter((handle) => handle != null)];
}
connect_signals() {
super.connect_signals();
const { editable, use_handles, handles, resizable, movable } = this.model.properties;
this.on_change([editable, use_handles, handles, resizable, movable], async () => {
this._update_handles();
await this._update_renderers();
});
this.connect(this.model.change, () => this.request_paint());
}
async _build_renderers() {
const build_result = await super._build_renderers();
const get = (handle) => {
return handle != null ? this._renderer_views.get(handle) : undefined;
};
this._handles_views = {
area: get(this._handles.area),
left: get(this._handles.left),
right: get(this._handles.right),
top: get(this._handles.top),
bottom: get(this._handles.bottom),
top_left: get(this._handles.top_left),
top_right: get(this._handles.top_right),
bottom_left: get(this._handles.bottom_left),
bottom_right: get(this._handles.bottom_right),
};
return build_result;
}
_synthetic_lrtb() {
const { left, left_units, right, right_units, top, top_units, bottom, bottom_units, } = this.model;
const { x_source, y_source } = this.coordinates;
const x_factor = x_source instanceof FactorRange;
const y_factor = y_source instanceof FactorRange;
return {
left: (() => {
if (left_units == "data" && !(left instanceof Coordinate)) {
if (x_factor) {
return x_source.synthetic(left);
}
else if (isNumber(left)) {
return left;
}
}
return NaN;
})(),
right: (() => {
if (right_units == "data" && !(right instanceof Coordinate)) {
if (x_factor) {
return x_source.synthetic(right);
}
else if (isNumber(right)) {
return right;
}
}
return NaN;
})(),
top: (() => {
if (top_units == "data" && !(top instanceof Coordinate)) {
if (y_factor) {
return y_source.synthetic(top);
}
else if (isNumber(top)) {
return top;
}
}
return NaN;
})(),
bottom: (() => {
if (bottom_units == "data" && !(bottom instanceof Coordinate)) {
if (y_factor) {
return y_source.synthetic(bottom);
}
else if (isNumber(bottom)) {
return bottom;
}
}
return NaN;
})(),
};
}
[auto_ranged] = true;
bounds() {
const { left, right, top, bottom } = this._synthetic_lrtb();
const left_ok = isFinite(left);
const right_ok = isFinite(right);
const top_ok = isFinite(top);
const bottom_ok = isFinite(bottom);
const [x0, x1] = (() => {
if (left_ok && right_ok) {
return left <= right ? [left, right] : [right, left];
}
else if (left_ok) {
return [left, left];
}
else if (right_ok) {
return [right, right];
}
else {
return [NaN, NaN];
}
})();
const [y0, y1] = (() => {
if (top_ok && bottom_ok) {
return top <= bottom ? [top, bottom] : [bottom, top];
}
else if (top_ok) {
return [top, top];
}
else if (bottom_ok) {
return [bottom, bottom];
}
else {
return [NaN, NaN];
}
})();
return { x0, x1, y0, y1 };
}
log_bounds() {
return empty();
}
get mappers() {
function mapper(units, scale, view, canvas) {
switch (units) {
case "canvas": return canvas;
case "screen": return view;
case "data": return scale;
}
}
const overlay = this.model;
const { x_scale, y_scale } = this.coordinates;
const { x_view, y_view } = this.plot_view.frame.bbox;
const { x_screen, y_screen } = this.plot_view.canvas.bbox;
const lrtb = {
left: mapper(overlay.left_units, x_scale, x_view, x_screen),
right: mapper(overlay.right_units, x_scale, x_view, x_screen),
top: mapper(overlay.top_units, y_scale, y_view, y_screen),
bottom: mapper(overlay.bottom_units, y_scale, y_view, y_screen),
};
return lrtb;
}
get border_radius() {
return resolve.border_radius(this.model.border_radius);
}
compute_geometry() {
super.compute_geometry();
const bbox = (() => {
const compute = (dim, value, mapper) => {
return value instanceof Coordinate ? this.resolve_as_scalar(value, dim) : mapper.compute(value);
};
const { left, right, top, bottom } = this.model;
const { mappers } = this;
return BBox.from_lrtb({
left: compute("x", left, mappers.left),
right: compute("x", right, mappers.right),
top: compute("y", top, mappers.top),
bottom: compute("y", bottom, mappers.bottom),
});
})();
this._bbox = bbox;
const width = 10;
const height = 10;
function update(renderer, bbox) {
const { left, right, top, bottom } = bbox;
renderer?.setv({ left, right, top, bottom }, { silent: true });
}
update(this._handles.area, new BBox({ ...bbox.center, width, height, origin: "center" }));
update(this._handles.left, new BBox({ ...bbox.center_left, width, height, origin: "center" }));
update(this._handles.right, new BBox({ ...bbox.center_right, width, height, origin: "center" }));
update(this._handles.top, new BBox({ ...bbox.top_center, width, height, origin: "center" }));
update(this._handles.bottom, new BBox({ ...bbox.bottom_center, width, height, origin: "center" }));
update(this._handles.top_left, new BBox({ ...bbox.top_left, width, height, origin: "center" }));
update(this._handles.top_right, new BBox({ ...bbox.top_right, width, height, origin: "center" }));
update(this._handles.bottom_left, new BBox({ ...bbox.bottom_left, width, height, origin: "center" }));
update(this._handles.bottom_right, new BBox({ ...bbox.bottom_right, width, height, origin: "center" }));
}
_paint(ctx) {
if (!this.bbox.is_valid) {
return;
}
const { _is_hovered, visuals } = this;
const fill = _is_hovered && visuals.hover_fill.doit ? visuals.hover_fill : visuals.fill;
const hatch = _is_hovered && visuals.hover_hatch.doit ? visuals.hover_hatch : visuals.hatch;
const line = _is_hovered && visuals.hover_line.doit ? visuals.hover_line : visuals.line;
ctx.save();
if (!this.model.inverted) {
ctx.beginPath();
round_rect(ctx, this.bbox, this.border_radius);
fill.apply(ctx);
hatch.apply(ctx);
line.apply(ctx);
}
else {
ctx.beginPath();
const parent = this.layout ?? this.plot_view.frame;
const { x, y, width, height } = parent.bbox;
ctx.rect(x, y, width, height);
round_rect(ctx, this.bbox, this.border_radius);
fill.apply(ctx, "evenodd");
hatch.apply(ctx, "evenodd");
ctx.beginPath();
round_rect(ctx, this.bbox, this.border_radius);
line.apply(ctx);
}
ctx.restore();
}
interactive_bbox() {
const tolerance = this.model.line_width + EDGE_TOLERANCE;
return this.bbox.grow_by(tolerance);
}
interactive_hit(sx, sy) {
if (!this.model.visible) {
return false;
}
const bbox = this.interactive_bbox();
return bbox.contains(sx, sy);
}
_hit_test(sx, sy) {
const { left, right, bottom, top } = this.bbox;
const tolerance = Math.max(EDGE_TOLERANCE, this.model.line_width / 2);
const dl = abs(left - sx);
const dr = abs(right - sx);
const dt = abs(top - sy);
const db = abs(bottom - sy);
const hits = {
left: dl < tolerance && dl < dr,
right: dr < tolerance && dr < dl,
top: dt < tolerance && dt < db,
bottom: db < tolerance && db < dt,
};
const hittable = this._hittable();
const hits_handle = (hit_target, condition) => {
if (!hittable[hit_target]) {
return false;
}
const handle = this._handles_views[hit_target];
if (handle != null) {
return handle.bbox.contains(sx, sy);
}
else {
return condition;
}
};
if (hits_handle("top_left", hits.top && hits.left)) {
return "top_left";
}
if (hits_handle("top_right", hits.top && hits.right)) {
return "top_right";
}
if (hits_handle("bottom_left", hits.bottom && hits.left)) {
return "bottom_left";
}
if (hits_handle("bottom_right", hits.bottom && hits.right)) {
return "bottom_right";
}
if (hits_handle("left", hits.left)) {
return "left";
}
if (hits_handle("right", hits.right)) {
return "right";
}
if (hits_handle("top", hits.top)) {
return "top";
}
if (hits_handle("bottom", hits.bottom)) {
return "bottom";
}
if (hits_handle("area", this.bbox.contains(sx, sy))) {
return "area";
}
return null;
}
get resizable() {
const { resizable } = this.model;
const left = resizable == "left" || resizable == "x" || resizable == "all";
const right = resizable == "right" || resizable == "x" || resizable == "all";
const top = resizable == "top" || resizable == "y" || resizable == "all";
const bottom = resizable == "bottom" || resizable == "y" || resizable == "all";
return {
left,
right,
top,
bottom,
top_left: top && left,
top_right: top && right,
bottom_left: bottom && left,
bottom_right: bottom && right,
};
}
get movable() {
const movable = this.model.movable != "none";
return this.model.is_handle ? movable : this.model.editable && movable;
}
_hittable() {
const { left, right, top, bottom } = this.resizable;
return {
top_left: top && left,
top_right: top && right,
bottom_left: bottom && left,
bottom_right: bottom && right,
left,
right,
top,
bottom,
area: this.movable,
};
}
_can_hit(target) {
if (!this.model.editable && !this.model.is_handle) {
return false;
}
const { left, right, top, bottom } = this.resizable;
switch (target) {
case "top_left": return top && left;
case "top_right": return top && right;
case "bottom_left": return bottom && left;
case "bottom_right": return bottom && right;
case "left": return left;
case "right": return right;
case "top": return top;
case "bottom": return bottom;
case "area": return this.movable;
}
}
_pan_state = null;
on_pan_start(ev) {
if (this.model.visible && this.model.editable) {
const { sx, sy } = ev;
const target = this._hit_test(sx, sy);
if (target != null && this._can_hit(target)) {
this._pan_state = {
bbox: this.bbox.clone(),
target,
};
this.model.pan.emit(["pan:start", ev.modifiers]);
return true;
}
}
return false;
}
on_pan(ev) {
assert(this._pan_state != null);
const { mappers } = this;
const resolve = (dim, limit, mapper) => {
if (limit instanceof Coordinate) {
return this.resolve_as_scalar(limit, dim);
}
else if (limit == null) {
return NaN;
}
else {
return mapper.compute(limit);
}
};
const slimits = BBox.from_lrtb({
left: resolve("x", this.model.left_limit, mappers.left),
right: resolve("x", this.model.right_limit, mappers.right),
top: resolve("y", this.model.top_limit, mappers.top),
bottom: resolve("y", this.model.bottom_limit, mappers.bottom),
});
const [dl, dr, dt, db] = (() => {
const { dx, dy } = ev;
const { target } = this._pan_state;
const { symmetric } = this.model;
const [Dx, Dy] = symmetric ? [-dx, -dy] : [0, 0];
switch (target) {
// corners
case "top_left": return [dx, Dx, dy, Dy];
case "top_right": return [Dx, dx, dy, Dy];
case "bottom_left": return [dx, Dx, Dy, dy];
case "bottom_right": return [Dx, dx, Dy, dy];
// edges
case "left": return [dx, Dx, 0, 0];
case "right": return [Dx, dx, 0, 0];
case "top": return [0, 0, dy, Dy];
case "bottom": return [0, 0, Dy, dy];
// area
case "area": {
switch (this.model.movable) {
case "both": return [dx, dx, dy, dy];
case "x": return [dx, dx, 0, 0];
case "y": return [0, 0, dy, dy];
case "none": return [0, 0, 0, 0];
}
}
}
})();
const slrtb = (() => {
const min = (a, b) => amin([a, b]);
const sgn = (v) => v < 0 ? -1 : (v > 0 ? 1 : 0);
const { bbox } = this._pan_state;
let { left, right, left_sign, right_sign } = (() => {
const left = bbox.left + dl;
const right = bbox.right + dr;
const left_sign = sgn(dl);
const right_sign = sgn(dr);
if (left <= right) {
return { left, right, left_sign, right_sign };
}
else {
return { left: right, right: left, left_sign: right_sign, right_sign: left_sign };
}
})();
let { top, bottom, top_sign, bottom_sign } = (() => {
const top = bbox.top + dt;
const bottom = bbox.bottom + db;
const top_sign = sgn(dt);
const bottom_sign = sgn(db);
if (top <= bottom) {
return { top, bottom, top_sign, bottom_sign };
}
else {
return { top: bottom, bottom: top, top_sign: bottom_sign, bottom_sign: top_sign };
}
})();
const Dl = left - slimits.left;
const Dr = slimits.right - right;
const Dx = min(Dl < 0 ? Dl : NaN, Dr < 0 ? Dr : NaN);
if (isFinite(Dx) && Dx < 0) {
left += -left_sign * (-Dx);
right += -right_sign * (-Dx);
}
const Dt = top - slimits.top;
const Db = slimits.bottom - bottom;
const Dy = min(Dt < 0 ? Dt : NaN, Db < 0 ? Db : NaN);
if (isFinite(Dy) && Dy < 0) {
top += -top_sign * (-Dy);
bottom += -bottom_sign * (-Dy);
}
return BBox.from_lrtb({ left, right, top, bottom });
})();
const { min_width, min_height, max_width, max_height } = this.model;
const { left, right, top, bottom } = this.model;
const lrtb = {
left: mappers.left.invert(slrtb.left),
right: mappers.right.invert(slrtb.right),
top: mappers.top.invert(slrtb.top),
bottom: mappers.bottom.invert(slrtb.bottom),
};
if (0 < min_width || max_width < Infinity) {
if (dl != 0 && dr == 0) {
const min_left = lrtb.right - max_width;
const max_left = lrtb.right - min_width;
lrtb.left = clamp(lrtb.left, min_left, max_left);
}
else if (dl == 0 && dr != 0) {
const min_right = lrtb.left + min_width;
const max_right = lrtb.left + max_width;
lrtb.right = clamp(lrtb.right, min_right, max_right);
}
}
if (0 < min_height || max_height < Infinity) {
if (dt != 0 && db == 0) {
const min_top = lrtb.bottom + max_height;
const max_top = lrtb.bottom + min_height;
lrtb.top = clamp(lrtb.top, min_top, max_top);
}
else if (dt == 0 && db != 0) {
const min_bottom = lrtb.top - min_height;
const max_bottom = lrtb.top - max_height;
lrtb.bottom = clamp(lrtb.bottom, min_bottom, max_bottom);
}
}
const computed_lrtb = (() => {
return {
left: left instanceof Coordinate ? left : lrtb.left,
right: right instanceof Coordinate ? right : lrtb.right,
top: top instanceof Coordinate ? top : lrtb.top,
bottom: bottom instanceof Coordinate ? bottom : lrtb.bottom,
};
})();
this.model.update(computed_lrtb);
this.model.pan.emit(["pan", ev.modifiers]);
}
on_pan_end(ev) {
this._pan_state = null;
this.model.pan.emit(["pan:end", ev.modifiers]);
}
_pinch_state = null;
on_pinch_start(ev) {
if (this.model.visible && this.model.editable && this.model.resizable != "none") {
const { sx, sy } = ev;
if (this.bbox.contains(sx, sy)) {
this._pinch_state = {
bbox: this.bbox.clone(),
};
this.model.pan.emit(["pan:start", ev.modifiers]); // TODO: pinch signal
return true;
}
}
return false;
}
on_pinch(ev) {
assert(this._pinch_state != null);
const slrtb = (() => {
const { scale } = ev;
const { bbox } = this._pinch_state;
const { left, top, right, bottom, width, height } = bbox;
const dw = width * (scale - 1);
const dh = height * (scale - 1);
const { resizable } = this;
const dl = resizable.left ? -dw / 2 : 0;
const dr = resizable.right ? dw / 2 : 0;
const dt = resizable.top ? -dh / 2 : 0;
const db = resizable.bottom ? dh / 2 : 0;
return BBox.from_lrtb({
left: left + dl,
right: right + dr,
top: top + dt,
bottom: bottom + db,
});
})();
const lrtb = (() => {
const { left, right, top, bottom } = this.model;
const { mappers } = this;
return {
left: left instanceof Coordinate ? left : mappers.left.invert(slrtb.left),
right: right instanceof Coordinate ? right : mappers.right.invert(slrtb.right),
top: top instanceof Coordinate ? top : mappers.top.invert(slrtb.top),
bottom: bottom instanceof Coordinate ? bottom : mappers.bottom.invert(slrtb.bottom),
};
})();
this.model.update(lrtb);
this.model.pan.emit(["pan", ev.modifiers]);
}
on_pinch_end(ev) {
this._pinch_state = null;
this.model.pan.emit(["pan:end", ev.modifiers]);
}
get _has_hover() {
const { hover_line, hover_fill, hover_hatch } = this.visuals;
return hover_line.doit || hover_fill.doit || hover_hatch.doit;
}
_is_hovered = false;
on_enter(_ev) {
const { _has_hover } = this;
if (_has_hover) {
this._is_hovered = true;
this.request_paint();
}
return _has_hover;
}
on_move(_ev) { }
on_leave(_ev) {
if (this._has_hover) {
this._is_hovered = false;
this.request_paint();
}
}
cursor(sx, sy) {
const target = this._pan_state?.target ?? this._hit_test(sx, sy);
if (target == null || !this._can_hit(target)) {
return null;
}
const { tl_cursor, tr_cursor, bl_cursor, br_cursor, ew_cursor, ns_cursor, in_cursor, } = this.model;
switch (target) {
case "top_left": return this._handles.top_left == null ? tl_cursor : this._handles.top_left.tl_cursor;
case "top_right": return this._handles.top_right == null ? tr_cursor : this._handles.top_right.tr_cursor;
case "bottom_left": return this._handles.bottom_left == null ? bl_cursor : this._handles.bottom_left.bl_cursor;
case "bottom_right": return this._handles.bottom_right == null ? br_cursor : this._handles.bottom_right.br_cursor;
case "left": return this._handles.left == null ? ew_cursor : this._handles.left.ew_cursor;
case "right": return this._handles.right == null ? ew_cursor : this._handles.right.ew_cursor;
case "top": return this._handles.top == null ? ns_cursor : this._handles.top.ns_cursor;
case "bottom": return this._handles.bottom == null ? ns_cursor : this._handles.bottom.ns_cursor;
case "area": {
if (!this.movable) {
return null;
}
else if (this._handles.area == null) {
if (this._pan_state != null && in_cursor === "grab") {
return "grabbing";
}
else {
return in_cursor;
}
}
else {
return this._handles.area.in_cursor;
}
}
}
}
}
export class BoxAnnotation extends Annotation {
static __name__ = "BoxAnnotation";
constructor(attrs) {
super(attrs);
}
clone(attrs) {
return super.clone(attrs);
}
static {
this.prototype.default_view = BoxAnnotationView;
this.mixins([
mixins.Line,
mixins.Fill,
mixins.Hatch,
["hover_", mixins.Line],
["hover_", mixins.Fill],
["hover_", mixins.Hatch],
]);
this.define(({ Bool, Float, Ref, NonNegative, Positive }) => ({
top: [CoordinateLike, () => new Node({ target: "frame", symbol: "top" })],
bottom: [CoordinateLike, () => new Node({ target: "frame", symbol: "bottom" })],
left: [CoordinateLike, () => new Node({ target: "frame", symbol: "left" })],
right: [CoordinateLike, () => new Node({ target: "frame", symbol: "right" })],
top_units: [CoordinateUnits, "data"],
bottom_units: [CoordinateUnits, "data"],
left_units: [CoordinateUnits, "data"],
right_units: [CoordinateUnits, "data"],
top_limit: [Box.Limit, null],
bottom_limit: [Box.Limit, null],
left_limit: [Box.Limit, null],
right_limit: [Box.Limit, null],
min_width: [NonNegative(Float), 0],
min_height: [NonNegative(Float), 0],
max_width: [Positive(Float), Infinity],
max_height: [Positive(Float), Infinity],
border_radius: [BorderRadius, 0],
editable: [Bool, false],
resizable: [Box.Resizable, "all"],
movable: [Box.Movable, "both"],
symmetric: [Bool, false],
use_handles: [Bool, false],
handles: [Ref(BoxInteractionHandles), DEFAULT_HANDLES],
inverted: [Bool, false],
}));
this.internal(({ Str, Bool }) => ({
tl_cursor: [Str, "nwse-resize"],
tr_cursor: [Str, "nesw-resize"],
bl_cursor: [Str, "nesw-resize"],
br_cursor: [Str, "nwse-resize"],
ew_cursor: [Str, "ew-resize"],
ns_cursor: [Str, "ns-resize"],
in_cursor: [Str, "grab"],
// Is this BoxAnnotation functioning as a handle for another BoxAnnotation?
is_handle: [Bool, false],
}));
this.override({
fill_color: "#fff9ba",
fill_alpha: 0.4,
line_color: "#cccccc",
line_alpha: 0.3,
hover_fill_color: null,
hover_fill_alpha: 0.4,
hover_line_color: null,
hover_line_alpha: 0.3,
});
}
pan = new Signal(this, "pan");
update({ left, right, top, bottom }) {
this.setv({ left, right, top, bottom, visible: true });
}
clear() {
this.visible = false;
}
nodes = (() => {
const known = new Map();
const node = (symbol) => {
let node = known.get(symbol);
if (node === undefined) {
known.set(symbol, node = new Node({ target: this, symbol }));
}
return node;
};
return {
get left() { return node("left"); },
get right() { return node("right"); },
get top() { return node("top"); },
get bottom() { return node("bottom"); },
get top_left() { return node("top_left"); },
get top_center() { return node("top_center"); },
get top_right() { return node("top_right"); },
get center_left() { return node("center_left"); },
get center() { return node("center"); },
get center_right() { return node("center_right"); },
get bottom_left() { return node("bottom_left"); },
get bottom_center() { return node("bottom_center"); },
get bottom_right() { return node("bottom_right"); },
get width() { return node("width"); },
get height() { return node("height"); },
};
})();
}
//# sourceMappingURL=box_annotation.js.map