@springernature/nn-charts
Version:
Visualization for DAS products
1,038 lines (947 loc) • 44.9 kB
JavaScript
;
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