Skip to content

Instantly share code, notes, and snippets.

@mhawksey
Forked from yohman/index.html
Created October 13, 2011 20:45
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mhawksey/1285471 to your computer and use it in GitHub Desktop.
Save mhawksey/1285471 to your computer and use it in GitHub Desktop.
Guardian Tag Explorer
<!DOCTYPE html>
<html>
<head>
<title>Guardian Tag Explorer</title>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.js?1.29.1"></script>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.geom.js?1.29.1"></script>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.layout.js?1.29.1"></script>
<link href='http://fonts.googleapis.com/css?family=Ubuntu:500' rel='stylesheet' type='text/css'>
<link href="http://jqueryui.com/themes/base/jquery.ui.all.css" type="text/css" rel="stylesheet" />
<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script type="text/javascript">
google.load('visualization', '1', {packages: ['corechart']});
</script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js"></script>
<style type="text/css">
body {
background:white;
overflow: hidden;
margin: 0;
font: 14px "Ubuntu", sans-serif;
}
.titleLink{
font-family: georgia,serif;
color: #005689;
font-size: 18px;
line-height: 21px;
}
.articleTitle{
margin-top:15px;
margin-bottom:3px;
}
a.titleLink{
text-decoration: none;
}
a.titleLink:hover{
text-decoration: underline;
}
.info {
display:inline;
}
.topbar{
text-align:right;
}
svg {
position:absolute;
top:0px;
z-index:0;
}
#popup {
display:none;
position:absolute;
z-index:9999;
opacity:0.9;
filter:alpha(opacity=90);
text-align:left;
max-width:300px;
max-height:300px;
overflow:auto;
}
.topic {
padding: 10px;
margin-bottom:10px;
background: gainsboro;
font-size: 10px;
cursor:pointer;
}
.fieldname {
font-family:Arial, Helvetica, sans-serif;
color: gray;
}
.connections {
font-size:42px;
color:#069;
}
.popup {
background:white;
border:1px solid gainsboro;
padding:5px;
min-width: 150px;
}
#sidepanel, #tagpanel {
border:5px solid gray;
position:absolute;
top:80px;
right:10px;
padding:10px;
width:300px;
height:400px;
overflow:auto;
background:white;
opacity:0.95;
display:none;
}
#tagpanel {
left:10px;
}
rect {
fill: none;
pointer-events: all;
}
line {
stroke: #000;
stroke-width: 1.5px;
}
.string, .regexp {
color: #f39;
}
.keyword {
color: #00c;
}
.comment {
color: #555;
}
.number {
color: #369;
}
.class, .special {
color: #1181B8;
}
path.link {
fill: none;
stroke:#999;
stroke-width: 2px;
}
marker#licensing {
fill: lightgreen;
}
path.link.licensing {
stroke: lightgreen;
}
path.link.resolved {
stroke-dasharray: 0,2 1;
}
circle {
fill: #ccc;
stroke: #333;
stroke-width: 2px;
opacity: 1;
}
text {
font:10px Tahoma, Geneva, sans-serif;
pointer-events: none;
}
text.shadow {
stroke: #fff;
stroke-width: 3px;
}
.result{
width:75px;
float:left;
}
.rowInfo{
margin:5px 0;
}
.link {
stroke: #ccc;
}
.nodetext {
pointer-events: none;
font: 10px sans-serif;
color: white;
stroke: #ccc;
}
path.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
marker#licensing {
fill: green;
}
path.link.licensing {
stroke: green;
}
path.link.resolved {
stroke-dasharray: 0,2 1;
}
circle {
fill: #ccc;
stroke: #333;
stroke-width: 1.5px;
}
text {
font: 10px sans-serif;
pointer-events: none;
}
text.shadow {
stroke: #fff;
stroke-width: 3px;
stroke-opacity: .8;
}
</style>
<script type="text/javascript">
/* =============================================================================== **
jQuery UI
** =============================================================================== */
/**
*
* credits for this plugin go to brandonaaron.net
*
* unfortunately his site is down
*
* @param {Object} up
* @param {Object} down
* @param {Object} preventDefault
*/
jQuery.fn.extend({
mousewheel: function(up, down, preventDefault) {
return this.hover(
function() {
jQuery.event.mousewheel.giveFocus(this, up, down, preventDefault);
},
function() {
jQuery.event.mousewheel.removeFocus(this);
}
);
},
mousewheeldown: function(fn, preventDefault) {
return this.mousewheel(function(){}, fn, preventDefault);
},
mousewheelup: function(fn, preventDefault) {
return this.mousewheel(fn, function(){}, preventDefault);
},
unmousewheel: function() {
return this.each(function() {
jQuery(this).unmouseover().unmouseout();
jQuery.event.mousewheel.removeFocus(this);
});
},
unmousewheeldown: jQuery.fn.unmousewheel,
unmousewheelup: jQuery.fn.unmousewheel
});
jQuery.event.mousewheel = {
giveFocus: function(el, up, down, preventDefault) {
if (el._handleMousewheel) jQuery(el).unmousewheel();
if (preventDefault == window.undefined && down && down.constructor != Function) {
preventDefault = down;
down = null;
}
el._handleMousewheel = function(event) {
if (!event) event = window.event;
if (preventDefault)
if (event.preventDefault) event.preventDefault();
else event.returnValue = false;
var delta = 0;
if (event.wheelDelta) {
delta = event.wheelDelta/120;
if (window.opera) delta = -delta;
} else if (event.detail) {
delta = -event.detail/3;
}
if (up && (delta > 0 || !down))
up.apply(el, [event, delta]);
else if (down && delta < 0)
down.apply(el, [event, delta]);
};
if (window.addEventListener)
window.addEventListener('DOMMouseScroll', el._handleMousewheel, false);
window.onmousewheel = document.onmousewheel = el._handleMousewheel;
},
removeFocus: function(el) {
if (!el._handleMousewheel) return;
if (window.removeEventListener)
window.removeEventListener('DOMMouseScroll', el._handleMousewheel, false);
window.onmousewheel = document.onmousewheel = null;
el._handleMousewheel = null;
}
};
$(document).ready(function() {
$(document).mousemove(function(e){
$('#popup').css('left', e.pageX+10);
$('#popup').css('top', e.pageY-30);
//$('#popup').css('padding', '5px');
});
$(window).resize(function() {
resize();
});
//allow pressing "enter"
$('#search').keypress(function(e) {
if(e.which == 13) {
doBits();
}
});
$("body").mousewheel(function(i,intDelta){
if (intDelta > 0 ) gravityUp();
if (intDelta < 0 ) gravityDown();
});
var q = $.getUrlVar('q');
if(typeof q !== 'undefined')
{
$('#search').val(decodeURI(q));
$("#permlink").attr('href', 'http://hawksey.info/tagexplorer/guardian.html?q='+encodeURI(q));
getLiveData(q);
}
});
$(function() {
$("#dialog").dialog({modal:true, height:400, width: 600, autoOpen:false});
});
$.extend({
getUrlVars: function(){
var vars = [], hash;
var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
for(var i = 0; i < hashes.length; i++)
{
hash = hashes[i].split('=');
vars.push(hash[0]);
vars[hash[0]] = hash[1];
}
return vars;
},
getUrlVar: function(name){
return $.getUrlVars()[name];
}
});
//+ Jonas Raoni Soares Silva
//@ http://jsfromhell.com/geral/utf-8 [rev. #1]
UTF8 = {
encode: function(s){
for(var c, i = -1, l = (s = s.split("")).length, o = String.fromCharCode; ++i < l;
s[i] = (c = s[i].charCodeAt(0)) >= 127 ? o(0xc0 | (c >>> 6)) + o(0x80 | (c & 0x3f)) : s[i]
);
return s.join("");
},
decode: function(s){
for(var a, b, i = -1, l = (s = s.split("")).length, o = String.fromCharCode, c = "charCodeAt"; ++i < l;
((a = s[i][c](0)) & 0x80) &&
(s[i] = (a & 0xfc) == 0xc0 && ((b = s[i + 1][c](0)) & 0xc0) == 0x80 ?
o(((a & 0x03) << 6) + (b & 0x3f)) : o(128), s[++i] = "")
);
return s.join("");
}
};
function doBits(){
qterm = $('#search').val();
$("#permlink").attr('href', 'http://hawksey.info/tagexplorer/guardian.html?q='+encodeURI(qterm));
getLiveData(qterm);
}
function init(){
force = d3.layout.force()
.charge(-100)
.gravity(0.1)
.linkDistance(150)
.size([w, h]);
nodes = force.nodes(),
links = force.links();
svg = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h);
force.on("tick", function() {
svg.selectAll("path").attr("d", function(d) {
var dx = (d.target.x - d.source.x),
dy = (d.target.y - d.source.y),
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
});
svg.selectAll("circle").attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
svg.selectAll("text").attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
// If zooming, change transformation
})
//
restart();
}
/* =============================================================================== **
Restart
** =============================================================================== */
function restart() {
// Per-type markers, as they don't inherit styles.
var path = svg.append("svg:g").selectAll("path")
.data(force.links())
.enter().append("svg:path")
.attr("class", function(d) { return "link " + d.type; });
var circle = svg.append("svg:g").selectAll("circle")
.data(force.nodes())
.enter().append("svg:circle")
.attr("r", 6)
.on("mouseover", function(d) {showThumb(d.name,d)})
.on("click", function(d) { findTag(d.name,d,d.type);})
//getLiveData(d.name);
.on("mouseout", function() {
$('#popup').hide();
$('#popup').html('');
})
.call(force.drag);
var text = svg.append("svg:g").selectAll("g")
.data(force.nodes())
.enter().append("svg:g");
// A copy of the text with a thick white stroke for legibility.
text.append("svg:text")
.attr("x", 8)
.attr("y", ".31em")
.attr("class", "shadow")
.attr("style", function(d) {
var size = parseInt(d.connections);
if(size > 35) size = 35;
//size = Math.sqrt(d.connections)*8;
if(size == 0) size = 4;
return "font-size:"+size+"px" })
.text(function(d) { return d.name; });
text.append("svg:text")
.attr("x", 8)
.attr("y", ".31em")
.attr("style", function(d) {
var size = parseInt(d.connections);
if(size > 35) size = 35;
//size = Math.sqrt(d.connections)*8;
if(size == 0) size = 4;
return "font-size:"+size+"px" })
.text(function(d) { return d.name; });
//circle.exit().remove();
force.start();
}
/* =============================================================================== **
Globals
** =============================================================================== */
var w,h,force,nodes,links,svg
var w = $(window).width(),
h = $(window).height();
var next_page = '1';
var refresh_url = '';
var q = '';
var doMore = false;
var nodes = [];
var links = [];
var nodeList = [];
/* =============================================================================== **
Get tweets
** =============================================================================== */
function getLiveData(q,doMore)
{
console.log('next...' + q);
//console.log("Getting more " +doMore);
if (doMore == true){
doMore = false;
console.log("Getting more");
q = q;
} else {
nodes = [];
links = [];
nodeList = [];
nodeCount = [];
next_page = 1;
$('#searchCount').html(0);
$('svg').remove();
init();
}
console.log('counting');
var url = 'http://pipes.yahoo.com/pipes/pipe.run?_id=3710670cffd070d96242785c7a4a727d&_render=json&q='+encodeURI(q)+'&page='+next_page;
$.getJSON(url, function(res)
{
var data = res.value.items[0];
console.log('Results ' + data.results.length);
if(typeof data.didYouMean != "undefined"){
alert('Did you mean ' + data.didYouMean);
did_you_mean = data.didYouMean;
}
if(data.pages == "0")
{
alert('no results found');
return false;
}
var searchCount = parseInt($('#searchCount').html())+data.results.length;
$('#searchCount').html(searchCount);
$('#searchTotal').html(data.total);
if(data.currentPage != data.pages){
next_page = parseInt(data.currentPage)+1;
console.log("Next page "+next_page);
}
var dr=data['results'];
$.each(dr,function(i,result){
// Collect a list of tags associated with the current article
var tags=[];
var story = { id: result['id'],
sectionId: result['sectionName'],
sectionName: result['sectionName'],
webPublicationDate: result['webPublicationDate'],
webTitle: result['webTitle'],
webUrl: result['webUrl'],
apiUrl: result['apiUrl']
};
// Now handle the article tags
if (typeof result['tags'] != "undefined") {
if($.isArray(result['tags'])){
$.each(result['tags'],function(j,tag){
processTag(tag,story);
$.each(result['tags'],function(k,tagB){
if (tag['id'] != tagB['id']){
links.push({source: {id: tag['id']}, target: {id: tagB['id']}});
}
});
});
} else {
processTag(result['tags'],story);
}
}
});
// got all nodes and links count connections and add source target
$.each(nodes,function(l,val2){
nodes[l].connections = 0;
});
$.each(links,function(l,aLink){
$.each(nodes,function(l,val2){
if (aLink.source.id == val2.id || aLink.target.id == val2.id){
nodes[l].connections = nodes[l].connections +1;
}
});
aLink.source = findNodePos(aLink.source.id);
aLink.target = findNodePos(aLink.target.id);
});
$('#nodeCount').html(nodes.length);
$('#linkCount').html(links.length);
restart();
});
}
function processTag(tag,story){
tag.stories = [story];
if($.inArray(tag['id'],nodeList) == -1){
tag.connections = 0;
tag.name = tag.webTitle;
nodes.push(tag);
nodeList.push(tag['id']);
} else {
if (typeof nodes[findNodePos(tag['id'])].stories != "undefined") {
nodes[findNodePos(tag['id'])].stories.push(story);
}
}
}
function findNodePos(element)
{
var foundin = 0;
$.each(nodes,function(i,val){
if (val.id == element)
{
foundin = i;
}
});
return foundin;
}
/* =============================================================================== **
Display stuff
** =============================================================================== */
function showThumb(index,d)
{
var box = '<table cellpadding="2" width="100%"><tr><td align="center"><span class="connections">'+d.connections+'</span><br><span class="fieldname">connections</span></td>';
box += '<td align="center"><span class="connections">'+d.stories.length+'</span><br><span class="fieldname">articles</span></td></tr></table>';
box += '<table cellpadding="2"><tr><td align="right"><span class="fieldname">tag </span></td><td>'+d.name+'</td></tr>';
box += '<tr><td align="right"><span class="fieldname">tag id</span></td><td>'+d.id+'</td></tr>';
box += '</table>';
$('#popup').fadeIn();
$('#popup').html('<div class="popup">'+box+'</div>');
}
var type = '';
function findTag(id,d,type)
{
$('#sidepanel').fadeOut();
$('#sidepanel').fadeIn('slow');
resize();
var box = '<div class="topbar"><a href="javascript:void()" onClick="javascript:$(\'#sidepanel\').fadeOut()">X</a></div>';
box += '<table cellpadding="2" width="100%"><tr><td align="center"><span class="connections">'+d.connections+'</span><br><span class="fieldname">connections</span></td>';
box += '<td align="center"><span class="connections">'+d.stories.length+'</span><br><span class="fieldname">articles</span></td></tr></table>';
box += '<table cellpadding="2"><tr><td align="right"><span class="fieldname">tag </span></td><td>'+d.name+'</td></tr>';
box += '<tr><td align="right"><span class="fieldname">tag id</span></td><td>'+d.id+'</td></tr>';
box += '</table>';
$.each(d.stories,function(i,val){
box += '<div class="articleLink"><div class="articleTitle"><a href="'+val.webUrl+'" target="_blank" class="titleLink">'+val.webTitle+'</a></div>';
box += '<div class="infoRow"><div class="fieldname result">Section</div> <div class="info">'+val.sectionName+'</div></div>';
box += '<div class="infoRow"><div class="fieldname result">Publication</div> <div class="info">'+$.datepicker.formatDate('D d MM yy', new Date(val.webPublicationDate))+'</div></div></div>';
});
$('#sidepanel').html(box);
}
function showTagDist()
{
$('#tagpanel').fadeOut();
$('#tagpanel').fadeIn('slow');
drawVisualization();
resize();
}
function cmp(a, b) {
return b[1].localeCompare(a[1]);
}
function drawVisualization() {
// Create and populate the data table.
var data = new google.visualization.DataTable();
data.addColumn('string', 'Tag');
data.addColumn('number', 'Connections');
data.addRows(nodes.length);
//for (var i = 0; i < raw_data.length; ++i) {
for (var j = 0; j < nodes.length; ++j) {
data.setValue(j, 0, nodes[j].name);
data.setValue(j, 1, nodes[j].connections);
}
//}
data.sort([{column: 1, desc: true}]);
// Create and draw the visualization.
new google.visualization.BarChart(document.getElementById('visualization')).
draw(data,
{ width:290,
height:nodes.length*10,
title:'Tag Occurences',
chartArea: {left:0,top:20,width:"100%",height:"100%"},
colors: ['#069'],
legend: 'none'}
);
}
/* =============================================================================== **
Redraw
** =============================================================================== */
function resize()
{
var h = $(window).height();
var w = $(window).width();
//svg window
//svg.attr("width",w);
//svg.attr("height",h);
//side panel
h = h - 145;
$('#sidepanel').height(h);
$('#tagpanel').height(h);
}
/* =============================================================================== **
Controls
** =============================================================================== */
function gravityUp()
{
var value = force.gravity()+0.01;
//var dis = force.linkDistance()-0.01;
var chrg = force.charge()+1;
if(value > 1) value = 1;
force.gravity(value);
//force.linkDistance(dis);
//force.charge(chrg);
force.start();
}
function gravityDown()
{
var value = force.gravity()-0.01;
var dis = force.linkDistance()+0.01;
var chrg = force.charge()-1;
if(value < 0) value = 0;
force.gravity(value);
//force.linkDistance(dis);
//force.charge(chrg);
force.start();
}
</script>
<body style="" onLoad="init()">
<div style="padding:5px;position:absolute;top:0px; text-align:center height:60px; background:#444; width:100%; z-index:1; opacity:0.9;">
<table align="" cellpadding="0" cellspacing="0" border="0">
<tr>
<td valign="top" rowspan="2"><span style="padding:5px; font-size:32px;color:gainsboro;">GuardianTagExplorer</span></td>
<td valign="top"><span style="padding:5px; font-size:12px;color:gainsboro;">What</span> </td>
<td valign="top"></td>
<td valign="top"></td>
<td rowspan="2" valign="bottom"><span style="padding:5px; font-size:12px;color:gainsboro;">[<a id="permlink" href="http://hawksey.info/tagexplorer/guardian.html?q=" style="color:#fff;">permalink</a>]</span></td>
</tr>
<tr>
<td valign="top"><input type="text" class="search" id="search" style="width:250px;" value="&quot;the open university&quot;">
</td>
<td valign="top"><input value="go" class="" type="submit" onClick="doBits();">
</td>
<td valign="top">&nbsp;</td>
</tr>
</table>
</div>
<div style="position:absolute;bottom:10px;left:-3px;background-color:#ffffff;display:block;z-index:2;width:100%;text-align:right;">displaying: <span id="searchCount">0</span> records of a possible <span id="searchTotal">-</span> | <span id="nodeCount">-</span> <a href="javascript:void(0);" onClick="showTagDist()">tags</a> <span id="linkCount">-</span> edges [<a href="javascript:void(0);" onClick="getLiveData($('#search').val(),true)">+</a>]&nbsp;&nbsp;&nbsp;
<div style="float:left;text-align:left">&nbsp;&nbsp;&nbsp;<a href="http://mbostock.github.com/d3/" target="_blank">d3.js</a> and the <a href="http://www.guardian.co.uk/open-platform" target="_blank">Guardian Open Platform</a> glued togther with lots of ideas and code from <a href="http://ouseful.info">Tony Hirst</a> and <a href="http://gis.yohman.com" target="_blank">yohman</a> by <a href="http://mashe.hawksey.info" target="_blank">mhawksey</a>. <a href="http://mashe.hawksey.info/2011/10/guardian-tag-explorer/" target="_blank">More info</a>... </div>
</div>
<!--
Side panel for tweet display
-->
<div id="sidepanel" style="z-index:100"></div>
<div id="tagpanel" style="z-index:100"><div class="topbar"><a href="javascript:void()" onClick="javascript:$('#tagpanel').fadeOut()">X</a></div><div id="visualization" style="height: 1000px; width: 290px;"></div></div>
<!--
Popup
-->
<div id="popup" style=""></div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment