You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
696 lines
26 KiB
696 lines
26 KiB
|
|
/*
|
|
* 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.
|
|
*/
|
|
|
|
|
|
/**
|
|
* AUTO-GENERATED FILE. DO NOT MODIFY.
|
|
*/
|
|
|
|
/*
|
|
* 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 { each, isObject, isArray, createHashMap, map, assert, isString, indexOf, isStringSafe, isNumber } from 'zrender/lib/core/util.js';
|
|
import env from 'zrender/lib/core/env.js';
|
|
import { isNumeric, getRandomIdBase, getPrecision, round } from './number.js';
|
|
import { warn } from './log.js';
|
|
function interpolateNumber(p0, p1, percent) {
|
|
return (p1 - p0) * percent + p0;
|
|
}
|
|
/**
|
|
* Make the name displayable. But we should
|
|
* make sure it is not duplicated with user
|
|
* specified name, so use '\0';
|
|
*/
|
|
var DUMMY_COMPONENT_NAME_PREFIX = 'series\0';
|
|
var INTERNAL_COMPONENT_ID_PREFIX = '\0_ec_\0';
|
|
/**
|
|
* If value is not array, then translate it to array.
|
|
* @param {*} value
|
|
* @return {Array} [value] or value
|
|
*/
|
|
export function normalizeToArray(value) {
|
|
return value instanceof Array ? value : value == null ? [] : [value];
|
|
}
|
|
/**
|
|
* Sync default option between normal and emphasis like `position` and `show`
|
|
* In case some one will write code like
|
|
* label: {
|
|
* show: false,
|
|
* position: 'outside',
|
|
* fontSize: 18
|
|
* },
|
|
* emphasis: {
|
|
* label: { show: true }
|
|
* }
|
|
*/
|
|
export function defaultEmphasis(opt, key, subOpts) {
|
|
// Caution: performance sensitive.
|
|
if (opt) {
|
|
opt[key] = opt[key] || {};
|
|
opt.emphasis = opt.emphasis || {};
|
|
opt.emphasis[key] = opt.emphasis[key] || {};
|
|
// Default emphasis option from normal
|
|
for (var i = 0, len = subOpts.length; i < len; i++) {
|
|
var subOptName = subOpts[i];
|
|
if (!opt.emphasis[key].hasOwnProperty(subOptName) && opt[key].hasOwnProperty(subOptName)) {
|
|
opt.emphasis[key][subOptName] = opt[key][subOptName];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
export var TEXT_STYLE_OPTIONS = ['fontStyle', 'fontWeight', 'fontSize', 'fontFamily', 'rich', 'tag', 'color', 'textBorderColor', 'textBorderWidth', 'width', 'height', 'lineHeight', 'align', 'verticalAlign', 'baseline', 'shadowColor', 'shadowBlur', 'shadowOffsetX', 'shadowOffsetY', 'textShadowColor', 'textShadowBlur', 'textShadowOffsetX', 'textShadowOffsetY', 'backgroundColor', 'borderColor', 'borderWidth', 'borderRadius', 'padding'];
|
|
// modelUtil.LABEL_OPTIONS = modelUtil.TEXT_STYLE_OPTIONS.concat([
|
|
// 'position', 'offset', 'rotate', 'origin', 'show', 'distance', 'formatter',
|
|
// 'fontStyle', 'fontWeight', 'fontSize', 'fontFamily',
|
|
// // FIXME: deprecated, check and remove it.
|
|
// 'textStyle'
|
|
// ]);
|
|
/**
|
|
* The method does not ensure performance.
|
|
* data could be [12, 2323, {value: 223}, [1221, 23], {value: [2, 23]}]
|
|
* This helper method retrieves value from data.
|
|
*/
|
|
export function getDataItemValue(dataItem) {
|
|
return isObject(dataItem) && !isArray(dataItem) && !(dataItem instanceof Date) ? dataItem.value : dataItem;
|
|
}
|
|
/**
|
|
* data could be [12, 2323, {value: 223}, [1221, 23], {value: [2, 23]}]
|
|
* This helper method determine if dataItem has extra option besides value
|
|
*/
|
|
export function isDataItemOption(dataItem) {
|
|
return isObject(dataItem) && !(dataItem instanceof Array);
|
|
// // markLine data can be array
|
|
// && !(dataItem[0] && isObject(dataItem[0]) && !(dataItem[0] instanceof Array));
|
|
}
|
|
|
|
;
|
|
/**
|
|
* Mapping to existings for merge.
|
|
*
|
|
* Mode "normalMege":
|
|
* The mapping result (merge result) will keep the order of the existing
|
|
* component, rather than the order of new option. Because we should ensure
|
|
* some specified index reference (like xAxisIndex) keep work.
|
|
* And in most cases, "merge option" is used to update partial option but not
|
|
* be expected to change the order.
|
|
*
|
|
* Mode "replaceMege":
|
|
* (1) Only the id mapped components will be merged.
|
|
* (2) Other existing components (except internal components) will be removed.
|
|
* (3) Other new options will be used to create new component.
|
|
* (4) The index of the existing components will not be modified.
|
|
* That means their might be "hole" after the removal.
|
|
* The new components are created first at those available index.
|
|
*
|
|
* Mode "replaceAll":
|
|
* This mode try to support that reproduce an echarts instance from another
|
|
* echarts instance (via `getOption`) in some simple cases.
|
|
* In this scenario, the `result` index are exactly the consistent with the `newCmptOptions`,
|
|
* which ensures the component index referring (like `xAxisIndex: ?`) corrent. That is,
|
|
* the "hole" in `newCmptOptions` will also be kept.
|
|
* On the contrary, other modes try best to eliminate holes.
|
|
* PENDING: This is an experimental mode yet.
|
|
*
|
|
* @return See the comment of <MappingResult>.
|
|
*/
|
|
export function mappingToExists(existings, newCmptOptions, mode) {
|
|
var isNormalMergeMode = mode === 'normalMerge';
|
|
var isReplaceMergeMode = mode === 'replaceMerge';
|
|
var isReplaceAllMode = mode === 'replaceAll';
|
|
existings = existings || [];
|
|
newCmptOptions = (newCmptOptions || []).slice();
|
|
var existingIdIdxMap = createHashMap();
|
|
// Validate id and name on user input option.
|
|
each(newCmptOptions, function (cmptOption, index) {
|
|
if (!isObject(cmptOption)) {
|
|
newCmptOptions[index] = null;
|
|
return;
|
|
}
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
// There is some legacy case that name is set as `false`.
|
|
// But should work normally rather than throw error.
|
|
if (cmptOption.id != null && !isValidIdOrName(cmptOption.id)) {
|
|
warnInvalidateIdOrName(cmptOption.id);
|
|
}
|
|
if (cmptOption.name != null && !isValidIdOrName(cmptOption.name)) {
|
|
warnInvalidateIdOrName(cmptOption.name);
|
|
}
|
|
}
|
|
});
|
|
var result = prepareResult(existings, existingIdIdxMap, mode);
|
|
if (isNormalMergeMode || isReplaceMergeMode) {
|
|
mappingById(result, existings, existingIdIdxMap, newCmptOptions);
|
|
}
|
|
if (isNormalMergeMode) {
|
|
mappingByName(result, newCmptOptions);
|
|
}
|
|
if (isNormalMergeMode || isReplaceMergeMode) {
|
|
mappingByIndex(result, newCmptOptions, isReplaceMergeMode);
|
|
} else if (isReplaceAllMode) {
|
|
mappingInReplaceAllMode(result, newCmptOptions);
|
|
}
|
|
makeIdAndName(result);
|
|
// The array `result` MUST NOT contain elided items, otherwise the
|
|
// forEach will omit those items and result in incorrect result.
|
|
return result;
|
|
}
|
|
function prepareResult(existings, existingIdIdxMap, mode) {
|
|
var result = [];
|
|
if (mode === 'replaceAll') {
|
|
return result;
|
|
}
|
|
// Do not use native `map` to in case that the array `existings`
|
|
// contains elided items, which will be omitted.
|
|
for (var index = 0; index < existings.length; index++) {
|
|
var existing = existings[index];
|
|
// Because of replaceMerge, `existing` may be null/undefined.
|
|
if (existing && existing.id != null) {
|
|
existingIdIdxMap.set(existing.id, index);
|
|
}
|
|
// For non-internal-componnets:
|
|
// Mode "normalMerge": all existings kept.
|
|
// Mode "replaceMerge": all existing removed unless mapped by id.
|
|
// For internal-components:
|
|
// go with "replaceMerge" approach in both mode.
|
|
result.push({
|
|
existing: mode === 'replaceMerge' || isComponentIdInternal(existing) ? null : existing,
|
|
newOption: null,
|
|
keyInfo: null,
|
|
brandNew: null
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
function mappingById(result, existings, existingIdIdxMap, newCmptOptions) {
|
|
// Mapping by id if specified.
|
|
each(newCmptOptions, function (cmptOption, index) {
|
|
if (!cmptOption || cmptOption.id == null) {
|
|
return;
|
|
}
|
|
var optionId = makeComparableKey(cmptOption.id);
|
|
var existingIdx = existingIdIdxMap.get(optionId);
|
|
if (existingIdx != null) {
|
|
var resultItem = result[existingIdx];
|
|
assert(!resultItem.newOption, 'Duplicated option on id "' + optionId + '".');
|
|
resultItem.newOption = cmptOption;
|
|
// In both mode, if id matched, new option will be merged to
|
|
// the existings rather than creating new component model.
|
|
resultItem.existing = existings[existingIdx];
|
|
newCmptOptions[index] = null;
|
|
}
|
|
});
|
|
}
|
|
function mappingByName(result, newCmptOptions) {
|
|
// Mapping by name if specified.
|
|
each(newCmptOptions, function (cmptOption, index) {
|
|
if (!cmptOption || cmptOption.name == null) {
|
|
return;
|
|
}
|
|
for (var i = 0; i < result.length; i++) {
|
|
var existing = result[i].existing;
|
|
if (!result[i].newOption // Consider name: two map to one.
|
|
// Can not match when both ids existing but different.
|
|
&& existing && (existing.id == null || cmptOption.id == null) && !isComponentIdInternal(cmptOption) && !isComponentIdInternal(existing) && keyExistAndEqual('name', existing, cmptOption)) {
|
|
result[i].newOption = cmptOption;
|
|
newCmptOptions[index] = null;
|
|
return;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
function mappingByIndex(result, newCmptOptions, brandNew) {
|
|
each(newCmptOptions, function (cmptOption) {
|
|
if (!cmptOption) {
|
|
return;
|
|
}
|
|
// Find the first place that not mapped by id and not internal component (consider the "hole").
|
|
var resultItem;
|
|
var nextIdx = 0;
|
|
while (
|
|
// Be `!resultItem` only when `nextIdx >= result.length`.
|
|
(resultItem = result[nextIdx]
|
|
// (1) Existing models that already have id should be able to mapped to. Because
|
|
// after mapping performed, model will always be assigned with an id if user not given.
|
|
// After that all models have id.
|
|
// (2) If new option has id, it can only set to a hole or append to the last. It should
|
|
// not be merged to the existings with different id. Because id should not be overwritten.
|
|
// (3) Name can be overwritten, because axis use name as 'show label text'.
|
|
) && (resultItem.newOption || isComponentIdInternal(resultItem.existing) ||
|
|
// In mode "replaceMerge", here no not-mapped-non-internal-existing.
|
|
resultItem.existing && cmptOption.id != null && !keyExistAndEqual('id', cmptOption, resultItem.existing))) {
|
|
nextIdx++;
|
|
}
|
|
if (resultItem) {
|
|
resultItem.newOption = cmptOption;
|
|
resultItem.brandNew = brandNew;
|
|
} else {
|
|
result.push({
|
|
newOption: cmptOption,
|
|
brandNew: brandNew,
|
|
existing: null,
|
|
keyInfo: null
|
|
});
|
|
}
|
|
nextIdx++;
|
|
});
|
|
}
|
|
function mappingInReplaceAllMode(result, newCmptOptions) {
|
|
each(newCmptOptions, function (cmptOption) {
|
|
// The feature "reproduce" requires "hole" will also reproduced
|
|
// in case that component index referring are broken.
|
|
result.push({
|
|
newOption: cmptOption,
|
|
brandNew: true,
|
|
existing: null,
|
|
keyInfo: null
|
|
});
|
|
});
|
|
}
|
|
/**
|
|
* Make id and name for mapping result (result of mappingToExists)
|
|
* into `keyInfo` field.
|
|
*/
|
|
function makeIdAndName(mapResult) {
|
|
// We use this id to hash component models and view instances
|
|
// in echarts. id can be specified by user, or auto generated.
|
|
// The id generation rule ensures new view instance are able
|
|
// to mapped to old instance when setOption are called in
|
|
// no-merge mode. So we generate model id by name and plus
|
|
// type in view id.
|
|
// name can be duplicated among components, which is convenient
|
|
// to specify multi components (like series) by one name.
|
|
// Ensure that each id is distinct.
|
|
var idMap = createHashMap();
|
|
each(mapResult, function (item) {
|
|
var existing = item.existing;
|
|
existing && idMap.set(existing.id, item);
|
|
});
|
|
each(mapResult, function (item) {
|
|
var opt = item.newOption;
|
|
// Force ensure id not duplicated.
|
|
assert(!opt || opt.id == null || !idMap.get(opt.id) || idMap.get(opt.id) === item, 'id duplicates: ' + (opt && opt.id));
|
|
opt && opt.id != null && idMap.set(opt.id, item);
|
|
!item.keyInfo && (item.keyInfo = {});
|
|
});
|
|
// Make name and id.
|
|
each(mapResult, function (item, index) {
|
|
var existing = item.existing;
|
|
var opt = item.newOption;
|
|
var keyInfo = item.keyInfo;
|
|
if (!isObject(opt)) {
|
|
return;
|
|
}
|
|
// Name can be overwritten. Consider case: axis.name = '20km'.
|
|
// But id generated by name will not be changed, which affect
|
|
// only in that case: setOption with 'not merge mode' and view
|
|
// instance will be recreated, which can be accepted.
|
|
keyInfo.name = opt.name != null ? makeComparableKey(opt.name) : existing ? existing.name
|
|
// Avoid that different series has the same name,
|
|
// because name may be used like in color pallet.
|
|
: DUMMY_COMPONENT_NAME_PREFIX + index;
|
|
if (existing) {
|
|
keyInfo.id = makeComparableKey(existing.id);
|
|
} else if (opt.id != null) {
|
|
keyInfo.id = makeComparableKey(opt.id);
|
|
} else {
|
|
// Consider this situatoin:
|
|
// optionA: [{name: 'a'}, {name: 'a'}, {..}]
|
|
// optionB [{..}, {name: 'a'}, {name: 'a'}]
|
|
// Series with the same name between optionA and optionB
|
|
// should be mapped.
|
|
var idNum = 0;
|
|
do {
|
|
keyInfo.id = '\0' + keyInfo.name + '\0' + idNum++;
|
|
} while (idMap.get(keyInfo.id));
|
|
}
|
|
idMap.set(keyInfo.id, item);
|
|
});
|
|
}
|
|
function keyExistAndEqual(attr, obj1, obj2) {
|
|
var key1 = convertOptionIdName(obj1[attr], null);
|
|
var key2 = convertOptionIdName(obj2[attr], null);
|
|
// See `MappingExistingItem`. `id` and `name` trade string equals to number.
|
|
return key1 != null && key2 != null && key1 === key2;
|
|
}
|
|
/**
|
|
* @return return null if not exist.
|
|
*/
|
|
function makeComparableKey(val) {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
if (val == null) {
|
|
throw new Error();
|
|
}
|
|
}
|
|
return convertOptionIdName(val, '');
|
|
}
|
|
export function convertOptionIdName(idOrName, defaultValue) {
|
|
if (idOrName == null) {
|
|
return defaultValue;
|
|
}
|
|
return isString(idOrName) ? idOrName : isNumber(idOrName) || isStringSafe(idOrName) ? idOrName + '' : defaultValue;
|
|
}
|
|
function warnInvalidateIdOrName(idOrName) {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
warn('`' + idOrName + '` is invalid id or name. Must be a string or number.');
|
|
}
|
|
}
|
|
function isValidIdOrName(idOrName) {
|
|
return isStringSafe(idOrName) || isNumeric(idOrName);
|
|
}
|
|
export function isNameSpecified(componentModel) {
|
|
var name = componentModel.name;
|
|
// Is specified when `indexOf` get -1 or > 0.
|
|
return !!(name && name.indexOf(DUMMY_COMPONENT_NAME_PREFIX));
|
|
}
|
|
/**
|
|
* @public
|
|
* @param {Object} cmptOption
|
|
* @return {boolean}
|
|
*/
|
|
export function isComponentIdInternal(cmptOption) {
|
|
return cmptOption && cmptOption.id != null && makeComparableKey(cmptOption.id).indexOf(INTERNAL_COMPONENT_ID_PREFIX) === 0;
|
|
}
|
|
export function makeInternalComponentId(idSuffix) {
|
|
return INTERNAL_COMPONENT_ID_PREFIX + idSuffix;
|
|
}
|
|
export function setComponentTypeToKeyInfo(mappingResult, mainType, componentModelCtor) {
|
|
// Set mainType and complete subType.
|
|
each(mappingResult, function (item) {
|
|
var newOption = item.newOption;
|
|
if (isObject(newOption)) {
|
|
item.keyInfo.mainType = mainType;
|
|
item.keyInfo.subType = determineSubType(mainType, newOption, item.existing, componentModelCtor);
|
|
}
|
|
});
|
|
}
|
|
function determineSubType(mainType, newCmptOption, existComponent, componentModelCtor) {
|
|
var subType = newCmptOption.type ? newCmptOption.type : existComponent ? existComponent.subType
|
|
// Use determineSubType only when there is no existComponent.
|
|
: componentModelCtor.determineSubType(mainType, newCmptOption);
|
|
// tooltip, markline, markpoint may always has no subType
|
|
return subType;
|
|
}
|
|
/**
|
|
* A helper for removing duplicate items between batchA and batchB,
|
|
* and in themselves, and categorize by series.
|
|
*
|
|
* @param batchA Like: [{seriesId: 2, dataIndex: [32, 4, 5]}, ...]
|
|
* @param batchB Like: [{seriesId: 2, dataIndex: [32, 4, 5]}, ...]
|
|
* @return result: [resultBatchA, resultBatchB]
|
|
*/
|
|
export function compressBatches(batchA, batchB) {
|
|
var mapA = {};
|
|
var mapB = {};
|
|
makeMap(batchA || [], mapA);
|
|
makeMap(batchB || [], mapB, mapA);
|
|
return [mapToArray(mapA), mapToArray(mapB)];
|
|
function makeMap(sourceBatch, map, otherMap) {
|
|
for (var i = 0, len = sourceBatch.length; i < len; i++) {
|
|
var seriesId = convertOptionIdName(sourceBatch[i].seriesId, null);
|
|
if (seriesId == null) {
|
|
return;
|
|
}
|
|
var dataIndices = normalizeToArray(sourceBatch[i].dataIndex);
|
|
var otherDataIndices = otherMap && otherMap[seriesId];
|
|
for (var j = 0, lenj = dataIndices.length; j < lenj; j++) {
|
|
var dataIndex = dataIndices[j];
|
|
if (otherDataIndices && otherDataIndices[dataIndex]) {
|
|
otherDataIndices[dataIndex] = null;
|
|
} else {
|
|
(map[seriesId] || (map[seriesId] = {}))[dataIndex] = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function mapToArray(map, isData) {
|
|
var result = [];
|
|
for (var i in map) {
|
|
if (map.hasOwnProperty(i) && map[i] != null) {
|
|
if (isData) {
|
|
result.push(+i);
|
|
} else {
|
|
var dataIndices = mapToArray(map[i], true);
|
|
dataIndices.length && result.push({
|
|
seriesId: i,
|
|
dataIndex: dataIndices
|
|
});
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
/**
|
|
* @param payload Contains dataIndex (means rawIndex) / dataIndexInside / name
|
|
* each of which can be Array or primary type.
|
|
* @return dataIndex If not found, return undefined/null.
|
|
*/
|
|
export function queryDataIndex(data, payload) {
|
|
if (payload.dataIndexInside != null) {
|
|
return payload.dataIndexInside;
|
|
} else if (payload.dataIndex != null) {
|
|
return isArray(payload.dataIndex) ? map(payload.dataIndex, function (value) {
|
|
return data.indexOfRawIndex(value);
|
|
}) : data.indexOfRawIndex(payload.dataIndex);
|
|
} else if (payload.name != null) {
|
|
return isArray(payload.name) ? map(payload.name, function (value) {
|
|
return data.indexOfName(value);
|
|
}) : data.indexOfName(payload.name);
|
|
}
|
|
}
|
|
/**
|
|
* Enable property storage to any host object.
|
|
* Notice: Serialization is not supported.
|
|
*
|
|
* For example:
|
|
* let inner = zrUitl.makeInner();
|
|
*
|
|
* function some1(hostObj) {
|
|
* inner(hostObj).someProperty = 1212;
|
|
* ...
|
|
* }
|
|
* function some2() {
|
|
* let fields = inner(this);
|
|
* fields.someProperty1 = 1212;
|
|
* fields.someProperty2 = 'xx';
|
|
* ...
|
|
* }
|
|
*
|
|
* @return {Function}
|
|
*/
|
|
export function makeInner() {
|
|
var key = '__ec_inner_' + innerUniqueIndex++;
|
|
return function (hostObj) {
|
|
return hostObj[key] || (hostObj[key] = {});
|
|
};
|
|
}
|
|
var innerUniqueIndex = getRandomIdBase();
|
|
/**
|
|
* The same behavior as `component.getReferringComponents`.
|
|
*/
|
|
export function parseFinder(ecModel, finderInput, opt) {
|
|
var _a = preParseFinder(finderInput, opt),
|
|
mainTypeSpecified = _a.mainTypeSpecified,
|
|
queryOptionMap = _a.queryOptionMap,
|
|
others = _a.others;
|
|
var result = others;
|
|
var defaultMainType = opt ? opt.defaultMainType : null;
|
|
if (!mainTypeSpecified && defaultMainType) {
|
|
queryOptionMap.set(defaultMainType, {});
|
|
}
|
|
queryOptionMap.each(function (queryOption, mainType) {
|
|
var queryResult = queryReferringComponents(ecModel, mainType, queryOption, {
|
|
useDefault: defaultMainType === mainType,
|
|
enableAll: opt && opt.enableAll != null ? opt.enableAll : true,
|
|
enableNone: opt && opt.enableNone != null ? opt.enableNone : true
|
|
});
|
|
result[mainType + 'Models'] = queryResult.models;
|
|
result[mainType + 'Model'] = queryResult.models[0];
|
|
});
|
|
return result;
|
|
}
|
|
export function preParseFinder(finderInput, opt) {
|
|
var finder;
|
|
if (isString(finderInput)) {
|
|
var obj = {};
|
|
obj[finderInput + 'Index'] = 0;
|
|
finder = obj;
|
|
} else {
|
|
finder = finderInput;
|
|
}
|
|
var queryOptionMap = createHashMap();
|
|
var others = {};
|
|
var mainTypeSpecified = false;
|
|
each(finder, function (value, key) {
|
|
// Exclude 'dataIndex' and other illgal keys.
|
|
if (key === 'dataIndex' || key === 'dataIndexInside') {
|
|
others[key] = value;
|
|
return;
|
|
}
|
|
var parsedKey = key.match(/^(\w+)(Index|Id|Name)$/) || [];
|
|
var mainType = parsedKey[1];
|
|
var queryType = (parsedKey[2] || '').toLowerCase();
|
|
if (!mainType || !queryType || opt && opt.includeMainTypes && indexOf(opt.includeMainTypes, mainType) < 0) {
|
|
return;
|
|
}
|
|
mainTypeSpecified = mainTypeSpecified || !!mainType;
|
|
var queryOption = queryOptionMap.get(mainType) || queryOptionMap.set(mainType, {});
|
|
queryOption[queryType] = value;
|
|
});
|
|
return {
|
|
mainTypeSpecified: mainTypeSpecified,
|
|
queryOptionMap: queryOptionMap,
|
|
others: others
|
|
};
|
|
}
|
|
export var SINGLE_REFERRING = {
|
|
useDefault: true,
|
|
enableAll: false,
|
|
enableNone: false
|
|
};
|
|
export var MULTIPLE_REFERRING = {
|
|
useDefault: false,
|
|
enableAll: true,
|
|
enableNone: true
|
|
};
|
|
export function queryReferringComponents(ecModel, mainType, userOption, opt) {
|
|
opt = opt || SINGLE_REFERRING;
|
|
var indexOption = userOption.index;
|
|
var idOption = userOption.id;
|
|
var nameOption = userOption.name;
|
|
var result = {
|
|
models: null,
|
|
specified: indexOption != null || idOption != null || nameOption != null
|
|
};
|
|
if (!result.specified) {
|
|
// Use the first as default if `useDefault`.
|
|
var firstCmpt = void 0;
|
|
result.models = opt.useDefault && (firstCmpt = ecModel.getComponent(mainType)) ? [firstCmpt] : [];
|
|
return result;
|
|
}
|
|
if (indexOption === 'none' || indexOption === false) {
|
|
assert(opt.enableNone, '`"none"` or `false` is not a valid value on index option.');
|
|
result.models = [];
|
|
return result;
|
|
}
|
|
// `queryComponents` will return all components if
|
|
// both all of index/id/name are null/undefined.
|
|
if (indexOption === 'all') {
|
|
assert(opt.enableAll, '`"all"` is not a valid value on index option.');
|
|
indexOption = idOption = nameOption = null;
|
|
}
|
|
result.models = ecModel.queryComponents({
|
|
mainType: mainType,
|
|
index: indexOption,
|
|
id: idOption,
|
|
name: nameOption
|
|
});
|
|
return result;
|
|
}
|
|
export function setAttribute(dom, key, value) {
|
|
dom.setAttribute ? dom.setAttribute(key, value) : dom[key] = value;
|
|
}
|
|
export function getAttribute(dom, key) {
|
|
return dom.getAttribute ? dom.getAttribute(key) : dom[key];
|
|
}
|
|
export function getTooltipRenderMode(renderModeOption) {
|
|
if (renderModeOption === 'auto') {
|
|
// Using html when `document` exists, use richText otherwise
|
|
return env.domSupported ? 'html' : 'richText';
|
|
} else {
|
|
return renderModeOption || 'html';
|
|
}
|
|
}
|
|
/**
|
|
* Group a list by key.
|
|
*/
|
|
export function groupData(array, getKey // return key
|
|
) {
|
|
var buckets = createHashMap();
|
|
var keys = [];
|
|
each(array, function (item) {
|
|
var key = getKey(item);
|
|
(buckets.get(key) || (keys.push(key), buckets.set(key, []))).push(item);
|
|
});
|
|
return {
|
|
keys: keys,
|
|
buckets: buckets
|
|
};
|
|
}
|
|
/**
|
|
* Interpolate raw values of a series with percent
|
|
*
|
|
* @param data data
|
|
* @param labelModel label model of the text element
|
|
* @param sourceValue start value. May be null/undefined when init.
|
|
* @param targetValue end value
|
|
* @param percent 0~1 percentage; 0 uses start value while 1 uses end value
|
|
* @return interpolated values
|
|
* If `sourceValue` and `targetValue` are `number`, return `number`.
|
|
* If `sourceValue` and `targetValue` are `string`, return `string`.
|
|
* If `sourceValue` and `targetValue` are `(string | number)[]`, return `(string | number)[]`.
|
|
* Other cases do not supported.
|
|
*/
|
|
export function interpolateRawValues(data, precision, sourceValue, targetValue, percent) {
|
|
var isAutoPrecision = precision == null || precision === 'auto';
|
|
if (targetValue == null) {
|
|
return targetValue;
|
|
}
|
|
if (isNumber(targetValue)) {
|
|
var value = interpolateNumber(sourceValue || 0, targetValue, percent);
|
|
return round(value, isAutoPrecision ? Math.max(getPrecision(sourceValue || 0), getPrecision(targetValue)) : precision);
|
|
} else if (isString(targetValue)) {
|
|
return percent < 1 ? sourceValue : targetValue;
|
|
} else {
|
|
var interpolated = [];
|
|
var leftArr = sourceValue;
|
|
var rightArr = targetValue;
|
|
var length_1 = Math.max(leftArr ? leftArr.length : 0, rightArr.length);
|
|
for (var i = 0; i < length_1; ++i) {
|
|
var info = data.getDimensionInfo(i);
|
|
// Don't interpolate ordinal dims
|
|
if (info && info.type === 'ordinal') {
|
|
// In init, there is no `sourceValue`, but should better not to get undefined result.
|
|
interpolated[i] = (percent < 1 && leftArr ? leftArr : rightArr)[i];
|
|
} else {
|
|
var leftVal = leftArr && leftArr[i] ? leftArr[i] : 0;
|
|
var rightVal = rightArr[i];
|
|
var value = interpolateNumber(leftVal, rightVal, percent);
|
|
interpolated[i] = round(value, isAutoPrecision ? Math.max(getPrecision(leftVal), getPrecision(rightVal)) : precision);
|
|
}
|
|
}
|
|
return interpolated;
|
|
}
|
|
} |