@bokeh/bokehjs
Version:
Interactive, novel data visualization
144 lines • 5.43 kB
JavaScript
import * as hittest from "../../core/hittest";
import * as p from "../../core/properties";
import { LineVector } from "../../core/property_mixins";
import { atan2 } from "../../core/util/math";
import { Glyph, GlyphView } from "./glyph";
import { generic_line_vector_legend } from "./utils";
import { Selection } from "../selections/selection";
export class SegmentView extends GlyphView {
static __name__ = "SegmentView";
_project_data() {
this._project_xy("x0", this.x0, "y0", this.y0);
this._project_xy("x1", this.x1, "y1", this.y1);
}
_index_data(index) {
const { min, max } = Math;
const { x0, x1, y0, y1, data_size } = this;
for (let i = 0; i < data_size; i++) {
const x0_i = x0[i];
const x1_i = x1[i];
const y0_i = y0[i];
const y1_i = y1[i];
index.add_rect(min(x0_i, x1_i), min(y0_i, y1_i), max(x0_i, x1_i), max(y0_i, y1_i));
}
}
_paint(ctx, indices, data) {
if (!this.visuals.line.doit) {
return;
}
const { sx0, sy0, sx1, sy1 } = { ...this, ...data };
for (const i of indices) {
const sx0_i = sx0[i];
const sy0_i = sy0[i];
const sx1_i = sx1[i];
const sy1_i = sy1[i];
if (!isFinite(sx0_i + sy0_i + sx1_i + sy1_i)) {
continue;
}
this._render_decorations(ctx, i, sx0_i, sy0_i, sx1_i, sy1_i);
ctx.beginPath();
ctx.moveTo(sx0_i, sy0_i);
ctx.lineTo(sx1_i, sy1_i);
this.visuals.line.apply(ctx, i);
}
}
_render_decorations(ctx, i, sx0, sy0, sx1, sy1) {
const { PI } = Math;
const angle = atan2([sx0, sy0], [sx1, sy1]) + PI / 2;
for (const decoration of this.decorations.values()) {
ctx.save();
if (decoration.model.node == "start") {
ctx.translate(sx0, sy0);
ctx.rotate(angle + PI);
}
else if (decoration.model.node == "end") {
ctx.translate(sx1, sy1);
ctx.rotate(angle);
}
decoration.marking.paint(ctx, i);
ctx.restore();
}
}
_hit_point(geometry) {
const { sx, sy } = geometry;
const point = { x: sx, y: sy };
const lw_voffset = 2; // FIXME: Use maximum of segments line_width/2 instead of magic constant 2
const [x0, x1] = this.renderer.xscale.r_invert(sx - lw_voffset, sx + lw_voffset);
const [y0, y1] = this.renderer.yscale.r_invert(sy - lw_voffset, sy + lw_voffset);
const candidates = this.index.indices({ x0, y0, x1, y1 });
const indices = [];
for (const i of candidates) {
const threshold2 = Math.max(2, this.line_width.get(i) / 2) ** 2;
const p0 = { x: this.sx0[i], y: this.sy0[i] };
const p1 = { x: this.sx1[i], y: this.sy1[i] };
const dist2 = hittest.dist_to_segment_squared(point, p0, p1);
if (dist2 < threshold2) {
indices.push(i);
}
}
return new Selection({ indices });
}
_hit_span(geometry) {
const [hr, vr] = this.renderer.plot_view.frame.bbox.ranges;
const { sx, sy } = geometry;
let v0;
let v1;
let val;
if (geometry.direction == "v") {
val = this.renderer.yscale.invert(sy);
[v0, v1] = [this.y0, this.y1];
}
else {
val = this.renderer.xscale.invert(sx);
[v0, v1] = [this.x0, this.x1];
}
const indices = [];
const [x0, x1] = this.renderer.xscale.r_invert(hr.start, hr.end);
const [y0, y1] = this.renderer.yscale.r_invert(vr.start, vr.end);
const candidates = this.index.indices({ x0, y0, x1, y1 });
for (const i of candidates) {
if ((v0[i] <= val && val <= v1[i]) || (v1[i] <= val && val <= v0[i])) {
indices.push(i);
}
const threshold = 1.5 + (this.line_width.get(i) / 2); // Maximum pixel difference to detect hit
if (v0[i] == v1[i]) {
if (geometry.direction == "h") {
if (Math.abs(this.sx0[i] - sx) <= threshold) {
indices.push(i);
}
}
else {
if (Math.abs(this.sy0[i] - sy) <= threshold) {
indices.push(i);
}
}
}
}
return new Selection({ indices });
}
scenterxy(i) {
const scx = this.sx0[i] / 2 + this.sx1[i] / 2;
const scy = this.sy0[i] / 2 + this.sy1[i] / 2;
return [scx, scy];
}
draw_legend_for_index(ctx, bbox, index) {
generic_line_vector_legend(this.visuals, ctx, bbox, index);
}
}
export class Segment extends Glyph {
static __name__ = "Segment";
constructor(attrs) {
super(attrs);
}
static {
this.prototype.default_view = SegmentView;
this.define(({}) => ({
x0: [p.XCoordinateSpec, { field: "x0" }],
y0: [p.YCoordinateSpec, { field: "y0" }],
x1: [p.XCoordinateSpec, { field: "x1" }],
y1: [p.YCoordinateSpec, { field: "y1" }],
}));
this.mixins(LineVector);
}
}
//# sourceMappingURL=segment.js.map