vega-functions
Version:
Custom functions for the Vega expression language.
109 lines (90 loc) • 3.13 kB
JavaScript
import intersect from './intersect';
import {Bounds} from 'vega-scenegraph';
import {array} from 'vega-util';
/**
* Appends a new point to the lasso
*
* @param {*} lasso the lasso in pixel space
* @param {*} x the x coordinate in pixel space
* @param {*} y the y coordinate in pixel space
* @param {*} minDist the minimum distance, in pixels, that thenew point needs to be apart from the last point
* @returns a new array containing the lasso with the new point
*/
export function lassoAppend(lasso, x, y, minDist = 5) {
lasso = array(lasso);
const last = lasso[lasso.length - 1];
// Add point to lasso if its the first point or distance to last point exceed minDist
return (last === undefined || Math.hypot(last[0] - x, last[1] - y) > minDist)
? [...lasso, [x, y]]
: lasso;
}
/**
* Generates a svg path command which draws a lasso
*
* @param {*} lasso the lasso in pixel space in the form [[x,y], [x,y], ...]
* @returns the svg path command that draws the lasso
*/
export function lassoPath(lasso) {
return array(lasso).reduce((svg, [x, y], i) => {
return svg += i == 0
? `M ${x},${y} `
: i === lasso.length - 1
? ' Z'
: `L ${x},${y} `;
}, '');
}
/**
* Inverts the lasso from pixel space to an array of vega scenegraph tuples
*
* @param {*} data the dataset
* @param {*} pixelLasso the lasso in pixel space, [[x,y], [x,y], ...]
* @param {*} unit the unit where the lasso is defined
*
* @returns an array of vega scenegraph tuples
*/
export function intersectLasso(markname, pixelLasso, unit) {
const { x, y, mark } = unit;
const bb = new Bounds().set(
Number.MAX_SAFE_INTEGER,
Number.MAX_SAFE_INTEGER,
Number.MIN_SAFE_INTEGER,
Number.MIN_SAFE_INTEGER
);
// Get bounding box around lasso
for (const [px, py] of pixelLasso) {
if (px < bb.x1) bb.x1 = px;
if (px > bb.x2) bb.x2 = px;
if (py < bb.y1) bb.y1 = py;
if (py > bb.y2) bb.y2 = py;
}
// Translate bb against unit coordinates
bb.translate(x, y);
const intersection = intersect([[bb.x1, bb.y1], [bb.x2, bb.y2]],
markname,
mark);
// Check every point against the lasso
return intersection.filter(tuple => pointInPolygon(tuple.x, tuple.y, pixelLasso));
}
/**
* Performs a test if a point is inside a polygon based on the idea from
* https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html
*
* This method will not need the same start/end point since it wraps around the edges of the array
*
* @param {*} test a point to test against
* @param {*} polygon a polygon in the form [[x,y], [x,y], ...]
* @returns true if the point lies inside the polygon, false otherwise
*/
function pointInPolygon(testx, testy, polygon) {
let intersections = 0;
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
const [prevX, prevY] = polygon[j];
const [x, y] = polygon[i];
// count intersections
if (((y > testy) != (prevY > testy)) && (testx < (prevX - x) * (testy - y) / (prevY - y) + x)) {
intersections++;
}
}
// point is in polygon if intersection count is odd
return intersections & 1;
}