1
This commit is contained in:
410
node_modules/zrender/src/svg/Painter.ts
generated
vendored
Normal file
410
node_modules/zrender/src/svg/Painter.ts
generated
vendored
Normal file
@@ -0,0 +1,410 @@
|
||||
/**
|
||||
* SVG Painter
|
||||
*/
|
||||
|
||||
import {
|
||||
brush,
|
||||
setClipPath,
|
||||
setGradient,
|
||||
setPattern
|
||||
} from './graphic';
|
||||
import Displayable from '../graphic/Displayable';
|
||||
import Storage from '../Storage';
|
||||
import { PainterBase } from '../PainterBase';
|
||||
import {
|
||||
createElement,
|
||||
createVNode,
|
||||
vNodeToString,
|
||||
SVGVNodeAttrs,
|
||||
SVGVNode,
|
||||
getCssString,
|
||||
BrushScope,
|
||||
createBrushScope,
|
||||
createSVGVNode
|
||||
} from './core';
|
||||
import { normalizeColor, encodeBase64, isGradient, isPattern } from './helper';
|
||||
import { extend, keys, logError, map, noop, retrieve2 } from '../core/util';
|
||||
import Path from '../graphic/Path';
|
||||
import patch, { updateAttrs } from './patch';
|
||||
import { getSize } from '../canvas/helper';
|
||||
import { GradientObject } from '../graphic/Gradient';
|
||||
import { PatternObject } from '../graphic/Pattern';
|
||||
|
||||
let svgId = 0;
|
||||
|
||||
interface SVGPainterOption {
|
||||
width?: number
|
||||
height?: number
|
||||
ssr?: boolean
|
||||
}
|
||||
|
||||
type SVGPainterBackgroundColor = string | GradientObject | PatternObject;
|
||||
|
||||
class SVGPainter implements PainterBase {
|
||||
|
||||
type = 'svg'
|
||||
|
||||
storage: Storage
|
||||
|
||||
root: HTMLElement
|
||||
|
||||
private _svgDom: SVGElement
|
||||
private _viewport: HTMLElement
|
||||
|
||||
private _opts: SVGPainterOption
|
||||
|
||||
private _oldVNode: SVGVNode
|
||||
private _bgVNode: SVGVNode
|
||||
private _mainVNode: SVGVNode
|
||||
|
||||
private _width: number
|
||||
private _height: number
|
||||
|
||||
private _backgroundColor: SVGPainterBackgroundColor
|
||||
|
||||
private _id: string
|
||||
|
||||
constructor(root: HTMLElement, storage: Storage, opts: SVGPainterOption) {
|
||||
this.storage = storage;
|
||||
this._opts = opts = extend({}, opts);
|
||||
|
||||
this.root = root;
|
||||
// A unique id for generating svg ids.
|
||||
this._id = 'zr' + svgId++;
|
||||
|
||||
this._oldVNode = createSVGVNode(opts.width, opts.height);
|
||||
|
||||
if (root && !opts.ssr) {
|
||||
const viewport = this._viewport = document.createElement('div');
|
||||
viewport.style.cssText = 'position:relative;overflow:hidden';
|
||||
const svgDom = this._svgDom = this._oldVNode.elm = createElement('svg');
|
||||
updateAttrs(null, this._oldVNode);
|
||||
viewport.appendChild(svgDom);
|
||||
root.appendChild(viewport);
|
||||
}
|
||||
|
||||
this.resize(opts.width, opts.height);
|
||||
}
|
||||
|
||||
getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
getViewportRoot() {
|
||||
return this._viewport;
|
||||
}
|
||||
getViewportRootOffset() {
|
||||
const viewportRoot = this.getViewportRoot();
|
||||
if (viewportRoot) {
|
||||
return {
|
||||
offsetLeft: viewportRoot.offsetLeft || 0,
|
||||
offsetTop: viewportRoot.offsetTop || 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
getSvgDom() {
|
||||
return this._svgDom;
|
||||
}
|
||||
|
||||
refresh() {
|
||||
if (this.root) {
|
||||
const vnode = this.renderToVNode({
|
||||
willUpdate: true
|
||||
});
|
||||
// Disable user selection.
|
||||
vnode.attrs.style = 'position:absolute;left:0;top:0;user-select:none';
|
||||
patch(this._oldVNode, vnode);
|
||||
this._oldVNode = vnode;
|
||||
}
|
||||
}
|
||||
|
||||
renderOneToVNode(el: Displayable) {
|
||||
return brush(el, createBrushScope(this._id));
|
||||
}
|
||||
|
||||
renderToVNode(opts?: {
|
||||
animation?: boolean,
|
||||
willUpdate?: boolean,
|
||||
compress?: boolean,
|
||||
useViewBox?: boolean,
|
||||
emphasis?: boolean
|
||||
}) {
|
||||
|
||||
opts = opts || {};
|
||||
|
||||
const list = this.storage.getDisplayList(true);
|
||||
const width = this._width;
|
||||
const height = this._height;
|
||||
|
||||
const scope = createBrushScope(this._id);
|
||||
scope.animation = opts.animation;
|
||||
scope.willUpdate = opts.willUpdate;
|
||||
scope.compress = opts.compress;
|
||||
scope.emphasis = opts.emphasis;
|
||||
scope.ssr = this._opts.ssr;
|
||||
|
||||
const children: SVGVNode[] = [];
|
||||
|
||||
const bgVNode = this._bgVNode = createBackgroundVNode(width, height, this._backgroundColor, scope);
|
||||
bgVNode && children.push(bgVNode);
|
||||
|
||||
// Ignore the root g if wan't the output to be more tight.
|
||||
const mainVNode = !opts.compress
|
||||
? (this._mainVNode = createVNode('g', 'main', {}, [])) : null;
|
||||
this._paintList(list, scope, mainVNode ? mainVNode.children : children);
|
||||
mainVNode && children.push(mainVNode);
|
||||
|
||||
const defs = map(keys(scope.defs), (id) => scope.defs[id]);
|
||||
if (defs.length) {
|
||||
children.push(createVNode('defs', 'defs', {}, defs));
|
||||
}
|
||||
|
||||
if (opts.animation) {
|
||||
const animationCssStr = getCssString(scope.cssNodes, scope.cssAnims, { newline: true });
|
||||
if (animationCssStr) {
|
||||
const styleNode = createVNode('style', 'stl', {}, [], animationCssStr);
|
||||
children.push(styleNode);
|
||||
}
|
||||
}
|
||||
|
||||
return createSVGVNode(width, height, children, opts.useViewBox);
|
||||
}
|
||||
|
||||
renderToString(opts?: {
|
||||
/**
|
||||
* If add css animation.
|
||||
* @default true
|
||||
*/
|
||||
cssAnimation?: boolean,
|
||||
/**
|
||||
* If add css emphasis.
|
||||
* @default true
|
||||
*/
|
||||
cssEmphasis?: boolean,
|
||||
/**
|
||||
* If use viewBox
|
||||
* @default true
|
||||
*/
|
||||
useViewBox?: boolean
|
||||
}) {
|
||||
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 });
|
||||
}
|
||||
|
||||
setBackgroundColor(backgroundColor: SVGPainterBackgroundColor) {
|
||||
this._backgroundColor = backgroundColor;
|
||||
}
|
||||
|
||||
getSvgRoot() {
|
||||
return this._mainVNode && this._mainVNode.elm as SVGElement;
|
||||
}
|
||||
|
||||
_paintList(list: Displayable[], scope: BrushScope, out?: SVGVNode[]) {
|
||||
const listLen = list.length;
|
||||
|
||||
const clipPathsGroupsStack: SVGVNode[] = [];
|
||||
let clipPathsGroupsStackDepth = 0;
|
||||
let currentClipPathGroup;
|
||||
let prevClipPaths: Path[];
|
||||
let clipGroupNodeIdx = 0;
|
||||
for (let i = 0; i < listLen; i++) {
|
||||
const displayable = list[i];
|
||||
if (!displayable.invisible) {
|
||||
const clipPaths = displayable.__clipPaths;
|
||||
const len = clipPaths && clipPaths.length || 0;
|
||||
const prevLen = prevClipPaths && prevClipPaths.length || 0;
|
||||
let lca;
|
||||
// Find the lowest common ancestor
|
||||
for (lca = Math.max(len - 1, prevLen - 1); lca >= 0; lca--) {
|
||||
if (clipPaths && prevClipPaths
|
||||
&& clipPaths[lca] === prevClipPaths[lca]
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// pop the stack
|
||||
for (let i = prevLen - 1; i > lca; i--) {
|
||||
clipPathsGroupsStackDepth--;
|
||||
// svgEls.push(closeGroup);
|
||||
currentClipPathGroup = clipPathsGroupsStack[clipPathsGroupsStackDepth - 1];
|
||||
}
|
||||
// Pop clip path group for clipPaths not match the previous.
|
||||
for (let i = lca + 1; i < len; i++) {
|
||||
const groupAttrs: SVGVNodeAttrs = {};
|
||||
setClipPath(
|
||||
clipPaths[i],
|
||||
groupAttrs,
|
||||
scope
|
||||
);
|
||||
const g = createVNode(
|
||||
'g',
|
||||
'clip-g-' + clipGroupNodeIdx++,
|
||||
groupAttrs,
|
||||
[]
|
||||
);
|
||||
(currentClipPathGroup ? currentClipPathGroup.children : out).push(g);
|
||||
clipPathsGroupsStack[clipPathsGroupsStackDepth++] = g;
|
||||
currentClipPathGroup = g;
|
||||
}
|
||||
prevClipPaths = clipPaths;
|
||||
|
||||
const ret = brush(displayable, scope);
|
||||
if (ret) {
|
||||
(currentClipPathGroup ? currentClipPathGroup.children : out).push(ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resize(width: number, height: number) {
|
||||
// Save input w/h
|
||||
const opts = this._opts;
|
||||
const root = this.root;
|
||||
const viewport = this._viewport;
|
||||
width != null && (opts.width = width);
|
||||
height != null && (opts.height = height);
|
||||
|
||||
if (root && viewport) {
|
||||
// FIXME Why ?
|
||||
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) {
|
||||
const viewportStyle = viewport.style;
|
||||
viewportStyle.width = width + 'px';
|
||||
viewportStyle.height = height + 'px';
|
||||
}
|
||||
|
||||
if (!isPattern(this._backgroundColor)) {
|
||||
const svgDom = this._svgDom;
|
||||
if (svgDom) {
|
||||
// Set width by 'svgRoot.width = width' is invalid
|
||||
svgDom.setAttribute('width', width as any);
|
||||
svgDom.setAttribute('height', height as any);
|
||||
}
|
||||
|
||||
const bgEl = this._bgVNode && this._bgVNode.elm as SVGElement;
|
||||
if (bgEl) {
|
||||
bgEl.setAttribute('width', width as any);
|
||||
bgEl.setAttribute('height', height as any);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// pattern backgroundColor requires a full refresh
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取绘图区域宽度
|
||||
*/
|
||||
getWidth() {
|
||||
return this._width;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取绘图区域高度
|
||||
*/
|
||||
getHeight() {
|
||||
return this._height;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this.root) {
|
||||
this.root.innerHTML = '';
|
||||
}
|
||||
|
||||
this._svgDom =
|
||||
this._viewport =
|
||||
this.storage =
|
||||
this._oldVNode =
|
||||
this._bgVNode =
|
||||
this._mainVNode = null;
|
||||
}
|
||||
clear() {
|
||||
if (this._svgDom) {
|
||||
this._svgDom.innerHTML = null;
|
||||
}
|
||||
this._oldVNode = null;
|
||||
}
|
||||
toDataURL(base64?: boolean) {
|
||||
let str = this.renderToString();
|
||||
const prefix = 'data:image/svg+xml;';
|
||||
if (base64) {
|
||||
str = encodeBase64(str);
|
||||
return str && prefix + 'base64,' + str;
|
||||
}
|
||||
return prefix + 'charset=UTF-8,' + encodeURIComponent(str);
|
||||
}
|
||||
|
||||
refreshHover = createMethodNotSupport('refreshHover') as PainterBase['refreshHover'];
|
||||
configLayer = createMethodNotSupport('configLayer') as PainterBase['configLayer'];
|
||||
}
|
||||
|
||||
|
||||
// Not supported methods
|
||||
function createMethodNotSupport(method: string): any {
|
||||
return function () {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
logError('In SVG mode painter not support method "' + method + '"');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createBackgroundVNode(
|
||||
width: number,
|
||||
height: number,
|
||||
backgroundColor: SVGPainterBackgroundColor,
|
||||
scope: BrushScope
|
||||
) {
|
||||
let bgVNode;
|
||||
if (backgroundColor && backgroundColor !== 'none') {
|
||||
bgVNode = createVNode(
|
||||
'rect',
|
||||
'bg',
|
||||
{
|
||||
width,
|
||||
height,
|
||||
x: '0',
|
||||
y: '0'
|
||||
}
|
||||
);
|
||||
if (isGradient(backgroundColor)) {
|
||||
setGradient({ fill: backgroundColor as any }, bgVNode.attrs, 'fill', scope);
|
||||
}
|
||||
else if (isPattern(backgroundColor)) {
|
||||
setPattern({
|
||||
style: {
|
||||
fill: backgroundColor
|
||||
},
|
||||
dirty: noop,
|
||||
getBoundingRect: () => ({ width, height })
|
||||
} as any, bgVNode.attrs, 'fill', scope);
|
||||
}
|
||||
else {
|
||||
const { color, opacity } = normalizeColor(backgroundColor);
|
||||
bgVNode.attrs.fill = color;
|
||||
opacity < 1 && (bgVNode.attrs['fill-opacity'] = opacity);
|
||||
}
|
||||
}
|
||||
return bgVNode;
|
||||
}
|
||||
|
||||
export default SVGPainter;
|
||||
Reference in New Issue
Block a user