mh-two-thousand-and-two
2024-03-25 b8c93990f3fa5e50a8aca16bdc9c2758168aa0fd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
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);
            }
        }
    }
 
 
}