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; }