Built with blockbuilder.org
forked from walkerjeffd's block: timeseries-uncertainty
Built with blockbuilder.org
forked from walkerjeffd's block: timeseries-uncertainty
<!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(1, -0.1); | |
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.cos(2*Math.PI*(d)/365) + | |
Math.random()*rHour*Math.cos(2*Math.PI*h/24); | |
return [datetime, value]; | |
}); | |
})); | |
} | |
function timeChart () { | |
var margin = {top: 20, right: 20, bottom: 30, left: 50}, | |
width = 960 - margin.left - margin.right, | |
height = 500 - 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.max(0, -20*yAccessor(d)+1.5);}) | |
.attr('opacity', function (d) { return yAccessor(d)+0.7; }) | |
.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> |