测试
This commit is contained in:
421
frontend/node_modules/zrender/src/svg-legacy/Painter.ts
generated
vendored
Normal file
421
frontend/node_modules/zrender/src/svg-legacy/Painter.ts
generated
vendored
Normal file
@@ -0,0 +1,421 @@
|
||||
/**
|
||||
* SVG Painter
|
||||
*/
|
||||
|
||||
import {createElement, SVGNS, XLINKNS, XMLNS} from '../svg/core';
|
||||
import { normalizeColor } from '../svg/helper';
|
||||
import * as util from '../core/util';
|
||||
import Path from '../graphic/Path';
|
||||
import ZRImage from '../graphic/Image';
|
||||
import TSpan from '../graphic/TSpan';
|
||||
import arrayDiff from '../core/arrayDiff';
|
||||
import GradientManager from './helper/GradientManager';
|
||||
import PatternManager from './helper/PatternManager';
|
||||
import ClippathManager, {hasClipPath} from './helper/ClippathManager';
|
||||
import ShadowManager from './helper/ShadowManager';
|
||||
import {
|
||||
path as svgPath,
|
||||
image as svgImage,
|
||||
text as svgText,
|
||||
SVGProxy
|
||||
} from './graphic';
|
||||
import Displayable from '../graphic/Displayable';
|
||||
import Storage from '../Storage';
|
||||
import { PainterBase } from '../PainterBase';
|
||||
import { getSize } from '../canvas/helper';
|
||||
|
||||
function getSvgProxy(el: Displayable) {
|
||||
if (el instanceof Path) {
|
||||
return svgPath;
|
||||
}
|
||||
else if (el instanceof ZRImage) {
|
||||
return svgImage;
|
||||
}
|
||||
else if (el instanceof TSpan) {
|
||||
return svgText;
|
||||
}
|
||||
else {
|
||||
return svgPath;
|
||||
}
|
||||
}
|
||||
|
||||
function checkParentAvailable(parent: SVGElement, child: SVGElement) {
|
||||
return child && parent && child.parentNode !== parent;
|
||||
}
|
||||
|
||||
function insertAfter(parent: SVGElement, child: SVGElement, prevSibling: SVGElement) {
|
||||
if (checkParentAvailable(parent, child) && prevSibling) {
|
||||
const nextSibling = prevSibling.nextSibling;
|
||||
nextSibling ? parent.insertBefore(child, nextSibling)
|
||||
: parent.appendChild(child);
|
||||
}
|
||||
}
|
||||
|
||||
function prepend(parent: SVGElement, child: SVGElement) {
|
||||
if (checkParentAvailable(parent, child)) {
|
||||
const firstChild = parent.firstChild;
|
||||
firstChild ? parent.insertBefore(child, firstChild)
|
||||
: parent.appendChild(child);
|
||||
}
|
||||
}
|
||||
|
||||
function remove(parent: SVGElement, child: SVGElement) {
|
||||
if (child && parent && child.parentNode === parent) {
|
||||
parent.removeChild(child);
|
||||
}
|
||||
}
|
||||
function removeFromMyParent(child: SVGElement) {
|
||||
if (child && child.parentNode) {
|
||||
child.parentNode.removeChild(child);
|
||||
}
|
||||
}
|
||||
|
||||
function getSvgElement(displayable: Displayable) {
|
||||
return displayable.__svgEl;
|
||||
}
|
||||
|
||||
interface SVGPainterOption {
|
||||
width?: number | string
|
||||
height?: number | string
|
||||
}
|
||||
|
||||
class SVGPainter implements PainterBase {
|
||||
|
||||
type = 'svg'
|
||||
|
||||
root: HTMLElement
|
||||
|
||||
storage: Storage
|
||||
|
||||
private _opts: SVGPainterOption
|
||||
|
||||
private _svgDom: SVGElement
|
||||
private _svgRoot: SVGGElement
|
||||
private _backgroundRoot: SVGGElement
|
||||
private _backgroundNode: SVGRectElement
|
||||
|
||||
private _gradientManager: GradientManager
|
||||
private _patternManager: PatternManager
|
||||
private _clipPathManager: ClippathManager
|
||||
private _shadowManager: ShadowManager
|
||||
|
||||
private _viewport: HTMLDivElement
|
||||
private _visibleList: Displayable[]
|
||||
|
||||
private _width: number
|
||||
private _height: number
|
||||
|
||||
constructor(root: HTMLElement, storage: Storage, opts: SVGPainterOption, zrId: number) {
|
||||
this.root = root;
|
||||
this.storage = storage;
|
||||
this._opts = opts = util.extend({}, opts || {});
|
||||
|
||||
const svgDom = createElement('svg');
|
||||
svgDom.setAttributeNS(XMLNS, 'xmlns', SVGNS);
|
||||
svgDom.setAttributeNS(XMLNS, 'xmlns:xlink', XLINKNS);
|
||||
|
||||
svgDom.setAttribute('version', '1.1');
|
||||
svgDom.setAttribute('baseProfile', 'full');
|
||||
svgDom.style.cssText = 'user-select:none;position:absolute;left:0;top:0;';
|
||||
|
||||
const bgRoot = createElement('g') as SVGGElement;
|
||||
svgDom.appendChild(bgRoot);
|
||||
const svgRoot = createElement('g') as SVGGElement;
|
||||
svgDom.appendChild(svgRoot);
|
||||
|
||||
this._gradientManager = new GradientManager(zrId, svgRoot);
|
||||
this._patternManager = new PatternManager(zrId, svgRoot);
|
||||
this._clipPathManager = new ClippathManager(zrId, svgRoot);
|
||||
this._shadowManager = new ShadowManager(zrId, svgRoot);
|
||||
|
||||
const viewport = document.createElement('div');
|
||||
viewport.style.cssText = 'overflow:hidden;position:relative';
|
||||
|
||||
this._svgDom = svgDom;
|
||||
this._svgRoot = svgRoot;
|
||||
this._backgroundRoot = bgRoot;
|
||||
this._viewport = viewport;
|
||||
|
||||
root.appendChild(viewport);
|
||||
viewport.appendChild(svgDom);
|
||||
|
||||
this.resize(opts.width, opts.height);
|
||||
|
||||
this._visibleList = [];
|
||||
}
|
||||
|
||||
getType() {
|
||||
return 'svg';
|
||||
}
|
||||
|
||||
getViewportRoot() {
|
||||
return this._viewport;
|
||||
}
|
||||
|
||||
getSvgDom() {
|
||||
return this._svgDom;
|
||||
}
|
||||
|
||||
getSvgRoot() {
|
||||
return this._svgRoot;
|
||||
}
|
||||
|
||||
getViewportRootOffset() {
|
||||
const viewportRoot = this.getViewportRoot();
|
||||
if (viewportRoot) {
|
||||
return {
|
||||
offsetLeft: viewportRoot.offsetLeft || 0,
|
||||
offsetTop: viewportRoot.offsetTop || 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
refresh() {
|
||||
const list = this.storage.getDisplayList(true);
|
||||
this._paintList(list);
|
||||
}
|
||||
|
||||
setBackgroundColor(backgroundColor: string) {
|
||||
// TODO gradient
|
||||
// Insert a bg rect instead of setting background to viewport.
|
||||
// Otherwise, the exported SVG don't have background.
|
||||
if (this._backgroundRoot && this._backgroundNode) {
|
||||
this._backgroundRoot.removeChild(this._backgroundNode);
|
||||
}
|
||||
|
||||
const bgNode = createElement('rect') as SVGRectElement;
|
||||
bgNode.setAttribute('width', this.getWidth() as any);
|
||||
bgNode.setAttribute('height', this.getHeight() as any);
|
||||
bgNode.setAttribute('x', 0 as any);
|
||||
bgNode.setAttribute('y', 0 as any);
|
||||
bgNode.setAttribute('id', 0 as any);
|
||||
const { color, opacity } = normalizeColor(backgroundColor);
|
||||
bgNode.setAttribute('fill', color);
|
||||
bgNode.setAttribute('fill-opacity', opacity as any);
|
||||
|
||||
this._backgroundRoot.appendChild(bgNode);
|
||||
this._backgroundNode = bgNode;
|
||||
}
|
||||
|
||||
createSVGElement(tag: string): SVGElement {
|
||||
return createElement(tag);
|
||||
}
|
||||
|
||||
paintOne(el: Displayable): SVGElement {
|
||||
const svgProxy = getSvgProxy(el);
|
||||
svgProxy && (svgProxy as SVGProxy<Displayable>).brush(el);
|
||||
return getSvgElement(el);
|
||||
}
|
||||
|
||||
_paintList(list: Displayable[]) {
|
||||
const gradientManager = this._gradientManager;
|
||||
const patternManager = this._patternManager;
|
||||
const clipPathManager = this._clipPathManager;
|
||||
const shadowManager = this._shadowManager;
|
||||
|
||||
gradientManager.markAllUnused();
|
||||
patternManager.markAllUnused();
|
||||
clipPathManager.markAllUnused();
|
||||
shadowManager.markAllUnused();
|
||||
|
||||
const svgRoot = this._svgRoot;
|
||||
const visibleList = this._visibleList;
|
||||
const listLen = list.length;
|
||||
|
||||
const newVisibleList = [];
|
||||
|
||||
for (let i = 0; i < listLen; i++) {
|
||||
const displayable = list[i];
|
||||
const svgProxy = getSvgProxy(displayable);
|
||||
let svgElement = getSvgElement(displayable);
|
||||
if (!displayable.invisible) {
|
||||
if (displayable.__dirty || !svgElement) {
|
||||
svgProxy && (svgProxy as SVGProxy<Displayable>).brush(displayable);
|
||||
svgElement = getSvgElement(displayable);
|
||||
// Update gradient and shadow
|
||||
if (svgElement && displayable.style) {
|
||||
gradientManager.update(displayable.style.fill);
|
||||
gradientManager.update(displayable.style.stroke);
|
||||
patternManager.update(displayable.style.fill);
|
||||
patternManager.update(displayable.style.stroke);
|
||||
shadowManager.update(svgElement, displayable);
|
||||
}
|
||||
|
||||
displayable.__dirty = 0;
|
||||
}
|
||||
|
||||
// May have optimizations and ignore brush(like empty string in TSpan)
|
||||
if (svgElement) {
|
||||
newVisibleList.push(displayable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const diff = arrayDiff(visibleList, newVisibleList);
|
||||
let prevSvgElement;
|
||||
let topPrevSvgElement;
|
||||
|
||||
// NOTE: First do remove, in case element moved to the head and do remove
|
||||
// after add
|
||||
for (let i = 0; i < diff.length; i++) {
|
||||
const item = diff[i];
|
||||
if (item.removed) {
|
||||
for (let k = 0; k < item.count; k++) {
|
||||
const displayable = visibleList[item.indices[k]];
|
||||
const svgElement = getSvgElement(displayable);
|
||||
hasClipPath(displayable) ? removeFromMyParent(svgElement)
|
||||
: remove(svgRoot, svgElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let prevDisplayable;
|
||||
let currentClipGroup;
|
||||
for (let i = 0; i < diff.length; i++) {
|
||||
const item = diff[i];
|
||||
// const isAdd = item.added;
|
||||
if (item.removed) {
|
||||
continue;
|
||||
}
|
||||
for (let k = 0; k < item.count; k++) {
|
||||
const displayable = newVisibleList[item.indices[k]];
|
||||
// Update clipPath
|
||||
const clipGroup = clipPathManager.update(displayable, prevDisplayable);
|
||||
if (clipGroup !== currentClipGroup) {
|
||||
// First pop to top level.
|
||||
prevSvgElement = topPrevSvgElement;
|
||||
if (clipGroup) {
|
||||
// Enter second level of clipping group.
|
||||
prevSvgElement ? insertAfter(svgRoot, clipGroup, prevSvgElement)
|
||||
: prepend(svgRoot, clipGroup);
|
||||
topPrevSvgElement = clipGroup;
|
||||
// Reset prevSvgElement in second level.
|
||||
prevSvgElement = null;
|
||||
}
|
||||
currentClipGroup = clipGroup;
|
||||
}
|
||||
|
||||
const svgElement = getSvgElement(displayable);
|
||||
// if (isAdd) {
|
||||
prevSvgElement
|
||||
? insertAfter(currentClipGroup || svgRoot, svgElement, prevSvgElement)
|
||||
: prepend(currentClipGroup || svgRoot, svgElement);
|
||||
// }
|
||||
|
||||
prevSvgElement = svgElement || prevSvgElement;
|
||||
if (!currentClipGroup) {
|
||||
topPrevSvgElement = prevSvgElement;
|
||||
}
|
||||
|
||||
gradientManager.markUsed(displayable);
|
||||
gradientManager.addWithoutUpdate(svgElement, displayable);
|
||||
|
||||
patternManager.markUsed(displayable);
|
||||
patternManager.addWithoutUpdate(svgElement, displayable);
|
||||
|
||||
clipPathManager.markUsed(displayable);
|
||||
|
||||
prevDisplayable = displayable;
|
||||
}
|
||||
}
|
||||
|
||||
gradientManager.removeUnused();
|
||||
patternManager.removeUnused();
|
||||
clipPathManager.removeUnused();
|
||||
shadowManager.removeUnused();
|
||||
|
||||
this._visibleList = newVisibleList;
|
||||
}
|
||||
|
||||
resize(width: number | string, height: number | string) {
|
||||
const viewport = this._viewport;
|
||||
// FIXME Why ?
|
||||
viewport.style.display = 'none';
|
||||
|
||||
// Save input w/h
|
||||
const opts = this._opts;
|
||||
width != null && (opts.width = width);
|
||||
height != null && (opts.height = height);
|
||||
|
||||
width = getSize(this.root, 0, opts);
|
||||
height = getSize(this.root, 1, opts);
|
||||
|
||||
viewport.style.display = '';
|
||||
|
||||
if (this._width !== width || this._height !== height) {
|
||||
this._width = width;
|
||||
this._height = height;
|
||||
|
||||
const viewportStyle = viewport.style;
|
||||
viewportStyle.width = width + 'px';
|
||||
viewportStyle.height = height + 'px';
|
||||
|
||||
const svgRoot = this._svgDom;
|
||||
// Set width by 'svgRoot.width = width' is invalid
|
||||
svgRoot.setAttribute('width', width + '');
|
||||
svgRoot.setAttribute('height', height + '');
|
||||
}
|
||||
|
||||
if (this._backgroundNode) {
|
||||
this._backgroundNode.setAttribute('width', width as any);
|
||||
this._backgroundNode.setAttribute('height', height as any);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取绘图区域宽度
|
||||
*/
|
||||
getWidth() {
|
||||
return this._width;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取绘图区域高度
|
||||
*/
|
||||
getHeight() {
|
||||
return this._height;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.root.innerHTML = '';
|
||||
|
||||
this._svgRoot =
|
||||
this._backgroundRoot =
|
||||
this._svgDom =
|
||||
this._backgroundNode =
|
||||
this._viewport = this.storage = null;
|
||||
}
|
||||
|
||||
clear() {
|
||||
const viewportNode = this._viewport;
|
||||
if (viewportNode && viewportNode.parentNode) {
|
||||
viewportNode.parentNode.removeChild(viewportNode);
|
||||
}
|
||||
}
|
||||
|
||||
toDataURL() {
|
||||
this.refresh();
|
||||
const svgDom = this._svgDom;
|
||||
const outerHTML = svgDom.outerHTML
|
||||
// outerHTML of `svg` tag is not supported in IE, use `parentNode.innerHTML` instead
|
||||
// PENDING: Or use `new XMLSerializer().serializeToString(svg)`?
|
||||
|| (svgDom.parentNode && (svgDom.parentNode as HTMLElement).innerHTML);
|
||||
const html = encodeURIComponent(outerHTML.replace(/></g, '>\n\r<'));
|
||||
return 'data:image/svg+xml;charset=UTF-8,' + html;
|
||||
}
|
||||
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') {
|
||||
util.logError('In SVG mode painter not support method "' + method + '"');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export default SVGPainter;
|
||||
194
frontend/node_modules/zrender/src/svg-legacy/graphic.ts
generated
vendored
Normal file
194
frontend/node_modules/zrender/src/svg-legacy/graphic.ts
generated
vendored
Normal file
@@ -0,0 +1,194 @@
|
||||
// TODO
|
||||
// 1. shadow
|
||||
// 2. Image: sx, sy, sw, sh
|
||||
|
||||
import {createElement, XLINKNS } from '../svg/core';
|
||||
import { getMatrixStr, TEXT_ALIGN_TO_ANCHOR, adjustTextY } from '../svg/helper';
|
||||
import * as matrix from '../core/matrix';
|
||||
import Path, { PathStyleProps } from '../graphic/Path';
|
||||
import ZRImage, { ImageStyleProps } from '../graphic/Image';
|
||||
import { getLineHeight } from '../contain/text';
|
||||
import TSpan, { TSpanStyleProps } from '../graphic/TSpan';
|
||||
import SVGPathRebuilder from '../svg/SVGPathRebuilder';
|
||||
import mapStyleToAttrs from '../svg/mapStyleToAttrs';
|
||||
import { DEFAULT_FONT } from '../core/platform';
|
||||
|
||||
export interface SVGProxy<T> {
|
||||
brush(el: T): void
|
||||
}
|
||||
|
||||
type AllStyleOption = PathStyleProps | TSpanStyleProps | ImageStyleProps;
|
||||
|
||||
function setTransform(svgEl: SVGElement, m: matrix.MatrixArray) {
|
||||
if (m) {
|
||||
attr(svgEl, 'transform', getMatrixStr(m));
|
||||
}
|
||||
}
|
||||
|
||||
function attr(el: SVGElement, key: string, val: string | number) {
|
||||
if (!val || (val as any).type !== 'linear' && (val as any).type !== 'radial') {
|
||||
// Don't set attribute for gradient, since it need new dom nodes
|
||||
el.setAttribute(key, val as any);
|
||||
}
|
||||
}
|
||||
|
||||
function attrXLink(el: SVGElement, key: string, val: string) {
|
||||
el.setAttributeNS(XLINKNS, key, val);
|
||||
}
|
||||
|
||||
function attrXML(el: SVGElement, key: string, val: string) {
|
||||
el.setAttributeNS('http://www.w3.org/XML/1998/namespace', key, val);
|
||||
}
|
||||
|
||||
function bindStyle(svgEl: SVGElement, style: PathStyleProps, el?: Path): void
|
||||
function bindStyle(svgEl: SVGElement, style: TSpanStyleProps, el?: TSpan): void
|
||||
function bindStyle(svgEl: SVGElement, style: ImageStyleProps, el?: ZRImage): void
|
||||
function bindStyle(svgEl: SVGElement, style: AllStyleOption, el?: Path | TSpan | ZRImage) {
|
||||
mapStyleToAttrs((key, val) => attr(svgEl, key, val), style, el, true);
|
||||
}
|
||||
|
||||
interface PathWithSVGBuildPath extends Path {
|
||||
__svgPathVersion: number
|
||||
__svgPathBuilder: SVGPathRebuilder
|
||||
}
|
||||
|
||||
const svgPath: SVGProxy<Path> = {
|
||||
brush(el: Path) {
|
||||
const style = el.style;
|
||||
|
||||
let svgEl = el.__svgEl;
|
||||
if (!svgEl) {
|
||||
svgEl = createElement('path');
|
||||
el.__svgEl = svgEl;
|
||||
}
|
||||
|
||||
if (!el.path) {
|
||||
el.createPathProxy();
|
||||
}
|
||||
const path = el.path;
|
||||
|
||||
if (el.shapeChanged()) {
|
||||
path.beginPath();
|
||||
el.buildPath(path, el.shape);
|
||||
el.pathUpdated();
|
||||
}
|
||||
|
||||
const pathVersion = path.getVersion();
|
||||
const elExt = el as PathWithSVGBuildPath;
|
||||
let svgPathBuilder = elExt.__svgPathBuilder;
|
||||
if (elExt.__svgPathVersion !== pathVersion || !svgPathBuilder || el.style.strokePercent < 1) {
|
||||
if (!svgPathBuilder) {
|
||||
svgPathBuilder = elExt.__svgPathBuilder = new SVGPathRebuilder();
|
||||
}
|
||||
svgPathBuilder.reset();
|
||||
path.rebuildPath(svgPathBuilder, el.style.strokePercent);
|
||||
svgPathBuilder.generateStr();
|
||||
elExt.__svgPathVersion = pathVersion;
|
||||
}
|
||||
|
||||
attr(svgEl, 'd', svgPathBuilder.getStr());
|
||||
|
||||
bindStyle(svgEl, style, el);
|
||||
setTransform(svgEl, el.transform);
|
||||
}
|
||||
};
|
||||
|
||||
export {svgPath as path};
|
||||
|
||||
/***************************************************
|
||||
* IMAGE
|
||||
**************************************************/
|
||||
const svgImage: SVGProxy<ZRImage> = {
|
||||
brush(el: ZRImage) {
|
||||
const style = el.style;
|
||||
let image = style.image;
|
||||
|
||||
if (image instanceof HTMLImageElement) {
|
||||
image = image.src;
|
||||
}
|
||||
// heatmap layer in geo may be a canvas
|
||||
else if (image instanceof HTMLCanvasElement) {
|
||||
image = image.toDataURL();
|
||||
}
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
|
||||
const x = style.x || 0;
|
||||
const y = style.y || 0;
|
||||
|
||||
const dw = style.width;
|
||||
const dh = style.height;
|
||||
|
||||
let svgEl = el.__svgEl;
|
||||
if (!svgEl) {
|
||||
svgEl = createElement('image');
|
||||
el.__svgEl = svgEl;
|
||||
}
|
||||
|
||||
if (image !== el.__imageSrc) {
|
||||
attrXLink(svgEl, 'href', image as string);
|
||||
// Caching image src
|
||||
el.__imageSrc = image as string;
|
||||
}
|
||||
|
||||
attr(svgEl, 'width', dw + '');
|
||||
attr(svgEl, 'height', dh + '');
|
||||
|
||||
attr(svgEl, 'x', x + '');
|
||||
attr(svgEl, 'y', y + '');
|
||||
|
||||
bindStyle(svgEl, style, el);
|
||||
setTransform(svgEl, el.transform);
|
||||
}
|
||||
};
|
||||
export {svgImage as image};
|
||||
|
||||
/***************************************************
|
||||
* TEXT
|
||||
**************************************************/
|
||||
|
||||
|
||||
const svgText: SVGProxy<TSpan> = {
|
||||
brush(el: TSpan) {
|
||||
const style = el.style;
|
||||
|
||||
let text = style.text;
|
||||
// Convert to string
|
||||
text != null && (text += '');
|
||||
if (!text || isNaN(style.x) || isNaN(style.y)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let textSvgEl = el.__svgEl as SVGTextElement;
|
||||
if (!textSvgEl) {
|
||||
textSvgEl = createElement('text') as SVGTextElement;
|
||||
attrXML(textSvgEl, 'xml:space', 'preserve');
|
||||
el.__svgEl = textSvgEl;
|
||||
}
|
||||
|
||||
const font = style.font || DEFAULT_FONT;
|
||||
|
||||
// style.font has been normalized by `normalizeTextStyle`.
|
||||
const textSvgElStyle = textSvgEl.style;
|
||||
textSvgElStyle.font = font;
|
||||
|
||||
textSvgEl.textContent = text;
|
||||
|
||||
bindStyle(textSvgEl, style, el);
|
||||
setTransform(textSvgEl, el.transform);
|
||||
|
||||
// Consider different font display differently in vertial align, we always
|
||||
// set vertialAlign as 'middle', and use 'y' to locate text vertically.
|
||||
const x = style.x || 0;
|
||||
const y = adjustTextY(style.y || 0, getLineHeight(font), style.textBaseline);
|
||||
const textAlign = TEXT_ALIGN_TO_ANCHOR[style.textAlign as keyof typeof TEXT_ALIGN_TO_ANCHOR]
|
||||
|| style.textAlign;
|
||||
|
||||
attr(textSvgEl, 'dominant-baseline', 'central');
|
||||
attr(textSvgEl, 'text-anchor', textAlign);
|
||||
attr(textSvgEl, 'x', x + '');
|
||||
attr(textSvgEl, 'y', y + '');
|
||||
}
|
||||
};
|
||||
export {svgText as text};
|
||||
173
frontend/node_modules/zrender/src/svg-legacy/helper/ClippathManager.ts
generated
vendored
Normal file
173
frontend/node_modules/zrender/src/svg-legacy/helper/ClippathManager.ts
generated
vendored
Normal file
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* @file Manages SVG clipPath elements.
|
||||
* @author Zhang Wenli
|
||||
*/
|
||||
|
||||
import Definable from './Definable';
|
||||
import * as zrUtil from '../../core/util';
|
||||
import Displayable from '../../graphic/Displayable';
|
||||
import Path from '../../graphic/Path';
|
||||
import {path} from '../graphic';
|
||||
import { Dictionary } from '../../core/types';
|
||||
import { isClipPathChanged } from '../../canvas/helper';
|
||||
import { getClipPathsKey, getIdURL } from '../../svg/helper';
|
||||
import { createElement } from '../../svg/core';
|
||||
|
||||
type PathExtended = Path & {
|
||||
_dom: SVGElement
|
||||
}
|
||||
|
||||
export function hasClipPath(displayable: Displayable) {
|
||||
const clipPaths = displayable.__clipPaths;
|
||||
return clipPaths && clipPaths.length > 0;
|
||||
}
|
||||
/**
|
||||
* Manages SVG clipPath elements.
|
||||
*/
|
||||
export default class ClippathManager extends Definable {
|
||||
|
||||
private _refGroups: Dictionary<SVGElement> = {};
|
||||
private _keyDuplicateCount: Dictionary<number> = {};
|
||||
|
||||
constructor(zrId: number, svgRoot: SVGElement) {
|
||||
super(zrId, svgRoot, 'clipPath', '__clippath_in_use__');
|
||||
}
|
||||
|
||||
markAllUnused() {
|
||||
super.markAllUnused();
|
||||
const refGroups = this._refGroups;
|
||||
for (let key in refGroups) {
|
||||
if (refGroups.hasOwnProperty(key)) {
|
||||
this.markDomUnused(refGroups[key]);
|
||||
}
|
||||
}
|
||||
this._keyDuplicateCount = {};
|
||||
}
|
||||
|
||||
|
||||
private _getClipPathGroup(displayable: Displayable, prevDisplayable: Displayable) {
|
||||
if (!hasClipPath(displayable)) {
|
||||
return;
|
||||
}
|
||||
const clipPaths = displayable.__clipPaths;
|
||||
|
||||
const keyDuplicateCount = this._keyDuplicateCount;
|
||||
let clipPathKey = getClipPathsKey(clipPaths);
|
||||
if (isClipPathChanged(clipPaths, prevDisplayable && prevDisplayable.__clipPaths)) {
|
||||
keyDuplicateCount[clipPathKey] = keyDuplicateCount[clipPathKey] || 0;
|
||||
keyDuplicateCount[clipPathKey] && (clipPathKey += '-' + keyDuplicateCount[clipPathKey]);
|
||||
keyDuplicateCount[clipPathKey]++;
|
||||
}
|
||||
|
||||
return this._refGroups[clipPathKey]
|
||||
|| (this._refGroups[clipPathKey] = createElement('g'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update clipPath.
|
||||
*
|
||||
* @param displayable displayable element
|
||||
*/
|
||||
update(displayable: Displayable, prevDisplayable: Displayable) {
|
||||
const clipGroup = this._getClipPathGroup(displayable, prevDisplayable);
|
||||
if (clipGroup) {
|
||||
this.markDomUsed(clipGroup);
|
||||
this.updateDom(clipGroup, displayable.__clipPaths);
|
||||
}
|
||||
return clipGroup;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create an SVGElement of displayable and create a <clipPath> of its
|
||||
* clipPath
|
||||
*/
|
||||
updateDom(parentEl: SVGElement, clipPaths: Path[]) {
|
||||
if (clipPaths && clipPaths.length > 0) {
|
||||
// Has clipPath, create <clipPath> with the first clipPath
|
||||
const defs = this.getDefs(true);
|
||||
const clipPath = clipPaths[0] as PathExtended;
|
||||
let clipPathEl;
|
||||
let id;
|
||||
|
||||
if (clipPath._dom) {
|
||||
// Use a dom that is already in <defs>
|
||||
id = clipPath._dom.getAttribute('id');
|
||||
clipPathEl = clipPath._dom;
|
||||
|
||||
// Use a dom that is already in <defs>
|
||||
if (!defs.contains(clipPathEl)) {
|
||||
// This happens when set old clipPath that has
|
||||
// been previously removed
|
||||
defs.appendChild(clipPathEl);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// New <clipPath>
|
||||
id = 'zr' + this._zrId + '-clip-' + this.nextId;
|
||||
++this.nextId;
|
||||
clipPathEl = createElement('clipPath');
|
||||
clipPathEl.setAttribute('id', id);
|
||||
defs.appendChild(clipPathEl);
|
||||
|
||||
clipPath._dom = clipPathEl;
|
||||
}
|
||||
|
||||
// Build path and add to <clipPath>
|
||||
path.brush(clipPath);
|
||||
|
||||
const pathEl = this.getSvgElement(clipPath);
|
||||
|
||||
clipPathEl.innerHTML = '';
|
||||
clipPathEl.appendChild(pathEl);
|
||||
|
||||
parentEl.setAttribute('clip-path', getIdURL(id));
|
||||
|
||||
if (clipPaths.length > 1) {
|
||||
// Make the other clipPaths recursively
|
||||
this.updateDom(clipPathEl, clipPaths.slice(1));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No clipPath
|
||||
if (parentEl) {
|
||||
parentEl.setAttribute('clip-path', 'none');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Mark a single clipPath to be used
|
||||
*
|
||||
* @param displayable displayable element
|
||||
*/
|
||||
markUsed(displayable: Displayable) {
|
||||
// displayable.__clipPaths can only be `null`/`undefined` or an non-empty array.
|
||||
if (displayable.__clipPaths) {
|
||||
zrUtil.each(displayable.__clipPaths, (clipPath: PathExtended) => {
|
||||
if (clipPath._dom) {
|
||||
super.markDomUsed(clipPath._dom);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
removeUnused() {
|
||||
super.removeUnused();
|
||||
|
||||
const newRefGroupsMap: Dictionary<SVGElement> = {};
|
||||
const refGroups = this._refGroups;
|
||||
for (let key in refGroups) {
|
||||
if (refGroups.hasOwnProperty(key)) {
|
||||
const group = refGroups[key];
|
||||
if (!this.isDomUnused(group)) {
|
||||
newRefGroupsMap[key] = group;
|
||||
}
|
||||
else if (group.parentNode) {
|
||||
group.parentNode.removeChild(group);
|
||||
}
|
||||
}
|
||||
}
|
||||
this._refGroups = newRefGroupsMap;
|
||||
}
|
||||
}
|
||||
235
frontend/node_modules/zrender/src/svg-legacy/helper/Definable.ts
generated
vendored
Normal file
235
frontend/node_modules/zrender/src/svg-legacy/helper/Definable.ts
generated
vendored
Normal file
@@ -0,0 +1,235 @@
|
||||
/**
|
||||
* @file Manages elements that can be defined in <defs> in SVG,
|
||||
* e.g., gradients, clip path, etc.
|
||||
* @author Zhang Wenli
|
||||
*/
|
||||
|
||||
import {createElement} from '../../svg/core';
|
||||
import * as zrUtil from '../../core/util';
|
||||
import Displayable from '../../graphic/Displayable';
|
||||
|
||||
|
||||
const MARK_UNUSED = '0';
|
||||
const MARK_USED = '1';
|
||||
|
||||
/**
|
||||
* Manages elements that can be defined in <defs> in SVG,
|
||||
* e.g., gradients, clip path, etc.
|
||||
*/
|
||||
export default class Definable {
|
||||
|
||||
nextId = 0
|
||||
|
||||
protected _zrId: number
|
||||
protected _svgRoot: SVGElement
|
||||
protected _tagNames: string[]
|
||||
protected _markLabel: string
|
||||
protected _domName: string = '_dom'
|
||||
|
||||
constructor(
|
||||
zrId: number, // zrender instance id
|
||||
svgRoot: SVGElement, // root of SVG document
|
||||
tagNames: string | string[], // possible tag names
|
||||
markLabel: string, // label name to make if the element
|
||||
domName?: string
|
||||
) {
|
||||
this._zrId = zrId;
|
||||
this._svgRoot = svgRoot;
|
||||
this._tagNames = typeof tagNames === 'string' ? [tagNames] : tagNames;
|
||||
this._markLabel = markLabel;
|
||||
|
||||
if (domName) {
|
||||
this._domName = domName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the <defs> tag for svgRoot; optionally creates one if not exists.
|
||||
*
|
||||
* @param isForceCreating if need to create when not exists
|
||||
* @return SVG <defs> element, null if it doesn't
|
||||
* exist and isForceCreating is false
|
||||
*/
|
||||
getDefs(isForceCreating?: boolean): SVGDefsElement {
|
||||
let svgRoot = this._svgRoot;
|
||||
let defs = this._svgRoot.getElementsByTagName('defs');
|
||||
if (defs.length === 0) {
|
||||
// Not exist
|
||||
if (isForceCreating) {
|
||||
let defs = svgRoot.insertBefore(
|
||||
createElement('defs'), // Create new tag
|
||||
svgRoot.firstChild // Insert in the front of svg
|
||||
) as SVGDefsElement;
|
||||
if (!defs.contains) {
|
||||
// IE doesn't support contains method
|
||||
defs.contains = function (el) {
|
||||
const children = defs.children;
|
||||
if (!children) {
|
||||
return false;
|
||||
}
|
||||
for (let i = children.length - 1; i >= 0; --i) {
|
||||
if (children[i] === el) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
return defs;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return defs[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update DOM element if necessary.
|
||||
*
|
||||
* @param element style element. e.g., for gradient,
|
||||
* it may be '#ccc' or {type: 'linear', ...}
|
||||
* @param onUpdate update callback
|
||||
*/
|
||||
doUpdate<T>(target: T, onUpdate?: (target: T) => void) {
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
const defs = this.getDefs(false);
|
||||
if ((target as any)[this._domName] && defs.contains((target as any)[this._domName])) {
|
||||
// Update DOM
|
||||
if (typeof onUpdate === 'function') {
|
||||
onUpdate(target);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No previous dom, create new
|
||||
const dom = this.add(target);
|
||||
if (dom) {
|
||||
(target as any)[this._domName] = dom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add(target: any): SVGElement {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add gradient dom to defs
|
||||
*
|
||||
* @param dom DOM to be added to <defs>
|
||||
*/
|
||||
addDom(dom: SVGElement) {
|
||||
const defs = this.getDefs(true);
|
||||
if (dom.parentNode !== defs) {
|
||||
defs.appendChild(dom);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove DOM of a given element.
|
||||
*
|
||||
* @param target Target where to attach the dom
|
||||
*/
|
||||
removeDom<T>(target: T) {
|
||||
const defs = this.getDefs(false);
|
||||
if (defs && (target as any)[this._domName]) {
|
||||
defs.removeChild((target as any)[this._domName]);
|
||||
(target as any)[this._domName] = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get DOMs of this element.
|
||||
*
|
||||
* @return doms of this defineable elements in <defs>
|
||||
*/
|
||||
getDoms() {
|
||||
const defs = this.getDefs(false);
|
||||
if (!defs) {
|
||||
// No dom when defs is not defined
|
||||
return [];
|
||||
}
|
||||
|
||||
let doms: SVGElement[] = [];
|
||||
zrUtil.each(this._tagNames, function (tagName) {
|
||||
const tags = defs.getElementsByTagName(tagName) as HTMLCollectionOf<SVGElement>;
|
||||
// Note that tags is HTMLCollection, which is array-like
|
||||
// rather than real array.
|
||||
// So `doms.concat(tags)` add tags as one object.
|
||||
for (let i = 0; i < tags.length; i++) {
|
||||
doms.push(tags[i]);
|
||||
}
|
||||
});
|
||||
|
||||
return doms;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Mark DOMs to be unused before painting, and clear unused ones at the end
|
||||
* of the painting.
|
||||
*/
|
||||
markAllUnused() {
|
||||
const doms = this.getDoms();
|
||||
const that = this;
|
||||
zrUtil.each(doms, function (dom) {
|
||||
(dom as any)[that._markLabel] = MARK_UNUSED;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Mark a single DOM to be used.
|
||||
*
|
||||
* @param dom DOM to mark
|
||||
*/
|
||||
markDomUsed(dom: SVGElement) {
|
||||
dom && ((dom as any)[this._markLabel] = MARK_USED);
|
||||
};
|
||||
|
||||
markDomUnused(dom: SVGElement) {
|
||||
dom && ((dom as any)[this._markLabel] = MARK_UNUSED);
|
||||
};
|
||||
|
||||
isDomUnused(dom: SVGElement) {
|
||||
return dom && (dom as any)[this._markLabel] !== MARK_USED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove unused DOMs defined in <defs>
|
||||
*/
|
||||
removeUnused() {
|
||||
const defs = this.getDefs(false);
|
||||
if (!defs) {
|
||||
// Nothing to remove
|
||||
return;
|
||||
}
|
||||
|
||||
const doms = this.getDoms();
|
||||
zrUtil.each(doms, (dom) => {
|
||||
if (this.isDomUnused(dom)) {
|
||||
// Remove gradient
|
||||
defs.removeChild(dom);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SVG element.
|
||||
*
|
||||
* @param displayable displayable element
|
||||
* @return SVG element
|
||||
*/
|
||||
getSvgElement(displayable: Displayable): SVGElement {
|
||||
return displayable.__svgEl;
|
||||
}
|
||||
|
||||
}
|
||||
225
frontend/node_modules/zrender/src/svg-legacy/helper/GradientManager.ts
generated
vendored
Normal file
225
frontend/node_modules/zrender/src/svg-legacy/helper/GradientManager.ts
generated
vendored
Normal file
@@ -0,0 +1,225 @@
|
||||
/**
|
||||
* @file Manages SVG gradient elements.
|
||||
* @author Zhang Wenli
|
||||
*/
|
||||
|
||||
import Definable from './Definable';
|
||||
import * as zrUtil from '../../core/util';
|
||||
import Displayable from '../../graphic/Displayable';
|
||||
import { GradientObject } from '../../graphic/Gradient';
|
||||
import { getIdURL, isGradient, isLinearGradient, isRadialGradient, normalizeColor, round4 } from '../../svg/helper';
|
||||
import { createElement } from '../../svg/core';
|
||||
|
||||
|
||||
type GradientObjectExtended = GradientObject & {
|
||||
__dom: SVGElement
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages SVG gradient elements.
|
||||
*
|
||||
* @param zrId zrender instance id
|
||||
* @param svgRoot root of SVG document
|
||||
*/
|
||||
export default class GradientManager extends Definable {
|
||||
|
||||
constructor(zrId: number, svgRoot: SVGElement) {
|
||||
super(zrId, svgRoot, ['linearGradient', 'radialGradient'], '__gradient_in_use__');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create new gradient DOM for fill or stroke if not exist,
|
||||
* but will not update gradient if exists.
|
||||
*
|
||||
* @param svgElement SVG element to paint
|
||||
* @param displayable zrender displayable element
|
||||
*/
|
||||
addWithoutUpdate(
|
||||
svgElement: SVGElement,
|
||||
displayable: Displayable
|
||||
) {
|
||||
if (displayable && displayable.style) {
|
||||
const that = this;
|
||||
zrUtil.each(['fill', 'stroke'], function (fillOrStroke: 'fill' | 'stroke') {
|
||||
let value = displayable.style[fillOrStroke] as GradientObject;
|
||||
if (isGradient(value)) {
|
||||
const gradient = value as GradientObjectExtended;
|
||||
const defs = that.getDefs(true);
|
||||
|
||||
// Create dom in <defs> if not exists
|
||||
let dom;
|
||||
if (gradient.__dom) {
|
||||
// Gradient exists
|
||||
dom = gradient.__dom;
|
||||
if (!defs.contains(gradient.__dom)) {
|
||||
// __dom is no longer in defs, recreate
|
||||
that.addDom(dom);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// New dom
|
||||
dom = that.add(gradient);
|
||||
}
|
||||
|
||||
that.markUsed(displayable);
|
||||
|
||||
svgElement.setAttribute(fillOrStroke, getIdURL(dom.getAttribute('id')));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a new gradient tag in <defs>
|
||||
*
|
||||
* @param gradient zr gradient instance
|
||||
*/
|
||||
add(gradient: GradientObject): SVGElement {
|
||||
let dom;
|
||||
if (isLinearGradient(gradient)) {
|
||||
dom = createElement('linearGradient');
|
||||
}
|
||||
else if (isRadialGradient(gradient)) {
|
||||
dom = createElement('radialGradient');
|
||||
}
|
||||
else {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
zrUtil.logError('Illegal gradient type.');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Set dom id with gradient id, since each gradient instance
|
||||
// will have no more than one dom element.
|
||||
// id may exists before for those dirty elements, in which case
|
||||
// id should remain the same, and other attributes should be
|
||||
// updated.
|
||||
gradient.id = gradient.id || this.nextId++;
|
||||
dom.setAttribute('id', 'zr' + this._zrId
|
||||
+ '-gradient-' + gradient.id);
|
||||
|
||||
this.updateDom(gradient, dom);
|
||||
this.addDom(dom);
|
||||
|
||||
return dom;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update gradient.
|
||||
*
|
||||
* @param gradient zr gradient instance or color string
|
||||
*/
|
||||
update(gradient: GradientObject | string) {
|
||||
if (!isGradient(gradient)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const that = this;
|
||||
this.doUpdate(gradient, function () {
|
||||
const dom = (gradient as GradientObjectExtended).__dom;
|
||||
if (!dom) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tagName = dom.tagName;
|
||||
const type = gradient.type;
|
||||
if (type === 'linear' && tagName === 'linearGradient'
|
||||
|| type === 'radial' && tagName === 'radialGradient'
|
||||
) {
|
||||
// Gradient type is not changed, update gradient
|
||||
that.updateDom(gradient, (gradient as GradientObjectExtended).__dom);
|
||||
}
|
||||
else {
|
||||
// Remove and re-create if type is changed
|
||||
that.removeDom(gradient);
|
||||
that.add(gradient);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update gradient dom
|
||||
*
|
||||
* @param gradient zr gradient instance
|
||||
* @param dom DOM to update
|
||||
*/
|
||||
updateDom(gradient: GradientObject, dom: SVGElement) {
|
||||
if (isLinearGradient(gradient)) {
|
||||
dom.setAttribute('x1', gradient.x as any);
|
||||
dom.setAttribute('y1', gradient.y as any);
|
||||
dom.setAttribute('x2', gradient.x2 as any);
|
||||
dom.setAttribute('y2', gradient.y2 as any);
|
||||
}
|
||||
else if (isRadialGradient(gradient)) {
|
||||
dom.setAttribute('cx', gradient.x as any);
|
||||
dom.setAttribute('cy', gradient.y as any);
|
||||
dom.setAttribute('r', gradient.r as any);
|
||||
}
|
||||
else {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
zrUtil.logError('Illegal gradient type.');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
dom.setAttribute('gradientUnits',
|
||||
gradient.global
|
||||
? 'userSpaceOnUse' // x1, x2, y1, y2 in range of 0 to canvas width or height
|
||||
: 'objectBoundingBox' // x1, x2, y1, y2 in range of 0 to 1
|
||||
);
|
||||
|
||||
// Remove color stops if exists
|
||||
dom.innerHTML = '';
|
||||
|
||||
// Add color stops
|
||||
const colors = gradient.colorStops;
|
||||
for (let i = 0, len = colors.length; i < len; ++i) {
|
||||
const stop = createElement('stop');
|
||||
stop.setAttribute('offset', round4(colors[i].offset) * 100 + '%');
|
||||
|
||||
const stopColor = colors[i].color;
|
||||
// Fix Safari bug that stop-color not recognizing alpha #9014
|
||||
const {color, opacity} = normalizeColor(stopColor);
|
||||
// stop-color cannot be color, since:
|
||||
// The opacity value used for the gradient calculation is the
|
||||
// *product* of the value of stop-opacity and the opacity of the
|
||||
// value of stop-color.
|
||||
// See https://www.w3.org/TR/SVG2/pservers.html#StopOpacityProperty
|
||||
stop.setAttribute('stop-color', color);
|
||||
if (opacity < 1) {
|
||||
stop.setAttribute('stop-opacity', opacity as any);
|
||||
}
|
||||
|
||||
dom.appendChild(stop);
|
||||
}
|
||||
|
||||
// Store dom element in gradient, to avoid creating multiple
|
||||
// dom instances for the same gradient element
|
||||
(gradient as GradientObject as GradientObjectExtended).__dom = dom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a single gradient to be used
|
||||
*
|
||||
* @param displayable displayable element
|
||||
*/
|
||||
markUsed(displayable: Displayable) {
|
||||
if (displayable.style) {
|
||||
let gradient = displayable.style.fill as GradientObject as GradientObjectExtended;
|
||||
if (gradient && gradient.__dom) {
|
||||
super.markDomUsed(gradient.__dom);
|
||||
}
|
||||
|
||||
gradient = displayable.style.stroke as GradientObject as GradientObjectExtended;
|
||||
if (gradient && gradient.__dom) {
|
||||
super.markDomUsed(gradient.__dom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
221
frontend/node_modules/zrender/src/svg-legacy/helper/PatternManager.ts
generated
vendored
Normal file
221
frontend/node_modules/zrender/src/svg-legacy/helper/PatternManager.ts
generated
vendored
Normal file
@@ -0,0 +1,221 @@
|
||||
/**
|
||||
* @file Manages SVG pattern elements.
|
||||
* @author Zhang Wenli
|
||||
*/
|
||||
|
||||
import Definable from './Definable';
|
||||
import * as zrUtil from '../../core/util';
|
||||
import Displayable from '../../graphic/Displayable';
|
||||
import {PatternObject} from '../../graphic/Pattern';
|
||||
import {createOrUpdateImage} from '../../graphic/helper/image';
|
||||
import WeakMap from '../../core/WeakMap';
|
||||
import { getIdURL, isPattern, isSVGPattern } from '../../svg/helper';
|
||||
import { createElement } from '../../svg/core';
|
||||
|
||||
const patternDomMap = new WeakMap<PatternObject, SVGElement>();
|
||||
|
||||
/**
|
||||
* Manages SVG pattern elements.
|
||||
*
|
||||
* @param zrId zrender instance id
|
||||
* @param svgRoot root of SVG document
|
||||
*/
|
||||
export default class PatternManager extends Definable {
|
||||
|
||||
constructor(zrId: number, svgRoot: SVGElement) {
|
||||
super(zrId, svgRoot, ['pattern'], '__pattern_in_use__');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create new pattern DOM for fill or stroke if not exist,
|
||||
* but will not update pattern if exists.
|
||||
*
|
||||
* @param svgElement SVG element to paint
|
||||
* @param displayable zrender displayable element
|
||||
*/
|
||||
addWithoutUpdate(
|
||||
svgElement: SVGElement,
|
||||
displayable: Displayable
|
||||
) {
|
||||
if (displayable && displayable.style) {
|
||||
const that = this;
|
||||
zrUtil.each(['fill', 'stroke'], function (fillOrStroke: 'fill' | 'stroke') {
|
||||
const pattern = displayable.style[fillOrStroke] as PatternObject;
|
||||
if (isPattern(pattern)) {
|
||||
const defs = that.getDefs(true);
|
||||
|
||||
// Create dom in <defs> if not exists
|
||||
let dom = patternDomMap.get(pattern);
|
||||
if (dom) {
|
||||
// Pattern exists
|
||||
if (!defs.contains(dom)) {
|
||||
// __dom is no longer in defs, recreate
|
||||
that.addDom(dom);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// New dom
|
||||
dom = that.add(pattern);
|
||||
}
|
||||
|
||||
that.markUsed(displayable);
|
||||
|
||||
svgElement.setAttribute(fillOrStroke, getIdURL(dom.getAttribute('id')));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a new pattern tag in <defs>
|
||||
*
|
||||
* @param pattern zr pattern instance
|
||||
*/
|
||||
add(pattern: PatternObject): SVGElement {
|
||||
if (!isPattern(pattern)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let dom = createElement('pattern');
|
||||
|
||||
pattern.id = pattern.id == null ? this.nextId++ : pattern.id;
|
||||
dom.setAttribute('id', 'zr' + this._zrId
|
||||
+ '-pattern-' + pattern.id);
|
||||
|
||||
dom.setAttribute('patternUnits', 'userSpaceOnUse');
|
||||
|
||||
this.updateDom(pattern, dom);
|
||||
this.addDom(dom);
|
||||
|
||||
return dom;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update pattern.
|
||||
*
|
||||
* @param pattern zr pattern instance or color string
|
||||
*/
|
||||
update(pattern: PatternObject | string) {
|
||||
if (!isPattern(pattern)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const that = this;
|
||||
this.doUpdate(pattern, function () {
|
||||
const dom = patternDomMap.get(pattern);
|
||||
that.updateDom(pattern, dom);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update pattern dom
|
||||
*
|
||||
* @param pattern zr pattern instance
|
||||
* @param patternDom DOM to update
|
||||
*/
|
||||
updateDom(pattern: PatternObject, patternDom: SVGElement) {
|
||||
if (isSVGPattern(pattern)) {
|
||||
// New SVGPattern will not been supported in the legacy SVG renderer.
|
||||
// svg-legacy will been removed soon.
|
||||
|
||||
// const svgElement = pattern.svgElement;
|
||||
// const isStringSVG = typeof svgElement === 'string';
|
||||
// if (isStringSVG || svgElement.parentNode !== patternDom) {
|
||||
// if (isStringSVG) {
|
||||
// patternDom.innerHTML = svgElement;
|
||||
// }
|
||||
// else {
|
||||
// patternDom.innerHTML = '';
|
||||
// patternDom.appendChild(svgElement);
|
||||
// }
|
||||
|
||||
// patternDom.setAttribute('width', pattern.svgWidth as any);
|
||||
// patternDom.setAttribute('height', pattern.svgHeight as any);
|
||||
// }
|
||||
}
|
||||
else {
|
||||
let img: SVGElement;
|
||||
const prevImage = patternDom.getElementsByTagName('image');
|
||||
if (prevImage.length) {
|
||||
if (pattern.image) {
|
||||
// Update
|
||||
img = prevImage[0];
|
||||
}
|
||||
else {
|
||||
// Remove
|
||||
patternDom.removeChild(prevImage[0]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (pattern.image) {
|
||||
// Create
|
||||
img = createElement('image');
|
||||
}
|
||||
|
||||
if (img) {
|
||||
let imageSrc;
|
||||
const patternImage = pattern.image;
|
||||
if (typeof patternImage === 'string') {
|
||||
imageSrc = patternImage;
|
||||
}
|
||||
else if (patternImage instanceof HTMLImageElement) {
|
||||
imageSrc = patternImage.src;
|
||||
}
|
||||
else if (patternImage instanceof HTMLCanvasElement) {
|
||||
imageSrc = patternImage.toDataURL();
|
||||
}
|
||||
|
||||
if (imageSrc) {
|
||||
img.setAttribute('href', imageSrc);
|
||||
|
||||
// No need to re-render so dirty is empty
|
||||
const hostEl = {
|
||||
dirty: () => {}
|
||||
};
|
||||
const updateSize = (img: HTMLImageElement) => {
|
||||
patternDom.setAttribute('width', img.width as any);
|
||||
patternDom.setAttribute('height', img.height as any);
|
||||
};
|
||||
|
||||
const createdImage = createOrUpdateImage(imageSrc, img as any, hostEl, updateSize);
|
||||
if (createdImage && createdImage.width && createdImage.height) {
|
||||
// Loaded before
|
||||
updateSize(createdImage as HTMLImageElement);
|
||||
}
|
||||
|
||||
patternDom.appendChild(img);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const x = pattern.x || 0;
|
||||
const y = pattern.y || 0;
|
||||
const rotation = (pattern.rotation || 0) / Math.PI * 180;
|
||||
const scaleX = pattern.scaleX || 1;
|
||||
const scaleY = pattern.scaleY || 1;
|
||||
const transform = `translate(${x}, ${y}) rotate(${rotation}) scale(${scaleX}, ${scaleY})`;
|
||||
patternDom.setAttribute('patternTransform', transform);
|
||||
patternDomMap.set(pattern, patternDom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a single pattern to be used
|
||||
*
|
||||
* @param displayable displayable element
|
||||
*/
|
||||
markUsed(displayable: Displayable) {
|
||||
if (displayable.style) {
|
||||
if (isPattern(displayable.style.fill)) {
|
||||
super.markDomUsed(patternDomMap.get(displayable.style.fill));
|
||||
}
|
||||
if (isPattern(displayable.style.stroke)) {
|
||||
super.markDomUsed(patternDomMap.get(displayable.style.stroke));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
149
frontend/node_modules/zrender/src/svg-legacy/helper/ShadowManager.ts
generated
vendored
Normal file
149
frontend/node_modules/zrender/src/svg-legacy/helper/ShadowManager.ts
generated
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* @file Manages SVG shadow elements.
|
||||
* @author Zhang Wenli
|
||||
*/
|
||||
|
||||
import Definable from './Definable';
|
||||
import Displayable from '../../graphic/Displayable';
|
||||
import { Dictionary } from '../../core/types';
|
||||
import { getIdURL, getShadowKey, hasShadow, normalizeColor } from '../../svg/helper';
|
||||
import { createElement } from '../../svg/core';
|
||||
|
||||
type DisplayableExtended = Displayable & {
|
||||
_shadowDom: SVGElement
|
||||
}
|
||||
/**
|
||||
* Manages SVG shadow elements.
|
||||
*
|
||||
*/
|
||||
export default class ShadowManager extends Definable {
|
||||
|
||||
private _shadowDomMap: Dictionary<SVGFilterElement> = {}
|
||||
private _shadowDomPool: SVGFilterElement[] = []
|
||||
|
||||
constructor(zrId: number, svgRoot: SVGElement) {
|
||||
super(zrId, svgRoot, ['filter'], '__filter_in_use__', '_shadowDom');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new shadow tag in <defs>
|
||||
*
|
||||
* @param displayable zrender displayable element
|
||||
* @return created DOM
|
||||
*/
|
||||
private _getFromPool(): SVGFilterElement {
|
||||
let shadowDom = this._shadowDomPool.pop(); // Try to get one from trash.
|
||||
if (!shadowDom) {
|
||||
shadowDom = createElement('filter') as SVGFilterElement;
|
||||
shadowDom.setAttribute('id', 'zr' + this._zrId + '-shadow-' + this.nextId++);
|
||||
const domChild = createElement('feDropShadow');
|
||||
shadowDom.appendChild(domChild);
|
||||
this.addDom(shadowDom);
|
||||
}
|
||||
|
||||
return shadowDom;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update shadow.
|
||||
*/
|
||||
update(svgElement: SVGElement, displayable: Displayable) {
|
||||
const style = displayable.style;
|
||||
if (hasShadow(style)) {
|
||||
// Try getting shadow from cache.
|
||||
const shadowKey = getShadowKey(displayable);
|
||||
let shadowDom = (displayable as DisplayableExtended)._shadowDom = this._shadowDomMap[shadowKey];
|
||||
if (!shadowDom) {
|
||||
shadowDom = this._getFromPool();
|
||||
this._shadowDomMap[shadowKey] = shadowDom;
|
||||
}
|
||||
this.updateDom(svgElement, displayable, shadowDom);
|
||||
}
|
||||
else {
|
||||
// Remove shadow
|
||||
this.remove(svgElement, displayable);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove DOM and clear parent filter
|
||||
*/
|
||||
remove(svgElement: SVGElement, displayable: Displayable) {
|
||||
if ((displayable as DisplayableExtended)._shadowDom != null) {
|
||||
(displayable as DisplayableExtended)._shadowDom = null;
|
||||
svgElement.removeAttribute('filter');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update shadow dom
|
||||
*
|
||||
* @param displayable zrender displayable element
|
||||
* @param shadowDom DOM to update
|
||||
*/
|
||||
updateDom(svgElement: SVGElement, displayable: Displayable, shadowDom: SVGElement) {
|
||||
let domChild = shadowDom.children[0];
|
||||
|
||||
const style = displayable.style;
|
||||
const globalScale = displayable.getGlobalScale();
|
||||
const scaleX = globalScale[0];
|
||||
const scaleY = globalScale[1];
|
||||
if (!scaleX || !scaleY) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: textBoxShadowBlur is not supported yet
|
||||
const offsetX = style.shadowOffsetX || 0;
|
||||
const offsetY = style.shadowOffsetY || 0;
|
||||
const blur = style.shadowBlur;
|
||||
const normalizedColor = normalizeColor(style.shadowColor);
|
||||
|
||||
domChild.setAttribute('dx', offsetX / scaleX + '');
|
||||
domChild.setAttribute('dy', offsetY / scaleY + '');
|
||||
domChild.setAttribute('flood-color', normalizedColor.color);
|
||||
domChild.setAttribute('flood-opacity', normalizedColor.opacity + '');
|
||||
|
||||
// Divide by two here so that it looks the same as in canvas
|
||||
// See: https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-shadowblur
|
||||
const stdDx = blur / 2 / scaleX;
|
||||
const stdDy = blur / 2 / scaleY;
|
||||
const stdDeviation = stdDx + ' ' + stdDy;
|
||||
domChild.setAttribute('stdDeviation', stdDeviation);
|
||||
|
||||
// Fix filter clipping problem
|
||||
shadowDom.setAttribute('x', '-100%');
|
||||
shadowDom.setAttribute('y', '-100%');
|
||||
shadowDom.setAttribute('width', '300%');
|
||||
shadowDom.setAttribute('height', '300%');
|
||||
|
||||
// Store dom element in shadow, to avoid creating multiple
|
||||
// dom instances for the same shadow element
|
||||
(displayable as DisplayableExtended)._shadowDom = shadowDom;
|
||||
|
||||
svgElement.setAttribute('filter', getIdURL(shadowDom.getAttribute('id')));
|
||||
}
|
||||
|
||||
removeUnused() {
|
||||
const defs = this.getDefs(false);
|
||||
if (!defs) {
|
||||
// Nothing to remove
|
||||
return;
|
||||
}
|
||||
let shadowDomsPool = this._shadowDomPool;
|
||||
|
||||
// let currentUsedShadow = 0;
|
||||
const shadowDomMap = this._shadowDomMap;
|
||||
for (let key in shadowDomMap) {
|
||||
if (shadowDomMap.hasOwnProperty(key)) {
|
||||
shadowDomsPool.push(shadowDomMap[key]);
|
||||
}
|
||||
// currentUsedShadow++;
|
||||
}
|
||||
|
||||
// Reset the map.
|
||||
this._shadowDomMap = {};
|
||||
}
|
||||
}
|
||||
4
frontend/node_modules/zrender/src/svg-legacy/svg-legacy.ts
generated
vendored
Normal file
4
frontend/node_modules/zrender/src/svg-legacy/svg-legacy.ts
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
import {registerPainter} from '../zrender';
|
||||
import Painter from './Painter';
|
||||
|
||||
registerPainter('svg-legacy', Painter);
|
||||
Reference in New Issue
Block a user