function CanvasGlobe(ctx, width, height, cfg, overlay) { 'use strict'; var maxScale = 3; var self; var projectionGlobe = d3.geo .orthographic() .clipAngle(90) .precision(0) .translate([height / 2, height / 2]) .scale(height / 2); var projectionRaw = d3.geo .orthographic() .precision(1) .clipAngle(90) .translate([height / 2, height / 2]) .scale(height / 2); 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(1) .clipAngle(90) .translate([height / 2, height / 2]) .scale(height / 2); var pathRaw = d3.geo.path() .context(ctx) .projection(projectionRaw); var pathLines = d3.geo.path() .context(ctx) .projection(projectionLinesGlobe); var zoom = d3.behavior.zoom().scaleExtent([1, maxScale]) .on('zoomstart', zoomed) .on('zoom', zoomed) .on('zoomend', zoomed); var graticuleData = d3.geo.graticule()(); var h = d3.geo.hexakisIcosahedron; var coolLinesData = h.icosahedronEdges(); var hotLinesData = h.hexakisCenterEdges(); var balancedLinesData = h.hexakisSideEdges(); overlay.call(zoom); function draw(running) { projectionRaw.precision(running ? 2 : 1); projectionLinesGlobe.precision(running ? 2 : 1); ctx.save(); ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.lineWidth = 1; ctx.translate(ctx.canvas.width - width, ctx.canvas.height - height); // Water ctx.beginPath(); ctx.arc(width / 2, height / 2, width / 2 - cfg.frameLineWidth, 0, Math.PI * 2); ctx.closePath(); ctx.fillStyle = !cfg.filter.map.off ? cfg.colors.water : cfg.colors.bg; ctx.fill(); // Frame ctx.beginPath(); ctx.arc(width / 2, height / 2, width / 2 - cfg.frameLineWidth / 2, 0, Math.PI * 2); ctx.closePath(); ctx.strokeStyle = cfg.colors.frame; ctx.lineWidth = cfg.frameLineWidth; ctx.stroke(); // Clip ctx.beginPath(); ctx.arc(width / 2, height / 2, width / 2 - cfg.frameLineWidth, 0, Math.PI * 2); ctx.clip(); ctx.lineWidth = 0.5; if (!cfg.filter.graticule.off) { ctx.beginPath(); pathRaw(graticuleData); ctx.strokeStyle = cfg.colors.graticule; ctx.setLineDash(cfg.dash); ctx.stroke(); ctx.setLineDash([]); } if (!cfg.filter.map.off && self.landDatum) { ctx.beginPath(); pathRaw(self.landDatum); ctx.fillStyle = cfg.colors.land; ctx.strokeStyle = cfg.colors.border; ctx.fill(); ctx.stroke(); } ctx.lineWidth = 1; if (!cfg.filter['cool-line'].off) { ctx.beginPath(); pathLines(coolLinesData); ctx.strokeStyle = cfg.colors.cool; ctx.stroke(); } if (!cfg.filter['hot-line'].off) { ctx.beginPath(); pathLines(hotLinesData); ctx.strokeStyle = cfg.colors.hot; ctx.stroke(); } if (!cfg.filter['balanced-line'].off) { ctx.beginPath(); pathLines(balancedLinesData); ctx.strokeStyle = cfg.colors.balanced; ctx.stroke(); } if (self.selection) { var p = pathRaw.centroid(self.selection.geometry); if (!isNaN(p[0]) && !isNaN(p[1])) { ctx.beginPath(); ctx.arc(p[0], p[1], 7, 0, 2 * Math.PI); ctx.strokeStyle = cfg.colors.focus; ctx.lineWidth = 3; ctx.fillStyle = cfg.colors.selection; ctx.fill(); ctx.stroke(); } } ctx.restore(); } 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); projectionGlobe.rotate(pgR); projectionLinesGlobe.rotate(plgR); if (self.onZoomed) { var s = zoom.scale(); if (cfg.mraf) { requestAnimationFrame(function () { self.onZoomed(null, s, pgR); }); } else { self.onZoomed(null, s, pgR); } } } }, zoomend: function () { } })[d3.event.type](); } function setZoom(t, s, i) { zoom.scale(s); projectionLinesGlobe.rotate([i[0] + cfg.shift, i[1]]); projectionGlobe.rotate(i); projectionRaw.rotate(i); } self = { draw: draw, pathRaw: pathRaw, setZoom: setZoom }; return self; }