@springernature/nn-charts
Version:
Visualization for DAS products
1,123 lines (982 loc) • 102 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var d3 = _interopRequireWildcard(require("d3"));
var _common = require("../../utils/common");
var _shapeGeneration = require("../../utils/shape-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 _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 _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
var DEFAULT_CONFIG = {
yAxis: {
title: "Published Research Papers"
},
xAxis: {
title: "Year Published In"
},
defaultTimeInterval: "0"
};
var LineChart = exports["default"] = /*#__PURE__*/function () {
function LineChart(chartData, title, parentElementClassName) {
var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : DEFAULT_CONFIG;
_classCallCheck(this, LineChart);
// function call to implement chart responsiveness after window resizing
// this.window = window.addEventListener("resize", () =>
// this.topicModellingLineChart_windowResize()
// );
// get browser doc window size dimensions
this.svgBasePanelClassNames = ["line-chart-svg"];
this.vis = {
width: document.body.clientWidth > 1000 ? document.body.clientWidth : 1000,
height: document.body.clientHeight > 450 ? document.body.clientHeight : 450
};
// update chart xis titles based on language (need to be completed)
this.axisTitle = {
y: options.yAxis.title,
x: options.xAxis.title
} /* chart axis titles definitions */;
this.parentElementClassName = parentElementClassName;
this.zoom = {};
this.focus_lineChartViewType = "cumulativeArticleCount"; // current view state of upper focus line chart (cumulative count of point in time count)
this.context_lineChartViewType = "cumulativeArticleCount"; // current view state of lower contxt line chart (cumulative count of point in time count
this.currentXAxisDomain = []; // current data domain of axis
this.currentDataBeingDisplayed = {};
this.data = chartData; // data ingested for building chart.
this.title = title; // chart titles
this.button_width = 40; // chart Form style button width
this.button_height = 14; // chart Form style button height
this.formatDate = d3.timeFormat("%m %Y"); // date formatter to allow data to be plotted on x axis
this.formatIntermediateDate = d3.timeFormat("%b '%y"); // date formatter to allow data to be plotted on x axis
this.formatWholeYearDate = d3.timeFormat("%Y"); // date formatter to allow data to be plotted on x axis
this.formatDateLabelling = d3.timeFormat("%B %e, %Y"); // get from programmable time to label format time
this.parseDatePEARL = d3.timeParse("%m %Y");
this.formatDatePEARLYear = d3.timeFormat("%Y");
this.formatDatePEARLMonth = d3.timeFormat("%m");
this.parseDatePEARLYear = d3.timeParse("%Y");
this.collapsibleTreeData = {
name: "root",
children: []
}; // base structure for topic modelling topic tree
this.maxLinesReached = false; // boolean var to denote if max number of lines is currenttly displayed on line chart
this.defaultLineToDraw = "total"; // draw this line at point of chart load and build
this.defaultLineColour = "#E25F2B"; // draw this line at point of chart load and build in this colour
this.defaultBarColour = "#999999"; //
this.colours = [
// colour ramop for styling line chart data lines
"#38A8DC", "#0370B4", "#253D7E", "#FFE7AC", "#F5AC5A", "#B63019", "#DDE2E9", "#BBC2CA", "#8A94A0"];
this.coloursUsed = []; // initialise array to contain colours that are currently being used on line chart
// initialise array to contain colours that are still available to use on line chart
this.coloursAvailable = [/* "#E25F2B" */ /* NATURE ORANGE - RETIREVED FROM 'defaultLineColour' BELOW */
"#D9035C" /* PINK */, "#694594" /* PURPLE */, "#266DB5" /* BLUE */, "#1E98A3" /* TURQUOISE */, /* "#8B949F", */ /* REMOVED AS ITS TOO CLOSE TO DEFAUTL GREY FILL FOR NODE CIRCLES ; NO DECERNIBLE DIFFERENCE */
"#DDB70C" /* YELLOW */, "#7DB72D" /* APPLE GREEN */, "#2F965E" /* FOREST GREEN */];
this.lineCounter = 0; // dataa line counter for lines being displayed
this.smallMultipleSortVariable = "page_rank"; // default sort variable for concepts bar chart.
this.yAxisDomainMaxRounding = 10; // rounding var for y axis
this.smallMultiplesChart = {};
this.smallMultiplesChartxDomainMaximum = 0;
this.selectedSubTopics = {};
this.brush = ""; // reference to brush interaction on context bar chart ...
this.bisect = ""; // bisector on upper line chart to detemine which date to display on line chart tooltip
this.focus = ""; // reference to upper focus line chart
this.context = ""; // reference to lower context line chart
/* upper line chart margins */
this.margin = {
top: 70,
right: 100,
bottom: 180,
left: 100
};
/* lower line chart margins */
this.margin2 = {
top: 365,
right: 100,
bottom: 50,
left: 100
};
// this.height2 =
// Number(this.svg.attr("height")) - this.margin2.top - this.margin2.bottom; // determine height of lower line chart
this.legend = null; // initialise ref to legend
this.focusLine = ""; // initialise ref to upper chart line path generator
this.contextLine = ""; // initialise ref to lower chart line path generator
this.LegendArray = []; // initialise array contain legend elements to draw.
this.formatDatePEARLLabelling_v2 = d3.timeFormat("%b %d, %Y"); // get from programmable time to label format time
this.constructedLineData2 = {}; // initialise obect to contain data for data lines
this.tickLabelFrequencyMatrixObject = {
/* timeRange */ /* Screen width */
150: {
3000: 5,
1200: 10,
900: 15,
600: 30,
400: 50,
100: 50
},
100: {
3000: 5,
1200: 10,
900: 10,
600: 25,
400: 25,
100: 50
},
50: {
3000: 4,
1200: 4,
900: 4,
600: 4,
400: 4,
100: 4
},
25: {
3000: 4,
1200: 4,
900: 4,
600: 2,
400: 2,
100: 2
},
10: {
3000: 1,
1200: 2,
900: 2,
600: 2,
400: 2,
100: 2
}
};
this.defaultTimeInterval = options.defaultTimeInterval; // 0, 5, 10, 20
}
/*
name: init
description: function to call fucntion to initially building of chart
arguments: none
returns: none
calls: drawLineChart
called from: textDemo.js (nn-charts-poc)
*/
return _createClass(LineChart, [{
key: "init",
value: function init() {
try {
this.drawLineChart();
} catch (error) {
console.error("Error in LineChart.init function: ", error);
}
}
/*
name: drawLineChart
description: function to call to initially building of chart
arguments: none
returns: none
calls: set_FocusYdomain
set_ContextYdomain
scaleDate
addLineChart_xAxisTickGridLines
addLineChart_yAxisTickGridLines
brushed_snapped
setYdomains
called from: init
*/
}, {
key: "drawLineChart",
value: function drawLineChart() {
//
var el = this;
d3.select(".".concat(this.parentElementClassName)).selectAll("*").remove();
// determine width on container at point of onload.
this.width = (d3.selectAll(".".concat(this.parentElementClassName)).node().getBoundingClientRect().width > 1000 ? d3.selectAll(".".concat(this.parentElementClassName)).node().getBoundingClientRect().width : 1000) - this.margin.left - this.margin.right; // change the width logic
el.svg = (0, _shapeGeneration.createBaseSVGPanel)(document.querySelector(".".concat(el.parentElementClassName)), el.svgBasePanelClassNames, d3.selectAll(".".concat(this.parentElementClassName)).node().getBoundingClientRect().width > 1000 ? d3.selectAll(".".concat(this.parentElementClassName)).node().getBoundingClientRect().width : 1000, 750);
this.height = 750 - this.margin.top - this.margin.bottom; // determine height of upper line chart
// initial x axis and its physical extent
this.x = d3.scaleTime().range([0, this.width]);
// initial y axis and its physical extent
this.y = d3.scaleLinear().range([this.height, 0]);
// determine and update dimensions of chart
el.height = el.svg.attr("height") - el.margin.top - el.margin.bottom;
el.height2 = el.svg.attr("height") - el.margin2.top - el.margin2.bottom;
// /* let */ el.N = el.getCorrectTickLabelFrequency(width);
// initialise array to contain legend elements
el.LegendArrayPrefix = [];
/* initialize selected subtopics list with main topic details
as line for main topic is drawn initially */
var mainTopicId = "topicID-0";
el.selectedSubTopics[mainTopicId] = {
subtopic: el.title,
topicID: mainTopicId,
colour: el.defaultLineColour
};
// filter out null years from data
el.data = el.data.filter(function (_ref) {
var year = _ref.year;
return Number(year);
});
// update main data with formmatted date stamps
el.data.forEach(function (d) {
d.formattedDate = el.parseDatePEARL(d.month + " " + d.year);
});
el.dates = d3.timeMonths(el.data[0].formattedDate, el.data[el.data.length - 1].formattedDate);
// filter full data down to just that relating to default line to draw (i.e. main topic for which page is built)
var data = el.data.filter(function (d, i) {
// filter on "total" value
return d.label == "total";
});
// update widths of both line charts
el.x2 = d3.scaleTime().range([0, el.width]);
el.y2 = d3.scaleLinear().range([el.height2, 0]);
// update definintions of all axes
el.lineChart_xAxis = d3.axisBottom(el.x).tickValues(el.dates).tickFormat(d3.timeFormat("%b '%y"));
el.lineChart_xAxis2 = d3.axisBottom(el.x2).tickValues(el.dates).tickFormat(d3.timeFormat("%Y"));
el.lineChart_yAxis = d3.axisLeft(el.y);
el.lineChart_yAxisRight = d3.axisRight(el.y);
el.lineChart_yAxis2 = d3.axisLeft(el.y2);
// define zoom function for lower context line chart.
el.zoom = d3.zoom().scaleExtent([1, Infinity]).translateExtent([[0, 0], [el.width, el.height]]).extent([[0, 0], [el.width, el.height]]).on("zoom", el.zoomed);
// Add the data line to focus line chart
// line path generator function for upper focus line chart
el.focusLine = d3.line().curve(d3.curveLinear).x(function (d) {
return el.x(d.formattedDate);
}).y(function (d) {
return el.y(d[el.focus_lineChartViewType]);
});
// Add the data line to context line chart
// line path generator function for lower context line chart
el.contextLine = d3.line().curve(d3.curveLinear).x(function (d) {
return el.x2(d.formattedDate);
}).y(function (d) {
return el.y2(d[el.context_lineChartViewType]);
});
// append clip path area rectangle to main base SVG panel to clip plotted chart area and prevent data lines
// and data dots from being displayed outside range of chart axes
/* .attr("clip-path", "url(#clip)"); */
el.svg.append("defs").append("clipPath").attr("id", "clip").append("rect").attr("id", "clipRect").attr("x", 0).attr("y", 0).attr("width", el.width).attr("height", el.height).style("fill", "red").style("opacity", 0.5);
// append group element to contain upper focus chart content
el.focus = el.svg.append("g").attr("class", "focus").attr("transform", "translate(" + el.margin.left + "," + el.margin.top + ")");
// append group element to contain lower context chart content
// el.context = el.svg
// .append("g")
// .attr("class", "context")
// .attr(
// "transform",
// "translate(" + el.margin2.left + "," + el.margin2.top + ")"
// );
// define x axis domain extent for both upper and lower chart, based on data date range
el.x.domain(d3.extent(data, function (d) {
return d.formattedDate;
}));
// generate a new data nest from base topic modelling data.
el.nestedData = d3.nest()
// new solution ...
// creates unique subtopic if they have idential names ...
.key(function (d) {
return d.id;
})
// old solution ...
// potentilly creates missing subtopic if they have idential names ...
// .key(function (d) {
// return d.label;
// })
.entries(el.data);
// update new JSON nest object with additional data.
el.nestedData.forEach(function (d, i) {
var id = "topicID-" + d.values[0].id;
el.constructedLineData2[id] = [];
el.constructedLineData2[id] = d.values;
// here we are adding a cumualtive article count to plot on to topic modelling line
var currentCumulativeArticleCount = 0;
el.constructedLineData2[id].forEach(function (d, i) {
// locally store data for line being considered in forEAch loop
var dateEntry = d;
// calculate cumulative article count to help define chart axis domains and tooltip content
currentCumulativeArticleCount = i == 0 ? 0 : el.constructedLineData2[id][i - 1].cumulativeArticleCount;
d.cumulativeArticleCount = Number(currentCumulativeArticleCount + dateEntry.articles);
});
}); // end forEach...
// define y-axis 'data' domain
// round to nearest X ...
el.y.domain([0, Math.ceil(d3.max(data, function (d) {
return d[el.focus_lineChartViewType];
}) / el.yAxisDomainMaxRounding) * el.yAxisDomainMaxRounding]).nice();
// PEARL-1142 - commented code
// copy y and x axis domains to y2 and el.x2
// el.x2.domain(el.x.domain());
// el.y2.domain(el.y.domain());
// add event listener for data view type toggle button for upper FOCUS chart
// https://stackoverflow.com/questions/39846282/how-to-add-the-text-on-and-off-to-toggle-button
// document
// .querySelector("#dataViewTypeToggleBtn_focus-horizontal")
// .addEventListener("change", function () {
// const toggle = d3.selectAll(".dataViewTypeToggleBtn_focus");
// toggle.classed(
// "cumulativeArticleCount",
// !toggle.classed("cumulativeArticleCount")
// );
// // update line type variable
// el.focus_lineChartViewType = toggle.classed("cumulativeArticleCount")
// ? "cumulativeArticleCount"
// : "articles";
// // action axis updates depending on if charts are locked together or not.
// if (d3.select("#locker").classed("locked")) {
// el.context_lineChartViewType = el.focus_lineChartViewType;
// el.set_FocusYdomain("transitionChartViewType");
// el.set_ContextYdomain("transitionChartViewType");
// // switch alternate line chart's toggle switch state to match that of the one just selected and switch by user
// if (
// document.getElementById("dataViewTypeToggleBtn_focus").checked ==
// true
// ) {
// document.getElementById(
// "dataViewTypeToggleBtn_context"
// ).checked = true;
// //
// d3.selectAll(".dataViewTypeToggleBtn_context").classed(
// "cumulativeArticleCount",
// false
// );
// } else {
// document.getElementById(
// "dataViewTypeToggleBtn_context"
// ).checked = false;
// //
// d3.selectAll(".dataViewTypeToggleBtn_context").classed(
// "cumulativeArticleCount",
// true
// );
// }
// } else {
// // call function to just update y aixs of Focus chart
// el.set_FocusYdomain("transitionChartViewType");
// } // end else ...
// });
// PEARL-1142 - commented code
// add event listener for data view type toggle button for upper FOCUS chart
// https://stackoverflow.com/questions/39846282/how-to-add-the-text-on-and-off-to-toggle-button
// document
// .querySelector("#dataViewTypeToggleBtn_context")
// .addEventListener("change", function () {
// const toggle = d3.selectAll(".dataViewTypeToggleBtn_context");
// toggle.classed(
// "cumulativeArticleCount",
// !toggle.classed("cumulativeArticleCount")
// );
// el.context_lineChartViewType = toggle.classed("cumulativeArticleCount")
// ? "cumulativeArticleCount"
// : "articles";
// if (d3.select("#locker").classed("locked")) {
// el.focus_lineChartViewType = el.context_lineChartViewType;
// el.set_FocusYdomain("transitionChartViewType");
// el.set_ContextYdomain("transitionChartViewType");
// // switch alternate line chart's toggle switch state to match that of the one just selected and switch by user
// if (
// document.getElementById("dataViewTypeToggleBtn_context").checked ==
// true
// ) {
// document.getElementById(
// "dataViewTypeToggleBtn_focus"
// ).checked = true;
// //
// d3.selectAll(".dataViewTypeToggleBtn_focus").classed(
// "cumulativeArticleCount",
// false
// );
// } else {
// document.getElementById(
// "dataViewTypeToggleBtn_focus"
// ).checked = false;
// //
// d3.selectAll(".dataViewTypeToggleBtn_focus").classed(
// "cumulativeArticleCount",
// true
// );
// }
// } else {
// el.set_ContextYdomain("transitionChartViewType");
// }
// });
// PEARL-1142 - commented code
// add event listener related to the chart locked checkbox (currentyl hidden and not functional to users)
// document.querySelector("#locker").addEventListener("change", function () {
// const checkbox = d3.select("#locker");
// checkbox.classed("locked", !checkbox.classed("locked"));
// d3.selectAll(".lockLabel").text(function () {
// return checkbox.classed("locked") ? "Locked" : "Unlocked";
// });
// if (checkbox.classed("locked")) {
// el.context_lineChartViewType = el.focus_lineChartViewType;
// el.set_ContextYdomain("transitionChartViewType");
// if (
// document.getElementById("dataViewTypeToggleBtn_focus").checked == true
// ) {
// document.getElementById(
// "dataViewTypeToggleBtn_context"
// ).checked = true;
// //
// d3.selectAll(".dataViewTypeToggleBtn_context").classed(
// "cumulativeArticleCount",
// false
// );
// } else {
// document.getElementById(
// "dataViewTypeToggleBtn_context"
// ).checked = false;
// //
// d3.selectAll(".dataViewTypeToggleBtn_context").classed(
// "cumulativeArticleCount",
// true
// );
// }
// } // end if ...
// });
// Initialise the main topic data line to draft by default on page load ...
el.defaultLineToDraw = el.title + " (total)";
// push default data line construct on to array of data lines as first item ...
el.constructedLineData2[el.defaultLineToDraw] = el.constructedLineData2["topicID-0"];
// ADDED TO ALLOW MAIN TOPIC DATA LINE TO BE ADDED/REMOVED VIA TOPIC TREE
el.constructedLineData2["topicID-0"] = [];
el.constructedLineData2["topicID-0"] = el.constructedLineData2[el.title + " (total)"];
// NEW
el.currentDataBeingDisplayed["topicID-0"] = el.constructedLineData2[el.title + " (total)"];
// append default path to upper focus chart.
el.focus.append("path").datum(el.constructedLineData2[el.defaultLineToDraw]).attr("class", function (d) {
return "focusChartLine line " + el.title + " DATALINE topicID-" + d[0].id;
}).style("stroke", el.defaultLineColour).attr("d", el.focusLine);
// append new group g element to attach all the focus chart data dots to (to tidy on DOM mainly)
el.focus.append("g").attr("class", "group-focus-dataDots").attr("transform", "translate(0,0)");
// append default data dots to upper focus chart.
d3.selectAll(".group-focus-dataDots").selectAll(".dataDots").data(el.constructedLineData2[el.defaultLineToDraw]).enter().append("circle").attr("class", function (d, i) {
return "focusChartLine dataDots " + el.title + " DATADOTS topicID-" + d.id;
}).style("stroke", el.defaultLineColour).style("fill", el.defaultLineColour).attr("cx", function (d, i) {
return el.x(d.formattedDate);
}).attr("cy", function (d, i) {
return el.y(d[el.focus_lineChartViewType]);
}).attr("r", 3.5);
// append x axis to upper focus chart.
el.focus.append("g").attr("class", "lineChart focusChart axis axis--x")
/* .attr("clip-path", "url(#clip)") */.attr("transform", "translate(0," + el.height + ")").call(el.lineChart_xAxis);
// append left y axis to upper focus chart.
el.focus.append("g").attr("class", "lineChart axis axis--y left").call(el.lineChart_yAxis);
// append right y-axis to upper focus chart.
el.focus.append("g").attr("class", "lineChart axis axis--y right").attr("transform", "translate(" + el.width + "," + 0 + ")").call(el.lineChart_yAxisRight);
// append y axis title to left y axis
d3.selectAll(".lineChart.axis.axis--y.left").append("text").attr("class", "axis-title").attr("transform", "rotate(-90)").attr("x", -el.margin.top - el.height / 2) // horizontal offset based on chart dimensions
.attr("y", -el.margin.left + 20) // vertical offset based on chart dimensions
// .style("text-anchor", "middle") // Center the text vertically
.text(el.axisTitle.y);
// append y axis title to right y axis
// d3.selectAll(".lineChart.axis.axis--y.right");
// .append("text")
// .attr("class", "yAxisTitleRight_topicModelling")
// .attr("x", 0)
// .attr("y", -10)
// .style("text-anchor", "end")
// .text(el.axisTitle.y);
// append the x-axis title
d3.selectAll(".lineChart.axis.axis--x").append("text").attr("class", "axis-title").attr("x", el.width / 2) // Center the title horizontally
.attr("y", el.height - 2 * el.margin.bottom - 30) // Position the title just below the axis
.style("text-anchor", "middle").text(el.axisTitle.x);
// append group element to base svg panel append legend
el.legend = el.focus.append("g").attr("class", "pearl-svg-legend").attr("pointer-events", "none").attr("transform", "translate(" + 10 + "," + 470.5 + ")");
// PEARL-1142 - commented code
// append data line to lower context chart
// el.context
// .append("path")
// .datum(el.constructedLineData2[el.defaultLineToDraw])
// .attr("class", function (d, i) {
// return (
// "contextChartLine context line " +
// el.title +
// " DATALINE topicID-" +
// d[0].id
// );
// })
// .style("stroke", el.defaultLineColour)
// .attr("d", el.contextLine);
// PEARL-1142 - commented code
// append default data dots to upper focus chart.
// d3.selectAll(".context")
// .selectAll(".dataDots")
// .data(el.constructedLineData2[el.defaultLineToDraw])
// .enter()
// .append("circle")
// .attr("class", function (d, i) {
// return (
// "contextChartLine dataDots " + el.title + " DATADOTS topicID-" + d.id
// );
// })
// .style("stroke", el.defaultLineColour)
// .style("fill", el.defaultLineColour)
// .attr("cx", function (d, i) {
// return el.x2(d.formattedDate);
// })
// .attr("cy", function (d, i) {
// return el.y2(d[el.context_lineChartViewType]);
// })
// .attr("r", 2.0);
// PEARL-1142 - commented code
// append x axis to lower context chart
// el.context
// .append("g")
// .attr("class", "lineChart contextChart axis axis--x")
// .attr("transform", "translate(0," + el.height2 + ")")
// .call(el.lineChart_xAxis2);
// PEARL-1142 - commented code
// append left y axis to lower context chart
// el.context
// .append("g")
// .attr("class", "lineChart contextChart axis axis--y")
// .attr("transform", "translate(" + 0 + "," + 0 + ")")
// .call(el.lineChart_yAxis2);
// call function to add grid lines to line chart (1)
el.addLineChart_yAxisTickGridLines();
el.addLineChart_xAxisTickGridLines();
// initialise default selection data time range to display on upper focus line chart ...
var defaultSelection = [el.x(el.x.domain()[0]), el.x(el.x.domain()[1])];
// define brush component for lower context chart ...
el.brush = d3.brushX().extent([[0, 0], [el.width, el.height2]]).on("brush", function () {
el.brushed_snapped();
});
// set up coordinates for fidged cross 'X' image on brush selection window
var crossCoords = [{
x1: 2,
y1: 18,
x2: 18,
y2: 2
}, {
x1: 2,
y1: 2,
x2: 18,
y2: 18
}];
d3.selectAll(".brush-close-group").append("rect").attr("class", "brush-close-group-background").attr("x", -2.5).attr("y", -2.5).attr("rs", 5).attr("ry", 5).attr("width", 25).attr("height", 25);
// append SVG lines to replicate close cross image icon
d3.selectAll(".brush-close-group").selectAll(".close-cross-lines").data(crossCoords).enter().append("line").attr("class", "close-cross-lines").style("fill", "none").style("stroke", "#00679A").style("stroke-width", 4).style("stroke-linecap", "round").style("pointer-events", "none").attr("x1", function (d, i) {
return d.x1;
}).attr("y1", function (d, i) {
return d.y1;
}).attr("x2", function (d, i) {
return d.x2;
}).attr("y2", function (d, i) {
return d.y2;
});
// append data[e] stamps to context brush...
d3.selectAll(".brush").selectAll(".dataStamps").data(el.x.domain()).enter().append("text").attr("class", "dataStamps").attr("x", function (d, i) {
return defaultSelection[i];
}).attr("y", -5).style("text-anchor", function (d, i) {
return i === 0 ? "end" : "start";
}).text(function (d, i) {
return el.formatDatePEARLLabelling_v2(el.x.domain()[i]);
}).classed("hide", true);
// modify and stylise brush handles.
d3.selectAll(".brush").selectAll(".resize").attr("transform", "translate(0," + 0 + ")").attr("rx", 2.5).attr("ry", 2.5).attr("height", el.height2 + 6).attr("width", 5);
// selection all brush handles and update dimensions and positionings
d3.selectAll(".handle").attr("transform", "translate(0," + 0 + ")").attr("rx", 4).attr("ry", 4).attr("height", Number(el.height2 / 3) + 6).attr("width", 8);
// update class definition to hide handles
d3.selectAll(".resize").classed("hide", true);
d3.selectAll(".handle").classed("hide", true);
// define prototype for move to front function
d3.selection.prototype.moveToFront = function () {
return this.each(function () {
this.parentNode.appendChild(this);
});
};
// define prototype for move to back function
d3.selection.prototype.moveToBack = function () {
return this.each(function () {
var firstChild = this.parentNode.firstChild;
if (firstChild) {
this.parentNode.insertBefore(this, firstChild);
}
});
};
el.redrawLegend();
if (this.defaultTimeInterval) this.scaleDate(this.defaultTimeInterval);
return;
} // end function drawLineChart()
/*
name: scaleDate
description: function to call to update line chart views to ay of the user defined/selected preset dates
arguments: none
returns: none
calls:addLineChart_xAxisTickGridLines
setYdomains
called from: drawLineChart
*/
}, {
key: "scaleDate",
value: function scaleDate() {
var timeIntervalValue = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "0";
var el = this;
var selectedSubTimeInterval = timeIntervalValue;
var start = null;
var end = null;
// remove all current/old tick gridlines from line chart
d3.selectAll(".xAxisTicks_topicModelling").remove();
// reset brush and both line charts to full extent of data
// if no time interval preset is selected (i.e. user wants to return to default, by selecting 'none')
if (selectedSubTimeInterval == "0") {
var startDate = el.data[0].formattedDate;
var endDate = el.data[el.data.length - 1].formattedDate;
// update x-axis domain to upper focus chart.
el.x.domain([startDate, endDate]);
el.currentXAxisDomain = [];
// update x axis on focus chart
d3.selectAll(".focus").selectAll(".lineChart.axis.axis--x").call(el.lineChart_xAxis);
// update data dots on focus chart
d3.selectAll(".focus").selectAll(".dataDots").attr("cx", function (d) {
return el.x(d.formattedDate);
}).attr("cy", function (d) {
return el.y(d[el.focus_lineChartViewType]);
});
// Update all data lines currently drawn on upper focus chart
el.focus.selectAll(".line").attr("d", el.focusLine);
// PEARL-1142 - commented code
// update as necessary all related class name definitions.
// d3.selectAll(".brush").selectAll(".resize").classed("hide", true);
// d3.selectAll(".brush").selectAll(".handle").classed("hide", true);
// d3.selectAll(".brush")
// .selectAll(".brush-close-group")
// .classed("hide", true);
// d3.selectAll(".brush").selectAll(".dataStamps").classed("hide", true);
// d3.selectAll(".brush").selectAll(".selection").style("display", "none");
// call function to add grid lines to line chart (3)
el.addLineChart_xAxisTickGridLines();
el.setYdomains("scaleDate");
return;
} // end if ...
//
// <option value="lastFullCalendarYear">Last Full Calendar Year</option>
else if (selectedSubTimeInterval == "lastFullCalendarYear") {
end = d3.timeYear.floor(el.x2.domain()[1]);
start = d3.timeYear.offset(d3.timeYear.floor(el.x2.domain()[1]), -1);
}
//
// <option value="fullTimeInterval">Full Time Interval</option>
else if (selectedSubTimeInterval == "fullTimeInterval") {
start = el.x2.domain()[0];
end = el.x2.domain()[1];
}
//
// <option value="lastYear">Last Year</option>
else if (selectedSubTimeInterval == "lastYear") {
end = el.x2.domain()[1];
start = d3.timeYear.offset(el.x2.domain()[1], -1);
// may need to reactivate this code snippet ...
// if (new Date(d3.timeYear.offset(el.x.domain()[0], 1)) > new Date()) {
// end = el.x2(el.x2.domain()[1]);
// }
}
//
// <option value="last5Years">Last 5 Year</option>
else if (selectedSubTimeInterval == "5") {
end = el.x.domain()[1];
// end = el.x2.domain()[1];
start = d3.timeYear.offset(el.x.domain()[1], -5);
} else if (selectedSubTimeInterval === "10") {
end = el.x.domain()[1];
// end = el.x2.domain()[1];
start = d3.timeYear.offset(el.x.domain()[1], -10);
} else if (selectedSubTimeInterval === "20") {
end = el.x.domain()[1];
// end = el.x2.domain()[1];
start = d3.timeYear.offset(el.x.domain()[1], -20);
// start = d3.timeYear.offset(el.x2.domain()[1], -20);
}
//
// <option value="lastMonth">Last Month</option>
else if (selectedSubTimeInterval == "lastMonth") {
end = el.x2.domain()[1];
start = d3.timeMonth.offset(el.x2.domain()[1], -1);
} // end else if ...
//
// <option value="last6Months">Last 6 Months</option>
else if (selectedSubTimeInterval == "last6Months") {
end = el.x2.domain()[1];
start = d3.timeMonth.offset(el.x2.domain()[1], -6);
} // end else if ... .
else {
console.log("time interval not accounted for yet.");
}
// update x axis domain definiton
el.x.domain([start, end]);
// update x axis on focus chart
d3.selectAll(".focus").selectAll(".lineChart.axis.axis--x").call(el.lineChart_xAxis);
// update data dots on focus chart
d3.selectAll(".focus").selectAll(".dataDots").attr("cx", function (d) {
return el.x(d.formattedDate);
}).attr("cy", function (d) {
return el.y(d[el.focus_lineChartViewType]);
});
// Update all data lines currently drawn on upper focus chart
d3.selectAll(".focusChartLine.line").attr("d", el.focusLine);
// PEARL-1142 - commented code
// move brush on context chart accordingly to user selection
// el.context
// .selectAll(".brush")
// .call(el.brush)
// .call(el.brush.move, [el.x2(start), el.x2(end)]);
// call function to add grid lines to line chart (4)
el.addLineChart_xAxisTickGridLines();
return;
} // end function scaleDate()
/*
name: zoomed
description: function to handle upper line chart zoom base on user interaxction on lower line chart
arguments: none
returns: none
calls:
called from:
*/
}, {
key: "zoomed",
value: function zoomed() {
var el = this;
// handle special use case
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") return; // ignore zoom-by-brush
// get transform state
var t = d3.event.transform;
el.x.domain(t.rescaleX(el.x2).domain());
// update all data lines on upper line chart
el.focus.selectAll(".line").attr("d", el.focusLine);
// update x axis on upper line chart
el.focus.selectAll(".lineChart.axis--x").call(el.lineChart_xAxis);
// update brush on lower line chart
el.context.selectAll(".brush").call(el.brush.move, x.range().map(t.invertX, t));
return;
} // end function zoomed
/*
name: brushed_snapped
description: function to handle upper line chart zoom base on user interaxction on lower line chart
arguments: none
returns: none
calls: addLineChart_xAxisTickGridLines
setYdomains
called from: drawLineChart
window resize function
*/
}, {
key: "brushed_snapped",
value: function brushed_snapped() {
var el = this;
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") {
return; // ignore zoom-by-brush
}
// display briush handles...
d3.selectAll(".resize").classed("hide", false);
d3.selectAll(".handle").classed("hide", false);
d3.selectAll(".selection").classed("hide", false);
var d0 = d3.event.selection.map(el.x2.invert),
d1 = d0.map(d3.timeMonth.round);
// If empty when rounded, use floor instead.
if (d1[0] >= d1[1]) {
d1[0] = d3.timeMonth.floor(d0[0]);
d1[1] = d3.timeMonth.offset(d1[0]);
}
// update brush definition
d3.selectAll(".brush").call(d3.event.target.move, d1.map(el.x2));
// update upper line chart x domain definiton
el.x.domain([d1[0], d1[1]]);
el.currentXAxisDomain = el.x.domain();
// update and move data lines on top focus chart
d3.selectAll(".focus").selectAll(".line").attr("d", el.focusLine);
// update data dots on upper focus chart
d3.selectAll(".focus").selectAll(".dataDots").attr("cx", function (d, i) {
return el.x(d.formattedDate);
}).attr("cy", function (d, i) {
return el.y(d[el.focus_lineChartViewType]);
});
d3.selectAll(".focus").selectAll(".lineChart.axis--x").call(el.lineChart_xAxis);
var s = d3.event.selection || el.x2.range();
// call and update zoom definiton
d3.select(e.svg).selectAll(".zoom").call(el.zoom.transform, d3.zoomIdentity.scale(el.width / (s[1] - s[0])).translate(-s[0], 0));
// update positioning of brush handle time stamps
d3.selectAll(".brush").selectAll(".dataStamps").attr("x", function (d, i) {
return el.x2(el.x.domain()[i]);
}).text(function (d, i) {
return el.formatDateLabelling(el.x.domain()[i]);
}).classed("hide", false);
// update positioning of gourp containing close cross icon
d3.selectAll(".brush-close-group").attr("transform", "translate(" + (el.x2(el.x.domain()[1]) + 10) + "," + 5 + ")").classed("hide", false);
// call function to add grid lines to line chart (5)
el.addLineChart_xAxisTickGridLines();
// call function to update and transition y-axis on both line charts to reflect a user adding/removing a data line,
// such that the new displayed data point maximum has changed (increased or decreased). This improves user understanding
// and readability.
el.setYdomains("brushed_snapped");
//
d3.selectAll(".focusChartLine").moveToFront();
d3.selectAll(".contextChartLine").moveToFront();
d3.selectAll(".brush").moveToFront();
// calculate array to containe all the data years in the full data time range ...
// el.currentFocusTimeRangeInYears = d3.timeYear.range(
// el.x.domain()[0],
// el.x.domain()[1],
// 1
// );
// el.currentFocusTimeRangeInMonths = d3.timeMonth.range(
// el.x.domain()[0],
// el.x.domain()[1],
// 1
// );
// calculate array to containe all the data years in the full data time range ...
el.currentContextTimeRangeInYears = d3.timeYear.range(el.x2.domain()[0], el.x2.domain()[1], 1);
el.currentContextTimeRangeInMonths = d3.timeMonth.range(el.x2.domain()[0], el.x2.domain()[1], 1);
// calculate the total number of data years in the full data time range ...
el.totalContextNumberOfYears = el.currentContextTimeRangeInYears.length;
el.totalContextNumberOfMonths = el.currentContextTimeRangeInMonths.length;
// d3.selectAll(".tick.xAxisTicks_topicModelling-tick-major").style(
// "display",
// "inline"
// );
el.contextTickLabelFrequencyCounter = -1;
// PEARL-1142 - commented code
// el.ContextN = el.getCorrectTickLabelFrequency(
// width,
// el.totalContextNumberOfYears
// );
// d3.selectAll(".lineChart.contextChart.axis.axis--x")
// .selectAll(".tick.xAxisTicks_topicModelling-tick-major")
// .style("display", function (d, i) {
// el.contextTickLabelFrequencyCounter++;
// return el.contextTickLabelFrequencyCounter % el.ContextN == 0
// ? "inline"
// : "none";
// });
return;
} // end function brushed_snapped
/*
name: setYdomains
description: function to handle transitioning all both y axis doamins after user interaction to change chart view type (cumu or PIT)
arguments: calledFrom - which other fucntion calls this one
returns: none
calls: addLineChart_xAxisTickGridLines
setYdomains
called from: drawLineChart
window resize function
*/
}, {
key: "setYdomains",
value: function setYdomains(calledFrom) {
var el = this;
// define transiton rate depending on where chart update request was called from
var chartFullTransitionDefinition = calledFrom == "transitionChartViewType" ? d3.transition().duration(750).ease(d3.easeLinear) : d3.transition().duration(0).ease(d3.easeLinear);
// this function dynamically changes the y-axis to fit the data in focus
// get the min and max date in focus
var focus_xleft = new Date(el.x.domain()[0]);
var focus_xright = new Date(el.x.domain()[1]);
// get the min and max date in context chart
var context_xleft = new Date(el.x2.domain()[0]);
var context_xright = new Date(el.x2.domain()[1]);
// https://stackoverflow.com/questions/46164712/filter-nested-array-of-objects-by-object-property-in-d3-js
var filteredLineData = {};
var focusMaxValues = [];
var contextMaxValues = [];
// for all line darta currently being delayed
for (var line in el.currentDataBeingDisplayed) {
var values = el.currentDataBeingDisplayed[line];
var focusMax = void 0;
var contextMax = void 0;
// filter data by time range being displayed on focus chart
// accomodate user zoom on context chart ...
filteredLineData[line] = values.filter(function (object) {
var date = object.formattedDate;
return date >= focus_xleft && date <= focus_xright;
});
// detemine maximum data value on all lines being displayed on focus chart
focusMax = d3.max(filteredLineData[line], function (d, i) {
return d[el.focus_lineChartViewType];
});
// push value on to array for later testing
focusMaxValues.push(focusMax);
// filter data by time range being displayed on context chart
// accomodate user zoom on context chart ...
filteredLineData[line] = values.filter(function (object) {
var date = object.formattedDate;
return date >= context_xleft && date <= context_xright;
});
// detemine maximum data value on all lines being displayed on context chart
// (may be different to focus chart if user has zoomed to sb time interval)
contextMax = d3.max(filteredLineData[line], function (d, i) {
return d[el.context_lineChartViewType];
});
// push value on to array for later testing
contextMaxValues.push(contextMax);
}
// finally detetmine max data values for both upper focus and lower context chart
// based on their respective states
var focusTotalMax = d3.max(focusMaxValues);
var contextTotalMax = d3.max(contextMaxValues);
// define y-axis 'data' domain for upper focus chart ...
el.y.domain([0, Math.ceil(focusTotalMax / el.yAxisDomainMaxRounding) * el.yAxisDomainMaxRounding]).nice();
// define y-axis 'data' domain for lower context chart ...
el.y2.domain([0, Math.ceil(contextTotalMax / el.yAxisDomainMaxRounding) * el.yAxisDomainMaxRounding]).nice();
// update right hand y-axis definition with new domain range
el.lineChart_yAxisRight = d3.axisRight(el.y);
d3.selectAll(".lineChart.axis.axis--y.right").transition(chartFullTransitionDefinition).call(el.lineChart_yAxisRight);
// update left hand y-axis definition with new domain range
el.lineChart_yAxis = d3.axisLeft(el.y);
d3.selectAll(".lineChart.axis.axis--y.left").transition(chartFullTransitionDefinition).call(el.lineChart_yAxis);
el.lineChart_yAxis2 = d3.axisLeft(el.y2);
d3.selectAll(".lineChart.contextChart.axis.axis--y").transition(chartFullTransitionDefinition).call(el.lineChart_yAxis2);
// update all data lines on focus chart
d3.selectAll(".focusChartLine.line").transition(chartFullTransitionDefinition).attr("d", el.focusLine);
// update all data dots on focus chart
d3.selectAll(".focus").selectAll(".dataDots").transition(chartFullTransitionDefinition).attr("cx", function (d, i) {
return el.x(d.formattedDate);
}).attr("cy", function (d, i) {
return el.y(d[el.focus_lineChartViewType]);
});
// PEARL-1142 - commented code
// update all data lines on context chart
// d3.selectAll(".context.line")
// .transition(chartFullTransitionDefinition)
// .attr("d", el.contextLine);
// // update all data dots on context chart
// d3.selectAll(".context")
// .selectAll(".dataDots")
// .transition(chartFullTransitionDefinition)
// .attr("cx", function (d, i) {
// return el.x2(d.formattedDate);
// })
// .attr("cy", function (d, i) {
// return el.y2(d[el.context_lineChartViewType]);
// });
// determine and update width var for container panel
var width = d3.selectAll(".".concat(el.parentElementClassName)).node().getBoundingClientRect().width > 1000 ? d3.selectAll(".".concat(el.parentElementClassName)).node().getBoundingClientRect().width : 1000 - el.margin.left - el.margin.right; // change the width logic
//
// remove all grid lines on chart
d3.selectAll(".yAxisTicks_topicModelling").remove();
// draw tick grid lines extending from y-axis ticks on axis across scatter graph
var yticks = d3.selectAll(".lineChart.axis.axis--y.left").selectAll(".tick");
// append y axis grid lines to left y axis
yticks.append("svg:line").attr("class", "yAxisTicks_topicModelling").attr("y0", 0).attr("y1", 0).attr("x1", 0).attr("x2", width);
// function to add/update y-axis gridlines to topic modelling line charts
// el.addLineChart_yAxisTickGridLines();
return;
} // end function setYdomains()
/*
name: addLineChart_yAxisTickGridLines
description: function called to full width gridlines to both focus line charts on X-AXIS
arguments: none
returns: none
calls: none
called from: window resize function
set_FocusYdomain
*/
}, {
key: "addLineChart_yAxisTickGridLines",
value: function addLineChart_yAxisTickGridLines() {
var el = this;
// determine and update width var for container panel
var width = d3.selectAll(".".concat(el.parentElementClassName)).node().getBoundingClientRect().width > 1000 ? d3.selectAll(".".concat(el.parentElementClassName)).node().getBound