define(["leaflet", "jshashes"], function (L, jsHashes) { var MD5 = new jsHashes.MD5(); return L.TileLayer.Canvas.extend({ setData: function (d) { this.data = d; //pre-calculate start angles this.data.all().forEach(function (d) { var hash = MD5.hex(d.node.nodeinfo.node_id); d.startAngle = (parseInt(hash.substr(0, 2), 16) / 255) * 2 * Math.PI; }); this.redraw(); }, drawTile: function (canvas, tilePoint) { function getTileBBox(s, map, tileSize, margin) { var tl = map.unproject([s.x - margin, s.y - margin]); var br = map.unproject([s.x + margin + tileSize, s.y + margin + tileSize]); return [br.lat, tl.lng, tl.lat, br.lng]; } if (!this.data) { return; } var tileSize = this.options.tileSize; var s = tilePoint.multiplyBy(tileSize); var map = this._map; var margin = 50; var bbox = getTileBBox(s, map, tileSize, margin); var nodes = this.data.search(bbox); if (nodes.length === 0) { return; } var ctx = canvas.getContext("2d"); var radius = 3; var a = 1.2; var startDistance = 12; ctx.beginPath(); nodes.forEach(function (d) { var p = map.project([d.node.nodeinfo.location.latitude, d.node.nodeinfo.location.longitude]); var clients = d.node.statistics.clients; if (clients === 0) { return; } p.x -= s.x; p.y -= s.y; for (var orbit = 0, i = 0; i < clients; orbit++) { var distance = startDistance + orbit * 2 * radius * a; var n = Math.floor((Math.PI * distance) / (a * radius)); var delta = clients - i; for (var j = 0; j < Math.min(delta, n); i++, j++) { var angle = 2 * Math.PI / n * j; var x = p.x + distance * Math.cos(angle + d.startAngle); var y = p.y + distance * Math.sin(angle + d.startAngle); ctx.moveTo(x, y); ctx.arc(x, y, radius, 0, 2 * Math.PI); } } }); ctx.fillStyle = "rgba(220, 0, 103, 0.7)"; ctx.fill(); } }); });