Difference between revisions of "MediaWiki:Network-graph.js"
Jump to navigation
Jump to search
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 = { | ||
− | + | 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, | strokeWidth: 2, | ||
− | + | color: 'grey', | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
}, | }, | ||
Line 43: | Line 56: | ||
}, | }, | ||
− | + | force: null, | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
svg: null, | svg: null, | ||
− | |||
− | |||
− | |||
} | } | ||
Line 69: | Line 70: | ||
function getQuery(name) { | function getQuery(name) { | ||
− | // e.g., https://devapps.pathology.jhu.edu/sudepwiki/index.php/ | + | // 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; | return title; | ||
+ | } | ||
+ | |||
+ | function getDepth() { | ||
+ | // Return the depth to use for the graph (?) | ||
+ | |||
+ | return 9; | ||
} | } | ||
function tickHandler() { | function tickHandler() { | ||
− | Ctrl. | + | 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. | + | Ctrl.svg |
+ | .selectAll('.node') | ||
+ | .attr('transform', function(node) { return 'translate(' + node.x + ',' + node.y + ')'; }); | ||
} | } | ||
− | function | + | function gotoPage(node, index) { |
− | // | + | // Go to the page associated with a node |
− | // node - the clicked | + | // node - the node that was double clicked (object) |
− | // index - the index of the clicked | + | // index - the index of the node that was double clicked |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | window.location.replace(getBaseUrl() + '/' + node.name); | |
}; | }; | ||
function main() { | function main() { | ||
− | var 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 < | + | 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 | + | .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('stroke', Ctrl.arrow.color) |
.style('stroke-width', Ctrl.arrow.strokeWidth) | .style('stroke-width', Ctrl.arrow.strokeWidth) | ||
− | |||
} | } | ||
// 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 = | + | 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) { |
− | + | // 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(); }); | $(document).ready(function() { main(); }); |
Revision as of 09: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(); });