import {Renderer} from './Renderer'; import * as DomUtil from '../../dom/DomUtil'; import * as DomEvent from '../../dom/DomEvent'; import * as Browser from '../../core/Browser'; import {stamp} from '../../core/Util'; import {svgCreate, pointsToPath} from './SVG.Util'; export {pointsToPath}; import {vmlMixin, vmlCreate} from './SVG.VML'; export var create = Browser.vml ? vmlCreate : svgCreate; /* * @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 } ); * ``` */ export var SVG = Renderer.extend({ getEvents: function () { var events = Renderer.prototype.getEvents.call(this); events.zoomstart = this._onZoomStart; return events; }, _initContainer: function () { this._container = 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 = create('g'); this._container.appendChild(this._rootGroup); }, _destroyContainer: function () { DomUtil.remove(this._container); DomEvent.off(this._container); delete this._container; delete this._rootGroup; delete this._svgSize; }, _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; } 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 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 = create('path'); // @namespace Path // @option className: String = null // Custom class name set on an element. Only for SVG renderer. if (layer.options.className) { DomUtil.addClass(path, layer.options.className); } if (layer.options.interactive) { DomUtil.addClass(path, 'leaflet-interactive'); } this._updateStyle(layer); this._layers[stamp(layer)] = layer; }, _addPath: function (layer) { if (!this._rootGroup) { this._initContainer(); } this._rootGroup.appendChild(layer._path); layer.addInteractiveTarget(layer._path); }, _removePath: function (layer) { DomUtil.remove(layer._path); layer.removeInteractiveTarget(layer._path); delete this._layers[stamp(layer)]; }, _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, pointsToPath(layer._parts, closed)); }, _updateCircle: function (layer) { var p = layer._point, r = Math.max(Math.round(layer._radius), 1), r2 = Math.max(Math.round(layer._radiusY), 1) || 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) { DomUtil.toFront(layer._path); }, _bringToBack: function (layer) { DomUtil.toBack(layer._path); } }); if (Browser.vml) { SVG.include(vmlMixin); } // @namespace SVG // @factory L.svg(options?: Renderer options) // Creates a SVG renderer with the given options. export function svg(options) { return Browser.svg || Browser.vml ? new SVG(options) : null; }