@bokeh/bokehjs
Version:
Interactive, novel data visualization
191 lines • 6.54 kB
JavaScript
import { GestureTool, GestureToolView } from "./gesture_tool";
import { MenuItem } from "../../ui/menus";
import { assert } from "../../../core/util/assert";
import { Dimensions } from "../../../core/enums";
import * as icons from "../../../styles/icons.css";
export function update_ranges(scales, p0, p1) {
const r = new Map();
for (const [, scale] of scales) {
const [start, end] = scale.r_invert(p0, p1);
r.set(scale.source_range, { start, end });
}
return r;
}
export class PanToolView extends GestureToolView {
static __name__ = "PanToolView";
pan_info;
state = null;
cursor(sx, sy) {
if (this.state != null) {
const { dims } = this.state;
switch (dims == "both" ? this.model.dimensions : dims) {
case "both": return "move";
case "width": return "ew-resize";
case "height": return "ns-resize";
}
}
return super.cursor(sx, sy);
}
_interactive_dims({ sx, sy }) {
const { dimensions } = this.model;
const { plot_view } = this;
const axis_view = plot_view.axis_views.find((view) => view.bbox.contains(sx, sy));
if (axis_view != null) {
switch (axis_view.dimension) {
case 0: {
if (dimensions == "width" || dimensions == "both") {
return "width";
}
break;
}
case 1: {
if (dimensions == "height" || dimensions == "both") {
return "height";
}
break;
}
}
}
else if (plot_view.frame.bbox.contains(sx, sy)) {
return "both";
}
return null;
}
_pan_start(ev) {
assert(this.state == null);
const { sx, sy } = ev;
const dims = this._interactive_dims({ sx, sy });
if (dims != null) {
this.state = { last_dx: 0, last_dy: 0, dims };
this.model.document?.interactive_start(this.plot_view.model);
}
}
_pan(ev) {
if (this.state != null) {
this._update(ev.dx, ev.dy);
this.model.document?.interactive_start(this.plot_view.model);
}
}
_pan_end(_e) {
if (this.state != null) {
this.state = null;
if (this.pan_info != null) {
this.plot_view.state.push("pan", { range: this.pan_info });
}
this.plot_view.trigger_ranges_update_event();
}
}
_update(dx, dy) {
const { state } = this;
assert(state != null);
const frame = this.plot_view.frame;
const new_dx = dx - state.last_dx;
const new_dy = dy - state.last_dy;
const hr = frame.bbox.h_range;
const sx_low = hr.start - new_dx;
const sx_high = hr.end - new_dx;
const vr = frame.bbox.v_range;
const sy_low = vr.start - new_dy;
const sy_high = vr.end - new_dy;
const dims = this.model.dimensions;
const { x_scales, y_scales } = frame;
const x_axis_only = state.dims == "width";
const y_axis_only = state.dims == "height";
// Here we are a bit careful to only update the range info for dimensions that
// are "in play". This is to avoid superfluous noise updates to dataranges that
// would cause windowed auto-ranging to turn off.
let sdx;
let xrs;
if ((dims == "width" || dims == "both") && !y_axis_only) {
sdx = -new_dx;
xrs = update_ranges(x_scales, sx_low, sx_high);
}
else {
sdx = 0;
xrs = new Map();
}
let sdy;
let yrs;
if ((dims == "height" || dims == "both") && !x_axis_only) {
sdy = -new_dy;
yrs = update_ranges(y_scales, sy_low, sy_high);
}
else {
sdy = 0;
yrs = new Map();
}
state.last_dx = dx;
state.last_dy = dy;
this.pan_info = { xrs, yrs, sdx, sdy };
this.plot_view.update_range(this.pan_info, { panning: true });
}
}
export class PanTool extends GestureTool {
static __name__ = "PanTool";
constructor(attrs) {
super(attrs);
}
static {
this.prototype.default_view = PanToolView;
this.define(() => ({
dimensions: [Dimensions, "both"],
}));
this.register_alias("pan", () => new PanTool({ dimensions: "both" }));
this.register_alias("xpan", () => new PanTool({ dimensions: "width" }));
this.register_alias("ypan", () => new PanTool({ dimensions: "height" }));
}
tool_name = "Pan";
event_type = "pan";
default_order = 10;
get tooltip() {
return this._get_dim_tooltip(this.dimensions);
}
get computed_icon() {
const icon = super.computed_icon;
if (icon != null) {
return icon;
}
else {
switch (this.dimensions) {
case "both": return `.${icons.tool_icon_pan}`;
case "width": return `.${icons.tool_icon_x_pan}`;
case "height": return `.${icons.tool_icon_y_pan}`;
}
}
}
get menu() {
return [
new MenuItem({
icon: `.${icons.tool_icon_pan}`,
label: "XY mode",
tooltip: "Pan in both dimensions",
checked: () => this.dimensions == "both",
action: () => {
this.dimensions = "both";
this.active = true;
},
}),
new MenuItem({
icon: `.${icons.tool_icon_x_pan}`,
label: "X-only",
tooltip: "Pan in x-dimension",
checked: () => this.dimensions == "width",
action: () => {
this.dimensions = "width";
this.active = true;
},
}),
new MenuItem({
icon: `.${icons.tool_icon_y_pan}`,
label: "Y-only",
tooltip: "Pan in y-dimension",
checked: () => this.dimensions == "height",
action: () => {
this.dimensions = "height";
this.active = true;
},
}),
];
}
}
//# sourceMappingURL=pan_tool.js.map