Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 34b6ea3

Browse filesBrowse files
committed
Import JSAnimation into the animation module.
This pulls http://github.com/jakevdp/JSAnimation into the code. Most of this is in the HTMLWriter class. This also adds the `jshtml` option for the animation.html setting.
1 parent 4f73376 commit 34b6ea3
Copy full SHA for 34b6ea3

File tree

Expand file treeCollapse file tree

4 files changed

+362
-2
lines changed
Filter options
Expand file treeCollapse file tree

4 files changed

+362
-2
lines changed

‎lib/matplotlib/_animation_data.py

Copy file name to clipboard
+210Lines changed: 210 additions & 0 deletions
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+
"""

‎lib/matplotlib/animation.py

Copy file name to clipboardExpand all lines: lib/matplotlib/animation.py
+146Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,18 @@
3737
import abc
3838
import contextlib
3939
import tempfile
40+
import uuid
4041
import warnings
42+
from matplotlib._animation_data import (DISPLAY_TEMPLATE, INCLUDED_FRAMES,
43+
JS_INCLUDE)
4144
from matplotlib.cbook import iterable, deprecated
4245
from matplotlib.compat import subprocess
4346
from matplotlib import verbose
4447
from matplotlib import rcParams, rcParamsDefault, rc_context
48+
if sys.version_info < (3, 0):
49+
from cStringIO import StringIO as InMemory
50+
else:
51+
from io import BytesIO as InMemory
4552

4653
# Process creation flag for subprocess to prevent it raising a terminal
4754
# window. See for example:
@@ -876,6 +883,112 @@ def _args(self):
876883
+ self.output_args)
877884

878885

886+
# Taken directly from jakevdp's JSAnimation package at
887+
# http://github.com/jakevdp/JSAnimation
888+
def _included_frames(frame_list, frame_format):
889+
"""frame_list should be a list of filenames"""
890+
return INCLUDED_FRAMES.format(Nframes=len(frame_list),
891+
frame_dir=os.path.dirname(frame_list[0]),
892+
frame_format=frame_format)
893+
894+
895+
def _embedded_frames(frame_list, frame_format):
896+
"""frame_list should be a list of base64-encoded png files"""
897+
template = ' frames[{0}] = "data:image/{1};base64,{2}"\n'
898+
embedded = "\n"
899+
for i, frame_data in enumerate(frame_list):
900+
embedded += template.format(i, frame_format,
901+
frame_data.replace('\n', '\\\n'))
902+
return embedded
903+
904+
905+
@writers.register('html')
906+
class HTMLWriter(FileMovieWriter):
907+
supported_formats = ['png', 'jpeg', 'tiff', 'svg']
908+
args_key = 'animation.html_args'
909+
910+
@classmethod
911+
def isAvailable(cls):
912+
return True
913+
914+
def __init__(self, fps=30, codec=None, bitrate=None, extra_args=None,
915+
metadata=None, embed_frames=False, default_mode='loop'):
916+
self.embed_frames = embed_frames
917+
self.default_mode = default_mode.lower()
918+
919+
if self.default_mode not in ['loop', 'once', 'reflect']:
920+
self.default_mode = 'loop'
921+
import warnings
922+
warnings.warn("unrecognized default_mode: using 'loop'")
923+
924+
self._saved_frames = list()
925+
super(HTMLWriter, self).__init__(fps, codec, bitrate,
926+
extra_args, metadata)
927+
928+
def setup(self, fig, outfile, dpi, frame_dir=None):
929+
if os.path.splitext(outfile)[-1] not in ['.html', '.htm']:
930+
raise ValueError("outfile must be *.htm or *.html")
931+
932+
if not self.embed_frames:
933+
if frame_dir is None:
934+
frame_dir = outfile.rstrip('.html') + '_frames'
935+
if not os.path.exists(frame_dir):
936+
os.makedirs(frame_dir)
937+
frame_prefix = os.path.join(frame_dir, 'frame')
938+
else:
939+
frame_prefix = None
940+
941+
super(HTMLWriter, self).setup(fig, outfile, dpi,
942+
frame_prefix, clear_temp=False)
943+
944+
def grab_frame(self, **savefig_kwargs):
945+
if self.embed_frames:
946+
suffix = '.' + self.frame_format
947+
f = InMemory()
948+
self.fig.savefig(f, format=self.frame_format,
949+
dpi=self.dpi, **savefig_kwargs)
950+
f.seek(0)
951+
imgdata64 = encodebytes(f.read()).decode('ascii')
952+
self._saved_frames.append(imgdata64)
953+
else:
954+
return super(HTMLWriter, self).grab_frame(**savefig_kwargs)
955+
956+
def _run(self):
957+
# make a duck-typed subprocess stand in
958+
# this is called by the MovieWriter base class, but not used here.
959+
class ProcessStandin(object):
960+
returncode = 0
961+
962+
def communicate(self):
963+
return '', ''
964+
965+
self._proc = ProcessStandin()
966+
967+
# save the frames to an html file
968+
if self.embed_frames:
969+
fill_frames = _embedded_frames(self._saved_frames,
970+
self.frame_format)
971+
else:
972+
# temp names is filled by FileMovieWriter
973+
fill_frames = _included_frames(self._temp_names,
974+
self.frame_format)
975+
976+
mode_dict = dict(once_checked='',
977+
loop_checked='',
978+
reflect_checked='')
979+
mode_dict[self.default_mode + '_checked'] = 'checked'
980+
981+
interval = int(1000. / self.fps)
982+
983+
with open(self.outfile, 'w') as of:
984+
of.write(JS_INCLUDE)
985+
of.write(DISPLAY_TEMPLATE.format(id=uuid.uuid4().hex,
986+
Nframes=len(self._temp_names),
987+
fill_frames=fill_frames,
988+
interval=interval,
989+
**mode_dict))
990+
991+
879992
class Animation(object):
880993
'''This class wraps the creation of an animation using matplotlib.
881994
@@ -1288,11 +1401,44 @@ def to_html5_video(self):
12881401
size=self._video_size,
12891402
options=' '.join(options))
12901403

1404+
def to_jshtml(self, fps=None, embed_frames=True, default_mode=None):
1405+
"""Generate HTML representation of the animation"""
1406+
if fps is None and hasattr(self, '_interval'):
1407+
# Convert interval in ms to frames per second
1408+
fps = 1000. / self._interval
1409+
1410+
# If we're not given a default mode, choose one base on the value of
1411+
# the repeat attribute
1412+
if default_mode is None:
1413+
default_mode = 'loop' if self.repeat else 'once'
1414+
1415+
if hasattr(self, "_html_representation"):
1416+
return self._html_representation
1417+
else:
1418+
# Can't open a second time while opened on windows. So we avoid
1419+
# deleting when closed, and delete manually later.
1420+
with tempfile.NamedTemporaryFile(suffix='.html',
1421+
delete=False) as f:
1422+
self.save(f.name, writer=HTMLWriter(fps=fps,
1423+
embed_frames=embed_frames,
1424+
default_mode=default_mode))
1425+
# Re-open and get content
1426+
with open(f.name) as fobj:
1427+
html = fobj.read()
1428+
1429+
# Now we can delete
1430+
os.remove(f.name)
1431+
1432+
self._html_representation = html
1433+
return html
1434+
12911435
def _repr_html_(self):
12921436
'''IPython display hook for rendering.'''
12931437
fmt = rcParams['animation.html']
12941438
if fmt == 'html5':
12951439
return self.to_html5_video()
1440+
elif fmt == 'jshtml':
1441+
return self.to_jshtml()
12961442

12971443

12981444
class TimedAnimation(Animation):

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.