Spaces:
Running
Running
| /* | |
| * 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, | |
| * until user specified 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 don't 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; | |