/* globals utils */
/* globals utils */
Run after the DOM loads
$(function () {
'use strict';
var map, layer, feature, ui, save, infoData = null, canvas;
Get query parameters scale: false – turn off category scale and colorng width: speed – width is proportional to speed opacity: false – don’t make lower categories somewhat transparent smooth: true – round corners of line angles hover: false – don’t show specific hurricane on hover x, y, zoom
var query = utils.getQuery();
var cscale = d3.scaleOrdinal()
.range([
'#7f7f7f',
'#d62728',
'#ff7f0e',
'#bcbd22',
'#9467bd',
'#17becf'
])
.domain([
0,
5,
4,
3,
2,
1
]);
var start = new Date('1980-01-01');
var drange = (new Date('2015-01-01') - start);
function any(a, f) {
var v = false;
a.forEach(function (d) {
v = v || f(d);
});
return v;
}
function all(a, f) {
var v = true;
a.forEach(function (d) {
v = v && f(d);
});
return v;
}
function category(d) {
var p = d;
if (p <= 0) {
return -1; // invalid data
}
if (p < 920) {
return 5;
}
if (p <= 944) {
return 4;
}
if (p <= 964) {
return 3;
}
if (p <= 979) {
return 2;
}
if (p >= 980 && p <= 994) {
return 1;
}
return 0;
}
function makeInfoBox(data) {
if (data) {
infoData = data;
} else {
data = infoData;
}
if (!data) {
return;
}
canvas = d3.select(ui.canvas()).select('svg.dynamic-content')
.attr('width', $(window).width())
.attr('height', $(window).height());
canvas.selectAll('.app-info-box').remove();
var width = 300, height = 600;
var mapWidth = map.node().width();
var mapHeight = map.node().height();
var group = canvas.append('g').attr('class', 'app-info-box');
group.attr(
'transform',
'translate(' + [mapWidth - width - 35, mapHeight - height + 10] + ')'
);
var name = data.name.toLowerCase();
var year = new Date(data.time[0]).getFullYear();
name = name[0].toUpperCase() + name.slice(1);
group.append('rect')
.attr('class', 'app-background')
.attr('rx', 5)
.attr('ry', 5)
.attr('y', '-3.3em')
.attr('width', width + 20)
.attr('height', '2em');
group.append('text')
.attr('x', width / 2)
.attr('y', '-2em')
.attr('text-anchor', 'middle')
.text('Hurricane ' + name + ' of ' + year)
.style('font', '20px');
function makePlot(d) {
var group = d3.select(this);
var t, f, i;
if (d === 0) {
i = 'wind';
t = 'Wind speed';
f = function (d, j) { return data[i][j]; };
} else if (d === 1) {
i = 'pressure';
t = 'Surface pressure';
f = function (d, j) { return data[i][j]; };
} else if (d === 2) {
i = 'pressure';
t = 'Category';
f = function (d, j) { return category(data[i][j]); };
}
group.attr('transform', 'translate(0,' + (d * height / 3) + ')');
group.append('rect')
.attr('class', 'app-background')
.attr('rx', 5)
.attr('ry', 5)
.attr('width', width + 20)
.attr('height', (height - 70) / 3);
data.time = data.time.map(function (t) { return new Date(t.valueOf()); });
var x = d3.scaleTime().nice(4)
.domain(d3.extent(data.time))
.range([50, width]);
var y = d3.scaleLinear().nice(5)
.domain(d3.extent(data.time, f))
.range([height / 3 - 50, 36]);
var xAxis = d3.axisBottom(x).ticks(4);
var yAxis = d3.axisLeft(y).ticks(5);
group.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + y.range()[0] + ')')
.call(xAxis);
group.append('g')
.attr('class', 'y axis')
.attr('transform', 'translate(' + x.range()[0] + ')')
.call(yAxis);
var line = d3.line()
.x(function (d) { return x(d); })
.y(function (d, j) { return y(f(d, j)); });
group.append('path')
.attr('class', 'plot-line')
.attr('d', line(data.time));
group.append('text')
.attr('x', width / 2 + 10)
.attr('y', '1em')
.attr('text-anchor', 'middle')
.text(t);
}
group.selectAll('.app-plot').data(d3.range(3)).enter()
.append('g')
.attr('class', 'app-plot')
.each(makePlot);
return data;
}
function makeHistogram(data) {
var margin = {top: 45, right: 10, left: 10, bottom: 30};
var mapHeight = map.node().height() - 15;
var width = 300;
var height = 100;
canvas = d3.select(ui.canvas()).select('svg.dynamic-content')
.attr('width', $(window).width())
.attr('height', $(window).height());
canvas.selectAll('.app-histogram').remove();
var group = canvas.append('g').attr('class', 'app-histogram');
var x = d3.scaleLinear()
.domain([-0.5, 5.5])
.range([15 + margin.left, 15 + margin.left + width]);
var cats = [];
data.forEach(function (d) {
d.pressure.forEach(function (p) {
cats.push(category(p));
});
});
var hist = d3.bin().domain(d3.extent(cats)).thresholds(d3.range(7))(cats);
var y = d3.scaleLinear()
.domain([0, d3.max(hist, function (d) { return d.length; })])
.range([mapHeight - margin.bottom, mapHeight - height - margin.bottom]);
var xAxis = d3.axisBottom(x)
.tickValues(d3.range(6))
.tickFormat(d3.format('.0f'));
group.append('rect')
.attr('class', 'app-background')
.attr('rx', 5)
.attr('ry', 5)
.attr('x', x.range()[0] - margin.left)
.attr('y', y.range()[1] - margin.top)
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.bottom + margin.top);
var bar = group.selectAll('.bar')
.data(hist)
.enter().append('g')
.attr('class', 'bar')
.attr('transform', function (d) {
return 'translate(' + [x(d.x0 - 0.5), 0] + ')';
});
bar.append('rect')
.attr('x', 1)
.attr('y', function (d) { return y(d.length); })
.attr('width', x(1) - x(0) - 2)
.attr('height', function (d) {
return y(0) - y(d.length);
})
.style('fill', function (d) {
return cscale(d.x0);
});
bar.append('text')
.attr('dy', '0.75em')
.attr('y', function (d) {
return y(d.length) - 18;
})
.attr('x', (x(1) - x(0)) / 2)
.attr('text-anchor', 'middle')
.text(function (d) {
return d.length;
});
group.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(' + [0, y(0)] + ')')
.style('font-size', 'unset')
.call(xAxis);
group.append('text')
.attr('x', x(2.5))
.attr('y', y.range()[1] - margin.top)
.attr('dy', '1em')
.attr('text-anchor', 'middle')
.text('All observations');
}
function draw(data) {
if (data) {
save = data;
} else {
data = save;
}
data = data.filter(function (d) {
if (any(d.pressure, function (d) { return d <= 0; })) {
return false;
}
if (all(d.pressure, function (d) { return category(d) === 0; })) {
return false;
}
return d.basin === 'North Atlantic';
});
makeHistogram(data);
feature.data(data)
.line(function (d) {
return d3.range(d.time.length).map(function (i) {
return {
x: d.longitude[i],
y: d.latitude[i],
t: new Date(d.time[i]),
w: d.wind[i],
p: d.pressure[i],
d: d.dist2land[i],
c: category(d.pressure[i])
};
});
})
.position(function (d) {
return d;
})
.style({
strokeColor: function (d) {
if (query.scale === 'false') {
return cscale(0);
}
return cscale(d.c);
},
strokeWidth: function (d, i, l, pos) {
var width = 1.5;
if (query.width === 'speed') {
width = Math.max(1.5, d.w / 20);
}
if (data[pos].hover) {
width += 3.5;
}
return width;
},
strokeOpacity: function (d, i, l, pos) {
if (data[pos].hover) {
return 1;
}
if (query.opacity === 'false') {
return 0.50;
}
if (d.c === 0) {
return 0.25;
}
return 0.25 + 0.5 * (d.t - start) / drange;
},
lineCap: query.smooth === 'true' ? 'round' : undefined,
lineJoin: query.smooth === 'true' ? 'round' : undefined
})
.geoOff(geo.event.feature.mouseover)
.geoOff(geo.event.feature.mouseout)
.geoOn(geo.event.feature.mouseover, function (evt) {
if (query.hover === 'false') {
return;
}
if (!evt.top) { return; }
data.forEach(function (d) {
d.hover = false;
});
evt.data.hover = true;
makeInfoBox(evt.data);
this.modified();
feature.draw();
});
feature.draw();
}
Create a map object
map = geo.map({
node: '#map',
center: {
x: query.x ? +query.x : -50,
y: query.y ? +query.y : 30
},
zoom: query.zoom ? +query.zoom : 4
});
Add the default osm layer
map.createLayer('osm');
Create a feature layer to draw on.
layer = map.createLayer('feature', {features: [geo.lineFeature.capabilities.multicolor]});
Create a line feature
feature = layer.createFeature('line', {selectionAPI: true});
Create a legend
ui = map.createLayer('ui');
ui = map.createLayer('ui');
var legend = [];
if (query.scale !== 'false') {
legend = legend.concat([{
name: 'Category 5',
style: {
strokeColor: cscale(5),
strokeWidth: 3
},
type: 'line'
}, {
name: 'Category 4',
style: {
strokeColor: cscale(4),
strokeWidth: 3
},
type: 'line'
}, {
name: 'Category 3',
style: {
strokeColor: cscale(3),
strokeWidth: 3
},
type: 'line'
}, {
name: 'Category 2',
style: {
strokeColor: cscale(2),
strokeWidth: 3
},
type: 'line'
}, {
name: 'Category 1',
style: {
strokeColor: cscale(1),
strokeWidth: 3
},
type: 'line'
}, {
name: 'Other',
style: {
strokeColor: cscale(0),
strokeWidth: 3
},
type: 'line'
}]);
}
if (query.width === 'speed') {
legend = legend.concat([{
name: '<30 knots',
style: {
strokeColor: 'black',
strokeWidth: 1.5
},
type: 'line'
}, {
name: '60 knots',
style: {
strokeColor: 'black',
strokeWidth: 3
},
type: 'line'
}, {
name: '110 knots',
style: {
strokeColor: 'black',
strokeWidth: 5.5
},
type: 'line'
}, {
name: '160 knots',
style: {
strokeColor: 'black',
strokeWidth: 8
},
type: 'line'
}]);
}
if (legend.length) {
ui.createWidget('legend', {position: {right: 20, top: 10}})
.categories(legend);
}
canvas = d3.select(ui.canvas()).append('svg')
.attr('class', 'dynamic-content');
Load the data
$.ajax({
url: '../../data/hurricanes.json',
success: draw
});
$(window).resize(function () {
draw();
makeInfoBox();
});
});