@springernature/nn-charts
Version:
Visualization for DAS products
1,011 lines (896 loc) • 69.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var d3 = _interopRequireWildcard(require("d3"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { "default": e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n["default"] = e, t && t.set(e, n), n; }
function _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) {
re