Data set : Twitter data collected for Conjuctivitis.
My narrative is author based. Social media can be used as a disease surveillance tool. By looking at the number of people tweeting about conjunctivitis, can indicate rise in the number of cases of the disease. My main motivation behind the visualization is to use social media data to identify the disease pattern of occurrence over a course of the year, and compare it with ground truth i.e. information from public health agencies.
People tweet their lives. Social media platforms promise increased opportunities for a more timely and accurate spreading of information. Time series pattern of of tweets collected between 2012 to 2014, the peak of the diseases occurs during the early spring. This pattern matches with the hospital records. Indicating the rise in conjunctivitis during the early spring.
These new sources of analysis and information are intended to complement traditional sources of epidemic intelligence. Further social media and web searches help identify individuals within a network who should be targeted for vaccinations to prevent the spread of disease. GIS analysis can, for example, locate a potential disease hot spot and calculate a nearby hospital’s ability to handle the expected increase in service demand if an outbreak should occur
To improve on this visualization, I would like to add a tooltip to see the exact numbers, and brush to select the range of the dates to visualize. Also Geo tags of tweets could be used to plot them in a map. This helps isolate the network of individuals, and a potential outbreak.
Dealing with all the noise and picking the correct signal is a challenge. Since people just tweet very generically, and do not write specific symptoms and causes. Maybe it’s an initial indicator of the disease outbreak and health officials can take preventive measures to avoid that region of the epidemic.
xxxxxxxxxx
<html>
<head>
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.12/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>
<style>
body {
font-family: futura;
width: 960px;
}
h2.title {
color: black;
text-align: center;
}
.axis {
font-family: arial;
font-size: 0.8em;
}
.axis text {
fill: black;
stroke: none;
}
path {
fill: none;
stroke: black;
stroke-width: 2px;
}
.tick {
fill: none;
stroke: black;
}
.line {
fill: none;
stroke: url(#temperature-gradient);
stroke-width: 1px;
}
div.day_buttons {
text-align: center;
}
div.day_buttons div {
text-align: center;
background-color: #438CCA;
color: white;
padding: 6px;
margin: 5px;
font-size: 0.8em;
cursor: pointer;
display: inline-block;
}
</style>
<script type="text/javascript">
// D3 utility to convert from timestamp string to Date object
//var format = d3.time.format("%Y-%m-%dT%H:%M:%S.%LZ");
//var format = d3.time.format("%m/%d/%y %H:%M:%S");
var format = d3.time.format("%m/%d/%Y %H:%M");
function draw(data) {
//alert(data);
//console.log(data);
"use strict";
// set margins according to Mike Bostock's margin conventions
// https://bl.ocks.org/mbostock/3019563
var margin = {top: 25, right: 40, bottom: 25, left: 75};
// set height and width of chart
var width = 960 - margin.left - margin.right,
height = 350 - margin.top - margin.bottom;
var field = 'temperature';
// Append the title for the graph
d3.select("body")
.append("h2")
.text("Tweets about Conjunctivitis Jan 2015.")
.attr('class', 'title');
// append the SVG tag with height and width to accommodate for margins
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append('g')
.attr('class','chart')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// object to keep track on animation state
var animation = {
"timeout": false,
"animating": false
};
// group data by day
var nested = d3.nest()
.key(function(d) {
var ts = d['timestamp'];
return (ts.getMonth() + 1).toString() + '/' + ts.getDate();
})
.entries(data);
// remove the first 10 days of data (month of January)
console.log(nested);
// debugger;
var day_data = nested.slice(10);
debugger;
console.log(day_data);
// extract the days we are visualizing
// (to be used as labels in buttons)
var days = day_data.map(function(d, i) {
return {
key: d.key,
selected: false,
idx: i
};
});
// dynamically create buttons to toggle days
var buttons = d3.select("body")
.append("div")
.attr("class", "day_buttons")
.selectAll("div")
.data(days)
.enter()
.append("div")
.text(function(d) {
return d.key;
})
.attr('id', function(d) {
// convert mm/dd to mm_dd for more readable #id
return 'd' + d.key.split('/').join('_');
});
// toggle button of first day
d3.select('.day_buttons')
.select('div:first-child')
.transition()
.duration(500)
.style("background", "#F7977A")
.style('color', 'black');
// Find min/max of temperature column
var temp_extent = d3.extent(data, function(d) {
return d[field];
});
// console.log(nested);
// get min/max times of first day of data
// (we will need a new extent for each day)
var time_extent = d3.extent(day_data[0].values, function(d){
return d['timestamp'];
});
// Create x-axis scale mapping dates -> pixels
var time_scale = d3.time.scale()
.range([0, width])
.domain(time_extent);
// Create y-axis scale mapping temperature -> pixels
var temp_scale = d3.scale.linear()
.range([height, 0])
.domain(temp_extent);
// Create D3 axis object from time_scale for the x-axis
var time_axis = d3.svg.axis()
.scale(time_scale)
.ticks(d3.time.hours, 2)
.tickFormat(d3.time.format("%I:00 %p"));
// Create D3 axis object from temp_scale for the y-axis
var temp_axis = d3.svg.axis()
.scale(temp_scale)
.orient("left");
// determine thresholds for gradient stops
var range = temp_extent[1] - temp_extent[0];
var interval = range / 3;
var low_threshold = temp_extent[0] + interval;
var high_threshold = temp_extent[0] + 2 * interval;
// Append a Gradient to double encode temperature
svg.append("linearGradient")
.attr("id", "temperature-gradient")
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", 0).attr("y1", temp_scale(low_threshold))
.attr("x2", 0).attr("y2", temp_scale(high_threshold))
.selectAll("stop")
.data([
{offset: "0%", color: "steelblue"},
{offset: "50%", color: "gray"},
{offset: "100%", color: "red"}
])
.enter().append("stop")
.attr("offset", function(d) { return d.offset; })
.attr("stop-color", function(d) { return d.color; });
// Append SVG to page corresponding to the x-axis
svg.append('g')
.attr('class', 'x axis')
.attr('transform', "translate(0," + height + ")")
.call(time_axis);
// Append SVG to page corresponding to the y-axis
svg.append('g')
.attr('class', 'y axis')
.call(temp_axis);
// add label to y-axis
d3.select(".y.axis")
.append("text")
.text("Count (Tweets)")
.attr("transform", "rotate(-90, -43, 0) translate(-260)")
.style('font-size', '1.2em');
// define the values to map for x and y position of the line
var line = d3.svg.line()
.x(function(d) { return time_scale(d['timestamp']); })
.y(function(d) { return temp_scale(d[field]); });
// append a SVG path that corresponds to the line chart
var path = svg.append("path")
.datum(day_data[0].values)
.attr("class", "line")
.attr("d", line);
// create a timeout interval to animate over the days
var interval_timeout = function(day_idx) {
// debugger;
// set timeout to run callback function every 2000 milliseconds
var day_interval = setInterval(function(){
// run the update function to update the bound data appropriately
update(day_data[day_idx].key);
// increment the day visualized by one
day_idx++;
if(day_idx >= day_data.length) {
// clear callback if we have reached the end of days
clearInterval(day_interval);
}
}, 2000);
// change animation state on the global object to true
animation.animating = true;
// return a function we can use to stop the animation
return function() {
clearInterval(day_interval);
};
};
// define a function to unselect all the buttons
var button_unhighlight = function() {
d3.select(".day_buttons")
.selectAll("div")
.datum(function(d) {
// change the internal state of each button to unselected
d.selected = false;
return d;
})
.transition()
.duration(500)
.style("color", "white")
.style("background", '#438CCA');
};
// highlight button with a specified id
var button_highlight = function(id) {
d3.select('#' + 'd' + id.split('/').join('_'))
.datum(function(d) {
// change internal state to selected
d.selected = true;
return d;
})
.transition()
.duration(500)
.style("background", "#F7977A")
.style('color', 'black');
};
// add hover to buttons
buttons.on('mouseover', function(d) {
if (!d.selected) {
d3.select(this)
.transition()
.duration(500)
.style("background", "#F7977A")
.style('color', 'black');
}
});
// add hover to buttons
buttons.on('mouseout', function(d) {
if (!d.selected) {
d3.select(this)
.transition()
.duration(500)
.style("color", "white")
.style("background", '#438CCA');
} else {
d3.select(this)
.transition()
.duration(500)
.style("background", "#F7977A")
.style('color', 'black');
}
})
// function to be run when on a clicked button
buttons.on("click", function(d) {
if(d.selected === true) {
// if button is selected an the animation is stopped
if (animation.animating === false) {
// change internal state of this button
d.selected = false;
button_unhighlight();
// start the animation on the next button
animation.timeout = interval_timeout(d.idx + 1);
} else {
// if the button is selected but animation
// is running, stop the animation
animation.timeout();
animation.animating = false;
}
} else {
// if button is not selected, selected it
d.selected = true;
// stop the animation
animation.animating = false;
animation.timeout();
}
// update the bound data for this button
d3.select(this).datum(d);
// run our update function for the line chart
update(d.key);
});
// function which updates the data bound to our chart
function update(key) {
// filter our data for the specified day in 'key'
var data_subset = day_data.filter(function(d) {
return d.key === key;
})[0].values;
// highlight buttons in sync with animation
button_unhighlight();
button_highlight(key);
var time_extent = d3.extent(data_subset, function(d) {
return d['timestamp'];
});
// Create x-axis scale mapping dates -> pixels
var time_scale = d3.time.scale()
.range([0, width])
.domain(time_extent);
// define the values to map for x and y position of the line
var line = d3.svg.line()
.x(function(d) { return time_scale(d['timestamp']); })
.y(function(d) { return temp_scale(d[field]); });
// update data bound to path
path.datum(data_subset);
// transition the line to animate with the changed data
path.transition().duration(1000).attr("d", line);
};
// start initial animation for first day
animation.timeout = interval_timeout(1);
};
</script>
</head>
<body>
<script type="text/javascript">
d3.csv("data.csv", function(d) {
// convert from string to Date object
//debugger;
var date = format.parse(d['timestamp']);
// console.log(date);
//alert(date);
// use moment.js to shift time back 8 hours (to PST)
var mom = moment(date).subtract(8, 'hours');
debugger;
d['timestamp'] = mom.toDate();
// coerce temperature from string to float
d['temperature'] = +d['temperature'];
return d;
}, draw);
</script>
</body>
</html>
https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.12/d3.min.js
https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js