Skip to content

Import JSAnimation into the animation module. (Fixes #4703) #4821

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Sep 1, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions doc/users/next_whats_new/js-animation.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Merge JSAnimation
-----------------

Jake Vanderplas' JSAnimation package has been merged into matplotlib. This
adds to matplotlib the `~matplotlib.animation.HTMLWriter` class for
generating a javascript HTML animation, suitable for the IPython notebook.
This can be activated by default by setting the ``animation.html`` rc
parameter to ``jshtml``. One can also call the ``anim_to_jshtml`` function
to manually convert an animation. This can be displayed using IPython's
``HTML`` display class::

from IPython.display import HTML
HTML(animation.anim_to_jshtml(anim))

The `~matplotlib.animation.HTMLWriter` class can also be used to generate
an HTML file by asking for the ``html`` writer.
210 changes: 210 additions & 0 deletions lib/matplotlib/_animation_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
# Javascript template for HTMLWriter
JS_INCLUDE = """
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/
css/font-awesome.min.css">
<script language="javascript">
/* Define the Animation class */
function Animation(frames, img_id, slider_id, interval, loop_select_id){
this.img_id = img_id;
this.slider_id = slider_id;
this.loop_select_id = loop_select_id;
this.interval = interval;
this.current_frame = 0;
this.direction = 0;
this.timer = null;
this.frames = new Array(frames.length);

for (var i=0; i<frames.length; i++)
{
this.frames[i] = new Image();
this.frames[i].src = frames[i];
}
document.getElementById(this.slider_id).max = this.frames.length - 1;
this.set_frame(this.current_frame);
}

Animation.prototype.get_loop_state = function(){
var button_group = document[this.loop_select_id].state;
for (var i = 0; i < button_group.length; i++) {
var button = button_group[i];
if (button.checked) {
return button.value;
}
}
return undefined;
}

Animation.prototype.set_frame = function(frame){
this.current_frame = frame;
document.getElementById(this.img_id).src =
this.frames[this.current_frame].src;
document.getElementById(this.slider_id).value = this.current_frame;
}

Animation.prototype.next_frame = function()
{
this.set_frame(Math.min(this.frames.length - 1, this.current_frame + 1));
}

Animation.prototype.previous_frame = function()
{
this.set_frame(Math.max(0, this.current_frame - 1));
}

Animation.prototype.first_frame = function()
{
this.set_frame(0);
}

Animation.prototype.last_frame = function()
{
this.set_frame(this.frames.length - 1);
}

Animation.prototype.slower = function()
{
this.interval /= 0.7;
if(this.direction > 0){this.play_animation();}
else if(this.direction < 0){this.reverse_animation();}
}

Animation.prototype.faster = function()
{
this.interval *= 0.7;
if(this.direction > 0){this.play_animation();}
else if(this.direction < 0){this.reverse_animation();}
}

Animation.prototype.anim_step_forward = function()
{
this.current_frame += 1;
if(this.current_frame < this.frames.length){
this.set_frame(this.current_frame);
}else{
var loop_state = this.get_loop_state();
if(loop_state == "loop"){
this.first_frame();
}else if(loop_state == "reflect"){
this.last_frame();
this.reverse_animation();
}else{
this.pause_animation();
this.last_frame();
}
}
}

Animation.prototype.anim_step_reverse = function()
{
this.current_frame -= 1;
if(this.current_frame >= 0){
this.set_frame(this.current_frame);
}else{
var loop_state = this.get_loop_state();
if(loop_state == "loop"){
this.last_frame();
}else if(loop_state == "reflect"){
this.first_frame();
this.play_animation();
}else{
this.pause_animation();
this.first_frame();
}
}
}

Animation.prototype.pause_animation = function()
{
this.direction = 0;
if (this.timer){
clearInterval(this.timer);
this.timer = null;
}
}

Animation.prototype.play_animation = function()
{
this.pause_animation();
this.direction = 1;
var t = this;
if (!this.timer) this.timer = setInterval(function() {
t.anim_step_forward();
}, this.interval);
}

Animation.prototype.reverse_animation = function()
{
this.pause_animation();
this.direction = -1;
var t = this;
if (!this.timer) this.timer = setInterval(function() {
t.anim_step_reverse();
}, this.interval);
}
</script>
"""


# HTML template for HTMLWriter
DISPLAY_TEMPLATE = """
<div class="animation" align="center">
<img id="_anim_img{id}">
<br>
<input id="_anim_slider{id}" type="range" style="width:350px"
name="points" min="0" max="1" step="1" value="0"
onchange="anim{id}.set_frame(parseInt(this.value));"></input>
<br>
<button onclick="anim{id}.slower()"><i class="fa fa-minus"></i></button>
<button onclick="anim{id}.first_frame()"><i class="fa fa-fast-backward">
</i></button>
<button onclick="anim{id}.previous_frame()">
<i class="fa fa-step-backward"></i></button>
<button onclick="anim{id}.reverse_animation()">
<i class="fa fa-play fa-flip-horizontal"></i></button>
<button onclick="anim{id}.pause_animation()"><i class="fa fa-pause">
</i></button>
<button onclick="anim{id}.play_animation()"><i class="fa fa-play"></i>
</button>
<button onclick="anim{id}.next_frame()"><i class="fa fa-step-forward">
</i></button>
<button onclick="anim{id}.last_frame()"><i class="fa fa-fast-forward">
</i></button>
<button onclick="anim{id}.faster()"><i class="fa fa-plus"></i></button>
<form action="#n" name="_anim_loop_select{id}" class="anim_control">
<input type="radio" name="state"
value="once" {once_checked}> Once </input>
<input type="radio" name="state"
value="loop" {loop_checked}> Loop </input>
<input type="radio" name="state"
value="reflect" {reflect_checked}> Reflect </input>
</form>
</div>


<script language="javascript">
/* Instantiate the Animation class. */
/* The IDs given should match those used in the template above. */
(function() {{
var img_id = "_anim_img{id}";
var slider_id = "_anim_slider{id}";
var loop_select_id = "_anim_loop_select{id}";
var frames = new Array({Nframes});
{fill_frames}

/* set a timeout to make sure all the above elements are created before
the object is initialized. */
setTimeout(function() {{
anim{id} = new Animation(frames, img_id, slider_id, {interval},
loop_select_id);
}}, 0);
}})()
</script>
"""

INCLUDED_FRAMES = """
for (var i=0; i<{Nframes}; i++){{
frames[i] = "{frame_dir}/frame" + ("0000000" + i).slice(-7) +
".{frame_format}";
}}
"""
Loading