UNPKG

@springernature/nn-charts

Version:
1,038 lines (947 loc) 44.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; var d3 = _interopRequireWildcard(require("d3")); 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 _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); } function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } function _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 _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 DrillDownColumnChart = exports["default"] = /*#__PURE__*/function () { function DrillDownColumnChart(chartData, language) { var _this = this; _classCallCheck(this, DrillDownColumnChart); // function call to implement chart responsiveness after window resizing this.window = window.addEventListener("resize", function () { _this.drillDown_windowResize(); }); this.data = chartData; /* ingested data */ this.drillDown = _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty({ mainTopic: "Proxy Data Set", chartTypeTransitionDuration: 750, chartRebuildTransitionDuration: 500, breadcrumbArray: ["root"], branding: "" /* "" ,"tomorrow-people" */, width: 0 /* chart width */, height: 0 /* chart height */, data: chartData /* chart data */, numberOfYears: 0 /* number of data years */, startYear: new Date() /* data start year */, endYear: new Date() /* data end year */, arrayOfYears: [] /* flat array to contain all data years to populate xTime chart axis labels */, chartData: chartData /* chart data */, numberOfColumns: 0 /* number of categories to chart with based on user Builder submission */, categories: [] /* flat array to contain all data categories considered on chart */, layers: [], /* stack: [], */ yStackMax: [] /* maximum y-axis data value for stacked column chart */, yGroupMax: [] /* maximum y0-axis data value for grouped column chart */, x: 0 /* x axis call definition */, xAxis: [] /* x axis call definition */, y: 0 /* y axis call definition */, yAxis: [] /* y axis call definition */, xTime: [] /* x Time axis call definition */, xTimeAxis: [] /* x Time axis call definition */, svg: [] /* root base SVG initialisation */, rect: [] /* chart data rect initialisation */, layer: [] /* chart data layer initialisation */, selectedLayer: null, /* [] */ /* what is the currently selected data category layer? */selectedLayerIndex: 0 /* what is the currently selected data category layer Index? */, stack: d3.stack(), // initialised stack variable margin: { top: 125, right: 200, bottom: /* 50 */75, left: 200 } /* define chart margins */, formatPercent: d3.format(".0%"), // format for percent labelling formatNumber: d3.format(","), // format for number labelling layerHighlightColour: "#F6AC59" /* highlight colour definition for user selected data/category horizon/layer */, axisTitle: { x: language.columnDrillDown.xAxisTitle ? language.columnDrillDown.xAxisTitle : "Year of Publication", y: language.columnDrillDown.yAxisTitle ? language.columnDrillDown.yAxisTitle : "Number of Publications" } /* chart axis titles definitions */, Rounding: { yMax: null, yGroupMax: 100, // y-axis rounding value for grouped column chart yStackMax: 10, // y-axis rounding value for stacked column chart percent: 100 // y-axis rounding value for 100% stacked column chart }, tooltipRectWidth: 300, // width of tooltip tooltipRectHeight: 150, // height of tooltip legendRectWidth: 100, // width of legend swatch rectangle legendRectHeight: 25 }, "categories", []), "numberOfColumns", -1), "chartData", []), "arrayOfYears", []), "categories", []), "numberOfColumns", []), "chartData", []), "arrayOfYears", []), "isLayerSelected", false), "isPreviousLayerSelected", false); this.hoverLabelIndex = null; this.stickyActiveLabel = null; this.vis = { width: document.body.clientWidth, height: document.body.clientHeight }; this.activeIndex = null; this.baseColourPalette = { /* ["#grey-80", "#grey-60", "#grey-40", "#grey-30", "#grey-20", "#grey-10", "#grey-07"], */ 7: ["#333333", "#666666", "#999999", "#bcbcbc", "#cccccc", "#e5e5e5", "#eeeeee"], 6: ["#666666", "#999999", "#bcbcbc", "#cccccc", "#e5e5e5", "#eeeeee"], 5: ["#666666", "#999999", "#bcbcbc", "#cccccc", "#e5e5e5"], 4: ["#999999", "#bcbcbc", "#cccccc", "#e5e5e5"], 3: ["#999999", "#bcbcbc", "#cccccc"], 2: ["#bcbcbc", "#cccccc"], 1: ["#bcbcbc"] }; this.color = {}; } /* name: init description: function to arguments: n/a returns: n/a calls: n/a called from: */ return _createClass(DrillDownColumnChart, [{ key: "init", value: function init() { this.drawDrillDownChart(this.data); } // end init /* name: drawDrillDownChart description: main function for building chart arguments: data - returns: n/a calls: called from: constructor */ }, { key: "drawDrillDownChart", value: function drawDrillDownChart(data) { // // added to hide all other visualisation .container DIVs. d3.selectAll(".container").classed("hide", true); d3.selectAll(".container.drill-down").classed("hide", false); // var el = this; el.drillDown.height = /* 750 */600 - el.drillDown.margin.top - el.drillDown.margin.bottom; // define chart height el.drillDown.data = data; // extract data array el.drillDown.numberOfYears = el.drillDown.data.length; // determine number of x-axis data year el.drillDown.startYear = el.drillDown.data[0].year; // determine START year of x-axis/data time domain el.drillDown.endYear = el.drillDown.data[el.drillDown.data.length - 1].year; // determine END year of x-axis/data time domain el.recurseTree(el.drillDown.data); return; } // end function drawDrillDownChart }, { key: "recurseTree", value: function recurseTree(t) { var el = this; el.drillDown.newData = []; el.drillDown.numberOfLayers = t[0].children.length; // number of horizons/layers el.drillDown.numberOfStacks = t.length; // number of stacks t[0].children.forEach(function (d, i) { el.drillDown.newData[i] = []; }); el.drillDown.stackNames = []; el.drillDown.layerNames = []; t.forEach(function (d, i) { var children = d.children; // el.drillDown.stackNames.indexOf(d.category) == -1 ? el.drillDown.stackNames.push(d.category) : ""; // children.forEach(function (d, i) { el.drillDown.layerNames.indexOf(d.category) == -1 ? el.drillDown.layerNames.push(d.category) : ""; el.drillDown.newData[i].push(d.size); }); }); // end forEach loop ... // manipulate chart data to new data contstuct. el.drillDown.newData = el.drillDown.newData[0].map(function (col, i) { return el.drillDown.newData.map(function (row) { return row[i]; }); }); el.drillDown.numberOfColumns = el.drillDown.newData.length; // manipulate chart data to new data contstuct. el.drillDown.layers = el.drillDown.stack.keys(d3.range(el.drillDown.numberOfColumns))(el.drillDown.newData); el.getyAxisMaximum(); // define chart x axis definition el.drillDown.x = d3.scaleBand().domain(d3.range(el.drillDown.numberOfYears)).rangeRound([0, el.vis.width - el.drillDown.margin.left - el.drillDown.margin.right]).padding(0.1).align(0.1); el.drillDown.xAxis = d3.axisBottom().scale(el.drillDown.x); // define dummy chart x axis for data years el.drillDown.xTime = d3.scaleBand().domain(el.drillDown.arrayOfYears).rangeRound([0, el.vis.width - el.drillDown.margin.left - el.drillDown.margin.right]).padding(0.1).align(0.1); el.drillDown.xTimeAxis = d3.axisBottom().scale(el.drillDown.xTime); // define colour ramp for chart el.color = d3.scaleLinear().domain([0, el.drillDown.numberOfColumns - 1]).range(el.baseColourPalette[el.drillDown.numberOfColumns]); // define chart y axis el.drillDown.y = d3.scaleLinear().domain([0, Math.ceil(el.drillDown. /* yStackMax */yMax / el.drillDown.Rounding. /* yStackMax */yMax) * el.drillDown.Rounding. /* yStackMax */yMax]).rangeRound([el.drillDown.height, 0]).nice(); el.drillDown.yAxis = d3.axisLeft().scale(el.drillDown.y).tickSize(2).tickPadding(6); d3.selectAll(".pearl-svg").remove(); // attach base SVG panel to draw chart on. el.drillDown.svg = d3.selectAll(".drill-down").append("svg").attr("class", "pearl-svg").attr("width", el.vis.width).attr("height", el.drillDown.height + el.drillDown.margin.top + el.drillDown.margin.bottom).append("g").attr("class", "drillDown-over-time-chart").attr("transform", "translate(" + el.drillDown.margin.left + "," + el.drillDown.margin.top + ")"); // call function to add dynamic legend to chart // el.addLegend(); // append y axis el.drillDown.svg.append("g").attr("class", "y axis").attr("transform", "translate(" + 0 + ",0)").call(el.drillDown.yAxis); // initialise and append y-axis main title label d3.selectAll(".y.axis").append("text").attr("class", "yAxisTitleLeft") /* CURRENT */.attr("x", -300).attr("y", 80).attr("dy", "-10em") /* PREFERRED */ // .attr("x", 0) // .attr("y", 0) // .attr("dy", "0em") .text(el.drillDown.axisTitle.y); el.addGridlines(); // append new groups for data layers el.drillDown.layer = el.drillDown.svg.selectAll(".layer").data(el.drillDown.layers).enter().append("g") /* .attr("class", function (d, i) { return "layer-" + i + " horizon"; }) */.attr("class", function (d, i) { return "layer layer-" + el.drillDown.layerNames[i]; }).attr("id", function (d) { return d.key; }).style("fill", function (d, i) { return el.baseColourPalette[el.drillDown.numberOfColumns][i]; }); // append new data rectangle to groups el.drillDown.rect = el.drillDown.layer.selectAll("rect").data(function (d) { return d; }).enter().append("rect") // .attr("class", function (d, i) { // var pc = this.parentNode.className.baseVal // .replace("layer ", "layer-") // .replace("horizon", ""); // return pc + "dataRect stack-" + i; // }) .attr("class", function (d, i) { return "rect " + d3.select(this.parentNode).attr("class").replace("layer layer-", ""); }).attr("x", function (d, i) { return el.drillDown.x(i); }).attr("y", el.drillDown.height).attr("width", el.drillDown.x.bandwidth()).attr("height", 0).on("mouseleave", function (d, i) { // el.articles_mouseleave(el, d, i); }).on("mouseover", function (d, i) { var selected = d3.select(this).attr("class"); var selectedCategory = selected.replace("rect ", ""); var counter = 0; // https://www.easyprogramming.net/javascript/recursive_nested_json_function.php iterateObject(el.drillDown.data); function iterateObject(data) { for (var prop in data) { if (_typeof(data[prop]) == "object") { iterateObject(data[prop]); } // end if ... else { if (data[prop] == selectedCategory) { if (counter == i) { if (data["children"].length > 0) { d3.selectAll("." + selected.split(" ").join(".")).style("cursor", "pointer"); } else { console.log("selected rect does not have any children to drill down to."); d3.selectAll("." + selected.split(" ").join(".")).style("cursor", "no-drop"); } } counter++; } // end if ... } // end else ... } // end for ... } // end function iterateObject return; }).on("click", function (d, i) { var selected = d3.select(this).attr("class"); var selectedCategory = selected.replace("rect ", ""); var newTree = []; var stackName = null; // https://www.easyprogramming.net/javascript/recursive_nested_json_function.php iterateObject(el.drillDown.data, selectedCategory); function iterateObject(data) { for (var prop in data) { if (_typeof(data[prop]) == "object") { iterateObject(data[prop]); } // end if ... else { if (el.drillDown.stackNames.indexOf(data[prop]) != -1) { stackName = data[prop]; } // end if ... if (data[prop] == selectedCategory) { newTree.push({ category: stackName, size: data["size"], children: data["children"] }); } // end if ... } // end else ... } // end for ... } // end function iterateObject var found = newTree.find(function (d) { return d.children.length > 0; }); if (found) { el.drillDown.breadcrumbArray.push(selectedCategory); el.recurseTree(newTree); } else { console.log("Selected category has no children to drill down to"); } return; }); // define transtioning behavious for data rectangles. el.drillDown.rect.transition().duration(el.drillDown.chartRebuildTransitionDuration).delay(function (d, i) { return i * 10; }).attr("y", function (d) { return el.drillDown.y(d[1]); }).attr("height", function (d) { return el.drillDown.y(d[0]) - el.drillDown.y(d[1]); }).style("opacity", 1); // append x numeric axis el.drillDown.svg.append("g").attr("class", "x axis").attr("transform", "translate(0," + el.drillDown.height + ")").call(el.drillDown.xAxis); // append x time axis el.drillDown.svg.append("g").attr("class", "xTime axis").attr("transform", "translate(0," + el.drillDown.height + ")").call(el.drillDown.xTimeAxis); // select all tick labels on xTime axis. remove/hide alternarting ones // this is a quick fix to imrpvoe readability and avoid labels overlapping on long time interval axes // in advance of better handling and responsiveness var ticks = d3.selectAll(".xTime.axis").selectAll(".tick text"); ticks.each(function (item, i) { if (i % 2 !== 0) d3.select(this).remove(); }); // initialise and append x-axis main title label d3.selectAll(".xTime.axis").append("text").attr("class", "xAxisTitle").attr("x", el.vis.width / 2).attr("y", 40).text(el.drillDown.axisTitle.x); // act on user selecting reset button. d3.selectAll(".resetChart").on("click", function () { document.getElementById("resetChart").disabled = true; d3.select("#resetChart").classed("disabled", true); // el.resetChart(this); }); // act on user selecting an alternative chart type button. d3.selectAll(".chartType").on("click", function (d, i) { var selection = this.id; document.getElementById("resetChart").disabled = false; d3.select("#resetChart").classed("disabled", false); // el.changeChart(this); }); el.buildBreadcrumb(); return; } // end function recurseTree }, { key: "getyAxisMaximum", value: function getyAxisMaximum() { var el = this; var selectedChartType = d3.selectAll(".chartType.selected").attr("id"); if (selectedChartType == "stacked") { // determine max data value for y-axis when chart is stacked ... round to nearest 100 ... el.drillDown.yMax = Math.ceil(d3.max(el.drillDown.layers, function (layer) { return d3.max(layer, function (d) { return d[1]; }); })); } else if (selectedChartType == "grouped") { // determine max data value for y-axis when chart is grouped ... round to nearest 10 ... el.drillDown.yMax = Math.ceil(d3.max(el.drillDown.layers, function (layer) { return d3.max(layer, function (d) { return d[1] - d[0]; }); })); } if (el.drillDown.yMax < 9) { el.drillDown.Rounding.yMax = 2; } else if (el.drillDown.yMax < 90) { el.drillDown.Rounding.yMax = 10; } else if (el.drillDown.yMax < 900) { el.drillDown.Rounding.yMax = 100; } else if (el.drillDown.yMax < 9000) { el.drillDown.Rounding.yMax = /* 1000 */500; } else if (el.drillDown.yMax < 90000) { el.drillDown.Rounding.yMax = /* 10000 */5000; } else if (el.drillDown.yMax < 900000) { el.drillDown.Rounding.yMax = 100000; } return; } // end function getyAxisMaximum() /* name: wrapText description: main function wrapping long SVG ttxt lines arguments: text - width - returns: calls: called from: */ }, { key: "wrapText", value: function wrapText(text, width) { text.each(function () { var text = d3.select(this), words = text.text().split(/\s+/).reverse(), word, line = [], lineNumber = 0, lineHeight = 1.2, // ems x = text.attr("x"), y = text.attr("y"), dy = text.attr("dy") ? text.attr("dy") : 0; var tspan = text.text(null).append("tspan").attr("x", x).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", x).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word); } } }); } /* name: changeChart description: function to handle user selecting a differrent chart style to visualise data with arguments: data - daat contained in ingested data file returns: n/a calls: transitionGrouped transitionStacked transitionPercent called from: drawArticlesOverTimeChart */ }, { key: "removeStickyTooltip", value: function removeStickyTooltip() { d3.selectAll(".selectedRect").classed("selectedRect", false); d3.selectAll(".sticky-tooltip").remove(); d3.selectAll(".sticky-group").remove(); } }, { key: "changeChart", value: function changeChart(e) { var el = this; d3.selectAll(".chartType").classed("selected", false); d3.select("#" + e.id).classed("selected", true); el.getyAxisMaximum(); if (e.value === "grouped") { this.transitionGrouped(); this.removeStickyTooltip(); } else if (e.value === "stacked") { this.transitionStacked(); this.removeStickyTooltip(); } else if (e.value === "percent") { this.transitionPercent(); this.removeStickyTooltip(); } this.addGridlines(); return; } // end function changeChart /* name: transitionGrouped description: function to transition chart to grouped form arguments: n/a returns: n/a calls: numberWithCommas called from: changeChart */ }, { key: "transitionGrouped", value: function transitionGrouped() { // update definition of y axis var el = this; el.drillDown.y.domain([0]); el.drillDown.y.domain([0, Math.ceil(el.drillDown.yMax / el.drillDown.Rounding.yMax) * el.drillDown.Rounding.yMax /* Math.ceil(el.drillDown.yGroupMax / el.drillDown.Rounding.yGroupMax) * el.drillDown.Rounding.yGroupMax, */]); // transition all data rectangles accordingly el.drillDown.rect.transition().duration(el.drillDown.chartTypeTransitionDuration).delay(function (d, i) { return i * 10; }).attr("x", function (d, i, j) { return el.drillDown.x(i) + el.drillDown.x.bandwidth() / el.drillDown.numberOfColumns * parseInt(this.parentNode.id); }).attr("width", el.drillDown.x.bandwidth() / el.drillDown.numberOfColumns).transition().attr("y", function (d) { return el.drillDown.height - (el.drillDown.y(d[0]) - el.drillDown.y(d[1])); }).attr("height", function (d) { return el.drillDown.y(d[0]) - el.drillDown.y(d[1]); }); el.drillDown.yAxis.tickFormat(el.drillDown.formatNumber); // transition y axis el.drillDown.svg.selectAll(".y.axis").transition().delay(500).duration(el.drillDown.chartTypeTransitionDuration).call(el.drillDown.yAxis); return; } // end function transitionGrouped /* name: transitionStacked description: function to transition chart to stacked column form arguments: n/a returns: n/a calls: numberWithCommas called from: changeChart */ }, { key: "transitionStacked", value: function transitionStacked() { // // update definition of y axis var el = this; el.drillDown.y.domain([0, Math.ceil(el.drillDown.yMax / el.drillDown.Rounding.yMax) * el.drillDown.Rounding.yMax /* Math.ceil(el.drillDown.yStackMax / el.drillDown.Rounding.yStackMax) * el.drillDown.Rounding.yStackMax, */]); // // transition all data rectangles accordingly el.drillDown.rect.transition().duration(el.drillDown.chartTypeTransitionDuration).delay(function (d, i) { return i * 10; }).attr("y", function (d) { return el.drillDown.y(d[1]); }).attr("height", function (d) { return el.drillDown.y(d[0]) - el.drillDown.y(d[1]); }).transition().attr("x", function (d, i) { return el.drillDown.x(i); }).attr("width", el.drillDown.x.bandwidth()); el.drillDown.yAxis.tickFormat(el.drillDown.formatNumber); // transition y axis el.drillDown.svg.selectAll(".y.axis").transition().delay(500).duration(el.drillDown.chartTypeTransitionDuration).call(el.drillDown.yAxis); return; } // end function transitionStacked /* name: transitionPercent description: function to transition chart to 100% stacked column form arguments: n/a returns: n/a calls: numberWithCommas called from: changeChart */ }, { key: "transitionPercent", value: function transitionPercent() { var el = this; // update definition of y axis el.drillDown.y.domain([0, 1]); // transition all data rectangles accordingly el.drillDown.rect.transition().duration(el.drillDown.chartTypeTransitionDuration).delay(function (d, i) { return i * 10; }).attr("y", function (d) { var total = d3.sum(d3.values(d.data)); return el.drillDown.y(d[1] / total); }).attr("height", function (d) { var total = d3.sum(d3.values(d.data)); return el.drillDown.y(d[0] / total) - el.drillDown.y(d[1] / total); }).transition().attr("x", function (d, i) { return el.drillDown.x(i); }).attr("width", el.drillDown.x.bandwidth()); // update format of y axis tick labels. el.drillDown.yAxis.tickFormat(el.drillDown.formatPercent); // transition y axis el.drillDown.svg.selectAll(".y.axis").transition().delay(500).duration(el.drillDown.chartTypeTransitionDuration).call(el.drillDown.yAxis); return; } // end function transitionPercent /* name: resetChart description: function to transition chart back to oroginsal default chart type view arguments: n/a returns: n/a calls: transitionStacked called from: reset button */ }, { key: "resetChart", value: function resetChart() { var el = this; // // remove classes from data rectangles. d3.selectAll(".dataRect").classed("dataCategoryLayer-selected", false); d3.selectAll(".horizon").classed("dataCategoryLayer-selected", false); // remove hihglighting from all charttype buttons. d3.selectAll(".chartType").classed("selected", false); // return chart-slected highlighting to stack-chart d3.selectAll(".chartType.stacked").classed("selected", true); if (el.drillDown.selectedLayer != null) { // revert data rectangle styling back to default d3.selectAll("." + el.drillDown.selectedLayer).classed("layerHighlightColour", false).classed("blockHighlightColour", false).style("stroke", "none").style("opacity", 1).style("fill", el.baseColourPalette[el.drillDown.numberOfColumns][el.drillDown.selectedLayerIndex]); d3.selectAll(".layerHighlightColour").classed("layerHighlightColour", false); // remove all tyopes of tooltips // document.querySelector(".selectedRect").classList.remove("selectedRect"); d3.selectAll(".selectedRect").classed("selectedRect", false); d3.selectAll(".sticky-tooltip").remove(); d3.selectAll(".sticky-group").remove(); } // end if ... // call function to transition back to default chart el.transitionStacked(); el.addGridlines(); return; } // end function resetChart /* name: resetChart description: The function that change the tooltip when user hover / move / leave a cell arguments: n/a returns: n/a calls: called from: */ }, { key: "articles_mouseover", value: function articles_mouseover(element, d, i) { var el = this; var year = el.drillDown.arrayOfYears[i]; var data = d.data; var rectX = Number(d3.select(element).attr("x")); var rectY = Number(d3.select(element).attr("y")); var rectWidth = Number(d3.select(element).attr("width")); var rectHeight = Number(d3.select(element).attr("height")); // localise width of visual to allow tooltip to be positioned correctly in relation to center of chart. var visWidth = this.vis.width; // attach temporary tooltip this.tooltip = d3.selectAll(".drillDown-over-time-chart").append("g").attr("class", "tooltip-group active-" + element.classList[0].match(/\d+/)[0]).attr("id", "tooltip-group").attr("transform", function () { if (rectX > visWidth / 2) { return "translate(" + (rectX - 6) + "," + (rectY - 35) + ")"; } else { return "translate(" + (rectX + rectWidth + 6) + "," + (rectY - 35) + ")"; } }).append("g").attr("class", "tooltip-pointer-group").attr("transform", "translate(0,0)").append("rect").attr("class", "tooltip-pointer").attr("x", 15).attr("y", 15).attr("width", 20).attr("height", 20); d3.selectAll(".tooltip-group").append("rect").attr("class", "tooltip-frame") // .attr("x", function () { // if (rectX > visWidth / 2) { // return Number(-el.drillDown.tooltipRectWidth); // } else { // return 0; // } // }) .attr("y", -10) /* .attr("width", el.drillDown.tooltipRectWidth) */.attr("height", el.drillDown.tooltipRectHeight); var total = 0; var tooltipEntries = data.map(function (d, i) { total = total + d; return el.drillDown.categories[i] + " - " + el.numberWithCommas(d); }); var tooltipEntriesReversed = _toConsumableArray(tooltipEntries).reverse(); tooltipEntriesReversed.push("------------------------------------------"); tooltipEntriesReversed.push("Total - " + el.numberWithCommas(total)); el.drillDown.selectedLayer = d3.select(element)._groups[0][0].classList[0]; el.drillDown.selectedLayerIndex = el.drillDown.selectedLayer.replaceAll("layer-", ""); d3.selectAll(".tooltip-group").append("text").attr("class", function () { return "tooltip-text year bold"; }).attr("x", function () { if (rectX > visWidth / 2) { return -el.drillDown.tooltipRectWidth + 15; } else { return 15; } }).attr("y", 20).text(year); d3.selectAll(".tooltip-group").selectAll(".tooltipEntries").data(tooltipEntriesReversed).enter().append("text").attr("class", function (d, i) { var str = "tooltip-text tooltip-text-" + i; if (i === el.drillDown.categories.length - 1 - +el.drillDown.selectedLayer.replaceAll("layer-", "")) { str = str + " layerHighlightColour bold"; } if (i === tooltipEntriesReversed.length - 1) { str = str + " bold large"; } return str; }).attr("x", function () { if (rectX > visWidth / 2) { return -el.drillDown.tooltipRectWidth + 15; } else { return 15; } }).attr("y", function (d, i) { return 25 + (i + 1) * 17.5; }).text(function (d) { return d; }); var list = document.querySelectorAll(".tooltip-label"); for (var i = 0; i < list.length; ++i) { if (i == this.hoverLabelIndex) { list[i].classList.add("selected"); } } // end for ... var toolTipWidth = d3.max(d3.selectAll(".tooltip-group").selectAll(".tooltip-text").nodes(), function (n) { return n.getComputedTextLength(); }) + 35; d3.selectAll(".tooltip-frame").attr("width", toolTipWidth).attr("x", function () { if (rectX > visWidth / 2) { return Number(-toolTipWidth); } else { return 0; } }); d3.selectAll(".tooltip-group").selectAll(".tooltip-text").attr("x", function () { return rectX > visWidth / 2 ? -toolTipWidth + 15 : 15; }); // // IMPROVED VERSION ... // add class definitions to legend DOM components to show highlighting/selection d3.selectAll(".legendItemRect.legendItemRect-" + el.drillDown.selectedLayerIndex).classed("layerHighlightColour", true); d3.selectAll(".dataRect.layer-" + el.drillDown.selectedLayerIndex).classed("layerHighlightColour", true); d3.select(element).classed("blockHighlightColour", true); // } // end function mouseover }, { key: "articles_mouseleave", value: function articles_mouseleave(element, d, i) { // return; var el = this; this.hoverLabelIndex = null; element.classList.contains(".tooltip-label") && document.querySelector(".tooltip-label").classList.remove("selected"); // // IMPROVED VERSION OF USER MOUSE INTERACTION ... // the user has just mouseleave'd a data category horizon, ... , but that data category horizon is NOT currently selected by the user ... if (el.drillDown.isLayerSelected == null) { d3.selectAll(".sticky-group").remove(); d3.selectAll(".tooltip-group:not(.clickedToCompare)").remove(); d3.selectAll(".pearl-div-tooltip").remove(); // // remove all styling from all legend swatches ... d3.selectAll(".legendItemRect").classed("layerHighlightColour", false); // // remove all styling from all swatches ... d3.selectAll(".dataRect").classed("layerHighlightColour", false).classed("blockHighlightColour", false); } // end if ... // // the user has just mouseleave'd a data category horizon, ... , and a different data category horizon is currently selected by the user ... else if (el.drillDown.isLayerSelected != el.drillDown.selectedLayer) { d3.selectAll(".tooltip-group:not(.clickedToCompare)").remove(); d3.selectAll(".pearl-div-tooltip").remove(); // // remove all styling from all legend swatches ... // d3.selectAll(".legendItemRect").classed("layerHighlightColour", false); // // remove all styling from dataRect blocks on selected layer d3.selectAll(".dataRect." + el.drillDown.selectedLayer).classed("layerHighlightColour", false); // // add lighter styling to relevant legend swatch d3.selectAll(".legendItemRect.legendItemRect-" + el.drillDown.selectedLayerIndex).classed("layerHighlightColour", false); // remove all styling from selected dataRect block d3.select(element).classed("blockHighlightColour", false); } // end else if ... // // the user has just mouseleave'd a data category horizon, ... , and that data category horizon is currently selected by the user ..., else { d3.selectAll(".tooltip-group:not(.clickedToCompare)").remove(); d3.selectAll(".pearl-div-tooltip").remove(); // // remove all styling from all legend swatches ... d3.selectAll(".legendItemRect").classed("layerHighlightColour", false); // // remove all styling from all swatches ... d3.selectAll(".dataRect").classed("layerHighlightColour", false).classed("blockHighlightColour", false); // // add lighter styling to relevant legend swatch d3.selectAll(".legendItemRect.legendItemRect-" + el.drillDown.selectedLayerIndex).classed("layerHighlightColour", true); // // add lighter styling to all dataRect blocks on selected data horizon d3.selectAll(".dataRect." + el.drillDown.selectedLayer).classed("layerHighlightColour", true); // // add darker highlighting styling from selected dataRect block d3.select(element).classed("blockHighlightColour", true); } // end else ... return; } // end mouseleave /* name: clickChart description: arguments: returns: calls: called from: */ }, { key: "clickChart", value: function clickChart() { var el = this; document.getElementById("resetChart").disabled = false; if (el.drillDown.isLayerSelected == el.drillDown.selectedLayer) { el.drillDown.isPreviousLayerSelected = el.drillDown.isLayerSelected; el.drillDown.isLayerSelected = null; } else { el.drillDown.isPreviousLayerSelected = el.drillDown.isLayerSelected; el.drillDown.isLayerSelected = el.drillDown.selectedLayer; // d3.selectAll(".dataRect") // .style("stroke", "none") // .style("stroke-width", 0); // d3.selectAll(".dataRect." + el.drillDown.isPreviousLayerSelected) // .style("stroke", "red") // .style("stroke-width", 2); } return; } // end function clickChart /* name: numberWithCommas description: function to add 1000s comma seperators to long numbers arguments: n/a returns: n/a calls: n/a called from: drawArticlesOverTimeChart */ }, { key: "numberWithCommas", value: function numberWithCommas(x) { if (x) { return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); } return ""; } // end function numberWithCommas /* name: addGridlines description: function to arguments: n/a returns: n/a calls: n/a called from: */ }, { key: "addGridlines", value: function addGridlines() { var el = this; // remove all y-axis tick gridlines before redraw d3.selectAll(".axisGridlines").remove(); // draw tick grid lines extending from y-axis ticks on axis across scatter graph var yticks = d3.selectAll(".y.axis").selectAll(".tick"); yticks.append("svg:line").attr("class", "axisGridlines").attr("y0", 0).attr("y1", 0).attr("x1", 0).attr("x2", el.vis.width - el.drillDown.margin.left - el.drillDown.margin.right); return; } // end function addGridlines /* name: buildBreadcrumb description: function to arguments: n/a returns: n/a calls: n/a called from: */ }, { key: "buildBreadcrumb", value: function buildBreadcrumb() { var el = this; d3.selectAll(".breadcrumbLevel").remove(); d3.selectAll(".breadcrumb").selectAll(".breadcrumbLevel").data(el.drillDown.breadcrumbArray).enter().append("a").attr("class", function (d, i) { return "breadcrumbLevel " + d; }).html(function (d, i) { return i == 0 ? el.drillDown.mainTopic : " > " + d; }).on("click", function (d, i) { var index = i; var selectedCategory = d; var newTree = []; var stackName = null; if (selectedCategory != "root") { var _iterateObject = function iterateObject(obj) { for (var prop in obj) { if (_typeof(obj[prop]) == "object") { _iterateObject(obj[prop]); } // end if ... else { if (el.drillDown.stackNames.indexOf(obj[prop]) != -1) { stackName = obj[prop]; } // end if ... if (obj[prop] == selectedCategory) { newTree.push({ category: stackName, size: obj["size"], children: obj["children"] }); } // end if ... } // end else ... } // end for ... }; // end function iterateObject // https://www.easyprogramming.net/javascript/recursive_nested_json_function.php _iterateObject( /* tree */el.drillDown.data, selectedCategory); } else { newTree = JSON.parse(JSON.stringify(el.drillDown.data)); } el.drillDown.breadcrumbArray = el.drillDown.breadcrumbArray.slice(0, index + 1); el.buildBreadcrumb(); el.recurseTree(newTree); return; }); return; } // end function buildBreadcrumb /* browser window screen widths and heights https://andylangton.co.uk/blog/development/get-viewportwindow-size-width-and-height-javascript http://ryanve.com/lab/dimensions/ If you are using jQuery, you can get the size of the window or the document using jQuery methods: $(window).height(); // returns height of browser viewport $(document).height(); // returns height of HTML document (same as pageHeight in screenshot) $(window).width(); // returns width of browser viewport $(document).width(); // returns width of HTML document (same as pageWidth in screenshot) For screen size you can use the screen object in the following way: screen.height; screen.width; */ }, { key: "alertSize", value: function alertSize() { var el = this; var myWidth = 0; var myHeight = 0; if (typeof window.innerWidth == "number") { //Non-IE myWidth = window.innerWidth; myHeight = window.innerHeight; } else if (document.documentElement && (document.documentElement.clientWidth || document.documentElement.clientHeight)) { //IE 6+ in 'standards compliant mode' myWidth = document.documentElement.clientWidth; myHeight = document.documentElement.clientHeight; } else if (document.body && (document.body.clientWidth || document.body.clientHeight)) { //IE 4 compatible myWidth = document.body.clientWidth; myHeight = document.body.clientHeight; } el.vis.width = myWidth; el.vis.height = myHeight; return; } // end function alertSize /* NAME: drillDown_windowResize DESCRIPTION: function called when user resizes window. handles updating of content reliant on dimension of window ARGUMENTS TAKEN: none ARGUMENTS RETURNED: none CALLED FROM: manual user resizing of browser window CALLS: alertSize addGridlines http://bl.ocks.org/johangithub/97a186c551e7f6587878 */ }, { key: "drillDown_windowResize", value: function drillDown_windowResize() { var el = this; el.alertSize(); // function call to get current browser window dimensions // update width of all base SVG panels on which charts are built/appended ... d3.selectAll(".pearl-svg").attr("width", el.vis.width); // // ARTICLES OVER TIME CHART ... // // update chart x axis el.drillDown.x.rangeRound([0, el.vis.width - el.drillDown.margin.left - el.drillDown.margin.right]); el.drillDown.xAxis = d3.axisBottom().scale(el.drillDown.x); // update x numeric axis d3.selectAll(".x.axis").call(el.drillDown.xAxis); // update chart x axis el.drillDown.xTime.rangeRound([0, el.vis.width - el.drillDown.margin.left - el.drillDown.margin.right]); el.drillDown.xAxisTime = d3.axisBottom().scale(el.drillDown.xTime); // update x numeric axis d3.selectAll(".xTime.axis").call(el.drillDown.xAxisTime); el.addGridlines(); // transition new data rectangle to groups d3.selectAll(".horizon").selectAll(".dataRect").attr("x", function (d, i) { return el.drillDown.x(i); }).attr("width", el.drillDown.x.bandwidth()); // update legend position d3.selectAll(".legend").attr("transform", "translate(" + Number(el.vis.width - el.drillDown.margin.right - 20) + "," + 75 + ")"); // initialise and append x-axis main title label d3.selectAll(".xAxisTitle").attr("x", el.vis.width / 2); return; } // end function drillDown_windowResize }]); }(); // end export default class