@bokeh/bokehjs
Version:
Interactive, novel data visualization
292 lines • 11.1 kB
JavaScript
import { SpatialIndex } from "../../core/util/spatial";
import { Glyph, GlyphView } from "./glyph";
import { generic_area_vector_legend } from "./utils";
import { minmax2 } from "../../core/util/arrayable";
import { sum } from "../../core/util/arrayable";
import { LineVector, FillVector, HatchVector } from "../../core/property_mixins";
import * as hittest from "../../core/hittest";
import * as p from "../../core/properties";
import { Selection } from "../selections/selection";
import { unreachable } from "../../core/util/assert";
export class MultiPolygonsView extends GlyphView {
static __name__ = "MultiPolygonsView";
_hole_index;
_project_data() {
// TODO
}
_index_data(index) {
const { min, max } = Math;
const { data_size } = this;
for (let i = 0; i < data_size; i++) {
const xsi = this.xs[i];
const ysi = this.ys[i];
if (xsi.length == 0 || ysi.length == 0) {
index.add_empty();
continue;
}
let xi0 = +Infinity;
let xi1 = -Infinity;
let yi0 = +Infinity;
let yi1 = -Infinity;
for (let j = 0, endj = xsi.length; j < endj; j++) {
const xsij = xsi[j][0]; // do not use holes
const ysij = ysi[j][0]; // do not use holes
if (xsij.length != 0 && ysij.length != 0) {
const [xij0, xij1, yij0, yij1] = minmax2(xsij, ysij);
xi0 = min(xi0, xij0);
xi1 = max(xi1, xij1);
yi0 = min(yi0, yij0);
yi1 = max(yi1, yij1);
}
}
index.add_rect(xi0, yi0, xi1, yi1);
}
this._hole_index = this._index_hole_data();
}
_index_hole_data() {
const { min, max } = Math;
const { data_size } = this;
const index = new SpatialIndex(data_size);
for (let i = 0; i < data_size; i++) {
const xsi = this.xs[i];
const ysi = this.ys[i];
if (xsi.length == 0 || ysi.length == 0) {
index.add_empty();
continue;
}
let xi0 = +Infinity;
let xi1 = -Infinity;
let yi0 = +Infinity;
let yi1 = -Infinity;
for (let j = 0, endj = xsi.length; j < endj; j++) {
const xsij = xsi[j];
const ysij = ysi[j];
if (xsij.length > 1 && ysij.length > 1) {
for (let k = 1, endk = xsij.length; k < endk; k++) {
const [xij0, xij1, yij0, yij1] = minmax2(xsij[k], ysij[k]);
xi0 = min(xi0, xij0);
xi1 = max(xi1, xij1);
yi0 = min(yi0, yij0);
yi1 = max(yi1, yij1);
}
}
}
index.add_rect(xi0, yi0, xi1, yi1);
}
index.finish();
return index;
}
_mask_data() {
const { x_source, y_source } = this.renderer.coordinates;
return this.index.indices({
x0: x_source.min, x1: x_source.max,
y0: y_source.min, y1: y_source.max,
});
}
_paint(ctx, indices, data) {
if (!this.visuals.fill.doit && !this.visuals.line.doit) {
return;
}
const { sxs, sys } = { ...this, ...data };
for (const i of indices) {
ctx.beginPath();
const sx_i = sxs[i];
const sy_i = sys[i];
const nj = Math.min(sx_i.length, sy_i.length);
for (let j = 0; j < nj; j++) {
const sx_ij = sx_i[j];
const sy_ij = sy_i[j];
const nk = Math.min(sx_ij.length, sy_ij.length);
for (let k = 0; k < nk; k++) {
const sx_ijk = sx_ij[k];
const sy_ijk = sy_ij[k];
const nl = Math.min(sx_ijk.length, sy_ijk.length);
for (let l = 0; l < nl; l++) {
const sx_ijkl = sx_ijk[l];
const sy_ijkl = sy_ijk[l];
if (l == 0) {
ctx.moveTo(sx_ijkl, sy_ijkl);
}
else {
ctx.lineTo(sx_ijkl, sy_ijkl);
}
}
ctx.closePath();
}
}
this.visuals.fill.apply(ctx, i, "evenodd");
this.visuals.hatch.apply(ctx, i, "evenodd");
this.visuals.line.apply(ctx, i);
}
}
_hit_poly(geometry) {
const { sx: sxs, sy: sys, greedy = false } = 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) {
const sxs_i = this.sxs[i];
const sys_i = this.sys[i];
let hit = !greedy;
const nj = sxs_i.length;
for (let j = 0; j < nj; j++) {
const sxs_ij0 = sxs_i[j][0];
const sys_ij0 = sys_i[j][0];
const nk = sxs_ij0.length;
for (let k = 0; k < nk; k++) {
const sxs_ij0k = sxs_ij0[k];
const sys_ij0k = sys_ij0[k];
if (!hittest.point_in_poly(sxs_ij0k, sys_ij0k, sxs, sys)) {
if (!greedy) {
hit = false;
break;
}
}
else {
if (greedy) {
hit = true;
break;
}
}
}
if (!greedy) {
if (!hit) {
break;
}
}
else {
if (hit) {
break;
}
}
}
if (hit) {
indices.push(i);
}
}
return new Selection({ indices });
}
_hit_rect(geometry) {
const { sx0, sx1, sy0, sy1, greedy } = geometry;
const sxs = [sx0, sx1, sx1, sx0];
const sys = [sy0, sy0, sy1, sy1];
return this._hit_poly({ type: "poly", sx: sxs, sy: sys, greedy });
}
_hit_point(geometry) {
const { sx, sy } = geometry;
const x = this.renderer.xscale.invert(sx);
const y = this.renderer.yscale.invert(sy);
const candidates = this.index.indices({ x0: x, y0: y, x1: x, y1: y });
const hole_candidates = this._hole_index.indices({ x0: x, y0: y, x1: x, y1: y });
const indices = [];
for (const index of candidates) {
const sxs = this.sxs[index];
const sys = this.sys[index];
for (let j = 0, endj = sxs.length; j < endj; j++) {
const nk = sxs[j].length;
if (hittest.point_in_poly(sx, sy, sxs[j][0], sys[j][0])) {
if (nk == 1) {
indices.push(index);
}
else if (!hole_candidates.get(index)) {
indices.push(index);
}
else if (nk > 1) {
let in_a_hole = false;
for (let k = 1; k < nk; k++) {
const sxs_k = sxs[j][k];
const sys_k = sys[j][k];
if (hittest.point_in_poly(sx, sy, sxs_k, sys_k)) {
in_a_hole = true;
break;
}
else {
continue;
}
}
if (!in_a_hole) {
indices.push(index);
}
}
}
}
}
return new Selection({ indices });
}
_get_snap_coord(array) {
return sum(array) / array.length;
}
scenterxy(i, sx, sy) {
if (this.sxs[i].length == 1) {
// We don't have discontinuous objects so we're ok
const scx = this._get_snap_coord(this.sxs[i][0][0]);
const scy = this._get_snap_coord(this.sys[i][0][0]);
return [scx, scy];
}
else {
// We have discontinuous objects, so we need to find which
// one we're in, we can use point_in_poly again
const sxs = this.sxs[i];
const sys = this.sys[i];
for (let j = 0, end = sxs.length; j < end; j++) {
if (hittest.point_in_poly(sx, sy, sxs[j][0], sys[j][0])) {
const scx = this._get_snap_coord(sxs[j][0]);
const scy = this._get_snap_coord(sys[j][0]);
return [scx, scy];
}
}
}
unreachable();
}
map_data() {
if (this.inherited_xs && this.inherited_ys) {
this._inherit_attr("sxs");
this._inherit_attr("sys");
}
else {
const { xs, ys } = this;
const n_i = xs.length;
const sxs = new Array(n_i);
const sys = new Array(n_i);
for (let i = 0; i < n_i; i++) {
const n_j = xs[i].length;
sxs[i] = new Array(n_j);
sys[i] = new Array(n_j);
for (let j = 0; j < n_j; j++) {
const n_k = xs[i][j].length;
sxs[i][j] = new Array(n_k);
sys[i][j] = new Array(n_k);
for (let k = 0; k < n_k; k++) {
const [sx, sy] = this.renderer.coordinates.map_to_screen(xs[i][j][k], ys[i][j][k]);
sxs[i][j][k] = sx;
sys[i][j][k] = sy;
}
}
}
this._define_attr("sxs", sxs);
this._define_attr("sys", sys);
}
}
draw_legend_for_index(ctx, bbox, index) {
generic_area_vector_legend(this.visuals, ctx, bbox, index);
}
}
export class MultiPolygons extends Glyph {
static __name__ = "MultiPolygons";
constructor(attrs) {
super(attrs);
}
static {
this.prototype.default_view = MultiPolygonsView;
this.define(({}) => ({
xs: [p.XCoordinateSeqSeqSeqSpec, { field: "xs" }],
ys: [p.YCoordinateSeqSeqSeqSpec, { field: "ys" }],
}));
this.mixins([LineVector, FillVector, HatchVector]);
}
}
//# sourceMappingURL=multi_polygons.js.map