UNPKG

@springernature/nn-charts

Version:
871 lines (832 loc) 39.5 kB
"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 _common = require("../../utils/common"); var _constants = require("../../constants"); var _zoomControls = require("../../utils/zoom-controls"); function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); } 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 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: "collapsible-tree-svg" /* tree-base-panel */, marginOffsets: { top: 50, right: 25, bottom: 25, left: 200 }, labelOffsets: { left: 5, right: 0, top: 0, bottom: 0 }, chartHeight: 700, sizeAttribute: "size", labelAttribute: "label", expandFullTree: false, collapseAllNodes: false, showNodeToggleBtn: false, nodeOptions: { minimumRadius: 5, maximumRadius: 50 }, legendOptions: { borderWidth: 1, containerWidth: 300, containerHeight: 40, containerPadding: 20, legendLabel: "Node size represents number of publications" }, zoomControls: { isVisible: true, chartZoomMinimum: 0.5, chartZoomMaximum: 10, zoomScaleFactor: 1.3, position: "bottom", // top, bottom placement: "right", // left , right, center icons: ["zoom-in", "zoom-out"] }, containerId: "Collapsible-tree--wrapper" }; var CollapsibleTreeChartV2 = exports["default"] = /*#__PURE__*/function () { function CollapsibleTreeChartV2(config, loadingInitiatedCb, loadingEndedCb) { var nodeClickCustomHandler = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : function () {}; _classCallCheck(this, CollapsibleTreeChartV2); this.config = config || DEFAULT_CONFIG; this.containerId = this.config.containerId; this.loadingInitiatedCb = loadingInitiatedCb; this.loadingEndedCb = loadingEndedCb; this.nodeClickCustomHandler = nodeClickCustomHandler; // class properties initialization this.maxResearcherAttributeValue = -1; this.currentBoundingRect = {}; this.collapsibleTreeNodeData = {}; this.colouredNodes = false; this.scaledNodes = true; this.transitionDuration = 750; this.circleRadius = -1; this.sizeAttribute = this.config.sizeAttribute; this.baseX = 0; this.columnMax = 0; this.treeBranchGapDefinition = "byLargestValueOnColumn"; /* "padded" */ /* "fixed" */ /* byLargestValueInDataSet" */ /* byLargestValueOnColumn" */ this.treeBranchGapPadding = 70; // 25 this.fixedOffset = 100; this.nodeLabelPadding = 2.5; this.selectedElements = null; this.onLoad = true; this.minimumRadius = this.config.nodeOptions.minimumRadius; this.maximumRadius = this.config.nodeOptions.maximumRadius; this.baseGroupElementClassName = "collapsible-tree-group"; // TODO - work on it later // window.addEventListener("resize", () => this.treeChartOnWindowResize()); } return _createClass(CollapsibleTreeChartV2, [{ key: "render", value: function render(chartData) { var _this = this; try { if (!chartData) { alert("Chart cannot be built as data is not available"); return; } (0, _common.clearSvgBasePanel)(this.config.svgBasePanelClassName); this.loadingInitiatedCb(); this.setChartDimensions(); this.chartZoom = this.getZoomBehaviour(this.config.zoomControls.chartZoomMinimum, this.config.zoomControls.chartZoomMaximum); this.maxResearcherAttributeValue = (0, _treeChartHelpers.getMaxAttributeValue)(chartData.children, this.config.sizeAttribute); this.svgBasePanel = this.getBaseSVGPanel(this.config.svgBasePanelClassName, this.getParentElementWidth("Collapsible-tree--wrapper"), this.chartHeight); this.svgBasePanel.call(this.chartZoom.transform, this.getInitialTransform()); this.svgBasePanel.on("mousedown", function () { return d3.select(".".concat(_this.config.svgBasePanelClassName)).classed("chart--pan", true); }).on("pointerup", function () { return d3.select(".".concat(_this.config.svgBasePanelClassName)).classed("chart--pan", false); }); this.baseGroupContainer = this.appendTreeBaseGroupElement(this.baseGroupElementClassName); this.appendGroupToBaseGroupContainer("link"); this.appendGroupToBaseGroupContainer("node"); // register zoom handler function this.chartZoom.on("zoom", _treeChartHelpers.zoomHandler); this.svgBasePanel.call(this.chartZoom).on("dblclick.zoom", null); // construct zoom controls var zoomControls = _objectSpread(_objectSpread({}, this.config.zoomControls), {}, { onZoomControlsContainerCreated: function onZoomControlsContainerCreated(element, icons) { element.style.width = "".concat(icons.length * 2, "rem"); element.style.left = "0.75rem"; } }); if (this.config.zoomControls.isVisible) (0, _zoomControls.constructZoomControls)(this.config.svgBasePanelClassName, zoomControls); // add zoom control click handlers this.registerZoomControlListeners(); this.appendLegendLabel(this.config.legendOptions.legendLabel); this.rebuildData(chartData); this.treeMap = this.getTreeMap(); this.updateBasePanelWidth(); // if we want the tree to be fully expanded at point of on load if (this.config.expandFullTree) { // expand initial tree to full extent on all branches. // http://jsfiddle.net/z9tmgpwd/ this.expandFullTree(); } // Expand tree fully only to first subtopic layer at point of on load this.updateTree(this.root, "load"); this.currentBoundingRect = this.getElementBoundingRect(this.baseGroupElementClassName); this.loadingEndedCb(); } catch (error) { console.error("Error in CollapsibleTreeChart.render function: ", error); } } }, { key: "updateBasePanelWidth", value: function updateBasePanelWidth() { var width = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "100%"; d3.select(".".concat(this.config.svgBasePanelClassName)).attr("width", width); } }, { key: "getTreeMap", value: function getTreeMap() { return d3.tree().size([this.chartHeight, this.getParentElementWidth(this.containerId)]); } // recursive function }, { key: "constructTree", value: function constructTree(childData) { var _this2 = this; if (!childData || !childData.length) return;else { childData.forEach(function (d) { if (d.children && d.children.length) { // sort children array selected sort variable var childrenList = d.children; _this2.sortByKey(childrenList, _this2.config.sizeAttribute); _this2.setArbitraryTestDataValue(d); _this2.collapsibleTreeNodeData[d.label] = d; d.children.forEach(function (d) { if (d.children && d.children.length) _this2.constructTree(d);else { _this2.setArbitraryTestDataValue(d); _this2.collapsibleTreeNodeData[d.label] = d; } }); } else { _this2.setArbitraryTestDataValue(d); _this2.collapsibleTreeNodeData[d.label] = d; } }); } } }, { key: "setArbitraryTestDataValue", value: function setArbitraryTestDataValue(d) { d.arbitraryTestDataValue = (Math.random() * (10000 - 500) + 500).toFixed(0); } }, { key: "sortByKey", value: function sortByKey(data, key) { if (data) data.sort(function (x, y) { return d3.descending(x[key], y[key]); }); } }, { key: "updateTree", value: function updateTree(source, treeLoadedFrom) { var _this3 = this; var i = 0; var currentDepth = 0; var horizonNodeCounter = 0; var cumulativeVerticalPosition = 20; // 0 // Assigns the x and y position for the nodes this.treeData = this.treeMap(this.root); // Compute the new tree layout. var nodes = this.treeData.descendants(); var links = this.treeData.descendants().slice(1); // Normalize for fixed-depth. nodes.forEach(function (d, i) { d.y = d.depth * _this3.treeHorizonStep; // treeHorizonStep = 200 if (i === 0) { d.recalculatedX = d.x; horizonNodeCounter = 0; } else { // if node is any first node on a NEW vertical horizon ... if (d.depth !== currentDepth) { horizonNodeCounter = 0; d.recalculatedX = d.x; _this3.baseX = d.x; cumulativeVerticalPosition = _this3.baseX; _this3.columnMax = d.data[_this3.sizeAttribute]; } // if node is any intermediate or final node on the CURRENT vertical horizon ... else { var gapDefinitionMap = _this3.getGapDefinitionMap(nodes, i); if (_this3.treeBranchGapDefinition in gapDefinitionMap) { d.recalculatedX = gapDefinitionMap[_this3.treeBranchGapDefinition](); } cumulativeVerticalPosition = cumulativeVerticalPosition + d.recalculatedX; d.recalculatedX = cumulativeVerticalPosition; } currentDepth = d.depth; horizonNodeCounter++; } }); if (!this.config.collapseAllNodes) this.calculateRootNodePosition(nodes); /* ****************** Nodes section *************************** */ // Existing nodes var node = d3.selectAll(".collapsible-tree-node-group").selectAll("g.node").data(nodes, function (d) { return d.data.id || d.id; }); // Enter any new nodes at the parent's previous position var nodeEnter = this.getEnteredGroupElement(node, source); // append background rectangle for labels this.appendBackgroundRectanglesToLabels(nodeEnter); // append main circles to scale by attribute this.appendShapeToEnteredNode(nodeEnter, "circle"); // Add labels to the nodes this.appendLabelsToEnteredNode(nodeEnter, this.config.labelAttribute); var nodeUpdate = node.merge(nodeEnter); this.transitionDuration = treeLoadedFrom === "windowResize" ? 0 : 750; // Transition to the proper position for the node nodeUpdate.transition().duration(this.transitionDuration).attr("transform", function (d) { return "translate(".concat(d.y, ",").concat(d.recalculatedX, ")"); }); // Update the node attributes and style nodeUpdate.select("circle.node__circle").transition().duration(this.transitionDuration).attr("cx", function (d, i) { return i ? _this3.circleRadius(d.data[_this3.config.sizeAttribute]) : 0; }).attr("cy", 0).attr("r", function (d, i) { return i && _this3.circleRadius(d.data[_this3.config.sizeAttribute]); }); // update labels to the nodes nodeUpdate.select("text") // .transition() // .duration(this.transitionDuration) .attr("x", function (d, i) { return i ? 2 * _this3.circleRadius(d.data[_this3.sizeAttribute]) + 5 : 0; }).text(function (d) { var dataLabel = d.data.name ? d.data.name : d.data.label; return "".concat(dataLabel, " (").concat((0, _common.numberWithCommas)(d.data[_this3.config.sizeAttribute]), ")"); }); // Remove any exiting nodes var nodeExit = node.exit() // .transition() // .duration(this.transitionDuration) .attr("transform", "translate(".concat(source.y, ", ").concat(source.x, ")")).remove(); // On exit reduce the node circles size to 0 nodeExit.select("circle").attr("r", 1e-6); // On exit reduce the opacity of text labels nodeExit.select("text").style("fill-opacity", 1e-6); /* ********************** LINKS SECTION ******************* */ var link = d3.selectAll(".collapsible-tree-link-group").selectAll("path.link").data(links, function (d) { return d.data.id; }); var data = { x: source.x0, y: source.y0 }; var path = (0, _treeChartHelpers.getPathToConnectNodes)(data, data); // Enter any new links at the parent's previous position var linkEnter = this.getEnteredLinkGroupElement(link, path); // Update links var linkUpdate = linkEnter.merge(link); // Transition back to the parent element position linkUpdate // .transition() // .duration(this.transitionDuration) .attr("d", function (d) { return (0, _treeChartHelpers.getPathToConnectNodes)(d, d.parent); }); var dataObject = { x: source.x, y: source.y }; var pathOld = (0, _treeChartHelpers.getPathToConnectNodes)(dataObject, dataObject); // Remove any exiting links this.removeExitingLinks(link, pathOld); // Store the old positions for transition. nodes.forEach(function (d) { d.x0 = d.recalculatedX ? d.recalculatedX : d.x; d.y0 = d.y; }); // Multiple line wrapping of long labels for each node" // setTimeout(() => { // d3.selectAll(".node__label").call( // this.wrapLongLabels, // 150, // this.onLoad ? this.nodeLabelPadding : 0 // ); // d3.selectAll(".node__background-rect").each((d, i) => // this.updateNodeLabelbackgroundDimensions(d, i) // ); // }, 0); this.wrapLongLabels(d3.selectAll(".node__label"), 150, this.onLoad ? this.nodeLabelPadding : 0); d3.selectAll(".node__background-rect").each(function (d, i) { return _this3.updateNodeLabelbackgroundDimensions(d, i); }); if (this.config.showNodeToggleBtn) this.appendExpandCollapseButton(nodeUpdate); } }, { key: "updateNodeLabelbackgroundDimensions", value: function updateNodeLabelbackgroundDimensions(d, i) { var _this4 = this; /* * set label background rectangle width, height and position https://stackoverflow.com/questions/20822466/how-to-set-multiple-attributes-with-one-value-function */ var relatedLabelClientBoundingRect = (0, _treeChartHelpers.getElementBoundingClientRectByClass)(".node__label-" + d.data.id); d3.selectAll(".node__background-rect-" + d.data.id).attr("width", relatedLabelClientBoundingRect.width + 2 * this.nodeLabelPadding).attr("height", function () { var paddingMultipler = d3.selectAll(".node__label-" + d.data.id).select("tspan").attr("value"); return relatedLabelClientBoundingRect.height + paddingMultipler * _this4.nodeLabelPadding; }).attr("x", i === 0 ? -Number(relatedLabelClientBoundingRect.width + this.nodeLabelPadding) : 2 * this.circleRadius(d.data[this.config.sizeAttribute]) + this.nodeLabelPadding).attr("y", -relatedLabelClientBoundingRect.height / 2); // .style("stroke-width", 2) // .style("stroke", "red"); } }, { key: "wrapLongLabels", value: function wrapLongLabels(textNodeSelection, width, nodeLabelPadding) { var dy = null; textNodeSelection.each(function () { var text = d3.select(this); var words = text.text().split(/\s+/).reverse(); var word = ""; var line = []; var lineNumber = 0; var lineHeight = 1.1; // ems var x = text.attr("x"); var y = text.attr("y"); /* const */ dy = parseFloat(text.attr("dy")); var tspan = text.text(null).append("tspan").attr("x", Number(x) + nodeLabelPadding).attr("value", lineNumber).attr("y", y).attr("dy", dy + "em"); while (word = words.pop()) { line.push(word); tspan.text(line.join(" ")); if (tspan.node().getComputedTextLength() > width) { line.pop(); tspan.text(line.join(" ")); line = [word]; tspan = text.append("tspan").attr("x", function () { return Number(x) + nodeLabelPadding; }).attr("y", y).attr("dy", function () { var dystr = ++lineNumber * lineHeight + dy + "em"; if (lineNumber > 0) { var parentNode = d3.select(this)._groups[0][0].parentNode; d3.select(parentNode).selectAll("tspan").classed("multiline", true).attr("value", lineNumber); } return dystr; }).text(word); } } }); d3.selectAll(".multiline").attr("dy", function () { var thisdy = d3.select(this).attr("dy").replaceAll("em", ""); var thisValue = d3.select(this).attr("value"); return Number(thisdy) - thisValue * dy + "em"; }); } }, { key: "getGapDefinitionMap", value: function getGapDefinitionMap(nodes, i) { var _this5 = this; return { padded: function padded() { return Math.ceil(_this5.circleRadius(nodes[i].data[_this5.sizeAttribute]) + _this5.circleRadius(nodes[i - 1].data[_this5.sizeAttribute]) + _this5.treeBranchGapPadding); }, fixed: function fixed() { return _this5.fixedOffset; }, byLargestValueInDataSet: function byLargestValueInDataSet() { return _this5.circleRadius(_this5.maxResearcherAttributeValue) + _this5.treeBranchGapPadding; }, byLargestValueOnColumn: function byLargestValueOnColumn() { return _this5.circleRadius(_this5.columnMax) + _this5.treeBranchGapPadding; } }; } }, { key: "removeExitingLinks", value: function removeExitingLinks(link, path) { link.exit() // .transition() // .duration(this.transitionDuration) .attr("d", path).remove(); } }, { key: "calculateRootNodePosition", value: function calculateRootNodePosition(nodes) { if (nodes) { var _nodes$, _nodes$this$indexOfLa; var x1 = ((_nodes$ = nodes[1]) === null || _nodes$ === void 0 ? void 0 : _nodes$.recalculatedX) || 0; var x2 = ((_nodes$this$indexOfLa = nodes[this.indexOfLastNodeOnFirstHorizon]) === null || _nodes$this$indexOfLa === void 0 ? void 0 : _nodes$this$indexOfLa.recalculatedX) || 0; var x3 = (x1 + x2) / 2; nodes[0].recalculatedX = x3; } } }, { key: "appendBackgroundRectanglesToLabels", value: function appendBackgroundRectanglesToLabels(nodeEnter) { var _this6 = this; return nodeEnter.append("rect").attr("class", function (d) { return "nodeContent node__background-rect node__background-rect-".concat(d.data.id); }).on("click", function (d, i) { if (i) { _this6.setSelectedElements(d); _this6.toggleNode(d); _this6.highlightSelectedNodeAndEdge(); } _this6.nodeClickCustomHandler(d); }); } }, { key: "appendShapeToEnteredNode", value: function appendShapeToEnteredNode(nodeEnter) { var _this7 = this; var shape = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "circle"; return nodeEnter.append(shape).attr("class", function (d) { var shapeId = d.data.id ? d.data.id : 0; var parentId = d.data.parentId ? d.data.parentId : 0; return "nodeContent node__circle node shapeId-".concat(shapeId, " parentId-").concat(parentId); }).attr("r", 1e-6).on("click", function (d, i) { if (i) { _this7.setSelectedElements(d); _this7.toggleNode(d); _this7.highlightSelectedNodeAndEdge(); _this7.nodeClickCustomHandler(d); } }); } }, { key: "appendLabelsToEnteredNode", value: function appendLabelsToEnteredNode(nodeEnter) { var _this8 = this; var labelKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "label"; return nodeEnter.append("text").attr("dy", ".35em").attr("class", function (d) { return "nodeContent node__label node__label-".concat(d.data.id); }).attr("x", function (d, i) { return i ? Number(2 * _this8.circleRadius(d.data[_this8.config.sizeAttribute]) + _this8.config.labelOffsets.left) // 5 : 0; }).attr("y", this.config.labelOffsets.top) // 0 .style("text-anchor", function (_, i) { return i ? "start" : "end"; }).text(function (d, i) { var nodeLabelTextIndex = i; var dataLabel = d.data[labelKey] ? d.data[labelKey] : d.data.label; return nodeLabelTextIndex > 0 ? "".concat(dataLabel, " (").concat((0, _common.numberWithCommas)(+d.data[_this8.config.sizeAttribute]), ")") : dataLabel; }); } }, { key: "appendExpandCollapseButton", value: function appendExpandCollapseButton(node) { var _this9 = this; if (node.selectAll(".node__icon").empty()) { var group = (0, _treeChartHelpers.appendToggleIcon)(node); group.attr("class", function (d) { return "node__icon icon-".concat(d.id); }).attr("width", 24).attr("height", 24).attr("transform", function (d) { var labelXVal = parseInt(d3.select(".node__label-".concat(d.data.id)).attr("x")); var textWidthVal = d3.select(".node__label-".concat(d.data.id)).node().getBBox().width; var iconXValue = labelXVal + textWidthVal + 10; return "translate(".concat(iconXValue, ", -12)"); }).style("cursor", "pointer").attr("isNodeExpanded", function (d) { d.isNodeExpanded = false; return false; }).on("click", function (d, i) { if (i) { _this9.setSelectedElements(d); _this9.toggleExpansionStateIcon(d); _this9.toggleNode(d); } }); } } }, { key: "toggleExpansionStateIcon", value: function toggleExpansionStateIcon(data) { var clickedIcon = d3.select(".icon-".concat(data.id)); // Toggle the expansion state upon click var currentExpansionState = data.isNodeExpanded; var newExpansionState = !currentExpansionState; clickedIcon.attr("isNodeExpanded", newExpansionState); data.isNodeExpanded = newExpansionState; (0, _treeChartHelpers.alterToggleIconPath)(clickedIcon, newExpansionState ? "minus" : "plus"); } }, { key: "highlightSelectedNodeAndEdge", value: function highlightSelectedNodeAndEdge() { d3.selectAll(".link").classed("link--highlighted", false); d3.selectAll(".node__circle").classed("node__circle--selected", false); d3.selectAll(this.selectedElements.link).classed("link--highlighted", true).moveToFront(); d3.selectAll(this.selectedElements.node).classed("node__circle--selected", true); } }, { key: "setSelectedElements", value: function setSelectedElements(d) { var topicId = d.data.id ? d.data.id : 0; var parentId = d.parent.data.hasOwnProperty("id") ? d.parent.data.id : 0; this.selectedElements = { data: d, link: ".link.childId-" + topicId + ".parentId-" + parentId, node: ".node__circle.shapeId-" + topicId }; } }, { key: "getEnteredGroupElement", value: function getEnteredGroupElement(node, source) { return node.enter().append("g").attr("class", function (d, i) { var nodeId = d.data.id ? d.data.id : 0; var parentId = d.data.parentId ? d.data.parentId : 0; var str = "node node-".concat(i, "-").concat(nodeId, " parentId-").concat(parentId); return str; }).attr("transform", "translate(".concat(this.treeHorizonStep, ",").concat(source.x0, ")")); } }, { key: "getEnteredLinkGroupElement", value: function getEnteredLinkGroupElement(link, path) { return link.enter().insert("path", "g").attr("class", function (d, i) { var childId = d.data.id ? d.data.id : 0; var parentId = d.parent.data.hasOwnProperty("id") ? d.parent.data.id : 0; return "link link-".concat(i, " childId-").concat(childId, " parentId-").concat(parentId); }).attr("d", path); } }, { key: "rebuildData", value: function rebuildData(chartData) { var _this0 = this; this.root = d3.hierarchy(chartData, function (data) { if (data[_this0.config.sizeAttribute] > _this0.maxResearcherAttributeValue) { _this0.maxResearcherAttributeValue = data[_this0.config.sizeAttribute]; } if (data.children) { return data.children.sort(function (x, y) { return d3.descending(x[_this0.config.sizeAttribute], y[_this0.config.sizeAttribute]); }); } }); this.circleRadius = this.getCircleRadius(); /* logical check to determine if sorting of tree branches is needed on number of sub - topics for each tree */ if (this.root.height > 1) { // sort in descending fashion tree branches based on number of children this.root.children.sort(function (x, y) { return d3.descending(x.data[_this0.config.sizeAttribute], y.data[_this0.config.sizeAttribute]); }); } this.treeHorizonStep = 375; /** * TODO - refine this logic later * this.getParentElementWidth(this.containerId) / * Number(this.root.height + 1); * */ this.root.x0 = this.getCalculatedChartHeight() / 2; this.root.y0 = 0; // Collapse after the second level this.root.children.forEach(function (data) { return (0, _treeChartHelpers.collapseTreeBranch)(data); }); /* determine array index value of last/bottom-most node on first true vertical horizon of sub-topics i.e. that after the main topic. needed to detemine pixel range between topmost and bottom most node, to allow neat positioning on main topic node on chart rebuild. */ this.indexOfLastNodeOnFirstHorizon = this.root.children.length; } }, { key: "expandFullTree", value: function expandFullTree() { (0, _treeChartHelpers.expandBranch)(this.root); this.updateTree(this.root, "expandFullTree"); } }, { key: "treeChartOnWindowResize", value: function treeChartOnWindowResize() { // const { widthHolder, heightHolder } = getWindowDimensions(); var width = this.getParentElementWidth("Collapsible-tree--wrapper"); // update values for step increemnt between vertical horizons of nodes this.treeHorizonStep = width / Number(this.root.height); // recalcualte tree construct. this.treeData = this.treeMap(this.root); // call function to update and rebuild tree during window resizing. this.updateTree(this.treeData, "windowResize"); // let vertexOffset1 = d3 // .selectAll("..node-0-0") // .attr("transform") // .replaceAll("translate(", "") // .replaceAll(")", "") // .split(","); // let vertexOffset2 = d3 // .selectAll("..node-1") // .attr("transform") // .replaceAll("translate(", "") // .replaceAll(")", "") // .split(","); // let vertexWidthGap = Number(vertexOffset2[0] - vertexOffset1[0] - 50); // TODO - Update to wrapLongLabels // d3.selectAll(".node__label").call( // this.wrapLabel, // vertexWidthGap / 2, // "node__label" // ); } }, { key: "getCalculatedChartHeight", value: function getCalculatedChartHeight() { return this.chartHeight - this.chartMargin.top - this.chartMargin.bottom; } // construct circle shape scaling law }, { key: "getCircleRadius", value: function getCircleRadius() { return d3.scalePow().exponent(0.5).domain([0, this.maxResearcherAttributeValue]).range([this.minimumRadius, this.maximumRadius]); } }, { key: "setChartDimensions", value: function setChartDimensions() { this.chartMargin = this.config.marginOffsets; this.chartHeight = this.config.chartHeight; } }, { key: "getZoomBehaviour", value: function getZoomBehaviour(minZoomScale, maxZoomScale) { return d3.zoom().scaleExtent([minZoomScale, maxZoomScale]); } }, { key: "getInitialOriginTranslateValues", value: function getInitialOriginTranslateValues() { var _this$chartMargin = this.chartMargin, initialOriginX = _this$chartMargin.left, initialOriginY = _this$chartMargin.top; return { initialOriginX: initialOriginX, initialOriginY: initialOriginY }; } }, { key: "getInitialTransform", value: function getInitialTransform() { var _this$getInitialOrigi = this.getInitialOriginTranslateValues(), initialOriginX = _this$getInitialOrigi.initialOriginX, initialOriginY = _this$getInitialOrigi.initialOriginY; var initialTransform = d3.zoomIdentity.translate(initialOriginX, initialOriginY).scale(1); return initialTransform; } }, { key: "getBaseSVGPanel", value: function getBaseSVGPanel(className, width, height) { return d3.selectAll(".".concat(className)).attr("width", width).attr("height", height); } }, { key: "getElementBoundingRect", value: function getElementBoundingRect(elmtClass) { var _d3$selectAll$node; return (_d3$selectAll$node = d3.selectAll(".".concat(elmtClass)).node()) === null || _d3$selectAll$node === void 0 ? void 0 : _d3$selectAll$node.getBoundingClientRect(); } }, { key: "getParentElementWidth", value: function getParentElementWidth(parentElmtClass) { return this.getElementBoundingRect(parentElmtClass).width - this.chartMargin.left - this.chartMargin.right; } // CREATING BASE GROUP ELEMENT }, { key: "appendTreeBaseGroupElement", value: function appendTreeBaseGroupElement() { var baseGroupClass = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "collapsible-tree-group"; var _this$getInitialOrigi2 = this.getInitialOriginTranslateValues(), initialOriginX = _this$getInitialOrigi2.initialOriginX, initialOriginY = _this$getInitialOrigi2.initialOriginY; return this.svgBasePanel.append("g").attr("class", baseGroupClass).attr("id", baseGroupClass).attr("transform", "translate(".concat(initialOriginX, ",").concat(initialOriginY, ") scale(", 1, ")")); } // Appending new group element to group links and nodes into individual groups }, { key: "appendGroupToBaseGroupContainer", value: function appendGroupToBaseGroupContainer(entity) { this.baseGroupContainer.append("g").attr("class", "collapsible-tree-".concat(entity, "-group")); } }, { key: "toggleNode", value: function toggleNode(d) { var _d$data$children; this.onLoad = false; if (!((_d$data$children = d.data.children) !== null && _d$data$children !== void 0 && _d$data$children.length)) return; // Toggle the children of the clicked node if (d.data.children) { if (d.children) { d._children = d.children; d.children = null; } else { d["children"] = d === null || d === void 0 ? void 0 : d._children; d["_children"] = null; } } // Update the tree layout this.updateTree(d, "newBranch"); // Collapse all other previously expanded branches of the tree this.collapseSiblingNodes(d); } }, { key: "collapseSiblingNodes", value: function collapseSiblingNodes(clickedNode) { var _this1 = this; // Traverse all nodes in the tree var needsUpdate = false; this.root.each(function (d) { // Check if the current node is not the clicked node and if it is expanded if (_this1.isSiblingNodeExpanded(d, clickedNode)) { // Collapse the node (0, _treeChartHelpers.collapseTreeBranch)(d); // Alter current node's toggle button icon if (_this1.config.showNodeToggleBtn) _this1.updateSiblingNodeIcon(d); // Set flag to indicate that an update is needed needsUpdate = true; } }); // Update the tree layout if any changes were made if (needsUpdate) this.updateTree(clickedNode, "newBranch"); } }, { key: "isSiblingNodeExpanded", value: function isSiblingNodeExpanded(node, clickedNode) { return node.depth === clickedNode.depth && node !== clickedNode && node.children; } }, { key: "updateSiblingNodeIcon", value: function updateSiblingNodeIcon(d) { var currentNodeIcon = d3.select(".icon-".concat(d.id)); d.isNodeExpanded = !d.isNodeExpanded; currentNodeIcon.attr("isNodeExpanded", d.isNodeExpanded); (0, _treeChartHelpers.alterToggleIconPath)(currentNodeIcon, d.isNodeExpanded ? "minus" : "plus"); } }, { key: "appendLegendLabel", value: function appendLegendLabel(legendText) { var legendLabelgroup = d3.selectAll(".".concat(this.config.svgBasePanelClassName)).append("g").attr("class", "legend-label-group").attr("transform", "translate(25, 25)"); var legendBackgroundRect = legendLabelgroup.append("rect").attr("class", "legend__background-rect"); legendBackgroundRect.attr("x", 0).attr("y", 0).attr("width", this.config.legendOptions.containerWidth).attr("height", this.config.legendOptions.containerHeight).attr("rx", 5).attr("ry", 5).style("stroke-width", this.config.legendOptions.borderWidth); legendLabelgroup.append("svg:image").attr("href", (0, _constants.getOutlinedInfoIcon)()).attr("id", "legend-info-icon").attr("x", 5).attr("y", 8).attr("width", 22).style("fill", "white"); legendLabelgroup.append("text").attr("id", "legend-label").text(legendText).attr("x", 35).attr("y", 25); var labelClientRect = (0, _common.getElementBoundingClientRect)("legend-label"); var iconClientRect = (0, _common.getElementBoundingClientRect)("legend-info-icon"); var legendContainerWidth = labelClientRect.width + iconClientRect.width + this.config.legendOptions.containerPadding; legendBackgroundRect.attr("width", legendContainerWidth); } }, { key: "registerZoomControlListeners", value: function registerZoomControlListeners() { var _this10 = this; d3.selectAll(".zoom-in").on("click", function () { return _this10.chartZoom.scaleBy(_this10.svgBasePanel.transition().duration(50), _this10.config.zoomControls.zoomScaleFactor); }); d3.selectAll(".zoom-out").on("click", function () { return _this10.chartZoom.scaleBy(_this10.svgBasePanel.transition().duration(50), 1 / _this10.config.zoomControls.zoomScaleFactor); }); d3.selectAll(".zoom-centre").on("click", function () { var _this10$getSvgPanelCe = _this10.getSvgPanelCenterTranslateValues(), initialOriginX = _this10$getSvgPanelCe.initialOriginX, initialOriginY = _this10$getSvgPanelCe.initialOriginY; var newTransform = d3.zoomIdentity.translate(initialOriginX, initialOriginY).scale(1); _this10.chartZoom.transform(_this10.svgBasePanel, newTransform); }); } }, { key: "getSvgPanelCenterTranslateValues", value: function getSvgPanelCenterTranslateValues() { var _this$getSvgBasePanel = this.getSvgBasePanelDimensions(), svgBasePanelWidth = _this$getSvgBasePanel.svgBasePanelWidth, svgBasePanelHeight = _this$getSvgBasePanel.svgBasePanelHeight; var initialOriginX = svgBasePanelWidth / 2; var initialOriginY = svgBasePanelHeight / 2; return { initialOriginX: initialOriginX, initialOriginY: initialOriginY }; } }, { 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: "getNewTransform", value: function getNewTransform() { var padding = 50; var bBox = d3.selectAll(".".concat(this.baseGroupElementClassName)).node().getBBox(); var basePanelHeight = d3.selectAll(".".concat(this.config.svgBasePanelClassName)).attr("height"); var basePanelWidth = this.getElementBoundingRect(this.config.svgBasePanelClassName).width; var hRatio = basePanelHeight / (bBox.height + padding); var wRatio = basePanelWidth / (bBox.width + padding); var newTransform = d3.zoomIdentity.translate(basePanelWidth / 2 - this.currentBoundingRect.width / 2, padding / 2).scale(hRatio < wRatio ? hRatio : wRatio); return newTransform; } }]); }();