echarts states 源码

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

echarts states 代码

文件路径:/src/util/states.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 { Dictionary } from 'zrender/src/core/types';
import LRU from 'zrender/src/core/LRU';
import Displayable, { DisplayableState } from 'zrender/src/graphic/Displayable';
import { PatternObject } from 'zrender/src/graphic/Pattern';
import { GradientObject } from 'zrender/src/graphic/Gradient';
import Element, { ElementEvent } from 'zrender/src/Element';
import Model from '../model/Model';
import {
    SeriesDataType,
    DisplayState,
    ECElement,
    ColorString,
    BlurScope,
    InnerFocus,
    Payload,
    ZRColor,
    HighlightPayload,
    DownplayPayload,
    ComponentMainType
} from './types';
import {
    extend,
    indexOf,
    isArrayLike,
    isObject,
    keys,
    isArray,
    each,
    isString,
    isGradientObject,
    map
} from 'zrender/src/core/util';
import { getECData } from './innerStore';
import * as colorTool from 'zrender/src/tool/color';
import SeriesData from '../data/SeriesData';
import SeriesModel from '../model/Series';
import { CoordinateSystemMaster, CoordinateSystem } from '../coord/CoordinateSystem';
import { queryDataIndex, makeInner } from './model';
import Path, { PathStyleProps } from 'zrender/src/graphic/Path';
import GlobalModel from '../model/Global';
import ExtensionAPI from '../core/ExtensionAPI';
import ComponentModel from '../model/Component';
import { error } from './log';
import type ComponentView from '../view/Component';

// Reserve 0 as default.
let _highlightNextDigit = 1;

const _highlightKeyMap: Dictionary<number> = {};

const getSavedStates = makeInner<{
    normalFill: ZRColor
    normalStroke: ZRColor
    selectFill?: ZRColor
    selectStroke?: ZRColor
}, Path>();

const getComponentStates = makeInner<{
    isBlured: boolean
}, SeriesModel | ComponentModel>();

export const HOVER_STATE_NORMAL: 0 = 0;
export const HOVER_STATE_BLUR: 1 = 1;
export const HOVER_STATE_EMPHASIS: 2 = 2;

export const SPECIAL_STATES = ['emphasis', 'blur', 'select'] as const;
export const DISPLAY_STATES = ['normal', 'emphasis', 'blur', 'select'] as const;

export const Z2_EMPHASIS_LIFT = 10;
export const Z2_SELECT_LIFT = 9;

export const HIGHLIGHT_ACTION_TYPE = 'highlight';
export const DOWNPLAY_ACTION_TYPE = 'downplay';

export const SELECT_ACTION_TYPE = 'select';
export const UNSELECT_ACTION_TYPE = 'unselect';
export const TOGGLE_SELECT_ACTION_TYPE = 'toggleSelect';

type ExtendedProps = {
    __highByOuter: number

    __highDownSilentOnTouch: boolean

    __highDownDispatcher: boolean
};
type ExtendedElement = Element & ExtendedProps;
type ExtendedDisplayable = Displayable & ExtendedProps;

function hasFillOrStroke(fillOrStroke: string | PatternObject | GradientObject) {
    return fillOrStroke != null && fillOrStroke !== 'none';
}
// Most lifted color are duplicated.
const liftedColorCache = new LRU<string>(100);
function liftColor(color: GradientObject): GradientObject;
function liftColor(color: string): string;
function liftColor(color: string | GradientObject): string | GradientObject {
    if (isString(color)) {
        let liftedColor = liftedColorCache.get(color);
        if (!liftedColor) {
            liftedColor = colorTool.lift(color, -0.1);
            liftedColorCache.put(color, liftedColor);
        }
        return liftedColor;
    }
    else if (isGradientObject(color)) {
        const ret = extend({}, color) as GradientObject;
        ret.colorStops = map(color.colorStops, stop => ({
            offset: stop.offset,
            color: colorTool.lift(stop.color, -0.1)
        }));
        return ret;
    }
    // Change nothing.
    return color;
}

function doChangeHoverState(el: ECElement, stateName: DisplayState, hoverStateEnum: 0 | 1 | 2) {
    if (el.onHoverStateChange && (el.hoverState || 0) !== hoverStateEnum) {
        el.onHoverStateChange(stateName);
    }
    el.hoverState = hoverStateEnum;
}

function singleEnterEmphasis(el: ECElement) {
    // Only mark the flag.
    // States will be applied in the echarts.ts in next frame.
    doChangeHoverState(el, 'emphasis', HOVER_STATE_EMPHASIS);
}

function singleLeaveEmphasis(el: ECElement) {
    // Only mark the flag.
    // States will be applied in the echarts.ts in next frame.
    if (el.hoverState === HOVER_STATE_EMPHASIS) {
        doChangeHoverState(el, 'normal', HOVER_STATE_NORMAL);
    }
}

function singleEnterBlur(el: ECElement) {
    doChangeHoverState(el, 'blur', HOVER_STATE_BLUR);
}

function singleLeaveBlur(el: ECElement) {
    if (el.hoverState === HOVER_STATE_BLUR) {
        doChangeHoverState(el, 'normal', HOVER_STATE_NORMAL);
    }
}

function singleEnterSelect(el: ECElement) {
    el.selected = true;
}
function singleLeaveSelect(el: ECElement) {
    el.selected = false;
}

function updateElementState<T>(
    el: ExtendedElement,
    updater: (this: void, el: Element, commonParam?: T) => void,
    commonParam?: T
) {
    updater(el, commonParam);
}

function traverseUpdateState<T>(
    el: ExtendedElement,
    updater: (this: void, el: Element, commonParam?: T) => void,
    commonParam?: T
) {
    updateElementState(el, updater, commonParam);
    el.isGroup && el.traverse(function (child: ExtendedElement) {
        updateElementState(child, updater, commonParam);
    });
}

export function setStatesFlag(el: ECElement, stateName: DisplayState) {
    switch (stateName) {
        case 'emphasis':
            el.hoverState = HOVER_STATE_EMPHASIS;
            break;
        case 'normal':
            el.hoverState = HOVER_STATE_NORMAL;
            break;
        case 'blur':
            el.hoverState = HOVER_STATE_BLUR;
            break;
        case 'select':
            el.selected = true;
    }
}

/**
 * If we reuse elements when rerender.
 * DONT forget to clearStates before we update the style and shape.
 * Or we may update on the wrong state instead of normal state.
 */
export function clearStates(el: Element) {
    if (el.isGroup) {
        el.traverse(function (child) {
            child.clearStates();
        });
    }
    else {
        el.clearStates();
    }
}

function getFromStateStyle(
    el: Displayable,
    props: (keyof PathStyleProps)[],
    toStateName: string,
    defaultValue?: PathStyleProps
): PathStyleProps {
    const style = el.style;
    const fromState: PathStyleProps = {};
    for (let i = 0; i < props.length; i++) {
        const propName = props[i];
        const val = style[propName];
        (fromState as any)[propName] = val == null ? (defaultValue && defaultValue[propName]) : val;
    }
    for (let i = 0; i < el.animators.length; i++) {
        const animator = el.animators[i];
        if (animator.__fromStateTransition
            // Dont consider the animation to emphasis state.
            && animator.__fromStateTransition.indexOf(toStateName) < 0
            && animator.targetName === 'style') {
            animator.saveTo(fromState, props);
        }
    }
    return fromState;
}

function createEmphasisDefaultState(
    el: Displayable,
    stateName: 'emphasis',
    targetStates: string[],
    state: Displayable['states'][number]
): DisplayableState {
    const hasSelect = targetStates && indexOf(targetStates, 'select') >= 0;
    let cloned = false;
    if (el instanceof Path) {
        const store = getSavedStates(el);
        const fromFill = hasSelect ? (store.selectFill || store.normalFill) : store.normalFill;
        const fromStroke = hasSelect ? (store.selectStroke || store.normalStroke) : store.normalStroke;
        if (hasFillOrStroke(fromFill) || hasFillOrStroke(fromStroke)) {
            state = state || {};
            let emphasisStyle = state.style || {};

            // inherit case
            if (emphasisStyle.fill === 'inherit') {
                cloned = true;
                state = extend({}, state);
                emphasisStyle = extend({}, emphasisStyle);
                emphasisStyle.fill = fromFill;
            }
            // Apply default color lift
            else if (!hasFillOrStroke(emphasisStyle.fill) && hasFillOrStroke(fromFill)) {
                cloned = true;
                // Not modify the original value.
                state = extend({}, state);
                emphasisStyle = extend({}, emphasisStyle);
                // Already being applied 'emphasis'. DON'T lift color multiple times.
                emphasisStyle.fill = liftColor(fromFill as ColorString);
            }
            // Not highlight stroke if fill has been highlighted.
            else if (!hasFillOrStroke(emphasisStyle.stroke) && hasFillOrStroke(fromStroke)) {
                if (!cloned) {
                    state = extend({}, state);
                    emphasisStyle = extend({}, emphasisStyle);
                }
                emphasisStyle.stroke = liftColor(fromStroke as ColorString);
            }
            state.style = emphasisStyle;
        }
    }
    if (state) {
        // TODO Share with textContent?
        if (state.z2 == null) {
            if (!cloned) {
                state = extend({}, state);
            }
            const z2EmphasisLift = (el as ECElement).z2EmphasisLift;
            state.z2 = el.z2 + (z2EmphasisLift != null ? z2EmphasisLift : Z2_EMPHASIS_LIFT);
        }
    }
    return state;
}

function createSelectDefaultState(
    el: Displayable,
    stateName: 'select',
    state: Displayable['states'][number]
): DisplayableState {
    // const hasSelect = indexOf(el.currentStates, stateName) >= 0;
    if (state) {
        // TODO Share with textContent?
        if (state.z2 == null) {
            state = extend({}, state);
            const z2SelectLift = (el as ECElement).z2SelectLift;
            state.z2 = el.z2 + (z2SelectLift != null ? z2SelectLift : Z2_SELECT_LIFT);
        }
    }
    return state;
}

function createBlurDefaultState(
    el: Displayable,
    stateName: 'blur',
    state: Displayable['states'][number]
): DisplayableState {
    const hasBlur = indexOf(el.currentStates, stateName) >= 0;
    const currentOpacity = el.style.opacity;

    const fromState = !hasBlur
        ? getFromStateStyle(el, ['opacity'], stateName, {
            opacity: 1
        })
        : null;

    state = state || {};
    let blurStyle = state.style || {};
    if (blurStyle.opacity == null) {
        // clone state
        state = extend({}, state);
        blurStyle = extend({
            // Already being applied 'emphasis'. DON'T mul opacity multiple times.
            opacity: hasBlur ? currentOpacity : (fromState.opacity * 0.1)
        }, blurStyle);
        state.style = blurStyle;
    }

    return state;
}

function elementStateProxy(this: Displayable, stateName: string, targetStates?: string[]): DisplayableState {
    const state = this.states[stateName];
    if (this.style) {
        if (stateName === 'emphasis') {
            return createEmphasisDefaultState(this, stateName, targetStates, state);
        }
        else if (stateName === 'blur') {
            return createBlurDefaultState(this, stateName, state);
        }
        else if (stateName === 'select') {
            return createSelectDefaultState(this, stateName, state);
        }
    }
    return state;
}

/**
 * Set hover style (namely "emphasis style") of element.
 * @param el Should not be `zrender/graphic/Group`.
 * @param focus 'self' | 'selfInSeries' | 'series'
 */
export function setDefaultStateProxy(el: Displayable) {
    el.stateProxy = elementStateProxy;
    const textContent = el.getTextContent();
    const textGuide = el.getTextGuideLine();
    if (textContent) {
        textContent.stateProxy = elementStateProxy;
    }
    if (textGuide) {
        textGuide.stateProxy = elementStateProxy;
    }
}

export function enterEmphasisWhenMouseOver(el: Element, e: ElementEvent) {
    !shouldSilent(el, e)
        // "emphasis" event highlight has higher priority than mouse highlight.
        && !(el as ExtendedElement).__highByOuter
        && traverseUpdateState((el as ExtendedElement), singleEnterEmphasis);
}

export function leaveEmphasisWhenMouseOut(el: Element, e: ElementEvent) {
    !shouldSilent(el, e)
        // "emphasis" event highlight has higher priority than mouse highlight.
        && !(el as ExtendedElement).__highByOuter
        && traverseUpdateState((el as ExtendedElement), singleLeaveEmphasis);
}

export function enterEmphasis(el: Element, highlightDigit?: number) {
    (el as ExtendedElement).__highByOuter |= 1 << (highlightDigit || 0);
    traverseUpdateState((el as ExtendedElement), singleEnterEmphasis);
}

export function leaveEmphasis(el: Element, highlightDigit?: number) {
    !((el as ExtendedElement).__highByOuter &= ~(1 << (highlightDigit || 0)))
        && traverseUpdateState((el as ExtendedElement), singleLeaveEmphasis);
}

export function enterBlur(el: Element) {
    traverseUpdateState(el as ExtendedElement, singleEnterBlur);
}

export function leaveBlur(el: Element) {
    traverseUpdateState(el as ExtendedElement, singleLeaveBlur);
}

export function enterSelect(el: Element) {
    traverseUpdateState(el as ExtendedElement, singleEnterSelect);
}

export function leaveSelect(el: Element) {
    traverseUpdateState(el as ExtendedElement, singleLeaveSelect);
}

function shouldSilent(el: Element, e: ElementEvent) {
    return (el as ExtendedElement).__highDownSilentOnTouch && e.zrByTouch;
}

export function allLeaveBlur(api: ExtensionAPI) {
    const model = api.getModel();
    const leaveBlurredSeries: SeriesModel[] = [];
    const allComponentViews: ComponentView[] = [];
    model.eachComponent(function (componentType, componentModel) {
        const componentStates = getComponentStates(componentModel);
        const isSeries = componentType === 'series';
        const view = isSeries ? api.getViewOfSeriesModel(componentModel as SeriesModel)
            : api.getViewOfComponentModel(componentModel);
        !isSeries && allComponentViews.push(view as ComponentView);
        if (componentStates.isBlured) {
            // Leave blur anyway
            view.group.traverse(function (child) {
                singleLeaveBlur(child);
            });
            isSeries && leaveBlurredSeries.push(componentModel as SeriesModel);
        }
        componentStates.isBlured = false;
    });
    each(allComponentViews, function (view) {
        if (view && view.toggleBlurSeries) {
            view.toggleBlurSeries(leaveBlurredSeries, false, model);
        }
    });
}

export function blurSeries(
    targetSeriesIndex: number,
    focus: InnerFocus,
    blurScope: BlurScope,
    api: ExtensionAPI
) {
    const ecModel = api.getModel();
    blurScope = blurScope || 'coordinateSystem';

    function leaveBlurOfIndices(data: SeriesData, dataIndices: ArrayLike<number>) {
        for (let i = 0; i < dataIndices.length; i++) {
            const itemEl = data.getItemGraphicEl(dataIndices[i]);
            itemEl && leaveBlur(itemEl);
        }
    }

    if (targetSeriesIndex == null) {
        return;
    }

    if (!focus || focus === 'none') {
        return;
    }

    const targetSeriesModel = ecModel.getSeriesByIndex(targetSeriesIndex);
    let targetCoordSys: CoordinateSystemMaster | CoordinateSystem = targetSeriesModel.coordinateSystem;
    if (targetCoordSys && (targetCoordSys as CoordinateSystem).master) {
        targetCoordSys = (targetCoordSys as CoordinateSystem).master;
    }

    const blurredSeries: SeriesModel[] = [];

    ecModel.eachSeries(function (seriesModel) {

        const sameSeries = targetSeriesModel === seriesModel;
        let coordSys: CoordinateSystemMaster | CoordinateSystem = seriesModel.coordinateSystem;
        if (coordSys && (coordSys as CoordinateSystem).master) {
            coordSys = (coordSys as CoordinateSystem).master;
        }
        const sameCoordSys = coordSys && targetCoordSys
            ? coordSys === targetCoordSys
            : sameSeries;   // If there is no coordinate system. use sameSeries instead.
        if (!(
            // Not blur other series if blurScope series
            blurScope === 'series' && !sameSeries
            // Not blur other coordinate system if blurScope is coordinateSystem
            || blurScope === 'coordinateSystem' && !sameCoordSys
            // Not blur self series if focus is series.
            || focus === 'series' && sameSeries
            // TODO blurScope: coordinate system
        )) {
            const view = api.getViewOfSeriesModel(seriesModel);
            view.group.traverse(function (child) {
                singleEnterBlur(child);
            });

            if (isArrayLike(focus)) {
                leaveBlurOfIndices(seriesModel.getData(), focus as ArrayLike<number>);
            }
            else if (isObject(focus)) {
                const dataTypes = keys(focus);
                for (let d = 0; d < dataTypes.length; d++) {
                    leaveBlurOfIndices(seriesModel.getData(dataTypes[d] as SeriesDataType), focus[dataTypes[d]]);
                }
            }

            blurredSeries.push(seriesModel);

            getComponentStates(seriesModel).isBlured = true;
        }
    });

    ecModel.eachComponent(function (componentType, componentModel) {
        if (componentType === 'series') {
            return;
        }
        const view = api.getViewOfComponentModel(componentModel);
        if (view && view.toggleBlurSeries) {
            view.toggleBlurSeries(blurredSeries, true, ecModel);
        }
    });
}

export function blurComponent(
    componentMainType: ComponentMainType,
    componentIndex: number,
    api: ExtensionAPI
) {
    if (componentMainType == null || componentIndex == null) {
        return;
    }

    const componentModel = api.getModel().getComponent(componentMainType, componentIndex);
    if (!componentModel) {
        return;
    }

    getComponentStates(componentModel).isBlured = true;

    const view = api.getViewOfComponentModel(componentModel);
    if (!view || !view.focusBlurEnabled) {
        return;
    }

    view.group.traverse(function (child) {
        singleEnterBlur(child);
    });
}

export function blurSeriesFromHighlightPayload(
    seriesModel: SeriesModel,
    payload: HighlightPayload,
    api: ExtensionAPI
) {
    const seriesIndex = seriesModel.seriesIndex;
    const data = seriesModel.getData(payload.dataType);
    if (!data) {
        if (__DEV__) {
            error(`Unknown dataType ${payload.dataType}`);
        }
        return;
    }
    let dataIndex = queryDataIndex(data, payload);
    // Pick the first one if there is multiple/none exists.
    dataIndex = (isArray(dataIndex) ? dataIndex[0] : dataIndex) || 0;
    let el = data.getItemGraphicEl(dataIndex as number);
    if (!el) {
        const count = data.count();
        let current = 0;
        // If data on dataIndex is NaN.
        while (!el && current < count) {
            el = data.getItemGraphicEl(current++);
        }
    }

    if (el) {
        const ecData = getECData(el);
        blurSeries(
            seriesIndex, ecData.focus, ecData.blurScope, api
        );
    }
    else {
        // If there is no element put on the data. Try getting it from raw option
        // TODO Should put it on seriesModel?
        const focus = seriesModel.get(['emphasis', 'focus']);
        const blurScope = seriesModel.get(['emphasis', 'blurScope']);
        if (focus != null) {
            blurSeries(seriesIndex, focus, blurScope, api);
        }
    }
}

export function findComponentHighDownDispatchers(
    componentMainType: ComponentMainType,
    componentIndex: number,
    name: string,
    api: ExtensionAPI
): {
    focusSelf: boolean;
    // If return null/undefined, do not support this feature.
    dispatchers: Element[];
} {
    const ret = {
        focusSelf: false,
        dispatchers: null as Element[]
    };
    if (componentMainType == null
        || componentMainType === 'series'
        || componentIndex == null
        || name == null
    ) {
        return ret;
    }

    const componentModel = api.getModel().getComponent(componentMainType, componentIndex);
    if (!componentModel) {
        return ret;
    }

    const view = api.getViewOfComponentModel(componentModel);
    if (!view || !view.findHighDownDispatchers) {
        return ret;
    }

    const dispatchers = view.findHighDownDispatchers(name);

    // At presnet, the component (like Geo) only blur inside itself.
    // So we do not use `blurScope` in component.
    let focusSelf: boolean;
    for (let i = 0; i < dispatchers.length; i++) {
        if (__DEV__ && !isHighDownDispatcher(dispatchers[i])) {
            error('param should be highDownDispatcher');
        }
        if (getECData(dispatchers[i]).focus === 'self') {
            focusSelf = true;
            break;
        }
    }

    return { focusSelf, dispatchers };
}

export function handleGlobalMouseOverForHighDown(
    dispatcher: Element,
    e: ElementEvent,
    api: ExtensionAPI
): void {
    if (__DEV__ && !isHighDownDispatcher(dispatcher)) {
        error('param should be highDownDispatcher');
    }

    const ecData = getECData(dispatcher);

    const { dispatchers, focusSelf } = findComponentHighDownDispatchers(
        ecData.componentMainType, ecData.componentIndex, ecData.componentHighDownName, api
    );
    // If `findHighDownDispatchers` is supported on the component,
    // highlight/downplay elements with the same name.
    if (dispatchers) {
        if (focusSelf) {
            blurComponent(ecData.componentMainType, ecData.componentIndex, api);
        }
        each(dispatchers, dispatcher => enterEmphasisWhenMouseOver(dispatcher, e));
    }
    else {
        // Try blur all in the related series. Then emphasis the hoverred.
        // TODO. progressive mode.
        blurSeries(ecData.seriesIndex, ecData.focus, ecData.blurScope, api);
        if (ecData.focus === 'self') {
            blurComponent(ecData.componentMainType, ecData.componentIndex, api);
        }
        // Other than series, component that not support `findHighDownDispatcher` will
        // also use it. But in this case, highlight/downplay are only supported in
        // mouse hover but not in dispatchAction.
        enterEmphasisWhenMouseOver(dispatcher, e);
    }
}

export function handleGlobalMouseOutForHighDown(
    dispatcher: Element,
    e: ElementEvent,
    api: ExtensionAPI
): void {
    if (__DEV__ && !isHighDownDispatcher(dispatcher)) {
        error('param should be highDownDispatcher');
    }

    allLeaveBlur(api);

    const ecData = getECData(dispatcher);
    const { dispatchers } = findComponentHighDownDispatchers(
        ecData.componentMainType, ecData.componentIndex, ecData.componentHighDownName, api
    );
    if (dispatchers) {
        each(dispatchers, dispatcher => leaveEmphasisWhenMouseOut(dispatcher, e));
    }
    else {
        leaveEmphasisWhenMouseOut(dispatcher, e);
    }
}


export function toggleSelectionFromPayload(
    seriesModel: SeriesModel,
    payload: Payload,
    api: ExtensionAPI
) {
    if (!(isSelectChangePayload(payload))) {
        return;
    }
    const dataType = payload.dataType;
    const data = seriesModel.getData(dataType);
    let dataIndex = queryDataIndex(data, payload);
    if (!isArray(dataIndex)) {
        dataIndex = [dataIndex];
    }

    seriesModel[
        payload.type === TOGGLE_SELECT_ACTION_TYPE ? 'toggleSelect'
            : payload.type === SELECT_ACTION_TYPE ? 'select' : 'unselect'
    ](dataIndex, dataType);
}


export function updateSeriesElementSelection(seriesModel: SeriesModel) {
    const allData = seriesModel.getAllData();
    each(allData, function ({ data, type }) {
        data.eachItemGraphicEl(function (el, idx) {
            seriesModel.isSelected(idx, type) ? enterSelect(el) : leaveSelect(el);
        });
    });
}

export function getAllSelectedIndices(ecModel: GlobalModel) {
    const ret: {
        seriesIndex: number
        dataType?: SeriesDataType
        dataIndex: number[]
    }[] = [];
    ecModel.eachSeries(function (seriesModel) {
        const allData = seriesModel.getAllData();
        each(allData, function ({ data, type }) {
            const dataIndices = seriesModel.getSelectedDataIndices();
            if (dataIndices.length > 0) {
                const item: typeof ret[number] = {
                    dataIndex: dataIndices,
                    seriesIndex: seriesModel.seriesIndex
                };
                if (type != null) {
                    item.dataType = type;
                }
                ret.push(item);

            }
        });
    });
    return ret;
}

/**
 * Enable the function that mouseover will trigger the emphasis state.
 *
 * NOTE:
 * This function should be used on the element with dataIndex, seriesIndex.
 *
 */
export function enableHoverEmphasis(el: Element, focus?: InnerFocus, blurScope?: BlurScope) {
    setAsHighDownDispatcher(el, true);
    traverseUpdateState(el as ExtendedElement, setDefaultStateProxy);

    enableHoverFocus(el, focus, blurScope);
}

export function disableHoverEmphasis(el: Element) {
    setAsHighDownDispatcher(el, false);
}

export function toggleHoverEmphasis(el: Element, focus: InnerFocus, blurScope: BlurScope, isDisabled: boolean) {
    isDisabled ? disableHoverEmphasis(el)
        : enableHoverEmphasis(el, focus, blurScope);
}

export function enableHoverFocus(el: Element, focus: InnerFocus, blurScope: BlurScope) {
    const ecData = getECData(el);
    if (focus != null) {
        // TODO dataIndex may be set after this function. This check is not useful.
        // if (ecData.dataIndex == null) {
        //     if (__DEV__) {
        //         console.warn('focus can only been set on element with dataIndex');
        //     }
        // }
        // else {
        ecData.focus = focus;
        ecData.blurScope = blurScope;
        // }
    }
    else if (ecData.focus) {
        ecData.focus = null;
    }
}

const OTHER_STATES = ['emphasis', 'blur', 'select'] as const;
const defaultStyleGetterMap: Dictionary<'getItemStyle' | 'getLineStyle' | 'getAreaStyle'> = {
    itemStyle: 'getItemStyle',
    lineStyle: 'getLineStyle',
    areaStyle: 'getAreaStyle'
};
/**
 * Set emphasis/blur/selected states of element.
 */
export function setStatesStylesFromModel(
    el: Displayable,
    itemModel: Model<Partial<Record<'emphasis' | 'blur' | 'select', any>>>,
    styleType?: string, // default itemStyle
    getter?: (model: Model) => Dictionary<any>
) {
    styleType = styleType || 'itemStyle';
    for (let i = 0; i < OTHER_STATES.length; i++) {
        const stateName = OTHER_STATES[i];
        const model = itemModel.getModel([stateName, styleType]);
        const state = el.ensureState(stateName);
        // Let it throw error if getterType is not found.
        state.style = getter ? getter(model) : model[defaultStyleGetterMap[styleType]]();
    }
}


/**
 *
 * Set element as highlight / downplay dispatcher.
 * It will be checked when element recieved mouseover event or from highlight action.
 * It's in change of all highlight/downplay behavior of it's children.
 *
 * @param el
 * @param el.highDownSilentOnTouch
 *        In touch device, mouseover event will be trigger on touchstart event
 *        (see module:zrender/dom/HandlerProxy). By this mechanism, we can
 *        conveniently use hoverStyle when tap on touch screen without additional
 *        code for compatibility.
 *        But if the chart/component has select feature, which usually also use
 *        hoverStyle, there might be conflict between 'select-highlight' and
 *        'hover-highlight' especially when roam is enabled (see geo for example).
 *        In this case, `highDownSilentOnTouch` should be used to disable
 *        hover-highlight on touch device.
 * @param asDispatcher If `false`, do not set as "highDownDispatcher".
 */
export function setAsHighDownDispatcher(el: Element, asDispatcher: boolean) {
    const disable = asDispatcher === false;
    const extendedEl = el as ExtendedElement;
    // Make `highDownSilentOnTouch` and `onStateChange` only work after
    // `setAsHighDownDispatcher` called. Avoid it is modified by user unexpectedly.
    if ((el as ECElement).highDownSilentOnTouch) {
        extendedEl.__highDownSilentOnTouch = (el as ECElement).highDownSilentOnTouch;
    }
    // Simple optimize, since this method might be
    // called for each elements of a group in some cases.
    if (!disable || extendedEl.__highDownDispatcher) {
        // Emphasis, normal can be triggered manually by API or other components like hover link.
        // el[method]('emphasis', onElementEmphasisEvent)[method]('normal', onElementNormalEvent);
        // Also keep previous record.
        extendedEl.__highByOuter = extendedEl.__highByOuter || 0;
        extendedEl.__highDownDispatcher = !disable;
    }
}

export function isHighDownDispatcher(el: Element): boolean {
    return !!(el && (el as ExtendedDisplayable).__highDownDispatcher);
}

/**
 * Enable component highlight/downplay features:
 * + hover link (within the same name)
 * + focus blur in component
 */
export function enableComponentHighDownFeatures(
    el: Element,
    componentModel: ComponentModel,
    componentHighDownName: string
): void {
    const ecData = getECData(el);
    ecData.componentMainType = componentModel.mainType;
    ecData.componentIndex = componentModel.componentIndex;
    ecData.componentHighDownName = componentHighDownName;
}

/**
 * Support hightlight/downplay record on each elements.
 * For the case: hover highlight/downplay (legend, visualMap, ...) and
 * user triggerred hightlight/downplay should not conflict.
 * Only all of the highlightDigit cleared, return to normal.
 * @param {string} highlightKey
 * @return {number} highlightDigit
 */
export function getHighlightDigit(highlightKey: number) {
    let highlightDigit = _highlightKeyMap[highlightKey];
    if (highlightDigit == null && _highlightNextDigit <= 32) {
        highlightDigit = _highlightKeyMap[highlightKey] = _highlightNextDigit++;
    }
    return highlightDigit;
}

export function isSelectChangePayload(payload: Payload) {
    const payloadType = payload.type;
    return payloadType === SELECT_ACTION_TYPE
        || payloadType === UNSELECT_ACTION_TYPE
        || payloadType === TOGGLE_SELECT_ACTION_TYPE;
}

export function isHighDownPayload(payload: Payload): payload is HighlightPayload | DownplayPayload {
    const payloadType = payload.type;
    return payloadType === HIGHLIGHT_ACTION_TYPE
        || payloadType === DOWNPLAY_ACTION_TYPE;
}

export function savePathStates(el: Path) {
    const store = getSavedStates(el);
    store.normalFill = el.style.fill;
    store.normalStroke = el.style.stroke;

    const selectState = el.states.select || {};
    store.selectFill = (selectState.style && selectState.style.fill) || null;
    store.selectStroke = (selectState.style && selectState.style.stroke) || null;
}

相关信息

echarts 源码目录

相关文章

echarts ECEventProcessor 源码

echarts KDTree 源码

echarts animation 源码

echarts clazz 源码

echarts component 源码

echarts conditionalExpression 源码

echarts decal 源码

echarts event 源码

echarts format 源码

echarts graphic 源码

0  赞