echarts ContinuousModel 源码

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

echarts ContinuousModel 代码

文件路径:/src/component/visualMap/ContinuousModel.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 * as zrUtil from 'zrender/src/core/util';
import VisualMapModel, { VisualMapOption, VisualMeta } from './VisualMapModel';
import * as numberUtil from '../../util/number';
import { VisualMappingOption } from '../../visual/VisualMapping';
import { inheritDefaultOption } from '../../util/component';
import { ItemStyleOption } from '../../util/types';

// Constant
const DEFAULT_BAR_BOUND = [20, 140];

type RangeWithAuto = {
    auto?: 0 | 1
};

type VisualState = VisualMapModel['stateList'][number];

export interface ContinousVisualMapOption extends VisualMapOption {

    align?: 'auto' | 'left' | 'right' | 'top' | 'bottom'

    /**
     * This prop effect default component type determine
     * @see echarts/component/visualMap/typeDefaulter.
     */
    calculable?: boolean

    /**
     * selected range. In default case `range` is [min, max]
     * and can auto change along with modification of min max,
     * util user specifid a range.
     */
    range?: number[]
    /**
     * Whether to enable hover highlight.
     */
    hoverLink?: boolean

    /**
     * The extent of hovered data.
     */
    hoverLinkDataSize?: number
    /**
     * Whether trigger hoverLink when hover handle.
     * If not specified, follow the value of `realtime`.
     */
    hoverLinkOnHandle?: boolean,

    handleIcon?: string,
    // Percent of the item width
    handleSize?: string | number,
    handleStyle?: ItemStyleOption

    indicatorIcon?: string,
    // Percent of the item width
    indicatorSize?: string | number,
    indicatorStyle?: ItemStyleOption

    emphasis?: {
        handleStyle?: ItemStyleOption
    }
}

class ContinuousModel extends VisualMapModel<ContinousVisualMapOption> {

    static type = 'visualMap.continuous' as const;
    type = ContinuousModel.type;

    /**
     * @override
     */
    optionUpdated(newOption: ContinousVisualMapOption, isInit: boolean) {
        super.optionUpdated.apply(this, arguments as any);

        this.resetExtent();

        this.resetVisual(function (mappingOption?: VisualMappingOption) {
            mappingOption.mappingMethod = 'linear';
            mappingOption.dataExtent = this.getExtent();
        });

        this._resetRange();
    }

    /**
     * @protected
     * @override
     */
    resetItemSize() {
        super.resetItemSize.apply(this, arguments as any);

        const itemSize = this.itemSize;

        (itemSize[0] == null || isNaN(itemSize[0])) && (itemSize[0] = DEFAULT_BAR_BOUND[0]);
        (itemSize[1] == null || isNaN(itemSize[1])) && (itemSize[1] = DEFAULT_BAR_BOUND[1]);
    }

    /**
     * @private
     */
    _resetRange() {
        const dataExtent = this.getExtent();
        const range = this.option.range;

        if (!range || (range as RangeWithAuto).auto) {
            // `range` should always be array (so we dont use other
            // value like 'auto') for user-friend. (consider getOption).
            (dataExtent as RangeWithAuto).auto = 1;
            this.option.range = dataExtent;
        }
        else if (zrUtil.isArray(range)) {
            if (range[0] > range[1]) {
                range.reverse();
            }
            range[0] = Math.max(range[0], dataExtent[0]);
            range[1] = Math.min(range[1], dataExtent[1]);
        }
    }

    /**
     * @protected
     * @override
     */
    completeVisualOption() {
        super.completeVisualOption.apply(this, arguments as any);

        zrUtil.each(this.stateList, function (state: VisualState) {
            const symbolSize = this.option.controller[state].symbolSize;
            if (symbolSize && symbolSize[0] !== symbolSize[1]) {
                symbolSize[0] = symbolSize[1] / 3; // For good looking.
            }
        }, this);
    }

    /**
     * @override
     */
    setSelected(selected: number[]) {
        this.option.range = selected.slice();
        this._resetRange();
    }

    /**
     * @public
     */
    getSelected(): [number, number] {
        const dataExtent = this.getExtent();

        const dataInterval = numberUtil.asc(
            (this.get('range') || []).slice()
        ) as [number, number];

        // Clamp
        dataInterval[0] > dataExtent[1] && (dataInterval[0] = dataExtent[1]);
        dataInterval[1] > dataExtent[1] && (dataInterval[1] = dataExtent[1]);
        dataInterval[0] < dataExtent[0] && (dataInterval[0] = dataExtent[0]);
        dataInterval[1] < dataExtent[0] && (dataInterval[1] = dataExtent[0]);

        return dataInterval;
    }

    /**
     * @override
     */
    getValueState(value: number): VisualState {
        const range = this.option.range;
        const dataExtent = this.getExtent();

        // When range[0] === dataExtent[0], any value larger than dataExtent[0] maps to 'inRange'.
        // range[1] is processed likewise.
        return (
            (range[0] <= dataExtent[0] || range[0] <= value)
            && (range[1] >= dataExtent[1] || value <= range[1])
        ) ? 'inRange' : 'outOfRange';
    }

    findTargetDataIndices(range: number[]) {
        type DataIndices = {
            seriesId: string
            dataIndex: number[]
        };
        const result: DataIndices[] = [];

        this.eachTargetSeries(function (seriesModel) {
            const dataIndices: number[] = [];
            const data = seriesModel.getData();

            data.each(this.getDataDimensionIndex(data), function (value, dataIndex) {
                range[0] <= value && value <= range[1] && dataIndices.push(dataIndex);
            }, this);

            result.push({
                seriesId: seriesModel.id,
                dataIndex: dataIndices
            });
        }, this);

        return result;
    }

    /**
     * @implement
     */
    getVisualMeta(
        getColorVisual: (value: number, valueState: VisualState) => string
    ) {
        type ColorStop = VisualMeta['stops'][number];
        const oVals = getColorStopValues(this, 'outOfRange', this.getExtent());
        const iVals = getColorStopValues(this, 'inRange', this.option.range.slice());
        const stops: ColorStop[] = [];

        function setStop(value: number, valueState: VisualState) {
            stops.push({
                value: value,
                color: getColorVisual(value, valueState)
            });
        }

        // Format to: outOfRange -- inRange -- outOfRange.
        let iIdx = 0;
        let oIdx = 0;
        const iLen = iVals.length;
        const oLen = oVals.length;

        for (; oIdx < oLen && (!iVals.length || oVals[oIdx] <= iVals[0]); oIdx++) {
            // If oVal[oIdx] === iVals[iIdx], oVal[oIdx] should be ignored.
            if (oVals[oIdx] < iVals[iIdx]) {
                setStop(oVals[oIdx], 'outOfRange');
            }
        }
        for (let first = 1; iIdx < iLen; iIdx++, first = 0) {
            // If range is full, value beyond min, max will be clamped.
            // make a singularity
            first && stops.length && setStop(iVals[iIdx], 'outOfRange');
            setStop(iVals[iIdx], 'inRange');
        }
        for (let first = 1; oIdx < oLen; oIdx++) {
            if (!iVals.length || iVals[iVals.length - 1] < oVals[oIdx]) {
                // make a singularity
                if (first) {
                    stops.length && setStop(stops[stops.length - 1].value, 'outOfRange');
                    first = 0;
                }
                setStop(oVals[oIdx], 'outOfRange');
            }
        }

        const stopsLen = stops.length;

        return {
            stops: stops,
            outerColors: [
                stopsLen ? stops[0].color : 'transparent',
                stopsLen ? stops[stopsLen - 1].color : 'transparent'
            ] as VisualMeta['outerColors']
        };
    }

    static defaultOption = inheritDefaultOption(VisualMapModel.defaultOption, {
        align: 'auto',           // 'auto', 'left', 'right', 'top', 'bottom'
        calculable: false,
        hoverLink: true,
        realtime: true,

        handleIcon: 'path://M-11.39,9.77h0a3.5,3.5,0,0,1-3.5,3.5h-22a3.5,3.5,0,0,1-3.5-3.5h0a3.5,3.5,0,0,1,3.5-3.5h22A3.5,3.5,0,0,1-11.39,9.77Z',
        handleSize: '120%',

        handleStyle: {
            borderColor: '#fff',
            borderWidth: 1
        },

        indicatorIcon: 'circle',
        indicatorSize: '50%',
        indicatorStyle: {
            borderColor: '#fff',
            borderWidth: 2,
            shadowBlur: 2,
            shadowOffsetX: 1,
            shadowOffsetY: 1,
            shadowColor: 'rgba(0,0,0,0.2)'
        }
        // emphasis: {
        //     handleStyle: {
        //         shadowBlur: 3,
        //         shadowOffsetX: 1,
        //         shadowOffsetY: 1,
        //         shadowColor: 'rgba(0,0,0,0.2)'
        //     }
        // }
    }) as ContinousVisualMapOption;
}


function getColorStopValues(
    visualMapModel: ContinuousModel,
    valueState: VisualState,
    dataExtent: number[]
) {
    if (dataExtent[0] === dataExtent[1]) {
        return dataExtent.slice();
    }

    // When using colorHue mapping, it is not linear color any more.
    // Moreover, canvas gradient seems not to be accurate linear.
    // FIXME
    // Should be arbitrary value 100? or based on pixel size?
    const count = 200;
    const step = (dataExtent[1] - dataExtent[0]) / count;

    let value = dataExtent[0];
    const stopValues = [];
    for (let i = 0; i <= count && value < dataExtent[1]; i++) {
        stopValues.push(value);
        value += step;
    }
    stopValues.push(dataExtent[1]);

    return stopValues;
}

export default ContinuousModel;

相关信息

echarts 源码目录

相关文章

echarts ContinuousView 源码

echarts PiecewiseModel 源码

echarts PiecewiseView 源码

echarts VisualMapModel 源码

echarts VisualMapView 源码

echarts helper 源码

echarts install 源码

echarts installCommon 源码

echarts installVisualMapContinuous 源码

echarts installVisualMapPiecewise 源码

0  赞