blob: accd72dd0f2c15cc431de54bf6399805121141e6 [file] [log] [blame]
/*
* 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.
*/
/**
* @file Visual solution, for consistent option specification.
*/
import * as zrUtil from 'zrender/src/core/util';
import VisualMapping, { VisualMappingOption } from './VisualMapping';
import { Dictionary } from 'zrender/src/core/types';
import {
BuiltinVisualProperty,
ParsedValue,
DimensionLoose,
StageHandlerProgressExecutor
} from '../util/types';
import List from '../data/List';
import { getItemVisualFromData, setItemVisualFromData } from './helper';
const each = zrUtil.each;
type VisualMappingCollection<VisualState extends string>
= {
[key in VisualState]?: {
[key in BuiltinVisualProperty]?: VisualMapping
} & {
__alphaForOpacity?: VisualMapping
}
};
function hasKeys(obj: Dictionary<any>) {
if (obj) {
for (const name in obj) {
if (obj.hasOwnProperty(name)) {
return true;
}
}
}
}
type VisualOption = {[key in BuiltinVisualProperty]?: any};
export function createVisualMappings<VisualState extends string>(
option: Partial<Record<VisualState, VisualOption>>,
stateList: readonly VisualState[],
supplementVisualOption: (mappingOption: VisualMappingOption, state: string) => void
) {
const visualMappings: VisualMappingCollection<VisualState> = {};
each(stateList, function (state) {
const mappings = visualMappings[state] = createMappings();
each(option[state], function (visualData: VisualOption, visualType: BuiltinVisualProperty) {
if (!VisualMapping.isValidType(visualType)) {
return;
}
let mappingOption = {
type: visualType,
visual: visualData
};
supplementVisualOption && supplementVisualOption(mappingOption, state);
mappings[visualType] = new VisualMapping(mappingOption);
// Prepare a alpha for opacity, for some case that opacity
// is not supported, such as rendering using gradient color.
if (visualType === 'opacity') {
mappingOption = zrUtil.clone(mappingOption);
mappingOption.type = 'colorAlpha';
mappings.__hidden.__alphaForOpacity = new VisualMapping(mappingOption);
}
});
});
return visualMappings;
function createMappings() {
const Creater = function () {};
// Make sure hidden fields will not be visited by
// object iteration (with hasOwnProperty checking).
Creater.prototype.__hidden = Creater.prototype;
const obj = new (Creater as any)();
return obj;
}
}
export function replaceVisualOption<T extends string>(
thisOption: Partial<Record<T, any>>, newOption: Partial<Record<T, any>>, keys: readonly T[]
) {
// Visual attributes merge is not supported, otherwise it
// brings overcomplicated merge logic. See #2853. So if
// newOption has anyone of these keys, all of these keys
// will be reset. Otherwise, all keys remain.
let has;
zrUtil.each(keys, function (key) {
if (newOption.hasOwnProperty(key) && hasKeys(newOption[key])) {
has = true;
}
});
has && zrUtil.each(keys, function (key) {
if (newOption.hasOwnProperty(key) && hasKeys(newOption[key])) {
thisOption[key] = zrUtil.clone(newOption[key]);
}
else {
delete thisOption[key];
}
});
}
/**
* @param stateList
* @param visualMappings
* @param list
* @param getValueState param: valueOrIndex, return: state.
* @param scope Scope for getValueState
* @param dimension Concrete dimension, if used.
*/
// ???! handle brush?
export function applyVisual<VisualState extends string, Scope>(
stateList: readonly VisualState[],
visualMappings: VisualMappingCollection<VisualState>,
data: List,
getValueState: (this: Scope, valueOrIndex: ParsedValue | number) => VisualState,
scope?: Scope,
dimension?: DimensionLoose
) {
const visualTypesMap: Partial<Record<VisualState, BuiltinVisualProperty[]>> = {};
zrUtil.each(stateList, function (state) {
const visualTypes = VisualMapping.prepareVisualTypes(visualMappings[state]);
visualTypesMap[state] = visualTypes;
});
let dataIndex: number;
function getVisual(key: string) {
return getItemVisualFromData(data, dataIndex, key) as string | number;
}
function setVisual(key: string, value: any) {
setItemVisualFromData(data, dataIndex, key, value);
}
if (dimension == null) {
data.each(eachItem);
}
else {
data.each([dimension], eachItem);
}
function eachItem(valueOrIndex: ParsedValue | number, index?: number) {
dataIndex = dimension == null
? valueOrIndex as number // First argument is index
: index;
const rawDataItem = data.getRawDataItem(dataIndex);
// Consider performance
// @ts-ignore
if (rawDataItem && rawDataItem.visualMap === false) {
return;
}
const valueState = getValueState.call(scope, valueOrIndex);
const mappings = visualMappings[valueState];
const visualTypes = visualTypesMap[valueState];
for (let i = 0, len = visualTypes.length; i < len; i++) {
const type = visualTypes[i];
mappings[type] && mappings[type].applyVisual(
valueOrIndex, getVisual, setVisual
);
}
}
}
/**
* @param data
* @param stateList
* @param visualMappings <state, Object.<visualType, module:echarts/visual/VisualMapping>>
* @param getValueState param: valueOrIndex, return: state.
* @param dim dimension or dimension index.
*/
export function incrementalApplyVisual<VisualState extends string>(
stateList: readonly VisualState[],
visualMappings: VisualMappingCollection<VisualState>,
getValueState: (valueOrIndex: ParsedValue | number) => VisualState,
dim?: DimensionLoose
): StageHandlerProgressExecutor {
const visualTypesMap: Partial<Record<VisualState, BuiltinVisualProperty[]>> = {};
zrUtil.each(stateList, function (state) {
const visualTypes = VisualMapping.prepareVisualTypes(visualMappings[state]);
visualTypesMap[state] = visualTypes;
});
return {
progress: function progress(params, data) {
let dimName: string;
if (dim != null) {
dimName = data.getDimension(dim);
}
function getVisual(key: string) {
return getItemVisualFromData(data, dataIndex, key) as string | number;
}
function setVisual(key: string, value: any) {
setItemVisualFromData(data, dataIndex, key, value);
}
let dataIndex: number;
while ((dataIndex = params.next()) != null) {
const rawDataItem = data.getRawDataItem(dataIndex);
// Consider performance
// @ts-ignore
if (rawDataItem && rawDataItem.visualMap === false) {
continue;
}
const value = dim != null
? data.get(dimName, dataIndex)
: dataIndex;
const valueState = getValueState(value);
const mappings = visualMappings[valueState];
const visualTypes = visualTypesMap[valueState];
for (let i = 0, len = visualTypes.length; i < len; i++) {
const type = visualTypes[i];
mappings[type] && mappings[type].applyVisual(value, getVisual, setVisual);
}
}
}
};
}