@@ -2531,9 +2531,13 @@ Plotly.animate = function(gd, frameOrGroupNameOrFrameList, animationOpts) {
2531
2531
2532
2532
for ( var i = 0 ; i < frameList . length ; i ++ ) {
2533
2533
var computedFrame ;
2534
+
2534
2535
if ( frameList [ i ] . name ) {
2536
+ // If it's a named frame, compute it:
2535
2537
computedFrame = Plots . computeFrame ( gd , frameList [ i ] . name ) ;
2536
2538
} else {
2539
+ // Otherwise we must have been given a simple object, so treat
2540
+ // the input itself as the computed frame.
2537
2541
computedFrame = frameList [ i ] . frame ;
2538
2542
}
2539
2543
@@ -2545,32 +2549,50 @@ Plotly.animate = function(gd, frameOrGroupNameOrFrameList, animationOpts) {
2545
2549
} ;
2546
2550
2547
2551
if ( i === frameList . length - 1 ) {
2552
+ // The last frame in this .animate call stores the promise resolve
2553
+ // and reject callbacks. This is how we ensure that the animation
2554
+ // loop (which may exist as a result of a *different* .animate call)
2555
+ // still resolves or rejecdts this .animate call's promise. once it's
2556
+ // complete.
2548
2557
nextFrame . onComplete = resolve ;
2549
2558
nextFrame . onInterrupt = reject ;
2550
2559
}
2551
2560
2552
2561
trans . _frameQueue . push ( nextFrame ) ;
2553
2562
}
2554
2563
2564
+ // Set it as never having transitioned to a frame. This will cause the animation
2565
+ // loop to immediately transition to the next frame (which, for immediate mode,
2566
+ // is the first frame in the list since all others would have been discarded
2567
+ // below)
2555
2568
if ( animationOpts . mode === 'immediate' ) {
2556
2569
trans . _lastFrameAt = - Infinity ;
2557
2570
}
2558
2571
2572
+ // Only it's not already running, start a RAF loop. This could be avoided in the
2573
+ // case that there's only one frame, but it significantly complicated the logic
2574
+ // and only sped things up by about 5% or so for a lorenz attractor simulation.
2575
+ // It would be a fine thing to implement, but the benefit of that optimization
2576
+ // doesn't seem worth the extra complexity.
2559
2577
if ( ! trans . _animationRaf ) {
2560
2578
beginAnimationLoop ( ) ;
2561
2579
}
2562
2580
}
2563
2581
2564
2582
function stopAnimationLoop ( ) {
2583
+ gd . emit ( 'plotly_animated' ) ;
2584
+
2585
+ // Be sure to unset also since it's how we know whether a loop is already running:
2565
2586
window . cancelAnimationFrame ( trans . _animationRaf ) ;
2566
2587
trans . _animationRaf = null ;
2567
2588
}
2568
2589
2569
2590
function nextFrame ( ) {
2570
- if ( trans . _currentFrame ) {
2571
- if ( trans . _currentFrame . onComplete ) {
2572
- trans . _currentFrame . onComplete ( ) ;
2573
- }
2591
+ if ( trans . _currentFrame && trans . _currentFrame . onComplete ) {
2592
+ // Execute the callback and unset it to ensure it doesn't
2593
+ // accidentally get called twice
2594
+ trans . _currentFrame . onComplete ( ) ;
2595
+ trans . _currentFrame . onComplete = null ;
2574
2596
}
2575
2597
2576
2598
var newFrame = trans . _currentFrame = trans . _frameQueue . shift ( ) ;
@@ -2579,61 +2601,60 @@ Plotly.animate = function(gd, frameOrGroupNameOrFrameList, animationOpts) {
2579
2601
trans . _lastFrameAt = Date . now ( ) ;
2580
2602
trans . _timeToNext = newFrame . frameOpts . duration ;
2581
2603
2604
+ // This is simply called and it's left to .transition to decide how to manage
2605
+ // interrupting current transitions. That means we don't need to worry about
2606
+ // how it resolves or what happens after this:
2582
2607
Plots . transition ( gd ,
2583
2608
newFrame . frame . data ,
2584
2609
newFrame . frame . layout ,
2585
2610
newFrame . frame . traces ,
2586
2611
newFrame . frameOpts ,
2587
2612
newFrame . transitionOpts
2588
- ) . then ( function ( ) {
2589
- if ( trans . _frameQueue . length === 0 ) {
2590
- gd . emit ( 'plotly_animated' ) ;
2591
- if ( trans . _currentFrame && trans . _currentFrame . onComplete ) {
2592
- trans . _currentFrame . onComplete ( ) ;
2593
- trans . _currentFrame = null ;
2594
- }
2595
- }
2596
- } ) ;
2597
- }
2598
-
2599
- if ( trans . _frameQueue . length === 0 ) {
2613
+ ) ;
2614
+ } else {
2615
+ // If there are no more frames, then stop the RAF loop:
2600
2616
stopAnimationLoop ( ) ;
2601
2617
}
2602
2618
}
2603
2619
2604
2620
function beginAnimationLoop ( ) {
2605
2621
gd . emit ( 'plotly_animating' ) ;
2606
2622
2607
- // If no timer is running, then set last frame = long ago:
2623
+ // If no timer is running, then set last frame = long ago so that the next
2624
+ // frame is immediately transitioned:
2608
2625
trans . _lastFrameAt = - Infinity ;
2609
2626
trans . _timeToNext = 0 ;
2610
2627
trans . _runningTransitions = 0 ;
2611
2628
trans . _currentFrame = null ;
2612
2629
2613
2630
var doFrame = function ( ) {
2614
- // Check if we need to pop a frame:
2631
+ // This *must* be requested before nextFrame since nextFrame may decide
2632
+ // to cancel it if there's nothing more to animated:
2633
+ trans . _animationRaf = window . requestAnimationFrame ( doFrame ) ;
2634
+
2635
+ // Check if we're ready for a new frame:
2615
2636
if ( Date . now ( ) - trans . _lastFrameAt > trans . _timeToNext ) {
2616
2637
nextFrame ( ) ;
2617
2638
}
2618
-
2619
- trans . _animationRaf = window . requestAnimationFrame ( doFrame ) ;
2620
2639
} ;
2621
2640
2622
- return doFrame ( ) ;
2641
+ doFrame ( ) ;
2623
2642
}
2624
2643
2625
- var counter = 0 ;
2644
+ // This is an animate-local counter that helps match up option input list
2645
+ // items with the particular frame.
2646
+ var configCounter = 0 ;
2626
2647
function setTransitionConfig ( frame ) {
2627
2648
if ( Array . isArray ( transitionOpts ) ) {
2628
- if ( counter >= transitionOpts . length ) {
2629
- frame . transitionOpts = transitionOpts [ counter ] ;
2649
+ if ( configCounter >= transitionOpts . length ) {
2650
+ frame . transitionOpts = transitionOpts [ configCounter ] ;
2630
2651
} else {
2631
2652
frame . transitionOpts = transitionOpts [ 0 ] ;
2632
2653
}
2633
2654
} else {
2634
2655
frame . transitionOpts = transitionOpts ;
2635
2656
}
2636
- counter ++ ;
2657
+ configCounter ++ ;
2637
2658
return frame ;
2638
2659
}
2639
2660
@@ -2684,13 +2705,17 @@ Plotly.animate = function(gd, frameOrGroupNameOrFrameList, animationOpts) {
2684
2705
}
2685
2706
}
2686
2707
2708
+ // If the mode is either next or immediate, then all currently queued frames must
2709
+ // be dumped and the corresponding .animate promises rejected.
2687
2710
if ( [ 'next' , 'immediate' ] . indexOf ( animationOpts . mode ) !== - 1 ) {
2688
2711
discardExistingFrames ( ) ;
2689
2712
}
2690
2713
2691
2714
if ( frameList . length > 0 ) {
2692
2715
queueFrames ( frameList ) ;
2693
2716
} else {
2717
+ // This is the case where there were simply no frames. It's a little strange
2718
+ // since there's not much to do:
2694
2719
gd . emit ( 'plotly_animated' ) ;
2695
2720
resolve ( ) ;
2696
2721
}
0 commit comments