MediaWiki:Network-graph.js: Difference between revisions

From SUDEP Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 1: Line 1:
/*
Name: https://devapps.pathology.jhu.edu/sudepwiki/index.php/MediaWiki:Network-graph.js
Purpose: Build the network graph for the page being displayed
Parameters: title = the internal title of the reference (e.g., Cerebral_hemispheric_lateralization_in_cardiac_autonomic_control)
depth = the number of levels to include in the graph (1 - 9)
Author: Alan O'Neill
Release Date: June 20, 2019
*/
var Ctrl = {
var Ctrl = {
baseUrl: '/site-links', // Where to find external files related to the network graph
canvas: {
width: 700,
height: 700,
},
 
color: [
'#e6194B', // red
'#f58231', // orange
'#ffe119', // yellow
'#3cb44b', // green
'#42d4f4', // cyan
'#4363d8', // blue
'#000075', // navy
'#f032e6', // magenta
'#800000', // maroon
'#9A6324', // brown
],
 
circle: {
radius: 10,
textXOffset: -4,
textYOffset: 4,
fontSize: '12px',
fontColor: 'white',
fontWeight: 'bold',
},


canvas: {
arrow: {
width: 500, // pixels
size: 6,
height: 500, // pixels
strokeWidth: 1, // 0 = no arrow
depth: 7, // levels to show (1 - 9)
color: 'grey',
},
},


style: {
line: {
strokeWidth: 2,
strokeWidth: 2,
strokeColor: [
color: 'grey',
'red', // level 0
'orange', // level 1 ...
'yellow',
'green',
'blue',
'purple',
'#880000',
'#008800',
'#000088',
'#888800', // level 9
],
fillColor: [
'purple', // level 0
'blue', // level 1 ...
'green',
'yellow',
'orange',
'red',
'#FFFF00',
'#FF00FF',
'#0000FF',
'#00FF00', // level 9
],
},
},


Line 43: Line 56:
},
},


circle: {
force: null,
radius: 6,
strokeWidth: 2, // 0 = no stroke
},
 
arrow: {
size: 6,
strokeWidth: 1, // 0 = no arrow
},
 
svg: null,
svg: null,
nodes: null,
links: null,
force: null,
}
}


Line 69: Line 70:


function getQuery(name) {
function getQuery(name) {
// e.g., https://devapps.pathology.jhu.edu/sudepwiki/index.php/Excess_mortality_and_sudden_unexpected_death_in_epilepsy
// Return the value of a parameter in the query string
// name - the name in the name/value pair
 
var value = '';
var query = window.location.search.substring(1).split('&');
for (var i = 0; i < query.length; i++) {
var nv = query[i].split('=');
if (nv[0] == name) {
value = nv[1];
break;
}
}
 
return value;
}
 
function getBaseUrl() {
// Return the base URL for the site (e.g., 'https://devapps.pathology.jhu.edu/sudepwiki')
 
var baseUrl = location.protocol + '//' + location.host + location.pathname;
baseUrl = baseUrl.substr(0, baseUrl.indexOf('/index.php'));
 
return baseUrl;
}
 
function getTitle() {
// Return the title of the article from the URL
 
var title = getQuery('title');
if (title.length == 0) {
var delim = '/index.php/';
title = location.pathname.substr(location.pathname.indexOf(delim) + delim.length); // e.g., Cerebral_hemispheric_lateralization_in_cardiac_autonomic_control
}


var url = $(location).attr('href').split('/');
var title = url[url.length - 1];
return title;
return title;
}
function getDepth() {
// Return the depth to use for the graph (?)
return 9;
}
}


function tickHandler() {
function tickHandler() {
Ctrl.links .attr('x1', function(link) { return link.source.x; })
Ctrl.svg
.attr('y1', function(link) { return link.source.y; })
.selectAll('.link')
.attr('x2', function(link) { return link.target.x; })
.attr('x1', function(link) { return link.source.x; })
.attr('y2', function(link) { return link.target.y; });
.attr('y1', function(link) { return link.source.y; })
.attr('x2', function(link) { return link.target.x; })
.attr('y2', function(link) { return link.target.y; });


Ctrl.nodes.attr('transform', function(node) { return 'translate(' + node.x + ',' + node.y + ')'; });
Ctrl.svg
.selectAll('.node')
.attr('transform', function(node) { return 'translate(' + node.x + ',' + node.y + ')'; });
}
}


function centerNode(node, index) {
function gotoPage(node, index) {
// Translate the graph to center around the selected node.
// Go to the page associated with a node
// node - the clicked node
// node - the node that was double clicked (object)
// index - the index of the clicked node in Ctrl.nodes[]
// index - the index of the node that was double clicked
 
for(var i = 0; i < Ctrl.nodes[0].length; i++) {
if (i != index) {
Ctrl.nodes[0][i].fixed = false;
Ctrl.nodes[0][i].cx = 0;
Ctrl.nodes[0][i].cy = 0;
} else {
Ctrl.nodes[0][i].fixed = true;
Ctrl.nodes[0][i].cx = Math.floor(Ctrl.canvas.width / 2);
Ctrl.nodes[0][i].cy = Math.floor(Ctrl.canvas.height / 2);
}
}


tickHandler();
window.location.replace(getBaseUrl() + '/' + node.name);
};
};


function main() {
function main() {
var title = getQuery('title');
var title = getTitle();
var depth = getDepth();
 
if (depth < 1) { depth = 1; }
else if (depth > 9) { depth = 9; }


// Create the canvas
// Create the canvas
Line 115: Line 148:


// Define the arrow head
// Define the arrow head
for (var level = 0; level < 10; level++ ) {
for (var level = 0; level < depth; level++ ) {
let fullSize = Ctrl.arrow.size;
let fullSize = Ctrl.arrow.size;
let halfSize = Math.floor(fullSize / 2);
let halfSize = Math.floor(fullSize / 2);
Ctrl.svg.append('defs').selectAll('marker')
Ctrl.svg
.append('defs')
.selectAll('marker')
.data(['arrow' + level])
.data(['arrow' + level])
.enter()
.enter()
.append('marker')
.append('marker')
.attr('id', function(node, index) { return node; })
.attr('id', function(node, index) { return node; }) // e.g., 'arrow0'
.attr('viewBox', '0 -' + halfSize + ' ' + fullSize + ' ' + fullSize)
.attr('viewBox', '0 -' + halfSize + ' ' + fullSize + ' ' + fullSize)
.attr('refX', Ctrl.circle.radius / 2 + Ctrl.arrow.size + Ctrl.circle.strokeWidth / 4)
.attr('refX', function(node, index) { return Math.floor(Ctrl.circle.radius / 2) + Ctrl.arrow.size; })
.attr('refY', 0)
.attr('refY', 0)
.attr('markerWidth', fullSize)
.attr('markerWidth', fullSize)
Line 130: Line 165:
.attr('orient', 'auto')
.attr('orient', 'auto')
.append('path')
.append('path')
.attr('d', 'M0,-' + halfSize + 'L' + fullSize + ',0L0,' + halfSize + 'L' + fullSize + ',0L0,-' + halfSize)
.attr('d', function(node, index) { return 'M0,-' + halfSize + 'L' + fullSize + ',0L0,' + halfSize + 'L' + fullSize + ',0L0,-' + halfSize; })
.style('stroke', Ctrl.style.strokeColor[level])
.style('stroke', Ctrl.arrow.color)
.style('stroke-width', Ctrl.arrow.strokeWidth)
.style('stroke-width', Ctrl.arrow.strokeWidth)
.style('opacity', '0.6');
}
}


// Define the characteristics of the force simulation
// Define the characteristics of the force simulation
Ctrl.force = d3.layout.force()
Ctrl.force = d3.layout
.force()
.size([Ctrl.canvas.width, Ctrl.canvas.height])
.size([Ctrl.canvas.width, Ctrl.canvas.height])
.charge(Ctrl.physical.charge)
.charge(Ctrl.physical.charge)
Line 145: Line 180:


// Read the data and use it to build the graph
// Read the data and use it to build the graph
var url = Ctrl.baseUrl + '/network-graph.php?depth=' + Ctrl.canvas.depth + '&title=' + title;
var url = getBaseUrl() + '/site-links/network-graph.php?depth=' + depth + '&title=' + title;
d3.json(url, function(json) {
d3.json(url, function(json) {
// Introduce the data to the force simulation
if (json) {
Ctrl.force
// Introduce the data to the force simulation
.nodes(json.nodes)
Ctrl.force
.links(json.links);
.nodes(json.nodes)
 
.links(json.links);
// Add the links (lines and arrows)
Ctrl.links = Ctrl.svg
.selectAll('.link')
.data(json.links)
.enter()
.append('line')
.attr('class', 'link')
.style('stroke', function(node, index) { return Ctrl.style.strokeColor[node.level]; })
.style('stroke-width', Ctrl.style.strokeWidth)
.style('marker-end', function(node, index) { return 'url(#arrow' + node.level + ')'; });


// Add the nodes
// Add the links (lines and arrows)
Ctrl.nodes = Ctrl.svg
Ctrl.svg
.selectAll('.node')
.selectAll('.link')
.data(json.nodes)
.data(json.links)
.enter()
.enter()
.append('g')
.append('line')
.attr('class', 'node')
.attr('class', 'link')
.on('dblclick', centerNode)
.style('stroke', Ctrl.line.color)
.append('circle')
.style('stroke-width', Ctrl.line.strokeWidth)
.attr('r', Ctrl.circle.radius)
.style('marker-end', function(node, index) { return 'url(#arrow0)'; }); //return 'url(#arrow' + node.level + ')'; });
.style('stroke', function(node, index) { return Ctrl.style.strokeColor[node.level]; })
.style('stroke-width', Ctrl.circle.strokeWidth)
.style('fill', function(node, index) { return Ctrl.style.fillColor[node.level]; })
.call(Ctrl.force.drag);


// Add text to each node
// Add the nodes (circles)
// Ctrl.nodes
Ctrl.svg
// .append('text')
.selectAll('.node')
// .attr('dx', 12)
.data(json.nodes)
// .attr('dy', '.35em')
.enter()
// .text(function(node, index) { return index; }); // was node.name, which is the title of the article
.append('g')
.attr('class', 'node')
.on('dblclick', gotoPage)
.append('circle')
.attr('r', Ctrl.circle.radius)
.style('fill', function(node, index) { return Ctrl.color[node.level]; })
.call(Ctrl.force.drag);


// Add a title to each node
// Add a title to each node
Ctrl.nodes
Ctrl.svg
.append('title')
.selectAll('circle')
.data(json.nodes)
.append('title')
.text(function(node, index) { return node.name.replace(/_/g, ' ').toTitleCase(); });
.text(function(node, index) { return node.name.replace(/_/g, ' ').toTitleCase(); });


Ctrl.svg.append('polyline')
// Add the level number to each node
.style('stroke', 'black')
Ctrl.svg
.style('fill', 'none')
.selectAll('.node')
.attr('points', '1,1,' +
.append('text')
+ Ctrl.canvas.width + ',1,'
.attr('dx', Ctrl.circle.textXOffset)
+ Ctrl.canvas.width + ',' + Ctrl.canvas.height + ','
.attr('dy', Ctrl.circle.textYOffset)
+ '1,' + Ctrl.canvas.height
.text(function(node, index) { return node.level; })
);
.style('font-size', Ctrl.circle.fontSize)
.style('fill', Ctrl.circle.fontColor)
.style('font-weight', Ctrl.circle.fontWeight);


// Set up the tick handler
// Set up the tick handler
Ctrl.force.on('tick', function() { tickHandler(); });
Ctrl.force.on('tick', function() { tickHandler(); });


// Start the force simulation
// Start the force simulation
Ctrl.force.start();
Ctrl.force.start();
} else {
$('#network-graph').html('No data is available for this graph.');
}
});
});
}
}


$(document).ready(function() { main(); });
$(document).ready(function() { main(); });

Revision as of 13:49, 20 June 2019

/*

Name:		https://devapps.pathology.jhu.edu/sudepwiki/index.php/MediaWiki:Network-graph.js
Purpose:	Build the network graph for the page being displayed
Parameters:	title = the internal title of the reference (e.g., Cerebral_hemispheric_lateralization_in_cardiac_autonomic_control)
		depth = the number of levels to include in the graph (1 - 9)
Author:		Alan O'Neill
Release Date:	June 20, 2019

*/

var Ctrl = {
	canvas: {
		width:		700,
		height:		700,
	},

	color: [
		'#e6194B',	// red
		'#f58231',	// orange
		'#ffe119',	// yellow
		'#3cb44b',	// green
		'#42d4f4',	// cyan
		'#4363d8',	// blue
		'#000075',	// navy
		'#f032e6',	// magenta
		'#800000',	// maroon
		'#9A6324',	// brown
	],

	circle: {
		radius:		10,
		textXOffset:	-4,
		textYOffset:	4,
		fontSize:	'12px',
		fontColor:	'white',
		fontWeight:	'bold',
	},

	arrow: {
		size:		6,
		strokeWidth:	1,		// 0 = no arrow
		color:		'grey',
	},

	line: {
		strokeWidth:	2,
		color:		'grey',
	},

	physical: {
		charge:		-10,
		linkDistance:	10,
		distance:	120,
		gravity:	.01,
	},

	force:			null,
	svg:			null,
}

String.prototype.toTitleCase = function() {
	return this.replace(
		/\w\S*/g,
		function(txt) {
			return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
		}
	);
}

function getQuery(name) {
	// Return the value of a parameter in the query string
	// name - the name in the name/value pair

	var value = '';
	var query = window.location.search.substring(1).split('&');
	for (var i = 0; i < query.length; i++) {
		var nv = query[i].split('=');
		if (nv[0] == name) {
			value = nv[1];
			break;
		}
	}

	return value;
}

function getBaseUrl() {
	// Return the base URL for the site (e.g., 'https://devapps.pathology.jhu.edu/sudepwiki')

	var baseUrl = location.protocol + '//' + location.host + location.pathname;
	baseUrl = baseUrl.substr(0, baseUrl.indexOf('/index.php'));

	return baseUrl;
}

function getTitle() {
	// Return the title of the article from the URL

	var title = getQuery('title');
	if (title.length == 0) {
		var delim = '/index.php/';
		title = location.pathname.substr(location.pathname.indexOf(delim) + delim.length); // e.g., Cerebral_hemispheric_lateralization_in_cardiac_autonomic_control
	}

	return title;
}

function getDepth() {
	// Return the depth to use for the graph (?)

	return 9;
}

function tickHandler() {
	Ctrl.svg
		.selectAll('.link')
		.attr('x1', function(link) { return link.source.x; })
		.attr('y1', function(link) { return link.source.y; })
		.attr('x2', function(link) { return link.target.x; })
		.attr('y2', function(link) { return link.target.y; });

	Ctrl.svg
		.selectAll('.node')
		.attr('transform', function(node) { return 'translate(' + node.x + ',' + node.y + ')'; });
}

function gotoPage(node, index) {
	// Go to the page associated with a node
	// node - the node that was double clicked (object)
	// index - the index of the node that was double clicked

	window.location.replace(getBaseUrl() + '/' + node.name);
};

function main() {
	var title = getTitle();
	var depth = getDepth();

	if (depth < 1) { depth = 1; }
	else if (depth > 9) { depth = 9; }

	// Create the canvas
	Ctrl.svg = d3.select('#network-graph')
		.append('svg')
		.attr('width', Ctrl.canvas.width)
		.attr('height', Ctrl.canvas.height);

	// Define the arrow head
	for (var level = 0; level < depth; level++ ) {
		let fullSize = Ctrl.arrow.size;
		let halfSize = Math.floor(fullSize / 2);
		Ctrl.svg
			.append('defs')
			.selectAll('marker')
			.data(['arrow' + level])
			.enter()
			.append('marker')
			.attr('id', function(node, index) { return node; }) // e.g., 'arrow0'
			.attr('viewBox', '0 -' + halfSize + ' ' + fullSize + ' ' + fullSize)
			.attr('refX', function(node, index) { return Math.floor(Ctrl.circle.radius / 2) + Ctrl.arrow.size; })
			.attr('refY', 0)
			.attr('markerWidth', fullSize)
			.attr('markerHeight', fullSize)
			.attr('orient', 'auto')
			.append('path')
			.attr('d', function(node, index) { return 'M0,-' + halfSize + 'L' + fullSize + ',0L0,' + halfSize + 'L' + fullSize + ',0L0,-' + halfSize; })
			.style('stroke', Ctrl.arrow.color)
			.style('stroke-width', Ctrl.arrow.strokeWidth)
	}

	// Define the characteristics of the force simulation
	Ctrl.force = d3.layout
		.force()
		.size([Ctrl.canvas.width, Ctrl.canvas.height])
		.charge(Ctrl.physical.charge)
		.linkDistance(Ctrl.physical.linkDistance)
		.distance(Ctrl.physical.distance)
		.gravity(Ctrl.physical.gravity);

	// Read the data and use it to build the graph
	var url = getBaseUrl() + '/site-links/network-graph.php?depth=' + depth + '&title=' + title;
	d3.json(url, function(json) {
		if (json) {
			// Introduce the data to the force simulation
			Ctrl.force
				.nodes(json.nodes)
				.links(json.links);

			// Add the links (lines and arrows)
			Ctrl.svg
				.selectAll('.link')
				.data(json.links)
				.enter()
				.append('line')
				.attr('class', 'link')
				.style('stroke', Ctrl.line.color)
				.style('stroke-width', Ctrl.line.strokeWidth)
				.style('marker-end',  function(node, index) { return 'url(#arrow0)'; }); //return 'url(#arrow' + node.level + ')'; });

			// Add the nodes (circles)
			Ctrl.svg
				.selectAll('.node')
				.data(json.nodes)
				.enter()
				.append('g')
				.attr('class', 'node')
				.on('dblclick', gotoPage)
				.append('circle')
				.attr('r', Ctrl.circle.radius)
				.style('fill', function(node, index) { return Ctrl.color[node.level]; })
				.call(Ctrl.force.drag);

			// Add a title to each node
			Ctrl.svg
				.selectAll('circle')
				.append('title')
				.text(function(node, index) { return node.name.replace(/_/g, ' ').toTitleCase(); });

			// Add the level number to each node
			Ctrl.svg
				.selectAll('.node')
				.append('text')
				.attr('dx', Ctrl.circle.textXOffset)
				.attr('dy', Ctrl.circle.textYOffset)
				.text(function(node, index) { return node.level; })
				.style('font-size', Ctrl.circle.fontSize)
				.style('fill', Ctrl.circle.fontColor)
				.style('font-weight', Ctrl.circle.fontWeight);

			// Set up the tick handler
			Ctrl.force.on('tick', function() { tickHandler(); });

			// Start the force simulation
			Ctrl.force.start();
		} else {
			$('#network-graph').html('No data is available for this graph.');
		}
	});
}

$(document).ready(function() { main(); });