synimatic 1.0.2
Simple and effective matplotlib animation wrapper
Requires Python: >=3.8

# Synimatic

[![PyPI version](https://img.shields.io/pypi/v/synimatic.svg)](https://pypi.org/project/synimatic/)
[![Build Status](https://img.shields.io/travis/com/jonathan-4a/synimatic.svg)](https://travis-ci.com/jonathan-4a/synimatic)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Python Versions](https://img.shields.io/pypi/pyversions/synimatic.svg)](https://pypi.org/project/synimatic/)

Synimatic simplifies animation creation by wrapping matplotlib's `FuncAnimation`. It removes repetitive setup, detects the execution environment (Jupyter or terminal), features progress indicators, and offers straightforward methods to display or save animations.

---

## Features

- **Easy Setup**: Define animation logic clearly through `setup` and `update` functions.  
- **Environment Detection**: Automatically renders inline in notebooks or in separate windows in terminals.  
- **Progress Display**: Shows progress bars during rendering and saving processes.  
- **Simple API**: `.show()` and `.save()` methods for effortless display or export.

---

## Installation

```bash
pip3 install synimatic
```

---

## Core Concepts

Animations require two key functions:

1. **`setup()`**: Executes once to establish the look of a static plot elements like figure, axes, and artists to animate. Returns `(fig, ax, *artists)`.

2. **`update(frame, context)`**: Called each frame to update plot elements.
   - `frame`: Current frame index or value.  
   - `context`: Tuple of objects returned by `setup`.

---

## Usage Example
Graph Update :

```python
import numpy as np
import matplotlib.pyplot as plt
from synimatic import Synimator

def setup():
    fig, ax = plt.subplots()
    ax.set_xlim(0, 4 * np.pi)
    ax.set_ylim(-2.2, 2.2)
    line, = ax.plot([], [], lw=2)
    return fig, ax, line

def update(frame, context):
    fig, ax, line = context
    x = np.linspace(0, 4 * np.pi, 500)
    y1 = np.sin(x + frame / 10)
    y2 = np.sin(1.2 * x - frame / 15)
    line.set_data(x, y1 + y2)

animator = Synimator(setup)
animator.animate(update, frames=200, fps=30)
animator.show()
```

---

## API Reference

- **`Synimator(setup)`**: Creates the animator instance.  
  - `setup`: Function returning `(fig, ax, *artists)`.

- **`animator.animate(update, frames, **kwargs)`**: Builds the animation.  
  - `update`: Function with signature `update(frame, context)`.  
  - `frames`: Frame count or iterable of frame values.  
  - `fps`: Frames per second, default 16.  
  - `blit`: Enable blitting for performance, default `True`.  
  - `embed`: Jupyter embedding format (`'jshtml'` or `'video'`), default `'jshtml'`.
  - `embed_limit`: Maximum file size for embedding animations in notebooks (MB).
  - `save_only`: Skip display pre-rendering to optimize save performance in notebooks, default `False`.
  

- **`animator.show()`**: Displays the animation according to the environment.

- **`animator.save(filename, **kwargs)`**: Saves animation to file.  
  - `filename`: Output filename (e.g., `animation.mp4`).  
  - `**kwargs`: Additional arguments for matplotlib's save method (e.g., `dpi`).

---

## Additional Examples
<details>
<summary><b>Conway's Game of Life animation</b></summary>

```python
import numpy as np
import matplotlib.pyplot as plt
from synimatic import Synimator

def setup():
    fig, ax = plt.subplots()
    grid = np.random.choice([0, 1], (30, 30), p=[0.8, 0.2])
    img = ax.imshow(grid, cmap='gray', vmin=0, vmax=1)
    ax.axis('off')
    update.grid = grid
    return fig, ax, img

def update(frame, context):
    fig, ax, img = context
    G = update.grid
    neighbors = sum(np.roll(np.roll(G, i, 0), j, 1)
                    for i in (-1, 0, 1) for j in (-1, 0, 1)
                    if (i != 0 or j != 0))
    G = ((neighbors == 3) | ((G == 1) & (neighbors == 2))).astype(int)
    update.grid = G
    img.set_data(G)

animator = Synimator(setup)
animator.animate(update, frames=100, fps=20)
animator.show()

# To save the animation:
# animator.save("game_of_life.mp4")
```

</details>

<details>
<summary><b>Double Pendulum</b></summary>

```python
import numpy as np
import matplotlib.pyplot as plt
from synimatic import Synimator

def setup():
    fig, ax = plt.subplots()
    ax.set_xlim(-2, 2)
    ax.set_ylim(-2, 2)
    ax.set_aspect('equal')
    arm1, = ax.plot([], [], lw=2, color='red')
    arm2, = ax.plot([], [], lw=2, color='blue')
    trail, = ax.plot([], [], lw=1, color='green', alpha=0.5)
    update.trail_x, update.trail_y = [], []
    return fig, ax, arm1, arm2, trail

def update(frame, context):
    fig, ax, arm1, arm2, trail = context
    theta1 = np.pi/2 * np.cos(frame)
    theta2 = np.pi/2 * np.sin(2 * frame)
    x1, y1 = np.sin(theta1), -np.cos(theta1)
    x2, y2 = x1 + np.sin(theta2), y1 - np.cos(theta2)
    arm1.set_data([0, x1], [0, y1])
    arm2.set_data([x1, x2], [y1, y2])
    update.trail_x.append(x2)
    update.trail_y.append(y2)
    trail.set_data(update.trail_x[-100:], update.trail_y[-100:])

frames = np.linspace(0, 4 * np.pi, 500)
animator = Synimator(setup)
animator.animate(update, frames, fps=60)
animator.show()
```

</details>

---

## Contributing

Contributions welcome. Report issues or submit pull requests at the [GitHub repository](https://github.com/jonathan-4a/synimatic).

---

## License

Distributed under the MIT License.

## Author

**Jonathan Ayalew** — [GitHub](https://github.com/jonathan-4a)