Skip to content

Instantly share code, notes, and snippets.

@yohman
Created October 8, 2011 05:40
Show Gist options
  • Save yohman/1271916 to your computer and use it in GitHub Desktop.
Save yohman/1271916 to your computer and use it in GitHub Desktop.
Twitter Smash
<!DOCTYPE html>
<html>
<head>
<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>
<script src="http://maps.googleapis.com/maps/api/js?sensor=false&libraries=places" type="text/javascript"></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" />
<link href="http://yohman.com/smash/css/style.css" type="text/css" rel="stylesheet" />
<script src="https://www.google.com/jsapi?key=ABQIAAAAtHf2Vbojx_f9l2digk62nRR721nvlaDHrh7xkOtCxSM-c80RpRRMoUR4qEAqmJpECcqpdsdEs7YdIw" type="text/javascript"></script>
<script language="Javascript" type="text/javascript">
//Load libraries
google.load("jquery", "1.6.2");
google.load("jqueryui", "1.8.16");
</script>
<style type="text/css">
body {
background:black;
overflow: hidden;
margin: 0;
font: 14px "Ubuntu", sans-serif;
}
svg {
background:#222;
position:absolute;
top:0px;
z-index:0;
}
.popup {
background:white;
border:1px solid gainsboro;
padding:5px;
min-width: 150px;
}
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;
}
.link {
stroke: #ccc;
}
.nodetext {
pointer-events: none;
font: 10px sans-serif;
}
</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) {
getLiveData($('#search').val());
}
});
$("body").mousewheel(function(i,intDelta){
if (intDelta > 0 ) gravityUp();
if (intDelta < 0 ) gravityDown();
});
});
$(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];
}
});
/* =============================================================================== **
Init
** =============================================================================== */
function init()
{
force = d3.layout.force()
.gravity(0.04)
.distance(100)
.charge(-100)
.size([w, h]);
nodes = force.nodes(),
links = force.links();
vis = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h);
force.on("tick", function() {
vis.selectAll("circle.node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
vis.selectAll("line.link")
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
});
restart();
//if q is defined in url, go straight to getting data
var q = $.getUrlVar('q');
if(typeof q !== 'undefined')
{
getLiveData(q);
}
}
/* =============================================================================== **
Restart
** =============================================================================== */
function restart() {
var link = vis.selectAll("line.link")
.data(links, function(d) { return d.source.id + "-" + d.target.id; });
link.enter().insert("svg:line", "circle.node")
.attr("class", "link");
link.exit().remove();
var node = vis.selectAll("circle.node")
.data(d3.values(nodes), function(d) { return d.id;});
var nodeEnter = node.enter().append("svg:circle")
.attr("class", "node")
.style("fill", function(d) {
if(d.type == 'solo')
{
color = 'blue';
}
else if (d.type == 'source')
{
color = 'red';
}
else
{
color = 'green';
}
return color;
}
)
.style("stroke", "#fff")
.on("mouseover", function(d) {showThumb(d.name,d)})
.on("click", function(d) {
if(d.type !=='target')findTwitterUser(d.name,d,d.type);
getLiveData(d.name);
})
.on("mouseout", function() {
$('#popup').hide();
$('#popup').html('');
})
.attr("r", function(d) {
console.log(d.id + ' circle size is ' + d.connections);
if(d.name == undefined)
{
size = 0;
}
else
{
var size = parseInt(d.connections)*8;
if(size > 50) size = 50;
//size = Math.sqrt(d.connections)*8;
if(size == 0) size = 4;
}
return (size);
})
.call(force.drag);
nodeEnter.append("svg:text")
.attr("class", "nodetext")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.id });
node.exit().remove();
force.start();
}
/* =============================================================================== **
Globals
** =============================================================================== */
var w,h,force,nodes,links,vis;
var w = $(window).width(),
h = $(window).height();
var livecounter = 0;
var next_page = '';
var refresh_url = '';
var q = '';
var blackholeindex;
var refresh = false;
var nodes = [];
var links = [];
var tweetids = [];
/* =============================================================================== **
Get tweets
** =============================================================================== */
function getLiveData(q,refresh)
{
console.log('next...' + q);
// setTimeout("console.log('timing')",2000);
// setTimeout("getLiveData('obama')",2000);
$('#map_canvas').fadeOut();
q = q;
livecounter = 0;
if(livecounter == 0)
{
console.log('counting');
var url = 'http://search.twitter.com/search.json?callback=?&rpp=100&q='+q;
if(typeof lat !== 'undefined')
{
//geocode it, and then get the lat/lon
url += '&geocode='+lat+','+lng+','+$("#searchradius").val();
$('#search-big-display').append(' ' +loc);
}
}
else
{
var url = 'http://search.twitter.com/search.json'+url+'&callback=?';
}
livecounter++;
$.getJSON(url, function(data)
{
if(data.results.length == 0)
{
if(livecounter == 1)
{
livecounter = 0;
alert('no results found');
return false;
}
else
{
//drawD3();
}
}
next_page = data.next_page;
refresh_url = data.refresh_url;
$.each(data.results,function(i,val){
//Only do anything if this tweet has never been accessed
if($.inArray(val.id,tweetids) == -1)
{
tweetids.push(val.id);
//if node exists, append the tweet
var tonodeexists = false;
var fromnodeexists = false;
//if first ever node, create it:
if(nodes.length == 0)
{
//source
var sourcenode =
{
name: val.from_user,
id: val.from_user,
type: 'solo',
profile_image_url: val.profile_image_url,
tweets: [val],
connections: 1
}
if(typeof val.to_user !== "undefined")
{
sourcenode.type = 'source';
}
nodes.push(sourcenode);
if(typeof val.to_user !== "undefined")
{
sourcenode.type = 'source';
var targetnode =
{
name: val.from_user,
id: val.from_user,
type: 'target',
tweets: [val],
connections: 1
}
nodes.push(targetnode);
}
}
else
{
$.each(nodes,function(i,val2){
if(val2.id == val.from_user)
{
fromnodeexists = true;
nodes[i].connections = nodes[i].connections+1;
nodes[i].tweets.push(val)
}
if(val2.id == val.to_user)
{
tonodeexists = true;
nodes[i].connections = nodes[i].connections+1;
nodes[i].tweets.push(val)
}
//last record
if(i == (nodes.length-1))
{
if(!fromnodeexists)
{
var sourcenode =
{
name: val.from_user,
id: val.from_user,
type: 'solo',
profile_image_url: val.profile_image_url,
tweets: [val],
connections: 1
}
if(typeof val.to_user !== "undefined")
{
sourcenode.type = 'source';
}
nodes.push(sourcenode);
}
if(!tonodeexists)
{
if(typeof val.to_user !== "undefined")
{
var targetnode =
{
name: val.to_user,
id: val.to_user,
type: 'target',
tweets: [val],
connections: 1
}
nodes.push(targetnode);
}
}
}
});
}
//Add Link
if(typeof val.to_user !== 'undefined')
{
//create the link record
var link = {};
link.tweetid = val.id;
link.source = findNodePos(val.from_user);
link.target = findNodePos(val.to_user);
link.type = 'suit';
link.tweets = [];
link.tweets.push(val);
links.push(link);
}
//last record
if(i == data.results.length-1)
{
if(refresh)
{
restart();
}
else
{
restart();
}
return false;
//drawPV();
if(nodes.length < 200)
{
if(typeof next_page == "undefined")
{
//drawD3();
}
else
{
//getLiveData(q,next_page);
}
}
else
{
//drawD3();
//drawPV();
}
}
}
});//end .each
});
}
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)
{
if(d.type !== 'target')
{
var box = '<table cellpadding="2"><tr><td><img src="'+d.profile_image_url+'"></td><td align="center"><span class="connections">'+d.connections+'</span><br><span class="fieldname">connections</span></td></tr>';
box += '<tr><td align="right"><span class="fieldname">twitter </span></td><td><a target="_blank" href="https://twitter.com/#!/'+d.id+'">'+d.name+'</a></td></tr>';
box += '<tr><td align="right"><span class="fieldname">type </span></td><td>'+d.type+'</td></tr>';
box += '</table>';
$('#popup').fadeIn();
$('#popup').html('<div class="popup">'+box+'</div>');
}
else
{
var box = '<table cellpadding="2"><tr><td></td><td align="center"><span class="connections">'+d.connections+'</span><br><span class="fieldname">connections</span></td></tr>';
box += '<tr><td align="right"><span class="fieldname">twitter </span></td><td><a target="_blank" href="https://twitter.com/#!/'+d.id+'">'+d.name+'</a></td></tr>';
box += '<tr><td align="right"><span class="fieldname">type </span></td><td>'+d.type+'</td></tr>';
box += '</table>';
$('#popup').fadeIn();
$('#popup').html('<div class="popup">'+box+'</div>');
/*
var url = 'https://api.twitter.com/1/users/lookup.json?callback=?&screen_name='+index;
$.getJSON(url, function(data)
{
$('#popup').fadeIn();
$('#popup').html('<div class="popup"><img src="'+data[0].profile_image_url+'"></div>');
});
*/
}
}
var type = '';
function findTwitterUser(id,d,type)
{
$('#sidepanel').fadeOut();
$('#sidepanel').fadeIn('slow');
resize();
var box = '<table cellpadding="2"><tr><td><img src="'+d.profile_image_url+'"></td><td align="center"><span class="connections">'+d.connections+'</span><br><span class="fieldname">connections</span></td></tr>';
box += '<tr><td align="right"><span class="fieldname">twitter </span></td><td><a target="_blank" href="https://twitter.com/#!/'+d.id+'">'+d.name+'</a></td></tr>';
box += '<tr><td align="right"><span class="fieldname">type </span></td><td>'+d.type+'</td></tr>';
box += '</table>';
$.each(d.tweets,function(i,val){
box += '<div class="tweet"><span class="time">'+val.created_at+'</span><br>'+val.text+'</div>';
});
$('#sidepanel').html(box);
/*
if(type == 'source')
{
var url = 'https://api.twitter.com/1/users/lookup.json?callback=?&screen_name='+id;
}
else
{
var url = 'https://api.twitter.com/1/users/lookup.json?callback=?&screen_name='+id;
//var url = 'https://api.twitter.com/1/users/lookup.json?callback=?&user_id='+id;
}
$.getJSON(url, function(data)
{
var box = '<table cellpadding="2"><tr><td><img src="'+data[0].profile_image_url+'"></td><td align="center"><span class="connections">'+(d.connections)+'</span><br><span class="fieldname">connections</span></td></tr>';
box += '<tr><td align="right"><span class="fieldname">name </span></td><td>'+data[0].name+'</td></tr>';
box += '<tr><td align="right"><span class="fieldname">location </span></td><td>'+data[0].location+'</td></tr>';
box += '<tr><td align="right"><span class="fieldname">twitter </span></td><td><a href="https://twitter.com/#!/'+data[0].screen_name+'">'+data[0].screen_name+'</td></tr>';
box += '<tr><td align="right"><span class="fieldname">twitter </span></td><td><a href="?q='+data[0].screen_name+'">view connections</a></td></tr>';
box += '</table>';
$.each(d.tweets,function(i,val){
box += '<div class="tweet"><span class="time">'+val.created_at+'</span><br>'+val.text+'</div>';
});
$('#sidepanel').html(box);
});
*/
}
function getTwitterName(id,d)
{
var url = 'https://api.twitter.com/1/users/lookup.json?callback=?&user_id='+id;
$.getJSON(url, function(data)
{
return data[0].name;
});
}
/* =============================================================================== **
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);
}
/* =============================================================================== **
Controls
** =============================================================================== */
function explode()
{
force.charge(force.charge()-10);force.start();
}
function implode()
{
var value = force.charge()+10;
if(value > 50) value = 50;
force.charge(value);force.start();
}
function gravityUp()
{
var value = force.gravity()+0.01;
if(value > 1) value = 1;
force.gravity(value);force.start();
}
function gravityDown()
{
var value = force.gravity()-0.01;
if(value < 0) value = 0;
force.gravity(value);force.start();
}
/* =============================================================================== **
Google Maps Autocomplete
** =============================================================================== */
var map,lat,lng, loc,searchradius;
function initialize() {
var mapOptions = {
center: new google.maps.LatLng(80,180),
zoom: 1,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById('map_canvas'),
mapOptions);
var input = document.getElementById('searchTextField');
var autocomplete = new google.maps.places.Autocomplete(input);
autocomplete.bindTo('bounds', map);
var infowindow = new google.maps.InfoWindow();
var marker = new google.maps.Marker({
map: map
});
google.maps.event.addListener(autocomplete, 'place_changed', function() {
infowindow.close();
var place = autocomplete.getPlace();
lat = place.geometry.location.lat();
lng = place.geometry.location.lng();
loc = place.name;
if (place.geometry.viewport) {
map.fitBounds(place.geometry.viewport);
} else {
map.setCenter(place.geometry.location);
map.setZoom(17); // Why 17? Because it looks good.
}
var image = new google.maps.MarkerImage(
place.icon,
new google.maps.Size(71, 71),
new google.maps.Point(0, 0),
new google.maps.Point(17, 34),
new google.maps.Size(35, 35));
marker.setIcon(image);
marker.setPosition(place.geometry.location);
var address = '';
if (place.address_components) {
address = [(place.address_components[0] &&
place.address_components[0].short_name || ''),
(place.address_components[1] &&
place.address_components[1].short_name || ''),
(place.address_components[2] &&
place.address_components[2].short_name || '')
].join(' ');
}
infowindow.setContent('<div><strong>' + place.name + '</strong><br>' + address);
//infowindow.open(map, marker);
});
// Sets a listener on a radio button to change the filter type on Places
// Autocomplete.
function setupClickListener(id, types) {
var radioButton = document.getElementById(id);
google.maps.event.addDomListener(radioButton, 'click', function() {
autocomplete.setTypes(types);
});
}
setupClickListener('changetype-all', []);
setupClickListener('changetype-establishment', ['establishment']);
setupClickListener('changetype-geocode', ['geocode']);
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
<body style="" onLoad="init()">
<!--
<div id="help" style="position:absolute;top:100px;margin:auto;width:400px;padding:10px;background:white;border:1px solid silver; z-index:11">
<ol>
<li>Enter a search term or twitter username (ex. "<a href="javascript:void(0)" onClick="init('wayne rooney')">wayne rooney</a>", "barackobama")</li>
<li>Optionally... enter a location</li>
</ol>
<div style="margin:auto;width:50px;border:1px solid #555;padding:4px;">Let's get smashing</div>
</div>
<div id="fullscreen" style="z-index:10"></div>
-->
<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" style="margin:auto;">
<tr>
<td width="250" valign="top" rowspan="2">
<span style="padding:5px; font-size:32px;color:gainsboro;">Twitter</span> <span style="padding:5px; font-size:32px;color:white;">Smash</span>
</td>
<td valign="top">
<span style="padding:5px; font-size:12px;color:gainsboro;">What</span>
</td>
<td valign="top">
<span style="padding:5px; font-size:12px;color:gainsboro;">Where (optional)</span>
</td>
<td valign="top">
</td>
<td valign="top">
</td>
</tr>
<tr>
<td valign="top">
<input type="text" id="search" class="search" value="">
</td>
<td valign="top">
<input id="searchTextField" class="search" style="width:300px;" type="text" onFocus="$('#map_canvas').fadeIn();google.maps.event.trigger(map, 'resize') ">
<div id="map_canvas" style="width:300px;height:300px; display:none;"></div>
</td>
<td valign="top">
<select id="searchradius">
<option value="10mi" selected>10 mile radius</option>
<option value="50mi">50 mile radius</option>
<option value="100mi">100 mile radius</option>
</select>
</td>
<td valign="top">
<input value="smash it" class="" type="submit" onClick="getLiveData($('#search').val())">
</td>
</tr>
</table>
</div>
<!--
Copyright
-->
<div style="position:absolute;bottom:10px;right:10px;color:#818181;display:block;z-index:2" id="copy"><a href="http://gis.yohman.com" target="_blank">yohman</a> &copy; 2011</div>
<div id="search-big-display" style=""></div>
<!--
Side panel for tweet display
-->
<div id="sidepanel" style="z-index:100"></div>
<div id="displaydivs" style="display:none">
<!--
Gravity
-->
<div id="gravitycontrols" class="round" style="position:absolute; top:20px; left:15px; cursor:pointer; width:45px; height:110px; background:gainsboro;">
<div id="explode" style="position:relative; top:10px; left:9px; cursor:pointer;" onClick="gravityUp()"><img src="http://cdn1.iconfinder.com/data/icons/iphone_toolbar_icons/iphone_toolbar_icons/plus.png"></div>
<div id="explode" style="position:relative; top:32px; left:9px; cursor:pointer;" onClick="gravityDown()"><img src="http://cdn1.iconfinder.com/data/icons/iphone_toolbar_icons/iphone_toolbar_icons/minus.png"></div>
<div style="position:relative; top:40px; left:8px;"><span style="font-size:9px">gravity</span></div>
</div>
<!--
Charge in out
-->
<div id="chargecontrols" class="round" style="position:absolute; top:140px; left:15px; cursor:pointer; width:45px; height:110px; background:gainsboro;">
<div id="explode" style="position:relative; top:10px; left:9px; cursor:pointer;" onClick="explode()"><img src="http://cdn1.iconfinder.com/data/icons/iphone_toolbar_icons/iphone_toolbar_icons/plus.png"></div>
<div id="explode" style="position:relative; top:32px; left:9px; cursor:pointer;" onClick="implode()"><img src="http://cdn1.iconfinder.com/data/icons/iphone_toolbar_icons/iphone_toolbar_icons/minus.png"></div>
<div style="position:relative; top:40px; left:10px;"><span style="font-size:9px">charge</span></div>
</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