;
var markupFragment;
if (isObject(result)) {
if (result.type) {
markupFragment = result;
} else {
if ("development" !== 'production') {
console.warn('The return type of `formatTooltip` is not supported: ' + makePrintable(result));
}
} // else {
// markupText = (result as TooltipFormatResultLegacyObject).html;
// markers = (result as TooltipFormatResultLegacyObject).markers;
// if (markersExisting) {
// markers = zrUtil.merge(markersExisting, markers);
// }
// }
} else {
markupText = result;
}
return {
markupText: markupText,
// markers: markers || markersExisting,
markupFragment: markupFragment
};
}
/**
* @param {Object} define
* @return See the return of `createTask`.
*/
function createTask(define) {
return new Task(define);
}
var Task =
/** @class */
function () {
function Task(define) {
define = define || {};
this._reset = define.reset;
this._plan = define.plan;
this._count = define.count;
this._onDirty = define.onDirty;
this._dirty = true;
}
/**
* @param step Specified step.
* @param skip Skip customer perform call.
* @param modBy Sampling window size.
* @param modDataCount Sampling count.
* @return whether unfinished.
*/
Task.prototype.perform = function (performArgs) {
var upTask = this._upstream;
var skip = performArgs && performArgs.skip; // TODO some refactor.
// Pull data. Must pull data each time, because context.data
// may be updated by Series.setData.
if (this._dirty && upTask) {
var context = this.context;
context.data = context.outputData = upTask.context.outputData;
}
if (this.__pipeline) {
this.__pipeline.currentTask = this;
}
var planResult;
if (this._plan && !skip) {
planResult = this._plan(this.context);
} // Support sharding by mod, which changes the render sequence and makes the rendered graphic
// elements uniformed distributed when progress, especially when moving or zooming.
var lastModBy = normalizeModBy(this._modBy);
var lastModDataCount = this._modDataCount || 0;
var modBy = normalizeModBy(performArgs && performArgs.modBy);
var modDataCount = performArgs && performArgs.modDataCount || 0;
if (lastModBy !== modBy || lastModDataCount !== modDataCount) {
planResult = 'reset';
}
function normalizeModBy(val) {
!(val >= 1) && (val = 1); // jshint ignore:line
return val;
}
var forceFirstProgress;
if (this._dirty || planResult === 'reset') {
this._dirty = false;
forceFirstProgress = this._doReset(skip);
}
this._modBy = modBy;
this._modDataCount = modDataCount;
var step = performArgs && performArgs.step;
if (upTask) {
if ("development" !== 'production') {
assert(upTask._outputDueEnd != null);
}
this._dueEnd = upTask._outputDueEnd;
} // DataTask or overallTask
else {
if ("development" !== 'production') {
assert(!this._progress || this._count);
}
this._dueEnd = this._count ? this._count(this.context) : Infinity;
} // Note: Stubs, that its host overall task let it has progress, has progress.
// If no progress, pass index from upstream to downstream each time plan called.
if (this._progress) {
var start = this._dueIndex;
var end = Math.min(step != null ? this._dueIndex + step : Infinity, this._dueEnd);
if (!skip && (forceFirstProgress || start < end)) {
var progress = this._progress;
if (isArray(progress)) {
for (var i = 0; i < progress.length; i++) {
this._doProgress(progress[i], start, end, modBy, modDataCount);
}
} else {
this._doProgress(progress, start, end, modBy, modDataCount);
}
}
this._dueIndex = end; // If no `outputDueEnd`, assume that output data and
// input data is the same, so use `dueIndex` as `outputDueEnd`.
var outputDueEnd = this._settedOutputEnd != null ? this._settedOutputEnd : end;
if ("development" !== 'production') {
// ??? Can not rollback.
assert(outputDueEnd >= this._outputDueEnd);
}
this._outputDueEnd = outputDueEnd;
} else {
// (1) Some overall task has no progress.
// (2) Stubs, that its host overall task do not let it has progress, has no progress.
// This should always be performed so it can be passed to downstream.
this._dueIndex = this._outputDueEnd = this._settedOutputEnd != null ? this._settedOutputEnd : this._dueEnd;
}
return this.unfinished();
};
Task.prototype.dirty = function () {
this._dirty = true;
this._onDirty && this._onDirty(this.context);
};
Task.prototype._doProgress = function (progress, start, end, modBy, modDataCount) {
iterator.reset(start, end, modBy, modDataCount);
this._callingProgress = progress;
this._callingProgress({
start: start,
end: end,
count: end - start,
next: iterator.next
}, this.context);
};
Task.prototype._doReset = function (skip) {
this._dueIndex = this._outputDueEnd = this._dueEnd = 0;
this._settedOutputEnd = null;
var progress;
var forceFirstProgress;
if (!skip && this._reset) {
progress = this._reset(this.context);
if (progress && progress.progress) {
forceFirstProgress = progress.forceFirstProgress;
progress = progress.progress;
} // To simplify no progress checking, array must has item.
if (isArray(progress) && !progress.length) {
progress = null;
}
}
this._progress = progress;
this._modBy = this._modDataCount = null;
var downstream = this._downstream;
downstream && downstream.dirty();
return forceFirstProgress;
};
Task.prototype.unfinished = function () {
return this._progress && this._dueIndex < this._dueEnd;
};
/**
* @param downTask The downstream task.
* @return The downstream task.
*/
Task.prototype.pipe = function (downTask) {
if ("development" !== 'production') {
assert(downTask && !downTask._disposed && downTask !== this);
} // If already downstream, do not dirty downTask.
if (this._downstream !== downTask || this._dirty) {
this._downstream = downTask;
downTask._upstream = this;
downTask.dirty();
}
};
Task.prototype.dispose = function () {
if (this._disposed) {
return;
}
this._upstream && (this._upstream._downstream = null);
this._downstream && (this._downstream._upstream = null);
this._dirty = false;
this._disposed = true;
};
Task.prototype.getUpstream = function () {
return this._upstream;
};
Task.prototype.getDownstream = function () {
return this._downstream;
};
Task.prototype.setOutputEnd = function (end) {
// This only happend in dataTask, dataZoom, map, currently.
// where dataZoom do not set end each time, but only set
// when reset. So we should record the setted end, in case
// that the stub of dataZoom perform again and earse the
// setted end by upstream.
this._outputDueEnd = this._settedOutputEnd = end;
};
return Task;
}();
var iterator = function () {
var end;
var current;
var modBy;
var modDataCount;
var winCount;
var it = {
reset: function (s, e, sStep, sCount) {
current = s;
end = e;
modBy = sStep;
modDataCount = sCount;
winCount = Math.ceil(modDataCount / modBy);
it.next = modBy > 1 && modDataCount > 0 ? modNext : sequentialNext;
}
};
return it;
function sequentialNext() {
return current < end ? current++ : null;
}
function modNext() {
var dataIndex = current % winCount * modBy + Math.ceil(current / winCount);
var result = current >= end ? null : dataIndex < modDataCount ? dataIndex // If modDataCount is smaller than data.count() (consider `appendData` case),
// Use normal linear rendering mode.
: current;
current++;
return result;
}
}(); ///////////////////////////////////////////////////////////
// For stream debug (Should be commented out after used!)
// @usage: printTask(this, 'begin');
// @usage: printTask(this, null, {someExtraProp});
// @usage: Use `__idxInPipeline` as conditional breakpiont.
//
// window.printTask = function (task: any, prefix: string, extra: { [key: string]: unknown }): void {
// window.ecTaskUID == null && (window.ecTaskUID = 0);
// task.uidDebug == null && (task.uidDebug = `task_${window.ecTaskUID++}`);
// task.agent && task.agent.uidDebug == null && (task.agent.uidDebug = `task_${window.ecTaskUID++}`);
// let props = [];
// if (task.__pipeline) {
// let val = `${task.__idxInPipeline}/${task.__pipeline.tail.__idxInPipeline} ${task.agent ? '(stub)' : ''}`;
// props.push({text: '__idxInPipeline/total', value: val});
// } else {
// let stubCount = 0;
// task.agentStubMap.each(() => stubCount++);
// props.push({text: 'idx', value: `overall (stubs: ${stubCount})`});
// }
// props.push({text: 'uid', value: task.uidDebug});
// if (task.__pipeline) {
// props.push({text: 'pipelineId', value: task.__pipeline.id});
// task.agent && props.push(
// {text: 'stubFor', value: task.agent.uidDebug}
// );
// }
// props.push(
// {text: 'dirty', value: task._dirty},
// {text: 'dueIndex', value: task._dueIndex},
// {text: 'dueEnd', value: task._dueEnd},
// {text: 'outputDueEnd', value: task._outputDueEnd}
// );
// if (extra) {
// Object.keys(extra).forEach(key => {
// props.push({text: key, value: extra[key]});
// });
// }
// let args = ['color: blue'];
// let msg = `%c[${prefix || 'T'}] %c` + props.map(item => (
// args.push('color: green', 'color: red'),
// `${item.text}: %c${item.value}`
// )).join('%c, ');
// console.log.apply(console, [msg].concat(args));
// // console.log(this);
// };
// window.printPipeline = function (task: any, prefix: string) {
// const pipeline = task.__pipeline;
// let currTask = pipeline.head;
// while (currTask) {
// window.printTask(currTask, prefix);
// currTask = currTask._downstream;
// }
// };
// window.showChain = function (chainHeadTask) {
// var chain = [];
// var task = chainHeadTask;
// while (task) {
// chain.push({
// task: task,
// up: task._upstream,
// down: task._downstream,
// idxInPipeline: task.__idxInPipeline
// });
// task = task._downstream;
// }
// return chain;
// };
// window.findTaskInChain = function (task, chainHeadTask) {
// let chain = window.showChain(chainHeadTask);
// let result = [];
// for (let i = 0; i < chain.length; i++) {
// let chainItem = chain[i];
// if (chainItem.task === task) {
// result.push(i);
// }
// }
// return result;
// };
// window.printChainAEachInChainB = function (chainHeadTaskA, chainHeadTaskB) {
// let chainA = window.showChain(chainHeadTaskA);
// for (let i = 0; i < chainA.length; i++) {
// console.log('chainAIdx:', i, 'inChainB:', window.findTaskInChain(chainA[i].task, chainHeadTaskB));
// }
// };
/**
* Convert raw the value in to inner value in List.
*
* [Performance sensitive]
*
* [Caution]: this is the key logic of user value parser.
* For backward compatibiliy, do not modify it until have to!
*/
function parseDataValue(value, // For high performance, do not omit the second param.
opt) {
// Performance sensitive.
var dimType = opt && opt.type;
if (dimType === 'ordinal') {
// If given value is a category string
var ordinalMeta = opt && opt.ordinalMeta;
return ordinalMeta ? ordinalMeta.parseAndCollect(value) : value;
}
if (dimType === 'time' // spead up when using timestamp
&& typeof value !== 'number' && value != null && value !== '-') {
value = +parseDate(value);
} // dimType defaults 'number'.
// If dimType is not ordinal and value is null or undefined or NaN or '-',
// parse to NaN.
// number-like string (like ' 123 ') can be converted to a number.
// where null/undefined or other string will be converted to NaN.
return value == null || value === '' ? NaN // If string (like '-'), using '+' parse to NaN
// If object, also parse to NaN
: +value;
}
var valueParserMap = createHashMap({
'number': function (val) {
// Do not use `numericToNumber` here. We have by defualt `numericToNumber`.
// Here the number parser can have loose rule:
// enable to cut suffix: "120px" => 120, "14%" => 14.
return parseFloat(val);
},
'time': function (val) {
// return timestamp.
return +parseDate(val);
},
'trim': function (val) {
return typeof val === 'string' ? trim(val) : val;
}
});
var SortOrderComparator =
/** @class */
function () {
/**
* @param order by defualt: 'asc'
* @param incomparable by defualt: Always on the tail.
* That is, if 'asc' => 'max', if 'desc' => 'min'
* See the definition of "incomparable" in [SORT_COMPARISON_RULE]
*/
function SortOrderComparator(order, incomparable) {
var isDesc = order === 'desc';
this._resultLT = isDesc ? 1 : -1;
if (incomparable == null) {
incomparable = isDesc ? 'min' : 'max';
}
this._incomparable = incomparable === 'min' ? -Infinity : Infinity;
} // See [SORT_COMPARISON_RULE].
// Performance sensitive.
SortOrderComparator.prototype.evaluate = function (lval, rval) {
// Most cases is 'number', and typeof maybe 10 times faseter than parseFloat.
var lvalTypeof = typeof lval;
var rvalTypeof = typeof rval;
var lvalFloat = lvalTypeof === 'number' ? lval : numericToNumber(lval);
var rvalFloat = rvalTypeof === 'number' ? rval : numericToNumber(rval);
var lvalNotNumeric = isNaN(lvalFloat);
var rvalNotNumeric = isNaN(rvalFloat);
if (lvalNotNumeric) {
lvalFloat = this._incomparable;
}
if (rvalNotNumeric) {
rvalFloat = this._incomparable;
}
if (lvalNotNumeric && rvalNotNumeric) {
var lvalIsStr = lvalTypeof === 'string';
var rvalIsStr = rvalTypeof === 'string';
if (lvalIsStr) {
lvalFloat = rvalIsStr ? lval : 0;
}
if (rvalIsStr) {
rvalFloat = lvalIsStr ? rval : 0;
}
}
return lvalFloat < rvalFloat ? this._resultLT : lvalFloat > rvalFloat ? -this._resultLT : 0;
};
return SortOrderComparator;
}();
/**
* TODO: disable writable.
* This structure will be exposed to users.
*/
var ExternalSource =
/** @class */
function () {
function ExternalSource() {}
ExternalSource.prototype.getRawData = function () {
// Only built-in transform available.
throw new Error('not supported');
};
ExternalSource.prototype.getRawDataItem = function (dataIndex) {
// Only built-in transform available.
throw new Error('not supported');
};
ExternalSource.prototype.cloneRawData = function () {
return;
};
/**
* @return If dimension not found, return null/undefined.
*/
ExternalSource.prototype.getDimensionInfo = function (dim) {
return;
};
/**
* dimensions defined if and only if either:
* (a) dataset.dimensions are declared.
* (b) dataset data include dimensions definitions in data (detected or via specified `sourceHeader`).
* If dimensions are defined, `dimensionInfoAll` is corresponding to
* the defined dimensions.
* Otherwise, `dimensionInfoAll` is determined by data columns.
* @return Always return an array (even empty array).
*/
ExternalSource.prototype.cloneAllDimensionInfo = function () {
return;
};
ExternalSource.prototype.count = function () {
return;
};
/**
* Only support by dimension index.
* No need to support by dimension name in transform function,
* becuase transform function is not case-specific, no need to use name literally.
*/
ExternalSource.prototype.retrieveValue = function (dataIndex, dimIndex) {
return;
};
ExternalSource.prototype.retrieveValueFromItem = function (dataItem, dimIndex) {
return;
};
ExternalSource.prototype.convertValue = function (rawVal, dimInfo) {
return parseDataValue(rawVal, dimInfo);
};
return ExternalSource;
}();
function createExternalSource(internalSource, externalTransform) {
var extSource = new ExternalSource();
var data = internalSource.data;
var sourceFormat = extSource.sourceFormat = internalSource.sourceFormat;
var sourceHeaderCount = internalSource.startIndex;
var errMsg = '';
if (internalSource.seriesLayoutBy !== SERIES_LAYOUT_BY_COLUMN) {
// For the logic simplicity in transformer, only 'culumn' is
// supported in data transform. Otherwise, the `dimensionsDefine`
// might be detected by 'row', which probably confuses users.
if ("development" !== 'production') {
errMsg = '`seriesLayoutBy` of upstream dataset can only be "column" in data transform.';
}
throwError(errMsg);
} // [MEMO]
// Create a new dimensions structure for exposing.
// Do not expose all dimension info to users directly.
// Becuase the dimension is probably auto detected from data and not might reliable.
// Should not lead the transformers to think that is relialbe and return it.
// See [DIMENSION_INHERIT_RULE] in `sourceManager.ts`.
var dimensions = [];
var dimsByName = {};
var dimsDef = internalSource.dimensionsDefine;
if (dimsDef) {
each(dimsDef, function (dimDef, idx) {
var name = dimDef.name;
var dimDefExt = {
index: idx,
name: name,
displayName: dimDef.displayName
};
dimensions.push(dimDefExt); // Users probably not sepcify dimension name. For simplicity, data transform
// do not generate dimension name.
if (name != null) {
// Dimension name should not be duplicated.
// For simplicity, data transform forbid name duplication, do not generate
// new name like module `completeDimensions.ts` did, but just tell users.
var errMsg_1 = '';
if (hasOwn(dimsByName, name)) {
if ("development" !== 'production') {
errMsg_1 = 'dimension name "' + name + '" duplicated.';
}
throwError(errMsg_1);
}
dimsByName[name] = dimDefExt;
}
});
} // If dimension definitions are not defined and can not be detected.
// e.g., pure data `[[11, 22], ...]`.
else {
for (var i = 0; i < internalSource.dimensionsDetectedCount || 0; i++) {
// Do not generete name or anything others. The consequence process in
// `transform` or `series` probably have there own name generation strategry.
dimensions.push({
index: i
});
}
} // Implement public methods:
var rawItemGetter = getRawSourceItemGetter(sourceFormat, SERIES_LAYOUT_BY_COLUMN);
if (externalTransform.__isBuiltIn) {
extSource.getRawDataItem = function (dataIndex) {
return rawItemGetter(data, sourceHeaderCount, dimensions, dataIndex);
};
extSource.getRawData = bind(getRawData, null, internalSource);
}
extSource.cloneRawData = bind(cloneRawData, null, internalSource);
var rawCounter = getRawSourceDataCounter(sourceFormat, SERIES_LAYOUT_BY_COLUMN);
extSource.count = bind(rawCounter, null, data, sourceHeaderCount, dimensions);
var rawValueGetter = getRawSourceValueGetter(sourceFormat);
extSource.retrieveValue = function (dataIndex, dimIndex) {
var rawItem = rawItemGetter(data, sourceHeaderCount, dimensions, dataIndex);
return retrieveValueFromItem(rawItem, dimIndex);
};
var retrieveValueFromItem = extSource.retrieveValueFromItem = function (dataItem, dimIndex) {
if (dataItem == null) {
return;
}
var dimDef = dimensions[dimIndex]; // When `dimIndex` is `null`, `rawValueGetter` return the whole item.
if (dimDef) {
return rawValueGetter(dataItem, dimIndex, dimDef.name);
}
};
extSource.getDimensionInfo = bind(getDimensionInfo, null, dimensions, dimsByName);
extSource.cloneAllDimensionInfo = bind(cloneAllDimensionInfo, null, dimensions);
return extSource;
}
function getRawData(upstream) {
var sourceFormat = upstream.sourceFormat;
if (!isSupportedSourceFormat(sourceFormat)) {
var errMsg = '';
if ("development" !== 'production') {
errMsg = '`getRawData` is not supported in source format ' + sourceFormat;
}
throwError(errMsg);
}
return upstream.data;
}
function cloneRawData(upstream) {
var sourceFormat = upstream.sourceFormat;
var data = upstream.data;
if (!isSupportedSourceFormat(sourceFormat)) {
var errMsg = '';
if ("development" !== 'production') {
errMsg = '`cloneRawData` is not supported in source format ' + sourceFormat;
}
throwError(errMsg);
}
if (sourceFormat === SOURCE_FORMAT_ARRAY_ROWS) {
var result = [];
for (var i = 0, len = data.length; i < len; i++) {
// Not strictly clone for performance
result.push(data[i].slice());
}
return result;
} else if (sourceFormat === SOURCE_FORMAT_OBJECT_ROWS) {
var result = [];
for (var i = 0, len = data.length; i < len; i++) {
// Not strictly clone for performance
result.push(extend({}, data[i]));
}
return result;
}
}
function getDimensionInfo(dimensions, dimsByName, dim) {
if (dim == null) {
return;
} // Keep the same logic as `List::getDimension` did.
if (typeof dim === 'number' // If being a number-like string but not being defined a dimension name.
|| !isNaN(dim) && !hasOwn(dimsByName, dim)) {
return dimensions[dim];
} else if (hasOwn(dimsByName, dim)) {
return dimsByName[dim];
}
}
function cloneAllDimensionInfo(dimensions) {
return clone(dimensions);
}
var externalTransformMap = createHashMap();
function registerExternalTransform(externalTransform) {
externalTransform = clone(externalTransform);
var type = externalTransform.type;
var errMsg = '';
if (!type) {
if ("development" !== 'production') {
errMsg = 'Must have a `type` when `registerTransform`.';
}
throwError(errMsg);
}
var typeParsed = type.split(':');
if (typeParsed.length !== 2) {
if ("development" !== 'production') {
errMsg = 'Name must include namespace like "ns:regression".';
}
throwError(errMsg);
} // Namespace 'echarts:xxx' is official namespace, where the transforms should
// be called directly via 'xxx' rather than 'echarts:xxx'.
var isBuiltIn = false;
if (typeParsed[0] === 'echarts') {
type = typeParsed[1];
isBuiltIn = true;
}
externalTransform.__isBuiltIn = isBuiltIn;
externalTransformMap.set(type, externalTransform);
}
function applyDataTransform(rawTransOption, sourceList, infoForPrint) {
var pipedTransOption = normalizeToArray(rawTransOption);
var pipeLen = pipedTransOption.length;
var errMsg = '';
if (!pipeLen) {
if ("development" !== 'production') {
errMsg = 'If `transform` declared, it should at least contain one transform.';
}
throwError(errMsg);
}
for (var i = 0, len = pipeLen; i < len; i++) {
var transOption = pipedTransOption[i];
sourceList = applySingleDataTransform(transOption, sourceList, infoForPrint, pipeLen === 1 ? null : i); // piped transform only support single input, except the fist one.
// piped transform only support single output, except the last one.
if (i !== len - 1) {
sourceList.length = Math.max(sourceList.length, 1);
}
}
return sourceList;
}
function applySingleDataTransform(transOption, upSourceList, infoForPrint, // If `pipeIndex` is null/undefined, no piped transform.
pipeIndex) {
var errMsg = '';
if (!upSourceList.length) {
if ("development" !== 'production') {
errMsg = 'Must have at least one upstream dataset.';
}
throwError(errMsg);
}
if (!isObject(transOption)) {
if ("development" !== 'production') {
errMsg = 'transform declaration must be an object rather than ' + typeof transOption + '.';
}
throwError(errMsg);
}
var transType = transOption.type;
var externalTransform = externalTransformMap.get(transType);
if (!externalTransform) {
if ("development" !== 'production') {
errMsg = 'Can not find transform on type "' + transType + '".';
}
throwError(errMsg);
} // Prepare source
var extUpSourceList = map(upSourceList, function (upSource) {
return createExternalSource(upSource, externalTransform);
});
var resultList = normalizeToArray(externalTransform.transform({
upstream: extUpSourceList[0],
upstreamList: extUpSourceList,
config: clone(transOption.config)
}));
if ("development" !== 'production') {
if (transOption.print) {
var printStrArr = map(resultList, function (extSource) {
var pipeIndexStr = pipeIndex != null ? ' === pipe index: ' + pipeIndex : '';
return ['=== dataset index: ' + infoForPrint.datasetIndex + pipeIndexStr + ' ===', '- transform result data:', makePrintable(extSource.data), '- transform result dimensions:', makePrintable(extSource.dimensions)].join('\n');
}).join('\n');
consoleLog(printStrArr);
}
}
return map(resultList, function (result, resultIndex) {
var errMsg = '';
if (!isObject(result)) {
if ("development" !== 'production') {
errMsg = 'A transform should not return some empty results.';
}
throwError(errMsg);
}
if (!result.data) {
if ("development" !== 'production') {
errMsg = 'Transform result data should be not be null or undefined';
}
throwError(errMsg);
}
var sourceFormat = detectSourceFormat(result.data);
if (!isSupportedSourceFormat(sourceFormat)) {
if ("development" !== 'production') {
errMsg = 'Transform result data should be array rows or object rows.';
}
throwError(errMsg);
}
var resultMetaRawOption;
var firstUpSource = upSourceList[0];
/**
* Intuitively, the end users known the content of the original `dataset.source`,
* calucating the transform result in mind.
* Suppose the original `dataset.source` is:
* ```js
* [
* ['product', '2012', '2013', '2014', '2015'],
* ['AAA', 41.1, 30.4, 65.1, 53.3],
* ['BBB', 86.5, 92.1, 85.7, 83.1],
* ['CCC', 24.1, 67.2, 79.5, 86.4]
* ]
* ```
* The dimension info have to be detected from the source data.
* Some of the transformers (like filter, sort) will follow the dimension info
* of upstream, while others use new dimensions (like aggregate).
* Transformer can output a field `dimensions` to define the its own output dimensions.
* We also allow transformers to ignore the output `dimensions` field, and
* inherit the upstream dimensions definition. It can reduce the burden of handling
* dimensions in transformers.
*
* See also [DIMENSION_INHERIT_RULE] in `sourceManager.ts`.
*/
if (firstUpSource && resultIndex === 0 // If transformer returns `dimensions`, it means that the transformer has different
// dimensions definitions. We do not inherit anything from upstream.
&& !result.dimensions) {
var startIndex = firstUpSource.startIndex; // We copy the header of upstream to the result becuase:
// (1) The returned data always does not contain header line and can not be used
// as dimension-detection. In this case we can not use "detected dimensions" of
// upstream directly, because it might be detected based on different `seriesLayoutBy`.
// (2) We should support that the series read the upstream source in `seriesLayoutBy: 'row'`.
// So the original detected header should be add to the result, otherwise they can not be read.
if (startIndex) {
result.data = firstUpSource.data.slice(0, startIndex).concat(result.data);
}
resultMetaRawOption = {
seriesLayoutBy: SERIES_LAYOUT_BY_COLUMN,
sourceHeader: startIndex,
dimensions: firstUpSource.metaRawOption.dimensions
};
} else {
resultMetaRawOption = {
seriesLayoutBy: SERIES_LAYOUT_BY_COLUMN,
sourceHeader: 0,
dimensions: result.dimensions
};
}
return createSource(result.data, resultMetaRawOption, null, null);
});
}
function isSupportedSourceFormat(sourceFormat) {
return sourceFormat === SOURCE_FORMAT_ARRAY_ROWS || sourceFormat === SOURCE_FORMAT_OBJECT_ROWS;
}
/**
* [REQUIREMENT_MEMO]:
* (0) `metaRawOption` means `dimensions`/`sourceHeader`/`seriesLayoutBy` in raw option.
* (1) Keep support the feature: `metaRawOption` can be specified both on `series` and
* `root-dataset`. Them on `series` has higher priority.
* (2) Do not support to set `metaRawOption` on a `non-root-dataset`, because it might
* confuse users: whether those props indicate how to visit the upstream source or visit
* the transform result source, and some transforms has nothing to do with these props,
* and some transforms might have multiple upstream.
* (3) Transforms should specify `metaRawOption` in each output, just like they can be
* declared in `root-dataset`.
* (4) At present only support visit source in `SERIES_LAYOUT_BY_COLUMN` in transforms.
* That is for reducing complexity in transfroms.
* PENDING: Whether to provide transposition transform?
*
* [IMPLEMENTAION_MEMO]:
* "sourceVisitConfig" are calculated from `metaRawOption` and `data`.
* They will not be calculated until `source` is about to be visited (to prevent from
* duplicate calcuation). `source` is visited only in series and input to transforms.
*
* [DIMENSION_INHERIT_RULE]:
* By default the dimensions are inherited from ancestors, unless a transform return
* a new dimensions definition.
* Consider the case:
* ```js
* dataset: [{
* source: [ ['Product', 'Sales', 'Prise'], ['Cookies', 321, 44.21], ...]
* }, {
* transform: { type: 'filter', ... }
* }]
* dataset: [{
* dimension: ['Product', 'Sales', 'Prise'],
* source: [ ['Cookies', 321, 44.21], ...]
* }, {
* transform: { type: 'filter', ... }
* }]
* ```
* The two types of option should have the same behavior after transform.
*
*
* [SCENARIO]:
* (1) Provide source data directly:
* ```js
* series: {
* encode: {...},
* dimensions: [...]
* seriesLayoutBy: 'row',
* data: [[...]]
* }
* ```
* (2) Series refer to dataset.
* ```js
* series: [{
* encode: {...}
* // Ignore datasetIndex means `datasetIndex: 0`
* // and the dimensions defination in dataset is used
* }, {
* encode: {...},
* seriesLayoutBy: 'column',
* datasetIndex: 1
* }]
* ```
* (3) dataset transform
* ```js
* dataset: [{
* source: [...]
* }, {
* source: [...]
* }, {
* // By default from 0.
* transform: { type: 'filter', config: {...} }
* }, {
* // Piped.
* transform: [
* { type: 'filter', config: {...} },
* { type: 'sort', config: {...} }
* ]
* }, {
* id: 'regressionData',
* fromDatasetIndex: 1,
* // Third-party transform
* transform: { type: 'ecStat:regression', config: {...} }
* }, {
* // retrieve the extra result.
* id: 'regressionFormula',
* fromDatasetId: 'regressionData',
* fromTransformResult: 1
* }]
* ```
*/
var SourceManager =
/** @class */
function () {
function SourceManager(sourceHost) {
// Cached source. Do not repeat calculating if not dirty.
this._sourceList = []; // version sign of each upstream source manager.
this._upstreamSignList = [];
this._versionSignBase = 0;
this._sourceHost = sourceHost;
}
/**
* Mark dirty.
*/
SourceManager.prototype.dirty = function () {
this._setLocalSource([], []);
};
SourceManager.prototype._setLocalSource = function (sourceList, upstreamSignList) {
this._sourceList = sourceList;
this._upstreamSignList = upstreamSignList;
this._versionSignBase++;
if (this._versionSignBase > 9e10) {
this._versionSignBase = 0;
}
};
/**
* For detecting whether the upstream source is dirty, so that
* the local cached source (in `_sourceList`) should be discarded.
*/
SourceManager.prototype._getVersionSign = function () {
return this._sourceHost.uid + '_' + this._versionSignBase;
};
/**
* Always return a source instance. Otherwise throw error.
*/
SourceManager.prototype.prepareSource = function () {
// For the case that call `setOption` multiple time but no data changed,
// cache the result source to prevent from repeating transform.
if (this._isDirty()) {
this._createSource();
}
};
SourceManager.prototype._createSource = function () {
this._setLocalSource([], []);
var sourceHost = this._sourceHost;
var upSourceMgrList = this._getUpstreamSourceManagers();
var hasUpstream = !!upSourceMgrList.length;
var resultSourceList;
var upstreamSignList;
if (isSeries(sourceHost)) {
var seriesModel = sourceHost;
var data = void 0;
var sourceFormat = void 0;
var upSource = void 0; // Has upstream dataset
if (hasUpstream) {
var upSourceMgr = upSourceMgrList[0];
upSourceMgr.prepareSource();
upSource = upSourceMgr.getSource();
data = upSource.data;
sourceFormat = upSource.sourceFormat;
upstreamSignList = [upSourceMgr._getVersionSign()];
} // Series data is from own.
else {
data = seriesModel.get('data', true);
sourceFormat = isTypedArray(data) ? SOURCE_FORMAT_TYPED_ARRAY : SOURCE_FORMAT_ORIGINAL;
upstreamSignList = [];
} // See [REQUIREMENT_MEMO], merge settings on series and parent dataset if it is root.
var newMetaRawOption = this._getSourceMetaRawOption();
var upMetaRawOption = upSource ? upSource.metaRawOption : null;
var seriesLayoutBy = retrieve2(newMetaRawOption.seriesLayoutBy, upMetaRawOption ? upMetaRawOption.seriesLayoutBy : null);
var sourceHeader = retrieve2(newMetaRawOption.sourceHeader, upMetaRawOption ? upMetaRawOption.sourceHeader : null); // Note here we should not use `upSource.dimensionsDefine`. Consider the case:
// `upSource.dimensionsDefine` is detected by `seriesLayoutBy: 'column'`,
// but series need `seriesLayoutBy: 'row'`.
var dimensions = retrieve2(newMetaRawOption.dimensions, upMetaRawOption ? upMetaRawOption.dimensions : null);
resultSourceList = [createSource(data, {
seriesLayoutBy: seriesLayoutBy,
sourceHeader: sourceHeader,
dimensions: dimensions
}, sourceFormat, seriesModel.get('encode', true))];
} else {
var datasetModel = sourceHost; // Has upstream dataset.
if (hasUpstream) {
var result = this._applyTransform(upSourceMgrList);
resultSourceList = result.sourceList;
upstreamSignList = result.upstreamSignList;
} // Is root dataset.
else {
var sourceData = datasetModel.get('source', true);
resultSourceList = [createSource(sourceData, this._getSourceMetaRawOption(), null, // Note: dataset option does not have `encode`.
null)];
upstreamSignList = [];
}
}
if ("development" !== 'production') {
assert(resultSourceList && upstreamSignList);
}
this._setLocalSource(resultSourceList, upstreamSignList);
};
SourceManager.prototype._applyTransform = function (upMgrList) {
var datasetModel = this._sourceHost;
var transformOption = datasetModel.get('transform', true);
var fromTransformResult = datasetModel.get('fromTransformResult', true);
if ("development" !== 'production') {
assert(fromTransformResult != null || transformOption != null);
}
if (fromTransformResult != null) {
var errMsg = '';
if (upMgrList.length !== 1) {
if ("development" !== 'production') {
errMsg = 'When using `fromTransformResult`, there should be only one upstream dataset';
}
doThrow(errMsg);
}
}
var sourceList;
var upSourceList = [];
var upstreamSignList = [];
each(upMgrList, function (upMgr) {
upMgr.prepareSource();
var upSource = upMgr.getSource(fromTransformResult || 0);
var errMsg = '';
if (fromTransformResult != null && !upSource) {
if ("development" !== 'production') {
errMsg = 'Can not retrieve result by `fromTransformResult`: ' + fromTransformResult;
}
doThrow(errMsg);
}
upSourceList.push(upSource);
upstreamSignList.push(upMgr._getVersionSign());
});
if (transformOption) {
sourceList = applyDataTransform(transformOption, upSourceList, {
datasetIndex: datasetModel.componentIndex
});
} else if (fromTransformResult != null) {
sourceList = [cloneSourceShallow(upSourceList[0])];
}
return {
sourceList: sourceList,
upstreamSignList: upstreamSignList
};
};
SourceManager.prototype._isDirty = function () {
var sourceList = this._sourceList;
if (!sourceList.length) {
return true;
} // All sourceList is from the some upsteam.
var upSourceMgrList = this._getUpstreamSourceManagers();
for (var i = 0; i < upSourceMgrList.length; i++) {
var upSrcMgr = upSourceMgrList[i];
if ( // Consider the case that there is ancestor diry, call it recursively.
// The performance is probably not an issue because usually the chain is not long.
upSrcMgr._isDirty() || this._upstreamSignList[i] !== upSrcMgr._getVersionSign()) {
return true;
}
}
};
/**
* @param sourceIndex By defualt 0, means "main source".
* Most cases there is only one source.
*/
SourceManager.prototype.getSource = function (sourceIndex) {
return this._sourceList[sourceIndex || 0];
};
/**
* PEDING: Is it fast enough?
* If no upstream, return empty array.
*/
SourceManager.prototype._getUpstreamSourceManagers = function () {
// Always get the relationship from the raw option.
// Do not cache the link of the dependency graph, so that
// no need to update them when change happen.
var sourceHost = this._sourceHost;
if (isSeries(sourceHost)) {
var datasetModel = querySeriesUpstreamDatasetModel(sourceHost);
return !datasetModel ? [] : [datasetModel.getSourceManager()];
} else {
return map(queryDatasetUpstreamDatasetModels(sourceHost), function (datasetModel) {
return datasetModel.getSourceManager();
});
}
};
SourceManager.prototype._getSourceMetaRawOption = function () {
var sourceHost = this._sourceHost;
var seriesLayoutBy;
var sourceHeader;
var dimensions;
if (isSeries(sourceHost)) {
seriesLayoutBy = sourceHost.get('seriesLayoutBy', true);
sourceHeader = sourceHost.get('sourceHeader', true);
dimensions = sourceHost.get('dimensions', true);
} // See [REQUIREMENT_MEMO], `non-root-dataset` do not support them.
else if (!this._getUpstreamSourceManagers().length) {
var model = sourceHost;
seriesLayoutBy = model.get('seriesLayoutBy', true);
sourceHeader = model.get('sourceHeader', true);
dimensions = model.get('dimensions', true);
}
return {
seriesLayoutBy: seriesLayoutBy,
sourceHeader: sourceHeader,
dimensions: dimensions
};
};
return SourceManager;
}();
// disable the transform merge, but do not disable transfrom clone from rawOption.
function disableTransformOptionMerge(datasetModel) {
var transformOption = datasetModel.option.transform;
transformOption && setAsPrimitive(datasetModel.option.transform);
}
function isSeries(sourceHost) {
// Avoid circular dependency with Series.ts
return sourceHost.mainType === 'series';
}
function doThrow(errMsg) {
throw new Error(errMsg);
}
var TOOLTIP_LINE_HEIGHT_CSS = 'line-height:1'; // TODO: more textStyle option
function getTooltipTextStyle(textStyle, renderMode) {
var nameFontColor = textStyle.color || '#6e7079';
var nameFontSize = textStyle.fontSize || 12;
var nameFontWeight = textStyle.fontWeight || '400';
var valueFontColor = textStyle.color || '#464646';
var valueFontSize = textStyle.fontSize || 14;
var valueFontWeight = textStyle.fontWeight || '900';
if (renderMode === 'html') {
// `textStyle` is probably from user input, should be encoded to reduce security risk.
return {
// eslint-disable-next-line max-len
nameStyle: "font-size:" + encodeHTML(nameFontSize + '') + "px;color:" + encodeHTML(nameFontColor) + ";font-weight:" + encodeHTML(nameFontWeight + ''),
// eslint-disable-next-line max-len
valueStyle: "font-size:" + encodeHTML(valueFontSize + '') + "px;color:" + encodeHTML(valueFontColor) + ";font-weight:" + encodeHTML(valueFontWeight + '')
};
} else {
return {
nameStyle: {
fontSize: nameFontSize,
fill: nameFontColor,
fontWeight: nameFontWeight
},
valueStyle: {
fontSize: valueFontSize,
fill: valueFontColor,
fontWeight: valueFontWeight
}
};
}
} // See `TooltipMarkupLayoutIntent['innerGapLevel']`.
// (value from UI design)
var HTML_GAPS = [0, 10, 20, 30];
var RICH_TEXT_GAPS = ['', '\n', '\n\n', '\n\n\n']; // eslint-disable-next-line max-len
function createTooltipMarkup(type, option) {
option.type = type;
return option;
}
function getBuilder(fragment) {
return hasOwn(builderMap, fragment.type) && builderMap[fragment.type];
}
var builderMap = {
/**
* A `section` block is like:
* ```
* header
* subBlock
* subBlock
* ...
* ```
*/
section: {
planLayout: function (fragment) {
var subBlockLen = fragment.blocks.length;
var thisBlockHasInnerGap = subBlockLen > 1 || subBlockLen > 0 && !fragment.noHeader;
var thisGapLevelBetweenSubBlocks = 0;
each(fragment.blocks, function (subBlock) {
getBuilder(subBlock).planLayout(subBlock);
var subGapLevel = subBlock.__gapLevelBetweenSubBlocks; // If the some of the sub-blocks have some gaps (like 10px) inside, this block
// should use a larger gap (like 20px) to distinguish those sub-blocks.
if (subGapLevel >= thisGapLevelBetweenSubBlocks) {
thisGapLevelBetweenSubBlocks = subGapLevel + (thisBlockHasInnerGap && ( // 0 always can not be readable gap level.
!subGapLevel // If no header, always keep the sub gap level. Otherwise
// look weird in case `multipleSeries`.
|| subBlock.type === 'section' && !subBlock.noHeader) ? 1 : 0);
}
});
fragment.__gapLevelBetweenSubBlocks = thisGapLevelBetweenSubBlocks;
},
build: function (ctx, fragment, topMarginForOuterGap, toolTipTextStyle) {
var noHeader = fragment.noHeader;
var gaps = getGap(fragment);
var subMarkupText = buildSubBlocks(ctx, fragment, noHeader ? topMarginForOuterGap : gaps.html, toolTipTextStyle);
if (noHeader) {
return subMarkupText;
}
var displayableHeader = makeValueReadable(fragment.header, 'ordinal', ctx.useUTC);
var nameStyle = getTooltipTextStyle(toolTipTextStyle, ctx.renderMode).nameStyle;
if (ctx.renderMode === 'richText') {
return wrapInlineNameRichText(ctx, displayableHeader, nameStyle) + gaps.richText + subMarkupText;
} else {
return wrapBlockHTML("" + encodeHTML(displayableHeader) + '
' + subMarkupText, topMarginForOuterGap);
}
}
},
/**
* A `nameValue` block is like:
* ```
* marker name value
* ```
*/
nameValue: {
planLayout: function (fragment) {
fragment.__gapLevelBetweenSubBlocks = 0;
},
build: function (ctx, fragment, topMarginForOuterGap, toolTipTextStyle) {
var renderMode = ctx.renderMode;
var noName = fragment.noName;
var noValue = fragment.noValue;
var noMarker = !fragment.markerType;
var name = fragment.name;
var value = fragment.value;
var useUTC = ctx.useUTC;
if (noName && noValue) {
return;
}
var markerStr = noMarker ? '' : ctx.markupStyleCreator.makeTooltipMarker(fragment.markerType, fragment.markerColor || '#333', renderMode);
var readableName = noName ? '' : makeValueReadable(name, 'ordinal', useUTC);
var valueTypeOption = fragment.valueType;
var readableValueList = noValue ? [] : isArray(value) ? map(value, function (val, idx) {
return makeValueReadable(val, isArray(valueTypeOption) ? valueTypeOption[idx] : valueTypeOption, useUTC);
}) : [makeValueReadable(value, isArray(valueTypeOption) ? valueTypeOption[0] : valueTypeOption, useUTC)];
var valueAlignRight = !noMarker || !noName; // It little weird if only value next to marker but far from marker.
var valueCloseToMarker = !noMarker && noName;
var _a = getTooltipTextStyle(toolTipTextStyle, renderMode),
nameStyle = _a.nameStyle,
valueStyle = _a.valueStyle;
return renderMode === 'richText' ? (noMarker ? '' : markerStr) + (noName ? '' : wrapInlineNameRichText(ctx, readableName, nameStyle)) // Value has commas inside, so use ' ' as delimiter for multiple values.
+ (noValue ? '' : wrapInlineValueRichText(ctx, readableValueList, valueAlignRight, valueCloseToMarker, valueStyle)) : wrapBlockHTML((noMarker ? '' : markerStr) + (noName ? '' : wrapInlineNameHTML(readableName, !noMarker, nameStyle)) + (noValue ? '' : wrapInlineValueHTML(readableValueList, valueAlignRight, valueCloseToMarker, valueStyle)), topMarginForOuterGap);
}
}
};
function buildSubBlocks(ctx, fragment, topMarginForOuterGap, tooltipTextStyle) {
var subMarkupTextList = [];
var subBlocks = fragment.blocks || [];
assert(!subBlocks || isArray(subBlocks));
subBlocks = subBlocks || [];
var orderMode = ctx.orderMode;
if (fragment.sortBlocks && orderMode) {
subBlocks = subBlocks.slice();
var orderMap = {
valueAsc: 'asc',
valueDesc: 'desc'
};
if (hasOwn(orderMap, orderMode)) {
var comparator_1 = new SortOrderComparator(orderMap[orderMode], null);
subBlocks.sort(function (a, b) {
return comparator_1.evaluate(a.sortParam, b.sortParam);
});
} // FIXME 'seriesDesc' necessary?
else if (orderMode === 'seriesDesc') {
subBlocks.reverse();
}
}
var gaps = getGap(fragment);
each(subBlocks, function (subBlock, idx) {
var subMarkupText = getBuilder(subBlock).build(ctx, subBlock, idx > 0 ? gaps.html : 0, tooltipTextStyle);
subMarkupText != null && subMarkupTextList.push(subMarkupText);
});
if (!subMarkupTextList.length) {
return;
}
return ctx.renderMode === 'richText' ? subMarkupTextList.join(gaps.richText) : wrapBlockHTML(subMarkupTextList.join(''), topMarginForOuterGap);
}
/**
* @return markupText. null/undefined means no content.
*/
function buildTooltipMarkup(fragment, markupStyleCreator, renderMode, orderMode, useUTC, toolTipTextStyle) {
if (!fragment) {
return;
}
var builder = getBuilder(fragment);
builder.planLayout(fragment);
var ctx = {
useUTC: useUTC,
renderMode: renderMode,
orderMode: orderMode,
markupStyleCreator: markupStyleCreator
};
return builder.build(ctx, fragment, 0, toolTipTextStyle);
}
function getGap(fragment) {
var gapLevelBetweenSubBlocks = fragment.__gapLevelBetweenSubBlocks;
return {
html: HTML_GAPS[gapLevelBetweenSubBlocks],
richText: RICH_TEXT_GAPS[gapLevelBetweenSubBlocks]
};
}
function wrapBlockHTML(encodedContent, topGap) {
var clearfix = '';
var marginCSS = "margin: " + topGap + "px 0 0";
return "" + encodedContent + clearfix + '
';
}
function wrapInlineNameHTML(name, leftHasMarker, style) {
var marginCss = leftHasMarker ? 'margin-left:2px' : '';
return "" + encodeHTML(name) + '';
}
function wrapInlineValueHTML(valueList, alignRight, valueCloseToMarker, style) {
// Do not too close to marker, considering there are multiple values separated by spaces.
var paddingStr = valueCloseToMarker ? '10px' : '20px';
var alignCSS = alignRight ? "float:right;margin-left:" + paddingStr : '';
return "" // Value has commas inside, so use ' ' as delimiter for multiple values.
+ map(valueList, function (value) {
return encodeHTML(value);
}).join(' ') + '';
}
function wrapInlineNameRichText(ctx, name, style) {
return ctx.markupStyleCreator.wrapRichTextStyle(name, style);
}
function wrapInlineValueRichText(ctx, valueList, alignRight, valueCloseToMarker, style) {
var styles = [style];
var paddingLeft = valueCloseToMarker ? 10 : 20;
alignRight && styles.push({
padding: [0, 0, 0, paddingLeft],
align: 'right'
}); // Value has commas inside, so use ' ' as delimiter for multiple values.
return ctx.markupStyleCreator.wrapRichTextStyle(valueList.join(' '), styles);
}
function retrieveVisualColorForTooltipMarker(series, dataIndex) {
var style = series.getData().getItemVisual(dataIndex, 'style');
var color = style[series.visualDrawType];
return convertToColorString(color);
}
function getPaddingFromTooltipModel(model, renderMode) {
var padding = model.get('padding');
return padding != null ? padding // We give slightly different to look pretty.
: renderMode === 'richText' ? [8, 10] : 10;
}
/**
* The major feature is generate styles for `renderMode: 'richText'`.
* But it also serves `renderMode: 'html'` to provide
* "renderMode-independent" API.
*/
var TooltipMarkupStyleCreator =
/** @class */
function () {
function TooltipMarkupStyleCreator() {
this.richTextStyles = {}; // Notice that "generate a style name" usuall happens repeatly when mouse moving and
// displaying a tooltip. So we put the `_nextStyleNameId` as a member of each creator
// rather than static shared by all creators (which will cause it increase to fast).
this._nextStyleNameId = getRandomIdBase();
}
TooltipMarkupStyleCreator.prototype._generateStyleName = function () {
return '__EC_aUTo_' + this._nextStyleNameId++;
};
TooltipMarkupStyleCreator.prototype.makeTooltipMarker = function (markerType, colorStr, renderMode) {
var markerId = renderMode === 'richText' ? this._generateStyleName() : null;
var marker = getTooltipMarker({
color: colorStr,
type: markerType,
renderMode: renderMode,
markerId: markerId
});
if (isString(marker)) {
return marker;
} else {
if ("development" !== 'production') {
assert(markerId);
}
this.richTextStyles[markerId] = marker.style;
return marker.content;
}
};
/**
* @usage
* ```ts
* const styledText = markupStyleCreator.wrapRichTextStyle([
* // The styles will be auto merged.
* {
* fontSize: 12,
* color: 'blue'
* },
* {
* padding: 20
* }
* ]);
* ```
*/
TooltipMarkupStyleCreator.prototype.wrapRichTextStyle = function (text, styles) {
var finalStl = {};
if (isArray(styles)) {
each(styles, function (stl) {
return extend(finalStl, stl);
});
} else {
extend(finalStl, styles);
}
var styleName = this._generateStyleName();
this.richTextStyles[styleName] = finalStl;
return "{" + styleName + "|" + text + "}";
};
return TooltipMarkupStyleCreator;
}();
function defaultSeriesFormatTooltip(opt) {
var series = opt.series;
var dataIndex = opt.dataIndex;
var multipleSeries = opt.multipleSeries;
var data = series.getData();
var tooltipDims = data.mapDimensionsAll('defaultedTooltip');
var tooltipDimLen = tooltipDims.length;
var value = series.getRawValue(dataIndex);
var isValueArr = isArray(value);
var markerColor = retrieveVisualColorForTooltipMarker(series, dataIndex); // Complicated rule for pretty tooltip.
var inlineValue;
var inlineValueType;
var subBlocks;
var sortParam;
if (tooltipDimLen > 1 || isValueArr && !tooltipDimLen) {
var formatArrResult = formatTooltipArrayValue(value, series, dataIndex, tooltipDims, markerColor);
inlineValue = formatArrResult.inlineValues;
inlineValueType = formatArrResult.inlineValueTypes;
subBlocks = formatArrResult.blocks; // Only support tooltip sort by the first inline value. It's enough in most cases.
sortParam = formatArrResult.inlineValues[0];
} else if (tooltipDimLen) {
var dimInfo = data.getDimensionInfo(tooltipDims[0]);
sortParam = inlineValue = retrieveRawValue(data, dataIndex, tooltipDims[0]);
inlineValueType = dimInfo.type;
} else {
sortParam = inlineValue = isValueArr ? value[0] : value;
} // Do not show generated series name. It might not be readable.
var seriesNameSpecified = isNameSpecified(series);
var seriesName = seriesNameSpecified && series.name || '';
var itemName = data.getName(dataIndex);
var inlineName = multipleSeries ? seriesName : itemName;
return createTooltipMarkup('section', {
header: seriesName,
// When series name not specified, do not show a header line with only '-'.
// This case alway happen in tooltip.trigger: 'item'.
noHeader: multipleSeries || !seriesNameSpecified,
sortParam: sortParam,
blocks: [createTooltipMarkup('nameValue', {
markerType: 'item',
markerColor: markerColor,
// Do not mix display seriesName and itemName in one tooltip,
// which might confuses users.
name: inlineName,
// name dimension might be auto assigned, where the name might
// be not readable. So we check trim here.
noName: !trim(inlineName),
value: inlineValue,
valueType: inlineValueType
})].concat(subBlocks || [])
});
}
function formatTooltipArrayValue(value, series, dataIndex, tooltipDims, colorStr) {
// check: category-no-encode-has-axis-data in dataset.html
var data = series.getData();
var isValueMultipleLine = reduce(value, function (isValueMultipleLine, val, idx) {
var dimItem = data.getDimensionInfo(idx);
return isValueMultipleLine = isValueMultipleLine || dimItem && dimItem.tooltip !== false && dimItem.displayName != null;
}, false);
var inlineValues = [];
var inlineValueTypes = [];
var blocks = [];
tooltipDims.length ? each(tooltipDims, function (dim) {
setEachItem(retrieveRawValue(data, dataIndex, dim), dim);
}) // By default, all dims is used on tooltip.
: each(value, setEachItem);
function setEachItem(val, dim) {
var dimInfo = data.getDimensionInfo(dim); // If `dimInfo.tooltip` is not set, show tooltip.
if (!dimInfo || dimInfo.otherDims.tooltip === false) {
return;
}
if (isValueMultipleLine) {
blocks.push(createTooltipMarkup('nameValue', {
markerType: 'subItem',
markerColor: colorStr,
name: dimInfo.displayName,
value: val,
valueType: dimInfo.type
}));
} else {
inlineValues.push(val);
inlineValueTypes.push(dimInfo.type);
}
}
return {
inlineValues: inlineValues,
inlineValueTypes: inlineValueTypes,
blocks: blocks
};
}
var inner$1 = makeInner();
function getSelectionKey(data, dataIndex) {
return data.getName(dataIndex) || data.getId(dataIndex);
}
var SeriesModel =
/** @class */
function (_super) {
__extends(SeriesModel, _super);
function SeriesModel() {
// [Caution]: Becuase this class or desecendants can be used as `XXX.extend(subProto)`,
// the class members must not be initialized in constructor or declaration place.
// Otherwise there is bad case:
// class A {xxx = 1;}
// enableClassExtend(A);
// class B extends A {}
// var C = B.extend({xxx: 5});
// var c = new C();
// console.log(c.xxx); // expect 5 but always 1.
var _this = _super !== null && _super.apply(this, arguments) || this; // ---------------------------------------
// Props about data selection
// ---------------------------------------
_this._selectedDataIndicesMap = {};
return _this;
}
SeriesModel.prototype.init = function (option, parentModel, ecModel) {
this.seriesIndex = this.componentIndex;
this.dataTask = createTask({
count: dataTaskCount,
reset: dataTaskReset
});
this.dataTask.context = {
model: this
};
this.mergeDefaultAndTheme(option, ecModel);
var sourceManager = inner$1(this).sourceManager = new SourceManager(this);
sourceManager.prepareSource();
var data = this.getInitialData(option, ecModel);
wrapData(data, this);
this.dataTask.context.data = data;
if ("development" !== 'production') {
assert(data, 'getInitialData returned invalid data.');
}
inner$1(this).dataBeforeProcessed = data; // If we reverse the order (make data firstly, and then make
// dataBeforeProcessed by cloneShallow), cloneShallow will
// cause data.graph.data !== data when using
// module:echarts/data/Graph or module:echarts/data/Tree.
// See module:echarts/data/helper/linkList
// Theoretically, it is unreasonable to call `seriesModel.getData()` in the model
// init or merge stage, because the data can be restored. So we do not `restoreData`
// and `setData` here, which forbids calling `seriesModel.getData()` in this stage.
// Call `seriesModel.getRawData()` instead.
// this.restoreData();
autoSeriesName(this);
this._initSelectedMapFromData(data);
};
/**
* Util for merge default and theme to option
*/
SeriesModel.prototype.mergeDefaultAndTheme = function (option, ecModel) {
var layoutMode = fetchLayoutMode(this);
var inputPositionParams = layoutMode ? getLayoutParams(option) : {}; // Backward compat: using subType on theme.
// But if name duplicate between series subType
// (for example: parallel) add component mainType,
// add suffix 'Series'.
var themeSubType = this.subType;
if (ComponentModel.hasClass(themeSubType)) {
themeSubType += 'Series';
}
merge(option, ecModel.getTheme().get(this.subType));
merge(option, this.getDefaultOption()); // Default label emphasis `show`
defaultEmphasis(option, 'label', ['show']);
this.fillDataTextStyle(option.data);
if (layoutMode) {
mergeLayoutParam(option, inputPositionParams, layoutMode);
}
};
SeriesModel.prototype.mergeOption = function (newSeriesOption, ecModel) {
// this.settingTask.dirty();
newSeriesOption = merge(this.option, newSeriesOption, true);
this.fillDataTextStyle(newSeriesOption.data);
var layoutMode = fetchLayoutMode(this);
if (layoutMode) {
mergeLayoutParam(this.option, newSeriesOption, layoutMode);
}
var sourceManager = inner$1(this).sourceManager;
sourceManager.dirty();
sourceManager.prepareSource();
var data = this.getInitialData(newSeriesOption, ecModel);
wrapData(data, this);
this.dataTask.dirty();
this.dataTask.context.data = data;
inner$1(this).dataBeforeProcessed = data;
autoSeriesName(this);
this._initSelectedMapFromData(data);
};
SeriesModel.prototype.fillDataTextStyle = function (data) {
// Default data label emphasis `show`
// FIXME Tree structure data ?
// FIXME Performance ?
if (data && !isTypedArray(data)) {
var props = ['show'];
for (var i = 0; i < data.length; i++) {
if (data[i] && data[i].label) {
defaultEmphasis(data[i], 'label', props);
}
}
}
};
/**
* Init a data structure from data related option in series
* Must be overriden.
*/
SeriesModel.prototype.getInitialData = function (option, ecModel) {
return;
};
/**
* Append data to list
*/
SeriesModel.prototype.appendData = function (params) {
// FIXME ???
// (1) If data from dataset, forbidden append.
// (2) support append data of dataset.
var data = this.getRawData();
data.appendData(params.data);
};
/**
* Consider some method like `filter`, `map` need make new data,
* We should make sure that `seriesModel.getData()` get correct
* data in the stream procedure. So we fetch data from upstream
* each time `task.perform` called.
*/
SeriesModel.prototype.getData = function (dataType) {
var task = getCurrentTask(this);
if (task) {
var data = task.context.data;
return dataType == null ? data : data.getLinkedData(dataType);
} else {
// When series is not alive (that may happen when click toolbox
// restore or setOption with not merge mode), series data may
// be still need to judge animation or something when graphic
// elements want to know whether fade out.
return inner$1(this).data;
}
};
SeriesModel.prototype.getAllData = function () {
var mainData = this.getData();
return mainData && mainData.getLinkedDataAll ? mainData.getLinkedDataAll() : [{
data: mainData
}];
};
SeriesModel.prototype.setData = function (data) {
var task = getCurrentTask(this);
if (task) {
var context = task.context; // Consider case: filter, data sample.
// FIXME:TS never used, so comment it
// if (context.data !== data && task.modifyOutputEnd) {
// task.setOutputEnd(data.count());
// }
context.outputData = data; // Caution: setData should update context.data,
// Because getData may be called multiply in a
// single stage and expect to get the data just
// set. (For example, AxisProxy, x y both call
// getData and setDate sequentially).
// So the context.data should be fetched from
// upstream each time when a stage starts to be
// performed.
if (task !== this.dataTask) {
context.data = data;
}
}
inner$1(this).data = data;
};
SeriesModel.prototype.getSource = function () {
return inner$1(this).sourceManager.getSource();
};
/**
* Get data before processed
*/
SeriesModel.prototype.getRawData = function () {
return inner$1(this).dataBeforeProcessed;
};
/**
* Get base axis if has coordinate system and has axis.
* By default use coordSys.getBaseAxis();
* Can be overrided for some chart.
* @return {type} description
*/
SeriesModel.prototype.getBaseAxis = function () {
var coordSys = this.coordinateSystem; // @ts-ignore
return coordSys && coordSys.getBaseAxis && coordSys.getBaseAxis();
};
/**
* Default tooltip formatter
*
* @param dataIndex
* @param multipleSeries
* @param dataType
* @param renderMode valid values: 'html'(by default) and 'richText'.
* 'html' is used for rendering tooltip in extra DOM form, and the result
* string is used as DOM HTML content.
* 'richText' is used for rendering tooltip in rich text form, for those where
* DOM operation is not supported.
* @return formatted tooltip with `html` and `markers`
* Notice: The override method can also return string
*/
SeriesModel.prototype.formatTooltip = function (dataIndex, multipleSeries, dataType) {
return defaultSeriesFormatTooltip({
series: this,
dataIndex: dataIndex,
multipleSeries: multipleSeries
});
};
SeriesModel.prototype.isAnimationEnabled = function () {
if (env.node) {
return false;
}
var animationEnabled = this.getShallow('animation');
if (animationEnabled) {
if (this.getData().count() > this.getShallow('animationThreshold')) {
animationEnabled = false;
}
}
return !!animationEnabled;
};
SeriesModel.prototype.restoreData = function () {
this.dataTask.dirty();
};
SeriesModel.prototype.getColorFromPalette = function (name, scope, requestColorNum) {
var ecModel = this.ecModel; // PENDING
var color = PaletteMixin.prototype.getColorFromPalette.call(this, name, scope, requestColorNum);
if (!color) {
color = ecModel.getColorFromPalette(name, scope, requestColorNum);
}
return color;
};
/**
* Use `data.mapDimensionsAll(coordDim)` instead.
* @deprecated
*/
SeriesModel.prototype.coordDimToDataDim = function (coordDim) {
return this.getRawData().mapDimensionsAll(coordDim);
};
/**
* Get progressive rendering count each step
*/
SeriesModel.prototype.getProgressive = function () {
return this.get('progressive');
};
/**
* Get progressive rendering count each step
*/
SeriesModel.prototype.getProgressiveThreshold = function () {
return this.get('progressiveThreshold');
}; // PENGING If selectedMode is null ?
SeriesModel.prototype.select = function (innerDataIndices, dataType) {
this._innerSelect(this.getData(dataType), innerDataIndices);
};
SeriesModel.prototype.unselect = function (innerDataIndices, dataType) {
var selectedMap = this.option.selectedMap;
if (!selectedMap) {
return;
}
var data = this.getData(dataType);
for (var i = 0; i < innerDataIndices.length; i++) {
var dataIndex = innerDataIndices[i];
var nameOrId = getSelectionKey(data, dataIndex);
selectedMap[nameOrId] = false;
this._selectedDataIndicesMap[nameOrId] = -1;
}
};
SeriesModel.prototype.toggleSelect = function (innerDataIndices, dataType) {
var tmpArr = [];
for (var i = 0; i < innerDataIndices.length; i++) {
tmpArr[0] = innerDataIndices[i];
this.isSelected(innerDataIndices[i], dataType) ? this.unselect(tmpArr, dataType) : this.select(tmpArr, dataType);
}
};
SeriesModel.prototype.getSelectedDataIndices = function () {
var selectedDataIndicesMap = this._selectedDataIndicesMap;
var nameOrIds = keys(selectedDataIndicesMap);
var dataIndices = [];
for (var i = 0; i < nameOrIds.length; i++) {
var dataIndex = selectedDataIndicesMap[nameOrIds[i]];
if (dataIndex >= 0) {
dataIndices.push(dataIndex);
}
}
return dataIndices;
};
SeriesModel.prototype.isSelected = function (dataIndex, dataType) {
var selectedMap = this.option.selectedMap;
if (!selectedMap) {
return false;
}
var data = this.getData(dataType);
var nameOrId = getSelectionKey(data, dataIndex);
return selectedMap[nameOrId] || false;
};
SeriesModel.prototype._innerSelect = function (data, innerDataIndices) {
var _a, _b;
var selectedMode = this.option.selectedMode;
var len = innerDataIndices.length;
if (!selectedMode || !len) {
return;
}
if (selectedMode === 'multiple') {
var selectedMap = this.option.selectedMap || (this.option.selectedMap = {});
for (var i = 0; i < len; i++) {
var dataIndex = innerDataIndices[i]; // TODO diffrent types of data share same object.
var nameOrId = getSelectionKey(data, dataIndex);
selectedMap[nameOrId] = true;
this._selectedDataIndicesMap[nameOrId] = data.getRawIndex(dataIndex);
}
} else if (selectedMode === 'single' || selectedMode === true) {
var lastDataIndex = innerDataIndices[len - 1];
var nameOrId = getSelectionKey(data, lastDataIndex);
this.option.selectedMap = (_a = {}, _a[nameOrId] = true, _a);
this._selectedDataIndicesMap = (_b = {}, _b[nameOrId] = data.getRawIndex(lastDataIndex), _b);
}
};
SeriesModel.prototype._initSelectedMapFromData = function (data) {
// Ignore select info in data if selectedMap exists.
// NOTE It's only for legacy usage. edge data is not supported.
if (this.option.selectedMap) {
return;
}
var dataIndices = [];
if (data.hasItemOption) {
data.each(function (idx) {
var rawItem = data.getRawDataItem(idx);
if (typeof rawItem === 'object' && rawItem.selected) {
dataIndices.push(idx);
}
});
}
if (dataIndices.length > 0) {
this._innerSelect(data, dataIndices);
}
}; // /**
// * @see {module:echarts/stream/Scheduler}
// */
// abstract pipeTask: null
SeriesModel.registerClass = function (clz) {
return ComponentModel.registerClass(clz);
};
SeriesModel.protoInitialize = function () {
var proto = SeriesModel.prototype;
proto.type = 'series.__base__';
proto.seriesIndex = 0;
proto.useColorPaletteOnData = false;
proto.ignoreStyleOnData = false;
proto.hasSymbolVisual = false;
proto.defaultSymbol = 'circle'; // Make sure the values can be accessed!
proto.visualStyleAccessPath = 'itemStyle';
proto.visualDrawType = 'fill';
}();
return SeriesModel;
}(ComponentModel);
mixin(SeriesModel, DataFormatMixin);
mixin(SeriesModel, PaletteMixin);
mountExtend(SeriesModel, ComponentModel);
/**
* MUST be called after `prepareSource` called
* Here we need to make auto series, especially for auto legend. But we
* do not modify series.name in option to avoid side effects.
*/
function autoSeriesName(seriesModel) {
// User specified name has higher priority, otherwise it may cause
// series can not be queried unexpectedly.
var name = seriesModel.name;
if (!isNameSpecified(seriesModel)) {
seriesModel.name = getSeriesAutoName(seriesModel) || name;
}
}
function getSeriesAutoName(seriesModel) {
var data = seriesModel.getRawData();
var dataDims = data.mapDimensionsAll('seriesName');
var nameArr = [];
each(dataDims, function (dataDim) {
var dimInfo = data.getDimensionInfo(dataDim);
dimInfo.displayName && nameArr.push(dimInfo.displayName);
});
return nameArr.join(' ');
}
function dataTaskCount(context) {
return context.model.getRawData().count();
}
function dataTaskReset(context) {
var seriesModel = context.model;
seriesModel.setData(seriesModel.getRawData().cloneShallow());
return dataTaskProgress;
}
function dataTaskProgress(param, context) {
// Avoid repead cloneShallow when data just created in reset.
if (context.outputData && param.end > context.outputData.count()) {
context.model.getRawData().cloneShallow(context.outputData);
}
} // TODO refactor
function wrapData(data, seriesModel) {
each(__spreadArrays(data.CHANGABLE_METHODS, data.DOWNSAMPLE_METHODS), function (methodName) {
data.wrapMethod(methodName, curry(onDataChange, seriesModel));
});
}
function onDataChange(seriesModel, newList) {
var task = getCurrentTask(seriesModel);
if (task) {
// Consider case: filter, selectRange
task.setOutputEnd((newList || this).count());
}
return newList;
}
function getCurrentTask(seriesModel) {
var scheduler = (seriesModel.ecModel || {}).scheduler;
var pipeline = scheduler && scheduler.getPipeline(seriesModel.uid);
if (pipeline) {
// When pipline finished, the currrentTask keep the last
// task (renderTask).
var task = pipeline.currentTask;
if (task) {
var agentStubMap = task.agentStubMap;
if (agentStubMap) {
task = agentStubMap.get(seriesModel.uid);
}
}
return task;
}
}
var ComponentView =
/** @class */
function () {
function ComponentView() {
this.group = new Group();
this.uid = getUID('viewComponent');
}
ComponentView.prototype.init = function (ecModel, api) {};
ComponentView.prototype.render = function (model, ecModel, api, payload) {};
ComponentView.prototype.dispose = function (ecModel, api) {};
ComponentView.prototype.updateView = function (model, ecModel, api, payload) {// Do nothing;
};
ComponentView.prototype.updateLayout = function (model, ecModel, api, payload) {// Do nothing;
};
ComponentView.prototype.updateVisual = function (model, ecModel, api, payload) {// Do nothing;
};
/**
* Hook for blur target series.
* Can be used in marker for blur the markers
*/
ComponentView.prototype.blurSeries = function (seriesModels, ecModel) {// Do nothing;
};
return ComponentView;
}();
enableClassExtend(ComponentView);
enableClassManagement(ComponentView);
/**
* @return {string} If large mode changed, return string 'reset';
*/
function createRenderPlanner() {
var inner = makeInner();
return function (seriesModel) {
var fields = inner(seriesModel);
var pipelineContext = seriesModel.pipelineContext;
var originalLarge = !!fields.large;
var originalProgressive = !!fields.progressiveRender; // FIXME: if the planner works on a filtered series, `pipelineContext` does not
// exists. See #11611 . Probably we need to modify this structure, see the comment
// on `performRawSeries` in `Schedular.js`.
var large = fields.large = !!(pipelineContext && pipelineContext.large);
var progressive = fields.progressiveRender = !!(pipelineContext && pipelineContext.progressiveRender);
return !!(originalLarge !== large || originalProgressive !== progressive) && 'reset';
};
}
var inner$2 = makeInner();
var renderPlanner = createRenderPlanner();
var ChartView =
/** @class */
function () {
function ChartView() {
this.group = new Group();
this.uid = getUID('viewChart');
this.renderTask = createTask({
plan: renderTaskPlan,
reset: renderTaskReset
});
this.renderTask.context = {
view: this
};
}
ChartView.prototype.init = function (ecModel, api) {};
ChartView.prototype.render = function (seriesModel, ecModel, api, payload) {};
/**
* Highlight series or specified data item.
*/
ChartView.prototype.highlight = function (seriesModel, ecModel, api, payload) {
toggleHighlight(seriesModel.getData(), payload, 'emphasis');
};
/**
* Downplay series or specified data item.
*/
ChartView.prototype.downplay = function (seriesModel, ecModel, api, payload) {
toggleHighlight(seriesModel.getData(), payload, 'normal');
};
/**
* Remove self.
*/
ChartView.prototype.remove = function (ecModel, api) {
this.group.removeAll();
};
/**
* Dispose self.
*/
ChartView.prototype.dispose = function (ecModel, api) {};
ChartView.prototype.updateView = function (seriesModel, ecModel, api, payload) {
this.render(seriesModel, ecModel, api, payload);
}; // FIXME never used?
ChartView.prototype.updateLayout = function (seriesModel, ecModel, api, payload) {
this.render(seriesModel, ecModel, api, payload);
}; // FIXME never used?
ChartView.prototype.updateVisual = function (seriesModel, ecModel, api, payload) {
this.render(seriesModel, ecModel, api, payload);
};
ChartView.markUpdateMethod = function (payload, methodName) {
inner$2(payload).updateMethod = methodName;
};
ChartView.protoInitialize = function () {
var proto = ChartView.prototype;
proto.type = 'chart';
}();
return ChartView;
}();
/**
* Set state of single element
*/
function elSetState(el, state, highlightDigit) {
if (el) {
(state === 'emphasis' ? enterEmphasis : leaveEmphasis)(el, highlightDigit);
}
}
function toggleHighlight(data, payload, state) {
var dataIndex = queryDataIndex(data, payload);
var highlightDigit = payload && payload.highlightKey != null ? getHighlightDigit(payload.highlightKey) : null;
if (dataIndex != null) {
each(normalizeToArray(dataIndex), function (dataIdx) {
elSetState(data.getItemGraphicEl(dataIdx), state, highlightDigit);
});
} else {
data.eachItemGraphicEl(function (el) {
elSetState(el, state, highlightDigit);
});
}
}
enableClassExtend(ChartView, ['dispose']);
enableClassManagement(ChartView);
function renderTaskPlan(context) {
return renderPlanner(context.model);
}
function renderTaskReset(context) {
var seriesModel = context.model;
var ecModel = context.ecModel;
var api = context.api;
var payload = context.payload; // FIXME: remove updateView updateVisual
var progressiveRender = seriesModel.pipelineContext.progressiveRender;
var view = context.view;
var updateMethod = payload && inner$2(payload).updateMethod;
var methodName = progressiveRender ? 'incrementalPrepareRender' : updateMethod && view[updateMethod] ? updateMethod // `appendData` is also supported when data amount
// is less than progressive threshold.
: 'render';
if (methodName !== 'render') {
view[methodName](seriesModel, ecModel, api, payload);
}
return progressMethodMap[methodName];
}
var progressMethodMap = {
incrementalPrepareRender: {
progress: function (params, context) {
context.view.incrementalRender(params, context.model, context.ecModel, context.api, context.payload);
}
},
render: {
// Put view.render in `progress` to support appendData. But in this case
// view.render should not be called in reset, otherwise it will be called
// twise. Use `forceFirstProgress` to make sure that view.render is called
// in any cases.
forceFirstProgress: true,
progress: function (params, context) {
context.view.render(context.model, context.ecModel, context.api, context.payload);
}
}
};
/*
* 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.
*/
var ORIGIN_METHOD = '\0__throttleOriginMethod';
var RATE = '\0__throttleRate';
var THROTTLE_TYPE = '\0__throttleType';
/**
* @public
* @param {(Function)} fn
* @param {number} [delay=0] Unit: ms.
* @param {boolean} [debounce=false]
* true: If call interval less than `delay`, only the last call works.
* false: If call interval less than `delay, call works on fixed rate.
* @return {(Function)} throttled fn.
*/
function throttle(fn, delay, debounce) {
var currCall;
var lastCall = 0;
var lastExec = 0;
var timer = null;
var diff;
var scope;
var args;
var debounceNextCall;
delay = delay || 0;
function exec() {
lastExec = new Date().getTime();
timer = null;
fn.apply(scope, args || []);
}
var cb = function () {
var cbArgs = [];
for (var _i = 0; _i < arguments.length; _i++) {
cbArgs[_i] = arguments[_i];
}
currCall = new Date().getTime();
scope = this;
args = cbArgs;
var thisDelay = debounceNextCall || delay;
var thisDebounce = debounceNextCall || debounce;
debounceNextCall = null;
diff = currCall - (thisDebounce ? lastCall : lastExec) - thisDelay;
clearTimeout(timer); // Here we should make sure that: the `exec` SHOULD NOT be called later
// than a new call of `cb`, that is, preserving the command order. Consider
// calculating "scale rate" when roaming as an example. When a call of `cb`
// happens, either the `exec` is called dierectly, or the call is delayed.
// But the delayed call should never be later than next call of `cb`. Under
// this assurance, we can simply update view state each time `dispatchAction`
// triggered by user roaming, but not need to add extra code to avoid the
// state being "rolled-back".
if (thisDebounce) {
timer = setTimeout(exec, thisDelay);
} else {
if (diff >= 0) {
exec();
} else {
timer = setTimeout(exec, -diff);
}
}
lastCall = currCall;
};
/**
* Clear throttle.
* @public
*/
cb.clear = function () {
if (timer) {
clearTimeout(timer);
timer = null;
}
};
/**
* Enable debounce once.
*/
cb.debounceNextCall = function (debounceDelay) {
debounceNextCall = debounceDelay;
};
return cb;
}
/**
* Create throttle method or update throttle rate.
*
* @example
* ComponentView.prototype.render = function () {
* ...
* throttle.createOrUpdate(
* this,
* '_dispatchAction',
* this.model.get('throttle'),
* 'fixRate'
* );
* };
* ComponentView.prototype.remove = function () {
* throttle.clear(this, '_dispatchAction');
* };
* ComponentView.prototype.dispose = function () {
* throttle.clear(this, '_dispatchAction');
* };
*
*/
function createOrUpdate(obj, fnAttr, rate, throttleType) {
var fn = obj[fnAttr];
if (!fn) {
return;
}
var originFn = fn[ORIGIN_METHOD] || fn;
var lastThrottleType = fn[THROTTLE_TYPE];
var lastRate = fn[RATE];
if (lastRate !== rate || lastThrottleType !== throttleType) {
if (rate == null || !throttleType) {
return obj[fnAttr] = originFn;
}
fn = obj[fnAttr] = throttle(originFn, rate, throttleType === 'debounce');
fn[ORIGIN_METHOD] = originFn;
fn[THROTTLE_TYPE] = throttleType;
fn[RATE] = rate;
}
return fn;
}
/**
* Clear throttle. Example see throttle.createOrUpdate.
*/
function clear(obj, fnAttr) {
var fn = obj[fnAttr];
if (fn && fn[ORIGIN_METHOD]) {
obj[fnAttr] = fn[ORIGIN_METHOD];
}
}
var inner$3 = makeInner();
var defaultStyleMappers = {
itemStyle: makeStyleMapper(ITEM_STYLE_KEY_MAP, true),
lineStyle: makeStyleMapper(LINE_STYLE_KEY_MAP, true)
};
var defaultColorKey = {
lineStyle: 'stroke',
itemStyle: 'fill'
};
function getStyleMapper(seriesModel, stylePath) {
var styleMapper = seriesModel.visualStyleMapper || defaultStyleMappers[stylePath];
if (!styleMapper) {
console.warn("Unkown style type '" + stylePath + "'.");
return defaultStyleMappers.itemStyle;
}
return styleMapper;
}
function getDefaultColorKey(seriesModel, stylePath) {
// return defaultColorKey[stylePath] ||
var colorKey = seriesModel.visualDrawType || defaultColorKey[stylePath];
if (!colorKey) {
console.warn("Unkown style type '" + stylePath + "'.");
return 'fill';
}
return colorKey;
}
var seriesStyleTask = {
createOnAllSeries: true,
performRawSeries: true,
reset: function (seriesModel, ecModel) {
var data = seriesModel.getData();
var stylePath = seriesModel.visualStyleAccessPath || 'itemStyle'; // Set in itemStyle
var styleModel = seriesModel.getModel(stylePath);
var getStyle = getStyleMapper(seriesModel, stylePath);
var globalStyle = getStyle(styleModel);
var decalOption = styleModel.getShallow('decal');
if (decalOption) {
data.setVisual('decal', decalOption);
decalOption.dirty = true;
} // TODO
var colorKey = getDefaultColorKey(seriesModel, stylePath);
var color = globalStyle[colorKey]; // TODO style callback
var colorCallback = isFunction(color) ? color : null; // Get from color palette by default.
if (!globalStyle[colorKey] || colorCallback) {
// Note: if some series has color specified (e.g., by itemStyle.color), we DO NOT
// make it effect palette. Bacause some scenarios users need to make some series
// transparent or as background, which should better not effect the palette.
globalStyle[colorKey] = seriesModel.getColorFromPalette( // TODO series count changed.
seriesModel.name, null, ecModel.getSeriesCount());
data.setVisual('colorFromPalette', true);
}
data.setVisual('style', globalStyle);
data.setVisual('drawType', colorKey); // Only visible series has each data be visual encoded
if (!ecModel.isSeriesFiltered(seriesModel) && colorCallback) {
data.setVisual('colorFromPalette', false);
return {
dataEach: function (data, idx) {
var dataParams = seriesModel.getDataParams(idx);
var itemStyle = extend({}, globalStyle);
itemStyle[colorKey] = colorCallback(dataParams);
data.setItemVisual(idx, 'style', itemStyle);
}
};
}
}
};
var sharedModel = new Model();
var dataStyleTask = {
createOnAllSeries: true,
performRawSeries: true,
reset: function (seriesModel, ecModel) {
if (seriesModel.ignoreStyleOnData || ecModel.isSeriesFiltered(seriesModel)) {
return;
}
var data = seriesModel.getData();
var stylePath = seriesModel.visualStyleAccessPath || 'itemStyle'; // Set in itemStyle
var getStyle = getStyleMapper(seriesModel, stylePath);
var colorKey = data.getVisual('drawType');
return {
dataEach: data.hasItemOption ? function (data, idx) {
// Not use getItemModel for performance considuration
var rawItem = data.getRawDataItem(idx);
if (rawItem && rawItem[stylePath]) {
sharedModel.option = rawItem[stylePath];
var style = getStyle(sharedModel);
var existsStyle = data.ensureUniqueItemVisual(idx, 'style');
extend(existsStyle, style);
if (sharedModel.option.decal) {
data.setItemVisual(idx, 'decal', sharedModel.option.decal);
sharedModel.option.decal.dirty = true;
}
if (colorKey in style) {
data.setItemVisual(idx, 'colorFromPalette', false);
}
}
} : null
};
}
}; // Pick color from palette for the data which has not been set with color yet.
// Note: do not support stream rendering. No such cases yet.
var dataColorPaletteTask = {
performRawSeries: true,
overallReset: function (ecModel) {
// Each type of series use one scope.
// Pie and funnel are using diferrent scopes
var paletteScopeGroupByType = createHashMap();
ecModel.eachSeries(function (seriesModel) {
if (!seriesModel.useColorPaletteOnData) {
return;
}
var colorScope = paletteScopeGroupByType.get(seriesModel.type);
if (!colorScope) {
colorScope = {};
paletteScopeGroupByType.set(seriesModel.type, colorScope);
}
inner$3(seriesModel).scope = colorScope;
});
ecModel.eachSeries(function (seriesModel) {
if (!seriesModel.useColorPaletteOnData || ecModel.isSeriesFiltered(seriesModel)) {
return;
}
var dataAll = seriesModel.getRawData();
var idxMap = {};
var data = seriesModel.getData();
var colorScope = inner$3(seriesModel).scope;
var stylePath = seriesModel.visualStyleAccessPath || 'itemStyle';
var colorKey = getDefaultColorKey(seriesModel, stylePath);
data.each(function (idx) {
var rawIdx = data.getRawIndex(idx);
idxMap[rawIdx] = idx;
}); // Iterate on data before filtered. To make sure color from palette can be
// Consistent when toggling legend.
dataAll.each(function (rawIdx) {
var idx = idxMap[rawIdx];
var fromPalette = data.getItemVisual(idx, 'colorFromPalette'); // Get color from palette for each data only when the color is inherited from series color, which is
// also picked from color palette. So following situation is not in the case:
// 1. series.itemStyle.color is set
// 2. color is encoded by visualMap
if (fromPalette) {
var itemStyle = data.ensureUniqueItemVisual(idx, 'style');
var name_1 = dataAll.getName(rawIdx) || rawIdx + '';
var dataCount = dataAll.count();
itemStyle[colorKey] = seriesModel.getColorFromPalette(name_1, colorScope, dataCount);
}
});
});
}
};
var PI$3 = Math.PI;
/**
* @param {module:echarts/ExtensionAPI} api
* @param {Object} [opts]
* @param {string} [opts.text]
* @param {string} [opts.color]
* @param {string} [opts.textColor]
* @return {module:zrender/Element}
*/
function defaultLoading(api, opts) {
opts = opts || {};
defaults(opts, {
text: 'loading',
textColor: '#000',
fontSize: 12,
fontWeight: 'normal',
fontStyle: 'normal',
fontFamily: 'sans-serif',
maskColor: 'rgba(255, 255, 255, 0.8)',
showSpinner: true,
color: '#5470c6',
spinnerRadius: 10,
lineWidth: 5,
zlevel: 0
});
var group = new Group();
var mask = new Rect({
style: {
fill: opts.maskColor
},
zlevel: opts.zlevel,
z: 10000
});
group.add(mask);
var textContent = new ZRText({
style: {
text: opts.text,
fill: opts.textColor,
fontSize: opts.fontSize,
fontWeight: opts.fontWeight,
fontStyle: opts.fontStyle,
fontFamily: opts.fontFamily
}
});
var labelRect = new Rect({
style: {
fill: 'none'
},
textContent: textContent,
textConfig: {
position: 'right',
distance: 10
},
zlevel: opts.zlevel,
z: 10001
});
group.add(labelRect);
var arc;
if (opts.showSpinner) {
arc = new Arc({
shape: {
startAngle: -PI$3 / 2,
endAngle: -PI$3 / 2 + 0.1,
r: opts.spinnerRadius
},
style: {
stroke: opts.color,
lineCap: 'round',
lineWidth: opts.lineWidth
},
zlevel: opts.zlevel,
z: 10001
});
arc.animateShape(true).when(1000, {
endAngle: PI$3 * 3 / 2
}).start('circularInOut');
arc.animateShape(true).when(1000, {
startAngle: PI$3 * 3 / 2
}).delay(300).start('circularInOut');
group.add(arc);
} // Inject resize
group.resize = function () {
var textWidth = textContent.getBoundingRect().width;
var r = opts.showSpinner ? opts.spinnerRadius : 0; // cx = (containerWidth - arcDiameter - textDistance - textWidth) / 2
// textDistance needs to be calculated when both animation and text exist
var cx = (api.getWidth() - r * 2 - (opts.showSpinner && textWidth ? 10 : 0) - textWidth) / 2 - (opts.showSpinner && textWidth ? 0 : 5 + textWidth / 2) // only show the text
+ (opts.showSpinner ? 0 : textWidth / 2) // only show the spinner
+ (textWidth ? 0 : r);
var cy = api.getHeight() / 2;
opts.showSpinner && arc.setShape({
cx: cx,
cy: cy
});
labelRect.setShape({
x: cx - r,
y: cy - r,
width: r * 2,
height: r * 2
});
mask.setShape({
x: 0,
y: 0,
width: api.getWidth(),
height: api.getHeight()
});
};
group.resize();
return group;
}
var Scheduler =
/** @class */
function () {
function Scheduler(ecInstance, api, dataProcessorHandlers, visualHandlers) {
// key: handlerUID
this._stageTaskMap = createHashMap();
this.ecInstance = ecInstance;
this.api = api; // Fix current processors in case that in some rear cases that
// processors might be registered after echarts instance created.
// Register processors incrementally for a echarts instance is
// not supported by this stream architecture.
dataProcessorHandlers = this._dataProcessorHandlers = dataProcessorHandlers.slice();
visualHandlers = this._visualHandlers = visualHandlers.slice();
this._allHandlers = dataProcessorHandlers.concat(visualHandlers);
}
Scheduler.prototype.restoreData = function (ecModel, payload) {
// TODO: Only restore needed series and components, but not all components.
// Currently `restoreData` of all of the series and component will be called.
// But some independent components like `title`, `legend`, `graphic`, `toolbox`,
// `tooltip`, `axisPointer`, etc, do not need series refresh when `setOption`,
// and some components like coordinate system, axes, dataZoom, visualMap only
// need their target series refresh.
// (1) If we are implementing this feature some day, we should consider these cases:
// if a data processor depends on a component (e.g., dataZoomProcessor depends
// on the settings of `dataZoom`), it should be re-performed if the component
// is modified by `setOption`.
// (2) If a processor depends on sevral series, speicified by its `getTargetSeries`,
// it should be re-performed when the result array of `getTargetSeries` changed.
// We use `dependencies` to cover these issues.
// (3) How to update target series when coordinate system related components modified.
// TODO: simply the dirty mechanism? Check whether only the case here can set tasks dirty,
// and this case all of the tasks will be set as dirty.
ecModel.restoreData(payload); // Theoretically an overall task not only depends on each of its target series, but also
// depends on all of the series.
// The overall task is not in pipeline, and `ecModel.restoreData` only set pipeline tasks
// dirty. If `getTargetSeries` of an overall task returns nothing, we should also ensure
// that the overall task is set as dirty and to be performed, otherwise it probably cause
// state chaos. So we have to set dirty of all of the overall tasks manually, otherwise it
// probably cause state chaos (consider `dataZoomProcessor`).
this._stageTaskMap.each(function (taskRecord) {
var overallTask = taskRecord.overallTask;
overallTask && overallTask.dirty();
});
}; // If seriesModel provided, incremental threshold is check by series data.
Scheduler.prototype.getPerformArgs = function (task, isBlock) {
// For overall task
if (!task.__pipeline) {
return;
}
var pipeline = this._pipelineMap.get(task.__pipeline.id);
var pCtx = pipeline.context;
var incremental = !isBlock && pipeline.progressiveEnabled && (!pCtx || pCtx.progressiveRender) && task.__idxInPipeline > pipeline.blockIndex;
var step = incremental ? pipeline.step : null;
var modDataCount = pCtx && pCtx.modDataCount;
var modBy = modDataCount != null ? Math.ceil(modDataCount / step) : null;
return {
step: step,
modBy: modBy,
modDataCount: modDataCount
};
};
Scheduler.prototype.getPipeline = function (pipelineId) {
return this._pipelineMap.get(pipelineId);
};
/**
* Current, progressive rendering starts from visual and layout.
* Always detect render mode in the same stage, avoiding that incorrect
* detection caused by data filtering.
* Caution:
* `updateStreamModes` use `seriesModel.getData()`.
*/
Scheduler.prototype.updateStreamModes = function (seriesModel, view) {
var pipeline = this._pipelineMap.get(seriesModel.uid);
var data = seriesModel.getData();
var dataLen = data.count(); // `progressiveRender` means that can render progressively in each
// animation frame. Note that some types of series do not provide
// `view.incrementalPrepareRender` but support `chart.appendData`. We
// use the term `incremental` but not `progressive` to describe the
// case that `chart.appendData`.
var progressiveRender = pipeline.progressiveEnabled && view.incrementalPrepareRender && dataLen >= pipeline.threshold;
var large = seriesModel.get('large') && dataLen >= seriesModel.get('largeThreshold'); // TODO: modDataCount should not updated if `appendData`, otherwise cause whole repaint.
// see `test/candlestick-large3.html`
var modDataCount = seriesModel.get('progressiveChunkMode') === 'mod' ? dataLen : null;
seriesModel.pipelineContext = pipeline.context = {
progressiveRender: progressiveRender,
modDataCount: modDataCount,
large: large
};
};
Scheduler.prototype.restorePipelines = function (ecModel) {
var scheduler = this;
var pipelineMap = scheduler._pipelineMap = createHashMap();
ecModel.eachSeries(function (seriesModel) {
var progressive = seriesModel.getProgressive();
var pipelineId = seriesModel.uid;
pipelineMap.set(pipelineId, {
id: pipelineId,
head: null,
tail: null,
threshold: seriesModel.getProgressiveThreshold(),
progressiveEnabled: progressive && !(seriesModel.preventIncremental && seriesModel.preventIncremental()),
blockIndex: -1,
step: Math.round(progressive || 700),
count: 0
});
scheduler._pipe(seriesModel, seriesModel.dataTask);
});
};
Scheduler.prototype.prepareStageTasks = function () {
var stageTaskMap = this._stageTaskMap;
var ecModel = this.api.getModel();
var api = this.api;
each(this._allHandlers, function (handler) {
var record = stageTaskMap.get(handler.uid) || stageTaskMap.set(handler.uid, {});
var errMsg = '';
if ("development" !== 'production') {
// Currently do not need to support to sepecify them both.
errMsg = '"reset" and "overallReset" must not be both specified.';
}
assert(!(handler.reset && handler.overallReset), errMsg);
handler.reset && this._createSeriesStageTask(handler, record, ecModel, api);
handler.overallReset && this._createOverallStageTask(handler, record, ecModel, api);
}, this);
};
Scheduler.prototype.prepareView = function (view, model, ecModel, api) {
var renderTask = view.renderTask;
var context = renderTask.context;
context.model = model;
context.ecModel = ecModel;
context.api = api;
renderTask.__block = !view.incrementalPrepareRender;
this._pipe(model, renderTask);
};
Scheduler.prototype.performDataProcessorTasks = function (ecModel, payload) {
// If we do not use `block` here, it should be considered when to update modes.
this._performStageTasks(this._dataProcessorHandlers, ecModel, payload, {
block: true
});
};
Scheduler.prototype.performVisualTasks = function (ecModel, payload, opt) {
this._performStageTasks(this._visualHandlers, ecModel, payload, opt);
};
Scheduler.prototype._performStageTasks = function (stageHandlers, ecModel, payload, opt) {
opt = opt || {};
var unfinished = false;
var scheduler = this;
each(stageHandlers, function (stageHandler, idx) {
if (opt.visualType && opt.visualType !== stageHandler.visualType) {
return;
}
var stageHandlerRecord = scheduler._stageTaskMap.get(stageHandler.uid);
var seriesTaskMap = stageHandlerRecord.seriesTaskMap;
var overallTask = stageHandlerRecord.overallTask;
if (overallTask) {
var overallNeedDirty_1;
var agentStubMap = overallTask.agentStubMap;
agentStubMap.each(function (stub) {
if (needSetDirty(opt, stub)) {
stub.dirty();
overallNeedDirty_1 = true;
}
});
overallNeedDirty_1 && overallTask.dirty();
scheduler.updatePayload(overallTask, payload);
var performArgs_1 = scheduler.getPerformArgs(overallTask, opt.block); // Execute stubs firstly, which may set the overall task dirty,
// then execute the overall task. And stub will call seriesModel.setData,
// which ensures that in the overallTask seriesModel.getData() will not
// return incorrect data.
agentStubMap.each(function (stub) {
stub.perform(performArgs_1);
});
if (overallTask.perform(performArgs_1)) {
unfinished = true;
}
} else if (seriesTaskMap) {
seriesTaskMap.each(function (task, pipelineId) {
if (needSetDirty(opt, task)) {
task.dirty();
}
var performArgs = scheduler.getPerformArgs(task, opt.block); // FIXME
// if intending to decalare `performRawSeries` in handlers, only
// stream-independent (specifically, data item independent) operations can be
// performed. Because is a series is filtered, most of the tasks will not
// be performed. A stream-dependent operation probably cause wrong biz logic.
// Perhaps we should not provide a separate callback for this case instead
// of providing the config `performRawSeries`. The stream-dependent operaions
// and stream-independent operations should better not be mixed.
performArgs.skip = !stageHandler.performRawSeries && ecModel.isSeriesFiltered(task.context.model);
scheduler.updatePayload(task, payload);
if (task.perform(performArgs)) {
unfinished = true;
}
});
}
});
function needSetDirty(opt, task) {
return opt.setDirty && (!opt.dirtyMap || opt.dirtyMap.get(task.__pipeline.id));
}
this.unfinished = unfinished || this.unfinished;
};
Scheduler.prototype.performSeriesTasks = function (ecModel) {
var unfinished;
ecModel.eachSeries(function (seriesModel) {
// Progress to the end for dataInit and dataRestore.
unfinished = seriesModel.dataTask.perform() || unfinished;
});
this.unfinished = unfinished || this.unfinished;
};
Scheduler.prototype.plan = function () {
// Travel pipelines, check block.
this._pipelineMap.each(function (pipeline) {
var task = pipeline.tail;
do {
if (task.__block) {
pipeline.blockIndex = task.__idxInPipeline;
break;
}
task = task.getUpstream();
} while (task);
});
};
Scheduler.prototype.updatePayload = function (task, payload) {
payload !== 'remain' && (task.context.payload = payload);
};
Scheduler.prototype._createSeriesStageTask = function (stageHandler, stageHandlerRecord, ecModel, api) {
var scheduler = this;
var oldSeriesTaskMap = stageHandlerRecord.seriesTaskMap; // The count of stages are totally about only several dozen, so
// do not need to reuse the map.
var newSeriesTaskMap = stageHandlerRecord.seriesTaskMap = createHashMap();
var seriesType = stageHandler.seriesType;
var getTargetSeries = stageHandler.getTargetSeries; // If a stageHandler should cover all series, `createOnAllSeries` should be declared mandatorily,
// to avoid some typo or abuse. Otherwise if an extension do not specify a `seriesType`,
// it works but it may cause other irrelevant charts blocked.
if (stageHandler.createOnAllSeries) {
ecModel.eachRawSeries(create);
} else if (seriesType) {
ecModel.eachRawSeriesByType(seriesType, create);
} else if (getTargetSeries) {
getTargetSeries(ecModel, api).each(create);
}
function create(seriesModel) {
var pipelineId = seriesModel.uid; // Init tasks for each seriesModel only once.
// Reuse original task instance.
var task = newSeriesTaskMap.set(pipelineId, oldSeriesTaskMap && oldSeriesTaskMap.get(pipelineId) || createTask({
plan: seriesTaskPlan,
reset: seriesTaskReset,
count: seriesTaskCount
}));
task.context = {
model: seriesModel,
ecModel: ecModel,
api: api,
// PENDING: `useClearVisual` not used?
useClearVisual: stageHandler.isVisual && !stageHandler.isLayout,
plan: stageHandler.plan,
reset: stageHandler.reset,
scheduler: scheduler
};
scheduler._pipe(seriesModel, task);
}
};
Scheduler.prototype._createOverallStageTask = function (stageHandler, stageHandlerRecord, ecModel, api) {
var scheduler = this;
var overallTask = stageHandlerRecord.overallTask = stageHandlerRecord.overallTask // For overall task, the function only be called on reset stage.
|| createTask({
reset: overallTaskReset
});
overallTask.context = {
ecModel: ecModel,
api: api,
overallReset: stageHandler.overallReset,
scheduler: scheduler
};
var oldAgentStubMap = overallTask.agentStubMap; // The count of stages are totally about only several dozen, so
// do not need to reuse the map.
var newAgentStubMap = overallTask.agentStubMap = createHashMap();
var seriesType = stageHandler.seriesType;
var getTargetSeries = stageHandler.getTargetSeries;
var overallProgress = true;
var shouldOverallTaskDirty = false; // FIXME:TS never used, so comment it
// let modifyOutputEnd = stageHandler.modifyOutputEnd;
// An overall task with seriesType detected or has `getTargetSeries`, we add
// stub in each pipelines, it will set the overall task dirty when the pipeline
// progress. Moreover, to avoid call the overall task each frame (too frequent),
// we set the pipeline block.
var errMsg = '';
if ("development" !== 'production') {
errMsg = '"createOnAllSeries" do not supported for "overallReset", ' + 'becuase it will block all streams.';
}
assert(!stageHandler.createOnAllSeries, errMsg);
if (seriesType) {
ecModel.eachRawSeriesByType(seriesType, createStub);
} else if (getTargetSeries) {
getTargetSeries(ecModel, api).each(createStub);
} // Otherwise, (usually it is legancy case), the overall task will only be
// executed when upstream dirty. Otherwise the progressive rendering of all
// pipelines will be disabled unexpectedly. But it still needs stubs to receive
// dirty info from upsteam.
else {
overallProgress = false;
each(ecModel.getSeries(), createStub);
}
function createStub(seriesModel) {
var pipelineId = seriesModel.uid;
var stub = newAgentStubMap.set(pipelineId, oldAgentStubMap && oldAgentStubMap.get(pipelineId) || ( // When the result of `getTargetSeries` changed, the overallTask
// should be set as dirty and re-performed.
shouldOverallTaskDirty = true, createTask({
reset: stubReset,
onDirty: stubOnDirty
})));
stub.context = {
model: seriesModel,
overallProgress: overallProgress // FIXME:TS never used, so comment it
// modifyOutputEnd: modifyOutputEnd
};
stub.agent = overallTask;
stub.__block = overallProgress;
scheduler._pipe(seriesModel, stub);
}
if (shouldOverallTaskDirty) {
overallTask.dirty();
}
};
Scheduler.prototype._pipe = function (seriesModel, task) {
var pipelineId = seriesModel.uid;
var pipeline = this._pipelineMap.get(pipelineId);
!pipeline.head && (pipeline.head = task);
pipeline.tail && pipeline.tail.pipe(task);
pipeline.tail = task;
task.__idxInPipeline = pipeline.count++;
task.__pipeline = pipeline;
};
Scheduler.wrapStageHandler = function (stageHandler, visualType) {
if (isFunction(stageHandler)) {
stageHandler = {
overallReset: stageHandler,
seriesType: detectSeriseType(stageHandler)
};
}
stageHandler.uid = getUID('stageHandler');
visualType && (stageHandler.visualType = visualType);
return stageHandler;
};
return Scheduler;
}();
function overallTaskReset(context) {
context.overallReset(context.ecModel, context.api, context.payload);
}
function stubReset(context) {
return context.overallProgress && stubProgress;
}
function stubProgress() {
this.agent.dirty();
this.getDownstream().dirty();
}
function stubOnDirty() {
this.agent && this.agent.dirty();
}
function seriesTaskPlan(context) {
return context.plan ? context.plan(context.model, context.ecModel, context.api, context.payload) : null;
}
function seriesTaskReset(context) {
if (context.useClearVisual) {
context.data.clearAllVisual();
}
var resetDefines = context.resetDefines = normalizeToArray(context.reset(context.model, context.ecModel, context.api, context.payload));
return resetDefines.length > 1 ? map(resetDefines, function (v, idx) {
return makeSeriesTaskProgress(idx);
}) : singleSeriesTaskProgress;
}
var singleSeriesTaskProgress = makeSeriesTaskProgress(0);
function makeSeriesTaskProgress(resetDefineIdx) {
return function (params, context) {
var data = context.data;
var resetDefine = context.resetDefines[resetDefineIdx];
if (resetDefine && resetDefine.dataEach) {
for (var i = params.start; i < params.end; i++) {
resetDefine.dataEach(data, i);
}
} else if (resetDefine && resetDefine.progress) {
resetDefine.progress(params, data);
}
};
}
function seriesTaskCount(context) {
return context.data.count();
}
/**
* Only some legacy stage handlers (usually in echarts extensions) are pure function.
* To ensure that they can work normally, they should work in block mode, that is,
* they should not be started util the previous tasks finished. So they cause the
* progressive rendering disabled. We try to detect the series type, to narrow down
* the block range to only the series type they concern, but not all series.
*/
function detectSeriseType(legacyFunc) {
seriesType = null;
try {
// Assume there is no async when calling `eachSeriesByType`.
legacyFunc(ecModelMock, apiMock);
} catch (e) {}
return seriesType;
}
var ecModelMock = {};
var apiMock = {};
var seriesType;
mockMethods(ecModelMock, GlobalModel);
mockMethods(apiMock, ExtensionAPI);
ecModelMock.eachSeriesByType = ecModelMock.eachRawSeriesByType = function (type) {
seriesType = type;
};
ecModelMock.eachComponent = function (cond) {
if (cond.mainType === 'series' && cond.subType) {
seriesType = cond.subType;
}
};
function mockMethods(target, Clz) {
/* eslint-disable */
for (var name_1 in Clz.prototype) {
// Do not use hasOwnProperty
target[name_1] = noop;
}
/* eslint-enable */
}
/*
* 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.
*/
var colorAll = ['#37A2DA', '#32C5E9', '#67E0E3', '#9FE6B8', '#FFDB5C', '#ff9f7f', '#fb7293', '#E062AE', '#E690D1', '#e7bcf3', '#9d96f5', '#8378EA', '#96BFFF'];
var lightTheme = {
color: colorAll,
colorLayer: [['#37A2DA', '#ffd85c', '#fd7b5f'], ['#37A2DA', '#67E0E3', '#FFDB5C', '#ff9f7f', '#E062AE', '#9d96f5'], ['#37A2DA', '#32C5E9', '#9FE6B8', '#FFDB5C', '#ff9f7f', '#fb7293', '#e7bcf3', '#8378EA', '#96BFFF'], colorAll]
};
/*
* 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.
*/
var contrastColor = '#B9B8CE';
var backgroundColor = '#100C2A';
var axisCommon = function () {
return {
axisLine: {
lineStyle: {
color: contrastColor
}
},
splitLine: {
lineStyle: {
color: '#484753'
}
},
splitArea: {
areaStyle: {
color: ['rgba(255,255,255,0.02)', 'rgba(255,255,255,0.05)']
}
},
minorSplitLine: {
lineStyle: {
color: '#20203B'
}
}
};
};
var colorPalette = ['#4992ff', '#7cffb2', '#fddd60', '#ff6e76', '#58d9f9', '#05c091', '#ff8a45', '#8d48e3', '#dd79ff'];
var theme = {
darkMode: true,
color: colorPalette,
backgroundColor: backgroundColor,
axisPointer: {
lineStyle: {
color: '#817f91'
},
crossStyle: {
color: '#817f91'
},
label: {
// TODO Contrast of label backgorundColor
color: '#fff'
}
},
legend: {
textStyle: {
color: contrastColor
}
},
textStyle: {
color: contrastColor
},
title: {
textStyle: {
color: '#EEF1FA'
},
subtextStyle: {
color: '#B9B8CE'
}
},
toolbox: {
iconStyle: {
borderColor: contrastColor
}
},
dataZoom: {
borderColor: '#71708A',
textStyle: {
color: contrastColor
},
brushStyle: {
color: 'rgba(135,163,206,0.3)'
},
handleStyle: {
color: '#353450',
borderColor: '#C5CBE3'
},
moveHandleStyle: {
color: '#B0B6C3',
opacity: 0.3
},
fillerColor: 'rgba(135,163,206,0.2)',
emphasis: {
handleStyle: {
borderColor: '#91B7F2',
color: '#4D587D'
},
moveHandleStyle: {
color: '#636D9A',
opacity: 0.7
}
},
dataBackground: {
lineStyle: {
color: '#71708A',
width: 1
},
areaStyle: {
color: '#71708A'
}
},
selectedDataBackground: {
lineStyle: {
color: '#87A3CE'
},
areaStyle: {
color: '#87A3CE'
}
}
},
visualMap: {
textStyle: {
color: contrastColor
}
},
timeline: {
lineStyle: {
color: contrastColor
},
label: {
color: contrastColor
},
controlStyle: {
color: contrastColor,
borderColor: contrastColor
}
},
calendar: {
itemStyle: {
color: backgroundColor
},
dayLabel: {
color: contrastColor
},
monthLabel: {
color: contrastColor
},
yearLabel: {
color: contrastColor
}
},
timeAxis: axisCommon(),
logAxis: axisCommon(),
valueAxis: axisCommon(),
categoryAxis: axisCommon(),
line: {
symbol: 'circle'
},
graph: {
color: colorPalette
},
gauge: {
title: {
color: contrastColor
},
axisLine: {
lineStyle: {
color: [[1, 'rgba(207,212,219,0.2)']]
}
},
axisLabel: {
color: contrastColor
},
detail: {
color: '#EEF1FA'
}
},
candlestick: {
itemStyle: {
color: '#f64e56',
color0: '#54ea92',
borderColor: '#f64e56',
borderColor0: '#54ea92' // borderColor: '#ca2824',
// borderColor0: '#09a443'
}
}
};
theme.categoryAxis.splitLine.show = false;
function parseXML(svg) {
if (isString(svg)) {
var parser = new DOMParser();
svg = parser.parseFromString(svg, 'text/xml');
}
var svgNode = svg;
if (svgNode.nodeType === 9) {
svgNode = svgNode.firstChild;
}
while (svgNode.nodeName.toLowerCase() !== 'svg' || svgNode.nodeType !== 1) {
svgNode = svgNode.nextSibling;
}
return svgNode;
}
var storage = createHashMap();
var mapDataStorage = {
/**
* Compatible with previous `echarts.registerMap`.
* @usage
* ```js
* $.get('USA.json', function (geoJson) {
* echarts.registerMap('USA', geoJson);
* // Or
* echarts.registerMap('USA', {
* geoJson: geoJson,
* specialAreas: {}
* })
* });
*
* $.get('airport.svg', function (svg) {
* echarts.registerMap('airport', {
* svg: svg
* }
* });
*
* echarts.registerMap('eu', [
* {svg: eu-topographic.svg},
* {geoJSON: eu.json}
* ])
* ```
*/
registerMap: function (mapName, rawDef, rawSpecialAreas) {
var records;
if (isArray(rawDef)) {
records = rawDef;
} else if (rawDef.svg) {
records = [{
type: 'svg',
source: rawDef.svg,
specialAreas: rawDef.specialAreas
}];
} else {
// Backward compatibility.
var geoSource = rawDef.geoJson || rawDef.geoJSON;
if (geoSource && !rawDef.features) {
rawSpecialAreas = rawDef.specialAreas;
rawDef = geoSource;
}
records = [{
type: 'geoJSON',
source: rawDef,
specialAreas: rawSpecialAreas
}];
}
each(records, function (record) {
var type = record.type;
type === 'geoJson' && (type = record.type = 'geoJSON');
var parse = parsers[type];
if ("development" !== 'production') {
assert(parse, 'Illegal map type: ' + type);
}
parse(record);
});
return storage.set(mapName, records);
},
retrieveMap: function (mapName) {
return storage.get(mapName);
}
};
var parsers = {
geoJSON: function (record) {
var source = record.source;
record.geoJSON = !isString(source) ? source : typeof JSON !== 'undefined' && JSON.parse ? JSON.parse(source) : new Function('return (' + source + ');')();
},
// Only perform parse to XML object here, which might be time
// consiming for large SVG.
// Although convert XML to zrender element is also time consiming,
// if we do it here, the clone of zrender elements has to be
// required. So we do it once for each geo instance, util real
// performance issues call for optimizing it.
svg: function (record) {
record.svgXML = parseXML(record.source);
}
};
/**
* Usage of query:
* `chart.on('click', query, handler);`
* The `query` can be:
* + The component type query string, only `mainType` or `mainType.subType`,
* like: 'xAxis', 'series', 'xAxis.category' or 'series.line'.
* + The component query object, like:
* `{seriesIndex: 2}`, `{seriesName: 'xx'}`, `{seriesId: 'some'}`,
* `{xAxisIndex: 2}`, `{xAxisName: 'xx'}`, `{xAxisId: 'some'}`.
* + The data query object, like:
* `{dataIndex: 123}`, `{dataType: 'link'}`, `{name: 'some'}`.
* + The other query object (cmponent customized query), like:
* `{element: 'some'}` (only available in custom series).
*
* Caveat: If a prop in the `query` object is `null/undefined`, it is the
* same as there is no such prop in the `query` object.
*/
var ECEventProcessor =
/** @class */
function () {
function ECEventProcessor() {}
ECEventProcessor.prototype.normalizeQuery = function (query) {
var cptQuery = {};
var dataQuery = {};
var otherQuery = {}; // `query` is `mainType` or `mainType.subType` of component.
if (isString(query)) {
var condCptType = parseClassType(query); // `.main` and `.sub` may be ''.
cptQuery.mainType = condCptType.main || null;
cptQuery.subType = condCptType.sub || null;
} // `query` is an object, convert to {mainType, index, name, id}.
else {
// `xxxIndex`, `xxxName`, `xxxId`, `name`, `dataIndex`, `dataType` is reserved,
// can not be used in `compomentModel.filterForExposedEvent`.
var suffixes_1 = ['Index', 'Name', 'Id'];
var dataKeys_1 = {
name: 1,
dataIndex: 1,
dataType: 1
};
each(query, function (val, key) {
var reserved = false;
for (var i = 0; i < suffixes_1.length; i++) {
var propSuffix = suffixes_1[i];
var suffixPos = key.lastIndexOf(propSuffix);
if (suffixPos > 0 && suffixPos === key.length - propSuffix.length) {
var mainType = key.slice(0, suffixPos); // Consider `dataIndex`.
if (mainType !== 'data') {
cptQuery.mainType = mainType;
cptQuery[propSuffix.toLowerCase()] = val;
reserved = true;
}
}
}
if (dataKeys_1.hasOwnProperty(key)) {
dataQuery[key] = val;
reserved = true;
}
if (!reserved) {
otherQuery[key] = val;
}
});
}
return {
cptQuery: cptQuery,
dataQuery: dataQuery,
otherQuery: otherQuery
};
};
ECEventProcessor.prototype.filter = function (eventType, query) {
// They should be assigned before each trigger call.
var eventInfo = this.eventInfo;
if (!eventInfo) {
return true;
}
var targetEl = eventInfo.targetEl;
var packedEvent = eventInfo.packedEvent;
var model = eventInfo.model;
var view = eventInfo.view; // For event like 'globalout'.
if (!model || !view) {
return true;
}
var cptQuery = query.cptQuery;
var dataQuery = query.dataQuery;
return check(cptQuery, model, 'mainType') && check(cptQuery, model, 'subType') && check(cptQuery, model, 'index', 'componentIndex') && check(cptQuery, model, 'name') && check(cptQuery, model, 'id') && check(dataQuery, packedEvent, 'name') && check(dataQuery, packedEvent, 'dataIndex') && check(dataQuery, packedEvent, 'dataType') && (!view.filterForExposedEvent || view.filterForExposedEvent(eventType, query.otherQuery, targetEl, packedEvent));
function check(query, host, prop, propOnHost) {
return query[prop] == null || host[propOnHost || prop] === query[prop];
}
};
ECEventProcessor.prototype.afterTrigger = function () {
// Make sure the eventInfo wont be used in next trigger.
this.eventInfo = null;
};
return ECEventProcessor;
}();
var seriesSymbolTask = {
createOnAllSeries: true,
// For legend.
performRawSeries: true,
reset: function (seriesModel, ecModel) {
var data = seriesModel.getData();
if (seriesModel.legendSymbol) {
data.setVisual('legendSymbol', seriesModel.legendSymbol);
}
if (!seriesModel.hasSymbolVisual) {
return;
}
var symbolType = seriesModel.get('symbol');
var symbolSize = seriesModel.get('symbolSize');
var keepAspect = seriesModel.get('symbolKeepAspect');
var symbolRotate = seriesModel.get('symbolRotate');
var hasSymbolTypeCallback = isFunction(symbolType);
var hasSymbolSizeCallback = isFunction(symbolSize);
var hasSymbolRotateCallback = isFunction(symbolRotate);
var hasCallback = hasSymbolTypeCallback || hasSymbolSizeCallback || hasSymbolRotateCallback;
var seriesSymbol = !hasSymbolTypeCallback && symbolType ? symbolType : seriesModel.defaultSymbol;
var seriesSymbolSize = !hasSymbolSizeCallback ? symbolSize : null;
var seriesSymbolRotate = !hasSymbolRotateCallback ? symbolRotate : null;
data.setVisual({
legendSymbol: seriesModel.legendSymbol || seriesSymbol,
// If seting callback functions on `symbol` or `symbolSize`, for simplicity and avoiding
// to bring trouble, we do not pick a reuslt from one of its calling on data item here,
// but just use the default value. Callback on `symbol` or `symbolSize` is convenient in
// some cases but generally it is not recommanded.
symbol: seriesSymbol,
symbolSize: seriesSymbolSize,
symbolKeepAspect: keepAspect,
symbolRotate: seriesSymbolRotate
}); // Only visible series has each data be visual encoded
if (ecModel.isSeriesFiltered(seriesModel)) {
return;
}
function dataEach(data, idx) {
var rawValue = seriesModel.getRawValue(idx);
var params = seriesModel.getDataParams(idx);
hasSymbolTypeCallback && data.setItemVisual(idx, 'symbol', symbolType(rawValue, params));
hasSymbolSizeCallback && data.setItemVisual(idx, 'symbolSize', symbolSize(rawValue, params));
hasSymbolRotateCallback && data.setItemVisual(idx, 'symbolRotate', symbolRotate(rawValue, params));
}
return {
dataEach: hasCallback ? dataEach : null
};
}
};
var dataSymbolTask = {
createOnAllSeries: true,
// For legend.
performRawSeries: true,
reset: function (seriesModel, ecModel) {
if (!seriesModel.hasSymbolVisual) {
return;
} // Only visible series has each data be visual encoded
if (ecModel.isSeriesFiltered(seriesModel)) {
return;
}
var data = seriesModel.getData();
function dataEach(data, idx) {
var itemModel = data.getItemModel(idx);
var itemSymbolType = itemModel.getShallow('symbol', true);
var itemSymbolSize = itemModel.getShallow('symbolSize', true);
var itemSymbolRotate = itemModel.getShallow('symbolRotate', true);
var itemSymbolKeepAspect = itemModel.getShallow('symbolKeepAspect', true); // If has item symbol
if (itemSymbolType != null) {
data.setItemVisual(idx, 'symbol', itemSymbolType);
}
if (itemSymbolSize != null) {
// PENDING Transform symbolSize ?
data.setItemVisual(idx, 'symbolSize', itemSymbolSize);
}
if (itemSymbolRotate != null) {
data.setItemVisual(idx, 'symbolRotate', itemSymbolRotate);
}
if (itemSymbolKeepAspect != null) {
data.setItemVisual(idx, 'symbolKeepAspect', itemSymbolKeepAspect);
}
}
return {
dataEach: data.hasItemOption ? dataEach : null
};
}
};
/*
* 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.
*/
function getItemVisualFromData(data, dataIndex, key) {
switch (key) {
case 'color':
var style = data.getItemVisual(dataIndex, 'style');
return style[data.getVisual('drawType')];
case 'opacity':
return data.getItemVisual(dataIndex, 'style').opacity;
case 'symbol':
case 'symbolSize':
case 'liftZ':
return data.getItemVisual(dataIndex, key);
default:
if ("development" !== 'production') {
console.warn("Unknown visual type " + key);
}
}
}
function getVisualFromData(data, key) {
switch (key) {
case 'color':
var style = data.getVisual('style');
return style[data.getVisual('drawType')];
case 'opacity':
return data.getVisual('style').opacity;
case 'symbol':
case 'symbolSize':
case 'liftZ':
return data.getVisual(key);
default:
if ("development" !== 'production') {
console.warn("Unknown visual type " + key);
}
}
}
var PI2$6 = Math.PI * 2;
var CMD$3 = PathProxy.CMD;
var DEFAULT_SEARCH_SPACE = ['top', 'right', 'bottom', 'left'];
function getCandidateAnchor(pos, distance, rect, outPt, outDir) {
var width = rect.width;
var height = rect.height;
switch (pos) {
case 'top':
outPt.set(rect.x + width / 2, rect.y - distance);
outDir.set(0, -1);
break;
case 'bottom':
outPt.set(rect.x + width / 2, rect.y + height + distance);
outDir.set(0, 1);
break;
case 'left':
outPt.set(rect.x - distance, rect.y + height / 2);
outDir.set(-1, 0);
break;
case 'right':
outPt.set(rect.x + width + distance, rect.y + height / 2);
outDir.set(1, 0);
break;
}
}
function projectPointToArc(cx, cy, r, startAngle, endAngle, anticlockwise, x, y, out) {
x -= cx;
y -= cy;
var d = Math.sqrt(x * x + y * y);
x /= d;
y /= d; // Intersect point.
var ox = x * r + cx;
var oy = y * r + cy;
if (Math.abs(startAngle - endAngle) % PI2$6 < 1e-4) {
// Is a circle
out[0] = ox;
out[1] = oy;
return d - r;
}
if (anticlockwise) {
var tmp = startAngle;
startAngle = normalizeRadian(endAngle);
endAngle = normalizeRadian(tmp);
} else {
startAngle = normalizeRadian(startAngle);
endAngle = normalizeRadian(endAngle);
}
if (startAngle > endAngle) {
endAngle += PI2$6;
}
var angle = Math.atan2(y, x);
if (angle < 0) {
angle += PI2$6;
}
if (angle >= startAngle && angle <= endAngle || angle + PI2$6 >= startAngle && angle + PI2$6 <= endAngle) {
// Project point is on the arc.
out[0] = ox;
out[1] = oy;
return d - r;
}
var x1 = r * Math.cos(startAngle) + cx;
var y1 = r * Math.sin(startAngle) + cy;
var x2 = r * Math.cos(endAngle) + cx;
var y2 = r * Math.sin(endAngle) + cy;
var d1 = (x1 - x) * (x1 - x) + (y1 - y) * (y1 - y);
var d2 = (x2 - x) * (x2 - x) + (y2 - y) * (y2 - y);
if (d1 < d2) {
out[0] = x1;
out[1] = y1;
return Math.sqrt(d1);
} else {
out[0] = x2;
out[1] = y2;
return Math.sqrt(d2);
}
}
function projectPointToLine(x1, y1, x2, y2, x, y, out, limitToEnds) {
var dx = x - x1;
var dy = y - y1;
var dx1 = x2 - x1;
var dy1 = y2 - y1;
var lineLen = Math.sqrt(dx1 * dx1 + dy1 * dy1);
dx1 /= lineLen;
dy1 /= lineLen; // dot product
var projectedLen = dx * dx1 + dy * dy1;
var t = projectedLen / lineLen;
if (limitToEnds) {
t = Math.min(Math.max(t, 0), 1);
}
t *= lineLen;
var ox = out[0] = x1 + t * dx1;
var oy = out[1] = y1 + t * dy1;
return Math.sqrt((ox - x) * (ox - x) + (oy - y) * (oy - y));
}
function projectPointToRect(x1, y1, width, height, x, y, out) {
if (width < 0) {
x1 = x1 + width;
width = -width;
}
if (height < 0) {
y1 = y1 + height;
height = -height;
}
var x2 = x1 + width;
var y2 = y1 + height;
var ox = out[0] = Math.min(Math.max(x, x1), x2);
var oy = out[1] = Math.min(Math.max(y, y1), y2);
return Math.sqrt((ox - x) * (ox - x) + (oy - y) * (oy - y));
}
var tmpPt = [];
function nearestPointOnRect(pt, rect, out) {
var dist = projectPointToRect(rect.x, rect.y, rect.width, rect.height, pt.x, pt.y, tmpPt);
out.set(tmpPt[0], tmpPt[1]);
return dist;
}
/**
* Calculate min distance corresponding point.
* This method won't evaluate if point is in the path.
*/
function nearestPointOnPath(pt, path, out) {
var xi = 0;
var yi = 0;
var x0 = 0;
var y0 = 0;
var x1;
var y1;
var minDist = Infinity;
var data = path.data;
var x = pt.x;
var y = pt.y;
for (var i = 0; i < data.length;) {
var cmd = data[i++];
if (i === 1) {
xi = data[i];
yi = data[i + 1];
x0 = xi;
y0 = yi;
}
var d = minDist;
switch (cmd) {
case CMD$3.M:
// moveTo 命令重新创建一个新的 subpath, 并且更新新的起点
// 在 closePath 的时候使用
x0 = data[i++];
y0 = data[i++];
xi = x0;
yi = y0;
break;
case CMD$3.L:
d = projectPointToLine(xi, yi, data[i], data[i + 1], x, y, tmpPt, true);
xi = data[i++];
yi = data[i++];
break;
case CMD$3.C:
d = cubicProjectPoint(xi, yi, data[i++], data[i++], data[i++], data[i++], data[i], data[i + 1], x, y, tmpPt);
xi = data[i++];
yi = data[i++];
break;
case CMD$3.Q:
d = quadraticProjectPoint(xi, yi, data[i++], data[i++], data[i], data[i + 1], x, y, tmpPt);
xi = data[i++];
yi = data[i++];
break;
case CMD$3.A:
// TODO Arc 判断的开销比较大
var cx = data[i++];
var cy = data[i++];
var rx = data[i++];
var ry = data[i++];
var theta = data[i++];
var dTheta = data[i++]; // TODO Arc 旋转
i += 1;
var anticlockwise = !!(1 - data[i++]);
x1 = Math.cos(theta) * rx + cx;
y1 = Math.sin(theta) * ry + cy; // 不是直接使用 arc 命令
if (i <= 1) {
// 第一个命令起点还未定义
x0 = x1;
y0 = y1;
} // zr 使用scale来模拟椭圆, 这里也对x做一定的缩放
var _x = (x - cx) * ry / rx + cx;
d = projectPointToArc(cx, cy, ry, theta, theta + dTheta, anticlockwise, _x, y, tmpPt);
xi = Math.cos(theta + dTheta) * rx + cx;
yi = Math.sin(theta + dTheta) * ry + cy;
break;
case CMD$3.R:
x0 = xi = data[i++];
y0 = yi = data[i++];
var width = data[i++];
var height = data[i++];
d = projectPointToRect(x0, y0, width, height, x, y, tmpPt);
break;
case CMD$3.Z:
d = projectPointToLine(xi, yi, x0, y0, x, y, tmpPt, true);
xi = x0;
yi = y0;
break;
}
if (d < minDist) {
minDist = d;
out.set(tmpPt[0], tmpPt[1]);
}
}
return minDist;
} // Temporal varible for intermediate usage.
var pt0 = new Point();
var pt1 = new Point();
var pt2 = new Point();
var dir = new Point();
var dir2 = new Point();
/**
* Calculate a proper guide line based on the label position and graphic element definition
* @param label
* @param labelRect
* @param target
* @param targetRect
*/
function updateLabelLinePoints(target, labelLineModel) {
if (!target) {
return;
}
var labelLine = target.getTextGuideLine();
var label = target.getTextContent(); // Needs to create text guide in each charts.
if (!(label && labelLine)) {
return;
}
var labelGuideConfig = target.textGuideLineConfig || {};
var points = [[0, 0], [0, 0], [0, 0]];
var searchSpace = labelGuideConfig.candidates || DEFAULT_SEARCH_SPACE;
var labelRect = label.getBoundingRect().clone();
labelRect.applyTransform(label.getComputedTransform());
var minDist = Infinity;
var anchorPoint = labelGuideConfig.anchor;
var targetTransform = target.getComputedTransform();
var targetInversedTransform = targetTransform && invert([], targetTransform);
var len = labelLineModel.get('length2') || 0;
if (anchorPoint) {
pt2.copy(anchorPoint);
}
for (var i = 0; i < searchSpace.length; i++) {
var candidate = searchSpace[i];
getCandidateAnchor(candidate, 0, labelRect, pt0, dir);
Point.scaleAndAdd(pt1, pt0, dir, len); // Transform to target coord space.
pt1.transform(targetInversedTransform); // Note: getBoundingRect will ensure the `path` being created.
var boundingRect = target.getBoundingRect();
var dist = anchorPoint ? anchorPoint.distance(pt1) : target instanceof Path ? nearestPointOnPath(pt1, target.path, pt2) : nearestPointOnRect(pt1, boundingRect, pt2); // TODO pt2 is in the path
if (dist < minDist) {
minDist = dist; // Transform back to global space.
pt1.transform(targetTransform);
pt2.transform(targetTransform);
pt2.toArray(points[0]);
pt1.toArray(points[1]);
pt0.toArray(points[2]);
}
}
limitTurnAngle(points, labelLineModel.get('minTurnAngle'));
labelLine.setShape({
points: points
});
} // Temporal variable for the limitTurnAngle function
var tmpArr = [];
var tmpProjPoint = new Point();
/**
* Reduce the line segment attached to the label to limit the turn angle between two segments.
* @param linePoints
* @param minTurnAngle Radian of minimum turn angle. 0 - 180
*/
function limitTurnAngle(linePoints, minTurnAngle) {
if (!(minTurnAngle <= 180 && minTurnAngle > 0)) {
return;
}
minTurnAngle = minTurnAngle / 180 * Math.PI; // The line points can be
// /pt1----pt2 (label)
// /
// pt0/
pt0.fromArray(linePoints[0]);
pt1.fromArray(linePoints[1]);
pt2.fromArray(linePoints[2]);
Point.sub(dir, pt0, pt1);
Point.sub(dir2, pt2, pt1);
var len1 = dir.len();
var len2 = dir2.len();
if (len1 < 1e-3 || len2 < 1e-3) {
return;
}
dir.scale(1 / len1);
dir2.scale(1 / len2);
var angleCos = dir.dot(dir2);
var minTurnAngleCos = Math.cos(minTurnAngle);
if (minTurnAngleCos < angleCos) {
// Smaller than minTurnAngle
// Calculate project point of pt0 on pt1-pt2
var d = projectPointToLine(pt1.x, pt1.y, pt2.x, pt2.y, pt0.x, pt0.y, tmpArr, false);
tmpProjPoint.fromArray(tmpArr); // Calculate new projected length with limited minTurnAngle and get the new connect point
tmpProjPoint.scaleAndAdd(dir2, d / Math.tan(Math.PI - minTurnAngle)); // Limit the new calculated connect point between pt1 and pt2.
var t = pt2.x !== pt1.x ? (tmpProjPoint.x - pt1.x) / (pt2.x - pt1.x) : (tmpProjPoint.y - pt1.y) / (pt2.y - pt1.y);
if (isNaN(t)) {
return;
}
if (t < 0) {
Point.copy(tmpProjPoint, pt1);
} else if (t > 1) {
Point.copy(tmpProjPoint, pt2);
}
tmpProjPoint.toArray(linePoints[1]);
}
}
/**
* Limit the angle of line and the surface
* @param maxSurfaceAngle Radian of minimum turn angle. 0 - 180. 0 is same direction to normal. 180 is opposite
*/
function limitSurfaceAngle(linePoints, surfaceNormal, maxSurfaceAngle) {
if (!(maxSurfaceAngle <= 180 && maxSurfaceAngle > 0)) {
return;
}
maxSurfaceAngle = maxSurfaceAngle / 180 * Math.PI;
pt0.fromArray(linePoints[0]);
pt1.fromArray(linePoints[1]);
pt2.fromArray(linePoints[2]);
Point.sub(dir, pt1, pt0);
Point.sub(dir2, pt2, pt1);
var len1 = dir.len();
var len2 = dir2.len();
if (len1 < 1e-3 || len2 < 1e-3) {
return;
}
dir.scale(1 / len1);
dir2.scale(1 / len2);
var angleCos = dir.dot(surfaceNormal);
var maxSurfaceAngleCos = Math.cos(maxSurfaceAngle);
if (angleCos < maxSurfaceAngleCos) {
// Calculate project point of pt0 on pt1-pt2
var d = projectPointToLine(pt1.x, pt1.y, pt2.x, pt2.y, pt0.x, pt0.y, tmpArr, false);
tmpProjPoint.fromArray(tmpArr);
var HALF_PI = Math.PI / 2;
var angle2 = Math.acos(dir2.dot(surfaceNormal));
var newAngle = HALF_PI + angle2 - maxSurfaceAngle;
if (newAngle >= HALF_PI) {
// parallel
Point.copy(tmpProjPoint, pt2);
} else {
// Calculate new projected length with limited minTurnAngle and get the new connect point
tmpProjPoint.scaleAndAdd(dir2, d / Math.tan(Math.PI / 2 - newAngle)); // Limit the new calculated connect point between pt1 and pt2.
var t = pt2.x !== pt1.x ? (tmpProjPoint.x - pt1.x) / (pt2.x - pt1.x) : (tmpProjPoint.y - pt1.y) / (pt2.y - pt1.y);
if (isNaN(t)) {
return;
}
if (t < 0) {
Point.copy(tmpProjPoint, pt1);
} else if (t > 1) {
Point.copy(tmpProjPoint, pt2);
}
}
tmpProjPoint.toArray(linePoints[1]);
}
}
function setLabelLineState(labelLine, ignore, stateName, stateModel) {
var isNormal = stateName === 'normal';
var stateObj = isNormal ? labelLine : labelLine.ensureState(stateName); // Make sure display.
stateObj.ignore = ignore; // Set smooth
var smooth = stateModel.get('smooth');
if (smooth && smooth === true) {
smooth = 0.3;
}
stateObj.shape = stateObj.shape || {};
if (smooth > 0) {
stateObj.shape.smooth = smooth;
}
var styleObj = stateModel.getModel('lineStyle').getLineStyle();
isNormal ? labelLine.useStyle(styleObj) : stateObj.style = styleObj;
}
function buildLabelLinePath(path, shape) {
var smooth = shape.smooth;
var points = shape.points;
if (!points) {
return;
}
path.moveTo(points[0][0], points[0][1]);
if (smooth > 0 && points.length >= 3) {
var len1 = dist(points[0], points[1]);
var len2 = dist(points[1], points[2]);
if (!len1 || !len2) {
path.lineTo(points[1][0], points[1][1]);
path.lineTo(points[2][0], points[2][1]);
return;
}
var moveLen = Math.min(len1, len2) * smooth;
var midPoint0 = lerp([], points[1], points[0], moveLen / len1);
var midPoint2 = lerp([], points[1], points[2], moveLen / len2);
var midPoint1 = lerp([], midPoint0, midPoint2, 0.5);
path.bezierCurveTo(midPoint0[0], midPoint0[1], midPoint0[0], midPoint0[1], midPoint1[0], midPoint1[1]);
path.bezierCurveTo(midPoint2[0], midPoint2[1], midPoint2[0], midPoint2[1], points[2][0], points[2][1]);
} else {
for (var i = 1; i < points.length; i++) {
path.lineTo(points[i][0], points[i][1]);
}
}
}
/**
* Create a label line if necessary and set it's style.
*/
function setLabelLineStyle(targetEl, statesModels, defaultStyle) {
var labelLine = targetEl.getTextGuideLine();
var label = targetEl.getTextContent();
if (!label) {
// Not show label line if there is no label.
if (labelLine) {
targetEl.removeTextGuideLine();
}
return;
}
var normalModel = statesModels.normal;
var showNormal = normalModel.get('show');
var labelIgnoreNormal = label.ignore;
for (var i = 0; i < DISPLAY_STATES.length; i++) {
var stateName = DISPLAY_STATES[i];
var stateModel = statesModels[stateName];
var isNormal = stateName === 'normal';
if (stateModel) {
var stateShow = stateModel.get('show');
var isLabelIgnored = isNormal ? labelIgnoreNormal : retrieve2(label.states[stateName] && label.states[stateName].ignore, labelIgnoreNormal);
if (isLabelIgnored // Not show when label is not shown in this state.
|| !retrieve2(stateShow, showNormal) // Use normal state by default if not set.
) {
var stateObj = isNormal ? labelLine : labelLine && labelLine.states.normal;
if (stateObj) {
stateObj.ignore = true;
}
continue;
} // Create labelLine if not exists
if (!labelLine) {
labelLine = new Polyline();
targetEl.setTextGuideLine(labelLine); // Reset state of normal because it's new created.
// NOTE: NORMAL should always been the first!
if (!isNormal && (labelIgnoreNormal || !showNormal)) {
setLabelLineState(labelLine, true, 'normal', statesModels.normal);
} // Use same state proxy.
if (targetEl.stateProxy) {
labelLine.stateProxy = targetEl.stateProxy;
}
}
setLabelLineState(labelLine, false, stateName, stateModel);
}
}
if (labelLine) {
defaults(labelLine.style, defaultStyle); // Not fill.
labelLine.style.fill = null;
var showAbove = normalModel.get('showAbove');
var labelLineConfig = targetEl.textGuideLineConfig = targetEl.textGuideLineConfig || {};
labelLineConfig.showAbove = showAbove || false; // Custom the buildPath.
labelLine.buildPath = buildLabelLinePath;
}
}
function getLabelLineStatesModels(itemModel, labelLineName) {
labelLineName = labelLineName || 'labelLine';
var statesModels = {
normal: itemModel.getModel(labelLineName)
};
for (var i = 0; i < SPECIAL_STATES.length; i++) {
var stateName = SPECIAL_STATES[i];
statesModels[stateName] = itemModel.getModel([stateName, labelLineName]);
}
return statesModels;
}
function prepareLayoutList(input) {
var list = [];
for (var i = 0; i < input.length; i++) {
var rawItem = input[i];
if (rawItem.defaultAttr.ignore) {
continue;
}
var label = rawItem.label;
var transform = label.getComputedTransform(); // NOTE: Get bounding rect after getComputedTransform, or label may not been updated by the host el.
var localRect = label.getBoundingRect();
var isAxisAligned = !transform || transform[1] < 1e-5 && transform[2] < 1e-5;
var minMargin = label.style.margin || 0;
var globalRect = localRect.clone();
globalRect.applyTransform(transform);
globalRect.x -= minMargin / 2;
globalRect.y -= minMargin / 2;
globalRect.width += minMargin;
globalRect.height += minMargin;
var obb = isAxisAligned ? new OrientedBoundingRect(localRect, transform) : null;
list.push({
label: label,
labelLine: rawItem.labelLine,
rect: globalRect,
localRect: localRect,
obb: obb,
priority: rawItem.priority,
defaultAttr: rawItem.defaultAttr,
layoutOption: rawItem.computedLayoutOption,
axisAligned: isAxisAligned,
transform: transform
});
}
return list;
}
function shiftLayout(list, xyDim, sizeDim, minBound, maxBound, balanceShift) {
var len = list.length;
if (len < 2) {
return;
}
list.sort(function (a, b) {
return a.rect[xyDim] - b.rect[xyDim];
});
var lastPos = 0;
var delta;
var adjusted = false;
var totalShifts = 0;
for (var i = 0; i < len; i++) {
var item = list[i];
var rect = item.rect;
delta = rect[xyDim] - lastPos;
if (delta < 0) {
// shiftForward(i, len, -delta);
rect[xyDim] -= delta;
item.label[xyDim] -= delta;
adjusted = true;
}
var shift = Math.max(-delta, 0);
totalShifts += shift;
lastPos = rect[xyDim] + rect[sizeDim];
}
if (totalShifts > 0 && balanceShift) {
// Shift back to make the distribution more equally.
shiftList(-totalShifts / len, 0, len);
} // TODO bleedMargin?
var first = list[0];
var last = list[len - 1];
var minGap;
var maxGap;
updateMinMaxGap(); // If ends exceed two bounds, squeeze at most 80%, then take the gap of two bounds.
minGap < 0 && squeezeGaps(-minGap, 0.8);
maxGap < 0 && squeezeGaps(maxGap, 0.8);
updateMinMaxGap();
takeBoundsGap(minGap, maxGap, 1);
takeBoundsGap(maxGap, minGap, -1); // Handle bailout when there is not enough space.
updateMinMaxGap();
if (minGap < 0) {
squeezeWhenBailout(-minGap);
}
if (maxGap < 0) {
squeezeWhenBailout(maxGap);
}
function updateMinMaxGap() {
minGap = first.rect[xyDim] - minBound;
maxGap = maxBound - last.rect[xyDim] - last.rect[sizeDim];
}
function takeBoundsGap(gapThisBound, gapOtherBound, moveDir) {
if (gapThisBound < 0) {
// Move from other gap if can.
var moveFromMaxGap = Math.min(gapOtherBound, -gapThisBound);
if (moveFromMaxGap > 0) {
shiftList(moveFromMaxGap * moveDir, 0, len);
var remained = moveFromMaxGap + gapThisBound;
if (remained < 0) {
squeezeGaps(-remained * moveDir, 1);
}
} else {
squeezeGaps(-gapThisBound * moveDir, 1);
}
}
}
function shiftList(delta, start, end) {
if (delta !== 0) {
adjusted = true;
}
for (var i = start; i < end; i++) {
var item = list[i];
var rect = item.rect;
rect[xyDim] += delta;
item.label[xyDim] += delta;
}
} // Squeeze gaps if the labels exceed margin.
function squeezeGaps(delta, maxSqeezePercent) {
var gaps = [];
var totalGaps = 0;
for (var i = 1; i < len; i++) {
var prevItemRect = list[i - 1].rect;
var gap = Math.max(list[i].rect[xyDim] - prevItemRect[xyDim] - prevItemRect[sizeDim], 0);
gaps.push(gap);
totalGaps += gap;
}
if (!totalGaps) {
return;
}
var squeezePercent = Math.min(Math.abs(delta) / totalGaps, maxSqeezePercent);
if (delta > 0) {
for (var i = 0; i < len - 1; i++) {
// Distribute the shift delta to all gaps.
var movement = gaps[i] * squeezePercent; // Forward
shiftList(movement, 0, i + 1);
}
} else {
// Backward
for (var i = len - 1; i > 0; i--) {
// Distribute the shift delta to all gaps.
var movement = gaps[i - 1] * squeezePercent;
shiftList(-movement, i, len);
}
}
}
/**
* Squeeze to allow overlap if there is no more space available.
* Let other overlapping strategy like hideOverlap do the job instead of keep exceeding the bounds.
*/
function squeezeWhenBailout(delta) {
var dir = delta < 0 ? -1 : 1;
delta = Math.abs(delta);
var moveForEachLabel = Math.ceil(delta / (len - 1));
for (var i = 0; i < len - 1; i++) {
if (dir > 0) {
// Forward
shiftList(moveForEachLabel, 0, i + 1);
} else {
// Backward
shiftList(-moveForEachLabel, len - i - 1, len);
}
delta -= moveForEachLabel;
if (delta <= 0) {
return;
}
}
}
return adjusted;
}
/**
* Adjust labels on x direction to avoid overlap.
*/
function shiftLayoutOnX(list, leftBound, rightBound, // If average the shifts on all labels and add them to 0
// TODO: Not sure if should enable it.
// Pros: The angle of lines will distribute more equally
// Cons: In some layout. It may not what user wanted. like in pie. the label of last sector is usually changed unexpectedly.
balanceShift) {
return shiftLayout(list, 'x', 'width', leftBound, rightBound, balanceShift);
}
/**
* Adjust labels on y direction to avoid overlap.
*/
function shiftLayoutOnY(list, topBound, bottomBound, // If average the shifts on all labels and add them to 0
balanceShift) {
return shiftLayout(list, 'y', 'height', topBound, bottomBound, balanceShift);
}
function hideOverlap(labelList) {
var displayedLabels = []; // TODO, render overflow visible first, put in the displayedLabels.
labelList.sort(function (a, b) {
return b.priority - a.priority;
});
var globalRect = new BoundingRect(0, 0, 0, 0);
function hideEl(el) {
if (!el.ignore) {
// Show on emphasis.
var emphasisState = el.ensureState('emphasis');
if (emphasisState.ignore == null) {
emphasisState.ignore = false;
}
}
el.ignore = true;
}
for (var i = 0; i < labelList.length; i++) {
var labelItem = labelList[i];
var isAxisAligned = labelItem.axisAligned;
var localRect = labelItem.localRect;
var transform = labelItem.transform;
var label = labelItem.label;
var labelLine = labelItem.labelLine;
globalRect.copy(labelItem.rect); // Add a threshold because layout may be aligned precisely.
globalRect.width -= 0.1;
globalRect.height -= 0.1;
globalRect.x += 0.05;
globalRect.y += 0.05;
var obb = labelItem.obb;
var overlapped = false;
for (var j = 0; j < displayedLabels.length; j++) {
var existsTextCfg = displayedLabels[j]; // Fast rejection.
if (!globalRect.intersect(existsTextCfg.rect)) {
continue;
}
if (isAxisAligned && existsTextCfg.axisAligned) {
// Is overlapped
overlapped = true;
break;
}
if (!existsTextCfg.obb) {
// If self is not axis aligned. But other is.
existsTextCfg.obb = new OrientedBoundingRect(existsTextCfg.localRect, existsTextCfg.transform);
}
if (!obb) {
// If self is axis aligned. But other is not.
obb = new OrientedBoundingRect(localRect, transform);
}
if (obb.intersect(existsTextCfg.obb)) {
overlapped = true;
break;
}
} // TODO Callback to determine if this overlap should be handled?
if (overlapped) {
hideEl(label);
labelLine && hideEl(labelLine);
} else {
label.attr('ignore', labelItem.defaultAttr.ignore);
labelLine && labelLine.attr('ignore', labelItem.defaultAttr.labelGuideIgnore);
displayedLabels.push(labelItem);
}
}
}
function cloneArr(points) {
if (points) {
var newPoints = [];
for (var i = 0; i < points.length; i++) {
newPoints.push(points[i].slice());
}
return newPoints;
}
}
function prepareLayoutCallbackParams(labelItem, hostEl) {
var label = labelItem.label;
var labelLine = hostEl && hostEl.getTextGuideLine();
return {
dataIndex: labelItem.dataIndex,
dataType: labelItem.dataType,
seriesIndex: labelItem.seriesModel.seriesIndex,
text: labelItem.label.style.text,
rect: labelItem.hostRect,
labelRect: labelItem.rect,
// x: labelAttr.x,
// y: labelAttr.y,
align: label.style.align,
verticalAlign: label.style.verticalAlign,
labelLinePoints: cloneArr(labelLine && labelLine.shape.points)
};
}
var LABEL_OPTION_TO_STYLE_KEYS = ['align', 'verticalAlign', 'width', 'height', 'fontSize'];
var dummyTransformable = new Transformable();
var labelLayoutInnerStore = makeInner();
var labelLineAnimationStore = makeInner();
function extendWithKeys(target, source, keys) {
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (source[key] != null) {
target[key] = source[key];
}
}
}
var LABEL_LAYOUT_PROPS = ['x', 'y', 'rotation'];
var LabelManager =
/** @class */
function () {
function LabelManager() {
this._labelList = [];
this._chartViewList = [];
}
LabelManager.prototype.clearLabels = function () {
this._labelList = [];
this._chartViewList = [];
};
/**
* Add label to manager
*/
LabelManager.prototype._addLabel = function (dataIndex, dataType, seriesModel, label, layoutOption) {
var labelStyle = label.style;
var hostEl = label.__hostTarget;
var textConfig = hostEl.textConfig || {}; // TODO: If label is in other state.
var labelTransform = label.getComputedTransform();
var labelRect = label.getBoundingRect().plain();
BoundingRect.applyTransform(labelRect, labelRect, labelTransform);
if (labelTransform) {
dummyTransformable.setLocalTransform(labelTransform);
} else {
// Identity transform.
dummyTransformable.x = dummyTransformable.y = dummyTransformable.rotation = dummyTransformable.originX = dummyTransformable.originY = 0;
dummyTransformable.scaleX = dummyTransformable.scaleY = 1;
}
var host = label.__hostTarget;
var hostRect;
if (host) {
hostRect = host.getBoundingRect().plain();
var transform = host.getComputedTransform();
BoundingRect.applyTransform(hostRect, hostRect, transform);
}
var labelGuide = hostRect && host.getTextGuideLine();
this._labelList.push({
label: label,
labelLine: labelGuide,
seriesModel: seriesModel,
dataIndex: dataIndex,
dataType: dataType,
layoutOption: layoutOption,
computedLayoutOption: null,
rect: labelRect,
hostRect: hostRect,
// Label with lower priority will be hidden when overlapped
// Use rect size as default priority
priority: hostRect ? hostRect.width * hostRect.height : 0,
// Save default label attributes.
// For restore if developers want get back to default value in callback.
defaultAttr: {
ignore: label.ignore,
labelGuideIgnore: labelGuide && labelGuide.ignore,
x: dummyTransformable.x,
y: dummyTransformable.y,
rotation: dummyTransformable.rotation,
style: {
x: labelStyle.x,
y: labelStyle.y,
align: labelStyle.align,
verticalAlign: labelStyle.verticalAlign,
width: labelStyle.width,
height: labelStyle.height,
fontSize: labelStyle.fontSize
},
cursor: label.cursor,
attachedPos: textConfig.position,
attachedRot: textConfig.rotation
}
});
};
LabelManager.prototype.addLabelsOfSeries = function (chartView) {
var _this = this;
this._chartViewList.push(chartView);
var seriesModel = chartView.__model;
var layoutOption = seriesModel.get('labelLayout');
/**
* Ignore layouting if it's not specified anything.
*/
if (!(isFunction(layoutOption) || keys(layoutOption).length)) {
return;
}
chartView.group.traverse(function (child) {
if (child.ignore) {
return true; // Stop traverse descendants.
} // Only support label being hosted on graphic elements.
var textEl = child.getTextContent();
var ecData = getECData(child); // Can only attach the text on the element with dataIndex
if (textEl && !textEl.disableLabelLayout) {
_this._addLabel(ecData.dataIndex, ecData.dataType, seriesModel, textEl, layoutOption);
}
});
};
LabelManager.prototype.updateLayoutConfig = function (api) {
var width = api.getWidth();
var height = api.getHeight();
function createDragHandler(el, labelLineModel) {
return function () {
updateLabelLinePoints(el, labelLineModel);
};
}
for (var i = 0; i < this._labelList.length; i++) {
var labelItem = this._labelList[i];
var label = labelItem.label;
var hostEl = label.__hostTarget;
var defaultLabelAttr = labelItem.defaultAttr;
var layoutOption = void 0; // TODO A global layout option?
if (typeof labelItem.layoutOption === 'function') {
layoutOption = labelItem.layoutOption(prepareLayoutCallbackParams(labelItem, hostEl));
} else {
layoutOption = labelItem.layoutOption;
}
layoutOption = layoutOption || {};
labelItem.computedLayoutOption = layoutOption;
var degreeToRadian = Math.PI / 180;
if (hostEl) {
hostEl.setTextConfig({
// Force to set local false.
local: false,
// Ignore position and rotation config on the host el if x or y is changed.
position: layoutOption.x != null || layoutOption.y != null ? null : defaultLabelAttr.attachedPos,
// Ignore rotation config on the host el if rotation is changed.
rotation: layoutOption.rotate != null ? layoutOption.rotate * degreeToRadian : defaultLabelAttr.attachedRot,
offset: [layoutOption.dx || 0, layoutOption.dy || 0]
});
}
var needsUpdateLabelLine = false;
if (layoutOption.x != null) {
// TODO width of chart view.
label.x = parsePercent$1(layoutOption.x, width);
label.setStyle('x', 0); // Ignore movement in style. TODO: origin.
needsUpdateLabelLine = true;
} else {
label.x = defaultLabelAttr.x;
label.setStyle('x', defaultLabelAttr.style.x);
}
if (layoutOption.y != null) {
// TODO height of chart view.
label.y = parsePercent$1(layoutOption.y, height);
label.setStyle('y', 0); // Ignore movement in style.
needsUpdateLabelLine = true;
} else {
label.y = defaultLabelAttr.y;
label.setStyle('y', defaultLabelAttr.style.y);
}
if (layoutOption.labelLinePoints) {
var guideLine = hostEl.getTextGuideLine();
if (guideLine) {
guideLine.setShape({
points: layoutOption.labelLinePoints
}); // Not update
needsUpdateLabelLine = false;
}
}
var labelLayoutStore = labelLayoutInnerStore(label);
labelLayoutStore.needsUpdateLabelLine = needsUpdateLabelLine;
label.rotation = layoutOption.rotate != null ? layoutOption.rotate * degreeToRadian : defaultLabelAttr.rotation;
for (var k = 0; k < LABEL_OPTION_TO_STYLE_KEYS.length; k++) {
var key = LABEL_OPTION_TO_STYLE_KEYS[k];
label.setStyle(key, layoutOption[key] != null ? layoutOption[key] : defaultLabelAttr.style[key]);
}
if (layoutOption.draggable) {
label.draggable = true;
label.cursor = 'move';
if (hostEl) {
var hostModel = labelItem.seriesModel;
if (labelItem.dataIndex != null) {
var data = labelItem.seriesModel.getData(labelItem.dataType);
hostModel = data.getItemModel(labelItem.dataIndex);
}
label.on('drag', createDragHandler(hostEl, hostModel.getModel('labelLine')));
}
} else {
// TODO Other drag functions?
label.off('drag');
label.cursor = defaultLabelAttr.cursor;
}
}
};
LabelManager.prototype.layout = function (api) {
var width = api.getWidth();
var height = api.getHeight();
var labelList = prepareLayoutList(this._labelList);
var labelsNeedsAdjustOnX = filter(labelList, function (item) {
return item.layoutOption.moveOverlap === 'shiftX';
});
var labelsNeedsAdjustOnY = filter(labelList, function (item) {
return item.layoutOption.moveOverlap === 'shiftY';
});
shiftLayoutOnX(labelsNeedsAdjustOnX, 0, width);
shiftLayoutOnY(labelsNeedsAdjustOnY, 0, height);
var labelsNeedsHideOverlap = filter(labelList, function (item) {
return item.layoutOption.hideOverlap;
});
hideOverlap(labelsNeedsHideOverlap);
};
/**
* Process all labels. Not only labels with layoutOption.
*/
LabelManager.prototype.processLabelsOverall = function () {
var _this = this;
each(this._chartViewList, function (chartView) {
var seriesModel = chartView.__model;
var ignoreLabelLineUpdate = chartView.ignoreLabelLineUpdate;
var animationEnabled = seriesModel.isAnimationEnabled();
chartView.group.traverse(function (child) {
if (child.ignore) {
return true; // Stop traverse descendants.
}
var needsUpdateLabelLine = !ignoreLabelLineUpdate;
var label = child.getTextContent();
if (!needsUpdateLabelLine && label) {
needsUpdateLabelLine = labelLayoutInnerStore(label).needsUpdateLabelLine;
}
if (needsUpdateLabelLine) {
_this._updateLabelLine(child, seriesModel);
}
if (animationEnabled) {
_this._animateLabels(child, seriesModel);
}
});
});
};
LabelManager.prototype._updateLabelLine = function (el, seriesModel) {
// Only support label being hosted on graphic elements.
var textEl = el.getTextContent(); // Update label line style.
var ecData = getECData(el);
var dataIndex = ecData.dataIndex; // Only support labelLine on the labels represent data.
if (textEl && dataIndex != null) {
var data = seriesModel.getData(ecData.dataType);
var itemModel = data.getItemModel(dataIndex);
var defaultStyle = {};
var visualStyle = data.getItemVisual(dataIndex, 'style');
var visualType = data.getVisual('drawType'); // Default to be same with main color
defaultStyle.stroke = visualStyle[visualType];
var labelLineModel = itemModel.getModel('labelLine');
setLabelLineStyle(el, getLabelLineStatesModels(itemModel), defaultStyle);
updateLabelLinePoints(el, labelLineModel);
}
};
LabelManager.prototype._animateLabels = function (el, seriesModel) {
var textEl = el.getTextContent();
var guideLine = el.getTextGuideLine(); // Animate
if (textEl && !textEl.ignore && !textEl.invisible && !el.disableLabelAnimation && !isElementRemoved(el)) {
var layoutStore = labelLayoutInnerStore(textEl);
var oldLayout = layoutStore.oldLayout;
var ecData = getECData(el);
var dataIndex = ecData.dataIndex;
var newProps = {
x: textEl.x,
y: textEl.y,
rotation: textEl.rotation
};
var data = seriesModel.getData(ecData.dataType);
if (!oldLayout) {
textEl.attr(newProps); // Disable fade in animation if value animation is enabled.
if (!labelInner(textEl).valueAnimation) {
var oldOpacity = retrieve2(textEl.style.opacity, 1); // Fade in animation
textEl.style.opacity = 0;
initProps(textEl, {
style: {
opacity: oldOpacity
}
}, seriesModel, dataIndex);
}
} else {
textEl.attr(oldLayout); // Make sure the animation from is in the right status.
var prevStates = el.prevStates;
if (prevStates) {
if (indexOf(prevStates, 'select') >= 0) {
textEl.attr(layoutStore.oldLayoutSelect);
}
if (indexOf(prevStates, 'emphasis') >= 0) {
textEl.attr(layoutStore.oldLayoutEmphasis);
}
}
updateProps(textEl, newProps, seriesModel, dataIndex);
}
layoutStore.oldLayout = newProps;
if (textEl.states.select) {
var layoutSelect = layoutStore.oldLayoutSelect = {};
extendWithKeys(layoutSelect, newProps, LABEL_LAYOUT_PROPS);
extendWithKeys(layoutSelect, textEl.states.select, LABEL_LAYOUT_PROPS);
}
if (textEl.states.emphasis) {
var layoutEmphasis = layoutStore.oldLayoutEmphasis = {};
extendWithKeys(layoutEmphasis, newProps, LABEL_LAYOUT_PROPS);
extendWithKeys(layoutEmphasis, textEl.states.emphasis, LABEL_LAYOUT_PROPS);
}
animateLabelValue(textEl, dataIndex, data, seriesModel, seriesModel);
}
if (guideLine && !guideLine.ignore && !guideLine.invisible) {
var layoutStore = labelLineAnimationStore(guideLine);
var oldLayout = layoutStore.oldLayout;
var newLayout = {
points: guideLine.shape.points
};
if (!oldLayout) {
guideLine.setShape(newLayout);
guideLine.style.strokePercent = 0;
initProps(guideLine, {
style: {
strokePercent: 1
}
}, seriesModel);
} else {
guideLine.attr({
shape: oldLayout
});
updateProps(guideLine, {
shape: newLayout
}, seriesModel);
}
layoutStore.oldLayout = newLayout;
}
};
return LabelManager;
}();
// Inlucdes: pieSelect, pieUnSelect, pieToggleSelect, mapSelect, mapUnSelect, mapToggleSelect
function createLegacyDataSelectAction(seriesType, ecRegisterAction) {
function getSeriesIndices(ecModel, payload) {
var seriesIndices = [];
ecModel.eachComponent({
mainType: 'series',
subType: seriesType,
query: payload
}, function (seriesModel) {
seriesIndices.push(seriesModel.seriesIndex);
});
return seriesIndices;
}
each([[seriesType + 'ToggleSelect', 'toggleSelect'], [seriesType + 'Select', 'select'], [seriesType + 'UnSelect', 'unselect']], function (eventsMap) {
ecRegisterAction(eventsMap[0], function (payload, ecModel, api) {
payload = extend({}, payload);
if ("development" !== 'production') {
deprecateReplaceLog(payload.type, eventsMap[1]);
}
api.dispatchAction(extend(payload, {
type: eventsMap[1],
seriesIndex: getSeriesIndices(ecModel, payload)
}));
});
});
}
function handleSeriesLegacySelectEvents(type, eventPostfix, ecIns, ecModel, payload) {
var legacyEventName = type + eventPostfix;
if (!ecIns.isSilent(legacyEventName)) {
if ("development" !== 'production') {
deprecateLog("event " + legacyEventName + " is deprecated.");
}
ecModel.eachComponent({
mainType: 'series',
subType: 'pie'
}, function (seriesModel) {
var seriesIndex = seriesModel.seriesIndex;
var selected = payload.selected;
for (var i = 0; i < selected.length; i++) {
if (selected[i].seriesIndex === seriesIndex) {
var data = seriesModel.getData();
var dataIndex = queryDataIndex(data, payload.fromActionPayload);
ecIns.trigger(legacyEventName, {
type: legacyEventName,
seriesId: seriesModel.id,
name: isArray(dataIndex) ? data.getName(dataIndex[0]) : data.getName(dataIndex),
selected: extend({}, seriesModel.option.selectedMap)
});
}
}
});
}
}
function handleLegacySelectEvents(messageCenter, ecIns, api) {
messageCenter.on('selectchanged', function (params) {
var ecModel = api.getModel();
if (params.isFromClick) {
handleSeriesLegacySelectEvents('map', 'selectchanged', ecIns, ecModel, params);
handleSeriesLegacySelectEvents('pie', 'selectchanged', ecIns, ecModel, params);
} else if (params.fromAction === 'select') {
handleSeriesLegacySelectEvents('map', 'selected', ecIns, ecModel, params);
handleSeriesLegacySelectEvents('pie', 'selected', ecIns, ecModel, params);
} else if (params.fromAction === 'unselect') {
handleSeriesLegacySelectEvents('map', 'unselected', ecIns, ecModel, params);
handleSeriesLegacySelectEvents('pie', 'unselected', ecIns, ecModel, params);
}
});
}
/*
* 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.
*/
function findEventDispatcher(target, det, returnFirstMatch) {
var found;
while (target) {
if (det(target)) {
found = target;
if (returnFirstMatch) {
break;
}
}
target = target.__hostTarget || target.parent;
}
return found;
}
var wmUniqueIndex = Math.round(Math.random() * 9);
var WeakMap = (function () {
function WeakMap() {
this._id = '__ec_inner_' + wmUniqueIndex++;
}
WeakMap.prototype.get = function (key) {
return this._guard(key)[this._id];
};
WeakMap.prototype.set = function (key, value) {
var target = this._guard(key);
if (typeof Object.defineProperty === 'function') {
Object.defineProperty(target, this._id, {
value: value,
enumerable: false,
configurable: true
});
}
else {
target[this._id] = value;
}
return this;
};
WeakMap.prototype["delete"] = function (key) {
if (this.has(key)) {
delete this._guard(key)[this._id];
return true;
}
return false;
};
WeakMap.prototype.has = function (key) {
return !!this._guard(key)[this._id];
};
WeakMap.prototype._guard = function (key) {
if (key !== Object(key)) {
throw TypeError('Value of WeakMap is not a non-null object.');
}
return key;
};
return WeakMap;
}());
/**
* Triangle shape
* @inner
*/
var Triangle = Path.extend({
type: 'triangle',
shape: {
cx: 0,
cy: 0,
width: 0,
height: 0
},
buildPath: function (path, shape) {
var cx = shape.cx;
var cy = shape.cy;
var width = shape.width / 2;
var height = shape.height / 2;
path.moveTo(cx, cy - height);
path.lineTo(cx + width, cy + height);
path.lineTo(cx - width, cy + height);
path.closePath();
}
});
/**
* Diamond shape
* @inner
*/
var Diamond = Path.extend({
type: 'diamond',
shape: {
cx: 0,
cy: 0,
width: 0,
height: 0
},
buildPath: function (path, shape) {
var cx = shape.cx;
var cy = shape.cy;
var width = shape.width / 2;
var height = shape.height / 2;
path.moveTo(cx, cy - height);
path.lineTo(cx + width, cy);
path.lineTo(cx, cy + height);
path.lineTo(cx - width, cy);
path.closePath();
}
});
/**
* Pin shape
* @inner
*/
var Pin = Path.extend({
type: 'pin',
shape: {
// x, y on the cusp
x: 0,
y: 0,
width: 0,
height: 0
},
buildPath: function (path, shape) {
var x = shape.x;
var y = shape.y;
var w = shape.width / 5 * 3; // Height must be larger than width
var h = Math.max(w, shape.height);
var r = w / 2; // Dist on y with tangent point and circle center
var dy = r * r / (h - r);
var cy = y - h + r + dy;
var angle = Math.asin(dy / r); // Dist on x with tangent point and circle center
var dx = Math.cos(angle) * r;
var tanX = Math.sin(angle);
var tanY = Math.cos(angle);
var cpLen = r * 0.6;
var cpLen2 = r * 0.7;
path.moveTo(x - dx, cy + dy);
path.arc(x, cy, r, Math.PI - angle, Math.PI * 2 + angle);
path.bezierCurveTo(x + dx - tanX * cpLen, cy + dy + tanY * cpLen, x, y - cpLen2, x, y);
path.bezierCurveTo(x, y - cpLen2, x - dx + tanX * cpLen, cy + dy + tanY * cpLen, x - dx, cy + dy);
path.closePath();
}
});
/**
* Arrow shape
* @inner
*/
var Arrow = Path.extend({
type: 'arrow',
shape: {
x: 0,
y: 0,
width: 0,
height: 0
},
buildPath: function (ctx, shape) {
var height = shape.height;
var width = shape.width;
var x = shape.x;
var y = shape.y;
var dx = width / 3 * 2;
ctx.moveTo(x, y);
ctx.lineTo(x + dx, y + height);
ctx.lineTo(x, y + height / 4 * 3);
ctx.lineTo(x - dx, y + height);
ctx.lineTo(x, y);
ctx.closePath();
}
});
/**
* Map of path contructors
*/
// TODO Use function to build symbol path.
var symbolCtors = {
// Use small height rect to simulate line.
// Avoid using stroke.
line: Rect,
rect: Rect,
roundRect: Rect,
square: Rect,
circle: Circle,
diamond: Diamond,
pin: Pin,
arrow: Arrow,
triangle: Triangle
}; // NOTICE Only use fill. No line!
var symbolShapeMakers = {
line: function (x, y, w, h, shape) {
var thickness = 2; // A thin line
shape.x = x;
shape.y = y + h / 2 - thickness / 2;
shape.width = w;
shape.height = thickness;
},
rect: function (x, y, w, h, shape) {
shape.x = x;
shape.y = y;
shape.width = w;
shape.height = h;
},
roundRect: function (x, y, w, h, shape) {
shape.x = x;
shape.y = y;
shape.width = w;
shape.height = h;
shape.r = Math.min(w, h) / 4;
},
square: function (x, y, w, h, shape) {
var size = Math.min(w, h);
shape.x = x;
shape.y = y;
shape.width = size;
shape.height = size;
},
circle: function (x, y, w, h, shape) {
// Put circle in the center of square
shape.cx = x + w / 2;
shape.cy = y + h / 2;
shape.r = Math.min(w, h) / 2;
},
diamond: function (x, y, w, h, shape) {
shape.cx = x + w / 2;
shape.cy = y + h / 2;
shape.width = w;
shape.height = h;
},
pin: function (x, y, w, h, shape) {
shape.x = x + w / 2;
shape.y = y + h / 2;
shape.width = w;
shape.height = h;
},
arrow: function (x, y, w, h, shape) {
shape.x = x + w / 2;
shape.y = y + h / 2;
shape.width = w;
shape.height = h;
},
triangle: function (x, y, w, h, shape) {
shape.cx = x + w / 2;
shape.cy = y + h / 2;
shape.width = w;
shape.height = h;
}
};
var symbolBuildProxies = {};
each(symbolCtors, function (Ctor, name) {
symbolBuildProxies[name] = new Ctor();
});
var SymbolClz = Path.extend({
type: 'symbol',
shape: {
symbolType: '',
x: 0,
y: 0,
width: 0,
height: 0
},
calculateTextPosition: function (out, config, rect) {
var res = calculateTextPosition(out, config, rect);
var shape = this.shape;
if (shape && shape.symbolType === 'pin' && config.position === 'inside') {
res.y = rect.y + rect.height * 0.4;
}
return res;
},
buildPath: function (ctx, shape, inBundle) {
var symbolType = shape.symbolType;
if (symbolType !== 'none') {
var proxySymbol = symbolBuildProxies[symbolType];
if (!proxySymbol) {
// Default rect
symbolType = 'rect';
proxySymbol = symbolBuildProxies[symbolType];
}
symbolShapeMakers[symbolType](shape.x, shape.y, shape.width, shape.height, proxySymbol.shape);
proxySymbol.buildPath(ctx, proxySymbol.shape, inBundle);
}
}
}); // Provide setColor helper method to avoid determine if set the fill or stroke outside
function symbolPathSetColor(color, innerColor) {
if (this.type !== 'image') {
var symbolStyle = this.style;
if (this.__isEmptyBrush) {
symbolStyle.stroke = color;
symbolStyle.fill = innerColor || '#fff'; // TODO Same width with lineStyle in LineView.
symbolStyle.lineWidth = 2;
} else {
symbolStyle.fill = color;
}
this.markRedraw();
}
}
/**
* Create a symbol element with given symbol configuration: shape, x, y, width, height, color
*/
function createSymbol(symbolType, x, y, w, h, color, // whether to keep the ratio of w/h,
keepAspect) {
// TODO Support image object, DynamicImage.
var isEmpty = symbolType.indexOf('empty') === 0;
if (isEmpty) {
symbolType = symbolType.substr(5, 1).toLowerCase() + symbolType.substr(6);
}
var symbolPath;
if (symbolType.indexOf('image://') === 0) {
symbolPath = makeImage(symbolType.slice(8), new BoundingRect(x, y, w, h), keepAspect ? 'center' : 'cover');
} else if (symbolType.indexOf('path://') === 0) {
symbolPath = makePath(symbolType.slice(7), {}, new BoundingRect(x, y, w, h), keepAspect ? 'center' : 'cover');
} else {
symbolPath = new SymbolClz({
shape: {
symbolType: symbolType,
x: x,
y: y,
width: w,
height: h
}
});
}
symbolPath.__isEmptyBrush = isEmpty; // TODO Should deprecate setColor
symbolPath.setColor = symbolPathSetColor;
if (color) {
symbolPath.setColor(color);
}
return symbolPath;
}
function createLinearGradient(ctx, obj, rect) {
var x = obj.x == null ? 0 : obj.x;
var x2 = obj.x2 == null ? 1 : obj.x2;
var y = obj.y == null ? 0 : obj.y;
var y2 = obj.y2 == null ? 0 : obj.y2;
if (!obj.global) {
x = x * rect.width + rect.x;
x2 = x2 * rect.width + rect.x;
y = y * rect.height + rect.y;
y2 = y2 * rect.height + rect.y;
}
x = isNaN(x) ? 0 : x;
x2 = isNaN(x2) ? 1 : x2;
y = isNaN(y) ? 0 : y;
y2 = isNaN(y2) ? 0 : y2;
var canvasGradient = ctx.createLinearGradient(x, y, x2, y2);
return canvasGradient;
}
function createRadialGradient(ctx, obj, rect) {
var width = rect.width;
var height = rect.height;
var min = Math.min(width, height);
var x = obj.x == null ? 0.5 : obj.x;
var y = obj.y == null ? 0.5 : obj.y;
var r = obj.r == null ? 0.5 : obj.r;
if (!obj.global) {
x = x * width + rect.x;
y = y * height + rect.y;
r = r * min;
}
var canvasGradient = ctx.createRadialGradient(x, y, 0, x, y, r);
return canvasGradient;
}
function getCanvasGradient(ctx, obj, rect) {
var canvasGradient = obj.type === 'radial'
? createRadialGradient(ctx, obj, rect)
: createLinearGradient(ctx, obj, rect);
var colorStops = obj.colorStops;
for (var i = 0; i < colorStops.length; i++) {
canvasGradient.addColorStop(colorStops[i].offset, colorStops[i].color);
}
return canvasGradient;
}
function isClipPathChanged(clipPaths, prevClipPaths) {
if (clipPaths === prevClipPaths || (!clipPaths && !prevClipPaths)) {
return false;
}
if (!clipPaths || !prevClipPaths || (clipPaths.length !== prevClipPaths.length)) {
return true;
}
for (var i = 0; i < clipPaths.length; i++) {
if (clipPaths[i] !== prevClipPaths[i]) {
return true;
}
}
return false;
}
function normalizeLineDash(lineType, lineWidth) {
if (!lineType || lineType === 'solid' || !(lineWidth > 0)) {
return null;
}
lineWidth = lineWidth || 1;
return lineType === 'dashed'
? [4 * lineWidth, 2 * lineWidth]
: lineType === 'dotted'
? [lineWidth]
: isNumber(lineType)
? [lineType] : isArray(lineType) ? lineType : null;
}
var pathProxyForDraw = new PathProxy(true);
function styleHasStroke(style) {
var stroke = style.stroke;
return !(stroke == null || stroke === 'none' || !(style.lineWidth > 0));
}
function styleHasFill(style) {
var fill = style.fill;
return fill != null && fill !== 'none';
}
function doFillPath(ctx, style) {
if (style.fillOpacity != null && style.fillOpacity !== 1) {
var originalGlobalAlpha = ctx.globalAlpha;
ctx.globalAlpha = style.fillOpacity * style.opacity;
ctx.fill();
ctx.globalAlpha = originalGlobalAlpha;
}
else {
ctx.fill();
}
}
function doStrokePath(ctx, style) {
if (style.strokeOpacity != null && style.strokeOpacity !== 1) {
var originalGlobalAlpha = ctx.globalAlpha;
ctx.globalAlpha = style.strokeOpacity * style.opacity;
ctx.stroke();
ctx.globalAlpha = originalGlobalAlpha;
}
else {
ctx.stroke();
}
}
function createCanvasPattern(ctx, pattern, el) {
var image = createOrUpdateImage(pattern.image, pattern.__image, el);
if (isImageReady(image)) {
var canvasPattern = ctx.createPattern(image, pattern.repeat || 'repeat');
if (typeof DOMMatrix === 'function') {
var matrix = new DOMMatrix();
matrix.rotateSelf(0, 0, (pattern.rotation || 0) / Math.PI * 180);
matrix.scaleSelf((pattern.scaleX || 1), (pattern.scaleY || 1));
matrix.translateSelf((pattern.x || 0), (pattern.y || 0));
canvasPattern.setTransform(matrix);
}
return canvasPattern;
}
}
function brushPath(ctx, el, style, inBatch) {
var hasStroke = styleHasStroke(style);
var hasFill = styleHasFill(style);
var strokePercent = style.strokePercent;
var strokePart = strokePercent < 1;
var firstDraw = !el.path;
if ((!el.silent || strokePart) && firstDraw) {
el.createPathProxy();
}
var path = el.path || pathProxyForDraw;
if (!inBatch) {
var fill = style.fill;
var stroke = style.stroke;
var hasFillGradient = hasFill && !!fill.colorStops;
var hasStrokeGradient = hasStroke && !!stroke.colorStops;
var hasFillPattern = hasFill && !!fill.image;
var hasStrokePattern = hasStroke && !!stroke.image;
var fillGradient = void 0;
var strokeGradient = void 0;
var fillPattern = void 0;
var strokePattern = void 0;
var rect = void 0;
if (hasFillGradient || hasStrokeGradient) {
rect = el.getBoundingRect();
}
if (hasFillGradient) {
fillGradient = el.__dirty
? getCanvasGradient(ctx, fill, rect)
: el.__canvasFillGradient;
el.__canvasFillGradient = fillGradient;
}
if (hasStrokeGradient) {
strokeGradient = el.__dirty
? getCanvasGradient(ctx, stroke, rect)
: el.__canvasStrokeGradient;
el.__canvasStrokeGradient = strokeGradient;
}
if (hasFillPattern) {
fillPattern = (el.__dirty || !el.__canvasFillPattern)
? createCanvasPattern(ctx, fill, el)
: el.__canvasFillPattern;
el.__canvasFillPattern = fillPattern;
}
if (hasStrokePattern) {
strokePattern = (el.__dirty || !el.__canvasStrokePattern)
? createCanvasPattern(ctx, stroke, el)
: el.__canvasStrokePattern;
el.__canvasStrokePattern = fillPattern;
}
if (hasFillGradient) {
ctx.fillStyle = fillGradient;
}
else if (hasFillPattern) {
if (fillPattern) {
ctx.fillStyle = fillPattern;
}
else {
hasFill = false;
}
}
if (hasStrokeGradient) {
ctx.strokeStyle = strokeGradient;
}
else if (hasStrokePattern) {
if (strokePattern) {
ctx.strokeStyle = strokePattern;
}
else {
hasStroke = false;
}
}
}
var lineDash = style.lineDash && style.lineWidth > 0 && normalizeLineDash(style.lineDash, style.lineWidth);
var lineDashOffset = style.lineDashOffset;
var ctxLineDash = !!ctx.setLineDash;
var scale = el.getGlobalScale();
path.setScale(scale[0], scale[1], el.segmentIgnoreThreshold);
if (lineDash) {
var lineScale_1 = (style.strokeNoScale && el.getLineScale) ? el.getLineScale() : 1;
if (lineScale_1 && lineScale_1 !== 1) {
lineDash = map(lineDash, function (rawVal) {
return rawVal / lineScale_1;
});
lineDashOffset /= lineScale_1;
}
}
var needsRebuild = true;
if (firstDraw || (el.__dirty & Path.SHAPE_CHANGED_BIT)
|| (lineDash && !ctxLineDash && hasStroke)) {
path.setDPR(ctx.dpr);
if (strokePart) {
path.setContext(null);
}
else {
path.setContext(ctx);
needsRebuild = false;
}
path.reset();
if (lineDash && !ctxLineDash) {
path.setLineDash(lineDash);
path.setLineDashOffset(lineDashOffset);
}
el.buildPath(path, el.shape, inBatch);
path.toStatic();
el.pathUpdated();
}
if (needsRebuild) {
path.rebuildPath(ctx, strokePart ? strokePercent : 1);
}
if (lineDash && ctxLineDash) {
ctx.setLineDash(lineDash);
ctx.lineDashOffset = lineDashOffset;
}
if (!inBatch) {
if (style.strokeFirst) {
if (hasStroke) {
doStrokePath(ctx, style);
}
if (hasFill) {
doFillPath(ctx, style);
}
}
else {
if (hasFill) {
doFillPath(ctx, style);
}
if (hasStroke) {
doStrokePath(ctx, style);
}
}
}
if (lineDash && ctxLineDash) {
ctx.setLineDash([]);
}
}
function brushImage(ctx, el, style) {
var image = el.__image = createOrUpdateImage(style.image, el.__image, el, el.onload);
if (!image || !isImageReady(image)) {
return;
}
var x = style.x || 0;
var y = style.y || 0;
var width = el.getWidth();
var height = el.getHeight();
var aspect = image.width / image.height;
if (width == null && height != null) {
width = height * aspect;
}
else if (height == null && width != null) {
height = width / aspect;
}
else if (width == null && height == null) {
width = image.width;
height = image.height;
}
if (style.sWidth && style.sHeight) {
var sx = style.sx || 0;
var sy = style.sy || 0;
ctx.drawImage(image, sx, sy, style.sWidth, style.sHeight, x, y, width, height);
}
else if (style.sx && style.sy) {
var sx = style.sx;
var sy = style.sy;
var sWidth = width - sx;
var sHeight = height - sy;
ctx.drawImage(image, sx, sy, sWidth, sHeight, x, y, width, height);
}
else {
ctx.drawImage(image, x, y, width, height);
}
}
function brushText(ctx, el, style) {
var text = style.text;
text != null && (text += '');
if (text) {
ctx.font = style.font || DEFAULT_FONT;
ctx.textAlign = style.textAlign;
ctx.textBaseline = style.textBaseline;
var hasLineDash = void 0;
if (ctx.setLineDash) {
var lineDash = style.lineDash && style.lineWidth > 0 && normalizeLineDash(style.lineDash, style.lineWidth);
var lineDashOffset = style.lineDashOffset;
if (lineDash) {
var lineScale_2 = (style.strokeNoScale && el.getLineScale) ? el.getLineScale() : 1;
if (lineScale_2 && lineScale_2 !== 1) {
lineDash = map(lineDash, function (rawVal) {
return rawVal / lineScale_2;
});
lineDashOffset /= lineScale_2;
}
ctx.setLineDash(lineDash);
ctx.lineDashOffset = lineDashOffset;
hasLineDash = true;
}
}
if (style.strokeFirst) {
if (styleHasStroke(style)) {
ctx.strokeText(text, style.x, style.y);
}
if (styleHasFill(style)) {
ctx.fillText(text, style.x, style.y);
}
}
else {
if (styleHasFill(style)) {
ctx.fillText(text, style.x, style.y);
}
if (styleHasStroke(style)) {
ctx.strokeText(text, style.x, style.y);
}
}
if (hasLineDash) {
ctx.setLineDash([]);
}
}
}
var SHADOW_NUMBER_PROPS = ['shadowBlur', 'shadowOffsetX', 'shadowOffsetY'];
var STROKE_PROPS = [
['lineCap', 'butt'], ['lineJoin', 'miter'], ['miterLimit', 10]
];
function bindCommonProps(ctx, style, prevStyle, forceSetAll, scope) {
var styleChanged = false;
if (!forceSetAll) {
prevStyle = prevStyle || {};
if (style === prevStyle) {
return false;
}
}
if (forceSetAll || style.opacity !== prevStyle.opacity) {
if (!styleChanged) {
flushPathDrawn(ctx, scope);
styleChanged = true;
}
ctx.globalAlpha = style.opacity == null ? DEFAULT_COMMON_STYLE.opacity : style.opacity;
}
if (forceSetAll || style.blend !== prevStyle.blend) {
if (!styleChanged) {
flushPathDrawn(ctx, scope);
styleChanged = true;
}
ctx.globalCompositeOperation = style.blend || DEFAULT_COMMON_STYLE.blend;
}
for (var i = 0; i < SHADOW_NUMBER_PROPS.length; i++) {
var propName = SHADOW_NUMBER_PROPS[i];
if (forceSetAll || style[propName] !== prevStyle[propName]) {
if (!styleChanged) {
flushPathDrawn(ctx, scope);
styleChanged = true;
}
ctx[propName] = ctx.dpr * (style[propName] || 0);
}
}
if (forceSetAll || style.shadowColor !== prevStyle.shadowColor) {
if (!styleChanged) {
flushPathDrawn(ctx, scope);
styleChanged = true;
}
ctx.shadowColor = style.shadowColor || DEFAULT_COMMON_STYLE.shadowColor;
}
return styleChanged;
}
function bindPathAndTextCommonStyle(ctx, el, prevEl, forceSetAll, scope) {
var style = getStyle(el, scope.inHover);
var prevStyle = forceSetAll
? null
: (prevEl && getStyle(prevEl, scope.inHover) || {});
if (style === prevStyle) {
return false;
}
var styleChanged = bindCommonProps(ctx, style, prevStyle, forceSetAll, scope);
if (forceSetAll || style.fill !== prevStyle.fill) {
if (!styleChanged) {
flushPathDrawn(ctx, scope);
styleChanged = true;
}
ctx.fillStyle = style.fill;
}
if (forceSetAll || style.stroke !== prevStyle.stroke) {
if (!styleChanged) {
flushPathDrawn(ctx, scope);
styleChanged = true;
}
ctx.strokeStyle = style.stroke;
}
if (forceSetAll || style.opacity !== prevStyle.opacity) {
if (!styleChanged) {
flushPathDrawn(ctx, scope);
styleChanged = true;
}
ctx.globalAlpha = style.opacity == null ? 1 : style.opacity;
}
if (el.hasStroke()) {
var lineWidth = style.lineWidth;
var newLineWidth = lineWidth / ((style.strokeNoScale && el && el.getLineScale) ? el.getLineScale() : 1);
if (ctx.lineWidth !== newLineWidth) {
if (!styleChanged) {
flushPathDrawn(ctx, scope);
styleChanged = true;
}
ctx.lineWidth = newLineWidth;
}
}
for (var i = 0; i < STROKE_PROPS.length; i++) {
var prop = STROKE_PROPS[i];
var propName = prop[0];
if (forceSetAll || style[propName] !== prevStyle[propName]) {
if (!styleChanged) {
flushPathDrawn(ctx, scope);
styleChanged = true;
}
ctx[propName] = style[propName] || prop[1];
}
}
return styleChanged;
}
function bindImageStyle(ctx, el, prevEl, forceSetAll, scope) {
return bindCommonProps(ctx, getStyle(el, scope.inHover), prevEl && getStyle(prevEl, scope.inHover), forceSetAll, scope);
}
function setContextTransform(ctx, el) {
var m = el.transform;
var dpr = ctx.dpr || 1;
if (m) {
ctx.setTransform(dpr * m[0], dpr * m[1], dpr * m[2], dpr * m[3], dpr * m[4], dpr * m[5]);
}
else {
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
}
function updateClipStatus(clipPaths, ctx, scope) {
var allClipped = false;
for (var i = 0; i < clipPaths.length; i++) {
var clipPath = clipPaths[i];
allClipped = allClipped || clipPath.isZeroArea();
setContextTransform(ctx, clipPath);
ctx.beginPath();
clipPath.buildPath(ctx, clipPath.shape);
ctx.clip();
}
scope.allClipped = allClipped;
}
function isTransformChanged(m0, m1) {
if (m0 && m1) {
return m0[0] !== m1[0]
|| m0[1] !== m1[1]
|| m0[2] !== m1[2]
|| m0[3] !== m1[3]
|| m0[4] !== m1[4]
|| m0[5] !== m1[5];
}
else if (!m0 && !m1) {
return false;
}
return true;
}
var DRAW_TYPE_PATH = 1;
var DRAW_TYPE_IMAGE = 2;
var DRAW_TYPE_TEXT = 3;
var DRAW_TYPE_INCREMENTAL = 4;
function canPathBatch(style) {
var hasFill = styleHasFill(style);
var hasStroke = styleHasStroke(style);
return !(style.lineDash
|| !(+hasFill ^ +hasStroke)
|| (hasFill && typeof style.fill !== 'string')
|| (hasStroke && typeof style.stroke !== 'string')
|| style.strokePercent < 1
|| style.strokeOpacity < 1
|| style.fillOpacity < 1);
}
function flushPathDrawn(ctx, scope) {
scope.batchFill && ctx.fill();
scope.batchStroke && ctx.stroke();
scope.batchFill = '';
scope.batchStroke = '';
}
function getStyle(el, inHover) {
return inHover ? (el.__hoverStyle || el.style) : el.style;
}
function brushSingle(ctx, el) {
brush(ctx, el, { inHover: false, viewWidth: 0, viewHeight: 0 }, true);
}
function brush(ctx, el, scope, isLast) {
var m = el.transform;
if (!el.shouldBePainted(scope.viewWidth, scope.viewHeight, false, false)) {
el.__dirty &= ~Element.REDARAW_BIT;
el.__isRendered = false;
return;
}
var clipPaths = el.__clipPaths;
var prevElClipPaths = scope.prevElClipPaths;
var forceSetTransform = false;
var forceSetStyle = false;
if (!prevElClipPaths || isClipPathChanged(clipPaths, prevElClipPaths)) {
if (prevElClipPaths && prevElClipPaths.length) {
flushPathDrawn(ctx, scope);
ctx.restore();
forceSetStyle = forceSetTransform = true;
scope.prevElClipPaths = null;
scope.allClipped = false;
scope.prevEl = null;
}
if (clipPaths && clipPaths.length) {
flushPathDrawn(ctx, scope);
ctx.save();
updateClipStatus(clipPaths, ctx, scope);
forceSetTransform = true;
}
scope.prevElClipPaths = clipPaths;
}
if (scope.allClipped) {
el.__isRendered = false;
return;
}
el.beforeBrush && el.beforeBrush();
el.innerBeforeBrush();
var prevEl = scope.prevEl;
if (!prevEl) {
forceSetStyle = forceSetTransform = true;
}
var canBatchPath = el instanceof Path
&& el.autoBatch
&& canPathBatch(el.style);
if (forceSetTransform || isTransformChanged(m, prevEl.transform)) {
flushPathDrawn(ctx, scope);
setContextTransform(ctx, el);
}
else if (!canBatchPath) {
flushPathDrawn(ctx, scope);
}
var style = getStyle(el, scope.inHover);
if (el instanceof Path) {
if (scope.lastDrawType !== DRAW_TYPE_PATH) {
forceSetStyle = true;
scope.lastDrawType = DRAW_TYPE_PATH;
}
bindPathAndTextCommonStyle(ctx, el, prevEl, forceSetStyle, scope);
if (!canBatchPath || (!scope.batchFill && !scope.batchStroke)) {
ctx.beginPath();
}
brushPath(ctx, el, style, canBatchPath);
if (canBatchPath) {
scope.batchFill = style.fill || '';
scope.batchStroke = style.stroke || '';
}
}
else {
if (el instanceof TSpan) {
if (scope.lastDrawType !== DRAW_TYPE_TEXT) {
forceSetStyle = true;
scope.lastDrawType = DRAW_TYPE_TEXT;
}
bindPathAndTextCommonStyle(ctx, el, prevEl, forceSetStyle, scope);
brushText(ctx, el, style);
}
else if (el instanceof ZRImage) {
if (scope.lastDrawType !== DRAW_TYPE_IMAGE) {
forceSetStyle = true;
scope.lastDrawType = DRAW_TYPE_IMAGE;
}
bindImageStyle(ctx, el, prevEl, forceSetStyle, scope);
brushImage(ctx, el, style);
}
else if (el instanceof IncrementalDisplayable) {
if (scope.lastDrawType !== DRAW_TYPE_INCREMENTAL) {
forceSetStyle = true;
scope.lastDrawType = DRAW_TYPE_INCREMENTAL;
}
brushIncremental(ctx, el, scope);
}
}
if (canBatchPath && isLast) {
flushPathDrawn(ctx, scope);
}
el.innerAfterBrush();
el.afterBrush && el.afterBrush();
scope.prevEl = el;
el.__dirty = 0;
el.__isRendered = true;
}
function brushIncremental(ctx, el, scope) {
var displayables = el.getDisplayables();
var temporalDisplayables = el.getTemporalDisplayables();
ctx.save();
var innerScope = {
prevElClipPaths: null,
prevEl: null,
allClipped: false,
viewWidth: scope.viewWidth,
viewHeight: scope.viewHeight,
inHover: scope.inHover
};
var i;
var len;
for (i = el.getCursor(), len = displayables.length; i < len; i++) {
var displayable = displayables[i];
displayable.beforeBrush && displayable.beforeBrush();
displayable.innerBeforeBrush();
brush(ctx, displayable, innerScope, i === len - 1);
displayable.innerAfterBrush();
displayable.afterBrush && displayable.afterBrush();
innerScope.prevEl = displayable;
}
for (var i_1 = 0, len_1 = temporalDisplayables.length; i_1 < len_1; i_1++) {
var displayable = temporalDisplayables[i_1];
displayable.beforeBrush && displayable.beforeBrush();
displayable.innerBeforeBrush();
brush(ctx, displayable, innerScope, i_1 === len_1 - 1);
displayable.innerAfterBrush();
displayable.afterBrush && displayable.afterBrush();
innerScope.prevEl = displayable;
}
el.clearTemporalDisplayables();
el.notClear = true;
ctx.restore();
}
var decalMap = new WeakMap();
var decalCache = new LRU(100);
var decalKeys = ['symbol', 'symbolSize', 'symbolKeepAspect', 'color', 'backgroundColor', 'dashArrayX', 'dashArrayY', 'maxTileWidth', 'maxTileHeight'];
/**
* Create or update pattern image from decal options
*
* @param {InnerDecalObject | 'none'} decalObject decal options, 'none' if no decal
* @return {Pattern} pattern with generated image, null if no decal
*/
function createOrUpdatePatternFromDecal(decalObject, api) {
if (decalObject === 'none') {
return null;
}
var dpr = api.getDevicePixelRatio();
var zr = api.getZr();
var isSVG = zr.painter.type === 'svg';
if (decalObject.dirty) {
decalMap["delete"](decalObject);
}
var oldPattern = decalMap.get(decalObject);
if (oldPattern) {
return oldPattern;
}
var decalOpt = defaults(decalObject, {
symbol: 'rect',
symbolSize: 1,
symbolKeepAspect: true,
color: 'rgba(0, 0, 0, 0.2)',
backgroundColor: null,
dashArrayX: 5,
dashArrayY: 5,
rotation: 0,
maxTileWidth: 512,
maxTileHeight: 512
});
if (decalOpt.backgroundColor === 'none') {
decalOpt.backgroundColor = null;
}
var pattern = {
repeat: 'repeat'
};
setPatternnSource(pattern);
pattern.rotation = decalOpt.rotation;
pattern.scaleX = pattern.scaleY = isSVG ? 1 : 1 / dpr;
decalMap.set(decalObject, pattern);
decalObject.dirty = false;
return pattern;
function setPatternnSource(pattern) {
var keys = [dpr];
var isValidKey = true;
for (var i = 0; i < decalKeys.length; ++i) {
var value = decalOpt[decalKeys[i]];
var valueType = typeof value;
if (value != null && !isArray(value) && valueType !== 'string' && valueType !== 'number' && valueType !== 'boolean') {
isValidKey = false;
break;
}
keys.push(value);
}
var cacheKey;
if (isValidKey) {
cacheKey = keys.join(',') + (isSVG ? '-svg' : '');
var cache = decalCache.get(cacheKey);
if (cache) {
isSVG ? pattern.svgElement = cache : pattern.image = cache;
}
}
var dashArrayX = normalizeDashArrayX(decalOpt.dashArrayX);
var dashArrayY = normalizeDashArrayY(decalOpt.dashArrayY);
var symbolArray = normalizeSymbolArray(decalOpt.symbol);
var lineBlockLengthsX = getLineBlockLengthX(dashArrayX);
var lineBlockLengthY = getLineBlockLengthY(dashArrayY);
var canvas = !isSVG && createCanvas();
var svgRoot = isSVG && zr.painter.createSVGElement('g');
var pSize = getPatternSize();
var ctx;
if (canvas) {
canvas.width = pSize.width * dpr;
canvas.height = pSize.height * dpr;
ctx = canvas.getContext('2d');
}
brushDecal();
if (isValidKey) {
decalCache.put(cacheKey, canvas || svgRoot);
}
pattern.image = canvas;
pattern.svgElement = svgRoot;
pattern.svgWidth = pSize.width;
pattern.svgHeight = pSize.height;
/**
* Get minumum length that can make a repeatable pattern.
*
* @return {Object} pattern width and height
*/
function getPatternSize() {
/**
* For example, if dash is [[3, 2], [2, 1]] for X, it looks like
* |--- --- --- --- --- ...
* |-- -- -- -- -- -- -- -- ...
* |--- --- --- --- --- ...
* |-- -- -- -- -- -- -- -- ...
* So the minumum length of X is 15,
* which is the least common multiple of `3 + 2` and `2 + 1`
* |--- --- --- |--- --- ...
* |-- -- -- -- -- |-- -- -- ...
*/
var width = 1;
for (var i = 0, xlen = lineBlockLengthsX.length; i < xlen; ++i) {
width = getLeastCommonMultiple(width, lineBlockLengthsX[i]);
}
var symbolRepeats = 1;
for (var i = 0, xlen = symbolArray.length; i < xlen; ++i) {
symbolRepeats = getLeastCommonMultiple(symbolRepeats, symbolArray[i].length);
}
width *= symbolRepeats;
var height = lineBlockLengthY * lineBlockLengthsX.length * symbolArray.length;
if ("development" !== 'production') {
var warn = function (attrName) {
/* eslint-disable-next-line */
console.warn("Calculated decal size is greater than " + attrName + " due to decal option settings so " + attrName + " is used for the decal size. Please consider changing the decal option to make a smaller decal or set " + attrName + " to be larger to avoid incontinuity.");
};
if (width > decalOpt.maxTileWidth) {
warn('maxTileWidth');
}
if (height > decalOpt.maxTileHeight) {
warn('maxTileHeight');
}
}
return {
width: Math.max(1, Math.min(width, decalOpt.maxTileWidth)),
height: Math.max(1, Math.min(height, decalOpt.maxTileHeight))
};
}
function brushDecal() {
if (ctx) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (decalOpt.backgroundColor) {
ctx.fillStyle = decalOpt.backgroundColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
}
var ySum = 0;
for (var i = 0; i < dashArrayY.length; ++i) {
ySum += dashArrayY[i];
}
if (ySum <= 0) {
// dashArrayY is 0, draw nothing
return;
}
var y = -lineBlockLengthY;
var yId = 0;
var yIdTotal = 0;
var xId0 = 0;
while (y < pSize.height) {
if (yId % 2 === 0) {
var symbolYId = yIdTotal / 2 % symbolArray.length;
var x = 0;
var xId1 = 0;
var xId1Total = 0;
while (x < pSize.width * 2) {
var xSum = 0;
for (var i = 0; i < dashArrayX[xId0].length; ++i) {
xSum += dashArrayX[xId0][i];
}
if (xSum <= 0) {
// Skip empty line
break;
} // E.g., [15, 5, 20, 5] draws only for 15 and 20
if (xId1 % 2 === 0) {
var size = (1 - decalOpt.symbolSize) * 0.5;
var left = x + dashArrayX[xId0][xId1] * size;
var top_1 = y + dashArrayY[yId] * size;
var width = dashArrayX[xId0][xId1] * decalOpt.symbolSize;
var height = dashArrayY[yId] * decalOpt.symbolSize;
var symbolXId = xId1Total / 2 % symbolArray[symbolYId].length;
brushSymbol(left, top_1, width, height, symbolArray[symbolYId][symbolXId]);
}
x += dashArrayX[xId0][xId1];
++xId1Total;
++xId1;
if (xId1 === dashArrayX[xId0].length) {
xId1 = 0;
}
}
++xId0;
if (xId0 === dashArrayX.length) {
xId0 = 0;
}
}
y += dashArrayY[yId];
++yIdTotal;
++yId;
if (yId === dashArrayY.length) {
yId = 0;
}
}
function brushSymbol(x, y, width, height, symbolType) {
var scale = isSVG ? 1 : dpr;
var symbol = createSymbol(symbolType, x * scale, y * scale, width * scale, height * scale, decalOpt.color, decalOpt.symbolKeepAspect);
if (isSVG) {
svgRoot.appendChild(zr.painter.paintOne(symbol));
} else {
// Paint to canvas for all other renderers.
brushSingle(ctx, symbol);
}
}
}
}
}
/**
* Convert symbol array into normalized array
*
* @param {string | (string | string[])[]} symbol symbol input
* @return {string[][]} normolized symbol array
*/
function normalizeSymbolArray(symbol) {
if (!symbol || symbol.length === 0) {
return [['rect']];
}
if (typeof symbol === 'string') {
return [[symbol]];
}
var isAllString = true;
for (var i = 0; i < symbol.length; ++i) {
if (typeof symbol[i] !== 'string') {
isAllString = false;
break;
}
}
if (isAllString) {
return normalizeSymbolArray([symbol]);
}
var result = [];
for (var i = 0; i < symbol.length; ++i) {
if (typeof symbol[i] === 'string') {
result.push([symbol[i]]);
} else {
result.push(symbol[i]);
}
}
return result;
}
/**
* Convert dash input into dashArray
*
* @param {DecalDashArrayX} dash dash input
* @return {number[][]} normolized dash array
*/
function normalizeDashArrayX(dash) {
if (!dash || dash.length === 0) {
return [[0, 0]];
}
if (typeof dash === 'number') {
var dashValue = Math.ceil(dash);
return [[dashValue, dashValue]];
}
/**
* [20, 5] should be normalized into [[20, 5]],
* while [20, [5, 10]] should be normalized into [[20, 20], [5, 10]]
*/
var isAllNumber = true;
for (var i = 0; i < dash.length; ++i) {
if (typeof dash[i] !== 'number') {
isAllNumber = false;
break;
}
}
if (isAllNumber) {
return normalizeDashArrayX([dash]);
}
var result = [];
for (var i = 0; i < dash.length; ++i) {
if (typeof dash[i] === 'number') {
var dashValue = Math.ceil(dash[i]);
result.push([dashValue, dashValue]);
} else {
var dashValue = map(dash[i], function (n) {
return Math.ceil(n);
});
if (dashValue.length % 2 === 1) {
// [4, 2, 1] means |---- - -- |---- - -- |
// so normalize it to be [4, 2, 1, 4, 2, 1]
result.push(dashValue.concat(dashValue));
} else {
result.push(dashValue);
}
}
}
return result;
}
/**
* Convert dash input into dashArray
*
* @param {DecalDashArrayY} dash dash input
* @return {number[]} normolized dash array
*/
function normalizeDashArrayY(dash) {
if (!dash || typeof dash === 'object' && dash.length === 0) {
return [0, 0];
}
if (typeof dash === 'number') {
var dashValue_1 = Math.ceil(dash);
return [dashValue_1, dashValue_1];
}
var dashValue = map(dash, function (n) {
return Math.ceil(n);
});
return dash.length % 2 ? dashValue.concat(dashValue) : dashValue;
}
/**
* Get block length of each line. A block is the length of dash line and space.
* For example, a line with [4, 1] has a dash line of 4 and a space of 1 after
* that, so the block length of this line is 5.
*
* @param {number[][]} dash dash arrary of X or Y
* @return {number[]} block length of each line
*/
function getLineBlockLengthX(dash) {
return map(dash, function (line) {
return getLineBlockLengthY(line);
});
}
function getLineBlockLengthY(dash) {
var blockLength = 0;
for (var i = 0; i < dash.length; ++i) {
blockLength += dash[i];
}
if (dash.length % 2 === 1) {
// [4, 2, 1] means |---- - -- |---- - -- |
// So total length is (4 + 2 + 1) * 2
return blockLength * 2;
}
return blockLength;
}
function decalVisual(ecModel, api) {
ecModel.eachRawSeries(function (seriesModel) {
if (ecModel.isSeriesFiltered(seriesModel)) {
return;
}
var data = seriesModel.getData();
if (data.hasItemVisual()) {
data.each(function (idx) {
var decal = data.getItemVisual(idx, 'decal');
if (decal) {
var itemStyle = data.ensureUniqueItemVisual(idx, 'style');
itemStyle.decal = createOrUpdatePatternFromDecal(decal, api);
}
});
}
var decal = data.getVisual('decal');
if (decal) {
var style = data.getVisual('style');
style.decal = createOrUpdatePatternFromDecal(decal, api);
}
});
}
var assert$1 = assert;
var each$3 = each;
var isFunction$1 = isFunction;
var isObject$2 = isObject;
var indexOf$1 = indexOf;
var hasWindow = typeof window !== 'undefined';
var version$1 = '5.0.2';
var dependencies = {
zrender: '5.0.4'
};
var TEST_FRAME_REMAIN_TIME = 1;
var PRIORITY_PROCESSOR_SERIES_FILTER = 800; // Some data processors depends on the stack result dimension (to calculate data extent).
// So data stack stage should be in front of data processing stage.
var PRIORITY_PROCESSOR_DATASTACK = 900; // "Data filter" will block the stream, so it should be
// put at the begining of data processing.
var PRIORITY_PROCESSOR_FILTER = 1000;
var PRIORITY_PROCESSOR_DEFAULT = 2000;
var PRIORITY_PROCESSOR_STATISTIC = 5000;
var PRIORITY_VISUAL_LAYOUT = 1000;
var PRIORITY_VISUAL_PROGRESSIVE_LAYOUT = 1100;
var PRIORITY_VISUAL_GLOBAL = 2000;
var PRIORITY_VISUAL_CHART = 3000;
var PRIORITY_VISUAL_COMPONENT = 4000; // Visual property in data. Greater than `PRIORITY_VISUAL_COMPONENT` to enable to
// overwrite the viusal result of component (like `visualMap`)
// using data item specific setting (like itemStyle.xxx on data item)
var PRIORITY_VISUAL_CHART_DATA_CUSTOM = 4500; // Greater than `PRIORITY_VISUAL_CHART_DATA_CUSTOM` to enable to layout based on
// visual result like `symbolSize`.
var PRIORITY_VISUAL_POST_CHART_LAYOUT = 4600;
var PRIORITY_VISUAL_BRUSH = 5000;
var PRIORITY_VISUAL_ARIA = 6000;
var PRIORITY_VISUAL_DECAL = 7000;
var PRIORITY = {
PROCESSOR: {
FILTER: PRIORITY_PROCESSOR_FILTER,
SERIES_FILTER: PRIORITY_PROCESSOR_SERIES_FILTER,
STATISTIC: PRIORITY_PROCESSOR_STATISTIC
},
VISUAL: {
LAYOUT: PRIORITY_VISUAL_LAYOUT,
PROGRESSIVE_LAYOUT: PRIORITY_VISUAL_PROGRESSIVE_LAYOUT,
GLOBAL: PRIORITY_VISUAL_GLOBAL,
CHART: PRIORITY_VISUAL_CHART,
POST_CHART_LAYOUT: PRIORITY_VISUAL_POST_CHART_LAYOUT,
COMPONENT: PRIORITY_VISUAL_COMPONENT,
BRUSH: PRIORITY_VISUAL_BRUSH,
CHART_ITEM: PRIORITY_VISUAL_CHART_DATA_CUSTOM,
ARIA: PRIORITY_VISUAL_ARIA,
DECAL: PRIORITY_VISUAL_DECAL
}
}; // Main process have three entries: `setOption`, `dispatchAction` and `resize`,
// where they must not be invoked nestedly, except the only case: invoke
// dispatchAction with updateMethod "none" in main process.
// This flag is used to carry out this rule.
// All events will be triggered out side main process (i.e. when !this[IN_MAIN_PROCESS]).
var IN_MAIN_PROCESS_KEY = '__flagInMainProcess';
var OPTION_UPDATED_KEY = '__optionUpdated';
var STATUS_NEEDS_UPDATE_KEY = '__needsUpdateStatus';
var ACTION_REG = /^[a-zA-Z0-9_]+$/;
var CONNECT_STATUS_KEY = '__connectUpdateStatus';
var CONNECT_STATUS_PENDING = 0;
var CONNECT_STATUS_UPDATING = 1;
var CONNECT_STATUS_UPDATED = 2;
function createRegisterEventWithLowercaseECharts(method) {
return function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
if (this.isDisposed()) {
disposedWarning(this.id);
return;
}
return toLowercaseNameAndCallEventful(this, method, args);
};
}
function createRegisterEventWithLowercaseMessageCenter(method) {
return function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
return toLowercaseNameAndCallEventful(this, method, args);
};
}
function toLowercaseNameAndCallEventful(host, method, args) {
// `args[0]` is event name. Event name is all lowercase.
args[0] = args[0] && args[0].toLowerCase();
return Eventful.prototype[method].apply(host, args);
}
var MessageCenter =
/** @class */
function (_super) {
__extends(MessageCenter, _super);
function MessageCenter() {
return _super !== null && _super.apply(this, arguments) || this;
}
return MessageCenter;
}(Eventful);
var messageCenterProto = MessageCenter.prototype;
messageCenterProto.on = createRegisterEventWithLowercaseMessageCenter('on');
messageCenterProto.off = createRegisterEventWithLowercaseMessageCenter('off'); // ---------------------------------------
// Internal method names for class ECharts
// ---------------------------------------
var prepare;
var prepareView;
var updateDirectly;
var updateMethods;
var doConvertPixel;
var updateStreamModes;
var doDispatchAction;
var flushPendingActions;
var triggerUpdatedEvent;
var bindRenderedEvent;
var bindMouseEvent;
var clearColorPalette;
var render;
var renderComponents;
var renderSeries;
var performPostUpdateFuncs;
var createExtensionAPI;
var enableConnect;
var setTransitionOpt;
var markStatusToUpdate;
var applyChangedStates;
var ECharts =
/** @class */
function (_super) {
__extends(ECharts, _super);
function ECharts(dom, // Theme name or themeOption.
theme, opts) {
var _this = _super.call(this, new ECEventProcessor()) || this;
_this._chartsViews = [];
_this._chartsMap = {};
_this._componentsViews = [];
_this._componentsMap = {}; // Can't dispatch action during rendering procedure
_this._pendingActions = [];
opts = opts || {}; // Get theme by name
if (typeof theme === 'string') {
theme = themeStorage[theme];
}
_this._dom = dom;
var defaultRenderer = 'canvas';
var defaultUseDirtyRect = false;
if ("development" !== 'production') {
var root =
/* eslint-disable-next-line */
hasWindow ? window : global;
defaultRenderer = root.__ECHARTS__DEFAULT__RENDERER__ || defaultRenderer;
var devUseDirtyRect = root.__ECHARTS__DEFAULT__USE_DIRTY_RECT__;
defaultUseDirtyRect = devUseDirtyRect == null ? defaultUseDirtyRect : devUseDirtyRect;
}
var zr = _this._zr = init(dom, {
renderer: opts.renderer || defaultRenderer,
devicePixelRatio: opts.devicePixelRatio,
width: opts.width,
height: opts.height,
useDirtyRect: opts.useDirtyRect == null ? defaultUseDirtyRect : opts.useDirtyRect
}); // Expect 60 fps.
_this._throttledZrFlush = throttle(bind(zr.flush, zr), 17);
theme = clone(theme);
theme && globalBackwardCompat(theme, true);
_this._theme = theme;
_this._locale = createLocaleObject(opts.locale || SYSTEM_LANG);
_this._coordSysMgr = new CoordinateSystemManager();
var api = _this._api = createExtensionAPI(_this); // Sort on demand
function prioritySortFunc(a, b) {
return a.__prio - b.__prio;
}
sort(visualFuncs, prioritySortFunc);
sort(dataProcessorFuncs, prioritySortFunc);
_this._scheduler = new Scheduler(_this, api, dataProcessorFuncs, visualFuncs);
_this._messageCenter = new MessageCenter();
_this._labelManager = new LabelManager(); // Init mouse events
_this._initEvents(); // In case some people write `window.onresize = chart.resize`
_this.resize = bind(_this.resize, _this);
zr.animation.on('frame', _this._onframe, _this);
bindRenderedEvent(zr, _this);
bindMouseEvent(zr, _this); // ECharts instance can be used as value.
setAsPrimitive(_this);
return _this;
}
ECharts.prototype._onframe = function () {
if (this._disposed) {
return;
}
applyChangedStates(this);
var scheduler = this._scheduler; // Lazy update
if (this[OPTION_UPDATED_KEY]) {
var silent = this[OPTION_UPDATED_KEY].silent;
this[IN_MAIN_PROCESS_KEY] = true;
prepare(this);
updateMethods.update.call(this); // At present, in each frame, zrender performs:
// (1) animation step forward.
// (2) trigger('frame') (where this `_onframe` is called)
// (3) zrender flush (render).
// If we do nothing here, since we use `setToFinal: true`, the step (3) above
// will render the final state of the elements before the real animation started.
this._zr.flush();
this[IN_MAIN_PROCESS_KEY] = false;
this[OPTION_UPDATED_KEY] = false;
flushPendingActions.call(this, silent);
triggerUpdatedEvent.call(this, silent);
} // Avoid do both lazy update and progress in one frame.
else if (scheduler.unfinished) {
// Stream progress.
var remainTime = TEST_FRAME_REMAIN_TIME;
var ecModel = this._model;
var api = this._api;
scheduler.unfinished = false;
do {
var startTime = +new Date();
scheduler.performSeriesTasks(ecModel); // Currently dataProcessorFuncs do not check threshold.
scheduler.performDataProcessorTasks(ecModel);
updateStreamModes(this, ecModel); // Do not update coordinate system here. Because that coord system update in
// each frame is not a good user experience. So we follow the rule that
// the extent of the coordinate system is determin in the first frame (the
// frame is executed immedietely after task reset.
// this._coordSysMgr.update(ecModel, api);
// console.log('--- ec frame visual ---', remainTime);
scheduler.performVisualTasks(ecModel);
renderSeries(this, this._model, api, 'remain');
remainTime -= +new Date() - startTime;
} while (remainTime > 0 && scheduler.unfinished); // Call flush explicitly for trigger finished event.
if (!scheduler.unfinished) {
this._zr.flush();
} // Else, zr flushing be ensue within the same frame,
// because zr flushing is after onframe event.
}
};
ECharts.prototype.getDom = function () {
return this._dom;
};
ECharts.prototype.getId = function () {
return this.id;
};
ECharts.prototype.getZr = function () {
return this._zr;
};
/* eslint-disable-next-line */
ECharts.prototype.setOption = function (option, notMerge, lazyUpdate) {
if ("development" !== 'production') {
assert$1(!this[IN_MAIN_PROCESS_KEY], '`setOption` should not be called during main process.');
}
if (this._disposed) {
disposedWarning(this.id);
return;
}
var silent;
var replaceMerge;
var transitionOpt;
if (isObject$2(notMerge)) {
lazyUpdate = notMerge.lazyUpdate;
silent = notMerge.silent;
replaceMerge = notMerge.replaceMerge;
transitionOpt = notMerge.transition;
notMerge = notMerge.notMerge;
}
this[IN_MAIN_PROCESS_KEY] = true;
if (!this._model || notMerge) {
var optionManager = new OptionManager(this._api);
var theme = this._theme;
var ecModel = this._model = new GlobalModel();
ecModel.scheduler = this._scheduler;
ecModel.init(null, null, null, theme, this._locale, optionManager);
}
this._model.setOption(option, {
replaceMerge: replaceMerge
}, optionPreprocessorFuncs);
setTransitionOpt(this, transitionOpt);
if (lazyUpdate) {
this[OPTION_UPDATED_KEY] = {
silent: silent
};
this[IN_MAIN_PROCESS_KEY] = false; // `setOption(option, {lazyMode: true})` may be called when zrender has been slept.
// It should wake it up to make sure zrender start to render at the next frame.
this.getZr().wakeUp();
} else {
prepare(this);
updateMethods.update.call(this); // Ensure zr refresh sychronously, and then pixel in canvas can be
// fetched after `setOption`.
this._zr.flush();
this[OPTION_UPDATED_KEY] = false;
this[IN_MAIN_PROCESS_KEY] = false;
flushPendingActions.call(this, silent);
triggerUpdatedEvent.call(this, silent);
}
};
/**
* @DEPRECATED
*/
ECharts.prototype.setTheme = function () {
console.error('ECharts#setTheme() is DEPRECATED in ECharts 3.0');
}; // We don't want developers to use getModel directly.
ECharts.prototype.getModel = function () {
return this._model;
};
ECharts.prototype.getOption = function () {
return this._model && this._model.getOption();
};
ECharts.prototype.getWidth = function () {
return this._zr.getWidth();
};
ECharts.prototype.getHeight = function () {
return this._zr.getHeight();
};
ECharts.prototype.getDevicePixelRatio = function () {
return this._zr.painter.dpr
/* eslint-disable-next-line */
|| hasWindow && window.devicePixelRatio || 1;
};
/**
* Get canvas which has all thing rendered
*/
ECharts.prototype.getRenderedCanvas = function (opts) {
if (!env.canvasSupported) {
return;
}
opts = extend({}, opts || {});
opts.pixelRatio = opts.pixelRatio || this.getDevicePixelRatio();
opts.backgroundColor = opts.backgroundColor || this._model.get('backgroundColor');
var zr = this._zr; // let list = zr.storage.getDisplayList();
// Stop animations
// Never works before in init animation, so remove it.
// zrUtil.each(list, function (el) {
// el.stopAnimation(true);
// });
return zr.painter.getRenderedCanvas(opts);
};
/**
* Get svg data url
*/
ECharts.prototype.getSvgDataURL = function () {
if (!env.svgSupported) {
return;
}
var zr = this._zr;
var list = zr.storage.getDisplayList(); // Stop animations
each(list, function (el) {
el.stopAnimation(null, true);
});
return zr.painter.toDataURL();
};
ECharts.prototype.getDataURL = function (opts) {
if (this._disposed) {
disposedWarning(this.id);
return;
}
opts = opts || {};
var excludeComponents = opts.excludeComponents;
var ecModel = this._model;
var excludesComponentViews = [];
var self = this;
each$3(excludeComponents, function (componentType) {
ecModel.eachComponent({
mainType: componentType
}, function (component) {
var view = self._componentsMap[component.__viewId];
if (!view.group.ignore) {
excludesComponentViews.push(view);
view.group.ignore = true;
}
});
});
var url = this._zr.painter.getType() === 'svg' ? this.getSvgDataURL() : this.getRenderedCanvas(opts).toDataURL('image/' + (opts && opts.type || 'png'));
each$3(excludesComponentViews, function (view) {
view.group.ignore = false;
});
return url;
};
ECharts.prototype.getConnectedDataURL = function (opts) {
if (this._disposed) {
disposedWarning(this.id);
return;
}
if (!env.canvasSupported) {
return;
}
var isSvg = opts.type === 'svg';
var groupId = this.group;
var mathMin = Math.min;
var mathMax = Math.max;
var MAX_NUMBER = Infinity;
if (connectedGroups[groupId]) {
var left_1 = MAX_NUMBER;
var top_1 = MAX_NUMBER;
var right_1 = -MAX_NUMBER;
var bottom_1 = -MAX_NUMBER;
var canvasList_1 = [];
var dpr_1 = opts && opts.pixelRatio || this.getDevicePixelRatio();
each(instances$1, function (chart, id) {
if (chart.group === groupId) {
var canvas = isSvg ? chart.getZr().painter.getSvgDom().innerHTML : chart.getRenderedCanvas(clone(opts));
var boundingRect = chart.getDom().getBoundingClientRect();
left_1 = mathMin(boundingRect.left, left_1);
top_1 = mathMin(boundingRect.top, top_1);
right_1 = mathMax(boundingRect.right, right_1);
bottom_1 = mathMax(boundingRect.bottom, bottom_1);
canvasList_1.push({
dom: canvas,
left: boundingRect.left,
top: boundingRect.top
});
}
});
left_1 *= dpr_1;
top_1 *= dpr_1;
right_1 *= dpr_1;
bottom_1 *= dpr_1;
var width = right_1 - left_1;
var height = bottom_1 - top_1;
var targetCanvas = createCanvas();
var zr_1 = init(targetCanvas, {
renderer: isSvg ? 'svg' : 'canvas'
});
zr_1.resize({
width: width,
height: height
});
if (isSvg) {
var content_1 = '';
each$3(canvasList_1, function (item) {
var x = item.left - left_1;
var y = item.top - top_1;
content_1 += '' + item.dom + '';
});
zr_1.painter.getSvgRoot().innerHTML = content_1;
if (opts.connectedBackgroundColor) {
zr_1.painter.setBackgroundColor(opts.connectedBackgroundColor);
}
zr_1.refreshImmediately();
return zr_1.painter.toDataURL();
} else {
// Background between the charts
if (opts.connectedBackgroundColor) {
zr_1.add(new Rect({
shape: {
x: 0,
y: 0,
width: width,
height: height
},
style: {
fill: opts.connectedBackgroundColor
}
}));
}
each$3(canvasList_1, function (item) {
var img = new ZRImage({
style: {
x: item.left * dpr_1 - left_1,
y: item.top * dpr_1 - top_1,
image: item.dom
}
});
zr_1.add(img);
});
zr_1.refreshImmediately();
return targetCanvas.toDataURL('image/' + (opts && opts.type || 'png'));
}
} else {
return this.getDataURL(opts);
}
};
ECharts.prototype.convertToPixel = function (finder, value) {
return doConvertPixel(this, 'convertToPixel', finder, value);
};
ECharts.prototype.convertFromPixel = function (finder, value) {
return doConvertPixel(this, 'convertFromPixel', finder, value);
};
/**
* Is the specified coordinate systems or components contain the given pixel point.
* @param {Array|number} value
* @return {boolean} result
*/
ECharts.prototype.containPixel = function (finder, value) {
if (this._disposed) {
disposedWarning(this.id);
return;
}
var ecModel = this._model;
var result;
var findResult = parseFinder(ecModel, finder);
each(findResult, function (models, key) {
key.indexOf('Models') >= 0 && each(models, function (model) {
var coordSys = model.coordinateSystem;
if (coordSys && coordSys.containPoint) {
result = result || !!coordSys.containPoint(value);
} else if (key === 'seriesModels') {
var view = this._chartsMap[model.__viewId];
if (view && view.containPoint) {
result = result || view.containPoint(value, model);
} else {
if ("development" !== 'production') {
console.warn(key + ': ' + (view ? 'The found component do not support containPoint.' : 'No view mapping to the found component.'));
}
}
} else {
if ("development" !== 'production') {
console.warn(key + ': containPoint is not supported');
}
}
}, this);
}, this);
return !!result;
};
/**
* Get visual from series or data.
* @param finder
* If string, e.g., 'series', means {seriesIndex: 0}.
* If Object, could contain some of these properties below:
* {
* seriesIndex / seriesId / seriesName,
* dataIndex / dataIndexInside
* }
* If dataIndex is not specified, series visual will be fetched,
* but not data item visual.
* If all of seriesIndex, seriesId, seriesName are not specified,
* visual will be fetched from first series.
* @param visualType 'color', 'symbol', 'symbolSize'
*/
ECharts.prototype.getVisual = function (finder, visualType) {
var ecModel = this._model;
var parsedFinder = parseFinder(ecModel, finder, {
defaultMainType: 'series'
});
var seriesModel = parsedFinder.seriesModel;
if ("development" !== 'production') {
if (!seriesModel) {
console.warn('There is no specified seires model');
}
}
var data = seriesModel.getData();
var dataIndexInside = parsedFinder.hasOwnProperty('dataIndexInside') ? parsedFinder.dataIndexInside : parsedFinder.hasOwnProperty('dataIndex') ? data.indexOfRawIndex(parsedFinder.dataIndex) : null;
return dataIndexInside != null ? getItemVisualFromData(data, dataIndexInside, visualType) : getVisualFromData(data, visualType);
};
/**
* Get view of corresponding component model
*/
ECharts.prototype.getViewOfComponentModel = function (componentModel) {
return this._componentsMap[componentModel.__viewId];
};
/**
* Get view of corresponding series model
*/
ECharts.prototype.getViewOfSeriesModel = function (seriesModel) {
return this._chartsMap[seriesModel.__viewId];
};
ECharts.prototype._initEvents = function () {
var _this = this;
each$3(MOUSE_EVENT_NAMES, function (eveName) {
var handler = function (e) {
var ecModel = _this.getModel();
var el = e.target;
var params;
var isGlobalOut = eveName === 'globalout'; // no e.target when 'globalout'.
if (isGlobalOut) {
params = {};
} else {
el && findEventDispatcher(el, function (parent) {
var ecData = getECData(parent);
if (ecData && ecData.dataIndex != null) {
var dataModel = ecData.dataModel || ecModel.getSeriesByIndex(ecData.seriesIndex);
params = dataModel && dataModel.getDataParams(ecData.dataIndex, ecData.dataType) || {};
return true;
} // If element has custom eventData of components
else if (ecData.eventData) {
params = extend({}, ecData.eventData);
return true;
}
}, true);
} // Contract: if params prepared in mouse event,
// these properties must be specified:
// {
// componentType: string (component main type)
// componentIndex: number
// }
// Otherwise event query can not work.
if (params) {
var componentType = params.componentType;
var componentIndex = params.componentIndex; // Special handling for historic reason: when trigger by
// markLine/markPoint/markArea, the componentType is
// 'markLine'/'markPoint'/'markArea', but we should better
// enable them to be queried by seriesIndex, since their
// option is set in each series.
if (componentType === 'markLine' || componentType === 'markPoint' || componentType === 'markArea') {
componentType = 'series';
componentIndex = params.seriesIndex;
}
var model = componentType && componentIndex != null && ecModel.getComponent(componentType, componentIndex);
var view = model && _this[model.mainType === 'series' ? '_chartsMap' : '_componentsMap'][model.__viewId];
if ("development" !== 'production') {
// `event.componentType` and `event[componentTpype + 'Index']` must not
// be missed, otherwise there is no way to distinguish source component.
// See `dataFormat.getDataParams`.
if (!isGlobalOut && !(model && view)) {
console.warn('model or view can not be found by params');
}
}
params.event = e;
params.type = eveName;
_this._$eventProcessor.eventInfo = {
targetEl: el,
packedEvent: params,
model: model,
view: view
};
_this.trigger(eveName, params);
}
}; // Consider that some component (like tooltip, brush, ...)
// register zr event handler, but user event handler might
// do anything, such as call `setOption` or `dispatchAction`,
// which probably update any of the content and probably
// cause problem if it is called previous other inner handlers.
handler.zrEventfulCallAtLast = true;
_this._zr.on(eveName, handler, _this);
});
each$3(eventActionMap, function (actionType, eventType) {
_this._messageCenter.on(eventType, function (event) {
this.trigger(eventType, event);
}, _this);
}); // Extra events
// TODO register?
each$3(['selectchanged'], function (eventType) {
_this._messageCenter.on(eventType, function (event) {
this.trigger(eventType, event);
}, _this);
});
handleLegacySelectEvents(this._messageCenter, this, this._api);
};
ECharts.prototype.isDisposed = function () {
return this._disposed;
};
ECharts.prototype.clear = function () {
if (this._disposed) {
disposedWarning(this.id);
return;
}
this.setOption({
series: []
}, true);
};
ECharts.prototype.dispose = function () {
if (this._disposed) {
disposedWarning(this.id);
return;
}
this._disposed = true;
setAttribute(this.getDom(), DOM_ATTRIBUTE_KEY, '');
var api = this._api;
var ecModel = this._model;
each$3(this._componentsViews, function (component) {
component.dispose(ecModel, api);
});
each$3(this._chartsViews, function (chart) {
chart.dispose(ecModel, api);
}); // Dispose after all views disposed
this._zr.dispose();
delete instances$1[this.id];
};
/**
* Resize the chart
*/
ECharts.prototype.resize = function (opts) {
if ("development" !== 'production') {
assert$1(!this[IN_MAIN_PROCESS_KEY], '`resize` should not be called during main process.');
}
if (this._disposed) {
disposedWarning(this.id);
return;
}
this._zr.resize(opts);
var ecModel = this._model; // Resize loading effect
this._loadingFX && this._loadingFX.resize();
if (!ecModel) {
return;
}
var optionChanged = ecModel.resetOption('media');
var silent = opts && opts.silent;
this[IN_MAIN_PROCESS_KEY] = true;
optionChanged && prepare(this);
updateMethods.update.call(this, {
type: 'resize',
animation: {
// Disable animation
duration: 0
}
});
this[IN_MAIN_PROCESS_KEY] = false;
flushPendingActions.call(this, silent);
triggerUpdatedEvent.call(this, silent);
};
ECharts.prototype.showLoading = function (name, cfg) {
if (this._disposed) {
disposedWarning(this.id);
return;
}
if (isObject$2(name)) {
cfg = name;
name = '';
}
name = name || 'default';
this.hideLoading();
if (!loadingEffects[name]) {
if ("development" !== 'production') {
console.warn('Loading effects ' + name + ' not exists.');
}
return;
}
var el = loadingEffects[name](this._api, cfg);
var zr = this._zr;
this._loadingFX = el;
zr.add(el);
};
/**
* Hide loading effect
*/
ECharts.prototype.hideLoading = function () {
if (this._disposed) {
disposedWarning(this.id);
return;
}
this._loadingFX && this._zr.remove(this._loadingFX);
this._loadingFX = null;
};
ECharts.prototype.makeActionFromEvent = function (eventObj) {
var payload = extend({}, eventObj);
payload.type = eventActionMap[eventObj.type];
return payload;
};
/**
* @param opt If pass boolean, means opt.silent
* @param opt.silent Default `false`. Whether trigger events.
* @param opt.flush Default `undefined`.
* true: Flush immediately, and then pixel in canvas can be fetched
* immediately. Caution: it might affect performance.
* false: Not flush.
* undefined: Auto decide whether perform flush.
*/
ECharts.prototype.dispatchAction = function (payload, opt) {
if (this._disposed) {
disposedWarning(this.id);
return;
}
if (!isObject$2(opt)) {
opt = {
silent: !!opt
};
}
if (!actions[payload.type]) {
return;
} // Avoid dispatch action before setOption. Especially in `connect`.
if (!this._model) {
return;
} // May dispatchAction in rendering procedure
if (this[IN_MAIN_PROCESS_KEY]) {
this._pendingActions.push(payload);
return;
}
var silent = opt.silent;
doDispatchAction.call(this, payload, silent);
var flush = opt.flush;
if (flush) {
this._zr.flush();
} else if (flush !== false && env.browser.weChat) {
// In WeChat embeded browser, `requestAnimationFrame` and `setInterval`
// hang when sliding page (on touch event), which cause that zr does not
// refresh util user interaction finished, which is not expected.
// But `dispatchAction` may be called too frequently when pan on touch
// screen, which impacts performance if do not throttle them.
this._throttledZrFlush();
}
flushPendingActions.call(this, silent);
triggerUpdatedEvent.call(this, silent);
};
ECharts.prototype.updateLabelLayout = function () {
var labelManager = this._labelManager;
labelManager.updateLayoutConfig(this._api);
labelManager.layout(this._api);
labelManager.processLabelsOverall();
};
ECharts.prototype.appendData = function (params) {
if (this._disposed) {
disposedWarning(this.id);
return;
}
var seriesIndex = params.seriesIndex;
var ecModel = this.getModel();
var seriesModel = ecModel.getSeriesByIndex(seriesIndex);
if ("development" !== 'production') {
assert$1(params.data && seriesModel);
}
seriesModel.appendData(params); // Note: `appendData` does not support that update extent of coordinate
// system, util some scenario require that. In the expected usage of
// `appendData`, the initial extent of coordinate system should better
// be fixed by axis `min`/`max` setting or initial data, otherwise if
// the extent changed while `appendData`, the location of the painted
// graphic elements have to be changed, which make the usage of
// `appendData` meaningless.
this._scheduler.unfinished = true;
this.getZr().wakeUp();
}; // A work around for no `internal` modifier in ts yet but
// need to strictly hide private methods to JS users.
ECharts.internalField = function () {
prepare = function (ecIns) {
var scheduler = ecIns._scheduler;
scheduler.restorePipelines(ecIns._model);
scheduler.prepareStageTasks();
prepareView(ecIns, true);
prepareView(ecIns, false);
scheduler.plan();
};
/**
* Prepare view instances of charts and components
*/
prepareView = function (ecIns, isComponent) {
var ecModel = ecIns._model;
var scheduler = ecIns._scheduler;
var viewList = isComponent ? ecIns._componentsViews : ecIns._chartsViews;
var viewMap = isComponent ? ecIns._componentsMap : ecIns._chartsMap;
var zr = ecIns._zr;
var api = ecIns._api;
for (var i = 0; i < viewList.length; i++) {
viewList[i].__alive = false;
}
isComponent ? ecModel.eachComponent(function (componentType, model) {
componentType !== 'series' && doPrepare(model);
}) : ecModel.eachSeries(doPrepare);
function doPrepare(model) {
// By defaut view will be reused if possible for the case that `setOption` with "notMerge"
// mode and need to enable transition animation. (Usually, when they have the same id, or
// especially no id but have the same type & name & index. See the `model.id` generation
// rule in `makeIdAndName` and `viewId` generation rule here).
// But in `replaceMerge` mode, this feature should be able to disabled when it is clear that
// the new model has nothing to do with the old model.
var requireNewView = model.__requireNewView; // This command should not work twice.
model.__requireNewView = false; // Consider: id same and type changed.
var viewId = '_ec_' + model.id + '_' + model.type;
var view = !requireNewView && viewMap[viewId];
if (!view) {
var classType = parseClassType(model.type);
var Clazz = isComponent ? ComponentView.getClass(classType.main, classType.sub) : // FIXME:TS
// (ChartView as ChartViewConstructor).getClass('series', classType.sub)
// For backward compat, still support a chart type declared as only subType
// like "liquidfill", but recommend "series.liquidfill"
// But need a base class to make a type series.
ChartView.getClass(classType.sub);
if ("development" !== 'production') {
assert$1(Clazz, classType.sub + ' does not exist.');
}
view = new Clazz();
view.init(ecModel, api);
viewMap[viewId] = view;
viewList.push(view);
zr.add(view.group);
}
model.__viewId = view.__id = viewId;
view.__alive = true;
view.__model = model;
view.group.__ecComponentInfo = {
mainType: model.mainType,
index: model.componentIndex
};
!isComponent && scheduler.prepareView(view, model, ecModel, api);
}
for (var i = 0; i < viewList.length;) {
var view = viewList[i];
if (!view.__alive) {
!isComponent && view.renderTask.dispose();
zr.remove(view.group);
view.dispose(ecModel, api);
viewList.splice(i, 1);
if (viewMap[view.__id] === view) {
delete viewMap[view.__id];
}
view.__id = view.group.__ecComponentInfo = null;
} else {
i++;
}
}
};
updateDirectly = function (ecIns, method, payload, mainType, subType) {
var ecModel = ecIns._model;
ecModel.setUpdatePayload(payload); // broadcast
if (!mainType) {
// FIXME
// Chart will not be update directly here, except set dirty.
// But there is no such scenario now.
each$3([].concat(ecIns._componentsViews).concat(ecIns._chartsViews), callView);
return;
}
var query = {};
query[mainType + 'Id'] = payload[mainType + 'Id'];
query[mainType + 'Index'] = payload[mainType + 'Index'];
query[mainType + 'Name'] = payload[mainType + 'Name'];
var condition = {
mainType: mainType,
query: query
};
subType && (condition.subType = subType); // subType may be '' by parseClassType;
var excludeSeriesId = payload.excludeSeriesId;
var excludeSeriesIdMap;
if (excludeSeriesId != null) {
excludeSeriesIdMap = createHashMap();
each$3(normalizeToArray(excludeSeriesId), function (id) {
var modelId = convertOptionIdName(id, null);
if (modelId != null) {
excludeSeriesIdMap.set(modelId, true);
}
});
} // If dispatchAction before setOption, do nothing.
ecModel && ecModel.eachComponent(condition, function (model) {
if (!excludeSeriesIdMap || excludeSeriesIdMap.get(model.id) == null) {
if (isHighDownPayload(payload) && !payload.notBlur) {
if (model instanceof SeriesModel) {
toggleSeriesBlurStateFromPayload(model, payload, ecIns._api);
}
} else if (isSelectChangePayload(payload)) {
// TODO geo
if (model instanceof SeriesModel) {
toggleSelectionFromPayload(model, payload, ecIns._api);
updateSeriesElementSelection(model);
markStatusToUpdate(ecIns);
}
}
callView(ecIns[mainType === 'series' ? '_chartsMap' : '_componentsMap'][model.__viewId]);
}
}, ecIns);
function callView(view) {
view && view.__alive && view[method] && view[method](view.__model, ecModel, ecIns._api, payload);
}
};
updateMethods = {
prepareAndUpdate: function (payload) {
prepare(this);
updateMethods.update.call(this, payload);
},
update: function (payload) {
// console.profile && console.profile('update');
var ecModel = this._model;
var api = this._api;
var zr = this._zr;
var coordSysMgr = this._coordSysMgr;
var scheduler = this._scheduler; // update before setOption
if (!ecModel) {
return;
}
ecModel.setUpdatePayload(payload);
scheduler.restoreData(ecModel, payload);
scheduler.performSeriesTasks(ecModel); // TODO
// Save total ecModel here for undo/redo (after restoring data and before processing data).
// Undo (restoration of total ecModel) can be carried out in 'action' or outside API call.
// Create new coordinate system each update
// In LineView may save the old coordinate system and use it to get the orignal point
coordSysMgr.create(ecModel, api);
scheduler.performDataProcessorTasks(ecModel, payload); // Current stream render is not supported in data process. So we can update
// stream modes after data processing, where the filtered data is used to
// deteming whether use progressive rendering.
updateStreamModes(this, ecModel); // We update stream modes before coordinate system updated, then the modes info
// can be fetched when coord sys updating (consider the barGrid extent fix). But
// the drawback is the full coord info can not be fetched. Fortunately this full
// coord is not requied in stream mode updater currently.
coordSysMgr.update(ecModel, api);
clearColorPalette(ecModel);
scheduler.performVisualTasks(ecModel, payload);
render(this, ecModel, api, payload); // Set background
var backgroundColor = ecModel.get('backgroundColor') || 'transparent';
var darkMode = ecModel.get('darkMode'); // In IE8
if (!env.canvasSupported) {
var colorArr = parse(backgroundColor);
backgroundColor = stringify(colorArr, 'rgb');
if (colorArr[3] === 0) {
backgroundColor = 'transparent';
}
} else {
zr.setBackgroundColor(backgroundColor); // Force set dark mode.
if (darkMode != null && darkMode !== 'auto') {
zr.setDarkMode(darkMode);
}
}
performPostUpdateFuncs(ecModel, api); // console.profile && console.profileEnd('update');
},
updateTransform: function (payload) {
var _this = this;
var ecModel = this._model;
var api = this._api; // update before setOption
if (!ecModel) {
return;
}
ecModel.setUpdatePayload(payload); // ChartView.markUpdateMethod(payload, 'updateTransform');
var componentDirtyList = [];
ecModel.eachComponent(function (componentType, componentModel) {
if (componentType === 'series') {
return;
}
var componentView = _this.getViewOfComponentModel(componentModel);
if (componentView && componentView.__alive) {
if (componentView.updateTransform) {
var result = componentView.updateTransform(componentModel, ecModel, api, payload);
result && result.update && componentDirtyList.push(componentView);
} else {
componentDirtyList.push(componentView);
}
}
});
var seriesDirtyMap = createHashMap();
ecModel.eachSeries(function (seriesModel) {
var chartView = _this._chartsMap[seriesModel.__viewId];
if (chartView.updateTransform) {
var result = chartView.updateTransform(seriesModel, ecModel, api, payload);
result && result.update && seriesDirtyMap.set(seriesModel.uid, 1);
} else {
seriesDirtyMap.set(seriesModel.uid, 1);
}
});
clearColorPalette(ecModel); // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline.
// this._scheduler.performVisualTasks(ecModel, payload, 'layout', true);
this._scheduler.performVisualTasks(ecModel, payload, {
setDirty: true,
dirtyMap: seriesDirtyMap
}); // Currently, not call render of components. Geo render cost a lot.
// renderComponents(ecIns, ecModel, api, payload, componentDirtyList);
renderSeries(this, ecModel, api, payload, seriesDirtyMap);
performPostUpdateFuncs(ecModel, this._api);
},
updateView: function (payload) {
var ecModel = this._model; // update before setOption
if (!ecModel) {
return;
}
ecModel.setUpdatePayload(payload);
ChartView.markUpdateMethod(payload, 'updateView');
clearColorPalette(ecModel); // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline.
this._scheduler.performVisualTasks(ecModel, payload, {
setDirty: true
});
render(this, this._model, this._api, payload);
performPostUpdateFuncs(ecModel, this._api);
},
updateVisual: function (payload) {
// updateMethods.update.call(this, payload);
var _this = this;
var ecModel = this._model; // update before setOption
if (!ecModel) {
return;
}
ecModel.setUpdatePayload(payload); // clear all visual
ecModel.eachSeries(function (seriesModel) {
seriesModel.getData().clearAllVisual();
}); // Perform visual
ChartView.markUpdateMethod(payload, 'updateVisual');
clearColorPalette(ecModel); // Keep pipe to the exist pipeline because it depends on the render task of the full pipeline.
this._scheduler.performVisualTasks(ecModel, payload, {
visualType: 'visual',
setDirty: true
});
ecModel.eachComponent(function (componentType, componentModel) {
if (componentType !== 'series') {
var componentView = _this.getViewOfComponentModel(componentModel);
componentView && componentView.__alive && componentView.updateVisual(componentModel, ecModel, _this._api, payload);
}
});
ecModel.eachSeries(function (seriesModel) {
var chartView = _this._chartsMap[seriesModel.__viewId];
chartView.updateVisual(seriesModel, ecModel, _this._api, payload);
});
performPostUpdateFuncs(ecModel, this._api);
},
updateLayout: function (payload) {
updateMethods.update.call(this, payload);
}
};
doConvertPixel = function (ecIns, methodName, finder, value) {
if (ecIns._disposed) {
disposedWarning(ecIns.id);
return;
}
var ecModel = ecIns._model;
var coordSysList = ecIns._coordSysMgr.getCoordinateSystems();
var result;
var parsedFinder = parseFinder(ecModel, finder);
for (var i = 0; i < coordSysList.length; i++) {
var coordSys = coordSysList[i];
if (coordSys[methodName] && (result = coordSys[methodName](ecModel, parsedFinder, value)) != null) {
return result;
}
}
if ("development" !== 'production') {
console.warn('No coordinate system that supports ' + methodName + ' found by the given finder.');
}
};
updateStreamModes = function (ecIns, ecModel) {
var chartsMap = ecIns._chartsMap;
var scheduler = ecIns._scheduler;
ecModel.eachSeries(function (seriesModel) {
scheduler.updateStreamModes(seriesModel, chartsMap[seriesModel.__viewId]);
});
};
doDispatchAction = function (payload, silent) {
var _this = this;
var ecModel = this.getModel();
var payloadType = payload.type;
var escapeConnect = payload.escapeConnect;
var actionWrap = actions[payloadType];
var actionInfo = actionWrap.actionInfo;
var cptTypeTmp = (actionInfo.update || 'update').split(':');
var updateMethod = cptTypeTmp.pop();
var cptType = cptTypeTmp[0] != null && parseClassType(cptTypeTmp[0]);
this[IN_MAIN_PROCESS_KEY] = true;
var payloads = [payload];
var batched = false; // Batch action
if (payload.batch) {
batched = true;
payloads = map(payload.batch, function (item) {
item = defaults(extend({}, item), payload);
item.batch = null;
return item;
});
}
var eventObjBatch = [];
var eventObj;
var isSelectChange = isSelectChangePayload(payload);
var isStatusChange = isHighDownPayload(payload) || isSelectChange;
each$3(payloads, function (batchItem) {
// Action can specify the event by return it.
eventObj = actionWrap.action(batchItem, _this._model, _this._api); // Emit event outside
eventObj = eventObj || extend({}, batchItem); // Convert type to eventType
eventObj.type = actionInfo.event || eventObj.type;
eventObjBatch.push(eventObj); // light update does not perform data process, layout and visual.
if (isStatusChange) {
// method, payload, mainType, subType
updateDirectly(_this, updateMethod, batchItem, 'series'); // Mark status to update
markStatusToUpdate(_this);
} else if (cptType) {
updateDirectly(_this, updateMethod, batchItem, cptType.main, cptType.sub);
}
});
if (updateMethod !== 'none' && !isStatusChange && !cptType) {
// Still dirty
if (this[OPTION_UPDATED_KEY]) {
prepare(this);
updateMethods.update.call(this, payload);
this[OPTION_UPDATED_KEY] = false;
} else {
updateMethods[updateMethod].call(this, payload);
}
} // Follow the rule of action batch
if (batched) {
eventObj = {
type: actionInfo.event || payloadType,
escapeConnect: escapeConnect,
batch: eventObjBatch
};
} else {
eventObj = eventObjBatch[0];
}
this[IN_MAIN_PROCESS_KEY] = false;
if (!silent) {
var messageCenter = this._messageCenter;
messageCenter.trigger(eventObj.type, eventObj); // Extra triggered 'selectchanged' event
if (isSelectChange) {
var newObj = {
type: 'selectchanged',
escapeConnect: escapeConnect,
selected: getAllSelectedIndices(ecModel),
isFromClick: payload.isFromClick || false,
fromAction: payload.type,
fromActionPayload: payload
};
messageCenter.trigger(newObj.type, newObj);
}
}
};
flushPendingActions = function (silent) {
var pendingActions = this._pendingActions;
while (pendingActions.length) {
var payload = pendingActions.shift();
doDispatchAction.call(this, payload, silent);
}
};
triggerUpdatedEvent = function (silent) {
!silent && this.trigger('updated');
};
/**
* Event `rendered` is triggered when zr
* rendered. It is useful for realtime
* snapshot (reflect animation).
*
* Event `finished` is triggered when:
* (1) zrender rendering finished.
* (2) initial animation finished.
* (3) progressive rendering finished.
* (4) no pending action.
* (5) no delayed setOption needs to be processed.
*/
bindRenderedEvent = function (zr, ecIns) {
zr.on('rendered', function (params) {
ecIns.trigger('rendered', params); // The `finished` event should not be triggered repeatly,
// so it should only be triggered when rendering indeed happend
// in zrender. (Consider the case that dipatchAction is keep
// triggering when mouse move).
if ( // Although zr is dirty if initial animation is not finished
// and this checking is called on frame, we also check
// animation finished for robustness.
zr.animation.isFinished() && !ecIns[OPTION_UPDATED_KEY] && !ecIns._scheduler.unfinished && !ecIns._pendingActions.length) {
ecIns.trigger('finished');
}
});
};
bindMouseEvent = function (zr, ecIns) {
zr.on('mouseover', function (e) {
var el = e.target;
var dispatcher = findEventDispatcher(el, isHighDownDispatcher);
if (dispatcher) {
var ecData = getECData(dispatcher); // Try blur all in the related series. Then emphasis the hoverred.
// TODO. progressive mode.
toggleSeriesBlurState(ecData.seriesIndex, ecData.focus, ecData.blurScope, ecIns._api, true);
enterEmphasisWhenMouseOver(dispatcher, e);
markStatusToUpdate(ecIns);
}
}).on('mouseout', function (e) {
var el = e.target;
var dispatcher = findEventDispatcher(el, isHighDownDispatcher);
if (dispatcher) {
var ecData = getECData(dispatcher);
toggleSeriesBlurState(ecData.seriesIndex, ecData.focus, ecData.blurScope, ecIns._api, false);
leaveEmphasisWhenMouseOut(dispatcher, e);
markStatusToUpdate(ecIns);
}
}).on('click', function (e) {
var el = e.target;
var dispatcher = findEventDispatcher(el, function (target) {
return getECData(target).dataIndex != null;
}, true);
if (dispatcher) {
var actionType = dispatcher.selected ? 'unselect' : 'select';
var ecData = getECData(dispatcher);
ecIns._api.dispatchAction({
type: actionType,
dataType: ecData.dataType,
dataIndexInside: ecData.dataIndex,
seriesIndex: ecData.seriesIndex,
isFromClick: true
});
}
});
};
clearColorPalette = function (ecModel) {
ecModel.clearColorPalette();
ecModel.eachSeries(function (seriesModel) {
seriesModel.clearColorPalette();
});
};
render = function (ecIns, ecModel, api, payload) {
renderComponents(ecIns, ecModel, api, payload);
each$3(ecIns._chartsViews, function (chart) {
chart.__alive = false;
});
renderSeries(ecIns, ecModel, api, payload); // Remove groups of unrendered charts
each$3(ecIns._chartsViews, function (chart) {
if (!chart.__alive) {
chart.remove(ecModel, api);
}
});
};
renderComponents = function (ecIns, ecModel, api, payload, dirtyList) {
each$3(dirtyList || ecIns._componentsViews, function (componentView) {
var componentModel = componentView.__model;
clearStates(componentModel, componentView);
componentView.render(componentModel, ecModel, api, payload);
updateZ(componentModel, componentView);
updateStates(componentModel, componentView);
});
};
/**
* Render each chart and component
*/
renderSeries = function (ecIns, ecModel, api, payload, dirtyMap) {
// Render all charts
var scheduler = ecIns._scheduler;
var labelManager = ecIns._labelManager;
labelManager.clearLabels();
var unfinished = false;
ecModel.eachSeries(function (seriesModel) {
var chartView = ecIns._chartsMap[seriesModel.__viewId];
chartView.__alive = true;
var renderTask = chartView.renderTask;
scheduler.updatePayload(renderTask, payload); // TODO states on marker.
clearStates(seriesModel, chartView);
if (dirtyMap && dirtyMap.get(seriesModel.uid)) {
renderTask.dirty();
}
if (renderTask.perform(scheduler.getPerformArgs(renderTask))) {
unfinished = true;
}
seriesModel.__transientTransitionOpt = null;
chartView.group.silent = !!seriesModel.get('silent'); // Should not call markRedraw on group, because it will disable zrender
// increamental render (alway render from the __startIndex each frame)
// chartView.group.markRedraw();
updateBlend(seriesModel, chartView);
updateSeriesElementSelection(seriesModel); // Add labels.
labelManager.addLabelsOfSeries(chartView);
});
scheduler.unfinished = unfinished || scheduler.unfinished;
labelManager.updateLayoutConfig(api);
labelManager.layout(api);
labelManager.processLabelsOverall();
ecModel.eachSeries(function (seriesModel) {
var chartView = ecIns._chartsMap[seriesModel.__viewId]; // Update Z after labels updated. Before applying states.
updateZ(seriesModel, chartView); // NOTE: Update states after label is updated.
// label should be in normal status when layouting.
updateStates(seriesModel, chartView);
}); // If use hover layer
updateHoverLayerStatus(ecIns, ecModel);
};
performPostUpdateFuncs = function (ecModel, api) {
each$3(postUpdateFuncs, function (func) {
func(ecModel, api);
});
};
markStatusToUpdate = function (ecIns) {
ecIns[STATUS_NEEDS_UPDATE_KEY] = true; // Wake up zrender if it's sleep. Let it update states in the next frame.
ecIns.getZr().wakeUp();
};
applyChangedStates = function (ecIns) {
if (!ecIns[STATUS_NEEDS_UPDATE_KEY]) {
return;
}
ecIns.getZr().storage.traverse(function (el) {
// Not applied on removed elements, it may still in fading.
if (isElementRemoved(el)) {
return;
}
applyElementStates(el);
});
ecIns[STATUS_NEEDS_UPDATE_KEY] = false;
};
function applyElementStates(el) {
var newStates = [];
var oldStates = el.currentStates; // Keep other states.
for (var i = 0; i < oldStates.length; i++) {
var stateName = oldStates[i];
if (!(stateName === 'emphasis' || stateName === 'blur' || stateName === 'select')) {
newStates.push(stateName);
}
} // Only use states when it's exists.
if (el.selected && el.states.select) {
newStates.push('select');
}
if (el.hoverState === HOVER_STATE_EMPHASIS && el.states.emphasis) {
newStates.push('emphasis');
} else if (el.hoverState === HOVER_STATE_BLUR && el.states.blur) {
newStates.push('blur');
}
el.useStates(newStates);
}
function updateHoverLayerStatus(ecIns, ecModel) {
var zr = ecIns._zr;
var storage = zr.storage;
var elCount = 0;
storage.traverse(function (el) {
if (!el.isGroup) {
elCount++;
}
});
if (elCount > ecModel.get('hoverLayerThreshold') && !env.node && !env.worker) {
ecModel.eachSeries(function (seriesModel) {
if (seriesModel.preventUsingHoverLayer) {
return;
}
var chartView = ecIns._chartsMap[seriesModel.__viewId];
if (chartView.__alive) {
chartView.group.traverse(function (el) {
if (el.states.emphasis) {
el.states.emphasis.hoverLayer = true;
}
});
}
});
}
}
/**
* Update chart and blend.
*/
function updateBlend(seriesModel, chartView) {
var blendMode = seriesModel.get('blendMode') || null;
if ("development" !== 'production') {
if (!env.canvasSupported && blendMode && blendMode !== 'source-over') {
console.warn('Only canvas support blendMode');
}
}
chartView.group.traverse(function (el) {
// FIXME marker and other components
if (!el.isGroup) {
// DONT mark the element dirty. In case element is incremental and don't wan't to rerender.
el.style.blend = blendMode;
}
if (el.eachPendingDisplayable) {
el.eachPendingDisplayable(function (displayable) {
displayable.style.blend = blendMode;
});
}
});
}
function updateZ(model, view) {
if (model.preventAutoZ) {
return;
}
var z = model.get('z');
var zlevel = model.get('zlevel'); // Set z and zlevel
view.group.traverse(function (el) {
if (!el.isGroup) {
z != null && (el.z = z);
zlevel != null && (el.zlevel = zlevel); // TODO if textContent is on group.
var label = el.getTextContent();
var labelLine = el.getTextGuideLine();
if (label) {
label.z = el.z;
label.zlevel = el.zlevel; // lift z2 of text content
// TODO if el.emphasis.z2 is spcefied, what about textContent.
label.z2 = el.z2 + 2;
}
if (labelLine) {
var showAbove = el.textGuideLineConfig && el.textGuideLineConfig.showAbove;
labelLine.z = el.z;
labelLine.zlevel = el.zlevel;
labelLine.z2 = el.z2 + (showAbove ? 1 : -1);
}
}
});
}
// TODO States on component.
function clearStates(model, view) {
view.group.traverse(function (el) {
// Not applied on removed elements, it may still in fading.
if (isElementRemoved(el)) {
return;
}
var textContent = el.getTextContent();
var textGuide = el.getTextGuideLine();
if (el.stateTransition) {
el.stateTransition = null;
}
if (textContent && textContent.stateTransition) {
textContent.stateTransition = null;
}
if (textGuide && textGuide.stateTransition) {
textGuide.stateTransition = null;
} // TODO If el is incremental.
if (el.hasState()) {
el.prevStates = el.currentStates;
el.clearStates();
} else if (el.prevStates) {
el.prevStates = null;
}
});
}
function updateStates(model, view) {
var stateAnimationModel = model.getModel('stateAnimation');
var enableAnimation = model.isAnimationEnabled();
var duration = stateAnimationModel.get('duration');
var stateTransition = duration > 0 ? {
duration: duration,
delay: stateAnimationModel.get('delay'),
easing: stateAnimationModel.get('easing') // additive: stateAnimationModel.get('additive')
} : null;
view.group.traverse(function (el) {
if (el.states && el.states.emphasis) {
// Not applied on removed elements, it may still in fading.
if (isElementRemoved(el)) {
return;
}
if (el instanceof Path) {
savePathStates(el);
} // Only updated on changed element. In case element is incremental and don't wan't to rerender.
// TODO, a more proper way?
if (el.__dirty) {
var prevStates = el.prevStates; // Restore states without animation
if (prevStates) {
el.useStates(prevStates);
}
} // Update state transition and enable animation again.
if (enableAnimation) {
el.stateTransition = stateTransition;
var textContent = el.getTextContent();
var textGuide = el.getTextGuideLine(); // TODO Is it necessary to animate label?
if (textContent) {
textContent.stateTransition = stateTransition;
}
if (textGuide) {
textGuide.stateTransition = stateTransition;
}
} // The use higlighted and selected flag to toggle states.
if (el.__dirty) {
applyElementStates(el);
}
}
});
}
createExtensionAPI = function (ecIns) {
return new (
/** @class */
function (_super) {
__extends(class_1, _super);
function class_1() {
return _super !== null && _super.apply(this, arguments) || this;
}
class_1.prototype.getCoordinateSystems = function () {
return ecIns._coordSysMgr.getCoordinateSystems();
};
class_1.prototype.getComponentByElement = function (el) {
while (el) {
var modelInfo = el.__ecComponentInfo;
if (modelInfo != null) {
return ecIns._model.getComponent(modelInfo.mainType, modelInfo.index);
}
el = el.parent;
}
};
class_1.prototype.enterEmphasis = function (el, highlightDigit) {
enterEmphasis(el, highlightDigit);
markStatusToUpdate(ecIns);
};
class_1.prototype.leaveEmphasis = function (el, highlightDigit) {
leaveEmphasis(el, highlightDigit);
markStatusToUpdate(ecIns);
};
class_1.prototype.enterBlur = function (el) {
enterBlur(el);
markStatusToUpdate(ecIns);
};
class_1.prototype.leaveBlur = function (el) {
leaveBlur(el);
markStatusToUpdate(ecIns);
};
class_1.prototype.enterSelect = function (el) {
enterSelect(el);
markStatusToUpdate(ecIns);
};
class_1.prototype.leaveSelect = function (el) {
leaveSelect(el);
markStatusToUpdate(ecIns);
};
class_1.prototype.getModel = function () {
return ecIns.getModel();
};
class_1.prototype.getViewOfComponentModel = function (componentModel) {
return ecIns.getViewOfComponentModel(componentModel);
};
class_1.prototype.getViewOfSeriesModel = function (seriesModel) {
return ecIns.getViewOfSeriesModel(seriesModel);
};
return class_1;
}(ExtensionAPI))(ecIns);
};
enableConnect = function (chart) {
function updateConnectedChartsStatus(charts, status) {
for (var i = 0; i < charts.length; i++) {
var otherChart = charts[i];
otherChart[CONNECT_STATUS_KEY] = status;
}
}
each$3(eventActionMap, function (actionType, eventType) {
chart._messageCenter.on(eventType, function (event) {
if (connectedGroups[chart.group] && chart[CONNECT_STATUS_KEY] !== CONNECT_STATUS_PENDING) {
if (event && event.escapeConnect) {
return;
}
var action_1 = chart.makeActionFromEvent(event);
var otherCharts_1 = [];
each$3(instances$1, function (otherChart) {
if (otherChart !== chart && otherChart.group === chart.group) {
otherCharts_1.push(otherChart);
}
});
updateConnectedChartsStatus(otherCharts_1, CONNECT_STATUS_PENDING);
each$3(otherCharts_1, function (otherChart) {
if (otherChart[CONNECT_STATUS_KEY] !== CONNECT_STATUS_UPDATING) {
otherChart.dispatchAction(action_1);
}
});
updateConnectedChartsStatus(otherCharts_1, CONNECT_STATUS_UPDATED);
}
});
});
};
setTransitionOpt = function (chart, transitionOpt) {
var ecModel = chart._model;
each(normalizeToArray(transitionOpt), function (transOpt) {
var errMsg;
var fromOpt = transOpt.from;
var toOpt = transOpt.to;
if (toOpt == null) {
if ("development" !== 'production') {
errMsg = '`transition.to` must be specified.';
}
throwError(errMsg);
}
var finderOpt = {
includeMainTypes: ['series'],
enableAll: false,
enableNone: false
};
var fromResult = fromOpt ? parseFinder(ecModel, fromOpt, finderOpt) : null;
var toResult = parseFinder(ecModel, toOpt, finderOpt);
var toSeries = toResult.seriesModel;
if (toSeries == null) {
errMsg = '';
if ("development" !== 'production') {
errMsg = '`transition` is only supported on series.';
}
}
if (fromResult && fromResult.seriesModel !== toSeries) {
errMsg = '';
if ("development" !== 'production') {
errMsg = '`transition.from` and `transition.to` must be specified to the same series.';
}
}
if (errMsg != null) {
throwError(errMsg);
} // Just a temp solution: mount them on series.
toSeries.__transientTransitionOpt = {
from: fromOpt ? fromOpt.dimension : null,
to: toOpt.dimension,
dividingMethod: transOpt.dividingMethod
};
});
};
}();
return ECharts;
}(Eventful);
var echartsProto = ECharts.prototype;
echartsProto.on = createRegisterEventWithLowercaseECharts('on');
echartsProto.off = createRegisterEventWithLowercaseECharts('off');
/**
* @deprecated
*/
// @ts-ignore
echartsProto.one = function (eventName, cb, ctx) {
var self = this;
deprecateLog('ECharts#one is deprecated.');
function wrapped() {
var args2 = [];
for (var _i = 0; _i < arguments.length; _i++) {
args2[_i] = arguments[_i];
}
cb && cb.apply && cb.apply(this, args2); // @ts-ignore
self.off(eventName, wrapped);
}
this.on.call(this, eventName, wrapped, ctx);
}; // /**
// * Encode visual infomation from data after data processing
// *
// * @param {module:echarts/model/Global} ecModel
// * @param {object} layout
// * @param {boolean} [layoutFilter] `true`: only layout,
// * `false`: only not layout,
// * `null`/`undefined`: all.
// * @param {string} taskBaseTag
// * @private
// */
// function startVisualEncoding(ecIns, ecModel, api, payload, layoutFilter) {
// each(visualFuncs, function (visual, index) {
// let isLayout = visual.isLayout;
// if (layoutFilter == null
// || (layoutFilter === false && !isLayout)
// || (layoutFilter === true && isLayout)
// ) {
// visual.func(ecModel, api, payload);
// }
// });
// }
var MOUSE_EVENT_NAMES = ['click', 'dblclick', 'mouseover', 'mouseout', 'mousemove', 'mousedown', 'mouseup', 'globalout', 'contextmenu'];
function disposedWarning(id) {
if ("development" !== 'production') {
console.warn('Instance ' + id + ' has been disposed');
}
}
var actions = {};
/**
* Map eventType to actionType
*/
var eventActionMap = {};
var dataProcessorFuncs = [];
var optionPreprocessorFuncs = [];
var postInitFuncs = [];
var postUpdateFuncs = [];
var visualFuncs = [];
var themeStorage = {};
var loadingEffects = {};
var instances$1 = {};
var connectedGroups = {};
var idBase = +new Date() - 0;
var groupIdBase = +new Date() - 0;
var DOM_ATTRIBUTE_KEY = '_echarts_instance_';
/**
* @param opts.devicePixelRatio Use window.devicePixelRatio by default
* @param opts.renderer Can choose 'canvas' or 'svg' to render the chart.
* @param opts.width Use clientWidth of the input `dom` by default.
* Can be 'auto' (the same as null/undefined)
* @param opts.height Use clientHeight of the input `dom` by default.
* Can be 'auto' (the same as null/undefined)
*/
function init$1(dom, theme, opts) {
if ("development" !== 'production') {
if (!dom) {
throw new Error('Initialize failed: invalid dom.');
}
}
var existInstance = getInstanceByDom(dom);
if (existInstance) {
if ("development" !== 'production') {
console.warn('There is a chart instance already initialized on the dom.');
}
return existInstance;
}
if ("development" !== 'production') {
if (isDom(dom) && dom.nodeName.toUpperCase() !== 'CANVAS' && (!dom.clientWidth && (!opts || opts.width == null) || !dom.clientHeight && (!opts || opts.height == null))) {
console.warn('Can\'t get DOM width or height. Please check ' + 'dom.clientWidth and dom.clientHeight. They should not be 0.' + 'For example, you may need to call this in the callback ' + 'of window.onload.');
}
}
var chart = new ECharts(dom, theme, opts);
chart.id = 'ec_' + idBase++;
instances$1[chart.id] = chart;
setAttribute(dom, DOM_ATTRIBUTE_KEY, chart.id);
enableConnect(chart);
each$3(postInitFuncs, function (postInitFunc) {
postInitFunc(chart);
});
return chart;
}
/**
* @usage
* (A)
* ```js
* let chart1 = echarts.init(dom1);
* let chart2 = echarts.init(dom2);
* chart1.group = 'xxx';
* chart2.group = 'xxx';
* echarts.connect('xxx');
* ```
* (B)
* ```js
* let chart1 = echarts.init(dom1);
* let chart2 = echarts.init(dom2);
* echarts.connect('xxx', [chart1, chart2]);
* ```
*/
function connect(groupId) {
// Is array of charts
if (isArray(groupId)) {
var charts = groupId;
groupId = null; // If any chart has group
each$3(charts, function (chart) {
if (chart.group != null) {
groupId = chart.group;
}
});
groupId = groupId || 'g_' + groupIdBase++;
each$3(charts, function (chart) {
chart.group = groupId;
});
}
connectedGroups[groupId] = true;
return groupId;
}
/**
* @deprecated
*/
function disConnect(groupId) {
connectedGroups[groupId] = false;
}
/**
* Alias and backword compat
*/
var disconnect = disConnect;
/**
* Dispose a chart instance
*/
function dispose$1(chart) {
if (typeof chart === 'string') {
chart = instances$1[chart];
} else if (!(chart instanceof ECharts)) {
// Try to treat as dom
chart = getInstanceByDom(chart);
}
if (chart instanceof ECharts && !chart.isDisposed()) {
chart.dispose();
}
}
function getInstanceByDom(dom) {
return instances$1[getAttribute(dom, DOM_ATTRIBUTE_KEY)];
}
function getInstanceById(key) {
return instances$1[key];
}
/**
* Register theme
*/
function registerTheme(name, theme) {
themeStorage[name] = theme;
}
/**
* Register option preprocessor
*/
function registerPreprocessor(preprocessorFunc) {
if (indexOf$1(optionPreprocessorFuncs, preprocessorFunc) < 0) {
optionPreprocessorFuncs.push(preprocessorFunc);
}
}
function registerProcessor(priority, processor) {
normalizeRegister(dataProcessorFuncs, priority, processor, PRIORITY_PROCESSOR_DEFAULT);
}
/**
* Register postIniter
* @param {Function} postInitFunc
*/
function registerPostInit(postInitFunc) {
if (indexOf$1(postInitFuncs, postInitFunc) < 0) {
postInitFunc && postInitFuncs.push(postInitFunc);
}
}
/**
* Register postUpdater
* @param {Function} postUpdateFunc
*/
function registerPostUpdate(postUpdateFunc) {
if (indexOf$1(postUpdateFuncs, postUpdateFunc) < 0) {
postUpdateFunc && postUpdateFuncs.push(postUpdateFunc);
}
}
function registerAction(actionInfo, eventName, action) {
if (typeof eventName === 'function') {
action = eventName;
eventName = '';
}
var actionType = isObject$2(actionInfo) ? actionInfo.type : [actionInfo, actionInfo = {
event: eventName
}][0]; // Event name is all lowercase
actionInfo.event = (actionInfo.event || actionType).toLowerCase();
eventName = actionInfo.event;
if (eventActionMap[eventName]) {
// Already registered.
return;
} // Validate action type and event name.
assert$1(ACTION_REG.test(actionType) && ACTION_REG.test(eventName));
if (!actions[actionType]) {
actions[actionType] = {
action: action,
actionInfo: actionInfo
};
}
eventActionMap[eventName] = actionType;
}
function registerCoordinateSystem(type, coordSysCreator) {
CoordinateSystemManager.register(type, coordSysCreator);
}
/**
* Get dimensions of specified coordinate system.
* @param {string} type
* @return {Array.}
*/
function getCoordinateSystemDimensions(type) {
var coordSysCreator = CoordinateSystemManager.get(type);
if (coordSysCreator) {
return coordSysCreator.getDimensionsInfo ? coordSysCreator.getDimensionsInfo() : coordSysCreator.dimensions.slice();
}
}
function registerLayout(priority, layoutTask) {
normalizeRegister(visualFuncs, priority, layoutTask, PRIORITY_VISUAL_LAYOUT, 'layout');
}
function registerVisual(priority, visualTask) {
normalizeRegister(visualFuncs, priority, visualTask, PRIORITY_VISUAL_CHART, 'visual');
}
var registeredTasks = [];
function normalizeRegister(targetList, priority, fn, defaultPriority, visualType) {
if (isFunction$1(priority) || isObject$2(priority)) {
fn = priority;
priority = defaultPriority;
}
if ("development" !== 'production') {
if (isNaN(priority) || priority == null) {
throw new Error('Illegal priority');
} // Check duplicate
each$3(targetList, function (wrap) {
assert$1(wrap.__raw !== fn);
});
} // Already registered
if (indexOf$1(registeredTasks, fn) >= 0) {
return;
}
registeredTasks.push(fn);
var stageHandler = Scheduler.wrapStageHandler(fn, visualType);
stageHandler.__prio = priority;
stageHandler.__raw = fn;
targetList.push(stageHandler);
}
function registerLoading(name, loadingFx) {
loadingEffects[name] = loadingFx;
}
/**
* ZRender need a canvas context to do measureText.
* But in node environment canvas may be created by node-canvas.
* So we need to specify how to create a canvas instead of using document.createElement('canvas')
*
* Be careful of using it in the browser.
*
* @example
* let Canvas = require('canvas');
* let echarts = require('echarts');
* echarts.setCanvasCreator(function () {
* // Small size is enough.
* return new Canvas(32, 32);
* });
*/
function setCanvasCreator(creator) {
$override('createCanvas', creator);
}
/**
* The parameters and usage: see `mapDataStorage.registerMap`.
* Compatible with previous `echarts.registerMap`.
*/
function registerMap(mapName, geoJson, specialAreas) {
mapDataStorage.registerMap(mapName, geoJson, specialAreas);
}
function getMap(mapName) {
// For backward compatibility, only return the first one.
var records = mapDataStorage.retrieveMap(mapName); // FIXME support SVG, where return not only records[0].
return records && records[0] && {
// @ts-ignore
geoJson: records[0].geoJSON,
specialAreas: records[0].specialAreas
};
}
var registerTransform = registerExternalTransform;
/**
* Globa dispatchAction to a specified chart instance.
*/
// export function dispatchAction(payload: { chartId: string } & Payload, opt?: Parameters[1]) {
// if (!payload || !payload.chartId) {
// // Must have chartId to find chart
// return;
// }
// const chart = instances[payload.chartId];
// if (chart) {
// chart.dispatchAction(payload, opt);
// }
// }
// Buitlin global visual
registerVisual(PRIORITY_VISUAL_GLOBAL, seriesStyleTask);
registerVisual(PRIORITY_VISUAL_CHART_DATA_CUSTOM, dataStyleTask);
registerVisual(PRIORITY_VISUAL_CHART_DATA_CUSTOM, dataColorPaletteTask);
registerVisual(PRIORITY_VISUAL_GLOBAL, seriesSymbolTask);
registerVisual(PRIORITY_VISUAL_CHART_DATA_CUSTOM, dataSymbolTask);
registerVisual(PRIORITY_VISUAL_DECAL, decalVisual);
registerPreprocessor(globalBackwardCompat);
registerProcessor(PRIORITY_PROCESSOR_DATASTACK, dataStack);
registerLoading('default', defaultLoading); // Default actions
registerAction({
type: HIGHLIGHT_ACTION_TYPE,
event: HIGHLIGHT_ACTION_TYPE,
update: HIGHLIGHT_ACTION_TYPE
}, noop);
registerAction({
type: DOWNPLAY_ACTION_TYPE,
event: DOWNPLAY_ACTION_TYPE,
update: DOWNPLAY_ACTION_TYPE
}, noop);
registerAction({
type: SELECT_ACTION_TYPE,
event: SELECT_ACTION_TYPE,
update: SELECT_ACTION_TYPE
}, noop);
registerAction({
type: UNSELECT_ACTION_TYPE,
event: UNSELECT_ACTION_TYPE,
update: UNSELECT_ACTION_TYPE
}, noop);
registerAction({
type: TOGGLE_SELECT_ACTION_TYPE,
event: TOGGLE_SELECT_ACTION_TYPE,
update: TOGGLE_SELECT_ACTION_TYPE
}, noop); // Default theme
registerTheme('light', lightTheme);
registerTheme('dark', theme); // For backward compatibility, where the namespace `dataTool` will
// be mounted on `echarts` is the extension `dataTool` is imported.
var dataTool = {};
var extensions = [];
var extensionRegisters = {
registerPreprocessor: registerPreprocessor,
registerProcessor: registerProcessor,
registerPostInit: registerPostInit,
registerPostUpdate: registerPostUpdate,
registerAction: registerAction,
registerCoordinateSystem: registerCoordinateSystem,
registerLayout: registerLayout,
registerVisual: registerVisual,
registerTransform: registerTransform,
registerLoading: registerLoading,
registerMap: registerMap,
PRIORITY: PRIORITY,
ComponentModel: ComponentModel,
ComponentView: ComponentView,
SeriesModel: SeriesModel,
ChartView: ChartView,
// TODO Use ComponentModel and SeriesModel instead of Constructor
registerComponentModel: function (ComponentModelClass) {
ComponentModel.registerClass(ComponentModelClass);
},
registerComponentView: function (ComponentViewClass) {
ComponentView.registerClass(ComponentViewClass);
},
registerSeriesModel: function (SeriesModelClass) {
SeriesModel.registerClass(SeriesModelClass);
},
registerChartView: function (ChartViewClass) {
ChartView.registerClass(ChartViewClass);
},
registerSubTypeDefaulter: function (componentType, defaulter) {
ComponentModel.registerSubTypeDefaulter(componentType, defaulter);
},
registerPainter: function (painterType, PainterCtor) {
registerPainter(painterType, PainterCtor);
}
};
function use(ext) {
if (isArray(ext)) {
// use([ChartLine, ChartBar]);
each(ext, function (singleExt) {
use(singleExt);
});
return;
}
if (indexOf(extensions, ext) >= 0) {
return;
}
extensions.push(ext);
if (isFunction(ext)) {
ext = {
install: ext
};
}
ext.install(extensionRegisters);
}
/*
* 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.
*/
function dataIndexMapValueLength(valNumOrArrLengthMoreThan2) {
return valNumOrArrLengthMoreThan2 == null ? 0 : valNumOrArrLengthMoreThan2.length || 1;
}
function defaultKeyGetter(item) {
return item;
}
var DataDiffer =
/** @class */
function () {
/**
* @param context Can be visited by this.context in callback.
*/
function DataDiffer(oldArr, newArr, oldKeyGetter, newKeyGetter, context, // By default: 'oneToOne'.
diffMode) {
this._old = oldArr;
this._new = newArr;
this._oldKeyGetter = oldKeyGetter || defaultKeyGetter;
this._newKeyGetter = newKeyGetter || defaultKeyGetter; // Visible in callback via `this.context`;
this.context = context;
this._diffModeMultiple = diffMode === 'multiple';
}
/**
* Callback function when add a data
*/
DataDiffer.prototype.add = function (func) {
this._add = func;
return this;
};
/**
* Callback function when update a data
*/
DataDiffer.prototype.update = function (func) {
this._update = func;
return this;
};
/**
* Callback function when update a data and only work in `cbMode: 'byKey'`.
*/
DataDiffer.prototype.updateManyToOne = function (func) {
this._updateManyToOne = func;
return this;
};
/**
* Callback function when update a data and only work in `cbMode: 'byKey'`.
*/
DataDiffer.prototype.updateOneToMany = function (func) {
this._updateOneToMany = func;
return this;
};
/**
* Callback function when remove a data
*/
DataDiffer.prototype.remove = function (func) {
this._remove = func;
return this;
};
DataDiffer.prototype.execute = function () {
this[this._diffModeMultiple ? '_executeMultiple' : '_executeOneToOne']();
};
DataDiffer.prototype._executeOneToOne = function () {
var oldArr = this._old;
var newArr = this._new;
var newDataIndexMap = {};
var oldDataKeyArr = new Array(oldArr.length);
var newDataKeyArr = new Array(newArr.length);
this._initIndexMap(oldArr, null, oldDataKeyArr, '_oldKeyGetter');
this._initIndexMap(newArr, newDataIndexMap, newDataKeyArr, '_newKeyGetter');
for (var i = 0; i < oldArr.length; i++) {
var oldKey = oldDataKeyArr[i];
var newIdxMapVal = newDataIndexMap[oldKey];
var newIdxMapValLen = dataIndexMapValueLength(newIdxMapVal); // idx can never be empty array here. see 'set null' logic below.
if (newIdxMapValLen > 1) {
// Consider there is duplicate key (for example, use dataItem.name as key).
// We should make sure every item in newArr and oldArr can be visited.
var newIdx = newIdxMapVal.shift();
if (newIdxMapVal.length === 1) {
newDataIndexMap[oldKey] = newIdxMapVal[0];
}
this._update && this._update(newIdx, i);
} else if (newIdxMapValLen === 1) {
newDataIndexMap[oldKey] = null;
this._update && this._update(newIdxMapVal, i);
} else {
this._remove && this._remove(i);
}
}
this._performRestAdd(newDataKeyArr, newDataIndexMap);
};
/**
* For example, consider the case:
* oldData: [o0, o1, o2, o3, o4, o5, o6, o7],
* newData: [n0, n1, n2, n3, n4, n5, n6, n7, n8],
* Where:
* o0, o1, n0 has key 'a' (many to one)
* o5, n4, n5, n6 has key 'b' (one to many)
* o2, n1 has key 'c' (one to one)
* n2, n3 has key 'd' (add)
* o3, o4 has key 'e' (remove)
* o6, o7, n7, n8 has key 'f' (many to many, treated as add and remove)
* Then:
* (The order of the following directives are not ensured.)
* this._updateManyToOne(n0, [o0, o1]);
* this._updateOneToMany([n4, n5, n6], o5);
* this._update(n1, o2);
* this._remove(o3);
* this._remove(o4);
* this._remove(o6);
* this._remove(o7);
* this._add(n2);
* this._add(n3);
* this._add(n7);
* this._add(n8);
*/
DataDiffer.prototype._executeMultiple = function () {
var oldArr = this._old;
var newArr = this._new;
var oldDataIndexMap = {};
var newDataIndexMap = {};
var oldDataKeyArr = [];
var newDataKeyArr = [];
this._initIndexMap(oldArr, oldDataIndexMap, oldDataKeyArr, '_oldKeyGetter');
this._initIndexMap(newArr, newDataIndexMap, newDataKeyArr, '_newKeyGetter');
for (var i = 0; i < oldDataKeyArr.length; i++) {
var oldKey = oldDataKeyArr[i];
var oldIdxMapVal = oldDataIndexMap[oldKey];
var newIdxMapVal = newDataIndexMap[oldKey];
var oldIdxMapValLen = dataIndexMapValueLength(oldIdxMapVal);
var newIdxMapValLen = dataIndexMapValueLength(newIdxMapVal);
if (oldIdxMapValLen > 1 && newIdxMapValLen === 1) {
this._updateManyToOne && this._updateManyToOne(newIdxMapVal, oldIdxMapVal);
newDataIndexMap[oldKey] = null;
} else if (oldIdxMapValLen === 1 && newIdxMapValLen > 1) {
this._updateOneToMany && this._updateOneToMany(newIdxMapVal, oldIdxMapVal);
newDataIndexMap[oldKey] = null;
} else if (oldIdxMapValLen === 1 && newIdxMapValLen === 1) {
this._update && this._update(newIdxMapVal, oldIdxMapVal);
newDataIndexMap[oldKey] = null;
} else if (oldIdxMapValLen > 1) {
for (var i_1 = 0; i_1 < oldIdxMapValLen; i_1++) {
this._remove && this._remove(oldIdxMapVal[i_1]);
}
} else {
this._remove && this._remove(oldIdxMapVal);
}
}
this._performRestAdd(newDataKeyArr, newDataIndexMap);
};
DataDiffer.prototype._performRestAdd = function (newDataKeyArr, newDataIndexMap) {
for (var i = 0; i < newDataKeyArr.length; i++) {
var newKey = newDataKeyArr[i];
var newIdxMapVal = newDataIndexMap[newKey];
var idxMapValLen = dataIndexMapValueLength(newIdxMapVal);
if (idxMapValLen > 1) {
for (var j = 0; j < idxMapValLen; j++) {
this._add && this._add(newIdxMapVal[j]);
}
} else if (idxMapValLen === 1) {
this._add && this._add(newIdxMapVal);
} // Support both `newDataKeyArr` are duplication removed or not removed.
newDataIndexMap[newKey] = null;
}
};
DataDiffer.prototype._initIndexMap = function (arr, // Can be null.
map, // In 'byKey', the output `keyArr` is duplication removed.
// In 'byIndex', the output `keyArr` is not duplication removed and
// its indices are accurately corresponding to `arr`.
keyArr, keyGetterName) {
var cbModeMultiple = this._diffModeMultiple;
for (var i = 0; i < arr.length; i++) {
// Add prefix to avoid conflict with Object.prototype.
var key = '_ec_' + this[keyGetterName](arr[i], i);
if (!cbModeMultiple) {
keyArr[i] = key;
}
if (!map) {
continue;
}
var idxMapVal = map[key];
var idxMapValLen = dataIndexMapValueLength(idxMapVal);
if (idxMapValLen === 0) {
// Simple optimize: in most cases, one index has one key,
// do not need array.
map[key] = i;
if (cbModeMultiple) {
keyArr.push(key);
}
} else if (idxMapValLen === 1) {
map[key] = [idxMapVal, i];
} else {
idxMapVal.push(i);
}
}
};
return DataDiffer;
}();
function summarizeDimensions(data) {
var summary = {};
var encode = summary.encode = {};
var notExtraCoordDimMap = createHashMap();
var defaultedLabel = [];
var defaultedTooltip = []; // See the comment of `List.js#userOutput`.
var userOutput = summary.userOutput = {
dimensionNames: data.dimensions.slice(),
encode: {}
};
each(data.dimensions, function (dimName) {
var dimItem = data.getDimensionInfo(dimName);
var coordDim = dimItem.coordDim;
if (coordDim) {
if ("development" !== 'production') {
assert(VISUAL_DIMENSIONS.get(coordDim) == null);
}
var coordDimIndex = dimItem.coordDimIndex;
getOrCreateEncodeArr(encode, coordDim)[coordDimIndex] = dimName;
if (!dimItem.isExtraCoord) {
notExtraCoordDimMap.set(coordDim, 1); // Use the last coord dim (and label friendly) as default label,
// because when dataset is used, it is hard to guess which dimension
// can be value dimension. If both show x, y on label is not look good,
// and conventionally y axis is focused more.
if (mayLabelDimType(dimItem.type)) {
defaultedLabel[0] = dimName;
} // User output encode do not contain generated coords.
// And it only has index. User can use index to retrieve value from the raw item array.
getOrCreateEncodeArr(userOutput.encode, coordDim)[coordDimIndex] = dimItem.index;
}
if (dimItem.defaultTooltip) {
defaultedTooltip.push(dimName);
}
}
VISUAL_DIMENSIONS.each(function (v, otherDim) {
var encodeArr = getOrCreateEncodeArr(encode, otherDim);
var dimIndex = dimItem.otherDims[otherDim];
if (dimIndex != null && dimIndex !== false) {
encodeArr[dimIndex] = dimItem.name;
}
});
});
var dataDimsOnCoord = [];
var encodeFirstDimNotExtra = {};
notExtraCoordDimMap.each(function (v, coordDim) {
var dimArr = encode[coordDim];
encodeFirstDimNotExtra[coordDim] = dimArr[0]; // Not necessary to remove duplicate, because a data
// dim canot on more than one coordDim.
dataDimsOnCoord = dataDimsOnCoord.concat(dimArr);
});
summary.dataDimsOnCoord = dataDimsOnCoord;
summary.encodeFirstDimNotExtra = encodeFirstDimNotExtra;
var encodeLabel = encode.label; // FIXME `encode.label` is not recommanded, because formatter can not be set
// in this way. Use label.formatter instead. May be remove this approach someday.
if (encodeLabel && encodeLabel.length) {
defaultedLabel = encodeLabel.slice();
}
var encodeTooltip = encode.tooltip;
if (encodeTooltip && encodeTooltip.length) {
defaultedTooltip = encodeTooltip.slice();
} else if (!defaultedTooltip.length) {
defaultedTooltip = defaultedLabel.slice();
}
encode.defaultedLabel = defaultedLabel;
encode.defaultedTooltip = defaultedTooltip;
return summary;
}
function getOrCreateEncodeArr(encode, dim) {
if (!encode.hasOwnProperty(dim)) {
encode[dim] = [];
}
return encode[dim];
} // FIXME:TS should be type `AxisType`
function getDimensionTypeByAxis(axisType) {
return axisType === 'category' ? 'ordinal' : axisType === 'time' ? 'time' : 'float';
}
function mayLabelDimType(dimType) {
// In most cases, ordinal and time do not suitable for label.
// Ordinal info can be displayed on axis. Time is too long.
return !(dimType === 'ordinal' || dimType === 'time');
} // function findTheLastDimMayLabel(data) {
// // Get last value dim
// let dimensions = data.dimensions.slice();
// let valueType;
// let valueDim;
// while (dimensions.length && (
// valueDim = dimensions.pop(),
// valueType = data.getDimensionInfo(valueDim).type,
// valueType === 'ordinal' || valueType === 'time'
// )) {} // jshint ignore:line
// return valueDim;
// }
var DataDimensionInfo =
/** @class */
function () {
/**
* @param opt All of the fields will be shallow copied.
*/
function DataDimensionInfo(opt) {
/**
* The format of `otherDims` is:
* ```js
* {
* tooltip: number optional,
* label: number optional,
* itemName: number optional,
* seriesName: number optional,
* }
* ```
*
* A `series.encode` can specified these fields:
* ```js
* encode: {
* // "3, 1, 5" is the index of data dimension.
* tooltip: [3, 1, 5],
* label: [0, 3],
* ...
* }
* ```
* `otherDims` is the parse result of the `series.encode` above, like:
* ```js
* // Suppose the index of this data dimension is `3`.
* this.otherDims = {
* // `3` is at the index `0` of the `encode.tooltip`
* tooltip: 0,
* // `3` is at the index `1` of the `encode.tooltip`
* label: 1
* };
* ```
*
* This prop should never be `null`/`undefined` after initialized.
*/
this.otherDims = {};
if (opt != null) {
extend(this, opt);
}
}
return DataDimensionInfo;
}();
var mathFloor = Math.floor;
var isObject$3 = isObject;
var map$1 = map;
var UNDEFINED = 'undefined';
var INDEX_NOT_FOUND = -1; // Use prefix to avoid index to be the same as otherIdList[idx],
// which will cause weird udpate animation.
var ID_PREFIX = 'e\0\0';
var dataCtors = {
'float': typeof Float64Array === UNDEFINED ? Array : Float64Array,
'int': typeof Int32Array === UNDEFINED ? Array : Int32Array,
// Ordinal data type can be string or int
'ordinal': Array,
'number': Array,
'time': Array
}; // Caution: MUST not use `new CtorUint32Array(arr, 0, len)`, because the Ctor of array is
// different from the Ctor of typed array.
var CtorUint32Array = typeof Uint32Array === UNDEFINED ? Array : Uint32Array;
var CtorInt32Array = typeof Int32Array === UNDEFINED ? Array : Int32Array;
var CtorUint16Array = typeof Uint16Array === UNDEFINED ? Array : Uint16Array;
var TRANSFERABLE_PROPERTIES = ['hasItemOption', '_nameList', '_idList', '_invertedIndicesMap', '_rawData', '_dimValueGetter', '_count', '_rawCount', '_nameDimIdx', '_idDimIdx', '_nameRepeatCount'];
var CLONE_PROPERTIES = ['_extent', '_approximateExtent', '_rawExtent']; // -----------------------------
// Internal method declarations:
// -----------------------------
var defaultDimValueGetters;
var prepareInvertedIndex;
var getIndicesCtor;
var prepareStorage;
var getRawIndexWithoutIndices;
var getRawIndexWithIndices;
var getId;
var getIdNameFromStore;
var makeIdFromName;
var normalizeDimensions;
var validateDimensions;
var cloneListForMapAndSample;
var getInitialExtent;
var setItemDataAndSeriesIndex;
var transferProperties;
var List =
/** @class */
function () {
/**
* @param dimensions
* For example, ['someDimName', {name: 'someDimName', type: 'someDimType'}, ...].
* Dimensions should be concrete names like x, y, z, lng, lat, angle, radius
*/
function List(dimensions, hostModel) {
this.type = 'list';
this._count = 0;
this._rawCount = 0;
this._storage = {}; // We have an extra array store here. It's faster to be acessed than KV structured `_storage`.
// We profile the code `storage[dim]` and it seems to be KeyedLoadIC_Megamorphic instead of fast property access.
// Not sure why this happens. But using an extra array seems leads to faster `initData`
// See https://github.com/apache/incubator-echarts/pull/13314 for more explanation.
this._storageArr = [];
this._nameList = [];
this._idList = []; // Models of data option is stored sparse for optimizing memory cost
// Never used yet (not used yet).
// private _optionModels: Model[] = [];
// Global visual properties after visual coding
this._visual = {}; // Globel layout properties.
this._layout = {}; // Item visual properties after visual coding
this._itemVisuals = []; // Item layout properties after layout
this._itemLayouts = []; // Graphic elemnents
this._graphicEls = []; // Raw extent will not be cloned, but only transfered.
// It will not be calculated util needed.
this._rawExtent = {};
this._extent = {}; // key: dim, value: extent
this._approximateExtent = {};
this._calculationInfo = {}; // Having detected that there is data item is non primitive type
// (in type `OptionDataItemObject`).
// Like `data: [ { value: xx, itemStyle: {...} }, ...]`
// At present it only happen in `SOURCE_FORMAT_ORIGINAL`.
this.hasItemOption = true; // Methods that create a new list based on this list should be listed here.
// Notice that those method should `RETURN` the new list.
this.TRANSFERABLE_METHODS = ['cloneShallow', 'downSample', 'lttbDownSample', 'map']; // Methods that change indices of this list should be listed here.
this.CHANGABLE_METHODS = ['filterSelf', 'selectRange'];
this.DOWNSAMPLE_METHODS = ['downSample', 'lttbDownSample'];
/**
* Get raw data index.
* Do not initialize.
* Default `getRawIndex`. And it can be changed.
*/
this.getRawIndex = getRawIndexWithoutIndices;
dimensions = dimensions || ['x', 'y'];
var dimensionInfos = {};
var dimensionNames = [];
var invertedIndicesMap = {};
for (var i = 0; i < dimensions.length; i++) {
// Use the original dimensions[i], where other flag props may exists.
var dimInfoInput = dimensions[i];
var dimensionInfo = isString(dimInfoInput) ? new DataDimensionInfo({
name: dimInfoInput
}) : !(dimInfoInput instanceof DataDimensionInfo) ? new DataDimensionInfo(dimInfoInput) : dimInfoInput;
var dimensionName = dimensionInfo.name;
dimensionInfo.type = dimensionInfo.type || 'float';
if (!dimensionInfo.coordDim) {
dimensionInfo.coordDim = dimensionName;
dimensionInfo.coordDimIndex = 0;
}
var otherDims = dimensionInfo.otherDims = dimensionInfo.otherDims || {};
dimensionNames.push(dimensionName);
dimensionInfos[dimensionName] = dimensionInfo;
dimensionInfo.index = i;
if (dimensionInfo.createInvertedIndices) {
invertedIndicesMap[dimensionName] = [];
}
if (otherDims.itemName === 0) {
this._nameDimIdx = i;
this._nameOrdinalMeta = dimensionInfo.ordinalMeta;
}
if (otherDims.itemId === 0) {
this._idDimIdx = i;
this._idOrdinalMeta = dimensionInfo.ordinalMeta;
}
}
this.dimensions = dimensionNames;
this._dimensionInfos = dimensionInfos;
this.hostModel = hostModel; // Cache summary info for fast visit. See "dimensionHelper".
this._dimensionsSummary = summarizeDimensions(this);
this._invertedIndicesMap = invertedIndicesMap;
this.userOutput = this._dimensionsSummary.userOutput;
}
/**
* The meanings of the input parameter `dim`:
*
* + If dim is a number (e.g., `1`), it means the index of the dimension.
* For example, `getDimension(0)` will return 'x' or 'lng' or 'radius'.
* + If dim is a number-like string (e.g., `"1"`):
* + If there is the same concrete dim name defined in `this.dimensions`, it means that concrete name.
* + If not, it will be converted to a number, which means the index of the dimension.
* (why? because of the backward compatbility. We have been tolerating number-like string in
* dimension setting, although now it seems that it is not a good idea.)
* For example, `visualMap[i].dimension: "1"` is the same meaning as `visualMap[i].dimension: 1`,
* if no dimension name is defined as `"1"`.
* + If dim is a not-number-like string, it means the concrete dim name.
* For example, it can be be default name `"x"`, `"y"`, `"z"`, `"lng"`, `"lat"`, `"angle"`, `"radius"`,
* or customized in `dimensions` property of option like `"age"`.
*
* Get dimension name
* @param dim See above.
* @return Concrete dim name.
*/
List.prototype.getDimension = function (dim) {
if (typeof dim === 'number' // If being a number-like string but not being defined a dimension name.
|| !isNaN(dim) && !this._dimensionInfos.hasOwnProperty(dim)) {
dim = this.dimensions[dim];
}
return dim;
};
/**
* Get type and calculation info of particular dimension
* @param dim
* Dimension can be concrete names like x, y, z, lng, lat, angle, radius
* Or a ordinal number. For example getDimensionInfo(0) will return 'x' or 'lng' or 'radius'
*/
List.prototype.getDimensionInfo = function (dim) {
// Do not clone, because there may be categories in dimInfo.
return this._dimensionInfos[this.getDimension(dim)];
};
/**
* concrete dimension name list on coord.
*/
List.prototype.getDimensionsOnCoord = function () {
return this._dimensionsSummary.dataDimsOnCoord.slice();
};
List.prototype.mapDimension = function (coordDim, idx) {
var dimensionsSummary = this._dimensionsSummary;
if (idx == null) {
return dimensionsSummary.encodeFirstDimNotExtra[coordDim];
}
var dims = dimensionsSummary.encode[coordDim];
return dims ? dims[idx] : null;
};
List.prototype.mapDimensionsAll = function (coordDim) {
var dimensionsSummary = this._dimensionsSummary;
var dims = dimensionsSummary.encode[coordDim];
return (dims || []).slice();
};
/**
* Initialize from data
* @param data source or data or data provider.
* @param nameList The name of a datum is used on data diff and
* default label/tooltip.
* A name can be specified in encode.itemName,
* or dataItem.name (only for series option data),
* or provided in nameList from outside.
*/
List.prototype.initData = function (data, nameList, dimValueGetter) {
var notProvider = isSourceInstance(data) || isArrayLike(data);
var provider = notProvider ? new DefaultDataProvider(data, this.dimensions.length) : data;
if ("development" !== 'production') {
assert(notProvider || isFunction(provider.getItem) && isFunction(provider.count), 'Inavlid data provider.');
}
this._rawData = provider;
var sourceFormat = provider.getSource().sourceFormat; // Clear
this._storage = {};
this._indices = null;
this._dontMakeIdFromName = this._idDimIdx != null || sourceFormat === SOURCE_FORMAT_TYPED_ARRAY // Cosndier performance.
|| !!provider.fillStorage;
this._nameList = (nameList || []).slice();
this._idList = [];
this._nameRepeatCount = {};
if (!dimValueGetter) {
this.hasItemOption = false;
}
this.defaultDimValueGetter = defaultDimValueGetters[sourceFormat]; // Default dim value getter
this._dimValueGetter = dimValueGetter = dimValueGetter || this.defaultDimValueGetter;
this._dimValueGetterArrayRows = defaultDimValueGetters.arrayRows; // Reset raw extent.
this._rawExtent = {};
this._initDataFromProvider(0, provider.count()); // If data has no item option.
if (provider.pure) {
this.hasItemOption = false;
}
};
List.prototype.getProvider = function () {
return this._rawData;
};
/**
* Caution: Can be only called on raw data (before `this._indices` created).
*/
List.prototype.appendData = function (data) {
if ("development" !== 'production') {
assert(!this._indices, 'appendData can only be called on raw data.');
}
var rawData = this._rawData;
var start = this.count();
rawData.appendData(data);
var end = rawData.count();
if (!rawData.persistent) {
end += start;
}
this._initDataFromProvider(start, end, true);
};
/**
* Caution: Can be only called on raw data (before `this._indices` created).
* This method does not modify `rawData` (`dataProvider`), but only
* add values to storage.
*
* The final count will be increased by `Math.max(values.length, names.length)`.
*
* @param values That is the SourceType: 'arrayRows', like
* [
* [12, 33, 44],
* [NaN, 43, 1],
* ['-', 'asdf', 0]
* ]
* Each item is exaclty cooresponding to a dimension.
*/
List.prototype.appendValues = function (values, names) {
var storage = this._storage;
var dimensions = this.dimensions;
var dimLen = dimensions.length;
var rawExtent = this._rawExtent;
var start = this.count();
var end = start + Math.max(values.length, names ? names.length : 0);
for (var i = 0; i < dimLen; i++) {
var dim = dimensions[i];
if (!rawExtent[dim]) {
rawExtent[dim] = getInitialExtent();
}
prepareStorage(storage, this._dimensionInfos[dim], end, true);
}
var rawExtentArr = map$1(dimensions, function (dim) {
return rawExtent[dim];
});
var storageArr = this._storageArr = map$1(dimensions, function (dim) {
return storage[dim];
});
var emptyDataItem = [];
for (var idx = start; idx < end; idx++) {
var sourceIdx = idx - start; // Store the data by dimensions
for (var dimIdx = 0; dimIdx < dimLen; dimIdx++) {
var dim = dimensions[dimIdx];
var val = this._dimValueGetterArrayRows(values[sourceIdx] || emptyDataItem, dim, sourceIdx, dimIdx);
storageArr[dimIdx][idx] = val;
var dimRawExtent = rawExtentArr[dimIdx];
val < dimRawExtent[0] && (dimRawExtent[0] = val);
val > dimRawExtent[1] && (dimRawExtent[1] = val);
}
if (names) {
this._nameList[idx] = names[sourceIdx];
if (!this._dontMakeIdFromName) {
makeIdFromName(this, idx);
}
}
}
this._rawCount = this._count = end; // Reset data extent
this._extent = {};
prepareInvertedIndex(this);
};
List.prototype._initDataFromProvider = function (start, end, append) {
if (start >= end) {
return;
}
var rawData = this._rawData;
var storage = this._storage;
var dimensions = this.dimensions;
var dimLen = dimensions.length;
var dimensionInfoMap = this._dimensionInfos;
var nameList = this._nameList;
var idList = this._idList;
var rawExtent = this._rawExtent;
var sourceFormat = rawData.getSource().sourceFormat;
var isFormatOriginal = sourceFormat === SOURCE_FORMAT_ORIGINAL;
for (var i = 0; i < dimLen; i++) {
var dim = dimensions[i];
if (!rawExtent[dim]) {
rawExtent[dim] = getInitialExtent();
}
prepareStorage(storage, dimensionInfoMap[dim], end, append);
}
var storageArr = this._storageArr = map$1(dimensions, function (dim) {
return storage[dim];
});
var rawExtentArr = map$1(dimensions, function (dim) {
return rawExtent[dim];
});
if (rawData.fillStorage) {
rawData.fillStorage(start, end, storageArr, rawExtentArr);
} else {
var dataItem = [];
for (var idx = start; idx < end; idx++) {
// NOTICE: Try not to write things into dataItem
dataItem = rawData.getItem(idx, dataItem); // Each data item is value
// [1, 2]
// 2
// Bar chart, line chart which uses category axis
// only gives the 'y' value. 'x' value is the indices of category
// Use a tempValue to normalize the value to be a (x, y) value
// Store the data by dimensions
for (var dimIdx = 0; dimIdx < dimLen; dimIdx++) {
var dim = dimensions[dimIdx];
var dimStorage = storageArr[dimIdx]; // PENDING NULL is empty or zero
var val = this._dimValueGetter(dataItem, dim, idx, dimIdx);
dimStorage[idx] = val;
var dimRawExtent = rawExtentArr[dimIdx];
val < dimRawExtent[0] && (dimRawExtent[0] = val);
val > dimRawExtent[1] && (dimRawExtent[1] = val);
} // If dataItem is {name: ...} or {id: ...}, it has highest priority.
// This kind of ids and names are always stored `_nameList` and `_idList`.
if (isFormatOriginal && !rawData.pure && dataItem) {
var itemName = dataItem.name;
if (nameList[idx] == null && itemName != null) {
nameList[idx] = convertOptionIdName(itemName, null);
}
var itemId = dataItem.id;
if (idList[idx] == null && itemId != null) {
idList[idx] = convertOptionIdName(itemId, null);
}
}
if (!this._dontMakeIdFromName) {
makeIdFromName(this, idx);
}
}
}
if (!rawData.persistent && rawData.clean) {
// Clean unused data if data source is typed array.
rawData.clean();
}
this._rawCount = this._count = end; // Reset data extent
this._extent = {};
prepareInvertedIndex(this);
};
List.prototype.count = function () {
return this._count;
};
List.prototype.getIndices = function () {
var newIndices;
var indices = this._indices;
if (indices) {
var Ctor = indices.constructor;
var thisCount = this._count; // `new Array(a, b, c)` is different from `new Uint32Array(a, b, c)`.
if (Ctor === Array) {
newIndices = new Ctor(thisCount);
for (var i = 0; i < thisCount; i++) {
newIndices[i] = indices[i];
}
} else {
newIndices = new Ctor(indices.buffer, 0, thisCount);
}
} else {
var Ctor = getIndicesCtor(this);
newIndices = new Ctor(this.count());
for (var i = 0; i < newIndices.length; i++) {
newIndices[i] = i;
}
}
return newIndices;
}; // Get data by index of dimension.
// Because in v8 access array by number variable is faster than access object by string variable
// Not sure why but the optimization just works.
List.prototype.getByDimIdx = function (dimIdx, idx) {
if (!(idx >= 0 && idx < this._count)) {
return NaN;
}
var dimStore = this._storageArr[dimIdx];
return dimStore ? dimStore[this.getRawIndex(idx)] : NaN;
};
/**
* Get value. Return NaN if idx is out of range.
* @param dim Dim must be concrete name.
*/
List.prototype.get = function (dim, idx) {
if (!(idx >= 0 && idx < this._count)) {
return NaN;
}
var dimStore = this._storage[dim];
return dimStore ? dimStore[this.getRawIndex(idx)] : NaN;
};
/**
* @param dim concrete dim
*/
List.prototype.getByRawIndex = function (dim, rawIdx) {
if (!(rawIdx >= 0 && rawIdx < this._rawCount)) {
return NaN;
}
var dimStore = this._storage[dim];
return dimStore ? dimStore[rawIdx] : NaN;
};
List.prototype.getValues = function (dimensions, idx) {
var values = [];
if (!isArray(dimensions)) {
// stack = idx;
idx = dimensions;
dimensions = this.dimensions;
}
for (var i = 0, len = dimensions.length; i < len; i++) {
values.push(this.get(dimensions[i], idx
/*, stack */
));
}
return values;
};
/**
* If value is NaN. Inlcuding '-'
* Only check the coord dimensions.
*/
List.prototype.hasValue = function (idx) {
var dataDimsOnCoord = this._dimensionsSummary.dataDimsOnCoord;
for (var i = 0, len = dataDimsOnCoord.length; i < len; i++) {
// Ordinal type originally can be string or number.
// But when an ordinal type is used on coord, it can
// not be string but only number. So we can also use isNaN.
if (isNaN(this.get(dataDimsOnCoord[i], idx))) {
return false;
}
}
return true;
};
/**
* Get extent of data in one dimension
*/
List.prototype.getDataExtent = function (dim) {
// Make sure use concrete dim as cache name.
dim = this.getDimension(dim);
var dimData = this._storage[dim];
var initialExtent = getInitialExtent(); // stack = !!((stack || false) && this.getCalculationInfo(dim));
if (!dimData) {
return initialExtent;
} // Make more strict checkings to ensure hitting cache.
var currEnd = this.count(); // let cacheName = [dim, !!stack].join('_');
// let cacheName = dim;
// Consider the most cases when using data zoom, `getDataExtent`
// happened before filtering. We cache raw extent, which is not
// necessary to be cleared and recalculated when restore data.
var useRaw = !this._indices; // && !stack;
var dimExtent;
if (useRaw) {
return this._rawExtent[dim].slice();
}
dimExtent = this._extent[dim];
if (dimExtent) {
return dimExtent.slice();
}
dimExtent = initialExtent;
var min = dimExtent[0];
var max = dimExtent[1];
for (var i = 0; i < currEnd; i++) {
var rawIdx = this.getRawIndex(i);
var value = dimData[rawIdx];
value < min && (min = value);
value > max && (max = value);
}
dimExtent = [min, max];
this._extent[dim] = dimExtent;
return dimExtent;
};
/**
* PENDING: In fact currently this function is only used to short-circuit
* the calling of `scale.unionExtentFromData` when data have been filtered by modules
* like "dataZoom". `scale.unionExtentFromData` is used to calculate data extent for series on
* an axis, but if a "axis related data filter module" is used, the extent of the axis have
* been fixed and no need to calling `scale.unionExtentFromData` actually.
* But if we add "custom data filter" in future, which is not "axis related", this method may
* be still needed.
*
* Optimize for the scenario that data is filtered by a given extent.
* Consider that if data amount is more than hundreds of thousand,
* extent calculation will cost more than 10ms and the cache will
* be erased because of the filtering.
*/
List.prototype.getApproximateExtent = function (dim) {
dim = this.getDimension(dim);
return this._approximateExtent[dim] || this.getDataExtent(dim);
};
/**
* Calculate extent on a filtered data might be time consuming.
* Approximate extent is only used for: calculte extent of filtered data outside.
*/
List.prototype.setApproximateExtent = function (extent, dim) {
dim = this.getDimension(dim);
this._approximateExtent[dim] = extent.slice();
};
List.prototype.getCalculationInfo = function (key) {
return this._calculationInfo[key];
};
List.prototype.setCalculationInfo = function (key, value) {
isObject$3(key) ? extend(this._calculationInfo, key) : this._calculationInfo[key] = value;
};
/**
* Get sum of data in one dimension
*/
List.prototype.getSum = function (dim) {
var dimData = this._storage[dim];
var sum = 0;
if (dimData) {
for (var i = 0, len = this.count(); i < len; i++) {
var value = this.get(dim, i);
if (!isNaN(value)) {
sum += value;
}
}
}
return sum;
};
/**
* Get median of data in one dimension
*/
List.prototype.getMedian = function (dim) {
var dimDataArray = []; // map all data of one dimension
this.each(dim, function (val) {
if (!isNaN(val)) {
dimDataArray.push(val);
}
}); // TODO
// Use quick select?
var sortedDimDataArray = dimDataArray.sort(function (a, b) {
return a - b;
});
var len = this.count(); // calculate median
return len === 0 ? 0 : len % 2 === 1 ? sortedDimDataArray[(len - 1) / 2] : (sortedDimDataArray[len / 2] + sortedDimDataArray[len / 2 - 1]) / 2;
}; // /**
// * Retreive the index with given value
// * @param {string} dim Concrete dimension.
// * @param {number} value
// * @return {number}
// */
// Currently incorrect: should return dataIndex but not rawIndex.
// Do not fix it until this method is to be used somewhere.
// FIXME Precision of float value
// indexOf(dim, value) {
// let storage = this._storage;
// let dimData = storage[dim];
// let chunkSize = this._chunkSize;
// if (dimData) {
// for (let i = 0, len = this.count(); i < len; i++) {
// let chunkIndex = mathFloor(i / chunkSize);
// let chunkOffset = i % chunkSize;
// if (dimData[chunkIndex][chunkOffset] === value) {
// return i;
// }
// }
// }
// return -1;
// }
/**
* Only support the dimension which inverted index created.
* Do not support other cases until required.
* @param dim concrete dim
* @param value ordinal index
* @return rawIndex
*/
List.prototype.rawIndexOf = function (dim, value) {
var invertedIndices = dim && this._invertedIndicesMap[dim];
if ("development" !== 'production') {
if (!invertedIndices) {
throw new Error('Do not supported yet');
}
}
var rawIndex = invertedIndices[value];
if (rawIndex == null || isNaN(rawIndex)) {
return INDEX_NOT_FOUND;
}
return rawIndex;
};
/**
* Retreive the index with given name
*/
List.prototype.indexOfName = function (name) {
for (var i = 0, len = this.count(); i < len; i++) {
if (this.getName(i) === name) {
return i;
}
}
return -1;
};
/**
* Retreive the index with given raw data index
*/
List.prototype.indexOfRawIndex = function (rawIndex) {
if (rawIndex >= this._rawCount || rawIndex < 0) {
return -1;
}
if (!this._indices) {
return rawIndex;
} // Indices are ascending
var indices = this._indices; // If rawIndex === dataIndex
var rawDataIndex = indices[rawIndex];
if (rawDataIndex != null && rawDataIndex < this._count && rawDataIndex === rawIndex) {
return rawIndex;
}
var left = 0;
var right = this._count - 1;
while (left <= right) {
var mid = (left + right) / 2 | 0;
if (indices[mid] < rawIndex) {
left = mid + 1;
} else if (indices[mid] > rawIndex) {
right = mid - 1;
} else {
return mid;
}
}
return -1;
};
/**
* Retreive the index of nearest value
* @param dim
* @param value
* @param [maxDistance=Infinity]
* @return If and only if multiple indices has
* the same value, they are put to the result.
*/
List.prototype.indicesOfNearest = function (dim, value, maxDistance) {
var storage = this._storage;
var dimData = storage[dim];
var nearestIndices = [];
if (!dimData) {
return nearestIndices;
}
if (maxDistance == null) {
maxDistance = Infinity;
}
var minDist = Infinity;
var minDiff = -1;
var nearestIndicesLen = 0; // Check the test case of `test/ut/spec/data/List.js`.
for (var i = 0, len = this.count(); i < len; i++) {
var dataIndex = this.getRawIndex(i);
var diff = value - dimData[dataIndex];
var dist = Math.abs(diff);
if (dist <= maxDistance) {
// When the `value` is at the middle of `this.get(dim, i)` and `this.get(dim, i+1)`,
// we'd better not push both of them to `nearestIndices`, otherwise it is easy to
// get more than one item in `nearestIndices` (more specifically, in `tooltip`).
// So we chose the one that `diff >= 0` in this csae.
// But if `this.get(dim, i)` and `this.get(dim, j)` get the same value, both of them
// should be push to `nearestIndices`.
if (dist < minDist || dist === minDist && diff >= 0 && minDiff < 0) {
minDist = dist;
minDiff = diff;
nearestIndicesLen = 0;
}
if (diff === minDiff) {
nearestIndices[nearestIndicesLen++] = i;
}
}
}
nearestIndices.length = nearestIndicesLen;
return nearestIndices;
};
/**
* Get raw data item
*/
List.prototype.getRawDataItem = function (idx) {
if (!this._rawData.persistent) {
var val = [];
for (var i = 0; i < this.dimensions.length; i++) {
var dim = this.dimensions[i];
val.push(this.get(dim, idx));
}
return val;
} else {
return this._rawData.getItem(this.getRawIndex(idx));
}
};
/**
* @return Never be null/undefined. `number` will be converted to string. Becuase:
* In most cases, name is used in display, where returning a string is more convenient.
* In other cases, name is used in query (see `indexOfName`), where we can keep the
* rule that name `2` equals to name `'2'`.
*/
List.prototype.getName = function (idx) {
var rawIndex = this.getRawIndex(idx);
var name = this._nameList[rawIndex];
if (name == null && this._nameDimIdx != null) {
name = getIdNameFromStore(this, this._nameDimIdx, this._nameOrdinalMeta, rawIndex);
}
if (name == null) {
name = '';
}
return name;
};
/**
* @return Never null/undefined. `number` will be converted to string. Becuase:
* In all cases having encountered at present, id is used in making diff comparison, which
* are usually based on hash map. We can keep the rule that the internal id are always string
* (treat `2` is the same as `'2'`) to make the related logic simple.
*/
List.prototype.getId = function (idx) {
return getId(this, this.getRawIndex(idx));
};
List.prototype.each = function (dims, cb, ctx, ctxCompat) {
var _this = this;
if (!this._count) {
return;
}
if (typeof dims === 'function') {
ctxCompat = ctx;
ctx = cb;
cb = dims;
dims = [];
} // ctxCompat just for compat echarts3
var fCtx = ctx || ctxCompat || this;
var dimNames = map$1(normalizeDimensions(dims), this.getDimension, this);
if ("development" !== 'production') {
validateDimensions(this, dimNames);
}
var dimSize = dimNames.length;
var dimIndices = map$1(dimNames, function (dimName) {
return _this._dimensionInfos[dimName].index;
});
var storageArr = this._storageArr;
for (var i = 0, len = this.count(); i < len; i++) {
var rawIdx = this.getRawIndex(i); // Simple optimization
switch (dimSize) {
case 0:
cb.call(fCtx, i);
break;
case 1:
cb.call(fCtx, storageArr[dimIndices[0]][rawIdx], i);
break;
case 2:
cb.call(fCtx, storageArr[dimIndices[0]][rawIdx], storageArr[dimIndices[1]][rawIdx], i);
break;
default:
var k = 0;
var value = [];
for (; k < dimSize; k++) {
value[k] = storageArr[dimIndices[k]][rawIdx];
} // Index
value[k] = i;
cb.apply(fCtx, value);
}
}
};
List.prototype.filterSelf = function (dims, cb, ctx, ctxCompat) {
var _this = this;
if (!this._count) {
return;
}
if (typeof dims === 'function') {
ctxCompat = ctx;
ctx = cb;
cb = dims;
dims = [];
} // ctxCompat just for compat echarts3
var fCtx = ctx || ctxCompat || this;
var dimNames = map$1(normalizeDimensions(dims), this.getDimension, this);
if ("development" !== 'production') {
validateDimensions(this, dimNames);
}
var count = this.count();
var Ctor = getIndicesCtor(this);
var newIndices = new Ctor(count);
var value = [];
var dimSize = dimNames.length;
var offset = 0;
var dimIndices = map$1(dimNames, function (dimName) {
return _this._dimensionInfos[dimName].index;
});
var dim0 = dimIndices[0];
var storageArr = this._storageArr;
for (var i = 0; i < count; i++) {
var keep = void 0;
var rawIdx = this.getRawIndex(i); // Simple optimization
if (dimSize === 0) {
keep = cb.call(fCtx, i);
} else if (dimSize === 1) {
var val = storageArr[dim0][rawIdx];
keep = cb.call(fCtx, val, i);
} else {
var k = 0;
for (; k < dimSize; k++) {
value[k] = storageArr[dimIndices[k]][rawIdx];
}
value[k] = i;
keep = cb.apply(fCtx, value);
}
if (keep) {
newIndices[offset++] = rawIdx;
}
} // Set indices after filtered.
if (offset < count) {
this._indices = newIndices;
}
this._count = offset; // Reset data extent
this._extent = {};
this.getRawIndex = this._indices ? getRawIndexWithIndices : getRawIndexWithoutIndices;
return this;
};
/**
* Select data in range. (For optimization of filter)
* (Manually inline code, support 5 million data filtering in data zoom.)
*/
List.prototype.selectRange = function (range) {
var _this = this;
var len = this._count;
if (!len) {
return;
}
var dimensions = [];
for (var dim in range) {
if (range.hasOwnProperty(dim)) {
dimensions.push(dim);
}
}
if ("development" !== 'production') {
validateDimensions(this, dimensions);
}
var dimSize = dimensions.length;
if (!dimSize) {
return;
}
var originalCount = this.count();
var Ctor = getIndicesCtor(this);
var newIndices = new Ctor(originalCount);
var offset = 0;
var dim0 = dimensions[0];
var dimIndices = map$1(dimensions, function (dimName) {
return _this._dimensionInfos[dimName].index;
});
var min = range[dim0][0];
var max = range[dim0][1];
var storageArr = this._storageArr;
var quickFinished = false;
if (!this._indices) {
// Extreme optimization for common case. About 2x faster in chrome.
var idx = 0;
if (dimSize === 1) {
var dimStorage = storageArr[dimIndices[0]];
for (var i = 0; i < len; i++) {
var val = dimStorage[i]; // NaN will not be filtered. Consider the case, in line chart, empty
// value indicates the line should be broken. But for the case like
// scatter plot, a data item with empty value will not be rendered,
// but the axis extent may be effected if some other dim of the data
// item has value. Fortunately it is not a significant negative effect.
if (val >= min && val <= max || isNaN(val)) {
newIndices[offset++] = idx;
}
idx++;
}
quickFinished = true;
} else if (dimSize === 2) {
var dimStorage = storageArr[dimIndices[0]];
var dimStorage2 = storageArr[dimIndices[1]];
var min2 = range[dimensions[1]][0];
var max2 = range[dimensions[1]][1];
for (var i = 0; i < len; i++) {
var val = dimStorage[i];
var val2 = dimStorage2[i]; // Do not filter NaN, see comment above.
if ((val >= min && val <= max || isNaN(val)) && (val2 >= min2 && val2 <= max2 || isNaN(val2))) {
newIndices[offset++] = idx;
}
idx++;
}
quickFinished = true;
}
}
if (!quickFinished) {
if (dimSize === 1) {
for (var i = 0; i < originalCount; i++) {
var rawIndex = this.getRawIndex(i);
var val = storageArr[dimIndices[0]][rawIndex]; // Do not filter NaN, see comment above.
if (val >= min && val <= max || isNaN(val)) {
newIndices[offset++] = rawIndex;
}
}
} else {
for (var i = 0; i < originalCount; i++) {
var keep = true;
var rawIndex = this.getRawIndex(i);
for (var k = 0; k < dimSize; k++) {
var dimk = dimensions[k];
var val = storageArr[dimIndices[k]][rawIndex]; // Do not filter NaN, see comment above.
if (val < range[dimk][0] || val > range[dimk][1]) {
keep = false;
}
}
if (keep) {
newIndices[offset++] = this.getRawIndex(i);
}
}
}
} // Set indices after filtered.
if (offset < originalCount) {
this._indices = newIndices;
}
this._count = offset; // Reset data extent
this._extent = {};
this.getRawIndex = this._indices ? getRawIndexWithIndices : getRawIndexWithoutIndices;
return this;
};
/* eslint-enable */
List.prototype.mapArray = function (dims, cb, ctx, ctxCompat) {
if (typeof dims === 'function') {
ctxCompat = ctx;
ctx = cb;
cb = dims;
dims = [];
} // ctxCompat just for compat echarts3
ctx = ctx || ctxCompat || this;
var result = [];
this.each(dims, function () {
result.push(cb && cb.apply(this, arguments));
}, ctx);
return result;
};
List.prototype.map = function (dims, cb, ctx, ctxCompat) {
var fCtx = ctx || ctxCompat || this;
var dimNames = map$1(normalizeDimensions(dims), this.getDimension, this);
if ("development" !== 'production') {
validateDimensions(this, dimNames);
}
var list = cloneListForMapAndSample(this, dimNames);
var storage = list._storage; // Following properties are all immutable.
// So we can reference to the same value
list._indices = this._indices;
list.getRawIndex = list._indices ? getRawIndexWithIndices : getRawIndexWithoutIndices;
var tmpRetValue = [];
var dimSize = dimNames.length;
var dataCount = this.count();
var values = [];
var rawExtent = list._rawExtent;
for (var dataIndex = 0; dataIndex < dataCount; dataIndex++) {
for (var dimIndex = 0; dimIndex < dimSize; dimIndex++) {
values[dimIndex] = this.get(dimNames[dimIndex], dataIndex);
}
values[dimSize] = dataIndex;
var retValue = cb && cb.apply(fCtx, values);
if (retValue != null) {
// a number or string (in oridinal dimension)?
if (typeof retValue !== 'object') {
tmpRetValue[0] = retValue;
retValue = tmpRetValue;
}
var rawIndex = this.getRawIndex(dataIndex);
for (var i = 0; i < retValue.length; i++) {
var dim = dimNames[i];
var val = retValue[i];
var rawExtentOnDim = rawExtent[dim];
var dimStore = storage[dim];
if (dimStore) {
dimStore[rawIndex] = val;
}
if (val < rawExtentOnDim[0]) {
rawExtentOnDim[0] = val;
}
if (val > rawExtentOnDim[1]) {
rawExtentOnDim[1] = val;
}
}
}
}
return list;
};
/**
* Large data down sampling on given dimension
* @param sampleIndex Sample index for name and id
*/
List.prototype.downSample = function (dimension, rate, sampleValue, sampleIndex) {
var list = cloneListForMapAndSample(this, [dimension]);
var targetStorage = list._storage;
var frameValues = [];
var frameSize = mathFloor(1 / rate);
var dimStore = targetStorage[dimension];
var len = this.count();
var rawExtentOnDim = list._rawExtent[dimension];
var newIndices = new (getIndicesCtor(this))(len);
var offset = 0;
for (var i = 0; i < len; i += frameSize) {
// Last frame
if (frameSize > len - i) {
frameSize = len - i;
frameValues.length = frameSize;
}
for (var k = 0; k < frameSize; k++) {
var dataIdx = this.getRawIndex(i + k);
frameValues[k] = dimStore[dataIdx];
}
var value = sampleValue(frameValues);
var sampleFrameIdx = this.getRawIndex(Math.min(i + sampleIndex(frameValues, value) || 0, len - 1)); // Only write value on the filtered data
dimStore[sampleFrameIdx] = value;
if (value < rawExtentOnDim[0]) {
rawExtentOnDim[0] = value;
}
if (value > rawExtentOnDim[1]) {
rawExtentOnDim[1] = value;
}
newIndices[offset++] = sampleFrameIdx;
}
list._count = offset;
list._indices = newIndices;
list.getRawIndex = getRawIndexWithIndices;
return list;
};
/**
* Large data down sampling using largest-triangle-three-buckets
* @param {string} valueDimension
* @param {number} targetCount
*/
List.prototype.lttbDownSample = function (valueDimension, rate) {
var list = cloneListForMapAndSample(this, []);
var targetStorage = list._storage;
var dimStore = targetStorage[valueDimension];
var len = this.count();
var newIndices = new (getIndicesCtor(this))(len);
var sampledIndex = 0;
var frameSize = mathFloor(1 / rate);
var currentRawIndex = this.getRawIndex(0);
var maxArea;
var area;
var nextRawIndex; // First frame use the first data.
newIndices[sampledIndex++] = currentRawIndex;
for (var i = 1; i < len - 1; i += frameSize) {
var nextFrameStart = Math.min(i + frameSize, len - 1);
var nextFrameEnd = Math.min(i + frameSize * 2, len);
var avgX = (nextFrameEnd + nextFrameStart) / 2;
var avgY = 0;
for (var idx = nextFrameStart; idx < nextFrameEnd; idx++) {
var rawIndex = this.getRawIndex(idx);
var y = dimStore[rawIndex];
if (isNaN(y)) {
continue;
}
avgY += y;
}
avgY /= nextFrameEnd - nextFrameStart;
var frameStart = i;
var frameEnd = Math.min(i + frameSize, len);
var pointAX = i - 1;
var pointAY = dimStore[currentRawIndex];
maxArea = -1;
nextRawIndex = frameStart; // Find a point from current frame that construct a triangel with largest area with previous selected point
// And the average of next frame.
for (var idx = frameStart; idx < frameEnd; idx++) {
var rawIndex = this.getRawIndex(idx);
var y = dimStore[rawIndex];
if (isNaN(y)) {
continue;
} // Calculate triangle area over three buckets
area = Math.abs((pointAX - avgX) * (y - pointAY) - (pointAX - idx) * (avgY - pointAY));
if (area > maxArea) {
maxArea = area;
nextRawIndex = rawIndex; // Next a is this b
}
}
newIndices[sampledIndex++] = nextRawIndex;
currentRawIndex = nextRawIndex; // This a is the next a (chosen b)
} // First frame use the last data.
newIndices[sampledIndex++] = this.getRawIndex(len - 1);
list._count = sampledIndex;
list._indices = newIndices;
list.getRawIndex = getRawIndexWithIndices;
return list;
};
/**
* Get model of one data item.
*/
// TODO: Type of data item
List.prototype.getItemModel = function (idx) {
var hostModel = this.hostModel;
var dataItem = this.getRawDataItem(idx);
return new Model(dataItem, hostModel, hostModel && hostModel.ecModel);
};
/**
* Create a data differ
*/
List.prototype.diff = function (otherList) {
var thisList = this;
return new DataDiffer(otherList ? otherList.getIndices() : [], this.getIndices(), function (idx) {
return getId(otherList, idx);
}, function (idx) {
return getId(thisList, idx);
});
};
/**
* Get visual property.
*/
List.prototype.getVisual = function (key) {
var visual = this._visual;
return visual && visual[key];
};
List.prototype.setVisual = function (kvObj, val) {
this._visual = this._visual || {};
if (isObject$3(kvObj)) {
extend(this._visual, kvObj);
} else {
this._visual[kvObj] = val;
}
};
/**
* Get visual property of single data item
*/
// eslint-disable-next-line
List.prototype.getItemVisual = function (idx, key) {
var itemVisual = this._itemVisuals[idx];
var val = itemVisual && itemVisual[key];
if (val == null) {
// Use global visual property
return this.getVisual(key);
}
return val;
};
/**
* If exists visual property of single data item
*/
List.prototype.hasItemVisual = function () {
return this._itemVisuals.length > 0;
};
/**
* Make sure itemVisual property is unique
*/
// TODO: use key to save visual to reduce memory.
List.prototype.ensureUniqueItemVisual = function (idx, key) {
var itemVisuals = this._itemVisuals;
var itemVisual = itemVisuals[idx];
if (!itemVisual) {
itemVisual = itemVisuals[idx] = {};
}
var val = itemVisual[key];
if (val == null) {
val = this.getVisual(key); // TODO Performance?
if (isArray(val)) {
val = val.slice();
} else if (isObject$3(val)) {
val = extend({}, val);
}
itemVisual[key] = val;
}
return val;
}; // eslint-disable-next-line
List.prototype.setItemVisual = function (idx, key, value) {
var itemVisual = this._itemVisuals[idx] || {};
this._itemVisuals[idx] = itemVisual;
if (isObject$3(key)) {
extend(itemVisual, key);
} else {
itemVisual[key] = value;
}
};
/**
* Clear itemVisuals and list visual.
*/
List.prototype.clearAllVisual = function () {
this._visual = {};
this._itemVisuals = [];
};
List.prototype.setLayout = function (key, val) {
if (isObject$3(key)) {
for (var name_1 in key) {
if (key.hasOwnProperty(name_1)) {
this.setLayout(name_1, key[name_1]);
}
}
return;
}
this._layout[key] = val;
};
/**
* Get layout property.
*/
List.prototype.getLayout = function (key) {
return this._layout[key];
};
/**
* Get layout of single data item
*/
List.prototype.getItemLayout = function (idx) {
return this._itemLayouts[idx];
};
/**
* Set layout of single data item
*/
List.prototype.setItemLayout = function (idx, layout, merge) {
this._itemLayouts[idx] = merge ? extend(this._itemLayouts[idx] || {}, layout) : layout;
};
/**
* Clear all layout of single data item
*/
List.prototype.clearItemLayouts = function () {
this._itemLayouts.length = 0;
};
/**
* Set graphic element relative to data. It can be set as null
*/
List.prototype.setItemGraphicEl = function (idx, el) {
var hostModel = this.hostModel;
if (el) {
var ecData = getECData(el); // Add data index and series index for indexing the data by element
// Useful in tooltip
ecData.dataIndex = idx;
ecData.dataType = this.dataType;
ecData.seriesIndex = hostModel && hostModel.seriesIndex; // TODO: not store dataIndex on children.
if (el.type === 'group') {
el.traverse(setItemDataAndSeriesIndex, el);
}
}
this._graphicEls[idx] = el;
};
List.prototype.getItemGraphicEl = function (idx) {
return this._graphicEls[idx];
};
List.prototype.eachItemGraphicEl = function (cb, context) {
each(this._graphicEls, function (el, idx) {
if (el) {
cb && cb.call(context, el, idx);
}
});
};
/**
* Shallow clone a new list except visual and layout properties, and graph elements.
* New list only change the indices.
*/
List.prototype.cloneShallow = function (list) {
if (!list) {
var dimensionInfoList = map$1(this.dimensions, this.getDimensionInfo, this);
list = new List(dimensionInfoList, this.hostModel);
} // FIXME
list._storage = this._storage;
list._storageArr = this._storageArr;
transferProperties(list, this); // Clone will not change the data extent and indices
if (this._indices) {
var Ctor = this._indices.constructor;
if (Ctor === Array) {
var thisCount = this._indices.length;
list._indices = new Ctor(thisCount);
for (var i = 0; i < thisCount; i++) {
list._indices[i] = this._indices[i];
}
} else {
list._indices = new Ctor(this._indices);
}
} else {
list._indices = null;
}
list.getRawIndex = list._indices ? getRawIndexWithIndices : getRawIndexWithoutIndices;
return list;
};
/**
* Wrap some method to add more feature
*/
List.prototype.wrapMethod = function (methodName, injectFunction) {
var originalMethod = this[methodName];
if (typeof originalMethod !== 'function') {
return;
}
this.__wrappedMethods = this.__wrappedMethods || [];
this.__wrappedMethods.push(methodName);
this[methodName] = function () {
var res = originalMethod.apply(this, arguments);
return injectFunction.apply(this, [res].concat(slice(arguments)));
};
}; // ----------------------------------------------------------
// A work around for internal method visiting private member.
// ----------------------------------------------------------
List.internalField = function () {
defaultDimValueGetters = {
arrayRows: getDimValueSimply,
objectRows: function (dataItem, dimName, dataIndex, dimIndex) {
return parseDataValue(dataItem[dimName], this._dimensionInfos[dimName]);
},
keyedColumns: getDimValueSimply,
original: function (dataItem, dimName, dataIndex, dimIndex) {
// Performance sensitive, do not use modelUtil.getDataItemValue.
// If dataItem is an plain object with no value field, the let `value`
// will be assigned with the object, but it will be tread correctly
// in the `convertValue`.
var value = dataItem && (dataItem.value == null ? dataItem : dataItem.value); // If any dataItem is like { value: 10 }
if (!this._rawData.pure && isDataItemOption(dataItem)) {
this.hasItemOption = true;
}
return parseDataValue(value instanceof Array ? value[dimIndex] // If value is a single number or something else not array.
: value, this._dimensionInfos[dimName]);
},
typedArray: function (dataItem, dimName, dataIndex, dimIndex) {
return dataItem[dimIndex];
}
};
function getDimValueSimply(dataItem, dimName, dataIndex, dimIndex) {
return parseDataValue(dataItem[dimIndex], this._dimensionInfos[dimName]);
}
prepareInvertedIndex = function (list) {
var invertedIndicesMap = list._invertedIndicesMap;
each(invertedIndicesMap, function (invertedIndices, dim) {
var dimInfo = list._dimensionInfos[dim]; // Currently, only dimensions that has ordinalMeta can create inverted indices.
var ordinalMeta = dimInfo.ordinalMeta;
if (ordinalMeta) {
invertedIndices = invertedIndicesMap[dim] = new CtorInt32Array(ordinalMeta.categories.length); // The default value of TypedArray is 0. To avoid miss
// mapping to 0, we should set it as INDEX_NOT_FOUND.
for (var i = 0; i < invertedIndices.length; i++) {
invertedIndices[i] = INDEX_NOT_FOUND;
}
for (var i = 0; i < list._count; i++) {
// Only support the case that all values are distinct.
invertedIndices[list.get(dim, i)] = i;
}
}
});
};
getIdNameFromStore = function (list, dimIdx, ordinalMeta, rawIndex) {
var val;
var chunk = list._storageArr[dimIdx];
if (chunk) {
val = chunk[rawIndex];
if (ordinalMeta && ordinalMeta.categories.length) {
val = ordinalMeta.categories[val];
}
}
return convertOptionIdName(val, null);
};
getIndicesCtor = function (list) {
// The possible max value in this._indicies is always this._rawCount despite of filtering.
return list._rawCount > 65535 ? CtorUint32Array : CtorUint16Array;
};
prepareStorage = function (storage, dimInfo, end, append) {
var DataCtor = dataCtors[dimInfo.type];
var dim = dimInfo.name;
if (append) {
var oldStore = storage[dim];
var oldLen = oldStore && oldStore.length;
if (!(oldLen === end)) {
var newStore = new DataCtor(end); // The cost of the copy is probably inconsiderable
// within the initial chunkSize.
for (var j = 0; j < oldLen; j++) {
newStore[j] = oldStore[j];
}
storage[dim] = newStore;
}
} else {
storage[dim] = new DataCtor(end);
}
};
getRawIndexWithoutIndices = function (idx) {
return idx;
};
getRawIndexWithIndices = function (idx) {
if (idx < this._count && idx >= 0) {
return this._indices[idx];
}
return -1;
};
/**
* @see the comment of `List['getId']`.
*/
getId = function (list, rawIndex) {
var id = list._idList[rawIndex];
if (id == null && list._idDimIdx != null) {
id = getIdNameFromStore(list, list._idDimIdx, list._idOrdinalMeta, rawIndex);
}
if (id == null) {
id = ID_PREFIX + rawIndex;
}
return id;
};
normalizeDimensions = function (dimensions) {
if (!isArray(dimensions)) {
dimensions = dimensions != null ? [dimensions] : [];
}
return dimensions;
};
validateDimensions = function (list, dims) {
for (var i = 0; i < dims.length; i++) {
// stroage may be empty when no data, so use
// dimensionInfos to check.
if (!list._dimensionInfos[dims[i]]) {
console.error('Unkown dimension ' + dims[i]);
}
}
}; // Data in excludeDimensions is copied, otherwise transfered.
cloneListForMapAndSample = function (original, excludeDimensions) {
var allDimensions = original.dimensions;
var list = new List(map$1(allDimensions, original.getDimensionInfo, original), original.hostModel); // FIXME If needs stackedOn, value may already been stacked
transferProperties(list, original);
var storage = list._storage = {};
var originalStorage = original._storage;
var storageArr = list._storageArr = []; // Init storage
for (var i = 0; i < allDimensions.length; i++) {
var dim = allDimensions[i];
if (originalStorage[dim]) {
// Notice that we do not reset invertedIndicesMap here, becuase
// there is no scenario of mapping or sampling ordinal dimension.
if (indexOf(excludeDimensions, dim) >= 0) {
storage[dim] = cloneChunk(originalStorage[dim]);
list._rawExtent[dim] = getInitialExtent();
list._extent[dim] = null;
} else {
// Direct reference for other dimensions
storage[dim] = originalStorage[dim];
}
storageArr.push(storage[dim]);
}
}
return list;
};
function cloneChunk(originalChunk) {
var Ctor = originalChunk.constructor; // Only shallow clone is enough when Array.
return Ctor === Array ? originalChunk.slice() : new Ctor(originalChunk);
}
getInitialExtent = function () {
return [Infinity, -Infinity];
};
setItemDataAndSeriesIndex = function (child) {
var childECData = getECData(child);
var thisECData = getECData(this);
childECData.seriesIndex = thisECData.seriesIndex;
childECData.dataIndex = thisECData.dataIndex;
childECData.dataType = thisECData.dataType;
};
transferProperties = function (target, source) {
each(TRANSFERABLE_PROPERTIES.concat(source.__wrappedMethods || []), function (propName) {
if (source.hasOwnProperty(propName)) {
target[propName] = source[propName];
}
});
target.__wrappedMethods = source.__wrappedMethods;
each(CLONE_PROPERTIES, function (propName) {
target[propName] = clone(source[propName]);
});
target._calculationInfo = extend({}, source._calculationInfo);
};
makeIdFromName = function (list, idx) {
var nameList = list._nameList;
var idList = list._idList;
var nameDimIdx = list._nameDimIdx;
var idDimIdx = list._idDimIdx;
var name = nameList[idx];
var id = idList[idx];
if (name == null && nameDimIdx != null) {
nameList[idx] = name = getIdNameFromStore(list, nameDimIdx, list._nameOrdinalMeta, idx);
}
if (id == null && idDimIdx != null) {
idList[idx] = id = getIdNameFromStore(list, idDimIdx, list._idOrdinalMeta, idx);
}
if (id == null && name != null) {
var nameRepeatCount = list._nameRepeatCount;
var nmCnt = nameRepeatCount[name] = (nameRepeatCount[name] || 0) + 1;
id = name;
if (nmCnt > 1) {
id += '__ec__' + nmCnt;
}
idList[idx] = id;
}
};
}();
return List;
}();
/**
* @see {module:echarts/test/ut/spec/data/completeDimensions}
*
* This method builds the relationship between:
* + "what the coord sys or series requires (see `sysDims`)",
* + "what the user defines (in `encode` and `dimensions`, see `opt.dimsDef` and `opt.encodeDef`)"
* + "what the data source provids (see `source`)".
*
* Some guess strategy will be adapted if user does not define something.
* If no 'value' dimension specified, the first no-named dimension will be
* named as 'value'.
*
* @param {Array.} sysDims Necessary dimensions, like ['x', 'y'], which
* provides not only dim template, but also default order.
* properties: 'name', 'type', 'displayName'.
* `name` of each item provides default coord name.
* [{dimsDef: [string|Object, ...]}, ...] dimsDef of sysDim item provides default dim name, and
* provide dims count that the sysDim required.
* [{ordinalMeta}] can be specified.
* @param {module:echarts/data/Source|Array|Object} source or data (for compatibal with pervious)
* @param {Object} [opt]
* @param {Array.