window.AppView = class AppView extends View
constructor: (conf) ->
super(conf)
q1 = new Query
airports_db: conf.airports_db
q2 = new Query
airports_db: conf.airports_db
new WeatherPanel
query: q1
parent: this
new WeatherPanel
query: q2
parent: this
q1.set
year: 2015
country: 'Italy'
icao: 'LIRP' # Pisa
q2.set
year: 2015
country: 'Japan'
icao: 'RJTT' # Tokyo
// Generated by CoffeeScript 1.10.0
(function() {
var AppView,
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
window.AppView = AppView = (function(superClass) {
extend(AppView, superClass);
function AppView(conf) {
var q1, q2;
AppView.__super__.constructor.call(this, conf);
q1 = new Query({
airports_db: conf.airports_db
});
q2 = new Query({
airports_db: conf.airports_db
});
new WeatherPanel({
query: q1,
parent: this
});
new WeatherPanel({
query: q2,
parent: this
});
q1.set({
year: 2015,
country: 'Italy',
icao: 'LIRP'
});
q2.set({
year: 2015,
country: 'Japan',
icao: 'RJTT'
});
}
return AppView;
})(View);
}).call(this);
window.Query = observable class Query
constructor: (conf) ->
@init
events: ['change', 'change_country']
@airports_db = conf.airports_db
get_years: () -> d3.range(2000,new Date().getFullYear()+1)
get_year: () -> @selected_year
set_year: (year) ->
@selected_year = year
@trigger 'change'
get_countries: () -> @airports_db.countries.map (d) -> d.key
set_country: (name) ->
@selected_country = name
@trigger 'change_country'
# also select the first airport
@set_airport @airports_db.by_country[@selected_country][0].icao
set_airport: (icao) ->
@selected_airport = @airports_db.index[icao]
@trigger 'change'
get_all: () -> @airports_db.by_country[@selected_country]
get_airport: () -> @selected_airport
get_country: () -> @selected_country
set: (conf) ->
@selected_year = conf.year
@selected_country = conf.country
@selected_airport = @airports_db.index[conf.icao]
@trigger 'change_country'
@trigger 'change'
// Generated by CoffeeScript 1.10.0
(function() {
var Query;
window.Query = observable(Query = (function() {
function Query(conf) {
this.init({
events: ['change', 'change_country']
});
this.airports_db = conf.airports_db;
}
Query.prototype.get_years = function() {
return d3.range(2000, new Date().getFullYear() + 1);
};
Query.prototype.get_year = function() {
return this.selected_year;
};
Query.prototype.set_year = function(year) {
this.selected_year = year;
return this.trigger('change');
};
Query.prototype.get_countries = function() {
return this.airports_db.countries.map(function(d) {
return d.key;
});
};
Query.prototype.set_country = function(name) {
this.selected_country = name;
this.trigger('change_country');
return this.set_airport(this.airports_db.by_country[this.selected_country][0].icao);
};
Query.prototype.set_airport = function(icao) {
this.selected_airport = this.airports_db.index[icao];
return this.trigger('change');
};
Query.prototype.get_all = function() {
return this.airports_db.by_country[this.selected_country];
};
Query.prototype.get_airport = function() {
return this.selected_airport;
};
Query.prototype.get_country = function() {
return this.selected_country;
};
Query.prototype.set = function(conf) {
this.selected_year = conf.year;
this.selected_country = conf.country;
this.selected_airport = this.airports_db.index[conf.icao];
this.trigger('change_country');
return this.trigger('change');
};
return Query;
})());
}).call(this);
window.QueryView = observer class QueryView extends View
constructor: (conf) ->
super(conf)
@init()
@query = conf.query
# Year select
@select_year_el = @d3el.append 'select'
year_options = @select_year_el.selectAll 'option'
.data @query.get_years().sort(), (d) -> d
year_options.enter().append 'option'
.text (d) -> d
.attrs
value: (d) -> d
@select_year_el.on 'change', () =>
@query.set_year @select_year_el.node().options[@select_year_el.node().selectedIndex].value
# Country select
@select_country_el = @d3el.append 'select'
country_options = @select_country_el.selectAll 'option'
.data @query.get_countries().sort(), (d) -> d
country_options.enter().append 'option'
.text (d) -> d
.attrs
value: (d) -> d
@select_country_el.on 'change', () =>
@query.set_country @select_country_el.node().options[@select_country_el.node().selectedIndex].value
# Airport select
@select_airport_el = @d3el.append 'select'
@select_airport_el.on 'change', () =>
@query.set_airport @select_airport_el.node().options[@select_airport_el.node().selectedIndex].value
# Listeners
@listen_to @query, 'change_country', () =>
@select_country_option @query.get_country()
@redraw()
@listen_to @query, 'change', () =>
@select_year_option @query.get_year()
@select_airport_option @query.get_airport().icao
select_year_option: (year) ->
@select_year_el.select "option[value='#{year}']"
.property 'selected', true
select_country_option: (name) ->
@select_country_el.select "option[value='#{name}']"
.property 'selected', true
select_airport_option: (icao) ->
@select_airport_el.select "option[value='#{icao}']"
.property 'selected', true
redraw: () ->
airport_options = @select_airport_el.selectAll 'option'
.data @query.get_all().sort( (a,b) -> d3.ascending(a.city, b.city) ), (d) -> d.icao
airport_options.enter().append 'option'
.text (d) -> "#{d.city} (#{d.icao})"
.attrs
value: (d) -> d.icao
airport_options.exit()
.remove()
// Generated by CoffeeScript 1.10.0
(function() {
var QueryView,
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
window.QueryView = observer(QueryView = (function(superClass) {
extend(QueryView, superClass);
function QueryView(conf) {
var country_options, year_options;
QueryView.__super__.constructor.call(this, conf);
this.init();
this.query = conf.query;
this.select_year_el = this.d3el.append('select');
year_options = this.select_year_el.selectAll('option').data(this.query.get_years().sort(), function(d) {
return d;
});
year_options.enter().append('option').text(function(d) {
return d;
}).attrs({
value: function(d) {
return d;
}
});
this.select_year_el.on('change', (function(_this) {
return function() {
return _this.query.set_year(_this.select_year_el.node().options[_this.select_year_el.node().selectedIndex].value);
};
})(this));
this.select_country_el = this.d3el.append('select');
country_options = this.select_country_el.selectAll('option').data(this.query.get_countries().sort(), function(d) {
return d;
});
country_options.enter().append('option').text(function(d) {
return d;
}).attrs({
value: function(d) {
return d;
}
});
this.select_country_el.on('change', (function(_this) {
return function() {
return _this.query.set_country(_this.select_country_el.node().options[_this.select_country_el.node().selectedIndex].value);
};
})(this));
this.select_airport_el = this.d3el.append('select');
this.select_airport_el.on('change', (function(_this) {
return function() {
return _this.query.set_airport(_this.select_airport_el.node().options[_this.select_airport_el.node().selectedIndex].value);
};
})(this));
this.listen_to(this.query, 'change_country', (function(_this) {
return function() {
_this.select_country_option(_this.query.get_country());
return _this.redraw();
};
})(this));
this.listen_to(this.query, 'change', (function(_this) {
return function() {
_this.select_year_option(_this.query.get_year());
return _this.select_airport_option(_this.query.get_airport().icao);
};
})(this));
}
QueryView.prototype.select_year_option = function(year) {
return this.select_year_el.select("option[value='" + year + "']").property('selected', true);
};
QueryView.prototype.select_country_option = function(name) {
return this.select_country_el.select("option[value='" + name + "']").property('selected', true);
};
QueryView.prototype.select_airport_option = function(icao) {
return this.select_airport_el.select("option[value='" + icao + "']").property('selected', true);
};
QueryView.prototype.redraw = function() {
var airport_options;
airport_options = this.select_airport_el.selectAll('option').data(this.query.get_all().sort(function(a, b) {
return d3.ascending(a.city, b.city);
}), function(d) {
return d.icao;
});
airport_options.enter().append('option').text(function(d) {
return d.city + " (" + d.icao + ")";
}).attrs({
value: function(d) {
return d.icao;
}
});
return airport_options.exit().remove();
};
return QueryView;
})(View));
}).call(this);
window.Weather = observable class Weather
constructor: (conf) ->
@init
events: ['change']
query: (icao, year) ->
@icao = icao
@year = year
d3.csv "wu_get_history.php?station=#{icao}&year=#{year}", (days) =>
@days = days
@days.forEach (d) =>
d.t = d[@days.columns[0]]
d.date = new Date(d.t)
d.day = d.date.getDOY()
d.MinTemperatureC = +d.MinTemperatureC
d.MaxTemperatureC = +d.MaxTemperatureC
d.MeanTemperatureC = +d.MeanTemperatureC
d.Precipitationmm = +d.Precipitationmm or 0
d.CloudCover = Math.max 0, +d.CloudCover
d['MeanWindSpeedKm/h'] = +d['MeanWindSpeedKm/h']
d.WindDirDegrees = +d.WindDirDegrees
@trigger 'change'
`
// code by Joe Orost
// http://stackoverflow.com/questions/8619879/javascript-calculate-the-day-of-the-year-1-366
Date.prototype.isLeapYear = function() {
var year = this.getFullYear();
if((year & 3) != 0) return false;
return ((year % 100) != 0 || (year % 400) == 0);
};
// Get Day of Year
Date.prototype.getDOY = function() {
var dayCount = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
var mn = this.getMonth();
var dn = this.getDate();
var dayOfYear = dayCount[mn] + dn;
if(mn > 1 && this.isLeapYear()) dayOfYear++;
return dayOfYear;
};
`
// Generated by CoffeeScript 1.10.0
(function() {
var Weather;
window.Weather = observable(Weather = (function() {
function Weather(conf) {
this.init({
events: ['change']
});
}
Weather.prototype.query = function(icao, year) {
this.icao = icao;
this.year = year;
return d3.csv("wu_get_history.php?station=" + icao + "&year=" + year, (function(_this) {
return function(days) {
_this.days = days;
_this.days.forEach(function(d) {
d.t = d[_this.days.columns[0]];
d.date = new Date(d.t);
d.day = d.date.getDOY();
d.MinTemperatureC = +d.MinTemperatureC;
d.MaxTemperatureC = +d.MaxTemperatureC;
d.MeanTemperatureC = +d.MeanTemperatureC;
d.Precipitationmm = +d.Precipitationmm || 0;
d.CloudCover = Math.max(0, +d.CloudCover);
d['MeanWindSpeedKm/h'] = +d['MeanWindSpeedKm/h'];
return d.WindDirDegrees = +d.WindDirDegrees;
});
return _this.trigger('change');
};
})(this));
};
return Weather;
})());
// code by Joe Orost
// http://stackoverflow.com/questions/8619879/javascript-calculate-the-day-of-the-year-1-366
Date.prototype.isLeapYear = function() {
var year = this.getFullYear();
if((year & 3) != 0) return false;
return ((year % 100) != 0 || (year % 400) == 0);
};
// Get Day of Year
Date.prototype.getDOY = function() {
var dayCount = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
var mn = this.getMonth();
var dn = this.getDate();
var dayOfYear = dayCount[mn] + dn;
if(mn > 1 && this.isLeapYear()) dayOfYear++;
return dayOfYear;
};
;
}).call(this);
window.WeatherPanel = observer class WeatherPanel extends View
constructor: (conf) ->
super(conf)
@init()
@query = conf.query
@weather = new Weather
@listen_to @query, 'change', () =>
@weather.query @query.get_airport().icao, @query.get_year()
new QueryView
query: @query
parent: this
new WeatherWheel
weather: @weather
parent: this
// Generated by CoffeeScript 1.10.0
(function() {
var WeatherPanel,
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
window.WeatherPanel = observer(WeatherPanel = (function(superClass) {
extend(WeatherPanel, superClass);
function WeatherPanel(conf) {
WeatherPanel.__super__.constructor.call(this, conf);
this.init();
this.query = conf.query;
this.weather = new Weather;
this.listen_to(this.query, 'change', (function(_this) {
return function() {
return _this.weather.query(_this.query.get_airport().icao, _this.query.get_year());
};
})(this));
new QueryView({
query: this.query,
parent: this
});
new WeatherWheel({
weather: this.weather,
parent: this
});
}
return WeatherPanel;
})(View));
}).call(this);
window.WeatherWheel = observer class WeatherWheel extends View
constructor: (conf) ->
super(conf)
@init()
@weather = conf.weather
@listen_to @weather, 'change', () => @redraw()
scale = 230
@svg = @d3el.append 'svg'
.attrs
viewBox: "#{-scale/2} #{-scale/2} #{scale} #{scale}"
@zoomable_layer = @svg.append 'g'
zoom = d3.zoom()
.scaleExtent([-Infinity,Infinity])
.on 'zoom', () =>
@zoomable_layer
.attrs
transform: d3.event.transform
@svg.call zoom
# Fixed scales
@temp2radius = d3.scaleLinear()
.domain [-40,40]
.range [10, 70]
@temp2color = d3.scaleLinear()
.domain([-20,0,20,40])
.range([d3.hcl(290,70,15),d3.hcl(230,70,45),d3.hcl(80,70,75),d3.hcl(10,70,45)])
.interpolate(d3.interpolateHcl)
@prec2radius = d3.scaleLinear()
.domain [0,40]
.range [80, 70]
@cloud2radius = d3.scaleLinear()
.domain [0,8]
.range [80, 86]
@wind2dradius = d3.scaleLinear()
.domain [0,100]
.range [0,60]
# References
@zoomable_layer.append 'circle'
.attrs
class: 'ref_line temp_line'
r: @temp2radius(-20)
@zoomable_layer.append 'circle'
.attrs
class: 'ref_line temp_line emph'
r: @temp2radius(0)
@zoomable_layer.append 'circle'
.attrs
class: 'ref_line temp_line'
r: @temp2radius(20)
@zoomable_layer.append 'circle'
.attrs
class: 'ref_line prec_line'
r: @prec2radius(20)
@zoomable_layer.append 'circle'
.attrs
class: 'ref_line prec_line'
r: @prec2radius(40)
@zoomable_layer.append 'circle'
.attrs
class: 'ref_line wind_line'
r: @wind2dradius(20) + 87 # FIXME magic number - base for wind
@zoomable_layer.append 'path'
.attrs
class: 'ref_line year emph'
d: "M#{0} #{-scale*0.1} L #{0} #{-scale*0.5}"
redraw: () ->
EPSILON = 0.01
ANIMATION_DURATION = 1500
# check if leap year
ndays = if ((@weather.year % 4 is 0) and (@weather.year % 100 isnt 0)) or (@weather.year % 400 is 0) then 366 else 365
day2radians = d3.scaleLinear()
.domain [1, ndays+1]
.range [0, 2*Math.PI]
arc_generator = d3.arc()
# Temperature
temp_bars = @zoomable_layer.selectAll '.temp_bar'
.data @weather.days, (d) -> d.day
enter_temp_bars = temp_bars.enter().append 'path'
.attrs
class: 'temp_bar bar'
enter_temp_bars.append 'title'
all_temp_bars = enter_temp_bars.merge(temp_bars)
all_temp_bars.select 'title'
.text (d) -> "#{d3.timeFormat('%Y, %B %e')(d.date)}\nTemperature:\n Maximum: #{d.MaxTemperatureC} °C\n Mean: #{d.MeanTemperatureC} °C\n Minimum: #{d.MinTemperatureC} °C"
all_temp_bars
.transition().duration(ANIMATION_DURATION)
.attrs
d: (d) => arc_generator
startAngle: day2radians d.day
endAngle: day2radians (d.day+1)
innerRadius: @temp2radius(d.MinTemperatureC)
outerRadius: @temp2radius(d.MaxTemperatureC+EPSILON) # this is needed to avoid interpolation errors
fill: (d) => @temp2color(d.MeanTemperatureC)
temp_bars.exit()
.remove()
# Precipitation
prec_bars = @zoomable_layer.selectAll '.prec_bar'
.data @weather.days, (d) -> d.day
enter_prec_bars = prec_bars.enter().append 'path'
.attrs
class: 'prec_bar bar'
enter_prec_bars.append 'title'
all_prec_bars = enter_prec_bars.merge(prec_bars)
all_prec_bars.select 'title'
.text (d) -> "#{d3.timeFormat('%Y, %B %e')(d.date)}\nPrecipitation: #{d.Precipitationmm} mm"
all_prec_bars
.transition().duration(ANIMATION_DURATION)
.attrs
d: (d) => arc_generator
startAngle: day2radians d.day
endAngle: day2radians (d.day+1)
innerRadius: @prec2radius(d.Precipitationmm)
outerRadius: @prec2radius(-EPSILON) # this is needed to avoid interpolation errors
prec_bars.exit()
.remove()
# Cloud cover
cloud_bars = @zoomable_layer.selectAll '.cloud_bar'
.data @weather.days, (d) -> d.day
enter_cloud_bars = cloud_bars.enter().append 'path'
.attrs
class: 'cloud_bar bar'
enter_cloud_bars.append 'title'
all_cloud_bars = enter_cloud_bars.merge(cloud_bars)
all_cloud_bars.select 'title'
.text (d) -> "#{d3.timeFormat('%Y, %B %e')(d.date)}\nCloud cover: #{d.CloudCover}/8"
all_cloud_bars
.transition().duration(ANIMATION_DURATION)
.attrs
d: (d) => arc_generator
startAngle: day2radians d.day
endAngle: day2radians (d.day+1)
innerRadius: @cloud2radius(-EPSILON) # this is needed to avoid interpolation errors
outerRadius: @cloud2radius(d.CloudCover)
cloud_bars.exit()
.remove()
# Wind
wind_bars = @zoomable_layer.selectAll '.wind_bar'
.data @weather.days, (d) -> d.day
enter_wind_bars = wind_bars.enter().append 'path'
.attrs
class: 'wind_bar bar'
enter_wind_bars.append 'title'
all_wind_bars = enter_wind_bars.merge(wind_bars)
all_wind_bars.select 'title'
.text (d) -> "#{d3.timeFormat('%Y, %B %e')(d.date)}\nMean wind speed: #{d['MeanWindSpeedKm/h']} Km/h\nWind direction: #{d.WindDirDegrees} °"
all_wind_bars
.transition().duration(ANIMATION_DURATION)
.attrs
d: (d) =>
theta = day2radians((d.day+0.5)) - Math.PI/2
rho = 87
x = rho*Math.cos(theta)
y = rho*Math.sin(theta)
r = @wind2dradius(d['MeanWindSpeedKm/h'])
dx = r*Math.cos(theta)
dy = r*Math.sin(theta)
a = 2*Math.PI*(d.WindDirDegrees-90)/360
dax = 2*Math.cos(a)
day = 2*Math.sin(a)
return "M#{x+dax} #{y+day} l#{-dax} #{-day} l#{dx} #{dy}"
# stroke: (d) -> wind2color d.WindDirDegrees
stroke: 'teal'
wind_bars.exit()
.remove()
// Generated by CoffeeScript 1.10.0
(function() {
var WeatherWheel,
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
window.WeatherWheel = observer(WeatherWheel = (function(superClass) {
extend(WeatherWheel, superClass);
function WeatherWheel(conf) {
var scale, zoom;
WeatherWheel.__super__.constructor.call(this, conf);
this.init();
this.weather = conf.weather;
this.listen_to(this.weather, 'change', (function(_this) {
return function() {
return _this.redraw();
};
})(this));
scale = 230;
this.svg = this.d3el.append('svg').attrs({
viewBox: (-scale / 2) + " " + (-scale / 2) + " " + scale + " " + scale
});
this.zoomable_layer = this.svg.append('g');
zoom = d3.zoom().scaleExtent([-Infinity, Infinity]).on('zoom', (function(_this) {
return function() {
return _this.zoomable_layer.attrs({
transform: d3.event.transform
});
};
})(this));
this.svg.call(zoom);
this.temp2radius = d3.scaleLinear().domain([-40, 40]).range([10, 70]);
this.temp2color = d3.scaleLinear().domain([-20, 0, 20, 40]).range([d3.hcl(290, 70, 15), d3.hcl(230, 70, 45), d3.hcl(80, 70, 75), d3.hcl(10, 70, 45)]).interpolate(d3.interpolateHcl);
this.prec2radius = d3.scaleLinear().domain([0, 40]).range([80, 70]);
this.cloud2radius = d3.scaleLinear().domain([0, 8]).range([80, 86]);
this.wind2dradius = d3.scaleLinear().domain([0, 100]).range([0, 60]);
this.zoomable_layer.append('circle').attrs({
"class": 'ref_line temp_line',
r: this.temp2radius(-20)
});
this.zoomable_layer.append('circle').attrs({
"class": 'ref_line temp_line emph',
r: this.temp2radius(0)
});
this.zoomable_layer.append('circle').attrs({
"class": 'ref_line temp_line',
r: this.temp2radius(20)
});
this.zoomable_layer.append('circle').attrs({
"class": 'ref_line prec_line',
r: this.prec2radius(20)
});
this.zoomable_layer.append('circle').attrs({
"class": 'ref_line prec_line',
r: this.prec2radius(40)
});
this.zoomable_layer.append('circle').attrs({
"class": 'ref_line wind_line',
r: this.wind2dradius(20) + 87
});
this.zoomable_layer.append('path').attrs({
"class": 'ref_line year emph',
d: "M" + 0. + " " + (-scale * 0.1) + " L " + 0. + " " + (-scale * 0.5)
});
}
WeatherWheel.prototype.redraw = function() {
var ANIMATION_DURATION, EPSILON, all_cloud_bars, all_prec_bars, all_temp_bars, all_wind_bars, arc_generator, cloud_bars, day2radians, enter_cloud_bars, enter_prec_bars, enter_temp_bars, enter_wind_bars, ndays, prec_bars, temp_bars, wind_bars;
EPSILON = 0.01;
ANIMATION_DURATION = 1500;
ndays = ((this.weather.year % 4 === 0) && (this.weather.year % 100 !== 0)) || (this.weather.year % 400 === 0) ? 366 : 365;
day2radians = d3.scaleLinear().domain([1, ndays + 1]).range([0, 2 * Math.PI]);
arc_generator = d3.arc();
temp_bars = this.zoomable_layer.selectAll('.temp_bar').data(this.weather.days, function(d) {
return d.day;
});
enter_temp_bars = temp_bars.enter().append('path').attrs({
"class": 'temp_bar bar'
});
enter_temp_bars.append('title');
all_temp_bars = enter_temp_bars.merge(temp_bars);
all_temp_bars.select('title').text(function(d) {
return (d3.timeFormat('%Y, %B %e')(d.date)) + "\nTemperature:\n Maximum: " + d.MaxTemperatureC + " °C\n Mean: " + d.MeanTemperatureC + " °C\n Minimum: " + d.MinTemperatureC + " °C";
});
all_temp_bars.transition().duration(ANIMATION_DURATION).attrs({
d: (function(_this) {
return function(d) {
return arc_generator({
startAngle: day2radians(d.day),
endAngle: day2radians(d.day + 1),
innerRadius: _this.temp2radius(d.MinTemperatureC),
outerRadius: _this.temp2radius(d.MaxTemperatureC + EPSILON)
});
};
})(this),
fill: (function(_this) {
return function(d) {
return _this.temp2color(d.MeanTemperatureC);
};
})(this)
});
temp_bars.exit().remove();
prec_bars = this.zoomable_layer.selectAll('.prec_bar').data(this.weather.days, function(d) {
return d.day;
});
enter_prec_bars = prec_bars.enter().append('path').attrs({
"class": 'prec_bar bar'
});
enter_prec_bars.append('title');
all_prec_bars = enter_prec_bars.merge(prec_bars);
all_prec_bars.select('title').text(function(d) {
return (d3.timeFormat('%Y, %B %e')(d.date)) + "\nPrecipitation: " + d.Precipitationmm + " mm";
});
all_prec_bars.transition().duration(ANIMATION_DURATION).attrs({
d: (function(_this) {
return function(d) {
return arc_generator({
startAngle: day2radians(d.day),
endAngle: day2radians(d.day + 1),
innerRadius: _this.prec2radius(d.Precipitationmm),
outerRadius: _this.prec2radius(-EPSILON)
});
};
})(this)
});
prec_bars.exit().remove();
cloud_bars = this.zoomable_layer.selectAll('.cloud_bar').data(this.weather.days, function(d) {
return d.day;
});
enter_cloud_bars = cloud_bars.enter().append('path').attrs({
"class": 'cloud_bar bar'
});
enter_cloud_bars.append('title');
all_cloud_bars = enter_cloud_bars.merge(cloud_bars);
all_cloud_bars.select('title').text(function(d) {
return (d3.timeFormat('%Y, %B %e')(d.date)) + "\nCloud cover: " + d.CloudCover + "/8";
});
all_cloud_bars.transition().duration(ANIMATION_DURATION).attrs({
d: (function(_this) {
return function(d) {
return arc_generator({
startAngle: day2radians(d.day),
endAngle: day2radians(d.day + 1),
innerRadius: _this.cloud2radius(-EPSILON),
outerRadius: _this.cloud2radius(d.CloudCover)
});
};
})(this)
});
cloud_bars.exit().remove();
wind_bars = this.zoomable_layer.selectAll('.wind_bar').data(this.weather.days, function(d) {
return d.day;
});
enter_wind_bars = wind_bars.enter().append('path').attrs({
"class": 'wind_bar bar'
});
enter_wind_bars.append('title');
all_wind_bars = enter_wind_bars.merge(wind_bars);
all_wind_bars.select('title').text(function(d) {
return (d3.timeFormat('%Y, %B %e')(d.date)) + "\nMean wind speed: " + d['MeanWindSpeedKm/h'] + " Km/h\nWind direction: " + d.WindDirDegrees + " °";
});
all_wind_bars.transition().duration(ANIMATION_DURATION).attrs({
d: (function(_this) {
return function(d) {
var a, dax, day, dx, dy, r, rho, theta, x, y;
theta = day2radians(d.day + 0.5) - Math.PI / 2;
rho = 87;
x = rho * Math.cos(theta);
y = rho * Math.sin(theta);
r = _this.wind2dradius(d['MeanWindSpeedKm/h']);
dx = r * Math.cos(theta);
dy = r * Math.sin(theta);
a = 2 * Math.PI * (d.WindDirDegrees - 90) / 360;
dax = 2 * Math.cos(a);
day = 2 * Math.sin(a);
return "M" + (x + dax) + " " + (y + day) + " l" + (-dax) + " " + (-day) + " l" + dx + " " + dy;
};
})(this),
stroke: 'teal'
});
return wind_bars.exit().remove();
};
return WeatherWheel;
})(View));
}).call(this);
File not shown (100K+ bytes).
// Generated by CoffeeScript 1.10.0
(function() {
var View, setup_init,
slice = [].slice;
setup_init = function(c, init) {
if (c.prototype.inits == null) {
c.prototype.inits = [];
}
c.prototype.inits.push(init);
return c.prototype.init = function(conf) {
var i, len, m, ref, results;
ref = this.inits;
results = [];
for (i = 0, len = ref.length; i < len; i++) {
m = ref[i];
results.push(m.call(this, conf));
}
return results;
};
};
window.observable = function(c) {
setup_init(c, function(config) {
this._dispatcher = d3.dispatch.apply(d3, config.events);
return this._next_id = 0;
});
c.prototype.on = function(event_type_ns, callback) {
var event_type, event_type_full, namespace, splitted_event_type_ns;
splitted_event_type_ns = event_type_ns.split('.');
event_type = splitted_event_type_ns[0];
if (splitted_event_type_ns.length > 1) {
namespace = splitted_event_type_ns[1];
} else {
namespace = this._next_id;
this._next_id += 1;
}
event_type_full = event_type + '.' + namespace;
this._dispatcher.on(event_type_full, callback);
return event_type_full;
};
c.prototype.trigger = function() {
var args, event_type;
event_type = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
this._dispatcher.apply(event_type, this, args);
return this;
};
return c;
};
window.observer = function(c) {
setup_init(c, function() {
return this._bindings = [];
});
c.prototype.listen_to = function(observed, event, cb) {
return this._bindings.push({
observed: observed,
event_type: observed.on(event, cb)
});
};
c.prototype.stop_listening = function() {
return this._bindings.forEach((function(_this) {
return function(l) {
return l.observed.on(l.event_type, null);
};
})(this));
};
return c;
};
window.View = View = (function() {
function View(conf) {
if (conf.tag == null) {
conf.tag = 'div';
}
this.el = document.createElement(conf.tag);
this.d3el = d3.select(this.el);
this.d3el.classed(this.constructor.name, true);
if (conf.parent != null) {
this.append_to(conf.parent, conf.prepend);
}
}
View.prototype.append_to = function(parent, prepend) {
var p_el;
if (parent.el != null) {
p_el = parent.el;
} else {
if (parent.node != null) {
p_el = parent.node();
} else {
p_el = d3.select(parent).node();
}
}
if (prepend) {
return p_el.insertBefore(this.el, p_el.firstChild);
} else {
return p_el.appendChild(this.el);
}
};
View.prototype.compute_size = function() {
this.width = this.el.getBoundingClientRect().width;
return this.height = this.el.getBoundingClientRect().height;
};
return View;
})();
}).call(this);
d3.csv 'airports.csv', (airports) ->
# build the airports database in memory
airports_db = {}
airports_db.list = airports
# ICAO-only airports
.filter (d) -> d.icao isnt '\\N' and d.icao isnt ''
airports_db.index = {}
airports_db.list.forEach (d) ->
airports_db.index[d.icao] = d
airports_db.countries = d3.nest()
.key (d) -> d.country
.entries airports_db.list
airports_db.by_country = {}
airports_db.countries.forEach (d) ->
airports_db.by_country[d.key] = d.values
new AppView
airports_db: airports_db
parent: 'body'
body, html {
padding: 0;
margin: 0;
width: 100%;
height: 100%;
}
.AppView {
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
}
.AppView > * {
width: 0;
flex-grow: 1;
}
.AppView > *:not(:last-child) {
border-right: 1px solid #DDD;
}
.WeatherPanel {
display: flex;
flex-direction: column;
}
.WeatherWheel {
height: 0;
flex-grow: 1;
}
.WeatherWheel svg {
width: 100%;
height: 100%;
}
.WeatherWheel .bar {
opacity: 0.8;
}
.WeatherWheel .bar:hover {
opacity: 1;
}
.WeatherWheel .temp_bar {
stroke-width: 0.1;
stroke: white;
}
.WeatherWheel .prec_bar {
fill: steelblue;
}
.WeatherWheel .cloud_bar {
fill: #AAA;
}
.WeatherWheel .wind_bar {
stroke-width: 0.4;
opacity: 0.4;
fill: none;
}
.WeatherWheel .ref_line {
fill: none;
stroke-width: 0.3;
stroke: #555;
vector-effect: non-scaling-stroke;
opacity: 0.5;
}
.WeatherWheel .ref_line.emph {
stroke-width: 0.6;
}
.WeatherWheel .prec_line {
stroke: steelblue;
}
.WeatherWheel .wind_line {
stroke: teal;
stroke-dasharray: 3 3;
}
.QueryView {
padding: 4px;
width: 100%;
box-sizing: border-box;
}
.QueryView select {
padding: 4px;
width: 100%;
font-size: 16px;
}
.QueryView select:first-child {
margin-bottom: 4px;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Weather wheel III</title>
<link type="text/css" href="index.css" rel="stylesheet"/>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-selection-multi.v0.4.min.js"></script>
<script src="eye.js"></script>
<script src="Query.js"></script>
<script src="Weather.js"></script>
<script src="AppView.js"></script>
<script src="WeatherPanel.js"></script>
<script src="QueryView.js"></script>
<script src="WeatherWheel.js"></script>
</head>
<body>
<script src="index.js"></script>
</body>
</html>
// Generated by CoffeeScript 1.10.0
(function() {
d3.csv('airports.csv', function(airports) {
var airports_db;
airports_db = {};
airports_db.list = airports.filter(function(d) {
return d.icao !== '\\N' && d.icao !== '';
});
airports_db.index = {};
airports_db.list.forEach(function(d) {
return airports_db.index[d.icao] = d;
});
airports_db.countries = d3.nest().key(function(d) {
return d.country;
}).entries(airports_db.list);
airports_db.by_country = {};
airports_db.countries.forEach(function(d) {
return airports_db.by_country[d.key] = d.values;
});
return new AppView({
airports_db: airports_db,
parent: 'body'
});
});
}).call(this);
File not shown (binary encoding).
<?php
$airport = $_GET['station'];
$year = $_GET['year'];
$url = "http://www.wunderground.com/history/airport/$airport/$year/1/1/CustomHistory.html?dayend=31&monthend=12&yearend=$year&req_city=&req_state=&req_statename=&reqdb.zip=&reqdb.magic=&reqdb.wmo=&MR=1&format=1";
$html = file_get_contents($url);
$csv = preg_replace('/^\n/', '', $html);
$lines = explode('<br />', $csv);
$lines[0] = str_replace(' ', '', $lines[0]);
$csv = implode('', $lines);
header('Content-Type: application/csv');
echo $csv;
?>