@@ -89,6 +89,8 @@ const segregated = ref([]);
8989const step = ref (0 );
9090const slicerStep = ref (0 );
9191const readyTeleport = ref (false );
92+ const pathWrapper = ref (null );
93+ const pathTop = ref (null );
9294
9395const timeLabelsEls = ref (null );
9496const scaleLabels = ref (null );
@@ -104,6 +106,18 @@ const { loading, FINAL_DATASET, manualLoading } = useLoading({
104106 ... toRefs (props),
105107 FINAL_CONFIG ,
106108 prepareConfig,
109+ callback : () => {
110+ Promise .resolve ().then (async () => {
111+ await nextTick ();
112+ if (chartType .value === detector .chartType .LINE && FINAL_CONFIG .value .lineAnimated && ! loading .value ) {
113+ animateLineNow ({
114+ pathDuration: 1000 ,
115+ pointDuration: 1200 ,
116+ labelDuration: 1200 ,
117+ });
118+ }
119+ })
120+ },
107121 skeletonDataset: [1 , 2 , 3 , 5 , 8 , 13 , 21 , 34 , 55 , 89 ],
108122 skeletonConfig: treeShake ({
109123 defaultConfig: FINAL_CONFIG .value ,
@@ -287,7 +301,7 @@ watch(FINAL_CONFIG, () => {
287301const resizeObserver = shallowRef (null );
288302const observedEl = shallowRef (null );
289303
290- onMounted (() => {
304+ onMounted (async () => {
291305 readyTeleport .value = true ;
292306 prepareChart ();
293307})
@@ -817,7 +831,7 @@ const line = computed(() => {
817831 maxSeries: Math .max (... ds .map (d => d .values .length ))
818832 };
819833
820- const scale = calculateNiceScale (extremes .min < 0 ? extremes .min : 0 , extremes .max < 0 ? 0 : extremes .max , FINAL_CONFIG .value .xyScaleSegments )
834+ const scale = extremes . max === extremes . min ? calculateNiceScale ( extremes . min , extremes . min + 1 , FINAL_CONFIG . value . xyScaleSegments ) : calculateNiceScale (extremes .min < 0 ? extremes .min : 0 , extremes .max < 0 ? 0 : extremes .max , FINAL_CONFIG .value .xyScaleSegments )
821835 const absoluteMin = extremes .min < 0 ? Math .abs (extremes .min ) : 0 ;
822836 const absoluteZero = extremes .max < 0 ? drawingArea .top : drawingArea .bottom - (absoluteMin / (scale .max + absoluteMin) * drawingArea .height )
823837 const slotSize = drawingArea .width / extremes .maxSeries ;
@@ -1018,7 +1032,7 @@ const bar = computed(() => {
10181032 maxSeries: Math .max (... ds .filter (d => ! segregated .value .includes (d .id )).map (d => d .values .length ))
10191033 }
10201034
1021- const scale = calculateNiceScale (extremes .min < 0 ? extremes .min : 0 , extremes .max , FINAL_CONFIG .value .xyScaleSegments )
1035+ const scale = extremes . min === extremes . max ? calculateNiceScale ( extremes . min , extremes . min + 1 , FINAL_CONFIG . value . xyScaleSegments ) : calculateNiceScale (extremes .min < 0 ? extremes .min : 0 , extremes .max , FINAL_CONFIG .value .xyScaleSegments )
10221036 const absoluteMin = scale .min < 0 ? Math .abs (scale .min ) : 0 ;
10231037 const absoluteZero = drawingArea .bottom - (absoluteMin / (scale .max + absoluteMin) * drawingArea .height )
10241038 const slotSize = drawingArea .width / extremes .maxSeries ;
@@ -1173,6 +1187,116 @@ const bar = computed(() => {
11731187 }
11741188});
11751189
1190+ function primePath (p ) {
1191+ if (! p) return ;
1192+ const len = p .getTotalLength ();
1193+ p .style .transition = ' none' ;
1194+ p .style .strokeDasharray = ` ${ len} ` ;
1195+ p .style .strokeDashoffset = ` ${ len} ` ;
1196+ }
1197+
1198+ function primeRevealables (els , { fromOpacity= ' 0' , fromScale= ' 0.85' } = {}) {
1199+ els .forEach (el => {
1200+ el .style .animation = ' none' ;
1201+ el .style .transition = ' none' ;
1202+ el .style .opacity = fromOpacity;
1203+ el .style .transform = ` scale(${ fromScale} )` ;
1204+ el .style .transformBox = ' fill-box' ;
1205+ el .style .transformOrigin = ' 50% 50%' ;
1206+ });
1207+ }
1208+
1209+ function getXFromCircle (el ) {
1210+ return el .cx ? .baseVal ? .value ?? parseFloat (el .getAttribute (' cx' ));
1211+ }
1212+ function getXFromText (el ) {
1213+ const xAttr = el .getAttribute (' x' );
1214+ if (xAttr != null ) return parseFloat (xAttr);
1215+ const ctm = el .getCTM ? .();
1216+ return ctm ? ctm .e : 0 ;
1217+ }
1218+
1219+ function bucketByXTolerance (elems , getX ) {
1220+ if (! elems .length ) return [];
1221+ const withX = elems .map (el => ({ el, x: getX (el) })).filter (o => Number .isFinite (o .x ));
1222+ withX .sort ((a , b ) => a .x - b .x );
1223+
1224+ let minGap = Infinity ;
1225+ for (let i = 1 ; i < withX .length ; i++ ) {
1226+ const d = withX[i].x - withX[i - 1 ].x ;
1227+ if (d > 0 && d < minGap) minGap = d;
1228+ }
1229+ const tol = (minGap === Infinity ? 1 : minGap) / 2 ;
1230+
1231+ const buckets = [];
1232+ let current = { x: withX[0 ].x , items: [withX[0 ].el ] };
1233+ for (let i = 1 ; i < withX .length ; i++ ) {
1234+ const { x , el } = withX[i];
1235+ if (Math .abs (x - current .x ) <= tol) {
1236+ current .items .push (el);
1237+ } else {
1238+ buckets .push (current);
1239+ current = { x, items: [el] };
1240+ }
1241+ }
1242+ buckets .push (current);
1243+ return buckets;
1244+ }
1245+
1246+ function animateLineNow ({
1247+ pathDuration,
1248+ pathEasing = ' ease-in-out' ,
1249+ pointDuration,
1250+ labelDuration,
1251+ pointDelay = 0 ,
1252+ labelDelay = 0 ,
1253+ pointStep = 0 ,
1254+ labelStep = 0 ,
1255+ intraSeriesStep = 0
1256+ } = {}) {
1257+ const wrappers = Array .isArray (pathWrapper .value ) ? pathWrapper .value : [pathWrapper .value ].filter (Boolean );
1258+ const tops = Array .isArray (pathTop .value ) ? pathTop .value : [pathTop .value ].filter (Boolean );
1259+ const paths = [... wrappers, ... tops].filter (Boolean );
1260+ const root = quickChart .value ;
1261+ const points = Array .from (root .querySelectorAll (' .vue-ui-quick-chart-plot' ));
1262+ const labels = Array .from (root .querySelectorAll (' .vue-ui-quick-chart-label' ));
1263+ paths .forEach (primePath);
1264+ primeRevealables (points, { fromOpacity: ' 0' , fromScale: ' 0.75' });
1265+ primeRevealables (labels, { fromOpacity: ' 0' , fromScale: ' 0.98' });
1266+ points .forEach (el => el .classList .remove (' quick-animation' ));
1267+ labels .forEach (el => el .classList .remove (' quick-animation' ));
1268+ void root .offsetWidth ;
1269+ const pointCols = bucketByXTolerance (points, getXFromCircle);
1270+ const labelCols = bucketByXTolerance (labels, getXFromText);
1271+
1272+ requestAnimationFrame (() => {
1273+ requestAnimationFrame (() => {
1274+ paths .forEach (p => {
1275+ p .style .transition = ` stroke-dashoffset ${ pathDuration} ms ${ pathEasing} ` ;
1276+ p .style .strokeDashoffset = ' 0' ;
1277+ });
1278+
1279+ pointCols .forEach ((col , colIndex ) => {
1280+ col .items .forEach ((el , k ) => {
1281+ const delay = pointDelay + colIndex * pointStep + k * intraSeriesStep;
1282+ el .style .transition = ` opacity ${ pointDuration} ms ease-out ${ delay} ms, transform ${ pointDuration} ms ease-out ${ delay} ms` ;
1283+ el .style .opacity = ' 1' ;
1284+ el .style .transform = ' scale(1)' ;
1285+ });
1286+ });
1287+
1288+ labelCols .forEach ((col , colIndex ) => {
1289+ col .items .forEach ((el , k ) => {
1290+ const delay = labelDelay + colIndex * labelStep + k * intraSeriesStep;
1291+ el .style .transition = ` opacity ${ labelDuration} ms ease-out ${ delay} ms, transform ${ labelDuration} ms ease-out ${ delay} ms` ;
1292+ el .style .opacity = ' 1' ;
1293+ el .style .transform = ' scale(1)' ;
1294+ });
1295+ });
1296+ });
1297+ });
1298+ }
1299+
11761300const allMinimaps = computed (() => {
11771301 if (chartType .value === detector .chartType .LINE ) {
11781302 return line .value .legend .map (ds => {
@@ -1678,29 +1802,32 @@ defineExpose({
16781802 < g class = " line-plot-series" >
16791803 < template v- if = " FINAL_CONFIG.lineSmooth" >
16801804 < path
1805+ ref= " pathWrapper"
16811806 data- cy= " datapoint-line-wrapper"
16821807 : d= " `M ${createSmoothPath(ds.coordinates)}`"
16831808 : stroke= " FINAL_CONFIG.backgroundColor"
16841809 : stroke- width= " FINAL_CONFIG.lineStrokeWidth + 1"
16851810 stroke- linecap= " round"
16861811 fill= " none"
16871812 : class = " {'quick-animation': !loading, 'vue-data-ui-line-animated': FINAL_CONFIG.lineAnimated && !loading }"
1688- : style= " { transition: loading ? undefined : 'all 0.3s ease-in-out' }"
1813+ : style= " { transition: loading ? undefined : 'all 0.3s ease-in-out'}"
16891814 / >
16901815 < path
1816+ ref= " pathTop"
16911817 data- cy= " datapoint-line"
16921818 : d= " `M ${createSmoothPath(ds.coordinates)}`"
16931819 : stroke= " ds.color"
16941820 : stroke- width= " FINAL_CONFIG.lineStrokeWidth"
16951821 stroke- linecap= " round"
16961822 fill= " none"
16971823 : class = " {'quick-animation': !loading, 'vue-data-ui-line-animated': FINAL_CONFIG.lineAnimated && !loading }"
1698- : style= " { transition: loading ? undefined : 'all 0.3s ease-in-out' }"
1824+ : style= " { transition: loading ? undefined : 'all 0.3s ease-in-out'}"
16991825 >
17001826 < / path>
17011827 < / template>
17021828 < template v- else >
17031829 < path
1830+ ref= " pathWrapper"
17041831 data- cy= " datapoint-line-wrapper"
17051832 : d= " `M ${ds.linePath}`"
17061833 : stroke= " FINAL_CONFIG.backgroundColor"
@@ -1711,14 +1838,15 @@ defineExpose({
17111838 : style= " { transition: loading ? undefined : 'all 0.3s ease-in-out' }"
17121839 / >
17131840 < path
1841+ ref= " pathTop"
17141842 data- cy= " datapoint-line"
17151843 : d= " `M ${ds.linePath}`"
17161844 : stroke= " ds.color"
17171845 : stroke- width= " FINAL_CONFIG.lineStrokeWidth"
17181846 stroke- linecap= " round"
17191847 fill= " none"
17201848 : class = " {'quick-animation': !loading, 'vue-data-ui-line-animated': FINAL_CONFIG.lineAnimated && !loading }"
1721- : style= " { transition: loading ? undefined : 'all 0.3s ease-in-out' }"
1849+ : style= " { transition: loading ? undefined : 'all 0.3s ease-in-out'}"
17221850 / >
17231851 < / template>
17241852 < template v- for = " (plot, j) in ds.coordinates" >
@@ -1730,7 +1858,7 @@ defineExpose({
17301858 : fill= " ds.color"
17311859 : stroke= " FINAL_CONFIG.backgroundColor"
17321860 stroke- width= " 0.5"
1733- : class = " { 'quick-animation': !loading }"
1861+ : class = " { 'vue-ui-quick-chart-plot': true, ' quick-animation': !loading }"
17341862 : style= " { transition: loading ? undefined : 'all 0.3s ease-in-out' }"
17351863 / >
17361864 < / template>
@@ -1747,7 +1875,7 @@ defineExpose({
17471875 : fill= " ds.color"
17481876 : x= " plot.x"
17491877 : y= " checkNaN(plot.y) - FINAL_CONFIG.dataLabelFontSize / 2"
1750- class = " quick-animation"
1878+ : class = " { 'vue-ui- quick-chart-label': true, 'quick- animation': !loading } "
17511879 : style= " { transition: loading ? undefined : 'all 0.3s ease-in-out' }"
17521880 >
17531881 {{ applyDataLabel (
@@ -2276,6 +2404,7 @@ defineExpose({
22762404 animation: quick 0 .5s ease- in - out;
22772405 transform- origin: center;
22782406}
2407+
22792408@keyframes quick {
22802409 0 % {
22812410 transform: scale (0.9 ,0.9 );
@@ -2291,18 +2420,6 @@ defineExpose({
22912420 }
22922421}
22932422
2294- .vue - data- ui- line- animated {
2295- stroke- dasharray: 2000 ;
2296- stroke- dashoffset: 2000 ;
2297- animation: vueDataUiLineAnimation 0 .5s cubic- bezier (0.790 , 0.210 , 0.790 , 0.210 ) forwards;
2298- }
2299-
2300- @keyframes vueDataUiLineAnimation {
2301- to {
2302- stroke- dashoffset: 0 ;
2303- }
2304- }
2305-
23062423.vue - data- ui- bar- animated {
23072424 animation: vueDataUiBarAnimation 0 .5s cubic- bezier (0.790 , 0.210 , 0.790 , 0.210 ) forwards;
23082425}
0 commit comments