@@ -28,7 +28,14 @@ import { EChartsExtensionInstallRegisters } from '../extension';
2828import { initProps } from '../util/graphic' ;
2929import DataDiffer from '../data/DataDiffer' ;
3030import SeriesData from '../data/SeriesData' ;
31- import { Dictionary , DimensionLoose , OptionDataItemObject , UniversalTransitionOption } from '../util/types' ;
31+ import {
32+ Dictionary ,
33+ DimensionLoose ,
34+ DimensionName ,
35+ DataVisualDimensions ,
36+ OptionDataItemObject ,
37+ UniversalTransitionOption
38+ } from '../util/types' ;
3239import {
3340 UpdateLifecycleParams ,
3441 UpdateLifecycleTransitionItem ,
@@ -42,52 +49,91 @@ import Model from '../model/Model';
4249import Displayable from 'zrender/src/graphic/Displayable' ;
4350
4451const DATA_COUNT_THRESHOLD = 1e4 ;
52+ const TRANSITION_NONE = 0 ;
53+ const TRANSITION_P2C = 1 ;
54+ const TRANSITION_C2P = 2 ;
4555
4656interface GlobalStore { oldSeries : SeriesModel [ ] , oldDataGroupIds : string [ ] , oldData : SeriesData [ ] } ;
4757const getUniversalTransitionGlobalStore = makeInner < GlobalStore , ExtensionAPI > ( ) ;
4858
4959interface DiffItem {
50- dataGroupId : string
5160 data : SeriesData
52- dim : DimensionLoose
61+ groupId : string
62+ childGroupId : string
5363 divide : UniversalTransitionOption [ 'divideShape' ]
5464 dataIndex : number
5565}
5666interface TransitionSeries {
5767 dataGroupId : string
5868 data : SeriesData
5969 divide : UniversalTransitionOption [ 'divideShape' ]
60- dim ?: DimensionLoose
70+ groupIdDim ?: DimensionLoose
6171}
6272
63- function getGroupIdDimension ( data : SeriesData ) {
73+ function getDimension ( data : SeriesData , visualDimension : string ) {
6474 const dimensions = data . dimensions ;
6575 for ( let i = 0 ; i < dimensions . length ; i ++ ) {
6676 const dimInfo = data . getDimensionInfo ( dimensions [ i ] ) ;
67- if ( dimInfo && dimInfo . otherDims . itemGroupId === 0 ) {
77+ if ( dimInfo && dimInfo . otherDims [ visualDimension as keyof DataVisualDimensions ] === 0 ) {
6878 return dimensions [ i ] ;
6979 }
7080 }
7181}
7282
83+ // get value by dimension. (only get value of itemGroupId or childGroupId, so convert it to string)
84+ function getValueByDimension ( data : SeriesData , dataIndex : number , dimension : DimensionName ) {
85+ const dimInfo = data . getDimensionInfo ( dimension ) ;
86+ const dimOrdinalMeta = dimInfo && dimInfo . ordinalMeta ;
87+ if ( dimInfo ) {
88+ const value = data . get ( dimInfo . name , dataIndex ) ;
89+ if ( dimOrdinalMeta ) {
90+ return ( dimOrdinalMeta . categories [ value as number ] as string ) || value + '' ;
91+ }
92+ return value + '' ;
93+ }
94+ }
95+
96+ function getGroupId ( data : SeriesData , dataIndex : number , dataGroupId : string , isChild : boolean ) {
97+ // try to get groupId from encode
98+ const visualDimension = isChild ? 'itemChildGroupId' : 'itemGroupId' ;
99+ const groupIdDim = getDimension ( data , visualDimension ) ;
100+ if ( groupIdDim ) {
101+ const groupId = getValueByDimension ( data , dataIndex , groupIdDim ) ;
102+ return groupId ;
103+ }
104+ // try to get groupId from raw data item
105+ const rawDataItem = data . getRawDataItem ( dataIndex ) as OptionDataItemObject < unknown > ;
106+ const property = isChild ? 'childGroupId' : 'groupId' ;
107+ if ( rawDataItem && rawDataItem [ property ] ) {
108+ return rawDataItem [ property ] + '' ;
109+ }
110+ // fallback
111+ if ( isChild ) {
112+ return ;
113+ }
114+ // try to use series.dataGroupId as groupId, otherwise use dataItem's id as groupId
115+ return ( dataGroupId || data . getId ( dataIndex ) ) ;
116+ }
117+
118+ // flatten all data items from different serieses into one arrary
73119function flattenDataDiffItems ( list : TransitionSeries [ ] ) {
74120 const items : DiffItem [ ] = [ ] ;
75121
76122 each ( list , seriesInfo => {
77123 const data = seriesInfo . data ;
124+ const dataGroupId = seriesInfo . dataGroupId ;
78125 if ( data . count ( ) > DATA_COUNT_THRESHOLD ) {
79126 if ( __DEV__ ) {
80127 warn ( 'Universal transition is disabled on large data > 10k.' ) ;
81128 }
82129 return ;
83130 }
84131 const indices = data . getIndices ( ) ;
85- const groupDim = getGroupIdDimension ( data ) ;
86132 for ( let dataIndex = 0 ; dataIndex < indices . length ; dataIndex ++ ) {
87133 items . push ( {
88- dataGroupId : seriesInfo . dataGroupId ,
89134 data,
90- dim : seriesInfo . dim || groupDim ,
135+ groupId : getGroupId ( data , dataIndex , dataGroupId , false ) , // either of groupId or childGroupId will be used as diffItem's key,
136+ childGroupId : getGroupId ( data , dataIndex , dataGroupId , true ) , // depending on the transition direction (see below)
91137 divide : seriesInfo . divide ,
92138 dataIndex
93139 } ) ;
@@ -185,18 +231,71 @@ function transitionBetween(
185231 }
186232 }
187233
234+ let hasMorphAnimation = false ;
188235
189- function findKeyDim ( items : DiffItem [ ] ) {
190- for ( let i = 0 ; i < items . length ; i ++ ) {
191- if ( items [ i ] . dim ) {
192- return items [ i ] . dim ;
193- }
236+ /**
237+ * With groupId and childGroupId, we can build parent-child relationships between dataItems.
238+ * However, we should mind the parent-child "direction" between old and new options.
239+ *
240+ * For example, suppose we have two dataItems from two series.data:
241+ *
242+ * dataA: [ dataB: [
243+ * { {
244+ * value: 5, value: 3,
245+ * groupId: 'creatures', groupId: 'animals',
246+ * childGroupId: 'animals' childGroupId: 'dogs'
247+ * }, },
248+ * ... ...
249+ * ] ]
250+ *
251+ * where dataA is belong to optionA and dataB is belong to optionB.
252+ *
253+ * When we `setOption(optionB)` from optionA, we choose childGroupId of dataItemA and groupId of
254+ * dataItemB as keys so the two keys are matched (both are 'animals'), then universalTransition
255+ * will work. This derection is "parent -> child".
256+ *
257+ * If we `setOption(optionA)` from optionB, we also choose groupId of dataItemB and childGroupId
258+ * of dataItemA as keys and universalTransition will work. This derection is "child -> parent".
259+ *
260+ * If there is no childGroupId specified, which means no multiLevelDrillDown/Up is needed and no
261+ * parent-child relationship exists. This direction is "none".
262+ *
263+ * So we need to know whether to use groupId or childGroupId as the key when we call the keyGetter
264+ * functions. Thus, we need to decide the direction first.
265+ *
266+ * The rule is:
267+ *
268+ * if (all childGroupIds in oldDiffItems and all groupIds in newDiffItems have common value) {
269+ * direction = 'parent -> child';
270+ * } else if (all groupIds in oldDiffItems and all childGroupIds in newDiffItems have common value) {
271+ * direction = 'child -> parent';
272+ * } else {
273+ * direction = 'none';
274+ * }
275+ */
276+ let direction = TRANSITION_NONE ;
277+
278+ // find all groupIds and childGroupIds from oldDiffItems
279+ const oldGroupIds = createHashMap ( ) ;
280+ const oldChildGroupIds = createHashMap ( ) ;
281+ oldDiffItems . forEach ( ( item ) => {
282+ item . groupId && oldGroupIds . set ( item . groupId , true ) ;
283+ item . childGroupId && oldChildGroupIds . set ( item . childGroupId , true ) ;
284+
285+ } ) ;
286+ // traverse newDiffItems and decide the direction according to the rule
287+ for ( let i = 0 ; i < newDiffItems . length ; i ++ ) {
288+ const newGroupId = newDiffItems [ i ] . groupId ;
289+ if ( oldChildGroupIds . get ( newGroupId ) ) {
290+ direction = TRANSITION_P2C ;
291+ break ;
292+ }
293+ const newChildGroupId = newDiffItems [ i ] . childGroupId ;
294+ if ( newChildGroupId && oldGroupIds . get ( newChildGroupId ) ) {
295+ direction = TRANSITION_C2P ;
296+ break ;
194297 }
195298 }
196- const oldKeyDim = findKeyDim ( oldDiffItems ) ;
197- const newKeyDim = findKeyDim ( newDiffItems ) ;
198-
199- let hasMorphAnimation = false ;
200299
201300 function createKeyGetter ( isOld : boolean , onlyGetId : boolean ) {
202301 return function ( diffItem : DiffItem ) : string {
@@ -206,36 +305,12 @@ function transitionBetween(
206305 if ( onlyGetId ) {
207306 return data . getId ( dataIndex ) ;
208307 }
209-
210- // Use group id as transition key by default.
211- // So we can achieve multiple to multiple animation like drilldown / up naturally.
212- // If group id not exits. Use id instead. If so, only one to one transition will be applied.
213- const dataGroupId = diffItem . dataGroupId ;
214-
215- // If specified key dimension(itemGroupId by default). Use this same dimension from other data.
216- // PENDING: If only use key dimension of newData.
217- const keyDim = isOld
218- ? ( oldKeyDim || newKeyDim )
219- : ( newKeyDim || oldKeyDim ) ;
220-
221- const dimInfo = keyDim && data . getDimensionInfo ( keyDim ) ;
222- const dimOrdinalMeta = dimInfo && dimInfo . ordinalMeta ;
223-
224- if ( dimInfo ) {
225- // Get from encode.itemGroupId.
226- const key = data . get ( dimInfo . name , dataIndex ) ;
227- if ( dimOrdinalMeta ) {
228- return dimOrdinalMeta . categories [ key as number ] as string || ( key + '' ) ;
229- }
230- return key + '' ;
308+ if ( isOld ) {
309+ return direction === TRANSITION_P2C ? diffItem . childGroupId : diffItem . groupId ;
231310 }
232-
233- // Get groupId from raw item. { groupId: '' }
234- const itemVal = data . getRawDataItem ( dataIndex ) as OptionDataItemObject < unknown > ;
235- if ( itemVal && itemVal . groupId ) {
236- return itemVal . groupId + '' ;
311+ else {
312+ return direction === TRANSITION_C2P ? diffItem . childGroupId : diffItem . groupId ;
237313 }
238- return ( dataGroupId || data . getId ( dataIndex ) ) ;
239314 } ;
240315 }
241316
@@ -541,6 +616,7 @@ function findTransitionSeriesBatches(
541616 }
542617 else {
543618 // Transition from multiple series.
619+ // e.g. 'female', 'male' -> ['female', 'male']
544620 if ( isArray ( transitionKey ) ) {
545621 if ( __DEV__ ) {
546622 checkTransitionSeriesKeyDuplicated ( transitionKeyStr ) ;
@@ -569,6 +645,7 @@ function findTransitionSeriesBatches(
569645 }
570646 else {
571647 // Try transition to multiple series.
648+ // e.g. ['female', 'male'] -> 'female', 'male'
572649 const oldData = oldDataMapForSplit . get ( transitionKey ) ;
573650 if ( oldData ) {
574651 let batch = updateBatches . get ( oldData . key ) ;
@@ -623,7 +700,7 @@ function transitionSeriesFromOpt(
623700 data : globalStore . oldData [ idx ] ,
624701 // TODO can specify divideShape in transition.
625702 divide : getDivideShapeFromData ( globalStore . oldData [ idx ] ) ,
626- dim : finder . dimension
703+ groupIdDim : finder . dimension
627704 } ) ;
628705 }
629706 } ) ;
@@ -635,7 +712,7 @@ function transitionSeriesFromOpt(
635712 dataGroupId : globalStore . oldDataGroupIds [ idx ] ,
636713 data,
637714 divide : getDivideShapeFromData ( data ) ,
638- dim : finder . dimension
715+ groupIdDim : finder . dimension
639716 } ) ;
640717 }
641718 } ) ;
@@ -665,6 +742,7 @@ export function installUniversalTransition(registers: EChartsExtensionInstallReg
665742
666743 // TODO multiple to multiple series.
667744 if ( globalStore . oldSeries && params . updatedSeries && params . optionChanged ) {
745+ // TODO transitionOpt was used in an old implementation and can be removed now
668746 // Use give transition config if its' give;
669747 const transitionOpt = params . seriesTransition ;
670748 if ( transitionOpt ) {
0 commit comments