blob: 67e37c4918375cbffb1617433806d6212f445193 [file] [log] [blame]
/**
* SVG Painter
* @module zrender/svg/Painter
*/
import { createElement } from './core';
import zrLog from '../core/log';
import Path from '../graphic/Path';
import ZImage from '../graphic/Image';
import ZText from '../graphic/Text';
import arrayDiff from '../core/arrayDiff2';
import GradientManager from './helper/GradientManager';
import ClippathManager from './helper/ClippathManager';
import { each } from '../core/util';
import { path as svgPath, image as svgImage, text as svgText } from './graphic';
function parseInt10(val) {
return parseInt(val, 10);
}
function getSvgProxy(el) {
if (el instanceof Path) {
return svgPath;
} else if (el instanceof ZImage) {
return svgImage;
} else if (el instanceof ZText) {
return svgText;
} else {
return svgPath;
}
}
function checkParentAvailable(parent, child) {
return child && parent && child.parentNode !== parent;
}
function insertAfter(parent, child, prevSibling) {
if (checkParentAvailable(parent, child) && prevSibling) {
var nextSibling = prevSibling.nextSibling;
nextSibling ? parent.insertBefore(child, nextSibling) : parent.appendChild(child);
}
}
function prepend(parent, child) {
if (checkParentAvailable(parent, child)) {
var firstChild = parent.firstChild;
firstChild ? parent.insertBefore(child, firstChild) : parent.appendChild(child);
}
}
function append(parent, child) {
if (checkParentAvailable(parent, child)) {
parent.appendChild(child);
}
}
function remove(parent, child) {
if (child && parent && child.parentNode === parent) {
parent.removeChild(child);
}
}
function getTextSvgElement(displayable) {
return displayable.__textSvgEl;
}
function getSvgElement(displayable) {
return displayable.__svgEl;
}
/**
* @alias module:zrender/svg/Painter
*/
var SVGPainter = function (root, storage) {
this.root = root;
this.storage = storage;
var svgRoot = createElement('svg');
svgRoot.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
svgRoot.setAttribute('version', '1.1');
svgRoot.setAttribute('baseProfile', 'full');
svgRoot.style['user-select'] = 'none';
this.gradientManager = new GradientManager(svgRoot);
this.clipPathManager = new ClippathManager(svgRoot);
var viewport = document.createElement('div');
viewport.style.cssText = 'overflow: hidden;';
this._svgRoot = svgRoot;
this._viewport = viewport;
root.appendChild(viewport);
viewport.appendChild(svgRoot);
this.resize();
this._visibleList = [];
};
SVGPainter.prototype = {
constructor: SVGPainter,
getType: function () {
return 'svg';
},
getViewportRoot: function () {
return this._viewport;
},
getViewportRootOffset: function () {
var viewportRoot = this.getViewportRoot();
if (viewportRoot) {
return {
offsetLeft: viewportRoot.offsetLeft || 0,
offsetTop: viewportRoot.offsetTop || 0
};
}
},
refresh: function () {
var list = this.storage.getDisplayList(true);
this._paintList(list);
},
_paintList: function (list) {
this.gradientManager.markAllUnused();
this.clipPathManager.markAllUnused();
var svgRoot = this._svgRoot;
var visibleList = this._visibleList;
var listLen = list.length;
var newVisibleList = [];
var i;
for (i = 0; i < listLen; i++) {
var displayable = list[i];
var svgProxy = getSvgProxy(displayable);
if (!displayable.invisible) {
if (displayable.__dirty) {
svgProxy && svgProxy.brush(displayable); // Update clipPath
this.clipPathManager.update(displayable); // Update gradient
if (displayable.style) {
this.gradientManager.update(displayable.style.fill);
this.gradientManager.update(displayable.style.stroke);
}
displayable.__dirty = false;
}
newVisibleList.push(displayable);
}
}
var diff = arrayDiff(visibleList, newVisibleList);
var prevSvgElement; // First do remove, in case element moved to the head and do remove
// after add
for (i = 0; i < diff.length; i++) {
var item = diff[i];
if (item.removed) {
for (var k = 0; k < item.count; k++) {
var displayable = visibleList[item.indices[k]];
var svgElement = getSvgElement(displayable);
var textSvgElement = getTextSvgElement(displayable);
remove(svgRoot, svgElement);
remove(svgRoot, textSvgElement);
}
}
}
for (i = 0; i < diff.length; i++) {
var item = diff[i];
if (item.added) {
for (var k = 0; k < item.count; k++) {
var displayable = newVisibleList[item.indices[k]];
var svgElement = getSvgElement(displayable);
var textSvgElement = getTextSvgElement(displayable);
prevSvgElement ? insertAfter(svgRoot, svgElement, prevSvgElement) : prepend(svgRoot, svgElement);
if (svgElement) {
insertAfter(svgRoot, textSvgElement, svgElement);
} else if (prevSvgElement) {
insertAfter(svgRoot, textSvgElement, prevSvgElement);
} else {
prepend(svgRoot, textSvgElement);
} // Insert text
insertAfter(svgRoot, textSvgElement, svgElement);
prevSvgElement = textSvgElement || svgElement || prevSvgElement;
this.gradientManager.addWithoutUpdate(svgElement, displayable);
this.clipPathManager.markUsed(displayable);
}
} else if (!item.removed) {
for (var k = 0; k < item.count; k++) {
var displayable = newVisibleList[item.indices[k]];
prevSvgElement = svgElement = getTextSvgElement(displayable) || getSvgElement(displayable) || prevSvgElement;
this.gradientManager.markUsed(displayable);
this.gradientManager.addWithoutUpdate(svgElement, displayable);
this.clipPathManager.markUsed(displayable);
}
}
}
this.gradientManager.removeUnused();
this.clipPathManager.removeUnused();
this._visibleList = newVisibleList;
},
_getDefs: function (isForceCreating) {
var svgRoot = this._svgRoot;
var defs = this._svgRoot.getElementsByTagName('defs');
if (defs.length === 0) {
// Not exist
if (isForceCreating) {
var defs = svgRoot.insertBefore(createElement('defs'), // Create new tag
svgRoot.firstChild // Insert in the front of svg
);
if (!defs.contains) {
// IE doesn't support contains method
defs.contains = function (el) {
var children = defs.children;
if (!children) {
return false;
}
for (var i = children.length - 1; i >= 0; --i) {
if (children[i] === el) {
return true;
}
}
return false;
};
}
return defs;
} else {
return null;
}
} else {
return defs[0];
}
},
resize: function () {
var width = this._getWidth();
var height = this._getHeight();
if (this._width !== width && this._height !== height) {
this._width = width;
this._height = height;
var viewportStyle = this._viewport.style;
viewportStyle.width = width + 'px';
viewportStyle.height = height + 'px';
var svgRoot = this._svgRoot; // Set width by 'svgRoot.width = width' is invalid
svgRoot.setAttribute('width', width);
svgRoot.setAttribute('height', height);
}
},
getWidth: function () {
return this._getWidth();
},
getHeight: function () {
return this._getHeight();
},
_getWidth: function () {
var root = this.root;
var stl = document.defaultView.getComputedStyle(root);
return (root.clientWidth || parseInt10(stl.width)) - parseInt10(stl.paddingLeft) - parseInt10(stl.paddingRight) | 0;
},
_getHeight: function () {
var root = this.root;
var stl = document.defaultView.getComputedStyle(root);
return (root.clientHeight || parseInt10(stl.height)) - parseInt10(stl.paddingTop) - parseInt10(stl.paddingBottom) | 0;
},
dispose: function () {
this.root.innerHTML = '';
this._svgRoot = this._viewport = this.storage = null;
},
clear: function () {
if (this._viewport) {
this.root.removeChild(this._viewport);
}
},
pathToSvg: function () {
this.refresh();
var html = this._svgRoot.outerHTML;
return 'data:img/svg+xml;utf-8,' + unescape(html);
}
}; // Not supported methods
function createMethodNotSupport(method) {
return function () {
zrLog('In SVG mode painter not support method "' + method + '"');
};
} // Unsuppoted methods
each(['getLayer', 'insertLayer', 'eachLayer', 'eachBuiltinLayer', 'eachOtherLayer', 'getLayers', 'modLayer', 'delLayer', 'clearLayer', 'toDataURL', 'pathToImage'], function (name) {
SVGPainter.prototype[name] = createMethodNotSupport(name);
});
export default SVGPainter;