UNPKG

@springernature/nn-charts

Version:
886 lines (846 loc) 40 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 _iconGeneration = require("../utils/icon-generation"); 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 _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } 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 outlinedInfoIcon = (0, _iconGeneration.getOutlinedInfoIcon)(); 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" }, chartZoomMinimum: 0.5, chartZoomMaximum: 10, 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.defineZoomProperty(); 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.zoom.transform, this.getInitialTransform()); this.svgBasePanel.call(this.zoom).on("dblclick.zoom", null); 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.zoom.on("zoom", _treeChartHelpers.zoomHandler); this.appendZoomControlsGroup(); this.appendZoomControlBtns(); (0, _treeChartHelpers.getZoomControls)().forEach(function (d) { var key = d.key; var selector = ".zoom-controls-" + key; var createLine = function createLine(lineData) { d3.selectAll(selector).selectAll("line").data(lineData).enter().append("line").attr("x1", function (d) { return d.x1; }).attr("x2", function (d) { return d.x2; }).attr("y1", function (d) { return d.y1; }).attr("y2", function (d) { return d.y2; }).attr("class", d.symbolType); // TODO - append icons // appendIcon(); }; var createCircle = function createCircle(circleData) { d3.selectAll(selector).selectAll("circle").data(circleData).enter().append("circle").attr("cx", function () { return d3.selectAll(selector + ".zoom-" + key).attr("width") / 2; }).attr("cy", function () { return d3.selectAll(selector + ".zoom-" + key).attr("height") / 2; }).attr("r", function (d) { return d.r; }).style("fill", function (d) { return d.fill; }).attr("class", d.symbolType); }; if (d.symbolType === "line") createLine(d.symbol); if (d.symbolType === "circle") createCircle(d.symbol); }); this.attachZoomBtnClickHandlers(); 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.getElement(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 _this10 = this; this.root = d3.hierarchy(chartData, function (data) { if (data[_this10.config.sizeAttribute] > _this10.maxResearcherAttributeValue) { _this10.maxResearcherAttributeValue = data[_this10.config.sizeAttribute]; } if (data.children) { return data.children.sort(function (x, y) { return d3.descending(x[_this10.config.sizeAttribute], y[_this10.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[_this10.config.sizeAttribute], y.data[_this10.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: "defineZoomProperty", value: function defineZoomProperty() { this.zoom = d3.zoom().scaleExtent([this.config.chartZoomMinimum, this.config.chartZoomMaximum]); } }, { 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: "getElement", value: function getElement(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.getElement(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 _this11 = 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 (_this11.isSiblingNodeExpanded(d, clickedNode)) { // Collapse the node (0, _treeChartHelpers.collapseTreeBranch)(d); // Alter current node's toggle button icon if (_this11.config.showNodeToggleBtn) _this11.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: "appendZoomControlsGroup", value: function appendZoomControlsGroup() { var zoomControlsPanelHeight = 40; var scaleBy = 1; d3.selectAll(".".concat(this.config.svgBasePanelClassName)).append("g").attr("class", "zoom-controls-container").attr("id", "zoom-controls-group") // .attr("transform", `translate(90%,95%) scale(${scaleBy})`) .attr("transform", "translate(".concat(10, ",", this.chartHeight - zoomControlsPanelHeight - 5 /* height + 5 */, ") scale(").concat(scaleBy, ")")).append("rect").attr("x", -5).attr("y", -5).attr("rx", 5).attr("ry", 5).attr("width", (0, _treeChartHelpers.getZoomControls)().length * 40 + 0).attr("height", zoomControlsPanelHeight); } }, { key: "appendZoomControlBtns", value: function appendZoomControlBtns() { d3.selectAll(".zoom-controls-container").selectAll(".zoom-controls").data((0, _treeChartHelpers.getZoomControls)()).enter().append("g").attr("class", function (d) { return "zoom-controls zoom-controls-" + d.key; }).attr("transform", function (_, i) { return "translate(".concat(i * 40, ",", 0, ") scale(", 1, ")"); }); d3.selectAll(".zoom-controls").append("rect").attr("class", function (d) { return "zoom-controls-".concat(d.key, " zoom-").concat(d.key); }).attr("id", function (d) { return "zoom-".concat(d.key); }).attr("x", 0).attr("y", 0).attr("width", 30).attr("height", 30); } }, { 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", outlinedInfoIcon).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: "attachZoomBtnClickHandlers", value: function attachZoomBtnClickHandlers() { var _this12 = this; d3.selectAll(".zoom-in").on("click", function () { _this12.zoom.scaleBy(_this12.svgBasePanel.transition().duration(50), 1.3); }); d3.selectAll(".zoom-out").on("click", function () { return _this12.zoom.scaleBy(_this12.svgBasePanel.transition().duration(50), 1 / 1.3); }); //Works (reset to a different, non-origin coordinate set) d3.select("#zoom-reset").on("click", function () { _this12.svgBasePanel.call(_this12.zoom.transform, _this12.getInitialTransform()); }); d3.select(".zoom-centre").on("click", function () { return ( // TODO - work on this logic later, James has updated it _this12.svgBasePanel.call(_this12.zoom.transform, _this12.getNewTransform()) ); }); } }, { 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.getElement(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; } }]); }();