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