leaflet
Version:
JavaScript library for mobile-friendly interactive maps
236 lines (192 loc) • 6.72 kB
JavaScript
/*
* @class SVG
* @inherits Renderer
* @aka L.SVG
*
* Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
* Inherits `Renderer`.
*
* Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
* available in all web browsers, notably Android 2.x and 3.x.
*
* Although SVG is not available on IE7 and IE8, these browsers support
* [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
* (a now deprecated technology), and the SVG renderer will fall back to VML in
* this case.
*
* @example
*
* Use SVG by default for all paths in the map:
*
* ```js
* var map = L.map('map', {
* renderer: L.svg()
* });
* ```
*
* Use a SVG renderer with extra padding for specific vector geometries:
*
* ```js
* var map = L.map('map');
* var myRenderer = L.svg({ padding: 0.5 });
* var line = L.polyline( coordinates, { renderer: myRenderer } );
* var circle = L.circle( center, { renderer: myRenderer } );
* ```
*/
L.SVG = L.Renderer.extend({
getEvents: function () {
var events = L.Renderer.prototype.getEvents.call(this);
events.zoomstart = this._onZoomStart;
return events;
},
_initContainer: function () {
this._container = L.SVG.create('svg');
// makes it possible to click through svg root; we'll reset it back in individual paths
this._container.setAttribute('pointer-events', 'none');
this._rootGroup = L.SVG.create('g');
this._container.appendChild(this._rootGroup);
},
_onZoomStart: function () {
// Drag-then-pinch interactions might mess up the center and zoom.
// In this case, the easiest way to prevent this is re-do the renderer
// bounds and padding when the zooming starts.
this._update();
},
_update: function () {
if (this._map._animatingZoom && this._bounds) { return; }
L.Renderer.prototype._update.call(this);
var b = this._bounds,
size = b.getSize(),
container = this._container;
// set size of svg-container if changed
if (!this._svgSize || !this._svgSize.equals(size)) {
this._svgSize = size;
container.setAttribute('width', size.x);
container.setAttribute('height', size.y);
}
// movement: update container viewBox so that we don't have to change coordinates of individual layers
L.DomUtil.setPosition(container, b.min);
container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
this.fire('update');
},
// methods below are called by vector layers implementations
_initPath: function (layer) {
var path = layer._path = L.SVG.create('path');
// @namespace Path
// @option className: String = null
// Custom class name set on an element. Only for SVG renderer.
if (layer.options.className) {
L.DomUtil.addClass(path, layer.options.className);
}
if (layer.options.interactive) {
L.DomUtil.addClass(path, 'leaflet-interactive');
}
this._updateStyle(layer);
},
_addPath: function (layer) {
this._rootGroup.appendChild(layer._path);
layer.addInteractiveTarget(layer._path);
},
_removePath: function (layer) {
L.DomUtil.remove(layer._path);
layer.removeInteractiveTarget(layer._path);
},
_updatePath: function (layer) {
layer._project();
layer._update();
},
_updateStyle: function (layer) {
var path = layer._path,
options = layer.options;
if (!path) { return; }
if (options.stroke) {
path.setAttribute('stroke', options.color);
path.setAttribute('stroke-opacity', options.opacity);
path.setAttribute('stroke-width', options.weight);
path.setAttribute('stroke-linecap', options.lineCap);
path.setAttribute('stroke-linejoin', options.lineJoin);
if (options.dashArray) {
path.setAttribute('stroke-dasharray', options.dashArray);
} else {
path.removeAttribute('stroke-dasharray');
}
if (options.dashOffset) {
path.setAttribute('stroke-dashoffset', options.dashOffset);
} else {
path.removeAttribute('stroke-dashoffset');
}
} else {
path.setAttribute('stroke', 'none');
}
if (options.fill) {
path.setAttribute('fill', options.fillColor || options.color);
path.setAttribute('fill-opacity', options.fillOpacity);
path.setAttribute('fill-rule', options.fillRule || 'evenodd');
} else {
path.setAttribute('fill', 'none');
}
},
_updatePoly: function (layer, closed) {
this._setPath(layer, L.SVG.pointsToPath(layer._parts, closed));
},
_updateCircle: function (layer) {
var p = layer._point,
r = layer._radius,
r2 = layer._radiusY || r,
arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
// drawing a circle with two half-arcs
var d = layer._empty() ? 'M0 0' :
'M' + (p.x - r) + ',' + p.y +
arc + (r * 2) + ',0 ' +
arc + (-r * 2) + ',0 ';
this._setPath(layer, d);
},
_setPath: function (layer, path) {
layer._path.setAttribute('d', path);
},
// SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
_bringToFront: function (layer) {
L.DomUtil.toFront(layer._path);
},
_bringToBack: function (layer) {
L.DomUtil.toBack(layer._path);
}
});
// @namespace SVG; @section
// There are several static functions which can be called without instantiating L.SVG:
L.extend(L.SVG, {
// @function create(name: String): SVGElement
// Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
// corresponding to the class name passed. For example, using 'line' will return
// an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
create: function (name) {
return document.createElementNS('http://www.w3.org/2000/svg', name);
},
// @function pointsToPath(rings: Point[], closed: Boolean): String
// Generates a SVG path string for multiple rings, with each ring turning
// into "M..L..L.." instructions
pointsToPath: function (rings, closed) {
var str = '',
i, j, len, len2, points, p;
for (i = 0, len = rings.length; i < len; i++) {
points = rings[i];
for (j = 0, len2 = points.length; j < len2; j++) {
p = points[j];
str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
}
// closes the ring for polygons; "x" is VML syntax
str += closed ? (L.Browser.svg ? 'z' : 'x') : '';
}
// SVG complains about empty path strings
return str || 'M0 0';
}
});
// @namespace Browser; @property svg: Boolean
// `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
L.Browser.svg = !!(document.createElementNS && L.SVG.create('svg').createSVGRect);
// @namespace SVG
// @factory L.svg(options?: Renderer options)
// Creates a SVG renderer with the given options.
L.svg = function (options) {
return L.Browser.svg || L.Browser.vml ? new L.SVG(options) : null;
};