diplotocus 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
diplotocus/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from ._version import __version__
2
+ from .core import *
3
+ import diplotocus.easings as easings
diplotocus/_version.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.0.1"
@@ -0,0 +1,599 @@
1
+ import numpy as np
2
+
3
+ #----------------------------------------------
4
+ # ANIMATION CLASSES
5
+ #----------------------------------------------
6
+
7
+ class Animation:
8
+ def __init__(self,duration,delay=0,easing=None,axis=None,**kwargs):
9
+ self.anim_function = None
10
+ self.delay = delay
11
+ self.duration = duration
12
+ self.set_easing(easing)
13
+ self.compute_timings()
14
+ self.set_axis(axis)
15
+ self.anim_type = 'show'
16
+ self.obj = None
17
+ kwargs = self.clean_kwargs(kwargs)
18
+ self.kwargs = kwargs
19
+ self.anim_function = self.function
20
+
21
+ def set_axis(self,axis):
22
+ self.axis = axis
23
+ return self
24
+
25
+ def compute_timings(self):
26
+ self.x_min = self.delay
27
+ self.x_max = self.duration + self.delay
28
+
29
+ def set_duration(self,duration):
30
+ self.duration = duration
31
+ self.compute_timings()
32
+ return self
33
+
34
+ def set_delay(self,delay):
35
+ self.delay = delay
36
+ self.compute_timings()
37
+ return self
38
+
39
+ def set_easing(self,easing):
40
+ self.easing = easing
41
+ return self
42
+
43
+ def apply(self,x):
44
+ if x < self.x_min or x >= self.x_max:
45
+ return
46
+ if self.duration == 1:
47
+ self.anim_function(1)
48
+ return
49
+ t = self.easing.ease((x-self.delay)/(self.duration-1))
50
+ self.anim_function(t)
51
+
52
+ def initialize(self):
53
+ self.clean(0)
54
+
55
+ def show(self):
56
+ self.anim_type = 'show'
57
+ return self
58
+
59
+ def hide(self,delay=0,duration=None):
60
+ self.anim_type = 'hide'
61
+ self.set_delay(delay)
62
+ if duration is not None:
63
+ self.set_duration(duration)
64
+ return self
65
+
66
+ def spawn(self):
67
+ self.anim_type = 'spawn'
68
+ return self
69
+
70
+ def despawn(self,delay=0,duration=None):
71
+ self.anim_type = 'despawn'
72
+ self.set_delay(delay)
73
+ if duration is not None:
74
+ self.set_duration(duration)
75
+ return self
76
+
77
+ def grow(self):
78
+ self.anim_type = 'grow'
79
+ return self
80
+
81
+ def shrink(self,delay=0,duration=None):
82
+ self.anim_type = 'shrink'
83
+ self.set_delay(delay)
84
+ if duration is not None:
85
+ self.set_duration(duration)
86
+ return self
87
+
88
+ def update(self):
89
+ self.anim_type = 'update'
90
+ return self
91
+
92
+ def deupdate(self):
93
+ self.anim_type = 'deupdate'
94
+ return self
95
+
96
+ def morph(self,new_x,new_y,new_alpha=None):
97
+ self.new_x = new_x
98
+ self.new_y = new_y
99
+ self.new_alpha = new_alpha
100
+ self.anim_type = 'morph'
101
+ return self
102
+
103
+ def demorph(self):
104
+ self.anim_type = 'demorph'
105
+ return self
106
+
107
+ def reverse(self,delay=0,duration=None):
108
+ self.set_delay(delay)
109
+ if duration is not None:
110
+ self.set_duration(duration)
111
+ return self
112
+
113
+ def clean(self,x):
114
+ if x >= self.x_min-1 and x < self.x_max-1 and self.obj is not None:
115
+ if isinstance(self.obj,list):
116
+ for obj in self.obj:
117
+ obj.remove()
118
+ else:
119
+ self.obj.remove()
120
+ self.obj = None
121
+
122
+ def clean_kwargs(self,kwargs):
123
+ if 'duration' in kwargs:
124
+ del kwargs['duration']
125
+ if 'delay' in kwargs:
126
+ del kwargs['delay']
127
+ if 'easing' in kwargs:
128
+ del kwargs['delay']
129
+ if 'axis' in kwargs:
130
+ del kwargs['axis']
131
+ return kwargs
132
+
133
+ class zoom(Animation):
134
+ def __init__(self,zoom, *args, **kwargs):
135
+ super().__init__(*args, **kwargs)
136
+ self.zoom = 1/zoom
137
+
138
+ def reverse(self):
139
+ super().reverse()
140
+ self.zoom = 1/self.zoom
141
+ return self
142
+
143
+ def initialize(self):
144
+ self.width_init = self.axis.get_xlim()[1]-self.axis.get_xlim()[0]
145
+ self.height_init = self.axis.get_ylim()[1]-self.axis.get_ylim()[0]
146
+ self.width_end = self.width_init * self.zoom
147
+ self.height_end = self.height_init * self.zoom
148
+
149
+ def function(self,t):
150
+ center = ((self.axis.get_xlim()[1]+self.axis.get_xlim()[0])/2,(self.axis.get_ylim()[1]+self.axis.get_ylim()[0])/2)
151
+ width = self.width_init + (self.width_end - self.width_init)*t
152
+ height = self.height_init + (self.height_end - self.height_init)*t
153
+ x_range = (center[0] - width/2, center[0] + width/2)
154
+ y_range = (center[1] - height/2, center[1] + height/2)
155
+ self.axis.set_xlim(x_range[0],x_range[1])
156
+ self.axis.set_ylim(y_range[0],y_range[1])
157
+
158
+ class lims(Animation):
159
+ def __init__(self,xlim=None,ylim=None, *args, **kwargs):
160
+ if xlim is None and ylim is None:
161
+ raise ValueError('Both xlim and ylim cannot be empty.')
162
+ super().__init__(*args, **kwargs)
163
+ self.xlim = xlim
164
+ self.ylim = ylim
165
+
166
+ def initialize(self):
167
+ self.xlim_init = self.axis.get_xlim()
168
+ self.ylim_init = self.axis.get_ylim()
169
+ if self.xlim is not None:
170
+ self.xlim_end = self.xlim
171
+ else:
172
+ self.xlim_end = self.xlim_init
173
+ if self.ylim is not None:
174
+ self.ylim_end = self.ylim
175
+ else:
176
+ self.ylim_end = self.ylim_init
177
+
178
+ def function(self,t):
179
+ xlim_left = self.xlim_init[0] + (self.xlim_end[0] - self.xlim_init[0])*t
180
+ xlim_right = self.xlim_init[1] + (self.xlim_end[1] - self.xlim_init[1])*t
181
+ ylim_bottom = self.ylim_init[0] + (self.ylim_end[0] - self.ylim_init[0])*t
182
+ ylim_top = self.ylim_init[1] + (self.ylim_end[1] - self.ylim_init[1])*t
183
+ self.axis.set_xlim(xlim_left,xlim_right)
184
+ self.axis.set_ylim(ylim_bottom,ylim_top)
185
+
186
+ class move(Animation):
187
+ def __init__(self,pos, *args, **kwargs):
188
+ super().__init__(*args, **kwargs)
189
+ self.pos = pos
190
+ self.anim_function = self.function
191
+
192
+ def reverse(self):
193
+ super().reverse()
194
+ self.pos = self.pos_init
195
+ return self
196
+
197
+ def initialize(self):
198
+ width = self.axis.get_xlim()[1]-self.axis.get_xlim()[0]
199
+ height = self.axis.get_ylim()[1]-self.axis.get_ylim()[0]
200
+ self.pos_init = (self.axis.get_xlim()[0]+width/2,self.axis.get_ylim()[0]+height/2)
201
+ self.pos_end = self.pos
202
+
203
+ def function(self,t):
204
+ width = self.axis.get_xlim()[1]-self.axis.get_xlim()[0]
205
+ height = self.axis.get_ylim()[1]-self.axis.get_ylim()[0]
206
+ pos_x = self.pos_init[0] + (self.pos_end[0] - self.pos_init[0])*t
207
+ pos_y = self.pos_init[1] + (self.pos_end[1] - self.pos_init[1])*t
208
+ self.axis.set_xlim(pos_x-width/2,pos_x+width/2)
209
+ self.axis.set_ylim(pos_y-height/2,pos_y+height/2)
210
+
211
+ class scatter(Animation):
212
+ def __init__(self,x,y, *args, **kwargs):
213
+ super().__init__(*args, **kwargs)
214
+ self.x = x
215
+ self.y = y
216
+
217
+ def function(self,t):
218
+ kwargs = self.kwargs.copy()
219
+ if 'alpha' not in kwargs:
220
+ kwargs['alpha'] = 1
221
+ if self.anim_type == 'show':
222
+ kwargs['alpha'] *= t
223
+ x = self.x
224
+ y = self.y
225
+ elif self.anim_type == 'hide':
226
+ kwargs['alpha'] *= 1-t
227
+ x = self.x
228
+ y = self.y
229
+ elif self.anim_type == 'spawn':
230
+ i_max = min(round(t*len(self.x)) + 1,len(self.x))
231
+ x = self.x[:i_max]
232
+ y = self.y[:i_max]
233
+ if 'c' in kwargs:
234
+ kwargs['c'] = kwargs['c'][:i_max]
235
+ elif self.anim_type == 'despawn':
236
+ i_max = max(round((1-t)*len(self.x)),0)
237
+ x = self.x[:i_max]
238
+ y = self.y[:i_max]
239
+ if 'c' in kwargs:
240
+ kwargs['c'] = kwargs['c'][:i_max]
241
+ elif self.anim_type == 'morph':
242
+ x = []
243
+ y = []
244
+ a = []
245
+ if 'alpha' not in kwargs:
246
+ kwargs['alpha'] = 1
247
+ for i in range(len(self.x)):
248
+ x.append(self.x[i] + (self.new_x[i] - self.x[i])*t)
249
+ y.append(self.y[i] + (self.new_y[i] - self.y[i])*t)
250
+ if self.new_alpha is not None:
251
+ a.append(kwargs['alpha'] + (self.new_alpha[i] - kwargs['alpha'])*t)
252
+ else:
253
+ a.append(kwargs['alpha'])
254
+ kwargs['alpha'] = a
255
+ elif self.anim_type == 'demorph':
256
+ x = []
257
+ y = []
258
+ a = []
259
+ if 'alpha' not in kwargs:
260
+ kwargs['alpha'] = 1
261
+ for i in range(len(self.x)):
262
+ x.append(self.x[i] + (self.new_x[i] - self.x[i])*(1-t))
263
+ y.append(self.y[i] + (self.new_y[i] - self.y[i])*(1-t))
264
+ if self.new_alpha is not None:
265
+ a.append(kwargs['alpha'] + (self.new_alpha[i] - kwargs['alpha'])*t)
266
+ else:
267
+ a.append(kwargs['alpha'])
268
+ kwargs['alpha'] = a
269
+
270
+ self.obj = self.axis.scatter(x,y,**kwargs)
271
+
272
+ class plot(Animation):
273
+ def __init__(self,x,y, *args, **kwargs):
274
+ super().__init__(*args, **kwargs)
275
+ self.new_x = None
276
+ self.new_y = None
277
+ self.x = x
278
+ self.y = y
279
+
280
+ def clean(self,x):
281
+ super().clean(x)
282
+ if x == self.x_max-1 and self.new_x is not None:
283
+ self.x = self.new_x
284
+ self.y = self.new_y
285
+ self.new_x = None
286
+ self.new_y = None
287
+
288
+ def function(self,t):
289
+ kwargs = self.kwargs.copy()
290
+ if 'alpha' not in kwargs:
291
+ kwargs['alpha'] = 1
292
+ if self.anim_type == 'show':
293
+ kwargs['alpha'] *= t
294
+ x = self.x
295
+ y = self.y
296
+ elif self.anim_type == 'hide':
297
+ kwargs['alpha'] *= 1-t
298
+ x = self.x
299
+ y = self.y
300
+ elif self.anim_type == 'spawn':
301
+ i_max = min(round(t*len(self.x)) + 1,len(self.x))
302
+ x = self.x[:i_max]
303
+ y = self.y[:i_max]
304
+ if 'c' in kwargs:
305
+ kwargs['c'] = kwargs['c'][:i_max]
306
+ elif self.anim_type == 'despawn':
307
+ i_max = max(round((1-t)*len(self.x)),0)
308
+ x = self.x[:i_max]
309
+ y = self.y[:i_max]
310
+ if 'c' in kwargs:
311
+ kwargs['c'] = kwargs['c'][:i_max]
312
+ elif self.anim_type == 'morph':
313
+ x = []
314
+ y = []
315
+ for i in range(len(self.x)):
316
+ x.append(self.x[i] + (self.new_x[i] - self.x[i])*t)
317
+ y.append(self.y[i] + (self.new_y[i] - self.y[i])*t)
318
+ elif self.anim_type == 'demorph':
319
+ x = []
320
+ y = []
321
+ for i in range(len(self.x)):
322
+ x.append(self.x[i] + (self.new_x[i] - self.x[i])*(1-t))
323
+ y.append(self.y[i] + (self.new_y[i] - self.y[i])*(1-t))
324
+
325
+ self.obj = self.axis.plot(x,y,**kwargs)
326
+
327
+ class fill_between(Animation):
328
+ def __init__(self,x,y1,y2, *args, **kwargs):
329
+ super().__init__(*args, **kwargs)
330
+ self.x = np.array(x)
331
+ self.y1 = np.array(y1).reshape(-1)
332
+ self.y2 = np.array(y2).reshape(-1)
333
+ if self.y1.size != self.x.size:
334
+ self.y1 = np.ones_like(self.x)*self.y1[0]
335
+ if self.y2.size != self.x.size:
336
+ self.y2 = np.ones_like(self.x)*self.y2[0]
337
+
338
+ def function(self,t):
339
+ kwargs = self.kwargs.copy()
340
+ if 'alpha' not in kwargs:
341
+ kwargs['alpha'] = 1
342
+ if self.anim_type == 'show':
343
+ kwargs['alpha'] *= t
344
+ x = self.x
345
+ y1 = self.y1
346
+ y2 = self.y2
347
+ elif self.anim_type == 'hide':
348
+ kwargs['alpha'] *= 1-t
349
+ x = self.x
350
+ y1 = self.y1
351
+ y2 = self.y2
352
+ elif self.anim_type == 'spawn':
353
+ i_max = min(round(t*len(self.x)) + 1,len(self.x))
354
+ x = self.x[:i_max]
355
+ y1 = self.y1[:i_max]
356
+ y2 = self.y2[:i_max]
357
+ if 'c' in kwargs:
358
+ kwargs['c'] = kwargs['c'][:i_max]
359
+ elif self.anim_type == 'despawn':
360
+ i_max = max(round((1-t)*len(self.x)),0)
361
+ x = self.x[:i_max]
362
+ y1 = self.y1[:i_max]
363
+ y2 = self.y2[:i_max]
364
+ if 'c' in kwargs:
365
+ kwargs['c'] = kwargs['c'][:i_max]
366
+ elif self.anim_type == 'grow':
367
+ widths = self.y2 - self.y1
368
+ centers = (self.y2 + self.y1)/2
369
+ x = self.x
370
+ y1 = centers - t*widths/2
371
+ y2 = centers + t*widths/2
372
+ elif self.anim_type == 'shrink':
373
+ widths = self.y2 - self.y1
374
+ centers = (self.y2 + self.y1)/2
375
+ x = self.x
376
+ y1 = centers - (1-t)*widths/2
377
+ y2 = centers + (1-t)*widths/2
378
+
379
+ self.obj = self.axis.fill_between(x=x,y1=y1,y2=y2,**kwargs)
380
+
381
+ class fill_betweenx(Animation):
382
+ def __init__(self,y,x1,x2, *args, **kwargs):
383
+ super().__init__(*args, **kwargs)
384
+ self.y = np.array(y)
385
+ self.x1 = np.array(x1).reshape(-1)
386
+ self.x2 = np.array(x2).reshape(-1)
387
+ if self.x1.size != self.y.size:
388
+ self.x1 = np.ones_like(self.y[0])*self.x1[0]
389
+ if self.x2.size != self.y.size:
390
+ self.x2 = np.ones_like(self.y[0])*self.x2[0]
391
+
392
+ def function(self,t):
393
+ kwargs = self.kwargs.copy()
394
+ if 'alpha' not in kwargs:
395
+ kwargs['alpha'] = 1
396
+ if self.anim_type == 'show':
397
+ kwargs['alpha'] *= t
398
+ y = self.y
399
+ x1 = self.x1
400
+ x2 = self.x2
401
+ elif self.anim_type == 'hide':
402
+ kwargs['alpha'] *= 1-t
403
+ y = self.y
404
+ x1 = self.x1
405
+ x2 = self.x2
406
+ elif self.anim_type == 'spawn':
407
+ i_max = min(round(t*len(self.y)) + 1,len(self.y))
408
+ y = self.y[:i_max]
409
+ x1 = self.x1[:i_max]
410
+ x2 = self.x2[:i_max]
411
+ if 'c' in kwargs:
412
+ kwargs['c'] = kwargs['c'][:i_max]
413
+ elif self.anim_type == 'despawn':
414
+ i_max = max(round((1-t)*len(self.y)),0)
415
+ y = self.y[:i_max]
416
+ x1 = self.x1[:i_max]
417
+ x2 = self.x2[:i_max]
418
+ if 'c' in kwargs:
419
+ kwargs['c'] = kwargs['c'][:i_max]
420
+ elif self.anim_type == 'grow':
421
+ widths = self.x2 - self.x1
422
+ centers = (self.x2 + self.x1)/2
423
+ y = self.y
424
+ x1 = centers - t*widths/2
425
+ x2 = centers + t*widths/2
426
+ elif self.anim_type == 'shrink':
427
+ widths = self.x2 - self.x1
428
+ centers = (self.x2 + self.x1)/2
429
+ y = self.y
430
+ x1 = centers - (1-t)*widths/2
431
+ x2 = centers + (1-t)*widths/2
432
+
433
+ self.obj = self.axis.fill_betweenx(y=y,x1=x1,x2=x2,**kwargs)
434
+
435
+ class axvline(Animation):
436
+ def __init__(self,x, *args, **kwargs):
437
+ super().__init__(*args, **kwargs)
438
+ self.x = x
439
+
440
+ def function(self,t):
441
+ kwargs = self.kwargs.copy()
442
+ if 'alpha' not in kwargs:
443
+ kwargs['alpha'] = 1
444
+ x = self.x
445
+ y_max = 1
446
+ if self.anim_type == 'show':
447
+ kwargs['alpha'] *= t
448
+ elif self.anim_type == 'hide':
449
+ kwargs['alpha'] *= 1-t
450
+ elif self.anim_type == 'spawn':
451
+ y_max = t
452
+ elif self.anim_type == 'despawn':
453
+ y_max = (1-t)
454
+
455
+ self.obj = self.axis.axvline(x,ymin=0,ymax=y_max,**kwargs)
456
+
457
+ class errorbar(Animation):
458
+ def __init__(self,x,y,xerr=None,yerr=None, *args, **kwargs):
459
+ super().__init__(*args, **kwargs)
460
+ self.x = x
461
+ self.y = y
462
+ if xerr is None:
463
+ xerr = np.zeros_like(x)
464
+ if yerr is None:
465
+ yerr = np.zeros_like(y)
466
+ self.xerr = xerr
467
+ self.yerr = yerr
468
+
469
+ def function(self,t):
470
+ kwargs = self.kwargs.copy()
471
+ if 'alpha' not in kwargs:
472
+ kwargs['alpha'] = 1
473
+ if self.anim_type == 'show':
474
+ kwargs['alpha'] *= t
475
+ x = self.x
476
+ y = self.y
477
+ xerr = self.xerr
478
+ yerr = self.yerr
479
+ elif self.anim_type == 'hide':
480
+ kwargs['alpha'] *= 1-t
481
+ x = self.x
482
+ y = self.y
483
+ xerr = self.xerr
484
+ yerr = self.yerr
485
+ elif self.anim_type == 'spawn':
486
+ i_max = min(round(t*len(self.x)) + 1,len(self.x))
487
+ x = self.x[:i_max]
488
+ y = self.y[:i_max]
489
+ xerr = self.xerr[:i_max]
490
+ yerr = self.yerr[:i_max]
491
+ if 'c' in kwargs:
492
+ kwargs['c'] = kwargs['c'][:i_max]
493
+ elif self.anim_type == 'despawn':
494
+ i_max = max(round((1-t)*len(self.x)),0)
495
+ x = self.x[:i_max]
496
+ y = self.y[:i_max]
497
+ xerr = self.xerr[:i_max]
498
+ yerr = self.yerr[:i_max]
499
+ if 'c' in kwargs:
500
+ kwargs['c'] = kwargs['c'][:i_max]
501
+
502
+ self.obj = self.axis.errorbar(x,y,xerr=xerr,yerr=yerr,**kwargs)
503
+
504
+ class hist(Animation):
505
+ def __init__(self,x,bins=None,range=None, *args, **kwargs):
506
+ super().__init__(*args, **kwargs)
507
+ self.x = x
508
+ self.bins = bins
509
+ self.range = range
510
+
511
+ def function(self,t):
512
+ kwargs = self.kwargs.copy()
513
+ if self.anim_type == 'show':
514
+ kwargs['alpha'] *= t
515
+ if self.anim_type == 'hide':
516
+ kwargs['alpha'] *= 1-t
517
+ if self.anim_type == 'spawn':
518
+ i_max = min(round(t*len(self.x)) + 1,len(self.x))
519
+ elif self.anim_type == 'despawn':
520
+ i_max = max(round((1-t)*len(self.x)),0)
521
+ x = self.x[:i_max]
522
+ h,edges,self.obj = self.axis.hist(x,bins=self.bins,range=self.range,**kwargs)
523
+
524
+ class hist2d(Animation):
525
+ def __init__(self,x,y,bins=None,range=None, *args, **kwargs):
526
+ super().__init__(*args, **kwargs)
527
+ self.x = x
528
+ self.y = y
529
+ self.bins = bins
530
+ self.range = range
531
+
532
+ def function(self,t):
533
+ kwargs = self.kwargs.copy()
534
+ if 'alpha' not in kwargs:
535
+ kwargs['alpha'] = 1
536
+ i_max = len(self.x)
537
+ if self.anim_type == 'show':
538
+ kwargs['alpha'] *= t
539
+ if self.anim_type == 'hide':
540
+ kwargs['alpha'] *= 1-t
541
+ elif self.anim_type == 'spawn':
542
+ i_max = min(round(t*len(self.x)) + 1,len(self.x))
543
+ elif self.anim_type == 'despawn':
544
+ i_max = max(round((1-t)*len(self.x)),0)
545
+ x = self.x[:i_max]
546
+ y = self.y[:i_max]
547
+ old_xlim = self.axis.get_xlim()
548
+ old_ylim = self.axis.get_ylim()
549
+ h,xedges,yedges,self.obj = self.axis.hist2d(x,y,bins=self.bins,range=self.range,**kwargs)
550
+ self.axis.set_xlim(old_xlim)
551
+ self.axis.set_ylim(old_ylim)
552
+
553
+ class contourf(Animation):
554
+ def __init__(self,z,levels=None, *args, **kwargs):
555
+ super().__init__(*args, **kwargs)
556
+ self.z = z
557
+ self.levels = levels
558
+
559
+ def function(self,t):
560
+ kwargs = self.kwargs.copy()
561
+ if 'alpha' not in kwargs:
562
+ kwargs['alpha'] = 1
563
+ if self.anim_type == 'show':
564
+ kwargs['alpha'] *= t
565
+ elif self.anim_type == 'hide':
566
+ kwargs['alpha'] *= (1-t)
567
+ old_xlim = self.axis.get_xlim()
568
+ old_ylim = self.axis.get_ylim()
569
+ self.obj = self.axis.contourf(self.z,levels=self.levels,**kwargs)
570
+ self.axis.set_xlim(old_xlim)
571
+ self.axis.set_ylim(old_ylim)
572
+
573
+ class text(Animation):
574
+ def __init__(self,x,y,string, *args, **kwargs):
575
+ super().__init__(*args, **kwargs)
576
+ self.x = x
577
+ self.y = y
578
+ self.string = string
579
+ if isinstance(self.string,str):
580
+ self.string = np.array(self.string).reshape(-1)
581
+
582
+ def function(self,t):
583
+ kwargs = self.kwargs.copy()
584
+ if 'alpha' not in kwargs:
585
+ kwargs['alpha'] = 1
586
+ if self.anim_type == 'show':
587
+ kwargs['alpha'] *= t
588
+ s = self.string[0]
589
+ elif self.anim_type == 'hide':
590
+ kwargs['alpha'] *= 1-t
591
+ s = self.string[0]
592
+ elif self.anim_type == 'update':
593
+ i = min(round(t*len(self.string)) + 1,len(self.string))
594
+ s = self.string[i]
595
+ elif self.anim_type == 'deupdate':
596
+ i = max(round((1-t)*len(self.string)),0)
597
+ s = self.string[i]
598
+
599
+ self.obj = self.axis.text(x=self.x,y=self.y,s=s,**kwargs)
diplotocus/core.py ADDED
@@ -0,0 +1,173 @@
1
+ import numpy as np
2
+ import subprocess,os,shlex,io,pickle
3
+ import matplotlib.pyplot as plt
4
+ import shutil
5
+
6
+ from .easings import *
7
+ from .animations import *
8
+
9
+ def in_notebook():
10
+ try:
11
+ from IPython import get_ipython
12
+ shell = get_ipython().__class__.__name__
13
+ return shell == "ZMQInteractiveShell"
14
+ except Exception:
15
+ return False
16
+
17
+ if in_notebook():
18
+ from tqdm.notebook import tqdm
19
+ else:
20
+ from tqdm import tqdm
21
+
22
+ def status_message(start_msg, end_msg):
23
+ if in_notebook():
24
+ from IPython.display import display, update_display, Markdown
25
+ display_id = display(Markdown(start_msg), display_id=True)
26
+ def update():
27
+ update_display(Markdown(end_msg), display_id=display_id.display_id)
28
+ return update
29
+ else:
30
+ print(start_msg, end="\r", flush=True)
31
+ def update():
32
+ print(end_msg + " " * max(0, len(start_msg) - len(end_msg)))
33
+ return update
34
+
35
+ #----------------------------------------------
36
+ # SEQUENCE CLASS
37
+ #----------------------------------------------
38
+
39
+ class Sequence:
40
+ def __init__(self,fig,name,tqdm=True,dpi=200,easing=easeLinear()):
41
+ #buf = io.BytesIO()
42
+ #pickle.dump(fig, buf)
43
+ #buf.seek(0)
44
+ #self.fig = pickle.load(buf)
45
+ self.fig = fig
46
+ self.ax = self.fig.get_axes()
47
+ if len(self.ax) == 1:
48
+ self.ax = self.ax[0]
49
+ else:
50
+ self.ax = np.array(self.ax)
51
+ if isinstance(self.ax,np.ndarray):
52
+ if isinstance(self.ax[0],np.ndarray):
53
+ self.main_axis = self.ax[0,0]
54
+ else:
55
+ self.main_axis = self.ax[0]
56
+ else:
57
+ self.main_axis = self.ax
58
+ self.name = name
59
+ self.tqdm = tqdm
60
+ self.dpi = dpi
61
+ self.easing = easing
62
+ self.x = 0
63
+ self.sequence_str = ''
64
+ self.set_white()
65
+
66
+ def set_white(self):
67
+ if isinstance(self.ax,np.ndarray) == False:
68
+ axes = (self.ax,)
69
+ else:
70
+ axes = self.ax.flatten()
71
+ for axis in axes:
72
+ axis.spines['bottom'].set_color('white')
73
+ axis.spines['top'].set_color('white')
74
+ axis.spines['left'].set_color('white')
75
+ axis.spines['right'].set_color('white')
76
+ axis.xaxis.label.set_color('white')
77
+ axis.yaxis.label.set_color('white')
78
+ axis.tick_params(axis='both',colors='white')
79
+
80
+ def plot(self,animations,easing=None,debug=False):
81
+ if isinstance(animations,Animation):
82
+ animations = (animations,)
83
+ for animation in animations:
84
+ if animation.axis is None:
85
+ animation.set_axis(self.main_axis)
86
+ animation.initialize()
87
+ if animation.easing == None:
88
+ if easing is None:
89
+ animation.easing = self.easing
90
+ else:
91
+ animation.easing = easing
92
+ animation.apply(animation.x_max-1)
93
+ self.save_plot(debug)
94
+
95
+ def animate(self,animations,easing=None,debug=False):
96
+ if isinstance(animations,Animation):
97
+ animations = (animations,)
98
+ x_max = 0
99
+ for animation in animations:
100
+ if animation.axis is None:
101
+ animation.set_axis(self.main_axis)
102
+ animation.initialize()
103
+ if animation.easing == None:
104
+ if easing is None:
105
+ animation.easing = self.easing
106
+ else:
107
+ animation.easing = easing
108
+
109
+ if animation.x_max > x_max:
110
+ x_max = animation.x_max
111
+
112
+ loop = range(x_max)
113
+ if self.tqdm:
114
+ loop = tqdm(loop)
115
+
116
+ for x in loop:
117
+ for animation in animations:
118
+ animation.apply(x)
119
+ self.save_plot(debug)
120
+ for animation in animations:
121
+ animation.clean(x)
122
+
123
+ def wait(self,duration):
124
+ #Have to do in two steps, because doesn't work in 1 if wait is the last animation of the sequence
125
+ self.sequence_str += 'file \'' + self.name + '_{}.png\'\n'.format(self.x - 1)
126
+ self.sequence_str += 'duration {}\n'.format((duration-1)/30)
127
+ self.sequence_str += 'file \'' + self.name + '_{}.png\'\n'.format(self.x - 1)
128
+ self.sequence_str += 'duration {}\n'.format(1/30)
129
+ self.x += 1
130
+
131
+ def save_plot(self,debug):
132
+ if debug is False:
133
+ if os.path.isdir(self.name) == False:
134
+ os.makedirs(self.name)
135
+ fn = self.name + '/' + self.name + '_{}.png'.format(self.x)
136
+ self.fig.savefig(fn,dpi=self.dpi,transparent=True)
137
+
138
+ self.sequence_str += 'file \'' + self.name + '_{}.png\'\n'.format(self.x)
139
+ self.sequence_str += 'duration {}\n'.format(1/30)
140
+ self.x += 1
141
+
142
+ def save_video(self,speed=1,multialpha=False,prerendered=False,clean=True):
143
+ video_fn = self.name + '.mov'
144
+ update = status_message("Rendering video...", "Saved video " + video_fn)
145
+ if not prerendered:
146
+ self.sequence_str += 'file \'' + self.name + '_{}.png\'\n'.format(self.x-1)#repeat last frame otherwise not seen for some reason
147
+ self.sequence_str += 'duration {}\n'.format(1/30)
148
+ with open(self.name + '/' + self.name + '.txt','w+') as f:
149
+ lines = self.sequence_str.split('\n')
150
+ for line in lines:
151
+ if speed != 1:
152
+ if 'duration' in line:
153
+ line = 'duration {}'.format(float(line.split(' ')[-1])/speed)
154
+ f.write(line + '\n')
155
+ sequence_fn = self.name + '/' + self.name + '.txt'
156
+
157
+ if multialpha:
158
+ command = 'ffmpeg -f concat -safe 0 -i {} -y -vf premultiply=inplace=1 -c:v prores_ks -profile:v 4 -pix_fmt yuva444p10le -hide_banner -loglevel error {}'.format(sequence_fn,video_fn)
159
+ else:
160
+ command = 'ffmpeg -f concat -safe 0 -i {} -y -c:v prores -pix_fmt yuva444p10le -hide_banner -loglevel error {}'.format(sequence_fn,video_fn)
161
+ subprocess.run(shlex.split(command))
162
+ if clean:
163
+ shutil.rmtree(self.name)
164
+ plt.close()
165
+ update()
166
+
167
+
168
+ #TODO : implementing blitting as in https://matplotlib.org/stable/users/explain/animations/blitting.html
169
+ #has to be an option on the sequence Class, as it can only draw objects on top of the saved background.
170
+
171
+ #TODO : implement deepcopy of fig so that we can rerun the same code of Sequence without having to reinitialize the fig, because it changes in the sequence code
172
+ #code implemented in the init of sequence, but the axes are regenerated from the copied figure, so if some anims use a specific axis, it does not exist anymore.
173
+ #have to, as initialisation, replace the specific axis with the new one (save old axis in Sequence, find index of specific axis in old axes, get new axis using index)
diplotocus/easings.py ADDED
@@ -0,0 +1,17 @@
1
+ #----------------------------------------------
2
+ # EASING CLASSES
3
+ #----------------------------------------------
4
+ class Easing:
5
+ def __init__(self,func):
6
+ self.func = func
7
+
8
+ def ease(self,x):
9
+ return self.func(x)
10
+
11
+ class easeInOutQuart(Easing):
12
+ def __init__(self):
13
+ super().__init__(lambda x:8*x**4 if x < 0.5 else 1 - (-2*x + 2)**4 / 2)
14
+
15
+ class easeLinear(Easing):
16
+ def __init__(self):
17
+ super().__init__(lambda x:x)
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: diplotocus
3
+ Version: 0.0.1
4
+ Summary: A python package to generate animations using pyplot.
5
+ Author-email: Tristan Boin <tristan.boin@obspm.fr>
6
+ License-Expression: MIT
7
+ Classifier: Intended Audience :: Science/Research
8
+ Classifier: Operating System :: OS Independent
9
+ Classifier: Programming Language :: Python :: 3
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Dynamic: license-file
13
+
14
+ Diplotocus is a package to render animations of matplotlib plots, in a sequential timeline style.
@@ -0,0 +1,10 @@
1
+ diplotocus/__init__.py,sha256=bODPCi7-gbI4nGoIUKVJ7WclnrncvNhAi7wIfZoaw4I,90
2
+ diplotocus/_version.py,sha256=ugyuFliEqtAwQmH4sTlc16YXKYbFWDmfyk87fErB8-8,21
3
+ diplotocus/animations.py,sha256=EbT8wd8tgY8CgRKLkQj3s0xfeCbfWcW4pxYDoZR9CTc,20137
4
+ diplotocus/core.py,sha256=lUMA3PXlSxCWWKbnYOMJYM9gEjGJdx9M4xcnDnC8FqQ,6909
5
+ diplotocus/easings.py,sha256=XqzW3jSQqczuDUVwVQ8HPOr7z_mkvveWnIp6QOn3Trw,454
6
+ diplotocus-0.0.1.dist-info/licenses/LICENSE,sha256=s8eQhVWRZLr1lA8akzj1JW1RiWTocDzEpUfKzSARYvU,1072
7
+ diplotocus-0.0.1.dist-info/METADATA,sha256=pF1ZpkmFPhZANlGoDBtE984-O5w707MLENQ1amqrYuI,520
8
+ diplotocus-0.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
+ diplotocus-0.0.1.dist-info/top_level.txt,sha256=0nwg4ncIvxlo7OPlygJSE2zdUYFkV87w8a2-wRzhIhg,11
10
+ diplotocus-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) [2026] [Tristan Boin]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ diplotocus