- Planetary grid by William Becker and Beth Hagens
- Places mostly based on Simon E. Davies design
- Location data based on this map
- Controls: Left/Right and Esc
Canvas version: Planetary Grid Browser II
Canvas version: Planetary Grid Browser II
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 | |
}; | |
} |
{"type":"Topology","objects":{"land":{"type":"GeometryCollection","bbox":[-180,-90.00000000000003,180.00000000000014,83.64513000000002],"geometries":[{"type":"Polygon","arcs":[[0,1]]},{"type":"Polygon","arcs":[[2,3]]},{"type":"Polygon","arcs":[[4,-1,5,-3,6]]},{"type":"Polygon","arcs":[[7,8,9,10]]},{"type":"Polygon","arcs":[[11]]},{"type":"Polygon","arcs":[[12]]},{"type":"Polygon","arcs":[[13]]},{"type":"Polygon","arcs":[[14]]},{"type":"Polygon","arcs":[[15]]},{"type":"Polygon","arcs":[[16,17]]},{"type":"Polygon","arcs":[[18,19,20]]},{"type":"Polygon","arcs":[[21]]},{"type":"Polygon","arcs":[[22]]},{"type":"Polygon","arcs":[[23,24]]},{"type":"Polygon","arcs":[[25]]},{"type":"Polygon","arcs":[[26]]},{"type":"Polygon","arcs":[[27,28,29,30]]},{"type":"Polygon","arcs":[[31,-29,32]]},{"type":"Polygon","arcs":[[33,34]]},{"type":"Polygon","arcs":[[35]]},{"type":"Polygon","arcs":[[36]]},{"type":"Polygon","arcs":[[37,38,39,40]]},{"type":"Polygon","arcs":[[41,42]]},{"type":"Polygon","arcs":[[43,-40,44,45]]},{"type":"Polygon","arcs":[[46]]},{"type":"Polygon","arcs":[[47]]},{"type":"Polygon","arcs":[[48,49]]},{"type":"Polygon","arcs":[[50]]},{"type":"Polygon","arcs":[[51]]},{"type":"Polygon","arcs":[[52]]},{"type":"Polygon","arcs":[[53]]},{"type":"Polygon","arcs":[[54,55,56,57,58,59,60,61,62,-9,63,64,65,66,67,68,69,70,71,72,73,74,75]]},{"type":"Polygon","arcs":[[76,77,78,-74,79,80,-71,81,82,83]]},{"type":"Polygon","arcs":[[-77,84]]},{"type":"Polygon","arcs":[[85,86,87,88,89]]},{"type":"Polygon","arcs":[[90]]},{"type":"Polygon","arcs":[[91,92]]},{"type":"Polygon","arcs":[[93]]},{"type":"Polygon","arcs":[[94]]},{"type":"Polygon","arcs":[[95,96,97]]},{"type":"Polygon","arcs":[[-49,98,99,100,-42,101,102,103,104,105],[106]]},{"type":"Polygon","arcs":[[-98,107,108]]},{"type":"Polygon","arcs":[[109,110]]},{"type":"Polygon","arcs":[[-110,111]]},{"type":"Polygon","arcs":[[112]]},{"type":"Polygon","arcs":[[113,114,115,116,117,118,119]]},{"type":"Polygon","arcs":[[120]]}]}},"arcs":[[[56,25],[-2,0]],[[54,25],[2,0]],[[77,29],[0,-2]],[[77,27],[-4,-2],[2,4],[2,0]],[[42,23],[12,-3],[0,5]],[[56,25],[21,-2],[0,4]],[[77,29],[1,4],[4,3],[0,-3],[-3,-1],[3,-5],[0,-5],[-6,-4],[-5,-4],[14,-4],[5,1],[14,1],[-4,4],[9,4],[7,7],[11,0],[4,2],[8,0],[6,-2],[4,4],[3,-2],[11,6],[3,-3],[6,0],[1,-7],[3,4],[6,4],[12,0],[2,2],[23,-2],[7,0],[5,-2],[11,-4],[0,-4],[-4,-3],[-3,-8],[9,-5],[-219,0],[-7,2],[5,5],[-7,2],[7,4],[9,2],[10,1]],[[74,52],[0,0]],[[74,52],[1,0]],[[75,52],[2,2]],[[77,54],[2,-3],[-2,-2],[-3,3]],[[225,71],[1,0],[0,-4],[-1,1],[0,3]],[[245,70],[-3,-4],[0,-4],[-2,2],[5,6]],[[245,70],[0,9],[1,-4],[2,0],[-1,-4],[-2,-1]],[[159,110],[0,-7],[-1,-1],[0,-5],[-1,-4],[-2,0],[-1,4],[1,1],[0,8],[2,0],[1,5],[1,-1]],[[224,109],[1,-5],[3,-5],[2,-6],[0,-3],[1,-1],[-1,-3],[0,-4],[-1,0],[-1,-7],[-6,-1],[-3,8],[-1,-1],[-3,3],[-5,-2],[0,-2],[-2,1],[-3,-2],[0,5],[-2,5],[0,9],[6,4],[0,2],[1,3],[0,2],[5,1],[1,3],[3,-1],[-1,-4],[4,-4],[0,6],[1,2],[1,-3]],[[205,117],[-4,0],[-5,2],[2,2]],[[198,121],[3,-1],[4,-3]],[[226,120],[4,3]],[[230,123],[0,-1]],[[230,122],[-1,-2],[-3,0]],[[226,120],[0,-2],[3,-4],[-3,1],[0,2],[-2,1],[0,-3],[-5,2],[0,4],[0,2],[-3,0],[0,6],[2,-5],[1,2],[4,-2],[2,0],[0,-3],[1,-1]],[[210,131],[-1,-3],[0,-4],[-2,-3],[0,8],[1,2],[2,0]],[[198,121],[-3,2],[-2,9],[-4,4],[3,1],[2,-5],[1,0]],[[195,132],[1,-4],[2,-2],[0,-5]],[[206,132],[-1,-8],[-4,0],[-1,2],[0,6],[3,2],[0,2],[3,3],[0,-7]],[[210,142],[0,1],[2,-3],[-2,-3],[0,1],[0,4]],[[210,142],[-1,2]],[[209,144],[1,1]],[[210,145],[0,0]],[[210,145],[0,-3]],[[210,146],[0,-1]],[[209,144],[1,2]],[[210,147],[-1,1]],[[209,148],[-1,1],[0,7],[1,-1],[0,-5],[1,-3]],[[74,158],[3,-1],[0,-2],[-4,0],[1,3]],[[69,162],[4,-4],[-2,0],[-2,4]],[[218,178],[-2,-1]],[[216,177],[0,1]],[[216,178],[1,0]],[[217,178],[1,0]],[[135,184],[0,-1]],[[135,183],[0,1]],[[218,178],[-1,0]],[[216,178],[0,-1]],[[216,177],[0,3],[2,0],[1,3],[2,0],[1,4],[0,-8],[-4,-1]],[[224,191],[0,-2],[-2,1],[1,4],[1,-3]],[[85,203],[0,-3],[3,0],[0,-4],[-4,0],[1,7]],[[222,204],[0,1]],[[222,205],[2,0],[0,-5],[-1,-2],[-1,6]],[[120,204],[-2,-1],[1,5],[2,-1],[-1,-3]],[[122,213],[1,-4],[3,-6],[-3,-3],[-2,3],[1,3],[-2,6],[2,1]],[[114,224],[1,-2],[-3,-2],[-3,1],[5,3]],[[0,228],[6,-4],[-3,-3],[-3,3],[0,4]],[[66,229],[2,-4],[-3,-1],[-6,-8],[2,-5],[6,-4],[2,-5],[1,5],[2,2],[-2,4],[0,5]],[[70,218],[3,0],[4,-5],[3,1],[3,-6],[3,-2],[-1,-3]],[[85,203],[-2,-3],[-3,0]],[[80,200],[-1,-2],[1,-2]],[[80,196],[1,-1]],[[81,195],[2,0],[-4,-4],[1,3],[-4,-3],[0,-2],[-3,-2],[-1,-8],[-4,-5],[1,-4],[0,-5],[-2,3],[0,4],[-4,-1],[-3,1],[-3,-3],[0,-10],[1,-3],[2,-1],[2,2],[0,2],[2,1],[0,-5],[-1,-3],[4,-1],[0,-8],[5,-1],[0,2],[5,1],[0,-1],[5,0]],[[82,144],[0,-1]],[[82,143],[2,-4],[2,-2],[3,-1],[1,-5],[-1,-2],[2,0],[3,-3],[3,-2],[3,-2],[0,-7],[-2,-3],[0,-5],[-1,-3],[-1,-7],[-4,-2],[-1,-3],[0,-4],[-3,-5],[-1,-4],[-3,1],[1,-2],[0,-4],[-3,-1],[-3,-3],[2,-2],[-2,-1],[0,-6],[-2,-4],[0,-3]],[[77,54],[-2,-2]],[[74,52],[-2,3],[0,7],[1,1],[0,6],[1,3],[0,6],[1,2],[0,9],[1,3],[0,11],[-2,3],[-2,1],[0,3],[-2,4],[0,4],[-2,5],[1,1],[0,6],[2,3],[0,6],[-2,3],[0,-3],[-4,4],[-1,5],[-2,0],[-2,4],[-3,0],[-4,3],[-3,9],[-4,8],[1,-5],[2,-5],[-1,0],[-1,4],[-2,2],[1,2],[-2,2],[-2,6],[-1,0],[-2,6],[0,14]],[[39,198],[0,1]],[[39,199],[-1,1]],[[38,200],[-3,2]],[[35,202],[-3,7],[-5,6],[-5,1],[-4,-1],[0,-3]],[[18,212],[-7,-5],[5,6],[-5,0],[1,2],[-2,1]],[[10,216],[0,2],[2,2],[-4,2],[3,3],[-2,2],[4,4],[12,-1],[3,-2],[14,1],[2,-1]],[[44,228],[1,0]],[[45,228],[1,-1]],[[46,227],[4,1]],[[50,228],[0,0]],[[50,228],[4,-2],[5,1],[0,4]],[[59,231],[6,-5],[1,3]],[[50,234],[2,-1]],[[52,233],[2,-4]],[[54,229],[-4,-1]],[[50,228],[-4,-1]],[[46,227],[-1,1]],[[44,228],[-2,5]],[[42,233],[3,1]],[[45,234],[5,-3],[0,3]],[[50,234],[2,-1]],[[69,233],[1,0]],[[70,233],[2,0]],[[72,233],[6,-3],[4,-6],[-3,-6],[-4,0],[-2,4],[0,5]],[[73,227],[-7,2]],[[66,229],[-4,2],[1,4],[6,-2]],[[59,231],[-1,4],[4,0],[-3,-4]],[[45,234],[-3,-1]],[[42,233],[-5,-2],[4,5],[4,-2]],[[50,238],[-1,-2],[-7,1],[8,1]],[[164,230],[-4,1],[5,5],[-1,-6]],[[59,240],[1,-3]],[[60,237],[-2,3]],[[58,240],[1,0]],[[222,204],[0,-4],[-3,-5],[-2,-5],[-1,1],[-3,-4],[1,-8],[-2,-1],[0,5],[-2,3],[-4,-1],[1,-3],[2,1],[-2,-4],[2,-4],[0,-5],[-2,-4],[0,-2],[-5,-4],[-1,-2]],[[201,158],[-1,2],[-2,-1],[0,-4],[2,-4],[0,-5],[-4,-5],[-1,6],[-2,-2],[1,-6],[2,-2],[0,-4],[-1,-1]],[[195,132],[-1,6],[-2,3],[1,2],[0,5],[-1,1],[0,4],[-3,-1],[0,3],[-2,5],[-4,-3],[0,-2],[-3,-3],[0,-8],[-2,-3],[-1,1],[0,4],[-1,1],[-1,5],[0,8],[-2,-1],[0,2],[-2,2],[-1,3],[-4,-1],[-2,1],[0,2],[-2,-1],[-4,6],[0,-4],[2,-6],[3,3],[1,-3],[2,-1],[-2,-7],[-3,-4],[-1,0],[-4,-4],[-2,1],[0,6],[-2,4],[0,3],[-2,3],[0,3],[-2,4],[0,-5],[2,-5],[0,-5],[1,-3],[3,-5],[0,-3],[2,-1],[3,1],[0,-6],[-1,-4],[-3,-4],[-3,-7],[0,-12],[1,-1],[-1,-7],[-3,-2],[0,-9],[-2,0],[0,-5],[-2,-5],[-3,-3],[-1,1],[-4,-1],[0,5],[-2,4],[0,4],[-1,2],[0,4],[-1,2],[0,9],[1,1],[0,6],[-1,1],[0,4],[-2,2],[0,11],[-2,0],[-2,3],[-3,0],[0,-2],[-4,0],[-1,-1],[-3,4],[-1,4],[-2,3],[0,17],[1,1],[1,5],[3,3],[0,4],[2,2],[0,2],[3,0],[3,2],[6,0],[0,-5],[6,-5],[0,3],[2,1],[4,-3],[5,2],[0,6],[-4,0],[-2,2],[0,4],[6,2],[4,0],[-3,4],[2,3],[-8,-3],[0,-4],[-4,-3],[1,-5],[-2,1],[-1,3],[0,5],[-5,4],[1,-4],[3,-3],[-2,-4]],[[135,184],[0,3],[-2,0],[-2,6],[-7,-8],[-1,-3],[-5,0],[0,9],[3,0],[2,0],[1,4],[-3,4],[2,0],[7,7],[0,5],[2,-2]],[[132,209],[-1,-2],[8,1],[0,3],[5,5],[-5,0],[0,4],[3,2],[-1,2],[-5,-7],[0,-6],[-3,-3]],[[133,208],[0,1]],[[133,209],[-1,5],[-4,-1],[0,5],[4,4],[9,9],[9,-3],[3,-3],[-4,-1],[0,-3],[6,3],[0,3],[9,0],[2,2],[6,-2],[0,7],[3,-3],[10,4],[2,3],[8,2],[8,-3],[-3,-2],[12,0],[2,-4],[5,0],[3,3],[13,-5],[7,1],[-242,-2]],[[0,228],[249,-10],[-4,0],[-3,-3],[-5,-1],[0,-5],[-4,-7],[-1,7],[4,7],[-1,2],[-6,-5],[-6,1],[-5,-6],[4,-3]],[[158,187],[0,-3],[2,-2],[2,0],[-2,9],[1,3],[-1,2],[-3,-1],[1,-8]],[[58,240],[0,0]],[[58,240],[1,0]],[[47,240],[1,0]],[[48,240],[-1,0]],[[47,240],[1,0]],[[137,243],[-1,-4],[-4,3],[5,1]],[[78,244],[-6,-1],[-1,-4],[-6,-1],[-2,2]],[[63,240],[1,2]],[[64,242],[1,1]],[[65,243],[-1,1]],[[64,244],[-1,0]],[[63,244],[-2,2]],[[61,246],[21,2],[-4,-4]],[[78,244],[10,2],[10,3],[18,-3],[-5,-3],[0,-7],[-4,-8],[-11,-6],[-2,-7],[-3,1],[-3,5],[-1,4],[0,8],[-5,5],[-5,0],[-3,4],[4,2]]],"transform":{"scale":[1.4457831325301205,0.69737],"translate":[-180,-90]}} |
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; | |
} |
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 | |
}; | |
})(); |