/**
|
* @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);
|
}
|
}
|
}
|
|
|
}
|