package.src.core.util.ts Maven / Gradle / Ivy
/* global: defineProperty */
import { Dictionary, ArrayLike, KeyOfDistributive } from './types';
import { GradientObject } from '../graphic/Gradient';
import { ImagePatternObject } from '../graphic/Pattern';
import { platformApi } from './platform';
// 用于处理merge时无法遍历Date等对象的问题
const BUILTIN_OBJECT: Record = reduce([
'Function',
'RegExp',
'Date',
'Error',
'CanvasGradient',
'CanvasPattern',
// For node-canvas
'Image',
'Canvas'
], (obj, val) => {
obj['[object ' + val + ']'] = true;
return obj;
}, {} as Record);
const TYPED_ARRAY: Record = reduce([
'Int8',
'Uint8',
'Uint8Clamped',
'Int16',
'Uint16',
'Int32',
'Uint32',
'Float32',
'Float64'
], (obj, val) => {
obj['[object ' + val + 'Array]'] = true;
return obj;
}, {} as Record);
const objToString = Object.prototype.toString;
const arrayProto = Array.prototype;
const nativeForEach = arrayProto.forEach;
const nativeFilter = arrayProto.filter;
const nativeSlice = arrayProto.slice;
const nativeMap = arrayProto.map;
// In case some env may redefine the global variable `Function`.
const ctorFunction = function () {}.constructor;
const protoFunction = ctorFunction ? ctorFunction.prototype : null;
const protoKey = '__proto__';
let idStart = 0x0907;
/**
* Generate unique id
*/
export function guid(): number {
return idStart++;
}
export function logError(...args: any[]) {
if (typeof console !== 'undefined') {
console.error.apply(console, args);
}
}
/**
* Those data types can be cloned:
* Plain object, Array, TypedArray, number, string, null, undefined.
* Those data types will be assigned using the original data:
* BUILTIN_OBJECT
* Instance of user defined class will be cloned to a plain object, without
* properties in prototype.
* Other data types is not supported (not sure what will happen).
*
* Caution: do not support clone Date, for performance consideration.
* (There might be a large number of date in `series.data`).
* So date should not be modified in and out of echarts.
*/
export function clone(source: T): T {
if (source == null || typeof source !== 'object') {
return source;
}
let result = source as any;
const typeStr = objToString.call(source);
if (typeStr === '[object Array]') {
if (!isPrimitive(source)) {
result = [] as any;
for (let i = 0, len = (source as any[]).length; i < len; i++) {
result[i] = clone((source as any[])[i]);
}
}
}
else if (TYPED_ARRAY[typeStr]) {
if (!isPrimitive(source)) {
/* eslint-disable-next-line */
const Ctor = source.constructor as typeof Float32Array;
if (Ctor.from) {
result = Ctor.from(source as Float32Array);
}
else {
result = new Ctor((source as Float32Array).length);
for (let i = 0, len = (source as Float32Array).length; i < len; i++) {
result[i] = (source as Float32Array)[i];
}
}
}
}
else if (!BUILTIN_OBJECT[typeStr] && !isPrimitive(source) && !isDom(source)) {
result = {} as any;
for (let key in source) {
// Check if key is __proto__ to avoid prototype pollution
if (source.hasOwnProperty(key) && key !== protoKey) {
result[key] = clone(source[key]);
}
}
}
return result;
}
export function merge<
T extends Dictionary,
S extends Dictionary
>(target: T, source: S, overwrite?: boolean): T & S;
export function merge<
T extends any,
S extends any
>(target: T, source: S, overwrite?: boolean): T | S;
export function merge(target: any, source: any, overwrite?: boolean): any {
// We should escapse that source is string
// and enter for ... in ...
if (!isObject(source) || !isObject(target)) {
return overwrite ? clone(source) : target;
}
for (let key in source) {
// Check if key is __proto__ to avoid prototype pollution
if (source.hasOwnProperty(key) && key !== protoKey) {
const targetProp = target[key];
const sourceProp = source[key];
if (isObject(sourceProp)
&& isObject(targetProp)
&& !isArray(sourceProp)
&& !isArray(targetProp)
&& !isDom(sourceProp)
&& !isDom(targetProp)
&& !isBuiltInObject(sourceProp)
&& !isBuiltInObject(targetProp)
&& !isPrimitive(sourceProp)
&& !isPrimitive(targetProp)
) {
// 如果需要递归覆盖,就递归调用merge
merge(targetProp, sourceProp, overwrite);
}
else if (overwrite || !(key in target)) {
// 否则只处理overwrite为true,或者在目标对象中没有此属性的情况
// NOTE,在 target[key] 不存在的时候也是直接覆盖
target[key] = clone(source[key]);
}
}
}
return target;
}
/**
* @param targetAndSources The first item is target, and the rests are source.
* @param overwrite
* @return Merged result
*/
export function mergeAll(targetAndSources: any[], overwrite?: boolean): any {
let result = targetAndSources[0];
for (let i = 1, len = targetAndSources.length; i < len; i++) {
result = merge(result, targetAndSources[i], overwrite);
}
return result;
}
export function extend<
T extends Dictionary,
S extends Dictionary
>(target: T, source: S): T & S {
// @ts-ignore
if (Object.assign) {
// @ts-ignore
Object.assign(target, source);
}
else {
for (let key in source) {
// Check if key is __proto__ to avoid prototype pollution
if (source.hasOwnProperty(key) && key !== protoKey) {
(target as S & T)[key] = (source as T & S)[key];
}
}
}
return target as T & S;
}
export function defaults<
T extends Dictionary,
S extends Dictionary
>(target: T, source: S, overlay?: boolean): T & S {
const keysArr = keys(source);
for (let i = 0, len = keysArr.length; i < len; i++) {
let key = keysArr[i];
if ((overlay ? source[key] != null : (target as T & S)[key] == null)) {
(target as S & T)[key] = (source as T & S)[key];
}
}
return target as T & S;
}
// Expose createCanvas in util for compatibility
export const createCanvas = platformApi.createCanvas;
/**
* 查询数组中元素的index
*/
export function indexOf(array: T[] | readonly T[] | ArrayLike, value: T): number {
if (array) {
if ((array as T[]).indexOf) {
return (array as T[]).indexOf(value);
}
for (let i = 0, len = array.length; i < len; i++) {
if (array[i] === value) {
return i;
}
}
}
return -1;
}
/**
* 构造类继承关系
*
* @param clazz 源类
* @param baseClazz 基类
*/
export function inherits(clazz: Function, baseClazz: Function) {
const clazzPrototype = clazz.prototype;
function F() {}
F.prototype = baseClazz.prototype;
clazz.prototype = new (F as any)();
for (let prop in clazzPrototype) {
if (clazzPrototype.hasOwnProperty(prop)) {
clazz.prototype[prop] = clazzPrototype[prop];
}
}
clazz.prototype.constructor = clazz;
(clazz as any).superClass = baseClazz;
}
export function mixin(target: T | Function, source: S | Function, override?: boolean) {
target = 'prototype' in target ? target.prototype : target;
source = 'prototype' in source ? source.prototype : source;
// If build target is ES6 class. prototype methods is not enumerable. Use getOwnPropertyNames instead
// TODO: Determine if source is ES6 class?
if (Object.getOwnPropertyNames) {
const keyList = Object.getOwnPropertyNames(source);
for (let i = 0; i < keyList.length; i++) {
const key = keyList[i];
if (key !== 'constructor') {
if ((override ? (source as any)[key] != null : (target as any)[key] == null)) {
(target as any)[key] = (source as any)[key];
}
}
}
}
else {
defaults(target, source, override);
}
}
/**
* Consider typed array.
* @param data
*/
export function isArrayLike(data: any): data is ArrayLike {
if (!data) {
return false;
}
if (typeof data === 'string') {
return false;
}
return typeof data.length === 'number';
}
/**
* 数组或对象遍历
*/
export function each | any[] | readonly any[] | ArrayLike, Context>(
arr: I,
cb: (
this: Context,
// Use unknown to avoid to infer to "any", which may disable typo check.
value: I extends (infer T)[] | readonly (infer T)[] | ArrayLike ? T
// Use Dictionary may cause infer fail when I is an interface.
// So here use a Record to infer type.
: I extends Dictionary ? I extends Record ? T : unknown : unknown,
index?: I extends any[] | readonly any[] | ArrayLike ? number : keyof I & string, // keyof Dictionary will return number | string
arr?: I
) => void,
context?: Context
) {
if (!(arr && cb)) {
return;
}
if ((arr as any).forEach && (arr as any).forEach === nativeForEach) {
(arr as any).forEach(cb, context);
}
else if (arr.length === +arr.length) {
for (let i = 0, len = arr.length; i < len; i++) {
// FIXME: should the elided item be travelled? like `[33,,55]`.
cb.call(context, (arr as any[])[i], i as any, arr);
}
}
else {
for (let key in arr) {
if (arr.hasOwnProperty(key)) {
cb.call(context, (arr as Dictionary)[key], key as any, arr);
}
}
}
}
/**
* Array mapping.
* @typeparam T Type in Array
* @typeparam R Type Returned
* @return Must be an array.
*/
export function map(
arr: readonly T[],
cb: (this: Context, val: T, index?: number, arr?: readonly T[]) => R,
context?: Context
): R[] {
// Take the same behavior with lodash when !arr and !cb,
// which might be some common sense.
if (!arr) {
return [];
}
if (!cb) {
return slice(arr) as unknown[] as R[];
}
if (arr.map && arr.map === nativeMap) {
return arr.map(cb, context);
}
else {
const result = [];
for (let i = 0, len = arr.length; i < len; i++) {
// FIXME: should the elided item be travelled, like `[33,,55]`.
result.push(cb.call(context, arr[i], i, arr));
}
return result;
}
}
export function reduce(
arr: readonly T[],
cb: (this: Context, previousValue: S, currentValue: T, currentIndex?: number, arr?: readonly T[]) => S,
memo?: S,
context?: Context
): S {
if (!(arr && cb)) {
return;
}
for (let i = 0, len = arr.length; i < len; i++) {
memo = cb.call(context, memo, arr[i], i, arr);
}
return memo;
}
/**
* Array filtering.
* @return Must be an array.
*/
export function filter(
arr: readonly T[],
cb: (this: Context, value: T, index: number, arr: readonly T[]) => boolean,
context?: Context
): T[] {
// Take the same behavior with lodash when !arr and !cb,
// which might be some common sense.
if (!arr) {
return [];
}
if (!cb) {
return slice(arr);
}
if (arr.filter && arr.filter === nativeFilter) {
return arr.filter(cb, context);
}
else {
const result = [];
for (let i = 0, len = arr.length; i < len; i++) {
// FIXME: should the elided items be travelled? like `[33,,55]`.
if (cb.call(context, arr[i], i, arr)) {
result.push(arr[i]);
}
}
return result;
}
}
/**
* 数组项查找
*/
export function find(
arr: readonly T[],
cb: (this: Context, value: T, index?: number, arr?: readonly T[]) => boolean,
context?: Context
): T {
if (!(arr && cb)) {
return;
}
for (let i = 0, len = arr.length; i < len; i++) {
if (cb.call(context, arr[i], i, arr)) {
return arr[i];
}
}
}
/**
* Get all object keys
*
* Will return an empty array if obj is null/undefined
*/
export function keys(obj: T): (KeyOfDistributive & string)[] {
if (!obj) {
return [];
}
// Return type should be `keyof T` but exclude `number`, becuase
// `Object.keys` only return string rather than `number | string`.
type TKeys = KeyOfDistributive & string;
if (Object.keys) {
return Object.keys(obj) as TKeys[];
}
let keyList: TKeys[] = [];
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
keyList.push(key as any);
}
}
return keyList;
}
// Remove this type in returned function. Or it will conflicts wicth callback with given context. Like Eventful.
// According to lib.es5.d.ts
/* eslint-disable max-len*/
export type Bind1 = F extends (this: Ctx, ...args: infer A) => infer R ? (...args: A) => R : unknown;
export type Bind2 = F extends (this: Ctx, a: T1, ...args: infer A) => infer R ? (...args: A) => R : unknown;
export type Bind3 = F extends (this: Ctx, a: T1, b: T2, ...args: infer A) => infer R ? (...args: A) => R : unknown;
export type Bind4 = F extends (this: Ctx, a: T1, b: T2, c: T3, ...args: infer A) => infer R ? (...args: A) => R : unknown;
export type Bind5 = F extends (this: Ctx, a: T1, b: T2, c: T3, d: T4, ...args: infer A) => infer R ? (...args: A) => R : unknown;
type BindFunc = (this: Ctx, ...arg: any[]) => any
interface FunctionBind {
, Ctx>(func: F, ctx: Ctx): Bind1
, Ctx, T1 extends Parameters[0]>(func: F, ctx: Ctx, a: T1): Bind2
, Ctx, T1 extends Parameters[0], T2 extends Parameters[1]>(func: F, ctx: Ctx, a: T1, b: T2): Bind3
, Ctx, T1 extends Parameters[0], T2 extends Parameters[1], T3 extends Parameters[2]>(func: F, ctx: Ctx, a: T1, b: T2, c: T3): Bind4
, Ctx, T1 extends Parameters[0], T2 extends Parameters[1], T3 extends Parameters[2], T4 extends Parameters[3]>(func: F, ctx: Ctx, a: T1, b: T2, c: T3, d: T4): Bind5
}
function bindPolyfill any>(
func: Fn, context: Ctx, ...args: any[]
): (...args: Parameters) => ReturnType {
return function (this: Ctx) {
return func.apply(context, args.concat(nativeSlice.call(arguments)));
};
}
export const bind: FunctionBind = (protoFunction && isFunction(protoFunction.bind))
? protoFunction.call.bind(protoFunction.bind)
: bindPolyfill;
export type Curry1 = F extends (a: T1, ...args: infer A) => infer R ? (...args: A) => R : unknown;
export type Curry2 = F extends (a: T1, b: T2, ...args: infer A) => infer R ? (...args: A) => R : unknown;
export type Curry3 = F extends (a: T1, b: T2, c: T3, ...args: infer A) => infer R ? (...args: A) => R : unknown;
export type Curry4 = F extends (a: T1, b: T2, c: T3, d: T4, ...args: infer A) => infer R ? (...args: A) => R : unknown;
type CurryFunc = (...arg: any[]) => any
function curry[0]>(func: F, a: T1): Curry1
function curry[0], T2 extends Parameters[1]>(func: F, a: T1, b: T2): Curry2
function curry[0], T2 extends Parameters[1], T3 extends Parameters[2]>(func: F, a: T1, b: T2, c: T3): Curry3
function curry[0], T2 extends Parameters[1], T3 extends Parameters[2], T4 extends Parameters[3]>(func: F, a: T1, b: T2, c: T3, d: T4): Curry4
function curry(func: Function, ...args: any[]): Function {
return function (this: any) {
return func.apply(this, args.concat(nativeSlice.call(arguments)));
};
}
export {curry};
/* eslint-enable max-len*/
export function isArray(value: any): value is any[] {
if (Array.isArray) {
return Array.isArray(value);
}
return objToString.call(value) === '[object Array]';
}
export function isFunction(value: any): value is Function {
return typeof value === 'function';
}
export function isString(value: any): value is string {
// Faster than `objToString.call` several times in chromium and webkit.
// And `new String()` is rarely used.
return typeof value === 'string';
}
export function isStringSafe(value: any): value is string {
return objToString.call(value) === '[object String]';
}
export function isNumber(value: any): value is number {
// Faster than `objToString.call` several times in chromium and webkit.
// And `new Number()` is rarely used.
return typeof value === 'number';
}
// Usage: `isObject(xxx)` or `isObject(SomeType)(xxx)`
// Generic T can be used to avoid "ts type gruards" casting the `value` from its original
// type `Object` implicitly so that loose its original type info in the subsequent code.
export function isObject(value: T): value is (object & T) {
// Avoid a V8 JIT bug in Chrome 19-20.
// See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
const type = typeof value;
return type === 'function' || (!!value && type === 'object');
}
export function isBuiltInObject(value: any): boolean {
return !!BUILTIN_OBJECT[objToString.call(value)];
}
export function isTypedArray(value: any): boolean {
return !!TYPED_ARRAY[objToString.call(value)];
}
export function isDom(value: any): value is HTMLElement {
return typeof value === 'object'
&& typeof value.nodeType === 'number'
&& typeof value.ownerDocument === 'object';
}
export function isGradientObject(value: any): value is GradientObject {
return (value as GradientObject).colorStops != null;
}
export function isImagePatternObject(value: any): value is ImagePatternObject {
return (value as ImagePatternObject).image != null;
}
export function isRegExp(value: unknown): value is RegExp {
return objToString.call(value) === '[object RegExp]';
}
/**
* Whether is exactly NaN. Notice isNaN('a') returns true.
*/
export function eqNaN(value: any): boolean {
/* eslint-disable-next-line no-self-compare */
return value !== value;
}
/**
* If value1 is not null, then return value1, otherwise judget rest of values.
* Low performance.
* @return Final value
*/
export function retrieve(...args: T[]): T {
for (let i = 0, len = args.length; i < len; i++) {
if (args[i] != null) {
return args[i];
}
}
}
export function retrieve2(value0: T, value1: R): T | R {
return value0 != null
? value0
: value1;
}
export function retrieve3(value0: T, value1: R, value2: W): T | R | W {
return value0 != null
? value0
: value1 != null
? value1
: value2;
}
type SliceParams = Parameters;
export function slice(arr: ArrayLike, ...args: SliceParams): T[] {
return nativeSlice.apply(arr, args as any[]);
}
/**
* Normalize css liked array configuration
* e.g.
* 3 => [3, 3, 3, 3]
* [4, 2] => [4, 2, 4, 2]
* [4, 3, 2] => [4, 3, 2, 3]
*/
export function normalizeCssArray(val: number | number[]) {
if (typeof (val) === 'number') {
return [val, val, val, val];
}
const len = val.length;
if (len === 2) {
// vertical | horizontal
return [val[0], val[1], val[0], val[1]];
}
else if (len === 3) {
// top | horizontal | bottom
return [val[0], val[1], val[2], val[1]];
}
return val;
}
export function assert(condition: any, message?: string) {
if (!condition) {
throw new Error(message);
}
}
/**
* @param str string to be trimmed
* @return trimmed string
*/
export function trim(str: string): string {
if (str == null) {
return null;
}
else if (typeof str.trim === 'function') {
return str.trim();
}
else {
return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
}
}
const primitiveKey = '__ec_primitive__';
/**
* Set an object as primitive to be ignored traversing children in clone or merge
*/
export function setAsPrimitive(obj: any) {
obj[primitiveKey] = true;
}
export function isPrimitive(obj: any): boolean {
return obj[primitiveKey];
}
interface MapInterface {
delete(key: KEY): boolean;
has(key: KEY): boolean;
get(key: KEY): T | undefined;
set(key: KEY, value: T): this;
keys(): KEY[];
forEach(callback: (value: T, key: KEY) => void): void;
}
class MapPolyfill implements MapInterface {
private data: Record = {} as Record;
delete(key: KEY): boolean {
const existed = this.has(key);
if (existed) {
delete this.data[key];
}
return existed;
}
has(key: KEY): boolean {
return this.data.hasOwnProperty(key);
}
get(key: KEY): T | undefined {
return this.data[key];
}
set(key: KEY, value: T): this {
this.data[key] = value;
return this;
}
keys(): KEY[] {
return keys(this.data);
}
forEach(callback: (value: T, key: KEY) => void): void {
// This is a potential performance bottleneck, see details in
// https://github.com/ecomfe/zrender/issues/965, however it is now
// less likely to occur as we default to native maps when possible.
const data = this.data;
for (const key in data) {
if (data.hasOwnProperty(key)) {
callback(data[key], key);
}
}
}
}
// We want to use native Map if it is available, but we do not want to polyfill the global scope
// in case users ship their own polyfills or patch the native map object in any way.
const isNativeMapSupported = typeof Map === 'function';
function maybeNativeMap(): MapInterface {
// Map may be a native class if we are running in an ES6 compatible environment.
// eslint-disable-next-line
return (isNativeMapSupported ? new Map() : new MapPolyfill()) as MapInterface;
}
/**
* @constructor
* @param {Object} obj
*/
export class HashMap {
data: MapInterface
constructor(obj?: HashMap | { [key in KEY]?: T } | KEY[]) {
const isArr = isArray(obj);
// Key should not be set on this, otherwise
// methods get/set/... may be overridden.
this.data = maybeNativeMap();
const thisMap = this;
(obj instanceof HashMap)
? obj.each(visit)
: (obj && each(obj, visit));
function visit(value: any, key: any) {
isArr ? thisMap.set(value, key) : thisMap.set(key, value);
}
}
// `hasKey` instead of `has` for potential misleading.
hasKey(key: KEY): boolean {
return this.data.has(key);
}
get(key: KEY): T {
return this.data.get(key);
}
set(key: KEY, value: T): T {
// Comparing with invocation chaining, `return value` is more commonly
// used in this case: `const someVal = map.set('a', genVal());`
this.data.set(key, value);
return value;
}
// Although util.each can be performed on this hashMap directly, user
// should not use the exposed keys, who are prefixed.
each(
cb: (this: Context, value?: T, key?: KEY) => void,
context?: Context
) {
this.data.forEach((value, key) => {
cb.call(context, value, key);
});
}
keys(): KEY[] {
const keys = this.data.keys();
return isNativeMapSupported
// Native map returns an iterator so we need to convert it to an array
? Array.from(keys)
: keys;
}
// Do not use this method if performance sensitive.
removeKey(key: KEY): void {
this.data.delete(key);
}
}
export function createHashMap(
obj?: HashMap | { [key in KEY]?: T } | KEY[]
) {
return new HashMap(obj);
}
export function concatArray(a: ArrayLike, b: ArrayLike): ArrayLike {
const newArray = new (a as any).constructor(a.length + b.length);
for (let i = 0; i < a.length; i++) {
newArray[i] = a[i];
}
const offset = a.length;
for (let i = 0; i < b.length; i++) {
newArray[i + offset] = b[i];
}
return newArray;
}
export function createObject(proto?: object, properties?: T): T {
// Performance of Object.create
// https://jsperf.com/style-strategy-proto-or-others
let obj: T;
if (Object.create) {
obj = Object.create(proto);
}
else {
const StyleCtor = function () {};
StyleCtor.prototype = proto;
obj = new (StyleCtor as any)();
}
if (properties) {
extend(obj, properties);
}
return obj;
}
export function disableUserSelect(dom: HTMLElement) {
const domStyle = dom.style;
domStyle.webkitUserSelect = 'none';
domStyle.userSelect = 'none';
// @ts-ignore
domStyle.webkitTapHighlightColor = 'rgba(0,0,0,0)';
(domStyle as any)['-webkit-touch-callout'] = 'none';
}
export function hasOwn(own: object, prop: string): boolean {
return own.hasOwnProperty(prop);
}
export function noop() {}
export const RADIAN_TO_DEGREE = 180 / Math.PI;
© 2015 - 2025 Weber Informatics LLC | Privacy Policy