chartx
Version:
Data Visualization Chart Library
166 lines (164 loc) • 5.51 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _intersect2 = _interopRequireDefault(require("./intersect"));
var _grid = _interopRequireDefault(require("./grid"));
var _format = _interopRequireDefault(require("./format"));
var _convex = _interopRequireDefault(require("./convex"));
/*
(c) 2014-2019, Andrii Heonia
Hull.js, a JavaScript library for concave hull generation by set of points.
https://github.com/AndriiHeonia/hull
*/
function _filterDuplicates(pointset) {
var unique = [pointset[0]];
var lastPoint = pointset[0];
for (var i = 1; i < pointset.length; i++) {
var currentPoint = pointset[i];
if (lastPoint[0] !== currentPoint[0] || lastPoint[1] !== currentPoint[1]) {
unique.push(currentPoint);
}
lastPoint = currentPoint;
}
return unique;
}
function _sortByX(pointset) {
return pointset.sort(function (a, b) {
return a[0] - b[0] || a[1] - b[1];
});
}
function _sqLength(a, b) {
return Math.pow(b[0] - a[0], 2) + Math.pow(b[1] - a[1], 2);
}
function _cos(o, a, b) {
var aShifted = [a[0] - o[0], a[1] - o[1]],
bShifted = [b[0] - o[0], b[1] - o[1]],
sqALen = _sqLength(o, a),
sqBLen = _sqLength(o, b),
dot = aShifted[0] * bShifted[0] + aShifted[1] * bShifted[1];
return dot / Math.sqrt(sqALen * sqBLen);
}
function _intersect(segment, pointset) {
for (var i = 0; i < pointset.length - 1; i++) {
var seg = [pointset[i], pointset[i + 1]];
if (segment[0][0] === seg[0][0] && segment[0][1] === seg[0][1] || segment[0][0] === seg[1][0] && segment[0][1] === seg[1][1]) {
continue;
}
if ((0, _intersect2.default)(segment, seg)) {
return true;
}
}
return false;
}
function _occupiedArea(pointset) {
var minX = Infinity;
var minY = Infinity;
var maxX = -Infinity;
var maxY = -Infinity;
for (var i = pointset.length - 1; i >= 0; i--) {
if (pointset[i][0] < minX) {
minX = pointset[i][0];
}
if (pointset[i][1] < minY) {
minY = pointset[i][1];
}
if (pointset[i][0] > maxX) {
maxX = pointset[i][0];
}
if (pointset[i][1] > maxY) {
maxY = pointset[i][1];
}
}
return [maxX - minX,
// width
maxY - minY // height
];
}
function _bBoxAround(edge) {
return [Math.min(edge[0][0], edge[1][0]),
// left
Math.min(edge[0][1], edge[1][1]),
// top
Math.max(edge[0][0], edge[1][0]),
// right
Math.max(edge[0][1], edge[1][1]) // bottom
];
}
function _midPoint(edge, innerPoints, convex) {
var point = null,
angle1Cos = MAX_CONCAVE_ANGLE_COS,
angle2Cos = MAX_CONCAVE_ANGLE_COS,
a1Cos,
a2Cos;
for (var i = 0; i < innerPoints.length; i++) {
a1Cos = _cos(edge[0], edge[1], innerPoints[i]);
a2Cos = _cos(edge[1], edge[0], innerPoints[i]);
if (a1Cos > angle1Cos && a2Cos > angle2Cos && !_intersect([edge[0], innerPoints[i]], convex) && !_intersect([edge[1], innerPoints[i]], convex)) {
angle1Cos = a1Cos;
angle2Cos = a2Cos;
point = innerPoints[i];
}
}
return point;
}
function _concave(convex, maxSqEdgeLen, maxSearchArea, grid, edgeSkipList) {
var midPointInserted = false;
for (var i = 0; i < convex.length - 1; i++) {
var edge = [convex[i], convex[i + 1]];
// generate a key in the format X0,Y0,X1,Y1
var keyInSkipList = edge[0][0] + ',' + edge[0][1] + ',' + edge[1][0] + ',' + edge[1][1];
if (_sqLength(edge[0], edge[1]) < maxSqEdgeLen || edgeSkipList.has(keyInSkipList)) {
continue;
}
var scaleFactor = 0;
var bBoxAround = _bBoxAround(edge);
var bBoxWidth = void 0;
var bBoxHeight = void 0;
var midPoint = void 0;
do {
bBoxAround = grid.extendBbox(bBoxAround, scaleFactor);
bBoxWidth = bBoxAround[2] - bBoxAround[0];
bBoxHeight = bBoxAround[3] - bBoxAround[1];
midPoint = _midPoint(edge, grid.rangePoints(bBoxAround), convex);
scaleFactor++;
} while (midPoint === null && (maxSearchArea[0] > bBoxWidth || maxSearchArea[1] > bBoxHeight));
if (bBoxWidth >= maxSearchArea[0] && bBoxHeight >= maxSearchArea[1]) {
edgeSkipList.add(keyInSkipList);
}
if (midPoint !== null) {
convex.splice(i + 1, 0, midPoint);
grid.removePoint(midPoint);
midPointInserted = true;
}
}
if (midPointInserted) {
return _concave(convex, maxSqEdgeLen, maxSearchArea, grid, edgeSkipList);
}
return convex;
}
function hull(pointset, concavity, format) {
var maxEdgeLen = concavity || 20;
var points = _filterDuplicates(_sortByX(_format.default.toXy(pointset, format)));
if (points.length < 4) {
return points.concat([points[0]]);
}
var occupiedArea = _occupiedArea(points);
var maxSearchArea = [occupiedArea[0] * MAX_SEARCH_BBOX_SIZE_PERCENT, occupiedArea[1] * MAX_SEARCH_BBOX_SIZE_PERCENT];
var convex = (0, _convex.default)(points);
var innerPoints = points.filter(function (pt) {
return convex.indexOf(pt) < 0;
});
var cellSize = Math.ceil(1 / (points.length / (occupiedArea[0] * occupiedArea[1])));
var concave = _concave(convex, Math.pow(maxEdgeLen, 2), maxSearchArea, (0, _grid.default)(innerPoints, cellSize), new Set());
if (format) {
return _format.default.fromXy(concave, format);
} else {
return concave;
}
}
var MAX_CONCAVE_ANGLE_COS = Math.cos(90 / (180 / Math.PI)); // angle = 90 deg
var MAX_SEARCH_BBOX_SIZE_PERCENT = 0.6;
var _default = exports.default = hull;