@bokeh/bokehjs
Version:
Interactive, novel data visualization
140 lines • 5.57 kB
JavaScript
import { XYGlyph, XYGlyphView } from "./xy_glyph";
import { LineVector, FillVector, HatchVector } from "../../core/property_mixins";
import * as hittest from "../../core/hittest";
import * as p from "../../core/properties";
import { minmax2 } from "../../core/util/arrayable";
import { Selection } from "../selections/selection";
export class MarkerView extends XYGlyphView {
static __name__ = "MarkerView";
_render_one;
_paint(ctx, indices, data) {
const { sx, sy, size, angle } = { ...this, ...data };
for (const i of indices) {
const sx_i = sx[i];
const sy_i = sy[i];
const size_i = size.get(i);
const angle_i = angle.get(i);
if (!isFinite(sx_i + sy_i + size_i + angle_i)) {
continue;
}
const r = size_i / 2;
ctx.beginPath();
ctx.translate(sx_i, sy_i);
if (angle_i != 0) {
ctx.rotate(angle_i);
}
this._render_one(ctx, i, r, this.visuals);
if (angle_i != 0) {
ctx.rotate(-angle_i);
}
ctx.translate(-sx_i, -sy_i);
}
}
_mask_data() {
// dilate the inner screen region by max_size and map back to data space for use in spatial query
const { x_target, y_target } = this.renderer.plot_view.frame;
const hr = x_target.widen(this.max_size).map((x) => this.renderer.xscale.invert(x));
const vr = y_target.widen(this.max_size).map((y) => this.renderer.yscale.invert(y));
return this.index.indices({
x0: hr.start, x1: hr.end,
y0: vr.start, y1: vr.end,
});
}
_hit_point(geometry) {
const { sx, sy } = geometry;
const { max_size } = this;
const { hit_dilation } = this.model;
const sx0 = sx - max_size * hit_dilation;
const sx1 = sx + max_size * hit_dilation;
const [x0, x1] = this.renderer.xscale.r_invert(sx0, sx1);
const sy0 = sy - max_size * hit_dilation;
const sy1 = sy + max_size * hit_dilation;
const [y0, y1] = this.renderer.yscale.r_invert(sy0, sy1);
const candidates = this.index.indices({ x0, x1, y0, y1 });
const indices = [];
for (const i of candidates) {
const s2 = this.size.get(i) / 2 * hit_dilation;
if (Math.abs(this.sx[i] - sx) <= s2 && Math.abs(this.sy[i] - sy) <= s2) {
indices.push(i);
}
}
return new Selection({ indices });
}
_hit_span(geometry) {
const { sx, sy } = geometry;
const bounds = this.bounds();
const half_size = this.max_size / 2;
const [x0, x1, y0, y1] = (() => {
if (geometry.direction == "h") {
const { y0, y1 } = bounds;
const sx0 = sx - half_size;
const sx1 = sx + half_size;
const [x0, x1] = this.renderer.xscale.r_invert(sx0, sx1);
return [x0, x1, y0, y1];
}
else {
const { x0, x1 } = bounds;
const sy0 = sy - half_size;
const sy1 = sy + half_size;
const [y0, y1] = this.renderer.yscale.r_invert(sy0, sy1);
return [x0, x1, y0, y1];
}
})();
const indices = [...this.index.indices({ x0, x1, y0, y1 })];
return new Selection({ indices });
}
_hit_rect(geometry) {
const { sx0, sx1, sy0, sy1 } = geometry;
const [x0, x1] = this.renderer.xscale.r_invert(sx0, sx1);
const [y0, y1] = this.renderer.yscale.r_invert(sy0, sy1);
const indices = [...this.index.indices({ x0, x1, y0, y1 })];
return new Selection({ indices });
}
_hit_poly(geometry) {
const { sx: sxs, sy: sys } = geometry;
const candidates = (() => {
const xs = this.renderer.xscale.v_invert(sxs);
const ys = this.renderer.yscale.v_invert(sys);
const [x0, x1, y0, y1] = minmax2(xs, ys);
return this.index.indices({ x0, x1, y0, y1 });
})();
const indices = [];
for (const i of candidates) {
if (hittest.point_in_poly(this.sx[i], this.sy[i], sxs, sys)) {
indices.push(i);
}
}
return new Selection({ indices });
}
_get_legend_args({ x0, x1, y0, y1 }, index) {
// using objects like this seems a little wonky, since the keys are coerced to strings, but it works
const n = index + 1;
const sx = new Array(n);
const sy = new Array(n);
sx[index] = (x0 + x1) / 2;
sy[index] = (y0 + y1) / 2;
const vsize = Math.min(Math.abs(x1 - x0), Math.abs(y1 - y0)) * 0.4;
const size = new p.UniformScalar(vsize, n);
const angle = new p.UniformScalar(0, n); // don't attempt to match glyph angle
return { sx, sy, size, angle };
}
draw_legend_for_index(ctx, { x0, x1, y0, y1 }, index) {
const args = this._get_legend_args({ x0, x1, y0, y1 }, index);
this._paint(ctx, [index], args);
}
}
export class Marker extends XYGlyph {
static __name__ = "Marker";
constructor(attrs) {
super(attrs);
}
static {
this.mixins([LineVector, FillVector, HatchVector]);
this.define(({ Float }) => ({
size: [p.ScreenSizeSpec, { value: 4 }],
angle: [p.AngleSpec, 0],
hit_dilation: [Float, 1.0],
}));
}
}
//# sourceMappingURL=marker.js.map