package.src.lib.geometry2d.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of plotly.js Show documentation
Show all versions of plotly.js Show documentation
The open source javascript graphing library that powers plotly
The newest version!
'use strict';
var mod = require('./mod').mod;
/*
* look for intersection of two line segments
* (1->2 and 3->4) - returns array [x,y] if they do, null if not
*/
exports.segmentsIntersect = segmentsIntersect;
function segmentsIntersect(x1, y1, x2, y2, x3, y3, x4, y4) {
var a = x2 - x1;
var b = x3 - x1;
var c = x4 - x3;
var d = y2 - y1;
var e = y3 - y1;
var f = y4 - y3;
var det = a * f - c * d;
// parallel lines? intersection is undefined
// ignore the case where they are colinear
if(det === 0) return null;
var t = (b * f - c * e) / det;
var u = (b * d - a * e) / det;
// segments do not intersect?
if(u < 0 || u > 1 || t < 0 || t > 1) return null;
return {x: x1 + a * t, y: y1 + d * t};
}
/*
* find the minimum distance between two line segments (1->2 and 3->4)
*/
exports.segmentDistance = function segmentDistance(x1, y1, x2, y2, x3, y3, x4, y4) {
if(segmentsIntersect(x1, y1, x2, y2, x3, y3, x4, y4)) return 0;
// the two segments and their lengths squared
var x12 = x2 - x1;
var y12 = y2 - y1;
var x34 = x4 - x3;
var y34 = y4 - y3;
var ll12 = x12 * x12 + y12 * y12;
var ll34 = x34 * x34 + y34 * y34;
// calculate distance squared, then take the sqrt at the very end
var dist2 = Math.min(
perpDistance2(x12, y12, ll12, x3 - x1, y3 - y1),
perpDistance2(x12, y12, ll12, x4 - x1, y4 - y1),
perpDistance2(x34, y34, ll34, x1 - x3, y1 - y3),
perpDistance2(x34, y34, ll34, x2 - x3, y2 - y3)
);
return Math.sqrt(dist2);
};
/*
* distance squared from segment ab to point c
* [xab, yab] is the vector b-a
* [xac, yac] is the vector c-a
* llab is the length squared of (b-a), just to simplify calculation
*/
function perpDistance2(xab, yab, llab, xac, yac) {
var fcAB = (xac * xab + yac * yab);
if(fcAB < 0) {
// point c is closer to point a
return xac * xac + yac * yac;
} else if(fcAB > llab) {
// point c is closer to point b
var xbc = xac - xab;
var ybc = yac - yab;
return xbc * xbc + ybc * ybc;
} else {
// perpendicular distance is the shortest
var crossProduct = xac * yab - yac * xab;
return crossProduct * crossProduct / llab;
}
}
// a very short-term cache for getTextLocation, just because
// we're often looping over the same locations multiple times
// invalidated as soon as we look at a different path
var locationCache, workingPath, workingTextWidth;
// turn a path and position along it into x, y, and angle for the given text
exports.getTextLocation = function getTextLocation(path, totalPathLen, positionOnPath, textWidth) {
if(path !== workingPath || textWidth !== workingTextWidth) {
locationCache = {};
workingPath = path;
workingTextWidth = textWidth;
}
if(locationCache[positionOnPath]) {
return locationCache[positionOnPath];
}
// for the angle, use points on the path separated by the text width
// even though due to curvature, the text will cover a bit more than that
var p0 = path.getPointAtLength(mod(positionOnPath - textWidth / 2, totalPathLen));
var p1 = path.getPointAtLength(mod(positionOnPath + textWidth / 2, totalPathLen));
// note: atan handles 1/0 nicely
var theta = Math.atan((p1.y - p0.y) / (p1.x - p0.x));
// center the text at 2/3 of the center position plus 1/3 the p0/p1 midpoint
// that's the average position of this segment, assuming it's roughly quadratic
var pCenter = path.getPointAtLength(mod(positionOnPath, totalPathLen));
var x = (pCenter.x * 4 + p0.x + p1.x) / 6;
var y = (pCenter.y * 4 + p0.y + p1.y) / 6;
var out = {x: x, y: y, theta: theta};
locationCache[positionOnPath] = out;
return out;
};
exports.clearLocationCache = function() {
workingPath = null;
};
/*
* Find the segment of `path` that's within the visible area
* given by `bounds` {left, right, top, bottom}, to within a
* precision of `buffer` px
*
* returns: undefined if nothing is visible, else object:
* {
* min: position where the path first enters bounds, or 0 if it
* starts within bounds
* max: position where the path last exits bounds, or the path length
* if it finishes within bounds
* len: max - min, ie the length of visible path
* total: the total path length - just included so the caller doesn't
* need to call path.getTotalLength() again
* isClosed: true iff the start and end points of the path are both visible
* and are at the same point
* }
*
* Works by starting from either end and repeatedly finding the distance from
* that point to the plot area, and if it's outside the plot, moving along the
* path by that distance (because the plot must be at least that far away on
* the path). Note that if a path enters, exits, and re-enters the plot, we
* will not capture this behavior.
*/
exports.getVisibleSegment = function getVisibleSegment(path, bounds, buffer) {
var left = bounds.left;
var right = bounds.right;
var top = bounds.top;
var bottom = bounds.bottom;
var pMin = 0;
var pTotal = path.getTotalLength();
var pMax = pTotal;
var pt0, ptTotal;
function getDistToPlot(len) {
var pt = path.getPointAtLength(len);
// hold on to the start and end points for `closed`
if(len === 0) pt0 = pt;
else if(len === pTotal) ptTotal = pt;
var dx = (pt.x < left) ? left - pt.x : (pt.x > right ? pt.x - right : 0);
var dy = (pt.y < top) ? top - pt.y : (pt.y > bottom ? pt.y - bottom : 0);
return Math.sqrt(dx * dx + dy * dy);
}
var distToPlot = getDistToPlot(pMin);
while(distToPlot) {
pMin += distToPlot + buffer;
if(pMin > pMax) return;
distToPlot = getDistToPlot(pMin);
}
distToPlot = getDistToPlot(pMax);
while(distToPlot) {
pMax -= distToPlot + buffer;
if(pMin > pMax) return;
distToPlot = getDistToPlot(pMax);
}
return {
min: pMin,
max: pMax,
len: pMax - pMin,
total: pTotal,
isClosed: pMin === 0 && pMax === pTotal &&
Math.abs(pt0.x - ptTotal.x) < 0.1 &&
Math.abs(pt0.y - ptTotal.y) < 0.1
};
};
/**
* Find point on SVG path corresponding to a given constraint coordinate
*
* @param {SVGPathElement} path
* @param {Number} val : constraint coordinate value
* @param {String} coord : 'x' or 'y' the constraint coordinate
* @param {Object} opts :
* - {Number} pathLength : supply total path length before hand
* - {Number} tolerance
* - {Number} iterationLimit
* @return {SVGPoint}
*/
exports.findPointOnPath = function findPointOnPath(path, val, coord, opts) {
opts = opts || {};
var pathLength = opts.pathLength || path.getTotalLength();
var tolerance = opts.tolerance || 1e-3;
var iterationLimit = opts.iterationLimit || 30;
// if path starts at a val greater than the path tail (like on vertical violins),
// we must flip the sign of the computed diff.
var mul = path.getPointAtLength(0)[coord] > path.getPointAtLength(pathLength)[coord] ? -1 : 1;
var i = 0;
var b0 = 0;
var b1 = pathLength;
var mid;
var pt;
var diff;
while(i < iterationLimit) {
mid = (b0 + b1) / 2;
pt = path.getPointAtLength(mid);
diff = pt[coord] - val;
if(Math.abs(diff) < tolerance) {
return pt;
} else {
if(mul * diff > 0) {
b1 = mid;
} else {
b0 = mid;
}
i++;
}
}
return pt;
};