Skip to content

Instantly share code, notes, and snippets.

@kenpenn
Last active August 29, 2015 14:22
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 kenpenn/506f180758a8ac581439 to your computer and use it in GitHub Desktop.
Save kenpenn/506f180758a8ac581439 to your computer and use it in GitHub Desktop.
Good People to Follow

Pictures are a scrollable ul; select "All" in the select box to scroll thru all people.

demonstrates:

genning a canvas globe with topojson

creating a circle from an object with lat-lon coords

and spinning the globe to it via transition, interpolation

canvas is partially responsive

smooth scrolling of ul via transition, interpolation

* { box-sizing : border-box; }
body {
background: #111;
color: #fff;
font-family: sans-serif;
font-weight:normal;
font-size: 13px;
min-height: 100%;
margin:0;
min-width: 100%;
}
h2::selection,
h3::selection,
p::selection,
span::selection {
background: #ff619b; /* a nod to mr. irish */
}
h2 { text-align: center; }
.peeps-box, .globe-box {
position: absolute;
top: 0;
bottom: 0.769em;
}
.peeps-box {
background: #222;
left: 0.769em;
overflow: hidden;
padding: 0.769em;
width: 14.615em;
}
.select-box {
background: #222;
padding-top: 0.769em;
}
.select-box select {
background: #555;
border: none;
color: #fff;
font-size: 1em;
margin:0;
outline: none;
width: 100%;
}
.list-box {
position: absolute;
top: 3.846em;
bottom: 0;
overflow-x: hidden;
overflow-y: auto;
width: 100%;
}
.list-box ul, .list-box li { list-style : none; }
.list-box ul {
margin-top: -0.95em;
padding: 0 3em 0 0;
}
.list-box ul li:first-of-type article {
margin-top: 0;
}
.peep-box {
background: #333;
margin: 0.769em 0 0 0;
padding: 0.769em;
width: 13.077em;
}
.peep-box img {
cursor: pointer;
margin: 0;
}
.peep-box .peep-name {
margin: 0 0 0.4em 0;
}
.peep-tweet {
margin: 0.2em 0 0 0;
text-align: right;
}
.twitter {
color: #2aa9e0;
}
.globe-box {
left: 15.384em;
right: 0; /* 0.769em; */
}
.globe-box h3 {
text-align: center;
width: 100%;
}
.canvas-box {
position: absolute;
top: 6.154em;
bottom: 0;
width: 100%;
}
.slug {
position: absolute;
top: 500px;
left: calc(50% - 11em);
background: #333;
width: 22em;
padding: 0.5em;
}
.slug-img {
background-size: 3.692em 3.692em;
float: left;
margin-right: 0.5em;
width: 3.692em;
height: 3.692em;
}
.slug p {
margin: 0.5em;
text-align: center;
}
.slug p:first-of-type { margin-top: 0.25em; }
.slug p:last-of-type { margin-bottom: 0; }
/*
* shamelessly adapted from World Tour, http://bl.ocks.org/mbostock/4183330
*/
(function () {
var crtKey = function (arr) {
var key = "";
arr.forEach(function (str) {
key += str.toLowerCase().replace(/[^a-z]/g, "");
});
return key;
};
var list = {
el: {},
selectBox : {},
listBox : {},
peeps : {},
init : function (opts) {
var self = this;
var template = opts.template;
var select = document.createElement("select");
var ul = document.createElement("ul");
this.el = d3.select(opts.selector);
opts.peeps.forEach(function (peep) {
var peepKey = crtKey([peep.firstname, peep.lastname]);
select.appendChild(list.crtOption(peep, peepKey));
ul.appendChild(list.crtLi(peep, peepKey, template));
list.peeps[peepKey] = peep;
});
select.appendChild(this.crtOption("All", "all"));
this.selectBox = d3.select(opts.selector + " .select-box");
this.selectBox.node().appendChild(select);
this.listBox = d3.select(opts.selector + " .list-box");
this.listBox.node().appendChild(ul);
this.el.selectAll("li img").on("click", function () {
var id = this.parentElement.parentElement.id;
var peep = list.peeps[id];
globe.rotateTo([ peep ], 0, 1);
});
this.el.select("select").on("change", function () {
var peepArr = [];
var peepKeys = [];
if ( this.value == "all" ) {
peepKeys = Object.keys(self.peeps);
peepKeys.forEach(function (peep) { peepArr.push(list.peeps[peep]) });
} else {
peepArr = [ list.peeps[this.value] ];
}
globe.rotateTo(peepArr, 0, peepArr.length);
});
},
crtOption : function (peep, val) {
var option = document.createElement("option");
option.value = val;
option.textContent = peep == "All" ? peep : peep.firstname + " " + peep.lastname;
return option;
},
crtLi : function (peep, id, template) {
var li = document.createElement("li");
li.id = id;
var peepBox = document.querySelector(template).cloneNode(true);
var peepName = peepBox.querySelector(".peep-name");
peepName.textContent = peep.firstname + " " + peep.lastname;
var peepImg = peepBox.querySelector("img");
peepImg.src = peep.img;
peepImg.alt = peep.firstname + ' ' + peep.lastname;
var peepLink = peepBox.querySelector("a");
peepLink.href = "https://twitter.com/" + peep.twitter
var peepHandle = peepBox.querySelector(".twitter");
peepHandle.textContent = "@" + peep.twitter;
li.appendChild(peepBox);
return li;
},
transScroll : function (id) {
var offsetTop = document.querySelector("#" + id).offsetTop;
var scrollTween = function (t) {
return function() {
var terpRound = d3.interpolateRound(this.scrollTop, offsetTop);
return function(t) {
this.scrollTop = terpRound(t);
};
};
};
this.selectBox.select("select").node().value = id;
this.listBox.transition()
.duration(1250)
.tween("scrollTween", scrollTween(0))
}
};
var globe = {
el : {},
canvas : {},
ctx : {},
cities : {},
path : {},
slug : {},
init : function (opts) {
var self = this;
this.el = d3.select(opts.selector).select(".canvas-box");
this.globe = {type: "Sphere"};
this.land = topojson.feature(opts.world, opts.world.objects.land);
this.countries = topojson.feature(opts.world, opts.world.objects.countries).features;
this.borders = topojson.mesh(opts.world, opts.world.objects.countries, function(a, b) { return a !== b; });
this.xref = this.initXref(opts.peeps);
this.slug = this.el.select(".slug");
this.initCanvas();
window.addEventListener("resize", function () { self.initCanvas() });
},
initXref : function (peeps) {
var self = this;
var xref = {};
var ids = [];
var xrefKeys = [];
var createGeoMarker = function (angles, lon, lat) {
var marker = [];
angles.forEach(function (angle) {
marker.push( d3.geo.circle().origin([lon, lat]).angle(angle)() );
});
return marker;
};
peeps.forEach(function(mbr) {
var cityKey = crtKey([mbr.location.city, mbr.location.country]);
var cityArr = [];
if ( ids.indexOf(mbr.location.id) === -1 ) {
ids.push(mbr.location.id);
}
if ( !self.cities[cityKey] ) {
self.cities[cityKey] = createGeoMarker( [1, .3], mbr.location.lon, mbr.location.lat );
}
});
this.countries.forEach(function(country, cx){
if ( ids.indexOf(country.id) > -1 ) {
xrefKeys.push({id : country.id, cx : cx });
}
})
peeps.forEach(function(mbr) {
var nameKey = crtKey([mbr.firstname, mbr.lastname]);
var cityKey = crtKey([mbr.location.city, mbr.location.country]);
var id, cx;
xrefKeys.forEach(function(uniq) {
if ( uniq.id === mbr.location.id ) {
id = uniq.id;
cx = uniq.cx;
}
});
xref[nameKey] = { id : id, cx: cx, cityKey : cityKey };
});
return xref;
},
initCanvas : function (selector) {
var defaultScale = 248;
var defaultHeight = 500;
var elRect = this.el.node().getBoundingClientRect();
var scaleFactor = (Math.min(elRect.width, elRect.height) -30) / defaultHeight;
this.slug.style("opacity", 0);
this.width = elRect.width;
this.height = elRect.height;
this.el.select("canvas").remove();
this.canvas = this.el.append("canvas")
.attr("width", this.width)
.attr("height", this.height);
this.ctx = this.canvas.node().getContext("2d");
this.center = { x: elRect.width / 2, y: elRect.height / 2 }
this.projection = d3.geo.orthographic()
.translate([this.center.x, this.center.y])
.scale(scaleFactor * defaultScale)
.clipAngle(90);
this.path = d3.geo.path()
.projection(this.projection)
.context(this.ctx);
this.slug.style("top", ( elRect.height * .75 ) + "px");
this.draw({});
},
draw : function (opts) {
this.ctx.clearRect(0, 0, this.width, this.height);
this.ctx.globalAlpha = 0.2;
this.ctx.strokeStyle = "#fff", this.ctx.lineWidth = 4, this.ctx.beginPath(), this.path(this.globe), this.ctx.stroke();
this.ctx.globalAlpha = 1;
this.ctx.fillStyle = "#62a4ea", this.ctx.beginPath(), this.path(this.globe), this.ctx.fill();
this.ctx.fillStyle = "#339633", this.ctx.beginPath(), this.path(this.land), this.ctx.fill();
if (opts.country) {
this.ctx.fillStyle = "#246b1d", this.ctx.beginPath(), this.path(opts.country), this.ctx.fill();
}
this.ctx.strokeStyle = "#246b1d", this.ctx.lineWidth = .5, this.ctx.beginPath(), this.path(this.borders), this.ctx.stroke();
if (opts.city) {
this.ctx.fillStyle = "#246b1d", this.ctx.beginPath(), this.path(opts.city[0]), this.ctx.fill();
this.ctx.strokeStyle = "#fff", this.ctx.lineWidth = .8, this.ctx.beginPath(), this.path(opts.city[0]), this.ctx.stroke();
this.ctx.fillStyle = "#fff", this.ctx.beginPath(), this.path(opts.city[1]), this.ctx.fill();
}
},
setSlug : function (peep) {
var self = this;
this.slug.transition()
.duration(500)
.style("opacity", 0)
.each('end', function () {
self.slug.select(".slug-img")
.style("background-image", "url(" + peep.img + ")");
self.slug.select(".name")
.text(peep.firstname + " " + peep.lastname + " - ");
self.slug.select("a")
.attr("href", "https://twitter.com/" + peep.twitter)
self.slug.select(".twitter")
.text("@" + peep.twitter);
self.slug.select('.city')
.text(peep.location.city + ", ");
self.slug.select('.loc').text( function () {
return peep.location.state ? peep.location.state + ", " + peep.location.country : peep.location.country;
});
})
.transition()
.duration(1250)
.style("opacity", 1);
},
rotateTo : function (peeps, px, pLen) {
var self = this;
var peep = peeps[px];
var nameKey = crtKey([peep.firstname, peep.lastname]);
var cityKey = crtKey([peep.location.city, peep.location.country]);
var opts = {
country : this.countries[this.xref[nameKey].cx],
city : this.cities[cityKey]
};
list.transScroll(nameKey);
this.setSlug(peep);
this.draw(opts);
d3.transition()
.delay(500)
.duration(1250)
.each("start", function() {
self.terp = d3.interpolate(self.projection.rotate(), [-peep.location.lon, -peep.location.lat]);
})
.tween("rotate", function() {
return function(t) {
self.projection.rotate(self.terp(t));
self.draw(opts);
};
})
.transition()
.each("end", function () {
px += 1;
if (px < pLen) {
self.rotateTo(peeps, px, pLen);
} else {
return;
}
});
}
};
var loaded = function (error, peeps, world) {
var listOpts = {
selector : ".peeps-box",
template : "#templates .peep-box",
peeps : peeps
};
var globeOpts = {
peeps : peeps,
selector : ".globe-box",
world : world
};
list.init(listOpts);
globe.init(globeOpts);
};
window.addEventListener('DOMContentLoaded', function () {
queue()
.defer(d3.json, "peeps.json")
.defer(d3.json, "world-110m.json")
.await(loaded);
});
}());
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Good People to Follow</title>
<link rel="shortcut icon" type="image/png" href="d3.png">
<link rel="stylesheet" href="globe-list.css">
</head>
<body>
<div class="peeps-box">
<div class="select-box"></div>
<p>Select:</p>
<div class="list-box"></div>
</div>
<div class="globe-box">
<h2>Good People to Follow</h2>
<h3>Click or Select Person to Rotate</h2>
<div class="canvas-box">
<div class="slug" style="opacity: 0">
<div class="slug-img"></div>
<p>
<span class="name"></span>
<a href="" target="_blank"><span class="twitter"></span></a>
</p>
<p>
<span class="city"></span>
<span class="loc"></span>
</p>
</div>
</div>
</div>
<div id="templates" style="display:none">
<article class="peep-box">
<p class="peep-name"></p>
<img src="" alt="">
<p class="peep-tweet">
<a href="" target="_blank">
<svg class="twitter-icon" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="1.363em" height="1.108em" version="1.1" viewBox="0 0 171.505 139.378">
<g transform="translate(-282.32053,-396.30734)">
<path fill="#2aa9e0" d="m453.826 412.806c-6.31 2.799-13.092 4.69-20.209 5.54 7.264-4.355 12.844-11.25 15.471-19.467-6.799 4.033-14.329 6.961-22.345 8.538-6.418-6.839-15.562-11.111-25.683-11.111-19.432 0-35.187 15.754-35.187 35.185 0 2.758 0.311 5.444 0.912 8.019-29.243-1.467-55.17-15.476-72.525-36.764-3.029 5.197-4.764 11.24-4.764 17.689 0 12.208 6.212 22.977 15.653 29.287-5.768-0.183-11.193-1.766-15.937-4.401-0.004 0.147-0.004 0.294-0.004 0.442 0 17.048 12.129 31.268 28.226 34.503-2.952 0.804-6.061 1.234-9.27 1.234-2.267 0-4.471-0.221-6.62-0.631 4.478 13.979 17.472 24.151 32.87 24.434-12.042 9.438-27.214 15.063-43.7 15.063-2.84 0-5.641-0.167-8.393-0.492 15.572 9.984 34.067 15.809 53.938 15.809 64.72 0 100.113-53.615 100.113-100.114 0-1.526-0.034-3.043-0.102-4.553 6.874-4.96 12.839-11.156 17.556-18.213z"/>
</g>
</svg>
<span class="twitter"></span>
</a>
</p>
</article>
</div>
<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="globe-list.js"></script>
</body>
</html>
[
{
"firstname": "Cameron",
"lastname": "Adams",
"twitter": "themaninblue",
"img": "adams-150x.png",
"location": {
"lat": -33.7969235,
"lon": 150.9224326,
"city": "Sydney",
"country": "Australia",
"id": 36
}
},
{
"firstname": "Mike",
"lastname": "Bostock",
"twitter": "mbostock",
"img": "bostock-150x.png",
"location": {
"lat": 37.765687,
"lon": -122.403149,
"city": "San Francisco",
"state" : "CA",
"country": "USA",
"id": 840
}
},
{
"firstname": "Isaac",
"lastname": "Cohen",
"twitter": "Cabbibo",
"img": "cohen-150x.png",
"location": {
"lat": 37.7919615,
"lon": -122.2287941,
"city": "Oakland",
"state" : "CA",
"country": "USA",
"id": 840
}
},
{
"firstname": "Chris",
"lastname": "Coyier",
"twitter": "chriscoyier",
"img": "coyier-150x.jpg",
"location": {
"lat": 43.0578914,
"lon": -87.96743,
"city": "Milwaukee",
"state" : "WI",
"country": "USA",
"id": 840
}
},
{
"firstname": "Pamela",
"lastname": "Fox",
"twitter": "pamelafox",
"img": "fox-150x.jpg",
"location": {
"lat": 37.765687,
"lon": -122.403149,
"city": "San Francisco",
"state" : "CA",
"country": "USA",
"id": 840
}
},
{
"firstname": "Vitaly",
"lastname": "Friedman",
"twitter": "smashingmag",
"img": "friedman-150x.png",
"location": {
"lat": 47.9873111,
"lon": 7.79642,
"city": "Frieburg",
"country": "Germany",
"id": 276
}
},
{
"firstname": "Marijn",
"lastname": "Haverbeke",
"twitter": "marijnjh",
"img": "haverbeke-150x.jpg",
"location": {
"lat": 52.5075419,
"lon": 13.4251364,
"city": "Berlin",
"country": "Germany",
"id": 276
}
},
{
"firstname": "Dave",
"lastname": "Herman",
"twitter": "littlecalculist",
"img": "herman-150x.jpg",
"location": {
"lat": 37.765687,
"lon": -122.403149,
"city": "San Francisco",
"state" : "CA",
"country": "USA",
"id": 840
}
},
{
"firstname": "Kitt",
"lastname": "Hodsden",
"twitter": "kitt",
"img": "hodsden-150x.png",
"location": {
"lat": 37.4038194,
"lon": -122.081267,
"city": "Mountain View",
"state" : "CA",
"country": "USA",
"id": 840
}
},
{
"firstname": "Matthew",
"lastname": "Inman",
"twitter": "Oatmeal",
"img": "inman-150x.png",
"location": {
"lat": 47.614848,
"lon": -122.3359058,
"city": "Seattle",
"state" : "WA",
"country": "USA",
"id": 840
}
},
{
"firstname": "Paul",
"lastname": "Irish",
"twitter": "paul_irish",
"img": "irish-150x.jpg",
"location": {
"lat": 37.404305,
"lon": -122.1642635,
"city": "Palo Alto",
"state" : "CA",
"country": "USA",
"id": 840
}
},
{
"firstname": "Ian",
"lastname": "Johnson",
"twitter": "enjalot",
"img": "johnson-150x.jpg",
"location": {
"lat": 37.7919615,
"lon": -122.2287941,
"city": "Oakland",
"state" : "CA",
"country": "USA",
"id": 840
}
},
{
"firstname": "Bruce",
"lastname": "Lawson",
"twitter": "brucel",
"img": "lawson-150x.jpg",
"location": {
"lat": 52.4774376,
"lon": -1.8636315,
"city": "Birmingham",
"country": "United Kingdom",
"id": 826
}
},
{
"firstname": "Ben",
"lastname": "Nadel",
"twitter": "BenNadel",
"img": "nadel-150x.jpg",
"location": {
"lat": 40.7033127,
"lon": -73.979681,
"city": "New York",
"state" : "NY",
"country": "USA",
"id": 840
}
},
{
"firstname": "Santiago",
"lastname": "Ortiz",
"twitter": "moebio",
"img": "ortiz-150x.png",
"location": {
"lat": -34.6158527,
"lon": -58.4333203,
"city": "Buenos Aires",
"country": "Argentina",
"id": 32
}
},
{
"firstname": "Tony",
"lastname": "Parisi",
"twitter": "auradeluxe",
"img": "parisi-150x.png",
"location": {
"lat": 37.765687,
"lon": -122.403149,
"city": "San Francisco",
"state" : "CA",
"country": "USA",
"id": 840
}
},
{
"firstname": "John",
"lastname": "Resig",
"twitter": "jeresig",
"img": "resig-150x.jpg",
"location": {
"lat": 40.645244,
"lon": -73.9449975,
"city": "Brooklyn",
"country": "USA",
"state" : "NY",
"id": 840
}
},
{
"firstname": "Irene",
"lastname": "Ros",
"twitter": "ireneros",
"img": "ros-150x.jpg",
"location": {
"lat": 42.3601397,
"lon": -71.0553883,
"city": "Boston",
"state" : "MA",
"country": "USA",
"id": 840
}
},
{
"firstname": "Remy",
"lastname": "Sharp",
"twitter": "rem",
"img": "sharp-150x.jpg",
"location": {
"lat": 50.8374204,
"lon": -0.1061897,
"city": "Brighton",
"country": "United Kingdom",
"id": 826
}
},
{
"firstname": "Jonathan",
"lastname": "Snook",
"twitter": "snookca",
"img": "snook-150x.jpg",
"location": {
"lat": 45.2501566,
"lon": -75.8002568,
"city": "Ottawa",
"country": "Canada",
"id": 124
}
},
{
"firstname": "Nicole",
"lastname": "Sullivan",
"twitter": "stubbornella",
"img": "sullivan-150x.png",
"location": {
"lat": 37.765687,
"lon": -122.403149,
"city": "San Francisco",
"state" : "CA",
"country": "USA",
"id": 840
}
},
{
"firstname": "Lea",
"lastname": "Verou",
"twitter": "LeaVerou",
"img": "verou-150x.png",
"location": {
"lat": 42.3783903,
"lon": -71.1129096,
"city": "Cambridge",
"state" : "MA",
"country": "USA",
"id": 840
}
},
{
"firstname": "Christophe",
"lastname": "Viau",
"twitter": "d3visualization",
"img": "viau-150x.jpg",
"location": {
"lat": 45.5601451,
"lon": -73.7120832,
"city": "Montreal",
"country": "Canada",
"id": 124
}
},
{
"firstname": "David",
"lastname": "Walsh",
"twitter": "davidwalshblog",
"img": "walsh-150x.png",
"location": {
"lat": 43.0849935,
"lon": -89.4064204,
"city": "Madison",
"state" : "WI",
"country": "USA",
"id": 840
}
},
{
"firstname": "Estelle",
"lastname": "Weyl",
"twitter": "estellevw",
"img": "weyl-150x.png",
"location": {
"lat": 37.689265,
"lon": -122.300861,
"city": "SF Bay Area",
"state" : "CA",
"country": "USA",
"id": 840
}
},
{
"firstname": "xkcd",
"lastname": "",
"twitter": "xkcdComic",
"img": "xkcd-150x.png",
"location": {
"lat": 42.395542,
"lon": -71.1037479,
"city": "Somerville",
"state" : "MA",
"country": "USA",
"id": 840
}
},
{
"firstname": "Nicholas",
"lastname": "Zakas",
"twitter": "slicknet",
"img": "zakas-150x.jpg",
"location": {
"lat": 37.4038194,
"lon": -122.081267,
"city": "Mountain View",
"state" : "CA",
"country": "USA",
"id": 840
}
}
]
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