echarts Symbol 源码

  • 2022-10-20
  • 浏览 (361)

echarts Symbol 代码

文件路径:/src/chart/helper/Symbol.ts

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import {createSymbol, normalizeSymbolOffset, normalizeSymbolSize} from '../../util/symbol';
import * as graphic from '../../util/graphic';
import {getECData} from '../../util/innerStore';
import { enterEmphasis, leaveEmphasis, toggleHoverEmphasis } from '../../util/states';
import {getDefaultLabel} from './labelHelper';
import SeriesData from '../../data/SeriesData';
import { ColorString, BlurScope, AnimationOption, ZRColor, AnimationOptionMixin } from '../../util/types';
import SeriesModel from '../../model/Series';
import { PathProps } from 'zrender/src/graphic/Path';
import { SymbolDrawSeriesScope, SymbolDrawItemModelOption } from './SymbolDraw';
import { extend } from 'zrender/src/core/util';
import { setLabelStyle, getLabelStatesModels } from '../../label/labelStyle';
import ZRImage from 'zrender/src/graphic/Image';
import { saveOldStyle } from '../../animation/basicTransition';
import Model from '../../model/Model';

type ECSymbol = ReturnType<typeof createSymbol>;

interface SymbolOpts {
    disableAnimation?: boolean

    useNameLabel?: boolean
    symbolInnerColor?: ZRColor
}

class Symbol extends graphic.Group {

    private _symbolType: string;

    /**
     * Original scale
     */
    private _sizeX: number;
    private _sizeY: number;

    private _z2: number;

    constructor(data: SeriesData, idx: number, seriesScope?: SymbolDrawSeriesScope, opts?: SymbolOpts) {
        super();
        this.updateData(data, idx, seriesScope, opts);
    }

    _createSymbol(
        symbolType: string,
        data: SeriesData,
        idx: number,
        symbolSize: number[],
        keepAspect: boolean
    ) {
        // Remove paths created before
        this.removeAll();

        // let symbolPath = createSymbol(
        //     symbolType, -0.5, -0.5, 1, 1, color
        // );
        // If width/height are set too small (e.g., set to 1) on ios10
        // and macOS Sierra, a circle stroke become a rect, no matter what
        // the scale is set. So we set width/height as 2. See #4150.
        const symbolPath = createSymbol(
            symbolType, -1, -1, 2, 2, null, keepAspect
        );

        symbolPath.attr({
            z2: 100,
            culling: true,
            scaleX: symbolSize[0] / 2,
            scaleY: symbolSize[1] / 2
        });
        // Rewrite drift method
        symbolPath.drift = driftSymbol;

        this._symbolType = symbolType;

        this.add(symbolPath);
    }

    /**
     * Stop animation
     * @param {boolean} toLastFrame
     */
    stopSymbolAnimation(toLastFrame: boolean) {
        this.childAt(0).stopAnimation(null, toLastFrame);
    }

    getSymbolType() {
        return this._symbolType;
    }
    /**
     * FIXME:
     * Caution: This method breaks the encapsulation of this module,
     * but it indeed brings convenience. So do not use the method
     * unless you detailedly know all the implements of `Symbol`,
     * especially animation.
     *
     * Get symbol path element.
     */
    getSymbolPath() {
        return this.childAt(0) as ECSymbol;
    }

    /**
     * Highlight symbol
     */
    highlight() {
        enterEmphasis(this.childAt(0));
    }

    /**
     * Downplay symbol
     */
    downplay() {
        leaveEmphasis(this.childAt(0));
    }

    /**
     * @param {number} zlevel
     * @param {number} z
     */
    setZ(zlevel: number, z: number) {
        const symbolPath = this.childAt(0) as ECSymbol;
        symbolPath.zlevel = zlevel;
        symbolPath.z = z;
    }

    setDraggable(draggable: boolean, hasCursorOption?: boolean) {
        const symbolPath = this.childAt(0) as ECSymbol;
        symbolPath.draggable = draggable;
        symbolPath.cursor = !hasCursorOption && draggable ? 'move' : symbolPath.cursor;
    }

    /**
     * Update symbol properties
     */
    updateData(data: SeriesData, idx: number, seriesScope?: SymbolDrawSeriesScope, opts?: SymbolOpts) {
        this.silent = false;

        const symbolType = data.getItemVisual(idx, 'symbol') || 'circle';
        const seriesModel = data.hostModel as SeriesModel;
        const symbolSize = Symbol.getSymbolSize(data, idx);
        const isInit = symbolType !== this._symbolType;
        const disableAnimation = opts && opts.disableAnimation;

        if (isInit) {
            const keepAspect = data.getItemVisual(idx, 'symbolKeepAspect');
            this._createSymbol(symbolType as string, data, idx, symbolSize, keepAspect);
        }
        else {
            const symbolPath = this.childAt(0) as ECSymbol;
            symbolPath.silent = false;
            const target = {
                scaleX: symbolSize[0] / 2,
                scaleY: symbolSize[1] / 2
            };
            disableAnimation ? symbolPath.attr(target)
                : graphic.updateProps(symbolPath, target, seriesModel, idx);

            saveOldStyle(symbolPath);
        }

        this._updateCommon(data, idx, symbolSize, seriesScope, opts);

        if (isInit) {
            const symbolPath = this.childAt(0) as ECSymbol;

            if (!disableAnimation) {
                const target: PathProps = {
                    scaleX: this._sizeX,
                    scaleY: this._sizeY,
                    style: {
                        // Always fadeIn. Because it has fadeOut animation when symbol is removed..
                        opacity: symbolPath.style.opacity
                    }
                };
                symbolPath.scaleX = symbolPath.scaleY = 0;
                symbolPath.style.opacity = 0;
                graphic.initProps(symbolPath, target, seriesModel, idx);
            }
        }

        if (disableAnimation) {
            // Must stop leave transition manually if don't call initProps or updateProps.
            this.childAt(0).stopAnimation('leave');
        }
    }

    _updateCommon(
        data: SeriesData,
        idx: number,
        symbolSize: number[],
        seriesScope?: SymbolDrawSeriesScope,
        opts?: SymbolOpts
    ) {
        const symbolPath = this.childAt(0) as ECSymbol;
        const seriesModel = data.hostModel as SeriesModel;

        let emphasisItemStyle;
        let blurItemStyle;
        let selectItemStyle;
        let focus;
        let blurScope: BlurScope;
        let emphasisDisabled: boolean;

        let labelStatesModels;

        let hoverScale: SymbolDrawSeriesScope['hoverScale'];
        let cursorStyle: SymbolDrawSeriesScope['cursorStyle'];

        if (seriesScope) {
            emphasisItemStyle = seriesScope.emphasisItemStyle;
            blurItemStyle = seriesScope.blurItemStyle;
            selectItemStyle = seriesScope.selectItemStyle;
            focus = seriesScope.focus;
            blurScope = seriesScope.blurScope;

            labelStatesModels = seriesScope.labelStatesModels;

            hoverScale = seriesScope.hoverScale;
            cursorStyle = seriesScope.cursorStyle;
            emphasisDisabled = seriesScope.emphasisDisabled;
        }

        if (!seriesScope || data.hasItemOption) {
            const itemModel = (seriesScope && seriesScope.itemModel)
                ? seriesScope.itemModel : data.getItemModel<SymbolDrawItemModelOption>(idx);
            const emphasisModel = itemModel.getModel('emphasis');

            emphasisItemStyle = emphasisModel.getModel('itemStyle').getItemStyle();
            selectItemStyle = itemModel.getModel(['select', 'itemStyle']).getItemStyle();
            blurItemStyle = itemModel.getModel(['blur', 'itemStyle']).getItemStyle();

            focus = emphasisModel.get('focus');
            blurScope = emphasisModel.get('blurScope');
            emphasisDisabled = emphasisModel.get('disabled');

            labelStatesModels = getLabelStatesModels(itemModel);

            hoverScale = emphasisModel.getShallow('scale');
            cursorStyle = itemModel.getShallow('cursor');
        }

        const symbolRotate = data.getItemVisual(idx, 'symbolRotate');
        symbolPath.attr('rotation', (symbolRotate || 0) * Math.PI / 180 || 0);

        const symbolOffset = normalizeSymbolOffset(data.getItemVisual(idx, 'symbolOffset'), symbolSize);
        if (symbolOffset) {
            symbolPath.x = symbolOffset[0];
            symbolPath.y = symbolOffset[1];
        }

        cursorStyle && symbolPath.attr('cursor', cursorStyle);

        const symbolStyle = data.getItemVisual(idx, 'style');
        const visualColor = symbolStyle.fill;

        if (symbolPath instanceof ZRImage) {
            const pathStyle = symbolPath.style;
            symbolPath.useStyle(extend({
                // TODO other properties like x, y ?
                image: pathStyle.image,
                x: pathStyle.x, y: pathStyle.y,
                width: pathStyle.width, height: pathStyle.height
            }, symbolStyle));
        }
        else {
            if (symbolPath.__isEmptyBrush) {
                // fill and stroke will be swapped if it's empty.
                // So we cloned a new style to avoid it affecting the original style in visual storage.
                // TODO Better implementation. No empty logic!
                symbolPath.useStyle(extend({}, symbolStyle));
            }
            else {
                symbolPath.useStyle(symbolStyle);
            }
            // Disable decal because symbol scale will been applied on the decal.
            symbolPath.style.decal = null;
            symbolPath.setColor(visualColor, opts && opts.symbolInnerColor);
            symbolPath.style.strokeNoScale = true;

        }
        const liftZ = data.getItemVisual(idx, 'liftZ');
        const z2Origin = this._z2;
        if (liftZ != null) {
            if (z2Origin == null) {
                this._z2 = symbolPath.z2;
                symbolPath.z2 += liftZ;
            }
        }
        else if (z2Origin != null) {
            symbolPath.z2 = z2Origin;
            this._z2 = null;
        }

        const useNameLabel = opts && opts.useNameLabel;

        setLabelStyle(
            symbolPath, labelStatesModels,
            {
                labelFetcher: seriesModel,
                labelDataIndex: idx,
                defaultText: getLabelDefaultText,
                inheritColor: visualColor as ColorString,
                defaultOpacity: symbolStyle.opacity
            }
        );

        // Do not execute util needed.
        function getLabelDefaultText(idx: number) {
            return useNameLabel ? data.getName(idx) : getDefaultLabel(data, idx);
        }

        this._sizeX = symbolSize[0] / 2;
        this._sizeY = symbolSize[1] / 2;

        const emphasisState = symbolPath.ensureState('emphasis');

        emphasisState.style = emphasisItemStyle;
        symbolPath.ensureState('select').style = selectItemStyle;
        symbolPath.ensureState('blur').style = blurItemStyle;

        // null / undefined / true means to use default strategy.
        // 0 / false / negative number / NaN / Infinity means no scale.
        const scaleRatio =
            hoverScale == null || hoverScale === true
                ? Math.max(1.1, 3 / this._sizeY)
                // PENDING: restrict hoverScale > 1? It seems unreasonable to scale down
                : isFinite(hoverScale as number) && hoverScale > 0
                    ? +hoverScale
                    : 1;
        // always set scale to allow resetting
        emphasisState.scaleX = this._sizeX * scaleRatio;
        emphasisState.scaleY = this._sizeY * scaleRatio;

        this.setSymbolScale(1);

        toggleHoverEmphasis(this, focus, blurScope, emphasisDisabled);
    }

    setSymbolScale(scale: number) {
        this.scaleX = this.scaleY = scale;
    }

    fadeOut(cb: () => void, seriesModel: Model<AnimationOptionMixin>, opt?: {
        fadeLabel: boolean,
        animation?: AnimationOption
    }) {
        const symbolPath = this.childAt(0) as ECSymbol;
        const dataIndex = getECData(this).dataIndex;
        const animationOpt = opt && opt.animation;
        // Avoid mistaken hover when fading out
        this.silent = symbolPath.silent = true;
        // Not show text when animating
        if (opt && opt.fadeLabel) {
            const textContent = symbolPath.getTextContent();
            if (textContent) {
                graphic.removeElement(textContent, {
                    style: {
                        opacity: 0
                    }
                }, seriesModel, {
                    dataIndex,
                    removeOpt: animationOpt,
                    cb() {
                        symbolPath.removeTextContent();
                    }
                });
            }
        }
        else {
            symbolPath.removeTextContent();
        }

        graphic.removeElement(
            symbolPath,
            {
                style: {
                    opacity: 0
                },
                scaleX: 0,
                scaleY: 0
            },
            seriesModel,
            { dataIndex, cb, removeOpt: animationOpt}
        );
    }

    static getSymbolSize(data: SeriesData, idx: number) {
        return normalizeSymbolSize(data.getItemVisual(idx, 'symbolSize'));
    }
}


function driftSymbol(this: ECSymbol, dx: number, dy: number) {
    this.parent.drift(dx, dy);
}

export default Symbol;

相关信息

echarts 源码目录

相关文章

echarts EffectLine 源码

echarts EffectPolyline 源码

echarts EffectSymbol 源码

echarts LargeLineDraw 源码

echarts LargeSymbolDraw 源码

echarts Line 源码

echarts LineDraw 源码

echarts LinePath 源码

echarts Polyline 源码

echarts SymbolDraw 源码

0  赞