sourceManager.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. /**
  20. * AUTO-GENERATED FILE. DO NOT MODIFY.
  21. */
  22. /*
  23. * Licensed to the Apache Software Foundation (ASF) under one
  24. * or more contributor license agreements. See the NOTICE file
  25. * distributed with this work for additional information
  26. * regarding copyright ownership. The ASF licenses this file
  27. * to you under the Apache License, Version 2.0 (the
  28. * "License"); you may not use this file except in compliance
  29. * with the License. You may obtain a copy of the License at
  30. *
  31. * http://www.apache.org/licenses/LICENSE-2.0
  32. *
  33. * Unless required by applicable law or agreed to in writing,
  34. * software distributed under the License is distributed on an
  35. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  36. * KIND, either express or implied. See the License for the
  37. * specific language governing permissions and limitations
  38. * under the License.
  39. */
  40. import { setAsPrimitive, map, isTypedArray, assert, each, retrieve2 } from 'zrender/lib/core/util.js';
  41. import { createSource, cloneSourceShallow } from '../Source.js';
  42. import { SOURCE_FORMAT_TYPED_ARRAY, SOURCE_FORMAT_ORIGINAL } from '../../util/types.js';
  43. import { querySeriesUpstreamDatasetModel, queryDatasetUpstreamDatasetModels } from './sourceHelper.js';
  44. import { applyDataTransform } from './transform.js';
  45. import DataStore from '../DataStore.js';
  46. import { DefaultDataProvider } from './dataProvider.js';
  47. /**
  48. * [REQUIREMENT_MEMO]:
  49. * (0) `metaRawOption` means `dimensions`/`sourceHeader`/`seriesLayoutBy` in raw option.
  50. * (1) Keep support the feature: `metaRawOption` can be specified both on `series` and
  51. * `root-dataset`. Them on `series` has higher priority.
  52. * (2) Do not support to set `metaRawOption` on a `non-root-dataset`, because it might
  53. * confuse users: whether those props indicate how to visit the upstream source or visit
  54. * the transform result source, and some transforms has nothing to do with these props,
  55. * and some transforms might have multiple upstream.
  56. * (3) Transforms should specify `metaRawOption` in each output, just like they can be
  57. * declared in `root-dataset`.
  58. * (4) At present only support visit source in `SERIES_LAYOUT_BY_COLUMN` in transforms.
  59. * That is for reducing complexity in transforms.
  60. * PENDING: Whether to provide transposition transform?
  61. *
  62. * [IMPLEMENTAION_MEMO]:
  63. * "sourceVisitConfig" are calculated from `metaRawOption` and `data`.
  64. * They will not be calculated until `source` is about to be visited (to prevent from
  65. * duplicate calcuation). `source` is visited only in series and input to transforms.
  66. *
  67. * [DIMENSION_INHERIT_RULE]:
  68. * By default the dimensions are inherited from ancestors, unless a transform return
  69. * a new dimensions definition.
  70. * Consider the case:
  71. * ```js
  72. * dataset: [{
  73. * source: [ ['Product', 'Sales', 'Prise'], ['Cookies', 321, 44.21], ...]
  74. * }, {
  75. * transform: { type: 'filter', ... }
  76. * }]
  77. * dataset: [{
  78. * dimension: ['Product', 'Sales', 'Prise'],
  79. * source: [ ['Cookies', 321, 44.21], ...]
  80. * }, {
  81. * transform: { type: 'filter', ... }
  82. * }]
  83. * ```
  84. * The two types of option should have the same behavior after transform.
  85. *
  86. *
  87. * [SCENARIO]:
  88. * (1) Provide source data directly:
  89. * ```js
  90. * series: {
  91. * encode: {...},
  92. * dimensions: [...]
  93. * seriesLayoutBy: 'row',
  94. * data: [[...]]
  95. * }
  96. * ```
  97. * (2) Series refer to dataset.
  98. * ```js
  99. * series: [{
  100. * encode: {...}
  101. * // Ignore datasetIndex means `datasetIndex: 0`
  102. * // and the dimensions defination in dataset is used
  103. * }, {
  104. * encode: {...},
  105. * seriesLayoutBy: 'column',
  106. * datasetIndex: 1
  107. * }]
  108. * ```
  109. * (3) dataset transform
  110. * ```js
  111. * dataset: [{
  112. * source: [...]
  113. * }, {
  114. * source: [...]
  115. * }, {
  116. * // By default from 0.
  117. * transform: { type: 'filter', config: {...} }
  118. * }, {
  119. * // Piped.
  120. * transform: [
  121. * { type: 'filter', config: {...} },
  122. * { type: 'sort', config: {...} }
  123. * ]
  124. * }, {
  125. * id: 'regressionData',
  126. * fromDatasetIndex: 1,
  127. * // Third-party transform
  128. * transform: { type: 'ecStat:regression', config: {...} }
  129. * }, {
  130. * // retrieve the extra result.
  131. * id: 'regressionFormula',
  132. * fromDatasetId: 'regressionData',
  133. * fromTransformResult: 1
  134. * }]
  135. * ```
  136. */
  137. var SourceManager = /** @class */function () {
  138. function SourceManager(sourceHost) {
  139. // Cached source. Do not repeat calculating if not dirty.
  140. this._sourceList = [];
  141. this._storeList = [];
  142. // version sign of each upstream source manager.
  143. this._upstreamSignList = [];
  144. this._versionSignBase = 0;
  145. this._dirty = true;
  146. this._sourceHost = sourceHost;
  147. }
  148. /**
  149. * Mark dirty.
  150. */
  151. SourceManager.prototype.dirty = function () {
  152. this._setLocalSource([], []);
  153. this._storeList = [];
  154. this._dirty = true;
  155. };
  156. SourceManager.prototype._setLocalSource = function (sourceList, upstreamSignList) {
  157. this._sourceList = sourceList;
  158. this._upstreamSignList = upstreamSignList;
  159. this._versionSignBase++;
  160. if (this._versionSignBase > 9e10) {
  161. this._versionSignBase = 0;
  162. }
  163. };
  164. /**
  165. * For detecting whether the upstream source is dirty, so that
  166. * the local cached source (in `_sourceList`) should be discarded.
  167. */
  168. SourceManager.prototype._getVersionSign = function () {
  169. return this._sourceHost.uid + '_' + this._versionSignBase;
  170. };
  171. /**
  172. * Always return a source instance. Otherwise throw error.
  173. */
  174. SourceManager.prototype.prepareSource = function () {
  175. // For the case that call `setOption` multiple time but no data changed,
  176. // cache the result source to prevent from repeating transform.
  177. if (this._isDirty()) {
  178. this._createSource();
  179. this._dirty = false;
  180. }
  181. };
  182. SourceManager.prototype._createSource = function () {
  183. this._setLocalSource([], []);
  184. var sourceHost = this._sourceHost;
  185. var upSourceMgrList = this._getUpstreamSourceManagers();
  186. var hasUpstream = !!upSourceMgrList.length;
  187. var resultSourceList;
  188. var upstreamSignList;
  189. if (isSeries(sourceHost)) {
  190. var seriesModel = sourceHost;
  191. var data = void 0;
  192. var sourceFormat = void 0;
  193. var upSource = void 0;
  194. // Has upstream dataset
  195. if (hasUpstream) {
  196. var upSourceMgr = upSourceMgrList[0];
  197. upSourceMgr.prepareSource();
  198. upSource = upSourceMgr.getSource();
  199. data = upSource.data;
  200. sourceFormat = upSource.sourceFormat;
  201. upstreamSignList = [upSourceMgr._getVersionSign()];
  202. }
  203. // Series data is from own.
  204. else {
  205. data = seriesModel.get('data', true);
  206. sourceFormat = isTypedArray(data) ? SOURCE_FORMAT_TYPED_ARRAY : SOURCE_FORMAT_ORIGINAL;
  207. upstreamSignList = [];
  208. }
  209. // See [REQUIREMENT_MEMO], merge settings on series and parent dataset if it is root.
  210. var newMetaRawOption = this._getSourceMetaRawOption() || {};
  211. var upMetaRawOption = upSource && upSource.metaRawOption || {};
  212. var seriesLayoutBy = retrieve2(newMetaRawOption.seriesLayoutBy, upMetaRawOption.seriesLayoutBy) || null;
  213. var sourceHeader = retrieve2(newMetaRawOption.sourceHeader, upMetaRawOption.sourceHeader);
  214. // Note here we should not use `upSource.dimensionsDefine`. Consider the case:
  215. // `upSource.dimensionsDefine` is detected by `seriesLayoutBy: 'column'`,
  216. // but series need `seriesLayoutBy: 'row'`.
  217. var dimensions = retrieve2(newMetaRawOption.dimensions, upMetaRawOption.dimensions);
  218. // We share source with dataset as much as possible
  219. // to avoid extra memory cost of high dimensional data.
  220. var needsCreateSource = seriesLayoutBy !== upMetaRawOption.seriesLayoutBy || !!sourceHeader !== !!upMetaRawOption.sourceHeader || dimensions;
  221. resultSourceList = needsCreateSource ? [createSource(data, {
  222. seriesLayoutBy: seriesLayoutBy,
  223. sourceHeader: sourceHeader,
  224. dimensions: dimensions
  225. }, sourceFormat)] : [];
  226. } else {
  227. var datasetModel = sourceHost;
  228. // Has upstream dataset.
  229. if (hasUpstream) {
  230. var result = this._applyTransform(upSourceMgrList);
  231. resultSourceList = result.sourceList;
  232. upstreamSignList = result.upstreamSignList;
  233. }
  234. // Is root dataset.
  235. else {
  236. var sourceData = datasetModel.get('source', true);
  237. resultSourceList = [createSource(sourceData, this._getSourceMetaRawOption(), null)];
  238. upstreamSignList = [];
  239. }
  240. }
  241. if (process.env.NODE_ENV !== 'production') {
  242. assert(resultSourceList && upstreamSignList);
  243. }
  244. this._setLocalSource(resultSourceList, upstreamSignList);
  245. };
  246. SourceManager.prototype._applyTransform = function (upMgrList) {
  247. var datasetModel = this._sourceHost;
  248. var transformOption = datasetModel.get('transform', true);
  249. var fromTransformResult = datasetModel.get('fromTransformResult', true);
  250. if (process.env.NODE_ENV !== 'production') {
  251. assert(fromTransformResult != null || transformOption != null);
  252. }
  253. if (fromTransformResult != null) {
  254. var errMsg = '';
  255. if (upMgrList.length !== 1) {
  256. if (process.env.NODE_ENV !== 'production') {
  257. errMsg = 'When using `fromTransformResult`, there should be only one upstream dataset';
  258. }
  259. doThrow(errMsg);
  260. }
  261. }
  262. var sourceList;
  263. var upSourceList = [];
  264. var upstreamSignList = [];
  265. each(upMgrList, function (upMgr) {
  266. upMgr.prepareSource();
  267. var upSource = upMgr.getSource(fromTransformResult || 0);
  268. var errMsg = '';
  269. if (fromTransformResult != null && !upSource) {
  270. if (process.env.NODE_ENV !== 'production') {
  271. errMsg = 'Can not retrieve result by `fromTransformResult`: ' + fromTransformResult;
  272. }
  273. doThrow(errMsg);
  274. }
  275. upSourceList.push(upSource);
  276. upstreamSignList.push(upMgr._getVersionSign());
  277. });
  278. if (transformOption) {
  279. sourceList = applyDataTransform(transformOption, upSourceList, {
  280. datasetIndex: datasetModel.componentIndex
  281. });
  282. } else if (fromTransformResult != null) {
  283. sourceList = [cloneSourceShallow(upSourceList[0])];
  284. }
  285. return {
  286. sourceList: sourceList,
  287. upstreamSignList: upstreamSignList
  288. };
  289. };
  290. SourceManager.prototype._isDirty = function () {
  291. if (this._dirty) {
  292. return true;
  293. }
  294. // All sourceList is from the some upstream.
  295. var upSourceMgrList = this._getUpstreamSourceManagers();
  296. for (var i = 0; i < upSourceMgrList.length; i++) {
  297. var upSrcMgr = upSourceMgrList[i];
  298. if (
  299. // Consider the case that there is ancestor diry, call it recursively.
  300. // The performance is probably not an issue because usually the chain is not long.
  301. upSrcMgr._isDirty() || this._upstreamSignList[i] !== upSrcMgr._getVersionSign()) {
  302. return true;
  303. }
  304. }
  305. };
  306. /**
  307. * @param sourceIndex By default 0, means "main source".
  308. * In most cases there is only one source.
  309. */
  310. SourceManager.prototype.getSource = function (sourceIndex) {
  311. sourceIndex = sourceIndex || 0;
  312. var source = this._sourceList[sourceIndex];
  313. if (!source) {
  314. // Series may share source instance with dataset.
  315. var upSourceMgrList = this._getUpstreamSourceManagers();
  316. return upSourceMgrList[0] && upSourceMgrList[0].getSource(sourceIndex);
  317. }
  318. return source;
  319. };
  320. /**
  321. *
  322. * Get a data store which can be shared across series.
  323. * Only available for series.
  324. *
  325. * @param seriesDimRequest Dimensions that are generated in series.
  326. * Should have been sorted by `storeDimIndex` asc.
  327. */
  328. SourceManager.prototype.getSharedDataStore = function (seriesDimRequest) {
  329. if (process.env.NODE_ENV !== 'production') {
  330. assert(isSeries(this._sourceHost), 'Can only call getDataStore on series source manager.');
  331. }
  332. var schema = seriesDimRequest.makeStoreSchema();
  333. return this._innerGetDataStore(schema.dimensions, seriesDimRequest.source, schema.hash);
  334. };
  335. SourceManager.prototype._innerGetDataStore = function (storeDims, seriesSource, sourceReadKey) {
  336. // TODO Can use other sourceIndex?
  337. var sourceIndex = 0;
  338. var storeList = this._storeList;
  339. var cachedStoreMap = storeList[sourceIndex];
  340. if (!cachedStoreMap) {
  341. cachedStoreMap = storeList[sourceIndex] = {};
  342. }
  343. var cachedStore = cachedStoreMap[sourceReadKey];
  344. if (!cachedStore) {
  345. var upSourceMgr = this._getUpstreamSourceManagers()[0];
  346. if (isSeries(this._sourceHost) && upSourceMgr) {
  347. cachedStore = upSourceMgr._innerGetDataStore(storeDims, seriesSource, sourceReadKey);
  348. } else {
  349. cachedStore = new DataStore();
  350. // Always create store from source of series.
  351. cachedStore.initData(new DefaultDataProvider(seriesSource, storeDims.length), storeDims);
  352. }
  353. cachedStoreMap[sourceReadKey] = cachedStore;
  354. }
  355. return cachedStore;
  356. };
  357. /**
  358. * PENDING: Is it fast enough?
  359. * If no upstream, return empty array.
  360. */
  361. SourceManager.prototype._getUpstreamSourceManagers = function () {
  362. // Always get the relationship from the raw option.
  363. // Do not cache the link of the dependency graph, so that
  364. // there is no need to update them when change happens.
  365. var sourceHost = this._sourceHost;
  366. if (isSeries(sourceHost)) {
  367. var datasetModel = querySeriesUpstreamDatasetModel(sourceHost);
  368. return !datasetModel ? [] : [datasetModel.getSourceManager()];
  369. } else {
  370. return map(queryDatasetUpstreamDatasetModels(sourceHost), function (datasetModel) {
  371. return datasetModel.getSourceManager();
  372. });
  373. }
  374. };
  375. SourceManager.prototype._getSourceMetaRawOption = function () {
  376. var sourceHost = this._sourceHost;
  377. var seriesLayoutBy;
  378. var sourceHeader;
  379. var dimensions;
  380. if (isSeries(sourceHost)) {
  381. seriesLayoutBy = sourceHost.get('seriesLayoutBy', true);
  382. sourceHeader = sourceHost.get('sourceHeader', true);
  383. dimensions = sourceHost.get('dimensions', true);
  384. }
  385. // See [REQUIREMENT_MEMO], `non-root-dataset` do not support them.
  386. else if (!this._getUpstreamSourceManagers().length) {
  387. var model = sourceHost;
  388. seriesLayoutBy = model.get('seriesLayoutBy', true);
  389. sourceHeader = model.get('sourceHeader', true);
  390. dimensions = model.get('dimensions', true);
  391. }
  392. return {
  393. seriesLayoutBy: seriesLayoutBy,
  394. sourceHeader: sourceHeader,
  395. dimensions: dimensions
  396. };
  397. };
  398. return SourceManager;
  399. }();
  400. export { SourceManager };
  401. // Call this method after `super.init` and `super.mergeOption` to
  402. // disable the transform merge, but do not disable transform clone from rawOption.
  403. export function disableTransformOptionMerge(datasetModel) {
  404. var transformOption = datasetModel.option.transform;
  405. transformOption && setAsPrimitive(datasetModel.option.transform);
  406. }
  407. function isSeries(sourceHost) {
  408. // Avoid circular dependency with Series.ts
  409. return sourceHost.mainType === 'series';
  410. }
  411. function doThrow(errMsg) {
  412. throw new Error(errMsg);
  413. }