Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
52867 views
1
import os
2
import sys
3
import random
4
import string
5
import warnings
6
if sys.version_info < (3, 0):
7
from cStringIO import StringIO as InMemory
8
else:
9
from io import BytesIO as InMemory
10
from matplotlib.animation import writers, FileMovieWriter
11
from base64 import b64encode
12
13
14
ICON_DIR = os.path.join(os.path.dirname(__file__), 'icons')
15
16
17
class _Icons(object):
18
"""This class is a container for base64 representations of the icons"""
19
icons = ['first', 'prev', 'reverse', 'pause', 'play', 'next', 'last']
20
21
def __init__(self, icon_dir=ICON_DIR, extension='png'):
22
self.icon_dir = icon_dir
23
self.extension = extension
24
for icon in self.icons:
25
setattr(self, icon,
26
self._load_base64('{0}.{1}'.format(icon, extension)))
27
28
def _load_base64(self, filename):
29
data = open(os.path.join(self.icon_dir, filename), 'rb').read()
30
return 'data:image/{0};base64,{1}'.format(self.extension,
31
b64encode(data).decode('ascii'))
32
33
34
JS_INCLUDE = """
35
<script language="javascript">
36
/* Define the Animation class */
37
function Animation(frames, img_id, slider_id, interval, loop_select_id){
38
this.img_id = img_id;
39
this.slider_id = slider_id;
40
this.loop_select_id = loop_select_id;
41
this.interval = interval;
42
this.current_frame = 0;
43
this.direction = 0;
44
this.timer = null;
45
this.frames = new Array(frames.length);
46
47
for (var i=0; i<frames.length; i++)
48
{
49
this.frames[i] = new Image();
50
this.frames[i].src = frames[i];
51
}
52
document.getElementById(this.slider_id).max = this.frames.length - 1;
53
this.set_frame(this.current_frame);
54
}
55
56
Animation.prototype.get_loop_state = function(){
57
var button_group = document[this.loop_select_id].state;
58
for (var i = 0; i < button_group.length; i++) {
59
var button = button_group[i];
60
if (button.checked) {
61
return button.value;
62
}
63
}
64
return undefined;
65
}
66
67
Animation.prototype.set_frame = function(frame){
68
this.current_frame = frame;
69
document.getElementById(this.img_id).src = this.frames[this.current_frame].src;
70
document.getElementById(this.slider_id).value = this.current_frame;
71
}
72
73
Animation.prototype.next_frame = function()
74
{
75
this.set_frame(Math.min(this.frames.length - 1, this.current_frame + 1));
76
}
77
78
Animation.prototype.previous_frame = function()
79
{
80
this.set_frame(Math.max(0, this.current_frame - 1));
81
}
82
83
Animation.prototype.first_frame = function()
84
{
85
this.set_frame(0);
86
}
87
88
Animation.prototype.last_frame = function()
89
{
90
this.set_frame(this.frames.length - 1);
91
}
92
93
Animation.prototype.slower = function()
94
{
95
this.interval /= 0.7;
96
if(this.direction > 0){this.play_animation();}
97
else if(this.direction < 0){this.reverse_animation();}
98
}
99
100
Animation.prototype.faster = function()
101
{
102
this.interval *= 0.7;
103
if(this.direction > 0){this.play_animation();}
104
else if(this.direction < 0){this.reverse_animation();}
105
}
106
107
Animation.prototype.anim_step_forward = function()
108
{
109
this.current_frame += 1;
110
if(this.current_frame < this.frames.length){
111
this.set_frame(this.current_frame);
112
}else{
113
var loop_state = this.get_loop_state();
114
if(loop_state == "loop"){
115
this.first_frame();
116
}else if(loop_state == "reflect"){
117
this.last_frame();
118
this.reverse_animation();
119
}else{
120
this.pause_animation();
121
this.last_frame();
122
}
123
}
124
}
125
126
Animation.prototype.anim_step_reverse = function()
127
{
128
this.current_frame -= 1;
129
if(this.current_frame >= 0){
130
this.set_frame(this.current_frame);
131
}else{
132
var loop_state = this.get_loop_state();
133
if(loop_state == "loop"){
134
this.last_frame();
135
}else if(loop_state == "reflect"){
136
this.first_frame();
137
this.play_animation();
138
}else{
139
this.pause_animation();
140
this.first_frame();
141
}
142
}
143
}
144
145
Animation.prototype.pause_animation = function()
146
{
147
this.direction = 0;
148
if (this.timer){
149
clearInterval(this.timer);
150
this.timer = null;
151
}
152
}
153
154
Animation.prototype.play_animation = function()
155
{
156
this.pause_animation();
157
this.direction = 1;
158
var t = this;
159
if (!this.timer) this.timer = setInterval(function(){t.anim_step_forward();}, this.interval);
160
}
161
162
Animation.prototype.reverse_animation = function()
163
{
164
this.pause_animation();
165
this.direction = -1;
166
var t = this;
167
if (!this.timer) this.timer = setInterval(function(){t.anim_step_reverse();}, this.interval);
168
}
169
</script>
170
"""
171
172
173
DISPLAY_TEMPLATE = """
174
<div class="animation" align="center">
175
<img id="_anim_img{id}">
176
<br>
177
<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>
178
<br>
179
<button onclick="anim{id}.slower()">&#8211;</button>
180
<button onclick="anim{id}.first_frame()"><img class="anim_icon" src="{icons.first}"></button>
181
<button onclick="anim{id}.previous_frame()"><img class="anim_icon" src="{icons.prev}"></button>
182
<button onclick="anim{id}.reverse_animation()"><img class="anim_icon" src="{icons.reverse}"></button>
183
<button onclick="anim{id}.pause_animation()"><img class="anim_icon" src="{icons.pause}"></button>
184
<button onclick="anim{id}.play_animation()"><img class="anim_icon" src="{icons.play}"></button>
185
<button onclick="anim{id}.next_frame()"><img class="anim_icon" src="{icons.next}"></button>
186
<button onclick="anim{id}.last_frame()"><img class="anim_icon" src="{icons.last}"></button>
187
<button onclick="anim{id}.faster()">+</button>
188
<form action="#n" name="_anim_loop_select{id}" class="anim_control">
189
<input type="radio" name="state" value="once" {once_checked}> Once </input>
190
<input type="radio" name="state" value="loop" {loop_checked}> Loop </input>
191
<input type="radio" name="state" value="reflect" {reflect_checked}> Reflect </input>
192
</form>
193
</div>
194
195
196
<script language="javascript">
197
/* Instantiate the Animation class. */
198
/* The IDs given should match those used in the template above. */
199
(function() {{
200
var img_id = "_anim_img{id}";
201
var slider_id = "_anim_slider{id}";
202
var loop_select_id = "_anim_loop_select{id}";
203
var frames = new Array({Nframes});
204
{fill_frames}
205
206
/* set a timeout to make sure all the above elements are created before
207
the object is initialized. */
208
setTimeout(function() {{
209
anim{id} = new Animation(frames, img_id, slider_id, {interval}, loop_select_id);
210
}}, 0);
211
}})()
212
</script>
213
"""
214
215
INCLUDED_FRAMES = """
216
for (var i=0; i<{Nframes}; i++){{
217
frames[i] = "{frame_dir}/frame" + ("0000000" + i).slice(-7) + ".{frame_format}";
218
}}
219
"""
220
221
222
def _included_frames(frame_list, frame_format):
223
"""frame_list should be a list of filenames"""
224
return INCLUDED_FRAMES.format(Nframes=len(frame_list),
225
frame_dir=os.path.dirname(frame_list[0]),
226
frame_format=frame_format)
227
228
229
def _embedded_frames(frame_list, frame_format):
230
"""frame_list should be a list of base64-encoded png files"""
231
template = ' frames[{0}] = "data:image/{1};base64,{2}"\n'
232
embedded = "\n"
233
for i, frame_data in enumerate(frame_list):
234
embedded += template.format(i, frame_format,
235
frame_data.replace('\n', '\\\n'))
236
return embedded
237
238
239
@writers.register('html')
240
class HTMLWriter(FileMovieWriter):
241
# we start the animation id count at a random number: this way, if two
242
# animations are meant to be included on one HTML page, there is a
243
# very small chance of conflict.
244
rng = random.Random()
245
exec_key = 'animation.ffmpeg_path'
246
args_key = 'animation.ffmpeg_args'
247
supported_formats = ['png', 'jpeg', 'tiff', 'svg']
248
249
@classmethod
250
def new_id(cls):
251
#return '%16x' % cls.rng.getrandbits(64)
252
return ''.join(cls.rng.choice(string.ascii_uppercase)
253
for x in range(16))
254
255
def __init__(self, fps=30, codec=None, bitrate=None, extra_args=None,
256
metadata=None, embed_frames=False, default_mode='loop'):
257
self.embed_frames = embed_frames
258
self.default_mode = default_mode.lower()
259
260
if self.default_mode not in ['loop', 'once', 'reflect']:
261
self.default_mode = 'loop'
262
warnings.warn("unrecognized default_mode: using 'loop'")
263
264
self._saved_frames = list()
265
super(HTMLWriter, self).__init__(fps, codec, bitrate,
266
extra_args, metadata)
267
268
def setup(self, fig, outfile, dpi, frame_dir=None):
269
if os.path.splitext(outfile)[-1] not in ['.html', '.htm']:
270
raise ValueError("outfile must be *.htm or *.html")
271
272
if not self.embed_frames:
273
if frame_dir is None:
274
frame_dir = outfile.rstrip('.html') + '_frames'
275
if not os.path.exists(frame_dir):
276
os.makedirs(frame_dir)
277
frame_prefix = os.path.join(frame_dir, 'frame')
278
else:
279
frame_prefix = None
280
281
super(HTMLWriter, self).setup(fig, outfile, dpi,
282
frame_prefix, clear_temp=False)
283
284
def grab_frame(self, **savefig_kwargs):
285
if self.embed_frames:
286
suffix = '.' + self.frame_format
287
f = InMemory()
288
self.fig.savefig(f, format=self.frame_format,
289
dpi=self.dpi, **savefig_kwargs)
290
f.seek(0)
291
self._saved_frames.append(b64encode(f.read()).decode('ascii'))
292
else:
293
return super(HTMLWriter, self).grab_frame(**savefig_kwargs)
294
295
def _run(self):
296
# make a ducktyped subprocess standin
297
# this is called by the MovieWriter base class, but not used here.
298
class ProcessStandin(object):
299
returncode = 0
300
def communicate(self):
301
return ('', '')
302
self._proc = ProcessStandin()
303
304
# save the frames to an html file
305
if self.embed_frames:
306
fill_frames = _embedded_frames(self._saved_frames,
307
self.frame_format)
308
else:
309
# temp names is filled by FileMovieWriter
310
fill_frames = _included_frames(self._temp_names,
311
self.frame_format)
312
313
mode_dict = dict(once_checked='',
314
loop_checked='',
315
reflect_checked='')
316
mode_dict[self.default_mode + '_checked'] = 'checked'
317
318
interval = int(1000. / self.fps)
319
320
with open(self.outfile, 'w') as of:
321
of.write(JS_INCLUDE)
322
of.write(DISPLAY_TEMPLATE.format(id=self.new_id(),
323
Nframes=len(self._temp_names),
324
fill_frames=fill_frames,
325
interval=interval,
326
icons=_Icons(),
327
**mode_dict))
328
329