UNPKG

@bokeh/bokehjs

Version:

Interactive, novel data visualization

190 lines 6.74 kB
import { isField } from "../../../core/vectorization"; import { includes } from "../../../core/util/array"; import { dict } from "../../../core/util/object"; import { isArray } from "../../../core/util/types"; import { unreachable } from "../../../core/util/assert"; import { GestureTool, GestureToolView } from "../gestures/gesture_tool"; export class EditToolView extends GestureToolView { static __name__ = "EditToolView"; _basepoint; _mouse_in_frame = true; _select_mode(ev) { const { shift, ctrl } = ev.modifiers; if (!shift && !ctrl) { return "replace"; } else if (shift && !ctrl) { return "append"; } else if (!shift && ctrl) { return "intersect"; } else if (shift && ctrl) { return "subtract"; } else { unreachable(); } } _move_enter(_e) { this._mouse_in_frame = true; } _move_exit(_e) { this._mouse_in_frame = false; } _map_drag(sx, sy, renderer) { // Maps screen to data coordinates const frame = this.plot_view.frame; if (!frame.bbox.contains(sx, sy)) { return null; } const renderer_view = this.plot_view.views.find_one(renderer); if (renderer_view == null) { return null; } const x = renderer_view.coordinates.x_scale.invert(sx); const y = renderer_view.coordinates.y_scale.invert(sy); return [x, y]; } _delete_selected(renderer) { // Deletes all selected rows in the ColumnDataSource const cds = renderer.data_source; const indices = cds.selected.indices; indices.sort(); for (const column of cds.columns()) { const values = cds.get_array(column); for (let index = 0; index < indices.length; index++) { const ind = indices[index]; values.splice(ind - index, 1); } } this._emit_cds_changes(cds); } _pop_glyphs(cds, num_objects) { // Pops rows in the CDS until only num_objects are left const columns = cds.columns(); if (num_objects == 0 || columns.length == 0) { return; } const data = dict(cds.data); for (const column of columns) { let array = cds.get_array(column); const drop = array.length - num_objects + 1; if (drop < 1) { continue; } if (!isArray(array)) { array = Array.from(array); data.set(column, array); } array.splice(0, drop); } } _emit_cds_changes(cds, redraw = true, clear = true, emit = true) { if (clear) { cds.selection_manager.clear(); } if (redraw) { cds.change.emit(); } if (emit) { const { data } = cds; cds.setv({ data }, { check_eq: false }); } } _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, dy] = [x - px, y - py]; // Type once dataspecs are typed const { glyph } = renderer; const cds = renderer.data_source; const data = dict(cds.data); const xkey = isField(glyph.x) ? glyph.x.field : null; const ykey = isField(glyph.y) ? glyph.y.field : null; for (const index of cds.selected.indices) { if (xkey != null && (dim == "width" || dim == "both")) { const column = (data.get(xkey) ?? []); column[index] += dx; } if (ykey != null && (dim == "height" || dim == "both")) { const column = (data.get(ykey) ?? []); column[index] += dy; } } cds.change.emit(); } this._basepoint = [ev.sx, ev.sy]; } _pad_empty_columns(cds, coord_columns) { // Pad ColumnDataSource non-coordinate columns with default values const { inferred_defaults } = cds; const default_values = dict(cds.default_values); const default_overrides = dict(this.model.default_overrides); for (const column of cds.columns()) { if (!includes(coord_columns, column)) { const default_value = (() => { if (default_overrides.has(column)) { return default_overrides.get(column); } else if (default_values.has(column)) { return default_values.get(column); } else if (inferred_defaults.has(column)) { return inferred_defaults.get(column); } else { return this.model.empty_value; } })(); cds.get_array(column).push(default_value); } } } _select_event(ev, mode, renderers) { // Process selection event on the supplied renderers and return selected renderers const frame = this.plot_view.frame; const { sx, sy } = ev; if (!frame.bbox.contains(sx, sy)) { return []; } const geometry = { type: "point", sx, sy }; const selected = []; for (const renderer of renderers) { const sm = renderer.get_selection_manager(); const cds = renderer.data_source; const view = this.plot_view.views.find_one(renderer); if (view != null) { const did_hit = sm.select([view], geometry, true, mode); if (did_hit) { selected.push(renderer); } cds.properties.selected.change.emit(); } } return selected; } } export class EditTool extends GestureTool { static __name__ = "EditTool"; constructor(attrs) { super(attrs); } static { this.define(({ Unknown, Dict }) => ({ default_overrides: [Dict(Unknown), {}], empty_value: [Unknown, 0], })); } } //# sourceMappingURL=edit_tool.js.map