@springernature/nn-charts
Version:
Visualization for DAS products
932 lines (894 loc) • 46.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var d3 = _interopRequireWildcard(require("d3"));
var _treeChartHelpers = require("../../utils/tree-chart-helpers");
var _shapeGeneration = require("../../utils/shape-generation");
var _common = require("../../utils/common");
var _tooltips = require("../../utils/tooltips");
var _zoomControls = require("../../utils/zoom-controls");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { "default": e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n["default"] = e, t && t.set(e, n), n; }
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
var DEFAULT_CONFIG = {
svgBasePanelClassName: "knowledge-graph-base-panel",
svgBasePanelWidth: "100%",
svgBasePanelHeight: 700,
marginOffsets: {
top: 50,
right: 50,
bottom: 50,
left: 50
},
graphLayout: {
circleRadiusScaleDomain: [0, 10],
circleRadiusScaleRange: [0, 20],
chargeStrength: -6000,
chargeDistanceMax: 2400,
forceLinkStrength: 1,
linkDistance: 150,
linkStrength: 1,
strokeFill: "#a0a0a0",
strokeOpacity: 0.15,
strokeWidth: 1.3
},
zoomControls: {
isVisible: true,
position: "bottom",
// top, bottom
placement: "right",
// left , right, center
icons: ["zoom-in", "zoom-out", "zoom-centre"],
chartZoomMinimum: 1,
chartZoomMaximum: 10,
zoomLabelDisplayThreshold: 1.5,
zoomScaleFactor: 1.25
},
legend: {
isVisible: true,
position: "bottom",
// top, bottom
placement: "right",
// left , right, center
legendItems: [] // array of objects where object should have name (string), color (hex code string) and swatchSymbol ('circle' | 'line')
},
tooltip: {
isVisible: true,
tooltipContainerClass: "tooltip-container",
headerTemplate: null,
bodyTemplate: null,
footerTemplate: null
},
nodes: {
nodeIdentifierKey: "nodeId",
// unique identifer of each node object
selectedNodeIdentifierValue: "",
// value to be passed by consumer of this chart
nodesBeScaledByKey: "articles",
// key from node object to be used for scaling nodes
nodeHighlightShadowColor: "#FF824A52",
nodesBeHighlightedByKey: "",
// country - used for highlighting nodes based on key passed by consumer
selectedNodeHighlightByKeyValue: [] // value of nodesBeHighlightedByKey for the selected node
},
links: {
startNodeIdentifier: "startNodeIdentifier",
endNodeIdentifier: "endNodeIdentifier"
},
label: {
nodes: [],
links: [],
displayNodeLabels: false
}
};
var KnowledgeGraphChart = exports["default"] = /*#__PURE__*/function () {
function KnowledgeGraphChart(element, config, loadingStartedCb, loadingEndedCb, onErrorCb, onTooltipFooterBtnClick) {
_classCallCheck(this, KnowledgeGraphChart);
this.config = (0, _common.updateNestedProperties)(DEFAULT_CONFIG, config);
this.chartParentElement = element;
this.loadingStartedCallback = loadingStartedCb;
this.loadingEndedCallback = loadingEndedCb;
this.onErrorCallback = onErrorCb;
this.onTooltipFooterBtnClickCallback = onTooltipFooterBtnClick;
this.circleRadiusScale = d3.scalePow().exponent(0.5);
// window.addEventListener("resize", () => this.resizeSvgContent());
}
return _createClass(KnowledgeGraphChart, [{
key: "render",
value: function render(_ref) {
var _this = this;
var nodes = _ref.nodes,
links = _ref.links;
try {
if (!nodes || !links) return alert("Chart cannot be built as data is not available");
if (this.loadingStartedCallback) this.loadingStartedCallback();
(0, _common.clearSvgBasePanel)(this.config.svgBasePanelClassName);
// initialize nodes and edges
this.nodes = nodes;
this.links = links;
this.svgBasePanel = (0, _shapeGeneration.createBaseSVGPanel)(this.chartParentElement, this.config.svgBasePanelClassName, this.config.svgBasePanelWidth, this.config.svgBasePanelHeight);
this.circleRadiusScale.domain(this.config.graphLayout.circleRadiusScaleDomain).range(this.config.graphLayout.circleRadiusScaleRange);
// sort node by name
this.nodes = (0, _treeChartHelpers.sortByKey)(this.nodes, "name");
// format nodeId field for each object of the nodes
this.nodes.forEach(function (node) {
node.nodeIdentierFormatted = node[_this.config.nodes.nodeIdentifierKey].replaceAll(".", "_");
});
var maximumDataRange = this.findMaximumValueFromList(this.nodes, this.config.nodes.nodesBeScaledByKey);
this.updateCircleRadiusRangeDefinition(maximumDataRange);
this.addditionalNodes = [];
// filter links
this.filteredLinks = this.links.filter(function (d) {
// add formatted Id attributes to ease processing ...
d.startNodeIdFormatted = d[_this.config.links.startNodeIdentifier].replaceAll(".", "_");
d.endNodeIdFormatted = d[_this.config.links.endNodeIdentifier].replaceAll(".", "_");
_this.addditionalNodes.push(d[_this.config.links.endNodeIdentifier]);
return d[_this.config.links.startNodeIdentifier];
});
this.filteredNodes = this.nodes.filter(function (d) {
return d[_this.config.nodes.nodeIdentifierKey] === _this.config.nodes.selectedNodeIdentifierValue || _this.addditionalNodes.indexOf(d[_this.config.nodes.nodeIdentifierKey]) !== -1;
});
this.filteredNodes.forEach(function (d, i) {
d.id = d[_this.config.nodes.nodeIdentifierKey];
// same object is pushed twice - courtesy from prototype code written by James Bayliss
_this.config.label.nodes.push({
node: d
});
_this.config.label.nodes.push({
node: d
});
_this.config.label.links.push({
source: i * 2,
target: i * 2 + 1
});
});
this.filteredLinks.forEach(function (d) {
d.source = d[_this.config.links.startNodeIdentifier];
d.target = d[_this.config.links.endNodeIdentifier];
});
// avoid labels overlapping node circles
d3.forceSimulation(this.config.label.nodes).force("charge", d3.forceManyBody().strength(-50)).force("link", d3.forceLink(this.config.label.links).distance(0).strength(2)).stop();
this.graphLayout = this.addSimulationToGraphLayout();
// store the indices of source and target links
this.adjlist = [];
this.filteredLinks.forEach(function (d) {
_this.adjlist[d.source.index + "-" + d.target.index] = true;
_this.adjlist[d.target.index + "-" + d.source.index] = true;
});
this.svgBasePanel = this.resizeSvgBasePanel();
this.chartZoom = this.getZoomBehaviour(this.config.zoomControls.chartZoomMinimum, this.config.zoomControls.chartZoomMaximum);
var _this$getSvgPanelCent = this.getSvgPanelCenterTranslateValues(),
initialOriginX = _this$getSvgPanelCent.initialOriginX,
initialOriginY = _this$getSvgPanelCent.initialOriginY;
this.initialOriginX = initialOriginX;
this.initialOriginY = initialOriginY;
this.initialZoomTransform = d3.zoomIdentity.translate(this.initialOriginX, this.initialOriginY).scale(1);
this.chartZoom.transform(this.svgBasePanel, this.initialZoomTransform);
this.baseGroup = this.appendBaseGroup();
this.linksGroupElement = this.appendEdgesToBaseGroup();
this.nodesGroupElement = this.appendNodesToBaseGroup();
this.nodeCirclesElement = this.appendCircleToBaseNodeGroup();
this.appendLabelToBaseNodeGroup();
// add zoom handler
this.chartZoom.on("zoom", function () {
return _this.zoomHandler();
});
this.svgBasePanel.call(this.chartZoom).on("dblclick.zoom", null);
// add drag support for base node group
var dragBehavior = this.getDragBehaviour();
// apply the drag behavior to the node group elements
this.nodesGroupElement.call(dragBehavior);
// construct tooltip
if (this.config.tooltip.isVisible) {
var customTemplate = {
header: this.config.tooltip.headerTemplate,
body: this.config.tooltip.bodyTemplate,
footer: this.config.tooltip.footerTemplate
};
var tooltipTemplate = this.constructTooltip(undefined, false, customTemplate);
var svgContainer = this.getSvgContainerElement();
(0, _common.appendElementToTarget)(tooltipTemplate, svgContainer);
this.registerSvgContainerListeners(svgContainer);
this.hideTooltip();
}
// construct legends
if (this.config.legend.isVisible) this.constructLegends();
// construct zoom controls
var zoomControls = _objectSpread(_objectSpread({}, this.config.zoomControls), {}, {
onZoomControlsContainerCreated: function onZoomControlsContainerCreated(element) {
return element.style.top = "2rem";
}
});
if (this.config.zoomControls.isVisible) (0, _zoomControls.constructZoomControls)(this.config.svgBasePanelClassName, zoomControls);
// add zoom control click handlers
this.registerZoomControlListeners();
} catch (error) {
console.error("Error in KnowledgeGraphChart.render function: ", error);
if (this.onErrorCallback) this.onErrorCallback(error);
}
if (this.loadingEndedCallback) this.loadingEndedCallback();
}
}, {
key: "getSvgBasePanelDimensions",
value: function getSvgBasePanelDimensions() {
var _this$svgBasePanel$no = this.svgBasePanel.node(),
svgBasePanelWidth = _this$svgBasePanel$no.clientWidth,
svgBasePanelHeight = _this$svgBasePanel$no.clientHeight;
return {
svgBasePanelWidth: parseFloat(svgBasePanelWidth),
svgBasePanelHeight: parseFloat(svgBasePanelHeight)
};
}
}, {
key: "registerSvgContainerListeners",
value: function registerSvgContainerListeners(svgContainer) {
var _this2 = this;
d3.select(svgContainer).on("click", function () {
var target = d3.event.target;
if (!target.classList.contains("node") && !target.classList.contains("link")) _this2.hideTooltip();
});
}
}, {
key: "registerZoomControlListeners",
value: function registerZoomControlListeners() {
var _this3 = this;
d3.selectAll(".zoom-in").on("click", function () {
return _this3.chartZoom.scaleBy(_this3.svgBasePanel.transition().duration(50), _this3.config.zoomControls.zoomScaleFactor);
});
d3.selectAll(".zoom-out").on("click", function () {
return _this3.chartZoom.scaleBy(_this3.svgBasePanel.transition().duration(50), 1 / _this3.config.zoomControls.zoomScaleFactor);
});
d3.selectAll(".zoom-centre").on("click", function () {
var newTransform = d3.zoomIdentity.translate(_this3.initialOriginX, _this3.initialOriginY)
// .scale(hRatio < wRatio ? hRatio : wRatio);
.scale(1);
_this3.chartZoom.transform(_this3.svgBasePanel, newTransform);
});
}
}, {
key: "resizeSvgContent",
value: function resizeSvgContent() {
var _this$getSvgBasePanel = this.getSvgBasePanelDimensions(),
svgBasePanelWidth = _this$getSvgBasePanel.svgBasePanelWidth,
svgBasePanelHeight = _this$getSvgBasePanel.svgBasePanelHeight;
var baseGroup = d3.select(".base-group");
// Get the initial bounding box of the base group
var baseGroupBBox = baseGroup.node().getBBox();
var initialWidth = baseGroupBBox.width,
initialHeight = baseGroupBBox.height;
var aspectRatio = initialWidth / initialHeight;
var newWidth = svgBasePanelWidth;
var newHeight = svgBasePanelHeight;
// Calculate the new dimensions while maintaining the aspect ratio
if (svgBasePanelWidth / svgBasePanelHeight > aspectRatio) {
newWidth = svgBasePanelHeight * aspectRatio;
} else {
newHeight = svgBasePanelWidth / aspectRatio;
}
// Calculate scale based on the new dimensions
var scale = newWidth / initialWidth;
// Calculate scale based on the smaller dimension to maintain aspect ratio
// const scale = Math.min(
// svgBasePanelWidth / initialWidth,
// svgBasePanelHeight / initialHeight
// );
// Calculate the translation to center the content
var translateX = (svgBasePanelWidth - newWidth) / 2;
var translateY = (svgBasePanelHeight - newHeight) / 2;
// Calculate the translation to center the content
// const translateX = (svgBasePanelWidth - initialWidth * scale) / 2;
// const translateY = (svgBasePanelHeight - initialHeight * scale) / 2;
// Apply the scale and translation to the base group
baseGroup.attr("transform", "translate(".concat(translateX, ", ").concat(translateY, ") scale(").concat(scale, ")"));
}
}, {
key: "resizeSvgBasePanel",
value: function resizeSvgBasePanel() {
return d3.selectAll(".".concat(this.config.svgBasePanelClassName)).attr("width", this.config.svgBasePanelWidth).attr("height", this.config.svgBasePanelHeight).on("click", function () {
return d3.event.preventDefault();
}).on("dblclick", function () {
return d3.event.preventDefault();
});
}
}, {
key: "appendBaseGroup",
value: function appendBaseGroup() {
return this.svgBasePanel.append("g").attr("class", "base-group").attr("transform", "translate(".concat(this.initialOriginX, ",").concat(this.initialOriginY, ") scale(", 1, ")"));
}
}, {
key: "appendEdgesToBaseGroup",
value: function appendEdgesToBaseGroup() {
var _this4 = this;
return this.baseGroup.append("g").attr("class", "links").selectAll("line").data(this.filteredLinks).enter().append("line").attr("class", function (d) {
return "link source-".concat(d.startNodeIdFormatted, " target-").concat(d.endNodeIdFormatted);
}).attr("stroke", this.config.graphLayout.strokeFill).style("stroke-width", this.config.graphLayout.strokeWidth).style("stroke-opacity", this.config.graphLayout.strokeOpacity).on("mouseover", function (d) {
var _this4$clickedLink;
// Check if this is not the clicked link
if (d3.event.target !== ((_this4$clickedLink = _this4.clickedLink) === null || _this4$clickedLink === void 0 ? void 0 : _this4$clickedLink.element) && !_this4.isLinkConnectedToClickedNode(d)) {
_this4.highlightEdge(d3.event.target, d.startNodeIdFormatted, d.endNodeIdFormatted);
}
}).on("mouseout", function (d) {
var _this4$clickedLink2;
// Check if this is not the clicked link
if (d3.event.target !== ((_this4$clickedLink2 = _this4.clickedLink) === null || _this4$clickedLink2 === void 0 ? void 0 : _this4$clickedLink2.element) && !_this4.isLinkConnectedToClickedNode(d)) {
_this4.dehighlightEdge(d3.event.target, d.startNodeIdFormatted, d.endNodeIdFormatted);
}
}).on("click", function (d) {
// remove highlighting from all nodes and edges
_this4.removeHighlightingFromKeyNodesEdges();
var link = d3.event.target;
// Set the clicked link as the selected one
_this4.clickedLink = {
element: link,
data: d
};
// Highlight the edge and connected nodes
_this4.highlightEdge(link, d.startNodeIdFormatted, d.endNodeIdFormatted);
_this4.highlightEdgeLinkedNodes(d.startNodeIdFormatted, d.endNodeIdFormatted);
// Clear any previously clicked node
_this4.clickedNode = null;
// Show tooltip
if (_this4.config.tooltip.isVisible) _this4.showTooltip(link, d, false);
});
}
}, {
key: "appendNodesToBaseGroup",
value: function appendNodesToBaseGroup() {
return this.baseGroup.append("g").attr("class", "nodes").selectAll("g").data(this.filteredNodes).enter().append("g").attr("class", function (d, i) {
return "base-node-group base-node-group-".concat(d.nodeIdentierFormatted, " base-node-group-").concat(i);
});
}
}, {
key: "appendCircleToBaseNodeGroup",
value: function appendCircleToBaseNodeGroup() {
var _this5 = this;
return d3.selectAll(".base-node-group").append("circle").attr("r", function (d) {
return d[_this5.config.nodes.nodesBeScaledByKey] ? _this5.circleRadiusScale(d[_this5.config.nodes.nodesBeScaledByKey]) : 5;
}).attr("class", function (d, i) {
var isRootNode = d[_this5.config.nodes.nodeIdentifierKey] === _this5.config.nodes.selectedNodeIdentifierValue;
var highlightClass = _this5.shouldHighlightNode(d, _this5.config.nodes.nodesBeHighlightedByKey, _this5.config.nodes.selectedNodeHighlightByKeyValue) ? "node--highlighted-".concat(_this5.config.nodes.nodesBeHighlightedByKey) : "";
return "nodeContentElement node node-".concat(i, " ").concat(isRootNode ? "node--root" : "", " ").concat(highlightClass);
})
// add mouse interaction event handlers for node circles
.on("mouseover", function (d) {
var _this5$clickedNode;
var isRootNode = d[_this5.config.nodes.nodeIdentifierKey] === _this5.config.nodes.selectedNodeIdentifierValue;
_this5.toggleHoveredNodeBorder(true, isRootNode);
if (((_this5$clickedNode = _this5.clickedNode) === null || _this5$clickedNode === void 0 || (_this5$clickedNode = _this5$clickedNode.data) === null || _this5$clickedNode === void 0 ? void 0 : _this5$clickedNode.nodeIdentierFormatted) !== d.nodeIdentierFormatted) _this5.toggleHoveredNodeLabel(d, true);
}).on("mouseout", function (d) {
var _this5$clickedNode2;
var isRootNode = d[_this5.config.nodes.nodeIdentifierKey] === _this5.config.nodes.selectedNodeIdentifierValue;
_this5.toggleHoveredNodeBorder(false, isRootNode);
if (((_this5$clickedNode2 = _this5.clickedNode) === null || _this5$clickedNode2 === void 0 || (_this5$clickedNode2 = _this5$clickedNode2.data) === null || _this5$clickedNode2 === void 0 ? void 0 : _this5$clickedNode2.nodeIdentierFormatted) !== d.nodeIdentierFormatted) _this5.toggleHoveredNodeLabel(d, false);
}).on("click", function (d) {
// remove highlighting from all nodes and edges (for the node which was previously clicked)
_this5.removeHighlightingFromKeyNodesEdges();
// add highlighting for clicked node and its connecting edges & nodes
_this5.clickedNode = {
element: d3.event.target,
data: d
};
_this5.highlightClickedNode(_this5.clickedNode.element);
_this5.highlightClickedNodesEdges(_this5.clickedNode.element);
if (_this5.config.tooltip.isVisible) {
_this5.hideTooltip(); // hide old tooltip
_this5.showTooltip(_this5.clickedNode.element, d); // show tooltip with updated content
}
});
}
}, {
key: "appendLabelToBaseNodeGroup",
value: function appendLabelToBaseNodeGroup() {
var _this6 = this;
return d3.selectAll(".base-node-group").append("text").attr("class", function (d, i) {
return "d-none nodeContentElement nodeLabels node-label-".concat(i, " node-label-").concat(d.nodeIdentierFormatted);
}).attr("x", function (d) {
return d[_this6.config.nodes.nodesBeScaledByKey] ? _this6.circleRadiusScale(d[_this6.config.nodes.nodesBeScaledByKey]) + 2.5 : 10;
}).attr("y", function (d) {
return d[_this6.config.nodes.nodesBeScaledByKey] ? _this6.circleRadiusScale(d[_this6.config.nodes.nodesBeScaledByKey]) + 2.5 : 10;
}).style("font-size", "14px").style("alignment-baseline", "central").text(function (d) {
return d.name;
});
}
}, {
key: "shouldHighlightNode",
value: function shouldHighlightNode(nodeData, nodesHighlightedByKey, selectedNodeHighlightByKeyValue) {
if (!nodeData[nodesHighlightedByKey]) return false;
return Array.isArray(nodeData[nodesHighlightedByKey]) && selectedNodeHighlightByKeyValue.length && nodeData[nodesHighlightedByKey].some(function (highlightedNodeKeyValue) {
return selectedNodeHighlightByKeyValue.includes(highlightedNodeKeyValue);
});
}
}, {
key: "getSvgContainerElement",
value: function getSvgContainerElement() {
var _document$getElements;
var svgPanel = (_document$getElements = document.getElementsByClassName("".concat(this.config.svgBasePanelClassName))) === null || _document$getElements === void 0 ? void 0 : _document$getElements[0];
var svgContainer = svgPanel.parentElement;
return svgContainer;
}
}, {
key: "getTooltipElement",
value: function getTooltipElement() {
return document.querySelector(".".concat(this.config.tooltip.tooltipContainerClass));
}
}, {
key: "resetChartStyling",
value: function resetChartStyling() {
d3.selectAll("*").classed("semi-transparent", false);
}
}, {
key: "removeTooltipFrame",
value: function removeTooltipFrame() {
if (this.config.tooltip.isVisible) {
var tooltipFrame = this.getTooltipElement();
tooltipFrame === null || tooltipFrame === void 0 || tooltipFrame.remove();
}
}
}, {
key: "resizeNodes",
value: function resizeNodes(resizeNodesByKey) {
var _this7 = this;
var maximumDataRange = this.findMaximumValueFromList(this.nodes, resizeNodesByKey);
this.updateCircleRadiusRangeDefinition(maximumDataRange);
d3.selectAll(".base-node-group").select("circle").attr("r", function (d) {
return d[resizeNodesByKey] ? _this7.circleRadiusScale(d[resizeNodesByKey]) : 5;
});
d3.selectAll(".base-node-group").select("text").attr("x", function (d) {
return d[resizeNodesByKey] ? _this7.circleRadiusScale(d[resizeNodesByKey]) + 2.5 : 10;
}).attr("y", function (d) {
return d[resizeNodesByKey] ? _this7.circleRadiusScale(d[resizeNodesByKey]) + 2.5 : 10;
});
}
}, {
key: "highlightNodes",
value: function highlightNodes(nodesHighlightedByKey, selectedNodeHighlightByKeyValue) {
var _this8 = this;
d3.selectAll(".base-node-group").select("circle").attr("class", function (d, i, nodes) {
var currentClasses = Array.from(nodes[i].classList);
var highlightClass = nodesHighlightedByKey ? "node--highlighted-".concat(nodesHighlightedByKey) : "";
if (!nodesHighlightedByKey) {
var classToRemove = "node--highlighted-".concat(_this8.currentNodesHighlightedByKey);
if (currentClasses.includes(classToRemove)) {
nodes[i].classList.remove(classToRemove);
}
currentClasses = Array.from(nodes[i].classList);
} else _this8.currentNodesHighlightedByKey = nodesHighlightedByKey;
var shouldHighlight = nodesHighlightedByKey && _this8.shouldHighlightNode(d, nodesHighlightedByKey, selectedNodeHighlightByKeyValue);
return [].concat(_toConsumableArray(currentClasses), [shouldHighlight ? highlightClass : null]).filter(Boolean).join(" ");
});
}
}, {
key: "constructTooltip",
value: function constructTooltip() {
var _this9 = this;
var data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var useNodeTemplate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
var customTemplate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
var _data$name = data.name,
name = _data$name === void 0 ? "" : _data$name,
_data$articles = data.articles,
articles = _data$articles === void 0 ? 0 : _data$articles,
_data$weight = data.weight,
weight = _data$weight === void 0 ? 0 : _data$weight;
var isRootNode = data ? data[this.config.nodes.nodeIdentifierKey] === this.config.nodes.selectedNodeIdentifierValue : false;
var _ref2 = customTemplate || {},
headerTemplate = _ref2.header,
footerTemplate = _ref2.footer;
var header = {
showHeader: true,
headerTemplate: headerTemplate,
title: useNodeTemplate ? name : "".concat(weight, " publication").concat(weight > 1 ? "s" : ""),
subtitle: useNodeTemplate ? "".concat(articles, " publications") : "co-authored"
};
var footer = {
showFooter: useNodeTemplate && !isRootNode,
footerTemplate: footerTemplate,
btnText: "View Profile",
btnClickHandler: function btnClickHandler() {
_this9.removeTooltipFrame();
_this9.onTooltipFooterBtnClickCallback(data);
}
};
return (0, _tooltips.getTooltipTemplate)(this.config.tooltip.tooltipContainerClass, header, footer);
}
}, {
key: "showTooltip",
value: function showTooltip(clickedElement, datum) {
var useNodeTemplate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
this.updateTooltipContent(datum, useNodeTemplate);
var tooltipFrame = this.getTooltipElement();
// Tooltip placement logic
var nodeRect = clickedElement.getBoundingClientRect();
var svgPanel = this.svgBasePanel.node().getBoundingClientRect(); // Assuming `this.svgPanelElement` is a reference to the SVG panel
// Calculate the position relative to the SVG panel
var top = nodeRect.top - svgPanel.top - tooltipFrame.offsetHeight / 2;
var left = nodeRect.left - svgPanel.left + nodeRect.width + 10; // Position to the right of the node
// Check and adjust for right boundary of SVG panel
if (left + tooltipFrame.offsetWidth > svgPanel.width) {
left = nodeRect.left - svgPanel.left - tooltipFrame.offsetWidth - 10; // Position to the left of the node
}
// Check and adjust for top/bottom boundaries of SVG panel
if (top < 0) {
top = 0; // Align to top of SVG panel if too high
} else if (top + tooltipFrame.offsetHeight > svgPanel.height) {
top = svgPanel.height - tooltipFrame.offsetHeight; // Align to bottom of SVG panel if too low
}
tooltipFrame.style.top = "".concat(top + 200, "px"); // Positioned 200px with respect to the top of the node
tooltipFrame.style.left = "".concat(left + 30, "px"); // Positioned 30px to the right of the node
}
}, {
key: "hideTooltip",
value: function hideTooltip() {
var tooltipFrame = this.getTooltipElement();
if (tooltipFrame) tooltipFrame.classList.add("d-none");
}
}, {
key: "updateTooltipContent",
value: function updateTooltipContent(datum) {
var useNodeTemplate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
var tooltipFrame = this.getTooltipElement();
if (!tooltipFrame) {
console.error("Tooltip container not found.");
return;
}
var tooltipTemplate = this.constructTooltip(datum, useNodeTemplate);
tooltipFrame.replaceWith(tooltipTemplate);
}
}, {
key: "addSimulationToGraphLayout",
value: function addSimulationToGraphLayout() {
var _this10 = this;
return d3.forceSimulation(this.filteredNodes).force("charge", d3.forceManyBody().strength(this.config.graphLayout.chargeStrength).distanceMax(this.config.graphLayout.chargeDistanceMax)).force("center", d3.forceCenter(1, 1)).force("x", d3.forceX(1).strength(1)).force("y", d3.forceY(1).strength(1)).force("link", d3.forceLink(this.filteredLinks).id(function (d) {
return d[_this10.config.nodes.nodeIdentifierKey];
}).distance(this.config.graphLayout.linkDistance).strength(this.config.graphLayout.linkStrength)).force("collide", d3.forceCollide().radius(function (d) {
return d[_this10.config.nodes.nodesBeScaledByKey] ? _this10.circleRadiusScale(d[_this10.config.nodes.nodesBeScaledByKey]) + 15 : 5;
}).iterations(3)).on("tick", function () {
return _this10.ticked();
});
}
}, {
key: "updateNodePositionAttributes",
value: function updateNodePositionAttributes(node) {
var _this11 = this;
node.attr("transform", function (d) {
return "translate(".concat(_this11.fixna(d.x), ",").concat(_this11.fixna(d.y), ")");
});
}
}, {
key: "fixna",
value: function fixna(x) {
return isFinite(x) ? x : 0;
}
}, {
key: "ticked",
value: function ticked() {
this.updateNodePositionAttributes(this.nodesGroupElement);
this.updateLinkPositionAttributes(this.linksGroupElement);
}
}, {
key: "updateLinkPositionAttributes",
value: function updateLinkPositionAttributes(link) {
var _this12 = this;
link.attr("x1", function (d) {
return _this12.fixna(d.source.x);
}).attr("y1", function (d) {
return _this12.fixna(d.source.y);
}).attr("x2", function (d) {
return _this12.fixna(d.target.x);
}).attr("y2", function (d) {
return _this12.fixna(d.target.y);
});
}
}, {
key: "areNodesNeighbouring",
value: function areNodesNeighbouring(a, b) {
return a === b || this.adjlist["".concat(a, "-").concat(b)];
}
}, {
key: "findMaximumValueFromList",
value: function findMaximumValueFromList(list, key) {
return d3.max(list, function (d) {
return d[key];
});
}
}, {
key: "updateCircleRadiusRangeDefinition",
value: function updateCircleRadiusRangeDefinition(maxDataValue) {
return this.circleRadiusScale.domain([0, maxDataValue]);
}
}, {
key: "getZoomBehaviour",
value: function getZoomBehaviour(minZoomScale, maxZoomScale) {
return d3.zoom().scaleExtent([minZoomScale, maxZoomScale]);
}
}, {
key: "getDragBehaviour",
value: function getDragBehaviour() {
var _this13 = this;
return d3.drag().on("start", function (d) {
return _this13.dragStarted(d);
}).on("drag", function (d) {
return _this13.dragged(d);
}).on("end", function (d) {
return _this13.dragEnded(d);
});
}
}, {
key: "getSvgPanelCenterTranslateValues",
value: function getSvgPanelCenterTranslateValues() {
var _this$getSvgBasePanel2 = this.getSvgBasePanelDimensions(),
svgBasePanelWidth = _this$getSvgBasePanel2.svgBasePanelWidth,
svgBasePanelHeight = _this$getSvgBasePanel2.svgBasePanelHeight;
var initialOriginX = svgBasePanelWidth / 2;
var initialOriginY = svgBasePanelHeight / 2;
return {
initialOriginX: initialOriginX,
initialOriginY: initialOriginY
};
}
}, {
key: "dragStarted",
value: function dragStarted(d) {
d3.event.sourceEvent.stopPropagation();
if (!d3.event.active) this.graphLayout.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
}, {
key: "dragged",
value: function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
}, {
key: "dragEnded",
value: function dragEnded(d) {
if (!d3.event.active) this.graphLayout.alphaTarget(0);
d.fx = null;
d.fy = null;
}
}, {
key: "zoomHandler",
value: function zoomHandler() {
// determine current transform ... and store in config var.
var _d3$event$transform = d3.event.transform,
x = _d3$event$transform.x,
y = _d3$event$transform.y,
k = _d3$event$transform.k;
var baseGroup = d3.select(".base-group");
// Get the SVG base panel width
var _this$getSvgBasePanel3 = this.getSvgBasePanelDimensions(),
svgBasePanelWidth = _this$getSvgBasePanel3.svgBasePanelWidth,
svgBasePanelHeight = _this$getSvgBasePanel3.svgBasePanelHeight;
// Get the bounding box of the base group
var baseGroupBBox = baseGroup.node().getBBox();
var baseGroupWidth = baseGroupBBox.width,
baseGroupHeight = baseGroupBBox.height;
// Calculate the horizontal & vertical center translation
var translateX = (svgBasePanelWidth - baseGroupWidth) / 2 - baseGroupBBox.x;
var translateY = (svgBasePanelHeight - baseGroupHeight) / 2 - baseGroupBBox.y;
// correctly scale SVG base group during user zoom in/out
d3.select(".base-group").attr("transform", "translate(".concat(x, ",").concat(y, ") scale(").concat(k, ")"));
//.attr("transform", `translate(${translateX},${translateY}) scale(${k})`);
// correctly scale SVG Circles during user zoom in/out
d3.selectAll(".nodeContentElement").attr("transform", "scale(".concat(1 / k, ")"));
// correctly scale SVG lines during user zoom in/out
d3.selectAll(".link").style("stroke-width", this.config.graphLayout.strokeWidth * (1 / k));
//if user has zoomed in beyond a certain threshold point, defined in config JSON, display all chart node labels ..
d3.selectAll(".nodeLabels").classed("d-none", k > this.config.zoomControls.zoomLabelDisplayThreshold ? false : true);
}
}, {
key: "isLinkConnectedToClickedNode",
value: function isLinkConnectedToClickedNode(linkData) {
var _this$clickedNode;
if (!((_this$clickedNode = this.clickedNode) !== null && _this$clickedNode !== void 0 && _this$clickedNode.data)) return false;
// Directly access the nodeIdFormatted of clickedNode
var clickedNodeId = this.clickedNode.data.nodeIdentierFormatted;
return clickedNodeId === linkData.startNodeIdFormatted || clickedNodeId === linkData.endNodeIdFormatted;
}
/***** START : NODE HIGHLIGHTING functions ***/
}, {
key: "toggleHoveredNodeBorder",
value: function toggleHoveredNodeBorder(isNodeHovered, isRootNode) {
var selectedNode = d3.select(d3.event.target).node();
if (!selectedNode) return;
if (isRootNode) {
return d3.select(selectedNode).classed("node--root-hovered", isNodeHovered);
}
d3.select(selectedNode).classed("node--hovered", isNodeHovered);
}
}, {
key: "highlightConnectedNodes",
value: function highlightConnectedNodes(clickedNodeIndex) {
var _this14 = this;
this.nodeCirclesElement.classed("node--adjacent-highlighted", function (d) {
var isRootNode = d[_this14.config.nodes.nodeIdentifierKey] === _this14.config.nodes.selectedNodeIdentifierValue;
return !isRootNode && _this14.areNodesNeighbouring(clickedNodeIndex, d.index) ? true : false;
});
}
}, {
key: "highlightClickedNode",
value: function highlightClickedNode(node) {
d3.select(node).classed("node--highlighted", true);
}
}, {
key: "highlightEdgeLinkedNodes",
value: function highlightEdgeLinkedNodes(source, target) {
d3.selectAll(".base-node-group").style("opacity", 0.1).filter(".base-node-group-".concat(source, ", .base-node-group-").concat(target)).style("opacity", 1).selectAll(".node").classed("node--adjacent-highlighted", true);
}
/***** END : NODE HIGHLIGHTING functions ***/
/***** START : LINK HIGHLIGHTING functions ***/
}, {
key: "highlightEdge",
value: function highlightEdge(hoveredLinkElmt, source, target) {
// d3.selectAll(".link").style("opacity", 0.1);
d3.select(hoveredLinkElmt).style("opacity", 1).style("stroke-opacity", 1).classed("link--hovered", true);
this.displayEdgeLinkedNodeLabels(source, target);
}
}, {
key: "dehighlightEdge",
value: function dehighlightEdge(hoveredLinkElmt, source, target) {
d3.select(hoveredLinkElmt).style("stroke-opacity", this.config.graphLayout.strokeOpacity).classed("link--hovered", false);
// reset opacity for link and base-node-group
d3.selectAll(".link").style("opacity", 1);
d3.selectAll(".base-node-group").style("opacity", 1);
// hide labels
d3.selectAll(".nodeLabels").classed("d-none", true);
}
}, {
key: "removeHighlightingFromEdge",
value: function removeHighlightingFromEdge(selectedLineElmt, source, target) {
var _this$clickedLink;
// remove highlighting from selected link
d3.select(selectedLineElmt).style("stroke-opacity", this.config.graphLayout.strokeOpacity).classed("link--hovered", false);
d3.selectAll(".link").style("opacity", 1);
d3.selectAll(".base-node-group").style("opacity", 1);
// hide labels
d3.selectAll(".nodeLabels").classed("d-none", true);
// remove node highlighting
d3.selectAll(".node").classed("node--highlighted", false);
// Reapply highlighting for nodes connected to the clicked link
if (!this.clickedNode && ((_this$clickedLink = this.clickedLink) === null || _this$clickedLink === void 0 ? void 0 : _this$clickedLink.element) === selectedLineElmt) {
d3.select(".base-node-group-".concat(source)).classed("node--highlighted", true);
d3.select(".base-node-group-".concat(target)).classed("node--highlighted", true);
}
this.resetChartStyling();
this.displayEdgeLinkedNodeLabels(null, null);
}
}, {
key: "highlightClickedNodesEdges",
value: function highlightClickedNodesEdges(selectedElement) {
var _this15 = this;
var index = d3.select(selectedElement).datum().index;
this.toggleNodeLabelsDisplay(false);
// show or hide nodes based on whether they are adjacent or connected to the clicked node
this.nodesGroupElement.style("opacity", function (d) {
return _this15.areNodesNeighbouring(index, d.index) ? 1 : 0.1;
});
// highlight connected/neighbour nodes
this.highlightConnectedNodes(index);
// highlight connected edges
this.highlightConnectedEdges(index);
}
}, {
key: "highlightConnectedEdges",
value: function highlightConnectedEdges(clickedNodeIndex) {
this.linksGroupElement.style("opacity", function (d) {
return d.source.index === clickedNodeIndex || d.target.index === clickedNodeIndex ? 1 : 0.1;
}).style("stroke-opacity", function (d) {
return d.source.index === clickedNodeIndex || d.target.index === clickedNodeIndex ? 1 : 0.1;
}).classed("link--highlighted", function (d) {
return d.source.index === clickedNodeIndex || d.target.index === clickedNodeIndex ? true : false;
});
}
/***** END : NODE HIGHLIGHTING functions ***/
/***** START : COMMON TO BOTH NODE AND LINK HIGHLIGHTING ****/
}, {
key: "removeHighlightingFromKeyNodesEdges",
value: function removeHighlightingFromKeyNodesEdges() {
this.nodesGroupElement.style("opacity", 1);
// remove node circle highlighting
this.nodeCirclesElement.classed("node--highlighted", false);
// remove node circle highlighting from adjacent nodes
this.nodeCirclesElement.classed("node--adjacent-highlighted", false);
// remove link highlighting and link hovered state
this.linksGroupElement.style("opacity", 1).style("stroke-opacity", this.config.graphLayout.strokeOpacity).classed("link--highlighted", false).classed("link--hovered", false);
// hide node labels
d3.selectAll(".nodeLabels").classed("d-none", true);
}
/***** END : COMMON TO BOTH NODE AND LINK HIGHLIGHTING ****/
}, {
key: "toggleNodeLabelsDisplay",
value: function toggleNodeLabelsDisplay(displayNodeLabels) {
var nodeLabels = d3.selectAll(".nodeLabels");
nodeLabels.classed("d-none", !displayNodeLabels ? false : !nodeLabels.classed("d-none"));
}
}, {
key: "toggleHoveredNodeLabel",
value: function toggleHoveredNodeLabel(d, isHovered) {
d3.selectAll(".node-label-".concat(d.nodeIdentierFormatted)).classed("d-none", !isHovered);
}
}, {
key: "displayEdgeLinkedNodeLabels",
value: function displayEdgeLinkedNodeLabels(sourceNode, targetNode) {
// if coming from edge interation, display labels for source and target nodes
if (sourceNode) {
d3.selectAll(".node-label-".concat(sourceNode)).classed("d-none", false);
d3.selectAll(".node-label-".concat(targetNode)).classed("d-none", false);
return;
}
d3.selectAll(".nodeLabels").classed("d-none", true);
}
}, {
key: "constructLegends",
value: function constructLegends() {
var _this16 = this;
var basePanelElement = document.querySelector(".".concat(this.config.svgBasePanelClassName));
// create legend container
var legendContainerElement = document.createElement("div");
legendContainerElement.classList.add("legend-container");
// create legend header
var legendHeaderElemt = document.createElement("p");
legendHeaderElemt.textContent = "legend";
legendHeaderElemt.classList.add("legend-header--uppercase");
// Create the legend body
var legendBodyElement = document.createElement("div");
legendBodyElement.classList.add("legend-body");
if (this.config.legend.legendItems.length) {
// Create the unordered list
var ulElement = document.createElement("ul");
// Create list items and append to ul
this.config.legend.legendItems.forEach(function (legend) {
var liElement = document.createElement("li");
liElement.classList.add("legend-item");
var swatchElement = document.createElement("span");
_this16.applyStylesToSwatchElement(legend, swatchElement);
liElement.appendChild(swatchElement);
liElement.appendChild(document.createTextNode(legend.name));
ulElement.appendChild(liElement);
});
// Append unordered list to legend body
legendBodyElement.appendChild(ulElement);
}
// Append legend header and body to container
legendContainerElement.appendChild(legendHeaderElemt);
legendContainerElement.appendChild(legendBodyElement);
if (this.config.legend.position === "top") {
legendContainerElement.style.bottom = "0";
legendContainerElement.style.top = "1rem";
}
this.config.legend.position === "bottom" ? basePanelElement.after(legendContainerElement) : basePanelElement.before(legendContainerElement);
}
}, {
key: "applyStylesToSwatchElement",
value: function applyStylesToSwatchElement(legend, swatchElement) {
swatchElement.classList.add("swatch");
if (legend.swatchSymbol === "circle") {
swatchElement.style.height = "1rem";
swatchElement.style.backgroundColor = (legend === null || legend === void 0 ? void 0 : legend.color) || "";
swatchElement.style.borderColor = legend.borderColor ? legend.borderColor : "";
swatchElement.style.borderRadius = "50%";
}
if (legend.swatchSymbol === "line") {
swatchElement.style.borderWidth = legend.borderWidth;
swatchElement.style.borderColor = legend.borderColor;
}
}
}]);
}();