|   | 
| /* | 
| * 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. | 
| */ | 
| // Universal transitions that can animate between any shapes(series) and any properties in any amounts. | 
| import { SERIES_UNIVERSAL_TRANSITION_PROP } from '../model/Series.js'; | 
| import { createHashMap, each, map, filter, isArray, extend } from 'zrender/lib/core/util.js'; | 
| import { applyMorphAnimation, getPathList } from './morphTransitionHelper.js'; | 
| import Path from 'zrender/lib/graphic/Path.js'; | 
| import { initProps } from '../util/graphic.js'; | 
| import DataDiffer from '../data/DataDiffer.js'; | 
| import { makeInner, normalizeToArray } from '../util/model.js'; | 
| import { warn } from '../util/log.js'; | 
| import { getAnimationConfig, getOldStyle } from './basicTransition.js'; | 
| import Displayable from 'zrender/lib/graphic/Displayable.js'; | 
| var DATA_COUNT_THRESHOLD = 1e4; | 
| var TRANSITION_NONE = 0; | 
| var TRANSITION_P2C = 1; | 
| var TRANSITION_C2P = 2; | 
| ; | 
| var getUniversalTransitionGlobalStore = makeInner(); | 
| function getDimension(data, visualDimension) { | 
|   var dimensions = data.dimensions; | 
|   for (var i = 0; i < dimensions.length; i++) { | 
|     var dimInfo = data.getDimensionInfo(dimensions[i]); | 
|     if (dimInfo && dimInfo.otherDims[visualDimension] === 0) { | 
|       return dimensions[i]; | 
|     } | 
|   } | 
| } | 
| // get value by dimension. (only get value of itemGroupId or childGroupId, so convert it to string) | 
| function getValueByDimension(data, dataIndex, dimension) { | 
|   var dimInfo = data.getDimensionInfo(dimension); | 
|   var dimOrdinalMeta = dimInfo && dimInfo.ordinalMeta; | 
|   if (dimInfo) { | 
|     var value = data.get(dimInfo.name, dataIndex); | 
|     if (dimOrdinalMeta) { | 
|       return dimOrdinalMeta.categories[value] || value + ''; | 
|     } | 
|     return value + ''; | 
|   } | 
| } | 
| function getGroupId(data, dataIndex, dataGroupId, isChild) { | 
|   // try to get groupId from encode | 
|   var visualDimension = isChild ? 'itemChildGroupId' : 'itemGroupId'; | 
|   var groupIdDim = getDimension(data, visualDimension); | 
|   if (groupIdDim) { | 
|     var groupId = getValueByDimension(data, dataIndex, groupIdDim); | 
|     return groupId; | 
|   } | 
|   // try to get groupId from raw data item | 
|   var rawDataItem = data.getRawDataItem(dataIndex); | 
|   var property = isChild ? 'childGroupId' : 'groupId'; | 
|   if (rawDataItem && rawDataItem[property]) { | 
|     return rawDataItem[property] + ''; | 
|   } | 
|   // fallback | 
|   if (isChild) { | 
|     return; | 
|   } | 
|   // try to use series.dataGroupId as groupId, otherwise use dataItem's id as groupId | 
|   return dataGroupId || data.getId(dataIndex); | 
| } | 
| // flatten all data items from different serieses into one arrary | 
| function flattenDataDiffItems(list) { | 
|   var items = []; | 
|   each(list, function (seriesInfo) { | 
|     var data = seriesInfo.data; | 
|     var dataGroupId = seriesInfo.dataGroupId; | 
|     if (data.count() > DATA_COUNT_THRESHOLD) { | 
|       if (process.env.NODE_ENV !== 'production') { | 
|         warn('Universal transition is disabled on large data > 10k.'); | 
|       } | 
|       return; | 
|     } | 
|     var indices = data.getIndices(); | 
|     for (var dataIndex = 0; dataIndex < indices.length; dataIndex++) { | 
|       items.push({ | 
|         data: data, | 
|         groupId: getGroupId(data, dataIndex, dataGroupId, false), | 
|         childGroupId: getGroupId(data, dataIndex, dataGroupId, true), | 
|         divide: seriesInfo.divide, | 
|         dataIndex: dataIndex | 
|       }); | 
|     } | 
|   }); | 
|   return items; | 
| } | 
| function fadeInElement(newEl, newSeries, newIndex) { | 
|   newEl.traverse(function (el) { | 
|     if (el instanceof Path) { | 
|       // TODO use fade in animation for target element. | 
|       initProps(el, { | 
|         style: { | 
|           opacity: 0 | 
|         } | 
|       }, newSeries, { | 
|         dataIndex: newIndex, | 
|         isFrom: true | 
|       }); | 
|     } | 
|   }); | 
| } | 
| function removeEl(el) { | 
|   if (el.parent) { | 
|     // Bake parent transform to element. | 
|     // So it can still have proper transform to transition after it's removed. | 
|     var computedTransform = el.getComputedTransform(); | 
|     el.setLocalTransform(computedTransform); | 
|     el.parent.remove(el); | 
|   } | 
| } | 
| function stopAnimation(el) { | 
|   el.stopAnimation(); | 
|   if (el.isGroup) { | 
|     el.traverse(function (child) { | 
|       child.stopAnimation(); | 
|     }); | 
|   } | 
| } | 
| function animateElementStyles(el, dataIndex, seriesModel) { | 
|   var animationConfig = getAnimationConfig('update', seriesModel, dataIndex); | 
|   animationConfig && el.traverse(function (child) { | 
|     if (child instanceof Displayable) { | 
|       var oldStyle = getOldStyle(child); | 
|       if (oldStyle) { | 
|         child.animateFrom({ | 
|           style: oldStyle | 
|         }, animationConfig); | 
|       } | 
|     } | 
|   }); | 
| } | 
| function isAllIdSame(oldDiffItems, newDiffItems) { | 
|   var len = oldDiffItems.length; | 
|   if (len !== newDiffItems.length) { | 
|     return false; | 
|   } | 
|   for (var i = 0; i < len; i++) { | 
|     var oldItem = oldDiffItems[i]; | 
|     var newItem = newDiffItems[i]; | 
|     if (oldItem.data.getId(oldItem.dataIndex) !== newItem.data.getId(newItem.dataIndex)) { | 
|       return false; | 
|     } | 
|   } | 
|   return true; | 
| } | 
| function transitionBetween(oldList, newList, api) { | 
|   var oldDiffItems = flattenDataDiffItems(oldList); | 
|   var newDiffItems = flattenDataDiffItems(newList); | 
|   function updateMorphingPathProps(from, to, rawFrom, rawTo, animationCfg) { | 
|     if (rawFrom || from) { | 
|       to.animateFrom({ | 
|         style: rawFrom && rawFrom !== from | 
|         // dividingMethod like clone may override the style(opacity) | 
|         // So extend it to raw style. | 
|         ? extend(extend({}, rawFrom.style), from.style) : from.style | 
|       }, animationCfg); | 
|     } | 
|   } | 
|   var hasMorphAnimation = false; | 
|   /** | 
|    * With groupId and childGroupId, we can build parent-child relationships between dataItems. | 
|    * However, we should mind the parent-child "direction" between old and new options. | 
|    * | 
|    * For example, suppose we have two dataItems from two series.data: | 
|    * | 
|    * dataA: [                          dataB: [ | 
|    *   {                                 { | 
|    *     value: 5,                         value: 3, | 
|    *     groupId: 'creatures',             groupId: 'animals', | 
|    *     childGroupId: 'animals'           childGroupId: 'dogs' | 
|    *   },                                }, | 
|    *   ...                               ... | 
|    * ]                                 ] | 
|    * | 
|    * where dataA is belong to optionA and dataB is belong to optionB. | 
|    * | 
|    * When we `setOption(optionB)` from optionA, we choose childGroupId of dataItemA and groupId of | 
|    * dataItemB as keys so the two keys are matched (both are 'animals'), then universalTransition | 
|    * will work. This derection is "parent -> child". | 
|    * | 
|    * If we `setOption(optionA)` from optionB, we also choose groupId of dataItemB and childGroupId | 
|    * of dataItemA as keys and universalTransition will work. This derection is "child -> parent". | 
|    * | 
|    * If there is no childGroupId specified, which means no multiLevelDrillDown/Up is needed and no | 
|    * parent-child relationship exists. This direction is "none". | 
|    * | 
|    * So we need to know whether to use groupId or childGroupId as the key when we call the keyGetter | 
|    * functions. Thus, we need to decide the direction first. | 
|    * | 
|    * The rule is: | 
|    * | 
|    * if (all childGroupIds in oldDiffItems and all groupIds in newDiffItems have common value) { | 
|    *   direction = 'parent -> child'; | 
|    * } else if (all groupIds in oldDiffItems and all childGroupIds in newDiffItems have common value) { | 
|    *   direction = 'child -> parent'; | 
|    * } else { | 
|    *   direction = 'none'; | 
|    * } | 
|    */ | 
|   var direction = TRANSITION_NONE; | 
|   // find all groupIds and childGroupIds from oldDiffItems | 
|   var oldGroupIds = createHashMap(); | 
|   var oldChildGroupIds = createHashMap(); | 
|   oldDiffItems.forEach(function (item) { | 
|     item.groupId && oldGroupIds.set(item.groupId, true); | 
|     item.childGroupId && oldChildGroupIds.set(item.childGroupId, true); | 
|   }); | 
|   // traverse newDiffItems and decide the direction according to the rule | 
|   for (var i = 0; i < newDiffItems.length; i++) { | 
|     var newGroupId = newDiffItems[i].groupId; | 
|     if (oldChildGroupIds.get(newGroupId)) { | 
|       direction = TRANSITION_P2C; | 
|       break; | 
|     } | 
|     var newChildGroupId = newDiffItems[i].childGroupId; | 
|     if (newChildGroupId && oldGroupIds.get(newChildGroupId)) { | 
|       direction = TRANSITION_C2P; | 
|       break; | 
|     } | 
|   } | 
|   function createKeyGetter(isOld, onlyGetId) { | 
|     return function (diffItem) { | 
|       var data = diffItem.data; | 
|       var dataIndex = diffItem.dataIndex; | 
|       // TODO if specified dim | 
|       if (onlyGetId) { | 
|         return data.getId(dataIndex); | 
|       } | 
|       if (isOld) { | 
|         return direction === TRANSITION_P2C ? diffItem.childGroupId : diffItem.groupId; | 
|       } else { | 
|         return direction === TRANSITION_C2P ? diffItem.childGroupId : diffItem.groupId; | 
|       } | 
|     }; | 
|   } | 
|   // Use id if it's very likely to be an one to one animation | 
|   // It's more robust than groupId | 
|   // TODO Check if key dimension is specified. | 
|   var useId = isAllIdSame(oldDiffItems, newDiffItems); | 
|   var isElementStillInChart = {}; | 
|   if (!useId) { | 
|     // We may have different diff strategy with basicTransition if we use other dimension as key. | 
|     // 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. | 
|     // We can't use the elements that already being morphed. Let it keep it's original basic transition. | 
|     for (var i = 0; i < newDiffItems.length; i++) { | 
|       var newItem = newDiffItems[i]; | 
|       var el = newItem.data.getItemGraphicEl(newItem.dataIndex); | 
|       if (el) { | 
|         isElementStillInChart[el.id] = true; | 
|       } | 
|     } | 
|   } | 
|   function updateOneToOne(newIndex, oldIndex) { | 
|     var oldItem = oldDiffItems[oldIndex]; | 
|     var newItem = newDiffItems[newIndex]; | 
|     var newSeries = newItem.data.hostModel; | 
|     // TODO Mark this elements is morphed and don't morph them anymore | 
|     var oldEl = oldItem.data.getItemGraphicEl(oldItem.dataIndex); | 
|     var newEl = newItem.data.getItemGraphicEl(newItem.dataIndex); | 
|     // Can't handle same elements. | 
|     if (oldEl === newEl) { | 
|       newEl && animateElementStyles(newEl, newItem.dataIndex, newSeries); | 
|       return; | 
|     } | 
|     if ( | 
|     // We can't use the elements that already being morphed | 
|     oldEl && isElementStillInChart[oldEl.id]) { | 
|       return; | 
|     } | 
|     if (newEl) { | 
|       // TODO: If keep animating the group in case | 
|       // some of the elements don't want to be morphed. | 
|       // TODO Label? | 
|       stopAnimation(newEl); | 
|       if (oldEl) { | 
|         stopAnimation(oldEl); | 
|         // If old element is doing leaving animation. stop it and remove it immediately. | 
|         removeEl(oldEl); | 
|         hasMorphAnimation = true; | 
|         applyMorphAnimation(getPathList(oldEl), getPathList(newEl), newItem.divide, newSeries, newIndex, updateMorphingPathProps); | 
|       } else { | 
|         fadeInElement(newEl, newSeries, newIndex); | 
|       } | 
|     } | 
|     // else keep oldEl leaving animation. | 
|   } | 
|   new DataDiffer(oldDiffItems, newDiffItems, createKeyGetter(true, useId), createKeyGetter(false, useId), null, 'multiple').update(updateOneToOne).updateManyToOne(function (newIndex, oldIndices) { | 
|     var newItem = newDiffItems[newIndex]; | 
|     var newData = newItem.data; | 
|     var newSeries = newData.hostModel; | 
|     var newEl = newData.getItemGraphicEl(newItem.dataIndex); | 
|     var oldElsList = filter(map(oldIndices, function (idx) { | 
|       return oldDiffItems[idx].data.getItemGraphicEl(oldDiffItems[idx].dataIndex); | 
|     }), function (oldEl) { | 
|       return oldEl && oldEl !== newEl && !isElementStillInChart[oldEl.id]; | 
|     }); | 
|     if (newEl) { | 
|       stopAnimation(newEl); | 
|       if (oldElsList.length) { | 
|         // If old element is doing leaving animation. stop it and remove it immediately. | 
|         each(oldElsList, function (oldEl) { | 
|           stopAnimation(oldEl); | 
|           removeEl(oldEl); | 
|         }); | 
|         hasMorphAnimation = true; | 
|         applyMorphAnimation(getPathList(oldElsList), getPathList(newEl), newItem.divide, newSeries, newIndex, updateMorphingPathProps); | 
|       } else { | 
|         fadeInElement(newEl, newSeries, newItem.dataIndex); | 
|       } | 
|     } | 
|     // else keep oldEl leaving animation. | 
|   }).updateOneToMany(function (newIndices, oldIndex) { | 
|     var oldItem = oldDiffItems[oldIndex]; | 
|     var oldEl = oldItem.data.getItemGraphicEl(oldItem.dataIndex); | 
|     // We can't use the elements that already being morphed | 
|     if (oldEl && isElementStillInChart[oldEl.id]) { | 
|       return; | 
|     } | 
|     var newElsList = filter(map(newIndices, function (idx) { | 
|       return newDiffItems[idx].data.getItemGraphicEl(newDiffItems[idx].dataIndex); | 
|     }), function (el) { | 
|       return el && el !== oldEl; | 
|     }); | 
|     var newSeris = newDiffItems[newIndices[0]].data.hostModel; | 
|     if (newElsList.length) { | 
|       each(newElsList, function (newEl) { | 
|         return stopAnimation(newEl); | 
|       }); | 
|       if (oldEl) { | 
|         stopAnimation(oldEl); | 
|         // If old element is doing leaving animation. stop it and remove it immediately. | 
|         removeEl(oldEl); | 
|         hasMorphAnimation = true; | 
|         applyMorphAnimation(getPathList(oldEl), getPathList(newElsList), oldItem.divide, | 
|         // Use divide on old. | 
|         newSeris, newIndices[0], updateMorphingPathProps); | 
|       } else { | 
|         each(newElsList, function (newEl) { | 
|           return fadeInElement(newEl, newSeris, newIndices[0]); | 
|         }); | 
|       } | 
|     } | 
|     // else keep oldEl leaving animation. | 
|   }).updateManyToMany(function (newIndices, oldIndices) { | 
|     // If two data are same and both have groupId. | 
|     // Normally they should be diff by id. | 
|     new DataDiffer(oldIndices, newIndices, function (rawIdx) { | 
|       return oldDiffItems[rawIdx].data.getId(oldDiffItems[rawIdx].dataIndex); | 
|     }, function (rawIdx) { | 
|       return newDiffItems[rawIdx].data.getId(newDiffItems[rawIdx].dataIndex); | 
|     }).update(function (newIndex, oldIndex) { | 
|       // Use the original index | 
|       updateOneToOne(newIndices[newIndex], oldIndices[oldIndex]); | 
|     }).execute(); | 
|   }).execute(); | 
|   if (hasMorphAnimation) { | 
|     each(newList, function (_a) { | 
|       var data = _a.data; | 
|       var seriesModel = data.hostModel; | 
|       var view = seriesModel && api.getViewOfSeriesModel(seriesModel); | 
|       var animationCfg = getAnimationConfig('update', seriesModel, 0); // use 0 index. | 
|       if (view && seriesModel.isAnimationEnabled() && animationCfg && animationCfg.duration > 0) { | 
|         view.group.traverse(function (el) { | 
|           if (el instanceof Path && !el.animators.length) { | 
|             // We can't accept there still exists element that has no animation | 
|             // if universalTransition is enabled | 
|             el.animateFrom({ | 
|               style: { | 
|                 opacity: 0 | 
|               } | 
|             }, animationCfg); | 
|           } | 
|         }); | 
|       } | 
|     }); | 
|   } | 
| } | 
| function getSeriesTransitionKey(series) { | 
|   var seriesKey = series.getModel('universalTransition').get('seriesKey'); | 
|   if (!seriesKey) { | 
|     // Use series id by default. | 
|     return series.id; | 
|   } | 
|   return seriesKey; | 
| } | 
| function convertArraySeriesKeyToString(seriesKey) { | 
|   if (isArray(seriesKey)) { | 
|     // Order independent. | 
|     return seriesKey.sort().join(','); | 
|   } | 
|   return seriesKey; | 
| } | 
| function getDivideShapeFromData(data) { | 
|   if (data.hostModel) { | 
|     return data.hostModel.getModel('universalTransition').get('divideShape'); | 
|   } | 
| } | 
| function findTransitionSeriesBatches(globalStore, params) { | 
|   var updateBatches = createHashMap(); | 
|   var oldDataMap = createHashMap(); | 
|   // Map that only store key in array seriesKey. | 
|   // Which is used to query the old data when transition from one to multiple series. | 
|   var oldDataMapForSplit = createHashMap(); | 
|   each(globalStore.oldSeries, function (series, idx) { | 
|     var oldDataGroupId = globalStore.oldDataGroupIds[idx]; | 
|     var oldData = globalStore.oldData[idx]; | 
|     var transitionKey = getSeriesTransitionKey(series); | 
|     var transitionKeyStr = convertArraySeriesKeyToString(transitionKey); | 
|     oldDataMap.set(transitionKeyStr, { | 
|       dataGroupId: oldDataGroupId, | 
|       data: oldData | 
|     }); | 
|     if (isArray(transitionKey)) { | 
|       // Same key can't in different array seriesKey. | 
|       each(transitionKey, function (key) { | 
|         oldDataMapForSplit.set(key, { | 
|           key: transitionKeyStr, | 
|           dataGroupId: oldDataGroupId, | 
|           data: oldData | 
|         }); | 
|       }); | 
|     } | 
|   }); | 
|   function checkTransitionSeriesKeyDuplicated(transitionKeyStr) { | 
|     if (updateBatches.get(transitionKeyStr)) { | 
|       warn("Duplicated seriesKey in universalTransition " + transitionKeyStr); | 
|     } | 
|   } | 
|   each(params.updatedSeries, function (series) { | 
|     if (series.isUniversalTransitionEnabled() && series.isAnimationEnabled()) { | 
|       var newDataGroupId = series.get('dataGroupId'); | 
|       var newData = series.getData(); | 
|       var transitionKey = getSeriesTransitionKey(series); | 
|       var transitionKeyStr = convertArraySeriesKeyToString(transitionKey); | 
|       // Only transition between series with same id. | 
|       var oldData = oldDataMap.get(transitionKeyStr); | 
|       // string transition key is the best match. | 
|       if (oldData) { | 
|         if (process.env.NODE_ENV !== 'production') { | 
|           checkTransitionSeriesKeyDuplicated(transitionKeyStr); | 
|         } | 
|         // TODO check if data is same? | 
|         updateBatches.set(transitionKeyStr, { | 
|           oldSeries: [{ | 
|             dataGroupId: oldData.dataGroupId, | 
|             divide: getDivideShapeFromData(oldData.data), | 
|             data: oldData.data | 
|           }], | 
|           newSeries: [{ | 
|             dataGroupId: newDataGroupId, | 
|             divide: getDivideShapeFromData(newData), | 
|             data: newData | 
|           }] | 
|         }); | 
|       } else { | 
|         // Transition from multiple series. | 
|         // e.g. 'female', 'male' -> ['female', 'male'] | 
|         if (isArray(transitionKey)) { | 
|           if (process.env.NODE_ENV !== 'production') { | 
|             checkTransitionSeriesKeyDuplicated(transitionKeyStr); | 
|           } | 
|           var oldSeries_1 = []; | 
|           each(transitionKey, function (key) { | 
|             var oldData = oldDataMap.get(key); | 
|             if (oldData.data) { | 
|               oldSeries_1.push({ | 
|                 dataGroupId: oldData.dataGroupId, | 
|                 divide: getDivideShapeFromData(oldData.data), | 
|                 data: oldData.data | 
|               }); | 
|             } | 
|           }); | 
|           if (oldSeries_1.length) { | 
|             updateBatches.set(transitionKeyStr, { | 
|               oldSeries: oldSeries_1, | 
|               newSeries: [{ | 
|                 dataGroupId: newDataGroupId, | 
|                 data: newData, | 
|                 divide: getDivideShapeFromData(newData) | 
|               }] | 
|             }); | 
|           } | 
|         } else { | 
|           // Try transition to multiple series. | 
|           // e.g. ['female', 'male'] -> 'female', 'male' | 
|           var oldData_1 = oldDataMapForSplit.get(transitionKey); | 
|           if (oldData_1) { | 
|             var batch = updateBatches.get(oldData_1.key); | 
|             if (!batch) { | 
|               batch = { | 
|                 oldSeries: [{ | 
|                   dataGroupId: oldData_1.dataGroupId, | 
|                   data: oldData_1.data, | 
|                   divide: getDivideShapeFromData(oldData_1.data) | 
|                 }], | 
|                 newSeries: [] | 
|               }; | 
|               updateBatches.set(oldData_1.key, batch); | 
|             } | 
|             batch.newSeries.push({ | 
|               dataGroupId: newDataGroupId, | 
|               data: newData, | 
|               divide: getDivideShapeFromData(newData) | 
|             }); | 
|           } | 
|         } | 
|       } | 
|     } | 
|   }); | 
|   return updateBatches; | 
| } | 
| function querySeries(series, finder) { | 
|   for (var i = 0; i < series.length; i++) { | 
|     var found = finder.seriesIndex != null && finder.seriesIndex === series[i].seriesIndex || finder.seriesId != null && finder.seriesId === series[i].id; | 
|     if (found) { | 
|       return i; | 
|     } | 
|   } | 
| } | 
| function transitionSeriesFromOpt(transitionOpt, globalStore, params, api) { | 
|   var from = []; | 
|   var to = []; | 
|   each(normalizeToArray(transitionOpt.from), function (finder) { | 
|     var idx = querySeries(globalStore.oldSeries, finder); | 
|     if (idx >= 0) { | 
|       from.push({ | 
|         dataGroupId: globalStore.oldDataGroupIds[idx], | 
|         data: globalStore.oldData[idx], | 
|         // TODO can specify divideShape in transition. | 
|         divide: getDivideShapeFromData(globalStore.oldData[idx]), | 
|         groupIdDim: finder.dimension | 
|       }); | 
|     } | 
|   }); | 
|   each(normalizeToArray(transitionOpt.to), function (finder) { | 
|     var idx = querySeries(params.updatedSeries, finder); | 
|     if (idx >= 0) { | 
|       var data = params.updatedSeries[idx].getData(); | 
|       to.push({ | 
|         dataGroupId: globalStore.oldDataGroupIds[idx], | 
|         data: data, | 
|         divide: getDivideShapeFromData(data), | 
|         groupIdDim: finder.dimension | 
|       }); | 
|     } | 
|   }); | 
|   if (from.length > 0 && to.length > 0) { | 
|     transitionBetween(from, to, api); | 
|   } | 
| } | 
| export function installUniversalTransition(registers) { | 
|   registers.registerUpdateLifecycle('series:beforeupdate', function (ecMOdel, api, params) { | 
|     each(normalizeToArray(params.seriesTransition), function (transOpt) { | 
|       each(normalizeToArray(transOpt.to), function (finder) { | 
|         var series = params.updatedSeries; | 
|         for (var i = 0; i < series.length; i++) { | 
|           if (finder.seriesIndex != null && finder.seriesIndex === series[i].seriesIndex || finder.seriesId != null && finder.seriesId === series[i].id) { | 
|             series[i][SERIES_UNIVERSAL_TRANSITION_PROP] = true; | 
|           } | 
|         } | 
|       }); | 
|     }); | 
|   }); | 
|   registers.registerUpdateLifecycle('series:transition', function (ecModel, api, params) { | 
|     // TODO api provide an namespace that can save stuff per instance | 
|     var globalStore = getUniversalTransitionGlobalStore(api); | 
|     // TODO multiple to multiple series. | 
|     if (globalStore.oldSeries && params.updatedSeries && params.optionChanged) { | 
|       // TODO transitionOpt was used in an old implementation and can be removed now | 
|       // Use give transition config if its' give; | 
|       var transitionOpt = params.seriesTransition; | 
|       if (transitionOpt) { | 
|         each(normalizeToArray(transitionOpt), function (opt) { | 
|           transitionSeriesFromOpt(opt, globalStore, params, api); | 
|         }); | 
|       } else { | 
|         // Else guess from series based on transition series key. | 
|         var updateBatches_1 = findTransitionSeriesBatches(globalStore, params); | 
|         each(updateBatches_1.keys(), function (key) { | 
|           var batch = updateBatches_1.get(key); | 
|           transitionBetween(batch.oldSeries, batch.newSeries, api); | 
|         }); | 
|       } | 
|       // Reset | 
|       each(params.updatedSeries, function (series) { | 
|         // Reset; | 
|         if (series[SERIES_UNIVERSAL_TRANSITION_PROP]) { | 
|           series[SERIES_UNIVERSAL_TRANSITION_PROP] = false; | 
|         } | 
|       }); | 
|     } | 
|     // Save all series of current update. Not only the updated one. | 
|     var allSeries = ecModel.getSeries(); | 
|     var savedSeries = globalStore.oldSeries = []; | 
|     var savedDataGroupIds = globalStore.oldDataGroupIds = []; | 
|     var savedData = globalStore.oldData = []; | 
|     for (var i = 0; i < allSeries.length; i++) { | 
|       var data = allSeries[i].getData(); | 
|       // Only save the data that can have transition. | 
|       // Avoid large data costing too much extra memory | 
|       if (data.count() < DATA_COUNT_THRESHOLD) { | 
|         savedSeries.push(allSeries[i]); | 
|         savedDataGroupIds.push(allSeries[i].get('dataGroupId')); | 
|         savedData.push(data); | 
|       } | 
|     } | 
|   }); | 
| } |