@@ -12,6 +12,7 @@ import {
12
12
createUid ,
13
13
dataLabel ,
14
14
downloadCsv ,
15
+ easeOutCubic ,
15
16
error ,
16
17
getMissingDatasetAttributes ,
17
18
isFunction ,
@@ -171,10 +172,6 @@ const FINAL_CONFIG = computed({
171
172
const isFirstLoad = ref (true );
172
173
const animatedValues = ref ([]);
173
174
174
- function easeOutCubic (t ) {
175
- return 1 - Math .pow (1 - t, 3 );
176
- }
177
-
178
175
function animateWithGhost (finalValues , duration = 1000 , stagger = 50 ) {
179
176
return new Promise (resolve => {
180
177
const N = finalValues .length ;
@@ -280,7 +277,6 @@ const donutThickness = computed(() => {
280
277
281
278
const emit = defineEmits ([' selectLegend' , ' selectDatapoint' ])
282
279
283
-
284
280
const immutableSet = computed (() => {
285
281
return props .dataset
286
282
.map ((serie , i ) => {
@@ -317,106 +313,102 @@ const rafUp = ref(null);
317
313
const rafDown = ref (null );
318
314
const isAnimating = ref (false );
319
315
316
+ function animateValue ({ from, to, duration, onUpdate, onDone, easing = easeOutCubic }) {
317
+ const start = performance .now ();
318
+ function step (now ) {
319
+ const t = Math .min ((now - start) / duration, 1 );
320
+ const eased = easing (t);
321
+ const current = from + (to - from) * eased;
322
+ onUpdate (current, t);
323
+ if (t < 1 ) {
324
+ requestAnimationFrame (step);
325
+ } else {
326
+ onUpdate (to, 1 );
327
+ if (onDone) onDone ();
328
+ }
329
+ }
330
+ requestAnimationFrame (step);
331
+ }
332
+
320
333
function segregate (index ) {
321
- const target = immutableSet .value .find ((_ , idx ) => idx === index)
322
- const source = mutableSet .value .find ((_ , idx ) => idx === index)
334
+ const target = immutableSet .value .find ((_ , idx ) => idx === index);
335
+ const source = mutableSet .value .find ((_ , idx ) => idx === index);
323
336
let initVal = source .value ;
337
+
324
338
if (segregated .value .includes (index)) {
325
339
segregated .value = segregated .value .filter (s => s !== index);
326
340
const targetVal = target .value ;
327
341
328
342
function setFinalUpState () {
329
- mutableSet .value = mutableSet .value .map ((ds , i ) => {
330
- if (index === i) {
331
- return {
332
- ... ds,
333
- value: targetVal
334
- }
335
- } else {
336
- return ds
337
- }
338
- });
343
+ mutableSet .value = mutableSet .value .map ((ds , i ) =>
344
+ index === i ? { ... ds, value: targetVal } : ds
345
+ );
339
346
}
340
347
341
- function animUp () {
342
- if (initVal > targetVal) {
343
- cancelAnimationFrame (rafUp .value );
344
- setFinalUpState ();
345
- isAnimating .value = false ;
346
- } else {
347
- isAnimating .value = true ;
348
- initVal += (targetVal * (FINAL_CONFIG .value .type === ' polar' ? 1 : 0.045 ));
349
- mutableSet .value = mutableSet .value .map ((ds , i ) => {
350
- if ((index === i)) {
351
- return {
352
- ... ds,
353
- value: initVal
354
- }
355
- } else {
356
- return ds
357
- }
358
- })
359
- rafUp .value = requestAnimationFrame (animUp)
360
- }
348
+ function doAnimUp () {
349
+ isAnimating .value = true ;
350
+ animateValue ({
351
+ from: initVal,
352
+ to: targetVal,
353
+ duration: FINAL_CONFIG .value .serieToggleAnimation .durationMs ,
354
+ onUpdate : (val , t ) => {
355
+ mutableSet .value = mutableSet .value .map ((ds , i ) =>
356
+ index === i ? { ... ds, value: val } : ds
357
+ );
358
+ },
359
+ onDone : () => {
360
+ setFinalUpState ();
361
+ isAnimating .value = false ;
362
+ }
363
+ });
361
364
}
362
365
363
- if (FINAL_CONFIG .value .useSerieToggleAnimation ) {
364
- animUp ();
366
+ if (FINAL_CONFIG .value .serieToggleAnimation . show && FINAL_CONFIG . value . type === ' classic ' ) {
367
+ doAnimUp ();
365
368
} else {
366
369
setFinalUpState ();
367
370
}
368
-
369
371
} else if (segregated .value .length < immutableSet .value .length - 1 ) {
370
372
function setFinalDownState () {
371
373
segregated .value .push (index);
372
- mutableSet .value = mutableSet .value .map ((ds , i ) => {
373
- if (index === i) {
374
- return {
375
- ... ds,
376
- value: 0 ,
377
- }
378
- } else {
379
- return ds;
380
- }
381
- });
374
+ mutableSet .value = mutableSet .value .map ((ds , i ) =>
375
+ index === i ? { ... ds, value: 0 } : ds
376
+ );
382
377
}
383
378
384
- function animDown () {
385
- if (initVal < source .value / 100 ) {
386
- cancelAnimationFrame (rafDown .value );
387
- setFinalDownState ();
388
- isAnimating .value = false ;
389
- } else {
390
- isAnimating .value = true ;
391
- initVal /= (FINAL_CONFIG .value .type === ' polar' ? 20 : 1.18 );
392
- mutableSet .value = mutableSet .value .map ((ds , i ) => {
393
- if (index === i) {
394
- return {
395
- ... ds,
396
- value: initVal
397
- }
398
- } else {
399
- return ds;
400
- }
401
- })
402
- rafDown .value = requestAnimationFrame (animDown);
403
- }
379
+ function doAnimDown () {
380
+ isAnimating .value = true ;
381
+ animateValue ({
382
+ from: initVal,
383
+ to: 0 ,
384
+ duration: FINAL_CONFIG .value .serieToggleAnimation .durationMs ,
385
+ onUpdate : (val , t ) => {
386
+ mutableSet .value = mutableSet .value .map ((ds , i ) =>
387
+ index === i ? { ... ds, value: val } : ds
388
+ );
389
+ },
390
+ onDone : () => {
391
+ setFinalDownState ();
392
+ isAnimating .value = false ;
393
+ }
394
+ });
404
395
}
405
- if (FINAL_CONFIG .value .useSerieToggleAnimation ) {
406
- animDown ();
396
+
397
+ if (FINAL_CONFIG .value .serieToggleAnimation .show && FINAL_CONFIG .value .type === ' classic' ) {
398
+ doAnimDown ();
407
399
} else {
408
400
setFinalDownState ();
409
401
}
410
402
}
411
- emit (' selectLegend' , donutSet .value .map (ds => {
412
- return {
413
- name: ds .name ,
414
- color: ds .color ,
415
- value: ds .value
416
- }
417
- }));
403
+
404
+ emit (' selectLegend' , donutSet .value .map (ds => ({
405
+ name: ds .name ,
406
+ color: ds .color ,
407
+ value: ds .value
408
+ })));
418
409
}
419
410
411
+
420
412
const _total = computed (() => props .dataset .reduce ((sum , ds ) => sum + ds .values .reduce ((a , b ) => a + b, 0 ), 0 ));
421
413
422
414
const donutSet = computed (() => {
@@ -922,14 +914,18 @@ defineExpose({
922
914
</template >
923
915
<template v-if =" FINAL_CONFIG .type === ' polar' " >
924
916
<g v-for =" (arc, i) in currentDonut.filter(el => !el.ghost)" >
925
- <line data-cy =" polar-datapoint" v-if =" isArcBigEnough(arc) && mutableConfig.dataLabels.show"
926
- :x1 =" offsetFromCenterPoint({ initX: polarAreas[i].middlePoint.x, initY: polarAreas[i].middlePoint.y, offset: 24, centerX: svg.width / 2, centerY: svg.height / 2 }).x"
927
- :y1 =" offsetFromCenterPoint({ initX: polarAreas[i].middlePoint.x, initY: polarAreas[i].middlePoint.y, offset: 24, centerX: svg.width / 2, centerY: svg.height / 2 }).y"
928
- :x2 =" polarAreas[i].middlePoint.x" :y2 =" polarAreas[i].middlePoint.y" :stroke =" arc.color"
929
- stroke-width =" 1" stroke-linecap =" round" stroke-linejoin =" round" fill =" none"
917
+ <path
918
+ data-cy =" polar-datapoint"
919
+ v-if =" isArcBigEnough(arc) && mutableConfig.dataLabels.show"
920
+ :d =" `M ${offsetFromCenterPoint({ initX: polarAreas[i].middlePoint.x, initY: polarAreas[i].middlePoint.y, offset: 24, centerX: svg.width / 2, centerY: svg.height / 2 }).x},${offsetFromCenterPoint({ initX: polarAreas[i].middlePoint.x, initY: polarAreas[i].middlePoint.y, offset: 24, centerX: svg.width / 2, centerY: svg.height / 2 }).y} ${polarAreas[i].middlePoint.x},${polarAreas[i].middlePoint.y}`"
921
+ :stroke =" arc.color"
922
+ stroke-width =" 1"
923
+ stroke-linecap =" round"
924
+ stroke-linejoin =" round"
925
+ fill =" none"
930
926
:filter =" getBlurFilter(i)"
931
927
:style =" {
932
- transition: isFirstLoad ? '' : ' all 0.1s ease-in-out'
928
+ transition: isFirstLoad || !FINAL_CONFIG.serieToggleAnimation.show ? 'none ' : ` all ${FINAL_CONFIG.serieToggleAnimation.durationMs}ms ease-in-out`,
933
929
}"
934
930
/>
935
931
</g >
@@ -969,7 +965,7 @@ defineExpose({
969
965
<path v-for =" (arc, i) in noGhostDonut" :stroke =" FINAL_CONFIG.style.chart.backgroundColor"
970
966
:d =" polarAreas[i].path" fill =" #FFFFFF"
971
967
:style =" {
972
- transition: isFirstLoad ? 'none' : ' all 0.1s ease-in-out'
968
+ transition: isFirstLoad || !FINAL_CONFIG.serieToggleAnimation.show ? 'none' : ` all ${FINAL_CONFIG.serieToggleAnimation.durationMs}ms ease-in-out`
973
969
}"
974
970
/>
975
971
<g v-if =" FINAL_CONFIG.style.chart.layout.donut.useShadow" >
@@ -979,7 +975,7 @@ defineExpose({
979
975
:stroke-width =" FINAL_CONFIG.style.chart.layout.donut.borderWidth"
980
976
:filter =" `url(#drop_shadow_${uid})`"
981
977
:style =" {
982
- transition: isFirstLoad ? 'none' : ' all 0.1s ease-in-out'
978
+ transition: isFirstLoad || !FINAL_CONFIG.serieToggleAnimation.show ? 'none' : ` all ${FINAL_CONFIG.serieToggleAnimation.durationMs}ms ease-in-out`
983
979
}"
984
980
/>
985
981
</g >
@@ -992,7 +988,7 @@ defineExpose({
992
988
:stroke-width =" FINAL_CONFIG.style.chart.layout.donut.borderWidth"
993
989
:filter =" getBlurFilter(i)"
994
990
:style =" {
995
- transition: isFirstLoad ? 'none' : ' all 0.1s ease-in-out'
991
+ transition: isFirstLoad || !FINAL_CONFIG.serieToggleAnimation.show ? 'none' : ` all ${FINAL_CONFIG.serieToggleAnimation.durationMs}ms ease-in-out`
996
992
}"
997
993
/>
998
994
</g >
@@ -1002,7 +998,7 @@ defineExpose({
1002
998
:stroke =" FINAL_CONFIG.style.chart.backgroundColor"
1003
999
:stroke-width =" FINAL_CONFIG.style.chart.layout.donut.borderWidth" :filter =" getBlurFilter(i)"
1004
1000
:style =" {
1005
- transition: isFirstLoad ? 'none' : ' all 0.1s ease-in-out'
1001
+ transition: isFirstLoad || !FINAL_CONFIG.serieToggleAnimation.show ? 'none' : ` all ${FINAL_CONFIG.serieToggleAnimation.durationMs}ms ease-in-out`
1006
1002
}"
1007
1003
/>
1008
1004
</g >
@@ -1145,7 +1141,11 @@ defineExpose({
1145
1141
:fill =" arc.color" :stroke =" FINAL_CONFIG.style.chart.backgroundColor" :stroke-width =" 1"
1146
1142
:r =" 3"
1147
1143
:filter =" !FINAL_CONFIG.useBlurOnHover || [null, undefined].includes(selectedSerie) || selectedSerie === i ? `` : `url(#blur_${uid})`"
1148
- @click =" selectDatapoint(arc, i)" />
1144
+ @click =" selectDatapoint(arc, i)"
1145
+ :style =" {
1146
+ transition: isFirstLoad || !FINAL_CONFIG.serieToggleAnimation.show ? 'none' : `all ${FINAL_CONFIG.serieToggleAnimation.durationMs}ms ease-in-out`
1147
+ }"
1148
+ />
1149
1149
</template >
1150
1150
<template v-if =" FINAL_CONFIG .type === ' classic' " >
1151
1151
<text data-cy =" donut-label-value" v-if =" isArcBigEnough(arc) && mutableConfig.dataLabels.show"
@@ -1185,7 +1185,10 @@ defineExpose({
1185
1185
:y =" offsetFromCenterPoint({ initX: polarAreas[i].middlePoint.x, initY: polarAreas[i].middlePoint.y, offset: 42, centerX: svg.width / 2, centerY: svg.height / 2 }).y"
1186
1186
:fill =" FINAL_CONFIG.style.chart.layout.labels.percentage.color"
1187
1187
:font-size =" FINAL_CONFIG.style.chart.layout.labels.percentage.fontSize"
1188
- :style =" `transition: all 0.1s ease-in-out; font-weight:${FINAL_CONFIG.style.chart.layout.labels.percentage.bold ? 'bold' : ''}`"
1188
+ :style =" {
1189
+ transition: isFirstLoad || !FINAL_CONFIG.serieToggleAnimation.show ? 'none' : `all ${FINAL_CONFIG.serieToggleAnimation.durationMs}ms ease-in-out`,
1190
+ fontWeight: FINAL_CONFIG.style.chart.layout.labels.percentage.bold ? 'bold': 'normal'
1191
+ }"
1189
1192
@click =" selectDatapoint(arc, i)" >
1190
1193
{{ displayArcPercentage(arc, noGhostDonut) }} {{
1191
1194
FINAL_CONFIG.style.chart.layout.labels.value.show ? `(${applyDataLabel(
@@ -1206,7 +1209,10 @@ defineExpose({
1206
1209
:y =" offsetFromCenterPoint({ initX: polarAreas[i].middlePoint.x, initY: polarAreas[i].middlePoint.y, offset: 42, centerX: svg.width / 2, centerY: svg.height / 2 }).y + FINAL_CONFIG.style.chart.layout.labels.percentage.fontSize"
1207
1210
:fill =" FINAL_CONFIG.style.chart.layout.labels.name.color"
1208
1211
:font-size =" FINAL_CONFIG.style.chart.layout.labels.name.fontSize"
1209
- :style =" `transition: all 0.1s ease-in-out; font-weight:${FINAL_CONFIG.style.chart.layout.labels.name.bold ? 'bold' : ''}`"
1212
+ :style =" {
1213
+ transition: isFirstLoad || !FINAL_CONFIG.serieToggleAnimation.show ? 'none' : `all ${FINAL_CONFIG.serieToggleAnimation.durationMs}ms ease-in-out`,
1214
+ fontWeight: FINAL_CONFIG.style.chart.layout.labels.name.bold ? 'bold': 'normal'
1215
+ }"
1210
1216
@click =" selectDatapoint(arc, i)" >
1211
1217
{{ arc.name }}
1212
1218
</text >
@@ -1228,7 +1234,12 @@ defineExpose({
1228
1234
:x =" FINAL_CONFIG.style.chart.comments.offsetX + (getPolarAnchor(polarAreas[i].middlePoint) === 'end' ? offsetFromCenterPoint({ initX: polarAreas[i].middlePoint.x, initY: polarAreas[i].middlePoint.y, offset: 42, centerX: svg.width / 2, centerY: svg.height / 2 }).x - FINAL_CONFIG.style.chart.comments.width : getPolarAnchor(polarAreas[i].middlePoint) === 'middle' ? offsetFromCenterPoint({ initX: polarAreas[i].middlePoint.x, initY: polarAreas[i].middlePoint.y, offset: 42, centerX: svg.width / 2, centerY: svg.height / 2 }).x - (FINAL_CONFIG.style.chart.comments.width / 2) : offsetFromCenterPoint({ initX: polarAreas[i].middlePoint.x, initY: polarAreas[i].middlePoint.y, offset: 42, centerX: svg.width / 2, centerY: svg.height / 2 }).x)"
1229
1235
:y =" getPolarCommentY(polarAreas[i]) + FINAL_CONFIG.style.chart.comments.offsetY"
1230
1236
:width =" FINAL_CONFIG.style.chart.comments.width" height =" 200"
1231
- style =" overflow : visible ; pointer-events : none " >
1237
+ :style =" {
1238
+ transition: isFirstLoad || !FINAL_CONFIG.serieToggleAnimation.show ? 'none' : `all ${FINAL_CONFIG.serieToggleAnimation.durationMs}ms ease-in-out`,
1239
+ overflow: 'visible',
1240
+ pointerEvents: 'none'
1241
+ }"
1242
+ >
1232
1243
<div >
1233
1244
<slot name =" plot-comment"
1234
1245
:plot =" { ...arc, textAlign: getPolarAnchor(polarAreas[i].middlePoint), flexAlign: getPolarAnchor(polarAreas[i].middlePoint), isFirstLoad }" />
0 commit comments