universalTransition.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  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. // Universal transitions that can animate between any shapes(series) and any properties in any amounts.
  41. import { SERIES_UNIVERSAL_TRANSITION_PROP } from '../model/Series.js';
  42. import { createHashMap, each, map, filter, isArray, extend } from 'zrender/lib/core/util.js';
  43. import { applyMorphAnimation, getPathList } from './morphTransitionHelper.js';
  44. import Path from 'zrender/lib/graphic/Path.js';
  45. import { initProps } from '../util/graphic.js';
  46. import DataDiffer from '../data/DataDiffer.js';
  47. import { makeInner, normalizeToArray } from '../util/model.js';
  48. import { warn } from '../util/log.js';
  49. import { getAnimationConfig, getOldStyle } from './basicTransition.js';
  50. import Displayable from 'zrender/lib/graphic/Displayable.js';
  51. var DATA_COUNT_THRESHOLD = 1e4;
  52. ;
  53. var getUniversalTransitionGlobalStore = makeInner();
  54. function getGroupIdDimension(data) {
  55. var dimensions = data.dimensions;
  56. for (var i = 0; i < dimensions.length; i++) {
  57. var dimInfo = data.getDimensionInfo(dimensions[i]);
  58. if (dimInfo && dimInfo.otherDims.itemGroupId === 0) {
  59. return dimensions[i];
  60. }
  61. }
  62. }
  63. function flattenDataDiffItems(list) {
  64. var items = [];
  65. each(list, function (seriesInfo) {
  66. var data = seriesInfo.data;
  67. if (data.count() > DATA_COUNT_THRESHOLD) {
  68. if (process.env.NODE_ENV !== 'production') {
  69. warn('Universal transition is disabled on large data > 10k.');
  70. }
  71. return;
  72. }
  73. var indices = data.getIndices();
  74. var groupDim = getGroupIdDimension(data);
  75. for (var dataIndex = 0; dataIndex < indices.length; dataIndex++) {
  76. items.push({
  77. dataGroupId: seriesInfo.dataGroupId,
  78. data: data,
  79. dim: seriesInfo.dim || groupDim,
  80. divide: seriesInfo.divide,
  81. dataIndex: dataIndex
  82. });
  83. }
  84. });
  85. return items;
  86. }
  87. function fadeInElement(newEl, newSeries, newIndex) {
  88. newEl.traverse(function (el) {
  89. if (el instanceof Path) {
  90. // TODO use fade in animation for target element.
  91. initProps(el, {
  92. style: {
  93. opacity: 0
  94. }
  95. }, newSeries, {
  96. dataIndex: newIndex,
  97. isFrom: true
  98. });
  99. }
  100. });
  101. }
  102. function removeEl(el) {
  103. if (el.parent) {
  104. // Bake parent transform to element.
  105. // So it can still have proper transform to transition after it's removed.
  106. var computedTransform = el.getComputedTransform();
  107. el.setLocalTransform(computedTransform);
  108. el.parent.remove(el);
  109. }
  110. }
  111. function stopAnimation(el) {
  112. el.stopAnimation();
  113. if (el.isGroup) {
  114. el.traverse(function (child) {
  115. child.stopAnimation();
  116. });
  117. }
  118. }
  119. function animateElementStyles(el, dataIndex, seriesModel) {
  120. var animationConfig = getAnimationConfig('update', seriesModel, dataIndex);
  121. animationConfig && el.traverse(function (child) {
  122. if (child instanceof Displayable) {
  123. var oldStyle = getOldStyle(child);
  124. if (oldStyle) {
  125. child.animateFrom({
  126. style: oldStyle
  127. }, animationConfig);
  128. }
  129. }
  130. });
  131. }
  132. function isAllIdSame(oldDiffItems, newDiffItems) {
  133. var len = oldDiffItems.length;
  134. if (len !== newDiffItems.length) {
  135. return false;
  136. }
  137. for (var i = 0; i < len; i++) {
  138. var oldItem = oldDiffItems[i];
  139. var newItem = newDiffItems[i];
  140. if (oldItem.data.getId(oldItem.dataIndex) !== newItem.data.getId(newItem.dataIndex)) {
  141. return false;
  142. }
  143. }
  144. return true;
  145. }
  146. function transitionBetween(oldList, newList, api) {
  147. var oldDiffItems = flattenDataDiffItems(oldList);
  148. var newDiffItems = flattenDataDiffItems(newList);
  149. function updateMorphingPathProps(from, to, rawFrom, rawTo, animationCfg) {
  150. if (rawFrom || from) {
  151. to.animateFrom({
  152. style: rawFrom && rawFrom !== from ? // dividingMethod like clone may override the style(opacity)
  153. // So extend it to raw style.
  154. extend(extend({}, rawFrom.style), from.style) : from.style
  155. }, animationCfg);
  156. }
  157. }
  158. function findKeyDim(items) {
  159. for (var i = 0; i < items.length; i++) {
  160. if (items[i].dim) {
  161. return items[i].dim;
  162. }
  163. }
  164. }
  165. var oldKeyDim = findKeyDim(oldDiffItems);
  166. var newKeyDim = findKeyDim(newDiffItems);
  167. var hasMorphAnimation = false;
  168. function createKeyGetter(isOld, onlyGetId) {
  169. return function (diffItem) {
  170. var data = diffItem.data;
  171. var dataIndex = diffItem.dataIndex; // TODO if specified dim
  172. if (onlyGetId) {
  173. return data.getId(dataIndex);
  174. } // Use group id as transition key by default.
  175. // So we can achieve multiple to multiple animation like drilldown / up naturally.
  176. // If group id not exits. Use id instead. If so, only one to one transition will be applied.
  177. var dataGroupId = diffItem.dataGroupId; // If specified key dimension(itemGroupId by default). Use this same dimension from other data.
  178. // PENDING: If only use key dimension of newData.
  179. var keyDim = isOld ? oldKeyDim || newKeyDim : newKeyDim || oldKeyDim;
  180. var dimInfo = keyDim && data.getDimensionInfo(keyDim);
  181. var dimOrdinalMeta = dimInfo && dimInfo.ordinalMeta;
  182. if (dimInfo) {
  183. // Get from encode.itemGroupId.
  184. var key = data.get(dimInfo.name, dataIndex);
  185. if (dimOrdinalMeta) {
  186. return dimOrdinalMeta.categories[key] || key + '';
  187. }
  188. return key + '';
  189. } // Get groupId from raw item. { groupId: '' }
  190. var itemVal = data.getRawDataItem(dataIndex);
  191. if (itemVal && itemVal.groupId) {
  192. return itemVal.groupId + '';
  193. }
  194. return dataGroupId || data.getId(dataIndex);
  195. };
  196. } // Use id if it's very likely to be an one to one animation
  197. // It's more robust than groupId
  198. // TODO Check if key dimension is specified.
  199. var useId = isAllIdSame(oldDiffItems, newDiffItems);
  200. var isElementStillInChart = {};
  201. if (!useId) {
  202. // We may have different diff strategy with basicTransition if we use other dimension as key.
  203. // If so, we can't simply check if oldEl is same with newEl. We need a map to check if oldEl is still being used in the new chart.
  204. // We can't use the elements that already being morphed. Let it keep it's original basic transition.
  205. for (var i = 0; i < newDiffItems.length; i++) {
  206. var newItem = newDiffItems[i];
  207. var el = newItem.data.getItemGraphicEl(newItem.dataIndex);
  208. if (el) {
  209. isElementStillInChart[el.id] = true;
  210. }
  211. }
  212. }
  213. function updateOneToOne(newIndex, oldIndex) {
  214. var oldItem = oldDiffItems[oldIndex];
  215. var newItem = newDiffItems[newIndex];
  216. var newSeries = newItem.data.hostModel; // TODO Mark this elements is morphed and don't morph them anymore
  217. var oldEl = oldItem.data.getItemGraphicEl(oldItem.dataIndex);
  218. var newEl = newItem.data.getItemGraphicEl(newItem.dataIndex); // Can't handle same elements.
  219. if (oldEl === newEl) {
  220. newEl && animateElementStyles(newEl, newItem.dataIndex, newSeries);
  221. return;
  222. }
  223. if ( // We can't use the elements that already being morphed
  224. oldEl && isElementStillInChart[oldEl.id]) {
  225. return;
  226. }
  227. if (newEl) {
  228. // TODO: If keep animating the group in case
  229. // some of the elements don't want to be morphed.
  230. // TODO Label?
  231. stopAnimation(newEl);
  232. if (oldEl) {
  233. stopAnimation(oldEl); // If old element is doing leaving animation. stop it and remove it immediately.
  234. removeEl(oldEl);
  235. hasMorphAnimation = true;
  236. applyMorphAnimation(getPathList(oldEl), getPathList(newEl), newItem.divide, newSeries, newIndex, updateMorphingPathProps);
  237. } else {
  238. fadeInElement(newEl, newSeries, newIndex);
  239. }
  240. } // else keep oldEl leaving animation.
  241. }
  242. new DataDiffer(oldDiffItems, newDiffItems, createKeyGetter(true, useId), createKeyGetter(false, useId), null, 'multiple').update(updateOneToOne).updateManyToOne(function (newIndex, oldIndices) {
  243. var newItem = newDiffItems[newIndex];
  244. var newData = newItem.data;
  245. var newSeries = newData.hostModel;
  246. var newEl = newData.getItemGraphicEl(newItem.dataIndex);
  247. var oldElsList = filter(map(oldIndices, function (idx) {
  248. return oldDiffItems[idx].data.getItemGraphicEl(oldDiffItems[idx].dataIndex);
  249. }), function (oldEl) {
  250. return oldEl && oldEl !== newEl && !isElementStillInChart[oldEl.id];
  251. });
  252. if (newEl) {
  253. stopAnimation(newEl);
  254. if (oldElsList.length) {
  255. // If old element is doing leaving animation. stop it and remove it immediately.
  256. each(oldElsList, function (oldEl) {
  257. stopAnimation(oldEl);
  258. removeEl(oldEl);
  259. });
  260. hasMorphAnimation = true;
  261. applyMorphAnimation(getPathList(oldElsList), getPathList(newEl), newItem.divide, newSeries, newIndex, updateMorphingPathProps);
  262. } else {
  263. fadeInElement(newEl, newSeries, newItem.dataIndex);
  264. }
  265. } // else keep oldEl leaving animation.
  266. }).updateOneToMany(function (newIndices, oldIndex) {
  267. var oldItem = oldDiffItems[oldIndex];
  268. var oldEl = oldItem.data.getItemGraphicEl(oldItem.dataIndex); // We can't use the elements that already being morphed
  269. if (oldEl && isElementStillInChart[oldEl.id]) {
  270. return;
  271. }
  272. var newElsList = filter(map(newIndices, function (idx) {
  273. return newDiffItems[idx].data.getItemGraphicEl(newDiffItems[idx].dataIndex);
  274. }), function (el) {
  275. return el && el !== oldEl;
  276. });
  277. var newSeris = newDiffItems[newIndices[0]].data.hostModel;
  278. if (newElsList.length) {
  279. each(newElsList, function (newEl) {
  280. return stopAnimation(newEl);
  281. });
  282. if (oldEl) {
  283. stopAnimation(oldEl); // If old element is doing leaving animation. stop it and remove it immediately.
  284. removeEl(oldEl);
  285. hasMorphAnimation = true;
  286. applyMorphAnimation(getPathList(oldEl), getPathList(newElsList), oldItem.divide, // Use divide on old.
  287. newSeris, newIndices[0], updateMorphingPathProps);
  288. } else {
  289. each(newElsList, function (newEl) {
  290. return fadeInElement(newEl, newSeris, newIndices[0]);
  291. });
  292. }
  293. } // else keep oldEl leaving animation.
  294. }).updateManyToMany(function (newIndices, oldIndices) {
  295. // If two data are same and both have groupId.
  296. // Normally they should be diff by id.
  297. new DataDiffer(oldIndices, newIndices, function (rawIdx) {
  298. return oldDiffItems[rawIdx].data.getId(oldDiffItems[rawIdx].dataIndex);
  299. }, function (rawIdx) {
  300. return newDiffItems[rawIdx].data.getId(newDiffItems[rawIdx].dataIndex);
  301. }).update(function (newIndex, oldIndex) {
  302. // Use the original index
  303. updateOneToOne(newIndices[newIndex], oldIndices[oldIndex]);
  304. }).execute();
  305. }).execute();
  306. if (hasMorphAnimation) {
  307. each(newList, function (_a) {
  308. var data = _a.data;
  309. var seriesModel = data.hostModel;
  310. var view = seriesModel && api.getViewOfSeriesModel(seriesModel);
  311. var animationCfg = getAnimationConfig('update', seriesModel, 0); // use 0 index.
  312. if (view && seriesModel.isAnimationEnabled() && animationCfg && animationCfg.duration > 0) {
  313. view.group.traverse(function (el) {
  314. if (el instanceof Path && !el.animators.length) {
  315. // We can't accept there still exists element that has no animation
  316. // if universalTransition is enabled
  317. el.animateFrom({
  318. style: {
  319. opacity: 0
  320. }
  321. }, animationCfg);
  322. }
  323. });
  324. }
  325. });
  326. }
  327. }
  328. function getSeriesTransitionKey(series) {
  329. var seriesKey = series.getModel('universalTransition').get('seriesKey');
  330. if (!seriesKey) {
  331. // Use series id by default.
  332. return series.id;
  333. }
  334. return seriesKey;
  335. }
  336. function convertArraySeriesKeyToString(seriesKey) {
  337. if (isArray(seriesKey)) {
  338. // Order independent.
  339. return seriesKey.sort().join(',');
  340. }
  341. return seriesKey;
  342. }
  343. function getDivideShapeFromData(data) {
  344. if (data.hostModel) {
  345. return data.hostModel.getModel('universalTransition').get('divideShape');
  346. }
  347. }
  348. function findTransitionSeriesBatches(globalStore, params) {
  349. var updateBatches = createHashMap();
  350. var oldDataMap = createHashMap(); // Map that only store key in array seriesKey.
  351. // Which is used to query the old data when transition from one to multiple series.
  352. var oldDataMapForSplit = createHashMap();
  353. each(globalStore.oldSeries, function (series, idx) {
  354. var oldDataGroupId = globalStore.oldDataGroupIds[idx];
  355. var oldData = globalStore.oldData[idx];
  356. var transitionKey = getSeriesTransitionKey(series);
  357. var transitionKeyStr = convertArraySeriesKeyToString(transitionKey);
  358. oldDataMap.set(transitionKeyStr, {
  359. dataGroupId: oldDataGroupId,
  360. data: oldData
  361. });
  362. if (isArray(transitionKey)) {
  363. // Same key can't in different array seriesKey.
  364. each(transitionKey, function (key) {
  365. oldDataMapForSplit.set(key, {
  366. key: transitionKeyStr,
  367. dataGroupId: oldDataGroupId,
  368. data: oldData
  369. });
  370. });
  371. }
  372. });
  373. function checkTransitionSeriesKeyDuplicated(transitionKeyStr) {
  374. if (updateBatches.get(transitionKeyStr)) {
  375. warn("Duplicated seriesKey in universalTransition " + transitionKeyStr);
  376. }
  377. }
  378. each(params.updatedSeries, function (series) {
  379. if (series.isUniversalTransitionEnabled() && series.isAnimationEnabled()) {
  380. var newDataGroupId = series.get('dataGroupId');
  381. var newData = series.getData();
  382. var transitionKey = getSeriesTransitionKey(series);
  383. var transitionKeyStr = convertArraySeriesKeyToString(transitionKey); // Only transition between series with same id.
  384. var oldData = oldDataMap.get(transitionKeyStr); // string transition key is the best match.
  385. if (oldData) {
  386. if (process.env.NODE_ENV !== 'production') {
  387. checkTransitionSeriesKeyDuplicated(transitionKeyStr);
  388. } // TODO check if data is same?
  389. updateBatches.set(transitionKeyStr, {
  390. oldSeries: [{
  391. dataGroupId: oldData.dataGroupId,
  392. divide: getDivideShapeFromData(oldData.data),
  393. data: oldData.data
  394. }],
  395. newSeries: [{
  396. dataGroupId: newDataGroupId,
  397. divide: getDivideShapeFromData(newData),
  398. data: newData
  399. }]
  400. });
  401. } else {
  402. // Transition from multiple series.
  403. if (isArray(transitionKey)) {
  404. if (process.env.NODE_ENV !== 'production') {
  405. checkTransitionSeriesKeyDuplicated(transitionKeyStr);
  406. }
  407. var oldSeries_1 = [];
  408. each(transitionKey, function (key) {
  409. var oldData = oldDataMap.get(key);
  410. if (oldData.data) {
  411. oldSeries_1.push({
  412. dataGroupId: oldData.dataGroupId,
  413. divide: getDivideShapeFromData(oldData.data),
  414. data: oldData.data
  415. });
  416. }
  417. });
  418. if (oldSeries_1.length) {
  419. updateBatches.set(transitionKeyStr, {
  420. oldSeries: oldSeries_1,
  421. newSeries: [{
  422. dataGroupId: newDataGroupId,
  423. data: newData,
  424. divide: getDivideShapeFromData(newData)
  425. }]
  426. });
  427. }
  428. } else {
  429. // Try transition to multiple series.
  430. var oldData_1 = oldDataMapForSplit.get(transitionKey);
  431. if (oldData_1) {
  432. var batch = updateBatches.get(oldData_1.key);
  433. if (!batch) {
  434. batch = {
  435. oldSeries: [{
  436. dataGroupId: oldData_1.dataGroupId,
  437. data: oldData_1.data,
  438. divide: getDivideShapeFromData(oldData_1.data)
  439. }],
  440. newSeries: []
  441. };
  442. updateBatches.set(oldData_1.key, batch);
  443. }
  444. batch.newSeries.push({
  445. dataGroupId: newDataGroupId,
  446. data: newData,
  447. divide: getDivideShapeFromData(newData)
  448. });
  449. }
  450. }
  451. }
  452. }
  453. });
  454. return updateBatches;
  455. }
  456. function querySeries(series, finder) {
  457. for (var i = 0; i < series.length; i++) {
  458. var found = finder.seriesIndex != null && finder.seriesIndex === series[i].seriesIndex || finder.seriesId != null && finder.seriesId === series[i].id;
  459. if (found) {
  460. return i;
  461. }
  462. }
  463. }
  464. function transitionSeriesFromOpt(transitionOpt, globalStore, params, api) {
  465. var from = [];
  466. var to = [];
  467. each(normalizeToArray(transitionOpt.from), function (finder) {
  468. var idx = querySeries(globalStore.oldSeries, finder);
  469. if (idx >= 0) {
  470. from.push({
  471. dataGroupId: globalStore.oldDataGroupIds[idx],
  472. data: globalStore.oldData[idx],
  473. // TODO can specify divideShape in transition.
  474. divide: getDivideShapeFromData(globalStore.oldData[idx]),
  475. dim: finder.dimension
  476. });
  477. }
  478. });
  479. each(normalizeToArray(transitionOpt.to), function (finder) {
  480. var idx = querySeries(params.updatedSeries, finder);
  481. if (idx >= 0) {
  482. var data = params.updatedSeries[idx].getData();
  483. to.push({
  484. dataGroupId: globalStore.oldDataGroupIds[idx],
  485. data: data,
  486. divide: getDivideShapeFromData(data),
  487. dim: finder.dimension
  488. });
  489. }
  490. });
  491. if (from.length > 0 && to.length > 0) {
  492. transitionBetween(from, to, api);
  493. }
  494. }
  495. export function installUniversalTransition(registers) {
  496. registers.registerUpdateLifecycle('series:beforeupdate', function (ecMOdel, api, params) {
  497. each(normalizeToArray(params.seriesTransition), function (transOpt) {
  498. each(normalizeToArray(transOpt.to), function (finder) {
  499. var series = params.updatedSeries;
  500. for (var i = 0; i < series.length; i++) {
  501. if (finder.seriesIndex != null && finder.seriesIndex === series[i].seriesIndex || finder.seriesId != null && finder.seriesId === series[i].id) {
  502. series[i][SERIES_UNIVERSAL_TRANSITION_PROP] = true;
  503. }
  504. }
  505. });
  506. });
  507. });
  508. registers.registerUpdateLifecycle('series:transition', function (ecModel, api, params) {
  509. // TODO api provide an namespace that can save stuff per instance
  510. var globalStore = getUniversalTransitionGlobalStore(api); // TODO multiple to multiple series.
  511. if (globalStore.oldSeries && params.updatedSeries && params.optionChanged) {
  512. // Use give transition config if its' give;
  513. var transitionOpt = params.seriesTransition;
  514. if (transitionOpt) {
  515. each(normalizeToArray(transitionOpt), function (opt) {
  516. transitionSeriesFromOpt(opt, globalStore, params, api);
  517. });
  518. } else {
  519. // Else guess from series based on transition series key.
  520. var updateBatches_1 = findTransitionSeriesBatches(globalStore, params);
  521. each(updateBatches_1.keys(), function (key) {
  522. var batch = updateBatches_1.get(key);
  523. transitionBetween(batch.oldSeries, batch.newSeries, api);
  524. });
  525. } // Reset
  526. each(params.updatedSeries, function (series) {
  527. // Reset;
  528. if (series[SERIES_UNIVERSAL_TRANSITION_PROP]) {
  529. series[SERIES_UNIVERSAL_TRANSITION_PROP] = false;
  530. }
  531. });
  532. } // Save all series of current update. Not only the updated one.
  533. var allSeries = ecModel.getSeries();
  534. var savedSeries = globalStore.oldSeries = [];
  535. var savedDataGroupIds = globalStore.oldDataGroupIds = [];
  536. var savedData = globalStore.oldData = [];
  537. for (var i = 0; i < allSeries.length; i++) {
  538. var data = allSeries[i].getData(); // Only save the data that can have transition.
  539. // Avoid large data costing too much extra memory
  540. if (data.count() < DATA_COUNT_THRESHOLD) {
  541. savedSeries.push(allSeries[i]);
  542. savedDataGroupIds.push(allSeries[i].get('dataGroupId'));
  543. savedData.push(data);
  544. }
  545. }
  546. });
  547. }