MediaWiki:Network-graph.js

From SUDEP Wiki
Revision as of 10:26, 18 June 2019 by Alano (talk | contribs)
Jump to navigation Jump to search

Note: After saving, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Go to Menu → Settings (Opera → Preferences on a Mac) and then to Privacy & security → Clear browsing data → Cached images and files.
var Ctrl = {
	baseUrl:		'/site-links',	// Where to find external files related to the network graph

	canvas: {
		width:		500,				// pixels
		height:		500,				// pixels
		depth:		7,				// levels to show (1 - 9)
	},

	style: {
		strokeWidth:	2,
		strokeColor: [
			'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
		],
	},

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

	circle: {
		radius:		6,
		strokeWidth:	2,				// 0 = no stroke
	},

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

	svg:			null,
	nodes:			null,
	links:			null,
	force:			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) {
	// e.g., https://devapps.pathology.jhu.edu/sudepwiki/index.php/Excess_mortality_and_sudden_unexpected_death_in_epilepsy

	var url = $(location).attr('href').split('/');
	var title = url[url.length - 1];
	return title;
}

function tickHandler() {
	Ctrl.links	.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.nodes.attr('transform', function(node) { return 'translate(' + node.x + ',' + node.y + ')'; });
}

function centerNode(node, index) {
	// Translate the graph to center around the selected node.
	// node - the clicked node
	// index - the index of the clicked node in Ctrl.nodes[]

	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();
};

function main() {
	var title = getQuery('title');

	// 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 < 10; 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; })
			.attr('viewBox', '0 -' + halfSize + ' ' + fullSize + ' ' + fullSize)
			.attr('refX', Ctrl.circle.radius / 2 + Ctrl.arrow.size + Ctrl.circle.strokeWidth / 4)
			.attr('refY', 0)
			.attr('markerWidth', fullSize)
			.attr('markerHeight', fullSize)
			.attr('orient', 'auto')
			.append('path')
			.attr('d', 'M0,-' + halfSize + 'L' + fullSize + ',0L0,' + halfSize + 'L' + fullSize + ',0L0,-' + halfSize)
			.style('stroke', Ctrl.style.strokeColor[level])
			.style('stroke-width', Ctrl.arrow.strokeWidth)
			.style('opacity', '0.6');
	}

	// 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 = Ctrl.baseUrl + '/network-graph.php?depth=' + Ctrl.canvas.depth + '&title=' + title;
	d3.json(url, function(json) {
		// Introduce the data to the force simulation
		Ctrl.force
			.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
		Ctrl.nodes = Ctrl.svg
			.selectAll('.node')
			.data(json.nodes)
			.enter()
			.append('g')
			.attr('class', 'node')
			.on('dblclick', centerNode)
			.append('circle')
			.attr('r', Ctrl.circle.radius)
			.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
//		Ctrl.nodes
//			.append('text')
//			.attr('dx', 12)
//			.attr('dy', '.35em')
//			.text(function(node, index) { return index; }); // was node.name, which is the title of the article

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

		Ctrl.svg.append('polyline')
			.style('stroke', 'black')
			.style('fill', 'none')
			.attr('points', '1,1,' +
				+ Ctrl.canvas.width + ',1,'
				+ Ctrl.canvas.width + ',' + Ctrl.canvas.height + ','
				+ '1,' + Ctrl.canvas.height
			);

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

		// Start the force simulation
		Ctrl.force.start();
	});
}

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