Difference between revisions of "MediaWiki:Network-graph.js"

From SUDEP Wiki
Jump to navigation Jump to search
Line 6: Line 6:
 
depth = the number of levels to include in the graph (1 - 9)
 
depth = the number of levels to include in the graph (1 - 9)
 
Author: Alan O'Neill
 
Author: Alan O'Neill
Release Date: June 20, 2019
+
Release Date: June 24, 2019
  
 
*/
 
*/
  
var Ctrl = {
+
var Ctrl;
canvas: {
 
width: $('#network-graph').width(),
 
height: $('#network-graph').width(),
 
borderColor: 'black',
 
borderWidth: '1px'
 
},
 
  
color: [
+
function init() {
'#e6194B', // red
+
Ctrl = {
'#f58231', // orange
+
canvas: {
'#ffe119', // yellow
+
width: $('#network-graph').width(),
'#3cb44b', // green
+
height: $('#network-graph').width(),
'#42d4f4', // cyan
+
borderColor: 'black',
'#4363d8', // blue
+
borderWidth: '1px'
'#000075', // navy
+
},
'#f032e6', // magenta
 
'#800000', // maroon
 
'#9A6324', // brown
 
],
 
  
circle: {
+
color: [
radius: 14,
+
'#e6194B', // red
textXOffset: -4,
+
'#f58231', // orange
textYOffset: 4,
+
'#ffe119', // yellow
fontSize: '12px',
+
'#3cb44b', // green
fontColor: 'white',
+
'#42d4f4', // cyan
fontWeight: 'bold',
+
'#4363d8', // blue
},
+
'#000075', // navy
 +
'#f032e6', // magenta
 +
'#800000', // maroon
 +
'#9A6324', // brown
 +
],
  
arrow: {
+
circle: {
size: 6,
+
radius: 14,
strokeWidth: 1, // 0 = no arrow
+
textXOffset: -4,
color: 'grey',
+
textYOffset: 4,
},
+
fontSize: '12px',
 +
fontColor: 'white',
 +
fontWeight: 'bold',
 +
},
  
line: {
+
arrow: {
strokeWidth: 2,
+
size: 6,
color: 'grey',
+
strokeWidth: 1, // 0 = no arrow
},
+
color: 'grey',
 +
},
  
physical: {
+
line: {
charge: -10,
+
strokeWidth: 2,
linkDistance: 10,
+
color: 'grey',
distance: 120,
+
},
gravity: .01,
 
},
 
  
force: null,
+
physical: {
svg: null,
+
charge: -10,
 +
linkDistance: 10,
 +
distance: 120,
 +
gravity: .01,
 +
},
 +
 
 +
force: null,
 +
svg: null,
 +
}
 
}
 
}
  
Line 143: Line 147:
 
};
 
};
  
function main() {
+
function renderGraph() {
 +
init();
 +
 
 
var title = getTitle();
 
var title = getTitle();
 
var depth = getDepth();
 
var depth = getDepth();
Line 262: Line 268:
 
}
 
}
 
});
 
});
 +
}
 +
 +
function main() {
 +
$(window).resize(function() {
 +
renderGraph();
 +
});
 +
 +
renderGraph():
 
}
 
}
  
 
$(document).ready(function() { main(); });
 
$(document).ready(function() { main(); });

Revision as of 08:57, 24 June 2019

/*

Name:		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 24, 2019

*/

var Ctrl;

function init() {
	Ctrl = {
		canvas: {
			width:		$('#network-graph').width(),
			height:		$('#network-graph').width(),
			borderColor:	'black',
			borderWidth:	'1px'
		},

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

		circle: {
			radius:		14,
			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://sudepwiki.pathology.jhmi.edu')

	var baseUrl = location.protocol + '//' + location.host + location.pathname;
	var i = baseUrl.indexOf('/index.php');
	if (i < 0) { i = baseUrl.indexOf('/site-links'); }
	baseUrl = baseUrl.substr(0, i);

	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

	// When node.level is zero, the node refers to the page being displayed
	if (node.level > 0) {
		window.location.href = getBaseUrl() + '/index.php/' + node.name;
	} else {
		alert("You're already viewing this page.");
	}
};

function renderGraph() {
	init();

	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) {
					var title = node.name.replace(/_/g, ' ').toTitleCase();
					if (node.level == 0) { title += ' (this page)'; }
					return title;
				});

			// 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);

			// Draw a border around the graph
			Ctrl.svg.append('rect')
				.attr('x', 0)
				.attr('y', 0)
				.attr('height', Ctrl.canvas.height)
				.attr('width', Ctrl.canvas.width)
				.style('stroke', Ctrl.canvas.borderColor)
				.style('fill', 'none')
				.style('stroke-width', Ctrl.canvas.borderWidth);

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

			// Start the force simulation
			Ctrl.force.start();
			$('#loadNetworkGraph').html('Hover over a node to see the title of the article. Double-click on a node to view the article.');
		} else {
			$('#loadNetworkGraph').html('No data is available for this graph.');
		}
	});
}

function main() {
	$(window).resize(function() {
		renderGraph();
	});

	renderGraph():
}

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