Icard/angular-clarity-master(work.../node_modules/highcharts/es-modules/Gantt/Pathfinder.js

438 lines
15 KiB
JavaScript
Raw Normal View History

2024-07-16 14:55:36 +00:00
/* *
*
* (c) 2016 Highsoft AS
* Authors: Øystein Moseng, Lars A. V. Cabrera
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import Connection from './Connection.js';
import PathfinderAlgorithms from './PathfinderAlgorithms.js';
import PathfinderComposition from './PathfinderComposition.js';
import Point from '../Core/Series/Point.js';
import U from '../Core/Utilities.js';
const { addEvent, defined, pick, splat } = U;
/* *
*
* Constants
*
* */
const max = Math.max, min = Math.min;
/* *
*
* Functions
*
* */
/**
* Get point bounding box using plotX/plotY and shapeArgs. If using
* graphic.getBBox() directly, the bbox will be affected by animation.
*
* @private
* @function
*
* @param {Highcharts.Point} point
* The point to get BB of.
*
* @return {Highcharts.Dictionary<number>|null}
* Result xMax, xMin, yMax, yMin.
*/
function getPointBB(point) {
const shapeArgs = point.shapeArgs;
// Prefer using shapeArgs (columns)
if (shapeArgs) {
return {
xMin: shapeArgs.x || 0,
xMax: (shapeArgs.x || 0) + (shapeArgs.width || 0),
yMin: shapeArgs.y || 0,
yMax: (shapeArgs.y || 0) + (shapeArgs.height || 0)
};
}
// Otherwise use plotX/plotY and bb
const bb = point.graphic && point.graphic.getBBox();
return bb ? {
xMin: point.plotX - bb.width / 2,
xMax: point.plotX + bb.width / 2,
yMin: point.plotY - bb.height / 2,
yMax: point.plotY + bb.height / 2
} : null;
}
/**
* Compute smallest distance between two rectangles.
* @private
*/
function calculateObstacleDistance(a, b, bbMargin) {
// Count the distance even if we are slightly off
const margin = pick(bbMargin, 10), yOverlap = a.yMax + margin > b.yMin - margin &&
a.yMin - margin < b.yMax + margin, xOverlap = a.xMax + margin > b.xMin - margin &&
a.xMin - margin < b.xMax + margin, xDistance = yOverlap ? (a.xMin > b.xMax ? a.xMin - b.xMax : b.xMin - a.xMax) : Infinity, yDistance = xOverlap ? (a.yMin > b.yMax ? a.yMin - b.yMax : b.yMin - a.yMax) : Infinity;
// If the rectangles collide, try recomputing with smaller margin.
// If they collide anyway, discard the obstacle.
if (xOverlap && yOverlap) {
return (margin ?
calculateObstacleDistance(a, b, Math.floor(margin / 2)) :
Infinity);
}
return min(xDistance, yDistance);
}
/**
* Calculate margin to place around obstacles for the pathfinder in pixels.
* Returns a minimum of 1 pixel margin.
*
* @private
* @function
*
* @param {Array<object>} obstacles
* Obstacles to calculate margin from.
*
* @return {number}
* The calculated margin in pixels. At least 1.
*/
function calculateObstacleMargin(obstacles) {
const len = obstacles.length, distances = [];
let onstacleDistance;
// Go over all obstacles and compare them to the others.
for (let i = 0; i < len; ++i) {
// Compare to all obstacles ahead. We will already have compared this
// obstacle to the ones before.
for (let j = i + 1; j < len; ++j) {
onstacleDistance =
calculateObstacleDistance(obstacles[i], obstacles[j]);
// TODO: Magic number 80
if (onstacleDistance < 80) { // Ignore large distances
distances.push(onstacleDistance);
}
}
}
// Ensure we always have at least one value, even in very spacious charts
distances.push(80);
return max(Math.floor(distances.sort(function (a, b) {
return (a - b);
})[
// Discard first 10% of the relevant distances, and then grab
// the smallest one.
Math.floor(distances.length / 10)] / 2 - 1 // Divide the distance by 2 and subtract 1.
), 1 // 1 is the minimum margin
);
}
/* *
*
* Class
*
* */
/**
* The Pathfinder class.
*
* @private
* @class
* @name Highcharts.Pathfinder
*
* @param {Highcharts.Chart} chart
* The chart to operate on.
*/
class Pathfinder {
/* *
*
* Static Functions
*
* */
static compose(ChartClass, PointClass) {
PathfinderComposition.compose(ChartClass, Pathfinder, PointClass);
}
/* *
*
* Constructor
*
* */
constructor(chart) {
this.init(chart);
}
/* *
*
* Functions
*
* */
/**
* Initialize the Pathfinder object.
*
* @function Highcharts.Pathfinder#init
*
* @param {Highcharts.Chart} chart
* The chart context.
*/
init(chart) {
// Initialize pathfinder with chart context
this.chart = chart;
// Init connection reference list
this.connections = [];
// Recalculate paths/obstacles on chart redraw
addEvent(chart, 'redraw', function () {
this.pathfinder.update();
});
}
/**
* Update Pathfinder connections from scratch.
*
* @function Highcharts.Pathfinder#update
*
* @param {boolean} [deferRender]
* Whether or not to defer rendering of connections until
* series.afterAnimate event has fired. Used on first render.
*/
update(deferRender) {
const chart = this.chart, pathfinder = this, oldConnections = pathfinder.connections;
// Rebuild pathfinder connections from options
pathfinder.connections = [];
chart.series.forEach(function (series) {
if (series.visible && !series.options.isInternal) {
series.points.forEach(function (point) {
const ganttPointOptions = point.options;
// For Gantt series the connect could be
// defined as a dependency
if (ganttPointOptions && ganttPointOptions.dependency) {
ganttPointOptions.connect = ganttPointOptions
.dependency;
}
const connects = (point.options?.connect &&
splat(point.options.connect));
let to;
if (point.visible && point.isInside !== false && connects) {
connects.forEach(function (connect) {
to = chart.get(typeof connect === 'string' ?
connect : connect.to);
if (to instanceof Point &&
to.series.visible &&
to.visible &&
to.isInside !== false) {
// Add new connection
pathfinder.connections.push(new Connection(point, // From
to, typeof connect === 'string' ?
{} :
connect));
}
});
}
});
}
});
// Clear connections that should not be updated, and move old info over
// to new connections.
for (let j = 0, k, found, lenOld = oldConnections.length, lenNew = pathfinder.connections.length; j < lenOld; ++j) {
found = false;
const oldCon = oldConnections[j];
for (k = 0; k < lenNew; ++k) {
const newCon = pathfinder.connections[k];
if ((oldCon.options && oldCon.options.type) ===
(newCon.options && newCon.options.type) &&
oldCon.fromPoint === newCon.fromPoint &&
oldCon.toPoint === newCon.toPoint) {
newCon.graphics = oldCon.graphics;
found = true;
break;
}
}
if (!found) {
oldCon.destroy();
}
}
// Clear obstacles to force recalculation. This must be done on every
// redraw in case positions have changed. Recalculation is handled in
// Connection.getPath on demand.
delete this.chartObstacles;
delete this.lineObstacles;
// Draw the pending connections
pathfinder.renderConnections(deferRender);
}
/**
* Draw the chart's connecting paths.
*
* @function Highcharts.Pathfinder#renderConnections
*
* @param {boolean} [deferRender]
* Whether or not to defer render until series animation is finished.
* Used on first render.
*/
renderConnections(deferRender) {
if (deferRender) {
// Render after series are done animating
this.chart.series.forEach(function (series) {
const render = function () {
// Find pathfinder connections belonging to this series
// that haven't rendered, and render them now.
const pathfinder = series.chart.pathfinder, conns = pathfinder && pathfinder.connections || [];
conns.forEach(function (connection) {
if (connection.fromPoint &&
connection.fromPoint.series === series) {
connection.render();
}
});
if (series.pathfinderRemoveRenderEvent) {
series.pathfinderRemoveRenderEvent();
delete series.pathfinderRemoveRenderEvent;
}
};
if (series.options.animation === false) {
render();
}
else {
series.pathfinderRemoveRenderEvent = addEvent(series, 'afterAnimate', render);
}
});
}
else {
// Go through connections and render them
this.connections.forEach(function (connection) {
connection.render();
});
}
}
/**
* Get obstacles for the points in the chart. Does not include connecting
* lines from Pathfinder. Applies algorithmMargin to the obstacles.
*
* @function Highcharts.Pathfinder#getChartObstacles
*
* @param {Object} options
* Options for the calculation. Currently only
* `options.algorithmMargin`.
*
* @param {number} options.algorithmMargin
* The algorithm margin to use for the obstacles.
* @return {Array<object>}
* An array of calculated obstacles. Each obstacle is defined as an
* object with xMin, xMax, yMin and yMax properties.
*/
getChartObstacles(options) {
const series = this.chart.series, margin = pick(options.algorithmMargin, 0);
let obstacles = [], calculatedMargin;
for (let i = 0, sLen = series.length; i < sLen; ++i) {
if (series[i].visible && !series[i].options.isInternal) {
for (let j = 0, pLen = series[i].points.length, bb, point; j < pLen; ++j) {
point = series[i].points[j];
if (point.visible) {
bb = getPointBB(point);
if (bb) {
obstacles.push({
xMin: bb.xMin - margin,
xMax: bb.xMax + margin,
yMin: bb.yMin - margin,
yMax: bb.yMax + margin
});
}
}
}
}
}
// Sort obstacles by xMin for optimization
obstacles = obstacles.sort(function (a, b) {
return a.xMin - b.xMin;
});
// Add auto-calculated margin if the option is not defined
if (!defined(options.algorithmMargin)) {
calculatedMargin =
options.algorithmMargin =
calculateObstacleMargin(obstacles);
obstacles.forEach(function (obstacle) {
obstacle.xMin -= calculatedMargin;
obstacle.xMax += calculatedMargin;
obstacle.yMin -= calculatedMargin;
obstacle.yMax += calculatedMargin;
});
}
return obstacles;
}
/**
* Utility function to get metrics for obstacles:
* - Widest obstacle width
* - Tallest obstacle height
*
* @function Highcharts.Pathfinder#getObstacleMetrics
*
* @param {Array<object>} obstacles
* An array of obstacles to inspect.
*
* @return {Object}
* The calculated metrics, as an object with maxHeight and maxWidth
* properties.
*/
getObstacleMetrics(obstacles) {
let maxWidth = 0, maxHeight = 0, width, height, i = obstacles.length;
while (i--) {
width = obstacles[i].xMax - obstacles[i].xMin;
height = obstacles[i].yMax - obstacles[i].yMin;
if (maxWidth < width) {
maxWidth = width;
}
if (maxHeight < height) {
maxHeight = height;
}
}
return {
maxHeight: maxHeight,
maxWidth: maxWidth
};
}
/**
* Utility to get which direction to start the pathfinding algorithm
* (X vs Y), calculated from a set of marker options.
*
* @function Highcharts.Pathfinder#getAlgorithmStartDirection
*
* @param {Highcharts.ConnectorsMarkerOptions} markerOptions
* Marker options to calculate from.
*
* @return {boolean}
* Returns true for X, false for Y, and undefined for autocalculate.
*/
getAlgorithmStartDirection(markerOptions) {
const xCenter = markerOptions.align !== 'left' &&
markerOptions.align !== 'right', yCenter = markerOptions.verticalAlign !== 'top' &&
markerOptions.verticalAlign !== 'bottom';
return xCenter ?
(yCenter ? void 0 : false) : // When x is centered
(yCenter ? true : void 0); // When x is off-center
}
}
/**
* @name Highcharts.Pathfinder#algorithms
* @type {Highcharts.Dictionary<Function>}
*/
Pathfinder.prototype.algorithms = PathfinderAlgorithms;
/* *
*
* Default Export
*
* */
export default Pathfinder;
/* *
*
* API Options
*
* */
/**
* The default pathfinder algorithm to use for a chart. It is possible to define
* your own algorithms by adding them to the
* `Highcharts.Pathfinder.prototype.algorithms`
* object before the chart has been created.
*
* The default algorithms are as follows:
*
* `straight`: Draws a straight line between the connecting
* points. Does not avoid other points when drawing.
*
* `simpleConnect`: Finds a path between the points using right angles
* only. Takes only starting/ending points into
* account, and will not avoid other points.
*
* `fastAvoid`: Finds a path between the points using right angles
* only. Will attempt to avoid other points, but its
* focus is performance over accuracy. Works well with
* less dense datasets.
*
* @typedef {"fastAvoid"|"simpleConnect"|"straight"|string} Highcharts.PathfinderTypeValue
*/
''; // Keeps doclets above in JS file