Skip to content

Instantly share code, notes, and snippets.

@darosh
Last active October 30, 2015 11:36
Show Gist options
  • Save darosh/14e2e4e14898f13e13c7 to your computer and use it in GitHub Desktop.
Save darosh/14e2e4e14898f13e13c7 to your computer and use it in GitHub Desktop.
Planetary Grid Browser I
function Config() {
'use strict';
return {
shift: 31.2,
lineWidth: 1,
symbolLine: 4,
dash: [2, 3],
fontSize: 16,
lineMid: 11,
titleSize: 18,
lineHeight: 19,
durationScale: 1.2,
durationSpeed: 1.5,
durationMin: 750,
durationMax: 2000,
frameLineWidth: 3,
mraf: !window.chrome,
filter: {
map: {},
graticule: {off: true},
'cool-line': {},
'hot-line': {},
'balanced-line': {},
'cool-point': {off: true},
'hot-point': {off: true},
'balanced-point': {off: true},
megalith: {},
mound: {},
pyramid: {},
temple: {},
volcano: {},
place: {}
},
colors: {
water: '#def4ff',
graticule: '#999',
land: '#ffffff',
border: 'rgba(0,0,0,0.5)',
cool: '#1f78b4',
hot: '#e31a1c',
balanced: '#333',
frame: '#333',
focus: 'rgba(0,0,0,0.87)',
selection: 'rgba(255,255,255,0.58)',
shape: '#333',
bg: '#fff'
},
shapes: {
megalith: '#299ae6',
mound: '#90de43',
pyramid: '#ffff4d',
temple: '#ff7f00',
volcano: '#e31a1c',
place: '#ccc'
},
sizes: {
megalith: 80,
mound: 66,
pyramid: 66,
temple: 80,
volcano: 48,
place: 48,
'hot-line': 80,
'cool-line': 80,
'balanced-line': 80,
'cool-point': 80,
'hot-point': 80,
'balanced-point': 80,
'map': 100
},
symbols: {
megalith: 'square',
mound: 'triangle-up',
pyramid: 'triangle-up',
temple: 'cross',
volcano: 'circle',
place: 'circle'
}
};
}
function SvgGlobe(root, width, height, cfg) {
'use strict';
var maxScale = 3;
var self;
var round = d3.geo.transform({
point: function (x, y) {
this.stream.point(~~x, ~~y);
}
});
var projectionGlobe = d3.geo
.orthographic()
.clipAngle(90)
.precision(0)
.translate([height / 2, height / 2])
.scale(height / 2);
var projectionRaw = d3.geo
.orthographic()
.precision(2)
.clipAngle(90)
.translate([height / 2, height / 2])
.scale(height / 2);
var projectionRawZero = d3.geo
.orthographic()
.precision(0)
.clipAngle(90)
.translate([height / 2, height / 2])
.scale(height / 2);
var projectionRawZeroRound = {
stream: function (s) {
return projectionRawZero.stream(round.stream(s));
}
};
var projectionGlobeCalc = d3.geo
.orthographic()
.clipAngle(90)
.translate([height / 2, height / 2])
.scale(height / 2);
var projectionLinesGlobe = d3.geo
.orthographic()
.rotate([cfg.shift, 0, 0])
.precision(10)
.clipAngle(90)
.translate([height / 2, height / 2])
.scale(height / 2);
var pathRaw = d3.geo.path()
.projection(projectionRaw);
var pathRawZero = d3.geo.path()
.projection(projectionRawZero);
var pathRawZeroRound = d3.geo.path()
.projection(projectionRawZeroRound);
var pathLinesGlobe = d3.geo.path()
.projection(projectionLinesGlobe);
var zoom = d3.behavior.zoom().scaleExtent([1, maxScale])
.on('zoomstart', zoomed)
.on('zoom', zoomed)
.on('zoomend', zoomed);
root.append('circle')
.attr('class', 'overlay-white')
.attr('cx', width / 2)
.attr('cy', height / 2)
.attr('r', height / 2);
root.append('circle')
.attr('class', 'map water')
.attr('cx', width / 2)
.attr('cy', height / 2)
.attr('r', height / 2);
var graticuleGlobe = root.append('path').datum(d3.geo.graticule()())
.attr('class', 'graticule')
.style('display', 'none')
.attr('d', pathRaw);
var landGlobe = root.append('path').attr('class', 'map land country');
var lineGlobeG = root.append('g').attr('stroke-width', 1);
root.call(zoom);
root.append('circle')
.attr('class', 'border')
.style('stroke-width', cfg.frameLineWidth)
.attr('cx', width / 2)
.attr('cy', height / 2)
.attr('r', height / 2);
var g = root.append('g');
var h = d3.geo.hexakisIcosahedron;
Utils.svgLines(lineGlobeG, pathLinesGlobe, h.icosahedronEdges(), 'cool-line', 'Cool line');
Utils.svgLines(lineGlobeG, pathLinesGlobe, h.hexakisCenterEdges(), 'hot-line', 'Hot line');
Utils.svgLines(lineGlobeG, pathLinesGlobe, h.hexakisSideEdges(), 'balanced-line', 'Balanced line');
var linesSelection = root.selectAll('.line');
var highlight = root.append('circle')
.style('display', 'none')
.attr('class', 'border land')
.attr('cx', width / 2)
.attr('cy', height / 2)
.attr('r', 5);
var a, pG, pGL;
function zoomed() {
var m = d3.mouse(this);
if (d3.event && d3.event.sourceEvent) {
d3.event.sourceEvent.stopPropagation();
d3.event.sourceEvent.preventDefault();
}
({
zoomstart: function () {
pG = projectionGlobe.rotate();
pGL = projectionLinesGlobe.rotate();
projectionGlobeCalc.rotate(pG);
a = projectionGlobeCalc.invert(m);
},
zoom: function () {
var b = projectionGlobeCalc.invert(m);
var pgR = [pG[0] + b[0] - a[0], pG[1] + b[1] - a[1]];
var plgR = [pGL[0] + b[0] - a[0], pGL[1] + b[1] - a[1]];
if (self.canZoom && !self.canZoom(pgR)) {
return;
}
if (!isNaN(b[0]) && !isNaN(b[1])) {
projectionRaw.rotate(pgR);
projectionRawZero.rotate(pgR);
projectionGlobe.rotate(pgR);
projectionLinesGlobe.rotate(plgR);
update();
if (self.onZoomed) {
var s = zoom.scale();
self.onZoomed(null, s, pgR);
}
}
},
zoomend: function () {
}
})[d3.event.type]();
}
function updateSelection() {
if (self.selection) {
var coo = self.selection.geometry ? self.selection : {
'type': 'Feature',
'geometry': {'type': 'Point', 'coordinates': [self.selection[0] + cfg.shift, self.selection[1], 0]}
};
var p = pathRaw.centroid(coo);
if (!isNaN(p[0]) && !isNaN(p[1])) {
p[0] -= width / 2;
p[1] -= height / 2;
highlight.style('display', null);
highlight.attr('transform', 'translate(' + p + ')');
} else {
highlight.style('display', 'none');
}
} else {
highlight.style('display', 'none');
}
}
function update(running) {
projectionLinesGlobe.precision(running ? 2 : 1);
if (!cfg.filter.map.off) {
landGlobe.attr('d', pathRawZeroRound);
}
if (!cfg.filter.graticule.off) {
graticuleGlobe.attr('d', pathRaw);
}
linesSelection.attr('d', pathLinesGlobe);
updateSelection();
}
function setZoom(t, s, i, running) {
zoom.scale(s);
projectionLinesGlobe.rotate([i[0] + cfg.shift, i[1]]);
projectionGlobe.rotate(i);
projectionRaw.rotate(i);
projectionRawZero.rotate(i);
update(running)
}
self = {
root: root,
land: landGlobe,
pathRaw: pathRaw,
setZoom: setZoom,
updateSelection: updateSelection,
update: update
};
return self;
}
<!DOCTYPE html>
<meta charset="utf-8">
<title>Planetary Grid I</title>
<link rel="stylesheet" href="style.css">
<body>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/topojson/1.6.19/topojson.min.js"></script>
<script src="/darosh/raw/2fe464efd794bde5ed68/hexakis-icosahedron.js"></script>
<script src="/darosh/raw/2d12a584a14910032ab8/togeojson.js"></script>
<script src="config.js"></script>
<script src="utils.js"></script>
<script src="globe.js"></script>
<script src="map.js"></script>
<script src="legend.js"></script>
<script src="list.js"></script>
<script src="info.js"></script>
<script>
(function () {
'use strict';
var widthList = 220;
var margin = 12;
var widthScrollBar = self.frameElement ? 0 : 20;
var widthScreen = Math.max(document.body.clientWidth || 0, 960) - widthScrollBar;
widthScreen = Math.min(1480, widthScreen);
var heightScreen = Math.max(500, widthScreen / (960 / 480));
margin = widthScreen > 960 ? margin * 2 : margin;
var widthGlobe = 200;
var heightGlobe = 200;
if (heightScreen > 660) {
widthGlobe = 280;
heightGlobe = 280;
}
var widthLegend = 140;
var widthMap = widthScreen - Math.max(widthLegend, widthGlobe / 2) - 2 * margin;
var heightMap = heightScreen - heightGlobe / 2 - 2 * margin;
var selected;
var cfg = new Config();
cfg.url = 'http://bl.ocks.org/darosh/14e2e4e14898f13e13c7';
var svg = d3.select('body').append('svg')
.attr('width', widthMap + Math.max(widthLegend, widthGlobe / 2) + 2 * margin)
.attr('height', heightMap + heightGlobe / 2 + 2 * margin);
var map = new SvgMap(svg, widthMap, heightMap, margin, cfg);
var legend = new SvgLegend(svg.append('g')
.attr('transform', 'translate(' + [widthMap + margin + cfg.lineMid, margin] + ')'),
cfg, clickedLegend
);
cfg.filter = legend.lookFilter;
var globe = new SvgGlobe(svg.append('g')
.attr('transform', 'translate(' + [widthMap - widthGlobe / 2 + margin, heightMap - heightGlobe / 2 + margin] + ')'),
widthGlobe, heightGlobe, cfg
);
var info = new HtmlInfo(d3.select('body').append('div')
.style('position', 'absolute')
.style('left', (2 * margin) + 'px')
.style('top', (2 * margin) + 'px')
.style('border', cfg.frameLineWidth + 'px solid ' + cfg.colors.frame)
.style('padding', (cfg.titleSize * 0.75) + 'px')
.style('min-width', (cfg.titleSize * 8) + 'px')
.style('background-color', cfg.colors.bg),
cfg
);
var controls = svg.append('g')
.attr('transform', 'translate(' + [margin * 1.5, heightMap + margin * .5 - 22] + ')');
Utils.svgControls(controls, setIndex, map.reset);
var list = new SvgList(svg.append('g')
.attr('transform', 'translate(' + [margin, (heightMap + margin + cfg.lineMid)] + ')'),
cfg, widthMap - widthGlobe / 2, widthList,
function (h) {
h = heightMap + h + 3 * margin;
svg.attr('height', h);
d3.select(self.frameElement).style('height', h + 'px');
},
selectedPlace
);
map.clickedPoint = function (p) {
selectedPlace(p);
};
map.onZoomed = globe.setZoom;
globe.onZoomed = map.setZoom;
globe.canZoom = function (i) {
var p = map.projection(i);
return !isNaN(p[0]) && !isNaN(p[1]);
};
if (self.frameElement) {
self.frameElement.focus();
}
d3.select('body').on('keydown', function () {
var i = null;
if (d3.event.keyCode === 37) {
i = -1;
} else if (d3.event.keyCode === 39) {
i = +1;
} else if (d3.event.keyCode === 27 && selected) {
selectedPlace(selected);
}
if (i !== null) {
setIndex(i);
}
});
d3.json('mercator-countries.json', function (topo) {
topojson.presimplify(topo);
map.countries.datum(topojson.mesh(topo, topo.objects.countries)).attr('d', map.path);
});
d3.json('mercator-land.json', function (topo) {
topojson.presimplify(topo);
map.land.datum(topojson.feature(topo, topo.objects.land)).attr('d', map.path);
});
d3.json('land.json', function (topo) {
globe.land.datum(topojson.feature(topo, topo.objects.land)).attr('d', globe.pathRaw);
});
d3.xml('/darosh/raw/2d12a584a14910032ab8/places.kml', function (xml) {
var geo = toGeoJSON.kml(xml);
Utils.parsePlaces(geo);
if (list) {
list.update(geo);
}
cfg.filtered = geo.features;
map.placesSelection = Utils.svgPlaces(map.g, geo, map.pathRaw, 1.25, cfg, selectedPlace);
});
function clickedLegend(d) {
map.root.selectAll('.' + d.key).style('display', d.off ? 'none' : null);
globe.root.selectAll('.' + d.key).style('display', d.off ? 'none' : null);
list.filter(legend.lookShapes);
if (!d.off) {
globe.update();
map.update();
}
}
function selectedPlace(d) {
list.selection(selected, d);
if (d === selected) {
d = null;
}
globe.selection = d;
if (d === null) {
globe.updateSelection();
}
info.update(d);
selected = d;
map.zoomTo(d, info.size);
}
function setIndex(i) {
var f = cfg.filtered;
var c = f.indexOf(selected);
c = c === -1 ? 0 : (c + i);
c = (c + f.length) % f.length;
if (list) {
list.selection(selected, f[c]);
}
info.update(f[c]);
globe.selection = f[c];
setTimeout(function () {
map.zoomTo(f[c], info.size);
}, 50);
selected = f[c];
}
})();
</script>
</body>
function HtmlInfo(root, cfg) {
'use strict';
var self;
var previous;
var a = root.append('a')
.style('font-size', (cfg.titleSize * 1.25) + 'px')
.style('line-height', 1)
.style('font-weight', 'bold')
.style('color', '#333')
.attr('href', 'http://bl.ocks.org/darosh/7b816a50e66bb62208a7')
.attr('target', '_blank')
.text('Planetary Grid');
root = root.append('div')
.style('display', 'none');
var b = root.append('b')
.style('display', 'block')
.style('font-size', cfg.titleSize + 'px');
var s = b.append('svg')
.attr('width', cfg.titleSize * 1.6)
.attr('height', cfg.titleSize * 1.6)
.style('float', 'left')
.style('margin-top', (-cfg.titleSize * 0.25) + 'px')
.style('margin-left', (-cfg.titleSize * 0.25) + 'px')
.style('margin-right', (cfg.titleSize * 0.25) + 'px')
.append('path')
.style('stroke-width', 2)
.attr('transform', 'translate(' + [cfg.titleSize * 1.6 / 2, cfg.titleSize * 1.6 / 2] + ')');
var t = b.append('span');
var c = root.append('small')
.style('display', 'block')
.style('text-align', 'right')
.style('margin-top', (cfg.titleSize / 4) + 'px')
.style('margin-bottom', (cfg.titleSize / 2) + 'px');
var d = root.append('a')
.style('display', 'block')
.attr('target', '_blank')
.text('Wikipedia');
var e = root.append('a')
.style('display', 'block')
.attr('target', '_blank')
.text('Google');
var f = root.append('a')
.style('display', 'block')
.attr('target', '_blank')
.text('Google Maps');
var g = root.append('a')
.style('display', 'block')
.attr('target', '_blank')
.text('Google Earth');
function update(feature) {
if (previous === feature) {
return;
}
previous = feature;
if (feature) {
t.text(feature.properties.name);
s
.attr('class', feature.coordinates ? 'legend-symbol ' + feature.type : 'symbol ' + feature.properties.description)
.attr('d', function () {
var s = cfg.sizes[feature.properties.description || 'place'] * cfg.titleSize / 6;
return d3.svg.symbol().size(s).type(cfg.symbols[feature.properties ? feature.properties.description : 'circle'])();
});
if (feature.type !== 'Feature') {
d.style('display', 'none');
e.style('display', 'none');
} else {
d.style('display', 'block');
e.style('display', 'block');
}
var coo = [feature.geometry.coordinates[1], feature.geometry.coordinates[0]];
coo[0] = d3.round(coo[0], 3);
coo[1] = d3.round(coo[1], 3);
c.text(d3.round(coo[0], 2) + ', ' + d3.round(coo[1], 2));
d.attr('href', 'https://wikipedia.org/wiki/Special:Search/' + feature.properties.name);
e.attr('href', 'https://www.google.com/search?q=' + feature.properties.name);
f.attr('href', 'https://www.google.com/maps/@' + coo[0] + ',' + coo[1] + ',12z');
g.attr('href', 'https://www.google.com/maps/@' + coo[0] + ',' + coo[1] + ',512m/data=!3m1!1e3');
a.style('display', 'none');
root.style('display', null);
self.size = root.node().getBoundingClientRect();
} else {
a.style('display', null);
root.style('display', 'none');
}
}
return self = {
update: update
};
}
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.
function SvgLegend(root, cfg, clicked) {
'use strict';
var shapes = cfg.symbols;
var data = d3.map(shapes).entries().filter(function (d) {
return d.key !== 'undefined'
});
data.forEach(function (v) {
v.classed = 'symbol ' + v.key;
v.symbol = true;
});
data = data.concat([
{key: 'cool-line', value: 'circle', path: 'M-10,0 L10,0'},
{key: 'hot-line', value: 'circle', path: 'M-10,0 L10,0'},
{key: 'balanced-line', value: 'circle', path: 'M-10,0 L10,0'},
{key: 'cool-point', value: 'circle', classed: 'cool-point', off: true},
{key: 'hot-point', value: 'circle', classed: 'hot-point', off: true},
{key: 'balanced-point', value: 'circle', classed: 'balanced-point', off: true},
{key: 'graticule', value: 'circle', path: 'M-8.25,0 L8.25,0', off: true},
{key: 'map', value: 'square', classed: 'map'}
]);
var l = root.selectAll('legend').data(data);
var g = l.enter().append('g')
.attr('class', 'legend')
.attr('transform', function (d, i) {
return 'translate(' + [0, i * cfg.lineHeight] + ')';
})
.style('opacity', function (d) {
return d.off ? 0.25 : null;
})
.on('click', function (d) {
d.off = !d.off;
d3.select(this).style('opacity', d.off ? 0.25 : null);
clicked(d);
});
g.append('path')
.attr('class', function (d) {
return d.classed || d.key;
})
.style('stroke-width', function (d) {
return (d.key === 'map') ? 1.5 : ((d.path || d.classed) && !d.symbol) ? 2 : 1;
})
.attr('transform', 'translate(' + [cfg.fontSize / 2, cfg.lineMid] + ')')
.attr('d', function (d) {
return d.path || d3.svg.symbol().size(cfg.sizes[d.key]).type(d.value)();
});
g.append('text')
.attr('dy', cfg.fontSize)
.attr('dx', cfg.fontSize + cfg.lineMid)
.text(function (d) {
return d.key.replace('-', ' ');
});
var lookShapes = {};
var lookFilter = {};
data.forEach(function (d) {
lookFilter[d.key] = d;
if (shapes[d.key]) {
lookShapes[d.key] = d;
}
});
return {
data: data,
lookShapes: lookShapes,
lookFilter: lookFilter
};
}
function SvgList(root, cfg, width, minItemWidth, updated, clicked) {
'use strict';
var self;
var shapes = cfg.symbols;
var cols = Math.floor(width / minItemWidth);
var itemWidth = Math.floor((width + cfg.fontSize) / cols);
var data;
var previousData = [];
function selection(o, n) {
root.selectAll('g').data(o ? [o, n] : [n], function (d) {
return d && d.properties ? d.properties.id : null;
})
.style('font-weight', function (d) {
return ((d === n) && (n !== o)) ? 'bold' : null;
})
.selectAll('text')
.each(wrap);
}
function update(topo) {
data = topo.features;
self.filtered = data;
var lastRow = Math.ceil(data.length / cols);
var height = lastRow * cfg.lineHeight;
updated(height);
enter(data, previousData);
previousData = data;
}
function filter(l) {
var filtered = data.filter(function (d) {
var t = d.properties.description;
return !l[t].off;
});
enter(filtered, previousData);
previousData = filtered;
cfg.filtered = filtered;
}
function enter(filtered, previousData) {
var lastRow = Math.ceil(filtered.length / cols);
var g = root.selectAll('g').data(filtered, function (d) {
return d.properties.id;
});
g.exit().remove();
g.transition().attr('transform', function (d, i) {
var row = i % lastRow;
var col = (i - row) / lastRow;
return 'translate(' + [col * itemWidth, row * cfg.lineHeight] + ')';
});
var a = g.enter().append('g')
.attr('class', 'item')
.style('opacity', previousData.length ? 0 : 1)
.attr('transform', function (d, i) {
var row = i % lastRow;
var col = (i - row) / lastRow;
return 'translate(' + [col * itemWidth, row * cfg.lineHeight] + ')';
})
.on('click', clicked);
a.transition().delay(previousData.length ? 250 : 0)
.style('opacity', 1);
a.append('path')
.attr('class', function (d) {
return 'symbol ' + d.properties.description;
})
.attr('transform', 'translate(' + [cfg.fontSize / 2, cfg.lineMid] + ')')
.attr('d', function (d) {
var s = cfg.sizes[d.properties.description];
return d3.svg.symbol().size(s).type(shapes[d.properties.description])();
});
a.append('text')
.attr('dy', cfg.fontSize)
.attr('dx', cfg.fontSize + cfg.lineMid)
.text(function (d) {
return d.properties.name;
})
.each(wrap);
}
function wrap() {
var self = d3.select(this);
var text = self.data()[0].properties.name;
self.text(text);
var textLength = self.node().getComputedTextLength();
while (textLength > (itemWidth - 50) && text.length > 0) {
text = text.slice(0, -1);
self.text(text + '…');
textLength = self.node().getComputedTextLength();
}
self.text(self.text().replace(' …', '…'));
}
self = {
update: update,
filter: filter,
selection: selection
};
return self;
}
function SvgMap(svg, width, height, margin, cfg) {
'use strict';
var maxScale = 3;
var self;
var focused;
var arrowStart;
var prevScale;
var referenceSize = Math.sqrt(height * height + width * width) * cfg.durationScale;
var clip = d3.geo.clipExtent().extent([[-width / 2, -height / 2], [width / 2, height / 2]]);
var projection = d3.geo.mercator()
.translate([0, 0])
.precision(0)
.scale(width / 2 / Math.PI);
var simplify = d3.geo.transform({
point: function (x, y, z) {
if (z >= projectionSimplified.area) {
this.stream.point(~~(x * width), ~~(y * width));
}
}
});
var round = d3.geo.transform({
point: function (x, y) {
this.stream.point(~~(x * 10) / 10, ~~(y * 10) / 10);
}
});
var projectionRounded = {
stream: function (s) {
return projection.stream(round.stream(s));
},
baseArea: 4e-3 / width
};
var projectionLinesRounded = {
stream: function (s) {
return projectionLines.stream(round.stream(s));
},
baseArea: 4e-3 / width
};
var projectionSimplified = {
stream: function (s) {
return simplify.stream(clip.stream(s));
},
baseArea: 4e-3 / width
};
projectionSimplified.area = projectionSimplified.baseArea;
var projectionLines = d3.geo.mercator()
.rotate([cfg.shift, 0, 0])
.translate([0, 0])
.precision(1)
.scale(width / 2 / Math.PI);
var path = d3.geo.path()
.projection(projectionSimplified);
var pathRaw = d3.geo.path()
.projection(projection);
var pathRawRounded = d3.geo.path()
.projection(projectionRounded);
var pathLines = d3.geo.path()
.projection(projectionLines);
var pathLinesRounded = d3.geo.path()
.projection(projectionLinesRounded);
var zoom = d3.behavior.zoom()
.scaleExtent([1, maxScale])
.on('zoom', zoomed);
var defs = svg.append('defs');
defs.append('clipPath')
.attr('id', 'clip-map')
.append('rect')
.attr("x", -width / 2)
.attr("y", -height / 2)
.attr("width", width)
.attr("height", height);
defs.append('clipPath')
.attr('id', 'clip-arrow')
.append('rect')
.attr("x", margin)
.attr("y", margin)
.attr("width", width)
.attr("height", height);
var clipGroup = svg.append("g")
.attr("transform", "translate(" + [width / 2 + margin, height / 2 + margin] + ")")
.style('clip-path', 'url(#clip-map)')
.call(zoom);
clipGroup.append("rect")
.attr("class", "map water")
.attr("x", -width / 2)
.attr("y", -height / 2)
.attr("width", width)
.attr("height", height);
clipGroup.append("rect")
.attr("class", "overlay")
.attr("x", -width / 2)
.attr("y", -height / 2)
.attr("width", width)
.attr("height", height);
var g = clipGroup.append('g');
var graticulePath = g.append('path')
.datum(d3.geo.graticule()())
.attr('class', 'graticule')
.style('display', 'none')
.attr('d', pathRaw);
var land = g.append('path').attr('class', 'map land');
var countries = g.append('path').attr('class', 'map country');
var h = d3.geo.hexakisIcosahedron;
var coolLines = Utils.svgLines(g, pathLines, h.icosahedronEdges(), 'cool-line');
var hotLines = Utils.svgLines(g, pathLines, h.hexakisCenterEdges(), 'hot-line');
var balancedLines = Utils.svgLines(g, pathLines, h.hexakisSideEdges(), 'balanced-line');
var coolPointsData = Utils.pointsToFeatures(h.icosahedronPoints(), 'cool-point', 'Cool point', cfg.shift);
var hotPointsData = Utils.pointsToFeatures(h.hexakisCenterPoints(), 'hot-point', 'Hot point', cfg.shift);
var balancedPointsData = Utils.pointsToFeatures(h.hexakisCrossPoints(), 'balanced-point', 'Balanced point', cfg.shift);
Utils.svgPoints(g, coolPointsData, 'cool-point', projectionLines, clickedPoint, 'none');
Utils.svgPoints(g, hotPointsData, 'hot-point', projectionLines, clickedPoint, 'none');
Utils.svgPoints(g, balancedPointsData, 'balanced-point', projectionLines, clickedPoint, 'none');
function clickedPoint(d) {
self.clickedPoint(d);
}
var placesGroup = g.append('g');
var selectedCircleGroup = svg.append('g')
.style('display', 'none')
.style('clip-path', 'url(#clip-arrow)');
var selectedCircle = selectedCircleGroup.append('circle')
.attr('cx', margin)
.attr('cy', margin)
.attr('r', 20)
.attr('class', 'border');
var selectedArrow = selectedCircleGroup.append('line')
.attr('x1', margin * 4)
.attr('y1', margin * 4)
.attr('class', 'border');
svg.append("rect")
.attr("class", "border")
.style('stroke-width', cfg.frameLineWidth)
.attr("transform", "translate(" + [width / 2 + margin, height / 2 + margin] + ")")
.attr("x", -width / 2 + cfg.frameLineWidth / 2)
.attr("y", -height / 2 + cfg.frameLineWidth / 2)
.attr("width", width - cfg.frameLineWidth)
.attr("height", height - cfg.frameLineWidth);
function getFixedZoom(t, s) {
var S = (width - height) / 2;
t[0] = Math.min(width / 2 * (s - 1), Math.max(width / 2 * (1 - s), t[0]));
t[1] = Math.min(height / 2 * (s - 1) + S * s, Math.max(height / 2 * (1 - s) - S * s, t[1]));
}
function fixZoom() {
var t = zoom.translate();
var s = zoom.scale();
getFixedZoom(t, s);
zoom.translate(t);
}
function roundTranslate(t) {
t[0] = Math.round(t[0]);
t[1] = Math.round(t[1]);
}
function zoomed() {
fixZoom();
var t = zoom.translate();
var s = zoom.scale();
var i = projection.invert([t[0] / s, t[1] / s]);
update();
if (self.onZoomed) {
self.onZoomed(t, s, i);
}
}
function update(running) {
var t = zoom.translate();
var s = Math.min(maxScale, d3.round(zoom.scale(), 6));
roundTranslate(t);
g.attr('transform', 'translate(' + t + ')scale(' + s + ')');
var ps = (running === true) ? 1 : zoom.scale();
var c = translateToCenter(t, s);
var ce = [[
-width / 2 + c[0] - width / 2 / s,
-height / 2 + c[1] - height / 2 / s], [
-width / 2 + c[0] + width / 2 / s,
-height / 2 + c[1] + height / 2 / s]];
clip.extent(ce);
projectionLines.precision(running ? 1 : Math.sqrt(1 / 2) / s / s);
projectionSimplified.area = projectionSimplified.baseArea / ps / ps;
projection.clipExtent(ce);
projectionLines.clipExtent(ce);
var localPathRaw, localPathLines;
if (running) {
localPathRaw = pathRawRounded;
localPathLines = pathLinesRounded;
} else {
localPathRaw = pathRaw;
localPathLines = pathLines;
}
if (!cfg.filter['graticule'].off) {
graticulePath.attr('d', localPathRaw);
}
if (!cfg.filter['cool-line'].off) {
coolLines.attr('d', localPathLines);
}
if (!cfg.filter['hot-line'].off) {
hotLines.attr('d', localPathLines);
}
if (!cfg.filter['balanced-line'].off) {
balancedLines.attr('d', localPathLines);
}
if (!cfg.filter.map.off) {
land.attr('d', path);
countries.attr('d', path);
}
if (prevScale !== s) {
g.style('stroke-width', 1 / s);
if (!cfg.filter['graticule'].off) {
graticulePath.style('stroke-dasharray', (2 / s ) + ',' + (3 / s));
}
//self.placesSelection.attr('transform', function (d) {
// return d.translate + 'scale(' + 1 / s + ')';
//});
self.placesSelection.defs.attr('transform', function () {
return 'scale(' + 1 / s + ')';
});
prevScale = s;
}
if (focused) {
var f = [width / 2 + focused[0] * s + t[0], height / 2 + focused[1] * s + t[1]];
var l = [[arrowStart.width / 2 + 2 * margin, arrowStart.height / 2 + 2 * margin], [f[0] + margin, f[1] + margin]];
var d = Math.sqrt(Math.pow(l[1][0] - l[0][0], 2) + Math.pow(l[1][1] - l[0][1], 2));
var r = (d - 20) / d;
l[1][0] = l[0][0] + r * (l[1][0] - l[0][0]);
l[1][1] = l[0][1] + r * (l[1][1] - l[0][1]);
selectedCircle
.attr('transform', 'translate(' + [d3.round(f[0]), d3.round(f[1])] + ')');
selectedArrow
.attr('x1', l[0][0])
.attr('y1', l[0][1])
.attr('x2', l[1][0])
.attr('y2', l[1][1]);
selectedCircleGroup.style('display', null);
} else {
selectedCircleGroup.style('display', 'none');
}
}
function setZoom(t, s, i) {
var p = projection(i);
zoom.scale(s);
zoom.translate([p[0] * s, p[1] * s]);
fixZoom();
update();
}
function translateToCenter(t, s) {
return [-t[0] / s + width / 2, -t[1] / s + height / 2];
}
function centerToTranslate(c, s) {
return [s * (-c[0] + width / 2), s * (-c[1] + height / 2)];
}
function zoomTransition(p, toScale) {
var fromScale = zoom.scale();
var fromTranslate = zoom.translate();
var toTranslate = [-p[0] * toScale, -p[1] * toScale];
getFixedZoom(toTranslate, toScale);
var from = translateToCenter(fromTranslate, fromScale);
from[2] = referenceSize / fromScale;
var to = translateToCenter(toTranslate, toScale);
to[2] = referenceSize / toScale;
var zi = d3.interpolateZoom(from, to);
var dur = Math.min(cfg.durationMax, Math.max(cfg.durationMin, zi.duration * cfg.durationSpeed));
d3.transition().duration(dur).tween('tween', tween);
function tween() {
return function (t) {
var z = zi(t);
var s = referenceSize / z[2];
var tr = centerToTranslate(z, s);
getFixedZoom(tr, s);
zoom.translate(tr);
zoom.scale(s);
update(t < 1);
if (self.onZoomed) {
var i = projection.invert([tr[0] / s, tr[1] / s]);
self.onZoomed(z, s, i, t < 1);
}
};
}
}
function zoomTo(d, ast) {
if (!d) {
focused = null;
selectedCircleGroup.style('display', 'none');
return;
}
var c = d.geometry ? [d.geometry.coordinates[0], d.geometry.coordinates[1]] : [d[0] + cfg.shift, d[1]];
var p = projection(c);
arrowStart = ast;
focused = p;
zoomTransition(p, maxScale)
}
function reset() {
zoomTransition(projection([0, 0]), 1);
}
self = {
root: svg,
land: land,
countries: countries,
path: path,
pathRaw: pathRaw,
g: placesGroup,
projection: projection,
setZoom: setZoom,
zoomTo: zoomTo,
reset: reset,
update: update
};
return self;
}
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.
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.
body {
margin: 0;
padding: 0;
font-family: Arial, Helvetica, sans-serif;
font-size: 16px;
line-height: 19px;
text-rendering: optimizeLegibility;
color: rgba(0,0,0,0.87);
}
svg, canvas {
display: block;
}
a {
text-decoration: none;
color: #1f78b4;
}
a:hover {
text-decoration: underline;
color: #e31a1c;
}
a:visited {
color: #333;
}
small {
color: rgba(0,0,0,0.64);
}
.no-select {
user-select: none;
}
.point {
pointer-events: all;
cursor: pointer;
}
.water {
fill: #def4ff;
}
.border {
fill: none;
stroke-width: 2.5;
stroke: #444;
stroke-opacity: 1;
}
.overlay {
fill: none;
pointer-events: all;
}
.overlay-white {
fill: #fff;
pointer-events: all;
}
.country {
stroke: #000;
stroke-opacity: .33;
fill: none;
}
.land {
fill: rgba(255, 255, 255, 0.87);
stroke: 0;
}
.graticule {
fill: none;
stroke: #bbb;
stroke-opacity: 0.66;
}
.cool-point {
fill: none;
stroke: #1f78b4;
stroke-opacity: 0.85;
}
.hot-point {
fill: none;
stroke: #e31a1c;
stroke-opacity: 0.86;
}
.balanced-point {
fill: none;
stroke: #333;
stroke-opacity: 0.85;
}
.cool-line {
fill: none;
stroke: #1f78b4;
stroke-opacity: .85;
}
.hot-line {
fill: none;
stroke: #e31a1c;
stroke-opacity: .85;
}
.balanced-line {
fill: none;
stroke: #333;
stroke-opacity: .85;
}
.symbol {
stroke-width: 1;
stroke: #000;
cursor: pointer;
}
.place {
fill: #888;
fill-opacity: 0.33;
}
.pyramid {
fill: #ffff4d;
}
.megalith {
fill: #299ae6;
}
.temple {
fill: #ff7f00;
}
.mound {
fill: #90de43;
}
.volcano {
fill: #e31a1c;
}
.graticule {
stroke-dasharray: 2, 3;
}
.legend {
cursor: pointer;
pointer-events: all;
}
.item {
cursor: pointer;
}
.control {
cursor: pointer;
}
.legend-symbol {
stroke-width: 2;
}
.legend .map {
fill: #def4ff;
stroke: #333;
}
var Utils = (function () {
'use strict';
function parsePlaces(geo) {
geo.features.sort(function (a, b) {
return a.properties.name.localeCompare(b.properties.name);
});
geo.features.forEach(function (v, k) {
v.properties.id = k;
v.properties.description = v.properties.description || 'place';
});
}
function pointsToFeatures(points, type, name, shift) {
return points.coordinates.map(function (p) {
var sh = p[0] + shift;
sh = (sh > 180) ? (sh - 360) : sh;
return {
coordinates: p,
geometry: {
coordinates: [sh, p[1]],
type: 'Point'
},
type: type,
properties: {
name: name
}
}
});
}
function trans(path) {
return function (d) {
var p = path.centroid(d);
if (isNaN(p[0]) || isNaN(p[1])) {
p[0] = -1e10;
p[1] = -1e10;
}
return d.translate = 'translate(' + p + ')';
};
}
function addSymbols(root, cfg, scale) {
var g = root.append('defs');
d3.keys(cfg.symbols).forEach(function (d) {
g.append('path')
.attr('id', d)
.attr('class', 'symbol ' + d)
.attr('d', d3.svg.symbol()
.size(scale * scale * cfg.sizes[d])
.type(cfg.symbols[d])());
});
return g.selectAll('path');
}
function svgPlaces(root, geo, t, scale, cfg, clicked) {
t = trans(t);
var defs = addSymbols(root, cfg, scale);
var places = root.selectAll()
.data(geo.features)
.enter()
.append('use')
.attr('transform', t)
.attr('xlink:href', function (d) {
return '#' + d.properties.description;
})
.on('click', clicked);
return {
defs: defs,
places: places
}
}
function _svgPlaces(root, geo, t, scale, cfg, clicked) {
t = trans(t);
return root.selectAll()
.data(geo.features)
.enter()
.append('path')
.attr('transform', t)
.attr('class', function (d) {
return 'symbol ' + d.properties.description;
})
.attr('d', function (d) {
return d3.svg.symbol()
.size(scale * cfg.sizes[d.properties.description])
.type(cfg.symbols[d.properties.description])();
})
.on('click', clicked);
}
function svgLines(root, path, data, type) {
return root.append('path')
.datum(data)
.attr('class', 'line ' + type)
.attr('d', path);
}
function svgPoints(root, points, type, projection, clicked, display) {
points = points.filter(function (d) {
var p = projection(d.coordinates);
if (Math.abs(p[0]) !== Infinity && Math.abs(p[1]) !== Infinity) {
d.projected = p;
return true;
}
});
var p = root.selectAll('.point.' + type).data(points);
p.enter().append('circle')
.attr('class', 'point ' + type)
.style('display', display)
.attr('r', 5)
.attr('transform', function (d) {
return 'translate(' + d.projected + ')';
})
.on('click', clicked);
}
function svgControls(root, setIndex, reset) {
root.append('path')
.attr('class', 'control')
.attr('d', d3.svg.symbol().size(120).type('triangle-up'))
.attr('transform', 'translate(60,10)rotate(90)')
.on('click', function () {
setIndex(+1);
});
root.append('path')
.attr('class', 'control')
.attr('d', d3.svg.symbol().size(120).type('circle'))
.attr('transform', 'translate(35,10)')
.on('click', reset);
root.append('path')
.attr('class', 'control')
.attr('d', d3.svg.symbol().size(120).type('triangle-up'))
.attr('transform', 'translate(10,10)rotate(-90)')
.on('click', function () {
setIndex(-1);
});
}
return {
svgLines: svgLines,
svgPoints: svgPoints,
svgPlaces: svgPlaces,
svgControls: svgControls,
parsePlaces: parsePlaces,
pointsToFeatures: pointsToFeatures
};
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment