|  | /* | 
|  | * 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 * as zrColor from 'zrender/src/tool/color'; | 
|  | import {linearMap} from '../util/number'; | 
|  | import { AllPropTypes, Dictionary } from 'zrender/src/core/types'; | 
|  | import { | 
|  | ColorString, | 
|  | BuiltinVisualProperty, | 
|  | VisualOptionPiecewise, | 
|  | VisualOptionUnit, | 
|  | ParsedValue | 
|  | } from '../util/types'; | 
|  | import { warn } from '../util/log'; | 
|  |  | 
|  | const each = zrUtil.each; | 
|  | const isObject = zrUtil.isObject; | 
|  |  | 
|  | const CATEGORY_DEFAULT_VISUAL_INDEX = -1; | 
|  |  | 
|  | // Type of raw value | 
|  | type RawValue = ParsedValue; | 
|  | // Type of mapping visual value | 
|  | type VisualValue = AllPropTypes<VisualOptionUnit>; | 
|  | // Type of value after normalized. 0 - 1 | 
|  | type NormalizedValue = number; | 
|  |  | 
|  | type MappingMethod = 'linear' | 'piecewise' | 'category' | 'fixed'; | 
|  |  | 
|  | // May include liftZ. which is not provided to developers. | 
|  |  | 
|  | interface Normalizer { | 
|  | (this: VisualMapping, value?: RawValue): NormalizedValue | 
|  | } | 
|  | interface ColorMapper { | 
|  | (this: VisualMapping, value: RawValue | NormalizedValue, isNormalized?: boolean, out?: number[]) | 
|  | : ColorString | number[] | 
|  | } | 
|  | interface DoMap { | 
|  | (this: VisualMapping, normalzied?: NormalizedValue, value?: RawValue): VisualValue | 
|  | } | 
|  | interface VisualValueGetter { | 
|  | (key: string): VisualValue | 
|  | } | 
|  | interface VisualValueSetter { | 
|  | (key: string, value: VisualValue): void | 
|  | } | 
|  | interface VisualHandler { | 
|  | applyVisual( | 
|  | this: VisualMapping, | 
|  | value: RawValue, | 
|  | getter: VisualValueGetter, | 
|  | setter: VisualValueSetter | 
|  | ): void | 
|  |  | 
|  | _normalizedToVisual: { | 
|  | linear(this: VisualMapping, normalized: NormalizedValue): VisualValue | 
|  | category(this: VisualMapping, normalized: NormalizedValue): VisualValue | 
|  | piecewise(this: VisualMapping, normalzied: NormalizedValue, value: RawValue): VisualValue | 
|  | fixed(this: VisualMapping): VisualValue | 
|  | } | 
|  | /** | 
|  | * Get color mapping for the outside usage. | 
|  | * Currently only used in `color` visual. | 
|  | * | 
|  | * The last parameter out is cached color array. | 
|  | */ | 
|  | getColorMapper?: (this: VisualMapping) => ColorMapper | 
|  | } | 
|  |  | 
|  | interface VisualMappingPiece { | 
|  | index?: number | 
|  |  | 
|  | value?: number | string | 
|  | interval?: [number, number] | 
|  | close?: [0 | 1, 0 | 1] | 
|  |  | 
|  | text?: string | 
|  |  | 
|  | visual?: VisualOptionPiecewise | 
|  | } | 
|  |  | 
|  | export interface VisualMappingOption { | 
|  | type?: BuiltinVisualProperty | 
|  |  | 
|  | mappingMethod?: MappingMethod | 
|  |  | 
|  | /** | 
|  | * required when mappingMethod is 'linear' | 
|  | */ | 
|  | dataExtent?: [number, number] | 
|  | /** | 
|  | *  required when mappingMethod is 'piecewise'. | 
|  | *  Visual for only each piece can be specified | 
|  | * [ | 
|  | *   {value: someValue}, | 
|  | *   {interval: [min1, max1], visual: {...}}, | 
|  | *   {interval: [min2, max2]} | 
|  | *  ],. | 
|  | */ | 
|  | pieceList?: VisualMappingPiece[] | 
|  | /** | 
|  | * required when mappingMethod is 'category'. If no option.categories, categories is set as [0, 1, 2, ...]. | 
|  | */ | 
|  | categories?: (string | number)[] | 
|  | /** | 
|  | * Whether loop mapping when mappingMethod is 'category'. | 
|  | * @default false | 
|  | */ | 
|  | loop?: boolean | 
|  | /** | 
|  | * Visual data | 
|  | * when mappingMethod is 'category', visual data can be array or object | 
|  | * (like: {cate1: '#222', none: '#fff'}) | 
|  | * or primary types (which represents default category visual), otherwise visual | 
|  | * can be array or primary (which will be normalized to array). | 
|  | */ | 
|  | visual?: VisualValue[] | Dictionary<VisualValue> | VisualValue | 
|  | } | 
|  |  | 
|  | interface VisualMappingInnerPiece extends VisualMappingPiece { | 
|  | originIndex: number | 
|  | } | 
|  | interface VisualMappingInnerOption extends VisualMappingOption { | 
|  | hasSpecialVisual: boolean | 
|  | pieceList: VisualMappingInnerPiece[] | 
|  | /** | 
|  | * Map to get category index | 
|  | */ | 
|  | categoryMap: Dictionary<number> | 
|  | /** | 
|  | * Cached parsed rgba array from string to avoid parse every time. | 
|  | */ | 
|  | parsedVisual: number[][] | 
|  |  | 
|  | // Have converted primary value to array. | 
|  | visual?: VisualValue[] | Dictionary<VisualValue> | 
|  | } | 
|  |  | 
|  | class VisualMapping { | 
|  |  | 
|  | option: VisualMappingInnerOption; | 
|  |  | 
|  | type: BuiltinVisualProperty; | 
|  |  | 
|  | mappingMethod: MappingMethod; | 
|  |  | 
|  | applyVisual: VisualHandler['applyVisual']; | 
|  |  | 
|  | getColorMapper: VisualHandler['getColorMapper']; | 
|  |  | 
|  | _normalizeData: Normalizer; | 
|  |  | 
|  | _normalizedToVisual: DoMap; | 
|  |  | 
|  | constructor(option: VisualMappingOption) { | 
|  | const mappingMethod = option.mappingMethod; | 
|  | const visualType = option.type; | 
|  |  | 
|  | const thisOption: VisualMappingInnerOption = this.option = zrUtil.clone(option) as VisualMappingInnerOption; | 
|  |  | 
|  | this.type = visualType; | 
|  | this.mappingMethod = mappingMethod; | 
|  |  | 
|  | this._normalizeData = normalizers[mappingMethod]; | 
|  | const visualHandler = VisualMapping.visualHandlers[visualType]; | 
|  |  | 
|  | this.applyVisual = visualHandler.applyVisual; | 
|  |  | 
|  | this.getColorMapper = visualHandler.getColorMapper; | 
|  |  | 
|  | this._normalizedToVisual = visualHandler._normalizedToVisual[mappingMethod]; | 
|  |  | 
|  | if (mappingMethod === 'piecewise') { | 
|  | normalizeVisualRange(thisOption); | 
|  | preprocessForPiecewise(thisOption); | 
|  | } | 
|  | else if (mappingMethod === 'category') { | 
|  | thisOption.categories | 
|  | ? preprocessForSpecifiedCategory(thisOption) | 
|  | // categories is ordinal when thisOption.categories not specified, | 
|  | // which need no more preprocess except normalize visual. | 
|  | : normalizeVisualRange(thisOption, true); | 
|  | } | 
|  | else { // mappingMethod === 'linear' or 'fixed' | 
|  | zrUtil.assert(mappingMethod !== 'linear' || thisOption.dataExtent); | 
|  | normalizeVisualRange(thisOption); | 
|  | } | 
|  | } | 
|  |  | 
|  | mapValueToVisual(value: RawValue): VisualValue { | 
|  | const normalized = this._normalizeData(value); | 
|  | return this._normalizedToVisual(normalized, value); | 
|  | } | 
|  |  | 
|  | getNormalizer() { | 
|  | return zrUtil.bind(this._normalizeData, this); | 
|  | } | 
|  |  | 
|  | static visualHandlers: {[key in BuiltinVisualProperty]: VisualHandler} = { | 
|  | color: { | 
|  | applyVisual: makeApplyVisual('color'), | 
|  | getColorMapper: function () { | 
|  | const thisOption = this.option; | 
|  |  | 
|  | return zrUtil.bind( | 
|  | thisOption.mappingMethod === 'category' | 
|  | ? function ( | 
|  | this: VisualMapping, | 
|  | value: NormalizedValue | RawValue, | 
|  | isNormalized?: boolean | 
|  | ): ColorString { | 
|  | !isNormalized && (value = this._normalizeData(value)); | 
|  | return doMapCategory.call(this, value) as ColorString; | 
|  | } | 
|  | : function ( | 
|  | this: VisualMapping, | 
|  | value: NormalizedValue | RawValue, | 
|  | isNormalized?: boolean, | 
|  | out?: number[] | 
|  | ): number[] | string { | 
|  | // If output rgb array | 
|  | // which will be much faster and useful in pixel manipulation | 
|  | const returnRGBArray = !!out; | 
|  | !isNormalized && (value = this._normalizeData(value)); | 
|  | out = zrColor.fastLerp(value as NormalizedValue, thisOption.parsedVisual, out); | 
|  | return returnRGBArray ? out : zrColor.stringify(out, 'rgba'); | 
|  | }, | 
|  | this | 
|  | ); | 
|  | }, | 
|  |  | 
|  | _normalizedToVisual: { | 
|  | linear: function (normalized) { | 
|  | return zrColor.stringify( | 
|  | zrColor.fastLerp(normalized, this.option.parsedVisual), | 
|  | 'rgba' | 
|  | ); | 
|  | }, | 
|  | category: doMapCategory, | 
|  | piecewise: function (normalized, value) { | 
|  | let result = getSpecifiedVisual.call(this, value); | 
|  | if (result == null) { | 
|  | result = zrColor.stringify( | 
|  | zrColor.fastLerp(normalized, this.option.parsedVisual), | 
|  | 'rgba' | 
|  | ); | 
|  | } | 
|  | return result; | 
|  | }, | 
|  | fixed: doMapFixed | 
|  | } | 
|  | }, | 
|  |  | 
|  | colorHue: makePartialColorVisualHandler(function (color: ColorString, value: number) { | 
|  | return zrColor.modifyHSL(color, value); | 
|  | }), | 
|  |  | 
|  | colorSaturation: makePartialColorVisualHandler(function (color: ColorString, value: number) { | 
|  | return zrColor.modifyHSL(color, null, value); | 
|  | }), | 
|  |  | 
|  | colorLightness: makePartialColorVisualHandler(function (color: ColorString, value: number) { | 
|  | return zrColor.modifyHSL(color, null, null, value); | 
|  | }), | 
|  |  | 
|  | colorAlpha: makePartialColorVisualHandler(function (color: ColorString, value: number) { | 
|  | return zrColor.modifyAlpha(color, value); | 
|  | }), | 
|  |  | 
|  | decal: { | 
|  | applyVisual: makeApplyVisual('decal'), | 
|  | _normalizedToVisual: { | 
|  | linear: null, | 
|  | category: doMapCategory, | 
|  | piecewise: null, | 
|  | fixed: null | 
|  | } | 
|  | }, | 
|  |  | 
|  | opacity: { | 
|  | applyVisual: makeApplyVisual('opacity'), | 
|  | _normalizedToVisual: createNormalizedToNumericVisual([0, 1]) | 
|  | }, | 
|  |  | 
|  | liftZ: { | 
|  | applyVisual: makeApplyVisual('liftZ'), | 
|  | _normalizedToVisual: { | 
|  | linear: doMapFixed, | 
|  | category: doMapFixed, | 
|  | piecewise: doMapFixed, | 
|  | fixed: doMapFixed | 
|  | } | 
|  | }, | 
|  |  | 
|  | symbol: { | 
|  | applyVisual: function (value, getter, setter) { | 
|  | const symbolCfg = this.mapValueToVisual(value); | 
|  | setter('symbol', symbolCfg as string); | 
|  | }, | 
|  | _normalizedToVisual: { | 
|  | linear: doMapToArray, | 
|  | category: doMapCategory, | 
|  | piecewise: function (normalized, value) { | 
|  | let result = getSpecifiedVisual.call(this, value); | 
|  | if (result == null) { | 
|  | result = doMapToArray.call(this, normalized); | 
|  | } | 
|  | return result; | 
|  | }, | 
|  | fixed: doMapFixed | 
|  | } | 
|  | }, | 
|  |  | 
|  | symbolSize: { | 
|  | applyVisual: makeApplyVisual('symbolSize'), | 
|  | _normalizedToVisual: createNormalizedToNumericVisual([0, 1]) | 
|  | } | 
|  | }; | 
|  |  | 
|  |  | 
|  | /** | 
|  | * List available visual types. | 
|  | * | 
|  | * @public | 
|  | * @return {Array.<string>} | 
|  | */ | 
|  | static listVisualTypes() { | 
|  | return zrUtil.keys(VisualMapping.visualHandlers); | 
|  | } | 
|  |  | 
|  | // /** | 
|  | //  * @public | 
|  | //  */ | 
|  | // static addVisualHandler(name, handler) { | 
|  | //     visualHandlers[name] = handler; | 
|  | // } | 
|  |  | 
|  | /** | 
|  | * @public | 
|  | */ | 
|  | static isValidType(visualType: string): boolean { | 
|  | return VisualMapping.visualHandlers.hasOwnProperty(visualType); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Convenient method. | 
|  | * Visual can be Object or Array or primary type. | 
|  | */ | 
|  | static eachVisual<Ctx, T>( | 
|  | visual: T | T[] | Dictionary<T>, | 
|  | callback: (visual: T, key?: string | number) => void, | 
|  | context?: Ctx | 
|  | ) { | 
|  | if (zrUtil.isObject(visual)) { | 
|  | zrUtil.each(visual as Dictionary<T>, callback, context); | 
|  | } | 
|  | else { | 
|  | callback.call(context, visual); | 
|  | } | 
|  | } | 
|  |  | 
|  | static mapVisual<Ctx, T>(visual: T, callback: (visual: T, key?: string | number) => T, context?: Ctx): T; | 
|  | static mapVisual<Ctx, T>(visual: T[], callback: (visual: T, key?: string | number) => T[], context?: Ctx): T[]; | 
|  | static mapVisual<Ctx, T>( | 
|  | visual: Dictionary<T>, | 
|  | callback: (visual: T, key?: string | number) => Dictionary<T>, | 
|  | context?: Ctx | 
|  | ): Dictionary<T>; | 
|  | static mapVisual<Ctx, T>( | 
|  | visual: T | T[] | Dictionary<T>, | 
|  | callback: (visual: T, key?: string | number) => T | T[] | Dictionary<T>, | 
|  | context?: Ctx | 
|  | ) { | 
|  | let isPrimary: boolean; | 
|  | let newVisual: T | T[] | Dictionary<T> = zrUtil.isArray(visual) | 
|  | ? [] | 
|  | : zrUtil.isObject(visual) | 
|  | ? {} | 
|  | : (isPrimary = true, null); | 
|  |  | 
|  | VisualMapping.eachVisual(visual, function (v, key) { | 
|  | const newVal = callback.call(context, v, key); | 
|  | isPrimary ? (newVisual = newVal) : ((newVisual as Dictionary<T>)[key as string] = newVal as T); | 
|  | }); | 
|  | return newVisual; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Retrieve visual properties from given object. | 
|  | */ | 
|  | static retrieveVisuals(obj: Dictionary<any>): VisualOptionPiecewise { | 
|  | const ret: VisualOptionPiecewise = {}; | 
|  | let hasVisual: boolean; | 
|  |  | 
|  | obj && each(VisualMapping.visualHandlers, function (h, visualType: BuiltinVisualProperty) { | 
|  | if (obj.hasOwnProperty(visualType)) { | 
|  | (ret as any)[visualType] = obj[visualType]; | 
|  | hasVisual = true; | 
|  | } | 
|  | }); | 
|  |  | 
|  | return hasVisual ? ret : null; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Give order to visual types, considering colorSaturation, colorAlpha depends on color. | 
|  | * | 
|  | * @public | 
|  | * @param {(Object|Array)} visualTypes If Object, like: {color: ..., colorSaturation: ...} | 
|  | *                                     IF Array, like: ['color', 'symbol', 'colorSaturation'] | 
|  | * @return {Array.<string>} Sorted visual types. | 
|  | */ | 
|  | static prepareVisualTypes( | 
|  | visualTypes: {[key in BuiltinVisualProperty]?: any} | BuiltinVisualProperty[] | 
|  | ) { | 
|  | if (zrUtil.isArray(visualTypes)) { | 
|  | visualTypes = visualTypes.slice(); | 
|  | } | 
|  | else if (isObject(visualTypes)) { | 
|  | const types: BuiltinVisualProperty[] = []; | 
|  | each(visualTypes, function (item: unknown, type: BuiltinVisualProperty) { | 
|  | types.push(type); | 
|  | }); | 
|  | visualTypes = types; | 
|  | } | 
|  | else { | 
|  | return []; | 
|  | } | 
|  |  | 
|  | visualTypes.sort(function (type1: BuiltinVisualProperty, type2: BuiltinVisualProperty) { | 
|  | // color should be front of colorSaturation, colorAlpha, ... | 
|  | // symbol and symbolSize do not matter. | 
|  | return (type2 === 'color' && type1 !== 'color' && type1.indexOf('color') === 0) | 
|  | ? 1 : -1; | 
|  | }); | 
|  |  | 
|  | return visualTypes; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * 'color', 'colorSaturation', 'colorAlpha', ... are depends on 'color'. | 
|  | * Other visuals are only depends on themself. | 
|  | */ | 
|  | static dependsOn(visualType1: BuiltinVisualProperty, visualType2: BuiltinVisualProperty) { | 
|  | return visualType2 === 'color' | 
|  | ? !!(visualType1 && visualType1.indexOf(visualType2) === 0) | 
|  | : visualType1 === visualType2; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param value | 
|  | * @param pieceList [{value: ..., interval: [min, max]}, ...] | 
|  | *                         Always from small to big. | 
|  | * @param findClosestWhenOutside Default to be false | 
|  | * @return index | 
|  | */ | 
|  | static findPieceIndex(value: number, pieceList: VisualMappingPiece[], findClosestWhenOutside?: boolean): number { | 
|  | let possibleI: number; | 
|  | let abs = Infinity; | 
|  |  | 
|  | // value has the higher priority. | 
|  | for (let i = 0, len = pieceList.length; i < len; i++) { | 
|  | const pieceValue = pieceList[i].value; | 
|  | if (pieceValue != null) { | 
|  | if (pieceValue === value | 
|  | // FIXME | 
|  | // It is supposed to compare value according to value type of dimension, | 
|  | // but currently value type can exactly be string or number. | 
|  | // Compromise for numeric-like string (like '12'), especially | 
|  | // in the case that visualMap.categories is ['22', '33']. | 
|  | || (zrUtil.isString(pieceValue) && pieceValue === value + '') | 
|  | ) { | 
|  | return i; | 
|  | } | 
|  | findClosestWhenOutside && updatePossible(pieceValue as number, i); | 
|  | } | 
|  | } | 
|  |  | 
|  | for (let i = 0, len = pieceList.length; i < len; i++) { | 
|  | const piece = pieceList[i]; | 
|  | const interval = piece.interval; | 
|  | const close = piece.close; | 
|  |  | 
|  | if (interval) { | 
|  | if (interval[0] === -Infinity) { | 
|  | if (littleThan(close[1], value, interval[1])) { | 
|  | return i; | 
|  | } | 
|  | } | 
|  | else if (interval[1] === Infinity) { | 
|  | if (littleThan(close[0], interval[0], value)) { | 
|  | return i; | 
|  | } | 
|  | } | 
|  | else if ( | 
|  | littleThan(close[0], interval[0], value) | 
|  | && littleThan(close[1], value, interval[1]) | 
|  | ) { | 
|  | return i; | 
|  | } | 
|  | findClosestWhenOutside && updatePossible(interval[0], i); | 
|  | findClosestWhenOutside && updatePossible(interval[1], i); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (findClosestWhenOutside) { | 
|  | return value === Infinity | 
|  | ? pieceList.length - 1 | 
|  | : value === -Infinity | 
|  | ? 0 | 
|  | : possibleI; | 
|  | } | 
|  |  | 
|  | function updatePossible(val: number, index: number) { | 
|  | const newAbs = Math.abs(val - value); | 
|  | if (newAbs < abs) { | 
|  | abs = newAbs; | 
|  | possibleI = index; | 
|  | } | 
|  | } | 
|  |  | 
|  | } | 
|  | } | 
|  |  | 
|  | function preprocessForPiecewise(thisOption: VisualMappingInnerOption) { | 
|  | const pieceList = thisOption.pieceList; | 
|  | thisOption.hasSpecialVisual = false; | 
|  |  | 
|  | zrUtil.each(pieceList, function (piece, index) { | 
|  | piece.originIndex = index; | 
|  | // piece.visual is "result visual value" but not | 
|  | // a visual range, so it does not need to be normalized. | 
|  | if (piece.visual != null) { | 
|  | thisOption.hasSpecialVisual = true; | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | function preprocessForSpecifiedCategory(thisOption: VisualMappingInnerOption) { | 
|  | // Hash categories. | 
|  | const categories = thisOption.categories; | 
|  | const categoryMap: VisualMappingInnerOption['categoryMap'] = thisOption.categoryMap = {}; | 
|  |  | 
|  | let visual = thisOption.visual; | 
|  | each(categories, function (cate, index) { | 
|  | categoryMap[cate] = index; | 
|  | }); | 
|  |  | 
|  | // Process visual map input. | 
|  | if (!zrUtil.isArray(visual)) { | 
|  | const visualArr: VisualValue[] = []; | 
|  |  | 
|  | if (zrUtil.isObject(visual)) { | 
|  | each(visual, function (v, cate) { | 
|  | const index = categoryMap[cate]; | 
|  | visualArr[index != null ? index : CATEGORY_DEFAULT_VISUAL_INDEX] = v; | 
|  | }); | 
|  | } | 
|  | else { // Is primary type, represents default visual. | 
|  | visualArr[CATEGORY_DEFAULT_VISUAL_INDEX] = visual; | 
|  | } | 
|  |  | 
|  | visual = setVisualToOption(thisOption, visualArr); | 
|  | } | 
|  |  | 
|  | // Remove categories that has no visual, | 
|  | // then we can mapping them to CATEGORY_DEFAULT_VISUAL_INDEX. | 
|  | for (let i = categories.length - 1; i >= 0; i--) { | 
|  | if (visual[i] == null) { | 
|  | delete categoryMap[categories[i]]; | 
|  | categories.pop(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | function normalizeVisualRange(thisOption: VisualMappingInnerOption, isCategory?: boolean) { | 
|  | const visual = thisOption.visual; | 
|  | const visualArr: VisualValue[] = []; | 
|  |  | 
|  | if (zrUtil.isObject(visual)) { | 
|  | each(visual, function (v) { | 
|  | visualArr.push(v); | 
|  | }); | 
|  | } | 
|  | else if (visual != null) { | 
|  | visualArr.push(visual); | 
|  | } | 
|  |  | 
|  | const doNotNeedPair = {color: 1, symbol: 1}; | 
|  |  | 
|  | if (!isCategory | 
|  | && visualArr.length === 1 | 
|  | && !doNotNeedPair.hasOwnProperty(thisOption.type) | 
|  | ) { | 
|  | // Do not care visualArr.length === 0, which is illegal. | 
|  | visualArr[1] = visualArr[0]; | 
|  | } | 
|  |  | 
|  | setVisualToOption(thisOption, visualArr); | 
|  | } | 
|  |  | 
|  | function makePartialColorVisualHandler( | 
|  | applyValue: (prop: VisualValue, value: NormalizedValue) => VisualValue | 
|  | ): VisualHandler { | 
|  | return { | 
|  | applyVisual: function (value, getter, setter) { | 
|  | // Only used in HSL | 
|  | const colorChannel = this.mapValueToVisual(value); | 
|  | // Must not be array value | 
|  | setter('color', applyValue(getter('color'), colorChannel as number)); | 
|  | }, | 
|  | _normalizedToVisual: createNormalizedToNumericVisual([0, 1]) | 
|  | }; | 
|  | } | 
|  |  | 
|  | function doMapToArray(this: VisualMapping, normalized: NormalizedValue): VisualValue { | 
|  | const visual = this.option.visual as VisualValue[]; | 
|  | return visual[ | 
|  | Math.round(linearMap(normalized, [0, 1], [0, visual.length - 1], true)) | 
|  | ] || {} as any;    // TODO {}? | 
|  | } | 
|  |  | 
|  | function makeApplyVisual(visualType: string): VisualHandler['applyVisual'] { | 
|  | return function (value, getter, setter) { | 
|  | setter(visualType, this.mapValueToVisual(value)); | 
|  | }; | 
|  | } | 
|  |  | 
|  | function doMapCategory(this: VisualMapping, normalized: NormalizedValue): VisualValue { | 
|  | const visual = this.option.visual as Dictionary<any>; | 
|  | return visual[ | 
|  | (this.option.loop && normalized !== CATEGORY_DEFAULT_VISUAL_INDEX) | 
|  | ? normalized % visual.length | 
|  | : normalized | 
|  | ]; | 
|  | } | 
|  |  | 
|  | function doMapFixed(this: VisualMapping): VisualValue { | 
|  | // visual will be convert to array. | 
|  | return (this.option.visual as VisualValue[])[0]; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Create mapped to numeric visual | 
|  | */ | 
|  | function createNormalizedToNumericVisual(sourceExtent: [number, number]): VisualHandler['_normalizedToVisual'] { | 
|  | return { | 
|  | linear: function (normalized) { | 
|  | return linearMap(normalized, sourceExtent, this.option.visual as [number, number], true); | 
|  | }, | 
|  | category: doMapCategory, | 
|  | piecewise: function (normalized, value) { | 
|  | let result = getSpecifiedVisual.call(this, value); | 
|  | if (result == null) { | 
|  | result = linearMap(normalized, sourceExtent, this.option.visual as [number, number], true); | 
|  | } | 
|  | return result; | 
|  | }, | 
|  | fixed: doMapFixed | 
|  | }; | 
|  | } | 
|  |  | 
|  | function getSpecifiedVisual(this: VisualMapping, value: number) { | 
|  | const thisOption = this.option; | 
|  | const pieceList = thisOption.pieceList; | 
|  | if (thisOption.hasSpecialVisual) { | 
|  | const pieceIndex = VisualMapping.findPieceIndex(value, pieceList); | 
|  | const piece = pieceList[pieceIndex]; | 
|  | if (piece && piece.visual) { | 
|  | return piece.visual[this.type]; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | function setVisualToOption(thisOption: VisualMappingInnerOption, visualArr: VisualValue[]) { | 
|  | thisOption.visual = visualArr; | 
|  | if (thisOption.type === 'color') { | 
|  | thisOption.parsedVisual = zrUtil.map(visualArr, function (item: string) { | 
|  | const color = zrColor.parse(item); | 
|  | if (!color && __DEV__) { | 
|  | warn(`'${item}' is an illegal color, fallback to '#000000'`, true); | 
|  | } | 
|  | return color || [0, 0, 0, 1]; | 
|  | }); | 
|  | } | 
|  | return visualArr; | 
|  | } | 
|  |  | 
|  |  | 
|  | /** | 
|  | * Normalizers by mapping methods. | 
|  | */ | 
|  | const normalizers: { [key in MappingMethod]: Normalizer } = { | 
|  | linear: function (value: RawValue): NormalizedValue { | 
|  | return linearMap(value as number, this.option.dataExtent, [0, 1], true); | 
|  | }, | 
|  |  | 
|  | piecewise: function (value: RawValue): NormalizedValue { | 
|  | const pieceList = this.option.pieceList; | 
|  | const pieceIndex = VisualMapping.findPieceIndex(value as number, pieceList, true); | 
|  | if (pieceIndex != null) { | 
|  | return linearMap(pieceIndex, [0, pieceList.length - 1], [0, 1], true); | 
|  | } | 
|  | }, | 
|  |  | 
|  | category: function (value: RawValue): NormalizedValue { | 
|  | const index: number = this.option.categories | 
|  | ? this.option.categoryMap[value] | 
|  | : value as number; // ordinal value | 
|  | return index == null ? CATEGORY_DEFAULT_VISUAL_INDEX : index; | 
|  | }, | 
|  |  | 
|  | fixed: zrUtil.noop as Normalizer | 
|  | }; | 
|  |  | 
|  |  | 
|  | function littleThan(close: boolean | 0 | 1, a: number, b: number): boolean { | 
|  | return close ? a <= b : a < b; | 
|  | } | 
|  |  | 
|  | export default VisualMapping; |