Skip to content

Commit

Permalink
[feat] Layer animation
Browse files Browse the repository at this point in the history
Signed-off-by: Ihor Dykhta <[email protected]>
  • Loading branch information
ilyabo authored and igorDykhta committed Oct 22, 2024
1 parent 0507bd6 commit dfc4da4
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 28 deletions.
2 changes: 1 addition & 1 deletion src/actions/src/vis-state-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -924,7 +924,7 @@ export function updateFilterAnimationSpeed(
}

export type SetLayerAnimationTimeUpdaterAction = {
value: number;
value: number | null;
};
/**
* Reset animation
Expand Down
112 changes: 87 additions & 25 deletions src/reducers/src/vis-state-updaters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ import {
DEFAULT_TEXT_LABEL,
COMPARE_TYPES,
LIGHT_AND_SHADOW_EFFECT,
PLOT_TYPES
PLOT_TYPES,
BASE_SPEED,
FPS
} from '@kepler.gl/constants';
import {
pick_,
Expand Down Expand Up @@ -199,6 +201,7 @@ export const DEFAULT_ANIMATION_CONFIG: AnimationConfig = {
currentTime: null,
speed: 1,
isAnimating: false,
timeSteps: null,
timeFormat: null,
timezone: null,
defaultTimeFormat: null,
Expand Down Expand Up @@ -321,7 +324,7 @@ export function updateStateOnLayerVisibilityChange<S extends VisState>(state: S,
}

if (layer.config.animation.enabled) {
newState = updateAnimationDomain(state);
newState = updateAnimationDomain(newState);
}

return newState;
Expand Down Expand Up @@ -507,6 +510,22 @@ export function layerConfigChangeUpdater(
});
}

export function layerAnimationChangeUpdater<S extends VisState>(state: S, action): S {
const {oldLayer, prop, value} = action;
const idx = state.layers.findIndex(l => l.id === oldLayer.id);

const newLayer = oldLayer.updateLayerConfig({
animation: {
...oldLayer.config.animation,
[prop]: value
}
});

const {layerData, layer} = calculateLayerData(newLayer, state, state.layerData[idx]);

return updateStateWithLayerAndData(state, {layerData, layer, idx});
}

/**
* Updates isValid flag of a layer.
* Updates isVisible based on the value of isValid.
Expand Down Expand Up @@ -821,10 +840,13 @@ export function layerVisualChannelChangeUpdater(

newLayer.updateLayerVisualChannel(dataset, channel);

const oldLayerData = state.layerData[idx];
const {layerData, layer} = calculateLayerData(newLayer, state, oldLayerData);
// calling update animation domain first to merge all layer animation domain
const updatedState = updateAnimationDomain(state);

return updateStateWithLayerAndData(state, {layerData, layer, idx});
const oldLayerData = updatedState.layerData[idx];
const {layerData, layer} = calculateLayerData(newLayer, updatedState, oldLayerData);

return updateStateWithLayerAndData(updatedState, {layerData, layer, idx});
}

/**
Expand Down Expand Up @@ -1308,16 +1330,30 @@ export const updateFilterAnimationSpeedUpdater = (
* @public
*
*/
export const setLayerAnimationTimeUpdater = (
state: VisState,
export const setLayerAnimationTimeUpdater = <S extends VisState>(
state: S,
{value}: VisStateActions.SetLayerAnimationTimeUpdaterAction
): VisState => ({
...state,
animationConfig: {
...state.animationConfig,
currentTime: value
}
});
): S => {
const currentTime = Array.isArray(value) ? value[0] : value;
const nextState = {
...state,
animationConfig: {
...state.animationConfig,
currentTime
}
};
// update animation config for each layer
const result = state.layers.reduce(
(accu, l) =>
l.config.animation.enabled && l.type !== 'trip'
? layerAnimationChangeUpdater(accu, {oldLayer: l, prop: 'currentTime', currentTime})
: accu,

nextState
);

return result;
};

/**
* Update animation speed with the vertical speed slider
Expand Down Expand Up @@ -2646,8 +2682,7 @@ export function updateAnimationDomain<S extends VisState>(state: S): S {
l.config.isVisible &&
l.config.animation &&
l.config.animation.enabled &&
// @ts-expect-error trip-layer-only
Array.isArray(l.animationDomain)
Array.isArray(l.config.animation.domain)
);

if (!animatableLayers.length) {
Expand All @@ -2656,33 +2691,60 @@ export function updateAnimationDomain<S extends VisState>(state: S): S {
animationConfig: {
...state.animationConfig,
domain: null,
isAnimating: false,
timeSteps: null,
defaultTimeFormat: null
}
};
}

const mergedDomain: [number, number] = animatableLayers.reduce(
(accu, layer) => [
// @ts-expect-error trip-layer-only
Math.min(accu[0], layer.animationDomain[0]),
// @ts-expect-error trip-layer-only
Math.max(accu[1], layer.animationDomain[1])
Math.min(accu[0], layer.config.animation.domain?.[0] ?? Infinity),
Math.max(accu[1], layer.config.animation.domain?.[1] ?? -Infinity)
],
[Number(Infinity), -Infinity]
);
const defaultTimeFormat = getTimeWidgetTitleFormatter(mergedDomain);

return {
// merge timeSteps
let mergedTimeSteps: number[] | null = uniq(
animatableLayers.reduce((accu, layer) => {
// @ts-ignore
accu.push(...(layer.config.animation.timeSteps || []));
return accu;
}, [])
).sort();

mergedTimeSteps = mergedTimeSteps.length ? mergedTimeSteps : null;

// TODO: better handling of duration calculation
const duration = mergedTimeSteps
? (BASE_SPEED * (1000 / FPS)) / mergedTimeSteps.length / (state.animationConfig.speed || 1)
: null;

const nextState = {
...state,
animationConfig: {
...state.animationConfig,
currentTime: isInRange(state.animationConfig.currentTime, mergedDomain)
? state.animationConfig.currentTime
: mergedDomain[0],
domain: mergedDomain,
defaultTimeFormat
defaultTimeFormat,
duration,
timeSteps: mergedTimeSteps
}
};

// reset currentTime based on new domain
const currentTime = isInRange(state.animationConfig.currentTime, mergedDomain)
? state.animationConfig.currentTime
: mergedDomain[0];

if (currentTime !== state.animationConfig.currentTime) {
// if currentTime changed, need to call animationTimeUpdater to re call formatLayerData
return setLayerAnimationTimeUpdater(nextState, {value: currentTime});
}

return nextState;
}

/**
Expand Down
23 changes: 21 additions & 2 deletions test/node/reducers/vis-state-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,8 @@ test('#visStateReducer -> LAYER_TYPE_CHANGE.3 -> animationConfig', t => {
...DEFAULT_ANIMATION_CONFIG,
domain: timeStampDomain,
currentTime: timeStampDomain[0],
duration: null,
timeSteps: null,
defaultTimeFormat: 'L LTS'
},
'should update visState.animationConfig'
Expand Down Expand Up @@ -610,7 +612,13 @@ test('#visStateReducer -> LAYER_CONFIG_CHANGE -> isVisible -> animationConfig',
...DEFAULT_ANIMATION_CONFIG,
domain: null,
currentTime: 1565577261000,
isAnimating: false
speed: 1,
isAnimating: false,
duration: null,
timeSteps: null,
defaultTimeFormat: null,
timeFormat: null,
timezone: null
},
'should set animationConfig to default'
);
Expand All @@ -626,6 +634,8 @@ test('#visStateReducer -> LAYER_CONFIG_CHANGE -> isVisible -> animationConfig',
...nextState2.animationConfig,
domain: timeStampDomain,
currentTime: timeStampDomain[0],
duration: null,
timeSteps: null,
defaultTimeFormat: 'L LTS'
},
'should set animationConfig domain and currentTime'
Expand Down Expand Up @@ -3620,14 +3630,18 @@ test('#visStateReducer -> SPLIT_MAP: REMOVE_LAYER. set animation domain', t => {
splitMaps: [],
animationConfig: {
domain: [1568502710000, 1568503060000],
currentTime: 1568502970000
currentTime: 1568502970000,
duration: null,
timeSteps: null
}
};

const newReducer = reducer(oldState, VisStateActions.removeLayer('t1'));
const expectedAnimationConfig = {
domain: [1568502810000, 1568503060000],
currentTime: 1568502970000,
duration: null,
timeSteps: null,
defaultTimeFormat: 'L LTS'
};

Expand All @@ -3641,6 +3655,8 @@ test('#visStateReducer -> SPLIT_MAP: REMOVE_LAYER. set animation domain', t => {
const expectedAnimationConfig2 = {
domain: [1568502710000, 1568502960000],
currentTime: 1568502710000,
duration: null,
timeSteps: null,
defaultTimeFormat: 'L LTS'
};
t.deepEqual(
Expand All @@ -3655,6 +3671,9 @@ test('#visStateReducer -> SPLIT_MAP: REMOVE_LAYER. set animation domain', t => {
{
domain: null,
currentTime: 1568502710000,
isAnimating: false,
duration: null,
timeSteps: null,
defaultTimeFormat: null
},
'remove last animation layer and set animation config to default'
Expand Down

0 comments on commit dfc4da4

Please sign in to comment.