A bar chart of Twitter images from Tweets that contain the phrase "in one [chart|graph]". The images themselves make up the areas of the bar chart. Because the Twitter Search API only indexes tweets from the previous seven days, the data is relatively uninteresting for a bar chart.Thus, this visualization is more a proof-of-concept and a unique talking piece for the increased prevalence of the phrase "in one chart". I wrote a short bit on this visualization here.
For this visualization, I wanted to group unique charts/graphs on the day they were first published. I used the following pipeline to determined the tweets that first published unique charts/graphs:
I did not know that step3 would yield such a reduction in the search space. I learned there is good reason for serious tweeters to not simply click retweet, but instead copy-paste the tweet of interest and fire it off themselves (i.e., manually retweet). Good thing, too, else the somewhat costly image comparison in step4 might have barred its fangs.
To avoid recomputation, each tweet that has gone through the pipeline is marked to state whether or not it is a unique-earliest-tweet. Thus, on successive runs of the pipeline, steps 1-3 only have to run on the new tweets. Step 4 is computationally lessened as well, but not to same degree because each new image must be compared to older images.
For image comparison, I use a root-means-squared metric between the two image's PIL.Image.histogram(). Histographic comparison, at least in my implementation, is not robust to cropping or small annotations; those types of edits rarely justify uniqueness.
The slow loading of images is ugly. A loading screen would be nicer.
Interactivity. I've tried my hand at embedding corresponding Tweets upon mouseover() for the images with some success. Uncomment out the .on(mouseover) line and you will get that interactivity with the embedded Tweet off to the side. Unfortunately, you can only view the tweet because once you move your cursor you inevitably mouseover() some other tweet along the way. A better way to embed the tweet is with a tooltip. I struggled in attempting that in large part due to the asynchrnous nature of embedding tweets. Current d3-tooltip libraries are unsuited for that. jQuery lacks support for svgs for this purpose, and I would much prefer a d3 native solution over other external libraries.
xxxxxxxxxx
<meta charset="utf-8">
<style>
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
</style>
<body>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 40, right: 20, bottom: 100, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.ordinal()
.rangeRoundBands([height, 0], .1);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var embed_tweets = d3.select("body").append("g")
.attr("id", 'embed_container');
d3.json("inonechart2.json", function(error, data) {
// cleaning data. d3.json() does not have any accessor methods (e.g. type(d))
var data_keys = Object.keys(data),
dates = [],
date_strs = [];
for (var i = data_keys.length - 1; i >= 0; i--) {
dates[i] = convertDateToUTC(new Date(data_keys[i]))
};
dates.sort(function(a,b) { return a.getTime() - b.getTime(); });
date_strs = dates.map(function(d) { return jsdateToKey(d); } );
x.domain(date_strs);
var y_domain = [],
sizes = data_keys.map(function(d) { return data[d].length; });
max_size = d3.max(sizes);
while(max_size--) y_domain[max_size] = max_size;
y.domain(y_domain);
// flatten out data
var temp_data = [];
for (var i = data_keys.length - 1; i >= 0; i--) {
for (var j = data[data_keys[i]].length - 1; j >= 0; j--) {
var doc = data[data_keys[i]][j];
doc['data_key'] = data_keys[i];
doc['created_at'] = convertDateToUTC(new Date(Date.parse(doc['created_at'])));
temp_data.push(doc);
};
};
data = temp_data;
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.text(function(d) { return d.substring(5,d.length).replace('-', '/'); });
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Charts that day");
svg.selectAll("image")
.data(data)
.enter().append("svg:image")
.attr("class", "twitter-pic")
.attr("xlink:href", function(d) { return d.media[0].media_url_https; })
.attr("x", function(d) { return x(d.data_key); })
.attr("y", function(d) { return height - (sizes[data_keys.indexOf(d.data_key)]-- * y.rangeBand()); })
.attr("width", x.rangeBand())
.attr("height", y.rangeBand());
//.on('mouseover', function(d) { mouseOver(d); })
});
function mouseOver(d) {
var iframe = d3.selectAll('iframe')
.data(d);
iframe.exit().remove();
twttr.ready(function() {
twttr.widgets.createTweet(d.id_str, document.getElementById('embed_container'), {'align': 'right', 'width': '300' });
});
}
function convertDateToUTC(date) {
// call this only once per date. successive calls will result in (n_calls - 1) too many offsets
return new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds());
}
function jsdateToKey(date) {
// note that this does not call UTC versions of the getters. do any conversinos before calling this
return date.getFullYear().toString() + '-' + pad(date.getMonth()+1, 2) + '-' + pad(date.getDate(), 2);
}
function pad(n, width, z) {
z = z || '0';
n = n + '';
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
}
</script>
<script>window.twttr = (function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0],
t = window.twttr || {};
if (d.getElementById(id)) return;
js = d.createElement(s);
js.id = id;
js.src = "https://platform.twitter.com/widgets.js";
fjs.parentNode.insertBefore(js, fjs);
t._e = [];
t.ready = function(f) {
t._e.push(f);
};
return t;
}(document, "script", "twitter-wjs"));</script>
Modified http://d3js.org/d3.v3.min.js to a secure url
https://d3js.org/d3.v3.min.js