Skip to content

Instantly share code, notes, and snippets.

@jeremycflin
Last active October 12, 2015 22:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jeremycflin/c69c202db2ace12ad0e1 to your computer and use it in GitHub Desktop.
Save jeremycflin/c69c202db2ace12ad0e1 to your computer and use it in GitHub Desktop.
ONA 15 Attendees

A data driven news package I pitched and built for the 2015 Online News Association Student Newsroom.

Built with blockbuilder.org

'use strict';
var D2R = Math.PI / 180;
var R2D = 180 / Math.PI;
var Coord = function(lon,lat) {
this.lon = lon;
this.lat = lat;
this.x = D2R * lon;
this.y = D2R * lat;
};
Coord.prototype.view = function() {
return String(this.lon).slice(0, 4) + ',' + String(this.lat).slice(0, 4);
};
Coord.prototype.antipode = function() {
var anti_lat = -1 * this.lat;
var anti_lon = (this.lon < 0) ? 180 + this.lon : (180 - this.lon) * -1;
return new Coord(anti_lon, anti_lat);
};
var LineString = function() {
this.coords = [];
this.length = 0;
};
LineString.prototype.move_to = function(coord) {
this.length++;
this.coords.push(coord);
};
var Arc = function(properties) {
this.properties = properties || {};
this.geometries = [];
};
Arc.prototype.json = function() {
if (this.geometries.length <= 0) {
return {'geometry': { 'type': 'LineString', 'coordinates': null },
'type': 'Feature', 'properties': this.properties
};
} else if (this.geometries.length == 1) {
return {'geometry': { 'type': 'LineString', 'coordinates': this.geometries[0].coords },
'type': 'Feature', 'properties': this.properties
};
} else {
var multiline = [];
for (var i = 0; i < this.geometries.length; i++) {
multiline.push(this.geometries[i].coords);
}
return {'geometry': { 'type': 'MultiLineString', 'coordinates': multiline },
'type': 'Feature', 'properties': this.properties
};
}
};
// TODO - output proper multilinestring
Arc.prototype.wkt = function() {
var wkt_string = '';
var wkt = 'LINESTRING(';
var collect = function(c) { wkt += c[0] + ' ' + c[1] + ','; };
for (var i = 0; i < this.geometries.length; i++) {
if (this.geometries[i].coords.length === 0) {
return 'LINESTRING(empty)';
} else {
var coords = this.geometries[i].coords;
coords.forEach(collect);
wkt_string += wkt.substring(0, wkt.length - 1) + ')';
}
}
return wkt_string;
};
/*
* http://en.wikipedia.org/wiki/Great-circle_distance
*
*/
var GreatCircle = function(start,end,properties) {
if (!start || start.x === undefined || start.y === undefined) {
throw new Error("GreatCircle constructor expects two args: start and end objects with x and y properties");
}
if (!end || end.x === undefined || end.y === undefined) {
throw new Error("GreatCircle constructor expects two args: start and end objects with x and y properties");
}
this.start = new Coord(start.x,start.y);
this.end = new Coord(end.x,end.y);
this.properties = properties || {};
var w = this.start.x - this.end.x;
var h = this.start.y - this.end.y;
var z = Math.pow(Math.sin(h / 2.0), 2) +
Math.cos(this.start.y) *
Math.cos(this.end.y) *
Math.pow(Math.sin(w / 2.0), 2);
this.g = 2.0 * Math.asin(Math.sqrt(z));
if (this.g == Math.PI) {
throw new Error('it appears ' + start.view() + ' and ' + end.view() + " are 'antipodal', e.g diametrically opposite, thus there is no single route but rather infinite");
} else if (isNaN(this.g)) {
throw new Error('could not calculate great circle between ' + start + ' and ' + end);
}
};
/*
* http://williams.best.vwh.net/avform.htm#Intermediate
*/
GreatCircle.prototype.interpolate = function(f) {
var A = Math.sin((1 - f) * this.g) / Math.sin(this.g);
var B = Math.sin(f * this.g) / Math.sin(this.g);
var x = A * Math.cos(this.start.y) * Math.cos(this.start.x) + B * Math.cos(this.end.y) * Math.cos(this.end.x);
var y = A * Math.cos(this.start.y) * Math.sin(this.start.x) + B * Math.cos(this.end.y) * Math.sin(this.end.x);
var z = A * Math.sin(this.start.y) + B * Math.sin(this.end.y);
var lat = R2D * Math.atan2(z, Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)));
var lon = R2D * Math.atan2(y, x);
return [lon, lat];
};
/*
* Generate points along the great circle
*/
GreatCircle.prototype.Arc = function(npoints,options) {
var first_pass = [];
if (!npoints || npoints <= 2) {
first_pass.push([this.start.lon, this.start.lat]);
first_pass.push([this.end.lon, this.end.lat]);
} else {
var delta = 1.0 / (npoints - 1);
for (var i = 0; i < npoints; ++i) {
var step = delta * i;
var pair = this.interpolate(step);
first_pass.push(pair);
}
}
/* partial port of dateline handling from:
gdal/ogr/ogrgeometryfactory.cpp
TODO - does not handle all wrapping scenarios yet
*/
var bHasBigDiff = false;
var dfMaxSmallDiffLong = 0;
// from http://www.gdal.org/ogr2ogr.html
// -datelineoffset:
// (starting with GDAL 1.10) offset from dateline in degrees (default long. = +/- 10deg, geometries within 170deg to -170deg will be splited)
var dfDateLineOffset = options && options.offset ? options.offset : 10;
var dfLeftBorderX = 180 - dfDateLineOffset;
var dfRightBorderX = -180 + dfDateLineOffset;
var dfDiffSpace = 360 - dfDateLineOffset;
// https://github.com/OSGeo/gdal/blob/7bfb9c452a59aac958bff0c8386b891edf8154ca/gdal/ogr/ogrgeometryfactory.cpp#L2342
for (var j = 1; j < first_pass.length; ++j) {
var dfPrevX = first_pass[j-1][0];
var dfX = first_pass[j][0];
var dfDiffLong = Math.abs(dfX - dfPrevX);
if (dfDiffLong > dfDiffSpace &&
((dfX > dfLeftBorderX && dfPrevX < dfRightBorderX) || (dfPrevX > dfLeftBorderX && dfX < dfRightBorderX))) {
bHasBigDiff = true;
} else if (dfDiffLong > dfMaxSmallDiffLong) {
dfMaxSmallDiffLong = dfDiffLong;
}
}
var poMulti = [];
if (bHasBigDiff && dfMaxSmallDiffLong < dfDateLineOffset) {
var poNewLS = [];
poMulti.push(poNewLS);
for (var k = 0; k < first_pass.length; ++k) {
var dfX0 = parseFloat(first_pass[k][0]);
if (k > 0 && Math.abs(dfX0 - first_pass[k-1][0]) > dfDiffSpace) {
var dfX1 = parseFloat(first_pass[k-1][0]);
var dfY1 = parseFloat(first_pass[k-1][1]);
var dfX2 = parseFloat(first_pass[k][0]);
var dfY2 = parseFloat(first_pass[k][1]);
if (dfX1 > -180 && dfX1 < dfRightBorderX && dfX2 == 180 &&
k+1 < first_pass.length &&
first_pass[k-1][0] > -180 && first_pass[k-1][0] < dfRightBorderX)
{
poNewLS.push([-180, first_pass[k][1]]);
k++;
poNewLS.push([first_pass[k][0], first_pass[k][1]]);
continue;
} else if (dfX1 > dfLeftBorderX && dfX1 < 180 && dfX2 == -180 &&
k+1 < first_pass.length &&
first_pass[k-1][0] > dfLeftBorderX && first_pass[k-1][0] < 180)
{
poNewLS.push([180, first_pass[k][1]]);
k++;
poNewLS.push([first_pass[k][0], first_pass[k][1]]);
continue;
}
if (dfX1 < dfRightBorderX && dfX2 > dfLeftBorderX)
{
// swap dfX1, dfX2
var tmpX = dfX1;
dfX1 = dfX2;
dfX2 = tmpX;
// swap dfY1, dfY2
var tmpY = dfY1;
dfY1 = dfY2;
dfY2 = tmpY;
}
if (dfX1 > dfLeftBorderX && dfX2 < dfRightBorderX) {
dfX2 += 360;
}
if (dfX1 <= 180 && dfX2 >= 180 && dfX1 < dfX2)
{
var dfRatio = (180 - dfX1) / (dfX2 - dfX1);
var dfY = dfRatio * dfY2 + (1 - dfRatio) * dfY1;
poNewLS.push([first_pass[k-1][0] > dfLeftBorderX ? 180 : -180, dfY]);
poNewLS = [];
poNewLS.push([first_pass[k-1][0] > dfLeftBorderX ? -180 : 180, dfY]);
poMulti.push(poNewLS);
}
else
{
poNewLS = [];
poMulti.push(poNewLS);
}
poNewLS.push([dfX0, first_pass[k][1]]);
} else {
poNewLS.push([first_pass[k][0], first_pass[k][1]]);
}
}
} else {
// add normally
var poNewLS0 = [];
poMulti.push(poNewLS0);
for (var l = 0; l < first_pass.length; ++l) {
poNewLS0.push([first_pass[l][0],first_pass[l][1]]);
}
}
var arc = new Arc(this.properties);
for (var m = 0; m < poMulti.length; ++m) {
var line = new LineString();
arc.geometries.push(line);
var points = poMulti[m];
for (var j0 = 0; j0 < points.length; ++j0) {
line.move_to(points[j0]);
}
}
return arc;
};
if (typeof window === 'undefined') {
// nodejs
module.exports.Coord = Coord;
module.exports.Arc = Arc;
module.exports.GreatCircle = GreatCircle;
} else {
// browser
var arc = {};
arc.Coord = Coord;
arc.Arc = Arc;
arc.GreatCircle = GreatCircle;
}
// Copyright (c) 2013, Jason Davies, http://www.jasondavies.com
// See LICENSE.txt for details.
(function() {
var radians = Math.PI / 180,
degrees = 180 / Math.PI;
// TODO make incremental rotate optional
d3.geo.zoom = function() {
var projection,
zoomPoint,
event = d3.dispatch("zoomstart", "zoom", "zoomend"),
zoom = d3.behavior.zoom()
.on("zoomstart", function() {
var mouse0 = d3.mouse(this),
rotate = quaternionFromEuler(projection.rotate()),
point = position(projection, mouse0);
if (point) zoomPoint = point;
zoomOn.call(zoom, "zoom", function() {
projection.scale(d3.event.scale);
var mouse1 = d3.mouse(this),
between = rotateBetween(zoomPoint, position(projection, mouse1));
projection.rotate(eulerFromQuaternion(rotate = between
? multiply(rotate, between)
: multiply(bank(projection, mouse0, mouse1), rotate)));
mouse0 = mouse1;
event.zoom.apply(this, arguments);
});
event.zoomstart.apply(this, arguments);
})
.on("zoomend", function() {
zoomOn.call(zoom, "zoom", null);
event.zoomend.apply(this, arguments);
}),
zoomOn = zoom.on;
zoom.projection = function(_) {
return arguments.length ? zoom.scale((projection = _).scale()) : projection;
};
return d3.rebind(zoom, event, "on");
};
function bank(projection, p0, p1) {
var t = projection.translate(),
angle = Math.atan2(p0[1] - t[1], p0[0] - t[0]) - Math.atan2(p1[1] - t[1], p1[0] - t[0]);
return [Math.cos(angle / 2), 0, 0, Math.sin(angle / 2)];
}
function position(projection, point) {
var t = projection.translate(),
spherical = projection.invert(point);
return spherical && isFinite(spherical[0]) && isFinite(spherical[1]) && cartesian(spherical);
}
function quaternionFromEuler(euler) {
var λ = .5 * euler[0] * radians,
φ = .5 * euler[1] * radians,
γ = .5 * euler[2] * radians,
sinλ = Math.sin(λ), cosλ = Math.cos(λ),
sinφ = Math.sin(φ), cosφ = Math.cos(φ),
sinγ = Math.sin(γ), cosγ = Math.cos(γ);
return [
cosλ * cosφ * cosγ + sinλ * sinφ * sinγ,
sinλ * cosφ * cosγ - cosλ * sinφ * sinγ,
cosλ * sinφ * cosγ + sinλ * cosφ * sinγ,
cosλ * cosφ * sinγ - sinλ * sinφ * cosγ
];
}
function multiply(a, b) {
var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3],
b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3];
return [
a0 * b0 - a1 * b1 - a2 * b2 - a3 * b3,
a0 * b1 + a1 * b0 + a2 * b3 - a3 * b2,
a0 * b2 - a1 * b3 + a2 * b0 + a3 * b1,
a0 * b3 + a1 * b2 - a2 * b1 + a3 * b0
];
}
function rotateBetween(a, b) {
if (!a || !b) return;
var axis = cross(a, b),
norm = Math.sqrt(dot(axis, axis)),
halfγ = .5 * Math.acos(Math.max(-1, Math.min(1, dot(a, b)))),
k = Math.sin(halfγ) / norm;
return norm && [Math.cos(halfγ), axis[2] * k, -axis[1] * k, axis[0] * k];
}
function eulerFromQuaternion(q) {
return [
Math.atan2(2 * (q[0] * q[1] + q[2] * q[3]), 1 - 2 * (q[1] * q[1] + q[2] * q[2])) * degrees,
Math.asin(Math.max(-1, Math.min(1, 2 * (q[0] * q[2] - q[3] * q[1])))) * degrees,
Math.atan2(2 * (q[0] * q[3] + q[1] * q[2]), 1 - 2 * (q[2] * q[2] + q[3] * q[3])) * degrees
];
}
function cartesian(spherical) {
var λ = spherical[0] * radians,
φ = spherical[1] * radians,
cosφ = Math.cos(φ);
return [
cosφ * Math.cos(λ),
cosφ * Math.sin(λ),
Math.sin(φ)
];
}
function dot(a, b) {
for (var i = 0, n = a.length, s = 0; i < n; ++i) s += a[i] * b[i];
return s;
}
function cross(a, b) {
return [
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0]
];
}
})();
lat_long count
"-0.180653,-78.467838" 1
"-12.046374,-77.042793" 1
"-15.794229,-47.882166" 1
"-22.906847,-43.172896" 1
"-23.55052,-46.633309" 6
"-27.468918,153.003239" 2
"-33.867487,151.20699" 3
"-34.603684,-58.381559" 5
"-36.84846,174.763332" 2
"-41.28646,174.776236" 2
"1.331355,103.869278" 1
"1.352083,103.819836" 2
"10.643873,-61.36477" 1
"17.385044,78.486671" 1
"19.075984,72.877656" 2
"19.432608,-99.133208" 2
"21.306944,-157.858333" 8
"22.396428,114.109497" 1
"25.29161,51.530437" 1
"25.657345,-100.40175" 1
"25.72149,-80.268384" 2
"25.76168,-80.19179" 14
"25.819542,-80.35533" 3
"25.987869,-80.174769" 1
"26.122439,-80.137317" 1
"26.479379,-81.780183' 1
"26.640628,-81.872308" 2
"26.715342,-80.053375" 5
"26.823395,-80.138655" 2
"27.717245,85.323961" 2
"27.749516,-82.583495" 1
"27.751828,-82.626734' 7
"27.800583,-97.396381" 3
"28.083627,-80.608109" 1
"28.393619,-81.538684" 1
"28.535516,77.391026" 2
"28.538336,-81.379236' 2
"28.6,-81.339235' 4
"28.613939,77.209021" 1
"29.424122,-98.493628" 1
"29.651634,-82.324826" 9
"29.760427,-95.369803" 4
"29.883275,-97.941394" 2
"29.951066,-90.071532" 8
"29.984092,-90.152852" 1
"3.42551,-111.940005" 4
"30.093274,-95.987734" 1
"30.22409,-92.019843" 1
"30.267153,-97.743061" 21
"30.332184,-81.655651" 3
"30.421309,-87.216915" 1
"30.438256,-84.280733" 1
"31.230416,121.473701" 1
"31.777576,-106.442456" 3
"32.0853,34.781768" 2
"32.298757,-90.18481" 1
"32.407359,-87.021101" 1
"32.448788,-81.783167' 1
"32.715738,-117.161084" 15
"32.735687,-97.108066" 12
"32.755488,-97.330766" 1
"32.776475,-79.931051" 2
"32.776664,-96.796988" 3
"32.840695,-83.632402" 3
"32.855038,-96.797592" 2
"33.036987,-117.291982" 1
"33.103174,-96.67055" 1
"33.158093,-117.350594" 2
"33.214841,-97.133068" 1
"33.285669,-86.809989" 1
"33.352826,-111.789027" 1
"33.380672,-84.799657" 1
"33.448377,-112.074037' 1
"33.448377,-112.074037" 25
"33.49417,-111.926052" 1
"33.520661,-86.80249" 4
"33.600023,-117.671995" 1
"33.683947,-117.794694" 2
"33.736062,-118.292246" 2
"33.748995,-84.387982" 56
"33.77005,-118.193739" 2
"33.792239,-118.315072" 1
"33.830296,-116.545292" 6
"33.835293,-117.914504" 3
"33.835849,-118.340629" 5
"33.849182,-118.388408" 2
"33.862237,-118.399519" 1
"33.888349,-118.308962" 5
"33.916403,-118.352575" 1
"33.91918,-118.416465" 2
"33.951935,-83.357567" 5
"33.952602,-84.549933" 1
"33.953349,-117.396156" 2
"33.97279,-118.427578" 1
"33.989819,-117.732585" 1
"34.00071,-81.034814" 19
"34.019454,-118.491191" 25
"34.021122,-118.396466' 3
"34.025922,-118.779757" 4
"34.052234,-118.243685" 179
"34.055103,-117.749991" 1
"34.055569,-117.182538" 1
"34.063344,-117.650888" 3
"34.068621,-117.938953" 4
"34.07362,-118.400356" 8
"34.080565,-118.072846" 1
"34.090009,-118.361744" 1
"34.095287,-118.127015" 6
"34.101487,-84.519375' 1
"34.133619,-117.907563" 1
"34.13956,-118.387099" 3
"34.139769,-118.350578" 14
"34.146647,-118.807373" 1
"34.147785,-118.144516" 20
"34.148972,-118.451357" 4
"34.151749,-118.521428" 3
"34.161673,-118.052846" 1
"34.165357,-118.608975" 3
"34.180839,-118.308966" 3
"34.190162,-118.131319" 1
"34.206818,-118.200028" 1
"34.216394,-119.037602" 1
"34.225726,-77.94471" 3
"34.228754,-118.235119" 1
"34.238125,-118.530123" 8
"34.269447,-118.781482" 1
"34.366495,-89.519248" 1
"34.420831,-119.69819" 7
"34.564537,-92.586828" 1
"34.759453,135.51686" 1
"34.852618,-82.39401" 1
"35.085334,-106.605553" 3
"35.149534,-90.04898" 1
"35.198284,-111.651302" 1
"35.222567,-97.439478" 2
"35.227087,-80.843127" 3
"35.282752,-120.659616" 2
"35.46756,-97.516428" 5
"35.564138,-121.080747" 1
"35.709026,139.731992" 10
"35.79154,-78.781117" 1
"35.880073,-90.167039" 1
"35.910144,-79.075289" 1
"35.9132,-79.055845" 11
"35.960638,-83.920739" 6
"35.994033,-78.898619" 5
"36.060949,-95.797453" 1
"36.072635,-79.791975" 3
"36.153982,-95.992775" 2
"36.162664,-86.781602" 5
"36.169941,-115.13983" 5
"36.20811,-86.291102" 1
"36.330228,-119.292058" 1
"36.600238,-121.894676" 1
"36.850769,-76.285873" 4
"37.029869,-76.345222" 1
"37.27528,-107.880067" 1
"37.322998,-122.032182" 8
"37.338208,-121.886329" 5
"37.36883,-122.03635" 1
"37.385218,-122.11413" 1
"37.386052,-122.083851" 13
"37.424106,-122.166076" 8
"37.441883,-122.143019" 6
"37.45296,-122.181725" 14
"37.485215,-122.236355" 8
"37.507877,15.08303" 1
"37.566535,126.977969" 1
"37.63049,-122.411084" 2
"37.687176,-97.330053" 2
"37.687924,-122.470208" 3
"37.77493,-122.419416" 76
"37.804364,-122.271114" 15
"37.831316,-122.285247" 4
"37.871593,-122.272747" 19
"37.906037,-122.544976" 1
"37.916133,-122.310765" 1
"37.977978,-122.031073" 1
"38.029306,-78.476678" 1
"38.040584,-84.503716" 1
"38.249358,-122.039966" 1
"38.252665,-85.758456' 2
"38.440429,-122.714055" 2
"38.559772,68.787038" 1
"38.580461,-121.530234" 1
"38.581572,-121.4944" 12
"38.615953,-76.613015" 2
"38.627003,-90.199404" 4
"38.685737,-121.082167" 1
"38.722252,-9.139337" 1
"38.750949,-77.475267" 2
"38.804836,-77.046921" 3
"38.833882,-104.821363" 1
"38.846224,-77.306373" 1
"38.86832,-107.592002' 1
"38.881396,-94.819128" 1
"38.907192,-77.036871" 149
"38.933868,-77.17726" 37
"38.953617,-94.733571" 1
"38.977888,-77.007476" 1
"38.984652,-77.094709" 7
"38.990666,-77.026088" 2
"39.028444,-76.601354" 1
"39.034832,-76.907474" 2
"39.097283,-93.617172" 1
"39.099727,-94.578567" 5
"39.103118,-84.51202" 8
"39.165325,-86.526386" 1
"39.290385,-76.612189" 6
"39.414269,-77.410541" 1
"39.529633,-119.813803" 5
"39.580745,-104.877173" 1
"39.629526,-79.955897" 2
"39.640264,-106.374195" 1
"39.728494,-121.837478" 2
"39.739236,-104.990251" 8
"39.758948,-84.191607" 2
"39.767458,-94.846681" 1
"39.768403,-86.158068" 4
"39.904211,116.407395" 4
"39.952584,-75.165222" 10
"39.961176,-82.998794" 2
"4.710989,-74.072092" 3
"40.014986,-105.270546" 2
"40.041902,-75.487644" 1
"40.110588,-88.20727" 1
"40.15784,-83.075187" 1
"40.179186,44.499103" 2
"40.193377,-85.38636" 2
"40.200547,-74.031416" 1
"40.202335,-74.012081" 1
"40.209122,-74.038627" 1
"40.214257,-77.008588" 1
"40.409262,49.867092" 1
"40.416775,-3.70379' 3
"40.425869,-86.908066" 1
"40.440625,-79.995886" 1
"40.484977,-106.831716" 1
"40.486216,-74.451819" 1
"40.575382,-74.32237" 3
"40.58526,-105.084423" 1
"40.58654,-122.391675" 1
"40.625932,-75.370458" 6
"40.678178,-73.944158" 6
"40.69759,-74.263164" 1
"40.712784,-74.005941' 1
"40.712784,-74.005941" 305
"40.725927,8.555683" 1
"40.725934,-73.514292" 2
"40.728157,-74.077642" 3
"40.735657,-74.172367" 1
"40.754484,-86.356666" 1
"40.760779,-111.891047" 6
"40.793395,-77.860001" 1
"40.793432,-73.415121" 3
"40.796767,-74.481544" 2
"40.825656,-73.698186" 1
"40.825763,-96.685198" 3
"40.825901,-74.209005" 1
"40.911427,-90.647358" 1
"40.93121,-73.898747" 1
"41.003672,-80.347009" 2
"41.033986,-73.76291" 1
"41.117744,-73.408158" 1
"41.242856,-73.200664" 1
"41.252363,-95.997988" 3
"41.308274,-72.927883" 3
"41.355654,-72.099521" 1
"41.49932,-81.694361" 3
"41.600545,-93.609106' 5
"41.671765,-72.94927" 5
"41.700371,-73.92097" 1
"41.715138,44.827096" 2
"41.747593,-74.08681" 1
"41.763711,-72.685093" 3
"41.808431,-72.249523" 1
"41.878114,-87.629798" 34
"41.902784,12.496366" 6
"41.966765,-71.549507" 1
"42.033361,-88.083406" 1
"42.045072,-87.687697" 10
"42.097002,-79.235326" 1
"42.110304,-88.03424' 1
"42.252877,-71.002271' 1
"42.279755,-71.162676" 1
"42.280826,-83.743038" 4
"42.331427,-83.045754" 9
"42.337041,-71.209221" 1
"42.353904,-71.133711" 1
"42.360083,-71.05888" 25
"42.37093,-71.182832" 1
"42.373616,-71.109733" 14
"42.46179,14.21609" 1
"42.471147,-83.142148" 1
"42.485093,-71.43284" 1
"42.51954,-70.896716" 1
"42.562967,-114.460871" 1
"42.732535,-84.555535" 1
"42.866632,-106.313081" 1
"42.874621,74.569762" 1
"42.96336,-85.668086" 1
"42.981429,-70.947755" 1
"42.99564,-71.454789" 2
"43.012527,-83.687456" 1
"43.038903,-87.906474" 2
"43.044848,-73.630073" 1
"43.048122,-76.147424" 3
"43.071755,-70.762553" 1
"43.073052,-89.40123" 5
"43.16103,-77.610922" 1
"43.222015,76.851249" 1
"43.467517,-79.687666" 2
"43.548473,10.310567" 1
"43.64896,-72.319258" 1
"43.653226,-79.383184" 57
"43.82311,-111.792424' 1
"44.052069,-123.086754" 7
"44.058173,-121.31531" 2
"44.261931,-88.415385" 3
"44.475883,-73.212072" 1
"44.647128,10.925227" 1
"44.648863,-63.57532" 1
"44.699487,-73.452912" 1
"44.724663,-63.690852" 1
"44.801182,-68.777814" 3
"44.953703,-93.089958" 7
"44.977753,-93.265011" 9
"45.056004,-92.808844" 1
"45.070312,7.686856" 1
"45.184725,9.158207" 1
"45.493488,12.246318" 1
"45.501689,-73.567256" 5
"45.523062,-122.676482" 14
"45.638728,-122.661486" 1
"46.482526,30.72331" 1
"46.588371,-112.024505" 2
"47.010453,28.86381" 3
"47.252877,-122.444291" 4
"47.322322,-122.312622" 1
"47.376887,8.541694" 1
"47.470377,-122.346792" 1
"47.60621,-122.332071" 35
"47.610377,-122.200679" 1
"47.65878,-117.426047" 1
"47.673988,-122.121512" 2
"47.681488,-122.208735" 2
"48.117039,-122.760447" 1
"48.208174,16.373819" 2
"48.824531,2.274342" 1
"48.856614,2.352222" 5
"49.282729,-123.120738" 5
"49.848471,-99.95009" 1
"49.899754,-97.137494" 2
"50.075538,14.4378" 2
"50.4501,30.5234" 6
"50.85034,4.35171" 1
"51.048615,-114.070846" 1
"51.178363,-115.570769" 1
"51.441642,5.469722" 1
"51.4982,31.28935" 1
"51.507351,-0.127758" 16
"51.769203,-4.464252" 2
"52.370216,4.895168" 1
"52.486243,-1.890401" 1
"52.520007,13.404954" 5
"53.349805,-6.26031" 7
"53.544389,-113.490927" 16
"53.551085,9.993682" 3
"53.688971,-2.332052" 1
"54.687156,25.279651" 2
"55.676097,12.568337" 11
"55.677069,12.513321" 1
"55.755826,37.6173" 1
"56.949649,24.105186" 2
"58.301944,-134.419722" 1
"59.220537,10.934701" 2
"59.329323,18.068581" 3
"59.436961,24.753575" 2
"59.913869,10.752245" 17
"6.524379,3.379206" 1
"60.173324,24.941025" 7
"60.391263,5.322054" 1
"61.218056,-149.900278" 2
"62.427173,6.310736" 1
"62.453972,-114.371789" 1
"62.472228,6.149482" 2
"9.928069,-84.090725" 3
<!doctype html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<title>ONA 15 Attendees: Demographic Breakdown</title>
<?php /* EDIT ME */ ?>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<!-- Latest compiled and minified Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<link href='http://fonts.googleapis.com/css?family=Crimson+Text:400,600,700,400italic,600italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Libre+Baskerville:400,700,400italic' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Source+Sans+Pro' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="style.css" />
</head>
<body class="">
<div id="main-wrapper" class="">
<div id="main-content" class="container">
<header class="row">
<div class="social-links col-md-12"></div>
<div class="col-md-12">
<h1 class="text-center">ONA 15 Attendees: <br>Demographic Breakdown</h1>
<div class="byline text-center">By <a href="https://twitter.com/Jeremy_CF_Lin">Jeremy C.F. Lin</a>, <a href="https://twitter.com/asilver360">Andrew Silver</a> and <a href="https://twitter.com/ernestorivera">Ernesto Rivera</a>
</div>
<div class="meta text-center">
Published September 25, 2015
</div>
</div>
</header>
<section class="row map-row">
<div class="col-md-5 globe-text">
<h3>A Global Contingent</h3>
<p>ONA conference has grown from a couple of hundred attendees in its first few years to more than 2,000 journalists from around the world. The conference sold out for its ninth year in a row and broke its record for number of attendees.</p>
<p><span class="instruction">Drag and zoom for more detail.</span>
</p>
<button type="button" class="btn btn-default reset-btn disabled background">Reset Map</button>
</div>
<div class="col-md-7">
<div id="map" class="chart"></div>
</div>
<div class="col-md-12">
</div>
</section>
<section class="charts">
<div class="row fix_margin">
<div class="col-md-7 desc">
<h3>Top Countries</h3>
<p>With approximately 300 attendees from other countries, there are three times as many international journalists than last year. They packed their meetup Thursday to standing-room only.
</p>
</div>
<div class="col-md-5 chart">
<table class="table table-condensed">
<tr>
<th>Rank</th>
<th>Country</th>
<th class='num'>Attendees</th>
</tr>
<tr>
<td>1</td>
<td>United States</td>
<td class='num'>1,811</td>
</tr>
<tr>
<td>2</td>
<td>Canada</td>
<td class='num'>93</td>
</tr>
<tr>
<td>3</td>
<td>Norway</td>
<td class='num'>23</td>
</tr>
<tr>
<td>4</td>
<td>England</td>
<td class='num'>17</td>
</tr>
<tr>
<td>5</td>
<td>Italy</td>
<td class='num'>14</td>
</tr>
<tr>
<td>6</td>
<td>Denmark</td>
<td class='num'>12</td>
</tr>
<tr>
<td>7</td>
<td>Japan</td>
<td class='num'>11</td>
</tr>
<tr>
<td>8</td>
<td>Brasil</td>
<td class='num'>8</td>
</tr>
<tr>
<td>8</td>
<td>Germany</td>
<td class='num'>8</td>
</tr>
<tr>
<td>8</td>
<td>Ukraine</td>
<td class='num'>8</td>
</tr>
</table>
</div>
</div>
<div class="row fix_margin">
<div class="col-md-7 desc">
<h3>Top Organizations</h3>
<p>The New York Times and The Los Angeles Times are the most represented organizations, with other media and tech giants also topping the list. Freelancers, however, almost made it to the top 10, coming in at 11.</p>
</div>
<div class="col-md-5 chart">
<table class="table table-condensed">
<tr>
<th>Rank</th>
<th>Affiliation</th>
<th class='num'>Attendees</th>
</tr>
<tr>
<td>1</td>
<td>CNN</td>
<td class='num'>40</td>
</tr>
<tr>
<td>2</td>
<td>The New York Times</td>
<td class='num'>38</td>
</tr>
<tr>
<td>3</td>
<td>Freelance</td>
<td class='num'>35</td>
</tr>
<tr>
<td>4</td>
<td>Los Angeles Times</td>
<td class='num'>33</td>
</tr>
<tr>
<td>5</td>
<td>Google</td>
<td class='num'>27</td>
</tr>
<tr>
<td>6</td>
<td>Gannett</td>
<td class='num'>26</td>
</tr>
<tr>
<td>7</td>
<td>Twitter, Inc.</td>
<td class='num'>23</td>
</tr>
<tr>
<td>8</td>
<td>NPR</td>
<td class='num'>21</td>
</tr>
<tr>
<td>9</td>
<td>Associated Press</td>
<td class='num'>19</td>
</tr>
<tr>
<td>10</td>
<td>The Washington Post</td>
<td class='num'>18</td>
</tr>
</table>
</div>
</div>
<div class="row fix_margin">
<div class="col-md-7 desc">
<h3>Newcomers</h3>
<p>More than 42 percent of people at ONA15 are attending the conference for the first time, down from 52 percent last year. More than 10 percent have been to three or more conferences.</p>
</div>
<div class="col-md-5 chart">
<table class="table table-condensed">
<tr>
<th>Conference attendended</th>
<th>Attendees</th>
<th class='num'>Percentage</th>
</tr>
<tr>
<td>This will be my first conference</td>
<td>899</td>
<td class='num'>42.4%</td>
</tr>
<tr>
<td>1</td>
<td>241</td>
<td class='num'>11.4%</td>
</tr>
<tr>
<td>2</td>
<td>104</td>
<td class='num'>4.9%</td>
</tr>
<tr>
<td>3</td>
<td>105</td>
<td class='num'>5.0%</td>
</tr>
<tr>
<td>4-7</td>
<td>166</td>
<td class='num'>7.8%</td>
</tr>
<tr>
<td>NO ANSWER</td>
<td>606</td>
<td class='num'>28.6%</td>
</tr>
</table>
</div>
</div>
</section>
<footer>
<div id="source-line" class="text-center">Source: ONA Registration database current as of Sept. 21, 2015. Note: Some location data could not be parsed. Some organizations and locations were missing from the data.</div>
</footer>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<!-- Latest compiled and minified Bootstrap JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/queue-async/1.0.7/queue.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.19/topojson.min.js'></script>
<script src="d3.geo.zoom.js"></script>
<script src='arc.js'></script>
<script src="script.js"></script>
</body>
</html>
////////////////////////////////
////////////////////////////////
// preparation: svg's width/height, projection, path, voronoi
////////////////////////////////
////////////////////////////////
// I followed Mike Bostock's margin convention to set margins first,
// and then set the width and height based on margins.
// Here's the link to the margin convention
// http://bl.ocks.org/mbostock/3019563
//Set map hieght to be the same as the width of the #Map div.
//Should be a squre.
$("#map").css("height", $("#map").outerWidth() + "px");
var sens = .1
var margin = {
top: 15,
right: 15,
bottom: 15,
left: 15
},
width = $("#map").outerWidth() - margin.left - margin.right,
height = $("#map").outerHeight() - margin.top - margin.bottom;
//This is the project for the globe
var projection = d3.geo.orthographic()
.scale(height / 2)
.translate([width / 2, height / 2])
.clipAngle(90)
.precision(0.5);
//Centerpoint for Los Angeles.
var losAngeles = [34.0575283, -118.4159553];
//Set intitial view so LA is centerpoint.
projection.rotate([94.7908512330838, -40.79682768575549, 0]);
//Path function. All paths are drawn through the projection.
var path = d3.geo.path()
.projection(projection)
.pointRadius(function(d) {
//This is where we set circle radii to show count of attendees
if (d.count) {
return Math.sqrt(d.count / Math.PI) * 1.3;
}
});
//Zoom is defined.
var zoom = d3.behavior.zoom()
.scaleExtent([1, 3])
.on("zoom", zoomed);
var zoomEnhanced = d3.geo.zoom().projection(projection)
.on("zoom", zoomedEnhanced);
// Create a voronoi layer for better mouse interaction experience
// For more reading on voronoi, check out
// http://www.visualcinnamon.com/2015/07/voronoi.html
var voronoi = d3.geom.voronoi()
.x(function(d) {
return d.x;
})
.y(function(d) {
return d.y;
})
.clipExtent([
[0, 0],
[width, height]
]);
var svg = d3.select('#map').append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.attr('class', 'graph-svg-component')
.call(responsivefy) // Call function responsivefy to make the graphic reponsive according to the window width/height
.append('g')
.attr("class", "globe-g")
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.call(zoomEnhanced);
// Globe Outline (Background circle)
// -------------
var globe = svg.selectAll('path.globe').data([{
type: 'Sphere'
}])
.enter().append('path')
.attr('class', 'globe')
.attr('d', path);
////////////////////////////////
////////////////////////////////
// Queue: queue is an asynchronous helper library for JavaScrip
// It helps coders to easily load multiple datasets
// Here's the link to queue github repository:
// https://github.com/mbostock/queue
////////////////////////////////
////////////////////////////////s
queue()
.defer(d3.json, 'world_countries.json') // load geojson/topojson data
.defer(d3.csv, 'dataSmall.csv')
// .defer(d3.csv, 'flights.csv')
.await(ready);
function ready(error, world, data) {
if (error) throw error;
data.forEach(
function(d) {
var latlong = d.lat_long.replace(/\"/g, "").replace(/\'/g, "").split(",");
latlong = [+latlong[0], +latlong[1]];
d.end_lat = latlong[0];
d.end_long = latlong[1];
d.start_lat = losAngeles[0];
d.start_long = losAngeles[1];
if (isNaN(latlong[0]) || isNaN(latlong[1])) {
//Do nothing.
} else {
d.greatcircle = new arc.GreatCircle({
x: d.start_long,
y: d.start_lat
}, {
x: d.end_long,
y: d.end_lat
});
d.line = d.greatcircle.Arc(100, {
offset: 10
});
d.arc = d.line.json();
}
}
);
data = data.filter(function(d) {
if (isNaN(d.end_lat) || isNaN(d.end_long)) {
//Do nothing.
} else {
return d;
}
});
svg.selectAll('baseMap')
.data(world.features)
.enter()
.append('path')
.attr('d', path)
// .append("g")
.attr('class', 'baseMap');
svg.selectAll('.cities_end')
.data(data)
.enter().append("path")
.datum(function(d) {
return {
type: "Point",
coordinates: [d.end_long, d.end_lat],
count: +d.count
};
})
.attr("d", path)
.attr('class', 'cities_end');
svg.append("g")
.attr("class", "line")
.selectAll(".arc")
.data(data.map(function(d) {
return d.arc;
}))
.enter()
.append("path")
.attr("class", "arc")
.attr("d", path);
}
d3.select("svg").call( //drag on the svg element
d3.behavior.drag()
.origin(function() {
var r = projection.rotate();
return {
x: r[0],
y: -r[1]
}; //starting point
})
.on("drag", function() {
var r = projection.rotate();
/* update retation angle */
projection.rotate([d3.event.x, -d3.event.y, r[2]]);
/* redraw the map and circles after rotation */
svg.selectAll(".baseMap").attr("d", path);
svg.selectAll(".arc").attr("d", path);
svg.selectAll('.cities_end')
.attr("d", path);
$(".reset-btn").removeClass("disabled");
})
);
d3.select("svg")
.call(d3.behavior.drag()
.origin(function() {
var r = projection.rotate();
return {
x: r[0] / sens,
y: -r[1] / sens
}; //starting point
})
.on("drag", function() {
var rotate = projection.rotate();
projection.rotate([d3.event.x * sens, -d3.event.y * sens, rotate[2]]);
/* redraw the map and circles after rotation */
svg.selectAll(".baseMap").attr("d", path);
svg.selectAll(".arc").attr("d", path);
svg.selectAll('.cities_end')
.attr("d", path);
$(".reset-btn").removeClass("disabled");
}))
$(".reset-btn").on("click", function() {
reset();
})
function reset() {
//This is the project for the globe
projection.scale(height / 2)
.translate([width / 2, height / 2]);
//Set intitial view so LA is centerpoint.
projection.rotate([94.7908512330838, -40.79682768575549, 0]);
svg.selectAll(".baseMap").transition().duration(500).attr("d", path);
svg.selectAll(".arc").transition().duration(500).attr("d", path);
svg.selectAll('.cities_end').transition().duration(500).attr("d", path);
svg.selectAll('.globe').transition().duration(500).attr("d", path);
$(".reset-btn").addClass("disabled");
}
// apply transformations to map and all elements on it
function zoomed(d) {
svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
//grids.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
//geofeatures.select("path.graticule").style("stroke-width", 0.5 / d3.event.scale);
svg.selectAll("path.boundary").style("stroke-width", 0.5 / d3.event.scale);
}
function zoomedEnhanced() {
$(".reset-btn").removeClass("disabled");
svg.selectAll(".baseMap").attr("d", path);
svg.selectAll(".arc").attr("d", path);
svg.selectAll('.cities_end').attr("d", path);
svg.selectAll('.globe').attr("d", path);
}
// function dragstarted(d) {
// console.log("dragstarted()");
// //stopPropagation prevents dragging to "bubble up" which triggers same event for all elements below this object
// d3.event.sourceEvent.stopPropagation();
// d3.select(this).classed("dragging", true);
// }
// function dragged() {
// console.log("dragged()");
// projection.rotate([d3.event.x, -d3.event.y]);
// svg.selectAll("path").attr("d", path);
// }
// function dragended(d) {
// console.log("dragended()");
// d3.select(this).classed("dragging", false);
// }
function responsivefy(svg) {
var container = d3.select(svg.node().parentNode),
width = parseInt(svg.style('width')),
height = parseInt(svg.style('height')),
aspect = width / height;
svg.attr('viewBox', '0 0 ' + width + ' ' + height)
.attr('perserveAspectRatio', 'xMinYMid')
.call(resize);
d3.select(window).on('resize', resize);
$("#map").css("height", $("#map").width() + "px");
function resize() {
$("#map").css("height", $("#map").width() + "px");
var targetWidth = parseInt(container.style('width'));
svg.attr('width', targetWidth);
svg.attr('height', Math.round(targetWidth / aspect));
}
}
/*div {
outline: 1px solid black;
}*/
/*svg {
outline: 1px solid orange;
position: absolute;
top: 0;
left: 0;
}
g {
outline: 1px solid red;
}*/
h1{
/*font-family:"Libre Baskerville";*/
font-family: "Source Sans Pro",sans-serif;
font-style: normal;
font-weight: 800;
/* font-size:22px;*/
}
body{
/*font-family: 'Crimson Text';*/
font-family: 'Source Sans Pro', sans-serif;
font-size:18px;
}
.instruction{
color: grey;
font-size: .8em;
}
h3{
font-weight: 600;
}
/* --------------------- */
/* SVG STYLES */
/* --------------------- */
.baseMap{
stroke-width:0.8px;
stroke:#555;
fill:#555;
opacity:0.5;
}
.globe {
fill: #000;
}
.cities_start{
fill:rgba(199,70,70,.8);
/* fill:none;*/
}
.cities_end{
/*fill:rgba(29, 168, 183, .5);*/
fill:rgba(254,194,65,.8);
254
stroke-width: .25;
stroke: white;
/* fill:none;*/
}
.line{
/* stroke:rgba(0, 0, 0, 0.3);*/
stroke:rgba(253,141,3,.3);
stroke-width:1px;
fill:none;
/*stroke-dasharray:3, 3;*/
}
.geo-globe {
fill: rgba(236,249,255,0.8);
/* fill:white;*/
}
/* --------------------- */
/* --------------------- */
/* CHARTS STYLES */
/* --------------------- */
.map-row {
text-align: center;
position: relative;
}
#map {
width: 100%;
max-width: 650px;
position: relative;
display: inline-block;
}
.overlay {
fill: #FFF;
}
.reset-btn {
margin: 0 auto;
}
.globe-text {
padding-top: 30px;
text-align: left;
}
/* --------------------- */
/* TABLES STYLES */
/* --------------------- */
.table {
font-size: 15px;
}
.background{
background-color: rgba(211,211,211, .3);
}
td.num {
text-align: right;
}
.fix_margin{
margin-top: 50px;
}
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment