|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="utf-8"> |
|
<link href='https://fonts.googleapis.com/css?family=Lato:300' rel='stylesheet' type='text/css'> |
|
|
|
<style> |
|
body { |
|
background-color: whitesmoke; |
|
} |
|
|
|
.extent { |
|
fill-opacity: .1; |
|
fill: rgb(205,130,42); |
|
cursor: move; |
|
} |
|
|
|
.e { |
|
fill: rgb(111,111,111); |
|
cursor: move; |
|
} |
|
|
|
.w { |
|
fill: rgb(169,169,169); |
|
cursor: move; |
|
} |
|
|
|
.freeze { |
|
fill: #A8DFE4; |
|
} |
|
|
|
.rain { |
|
fill: #209CD3; |
|
} |
|
|
|
.clear { |
|
fill: white |
|
} |
|
|
|
.scattered { |
|
fill: lightgray; |
|
} |
|
|
|
.cloudy { |
|
fill: #a1a1a1; |
|
} |
|
|
|
.overcast { |
|
fill: #616161; |
|
} |
|
|
|
|
|
svg { |
|
background-color: white; |
|
font-family: 'Lato'; |
|
} |
|
|
|
.axis { |
|
stroke: white; |
|
opacity: .8; |
|
} |
|
|
|
text { |
|
pointer-events: none; |
|
-moz-user-select: none; |
|
-webkit-user-select: none; |
|
-ms-user-select: none; |
|
} |
|
|
|
text.title { |
|
font-size: 26px; |
|
} |
|
|
|
text.months, text.temp { |
|
text-anchor: middle; |
|
font-size: 12px; |
|
fill: #39837B; |
|
} |
|
|
|
circle.axis { |
|
stroke: white; |
|
stroke-width: 1px; |
|
fill: none; |
|
} |
|
|
|
circle.axis.record { |
|
stroke: #bae0d6; |
|
stroke-width: 1.2px; |
|
opacity: 1; |
|
} |
|
|
|
line.record, line.avg, line.yearLow, line.yearHigh{ |
|
stroke-width: 2px; |
|
} |
|
|
|
line.record { |
|
stroke: #bae0d6; |
|
} |
|
|
|
line.avg { |
|
stroke: #3FA39E; |
|
opacity: .5; |
|
} |
|
|
|
line.year { |
|
stroke: #006358; |
|
|
|
} |
|
|
|
line.yearLow, line.yearHigh{ |
|
stroke: #F97F5A; |
|
} |
|
|
|
.avg { |
|
stroke: #3FA39E; |
|
fill: #3FA39E; |
|
} |
|
|
|
.record { |
|
stroke: #bae0d6; |
|
fill: #bae0d6; |
|
.opacity: .5; |
|
} |
|
|
|
.year { |
|
stroke: #F97F5A; |
|
fill: #F97F5A; |
|
} |
|
.beyond { |
|
stroke: #445E5B; |
|
fill: #445E5B; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<svg width=960 height=500></svg> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.js"></script> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/1.3.0/d3-legend.min.js"></script> |
|
<script src="d3.svg.circularbrush.js" charset="utf-8" type="text/javascript"></script> |
|
<script> |
|
var width = 960, |
|
margin = 20, |
|
height = 500, |
|
svg = d3.select('svg'), |
|
origin = svg.append('g') |
|
.attr('transform', 'translate(' + width*3/5 + ',' + height/2 + ')'), |
|
rScale = d3.scale.linear() |
|
.domain([-10, 110]) |
|
.range([0, height/2 - margin]), |
|
yScale = function (day, temp) {return -Math.cos(angleScale(day)*Math.PI/180)*rScale(parseInt(temp))}, |
|
xScale = function (day, temp) {return Math.sin(angleScale(day)*Math.PI/180)*rScale(parseInt(temp))}, |
|
angleScale = d3.scale.linear() |
|
.range([0, 360]); |
|
|
|
var drawRadial = function(chart, cl, data, low, high){ |
|
chart.selectAll('line.' + cl) |
|
.data(data) |
|
.enter().append('line') |
|
.attr('x1', function (d) {return xScale(d.index, d[low])}) |
|
.attr('x2', function (d) {return xScale(d.index, d[high])}) |
|
.attr('y1', function (d) {return yScale(d.index, d[low])}) |
|
.attr('y2', function (d) {return yScale(d.index, d[high])}) |
|
.attr('class', cl); |
|
}; |
|
|
|
d3.json('ny.json', function(err, json){ |
|
|
|
rawData = json; |
|
|
|
angleScale.domain([0, json.values.length - 1]); |
|
|
|
var min = d3.min(json.values, function (d) {return parseInt(d.recLow)}), |
|
max = d3.max(json.values, function (d) {return parseInt(d.recHigh)}); |
|
|
|
var months = []; |
|
//find index for months based on data |
|
json.values.forEach(function (d, i) { |
|
var month = d.date.split('-')[1], |
|
prevDaysMonth = ( i === 0 ) ? undefined : json.values[i - 1].date.split('-')[1]; |
|
if (i === 0 || month != prevDaysMonth){ |
|
months.push({ |
|
month: month, |
|
index: i |
|
}); |
|
} |
|
}) |
|
|
|
//circle axis |
|
origin.selectAll('circle.axis-green') |
|
.data([40, 60, 80, 100]) |
|
.enter().append('circle') |
|
.attr('r', function (d) {return rScale(d)}) |
|
.attr('class', 'axis record') |
|
|
|
//record low and high |
|
drawRadial(origin, 'record', json.values, 'recLow', 'recHigh') |
|
|
|
//avg low and high |
|
drawRadial(origin, 'avg', json.values, 'avgLow', 'avgHigh') |
|
|
|
//this year's temperature |
|
var thisYear = json.values.filter(function (d) {return d.min }); |
|
|
|
drawRadial(origin, 'year', thisYear, 'min', 'max') |
|
|
|
var lowLower = json.values.filter(function (d) {return d.min && parseInt(d.min) < parseInt(d.avgLow)}); |
|
drawRadial(origin, 'yearLow', lowLower, 'min', 'avgLow') |
|
|
|
var highHigher = json.values.filter(function (d) {return d.min && parseInt(d.max) > parseInt(d.avgHigh)}); |
|
drawRadial(origin, 'yearHigh', highHigher, 'max', 'avgHigh') |
|
|
|
var circleAxis = [0, 32, 60, 80, 100] |
|
circleAxis = circleAxis.map( function (d) {return {temp: d, index: 320}}) |
|
|
|
//temperature axis |
|
origin.selectAll('circle.axis-white') |
|
.data(circleAxis) |
|
.enter().append('circle') |
|
.attr('r', function (d) {return rScale(d.temp)}) |
|
.attr('class', 'axis') |
|
|
|
//temperature axis labels |
|
origin.selectAll('text.temp') |
|
.data(circleAxis) |
|
.enter().append('text') |
|
.attr('x', function (d) { |
|
return xScale(d.index, d.temp)}) |
|
.attr('y', function (d) {return yScale(d.index, d.temp)}) |
|
.text(function (d) {return d.temp + '°'}) |
|
.attr('class', 'temp'); |
|
|
|
//axis lines |
|
var axis = origin.append('g'); |
|
|
|
axis.selectAll('line.axis') |
|
.data(months) |
|
.enter().append('line') |
|
.attr('x2', function (d) { |
|
return xScale(d.index, 120)}) |
|
.attr('y2', function (d) {return -yScale(d.index, 120)}) |
|
.attr('class', 'axis'); |
|
|
|
var monthLabels = months.filter( function (d,i) {return i%3 === 0}) |
|
//month labels |
|
axis.selectAll('text.months') |
|
.data(monthLabels) |
|
.enter().append('text') |
|
.attr('x', function (d) { |
|
return xScale(d.index, 110)}) |
|
.attr('y', function (d) {return yScale(d.index, 110)}) |
|
.text(function (d) {return d.month}) |
|
.attr('class', 'months'); |
|
|
|
//center for reference |
|
axis.append('circle') |
|
.attr('r', 3) |
|
.attr('class', 'avg') |
|
|
|
//title |
|
svg.append('text') |
|
.attr('x', 30) |
|
.attr('y', 60) |
|
.text(json.name) |
|
.attr('class', 'title') |
|
|
|
//subtitle |
|
svg.append('text') |
|
.attr('x', 30) |
|
.attr('y', 100) |
|
.text('Historical Data') |
|
|
|
//create legend |
|
var legendScale = d3.scale.ordinal() |
|
.domain(['Record', 'Average', 'This Year - within avg', 'This Year - beyond avg', 'Freezing', 'Precipitation', 'Scattered Clouds', "Cloudy", "Overcast"]) |
|
.range(['record', 'avg', 'beyond', 'year', 'freeze', 'rain', 'scattered', 'cloudy', 'overcast']) |
|
|
|
//d3-legend |
|
var legend = d3.legend.color() |
|
.shapePadding(5) |
|
.useClass(true) |
|
.scale(legendScale); |
|
|
|
svg.append('g') |
|
.attr('transform', 'translate(30,120)') |
|
.call(legend); |
|
|
|
d3.json("cloud_rain_freeze.json", loadBars); |
|
|
|
|
|
}); |
|
|
|
|
|
function loadBars(data) { |
|
freezeBars = []; |
|
rainBars = []; |
|
cloudBars = []; |
|
var freeze = {}; |
|
var cloud = {start: data[0].date, category: data[0].cloud}; |
|
var rain = {}; |
|
var dateScale = d3.time.scale().domain([new Date("01/01/2015"), new Date("12/31/2015")]).range([1,366]); |
|
|
|
data.forEach(function (d, i) { |
|
|
|
if (d.cloud !== cloud.category) { |
|
cloud.end = d.date; |
|
cloud.endInt = dateScale(new Date(d.date)); |
|
cloudBars.push(cloud); |
|
cloud = {start: d.date, startInt: dateScale(new Date(d.date)), category: d.cloud}; |
|
} |
|
|
|
if (freeze.start && !d.freeze) { |
|
freeze.end = d.date; |
|
freeze.endInt = dateScale(new Date(d.date)); |
|
freezeBars.push(freeze); |
|
freeze = {}; |
|
} |
|
else if (d.freeze && !freeze.start) { |
|
freeze.start = d.date; |
|
freeze.startInt = dateScale(new Date(d.date)); |
|
} |
|
if (rain.start && !d.rain) { |
|
rain.end = d.date; |
|
rain.endInt = dateScale(new Date(d.date)); |
|
rainBars.push(rain); |
|
rain = {}; |
|
} |
|
else if (d.rain && !rain.start) { |
|
rain.start = d.date; |
|
rain.startInt = dateScale(new Date(d.date)); |
|
} |
|
|
|
}); |
|
|
|
drawBars(rainBars, "rain", 205); |
|
drawBars(freezeBars, "freeze", 200); |
|
drawBars(cloudBars, "cloud", 210); |
|
|
|
drawBrush(); |
|
} |
|
|
|
function drawBrush() { |
|
brush = d3.svg.circularbrush() |
|
.range([1,366]) |
|
.innerRadius(10) |
|
.outerRadius(218) |
|
.handleSize(0.1) |
|
.on("brush", brush); |
|
|
|
d3.select("svg") |
|
.append("g") |
|
.attr("class", "brush") |
|
.attr("transform", "translate(576,250)") |
|
.call(brush); |
|
|
|
d3.select("svg").append("g") |
|
.attr("class", "linear") |
|
.attr("transform", "translate(40,350)") |
|
|
|
function brush() { |
|
extent = brush.extent(); |
|
var yScale = d3.scale.linear().domain([-10,110]).range([100,0]).clamp(true); |
|
var xScale = d3.scale.linear().domain([1,366]).range([0,250]); |
|
|
|
var start = extent[0]; |
|
var end = extent[1]; |
|
|
|
var barOffset = 0; |
|
|
|
if (start < end) { |
|
filteredData = rawData.values.filter(function (d) { |
|
return d.index >= start && d.index <= end; |
|
}); |
|
filteredRainbars = rainBars.filter(function (d) { |
|
var sDate = d.startInt; |
|
var eDate = d.endInt; |
|
return (eDate <= end && eDate >= start) || (sDate <= end && sDate >= start) |
|
}) |
|
filteredFreezebars = freezeBars.filter(function (d) { |
|
var sDate = d.startInt; |
|
var eDate = d.endInt; |
|
return (eDate <= end && eDate >= start) || (sDate <= end && sDate >= start) |
|
}) |
|
filteredCloudbars = cloudBars.filter(function (d) { |
|
var sDate = d.startInt; |
|
var eDate = d.endInt; |
|
return (eDate <= end && eDate >= start) || (sDate <= end && sDate >= start) |
|
}) |
|
|
|
} |
|
else { |
|
var janone = 1; |
|
var decthirtyone = 366 |
|
var filteredDataEarly = rawData.values.filter(function (d) { |
|
return (d.index >= start && d.index <= decthirtyone); |
|
}); |
|
var filteredDataAfter = rawData.values.filter(function (d) { |
|
return (d.index <= end && d.index >= janone); |
|
}); |
|
|
|
barOffset = filteredDataEarly.length; |
|
earlyMin = d3.min(filteredDataEarly, function (d) {return d.index}) |
|
|
|
filteredFreezebarsEarly = beforeBars(freezeBars) |
|
filteredFreezebarsAfter = afterBars(freezeBars) |
|
filteredRainbarsEarly = beforeBars(rainBars) |
|
filteredRainbarsAfter = afterBars(rainBars) |
|
filteredCloudbarsEarly = beforeBars(cloudBars); |
|
filteredCloudbarsAfter = afterBars(cloudBars); |
|
|
|
filteredData = filteredDataEarly.concat(filteredDataAfter); |
|
filteredFreezebars = filteredFreezebarsEarly.concat(filteredFreezebarsAfter); |
|
filteredRainbars = filteredRainbarsEarly.concat(filteredRainbarsAfter); |
|
filteredCloudbars = filteredCloudbarsEarly.concat(filteredCloudbarsAfter); |
|
|
|
function beforeBars(bars) { |
|
return bars.filter(function (d) { |
|
var sDate = d.startInt; |
|
var eDate = d.endInt; |
|
(d.index >= start && d.index <= decthirtyone) |
|
return (eDate >= start && eDate <= decthirtyone) || (sDate >= start && sDate <= decthirtyone) |
|
}).map(function (d) { |
|
return {startInt: d.startInt - earlyMin, endInt: d.endInt - earlyMin, category: d.category} |
|
}) |
|
} |
|
|
|
function afterBars(bars) { |
|
return bars.filter(function (d) { |
|
var sDate = d.startInt; |
|
var eDate = d.endInt; |
|
(d.index >= start && d.index <= decthirtyone) |
|
return (eDate <= end && eDate >= janone) || (sDate <= end && sDate >= janone) |
|
}).map(function (d) { |
|
return {startInt: d.startInt + barOffset, endInt: d.endInt + barOffset, category: d.category} |
|
}) |
|
} |
|
|
|
} |
|
|
|
var lineWidth = 250 / filteredData.length; |
|
|
|
minDate = d3.min(filteredData, function (d) {return d.index}) |
|
maxDate = d3.max(filteredData, function (d) {return d.index}) |
|
|
|
xScale.domain([0, filteredData.length]); |
|
|
|
d3.select("g.linear") |
|
.selectAll("g.linearBars") |
|
.remove(); |
|
|
|
d3.select("g.linear") |
|
.selectAll("rect") |
|
.remove(); |
|
|
|
d3.select("g.linear") |
|
.selectAll("text") |
|
.remove(); |
|
|
|
d3.select("g.linear") |
|
.selectAll("rect.rainbars") |
|
.data(filteredRainbars) |
|
.enter() |
|
.append("rect") |
|
.attr("class", "rain") |
|
.attr("y", -10) |
|
.attr("x", function (d) {return xScale((d.startInt - minDate)) }) |
|
.attr("width", function (d) {return Math.min(250 - xScale((d.startInt - minDate)), (d.endInt - d.startInt) * lineWidth) }) |
|
.attr("height", "5px") |
|
|
|
d3.select("g.linear") |
|
.selectAll("rect.freezebars") |
|
.data(filteredFreezebars) |
|
.enter() |
|
.append("rect") |
|
.attr("class", "freeze") |
|
.attr("y", -0) |
|
.attr("x", function (d) {return xScale((d.startInt - minDate)) }) |
|
.attr("width", function (d) {return Math.min(250 - xScale((d.startInt - minDate)), (d.endInt - d.startInt) * lineWidth) }) |
|
.attr("height", "5px") |
|
|
|
d3.select("g.linear") |
|
.selectAll("rect.cloudbars") |
|
.data(filteredCloudbars) |
|
.enter() |
|
.append("rect") |
|
.attr("class", function (d) {return d.category }) |
|
.attr("y", -20) |
|
.attr("x", function (d) {return xScale((d.startInt - minDate)) }) |
|
.attr("width", function (d) {return Math.min(250 - xScale((d.startInt - minDate)), (d.endInt - d.startInt) * lineWidth) }) |
|
.attr("height", "5px") |
|
|
|
d3.select("g.linear") |
|
.selectAll("g.linearBars") |
|
.data(filteredData, function (d) {return d.date}) |
|
.enter() |
|
.insert("g", "rect") |
|
.attr("class", "linearBars") |
|
.each(function (d, i) { |
|
if (i === 0 || i === filteredData.length - 1) { |
|
d3.select(this).append("text") |
|
.text(d.date) |
|
.attr("y", -30) |
|
.style("text-anchor", "middle"); |
|
} |
|
d3.select(this).append("line").style("stroke-width", lineWidth).attr("class", "highlightline") |
|
d3.select(this).append("line").style("stroke-width", lineWidth).attr("class", "record") |
|
d3.select(this).append("line").style("stroke-width", lineWidth).attr("class", "avg") |
|
d3.select(this).append("line").style("stroke-width", lineWidth).attr("class", "yearLow") |
|
d3.select(this).append("line").style("stroke-width", lineWidth).attr("class", "yearHigh") |
|
d3.select(this).append("line").style("stroke-width", lineWidth).attr("class", "year") |
|
d3.select(this).append("line").style("stroke-width", lineWidth).attr("class", "hoverline") |
|
}); |
|
|
|
d3.selectAll("g.linearBars") |
|
.attr("transform", function (d,i) {return "translate(" + xScale(i) +",0)" }); |
|
|
|
d3.selectAll("g.linearBars") |
|
.each(function (d) { |
|
var thisG = this; |
|
d3.select(this).select("line.highlightline") |
|
.attr("y1", -30) |
|
.attr("y2", 100) |
|
.style("stroke-width", 1) |
|
.style("stroke", "black") |
|
.style("opacity", 0); |
|
d3.select(this).select("line.hoverline") |
|
.attr("y1", -30) |
|
.attr("y2", 100) |
|
.style("stroke-width", lineWidth) |
|
.style("opacity", 0.0) |
|
.style("stroke", "black") |
|
.on("mouseover", function () { |
|
d3.select(thisG).select("line.highlightline").style("opacity", 1); |
|
}) |
|
.on("mouseout", function () { |
|
d3.selectAll("line.highlightline").style("opacity", 0); |
|
}) |
|
|
|
d3.select(this).select("line.record") |
|
.attr("y1", yScale(parseInt(d.recHigh))) |
|
.attr("y2", yScale(parseInt(d.recLow))); |
|
d3.select(this).select("line.avg") |
|
.attr("y1", yScale(parseInt(d.avgHigh))) |
|
.attr("y2", yScale(parseInt(d.avgLow))); |
|
if (d.max != null) { |
|
if (d.min < parseInt(d.avgLow)) { |
|
d3.select(this).select("line.yearLow") |
|
.attr("y1", yScale(parseInt(d.min))) |
|
.attr("y2", yScale(parseInt(d.avgLow))); |
|
} |
|
if (d.max > parseInt(d.avgHigh)) { |
|
d3.select(this).select("line.yearHigh") |
|
.attr("y1", yScale(parseInt(d.max))) |
|
.attr("y2", yScale(parseInt(d.avgHigh))); |
|
} |
|
if (!(d.min > parseInt(d.avgHigh) || d.max < parseInt(d.avgLow))) { |
|
d3.select(this).select("line.year") |
|
.attr("y1", yScale(Math.max(d.min,parseInt(d.avgLow)))) |
|
.attr("y2", yScale(Math.min(d.max,parseInt(d.avgHigh)))); |
|
} |
|
} |
|
}) |
|
|
|
} |
|
|
|
} |
|
|
|
function drawBars(data, type, offset) { |
|
|
|
dateScale = d3.scale.linear() |
|
.domain([1,366]) |
|
.range([0,(2 * Math.PI)]); |
|
|
|
var arc = d3.svg.arc().innerRadius(offset).outerRadius(offset + 5); |
|
|
|
d3.select("svg").append("g") |
|
.attr("class", type + "bars") |
|
.attr("transform", "translate(576,250)") |
|
.selectAll("path") |
|
.data(data) |
|
.enter() |
|
.append("path") |
|
.attr("d", drawArc) |
|
.attr("class", function (d) {return type + " " + d.category}) |
|
|
|
function drawArc(d) { |
|
projected = {startAngle: dateScale(d.startInt), endAngle: dateScale(d.endInt) }; |
|
return arc(projected); |
|
} |
|
} |
|
|
|
|
|
</script> |
|
</body> |
|
</html> |