diplotocus 0.0.0__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/core.py ADDED
@@ -0,0 +1,358 @@
1
+ import numpy as np
2
+ import subprocess,os,shlex,pickle
3
+ import matplotlib.pyplot as plt
4
+ import matplotlib
5
+ import warnings
6
+ from copy import deepcopy
7
+ matplotlib.use("Agg")
8
+ import shutil
9
+ try:
10
+ has_ipython = True
11
+ from IPython.display import HTML, display
12
+ except:
13
+ has_ipython = False
14
+ pass
15
+
16
+ from .easings import *
17
+ from .animations import *
18
+
19
+ if shutil.which("ffmpeg") is None:
20
+ warnings.warn("FFmpeg is required to render videos, but is not installed. Please install it system-wide.")
21
+
22
+ def in_notebook():
23
+ try:
24
+ from IPython import get_ipython
25
+ from IPython.display import HTML, display
26
+ shell = get_ipython().__class__.__name__
27
+ return shell == "ZMQInteractiveShell"
28
+ except Exception:
29
+ return False
30
+
31
+ if in_notebook():
32
+ try:
33
+ from tqdm.notebook import tqdm
34
+ has_tqdm = True
35
+ except:
36
+ has_tqdm = False
37
+ else:
38
+ try:
39
+ from tqdm import tqdm
40
+ has_tqdm = True
41
+ except:
42
+ has_tqdm = False
43
+
44
+ def status_message(start_msg, end_msg):
45
+ if in_notebook():
46
+ from IPython.display import display, update_display, Markdown
47
+ display_id = display(Markdown(start_msg), display_id=True)
48
+ def update():
49
+ update_display(Markdown(end_msg), display_id=display_id.display_id)
50
+ return update
51
+ else:
52
+ print(start_msg, end="\r", flush=True)
53
+ def update():
54
+ print(end_msg + " " * max(0, len(start_msg) - len(end_msg)))
55
+ return update
56
+
57
+ #----------------------------------------------
58
+ # TIMELINE CLASS
59
+ #----------------------------------------------
60
+
61
+ def load_project(path):
62
+ tl = pickle.load(open(path, 'rb'))
63
+ animations = tl.animations
64
+ tl.x = 0
65
+ tl.clean_all()
66
+ return tl,animations
67
+
68
+ class Timeline:
69
+ """Create and manage an animation timeline.
70
+
71
+ Parameters
72
+ ----------
73
+ name : str
74
+ the name used for the rendered images folder.
75
+ fig : Figure | None
76
+ an existing Figure to plot on. If None, a new Figure will be created.
77
+ quiet : bool
78
+ if True, any message will not be printed, and no progress bar will be shown.
79
+ dpi : int
80
+ The DPI of the rendered images (higher = better resolution/slower render).
81
+ transparent : bool
82
+ if True, the background color of the Figure and axes will be transparent. Can be used to render a transparent video (only compatible with a .mov video file).
83
+ axis_color : str
84
+ if True, changes the color of the axes, ticks and labels to the specified color.
85
+ noaxis : bool
86
+ if True, hides the axis lines, ticks and labels.
87
+ easing : easing
88
+ a global easing to be used if no other easing is specified on an animation.
89
+ xlim : tuple | None
90
+ if fig is None, sets the X limits of the created axis.
91
+ ylim : tuple | None
92
+ if fig is None, sets the Y limits of the created axis.
93
+ """
94
+ def __init__(self,
95
+ name='Unnamed',
96
+ fig=None,
97
+ quiet=False,
98
+ dpi=200,
99
+ transparent=False,
100
+ axis_color='k',
101
+ noaxis=False,
102
+ easing=easeLinear(),
103
+ xlim=None,
104
+ ylim=None,
105
+ figsize=None
106
+ ):
107
+ if fig is None:
108
+ if figsize is not None:
109
+ fig,_ = plt.subplots(figsize=figsize)
110
+ else:
111
+ fig,_ = plt.subplots()
112
+ self.fig = fig
113
+ self.ax = self.fig.get_axes()
114
+ if len(self.ax) == 1:
115
+ self.ax = self.ax[0]
116
+ else:
117
+ self.ax = np.array(self.ax)
118
+ if isinstance(self.ax,np.ndarray):
119
+ if isinstance(self.ax[0],np.ndarray):
120
+ self.main_axis = self.ax[0,0]
121
+ else:
122
+ self.main_axis = self.ax[0]
123
+ else:
124
+ self.main_axis = self.ax
125
+ if xlim is not None:
126
+ self.main_axis.set_xlim(*xlim)
127
+ if ylim is not None:
128
+ self.main_axis.set_ylim(*ylim)
129
+ self.name = name
130
+ self.quiet = quiet
131
+ self.dpi = dpi
132
+ self.easing = easing
133
+ self.x = 0
134
+ self.timeline_str = ''
135
+ self.transparent = transparent
136
+ self.animations = []
137
+ if in_notebook() == False:
138
+ namespace = sys._getframe(1).f_globals['__file__']
139
+ self.full_path = '/'.join(namespace.split('/')[:-1])
140
+ if axis_color != 'k':
141
+ self.set_axis_color(axis_color)
142
+ if noaxis:
143
+ self.main_axis.set_axis_off()
144
+
145
+ def set_axis_color(self,color):
146
+ if isinstance(self.ax,np.ndarray) == False:
147
+ axes = (self.ax,)
148
+ else:
149
+ axes = self.ax.flatten()
150
+ for axis in axes:
151
+ axis.spines['bottom'].set_color(color)
152
+ axis.spines['top'].set_color(color)
153
+ axis.spines['left'].set_color(color)
154
+ axis.spines['right'].set_color(color)
155
+ axis.xaxis.label.set_color(color)
156
+ axis.yaxis.label.set_color(color)
157
+ axis.tick_params(axis='both',colors=color)
158
+
159
+ def clean_all(self):
160
+ axes = np.ravel(self.ax)
161
+ for axis in axes:
162
+ for line in axis.lines:
163
+ line = np.ravel(line)
164
+ for l in line:
165
+ if l.get_animated():
166
+ l.remove()
167
+ for coll in axis.collections:
168
+ coll = np.ravel(coll)
169
+ for c in coll:
170
+ if c.get_animated():
171
+ c.remove()
172
+ for patch in axis.patches:
173
+ patch = np.ravel(patch)
174
+ for p in patch:
175
+ if p.get_animated():
176
+ p.remove()
177
+
178
+ def plot(self,animations,x=0,easing=None,debug=False):
179
+ if isinstance(animations,Animation):
180
+ animations = (animations,)
181
+
182
+ for animation in animations:
183
+ if animation.obj is not None and ((animation.x_min <= x < animation.x_max) or (x >= animation.x_max and animation.persistent == False)):
184
+ objects = np.ravel(animation.obj)
185
+ for obj in objects:
186
+ try:
187
+ obj.remove()
188
+ except:
189
+ pass
190
+
191
+ animation.initialize(self)
192
+ #only plot if within an anim or if last one is persistent
193
+ last_anim = None
194
+ closest_dist = np.inf
195
+ for anim in animation.anims:
196
+ anim_x_max = anim['duration']+anim['delay']
197
+ if anim_x_max > x:
198
+ continue
199
+ dist = x - anim_x_max
200
+ if dist < closest_dist:
201
+ closest_dist = dist
202
+ last_anim = anim
203
+ is_within_anim = False
204
+ if last_anim is not None:
205
+ is_within_anim = last_anim['persistent'] == True
206
+ if is_within_anim == False:
207
+ for anim in animation.anims:
208
+ anim_x_min = anim['delay']
209
+ anim_x_max = anim['duration']+anim['delay']
210
+ if anim_x_min <= x < anim_x_max:
211
+ is_within_anim = True
212
+ break
213
+ if is_within_anim:
214
+ animation.apply(x,easing)
215
+ prev_x = self.x
216
+ self.x = x
217
+ self.save_plot(debug)
218
+ for animation in animations:
219
+ animation.clean(x,clear_anims=False)
220
+ self.x = prev_x
221
+
222
+ def animate(self,plot_objects,easing=None,debug=False):
223
+ """Render one or more plot objects into the timeline.
224
+
225
+ Parameters
226
+ ----------
227
+ plot_objects : Animation or sequence of Animation
228
+ a single plot object (e.g. `scatter`, `plot`, `hist`...) or a list of plot objects.
229
+ easing : easing
230
+ an easing override used while rendering.
231
+ debug : bool
232
+ if True, skip writing frame images to disk.
233
+ """
234
+ if isinstance(plot_objects,Animation):
235
+ plot_objects = (plot_objects,)
236
+ self.animations = deepcopy(np.array(plot_objects))
237
+
238
+ x_max = 0
239
+ for animation in plot_objects:
240
+ for anim in animation.anims:
241
+ if anim['played'] == False:
242
+ anim['delay'] += self.x
243
+ animation.initialize(self)
244
+
245
+ if animation.x_max > x_max:
246
+ x_max = animation.x_max
247
+
248
+ loop = range(self.x,x_max)
249
+ if not self.quiet and has_tqdm:
250
+ loop = tqdm(loop)
251
+
252
+ for x in loop:
253
+ self.plot(animations=plot_objects,x=x,easing=easing,debug=debug)
254
+ for animation in plot_objects:
255
+ for anim in animation.anims:
256
+ anim['played'] = True
257
+ self.x = x_max
258
+
259
+ def wait(self,duration):
260
+ """Pause the animation for a number of frames.
261
+
262
+ Parameters
263
+ ----------
264
+ duration : int
265
+ the number of frames to pause the animation for.
266
+ """
267
+ if self.x == 0:
268
+ raise UserWarning('No rendered frames, run animate with animations to generate frames !')
269
+
270
+ last_frame = self.x - 1
271
+ source_png = self.name + '/' + self.name + '_{}.png'.format(last_frame)
272
+ for _ in range(duration):
273
+ target_png = self.name + '/' + self.name + '_{}.png'.format(self.x)
274
+ shutil.copyfile(source_png, target_png)
275
+ self.timeline_str += 'file \' ' + self.name + '_{}.png\'\n'.format(self.x)
276
+ self.timeline_str += 'duration {}\n'.format(1/30)
277
+ self.x += 1
278
+
279
+ def save_plot(self,debug):
280
+ if debug is False:
281
+ if os.path.isdir(self.name) == False:
282
+ os.makedirs(self.name)
283
+ fn = self.name + '/' + self.name + '_{}.png'.format(self.x)
284
+ self.fig.savefig(fn,dpi=self.dpi,transparent=self.transparent)
285
+
286
+ self.timeline_str += 'file \'' + self.name + '_{}.png\'\n'.format(self.x)
287
+ self.timeline_str += 'duration {}\n'.format(1/30)
288
+ self.x += 1
289
+
290
+ def save_video(self,path=None,speed=1,ffmpeg_path='ffmpeg',multialpha=False,prerendered=False,clean=True):
291
+ """Render the current timeline to a video file.
292
+
293
+ Parameters
294
+ ----------
295
+ path : str | None
296
+ the path to save the video file to.
297
+ speed : float
298
+ by default, animations are rendered at 30fps (frames per second). You can change this by setting the speed. For 60fps videos, choose speed=2.
299
+ ffmpeg_path : str
300
+ if ffmpeg is not recognised as is, you can set the path to the ffmpeg executable through this parameter.
301
+ multialpha : bool
302
+ when rendering to a transparent .mov video, if you have semi-transparent frames, can be used to get better results.
303
+ prerendered : bool
304
+ if save_video() was already called and clean was set to False, image files and the text list file associated still exist. If you want to rerender the video, without having to rerender all the frames, you can set prerendered to true, which will only run the ffmpeg command.
305
+ clean : bool
306
+ if True, image files and the text list file associated are deleted after the video has been rendered.
307
+ """
308
+ if self.x == 0:
309
+ raise UserWarning('No rendered frames, run animate with animations to generate frames !')
310
+ if path is None:
311
+ if self.transparent:
312
+ path = self.name + '.mov'
313
+ else:
314
+ path = self.name + '.mp4'
315
+ video_fn = path
316
+ is_gif = video_fn.lower().endswith('.gif')
317
+ if not self.quiet:
318
+ update = status_message("Rendering video...", "Saved video " + video_fn)
319
+ if not prerendered:
320
+ self.timeline_str = ''
321
+ for i in range(self.x):
322
+ self.timeline_str += 'file \'' + self.name + '_{}.png\'\n'.format(i)
323
+ self.timeline_str += 'duration {}\n'.format(1/30/speed)
324
+ self.timeline_str += 'file \'' + self.name + '_{}.png\'\n'.format(i)
325
+ self.timeline_str += 'duration {}\n'.format(1/30/speed)
326
+ with open(self.name + '/' + self.name + '.txt','w+') as f:
327
+ f.write(self.timeline_str)
328
+ sequence_fn = self.name + '/' + self.name + '.txt'
329
+
330
+ if '.mov' in video_fn:
331
+ if multialpha:
332
+ command = ffmpeg_path + ' -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)
333
+ else:
334
+ command = ffmpeg_path + ' -f concat -safe 0 -i {} -y -c:v prores -pix_fmt yuva444p10le -hide_banner -loglevel error {}'.format(sequence_fn,video_fn)
335
+ elif is_gif:
336
+ command = ffmpeg_path + ' -f concat -safe 0 -i {} -y -lavfi "split[s0][s1];[s0]palettegen=reserve_transparent=1:stats_mode=diff[p];[s1][p]paletteuse=dither=sierra2_4a:alpha_threshold=128" -gifflags -offsetting -loop 0 -hide_banner -loglevel error {}'.format(sequence_fn,video_fn)
337
+ else:
338
+ command = ffmpeg_path + ' -f concat -safe 0 -i {} -c:v libx264 -pix_fmt yuv420p -c:a aac -movflags +faststart -hide_banner -loglevel error -y {}'.format(sequence_fn,video_fn)
339
+ subprocess.run(shlex.split(command))
340
+ if clean:
341
+ shutil.rmtree(self.name)
342
+ plt.close()
343
+ if not self.quiet:
344
+ update()
345
+
346
+ rand_id = np.random.randint(0,10_000)
347
+ if has_ipython:
348
+ if is_gif:
349
+ return display(HTML('<img src="{}?randId={}" style="max-width:640px;height:auto;" />'.format(video_fn,rand_id)))
350
+ return display(HTML("""
351
+ <video width="640" height="360" autoplay loop muted playsinline>
352
+ <source src="{}?randId={}" type="video/mp4">
353
+ </video>
354
+ """.format(video_fn,rand_id)))
355
+
356
+ def save_project(self,path):
357
+ with open(path, 'wb') as f:
358
+ pickle.dump(self, f)
diplotocus/easings.py ADDED
@@ -0,0 +1,284 @@
1
+ import numpy as np
2
+ import sys, inspect
3
+ #----------------------------------------------
4
+ # EASING CLASSES
5
+ #----------------------------------------------
6
+ #TODO: add invert method to easing to behave in the inverted way so all inverted animations can work both ways
7
+ #TODO: change plot of easings.ipynb like this : https://api.flutter.dev/flutter/animation/Curves/easeInBack-constant.html
8
+
9
+ class Easing:
10
+ def __init__(self, func=None):
11
+ self.func = func if func is not None else (lambda x: x)
12
+
13
+ def ease(self,x):
14
+ return self.func(x)
15
+
16
+ class easeCubicBezier(Easing):
17
+ def bezier(self, t, a1, a2):
18
+ return (
19
+ 3 * (1 - t)**2 * t * a1
20
+ + 3 * (1 - t) * t**2 * a2
21
+ + t**3
22
+ )
23
+
24
+ def bezier_derivative(self, t, a1, a2):
25
+ return (
26
+ 3 * (1 - t)**2 * a1
27
+ + 6 * (1 - t) * t * (a2 - a1)
28
+ + 3 * t**2 * (1 - a2)
29
+ )
30
+
31
+ def ease(self, times):
32
+ times = np.asarray(times, dtype=float)
33
+ times_clipped = np.clip(times, 0.0, 1.0)
34
+
35
+ # Initial guess: t ≈ x
36
+ t = times_clipped.copy()
37
+
38
+ # Newton–Raphson (vectorized)
39
+ for _ in range(self.max_iter):
40
+ x_t = self.bezier(t, self.x1, self.x2)
41
+ dx = x_t - times_clipped
42
+
43
+ if np.all(np.abs(dx) < self.epsilon):
44
+ break
45
+
46
+ dxd_t = self.bezier_derivative(t, self.x1, self.x2)
47
+ mask = np.abs(dxd_t) > 1e-8
48
+ if isinstance(t,np.ndarray):
49
+ t[mask] -= dx[mask] / dxd_t[mask]
50
+ else:
51
+ t -= dx[mask] / dxd_t[mask]
52
+
53
+ # Keep t in bounds
54
+ t = np.clip(t, 0.0, 1.0)
55
+
56
+ # Compute y(t)
57
+ y = self.bezier(t, self.y1, self.y2)
58
+
59
+ # Respect exact endpoints
60
+ if isinstance(y,np.ndarray):
61
+ y[times <= 0.0] = 0.0
62
+ y[times >= 1.0] = 1.0
63
+ else:
64
+ if times <= 0.0:
65
+ y = 0.0
66
+ elif times >= 1.0:
67
+ y = 1.0
68
+
69
+ if isinstance(y,np.ndarray) and len(y) == 1:
70
+ return y[0]
71
+ return y
72
+
73
+
74
+ def __init__(self,x1,y1,x2,y2,epsilon=1e-6, max_iter=10):
75
+ self.x1 = x1
76
+ self.x2 = x2
77
+ self.y1 = y1
78
+ self.y2 = y2
79
+ self.epsilon = epsilon
80
+ self.max_iter = max_iter
81
+
82
+ #LINEAR
83
+ class easeLinear(Easing):
84
+ def __init__(self):
85
+ pass
86
+
87
+ def func(self,x):
88
+ return x
89
+
90
+ # SINE
91
+ class easeInSine(Easing):
92
+ def __init__(self):
93
+ super().__init__(lambda x:1 - np.cos((x * np.pi) / 2))
94
+
95
+ class easeOutSine(Easing):
96
+ def __init__(self):
97
+ super().__init__(lambda x: np.sin((x * np.pi) / 2))
98
+
99
+ class easeInOutSine(Easing):
100
+ def __init__(self):
101
+ super().__init__(lambda x: -(np.cos(np.pi * x) - 1) / 2)
102
+
103
+ # QUADRATIC
104
+ class easeInQuad(Easing):
105
+ def __init__(self):
106
+ super().__init__(lambda x: x**2)
107
+
108
+ class easeOutQuad(Easing):
109
+ def __init__(self):
110
+ super().__init__(lambda x: 1 - (1-x)**2)
111
+
112
+ class easeInOutQuad(Easing):
113
+ def __init__(self):
114
+ super().__init__(lambda x: np.where(x<0.5,2*x**2,1-(-2*x+2)**2 / 2))
115
+
116
+ # SUBIC
117
+ class easeInCubic(Easing):
118
+ def __init__(self):
119
+ super().__init__(lambda x: x**3)
120
+
121
+ class easeOutCubic(Easing):
122
+ def __init__(self):
123
+ super().__init__(lambda x: 1 - (1-x)**3)
124
+
125
+ class easeInOutCubic(Easing):
126
+ def __init__(self):
127
+ super().__init__(lambda x: np.where(x<0.5,4*x**3,1-(-2*x+2)**3 / 2))
128
+
129
+ # QUARTIC
130
+ class easeInQuart(Easing):
131
+ def __init__(self):
132
+ super().__init__(lambda x: x**4)
133
+
134
+ class easeOutQuart(Easing):
135
+ def __init__(self):
136
+ super().__init__(lambda x: 1-(1-x)**4)
137
+
138
+ class easeInOutQuart(Easing):
139
+ def __init__(self):
140
+ super().__init__(lambda x:np.where(x<0.5,8*x**4,1-(-2*x+2)**4 / 2))
141
+
142
+ # QUINTIC
143
+ class easeInQuint(Easing):
144
+ def __init__(self):
145
+ super().__init__(lambda x: x**5)
146
+
147
+ class easeOutQuint(Easing):
148
+ def __init__(self):
149
+ super().__init__(lambda x: 1-(1-x)**5)
150
+
151
+ class easeInOutQuint(Easing):
152
+ def __init__(self):
153
+ super().__init__(lambda x:np.where(x<0.5,16*x**5,1-(-2*x+2)**5 / 2))
154
+
155
+ # EXPONENTIAL
156
+ class easeInExpo(Easing):
157
+ def __init__(self):
158
+ super().__init__(lambda x:np.where(x==0,0,2**(10*x-10)))
159
+
160
+ class easeOutExpo(Easing):
161
+ def __init__(self):
162
+ super().__init__(lambda x:np.where(x==1,1,1-2**(-10*x)))
163
+
164
+ class easeInOutExpo(Easing):
165
+ def __init__(self):
166
+ super().__init__(lambda x:np.where(x==0,0,np.where(x==1,1,np.where(x<0.5,2**(20*x-10) / 2,(2-2**(-20*x+10)) / 2))))
167
+
168
+ # CIRCLE
169
+ class easeInCirc(Easing):
170
+ def __init__(self):
171
+ super().__init__(lambda x: 1 - np.sqrt(1 - x**2))
172
+
173
+ class easeOutCirc(Easing):
174
+ def __init__(self):
175
+ super().__init__(lambda x: np.sqrt(1 - (x - 1)**2))
176
+
177
+ class easeInOutCirc(Easing):
178
+ def __init__(self):
179
+ #(Math.sqrt(1 - Math.pow(-2 * x + 2, 2)) + 1) / 2
180
+ #(np.sqrt(1 - (-2*x)**2) + 1) / 2
181
+ super().__init__(lambda x:np.where(
182
+ x<0.5,
183
+ (1 - np.sqrt(np.abs(1 - (2*x)**2))) / 2,
184
+ (np.sqrt(np.abs(1 - (-2*x+2)**2)) + 1) / 2)
185
+ )
186
+
187
+ # BACK
188
+ class easeInBack(Easing):
189
+ def __init__(self):
190
+ def func(x):
191
+ c1 = 1.70158
192
+ c3 = c1 + 1
193
+
194
+ return c3 * x * x * x - c1 * x * x
195
+ super().__init__(func)
196
+
197
+ class easeOutBack(Easing):
198
+ def __init__(self):
199
+ def func(x):
200
+ c1 = 1.70158
201
+ c3 = c1 + 1
202
+
203
+ return 1 + c3 * (x - 1)**3 + c1 * (x - 1)**2
204
+ super().__init__(func)
205
+
206
+ class easeInOutBack(Easing):
207
+ def __init__(self):
208
+ def func(x):
209
+ c1 = 1.70158
210
+ c2 = c1 * 1.525
211
+
212
+ return np.where(x < 0.5,
213
+ ((2 * x)**2 * ((c2 + 1) * 2 * x - c2)) / 2,
214
+ ((2 * x - 2)**2 * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2
215
+ )
216
+ super().__init__(func)
217
+
218
+ # ELASTIC
219
+ class easeInElastic(Easing):
220
+ def __init__(self):
221
+ def func(x):
222
+ c4 = (2 * np.pi) / 3
223
+
224
+ return np.where(x == 0,0,np.where(x == 1,1,
225
+ -2**(10 * x - 10) * np.sin((x * 10 - 10.75) * c4)
226
+ ))
227
+ super().__init__(func)
228
+
229
+ class easeOutElastic(Easing):
230
+ def __init__(self):
231
+ def func(x):
232
+ c4 = (2 * np.pi) / 3
233
+
234
+ return np.where(x == 0,0,np.where(x == 1,1,
235
+ 2**(-10 * x) * np.sin((x * 10 - 0.75) * c4) + 1
236
+ ))
237
+ super().__init__(func)
238
+
239
+ class easeInOutElastic(Easing):
240
+ def __init__(self):
241
+ def func(x):
242
+ c5 = (2 * np.pi) / 4.5
243
+
244
+ return np.where(x == 0,0,np.where(x == 1,1,np.where(x<0.5,
245
+ -(2**(20 * x - 10) * np.sin((20 * x - 11.125) * c5)) / 2,
246
+ (2**(-20 * x + 10) * np.sin((20 * x - 11.125) * c5)) / 2 + 1
247
+ )))
248
+ super().__init__(func)
249
+
250
+ # BOUNCE
251
+ def outBounce(x):
252
+ n1 = 7.5625
253
+ d1 = 2.75
254
+
255
+ return np.where(x < 1 / d1,
256
+ n1 * x * x,
257
+ np.where(x < 2 / d1,
258
+ n1 * (x - 1.5 / d1) * (x - 1.5 / d1) + 0.75,
259
+ np.where(x < 2.5 / d1,
260
+ n1 * (x - 2.25 / d1) * (x - 2.25 / d1) + 0.9375,
261
+ n1 * (x - 2.625 / d1) * (x - 2.625 / d1) + 0.984375
262
+ )))
263
+
264
+ class easeOutBounce(Easing):
265
+ def __init__(self):
266
+ super().__init__(outBounce)
267
+
268
+ class easeInBounce(Easing):
269
+ def __init__(self):
270
+ def func(x):
271
+ x = 1-x
272
+ return 1-outBounce(x)
273
+ super().__init__(func)
274
+
275
+ class easeInOutBounce(Easing):
276
+ def __init__(self):
277
+ def func(x):
278
+ return np.where(x<0.5,(1 - outBounce(1 - 2 * x)) / 2,(1 + outBounce(2 * x - 1)) / 2)
279
+ super().__init__(func)
280
+
281
+ available_easings = []
282
+ for name, obj in inspect.getmembers(sys.modules[__name__]):
283
+ if inspect.isclass(obj) and name != 'Easing':
284
+ available_easings.append(name)
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: diplotocus
3
+ Version: 0.0.0
4
+ Summary: A python package to generate animations of matplotlib objects.
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: Framework :: Matplotlib
10
+ Classifier: Programming Language :: Python :: 3
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: numpy
14
+ Requires-Dist: matplotlib
15
+ Requires-Dist: svgpath2mpl
16
+ Dynamic: license-file
@@ -0,0 +1,26 @@
1
+ diplotocus/__init__.py,sha256=57u1-7-LURrHkFfv_ZxrsxjD69j4AJFQ56GDR9Ii2mY,111
2
+ diplotocus/_version.py,sha256=rqyZtt1djmoU2vxYGslRgV4TaBWrxK8uesP_Xai_8-U,19
3
+ diplotocus/animations.py,sha256=Bnbc7O8MnSscYKNSxfwlyDCaQLI6CLQqj1v_Kq0NIpA,138717
4
+ diplotocus/core.py,sha256=ITDigZA78T4vOYz1pCP9Eksm2DvgKQ_FP_rKQy8Z0DM,14019
5
+ diplotocus/easings.py,sha256=zhwPItn_YnOczCX5czOQ3H47rJ0-qY3nsx3Gbgt8PNc,7798
6
+ diplotocus/GUI/GUI.html,sha256=r62SAu4Q_HM1vg04P0l1-rpWFulZ1DVypyO9uvfAA_k,7096
7
+ diplotocus/GUI/__init__.py,sha256=lKTP_zNmcPE__OcOD9QniK7h6nLZ7U93b9Izo6oxtc8,42
8
+ diplotocus/GUI/app.js,sha256=x_sZiaQCnKDe8IyN8RbU8qKUODDpi7EpgeXnmtidCec,50053
9
+ diplotocus/GUI/server.py,sha256=cHC1cbTW_wSnH4_42jL2W-OjptgE2MOJlZb2APn52SI,56039
10
+ diplotocus/GUI/style.css,sha256=rT__vQuB20q1PZywP8te0DV917H9AQHwJclBg4elqY0,10824
11
+ diplotocus/GUI/src/bg.png,sha256=ySX6slK8Ddlg7V16fOeAJxWuVT9i3_Dub-wsT5Y1ZqI,64165
12
+ diplotocus/GUI/src/favicon.ico,sha256=7sz7EfCWA-cba-_z_1jO7Ik2VY24K1VvubgEPli-1I4,11643
13
+ diplotocus/GUI/src/logo_bg.png,sha256=SrlSmKGQ8-O2quENjM16NFq2RjtYtbU7YH_c_bO27h8,33131
14
+ diplotocus/GUI/src/btn/btn_bg_Draw.png,sha256=PUG13hBQpFdd5mQsmB_wD6sigWC7HDi-npJVBwNIn34,34804
15
+ diplotocus/GUI/src/btn/btn_bg_Morph.png,sha256=QgLIpy7P4lidTVQQ32kivqndBgNU9cZPYNmgTixtxKo,35437
16
+ diplotocus/GUI/src/btn/btn_bg_Rotate.png,sha256=Sqji90eWk8oRHRjvm1a2qez07c0AuAL4sRMr6cWJows,35000
17
+ diplotocus/GUI/src/btn/btn_bg_Scale.png,sha256=VgykxIa4RJ_2bfISLU3rYvJ7yy95NQ91VMndpDe7JvU,35441
18
+ diplotocus/GUI/src/btn/btn_bg_Sequence.png,sha256=Z7yQytDrtwaNfHL5Sazt3AFIWHBE7mOQ2qYocg0oCOo,36230
19
+ diplotocus/GUI/src/btn/btn_bg_Translate.png,sha256=RVrUq2RwnVLWj0SzoE-NnZeLJHwfvkeoLfUdeWBFmm8,35179
20
+ diplotocus/GUI/src/btn/btn_bg_Tween.png,sha256=IjtpQqIQP8ePltwPJ2zqKBQooobI47Ojczi0PSSbSWo,34926
21
+ diplotocus/GUI/src/btn/btn_bg_default.png,sha256=QfrqN7N8c5TRgtBfU3Pw01tU5KfxyKihFXy5R9VrQsw,33705
22
+ diplotocus-0.0.0.dist-info/licenses/LICENSE,sha256=s8eQhVWRZLr1lA8akzj1JW1RiWTocDzEpUfKzSARYvU,1072
23
+ diplotocus-0.0.0.dist-info/METADATA,sha256=g9GeD3mWF2e5OpPQwUgT58s3AyHJC5KEUMQHhzDGjts,540
24
+ diplotocus-0.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
25
+ diplotocus-0.0.0.dist-info/top_level.txt,sha256=0nwg4ncIvxlo7OPlygJSE2zdUYFkV87w8a2-wRzhIhg,11
26
+ diplotocus-0.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+