253 lines
9.8 KiB
JavaScript
253 lines
9.8 KiB
JavaScript
import { brush, setClipPath, setGradient, setPattern } from './graphic.js';
|
|
import { createElement, createVNode, vNodeToString, getCssString, createBrushScope, createSVGVNode } from './core.js';
|
|
import { normalizeColor, encodeBase64, isGradient, isPattern } from './helper.js';
|
|
import { extend, keys, logError, map, noop, retrieve2 } from '../core/util.js';
|
|
import patch, { updateAttrs } from './patch.js';
|
|
import { getSize } from '../canvas/helper.js';
|
|
var svgId = 0;
|
|
var SVGPainter = (function () {
|
|
function SVGPainter(root, storage, opts) {
|
|
this.type = 'svg';
|
|
this.refreshHover = createMethodNotSupport('refreshHover');
|
|
this.configLayer = createMethodNotSupport('configLayer');
|
|
this.storage = storage;
|
|
this._opts = opts = extend({}, opts);
|
|
this.root = root;
|
|
this._id = 'zr' + svgId++;
|
|
this._oldVNode = createSVGVNode(opts.width, opts.height);
|
|
if (root && !opts.ssr) {
|
|
var viewport = this._viewport = document.createElement('div');
|
|
viewport.style.cssText = 'position:relative;overflow:hidden';
|
|
var svgDom = this._svgDom = this._oldVNode.elm = createElement('svg');
|
|
updateAttrs(null, this._oldVNode);
|
|
viewport.appendChild(svgDom);
|
|
root.appendChild(viewport);
|
|
}
|
|
this.resize(opts.width, opts.height);
|
|
}
|
|
SVGPainter.prototype.getType = function () {
|
|
return this.type;
|
|
};
|
|
SVGPainter.prototype.getViewportRoot = function () {
|
|
return this._viewport;
|
|
};
|
|
SVGPainter.prototype.getViewportRootOffset = function () {
|
|
var viewportRoot = this.getViewportRoot();
|
|
if (viewportRoot) {
|
|
return {
|
|
offsetLeft: viewportRoot.offsetLeft || 0,
|
|
offsetTop: viewportRoot.offsetTop || 0
|
|
};
|
|
}
|
|
};
|
|
SVGPainter.prototype.getSvgDom = function () {
|
|
return this._svgDom;
|
|
};
|
|
SVGPainter.prototype.refresh = function () {
|
|
if (this.root) {
|
|
var vnode = this.renderToVNode({
|
|
willUpdate: true
|
|
});
|
|
vnode.attrs.style = 'position:absolute;left:0;top:0;user-select:none';
|
|
patch(this._oldVNode, vnode);
|
|
this._oldVNode = vnode;
|
|
}
|
|
};
|
|
SVGPainter.prototype.renderOneToVNode = function (el) {
|
|
return brush(el, createBrushScope(this._id));
|
|
};
|
|
SVGPainter.prototype.renderToVNode = function (opts) {
|
|
opts = opts || {};
|
|
var list = this.storage.getDisplayList(true);
|
|
var width = this._width;
|
|
var height = this._height;
|
|
var scope = createBrushScope(this._id);
|
|
scope.animation = opts.animation;
|
|
scope.willUpdate = opts.willUpdate;
|
|
scope.compress = opts.compress;
|
|
scope.emphasis = opts.emphasis;
|
|
var children = [];
|
|
var bgVNode = this._bgVNode = createBackgroundVNode(width, height, this._backgroundColor, scope);
|
|
bgVNode && children.push(bgVNode);
|
|
var mainVNode = !opts.compress
|
|
? (this._mainVNode = createVNode('g', 'main', {}, [])) : null;
|
|
this._paintList(list, scope, mainVNode ? mainVNode.children : children);
|
|
mainVNode && children.push(mainVNode);
|
|
var defs = map(keys(scope.defs), function (id) { return scope.defs[id]; });
|
|
if (defs.length) {
|
|
children.push(createVNode('defs', 'defs', {}, defs));
|
|
}
|
|
if (opts.animation) {
|
|
var animationCssStr = getCssString(scope.cssNodes, scope.cssAnims, { newline: true });
|
|
if (animationCssStr) {
|
|
var styleNode = createVNode('style', 'stl', {}, [], animationCssStr);
|
|
children.push(styleNode);
|
|
}
|
|
}
|
|
return createSVGVNode(width, height, children, opts.useViewBox);
|
|
};
|
|
SVGPainter.prototype.renderToString = function (opts) {
|
|
opts = opts || {};
|
|
return vNodeToString(this.renderToVNode({
|
|
animation: retrieve2(opts.cssAnimation, true),
|
|
emphasis: retrieve2(opts.cssEmphasis, true),
|
|
willUpdate: false,
|
|
compress: true,
|
|
useViewBox: retrieve2(opts.useViewBox, true)
|
|
}), { newline: true });
|
|
};
|
|
SVGPainter.prototype.setBackgroundColor = function (backgroundColor) {
|
|
this._backgroundColor = backgroundColor;
|
|
};
|
|
SVGPainter.prototype.getSvgRoot = function () {
|
|
return this._mainVNode && this._mainVNode.elm;
|
|
};
|
|
SVGPainter.prototype._paintList = function (list, scope, out) {
|
|
var listLen = list.length;
|
|
var clipPathsGroupsStack = [];
|
|
var clipPathsGroupsStackDepth = 0;
|
|
var currentClipPathGroup;
|
|
var prevClipPaths;
|
|
var clipGroupNodeIdx = 0;
|
|
for (var i = 0; i < listLen; i++) {
|
|
var displayable = list[i];
|
|
if (!displayable.invisible) {
|
|
var clipPaths = displayable.__clipPaths;
|
|
var len = clipPaths && clipPaths.length || 0;
|
|
var prevLen = prevClipPaths && prevClipPaths.length || 0;
|
|
var lca = void 0;
|
|
for (lca = Math.max(len - 1, prevLen - 1); lca >= 0; lca--) {
|
|
if (clipPaths && prevClipPaths
|
|
&& clipPaths[lca] === prevClipPaths[lca]) {
|
|
break;
|
|
}
|
|
}
|
|
for (var i_1 = prevLen - 1; i_1 > lca; i_1--) {
|
|
clipPathsGroupsStackDepth--;
|
|
currentClipPathGroup = clipPathsGroupsStack[clipPathsGroupsStackDepth - 1];
|
|
}
|
|
for (var i_2 = lca + 1; i_2 < len; i_2++) {
|
|
var groupAttrs = {};
|
|
setClipPath(clipPaths[i_2], groupAttrs, scope);
|
|
var g = createVNode('g', 'clip-g-' + clipGroupNodeIdx++, groupAttrs, []);
|
|
(currentClipPathGroup ? currentClipPathGroup.children : out).push(g);
|
|
clipPathsGroupsStack[clipPathsGroupsStackDepth++] = g;
|
|
currentClipPathGroup = g;
|
|
}
|
|
prevClipPaths = clipPaths;
|
|
var ret = brush(displayable, scope);
|
|
if (ret) {
|
|
(currentClipPathGroup ? currentClipPathGroup.children : out).push(ret);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
SVGPainter.prototype.resize = function (width, height) {
|
|
var opts = this._opts;
|
|
var root = this.root;
|
|
var viewport = this._viewport;
|
|
width != null && (opts.width = width);
|
|
height != null && (opts.height = height);
|
|
if (root && viewport) {
|
|
viewport.style.display = 'none';
|
|
width = getSize(root, 0, opts);
|
|
height = getSize(root, 1, opts);
|
|
viewport.style.display = '';
|
|
}
|
|
if (this._width !== width || this._height !== height) {
|
|
this._width = width;
|
|
this._height = height;
|
|
if (viewport) {
|
|
var viewportStyle = viewport.style;
|
|
viewportStyle.width = width + 'px';
|
|
viewportStyle.height = height + 'px';
|
|
}
|
|
if (!isPattern(this._backgroundColor)) {
|
|
var svgDom = this._svgDom;
|
|
if (svgDom) {
|
|
svgDom.setAttribute('width', width);
|
|
svgDom.setAttribute('height', height);
|
|
}
|
|
var bgEl = this._bgVNode && this._bgVNode.elm;
|
|
if (bgEl) {
|
|
bgEl.setAttribute('width', width);
|
|
bgEl.setAttribute('height', height);
|
|
}
|
|
}
|
|
else {
|
|
this.refresh();
|
|
}
|
|
}
|
|
};
|
|
SVGPainter.prototype.getWidth = function () {
|
|
return this._width;
|
|
};
|
|
SVGPainter.prototype.getHeight = function () {
|
|
return this._height;
|
|
};
|
|
SVGPainter.prototype.dispose = function () {
|
|
if (this.root) {
|
|
this.root.innerHTML = '';
|
|
}
|
|
this._svgDom =
|
|
this._viewport =
|
|
this.storage =
|
|
this._oldVNode =
|
|
this._bgVNode =
|
|
this._mainVNode = null;
|
|
};
|
|
SVGPainter.prototype.clear = function () {
|
|
if (this._svgDom) {
|
|
this._svgDom.innerHTML = null;
|
|
}
|
|
this._oldVNode = null;
|
|
};
|
|
SVGPainter.prototype.toDataURL = function (base64) {
|
|
var str = this.renderToString();
|
|
var prefix = 'data:image/svg+xml;';
|
|
if (base64) {
|
|
str = encodeBase64(str);
|
|
return str && prefix + 'base64,' + str;
|
|
}
|
|
return prefix + 'charset=UTF-8,' + encodeURIComponent(str);
|
|
};
|
|
return SVGPainter;
|
|
}());
|
|
function createMethodNotSupport(method) {
|
|
return function () {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
logError('In SVG mode painter not support method "' + method + '"');
|
|
}
|
|
};
|
|
}
|
|
function createBackgroundVNode(width, height, backgroundColor, scope) {
|
|
var bgVNode;
|
|
if (backgroundColor && backgroundColor !== 'none') {
|
|
bgVNode = createVNode('rect', 'bg', {
|
|
width: width,
|
|
height: height,
|
|
x: '0',
|
|
y: '0'
|
|
});
|
|
if (isGradient(backgroundColor)) {
|
|
setGradient({ fill: backgroundColor }, bgVNode.attrs, 'fill', scope);
|
|
}
|
|
else if (isPattern(backgroundColor)) {
|
|
setPattern({
|
|
style: {
|
|
fill: backgroundColor
|
|
},
|
|
dirty: noop,
|
|
getBoundingRect: function () { return ({ width: width, height: height }); }
|
|
}, bgVNode.attrs, 'fill', scope);
|
|
}
|
|
else {
|
|
var _a = normalizeColor(backgroundColor), color = _a.color, opacity = _a.opacity;
|
|
bgVNode.attrs.fill = color;
|
|
opacity < 1 && (bgVNode.attrs['fill-opacity'] = opacity);
|
|
}
|
|
}
|
|
return bgVNode;
|
|
}
|
|
export default SVGPainter;
|