UNPKG

@springernature/nn-charts

Version:
1,123 lines (982 loc) 102 kB
"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