├── .gitignore ├── LICENSE.txt ├── README.md ├── animation_playground.ipynb ├── easing ├── __init__.py └── easing.py ├── media ├── animatedbar.gif ├── chroma.gif ├── comparison.gif ├── interpolation_schema.png ├── multipoint.gif ├── normdist.gif ├── singlepoint.gif └── traces.png ├── outputs └── output.mp4 ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/a 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | /tools/df/genomes 106 | 107 | # Andrew's 108 | notebooks/tmp/ 109 | *.DS_Store 110 | .gitignore 111 | settings.json 112 | notebooks/MultiGuide_Analysis/tmp 113 | tools/cytof_atlas/ 114 | tools/seqlogo/ 115 | tools/iceio/ 116 | .Rproj.user 117 | 118 | # Cytof Data 119 | ## ignore tmp folder 120 | epic/cytof/projects/090618_tmp/ 121 | 122 | ## all csv, pdf, png output 123 | epic/cytof/**/*.csv 124 | epic/cytof/**/*.pdf 125 | epic/cytof/**/*.png 126 | epic/cytof/**/*.zip 127 | 128 | ## rmarkdown related 129 | epic/cytof/**/*.html 130 | epic/cytof/**/*.htm 131 | epic/cytof/*.DS_Store 132 | 133 | ## fcs data 134 | epic/cytof/**/*.fcs 135 | epic/cytof/**/*.wsp 136 | 137 | ## models, objects 138 | epic/cytof/**/*.pkl 139 | epic/cytof/**/*.Rdata 140 | epic/cytof/*checkpoint.ipynb 141 | epic/cytof/projects/**/models/ 142 | epic/cytof/.Rproj.user 143 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | Copyright (c) 2019 NICHOLAS ROSSI 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | The above copyright notice and this permission notice shall be included in all 10 | copies or substantial portions of the Software. 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Easing Animations with Python 2 | ![chroma](media/chroma.gif) 3 | 4 | This package is meant to make animations easy in python. There are a number of methods built around an "easing" class. 5 | 6 | # Quick-start guide 7 | Full walk-through available [in this notebook](animation_playground.ipynb) 8 | 9 | begin by installing via pip 10 | 11 | ```python 12 | pip install easing 13 | 14 | from easing import easing 15 | ``` 16 | 17 | In order to render any of these animation, you'll need to have the following installed 18 | 19 | * FFMPEG for .mp4 renders (brew install ffmpeg on mac) 20 | * Imagekick for .gif renders(brew install imagemagick) 21 | 22 | 23 | This code takes either numpy arrays or pandas dataframes and turns them into animations. You can make scatter plots.. 24 | ```python 25 | data=np.random.random((10,2)) 26 | easing.Eased(data).scatter_animation2d(n=3,speed=0.5,destination='media/singlepoint.gif') 27 | ``` 28 | ![scatter1](media/singlepoint.gif) 29 | 30 | ```python 31 | size=100 32 | u=np.random.multivariate_normal([1,1],[[1, 0.5], [0.5, 1]],size=size).reshape(1,-1) 33 | v=np.random.multivariate_normal([1,1],[[2, 1], [1, 2]],size=size).reshape(1,-1) 34 | w=np.random.multivariate_normal([1,1],[[4, 2], [2, 4]],size=size).reshape(1,-1) 35 | 36 | data=pd.DataFrame(np.vstack([u,v,w]),index=['small','medium','large']) 37 | easing.Eased(data).scatter_animation2d(speed=0.5,label=True,plot_kws={'alpha':0.5},destination='media/multipoint.gif') 38 | ``` 39 | ![scatter2](media/multipoint.gif) 40 | 41 | Animated barcharts... 42 | ```python 43 | data=pd.DataFrame(abs(np.random.random((3, 5))), 44 | index=['Jurassic', 'Cretaceous', 'Precambrian'], 45 | columns=['Tyrannosaurus','Pterodactyl','Stegosaurus','Raptor','Megaloadon']) 46 | 47 | easing.Eased(data).barchart_animation(plot_kws={'ylim':[0,1],'xlim':[-2,5]},smoothness=40,speed=0.5,label=True,destination='media/animatedbar.gif') 48 | ``` 49 | ![bar](media/animatedbar.gif) 50 | 51 | Or time series plots... 52 | ```python 53 | data = np.random.normal(scale=0.5,size=400) 54 | easing.Eased(data).timeseries_animation(starting_pos = 25, 55 | speed=25, 56 | plot_kws={'ylim':[-3,3],'bins':np.linspace(-3,3,50)},destination='media/normdist.gif') 57 | ``` 58 | ![ergodic](media/normdist.gif) 59 | 60 | # Further Explanations 61 | 62 | Ingredients necessary to make the animations: 63 | * an intitial time vector 64 | * a dependent data matrix - each row will be a different variable, the columns correspond to the initial time vector 65 | * an output time vecotr upon which to be interpolated 66 | 67 | # Interpolation class 68 | Before generating the easing, you must create the object of class easing 69 | ```python 70 | ease = Eased(data, input_time_vector, output_time_vector) 71 | ``` 72 | All subsequent functions will be called on this object. 73 | 74 | ![traces](media/interpolation_schema.png) 75 | 76 | # Power Interpolation 77 | 78 | The primary form of interpolative easing (or *tweening*) is based on powers (e.g. linear, quadratic, cubic etc.) 79 | The power easing function takes one variable - the exponent integer (n). Increasing the interger 80 | increases the *sharpness* of the transition. 81 | 82 | ```python 83 | out_data = ease.power_ease(n) 84 | ``` 85 | ![traces](media/traces.png) 86 | 87 | ![Demo](media/comparison.gif) 88 | 89 | # No interpolation 90 | If you simply want to extend the data to have the same number of points as an interpolated set 91 | without actually interpolating, simply call the No_interp() function 92 | ```python 93 | out_data = ease.No_interp() 94 | ``` 95 | 96 | 119 | -------------------------------------------------------------------------------- /easing/__init__.py: -------------------------------------------------------------------------------- 1 | from easing import easing 2 | -------------------------------------------------------------------------------- /easing/easing.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | import pandas as pd 4 | from matplotlib import animation, rc 5 | rc('animation', html='html5') 6 | from IPython.display import HTML, Image 7 | from itertools import groupby 8 | 9 | 10 | class Eased: 11 | """ This class takes the original time vector and raw data (as a m*n matrix or dataframe) along with an output vector and interpolation function 12 | For the input data, the rows are the different variables and the columns correspond to the time points""" 13 | 14 | def __init__(self, data,data_y=None, in_t=None): 15 | 16 | if isinstance(data, pd.DataFrame): 17 | self.labels=np.append(data.index.values,np.array([data.index.values[0],data.index.values[0]])) 18 | self.int_t = np.arange(len(self.labels)-1) 19 | 20 | 21 | self.data = np.vstack((data.values,data.values[0,:])) 22 | self.n_dims = data.shape[1] 23 | self.columns=data.columns 24 | elif isinstance(data, np.ndarray): 25 | if in_t is None: 26 | in_t=np.arange(np.shape(data)[0]) 27 | print("No time vector included - defaulting to number of rows") 28 | 29 | self.int_t = in_t 30 | self.data = data 31 | self.n_dims = len(np.shape(data)) 32 | else: 33 | print('\033[91m' + "Data is unrecognized type : must be either a numpy array or pandas dataframe") 34 | 35 | 36 | def No_interp(self,smoothness=10): 37 | out_t=np.linspace(min(self.int_t),max(self.int_t),len(self.int_t)*smoothness) 38 | self.n_steps = int(np.ceil(len(out_t) / len(self.int_t))) 39 | self.out_t = out_t 40 | 41 | #This Function maps the input vecotor over the outuput time vector without interoplation 42 | if self.n_dims == 1: # if the input is only one row 43 | self.eased = np.zeros((len(self.out_t), 1)) 44 | for i, t in enumerate(self.out_t): 45 | self.eased[i] = self.data[int(np.floor(i / self.n_steps))] 46 | else: #if the input is a multidimensional row 47 | self.eased = np.zeros((np.shape(self.data)[0], len(self.out_t))) 48 | for z in range(np.shape(self.data)[0]): 49 | for i, t in enumerate(self.out_t): 50 | self.eased[z, i] = self.data[z, int(np.floor(i / self.n_steps))] 51 | 52 | return self.eased 53 | 54 | def power_ease(self, n,smoothness=10): 55 | out_t=np.linspace(min(self.int_t),max(self.int_t),len(self.int_t)*smoothness) 56 | self.n_steps = int(np.ceil(len(out_t) / len(self.int_t))) 57 | self.out_t = out_t 58 | sign = n % 2 * 2 59 | if self.n_dims == 1: 60 | self.eased = np.zeros((len(self.out_t), 1)) 61 | j = 0 62 | for i in range(len(self.int_t) - 1): 63 | 64 | start = self.data[i] 65 | end = self.data[i + 1] 66 | for t in np.linspace(0, 2, self.n_steps): 67 | if (t < 1): 68 | val = (end - start) / 2 * t ** n + start 69 | 70 | else: 71 | t -= 2 72 | val = (1 - sign) * (-(end - start) / 2) * (t ** n - 2 * (1 - sign)) + start 73 | 74 | self.eased[j] = val 75 | j += 1 76 | self.eased[j:] = self.data[i + 1] 77 | 78 | else: 79 | self.eased = np.zeros(( len(self.out_t),np.shape(self.data)[1])) 80 | for z in range(np.shape(self.data)[1]): 81 | j = 0 82 | for i in range(len(self.int_t) - 1): 83 | 84 | start = self.data[ i,z] 85 | end = self.data[ i + 1,z] 86 | for t in np.linspace(0, 2, self.n_steps): 87 | if (t < 1): 88 | val = (end - start) / 2 * t ** n + start 89 | 90 | else: 91 | t -= 2 92 | val = (1 - sign) * (-(end - start) / 2) * (t ** n - 2 * (1 - sign)) + start 93 | 94 | self.eased[ j,z] = val 95 | j += 1 96 | self.eased[ j:,z] = self.data[ i + 1,z] 97 | 98 | return self.eased 99 | 100 | 101 | def scatter_animation2d(self,n=3,smoothness=30,speed=1.0,gif=False,destination=None,plot_kws=None,label=False): 102 | """ 103 | Flexibly create a 2d scatter plot animation. 104 | 105 | This function creates a matplotlib animation from a pandas Dataframe or a MxN numpy array. The Columns are paired 106 | with x and y coordinates while the rows are the individual time points. 107 | 108 | This takes a number of parameters for the animation, as well as 109 | 110 | 111 | Parameters 112 | ---------- 113 | n: Exponent of the power smoothing 114 | smoothness: how smooth the frames of the animation are 115 | speed: speed 116 | inline: 117 | gif: 118 | destination: 119 | :return: 120 | """ 121 | 122 | 123 | #Running checks on data for mishappen arrays. 124 | if np.shape(self.data)[1]%2!=0: 125 | print('\033[91m' + "Failed: Data must have an even number of columns") 126 | exit() 127 | if np.shape(self.data)[0]0] 197 | 198 | :param destination: This is the output file (if none it will be displayed inline for jupyter notebooks) - extension determines filetype 199 | 200 | 201 | :param plot_kws: These are the matplotlib key work arghuments that can be passed in the event the defaults don't work great 202 | :param label: This is an optional paramter that will display labels of the pandas rows as the animation cycles through 203 | 204 | :return: rendered animation 205 | ''' 206 | 207 | 208 | 209 | it_data = self.power_ease(n, smoothness) 210 | 211 | x_vect=np.arange(len(self.columns)) 212 | 213 | 214 | ### running checks on the paramters 215 | 216 | #Runing checks on parameters 217 | assert speed>0, "Speed value must be greater than zero" 218 | 219 | 220 | # filling out missing keys 221 | vanilla_params = {'s': 10, 'color': 'black', 'xlim': [min(x_vect) - 1, max(x_vect) + 1], 222 | 'ylim': [np.min(it_data) - 1, np.max(it_data) + 1], 'xlabel': '', 'ylabel': '','title': '', 223 | 'alpha': 1.0, 'figsize': (6, 6)} 224 | for key in vanilla_params.keys(): 225 | if key not in plot_kws.keys(): 226 | plot_kws[key] = vanilla_params[key] 227 | 228 | fig, ax = plt.subplots(figsize=plot_kws['figsize']) 229 | ax.set_xlim(plot_kws['xlim']) 230 | ax.set_ylim(plot_kws['ylim']) 231 | ax.set_title(plot_kws['title']) 232 | ax.set_xlabel(plot_kws['xlabel']) 233 | ax.set_ylabel(plot_kws['ylabel']) 234 | ax.set_xticks(x_vect-np.mean(np.diff(x_vect))/2) 235 | ax.set_xticklabels(list(self.columns),rotation=90) 236 | 237 | plt.tight_layout() 238 | if label == True: 239 | label_text = ax.text(plot_kws['xlim'][1] * 0.25, plot_kws['ylim'][1] * .9, '', fontsize=18) 240 | 241 | lines=[] 242 | lines.append(ax.plot([], [], linewidth=3, drawstyle='steps-pre', color=plot_kws['color'], alpha=plot_kws['alpha'])) 243 | 244 | 245 | # add zero padding to the data // makes for prettier histogram presentation 246 | if zero_edges==True: 247 | zero_pad=np.zeros((it_data.shape[0],1)) 248 | it_data=np.hstack((zero_pad,it_data,zero_pad)) 249 | x_vect=[min(x_vect)-1]+list(x_vect)+[max(x_vect)+1] 250 | 251 | def animate(z): 252 | lines[0][0].set_data(x_vect, it_data[z, :]) 253 | 254 | if label==True: 255 | label_text.set_text(self.labels[int(np.floor((z+smoothness/2)/smoothness))]) 256 | return lines,label_text 257 | else: 258 | return lines 259 | 260 | 261 | anim = animation.FuncAnimation(fig, animate, frames=it_data.shape[0],interval=400/smoothness/speed, blit=False) 262 | 263 | 264 | if destination is not None: 265 | if destination.split('.')[-1]=='mp4': 266 | writer = animation.writers['ffmpeg'](fps=60) 267 | anim.save(destination, writer=writer, dpi=100) 268 | if destination.split('.')[-1]=='gif': 269 | anim.save(destination, writer='imagemagick', fps=smoothness) 270 | 271 | if gif==True: 272 | return Image(url='animation.gif') 273 | else: 274 | return anim 275 | 276 | def timeseries_animation(self,n=1,speed=1.0,interp_freq=0,starting_pos = 25,gif=False,destination=None,plot_kws=None,final_dist=False): 277 | ''' 278 | This method creates a timeseiers animation of ergodic processes 279 | :param smoothness: 280 | :param speed: 281 | :param interp_freq: This is the number of steps between each given datapoint interp_freq=1 // no additional steps 282 | :param gif: 283 | :param destination: 284 | :param plot_kws: 285 | :param label: 286 | :param zero_edges: 287 | :param loop: 288 | :return: 289 | ''' 290 | interp_freq+=1 291 | 292 | data = self.power_ease(n=n, smoothness=interp_freq) 293 | 294 | assert min(data.shape)==1, "timeseries animation only take 1 dimensional arrays" 295 | 296 | data=[k for k, g in groupby(list(data))] 297 | 298 | fig, ax = plt.subplots(1, 2, figsize=(12, 4),gridspec_kw={'width_ratios': [3, 1]},sharey=True) 299 | 300 | 301 | 302 | max_steps=len(data) 303 | 304 | 305 | vanilla_params = {'s': 10, 'color': 'black', 'xlim': [0, starting_pos], 306 | 'ylim': [np.min(data) - 1, np.max(data) + 1], 'xlabel': '', 'ylabel': '','title': '', 307 | 'alpha': 1.0, 'figsize': (12, 3),'linestyle':'none','marker':'o'} 308 | if plot_kws==None: 309 | plot_kws={} 310 | x_vect=np.linspace(1,starting_pos,starting_pos*interp_freq) 311 | 312 | # Creating NaN padding at the end for time series plot 313 | data = np.append(data, x_vect * np.nan) 314 | 315 | # fill out parameters 316 | for key in vanilla_params.keys(): 317 | if key not in plot_kws.keys(): 318 | plot_kws[key] = vanilla_params[key] 319 | 320 | ax[0].set_ylim(plot_kws['ylim']) 321 | ax[1].set_ylim(plot_kws['ylim']) 322 | 323 | ax[0].set_xlim(plot_kws['xlim']) 324 | lines=[] 325 | lines.append(ax[0].plot([], [], linewidth=3, color=plot_kws['color'], alpha=plot_kws['alpha'],linestyle=plot_kws['linestyle'], marker=plot_kws['marker'])) 326 | if 'bins' not in plot_kws.keys(): 327 | plot_kws['bins']=np.linspace(plot_kws['ylim'][0],plot_kws['ylim'][1],20) 328 | 329 | 330 | #plotting light grey final dist: 331 | if final_dist==True: 332 | bins, x = np.histogram(data,bins=plot_kws['bins']) 333 | ax[1].plot(bins, x[1:], linewidth=3, drawstyle='steps-pre', color='#d3d3d3') 334 | 335 | else: 336 | bins, x = np.histogram(data,bins=plot_kws['bins']) 337 | ax[1].plot(bins, x[1:], linewidth=3, drawstyle='steps-pre', color='#d3d3d3',alpha=0) 338 | 339 | 340 | histlines=[] 341 | histlines.append(ax[1].plot([], [], linewidth=3, drawstyle='steps-pre',color=plot_kws['color'], alpha=plot_kws['alpha'])) 342 | 343 | 344 | # This function plots the distribution of flowing information // so we start at the beining and plot forward 345 | # reverse the orientation of data 346 | trace_data=data[::-1] 347 | 348 | 349 | def animate(z): 350 | lines[0][0].set_data(x_vect, trace_data[-(starting_pos*interp_freq+1)-z:-1-z]) 351 | 352 | # compute the histogram of what what has passed 353 | if z>0: 354 | bins, x = np.histogram(trace_data[-(z):-1],bins=plot_kws['bins']) 355 | histlines[0][0].set_data(bins,x[1:]) 356 | lines.append(ax[1].plot([], [], linewidth=3, color=plot_kws['color'], alpha=plot_kws['alpha'])) 357 | 358 | 359 | return lines 360 | 361 | 362 | anim = animation.FuncAnimation(fig, animate, frames=max_steps,interval=400/speed, blit=False) 363 | 364 | 365 | if destination is not None: 366 | if destination.split('.')[-1]=='mp4': 367 | writer = animation.writers['ffmpeg'](fps=60) 368 | anim.save(destination, writer=writer, dpi=100) 369 | if destination.split('.')[-1]=='gif': 370 | anim.save(destination, writer='imagemagick', fps=30) 371 | 372 | if gif==True: 373 | return Image(url='animation.gif') 374 | else: 375 | return anim 376 | 377 | 378 | 379 | if __name__ == "__main__": 380 | print('EASING : A library for smooth animations in python : version 0.1.0') 381 | # simple example : one point moving over time 382 | # data = np.random.random((10, 2)) 383 | # Eased(data).scatter_animation2d(n=3, speed=0.5, destination='media/singlepoint.gif') 384 | 385 | -------------------------------------------------------------------------------- /media/animatedbar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicholasARossi/Easing-Animations-with-Python/aa400fa601dc45f4e415f089cea7a3611e017d92/media/animatedbar.gif -------------------------------------------------------------------------------- /media/chroma.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicholasARossi/Easing-Animations-with-Python/aa400fa601dc45f4e415f089cea7a3611e017d92/media/chroma.gif -------------------------------------------------------------------------------- /media/comparison.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicholasARossi/Easing-Animations-with-Python/aa400fa601dc45f4e415f089cea7a3611e017d92/media/comparison.gif -------------------------------------------------------------------------------- /media/interpolation_schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicholasARossi/Easing-Animations-with-Python/aa400fa601dc45f4e415f089cea7a3611e017d92/media/interpolation_schema.png -------------------------------------------------------------------------------- /media/multipoint.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicholasARossi/Easing-Animations-with-Python/aa400fa601dc45f4e415f089cea7a3611e017d92/media/multipoint.gif -------------------------------------------------------------------------------- /media/normdist.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicholasARossi/Easing-Animations-with-Python/aa400fa601dc45f4e415f089cea7a3611e017d92/media/normdist.gif -------------------------------------------------------------------------------- /media/singlepoint.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicholasARossi/Easing-Animations-with-Python/aa400fa601dc45f4e415f089cea7a3611e017d92/media/singlepoint.gif -------------------------------------------------------------------------------- /media/traces.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicholasARossi/Easing-Animations-with-Python/aa400fa601dc45f4e415f089cea7a3611e017d92/media/traces.png -------------------------------------------------------------------------------- /outputs/output.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NicholasARossi/Easing-Animations-with-Python/aa400fa601dc45f4e415f089cea7a3611e017d92/outputs/output.mp4 -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='easing', 5 | version='0.1.0', 6 | packages=['easing'], 7 | license='MIT', 8 | install_requires=[ 9 | 'matplotlib>=3.0.3', 10 | 'numpy>=1.16.1', 11 | 'pandas>=0.24.1' 12 | ], 13 | url='https://github.com/NicholasARossi/Easing-Animations-with-Python', 14 | download_url = 'https://github.com/user/reponame/archive/v_01.tar.gz', 15 | author='Nicholas A. Rossi', 16 | author_email='nicholas.rossi2@gmail.com', 17 | description='Generating smooth animations in python', 18 | keywords = ['ANIMATION', 'SMOOTH', 'VISUALIZATION'], 19 | classifiers=[ 20 | 'Development Status :: 3 - Alpha', 21 | 'Intended Audience :: Developers', # Define that your audience are developers 22 | 'Topic :: Software Development :: Build Tools', 23 | 'License :: OSI Approved :: MIT License', # Again, pick a license 24 | 'Programming Language :: Python :: 3', # Specify which pyhton versions that you want to support 25 | 'Programming Language :: Python :: 3.4', 26 | 'Programming Language :: Python :: 3.5', 27 | 'Programming Language :: Python :: 3.6', 28 | ] 29 | ) 30 | 31 | 32 | --------------------------------------------------------------------------------