diplotocus 0.5.0__tar.gz → 0.6.0__tar.gz

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.
Files changed (70) hide show
  1. {diplotocus-0.5.0 → diplotocus-0.6.0}/PKG-INFO +1 -1
  2. {diplotocus-0.5.0 → diplotocus-0.6.0}/docs/source/index.rst +1 -1
  3. {diplotocus-0.5.0 → diplotocus-0.6.0}/docs/source/notebooks/GUI.ipynb +5 -0
  4. {diplotocus-0.5.0 → diplotocus-0.6.0}/docs/source/notebooks/animations.ipynb +76 -0
  5. {diplotocus-0.5.0 → diplotocus-0.6.0}/docs/source/notebooks/plot_objects.ipynb +1 -0
  6. diplotocus-0.6.0/src/diplotocus/_version.py +1 -0
  7. {diplotocus-0.5.0 → diplotocus-0.6.0}/src/diplotocus/animations.py +398 -30
  8. {diplotocus-0.5.0 → diplotocus-0.6.0}/src/diplotocus/core.py +6 -4
  9. {diplotocus-0.5.0 → diplotocus-0.6.0}/src/diplotocus.egg-info/PKG-INFO +1 -1
  10. diplotocus-0.5.0/src/diplotocus/_version.py +0 -1
  11. {diplotocus-0.5.0 → diplotocus-0.6.0}/.gitattributes +0 -0
  12. {diplotocus-0.5.0 → diplotocus-0.6.0}/.gitignore +0 -0
  13. {diplotocus-0.5.0 → diplotocus-0.6.0}/.readthedocs.yaml +0 -0
  14. {diplotocus-0.5.0 → diplotocus-0.6.0}/LICENSE +0 -0
  15. {diplotocus-0.5.0 → diplotocus-0.6.0}/docs/Makefile +0 -0
  16. {diplotocus-0.5.0 → diplotocus-0.6.0}/docs/_static/force-light.css +0 -0
  17. {diplotocus-0.5.0 → diplotocus-0.6.0}/docs/_static/force-light.js +0 -0
  18. {diplotocus-0.5.0 → diplotocus-0.6.0}/docs/make.bat +0 -0
  19. {diplotocus-0.5.0 → diplotocus-0.6.0}/docs/requirements.txt +0 -0
  20. {diplotocus-0.5.0 → diplotocus-0.6.0}/docs/source/_static/Web_GUI.png +0 -0
  21. {diplotocus-0.5.0 → diplotocus-0.6.0}/docs/source/_static/button.svg +0 -0
  22. {diplotocus-0.5.0 → diplotocus-0.6.0}/docs/source/_static/demo.mp4 +0 -0
  23. {diplotocus-0.5.0 → diplotocus-0.6.0}/docs/source/_static/favicon.ico +0 -0
  24. {diplotocus-0.5.0 → diplotocus-0.6.0}/docs/source/_static/force-light.css +0 -0
  25. {diplotocus-0.5.0 → diplotocus-0.6.0}/docs/source/_static/force-light.js +0 -0
  26. {diplotocus-0.5.0 → diplotocus-0.6.0}/docs/source/_static/logo.mp4 +0 -0
  27. {diplotocus-0.5.0 → diplotocus-0.6.0}/docs/source/_static/logo.svg +0 -0
  28. {diplotocus-0.5.0 → diplotocus-0.6.0}/docs/source/api/core.rst +0 -0
  29. {diplotocus-0.5.0 → diplotocus-0.6.0}/docs/source/api/index.rst +0 -0
  30. {diplotocus-0.5.0 → diplotocus-0.6.0}/docs/source/conf.py +0 -0
  31. {diplotocus-0.5.0 → diplotocus-0.6.0}/docs/source/notebooks/easings.ipynb +0 -0
  32. {diplotocus-0.5.0 → diplotocus-0.6.0}/docs/source/notebooks/index.ipynb +0 -0
  33. {diplotocus-0.5.0 → diplotocus-0.6.0}/docs/source/notebooks/installation.ipynb +0 -0
  34. {diplotocus-0.5.0 → diplotocus-0.6.0}/docs/source/notebooks/quick-start.ipynb +0 -0
  35. {diplotocus-0.5.0 → diplotocus-0.6.0}/docs/source/notebooks/timeline.ipynb +0 -0
  36. {diplotocus-0.5.0 → diplotocus-0.6.0}/logo.png +0 -0
  37. {diplotocus-0.5.0 → diplotocus-0.6.0}/pyproject.toml +0 -0
  38. {diplotocus-0.5.0 → diplotocus-0.6.0}/readme.md +0 -0
  39. {diplotocus-0.5.0 → diplotocus-0.6.0}/setup.cfg +0 -0
  40. {diplotocus-0.5.0 → diplotocus-0.6.0}/src/diplotocus/GUI/GUI.html +0 -0
  41. {diplotocus-0.5.0 → diplotocus-0.6.0}/src/diplotocus/GUI/__init__.py +0 -0
  42. {diplotocus-0.5.0 → diplotocus-0.6.0}/src/diplotocus/GUI/app.js +0 -0
  43. {diplotocus-0.5.0 → diplotocus-0.6.0}/src/diplotocus/GUI/server.py +0 -0
  44. {diplotocus-0.5.0 → diplotocus-0.6.0}/src/diplotocus/GUI/src/bg.png +0 -0
  45. {diplotocus-0.5.0 → diplotocus-0.6.0}/src/diplotocus/GUI/src/btn/btn_bg_Draw.png +0 -0
  46. {diplotocus-0.5.0 → diplotocus-0.6.0}/src/diplotocus/GUI/src/btn/btn_bg_Morph.png +0 -0
  47. {diplotocus-0.5.0 → diplotocus-0.6.0}/src/diplotocus/GUI/src/btn/btn_bg_Rotate.png +0 -0
  48. {diplotocus-0.5.0 → diplotocus-0.6.0}/src/diplotocus/GUI/src/btn/btn_bg_Scale.png +0 -0
  49. {diplotocus-0.5.0 → diplotocus-0.6.0}/src/diplotocus/GUI/src/btn/btn_bg_Sequence.png +0 -0
  50. {diplotocus-0.5.0 → diplotocus-0.6.0}/src/diplotocus/GUI/src/btn/btn_bg_Translate.png +0 -0
  51. {diplotocus-0.5.0 → diplotocus-0.6.0}/src/diplotocus/GUI/src/btn/btn_bg_Tween.png +0 -0
  52. {diplotocus-0.5.0 → diplotocus-0.6.0}/src/diplotocus/GUI/src/btn/btn_bg_default.png +0 -0
  53. {diplotocus-0.5.0 → diplotocus-0.6.0}/src/diplotocus/GUI/src/favicon.ico +0 -0
  54. {diplotocus-0.5.0 → diplotocus-0.6.0}/src/diplotocus/GUI/src/logo_bg.png +0 -0
  55. {diplotocus-0.5.0 → diplotocus-0.6.0}/src/diplotocus/GUI/style.css +0 -0
  56. {diplotocus-0.5.0 → diplotocus-0.6.0}/src/diplotocus/__init__.py +0 -0
  57. {diplotocus-0.5.0 → diplotocus-0.6.0}/src/diplotocus/easings.py +0 -0
  58. {diplotocus-0.5.0 → diplotocus-0.6.0}/src/diplotocus.egg-info/SOURCES.txt +0 -0
  59. {diplotocus-0.5.0 → diplotocus-0.6.0}/src/diplotocus.egg-info/dependency_links.txt +0 -0
  60. {diplotocus-0.5.0 → diplotocus-0.6.0}/src/diplotocus.egg-info/requires.txt +0 -0
  61. {diplotocus-0.5.0 → diplotocus-0.6.0}/src/diplotocus.egg-info/top_level.txt +0 -0
  62. {diplotocus-0.5.0 → diplotocus-0.6.0}/tests/test.py +0 -0
  63. {diplotocus-0.5.0 → diplotocus-0.6.0}/tests/test10.py +0 -0
  64. {diplotocus-0.5.0 → diplotocus-0.6.0}/tests/test2.py +0 -0
  65. {diplotocus-0.5.0 → diplotocus-0.6.0}/tests/test3.py +0 -0
  66. {diplotocus-0.5.0 → diplotocus-0.6.0}/tests/test4.py +0 -0
  67. {diplotocus-0.5.0 → diplotocus-0.6.0}/tests/test5.py +0 -0
  68. {diplotocus-0.5.0 → diplotocus-0.6.0}/tests/test6.py +0 -0
  69. {diplotocus-0.5.0 → diplotocus-0.6.0}/tests/test7.py +0 -0
  70. {diplotocus-0.5.0 → diplotocus-0.6.0}/tests/test8.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: diplotocus
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: A python package to generate animations of matplotlib objects.
5
5
  Author-email: Tristan Boin <tristan.boin@obspm.fr>
6
6
  License-Expression: MIT
@@ -45,7 +45,7 @@ Here's a quick example of the syntax of diplotocus:
45
45
  tl.animate(p)
46
46
 
47
47
  #We render our video
48
- tl.save_video(path='../_static/demo.mp4')
48
+ tl.save_video(path='demo.mp4')
49
49
 
50
50
  .. raw:: html
51
51
 
@@ -11,6 +11,11 @@
11
11
  "\n",
12
12
  "![](../_static/Web_GUI.png)\n",
13
13
  "\n",
14
+ "```{warning}\n",
15
+ "\n",
16
+ "The GUI might not be as stable as the python interface. Feel free to report any issues you encounters.\n",
17
+ "```\n",
18
+ "\n",
14
19
  "When running diplotocus through this mode, you will make edit your animation through a local web page in your browser. This page communicates with a python server, which links the GUI and diplotocus objects in the background. Hence, everything you can do through python, you can do through the GUI, and vice versa.\n",
15
20
  "\n",
16
21
  "```{note}\n",
@@ -817,6 +817,82 @@
817
817
  "tl.save_video('../_static/animations/axis_limits.mp4')"
818
818
  ]
819
819
  },
820
+ {
821
+ "cell_type": "markdown",
822
+ "id": "463d3df9",
823
+ "metadata": {},
824
+ "source": [
825
+ "### Axis pos and size\n",
826
+ "\n",
827
+ "```{eval-rst}\n",
828
+ ".. automethod:: diplotocus.animations.axis_pos_and_size\n",
829
+ " :noindex:\n",
830
+ "```"
831
+ ]
832
+ },
833
+ {
834
+ "cell_type": "code",
835
+ "execution_count": 4,
836
+ "id": "c01a5513",
837
+ "metadata": {},
838
+ "outputs": [
839
+ {
840
+ "data": {
841
+ "application/vnd.jupyter.widget-view+json": {
842
+ "model_id": "0c53fc7065614884aba3cfb420806912",
843
+ "version_major": 2,
844
+ "version_minor": 0
845
+ },
846
+ "text/plain": [
847
+ " 0%| | 0/60 [00:00<?, ?it/s]"
848
+ ]
849
+ },
850
+ "metadata": {},
851
+ "output_type": "display_data"
852
+ },
853
+ {
854
+ "data": {
855
+ "text/markdown": [
856
+ "Saved video ../_static/animations/axis_pos_and_size.mp4"
857
+ ],
858
+ "text/plain": [
859
+ "<IPython.core.display.Markdown object>"
860
+ ]
861
+ },
862
+ "metadata": {},
863
+ "output_type": "display_data"
864
+ },
865
+ {
866
+ "data": {
867
+ "text/html": [
868
+ "\n",
869
+ " <video width=\"640\" height=\"360\" autoplay loop muted playsinline>\n",
870
+ " <source src=\"../_static/animations/axis_pos_and_size.mp4?randId=4292\" type=\"video/mp4\">\n",
871
+ " </video>\n",
872
+ " "
873
+ ],
874
+ "text/plain": [
875
+ "<IPython.core.display.HTML object>"
876
+ ]
877
+ },
878
+ "metadata": {},
879
+ "output_type": "display_data"
880
+ }
881
+ ],
882
+ "source": [
883
+ "import diplotocus as dpl\n",
884
+ "import matplotlib.pyplot as plt\n",
885
+ "\n",
886
+ "fig,ax = plt.subplots(1,2)\n",
887
+ "\n",
888
+ "tl = dpl.Timeline(fig=fig)\n",
889
+ "\n",
890
+ "a = dpl.axis_pos_and_size(start_width=0.0,end_width=0.4,duration=60,axis=ax[1])\n",
891
+ "\n",
892
+ "tl.animate(a)\n",
893
+ "tl.save_video('../_static/animations/axis_pos_and_size.mp4')"
894
+ ]
895
+ },
820
896
  {
821
897
  "cell_type": "markdown",
822
898
  "id": "bc66aa88",
@@ -34,6 +34,7 @@
34
34
  "- `Rectangle()`\n",
35
35
  "- `RegularPolygon()`\n",
36
36
  "- `Wedge()`\n",
37
+ "- `bar()`\n",
37
38
  "\n",
38
39
  "Their signature are the same as the [matplotlib ones](https://matplotlib.org).\n",
39
40
  "\n",
@@ -0,0 +1 @@
1
+ __version__ = "0.6.0"
@@ -83,8 +83,6 @@ class plotObject:
83
83
  if self.mpl_obj_type is None:
84
84
  return None
85
85
  alias_map = self.mpl_obj_type._alias_map
86
- if self.mpl_plot_type == plt.plot and False:
87
- alias_map['color'] = ['c']
88
86
  return alias_map
89
87
 
90
88
  def get_possible_kwargs(self):
@@ -224,9 +222,13 @@ class plotObject:
224
222
  new_data_x = []
225
223
  new_data_y = []
226
224
  for i in range(len(data_x)):
227
- new_data_x.append(data_x[i] + (anim['new_x'][i] - data_x[i])*t)
225
+ if anim['sequential']:
226
+ _t = self.get_sequential_t(i,x,len(data_x),anim)
227
+ else:
228
+ _t = t
229
+ new_data_x.append(data_x[i] + (anim['new_x'][i] - data_x[i])*_t)
228
230
  if len(data_y) == len(anim['new_y']):
229
- new_data_y.append(data_y[i] + (anim['new_y'][i] - data_y[i])*t)
231
+ new_data_y.append(data_y[i] + (anim['new_y'][i] - data_y[i])*_t)
230
232
  data_x = new_data_x
231
233
  data_y = new_data_y
232
234
 
@@ -234,26 +236,50 @@ class plotObject:
234
236
  new_data_x_err = []
235
237
  new_data_y_err = []
236
238
  for i in range(len(kwargs['xerr'])):
237
- new_data_x_err.append(kwargs['xerr'][i] + (anim['new_x_err'][i] - kwargs['xerr'][i])*t)
238
- new_data_y_err.append(kwargs['yerr'][i] + (anim['new_y_err'][i] - kwargs['yerr'][i])*t)
239
+ if anim['sequential']:
240
+ _t = self.get_sequential_t(i,x,len(data_x),anim)
241
+ else:
242
+ _t = t
243
+ new_data_x_err.append(kwargs['xerr'][i] + (anim['new_x_err'][i] - kwargs['xerr'][i])*_t)
244
+ new_data_y_err.append(kwargs['yerr'][i] + (anim['new_y_err'][i] - kwargs['yerr'][i])*_t)
239
245
  kwargs['xerr'] = new_data_x_err
240
246
  kwargs['xerr'] = new_data_y_err
241
247
  elif self.__class__.__name__ == 'fill_between':
242
248
  new_y1 = []
243
249
  new_y2 = []
244
250
  for i in range(len(kwargs['y1'])):
245
- new_y1.append(kwargs['y1'][i] + (anim['new_y1'][i] - kwargs['y1'][i])*t)
246
- new_y2.append(kwargs['y2'][i] + (anim['new_y2'][i] - kwargs['y2'][i])*t)
251
+ if anim['sequential']:
252
+ _t = self.get_sequential_t(i,x,len(data_x),anim)
253
+ else:
254
+ _t = t
255
+ new_y1.append(kwargs['y1'][i] + (anim['new_y1'][i] - kwargs['y1'][i])*_t)
256
+ new_y2.append(kwargs['y2'][i] + (anim['new_y2'][i] - kwargs['y2'][i])*_t)
247
257
  kwargs['y1'] = new_y1
248
258
  kwargs['y2'] = new_y2
249
259
  elif self.__class__.__name__ == 'fill_betweenx':
250
260
  new_x1 = []
251
261
  new_x2 = []
252
262
  for i in range(len(kwargs['x1'])):
253
- new_x1.append(kwargs['x1'][i] + (anim['new_x1'][i] - kwargs['x1'][i])*t)
254
- new_x2.append(kwargs['x2'][i] + (anim['new_x2'][i] - kwargs['x2'][i])*t)
263
+ if anim['sequential']:
264
+ _t = self.get_sequential_t(i,x,len(data_x),anim)
265
+ else:
266
+ _t = t
267
+ new_x1.append(kwargs['x1'][i] + (anim['new_x1'][i] - kwargs['x1'][i])*_t)
268
+ new_x2.append(kwargs['x2'][i] + (anim['new_x2'][i] - kwargs['x2'][i])*_t)
255
269
  kwargs['x1'] = new_x1
256
270
  kwargs['x2'] = new_x2
271
+ elif self.__class__.__name__ == 'bar':
272
+ new_width = []
273
+ new_bottom = []
274
+ for i in range(len(data_x)):
275
+ if anim['sequential']:
276
+ _t = self.get_sequential_t(i,x,len(data_x),anim)
277
+ else:
278
+ _t = t
279
+ new_width.append(kwargs['width'][i] + (anim['new_width'][i] - kwargs['width'][i])*_t)
280
+ new_bottom.append(kwargs['bottom'][i] + (anim['new_bottom'][i] - kwargs['bottom'][i])*_t)
281
+ kwargs['width'] = new_width
282
+ kwargs['bottom'] = new_bottom
257
283
 
258
284
  data_x = to_np_array(data_x)
259
285
  data_y = to_np_array(data_y)
@@ -261,6 +287,19 @@ class plotObject:
261
287
  if self.function is not None:
262
288
  self.function(data_x,data_y,x,kwargs)
263
289
 
290
+ def get_sequential_t(self,i,x,size,anim):
291
+ delay = anim['delay'] + i/size*anim['duration']*(1-anim['subduration'])
292
+ duration = anim['duration']*anim['subduration']
293
+ if x < delay:
294
+ return 0
295
+ if x > delay + duration:
296
+ return 1
297
+ return self.get_t_from_x({
298
+ 'delay':delay,
299
+ 'duration':duration,
300
+ 'easing':anim['easing'],
301
+ },x)
302
+
264
303
  def initialize(self,timeline):
265
304
  self.tl = timeline
266
305
  if self.axis is None:
@@ -327,7 +366,7 @@ class plotObject:
327
366
  self.compute_timings()
328
367
  return self
329
368
 
330
- def tween(self,property,start,end,duration,delay=0,easing=None,persistent=True):
369
+ def tween(self,property,start,end,duration,delay=0,easing=None,persistent=True,sequential=False,subduration=0.1):
331
370
  """Animate a change in a property of the plot object.
332
371
 
333
372
  Parameters
@@ -344,10 +383,16 @@ class plotObject:
344
383
  the number of frames after what the animation starts playing.
345
384
  easing : callable, optional
346
385
  the easing used for this animation. If None, a linear easing is applied.
386
+ persistent : bool, default=True
387
+ if True, the plot object will continue to be plotted after its last animation has played.
388
+ sequential : bool, default=False
389
+ if True, the morphing will happen gradually over the data length
390
+ subduration : float, default=0.1
391
+ if sequential=True, the fractional duration of animation of each data point, in units of total duration
347
392
  """
348
- return self.tweens([property],[start],[end],duration,delay,easing,persistent=persistent)
393
+ return self.tweens([property],[start],[end],duration,delay,easing,persistent=persistent,sequential=sequential,subduration=subduration)
349
394
 
350
- def tweens(self,properties,starts,ends,duration,delay=0,easing=None,persistent=True):
395
+ def tweens(self,properties,starts,ends,duration,delay=0,easing=None,persistent=True,sequential=False,subduration=0.1):
351
396
  """Animate multiple properties at once.
352
397
 
353
398
  Parameters
@@ -364,6 +409,12 @@ class plotObject:
364
409
  the number of frames after what the animation starts playing.
365
410
  easing : callable, optional
366
411
  the easing used for this animation. If None, a linear easing is applied.
412
+ persistent : bool, default=True
413
+ if True, the plot object will continue to be plotted after its last animation has played.
414
+ sequential : bool, default=False
415
+ if True, the morphing will happen gradually over the data length
416
+ subduration : float, default=0.1
417
+ if sequential=True, the fractional duration of animation of each data point, in units of total duration
367
418
  """
368
419
  starts = [to_np_array(start) for start in starts]
369
420
  ends = [to_np_array(end) for end in ends]
@@ -380,6 +431,8 @@ class plotObject:
380
431
  'start':start,
381
432
  'end':end,
382
433
  'persistent':persistent,
434
+ 'sequential':sequential,
435
+ 'subduration':subduration,
383
436
  'played':False
384
437
  }
385
438
  self.anims.append(new_anim)
@@ -436,16 +489,24 @@ class plotObject:
436
489
  continue
437
490
  if anim['name'] != 'tween':
438
491
  continue
439
- _x = min(x,anim['duration'] + anim['delay'])
440
- t = self.get_t_from_x(anim,_x)
441
- t = np.clip(t,0,1)
442
492
  start = to_np_array(anim['start'])
443
493
  end = to_np_array(anim['end'])
444
- current = start + (end - start)*t
494
+
495
+ _x = min(x,anim['duration'] + anim['delay'])
496
+
497
+ if anim['sequential'] == False:
498
+ t = self.get_t_from_x(anim,_x)
499
+ t = np.clip(t,0,1)
500
+ current = start + (end - start)*t
501
+ else:
502
+ current = []
503
+ for i in range(len(self.x)):
504
+ _t = self.get_sequential_t(i,_x,len(self.x),anim)
505
+ current.append(start + (end - start)*_t)
445
506
  kwargs[anim['property']] = current
446
507
  return kwargs
447
508
 
448
- def show(self,duration,delay=0,easing=None,persistent=True):
509
+ def show(self,duration,delay=0,easing=None,persistent=True,sequential=False,subduration=0.1):
449
510
  """Fade in plot objects.
450
511
 
451
512
  This is a shorthand animation that fades in plot objects. It is equivalent to `tween('alpha',0,1)`.
@@ -460,10 +521,14 @@ class plotObject:
460
521
  the easing used for this animation. If None, a linear easing is applied.
461
522
  persistent : bool, default=True
462
523
  if True, the plot object will continue to be plotted after its last animation has played.
524
+ sequential : bool, default=False
525
+ if True, the morphing will happen gradually over the data length
526
+ subduration : float, default=0.1
527
+ if sequential=True, the fractional duration of animation of each data point, in units of total duration
463
528
  """
464
- return self.tween('alpha',start=0,end=1,duration=duration,delay=delay,easing=easing,persistent=persistent)
529
+ return self.tween('alpha',start=0,end=1,duration=duration,delay=delay,easing=easing,persistent=persistent,sequential=sequential,subduration=subduration)
465
530
 
466
- def hide(self,duration,delay=0,easing=None,persistent=True):
531
+ def hide(self,duration,delay=0,easing=None,persistent=True,sequential=False,subduration=0.1):
467
532
  """Fade out plot objects.
468
533
 
469
534
  This is a shorthand animation that fades out plot objects. It is equivalent to `tween('alpha',1,0)`.
@@ -478,8 +543,12 @@ class plotObject:
478
543
  the easing used for this animation. If None, a linear easing is applied.
479
544
  persistent : bool, default=True
480
545
  if True, the plot object will continue to be plotted after its last animation has played.
546
+ sequential : bool, default=False
547
+ if True, the morphing will happen gradually over the data length
548
+ subduration : float, default=0.1
549
+ if sequential=True, the fractional duration of animation of each data point, in units of total duration
481
550
  """
482
- return self.tween('alpha',start=1,end=0,duration=duration,delay=delay,easing=easing,persistent=persistent)
551
+ return self.tween('alpha',start=1,end=0,duration=duration,delay=delay,easing=easing,persistent=persistent,sequential=sequential,subduration=subduration)
483
552
 
484
553
  def draw(self,duration,reverse=False,sort=None,delay=0,easing=None,persistent=True):
485
554
  """Animate the plot object by sequentially adding more points of the dataset.
@@ -705,7 +774,7 @@ class plotObject:
705
774
  else:
706
775
  o.set_transform(self.T + self.axis.transData)
707
776
 
708
- def morph(self,new_x,new_y,duration,delay=0,easing=None,persistent=True):
777
+ def morph(self,new_x,new_y,duration,delay=0,easing=None,persistent=True,sequential=False,subduration=0.1):
709
778
  """Morph between the base dataset and a new dataset.
710
779
 
711
780
  Parameters
@@ -722,6 +791,10 @@ class plotObject:
722
791
  the easing used for this animation. If None, a linear easing is applied.
723
792
  persistent : bool, default=True
724
793
  if True, the plot object will continue to be plotted after its last animation has played.
794
+ sequential : bool, default=False
795
+ if True, the morphing will happen gradually over the data length
796
+ subduration : float, default=0.1
797
+ if sequential=True, the fractional duration of animation of each data point, in units of total duration
725
798
 
726
799
  For plot objects that take 1D datasets (e.g. hist()), `morph()` only accepts `new_x`.
727
800
  """
@@ -740,6 +813,8 @@ class plotObject:
740
813
  'new_x':new_x,
741
814
  'new_y':new_y,
742
815
  'persistent':persistent,
816
+ 'sequential':sequential,
817
+ 'subduration':subduration,
743
818
  'played':False
744
819
  })
745
820
  self.compute_timings()
@@ -775,7 +850,9 @@ class plotObject:
775
850
  def clean(self,x,clear_anims=True):
776
851
  if self.obj is not None and self.base_color is None:
777
852
  if isinstance(self.obj,(list,np.ndarray)):
778
- self.base_color = self.obj[0].get_color()
853
+ self.base_color = self.obj[0].get_color()
854
+ elif isinstance(self.obj,mpl.container.BarContainer):
855
+ self.base_color = self.obj[0].get_fc()
779
856
  elif hasattr(self.obj,'get_color'):
780
857
  self.base_color = self.obj.get_color()
781
858
  if 'color' not in self.kwargs and 'c' not in self.kwargs and self.base_color is not None:
@@ -789,7 +866,8 @@ class plotObject:
789
866
  self.x = anim['new_x']
790
867
  self.y = anim['new_y']
791
868
 
792
- if x >= self.x_min-1 and x < self.x_max-1 and self.obj is not None:
869
+ #if x >= self.x_min-1 and x < self.x_max-1 and self.obj is not None:
870
+ if self.obj is not None:
793
871
  if isinstance(self.obj,list):
794
872
  for obj in self.obj:
795
873
  try:
@@ -1005,6 +1083,94 @@ class axis_limits(plotObject):
1005
1083
  ylim_top = anim['start_ylim'][1] + (anim['ylim'][1] - anim['start_ylim'][1])*t
1006
1084
  self.axis.set_ylim(ylim_bottom,ylim_top)
1007
1085
 
1086
+ class axis_pos_and_size(plotObject):
1087
+ """
1088
+ Resize the axis to a given position and size.
1089
+
1090
+ Parameters
1091
+ ----------
1092
+ end_left : float
1093
+ Target left position
1094
+ end_bottom : float
1095
+ Target bottom position
1096
+ end_width : float
1097
+ Target width
1098
+ end_height : float
1099
+ Target height
1100
+ start_left = float
1101
+ Initial left position
1102
+ start_bottom : float
1103
+ Initial bottom position
1104
+ start_width : float
1105
+ Initial width
1106
+ start_height : float
1107
+ Initial height
1108
+ pos : array-like
1109
+ Target left and bottom position
1110
+ size : array-like
1111
+ Target width and height
1112
+ duration : float
1113
+ Duration of the animation
1114
+ delay : float, default=0
1115
+ Delay before starting
1116
+ easing : callable, optional
1117
+ Easing function
1118
+ axis : matplotlib.axes.Axes, optional
1119
+ Axis to plot on
1120
+ """
1121
+ def __init__(self,duration,
1122
+ end_left=None,end_bottom=None,end_width=None,end_height=None,
1123
+ start_left=None,start_bottom=None,start_width=None,start_height=None,
1124
+ delay=0,easing=None,axis=None,*args,**kwargs):
1125
+ super().__init__(axis=axis,*args, **kwargs)
1126
+ self.anims = [{
1127
+ 'name':'axis_move',
1128
+ 'duration':duration,
1129
+ 'delay':delay,
1130
+ 'easing':easing,
1131
+ 'persistent':True,
1132
+ 'start':[start_left,start_bottom,start_width,start_height],
1133
+ 'end':[end_left,end_bottom,end_width,end_height],
1134
+ 'played':False
1135
+ }]
1136
+ self.compute_timings()
1137
+
1138
+ def init(self):
1139
+ left_i = self.axis.get_position().x0
1140
+ bottom_i = self.axis.get_position().y0
1141
+ width_i = self.axis.get_position().width
1142
+ height_i = self.axis.get_position().height
1143
+ if self.anims[0]['start'][0] is None:
1144
+ self.anims[0]['start'][0] = left_i
1145
+ if self.anims[0]['start'][1] is None:
1146
+ self.anims[0]['start'][1] = bottom_i
1147
+ if self.anims[0]['start'][2] is None:
1148
+ self.anims[0]['start'][2] = width_i
1149
+ if self.anims[0]['start'][3] is None:
1150
+ self.anims[0]['start'][3] = height_i
1151
+ if self.anims[0]['end'][0] is None:
1152
+ self.anims[0]['end'][0] = left_i
1153
+ if self.anims[0]['end'][1] is None:
1154
+ self.anims[0]['end'][1] = bottom_i
1155
+ if self.anims[0]['end'][2] is None:
1156
+ self.anims[0]['end'][2] = width_i
1157
+ if self.anims[0]['end'][3] is None:
1158
+ self.anims[0]['end'][3] = height_i
1159
+
1160
+ def function(self,data_x,data_y,x,kwargs):
1161
+ anim = self.anims[0]
1162
+ if x < anim['delay']:
1163
+ return
1164
+ _x = min(x,anim['duration'] + anim['delay']-1)
1165
+ t = self.get_t_from_x(anim,x)
1166
+ width = self.axis.get_xlim()[1]-self.axis.get_xlim()[0]
1167
+ height = self.axis.get_ylim()[1]-self.axis.get_ylim()[0]
1168
+ pos_x = anim['start'][0] + (anim['end'][0] - anim['start'][0])*t
1169
+ pos_y = anim['start'][1] + (anim['end'][1] - anim['start'][1])*t
1170
+ width = anim['start'][2] + (anim['end'][2] - anim['start'][2])*t
1171
+ height = anim['start'][3] + (anim['end'][3] - anim['start'][3])*t
1172
+ self.axis.set_position((pos_x,pos_y,width,height))
1173
+
1008
1174
  class axis_move(plotObject):
1009
1175
  """
1010
1176
  Move the center of the axis to a given position.
@@ -2113,6 +2279,7 @@ class fill_between(plotObject):
2113
2279
  self.mpl_plot_type = plt.fill_between
2114
2280
  y1 = to_np_array(y1).reshape(-1)
2115
2281
  y2 = to_np_array(y2).reshape(-1)
2282
+ x = to_np_array(x)
2116
2283
  if y1.size != x.size:
2117
2284
  y1 = np.ones_like(x)*y1[0]
2118
2285
  if y2.size != x.size:
@@ -2130,7 +2297,7 @@ class fill_between(plotObject):
2130
2297
  self.kwargs['facecolor'] = self.base_color
2131
2298
  super().clean(x,clear_anims)
2132
2299
 
2133
- def morph(self,new_x,duration,new_y1=None,new_y2=None,delay=0,easing=None,persistent=True):
2300
+ def morph(self,new_x,duration,new_y1=None,new_y2=None,delay=0,easing=None,persistent=True,sequential=False,subduration=0.1):
2134
2301
  if new_y1 is None:
2135
2302
  new_y1 = self.y1
2136
2303
  if new_y2 is None:
@@ -2150,6 +2317,8 @@ class fill_between(plotObject):
2150
2317
  'new_y1':new_y1,
2151
2318
  'new_y2':new_y2,
2152
2319
  'persistent':persistent,
2320
+ 'sequential':sequential,
2321
+ 'subduration':subduration,
2153
2322
  'played':False
2154
2323
  })
2155
2324
  self.compute_timings()
@@ -2308,6 +2477,7 @@ class fill_betweenx(plotObject):
2308
2477
  self.mpl_plot_type = plt.fill_betweenx
2309
2478
  x1 = to_np_array(x1).reshape(-1)
2310
2479
  x2 = to_np_array(x2).reshape(-1)
2480
+ y = to_np_array(y)
2311
2481
  if x1.size != y.size:
2312
2482
  x1 = np.ones_like(y)*x1[0]
2313
2483
  if x2.size != y.size:
@@ -2325,7 +2495,7 @@ class fill_betweenx(plotObject):
2325
2495
  self.kwargs['facecolor'] = self.base_color
2326
2496
  super().clean(x,clear_anims)
2327
2497
 
2328
- def morph(self,new_y,duration,new_x1=None,new_x2=None,delay=0,easing=None,persistent=True):
2498
+ def morph(self,new_y,duration,new_x1=None,new_x2=None,delay=0,easing=None,persistent=True,sequential=False,subduration=0.1):
2329
2499
  if new_x1 is None:
2330
2500
  new_x1 = self.x1
2331
2501
  if new_x2 is None:
@@ -2345,6 +2515,8 @@ class fill_betweenx(plotObject):
2345
2515
  'new_x1':new_x1,
2346
2516
  'new_x2':new_x2,
2347
2517
  'persistent':persistent,
2518
+ 'sequential':sequential,
2519
+ 'subduration':subduration,
2348
2520
  'played':False
2349
2521
  })
2350
2522
  self.compute_timings()
@@ -2778,7 +2950,7 @@ class errorbar(plotObject):
2778
2950
  self.xerr = xerr
2779
2951
  self.yerr = yerr
2780
2952
 
2781
- def morph(self,new_x,new_y,duration,new_xerr=None,new_yerr=None,delay=0,easing=None,persistent=True):
2953
+ def morph(self,new_x,new_y,duration,new_xerr=None,new_yerr=None,delay=0,easing=None,persistent=True,sequential=False,subduration=0.1):
2782
2954
  if new_xerr is None:
2783
2955
  new_xerr = self.xerr
2784
2956
  if new_yerr is None:
@@ -2799,6 +2971,8 @@ class errorbar(plotObject):
2799
2971
  'new_x_err':new_xerr,
2800
2972
  'new_y_err':new_yerr,
2801
2973
  'persistent':persistent,
2974
+ 'sequential':sequential,
2975
+ 'subduration':subduration,
2802
2976
  'played':False
2803
2977
  })
2804
2978
  self.compute_timings()
@@ -3035,7 +3209,7 @@ class hist(plotObject):
3035
3209
  self.kwargs['color'] = self.base_color
3036
3210
  super().clean(x,clear_anims)
3037
3211
 
3038
- def morph(self,new_x,duration,delay=0,easing=None,persistent=True,**kwargs):
3212
+ def morph(self,new_x,duration,delay=0,easing=None,persistent=True,sequential=False,subduration=0.1,**kwargs):
3039
3213
  if isinstance(new_x,numbers.Number):
3040
3214
  new_x = [new_x]
3041
3215
 
@@ -3047,6 +3221,8 @@ class hist(plotObject):
3047
3221
  'new_x':new_x,
3048
3222
  'new_y':np.zeros_like(self.y),
3049
3223
  'persistent':persistent,
3224
+ 'sequential':sequential,
3225
+ 'subduration':subduration,
3050
3226
  'played':False
3051
3227
  })
3052
3228
  self.compute_timings()
@@ -3530,7 +3706,7 @@ class contourf(plotObject):
3530
3706
  self._y = np.array(self._y)
3531
3707
  self.init_shape = z.shape
3532
3708
 
3533
- def morph(self,new_z,duration,delay=0,easing=None,persistent=True,**kwargs):
3709
+ def morph(self,new_z,duration,delay=0,easing=None,persistent=True,sequential=False,subduration=0.1,**kwargs):
3534
3710
  self.anims.append({
3535
3711
  'name':'morph',
3536
3712
  'duration':duration,
@@ -3539,6 +3715,8 @@ class contourf(plotObject):
3539
3715
  'new_x':np.ravel(new_z),
3540
3716
  'new_y':np.ravel(new_z),
3541
3717
  'persistent':persistent,
3718
+ 'sequential':sequential,
3719
+ 'subduration':subduration,
3542
3720
  'played':False
3543
3721
  })
3544
3722
  self.compute_timings()
@@ -4390,4 +4568,194 @@ class Wedge(plotObject):
4390
4568
  kwargs['theta2'] = kwargs['theta2'][0]
4391
4569
 
4392
4570
  self.obj = mpl.patches.Wedge(**kwargs)
4393
- self.axis.add_patch(self.obj)
4571
+ self.axis.add_patch(self.obj)
4572
+
4573
+ class bar(plotObject):
4574
+ """
4575
+ Make a bar plot.
4576
+
4577
+ The bars are positioned at *x* with the given *align*ment. Their
4578
+ dimensions are given by *height* and *width*. The vertical baseline
4579
+ is *bottom* (default 0).
4580
+
4581
+ Many parameters can take either a single value applying to all bars
4582
+ or a sequence of values, one for each bar.
4583
+
4584
+ Parameters
4585
+ ----------
4586
+ x : float or array-like
4587
+ The x coordinates of the bars. See also *align* for the
4588
+ alignment of the bars to the coordinates.
4589
+
4590
+ Bars are often used for categorical data, i.e. string labels below
4591
+ the bars. You can provide a list of strings directly to *x*.
4592
+ ``bar(['A', 'B', 'C'], [1, 2, 3])`` is often a shorter and more
4593
+ convenient notation compared to
4594
+ ``bar(range(3), [1, 2, 3], tick_label=['A', 'B', 'C'])``. They are
4595
+ equivalent as long as the names are unique. The explicit *tick_label*
4596
+ notation draws the names in the sequence given. However, when having
4597
+ duplicate values in categorical *x* data, these values map to the same
4598
+ numerical x coordinate, and hence the corresponding bars are drawn on
4599
+ top of each other.
4600
+
4601
+ height : float or array-like
4602
+ The height(s) of the bars.
4603
+
4604
+ Note that if *bottom* has units (e.g. datetime), *height* should be in
4605
+ units that are a difference from the value of *bottom* (e.g. timedelta).
4606
+
4607
+ width : float or array-like, default: 0.8
4608
+ The width(s) of the bars.
4609
+
4610
+ Note that if *x* has units (e.g. datetime), then *width* should be in
4611
+ units that are a difference (e.g. timedelta) around the *x* values.
4612
+
4613
+ bottom : float or array-like, default: 0
4614
+ The y coordinate(s) of the bottom side(s) of the bars.
4615
+
4616
+ Note that if *bottom* has units, then the y-axis will get a Locator and
4617
+ Formatter appropriate for the units (e.g. dates, or categorical).
4618
+
4619
+ align : {'center', 'edge'}, default: 'center'
4620
+ Alignment of the bars to the *x* coordinates:
4621
+
4622
+ - 'center': Center the base on the *x* positions.
4623
+ - 'edge': Align the left edges of the bars with the *x* positions.
4624
+
4625
+ To align the bars on the right edge pass a negative *width* and
4626
+ ``align='edge'``.
4627
+
4628
+ Returns
4629
+ -------
4630
+ `.BarContainer`
4631
+ Container with all the bars and optionally errorbars.
4632
+
4633
+ Other Parameters
4634
+ ----------------
4635
+ color : :mpltype:`color` or list of :mpltype:`color`, optional
4636
+ The colors of the bar faces. This is an alias for *facecolor*.
4637
+ If both are given, *facecolor* takes precedence.
4638
+
4639
+ facecolor : :mpltype:`color` or list of :mpltype:`color`, optional
4640
+ The colors of the bar faces.
4641
+ If both *color* and *facecolor are given, *facecolor* takes precedence.
4642
+
4643
+ edgecolor : :mpltype:`color` or list of :mpltype:`color`, optional
4644
+ The colors of the bar edges.
4645
+
4646
+ linewidth : float or array-like, optional
4647
+ Width of the bar edge(s). If 0, don't draw edges.
4648
+
4649
+ tick_label : str or list of str, optional
4650
+ The tick labels of the bars.
4651
+ Default: None (Use default numeric labels.)
4652
+
4653
+ label : str or list of str, optional
4654
+ A single label is attached to the resulting `.BarContainer` as a
4655
+ label for the whole dataset.
4656
+ If a list is provided, it must be the same length as *x* and
4657
+ labels the individual bars. Repeated labels are not de-duplicated
4658
+ and will cause repeated label entries, so this is best used when
4659
+ bars also differ in style (e.g., by passing a list to *color*.)
4660
+
4661
+ xerr, yerr : float or array-like of shape(N,) or shape(2, N), optional
4662
+ If not *None*, add horizontal / vertical errorbars to the bar tips.
4663
+ The values are +/- sizes relative to the data:
4664
+
4665
+ - scalar: symmetric +/- values for all bars
4666
+ - shape(N,): symmetric +/- values for each bar
4667
+ - shape(2, N): Separate - and + values for each bar. First row
4668
+ contains the lower errors, the second row contains the upper
4669
+ errors.
4670
+ - *None*: No errorbar. (Default)
4671
+
4672
+ See :doc:`/gallery/statistics/errorbar_features` for an example on
4673
+ the usage of *xerr* and *yerr*.
4674
+
4675
+ ecolor : :mpltype:`color` or list of :mpltype:`color`, default: 'black'
4676
+ The line color of the errorbars.
4677
+
4678
+ capsize : float, default: :rc:`errorbar.capsize`
4679
+ The length of the error bar caps in points.
4680
+
4681
+ error_kw : dict, optional
4682
+ Dictionary of keyword arguments to be passed to the
4683
+ `~.Axes.errorbar` method. Values of *ecolor* or *capsize* defined
4684
+ here take precedence over the independent keyword arguments.
4685
+
4686
+ log : bool, default: False
4687
+ If *True*, set the y-axis to be log scale.
4688
+
4689
+ data : indexable object, optional
4690
+ DATA_PARAMETER_PLACEHOLDER
4691
+
4692
+ **kwargs : `.Rectangle` properties
4693
+
4694
+ %(Rectangle:kwdoc)s
4695
+
4696
+ See Also
4697
+ --------
4698
+ barh : Plot a horizontal bar plot.
4699
+
4700
+ Notes
4701
+ -----
4702
+ Stacked bars can be achieved by passing individual *bottom* values per
4703
+ bar. See :doc:`/gallery/lines_bars_and_markers/bar_stacked`.
4704
+ """
4705
+ def __init__(self,x,height,width=0.8,bottom=0,easing=None,axis=None, *args, **kwargs):
4706
+ #self.mpl_obj_type = mpl.container.BarContainer
4707
+ self.mpl_obj_type = mpl.patches.Rectangle
4708
+ self.mpl_plot_type = plt.bar
4709
+ if np.ravel(width).size == 1:
4710
+ width = np.ones_like(x)*width
4711
+ if np.ravel(height).size == 1:
4712
+ height = np.ones_like(x)*height
4713
+ if np.ravel(bottom).size == 1:
4714
+ bottom = np.ones_like(x)*bottom
4715
+ kwargs['width'] = width
4716
+ kwargs['bottom'] = bottom
4717
+ super().__init__(easing=easing,axis=axis,*args, **kwargs)
4718
+ self.x = x
4719
+ self.y = height
4720
+
4721
+ def morph(self,new_x,new_height,duration,new_width=None,new_bottom=None,delay=0,easing=None,persistent=True,sequential=False,subduration=0.1):
4722
+ if new_width is None:
4723
+ new_width = self.kwargs['width']
4724
+ elif np.ravel(new_width).size == 1:
4725
+ new_width = np.ones_like(new_x)*new_width
4726
+ if new_bottom is None:
4727
+ new_bottom = self.kwargs['bottom']
4728
+ elif np.ravel(new_bottom).size == 1:
4729
+ new_bottom = np.ones_like(new_x)*new_bottom
4730
+ if isinstance(new_x,numbers.Number):
4731
+ new_x = [new_x]
4732
+ new_height = [new_height]
4733
+ new_width = [new_width]
4734
+ new_bottom = [new_bottom]
4735
+
4736
+ self.anims.append({
4737
+ 'name':'morph',
4738
+ 'duration':duration,
4739
+ 'delay':delay,
4740
+ 'easing':easing,
4741
+ 'new_x':new_x,
4742
+ 'new_y':new_height,
4743
+ 'new_width':new_width,
4744
+ 'new_bottom':new_bottom,
4745
+ 'persistent':persistent,
4746
+ 'sequential':sequential,
4747
+ 'subduration':subduration,
4748
+ 'played':False
4749
+ })
4750
+ self.compute_timings()
4751
+ return self
4752
+
4753
+ def function(self,data_x,data_y,x,kwargs):
4754
+ #This is a hack because when plotting a bar with empty data, no children are stored
4755
+ #but the color cycler still triggers, so a color is assigned by we can't know what
4756
+ #it is. So instead we plot a single bar with height 0 and get its color.
4757
+ if len(data_x) == 0:
4758
+ data_x = [0]
4759
+ data_y = [0]
4760
+ obj = self.axis.bar(data_x,data_y,**kwargs)
4761
+ self.obj = obj
@@ -385,15 +385,17 @@ class Timeline:
385
385
  f.write(self.timeline_str)
386
386
  sequence_fn = self.name + '/' + self.name + '.txt'
387
387
 
388
+ ffmpeg_flags = '-hide_banner -loglevel error -vf "pad=ceil(iw/2)*2:ceil(ih/2)*2"'
389
+
388
390
  if '.mov' in video_fn:
389
391
  if multialpha:
390
- 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)
392
+ command = ffmpeg_path + ' -f concat -safe 0 -i {} -y -vf premultiply=inplace=1 -c:v prores_ks -profile:v 4 -pix_fmt yuva444p10le {} {}'.format(sequence_fn,ffmpeg_flags,video_fn)
391
393
  else:
392
- command = ffmpeg_path + ' -f concat -safe 0 -i {} -y -c:v prores -pix_fmt yuva444p10le -hide_banner -loglevel error {}'.format(sequence_fn,video_fn)
394
+ command = ffmpeg_path + ' -f concat -safe 0 -i {} -y -c:v prores -pix_fmt yuva444p10le {} {}'.format(sequence_fn,ffmpeg_flags,video_fn)
393
395
  elif is_gif:
394
- 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)
396
+ 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 {}'.format(sequence_fn,ffmpeg_flags,video_fn)
395
397
  else:
396
- 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)
398
+ command = ffmpeg_path + ' -f concat -safe 0 -i {} -y -c:v libx264 -pix_fmt yuv420p -c:a aac -movflags +faststart {} {}'.format(sequence_fn,ffmpeg_flags,video_fn)
397
399
  subprocess.run(shlex.split(command))
398
400
  if clean:
399
401
  shutil.rmtree(self.name)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: diplotocus
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: A python package to generate animations of matplotlib objects.
5
5
  Author-email: Tristan Boin <tristan.boin@obspm.fr>
6
6
  License-Expression: MIT
@@ -1 +0,0 @@
1
- __version__ = "0.5.0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes