feat(Segmentation): Segmentation highlight animation function selection (#5401)
This commit is contained in:
parent
de201d567f
commit
69dbe27784
@ -31,7 +31,9 @@
|
|||||||
"dev:cornerstone": "yarn run dev",
|
"dev:cornerstone": "yarn run dev",
|
||||||
"build": "cross-env NODE_ENV=production webpack --progress --config .webpack/webpack.prod.js",
|
"build": "cross-env NODE_ENV=production webpack --progress --config .webpack/webpack.prod.js",
|
||||||
"build:package-1": "yarn run build",
|
"build:package-1": "yarn run build",
|
||||||
"start": "yarn run dev"
|
"start": "yarn run dev",
|
||||||
|
"test:unit": "jest --watchAll",
|
||||||
|
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@cornerstonejs/codec-charls": "^1.2.3",
|
"@cornerstonejs/codec-charls": "^1.2.3",
|
||||||
|
|||||||
@ -42,6 +42,7 @@ import { getUpdatedViewportsForSegmentation } from './utils/hydrationUtils';
|
|||||||
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
|
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
|
||||||
import { isMeasurementWithinViewport } from './utils/isMeasurementWithinViewport';
|
import { isMeasurementWithinViewport } from './utils/isMeasurementWithinViewport';
|
||||||
import { getCenterExtent } from './utils/getCenterExtent';
|
import { getCenterExtent } from './utils/getCenterExtent';
|
||||||
|
import { EasingFunctionEnum } from './utils/transitions';
|
||||||
|
|
||||||
const { DefaultHistoryMemo } = csUtils.HistoryMemo;
|
const { DefaultHistoryMemo } = csUtils.HistoryMemo;
|
||||||
const toggleSyncFunctions = {
|
const toggleSyncFunctions = {
|
||||||
@ -1518,7 +1519,33 @@ function commandsModule({
|
|||||||
segmentationId
|
segmentationId
|
||||||
);
|
);
|
||||||
segmentationService.setActiveSegment(segmentationId, segmentIndex);
|
segmentationService.setActiveSegment(segmentationId, segmentIndex);
|
||||||
segmentationService.jumpToSegmentCenter(segmentationId, segmentIndex);
|
|
||||||
|
const { highlightAlpha, highlightSegment, animationLength, animationFunctionType } =
|
||||||
|
(customizationService.getCustomization(
|
||||||
|
'panelSegmentation.jumpToSegmentHighlightAnimationConfig'
|
||||||
|
) as Object as {
|
||||||
|
highlightAlpha?: number;
|
||||||
|
highlightSegment?: boolean;
|
||||||
|
animationLength?: number;
|
||||||
|
animationFunctionType?: EasingFunctionEnum;
|
||||||
|
}) ?? {};
|
||||||
|
|
||||||
|
const validAnimationFunctionType = Object.values(EasingFunctionEnum).includes(
|
||||||
|
animationFunctionType
|
||||||
|
)
|
||||||
|
? animationFunctionType
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
segmentationService.jumpToSegmentCenter(
|
||||||
|
segmentationId,
|
||||||
|
segmentIndex,
|
||||||
|
undefined,
|
||||||
|
highlightAlpha,
|
||||||
|
highlightSegment,
|
||||||
|
animationLength,
|
||||||
|
undefined,
|
||||||
|
validAnimationFunctionType
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import {
|
|||||||
} from '@cornerstonejs/tools';
|
} from '@cornerstonejs/tools';
|
||||||
import { PubSubService, Types as OHIFTypes } from '@ohif/core';
|
import { PubSubService, Types as OHIFTypes } from '@ohif/core';
|
||||||
import i18n from '@ohif/i18n';
|
import i18n from '@ohif/i18n';
|
||||||
import { easeInOutBell, easeInOutBellRelative } from '../../utils/transitions';
|
import { EasingFunctionEnum, EasingFunctionMap } from '../../utils/transitions';
|
||||||
import { mapROIContoursToRTStructData } from './RTSTRUCT/mapROIContoursToRTStructData';
|
import { mapROIContoursToRTStructData } from './RTSTRUCT/mapROIContoursToRTStructData';
|
||||||
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
|
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
|
||||||
import { addColorLUT } from '@cornerstonejs/tools/segmentation/addColorLUT';
|
import { addColorLUT } from '@cornerstonejs/tools/segmentation/addColorLUT';
|
||||||
@ -1178,7 +1178,7 @@ class SegmentationService extends PubSubService {
|
|||||||
highlightSegment = true,
|
highlightSegment = true,
|
||||||
animationLength = 750,
|
animationLength = 750,
|
||||||
highlightHideOthers = false,
|
highlightHideOthers = false,
|
||||||
highlightFunctionType = 'ease-in-out' // todo: make animation functions configurable from outside
|
animationFunctionType: EasingFunctionEnum = EasingFunctionEnum.EASE_IN_OUT
|
||||||
): void {
|
): void {
|
||||||
const center = this._getSegmentCenter(segmentationId, segmentIndex);
|
const center = this._getSegmentCenter(segmentationId, segmentIndex);
|
||||||
if (!center) {
|
if (!center) {
|
||||||
@ -1204,7 +1204,8 @@ class SegmentationService extends PubSubService {
|
|||||||
viewportId,
|
viewportId,
|
||||||
highlightAlpha,
|
highlightAlpha,
|
||||||
animationLength,
|
animationLength,
|
||||||
highlightHideOthers
|
highlightHideOthers,
|
||||||
|
animationFunctionType
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1216,7 +1217,7 @@ class SegmentationService extends PubSubService {
|
|||||||
alpha = 0.9,
|
alpha = 0.9,
|
||||||
animationLength = 750,
|
animationLength = 750,
|
||||||
hideOthers = true,
|
hideOthers = true,
|
||||||
highlightFunctionType = 'ease-in-out'
|
animationFunctionType: EasingFunctionEnum = EasingFunctionEnum.EASE_IN_OUT
|
||||||
): void {
|
): void {
|
||||||
if (this.highlightIntervalId) {
|
if (this.highlightIntervalId) {
|
||||||
clearInterval(this.highlightIntervalId);
|
clearInterval(this.highlightIntervalId);
|
||||||
@ -1249,7 +1250,8 @@ class SegmentationService extends PubSubService {
|
|||||||
segments,
|
segments,
|
||||||
viewportId,
|
viewportId,
|
||||||
animationLength,
|
animationLength,
|
||||||
representation
|
representation,
|
||||||
|
animationFunctionType
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1614,7 +1616,8 @@ class SegmentationService extends PubSubService {
|
|||||||
segments: Segment[],
|
segments: Segment[],
|
||||||
viewportId: string,
|
viewportId: string,
|
||||||
animationLength: number,
|
animationLength: number,
|
||||||
representation: cstTypes.SegmentationRepresentation
|
representation: cstTypes.SegmentationRepresentation,
|
||||||
|
animationFunctionType: EasingFunctionEnum
|
||||||
) {
|
) {
|
||||||
const { segmentationId } = representation;
|
const { segmentationId } = representation;
|
||||||
const newSegmentSpecificConfig = {
|
const newSegmentSpecificConfig = {
|
||||||
@ -1648,6 +1651,8 @@ class SegmentationService extends PubSubService {
|
|||||||
const elapsed = timestamp - startTime;
|
const elapsed = timestamp - startTime;
|
||||||
const progress = Math.min(elapsed / animationLength, 1);
|
const progress = Math.min(elapsed / animationLength, 1);
|
||||||
|
|
||||||
|
const easingFunction = EasingFunctionMap.get(animationFunctionType);
|
||||||
|
|
||||||
cstSegmentation.config.style.setStyle(
|
cstSegmentation.config.style.setStyle(
|
||||||
{
|
{
|
||||||
segmentationId,
|
segmentationId,
|
||||||
@ -1655,7 +1660,7 @@ class SegmentationService extends PubSubService {
|
|||||||
type: LABELMAP,
|
type: LABELMAP,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fillAlpha: easeInOutBell(progress, fillAlpha),
|
fillAlpha: easingFunction(progress, fillAlpha),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1683,7 +1688,8 @@ class SegmentationService extends PubSubService {
|
|||||||
segments: Segment[],
|
segments: Segment[],
|
||||||
viewportId: string,
|
viewportId: string,
|
||||||
animationLength: number,
|
animationLength: number,
|
||||||
representation: cstTypes.SegmentationRepresentation
|
representation: cstTypes.SegmentationRepresentation,
|
||||||
|
animationFunctionType: EasingFunctionEnum
|
||||||
) {
|
) {
|
||||||
const { segmentationId } = representation;
|
const { segmentationId } = representation;
|
||||||
const startTime = performance.now();
|
const startTime = performance.now();
|
||||||
@ -1693,8 +1699,6 @@ class SegmentationService extends PubSubService {
|
|||||||
}) as ContourStyle;
|
}) as ContourStyle;
|
||||||
|
|
||||||
const prevOutlineWidth = prevStyle.outlineWidth;
|
const prevOutlineWidth = prevStyle.outlineWidth;
|
||||||
// make this configurable
|
|
||||||
const baseline = Math.max(prevOutlineWidth * 3.5, 5);
|
|
||||||
|
|
||||||
const animate = (currentTime: number) => {
|
const animate = (currentTime: number) => {
|
||||||
const progress = (currentTime - startTime) / animationLength;
|
const progress = (currentTime - startTime) / animationLength;
|
||||||
@ -1703,7 +1707,8 @@ class SegmentationService extends PubSubService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const reversedProgress = easeInOutBellRelative(progress, baseline, prevOutlineWidth);
|
const OUTLINE_ANIMATION_SCALE_FACTOR = 5;
|
||||||
|
const easingFunction = EasingFunctionMap.get(animationFunctionType);
|
||||||
|
|
||||||
cstSegmentation.config.style.setStyle(
|
cstSegmentation.config.style.setStyle(
|
||||||
{
|
{
|
||||||
@ -1712,7 +1717,7 @@ class SegmentationService extends PubSubService {
|
|||||||
type: CONTOUR,
|
type: CONTOUR,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
outlineWidth: reversedProgress,
|
outlineWidth: easingFunction(progress, prevOutlineWidth, OUTLINE_ANIMATION_SCALE_FACTOR),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
367
extensions/cornerstone/src/utils/transitions.test.ts
Normal file
367
extensions/cornerstone/src/utils/transitions.test.ts
Normal file
@ -0,0 +1,367 @@
|
|||||||
|
import { linear, ease, easeIn, easeOut, easeInOut, cubicBezier } from './transitions';
|
||||||
|
|
||||||
|
describe('Transitions Module', () => {
|
||||||
|
const EPSILON = 1e-6;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to check if two numbers are approximately equal
|
||||||
|
*/
|
||||||
|
const approxEqual = (actual: number, expected: number, tolerance = EPSILON): boolean => {
|
||||||
|
return Math.abs(actual - expected) < tolerance;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Standard CSS Easing (Mode 1: no baseline)', () => {
|
||||||
|
describe('linear', () => {
|
||||||
|
it('should return 0 at timeProgress=0', () => {
|
||||||
|
expect(linear(0)).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 1 at timeProgress=1', () => {
|
||||||
|
expect(linear(1)).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return timeProgress for linear progression', () => {
|
||||||
|
expect(linear(0.25)).toBe(0.25);
|
||||||
|
expect(linear(0.5)).toBe(0.5);
|
||||||
|
expect(linear(0.75)).toBe(0.75);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('easeInOut', () => {
|
||||||
|
it('should return 0 at timeProgress=0', () => {
|
||||||
|
expect(easeInOut(0)).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 1 at timeProgress=1', () => {
|
||||||
|
expect(easeInOut(1)).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 0.5 at timeProgress=0.5', () => {
|
||||||
|
const result = easeInOut(0.5);
|
||||||
|
expect(approxEqual(result, 0.5)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be slower at start than linear', () => {
|
||||||
|
const easedValue = easeInOut(0.2);
|
||||||
|
expect(easedValue).toBeLessThan(0.2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should provide smoothing toward the end', () => {
|
||||||
|
const baseTime = 0.9;
|
||||||
|
const controlTimeProgressionDifference = 0.2;
|
||||||
|
|
||||||
|
const result1 = easeInOut(baseTime);
|
||||||
|
const result2 = easeInOut(baseTime - controlTimeProgressionDifference);
|
||||||
|
|
||||||
|
expect(result1 - result2).toBeLessThan(controlTimeProgressionDifference);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('easeIn', () => {
|
||||||
|
it('should return 0 at timeProgress=0', () => {
|
||||||
|
expect(easeIn(0)).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 1 at timeProgress=1', () => {
|
||||||
|
expect(easeIn(1)).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be slower at start', () => {
|
||||||
|
const easedValue = easeIn(0.4);
|
||||||
|
expect(easedValue).toBeLessThan(0.4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should accelerate toward the end', () => {
|
||||||
|
const baseTime = 0.9;
|
||||||
|
const controlTimeProgressionDifference = 0.2;
|
||||||
|
|
||||||
|
const result1 = easeIn(baseTime);
|
||||||
|
const result2 = easeIn(baseTime - controlTimeProgressionDifference);
|
||||||
|
|
||||||
|
expect(result1 - result2).toBeGreaterThan(controlTimeProgressionDifference);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('easeOut', () => {
|
||||||
|
it('should return 0 at timeProgress=0', () => {
|
||||||
|
expect(easeOut(0)).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 1 at timeProgress=1', () => {
|
||||||
|
expect(easeOut(1)).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be faster at start', () => {
|
||||||
|
const easedValue = easeOut(0.4);
|
||||||
|
expect(easedValue).toBeGreaterThan(0.4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should provide smoothing toward the end', () => {
|
||||||
|
const baseTime = 0.9;
|
||||||
|
const controlTimeProgressionDifference = 0.2;
|
||||||
|
|
||||||
|
const result1 = ease(baseTime);
|
||||||
|
const result2 = ease(baseTime - controlTimeProgressionDifference);
|
||||||
|
|
||||||
|
expect(result1 - result2).toBeLessThan(controlTimeProgressionDifference);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ease', () => {
|
||||||
|
it('should return 0 at timeProgress=0', () => {
|
||||||
|
expect(ease(0)).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 1 at timeProgress=1', () => {
|
||||||
|
expect(ease(1)).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should provide significant growth at first half', () => {
|
||||||
|
const result = ease(0.5);
|
||||||
|
expect(result).toBeGreaterThan(0.75);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should provide smoothing toward the end', () => {
|
||||||
|
const baseTime = 0.9;
|
||||||
|
const controlTimeProgressionDifference = 0.2;
|
||||||
|
|
||||||
|
const result1 = ease(baseTime);
|
||||||
|
const result2 = ease(baseTime - controlTimeProgressionDifference);
|
||||||
|
|
||||||
|
expect(result1 - result2).toBeLessThan(controlTimeProgressionDifference);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Baseline Bell-Curve (Mode 2: baseline only)', () => {
|
||||||
|
const baseline = 0.2;
|
||||||
|
|
||||||
|
describe('linear with baseline', () => {
|
||||||
|
it('should start and end at baseline', () => {
|
||||||
|
expect(linear(0, baseline)).toBe(baseline);
|
||||||
|
expect(linear(1, baseline)).toBe(baseline);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should peak at 1.0 at timeProgress=0.5', () => {
|
||||||
|
const result = linear(0.5, baseline);
|
||||||
|
expect(approxEqual(result, 1.0)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create symmetric bell curve', () => {
|
||||||
|
const quarterValue = linear(0.25, baseline);
|
||||||
|
const threeQuarterValue = linear(0.75, baseline);
|
||||||
|
expect(approxEqual(quarterValue, threeQuarterValue)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be between baseline and 1.0', () => {
|
||||||
|
for (let t = 0; t <= 1; t += 0.1) {
|
||||||
|
const result = linear(t, baseline);
|
||||||
|
expect(result).toBeGreaterThanOrEqual(baseline);
|
||||||
|
expect(result).toBeLessThanOrEqual(1.0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('easeInOut with baseline', () => {
|
||||||
|
it('should start and end at baseline', () => {
|
||||||
|
expect(easeInOut(0, baseline)).toBe(baseline);
|
||||||
|
expect(easeInOut(1, baseline)).toBe(baseline);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should peak near 1.0 around timeProgress=0.5', () => {
|
||||||
|
const result = easeInOut(0.5, baseline);
|
||||||
|
expect(result).toBeGreaterThan(0.8);
|
||||||
|
expect(result).toBeLessThanOrEqual(1.0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create bell curve with easing', () => {
|
||||||
|
const quarterValue = easeInOut(0.25, baseline);
|
||||||
|
const threeQuarterValue = easeInOut(0.75, baseline);
|
||||||
|
expect(approxEqual(quarterValue, threeQuarterValue, 0.01)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('all functions with baseline', () => {
|
||||||
|
const testBaseline = 0.3;
|
||||||
|
|
||||||
|
it('should all start and end at baseline', () => {
|
||||||
|
const functions = [linear, ease, easeIn, easeOut, easeInOut];
|
||||||
|
|
||||||
|
functions.forEach(fn => {
|
||||||
|
expect(fn(0, testBaseline)).toBe(testBaseline);
|
||||||
|
expect(fn(1, testBaseline)).toBe(testBaseline);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Scaled Bell-Curve (Mode 3: baseline + scale)', () => {
|
||||||
|
const baseline = 2.0;
|
||||||
|
const scale = 3.0;
|
||||||
|
const expectedPeak = baseline * scale;
|
||||||
|
|
||||||
|
describe('linear with baseline and scale', () => {
|
||||||
|
it('should start and end at baseline', () => {
|
||||||
|
expect(linear(0, baseline, scale)).toBe(baseline);
|
||||||
|
expect(linear(1, baseline, scale)).toBe(baseline);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should peak at baseline*scale at timeProgress=0.5', () => {
|
||||||
|
const result = linear(0.5, baseline, scale);
|
||||||
|
expect(approxEqual(result, expectedPeak)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create symmetric scaled bell curve', () => {
|
||||||
|
const quarterValue = linear(0.25, baseline, scale);
|
||||||
|
const threeQuarterValue = linear(0.75, baseline, scale);
|
||||||
|
expect(approxEqual(quarterValue, threeQuarterValue)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('easeInOut with baseline and scale', () => {
|
||||||
|
it('should start and end at baseline', () => {
|
||||||
|
expect(easeInOut(0, baseline, scale)).toBe(baseline);
|
||||||
|
expect(easeInOut(1, baseline, scale)).toBe(baseline);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should peak near baseline*scale around timeProgress=0.5', () => {
|
||||||
|
const result = easeInOut(0.5, baseline, scale);
|
||||||
|
expect(result).toBeGreaterThan(baseline + (expectedPeak - baseline) * 0.8);
|
||||||
|
expect(result).toBeLessThanOrEqual(expectedPeak);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('different scale values', () => {
|
||||||
|
it('should handle scale < 1 (shrinking)', () => {
|
||||||
|
const shrinkScale = 0.5;
|
||||||
|
const result = linear(0.5, 1.0, shrinkScale);
|
||||||
|
expect(approxEqual(result, 0.5)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle scale > 1 (growing)', () => {
|
||||||
|
const growScale = 2.5;
|
||||||
|
const result = linear(0.5, 1.0, growScale);
|
||||||
|
expect(approxEqual(result, 2.5)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle scale = 1 (baseline to baseline)', () => {
|
||||||
|
const result = linear(0.5, 0.5, 1);
|
||||||
|
expect(approxEqual(result, 0.5)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Edge Cases', () => {
|
||||||
|
describe('baseline = 0 behavior', () => {
|
||||||
|
it('should behave like standard easing when baseline=0', () => {
|
||||||
|
expect(linear(0.5, 0)).toBe(linear(0.5));
|
||||||
|
expect(easeInOut(0.5, 0)).toBe(easeInOut(0.5));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('extreme values', () => {
|
||||||
|
it('should handle negative baseline', () => {
|
||||||
|
const result = linear(0.5, -1.0, 2.0);
|
||||||
|
expect(approxEqual(result, -2.0)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle large baseline values', () => {
|
||||||
|
const result = linear(0.5, 100, 1.5);
|
||||||
|
expect(approxEqual(result, 150)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('timeProgress edge cases', () => {
|
||||||
|
it('should handle timeProgress < 0', () => {
|
||||||
|
expect(linear(-0.1)).toBe(0);
|
||||||
|
expect(easeInOut(-0.1, 0.2)).toBe(0.2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle timeProgress > 1', () => {
|
||||||
|
expect(linear(1.1)).toBe(1);
|
||||||
|
expect(easeInOut(1.1, 0.2)).toBe(0.2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Mathematical Properties', () => {
|
||||||
|
describe('bell curve symmetry for symmetric cubic bezier functions', () => {
|
||||||
|
it('should be symmetric around timeProgress=0.5', () => {
|
||||||
|
const baseline = 0.1;
|
||||||
|
const scale = 2.0;
|
||||||
|
|
||||||
|
[linear, easeInOut].forEach(fn => {
|
||||||
|
for (let offset = 0.1; offset <= 0.4; offset += 0.1) {
|
||||||
|
const leftValue = fn(0.5 - offset, baseline, scale);
|
||||||
|
const rightValue = fn(0.5 + offset, baseline, scale);
|
||||||
|
expect(approxEqual(leftValue, rightValue, 0.01)).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('monotonicity in first half', () => {
|
||||||
|
it('should be non-decreasing from 0 to 0.5 for functions that grow under linear in first half', () => {
|
||||||
|
const baseline = 0.2;
|
||||||
|
|
||||||
|
[linear, easeIn, easeInOut].forEach(fn => {
|
||||||
|
let prevValue = fn(0, baseline);
|
||||||
|
for (let time = 0.1; time <= 0.5; time += 0.1) {
|
||||||
|
const currentValue = fn(time, baseline);
|
||||||
|
expect(currentValue).toBeGreaterThanOrEqual(prevValue - EPSILON);
|
||||||
|
prevValue = currentValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Utility Functions', () => {
|
||||||
|
describe('cubicBezier', () => {
|
||||||
|
it('should create functions that return 0 at t=0 and 1 at t=1', () => {
|
||||||
|
const customEasing = cubicBezier(0.25, 0.1, 0.75, 0.9);
|
||||||
|
expect(customEasing(0)).toBe(0);
|
||||||
|
expect(customEasing(1)).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error for invalid x values', () => {
|
||||||
|
expect(() => cubicBezier(-0.1, 0, 1, 1)).toThrow();
|
||||||
|
expect(() => cubicBezier(0, 0, 1.1, 1)).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fallback to binary search when Newton-Raphson fails due to small derivative', () => {
|
||||||
|
/**
|
||||||
|
* Create a cubic-bezier curve that has a very flat section (small derivative)
|
||||||
|
* This will cause Newton-Raphson to fail and use binary search fallback
|
||||||
|
* Using control points that create a nearly horizontal tangent
|
||||||
|
* Obs: Values obtained by imperative testing
|
||||||
|
*/
|
||||||
|
const problematicEasing = cubicBezier(0.98888, 0.00001, 0.00001, 0.98888);
|
||||||
|
|
||||||
|
/** Test a value in the problematic region where derivative is very small*/
|
||||||
|
const result = problematicEasing(0.5);
|
||||||
|
|
||||||
|
expect(typeof result).toBe('number');
|
||||||
|
expect(isNaN(result)).toBe(false);
|
||||||
|
expect(result).toBeGreaterThanOrEqual(0);
|
||||||
|
expect(result).toBeLessThanOrEqual(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Performance Validation', () => {
|
||||||
|
it('should handle many rapid calls without issues', () => {
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
|
for (let i = 0; i < 1000; i++) {
|
||||||
|
const t = i / 1000;
|
||||||
|
linear(t);
|
||||||
|
easeInOut(t, 0.1);
|
||||||
|
ease(t, 0.2, 1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
const endTime = performance.now();
|
||||||
|
expect(endTime - startTime).toBeLessThan(10);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,63 +1,300 @@
|
|||||||
/**
|
/** Cubic Bezier Implementation */
|
||||||
* It is a bell curved function that uses ease in out quadratic for css
|
|
||||||
* transition timing function for each side of the curve.
|
|
||||||
*
|
|
||||||
* @param {number} x - The current time, in the range [0, 1].
|
|
||||||
* @param {number} baseline - The baseline value to start from and return to.
|
|
||||||
* @returns the value of the transition at time x.
|
|
||||||
*/
|
|
||||||
export function easeInOutBell(x: number, baseline: number): number {
|
|
||||||
const alpha = 1 - baseline;
|
|
||||||
|
|
||||||
// prettier-ignore
|
/**
|
||||||
if (x < 1 / 4) {
|
* Finds the parameter t for a given x value on a cubic Bézier curve.
|
||||||
return 4 * Math.pow(2 * x, 3) * alpha + baseline;
|
* Uses Newton's method first (fast), then falls back to binary search (reliable).
|
||||||
} else if (x < 1 / 2) {
|
* Based on WebKit/Chromium's UnitBezier implementation.
|
||||||
return (1 - Math.pow(-4 * x + 2, 3) / 2) * alpha + baseline;
|
*
|
||||||
} else if (x < 3 / 4) {
|
* @param {number} targetX - The x value to find parameter t for
|
||||||
return (1 - Math.pow(4 * x - 2, 3) / 2) * alpha + baseline;
|
* @param {number} x1 - x-coordinate of first control point
|
||||||
} else {
|
* @param {number} x2 - x-coordinate of second control point
|
||||||
return (- 4 * Math.pow(2 * x - 2, 3)) * alpha + baseline;
|
* @returns {number} The parameter t that gives the specified x value
|
||||||
|
*/
|
||||||
|
function solveCubicBezierX(targetX: number, x1: number, x2: number): number {
|
||||||
|
const epsilon = 1e-6;
|
||||||
|
|
||||||
|
// Pre-compute polynomial coefficients for performance
|
||||||
|
const cx = 3.0 * x1;
|
||||||
|
const bx = 3.0 * (x2 - x1) - cx;
|
||||||
|
const ax = 1.0 - cx - bx;
|
||||||
|
|
||||||
|
function sampleCurveX(t: number): number {
|
||||||
|
// Evaluate: ax*t³ + bx*t² + cx*t using Horner's rule
|
||||||
|
return ((ax * t + bx) * t + cx) * t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sampleCurveDerivativeX(t: number): number {
|
||||||
|
return (3.0 * ax * t + 2.0 * bx) * t + cx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A reversed bell curved function that starts from 1 and goes to baseline and
|
* Newton's method - fast convergence with good initial guess
|
||||||
* come back to 1 again. It uses ease in out quadratic for css transition
|
* Try this first as it's normally very fast
|
||||||
* timing function for each side of the curve.
|
|
||||||
*
|
|
||||||
* @param {number} x - The current time, in the range [0, 1].
|
|
||||||
* @param {number} baseline - The baseline value to start from and return to.
|
|
||||||
* @returns the value of the transition at time x.
|
|
||||||
*/
|
*/
|
||||||
export function reverseEaseInOutBell(x: number, baseline: number): number {
|
function newtonRaphsonMethod(targetX: number): number | null {
|
||||||
const y = easeInOutBell(x, baseline);
|
let t = targetX;
|
||||||
return -y + 1 + baseline;
|
|
||||||
|
for (let iteration = 0; iteration < 8; iteration++) {
|
||||||
|
const currentX = sampleCurveX(t);
|
||||||
|
const error = currentX - targetX;
|
||||||
|
|
||||||
|
if (Math.abs(error) < epsilon) {
|
||||||
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function easeInOutBellRelative(
|
const derivative = sampleCurveDerivativeX(t);
|
||||||
x: number,
|
|
||||||
baseline: number,
|
|
||||||
prevOutlineWidth: number
|
|
||||||
): number {
|
|
||||||
const range = baseline - prevOutlineWidth;
|
|
||||||
|
|
||||||
if (x < 1 / 4) {
|
// Break if derivative is too small (avoid division by zero)
|
||||||
return prevOutlineWidth + 4 * Math.pow(2 * x, 3) * range;
|
if (Math.abs(derivative) < epsilon) {
|
||||||
} else if (x < 1 / 2) {
|
break;
|
||||||
return prevOutlineWidth + (1 - Math.pow(-4 * x + 2, 3) / 2) * range;
|
}
|
||||||
} else if (x < 3 / 4) {
|
|
||||||
return prevOutlineWidth + (1 - Math.pow(4 * x - 2, 3) / 2) * range;
|
// Newton-Raphson step: t_new = t_old - f(t_old) / f'(t_old)
|
||||||
|
t = t - error / derivative;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binary search fallback - guaranteed to converge
|
||||||
|
* Use this when Newton's method fails
|
||||||
|
*/
|
||||||
|
function binarySearchFallback(targetX: number): number {
|
||||||
|
let lowerBound = 0.0;
|
||||||
|
let upperBound = 1.0;
|
||||||
|
let t = targetX;
|
||||||
|
|
||||||
|
while (lowerBound < upperBound) {
|
||||||
|
const currentX = sampleCurveX(t);
|
||||||
|
|
||||||
|
if (Math.abs(currentX - targetX) < epsilon) {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetX > currentX) {
|
||||||
|
lowerBound = t;
|
||||||
} else {
|
} else {
|
||||||
return prevOutlineWidth + -4 * Math.pow(2 * x - 2, 3) * range;
|
upperBound = t;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function reverseEaseInOutBellRelative(
|
t = (upperBound - lowerBound) * 0.5 + lowerBound;
|
||||||
x: number,
|
|
||||||
baseline: number,
|
|
||||||
prevOutlineWidth: number
|
|
||||||
): number {
|
|
||||||
const y = easeInOutBellRelative(x, baseline, prevOutlineWidth);
|
|
||||||
return y;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newtonResult = newtonRaphsonMethod(targetX);
|
||||||
|
if (newtonResult !== null) {
|
||||||
|
return newtonResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
return binarySearchFallback(targetX);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluates the Y coordinate of a cubic Bézier curve at parameter t.
|
||||||
|
* Optimized for CSS timing functions where P0=(0,0) and P3=(1,1).
|
||||||
|
* Uses pre-computed polynomial coefficients and Horner's rule for performance.
|
||||||
|
*
|
||||||
|
* @param {number} t - Parameter value along the curve, typically in [0, 1]
|
||||||
|
* @param {number} y1 - y-coordinate of first control point
|
||||||
|
* @param {number} y2 - y-coordinate of second control point
|
||||||
|
* @returns {number} The Y value of the curve at parameter t
|
||||||
|
*/
|
||||||
|
function sampleCurveY(t: number, y1: number, y2: number): number {
|
||||||
|
// Pre-compute polynomial coefficients for performance
|
||||||
|
const cy = 3.0 * y1;
|
||||||
|
const by = 3.0 * (y2 - y1) - cy;
|
||||||
|
const ay = 1.0 - cy - by;
|
||||||
|
|
||||||
|
// Evaluate: ay*t³ + by*t² + cy*t using Horner's rule
|
||||||
|
return ((ay * t + by) * t + cy) * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cubic Bézier easing function implementation following CSS specifications.
|
||||||
|
*
|
||||||
|
* A cubic Bézier curve is defined by four points: P0, P1, P2, and P3.
|
||||||
|
* In CSS animations, P0 is fixed at (0, 0) and P3 is fixed at (1, 1).
|
||||||
|
* This function allows you to specify the intermediate control points P1 and P2.
|
||||||
|
*
|
||||||
|
* @param {number} x1 - x-coordinate of the first control point P1 (must be in [0, 1])
|
||||||
|
* @param {number} y1 - y-coordinate of the first control point P1
|
||||||
|
* @param {number} x2 - x-coordinate of the second control point P2 (must be in [0, 1])
|
||||||
|
* @param {number} y2 - y-coordinate of the second control point P2
|
||||||
|
* @returns {function} A function that takes time t ∈ [0, 1] and returns the eased value
|
||||||
|
*/
|
||||||
|
export function cubicBezier(x1: number, y1: number, x2: number, y2: number) {
|
||||||
|
if (x1 < 0 || x1 > 1 || x2 < 0 || x2 > 1) {
|
||||||
|
throw new Error('x1 and x2 must be in the range [0, 1]');
|
||||||
|
}
|
||||||
|
|
||||||
|
return function (timeProgress: number): number {
|
||||||
|
if (timeProgress <= 0) return 0;
|
||||||
|
if (timeProgress >= 1) return 1;
|
||||||
|
|
||||||
|
if (x1 === y1 && x2 === y2) {
|
||||||
|
return timeProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
const curveParameter = solveCubicBezierX(timeProgress, x1, x2);
|
||||||
|
return sampleCurveY(curveParameter, y1, y2);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Core Easing Functions */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Linear easing function - constant speed throughout the animation.
|
||||||
|
* Equivalent to CSS: cubic-bezier(0.0, 0.0, 1.0, 1.0)
|
||||||
|
*
|
||||||
|
* @param {number} timeProgress - The animation progress, in the range [0, 1].
|
||||||
|
* @returns {number} The linear eased value.
|
||||||
|
*/
|
||||||
|
const linearCore = cubicBezier(0.0, 0.0, 1.0, 1.0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ease-in easing function - starts slow and accelerates.
|
||||||
|
* Equivalent to CSS: cubic-bezier(0.42, 0, 1.0, 1.0)
|
||||||
|
*
|
||||||
|
* @param {number} timeProgress - The animation progress, in the range [0, 1].
|
||||||
|
* @returns {number} The eased value.
|
||||||
|
*/
|
||||||
|
const easeInCore = cubicBezier(0.42, 0, 1.0, 1.0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ease-out easing function - starts fast and decelerates.
|
||||||
|
* Equivalent to CSS: cubic-bezier(0, 0, 0.58, 1.0)
|
||||||
|
*
|
||||||
|
* @param {number} timeProgress - The animation progress, in the range [0, 1].
|
||||||
|
* @returns {number} The eased value.
|
||||||
|
*/
|
||||||
|
const easeOutCore = cubicBezier(0, 0, 0.58, 1.0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ease-in-out easing function - starts slow, accelerates, then decelerates.
|
||||||
|
* Equivalent to CSS: cubic-bezier(0.42, 0, 0.58, 1.0)
|
||||||
|
*
|
||||||
|
* @param {number} timeProgress - The animation progress, in the range [0, 1].
|
||||||
|
* @returns {number} The eased value.
|
||||||
|
*/
|
||||||
|
const easeInOutCore = cubicBezier(0.42, 0, 0.58, 1.0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard ease easing function (CSS default).
|
||||||
|
* Equivalent to CSS: cubic-bezier(0.25, 0.1, 0.25, 1.0)
|
||||||
|
*
|
||||||
|
* @param {number} timeProgress - The animation progress, in the range [0, 1].
|
||||||
|
* @returns {number} The eased value.
|
||||||
|
*/
|
||||||
|
const easeCore = cubicBezier(0.25, 0.1, 0.25, 1.0);
|
||||||
|
|
||||||
|
/** Flexible Easing Function Factory */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flexible factory function that creates easing functions with optional baseline and scale support.
|
||||||
|
* Provides bell-curve behavior: baseline → baseline*scale → baseline
|
||||||
|
*
|
||||||
|
* @param {Function} coreEasingFn - The core easing function to wrap
|
||||||
|
* @returns {Function} A function that accepts (timeProgress, baseline?, scale?) and provides flexible behavior
|
||||||
|
*/
|
||||||
|
function flexibleEasingFunctionFactory(coreEasingFn: (timeProgress: number) => number) {
|
||||||
|
return function (timeProgress: number, baseline: number = 0, scale?: number): number {
|
||||||
|
if (baseline === 0) {
|
||||||
|
return coreEasingFn(timeProgress);
|
||||||
|
}
|
||||||
|
|
||||||
|
const easedProgress = coreEasingFn(timeProgress);
|
||||||
|
|
||||||
|
const targetValue = scale ? baseline * scale : 1;
|
||||||
|
const range = targetValue - baseline;
|
||||||
|
|
||||||
|
// Create bell-curve: baseline → targetValue → baseline
|
||||||
|
const bellMultiplier = 1 - Math.abs(2 * easedProgress - 1);
|
||||||
|
return baseline + bellMultiplier * range;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Linear easing function with optional baseline and scale support.
|
||||||
|
* - No params: standard linear progression [0, 1]
|
||||||
|
* - With baseline only: bell-curve baseline → 1 → baseline
|
||||||
|
* - With baseline+scale: bell-curve baseline → baseline*scale → baseline
|
||||||
|
*
|
||||||
|
* @param {number} timeProgress - The animation progress, in the range [0, 1].
|
||||||
|
* @param {number} baseline - Optional baseline value (default: 0). Creates bell-curve effect.
|
||||||
|
* @param {number} scale - Optional scale multiplier (default: 1). Peak value = baseline * scale.
|
||||||
|
* @returns {number} The linear eased value.
|
||||||
|
*/
|
||||||
|
export const linear = flexibleEasingFunctionFactory(linearCore);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard ease easing function with optional baseline and scale support.
|
||||||
|
* - No params: standard ease progression [0, 1]
|
||||||
|
* - With baseline only: bell-curve baseline → 1 → baseline
|
||||||
|
* - With baseline+scale: bell-curve baseline → baseline*scale → baseline
|
||||||
|
*
|
||||||
|
* @param {number} timeProgress - The animation progress, in the range [0, 1].
|
||||||
|
* @param {number} baseline - Optional baseline value (default: 0). Creates bell-curve effect.
|
||||||
|
* @param {number} scale - Optional scale multiplier (default: 1). Peak value = baseline * scale.
|
||||||
|
* @returns {number} The eased value.
|
||||||
|
*/
|
||||||
|
export const ease = flexibleEasingFunctionFactory(easeCore);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ease-in easing function with optional baseline and scale support.
|
||||||
|
* - No params: standard ease-in progression [0, 1]
|
||||||
|
* - With baseline only: bell-curve baseline → 1 → baseline
|
||||||
|
* - With baseline+scale: bell-curve baseline → baseline*scale → baseline
|
||||||
|
*
|
||||||
|
* @param {number} timeProgress - The animation progress, in the range [0, 1].
|
||||||
|
* @param {number} baseline - Optional baseline value (default: 0). Creates bell-curve effect.
|
||||||
|
* @param {number} scale - Optional scale multiplier (default: 1). Peak value = baseline * scale.
|
||||||
|
* @returns {number} The eased value.
|
||||||
|
*/
|
||||||
|
export const easeIn = flexibleEasingFunctionFactory(easeInCore);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ease-out easing function with optional baseline and scale support.
|
||||||
|
* - No params: standard ease-out progression [0, 1]
|
||||||
|
* - With baseline only: bell-curve baseline → 1 → baseline
|
||||||
|
* - With baseline+scale: bell-curve baseline → baseline*scale → baseline
|
||||||
|
*
|
||||||
|
* @param {number} timeProgress - The animation progress, in the range [0, 1].
|
||||||
|
* @param {number} baseline - Optional baseline value (default: 0). Creates bell-curve effect.
|
||||||
|
* @param {number} scale - Optional scale multiplier (default: 1). Peak value = baseline * scale.
|
||||||
|
* @returns {number} The eased value.
|
||||||
|
*/
|
||||||
|
export const easeOut = flexibleEasingFunctionFactory(easeOutCore);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ease-in-out easing function with optional baseline and scale support.
|
||||||
|
* - No params: standard ease-in-out progression [0, 1]
|
||||||
|
* - With baseline only: bell-curve baseline → 1 → baseline
|
||||||
|
* - With baseline+scale: bell-curve baseline → baseline*scale → baseline
|
||||||
|
*
|
||||||
|
* @param {number} timeProgress - The animation progress, in the range [0, 1].
|
||||||
|
* @param {number} baseline - Optional baseline value (default: 0). Creates bell-curve effect.
|
||||||
|
* @param {number} scale - Optional scale multiplier (default: 1). Peak value = baseline * scale.
|
||||||
|
* @returns {number} The eased value.
|
||||||
|
*/
|
||||||
|
export const easeInOut = flexibleEasingFunctionFactory(easeInOutCore);
|
||||||
|
|
||||||
|
/** Export interfaces */
|
||||||
|
|
||||||
|
export enum EasingFunctionEnum {
|
||||||
|
EASE = 'ease',
|
||||||
|
EASE_IN = 'ease-in',
|
||||||
|
EASE_OUT = 'ease-out',
|
||||||
|
EASE_IN_OUT = 'ease-in-out',
|
||||||
|
LINEAR = 'linear',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EasingFunctionMap = new Map([
|
||||||
|
[EasingFunctionEnum.EASE, ease],
|
||||||
|
[EasingFunctionEnum.EASE_IN, easeIn],
|
||||||
|
[EasingFunctionEnum.EASE_OUT, easeOut],
|
||||||
|
[EasingFunctionEnum.EASE_IN_OUT, easeInOut],
|
||||||
|
[EasingFunctionEnum.LINEAR, linear],
|
||||||
|
]);
|
||||||
|
|||||||
@ -346,7 +346,7 @@ jumpToSegmentCenter(
|
|||||||
highlightSegment = true,
|
highlightSegment = true,
|
||||||
animationLength = 750,
|
animationLength = 750,
|
||||||
highlightHideOthers = false,
|
highlightHideOthers = false,
|
||||||
highlightFunctionType = 'ease-in-out'
|
animationFunctionType = 'ease-in-out'
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -1328,6 +1328,35 @@ window.config = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'panelSegmentation.jumpToSegmentHighlightAnimationConfig',
|
||||||
|
description:
|
||||||
|
'Customize the highlight animation when clicking on a segment at the segmentation panel and jumping to it.',
|
||||||
|
default: {
|
||||||
|
highlightAlpha: 0.9,
|
||||||
|
highlightSegment: true,
|
||||||
|
animationLength: 750,
|
||||||
|
animationFunctionType: 'ease-in-out',
|
||||||
|
},
|
||||||
|
image: [],
|
||||||
|
configuration: `
|
||||||
|
window.config = {
|
||||||
|
// rest of window config
|
||||||
|
customizationService: [
|
||||||
|
{
|
||||||
|
'panelSegmentation.jumpToSegmentHighlightAnimationConfig': {
|
||||||
|
$set: {
|
||||||
|
highlightAlpha: 1.0,
|
||||||
|
highlightSegment: true,
|
||||||
|
animationLength: 900,
|
||||||
|
animationFunctionType: 'linear', // one of 'ease-in-out', 'ease-in', 'ease-out', 'ease', 'linear'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -346,7 +346,7 @@ jumpToSegmentCenter(
|
|||||||
highlightSegment = true,
|
highlightSegment = true,
|
||||||
animationLength = 750,
|
animationLength = 750,
|
||||||
highlightHideOthers = false,
|
highlightHideOthers = false,
|
||||||
highlightFunctionType = 'ease-in-out'
|
animationFunctionType = 'ease-in-out'
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user