UNPKG

@bokeh/bokehjs

Version:

Interactive, novel data visualization

218 lines 7.67 kB
import { RegionSelectTool, RegionSelectToolView } from "./region_select_tool"; import { BoxAnnotation } from "../../annotations/box_annotation"; import { Coordinate } from "../../coordinates/coordinate"; import { Dimensions, BoxOrigin } from "../../../core/enums"; import * as icons from "../../../styles/icons.css"; export class BoxSelectToolView extends RegionSelectToolView { static __name__ = "BoxSelectToolView"; connect_signals() { super.connect_signals(); const { pan } = this.model.overlay; this.connect(pan, ([phase, ev]) => { if ((phase == "pan" && this._is_continuous(ev)) || phase == "pan:end") { const { left, top, right, bottom } = this.model.overlay; if (!(left instanceof Coordinate) && !(top instanceof Coordinate) && !(right instanceof Coordinate) && !(bottom instanceof Coordinate)) { const screen = this._compute_lrtb({ left, right, top, bottom }); this._do_select([screen.left, screen.right], [screen.top, screen.bottom], false, this._select_mode(ev)); } } }); const { active } = this.model.properties; this.on_change(active, () => { if (!this.model.active && !this.model.persistent) { this._clear_overlay(); } }); } _base_point; _compute_limits(curpoint) { const frame = this.plot_view.frame; const dims = this.model.dimensions; let base_point = this._base_point; if (this.model.origin == "center") { const [cx, cy] = base_point; const [dx, dy] = curpoint; base_point = [cx - (dx - cx), cy - (dy - cy)]; } return this.model._get_dim_limits(base_point, curpoint, frame, dims); } _mappers() { const mapper = (units, scale, view, canvas) => { switch (units) { case "canvas": return canvas; case "screen": return view; case "data": return scale; } }; const { overlay } = this.model; const { frame, canvas } = this.plot_view; const { x_scale, y_scale } = frame; const { x_view, y_view } = frame.bbox; const { x_screen, y_screen } = canvas.bbox; return { 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), }; } _compute_lrtb({ left, right, top, bottom }) { const lrtb = this._mappers(); return { left: lrtb.left.compute(left), right: lrtb.right.compute(right), top: lrtb.top.compute(top), bottom: lrtb.bottom.compute(bottom), }; } _invert_lrtb({ left, right, top, bottom }) { const lrtb = this._mappers(); return { left: lrtb.left.invert(left), right: lrtb.right.invert(right), top: lrtb.top.invert(top), bottom: lrtb.bottom.invert(bottom), }; } _pan_start(ev) { const { sx, sy } = ev; const { frame } = this.plot_view; if (!frame.bbox.contains(sx, sy)) { return; } this._clear_other_overlays(); this._base_point = [sx, sy]; } _pan(ev) { if (this._base_point == null) { return; } const { sx, sy } = ev; const [sxlim, sylim] = this._compute_limits([sx, sy]); const [[left, right], [top, bottom]] = [sxlim, sylim]; this.model.overlay.update(this._invert_lrtb({ left, right, top, bottom })); if (this._is_continuous(ev.modifiers)) { this._do_select(sxlim, sylim, false, this._select_mode(ev.modifiers)); } } _pan_end(ev) { if (this._base_point == null) { return; } const { sx, sy } = ev; const [sxlim, sylim] = this._compute_limits([sx, sy]); this._do_select(sxlim, sylim, true, this._select_mode(ev.modifiers)); if (!this.model.persistent) { this._clear_overlay(); } this._base_point = null; this.plot_view.state.push("box_select", { selection: this.plot_view.get_selection() }); } get _is_selecting() { return this._base_point != null; } _stop() { this._clear_overlay(); this._base_point = null; } _keyup(ev) { if (!this.model.active) { return; } if (ev.key == "Escape") { if (this._is_selecting) { this._stop(); return; } if (this.model.overlay.visible) { this._clear_overlay(); return; } } super._keyup(ev); } _clear_selection() { if (this.model.overlay.visible) { this._clear_overlay(); } else { super._clear_selection(); } } _do_select([sx0, sx1], [sy0, sy1], final, mode = "replace") { const { greedy } = this.model; const geometry = { type: "rect", sx0, sx1, sy0, sy1, greedy }; this._select(geometry, final, mode); } } const DEFAULT_BOX_OVERLAY = () => { return new BoxAnnotation({ syncable: false, level: "overlay", visible: false, editable: true, left: NaN, right: NaN, top: NaN, bottom: NaN, top_units: "data", left_units: "data", bottom_units: "data", right_units: "data", fill_color: "lightgrey", fill_alpha: 0.5, line_color: "black", line_alpha: 1.0, line_width: 2, line_dash: [4, 4], }); }; export class BoxSelectTool extends RegionSelectTool { static __name__ = "BoxSelectTool"; constructor(attrs) { super(attrs); } static { this.prototype.default_view = BoxSelectToolView; this.define(({ Ref }) => ({ dimensions: [Dimensions, "both"], overlay: [Ref(BoxAnnotation), DEFAULT_BOX_OVERLAY], origin: [BoxOrigin, "corner"], })); this.register_alias("box_select", () => new BoxSelectTool()); this.register_alias("xbox_select", () => new BoxSelectTool({ dimensions: "width" })); this.register_alias("ybox_select", () => new BoxSelectTool({ dimensions: "height" })); } initialize() { super.initialize(); const [resizable, movable] = (() => { switch (this.dimensions) { case "width": return ["x", "x"]; case "height": return ["y", "y"]; case "both": return ["all", "both"]; } })(); const symmetric = this.origin == "center"; this.overlay.setv({ resizable, movable, symmetric }); } tool_name = "Box Select"; event_type = "pan"; default_order = 30; get computed_icon() { const icon = super.computed_icon; if (icon != null) { return icon; } else { switch (this.dimensions) { case "both": return `.${icons.tool_icon_box_select}`; case "width": return `.${icons.tool_icon_x_box_select}`; case "height": return `.${icons.tool_icon_y_box_select}`; } } } get tooltip() { return this._get_dim_tooltip(this.dimensions); } } //# sourceMappingURL=box_select_tool.js.map