Last active
April 16, 2016 04:49
-
-
Save sbhargava24/2a567b81be88c6c136da2a42aeabcd48 to your computer and use it in GitHub Desktop.
Simple Line Chart with Color Gradient
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<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"); | |
function draw(data) { | |
"use strict"; | |
// set margins according to Mike Bostock's margin conventions | |
// http://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("Noe Valley Temperature") | |
.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 | |
}; | |
// Q1: group data by day | |
var data_sub = data.slice(1000, 10000); | |
var nested_data = d3.nest() | |
.key(function(dt) { | |
var tmp = dt['timestamp']; | |
return (tmp.getMonth()).toString() + '/' + tmp.getDate(); | |
}) | |
.entries(data); | |
// Q2: remove the first 10 days of data (month of January) | |
var day_new = nested_data.slice(10); | |
// Q3: extract the days we are visualizing | |
// (to be used as labels in buttons) | |
// var getKeys = function(arr) { | |
// var key, keys = []; | |
// for (i=0; i<arr.length; i++) | |
// for (key in arr[i]) { | |
// keys.push(key); | |
// } | |
// } | |
// return keys; | |
// }; | |
// var days = getKeys(day_new); | |
var days = day_new.map(function(dt, x) { | |
return { | |
key: dt.key, | |
selected: false, | |
idx: x | |
}; | |
}); | |
// Q4: 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; | |
}); | |
// Q5: toggle button of first day | |
d3.select('.day_buttons') | |
.select('div:first-child') | |
.transition() | |
.duration(500) | |
.style("background", "#FF0000") | |
.style('color', 'black'); | |
// Find min/max of temperature column | |
var temp_extent = d3.extent(data_sub, function(d) { | |
return d[field]; | |
}); | |
// get min/max times of first day of data | |
// (we will need a new extent for each day) | |
var time_extent = d3.extent(data_sub, 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("Degrees (Celsius)") | |
.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(data_sub) | |
.attr("class", "line") | |
.attr("d", line); | |
// Q6: create a timeout interval to animate over the days | |
var interval_timeout = function(day_idx) { | |
// 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(nested_data[day_idx].key); | |
// increment the day visualized by one | |
day_idx++; | |
if(day_idx >= nested_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); | |
}; | |
}; | |
// Q7: 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'); | |
}; | |
// Q8: 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", "#FF0000") | |
.style('color', 'black'); | |
}; | |
// Q9: add hover to buttons | |
buttons.on('mouseover', function(d) { | |
if (!d.selected) { | |
d3.select(this) | |
.transition() | |
.duration(500) | |
.style("background", "#FF0000") | |
.style('color', 'black'); | |
} | |
}); | |
// Q10: 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", "#FF0000") | |
.style('color', 'black'); | |
} | |
}) | |
// Q11: 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); | |
}); | |
// Q12: function which updates the data bound to our chart | |
function update(key) { | |
// filter our data for the specified day in 'key' | |
var data_subset = nested_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("http://jay-oh-en.github.io/interactive-data-viz/data/datacanvas/noevalley.csv", function(d) { | |
// convert from string to Date object | |
var date = format.parse(d['timestamp']); | |
// use moment.js to shift time back 8 hours (to PST) | |
var mom = moment(date).subtract(8, 'hours'); | |
d['timestamp'] = mom.toDate(); | |
// coerce temperature from string to float | |
d['temperature'] = +d['temperature']; | |
return d; | |
}, draw); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment