@bokeh/bokehjs
Version:
Interactive, novel data visualization
336 lines • 12.2 kB
JavaScript
import { Dimensions } from "../../../core/enums";
import { isField } from "../../../core/vectorization";
import { XYGlyph } from "../../glyphs/xy_glyph";
import { Rect } from "../../glyphs/rect";
import { Block } from "../../glyphs/block";
import { Quad } from "../../glyphs/quad";
import { HBar } from "../../glyphs/hbar";
import { VBar } from "../../glyphs/vbar";
import { HStrip } from "../../glyphs/hstrip";
import { VStrip } from "../../glyphs/vstrip";
import { GlyphRenderer } from "../../renderers/glyph_renderer";
import { EditTool, EditToolView } from "./edit_tool";
import { tool_icon_box_edit } from "../../../styles/icons.css";
import { unreachable } from "../../../core/util/assert";
import { entries, keys, dict } from "../../../core/util/object";
export class BoxEditToolView extends EditToolView {
static __name__ = "BoxEditToolView";
_draw_basepoint;
_recent_renderers = [];
_tap(ev) {
if ((this._draw_basepoint != null) || (this._basepoint != null)) {
return;
}
this._recent_renderers = this._select_event(ev, this._select_mode(ev), this.model.renderers);
}
_keyup(ev) {
if (!this.model.active || !this._mouse_in_frame) {
return;
}
for (const renderer of this.model.renderers) {
if (ev.key == "Backspace") {
this._delete_selected(renderer);
}
else if (ev.key == "Escape") {
// Type properly once selection_manager is typed
const cds = renderer.data_source;
cds.selection_manager.clear();
}
}
}
_set_extent([sx0, sx1], [sy0, sy1], append, emit = false) {
const renderer = this._recent_renderers[0] ?? this.model.renderers[0];
const renderer_view = this.plot_view.views.find_one(renderer);
if (renderer_view == null) {
return;
}
const { glyph } = renderer;
const cds = renderer.data_source;
const data = dict(cds.data);
const [dx0, dx1] = renderer_view.coordinates.x_scale.r_invert(sx0, sx1);
const [dy0, dy1] = renderer_view.coordinates.y_scale.r_invert(sy0, sy1);
const fields = (() => {
if (glyph instanceof Rect) {
const { x, y, width, height } = glyph;
if (isField(x) && isField(y) && isField(width) && isField(height)) {
return {
[x.field]: (dx0 + dx1) / 2,
[y.field]: (dy0 + dy1) / 2,
[width.field]: dx1 - dx0,
[height.field]: dy1 - dy0,
};
}
}
else if (glyph instanceof Block) {
const { x, y, width, height } = glyph;
if (isField(x) && isField(y) && isField(width) && isField(height)) {
return {
[x.field]: dx0,
[y.field]: dy0,
[width.field]: dx1 - dx0,
[height.field]: dy1 - dy0,
};
}
}
else if (glyph instanceof Quad) {
const { right, bottom, left, top } = glyph;
if (isField(right) && isField(bottom) && isField(left) && isField(top)) {
return {
[right.field]: dx1,
[bottom.field]: dy0,
[left.field]: dx0,
[top.field]: dy1,
};
}
}
else if (glyph instanceof HBar) {
const { left, y, height, right } = glyph;
if (isField(left) && isField(y) && isField(height) && isField(right)) {
return {
[left.field]: dx0,
[y.field]: (dy0 + dy1) / 2.0,
[height.field]: dy1 - dy0,
[right.field]: dx1,
};
}
}
else if (glyph instanceof VBar) {
const { x, bottom, width, top } = glyph;
if (isField(x) && isField(bottom) && isField(width) && isField(top)) {
return {
[x.field]: (dx0 + dx1) / 2.0,
[bottom.field]: dy0,
[width.field]: dx1 - dx0,
[top.field]: dy1,
};
}
}
else if (glyph instanceof HStrip) {
const { y0, y1 } = glyph;
if (isField(y0) && isField(y1)) {
return {
[y0.field]: dy0,
[y1.field]: dy1,
};
}
}
else if (glyph instanceof VStrip) {
const { x0, x1 } = glyph;
if (isField(x0) && isField(x1)) {
return {
[x0.field]: dx0,
[x1.field]: dx1,
};
}
}
else {
unreachable(`'${glyph.type}' is not supported"`);
}
return null;
})();
if (fields == null) {
return;
}
if (append) {
this._pop_glyphs(cds, this.model.num_objects);
for (const [key, val] of entries(fields)) {
cds.get_array(key).push(val);
}
this._pad_empty_columns(cds, keys(fields));
}
else {
const length = cds.get_length();
if (length == null) {
return;
}
const index = length - 1;
for (const [key, val] of entries(fields)) {
data.get(key)[index] = val;
}
}
this._emit_cds_changes(cds, true, false, emit);
}
_update_box(ev, append = false, emit = false) {
if (this._draw_basepoint == null) {
return;
}
const curpoint = [ev.sx, ev.sy];
const frame = this.plot_view.frame;
const dims = this.model.dimensions;
const [sxlim, sylim] = this.model._get_dim_limits(this._draw_basepoint, curpoint, frame, dims);
this._set_extent(sxlim, sylim, append, emit);
}
_press(ev) {
if (!this.model.active) {
return;
}
if (this._draw_basepoint != null) {
this._update_box(ev, false, true);
this._draw_basepoint = null;
}
else {
this._draw_basepoint = [ev.sx, ev.sy];
this._select_event(ev, "append", this.model.renderers);
this._update_box(ev, true, false);
}
}
_move(ev) {
this._update_box(ev, false, false);
}
_pan_start(ev) {
if (ev.modifiers.shift) {
if (this._draw_basepoint != null) {
return;
}
this._draw_basepoint = [ev.sx, ev.sy];
this._update_box(ev, true, false);
}
else {
if (this._basepoint != null) {
return;
}
this._recent_renderers = this._select_event(ev, "append", this.model.renderers);
this._basepoint = [ev.sx, ev.sy];
}
}
_pan(ev, append = false, emit = false) {
if (ev.modifiers.shift) {
if (this._draw_basepoint == null) {
return;
}
this._update_box(ev, append, emit);
}
else {
if (this._basepoint == null) {
return;
}
this._drag_points(ev, this.model.renderers);
}
}
_drag_points(ev, renderers, dim = "both") {
if (this._basepoint == null) {
return;
}
const [bx, by] = this._basepoint;
for (const renderer of renderers) {
const basepoint = this._map_drag(bx, by, renderer);
const point = this._map_drag(ev.sx, ev.sy, renderer);
if (point == null || basepoint == null) {
continue;
}
const [x, y] = point;
const [px, py] = basepoint;
const dx = dim == "width" || dim == "both" ? x - px : 0;
const dy = dim == "height" || dim == "both" ? y - py : 0;
const { glyph } = renderer;
const cds = renderer.data_source;
const data = dict(cds.data);
const fields = {};
if (glyph instanceof XYGlyph) {
const { x, y } = glyph;
if (isField(x)) {
fields[x.field] = dx;
}
if (isField(y)) {
fields[y.field] = dy;
}
}
else if (glyph instanceof Block) {
const { x, y } = glyph;
if (isField(x)) {
fields[x.field] = dx;
}
if (isField(y)) {
fields[y.field] = dy;
}
}
else if (glyph instanceof Quad) {
const { right, bottom, left, top } = glyph;
if (isField(left) && isField(right)) {
fields[left.field] = dx;
fields[right.field] = dx;
}
if (isField(top) && isField(bottom)) {
fields[top.field] = dy;
fields[bottom.field] = dy;
}
}
else if (glyph instanceof HBar) {
const { left, right, y } = glyph;
if (isField(left) && isField(right)) {
fields[left.field] = dx;
fields[right.field] = dx;
}
if (isField(y)) {
fields[y.field] = dy;
}
}
else if (glyph instanceof VBar) {
const { x, top, bottom } = glyph;
if (isField(x)) {
fields[x.field] = dx;
}
if (isField(top) && isField(bottom)) {
fields[top.field] = dy;
fields[bottom.field] = dy;
}
}
else if (glyph instanceof HStrip) {
const { y0, y1 } = glyph;
if (isField(y0) && isField(y1)) {
fields[y0.field] = dy;
fields[y1.field] = dy;
}
}
else if (glyph instanceof VStrip) {
const { x0, x1 } = glyph;
if (isField(x0) && isField(x1)) {
fields[x0.field] = dx;
fields[x1.field] = dx;
}
}
else {
unreachable(`'${glyph.type}' is not supported"`);
}
for (const index of cds.selected.indices) {
for (const [key, val] of entries(fields)) {
const column = (data.get(key) ?? []);
column[index] += val;
}
}
cds.change.emit();
}
this._basepoint = [ev.sx, ev.sy];
}
_pan_end(ev) {
this._pan(ev, false, true);
if (ev.modifiers.shift) {
this._draw_basepoint = null;
}
else {
this._basepoint = null;
for (const renderer of this.model.renderers) {
this._emit_cds_changes(renderer.data_source, false, true, true);
}
}
}
}
export class BoxEditTool extends EditTool {
static __name__ = "BoxEditTool";
constructor(attrs) {
super(attrs);
}
static {
this.prototype.default_view = BoxEditToolView;
this.define(({ Int, List, Ref }) => ({
dimensions: [Dimensions, "both"],
num_objects: [Int, 0],
renderers: [List(Ref(GlyphRenderer)), []],
}));
}
tool_name = "Box Edit Tool";
tool_icon = tool_icon_box_edit;
event_type = ["tap", "press", "pan", "move"];
default_order = 1;
}
//# sourceMappingURL=box_edit_tool.js.map