UNPKG

@bokeh/bokehjs

Version:

Interactive, novel data visualization

216 lines 8.32 kB
import { CenterRotatable, CenterRotatableView } from "./center_rotatable"; import { generic_area_vector_legend } from "./utils"; import { ScreenArray, to_screen, infer_type } from "../../core/types"; import { max } from "../../core/util/arrayable"; import { Selection } from "../selections/selection"; import { BBox } from "../../core/util/bbox"; import { rotate_around } from "../../core/util/affine"; import { BorderRadius } from "../common/kinds"; import * as resolve from "../common/resolve"; import { round_rect } from "../common/painting"; const { abs, sqrt } = Math; export class RectView extends CenterRotatableView { static __name__ = "RectView"; async load_glglyph() { const { RectGL } = await import("./webgl/rect"); return RectGL; } _set_data(indices) { super._set_data(indices); this.border_radius = resolve.border_radius(this.model.border_radius); } _map_data() { const n = this.data_size; if (this.inherited_x && this.inherited_width) { this._inherit_attr("swidth"); this._inherit_attr("sx0"); } else { let swidth; let sx0; if (this.model.properties.width.units == "data") { [swidth, sx0] = this._map_dist_corner_for_data_side_length(this.x, this.width, this.renderer.xscale); } else { swidth = to_screen(this.width); sx0 = new ScreenArray(n); const { sx } = this; for (let i = 0; i < n; i++) { sx0[i] = sx[i] - swidth[i] / 2; } } this._define_attr("swidth", swidth); this._define_attr("sx0", sx0); } if (this.inherited_y && this.inherited_height) { this._inherit_attr("sheight"); this._inherit_attr("sy1"); } else { let sheight; let sy1; if (this.model.properties.height.units == "data") { [sheight, sy1] = this._map_dist_corner_for_data_side_length(this.y, this.height, this.renderer.yscale); } else { sheight = to_screen(this.height); sy1 = new ScreenArray(n); const { sy } = this; for (let i = 0; i < n; i++) { sy1[i] = sy[i] - sheight[i] / 2; } } this._define_attr("sheight", sheight); this._define_attr("sy1", sy1); } if (this.inherited_swidth && this.inherited_sheight) { this._inherit_attr("max_x2_ddist"); this._inherit_attr("max_y2_ddist"); } else { const { sx0, sy1, swidth, sheight } = this; const ssemi_diag = new ScreenArray(n); for (let i = 0; i < n; i++) { const swidth_i = swidth[i]; const sheight_i = sheight[i]; ssemi_diag[i] = sqrt(swidth_i ** 2 + sheight_i ** 2) / 2; } const scenter_x = new ScreenArray(n); const scenter_y = new ScreenArray(n); for (let i = 0; i < n; i++) { scenter_x[i] = sx0[i] + swidth[i] / 2; scenter_y[i] = sy1[i] + sheight[i] / 2; } const max_x2_ddist = max(this._ddist(0, scenter_x, ssemi_diag)); const max_y2_ddist = max(this._ddist(1, scenter_y, ssemi_diag)); this._define_attr("max_x2_ddist", max_x2_ddist); this._define_attr("max_y2_ddist", max_y2_ddist); } } _paint(ctx, indices, data) { const { sx, sy, sx0, sy1, swidth, sheight, angle, border_radius } = { ...this, ...data }; for (const i of indices) { const sx_i = sx[i]; const sy_i = sy[i]; const sx0_i = sx0[i]; const sy1_i = sy1[i]; const swidth_i = swidth[i]; const sheight_i = sheight[i]; const angle_i = angle.get(i); if (!isFinite(sx_i + sy_i + sx0_i + sy1_i + swidth_i + sheight_i + angle_i)) { continue; } if (swidth_i == 0 || sheight_i == 0) { continue; } ctx.beginPath(); if (angle_i != 0) { ctx.translate(sx_i, sy_i); ctx.rotate(angle_i); const box = new BBox({ x: -swidth_i / 2, y: -sheight_i / 2, width: swidth_i, height: sheight_i }); round_rect(ctx, box, border_radius); ctx.rotate(-angle_i); ctx.translate(-sx_i, -sy_i); } else { const box = new BBox({ x: sx0_i, y: sy1_i, width: swidth_i, height: sheight_i }); round_rect(ctx, box, border_radius); } this.visuals.fill.apply(ctx, i); this.visuals.hatch.apply(ctx, i); this.visuals.line.apply(ctx, i); } } _hit_rect(geometry) { return this._hit_rect_against_index(geometry); } _hit_point(geometry) { const hit_xy = { x: geometry.sx, y: geometry.sy }; const x = this.renderer.xscale.invert(hit_xy.x); const y = this.renderer.yscale.invert(hit_xy.y); const candidates = this.index.indices({ x0: x - this.max_x2_ddist, x1: x + this.max_x2_ddist, y0: y - this.max_y2_ddist, y1: y + this.max_y2_ddist, }); const { sx, sy, sx0, sy1, swidth: sw, sheight: sh, angle } = this; const indices = []; for (const i of candidates) { const sx_i = sx[i]; const sy_i = sy[i]; const sx0_i = sx0[i]; const sy1_i = sy1[i]; const sw_i = sw[i]; const sh_i = sh[i]; const angle_i = angle.get(i); const hit_rxy = rotate_around(hit_xy, { x: sx_i, y: sy_i }, -angle_i); const x = hit_rxy.x - sx0_i; const y = hit_rxy.y - sy1_i; // TODO: consider round corners if (0 <= x && x <= sw_i && 0 <= y && y <= sh_i) { indices.push(i); } } return new Selection({ indices }); } _map_dist_corner_for_data_side_length(coord, side_length, scale) { const n = coord.length; const pt0 = new Float64Array(n); const pt1 = new Float64Array(n); for (let i = 0; i < n; i++) { const coord_i = coord[i]; const half_side_length_i = side_length.get(i) / 2; pt0[i] = coord_i - half_side_length_i; pt1[i] = coord_i + half_side_length_i; } const spt0 = scale.v_compute(pt0); const spt1 = scale.v_compute(pt1); const sside_length = this.sdist(scale, pt0, side_length, "edge", this.model.dilate); let spt_corner = spt0; for (let i = 0; i < n; i++) { const spt0i = spt0[i]; const spt1i = spt1[i]; if (!isNaN(spt0i + spt1i) && spt0i != spt1i) { spt_corner = spt0i < spt1i ? spt0 : spt1; break; } } return [sside_length, spt_corner]; } _ddist(dim, spts, spans) { const ArrayType = infer_type(spts, spans); const scale = dim == 0 ? this.renderer.xscale : this.renderer.yscale; const spt0 = spts; const m = spt0.length; const spt1 = new ArrayType(m); for (let i = 0; i < m; i++) { spt1[i] = spt0[i] + spans[i]; } const pt0 = scale.v_invert(spt0); const pt1 = scale.v_invert(spt1); const n = pt0.length; const ddist = new ArrayType(n); for (let i = 0; i < n; i++) { ddist[i] = abs(pt1[i] - pt0[i]); } return ddist; } draw_legend_for_index(ctx, bbox, index) { generic_area_vector_legend(this.visuals, ctx, bbox, index); } } export class Rect extends CenterRotatable { static __name__ = "Rect"; constructor(attrs) { super(attrs); } static { this.prototype.default_view = RectView; this.define(({ Bool }) => ({ border_radius: [BorderRadius, 0], dilate: [Bool, false], })); } } //# sourceMappingURL=rect.js.map