highcharts
Version:
JavaScript charting framework
272 lines (271 loc) • 9.11 kB
JavaScript
/* *
*
* Networkgraph series
*
* (c) 2010-2025 Paweł Fus
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import NodesComposition from '../NodesComposition.js';
import SeriesRegistry from '../../Core/Series/SeriesRegistry.js';
const { series: { prototype: seriesProto, prototype: { pointClass: Point } } } = SeriesRegistry;
import U from '../../Core/Utilities.js';
const { addEvent, css, defined, extend, pick } = U;
/* *
*
* Class
*
* */
class NetworkgraphPoint extends Point {
/* *
*
* Functions
*
* */
/**
* Destroy point. If it's a node, remove all links coming out of this
* node. Then remove point from the layout.
* @private
*/
destroy() {
if (this.isNode) {
this.linksFrom.concat(this.linksTo).forEach(function (link) {
// Removing multiple nodes at the same time
// will try to remove link between nodes twice
if (link.destroyElements) {
link.destroyElements();
}
});
}
this.series.layout.removeElementFromCollection(this, this.series.layout[this.isNode ? 'nodes' : 'links']);
return Point.prototype.destroy.apply(this, arguments);
}
/**
* Return degree of a node. If node has no connections, it still has
* deg=1.
* @private
*/
getDegree() {
const deg = this.isNode ?
this.linksFrom.length + this.linksTo.length :
0;
return deg === 0 ? 1 : deg;
}
/**
* Get presentational attributes of link connecting two nodes.
* @private
*/
getLinkAttributes() {
const linkOptions = this.series.options.link, pointOptions = this.options;
return {
'stroke-width': pick(pointOptions.width, linkOptions.width),
stroke: (pointOptions.color || linkOptions.color),
dashstyle: (pointOptions.dashStyle || linkOptions.dashStyle),
opacity: pick(pointOptions.opacity, linkOptions.opacity, 1)
};
}
/**
* Get link path connecting two nodes.
* @private
* @return {Array<Highcharts.SVGPathArray>}
* Path: `['M', x, y, 'L', x, y]`
*/
getLinkPath() {
let left = this.fromNode, right = this.toNode;
// Start always from left to the right node, to prevent rendering
// labels upside down
if (left.plotX > right.plotX) {
left = this.toNode;
right = this.fromNode;
}
return [
['M', left.plotX || 0, left.plotY || 0],
['L', right.plotX || 0, right.plotY || 0]
];
/*
IDEA: different link shapes?
return [
'M',
from.plotX,
from.plotY,
'Q',
(to.plotX + from.plotX) / 2,
(to.plotY + from.plotY) / 2 + 15,
to.plotX,
to.plotY
];*/
}
/**
* Get mass fraction applied on two nodes connected to each other. By
* default, when mass is equal to `1`, mass fraction for both nodes
* equal to 0.5.
* @private
* @return {Highcharts.Dictionary<number>}
* For example `{ fromNode: 0.5, toNode: 0.5 }`
*/
getMass() {
const m1 = this.fromNode.mass, m2 = this.toNode.mass, sum = m1 + m2;
return {
fromNode: 1 - m1 / sum,
toNode: 1 - m2 / sum
};
}
/**
* Basic `point.init()` and additional styles applied when
* `series.draggable` is enabled.
* @private
*/
constructor(series, options, x) {
super(series, options, x);
if (this.series.options.draggable &&
!this.series.chart.styledMode) {
addEvent(this, 'mouseOver', function () {
css(this.series.chart.container, { cursor: 'move' });
});
addEvent(this, 'mouseOut', function () {
css(this.series.chart.container, { cursor: 'default' });
});
}
}
/**
* @private
*/
isValid() {
return !this.isNode || defined(this.id);
}
/**
* Redraw link's path.
* @private
*/
redrawLink() {
const path = this.getLinkPath();
let attribs;
if (this.graphic) {
this.shapeArgs = {
d: path
};
if (!this.series.chart.styledMode) {
attribs = this.series.pointAttribs(this);
this.graphic.attr(attribs);
(this.dataLabels || []).forEach(function (label) {
if (label) {
label.attr({
opacity: attribs.opacity
});
}
});
}
this.graphic.animate(this.shapeArgs);
// Required for dataLabels
const start = path[0];
const end = path[1];
if (start[0] === 'M' && end[0] === 'L') {
this.plotX = (start[1] + end[1]) / 2;
this.plotY = (start[2] + end[2]) / 2;
}
}
}
/**
* Common method for removing points and nodes in networkgraph. To
* remove `link`, use `series.data[index].remove()`. To remove `node`
* with all connections, use `series.nodes[index].remove()`.
* @private
* @param {boolean} [redraw=true]
* Whether to redraw the chart or wait for an explicit call. When
* doing more operations on the chart, for example running
* `point.remove()` in a loop, it is best practice to set
* `redraw` to false and call `chart.redraw()` after.
* @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=false]
* Whether to apply animation, and optionally animation
* configuration.
*/
remove(redraw, animation) {
const point = this, series = point.series, nodesOptions = series.options.nodes || [];
let index, i = nodesOptions.length;
// For nodes, remove all connected links:
if (point.isNode) {
// Temporary disable series.points array, because
// Series.removePoint() modifies it
series.points = [];
// Remove link from all nodes collections:
[]
.concat(point.linksFrom)
.concat(point.linksTo)
.forEach(function (linkFromTo) {
// Incoming links
index = linkFromTo.fromNode.linksFrom.indexOf(linkFromTo);
if (index > -1) {
linkFromTo.fromNode.linksFrom.splice(index, 1);
}
// Outcoming links
index = linkFromTo.toNode.linksTo.indexOf(linkFromTo);
if (index > -1) {
linkFromTo.toNode.linksTo.splice(index, 1);
}
// Remove link from data/points collections
seriesProto.removePoint.call(series, series.data.indexOf(linkFromTo), false, false);
});
// Restore points array, after links are removed
series.points = series.data.slice();
// Proceed with removing node. It's similar to
// Series.removePoint() method, but doesn't modify other arrays
series.nodes.splice(series.nodes.indexOf(point), 1);
// Remove node options from config
while (i--) {
if (nodesOptions[i].id === point.options.id) {
series.options.nodes.splice(i, 1);
break;
}
}
if (point) {
point.destroy();
}
// Run redraw if requested
series.isDirty = true;
series.isDirtyData = true;
if (redraw) {
series.chart.redraw(redraw);
}
}
else {
series.removePoint(series.data.indexOf(point), redraw, animation);
}
}
/**
* Render link and add it to the DOM.
* @private
*/
renderLink() {
let attribs;
if (!this.graphic) {
this.graphic = this.series.chart.renderer
.path(this.getLinkPath())
.addClass(this.getClassName(), true)
.add(this.series.group);
if (!this.series.chart.styledMode) {
attribs = this.series.pointAttribs(this);
this.graphic.attr(attribs);
(this.dataLabels || []).forEach(function (label) {
if (label) {
label.attr({
opacity: attribs.opacity
});
}
});
}
}
}
}
extend(NetworkgraphPoint.prototype, {
setState: NodesComposition.setNodeState
});
/* *
*
* Default Export
*
* */
export default NetworkgraphPoint;