Skip to content

Instantly share code, notes, and snippets.

@seb-thomas
Created November 17, 2016 22:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save seb-thomas/d678302063457105edf174acdcccc703 to your computer and use it in GitHub Desktop.
Save seb-thomas/d678302063457105edf174acdcccc703 to your computer and use it in GitHub Desktop.
Walking circle for Living Map, uses location API and D3
L.WalkingCircleLayer = (L.Layer ? L.Layer : L.Class).extend({
initialize: function() {
this._prefix = this._prefixMatch(["webkit", "ms", "Moz", "O"]),
this._canvasPadding = 4,
this._isActive = !1
},
onAdd: function(t) {
var a = L.DomUtil.create("div", "leaflet-control-walkingcircle leaflet-bar leaflet-control");
this._link = L.DomUtil.create("a", "leaflet-bar-part leaflet-bar-part-single", a),
this._icon = L.DomUtil.create("span", "fa fa-location-arrow", this._link),
this._link.href = "#",
document.querySelector(".leaflet-right").appendChild(a);
var i = this;
L.DomEvent.on(this._link, "click", L.DomEvent.stopPropagation).on(this._link, "click", L.DomEvent.preventDefault).on(this._link, "click", function() {
this._isActive ? i.onRemove(t) : this._locate()
}, this),
this._map = t
},
onRemove: function(t) {
this._isActive = !1,
t.getPanes().overlayPane.removeChild(this._canvas),
t.stopLocate(),
t.off("viewreset moveend resize", this._reset, this),
t.off("zoomanim", this._animateZoom, this),
t.off("locationfound", this._onLocationFound, this),
t.off("locationerror", this._onLocationError, this),
L.DomUtil.removeClass(this._link, "active")
},
addTo: function(t) {
return t.addLayer(this),
this
},
_initSVG: function() {
var t = (this._map.getSize(),
d3.select(map.getPanes().overlayPane).append("svg").attr("id", "walking-circle-canvas").attr("class", "svg-zoom-animated"));
this.D3_wcContainer = t.append("g").attr("id", "walking-circle-container").attr("visibility", "hidden"),
this._canvas = L.DomUtil.get("walking-circle-canvas"),
this._drawWalkingCircle(),
this._drawPositionCircle(),
this._reset(),
map.on("viewreset moveend resize", this._reset, this)
},
_reset: function(t) {
var a = this._map.getSize()
, i = a.x * this._canvasPadding
, e = a.y * this._canvasPadding
, r = -(i - a.x) / 2
, n = -(e - a.y) / 2
, o = this._map.containerPointToLayerPoint([r, n]).round();
this._canvas.setAttribute("width", i),
this._canvas.setAttribute("height", e),
this._canvas.setAttribute("viewBox", [o.x, o.y, i, e].join(" ")),
L.DomUtil.setPosition(this._canvas, o);
var s = this;
void 0 !== t && "viewreset" == t.type && t.hard && this.D3_wcContainer.attr({
style: function() {
var t = map.latLngToLayerPoint(s._latlng);
return s._prefix + "transform: translate(" + t.x + "px," + t.y + "px)"
}
})
},
_drawWalkingCircle: function() {
this._count = 1;
this.D3_wcContainer.append("g").attr("id", "walking-circle").attr("transform", "rotate(0)");
this.D3_wc = d3.select("#walking-circle"),
this._minsPerZoom = {
15: [1, 2, 5],
14: [2, 5, 10],
13: [3, 8, 15],
12: [5, 12, 30],
11: [10, 25, 45],
10: [15, 30, 60],
9: [25, 40, 60],
8: [25, 50, 75],
7: [30, 75, 120]
},
this._zoomToRadius = {};
for (var t = 15, a = 7, i = 1, e = t; e >= a; e--)
this._zoomToRadius[e] = 672 / i,
i += i;
this._arc = d3.svg.arc().startAngle(0);
var r = this;
this._minsPerZoom[this._map.getZoom()].forEach(function(t) {
r._makeCircle(t)
})
},
_makeCircle: function(t) {
var a = this.D3_wc
, i = 2 * Math.PI
, e = this._map.getZoom()
, r = this._zoomToRadius[e]
, n = this._arc
, o = this._calcArc(r * t, 3)
, s = a.append("g").attr("class", "circle-container")
, c = (s.append("path").datum({
endAngle: i,
innerRadius: o.innerRadius,
outerRadius: o.outerRadius
}).style("fill", "white").attr("class", "circle circle" + this._count).attr("d", n),
s.append("path").datum({
endAngle: i / 16,
innerRadius: o.tagInnerRadius,
outerRadius: o.tagOuterRadius
}).style("fill", "white").attr("d", n).attr("class", "tag").attr("id", "tag" + this._count),
s.append("text").attr("x", 6).attr("dy", 12));
c.append("textPath").attr("class", "walking-circle-text").attr("xlink:href", "#tag" + this._count).text(t + " mins walk"),
this._count += 1
},
_drawPositionCircle: function() {
{
var t = this.D3_wcContainer.append("g").attr("id", "position-circle").attr("transform", "rotate(0)")
, a = d3.svg.line().x(function(t) {
return t.x
}).y(function(t) {
return t.y
}).interpolate("linear")
, i = {
arrow: {
width: 14,
height: 26,
tip: 14
},
circle: {
radius: 40
}
}
, e = [{
x: i.arrow.width,
y: i.arrow.height
}, {
x: i.arrow.width / 2,
y: 0
}, {
x: 0,
y: i.arrow.height
}, {
x: i.arrow.width / 2,
y: i.arrow.height - 5
}];
t.append("circle").attr("id", "pc-circle").attr("r", i.circle.radius).attr("fill", "none").attr("stroke", "white").attr("stroke-width", 4).attr("stroke-linecap", "round").attr("stroke-dasharray", "0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 33"),
t.append("path").attr("id", "pc-letter-n").attr("d", "M3.8,5.4L1.3,2.2v3.3H0V0h1.2l2.5,3.3V0H5v5.4H3.8z").attr("fill", "white").attr("transform", "translate(1, -60) scale(0)"),
t.append("path").attr("id", "pc-arrow").attr("d", a(e) + "Z").attr("stroke", "white").attr("stroke-width", 3).attr("stroke-linejoin", "round").attr("fill", "white").style("fill-opacity", 0).attr("transform", "translate(-6, " + -(i.circle.radius + i.arrow.tip) + ") scale(1)"),
t.append("path").attr("id", "pc-bars").attr("d", "M40,0 L31,0 M0,40 L0,31 M-40,0 L-31,0").attr("stroke", "white").attr("stroke-width", 4).attr("stroke-linecap", "round")
}
},
_locate: function() {
L.DomUtil.addClass(this._link, "finding"),
map.on("locationfound", this._onLocationFound, this),
map.once("locationfound", function(t) {
map.panTo(params.demo ? [CONFIG.startCenter.lat, CONFIG.startCenter.lng] : t.latlng)
}, this),
map.on("locationerror", this._onLocationError, this),
this._isActive = !0,
this._initSVG(),
map.locate({
watch: !0
}),
map.on("zoomanim", this._animateZoom, this),
window.setTimeout(function() {
var t = 0
, a = 0;
if ("ondeviceorientation"in window) {
window.addEventListener("deviceorientation", function(i) {
var e, r, n;
for ("undefined" != typeof i.webkitCompassHeading ? (e = i.webkitCompassHeading,
"undefined" != typeof window.orientation && (e += window.orientation)) : e = 360 - i.alpha,
r = Math.round(e) - a,
a = Math.round(e),
-180 > r && (r += 360),
r > 180 && (r -= 360),
t += r,
n = e; n >= 360; )
n -= 360;
for (; 0 > n; )
n += 360;
n = Math.round(n)
});
var i, e = d3.select("#position-circle"), r = d3.select("#pc-arrow"), n = d3.select("#pc-letter-n"), o = r.attr("transform"), s = n.attr("transform");
window.setInterval(function() {
var a = -t
, c = Math.abs(a - i);
c >= 1 && (a > -8 && 8 > a ? (e.transition().duration(300).ease("bounce").attr("transform", "rotate(0)"),
r.transition().duration(200).ease("elastic").attr("transform", "translate(-8, -58) scale(1.3)").style("fill-opacity", 1).transition().duration(200).ease("out").attr("transform", o),
n.transition().duration(200).ease("elastic").attr("transform", "translate(-5, -78) scale(2.5)").transition().duration(200).ease("out").attr("transform", s)) : (e.transition().duration(500).attr("transform", "rotate(" + a + ")"),
r.transition().attr("transform", o).style("fill-opacity", 0))),
i = a
}, 200)
}
}, 400)
},
_onLocationFound: function(t) {
this._latlng = params.demo ? [CONFIG.startCenter.lat, CONFIG.startCenter.lng] : [t.latitude, t.longitude];
var a = this;
L.DomUtil.addClass(this._link, "active"),
L.DomUtil.removeClass(this._link, "finding"),
this.D3_wcContainer.attr("visibility", "visible").attr({
style: function() {
var t = map.latLngToLayerPoint(a._latlng);
return a._prefix + "transform: translate(" + t.x + "px," + t.y + "px)"
}
})
},
_onLocationError: function(t) {
alert(t.message)
},
_animateZoom: function(t) {
var a = t.zoom
, i = this;
this.D3_wcContainer.attr({
style: function() {
var e = map._latLngToNewLayerPoint(i._latlng, a, t.center);
return i._prefix + "transform: translate(" + e.x + "px," + e.y + "px)"
}
});
var e = this._minsPerZoom[a]
, r = this._zoomToRadius[a];
d3.selectAll(".circle-container").each(function(t, a) {
var n = e[a]
, o = i._calcArc(r * n, 3)
, s = n + (1 == n ? " min walk" : " mins walk");
d3.select(this).select(".circle").transition().duration(750).ease("elastic").call(i._arcTween, i._arc, o.innerRadius, o.outerRadius),
d3.select(this).select(".tag").transition().duration(750).ease("elastic").call(i._arcTween, i._arc, o.tagInnerRadius, o.tagOuterRadius),
d3.select(this).select(".walking-circle-text").text(s)
})
},
_arcTween: function(t, a, i, e) {
t.attrTween("d", function(t) {
var r = d3.interpolate(t.innerRadius, i)
, n = d3.interpolate(t.outerRadius, e);
return function(i) {
return t.outerRadius = n(i),
t.innerRadius = r(i),
a(t)
}
})
},
_calcArc: function(t, a) {
var i = {
innerRadius: t,
outerRadius: t + a,
tagInnerRadius: t - a - 12,
tagOuterRadius: t + 1
};
return i
},
_prefixMatch: function(t) {
for (var a = -1, i = t.length, e = document.body.style; ++a < i; )
if (t[a] + "Transform"in e)
return "-" + t[a].toLowerCase() + "-";
return ""
}
}),
L.walkingCircle = function() {
return new L.WalkingCircleLayer
}
;
var wc = L.walkingCircle().addTo(map);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment