UNPKG

@bokeh/bokehjs

Version:

Interactive, novel data visualization

297 lines 10.8 kB
import { isField, isValue } from "../../../core/vectorization"; import { assert } from "../../../core/util/assert"; import { isArray } from "../../../core/util/types"; import { dict } from "../../../core/util/object"; import { GlyphRenderer } from "../../renderers/glyph_renderer"; import { PolyTool, PolyToolView } from "./poly_tool"; import { tool_icon_poly_edit } from "../../../styles/icons.css"; export class PolyEditToolView extends PolyToolView { static __name__ = "PolyEditToolView"; _selected_renderer; _drawing = false; _cur_index = null; _press(ev) { if (this.model.vertex_renderer == null || !this.model.active) { return; } const point = this._map_drag(ev.sx, ev.sy, this.model.vertex_renderer); if (point == null) { return; } const [x, y] = point; // Perform hit testing const vertex_selected = this._select_event(ev, "replace", [this.model.vertex_renderer]); const point_cds = this.model.vertex_renderer.data_source; // Type once dataspecs are typed const point_glyph = this.model.vertex_renderer.glyph; const pxkey = isField(point_glyph.x) ? point_glyph.x.field : null; const pykey = isField(point_glyph.y) ? point_glyph.y.field : null; if (vertex_selected.length != 0 && this._selected_renderer != null) { // Insert a new point after the selected vertex and enter draw mode const index = point_cds.selected.indices[0]; if (this._drawing) { this._drawing = false; point_cds.selection_manager.clear(); } else { point_cds.selected.indices = [index + 1]; if (pxkey != null) { point_cds.get_array(pxkey).splice(index + 1, 0, x); } if (pykey != null) { point_cds.get_array(pykey).splice(index + 1, 0, y); } this._drawing = true; } point_cds.change.emit(); this._emit_cds_changes(this._selected_renderer.data_source); } else { this._show_vertices(ev); } } _show_vertices(ev) { if (!this.model.active) { return; } if (this.model.renderers.length == 0) { return; } const vsync_renderer = this.model.renderers[0]; const vsync_updater = () => this._update_vertices(vsync_renderer); const vsync_ds = vsync_renderer.data_source; const renderers = this._select_event(ev, "replace", this.model.renderers); if (renderers.length == 0) { this._set_vertices([], []); this._selected_renderer = null; this._drawing = false; this._cur_index = null; vsync_ds.disconnect(vsync_ds.properties.data.change, vsync_updater); return; } vsync_ds.connect(vsync_ds.properties.data.change, vsync_updater); this._cur_index = renderers[0].data_source.selected.indices[0]; this._update_vertices(renderers[0]); } _update_vertices(renderer) { const { glyph } = renderer; const xkey = isField(glyph.xs) ? glyph.xs.field : null; const ykey = isField(glyph.ys) ? glyph.ys.field : null; const data = dict(renderer.data_source.data); const index = this._cur_index; if (this._drawing) { return; } if (index == null && (xkey != null || ykey != null)) { return; } let xs; let ys; if (xkey != null && index != null) { // redundant xkey null check to satisfy build-time checks const column = data.get(xkey) ?? []; xs = column[index]; if (!isArray(xs)) { column[index] = xs = Array.from(xs); } } else { assert(isValue(glyph.xs)); xs = glyph.xs.value; // TODO this cast is wrong } if (ykey != null && index != null) { const column = data.get(ykey) ?? []; ys = column[index]; if (!isArray(ys)) { column[index] = ys = Array.from(ys); } } else { assert(isValue(glyph.ys)); ys = glyph.ys.value; // TODO this cast is wrong } this._selected_renderer = renderer; this._set_vertices(xs, ys); } _move(ev) { if (this._drawing && this._selected_renderer != null) { const renderer = this.model.vertex_renderer; if (renderer == null) { return; } const cds = renderer.data_source; const data = dict(cds.data); const { glyph } = renderer; const point = this._map_drag(ev.sx, ev.sy, renderer); if (point == null) { return; } let [x, y] = point; const indices = cds.selected.indices; [x, y] = this._snap_to_vertex(ev, x, y); cds.selected.indices = indices; const xkey = isField(glyph.x) ? glyph.x.field : null; const ykey = isField(glyph.y) ? glyph.y.field : null; const index = indices[0]; if (xkey != null) { data.get(xkey)[index] = x; } if (ykey != null) { data.get(ykey)[index] = y; } cds.change.emit(); this._selected_renderer.data_source.change.emit(); } } _tap(ev) { const renderer = this.model.vertex_renderer; if (renderer == null) { return; } const point = this._map_drag(ev.sx, ev.sy, renderer); if (point == null) { return; } else if (this._drawing && this._selected_renderer != null) { let [x, y] = point; const cds = renderer.data_source; // Type once dataspecs are typed const { glyph } = renderer; const xkey = isField(glyph.x) ? glyph.x.field : null; const ykey = isField(glyph.y) ? glyph.y.field : null; const indices = cds.selected.indices; [x, y] = this._snap_to_vertex(ev, x, y); const index = indices[0]; cds.selected.indices = [index + 1]; if (xkey != null) { const xs = cds.get_array(xkey); const nx = xs[index]; xs[index] = x; xs.splice(index + 1, 0, nx); } if (ykey != null) { const ys = cds.get_array(ykey); const ny = ys[index]; ys[index] = y; ys.splice(index + 1, 0, ny); } cds.change.emit(); this._emit_cds_changes(this._selected_renderer.data_source, true, false, true); return; } const mode = this._select_mode(ev); this._select_event(ev, mode, [renderer]); this._select_event(ev, mode, this.model.renderers); } _remove_vertex() { if (!this._drawing || this._selected_renderer == null) { return; } const renderer = this.model.vertex_renderer; if (renderer == null) { return; } const { glyph, data_source } = renderer; const index = data_source.selected.indices[0]; const xkey = isField(glyph.x) ? glyph.x.field : null; const ykey = isField(glyph.y) ? glyph.y.field : null; if (xkey != null) { data_source.get_array(xkey).splice(index, 1); } if (ykey != null) { data_source.get_array(ykey).splice(index, 1); } data_source.change.emit(); this._emit_cds_changes(this._selected_renderer.data_source); } _pan_start(ev) { if (this.model.vertex_renderer == null) { return; } this._select_event(ev, "append", [this.model.vertex_renderer]); this._basepoint = [ev.sx, ev.sy]; } _pan(ev) { if (this._basepoint == null) { return; } if (this.model.vertex_renderer == null) { return; } this._drag_points(ev, [this.model.vertex_renderer]); if (this._selected_renderer != null) { this._selected_renderer.data_source.change.emit(); } } _pan_end(ev) { if (this._basepoint == null) { return; } if (this.model.vertex_renderer == null) { return; } this._drag_points(ev, [this.model.vertex_renderer]); this._emit_cds_changes(this.model.vertex_renderer.data_source, false, true, true); if (this._selected_renderer != null) { this._emit_cds_changes(this._selected_renderer.data_source); } this._basepoint = null; } _keyup(ev) { if (!this.model.active || !this._mouse_in_frame) { return; } let renderers; if (this._selected_renderer != null) { const { vertex_renderer } = this.model; renderers = vertex_renderer != null ? [vertex_renderer] : []; } else { renderers = this.model.renderers; } for (const renderer of renderers) { if (ev.key == "Backspace") { this._delete_selected(renderer); if (this._selected_renderer != null) { this._emit_cds_changes(this._selected_renderer.data_source); } } else if (ev.key == "Escape") { if (this._drawing) { this._remove_vertex(); this._drawing = false; } else if (this._selected_renderer != null) { this._hide_vertices(); } renderer.data_source.selection_manager.clear(); } } } deactivate() { if (this._selected_renderer == null) { return; } else if (this._drawing) { this._remove_vertex(); this._drawing = false; } this._hide_vertices(); } } export class PolyEditTool extends PolyTool { static __name__ = "PolyEditTool"; constructor(attrs) { super(attrs); } static { this.prototype.default_view = PolyEditToolView; this.define(({ List, Ref }) => ({ renderers: [List(Ref((GlyphRenderer))), []], })); } tool_name = "Poly Edit Tool"; tool_icon = tool_icon_poly_edit; event_type = ["tap", "press", "pan", "move"]; default_order = 4; } //# sourceMappingURL=poly_edit_tool.js.map