417 lines
15 KiB
JavaScript
417 lines
15 KiB
JavaScript
|
|
/*
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
|
|
|
|
/**
|
|
* AUTO-GENERATED FILE. DO NOT MODIFY.
|
|
*/
|
|
|
|
/*
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
import { setAsPrimitive, map, isTypedArray, assert, each, retrieve2 } from 'zrender/lib/core/util.js';
|
|
import { createSource, cloneSourceShallow } from '../Source.js';
|
|
import { SOURCE_FORMAT_TYPED_ARRAY, SOURCE_FORMAT_ORIGINAL } from '../../util/types.js';
|
|
import { querySeriesUpstreamDatasetModel, queryDatasetUpstreamDatasetModels } from './sourceHelper.js';
|
|
import { applyDataTransform } from './transform.js';
|
|
import DataStore from '../DataStore.js';
|
|
import { DefaultDataProvider } from './dataProvider.js';
|
|
/**
|
|
* [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 transforms.
|
|
* 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 = [];
|
|
this._storeList = [];
|
|
// version sign of each upstream source manager.
|
|
this._upstreamSignList = [];
|
|
this._versionSignBase = 0;
|
|
this._dirty = true;
|
|
this._sourceHost = sourceHost;
|
|
}
|
|
/**
|
|
* Mark dirty.
|
|
*/
|
|
SourceManager.prototype.dirty = function () {
|
|
this._setLocalSource([], []);
|
|
this._storeList = [];
|
|
this._dirty = true;
|
|
};
|
|
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();
|
|
this._dirty = false;
|
|
}
|
|
};
|
|
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 || {};
|
|
var seriesLayoutBy = retrieve2(newMetaRawOption.seriesLayoutBy, upMetaRawOption.seriesLayoutBy) || null;
|
|
var sourceHeader = retrieve2(newMetaRawOption.sourceHeader, upMetaRawOption.sourceHeader);
|
|
// 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.dimensions);
|
|
// We share source with dataset as much as possible
|
|
// to avoid extra memory cost of high dimensional data.
|
|
var needsCreateSource = seriesLayoutBy !== upMetaRawOption.seriesLayoutBy || !!sourceHeader !== !!upMetaRawOption.sourceHeader || dimensions;
|
|
resultSourceList = needsCreateSource ? [createSource(data, {
|
|
seriesLayoutBy: seriesLayoutBy,
|
|
sourceHeader: sourceHeader,
|
|
dimensions: dimensions
|
|
}, sourceFormat)] : [];
|
|
} 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)];
|
|
upstreamSignList = [];
|
|
}
|
|
}
|
|
if (process.env.NODE_ENV !== '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 (process.env.NODE_ENV !== 'production') {
|
|
assert(fromTransformResult != null || transformOption != null);
|
|
}
|
|
if (fromTransformResult != null) {
|
|
var errMsg = '';
|
|
if (upMgrList.length !== 1) {
|
|
if (process.env.NODE_ENV !== '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 (process.env.NODE_ENV !== '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 () {
|
|
if (this._dirty) {
|
|
return true;
|
|
}
|
|
// All sourceList is from the some upstream.
|
|
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 default 0, means "main source".
|
|
* In most cases there is only one source.
|
|
*/
|
|
SourceManager.prototype.getSource = function (sourceIndex) {
|
|
sourceIndex = sourceIndex || 0;
|
|
var source = this._sourceList[sourceIndex];
|
|
if (!source) {
|
|
// Series may share source instance with dataset.
|
|
var upSourceMgrList = this._getUpstreamSourceManagers();
|
|
return upSourceMgrList[0] && upSourceMgrList[0].getSource(sourceIndex);
|
|
}
|
|
return source;
|
|
};
|
|
/**
|
|
*
|
|
* Get a data store which can be shared across series.
|
|
* Only available for series.
|
|
*
|
|
* @param seriesDimRequest Dimensions that are generated in series.
|
|
* Should have been sorted by `storeDimIndex` asc.
|
|
*/
|
|
SourceManager.prototype.getSharedDataStore = function (seriesDimRequest) {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
assert(isSeries(this._sourceHost), 'Can only call getDataStore on series source manager.');
|
|
}
|
|
var schema = seriesDimRequest.makeStoreSchema();
|
|
return this._innerGetDataStore(schema.dimensions, seriesDimRequest.source, schema.hash);
|
|
};
|
|
SourceManager.prototype._innerGetDataStore = function (storeDims, seriesSource, sourceReadKey) {
|
|
// TODO Can use other sourceIndex?
|
|
var sourceIndex = 0;
|
|
var storeList = this._storeList;
|
|
var cachedStoreMap = storeList[sourceIndex];
|
|
if (!cachedStoreMap) {
|
|
cachedStoreMap = storeList[sourceIndex] = {};
|
|
}
|
|
var cachedStore = cachedStoreMap[sourceReadKey];
|
|
if (!cachedStore) {
|
|
var upSourceMgr = this._getUpstreamSourceManagers()[0];
|
|
if (isSeries(this._sourceHost) && upSourceMgr) {
|
|
cachedStore = upSourceMgr._innerGetDataStore(storeDims, seriesSource, sourceReadKey);
|
|
} else {
|
|
cachedStore = new DataStore();
|
|
// Always create store from source of series.
|
|
cachedStore.initData(new DefaultDataProvider(seriesSource, storeDims.length), storeDims);
|
|
}
|
|
cachedStoreMap[sourceReadKey] = cachedStore;
|
|
}
|
|
return cachedStore;
|
|
};
|
|
/**
|
|
* PENDING: 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
|
|
// there is no need to update them when change happens.
|
|
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;
|
|
}();
|
|
export { SourceManager };
|
|
// Call this method after `super.init` and `super.mergeOption` to
|
|
// disable the transform merge, but do not disable transform clone from rawOption.
|
|
export 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);
|
|
} |