UNPKG

@springernature/nn-charts

Version:
1,012 lines (897 loc) 69.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; var d3 = _interopRequireWildcard(require("d3")); function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); } function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function _classCallCheck(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); } /* POSSILBE SOLUTION TO PAN/ZOOM PROBLEM: d3 v4 https://gist.github.com/patricker/0434d73a1b513baf8558d60daee78418 d3 v3 https://gist.github.com/TWiStErRob/b1c62730e01fe33baa2dea0d0aa29359 */ /* CollaborationNetworkChart class definition */ var CollaborationNetworkChart = exports["default"] = /*#__PURE__*/function () { // function CollaborationNetworkChart(nodeData, edgeData, researcherID) { var _this = this; _classCallCheck(this, CollaborationNetworkChart); // function call to implement chart responsiveness after window resizing this.window = window.addEventListener("resize", function () { _this.collaboration_windowResize(); }); this.researcherID = researcherID; // database ID associated with researcher selected // dimensions stored for browser width and height this.vis = { width: document.body.clientWidth, height: document.body.clientHeight }; // /* communitynetwork obj */ this.communitynetwork = { hasBeenZoomed: false, // chart has ben zoomed in by user somehow ... resetZoom: { k: null, x: null, y: null }, // store value for zoom specfiic to chart build. Allows any vis built to be handles on load. subtopicLookup: {}, // obj to hold all sub topics associated with slected researcher's network. listSelectedResearcher: null, /* selectedResearcherRectangle: 50, */ // legendBuildCounter: 0, // counter associated with legend ... // obj containing attributes that researcher nodes can be styled/colour-filled by stylingLookup: { subtopic: "Sub-topics", countries: "Countries", F_L0: "Field of Study" }, // updated to true after chart has been built onCollaborationPageLoad: false, // obj containing attributes that researcher nodes can be scaled by scalingAttributes: [["articles", "Number of Publications"] /* ["rank", "Author Ranking on 'Dimensions'"], */ /* ["hindex", "H-Index"], */], // stotes all loaded node data nodeData: nodeData[0], // stotes all loaded edge data edgeData: edgeData[0], researcherSelected: false, // set to true if user has selected an individual researcher within netowrk presented researcherEdgeLinkSelected: false, // set to true if user has selected an individual researcher link within netowrk presented /* tooltipRectWidth: 350, */ /* tooltipRectHeight: 100, */ // legendColour: [ // "#B345A3", // "#490058", // "#01324b", // "#0070a8", // "#785ba7", // "#007373", // ], /* VRESION 1 */ /* legendColour: [ "#38A8DC", "#0370B4", "#253D7E", "#FFE7AC", "#F5AC5A", "#B63019", "#DDE2E9", "#BBC2CA", "#8A94A0", ], */ /* VRESION 2 */ /* Accent Colours: Ostwald Palette */ /* https://docs.google.com/presentation/d/11tzkjJr03TBJ-ybuWZ2f2AVVpU3AqpKPHkqi9FLCpuo/edit?pli=1#slide=id.g116540d9cba_0_1829 */ legendColour: ["#efd600", "#c82285", "#0094a4", "#ffd500", "#964091", "#00928c", "#fbba00", "#6c4796", "#008b68", "#f7a70a", "#494495", "#229863", "#eb5b25", "#1951a0", "#299751", "#e63323", "#006eb7", "#3fa535", "#e40428", "#0095bb", "#c7d530", "#e5005b", "#0085c8", "#76b82a"], width: 800, // default width of collab chart height: 600, // default height of collab chart margin: { top: 40, right: 10, bottom: 40, left: 35 }, /* topicId: "", */ /* subTopicId: "", */ /* assetId: "", */ // initialisation of obj to hold all data for nodes and links network chart graph: { nodes: [], links: [] }, // initialisation of obj to hold all data for labels on network chart label: { nodes: [], links: [] }, affiliations: [], // array to hold affiliations associrted with network ... maxNumberArticles: -1, // var to hold determined max number of articles associdated with collab network. maxNumberhIndex: -1, // var to hold determined max h index associdated with collab network. maxCircleRadius: 33, // maxmimum size scaled nodes circles. selectedChartView: /* null */ /* "Subtopics" */"subtopic", selectedRankView: /* "rank" */"articles" /* "hindex" */, communityStartYear: Infinity, // holds first year of publication for researchers in network communityEndYear: -Infinity // holds last year of publication for researchers in network }; } // end constructor /* name: init description: function to call function to initially draw researcher network collab chart. arguments: none returns: none calls: drawResearcherCollaborationNetworkChart called from: textDemo.js (nn-charts-poc) */ return _createClass(CollaborationNetworkChart, [{ key: "init", value: function init() { this.drawResearcherCollaborationNetworkChart(); return; } // end function init() /* name: drawResearcherCollaborationNetworkChart description: function to initially draw researcher network collab chart. arguments: none returns: none calls:compareStrings getViewType getScaleType drawCollaborationNetworkLegend collaboration_autocomplete called from: init */ }, { key: "drawResearcherCollaborationNetworkChart", value: function drawResearcherCollaborationNetworkChart() { var el = this; // REMOVING EXISTING CHART AND LOADING NEW d3.selectAll(".collaboration-network-svg-Legend").remove(); // Added this to remove existing chart before loading new d3.selectAll(".navigator-collaborationNetwork-group").remove(); // Added this to remove existing chart before loading new d3.selectAll(".collaborationNetworkBannerlabel").remove(); // store data into constituent parts to network chart el.communitynetwork.graph.nodes = el.communitynetwork.nodeData; el.communitynetwork.graph.links = el.communitynetwork.edgeData; // var label = { nodes: [], links: [] }; // loop through all links information and resave info el.communitynetwork.graph.links.forEach(function (d, i) { d.source = d.researcher_id_1; d.target = d.researcher_id_2; d.value = d.weight; }); // end looping through links ... // NEW CODE - to accommodate new JSON API structure. el.communitynetwork.TopicCategories2 = ["subtopic", "countries", "F_L0"]; el.communitynetwork.TopicCategories2.forEach(function (d, i) { el.communitynetwork[d] = []; }); // end forEach // loop through all nodes information and resave info and construct new attributes el.communitynetwork.graph.nodes.forEach(function (d, i) { var researcher = d; // locally store researcehr information d.id = d.researcher_id; d.fullNameBackward = d.last_name + ", " + d.first_name; // construct alternative name construct foe ue on tooltip/hover over/chart label d.fullNameForward = d.first_name + " " + d.last_name; // construct alternative name construct foe ue on tooltip/hover over/chart label // detemine first year of publication within netowrk .. if (d.year < el.communitynetwork.communityStartYear) { el.communitynetwork.communityStartYear = d.year; } // detemine last year of publication within netowrk .. if (d.year > el.communitynetwork.communityEndYear) { el.communitynetwork.communityEndYear = d.year; } // NEW CODE - to accommodate new JSON API structure. if (el.communitynetwork["F_L0"].indexOf(researcher["F_L0"]) == -1) { el.communitynetwork["F_L0"].push(researcher["F_L0"]); } // end if .. // populate 'countries' array. if (el.communitynetwork["countries"].indexOf(researcher["countries"]) == -1) { el.communitynetwork["countries"].push(researcher["countries"]); } // end if .. // populate 'subtopic' array. if (el.communitynetwork["subtopic"].indexOf(researcher["subtopic"]) == -1) { el.communitynetwork["subtopic"].push(researcher["subtopic"]); el.communitynetwork.subtopicLookup[researcher["subtopic"]] = researcher["subtopic_id"]; } // end if .. // determine maximum number of articles published by a single researcher in network if (d.articles > el.communitynetwork.maxNumberArticles) { el.communitynetwork.maxNumberArticles = d.articles; } // determine maximum h-index published by a single researcher in network if (d.hindex > el.communitynetwork.maxNumberhIndex) { el.communitynetwork.maxNumberhIndex = d.hindex; } // populate 'affiliation' array. if (el.communitynetwork.affiliations.indexOf(d.affiliation) == -1) { el.communitynetwork.affiliations.push(d.affiliation); } // construct label array content label.nodes.push({ node: d }); label.nodes.push({ node: d }); label.links.push({ source: i * 2, target: i * 2 + 1 }); }); // end forEach looping through researcher nodes // filter data obj down to ID of researcher selected el.communitynetwork.researcherDataObj = el.communitynetwork.nodeData.filter(function (d, i) { return d.researcher_id == el.researcherID; }); // isolate preferred constuct of researcher name for which network is built. el.researcherName = el.communitynetwork.researcherDataObj[0].fullNameForward; // append topic name label d3.selectAll(".collborationNetworkBanner").append("label").attr("class", "collaborationNetworkBannerlabel").text("Reseacher collaboration network for " + el.researcherName); /* name: compareStrings description: function to sort strings arguments: a, b - arguments to comapre in sort returns: reordered content calls: none called from: drawResearcherCollaborationNetworkChart */ function compareStrings(a, b) { // Assuming you want case-insensitive comparison a = a.toLowerCase(); b = b.toLowerCase(); return a < b ? -1 : a > b ? 1 : 0; } // end function compareStrings // nullify, empty and reset 'el.communitynetwork.sortedData' array to populate selection list el.communitynetwork.sortedData = []; // sort node information for basic HTML selection list ordering el.communitynetwork.sortedData = el.communitynetwork.graph.nodes.sort(function (a, b) { return compareStrings(a.last_name, b.last_name); }); // Update styling options list with options from input data d3.select("#styleNodesByListing").selectAll(".collaboration-nodeStylingOption").data(el.communitynetwork.TopicCategories2).enter().append("option").attr("class", "collaboration-nodeStylingOption").attr("value", function (d, i) { return d; }).text(function (d, i) { return el.communitynetwork.stylingLookup[d]; }); // listener for user selection change d3.select("#styleNodesByListing").on("change", function () { var stylingPreference = this.value; el.getViewType(stylingPreference); }); // end .on change ... // Update scaling options list with options .... d3.select("#scaleNodesByListing").selectAll(".collaboration-nodeScalingOption").data(el.communitynetwork.scalingAttributes).enter().append("option").attr("class", "collaboration-nodeScalingOption").attr("value", function (d, i) { return d[0]; }).attr("selected", function (d, i) { if (el.communitynetwork.selectedRankView == d[0]) { return true; } }).text(function (d, i) { return d[1]; }); // event handler to react to user choosing new option by which to scale researcher nodes by. d3.select("#scaleNodesByListing").on("change", function () { var scalingPreference = this.value; el.getScaleType(scalingPreference); }); // end .on change ... // remove previously appended HTML options to allow a new selction list to be constructed if user wants to view a different collab network. d3.selectAll(".collaboration-authorSearchOption").remove(); d3.selectAll(".collaboration-authorSearch").selectAll(".collaboration-authorSearchOption").data(el.communitynetwork.sortedData).enter().append("option").attr("class", function (d, i) { return d.researcher_id == el.researcherID ? "collaboration-authorSearchOption selectedExpertSpan" : "collaboration-authorSearchOption"; }).attr("value", function (d, i) { return d.researcher_id; }).html(function (d, i) { // let name = d.last_name + ", " + d.first_name; var name = d.first_name + " " + d.last_name; return d.researcher_id == el.researcherID ? name + " (Your Selected Expert)" : name; }); // add handler to react to user slecting an individual to locate and find on chart. // effectively reset... d3.selectAll(".collaboration-authorSearch").on("change", function () { el.communitynetwork.listSelectedResearcher = this.value; // if user selects "none/reset" as option on dropdown. if (el.communitynetwork.listSelectedResearcher == "none") { // // remove all back styling of all researcher node circles. d3.selectAll(".node").classed("semi-transparent", false); // // remove all push back styling of all researcher edges d3.selectAll(".communityNetworkEdgeLine").classed("semi-transparent", false).classed("edgeSelected", false); // // remove all push back styling of all researcher node labels d3.selectAll(".nodeLabel").classed("semi-transparent", false); // disable reset button // document.getElementById("collaborationReset").disabled = true; // d3.select("#collaborationReset").classed("disabled", true); return; } // end if logical check else { // // enable/activarte reset button // document.getElementById("collaborationReset").disabled = false; // d3.select("#collaborationReset").classed("disabled", false); // push back all styling of all nodes d3.selectAll(".node").classed("semi-transparent", true); // push back all styling of all node labels d3.selectAll(".nodeLabel").classed("semi-transparent", true); // push back all styling of all edges d3.selectAll(".communityNetworkEdgeLine").classed("semi-transparent", true).classed("edgeSelected", false); // fillter all researcher nodes based on researcher IDand remove push back styling d3.selectAll(".node").filter(function (d, i) { return el.communitynetwork.listSelectedResearcher == d.researcher_id; }).classed("semi-transparent", false); // fillter all researcher node labels based on researcher ID and remove push back styling d3.selectAll(".nodeLabel").filter(function (d, i) { return el.communitynetwork.listSelectedResearcher == d.researcher_id; }).classed("semi-transparent", false); // fillter all researcher node edges based on researcher ID and remove push back styling d3.selectAll(".communityNetworkEdgeLine").filter(function (d, i) { // implement code if _id1 is equal to id of selected researcher if (el.communitynetwork.listSelectedResearcher == d.researcher_id_1) { d3.selectAll(".node.researchers.researcher-" + d.researcher_id_2.replaceAll(".", "-")).classed("semi-transparent", false); d3.selectAll(".nodeLabel.researcher-" + d.researcher_id_2.replaceAll(".", "-")).classed("semi-transparent", false); } // end if ... // implement code if _id2 is equal to id of selected researcher else if (el.communitynetwork.listSelectedResearcher == d.researcher_id_2) { // remove push back styling on selected researcher node d3.selectAll(".node.researchers.researcher-" + d.researcher_id_1.replaceAll(".", "-")).classed("semi-transparent", false); // remove push back styling on selected researcher node label d3.selectAll(".nodeLabel.researcher-" + d.researcher_id_2.replaceAll(".", "-")).classed("semi-transparent", false); } // retrun filter if selected researchers ID is same as _id1 or _id2 on data return el.communitynetwork.listSelectedResearcher == d.researcher_id_1 || el.communitynetwork.listSelectedResearcher == d.researcher_id_2; }).classed("semi-transparent", false).classed("edgeSelected", true); } // end else if . }); // end .on change ... // define scaling law to implement on number of publications. el.communitynetwork.articleCircleRadiusScale = d3.scalePow().exponent(0.5).domain([0, el.communitynetwork.maxNumberArticles]).range([0, el.communitynetwork.maxCircleRadius]); // define scaling law to implement on page rank // el.communitynetwork.rankCircleRadiusScale = d3 // .scalePow() // .exponent(0.5) // .domain([0, 0.0005]) // .range([0, el.communitynetwork.maxCircleRadius]); // define scaling law to implement on h index el.communitynetwork.hindexCircleRadiusScale = d3.scalePow().exponent(0.5).domain([0, el.communitynetwork.maxNumberhIndex]).range([0, el.communitynetwork.maxCircleRadius]); // implemented D3 forced layout on label data el.communitynetwork.labelLayout = d3.forceSimulation(label.nodes).force("charge", d3.forceManyBody().strength(0)).force("link", d3.forceLink(label.links).distance(0).strength(100)).stop(); // implemented D3 forced layout on node data el.communitynetwork.graphLayout = d3.forceSimulation(el.communitynetwork.graph.nodes).force("charge", d3.forceManyBody().strength(-20000)).force("center", d3.forceCenter(window.innerWidth / 2, window.innerHeight / 2)).force("x", d3.forceX(el.communitynetwork.width / 2).strength(1)).force("y", d3.forceY(el.communitynetwork.height / 2).strength(1)).force("link", d3.forceLink(el.communitynetwork.graph.links).id(function (d, i) { return d.researcher_id; }).distance(50).strength(1)); // remove tick building https://bl.ocks.org/mbostock/1667139 // allows chart to be built instantly on page load. d3.timeout(function () { for (var i = 0, n = Math.ceil(Math.log(el.communitynetwork.graphLayout.alphaMin()) / Math.log(1 - el.communitynetwork.graphLayout.alphaDecay())); i < n; ++i) { el.communitynetwork.graphLayout.on("tick", ticked).tick(); } var adjlist = []; el.communitynetwork.graph.links.forEach(function (d) { adjlist[d.source.index + "-" + d.target.index] = true; adjlist[d.target.index + "-" + d.source.index] = true; }); /* name: neigh description: function to detemine neighbouring nodes to selected node arguments: a, b - nodes to compare returns: populated array of neighbouring nodes calls: none called from: drawResearcherCollaborationNetworkChart */ function neigh(a, b) { return a == b || adjlist[a + "-" + b]; } // end function neigh // determine new width and hieght dimensions SVG base panel should be ... var svgWidth = window.innerWidth; el.communitynetwork.svgHeight = Number(window.innerHeight + el.communitynetwork.margin.top + el.communitynetwork.margin.bottom); // update key attributes for bas SVG panel el.communitynetwork.svg = d3.selectAll(".Community-Network").select("svg").attr("class", "collaboration-network-svg").attr("width", svgWidth).attr("height", el.communitynetwork.svgHeight); // obtain and store height attribute of base SVG panel el.communitynetwork.svgBaseHeight = d3.selectAll(".collaboration-network-svg").attr("height"); // append base group element and update key attributes and positioning el.container = el.communitynetwork.svg.append("g").attr("class", "navigator-collaborationNetwork-group").attr("id", "navigator-collaborationNetwork-group").attr("transform", "translate(" + 0 + "," + el.communitynetwork.margin.top + ")"); // define zoom interaction with chart. el.zoom = d3.zoom().extent([[0, 0], [svgWidth, el.communitynetwork.svgHeight]]).scaleExtent([0, 10]).on("zoom", function () { el.communitynetwork.hasBeenZoomed = true; // document.getElementById("collaborationReset").disabled = false; // d3.select("#collaborationReset").classed("disabled", false); el.container.attr("transform", d3.event.transform); }); // // call zoom defintion function el.communitynetwork.svg.call(el.zoom); // append new group elements for each link; define interactivity var link = el.container.append("g").attr("class", function (d, i) { return "communityNetworkLink"; }).selectAll("line").data(el.communitynetwork.graph.links).enter().append("line").attr("class", function (d, i) { return "communityNetworkEdgeLine " + d.researcher_id_1.replaceAll(".", "-") + " " + d.researcher_id_2.replaceAll(".", "-") + " sourceTopicID" + d.source.subtopic_id + " targetTopicID" + d.target.subtopic_id; }).on("mouseover", function (d, i) { el.modifyNetworkHighlighting_viaEdge(d); return; }).on("mouseout", function (d, i) { if (el.communitynetwork.researcherEdgeLinkSelected == false) { d3.selectAll(".communityNetworkEdgeLine").classed("semi-transparent", false); d3.selectAll(".circles.researchers").classed("semi-transparent", false); d3.selectAll(".nodeLabel").classed("semi-transparent", false); } // end if ... return; }).on("click", function (d, i) { if (el.communitynetwork.researcherEdgeLinkSelected == true) { el.communitynetwork.researcherEdgeLinkSelected = false; // document.getElementById("collaborationReset").disabled = true; // d3.select("#collaborationReset").classed("disabled", true); d3.select(this).classed("edgeSelected", false); } else { el.communitynetwork.researcherEdgeLinkSelected = true; // document.getElementById("collaborationReset").disabled = false; // d3.select("#collaborationReset").classed("disabled", false); d3.select(this).classed("edgeSelected", true); } }); // append new group elements for each node var node = el.container.append("g").attr("class", "communityNetworkNode").selectAll("g").data(el.communitynetwork.graph.nodes).enter().append("g").attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; }).attr("class", function (d) { var str = "node researchers researcher-" + d.researcher_id.replaceAll(".", "-") + " subtopic_id" + d.subtopic_id; if (d.researcher_id == el.researcherID) { str = str + " selectedResearcher"; } return d.researcher_id == el.researcherID ? str : str; }); // append new circle components to each nodes group element for each node // define scale sizing snd styling node.append("circle").attr("class", function (d, i) { var str = "circles researchers researcher-" + d.researcher_id.replaceAll(".", "-"); if (d.researcher_id == el.researcherID) { str = str + " selectedResearcher"; } return str; }).attr("r", function (d, i) { // scale by number of articles if (el.communitynetwork.selectedRankView == "articles") { return el.communitynetwork.articleCircleRadiusScale(d.articles); } // scale by h-index else { // return el.communitynetwork.rankCircleRadiusScale(d.pagerank); return el.communitynetwork.hindexCircleRadiusScale(d.hindex); } }) // scale by currently selected styling variables .style("fill", function (d, i) { return el.communitynetwork.legendColour[el.communitynetwork[el.communitynetwork.selectedChartView.replaceAll("-", " ")].indexOf(d[el.communitynetwork.selectedChartView.replaceAll("-", " ")])]; }).style("stroke", function (d, i) { return el.communitynetwork.legendColour[el.communitynetwork[el.communitynetwork.selectedChartView.replaceAll("-", " ")].indexOf(d[el.communitynetwork.selectedChartView.replaceAll("-", " ")])]; }); // append new group element for each node label var label = el.container.append("g").attr("class", "communityNetworkLabel").selectAll("g").data(el.communitynetwork.graph.nodes).enter().append("g").attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; }).attr("class", function (d) { var str = "label researchers researcher-" + d.researcher_id.replaceAll(".", "-") + " subtopic_id" + d.subtopic_id; return d.researcher_id == el.researcherID ? str : str; }); // update positioning and labelling content for each label label.append("text").attr("class", function (d, i) { return "nodeLabel researcher-" + d.researcher_id.replaceAll(".", "-"); }).attr("x", function (d, i) { if (el.communitynetwork.selectedRankView == "articles") { return el.communitynetwork.articleCircleRadiusScale(d.articles) + 6.5; } else { /* return el.communitynetwork.rankCircleRadiusScale(d.pagerank) + 6.5; */ return el.communitynetwork.hindexCircleRadiusScale(d.hindex) + 6.5; } }).attr("y", 0).attr("dy", "0.33rem").style("text-anchor", "start").text(function (d) { var string = el.communitynetwork.selectedRankView == "articles" ? " (" + d.articles + ")" : " "; return d.first_name + " " + d.last_name + " " + string; }); // define user interaction for mouse events on node circles node.on("mouseover", focus).on("mouseout", unfocus).on("click", function () { if (el.communitynetwork.researcherSelected == true) { el.communitynetwork.researcherSelected = false; // document.getElementById("collaborationReset").disabled = true; // d3.select("#collaborationReset").classed("disabled", true); } else { el.communitynetwork.researcherSelected = true; // document.getElementById("collaborationReset").disabled = false; // d3.select("#collaborationReset").classed("disabled", false); } // end else ... }); /* name: ticked description: function to implement decay tick transtioning on chart load arguments: none returns: none calls: none called from: drawResearcherCollaborationNetworkChart */ function ticked() { node.call(updateNode); link.call(updateLink); return; } // end function ticked /* name: fixna description: function to implement decay tick transtioning on chart load arguments: x - returns: x or 0 depending on outcome state calls: none called from:updateLink updateNode */ function fixna(x) { if (isFinite(x)) return x; return 0; } // end function fixna /* name: focus description: function to implement alternate chart styling if user mouseovers a node arguments: none returns: none calls: none called from: drawResearcherCollaborationChart */ function focus() { // // is user has not alrady selected a researcher to highlight by clinking on it ... if (el.communitynetwork.researcherSelected == false) { // // move node element to front of chart so not covered/masked by other content d3.select(this).moveToFront(); // remove/reset all alternative push back styling from edges d3.selectAll(".communityNetworkEdgeLine").classed("semi-transparent", false); // remove/reset all alternative push back styling from node circles d3.selectAll(".circles.researchers").classed("semi-transparent", false); // remove/reset all alternative push back styling from node labels d3.selectAll(".nodeLabel").classed("semi-transparent", false); /* */ // reapply push back styling to high all chart. d3.selectAll(".communityNetworkEdgeLine").classed("semi-transparent", true); d3.selectAll(".circles.researchers").classed("semi-transparent", true); d3.selectAll(".nodeLabel").classed("semi-transparent", true); /* */ // determine index of selected node ... var index = d3.select(d3.event.target).datum().index; // // KEEP THIS ... // original code; may need to revert back to this ... // // node.style("opacity", function (o) { // if (neigh(index, o.index) == true) { // d3.selectAll( // ".communityNetworkEdgeLine." + // o.researcher_id.replaceAll(".", "-") // ).classed("semi-transparent", false); // // // d3.selectAll( // ".circles.researchers.researcher-" + // o.researcher_id.replaceAll(".", "-") // ).classed("semi-transparent", false); // // // d3.selectAll( // ".nodeLabel.researcher-" + o.researcher_id.replaceAll(".", "-") // ).classed("semi-transparent", false); // } // // return neigh(index, o.index) ? 1 : 0.1; // }); // remove alternative pushback styling only from those edges that are relevant link.style("opacity", function (o) { if ((o.source.index == index || o.target.index == index) == true) { d3.selectAll(".communityNetworkEdgeLine." + o.researcher_id_1.replaceAll(".", "-") + "." + o.researcher_id_2.replaceAll(".", "-")).classed("semi-transparent", false); // remove alternative pushback styling only from those nodes that are relevant d3.selectAll(".circles.researchers.researcher-" + o.researcher_id_1.replaceAll(".", "-")).classed("semi-transparent", false); d3.selectAll(".circles.researchers.researcher-" + o.researcher_id_2.replaceAll(".", "-")).classed("semi-transparent", false); // remove alternative pushback styling only from those node labels thart are relevant d3.selectAll(".nodeLabel.researcher-" + o.researcher_id_1.replaceAll(".", "-")).classed("semi-transparent", false); d3.selectAll(".nodeLabel.researcher-" + o.researcher_id_2.replaceAll(".", "-")).classed("semi-transparent", false); } // return o.source.index == index || o.target.index == index ? 1 : 0.1; }); // link.style("opacity", function (o) { // return o.source.index == index || o.target.index == index ? 1 : 0.1; // }); } // end if return; } // end function focus /* name: unfocus description: function to remove alternate pushback chart styling if user mouseouts from a node arguments: none returns: none calls: none called from: drawResearcherCollaborationChart */ function unfocus() { if (el.communitynetwork.researcherSelected == false) { d3.selectAll(".communityNetworkEdgeLine").classed("semi-transparent", false); d3.selectAll(".circles.researchers").classed("semi-transparent", false); d3.selectAll(".nodeLabel").classed("semi-transparent", false); } return; } // end function unfocus /* name: updateLink description: function to update positioning of edge link during chart ticking and transtioning arguments: link - reference to link attribution returns: none calls: none called from: ticked */ function updateLink(link) { link.attr("x1", function (d) { return fixna(d.source.x); }).attr("y1", function (d) { return fixna(d.source.y); }).attr("x2", function (d) { return fixna(d.target.x); }).attr("y2", function (d) { return fixna(d.target.y); }); return; } // end function updateLink /* name: updateLink description: function to update positioning of node during chart ticking and transtioning arguments: link - reference to node attribution returns: new node translation positioning calls: fixna called from: ticked */ function updateNode(node) { node.attr("transform", function (d) { return "translate(" + fixna(d.x) + "," + fixna(d.y) + ")"; }); } // end function updateLink // call function to initiate drawing of legend el.drawCollaborationNetworkLegend(); // el.communitynetwork.legendBuildCounter++; }); // end timeout // implement code if page is being loaded for the first time if (el.communitynetwork.onCollaborationPageLoad == false) { // // call function to build auto-complete selection list // el.collaboration_autocomplete( // document.getElementById("myCollaborationResearcherInput"), // el.communitynetwork.graph.nodes // ); // update variable implying chart has been loaded for the first time. any subsequent build is an Nth build el.communitynetwork.onCollaborationPageLoad = true; } // end if ... // event handler to handle user click of reset button. // d3.select("#collaborationReset").on("click", function () { // // update selected value of author search // document.getElementById("collaboration-authorSearch").value = "none"; // document.querySelector(".myCollaborationResearcherInput").value = ""; // // // // update state of reset button // document.getElementById("collaborationReset").disabled = true; // d3.select("#collaborationReset").classed("disabled", true); // // remove class defintion associated with pushing back styling of all lines, node circles and labels // d3.selectAll(".communityNetworkEdgeLine") // .classed("semi-transparent", false) // .classed("edgeSelected", false); // d3.selectAll(".node.researchers").classed("semi-transparent", false); // d3.selectAll(".circles.researchers").classed("semi-transparent", false); // d3.selectAll(".nodeLabel").classed("semi-transparent", false); // // reset global selection vars // el.communitynetwork.researcherEdgeLinkSelected = false; // el.communitynetwork.researcherSelected = false; // el.communitynetwork.listSelectedResearcher = null; // // reset chart zoom/translation/scaling to that needed to fully display chart at point of onload // var t = d3.zoomIdentity // .translate( // el.communitynetwork.resetZoom.x, // el.communitynetwork.resetZoom.y // ) // .scale(el.communitynetwork.resetZoom.k); // // call functions to implement reset zoom // el.communitynetwork.svg.call(el.zoom.transform, t); // el.communitynetwork.svg.call(el.zoom); // return; // }); // end function called from 'reset' button // get bounding rectangle attribution of group container of entire chart. var myTimeout = setTimeout(function () { var element = document.getElementById("navigator-collaborationNetwork-group"); var group = element.getBoundingClientRect(); var BBoxX = document.getElementById("navigator-collaborationNetwork-group").getBBox().x; var BBoxY = document.getElementById("navigator-collaborationNetwork-group").getBBox().y; var width = document.getElementById("navigator-collaborationNetwork-group").getBBox().width; var height = document.getElementById("navigator-collaborationNetwork-group").getBBox().height; var x = width / 2 + BBoxX; var y = height / 2 + Math.abs(BBoxY); // calculate necessary ratios to display chart at full extent on load. var ratio1 = 1 / (d3.min([el.communitynetwork.svgBaseHeight, group.height]) / d3.max([el.communitynetwork.svgBaseHeight, group.height])); var ratio2 = 1 * (d3.min([el.communitynetwork.svgBaseHeight, group.height]) / d3.max([el.communitynetwork.svgBaseHeight, group.height])); // determine resultant bespoke scaling ratio for current chart var collaborationChartScalingRatio = group.height > el.communitynetwork.svgBaseHeight ? ratio2 : ratio1; // determine resultant bespoke x and y translations for current chart var xTranslation = d3.selectAll(".collaboration-network-svg").attr("width") / 2 - collaborationChartScalingRatio * x; var yTranslation = group.height > el.communitynetwork.svgBaseHeight ? (d3.selectAll(".collaboration-network-svg").attr("height") / 2 - collaborationChartScalingRatio * y) * -1 : d3.selectAll(".collaboration-network-svg").attr("height") / 2 - collaborationChartScalingRatio * y; var translateCollabChart = [xTranslation, yTranslation]; // determine reset zoom attributes for chart to be scaled properly el.communitynetwork.resetZoom.k = collaborationChartScalingRatio; el.communitynetwork.resetZoom.x = translateCollabChart[0]; el.communitynetwork.resetZoom.y = translateCollabChart[1]; var t = d3.zoomIdentity.translate(el.communitynetwork.resetZoom.x, el.communitynetwork.resetZoom.y).scale(el.communitynetwork.resetZoom.k); // el.communitynetwork.svg.call(el.zoom.transform, t); el.communitynetwork.svg.call(el.zoom); // append new highlight circle to denote the researcher user selected in previous hierarchy chart d3.selectAll(".node.selectedResearcher").append("circle").attr("class", function (d, i) { return "selectedResearcherHighLight"; }).attr("r", function (d, i) { if (el.communitynetwork.selectedRankView == "articles") { return el.communitynetwork.articleCircleRadiusScale(d.articles) + 5; } else { /* return el.communitynetwork.rankCircleRadiusScale(d.pagerank) + 5; */ return el.communitynetwork.hindexCircleRadiusScale(d.hindex) + 5; } }).style("stroke", function (d, i) { return el.communitynetwork.legendColour[el.communitynetwork[el.communitynetwork.selectedChartView.replaceAll("-", " ")].indexOf(d[el.communitynetwork.selectedChartView.replaceAll("-", " ")])]; }).style("fill", "none").style("stroke-dasharray", "5 2").style("stroke-width", 2); }, 25); return; } // end function drawResearcherCollaborationNetworkChart /* name: getScaleType description: function to initiate change in node circle scaling after user change arguments: fid - reference of selection list where user changes scaling variable returns: none calls:none called from: init */ }, { key: "getScaleType", value: function getScaleType(fid) { var el = this; // update scale sizing of all researcher nodes. d3.selectAll(".circles.researchers").transition().duration(750).ease(d3.easeLinear).attr("r", function (d, i) { if (fid == "articles") { return el.communitynetwork.articleCircleRadiusScale(d.articles); } else { /* return el.communitynetwork.rankCircleRadiusScale(d.pagerank); */ return el.communitynetwork.hindexCircleRadiusScale(d.hindex); } }); // update positioning of all researcher node labels. d3.selectAll(".nodeLabel").transition().duration(750).ease(d3.easeLinear).attr("x", function (d, i) { if (fid == "articles") { return el.communitynetwork.articleCircleRadiusScale(d.articles) + 6.5; } else { /* return el.communitynetwork.rankCircleRadiusScale(d.pagerank) + 6.5; */ return el.communitynetwork.hindexCircleRadiusScale(d.hindex) + 6.5; } }).attr("y", 0).text(function (d, i) { var string = fid == "articles" ? " (" + d.articles + ")" : " "; return d.first_name + " " + d.last_name + " " + string; }); // update scale sizing of selected researcher highlighting node circle. d3.selectAll(".selectedResearcherHighLight").transition().duration(750).ease(d3.easeLinear).attr("r", function (d, i) { if (fid == "articles") { return el.communitynetwork.articleCircleRadiusScale(d.articles) + 5; } else { /* return el.communitynetwork.rankCircleRadiusScale(d.pagerank) + 5; */ return el.communitynetwork.hindexCircleRadiusScale(d.hindex) + 5; } }); return; } // end function getScaleType /* name: getViewType description: function to initiate change in node circle scaling after user change arguments: fid - reference of selection list where user changes styling variable returns: none calls:none called from: init */ }, { key: "getViewType", value: function getViewType(fid) { var el = this; // update value of var defining styling variable el.communitynetwork.selectedChartView = fid; // update styling of all node circles. d3.selectAll(".circles.researchers").transition().duration(750).ease(d3.easeLinear).style("fill", function (d, i) { return el.communitynetwork.legendColour[el.communitynetwork[el.communitynetwork.selectedChartView].indexOf(d[el.communitynetwork.selectedChartView])]; }).style("stroke", function (d, i) { return el.communitynetwork.legendColour[el.communitynetwork[el.communitynetwork.selectedChartView].indexOf(d[el.communitynetwork.selectedChartView])]; }); // call function to redraw legend based on new selected styling variable/attribute. el.drawCollaborationNetworkLegend(); return; } // end function getViewType /* name: drawCollaborationNetworkLegend description: function to [re]draw chart legend arguments: none returns: none calls:transitionBacktoHierarchy highlightRelevantNetworkElementsByLegend called from:drawCollaborationChartNetwork getViewType */ }, { key: "drawCollaborationNetworkLegend", value: function drawCollaborationNetworkLegend() { var el = this; // initate array to contain legend content var legendElements = el.communitynetwork[el.communitynetwork.selectedChartView.replaceAll("-", " ")]; // add default entry for selected researcher. // Will be drawn last on each legend legendElements[legendElements.length] = "Your Selected Researcher"; // remove old legend group element d3.selectAll(".collaboration-network-svg-Legend").remove(); // select base SCG panel element and add new legend group element, // translate to correct position d3.selectAll(".collaborationTableDivSVG").append("g").attr("class", "collaboration-network-backButton-group").attr("transform", "translate(" + Number(window.innerWidth / 2) + "," + 0 + ")") // append new black background rectangle for 'back to hierarchy' button at base of page .append("rect").attr("class", "collaboration-network-backButton-groupElement collaboration-network-backButton-rect").attr("x", -95).attr("y", 0).attr("width", 190).attr("height", 60).on("click", function () { el.transitionBacktoHierarchy(); return; }); // add text label for back to hieratchy chart button d3.selectAll(".collaboration-network-backButton-group").append("text").attr("class", "collaboration-network-backButton-groupElement collaboration-network-backButton-text").attr("x", 0).attr("y", 50).text("Back to Hierarchy Chart").on("click", function () { el.transitionBacktoHierarchy(); return; }); // fudge coorddiantes for line coordiantes for up arrow on 'back to ...' button // this replaces adding a single SVG image file as we dont know how to do that at the moment. var crossCoords = [/* bottom left to top right */ { x1: 0, y1: 24, x2: 8, y2: 16 }, /* top left to bottom right */ { x1: 8, y1: 16, x2: 16, y2: 24 }]; // append and draw lines based on coords in array d3.selectAll(".collaboration-network-backButton-group").selectAll(".collaboration-network-backButton-arrows").data(crossCoords).enter().append("line").attr("class", "collaboration-network-backButton-groupElement collaboration-network-backButton-arrows").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; }).on("click", function () { el.transitionBacktoHierarchy(); }); // append new group element for base chart SVG panel to containe legend. // this is to avoid it being zoomed/scaled when user refocused main chart. d3.selectAll(".collaboration-network-svg").append("g").attr("class", "collaboration-network-svg-Legend").attr("transform", function (d, i) { return "translate(" + el.communitynetwork.margin.left + "," + 0 + ") "; }) // append black background rectangle for legend .append("rect").attr("class", "collaboration-network-svg-Legend-Background").attr("x", -10).attr("y", 30).attr("rx", 0).attr("ry", 0).attr("width", 150).attr("height", 10 + el.communitynetwork[el.communitynetwork.selectedChartView.replaceAll("-", " ")].length * 30); // append new group elements fro each legend entry // define user interaction d3.selectAll(".collaboration-network-svg-Legend").selectAll(".legendElements").data(el.communitynetwork[el.communitynetwork.selectedChartView.replaceAll("-", " ")]).enter().append("g").attr("class", function (d, i) { return "legendElements legendElement-" + i + " subtopic_id" + el.communitynetwork.subtopicLookup[d]; }).attr("transform", function (d, i) { return "translate(" + 0 + "," + Number(el.communitynetwork.margin.top + i * 30) + ")"; }).on("mouseover", function (d) { el.highlightRelevantNetworkElementsB