UNPKG

@bokeh/bokehjs

Version:

Interactive, novel data visualization

276 lines 9.41 kB
import { isField } from "../../../core/vectorization"; import { dict } from "../../../core/util/object"; import { isArray } from "../../../core/util/types"; import { GlyphRenderer } from "../../renderers/glyph_renderer"; import { PolyTool, PolyToolView } from "./poly_tool"; import { tool_icon_poly_draw } from "../../../styles/icons.css"; export class PolyDrawToolView extends PolyToolView { static __name__ = "PolyDrawToolView"; _drawing = false; _initialized = false; _tap(ev) { if (this._drawing) { this._draw(ev, "add", true); } else { this._select_event(ev, this._select_mode(ev), this.model.renderers); } } _draw(ev, mode, emit = false) { const renderer = this.model.renderers[0]; const point = this._map_drag(ev.sx, ev.sy, renderer); if (!this._initialized) { this.activate(); // Ensure that activate has been called } if (point == null) { return; } const [x, y] = this._snap_to_vertex(ev, ...point); const cds = renderer.data_source; const data = dict(cds.data); const glyph = renderer.glyph; const xkey = isField(glyph.xs) ? glyph.xs.field : null; const ykey = isField(glyph.ys) ? glyph.ys.field : null; if (mode == "new") { this._pop_glyphs(cds, this.model.num_objects); if (xkey != null) { cds.get_array(xkey).push([x, x]); } if (ykey != null) { cds.get_array(ykey).push([y, y]); } this._pad_empty_columns(cds, [xkey, ykey]); } else if (mode == "edit") { if (xkey != null) { const column = data.get(xkey) ?? []; const xs = column[column.length - 1]; xs[xs.length - 1] = x; } if (ykey != null) { const column = data.get(ykey) ?? []; const ys = column[column.length - 1]; ys[ys.length - 1] = y; } } else if (mode == "add") { if (xkey != null) { const column = data.get(xkey) ?? []; const xidx = column.length - 1; let xs = cds.get_array(xkey)[xidx]; const nx = xs[xs.length - 1]; xs[xs.length - 1] = x; if (!isArray(xs)) { xs = Array.from(xs); column[xidx] = xs; } xs.push(nx); } if (ykey != null) { const column = data.get(ykey) ?? []; const yidx = column.length - 1; let ys = cds.get_array(ykey)[yidx]; const ny = ys[ys.length - 1]; ys[ys.length - 1] = y; if (!isArray(ys)) { ys = Array.from(ys); column[yidx] = ys; } ys.push(ny); } } this._emit_cds_changes(cds, true, false, emit); } _show_vertices() { if (!this.model.active) { return; } const xs = []; const ys = []; for (let i = 0; i < this.model.renderers.length; i++) { const renderer = this.model.renderers[i]; const { glyph, data_source } = renderer; const xkey = isField(glyph.xs) ? glyph.xs.field : null; const ykey = isField(glyph.ys) ? glyph.ys.field : null; if (xkey != null) { for (const array of data_source.get_array(xkey)) { xs.push(...array); } } if (ykey != null) { for (const array of data_source.get_array(ykey)) { ys.push(...array); } } if (this._drawing && (i == (this.model.renderers.length - 1))) { // Skip currently drawn vertex xs.splice(xs.length - 1, 1); ys.splice(ys.length - 1, 1); } } this._set_vertices(xs, ys); } _press(ev) { if (!this.model.active) { return; } if (this._drawing) { this._drawing = false; this._draw(ev, "edit", true); } else { this._drawing = true; this._draw(ev, "new", true); } } _move(ev) { if (this._drawing) { this._draw(ev, "edit"); } } _remove() { const renderer = this.model.renderers[0]; const { glyph, data_source } = renderer; const xkey = isField(glyph.xs) ? glyph.xs.field : null; const ykey = isField(glyph.ys) ? glyph.ys.field : null; const data = dict(data_source.data); if (xkey != null) { const column = data.get(xkey) ?? []; const xidx = column.length - 1; const xs = data_source.get_array(xkey)[xidx]; xs.splice(xs.length - 1, 1); } if (ykey != null) { const column = data.get(ykey) ?? []; const yidx = column.length - 1; const ys = data_source.get_array(ykey)[yidx]; ys.splice(ys.length - 1, 1); } this._emit_cds_changes(data_source); } _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") { if (this._drawing) { this._remove(); this._drawing = false; } renderer.data_source.selection_manager.clear(); } } } _pan_start(ev) { if (!this.model.drag) { return; } this._select_event(ev, "append", this.model.renderers); this._basepoint = [ev.sx, ev.sy]; } _pan(ev) { if (this._basepoint == null || !this.model.drag) { return; } const [bx, by] = this._basepoint; // Process polygon/line dragging for (const renderer of this.model.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 cds = renderer.data_source; const { glyph } = renderer; const xkey = isField(glyph.xs) ? glyph.xs.field : null; const ykey = isField(glyph.ys) ? glyph.ys.field : null; if (xkey == null && ykey == null) { continue; } const [x, y] = point; const [px, py] = basepoint; const [dx, dy] = [x - px, y - py]; const data = dict(cds.data); for (const index of cds.selected.indices) { let length, xs, ys; if (xkey != null) { const column = data.get(xkey) ?? []; xs = column[index]; } if (ykey != null) { const column = data.get(ykey) ?? []; ys = column[index]; length = ys.length; } else { length = xs.length; } for (let i = 0; i < length; i++) { if (xs) { xs[i] += dx; } if (ys) { ys[i] += dy; } } } cds.change.emit(); } this._basepoint = [ev.sx, ev.sy]; } _pan_end(ev) { if (!this.model.drag) { return; } this._pan(ev); for (const renderer of this.model.renderers) { this._emit_cds_changes(renderer.data_source); } this._basepoint = null; } activate() { if (this.model.vertex_renderer == null || !this.model.active) { return; } this._show_vertices(); if (!this._initialized) { for (const renderer of this.model.renderers) { const cds = renderer.data_source; cds.connect(cds.properties.data.change, () => this._show_vertices()); } } this._initialized = true; } deactivate() { if (this._drawing) { this._remove(); this._drawing = false; } if (this.model.vertex_renderer != null) { this._hide_vertices(); } } } export class PolyDrawTool extends PolyTool { static __name__ = "PolyDrawTool"; constructor(attrs) { super(attrs); } static { this.prototype.default_view = PolyDrawToolView; this.define(({ Bool, Int, List, Ref }) => ({ drag: [Bool, true], num_objects: [Int, 0], renderers: [List(Ref((GlyphRenderer))), []], })); } tool_name = "Polygon Draw Tool"; tool_icon = tool_icon_poly_draw; event_type = ["pan", "tap", "press", "move"]; default_order = 3; } //# sourceMappingURL=poly_draw_tool.js.map