Skip to content

Instantly share code, notes, and snippets.

@willzjc
Last active June 24, 2019 01:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save willzjc/e16e943b2bc4fa15aa5708a4688d77f0 to your computer and use it in GitHub Desktop.
Save willzjc/e16e943b2bc4fa15aa5708a4688d77f0 to your computer and use it in GitHub Desktop.
TimeLine
license: mit
{"tweet": {"num_photos": 0, "num_user_mentions": 0, "text": "Stop Working All Those Hours http://t.co/R7oc0xzw", "created_at": "2012-06-15T15:35:03", "num_hashtag": 0, "num_urls": 1, "retweet_count": 155, "id": "213655868590407680"}, "arcs": [{"to": 1, "from": 0}, {"to": 2, "from": 0}, {"to": 3, "from": 0}, {"to": 4, "from": 0}, {"to": 5, "from": 0}, {"to": 6, "from": 0}, {"to": 7, "from": 0}, {"to": 8, "from": 0}, {"to": 9, "from": 0}, {"to": 10, "from": 0}, {"to": 11, "from": 0}, {"to": 12, "from": 0}, {"to": 13, "from": 0}, {"to": 14, "from": 0}, {"to": 15, "from": 0}, {"to": 16, "from": 0}, {"to": 18, "from": 0}, {"to": 19, "from": 0}, {"to": 20, "from": 0}, {"to": 21, "from": 0}, {"to": 22, "from": 0}, {"to": 23, "from": 0}, {"to": 25, "from": 0}, {"to": 26, "from": 0}, {"to": 27, "from": 0}, {"to": 29, "from": 0}, {"to": 31, "from": 0}, {"to": 33, "from": 0}, {"to": 34, "from": 0}, {"to": 35, "from": 0}, {"to": 37, "from": 0}, {"to": 39, "from": 0}, {"to": 41, "from": 0}, {"to": 42, "from": 0}, {"to": 44, "from": 0}, {"to": 45, "from": 0}, {"to": 46, "from": 0}, {"to": 47, "from": 0}, {"to": 48, "from": 0}, {"to": 51, "from": 0}, {"to": 52, "from": 0}, {"to": 53, "from": 0}, {"to": 54, "from": 0}, {"to": 55, "from": 0}, {"to": 57, "from": 0}, {"to": 58, "from": 53}, {"to": 61, "from": 0}, {"to": 62, "from": 0}, {"to": 63, "from": 0}, {"to": 64, "from": 0}, {"to": 66, "from": 0}, {"to": 67, "from": 0}, {"to": 68, "from": 0}, {"to": 70, "from": 0}, {"to": 71, "from": 62}, {"to": 71, "from": 0}, {"to": 72, "from": 0}, {"to": 74, "from": 0}, {"to": 76, "from": 61}, {"to": 76, "from": 0}, {"to": 77, "from": 0}, {"to": 78, "from": 0}, {"to": 79, "from": 76}, {"to": 80, "from": 0}, {"to": 82, "from": 0}, {"to": 83, "from": 0}, {"to": 84, "from": 0}, {"to": 85, "from": 0}, {"to": 87, "from": 0}], "nodes": [{"followers_count": 769805, "id": 14800270, "created_after": 0, "screen_name": "HarvardBiz"}, {"followers_count": 411, "id": 272583709, "created_after": 2184, "screen_name": "EnzodellaRocca"}, {"followers_count": 60, "id": 64971088, "created_after": 2194, "screen_name": "innaratieva"}, {"followers_count": 88, "id": 106523940, "created_after": 2200, "screen_name": "ChangeGijsbert"}, {"followers_count": 257, "id": 21844846, "created_after": 2364, "screen_name": "jdomingo57"}, {"followers_count": 8, "id": 276985083, "created_after": 2389, "screen_name": "mcktaryn"}, {"followers_count": 123, "id": 108849605, "created_after": 2417, "screen_name": "4waytest"}, {"followers_count": 54, "id": 251553111, "created_after": 2456, "screen_name": "Wabbiss"}, {"followers_count": 67, "id": 77160350, "created_after": 2471, "screen_name": "Cino1933"}, {"followers_count": 119, "id": 179593914, "created_after": 2520, "screen_name": "jeremybeaudry"}, {"followers_count": 111, "id": 23790487, "created_after": 3174, "screen_name": "thecarriefisher"}, {"followers_count": 40, "id": 197272778, "created_after": 3216, "screen_name": "fernitorres"}, {"followers_count": 10, "id": 31459122, "created_after": 3256, "screen_name": "Davide_Cinguino"}, {"followers_count": 184, "id": 6280722, "created_after": 3446, "screen_name": "rfinney"}, {"followers_count": 48, "id": 133186284, "created_after": 3545, "screen_name": "PShoe_turtle"}, {"followers_count": 83, "id": 17110045, "created_after": 4113, "screen_name": "taravati"}, {"followers_count": 2532, "id": 14581524, "created_after": 4205, "screen_name": "tbump"}, {"followers_count": 276, "id": 20437113, "created_after": 4303, "screen_name": "manselk"}, {"followers_count": 10, "id": 585961221, "created_after": 4507, "screen_name": "AtlasBHConsult"}, {"followers_count": 27, "id": 404077760, "created_after": 4606, "screen_name": "CGarcesA"}, {"followers_count": 85, "id": 294561070, "created_after": 4735, "screen_name": "mireychalouhi"}, {"followers_count": 33, "id": 483271276, "created_after": 4971, "screen_name": "SStothard"}, {"followers_count": 73, "id": 278005613, "created_after": 5147, "screen_name": "IstisharaSTK"}, {"followers_count": 80, "id": 36205250, "created_after": 5325, "screen_name": "emargarettaylor"}, {"followers_count": 19, "id": 125170828, "created_after": 5593, "screen_name": "MattMurph63"}, {"followers_count": 134, "id": 19600661, "created_after": 6201, "screen_name": "spooper"}, {"followers_count": 108, "id": 69250260, "created_after": 6339, "screen_name": "dorotazys"}, {"followers_count": 65, "id": 32629004, "created_after": 7475, "screen_name": "BigBZ"}, {"followers_count": 113, "id": 147317390, "created_after": 8148, "screen_name": "Jason_Southern"}, {"followers_count": 23, "id": 334199812, "created_after": 8318, "screen_name": "Hey_RJ"}, {"followers_count": 197, "id": 202171628, "created_after": 8335, "screen_name": "RedMarlinUK"}, {"followers_count": 115, "id": 22792124, "created_after": 8485, "screen_name": "elisecarbone"}, {"followers_count": 109, "id": 241850910, "created_after": 8523, "screen_name": "sarahstephens08"}, {"followers_count": 41, "id": 117355010, "created_after": 8603, "screen_name": "priyajain_15"}, {"followers_count": 33, "id": 271451747, "created_after": 8667, "screen_name": "cponce14"}, {"followers_count": 161, "id": 121579335, "created_after": 8721, "screen_name": "ImamPujaya"}, {"followers_count": 21, "id": 218063812, "created_after": 9563, "screen_name": "MHB9978"}, {"followers_count": 251, "id": 250375417, "created_after": 10887, "screen_name": "msrobbin1"}, {"followers_count": 55, "id": 16667435, "created_after": 11066, "screen_name": "zsidane"}, {"followers_count": 166, "id": 16201841, "created_after": 11317, "screen_name": "marktilbury"}, {"followers_count": 2377, "id": 11940352, "created_after": 11496, "screen_name": "duanebrown"}, {"followers_count": 54, "id": 376044422, "created_after": 11540, "screen_name": "ErikMan8"}, {"followers_count": 147, "id": 44200554, "created_after": 13109, "screen_name": "j_bonesy"}, {"followers_count": 15, "id": 423448903, "created_after": 13170, "screen_name": "waleed_mas"}, {"followers_count": 56, "id": 135910726, "created_after": 15337, "screen_name": "vrdgez"}, {"followers_count": 20, "id": 464493163, "created_after": 17140, "screen_name": "ThThemi"}, {"followers_count": 149, "id": 413491009, "created_after": 17997, "screen_name": "comichph"}, {"followers_count": 9, "id": 555250396, "created_after": 18243, "screen_name": "dtapartnership"}, {"followers_count": 7, "id": 136963260, "created_after": 19779, "screen_name": "Martinodm10"}, {"followers_count": 212, "id": 39491102, "created_after": 20224, "screen_name": "beuchelt"}, {"followers_count": 142, "id": 40975622, "created_after": 22038, "screen_name": "IvanLares"}, {"followers_count": 9, "id": 428583562, "created_after": 22125, "screen_name": "Timasovich"}, {"followers_count": 78, "id": 348662725, "created_after": 23220, "screen_name": "EricLuistro"}, {"followers_count": 698, "id": 51343383, "created_after": 26612, "screen_name": "danklynn"}, {"followers_count": 21, "id": 448284720, "created_after": 26651, "screen_name": "EHuff_Triathlon"}, {"followers_count": 64, "id": 88299296, "created_after": 27407, "screen_name": "jonbg"}, {"followers_count": 15, "id": 149572914, "created_after": 28833, "screen_name": "jackcartwright"}, {"followers_count": 626, "id": 98599779, "created_after": 31344, "screen_name": "QyunHuda"}, {"followers_count": 64, "id": 74358989, "created_after": 34065, "screen_name": "sedavison"}, {"followers_count": 22, "id": 26835421, "created_after": 34510, "screen_name": "oscar_uk"}, {"followers_count": 5, "id": 466065047, "created_after": 35884, "screen_name": "smemadi"}, {"followers_count": 605, "id": 25230340, "created_after": 36465, "screen_name": "wenhwu"}, {"followers_count": 92, "id": 17750619, "created_after": 38135, "screen_name": "subbins"}, {"followers_count": 23, "id": 159319367, "created_after": 41713, "screen_name": "GregoryArtinoff"}, {"followers_count": 15, "id": 79173847, "created_after": 42963, "screen_name": "manuelsimo"}, {"followers_count": 65, "id": 21202409, "created_after": 43380, "screen_name": "chrismarin"}, {"followers_count": 11, "id": 192930772, "created_after": 43860, "screen_name": "Jzigne"}, {"followers_count": 893, "id": 43287399, "created_after": 44108, "screen_name": "UmYazan"}, {"followers_count": 7, "id": 35846773, "created_after": 47646, "screen_name": "mmmAlinka"}, {"followers_count": 6, "id": 56656054, "created_after": 48076, "screen_name": "kaizenworld_com"}, {"followers_count": 294, "id": 41386884, "created_after": 48310, "screen_name": "ATradSquare"}, {"followers_count": 154, "id": 469158564, "created_after": 49714, "screen_name": "RahulPratti"}, {"followers_count": 20, "id": 50977034, "created_after": 49875, "screen_name": "mushma0c"}, {"followers_count": 473, "id": 11856012, "created_after": 53646, "screen_name": "jonnyashton"}, {"followers_count": 275, "id": 82065217, "created_after": 54603, "screen_name": "AndhyMu"}, {"followers_count": 2, "id": 537635860, "created_after": 59397, "screen_name": "tom_donlon"}, {"followers_count": 453, "id": 320950469, "created_after": 59835, "screen_name": "ronanlupton"}, {"followers_count": 66, "id": 376467006, "created_after": 59846, "screen_name": "ShinboneStarr"}, {"followers_count": 6, "id": 510562286, "created_after": 62760, "screen_name": "capriconwells"}, {"followers_count": 32, "id": 44468461, "created_after": 70367, "screen_name": "DeirdreArdagh"}, {"followers_count": 57, "id": 252261291, "created_after": 72612, "screen_name": "AH2C"}, {"followers_count": 9, "id": 110491822, "created_after": 73663, "screen_name": "catalinccc"}, {"followers_count": 1140, "id": 14422924, "created_after": 74145, "screen_name": "pedrowm"}, {"followers_count": 39, "id": 187513216, "created_after": 75282, "screen_name": "thewonderloft"}, {"followers_count": 48, "id": 452391901, "created_after": 77082, "screen_name": "kamilest"}, {"followers_count": 14, "id": 241786069, "created_after": 78117, "screen_name": "woodybrokenburr"}, {"followers_count": 52, "id": 33552828, "created_after": 78873, "screen_name": "juantxon"}, {"followers_count": 24, "id": 184278988, "created_after": 79099, "screen_name": "MarcRLup"}, {"followers_count": 94, "id": 106525992, "created_after": 82288, "screen_name": "DadisiSPEAKS"}], "author": {"screen_name": "HarvardBiz", "profile_image": "https://si0.twimg.com/profile_images/661586716/favicon_64_normal.png", "id": 14800270, "name": "Harvard Biz Review"}}
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<style>
body {
font-family: 'HelveticaNeue-Light', Arial, Helvetica, sans-serif;
font-size: 14px;
}
.container#tweet {
padding-top: 30px;
}
div.tooltip {
background: rgba(0, 0, 0, 0.6);
color: #FFFFFF;
font-size: 12px;
font-family: 'HelveticaNeue-Light', Arial, Helvetica, sans-serif;
padding: 4px 6px;
pointer-events: none;
position: absolute;
text-align: center;
}
</style>
</head>
<body>
<div class="container" id="tweet"></div>
<div class="container" id="plot"></div>
<script>
function addCommas(nStr){
nStr += '';
x = nStr.split('.');
x1 = x[0];
x2 = x.length > 1 ? '.' + x[1] : '';
var rgx = /(\d+)(\d{3})/;
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + ',' + '$2');
}
return x1 + x2;
}
var tooltip = d3.select("div#plot").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
function showtt(text){
tooltip.text(text).style("opacity", 1);
}
function movett(){
tooltip.style("left", d3.event.pageX + 12 + "px")
.style("top", d3.event.pageY + 5 + "px");
}
function hidett(){
tooltip.style("opacity", 0);
}
var data;
var isEvenSpaced = false;
var isInit = false;
d3.json("213655868590407680.json", function(json){
data = json;
plot(data, isEvenSpaced);
});
function plot(data, isEvenSpaced){
var width = 1000;
var height = 200;
var profile = d3.select("div#tweet")
.append("svg")
.attr("class", "profile")
.attr("width", width)
.attr("height", 120);
profile.append("image")
.attr("xlink:href", "https://pbs.twimg.com/profile_images/600303920125214721/uy-506Vm_400x400.jpg")
.attr("x", 50)
.attr("y", "0")
.attr("width", "48px")
.attr("height", "48px")
.style("cursor", "hand")
.on("click", function(){
window.open("http://www.twitter.com/" + data.author.screen_name, "_blank");
});
profile.append("text")
.text(data.author.name)
.attr("x", 105)
.attr("y", 2)
.attr("dy", "1em")
.style("fill", "#000000")
.style("font-weight", "bold")
.style("font-size", "24px")
.style("cursor", "hand")
.on("click", function(d,i){
window.open("http://www.twitter.com/" + data.author.screen_name, "_blank");
});
profile.append("text")
.text("@" + data.author.screen_name)
.attr("x", 105)
.attr("y", 48 - 2)
.attr("dy", "-0.1em")
.style("fill", "#999999")
.style("font-size", "14px")
.style("cursor", "hand")
.on("click", function(d,i){
window.open("http://www.twitter.com/" + data.author.screen_name, "_blank");
});
profile.append("text")
.text(data.tweet.text)
.attr("x", 50)
.attr("y", 48 + 10)
.attr("dy", "1em")
.style("fill", "#000000")
.style("font-size", "14px")
.style("cursor", "hand")
.on("mouseover", function(d,i){
d3.select(this).style("fill", "#F1B054");
})
.on("mouseout", function(d,i){
d3.select(this).style("fill", "#000000");
})
.on("click", function(d,i){
window.open("http://www.twitter.com/" + data.author.screen_name + "/status/" + data.tweet.id, "_blank");
});
profile.append("text")
.text("Retweeted " + (data.nodes.length - 1) +
" times. Displayed to " + addCommas(d3.sum(data.nodes, function(d) { return d.followers_count; })) + " users.")
.attr("x", 50)
.attr("y", 48 + 45)
.attr("dy", "1em")
.style("fill", "#993333")
.style("font-size", "14px");
var timeline = d3.select("div#plot").append("svg")
.attr("class", "timeline")
.attr("width", width)
.attr("height", height);
var created_after_list = data.nodes.map(function(n) { return n.created_after; });
var max_followers_count = d3.max(data.nodes.map(function(n) { return n.followers_count; } ));
var max_created_after = d3.max(created_after_list);
var x;
if(isEvenSpaced) {
x = d3.scale.ordinal()
.domain(created_after_list)
.rangeBands([100, width - 100]);
} else {
x = d3.scale.linear()
.domain([0, max_created_after])
.range([100, width - 100]);
}
var y = d3.scale.linear()
.domain([0, width - 2 * 100])
.range([0, height / 2 - 20]);
var r = d3.scale.log()
.domain([1, max_followers_count])
.range([0, height / 2 - 20]);
var curve_gen = d3.svg.line()
.interpolate("cardinal")
.x(function(d) { return d.x; })
.y(function(d) { return d.y; });
var arcs = timeline.selectAll("arc")
.data(data.arcs).enter();
function generateControlPoints(x1, x2){
return [ {x: x1, y: 100},
{x: (x1+x2)/2.0, y: 100 - y(Math.abs(x1-x2))},
{x: x2, y: 100} ];
}
var curves = arcs.append("path")
.style("stroke", "#444444")
.style("stroke-opacity", "0.2")
.style("stroke-width", "1.5px")
.style("fill", "none")
.attr("d", function(d) {
x1 = x(created_after_list[d.from]);
x2 = x(created_after_list[d.to]);
return curve_gen.tension(0)(generateControlPoints(x1, x2));
});
timeline.append("rect")
.attr("x", x(0))
.attr("y", 0)
.attr("width", width)
.attr("height", 100)
.style("fill", "#FFFFFF")
.transition()
.duration(function() { return (isInit) ? 0 : 1100; })
.ease("cubic-in-out")
.attr("x", width)
.remove();
var nodes = timeline.selectAll("node")
.data(data.nodes).enter();
nodes.append("circle")
.attr("class", "outer")
.attr("id", function(d,i) { return "outer-" + i; })
.attr("cx", function(d) { return x(d.created_after); } )
.attr("cy", 100)
.style("fill", "#993333")
.style("fill-opacity", 0.1)
.style("stroke", "none")
.style("stroke-width", "3px")
.transition()
.delay(function(d,i) { return (isInit) ? 0 : d.created_after * 500 / max_created_after; })
.duration(function() { return (isInit) ? 0 : 600; })
.ease("bounce")
.attr("r", function(d) { return Math.max(1, r(d.followers_count)); });
nodes.append("circle")
.attr("class", "inner")
.attr("id", function(d,i) { return "inner-" +i; })
.attr("cx", function(d) { return x(d.created_after); } )
.attr("cy", 100)
.attr("r", 3)
.style("fill", "#444444")
.style("opacity", 0.0)
.transition()
.delay(function(d,i) { return (isInit) ? 0 : d.created_after * 500 / max_created_after; })
.duration(function() { return (isInit) ? 0 : 600; })
.style("opacity", 1.0);
function created_after_toString(created_after){
var day = Math.floor(created_after / (60 * 60 * 24));
var remainder = created_after - day * (60 * 60 * 24);
var hour = Math.floor(remainder / (60 * 60));
remainder -= hour * (60 * 60);
var min = Math.floor(remainder / 60);
remainder -= min * 60;
var second = remainder;
var out = "";
if(day > 0) out += day + " days ";
if(hour > 0) out += hour + " hrs ";
if(min > 0 && day==0) out += min + " mins ";
if(second > 0 && hour==0) out += second + " secs ";
if(created_after > 0) return out;
else return "less than a second";
}
nodes.append("rect")
.attr("fill-opacity", 0.0)
.attr("x", function(d,i) {
return (i == 0) ? 0 :
(x(data.nodes[i-1].created_after) + x(data.nodes[i].created_after)) / 2.0;
})
.attr("width", function(d,i) {
x1 = (i == 0) ? 0 :
(x(data.nodes[i-1].created_after) + x(data.nodes[i].created_after)) / 2.0;
x2 = (i == data.nodes.length - 1) ? width :
(x(data.nodes[i].created_after) + x(data.nodes[i+1].created_after)) / 2.0;
return x2 - x1;
})
.attr("y", 100 - 25)
.attr("height", 100)
.style("cursor", "hand")
.on("mouseover", function(d, i) {
var xpos = x(d.created_after);
var radius = Math.max(1, r(d.followers_count));
timeline.select("circle#inner-" + i).style("fill", "#993333");
timeline.select("circle#outer-" + i)
.style("visibility", "visible")
.style("stroke", "#993333");
if (i > 0) {
timeline.append("line")
.attr("class", "time_label")
.attr("x1", xpos)
.attr("y1", 100)
.attr("x2", xpos)
.attr("y2", 100 + 67)
.style("stroke", "#444444")
.style("stroke-width", 0.5);
timeline.append("text")
.attr("class", "time_label")
.text(created_after_toString(d.created_after))
.attr("x", xpos)
.attr("dx", 5)
.attr("y", 100 + 67)
.attr("dy", "-0.1em")
.attr("text-anchor", "start")
.style("fill", "#444444")
.style("font-size", "12px");
}
var count_label = timeline.append("text").text(addCommas(d.followers_count) + " followers");
var bbox = count_label.node().getBBox();
var rect_w = bbox.width + 6;
var rect_h = bbox.height + 2;
count_label.remove();
timeline.append("rect")
.attr("class", "user_label")
.attr("x", xpos - rect_w / 2)
.attr("y", 85 - radius - rect_h / 2)
.attr("width", rect_w)
.attr("height", rect_h)
.style("fill", "#FFFFFF");
timeline.append("text")
.attr("class", "user_label")
.text(addCommas(d.followers_count) + " followers")
.attr("x", xpos)
.attr("y", 85 - radius)
.attr("dy", "0.4em")
.attr("text-anchor", "middle")
.style("fill", "#77A396");
showtt("@" + d.screen_name);
timeline.selectAll("path")
.style("stroke-opacity", function(e, j) {
if((e.from != i) && (e.to != i))
return 0.05;
else
return 0.5;
});
})
.on("mousemove", function(d, i){
movett();
})
.on("mouseout", function(d, i){
timeline.select("circle#inner-" + i).style("fill", "#444444");
timeline.select("circle#outer-" + i)
.style("stroke", "none");
if(i > 0) timeline.selectAll(".time_label").remove();
timeline.selectAll(".user_label").remove();
hidett();
timeline.selectAll("path").style("stroke-opacity", 0.2);
})
.on("click", function(d, i){
window.open("http://www.twitter.com/" + d.screen_name, "_blank");
});
timeline.append("line")
.attr("x1", x(0))
.attr("y1", 100)
.attr("x2", x(0))
.attr("y2", 100 + 100)
.style("stroke", "#444444")
.style("stroke-width", 0.5);
var date_iso = d3.time.format("%Y-%m-%dT%H:%M:%S");
var date_pretty = d3.time.format("%I:%M %p - %d %b %y");
timeline.append("text")
.text(date_pretty(new Date(date_iso.parse(data.tweet.created_at) - 60000 * (new Date().getTimezoneOffset()))))
.attr("x", x(0))
.attr("dx", 5)
.attr("y", 100 + 100)
.attr("dy", "-0.1em")
.attr("text-anchor", "start")
.style("fill", "black")
.style("font-size", "20px");
timeline.append("text")
.text("rescale")
.attr("x", width-100)
.attr("dx", 0)
.attr("y", 100 + 100)
.attr("dy", "-0.1em")
.attr("text-anchor", "end")
.style("cursor", "hand")
.style("fill", "#666666")
.style("font-size", "14px")
.on("mouseover", function(d,i) {
d3.select(this).style("fill", "#77A396");
})
.on("mouseout", function(d,i) {
d3.select(this).style("fill", "#666666");
})
.on("click", function(d,i) {
isEvenSpaced = !isEvenSpaced;
d3.selectAll("svg").remove();
plot(data, isEvenSpaced);
});
isInit = true;
}
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment