Skip to content

Commit e5b9f7d

Browse files
authored
Merge pull request #4821 from dopplershift/add-jsanimation
ENH: vendor JSAnimation into the animation module. Fixes #4703
2 parents 751c9af + ffb2ccd commit e5b9f7d

File tree

6 files changed

+439
-14
lines changed

6 files changed

+439
-14
lines changed
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Merge JSAnimation
2+
-----------------
3+
4+
Jake Vanderplas' JSAnimation package has been merged into matplotlib. This
5+
adds to matplotlib the `~matplotlib.animation.HTMLWriter` class for
6+
generating a javascript HTML animation, suitable for the IPython notebook.
7+
This can be activated by default by setting the ``animation.html`` rc
8+
parameter to ``jshtml``. One can also call the ``anim_to_jshtml`` function
9+
to manually convert an animation. This can be displayed using IPython's
10+
``HTML`` display class::
11+
12+
from IPython.display import HTML
13+
HTML(animation.anim_to_jshtml(anim))
14+
15+
The `~matplotlib.animation.HTMLWriter` class can also be used to generate
16+
an HTML file by asking for the ``html`` writer.

lib/matplotlib/_animation_data.py

+210
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
# Javascript template for HTMLWriter
2+
JS_INCLUDE = """
3+
<link rel="stylesheet"
4+
href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/
5+
css/font-awesome.min.css">
6+
<script language="javascript">
7+
/* Define the Animation class */
8+
function Animation(frames, img_id, slider_id, interval, loop_select_id){
9+
this.img_id = img_id;
10+
this.slider_id = slider_id;
11+
this.loop_select_id = loop_select_id;
12+
this.interval = interval;
13+
this.current_frame = 0;
14+
this.direction = 0;
15+
this.timer = null;
16+
this.frames = new Array(frames.length);
17+
18+
for (var i=0; i<frames.length; i++)
19+
{
20+
this.frames[i] = new Image();
21+
this.frames[i].src = frames[i];
22+
}
23+
document.getElementById(this.slider_id).max = this.frames.length - 1;
24+
this.set_frame(this.current_frame);
25+
}
26+
27+
Animation.prototype.get_loop_state = function(){
28+
var button_group = document[this.loop_select_id].state;
29+
for (var i = 0; i < button_group.length; i++) {
30+
var button = button_group[i];
31+
if (button.checked) {
32+
return button.value;
33+
}
34+
}
35+
return undefined;
36+
}
37+
38+
Animation.prototype.set_frame = function(frame){
39+
this.current_frame = frame;
40+
document.getElementById(this.img_id).src =
41+
this.frames[this.current_frame].src;
42+
document.getElementById(this.slider_id).value = this.current_frame;
43+
}
44+
45+
Animation.prototype.next_frame = function()
46+
{
47+
this.set_frame(Math.min(this.frames.length - 1, this.current_frame + 1));
48+
}
49+
50+
Animation.prototype.previous_frame = function()
51+
{
52+
this.set_frame(Math.max(0, this.current_frame - 1));
53+
}
54+
55+
Animation.prototype.first_frame = function()
56+
{
57+
this.set_frame(0);
58+
}
59+
60+
Animation.prototype.last_frame = function()
61+
{
62+
this.set_frame(this.frames.length - 1);
63+
}
64+
65+
Animation.prototype.slower = function()
66+
{
67+
this.interval /= 0.7;
68+
if(this.direction > 0){this.play_animation();}
69+
else if(this.direction < 0){this.reverse_animation();}
70+
}
71+
72+
Animation.prototype.faster = function()
73+
{
74+
this.interval *= 0.7;
75+
if(this.direction > 0){this.play_animation();}
76+
else if(this.direction < 0){this.reverse_animation();}
77+
}
78+
79+
Animation.prototype.anim_step_forward = function()
80+
{
81+
this.current_frame += 1;
82+
if(this.current_frame < this.frames.length){
83+
this.set_frame(this.current_frame);
84+
}else{
85+
var loop_state = this.get_loop_state();
86+
if(loop_state == "loop"){
87+
this.first_frame();
88+
}else if(loop_state == "reflect"){
89+
this.last_frame();
90+
this.reverse_animation();
91+
}else{
92+
this.pause_animation();
93+
this.last_frame();
94+
}
95+
}
96+
}
97+
98+
Animation.prototype.anim_step_reverse = function()
99+
{
100+
this.current_frame -= 1;
101+
if(this.current_frame >= 0){
102+
this.set_frame(this.current_frame);
103+
}else{
104+
var loop_state = this.get_loop_state();
105+
if(loop_state == "loop"){
106+
this.last_frame();
107+
}else if(loop_state == "reflect"){
108+
this.first_frame();
109+
this.play_animation();
110+
}else{
111+
this.pause_animation();
112+
this.first_frame();
113+
}
114+
}
115+
}
116+
117+
Animation.prototype.pause_animation = function()
118+
{
119+
this.direction = 0;
120+
if (this.timer){
121+
clearInterval(this.timer);
122+
this.timer = null;
123+
}
124+
}
125+
126+
Animation.prototype.play_animation = function()
127+
{
128+
this.pause_animation();
129+
this.direction = 1;
130+
var t = this;
131+
if (!this.timer) this.timer = setInterval(function() {
132+
t.anim_step_forward();
133+
}, this.interval);
134+
}
135+
136+
Animation.prototype.reverse_animation = function()
137+
{
138+
this.pause_animation();
139+
this.direction = -1;
140+
var t = this;
141+
if (!this.timer) this.timer = setInterval(function() {
142+
t.anim_step_reverse();
143+
}, this.interval);
144+
}
145+
</script>
146+
"""
147+
148+
149+
# HTML template for HTMLWriter
150+
DISPLAY_TEMPLATE = """
151+
<div class="animation" align="center">
152+
<img id="_anim_img{id}">
153+
<br>
154+
<input id="_anim_slider{id}" type="range" style="width:350px"
155+
name="points" min="0" max="1" step="1" value="0"
156+
onchange="anim{id}.set_frame(parseInt(this.value));"></input>
157+
<br>
158+
<button onclick="anim{id}.slower()"><i class="fa fa-minus"></i></button>
159+
<button onclick="anim{id}.first_frame()"><i class="fa fa-fast-backward">
160+
</i></button>
161+
<button onclick="anim{id}.previous_frame()">
162+
<i class="fa fa-step-backward"></i></button>
163+
<button onclick="anim{id}.reverse_animation()">
164+
<i class="fa fa-play fa-flip-horizontal"></i></button>
165+
<button onclick="anim{id}.pause_animation()"><i class="fa fa-pause">
166+
</i></button>
167+
<button onclick="anim{id}.play_animation()"><i class="fa fa-play"></i>
168+
</button>
169+
<button onclick="anim{id}.next_frame()"><i class="fa fa-step-forward">
170+
</i></button>
171+
<button onclick="anim{id}.last_frame()"><i class="fa fa-fast-forward">
172+
</i></button>
173+
<button onclick="anim{id}.faster()"><i class="fa fa-plus"></i></button>
174+
<form action="#n" name="_anim_loop_select{id}" class="anim_control">
175+
<input type="radio" name="state"
176+
value="once" {once_checked}> Once </input>
177+
<input type="radio" name="state"
178+
value="loop" {loop_checked}> Loop </input>
179+
<input type="radio" name="state"
180+
value="reflect" {reflect_checked}> Reflect </input>
181+
</form>
182+
</div>
183+
184+
185+
<script language="javascript">
186+
/* Instantiate the Animation class. */
187+
/* The IDs given should match those used in the template above. */
188+
(function() {{
189+
var img_id = "_anim_img{id}";
190+
var slider_id = "_anim_slider{id}";
191+
var loop_select_id = "_anim_loop_select{id}";
192+
var frames = new Array({Nframes});
193+
{fill_frames}
194+
195+
/* set a timeout to make sure all the above elements are created before
196+
the object is initialized. */
197+
setTimeout(function() {{
198+
anim{id} = new Animation(frames, img_id, slider_id, {interval},
199+
loop_select_id);
200+
}}, 0);
201+
}})()
202+
</script>
203+
"""
204+
205+
INCLUDED_FRAMES = """
206+
for (var i=0; i<{Nframes}; i++){{
207+
frames[i] = "{frame_dir}/frame" + ("0000000" + i).slice(-7) +
208+
".{frame_format}";
209+
}}
210+
"""

0 commit comments

Comments
 (0)