@springernature/nn-charts
Version:
Visualization for DAS products
886 lines (846 loc) • 40 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 _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;
}
}]);
}();