Built with blockbuilder.org
Last active
November 17, 2015 02:09
-
-
Save walkerjeffd/9a77fe4663f531c0d8cd to your computer and use it in GitHub Desktop.
timeseries-uncertainty
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> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.js"></script> | |
<script src="timechart.js"></script> | |
<style> | |
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } | |
svg { width: 100%; height: 100%; } | |
.axis path, | |
.axis line { | |
fill: none; | |
stroke: #000; | |
shape-rendering: crispEdges; | |
} | |
.dot { | |
fill: steelblue; | |
} | |
.line { | |
fill: none; | |
stroke: steelblue; | |
stroke-width: 1.5px; | |
} | |
rect.pane { | |
cursor: move; | |
fill: none; | |
pointer-events: all; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="controls"> | |
<input id="slider1" type="range" min="0" max="100" value="50"> | |
<input id="slider2" type="range" min="0" max="100" value="50"> | |
</div> | |
<div id="viz"></div> | |
<script> | |
var startDate = new Date("2001-10-01"), | |
rHour = -0.0406079999999997, | |
rDay = 0.1; | |
var days = d3.range(0, 366, 1), | |
hours = d3.range(0, 24, 1); | |
var data = compute(+d3.select('#slider1').property('value')/100, | |
+d3.select('#slider2').property('value')/100); | |
d3.select('#slider1').on('input', render); | |
d3.select('#slider2').on('input', render); | |
function render () { | |
data = compute(+d3.select('#slider1').property('value')/100, | |
+d3.select('#slider2').property('value')/100); | |
console.log(data[0]); | |
chart.data([{ | |
key: 'obs', | |
label: 'Obs', | |
color: 'forestgreen', | |
opacity: 0.5, | |
primary: true, | |
values: data | |
}]); | |
d3.select('#viz').call(chart); | |
} | |
var chart = timeChart() | |
.data([{ | |
key: 'obs', | |
label: 'Obs', | |
color: 'forestgreen', | |
opacity: 0.5, | |
primary: true, | |
values: data | |
}]); | |
d3.select('#viz').call(chart); | |
function compute(rDay, rHour) { | |
return _.flatten(days.map(function(d) { | |
return hours.map(function (h) { | |
var datetime = d3.time.hour.offset(d3.time.day.offset(startDate, d), h), | |
value = Math.random()*rDay*Math.sin(2*Math.PI*(d)/365) + | |
Math.random()*Math.sin(2*Math.PI*(h/24*rHour)); | |
return [datetime, value]; | |
}); | |
})); | |
} | |
function timeChart () { | |
var margin = {top: 20, right: 20, bottom: 30, left: 50}, | |
width = 960 - margin.left - margin.right, | |
height = 450 - margin.top - margin.bottom, | |
x = d3.time.scale().range([0, width]), | |
y = d3.scale.linear().range([height, 0]), | |
xAxis = d3.svg.axis().scale(x).orient('bottom'), | |
yAxis = d3.svg.axis().scale(y).orient('left'), | |
xAccessor = function (d) { return d[0]; }, | |
yAccessor = function (d) { return d[1]; }, | |
yLabel = '', | |
bisectDate = d3.bisector(xAccessor).left, | |
numberFormat = d3.format('.1f'), | |
dateFormat = d3.time.format('%Y-%m-%d %H:%M'), | |
focus, | |
data, | |
showBand = true, | |
svg, g, container, | |
onZoom = function () {}; | |
var area = d3.svg.area() | |
.x(function (d) { return x(xAccessor(d)); }) | |
.y0(function (d) { return y(d.max); }) | |
.y1(function (d) { return y(d.min); }) | |
.interpolate('step-after'); | |
var line = d3.svg.line() | |
.x(function (d) { return x(xAccessor(d)); }) | |
.y(function (d) { return y(yAccessor(d)); }) | |
.interpolate('step-after'); | |
var zoom = d3.behavior.zoom() | |
.scaleExtent([1, 80]) | |
.on('zoom', zoomed); | |
var customTimeFormat = d3.time.format.multi([ | |
['.%L', function (d) { return d.getMilliseconds(); }], | |
[':%S', function (d) { return d.getSeconds(); }], | |
['%I:%M', function (d) { return d.getMinutes(); }], | |
['%I %p', function (d) { return d.getHours(); }], | |
['%b %d', function (d) { return d.getDay() && d.getDate() != 1; }], | |
['%b %d', function (d) { return d.getDate() != 1; }], | |
['%b %Y', function (d) { return d.getMonth(); }], | |
['%Y', function () { return true; }] | |
]); | |
xAxis.tickFormat(customTimeFormat).ticks(5); | |
function chart (el) { | |
if (el.selectAll('svg').empty()) { | |
svg = el | |
.append('svg') | |
.attr('height', height + margin.top + margin.bottom) | |
.attr('width', width + margin.left + margin.right) | |
.append('g') | |
.attr('transform', 'translate(' + margin.left + ',' + | |
margin.top + ')'); | |
svg.append('clipPath') | |
.attr('id', 'clip') | |
.append('rect') | |
.attr('x', 0) | |
.attr('y', 0) | |
.attr('width', width) | |
.attr('height', height); | |
svg.append('g') | |
.attr('class', 'x axis') | |
.attr('transform', 'translate(0,' + height + ')'); | |
svg.append('g') | |
.attr('class', 'y axis') | |
.append('text') | |
.attr('transform', 'rotate(-90)') | |
.attr('y', 6) | |
.attr('dy', '.71em') | |
.style('text-anchor', 'end') | |
.text(yLabel); | |
container = svg.append('g') | |
.attr('class', 'data') | |
.attr('clip-path', 'url(#clip)'); | |
container.append('g').attr('class', 'areas'); | |
container.append('g').attr('class', 'lines'); | |
container.append('g').attr('class', 'points'); | |
container.append('g').attr('class', 'ref-lines'); | |
focus = svg.append('g') | |
.attr('class', 'focus') | |
.style('display', 'none'); | |
focus.append('circle') | |
.style('fill', 'none') | |
.style('stroke', 'steelblue') | |
.attr('r', 4.5); | |
focus.append('text') | |
.attr('x', 9) | |
.attr('dy', '.35em'); | |
svg.append('rect') | |
.attr('width', width) | |
.attr('height', height) | |
.attr('class', 'pane') | |
.on('mouseover', function () { focus.style('display', null); }) | |
.on('mouseout', function () { focus.style('display', 'none'); }) | |
.on('mousemove', mousemove) | |
.call(zoom); | |
} | |
zoomed(); | |
} | |
function zoomed() { | |
if (data) { | |
var xExtent = data.map(function (d) { | |
return d3.extent(d.values, xAccessor); | |
}); | |
xExtent = d3.extent(_.flatten(xExtent)); | |
// clamp x-axis zoom | |
if (x.domain()[0] < xExtent[0]) { | |
zoom.translate([zoom.translate()[0] - x(xExtent[0]) + x.range()[0], | |
zoom.translate()[1]]); | |
} else if (x.domain()[1] > xExtent[1]) { | |
zoom.translate([zoom.translate()[0] - x(xExtent[1]) + x.range()[1], | |
zoom.translate()[1]]); | |
} | |
var yExtent = data.map(function (d) { | |
if (d.showBand) { | |
var yMin = data.map(function (d) { | |
return d3.extent(d.values, function (d) { return d.min; }); | |
}); | |
var yMax = data.map(function (d) { | |
return d3.extent(d.values, function (d) { return d.max; }); | |
}); | |
return [d3.min(_.flatten(yMin)), d3.max(_.flatten(yMax))]; | |
} else { | |
return d3.extent(d.values, yAccessor); | |
} | |
}); | |
yExtent = d3.extent(_.flatten(yExtent)); | |
y.domain(yExtent); | |
onZoom(x.domain()); | |
svg.select('.x.axis') | |
.call(xAxis); | |
svg.select('.y.axis') | |
.call(yAxis); | |
var areas = container.select('g.areas') | |
.selectAll('.area') | |
.data(data.filter(function (d) { return d.showBand; })); | |
areas.enter().append('path') | |
.attr('class', 'area') | |
.attr('fill', 'gray') | |
.style('opacity', 0.25); | |
areas.attr('d', function (d, i) { | |
return area(d.values, i); | |
}); | |
areas.exit().remove(); | |
var gLines = container.select('g.lines') | |
.selectAll('.line') | |
.data(data); | |
gLines.enter().append('path') | |
.attr('class', 'line'); | |
gLines.attr('d', function (d, i) { | |
return line(d.values, i); | |
}) | |
.style('stroke', function (d) { | |
return d.color || 'orangered'; | |
}) | |
.style('opacity', function (d) { | |
return d.opacity || 0.5; | |
}); | |
gLines.exit().remove(); | |
var gPoints = container.select('g.points') | |
.selectAll('.point') | |
.data(data[0].values); | |
gPoints.enter().append('circle') | |
.attr('class', 'point'); | |
gPoints.attr('cx', function (d) { return x(xAccessor(d)); }) | |
.attr('cy', function (d) { return y(yAccessor(d)); }) | |
.attr('r', function (d) { return Math.pow(Math.max(0, -10*yAccessor(d))+1, 2);}) | |
.attr('opacity', function (d) { return (Math.random()+2)*y(yAccessor(d)); }) | |
.attr('fill', 'deepskyblue'); | |
gPoints.exit().remove(); | |
} | |
} | |
function mousemove() { | |
var mouseDate = x.invert(d3.mouse(this)[0]); | |
// filter series | |
var filtered = data.filter(function (d) { | |
return mouseDate >= d.extent[0] && mouseDate <= d.extent[1]; | |
}); | |
if (filtered.length > 0) { | |
var series = filtered[0]; | |
var i = bisectDate(series.values, mouseDate, 1); | |
var d; | |
if (i === 0) { | |
d = series.values[0]; | |
} else { | |
var d0 = series.values[i - 1], | |
d1 = series.values[i]; | |
// d = mouseDate - xAccessor(d0) > xAccessor(d1) - mouseDate ? d1 : d0; | |
d = d0; | |
} | |
focus.attr('transform', | |
'translate(' + x(mouseDate) + ',' + | |
y(yAccessor(d)) + ')'); | |
var text = dateFormat(mouseDate) + ' | ' + numberFormat(yAccessor(d)); | |
focus.style('display', null); | |
focus.select('text').text(text); | |
var textWidth = focus.node().getBBox().width, | |
mouseX = d3.mouse(this)[0], | |
xWidth = x.range()[1]; | |
// flip/flop text side | |
if (textWidth + mouseX > xWidth) { | |
focus.select('text') | |
.attr('text-anchor', 'end') | |
.attr('x', -9); | |
} else { | |
focus.select('text') | |
.attr('text-anchor', 'start') | |
.attr('x', 9); | |
} | |
} else { | |
focus.style('display', 'none'); | |
} | |
} | |
chart.width = function (_) { | |
if (!arguments.length) { | |
return width; | |
} | |
width = _ - margin.left - margin.right; | |
x.range([0, width]); | |
return chart; | |
}; | |
chart.height = function (_) { | |
if (!arguments.length) { | |
return height; | |
} | |
height = _ - margin.top - margin.bottom; | |
y.range([height, 0]); | |
return chart; | |
}; | |
chart.x = function (_) { | |
if (!arguments.length) { | |
return xAccessor; | |
} | |
xAccessor = _; | |
bisectDate = d3.bisector(xAccessor).left; | |
return chart; | |
}; | |
chart.y = function (_) { | |
if (!arguments.length) { | |
return yAccessor; | |
} | |
yAccessor = _; | |
return chart; | |
}; | |
chart.yLabel = function (_) { | |
if (!arguments.length) { | |
return yLabel; | |
} | |
yLabel = _; | |
return chart; | |
}; | |
chart.data = function (__) { | |
if (!arguments.length) { | |
return data; | |
} | |
data = __; | |
var oldTranslate = zoom.translate(), | |
oldScale = zoom.scale(); | |
data.forEach(function (d) { | |
d.extent = d3.extent(d.values, xAccessor); | |
}); | |
x.domain(d3.extent(_.flatten(_.pluck(data, 'extent')))); | |
zoom | |
.x(x) | |
.translate(oldTranslate) | |
.scale(oldScale); | |
return chart; | |
}; | |
chart.onZoom = function (_) { | |
if (!arguments.length) { | |
return onZoom; | |
} | |
onZoom = _; | |
return chart; | |
}; | |
return chart; | |
} | |
</script> | |
</body> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment