Metadata-Version: 2.4
Name: visdecode
Version: 0.3.0
Summary: Design-guideline checking for Matplotlib figures via Answer Set Programming.
Author: LIA - Universidad Torcuato Di Tella
License-Expression: MIT
Project-URL: Homepage, https://github.com/LIA-DiTella/visdecode-tvcg2026
Project-URL: Repository, https://github.com/LIA-DiTella/visdecode-tvcg2026
Project-URL: Issues, https://github.com/LIA-DiTella/visdecode-tvcg2026/issues
Keywords: matplotlib,visualization,design-guidelines,answer-set-programming,clingo,linting
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Framework :: Matplotlib
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Visualization
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: matplotlib>=3.7
Requires-Dist: clingo
Provides-Extra: notebook
Requires-Dist: ipython; extra == "notebook"
Requires-Dist: jupyter; extra == "notebook"
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Dynamic: license-file

# visdecode

**Automatic design-guideline checking for Matplotlib figures.**

[![PyPI](https://img.shields.io/pypi/v/visdecode.svg)](https://pypi.org/project/visdecode/)
[![Python](https://img.shields.io/pypi/pyversions/visdecode.svg)](https://pypi.org/project/visdecode/)
[![License](https://img.shields.io/pypi/l/visdecode.svg)](https://github.com/LIA-DiTella/visdecode-tvcg2026/blob/main/LICENSE)

`visdecode` converts a Matplotlib `Figure` into Answer Set Programming facts and
evaluates them with [clingo](https://potassco.org/clingo/) against a set of
design rules drawn from the visualization literature.

One import, and every `plt.show()` tells you what is wrong with your chart.

---

## Install

```bash
pip install visdecode
```

Requires Python ≥ 3.10. Matplotlib and clingo are installed automatically.
Nothing is compiled: it is a pure-Python wheel, and Matplotlib itself is never
patched or rebuilt.

## Usage

Add one line. That is the whole setup.

```python
import visdecode
import matplotlib.pyplot as plt

plt.plot([1, 2, 3])
plt.show()
```

```
Figure 1: 3 design warning(s) across 1 panel(s)
  figure: 3 warning(s)
    [missing_axis_label] Add a textual label so the axis variable is identifiable.
      at: axis(a0,x)
    [missing_axis_label] Add a textual label so the axis variable is identifiable.
      at: axis(a0,y)
    [missing_title] Add a descriptive title to the chart.
      at: a0
```

`import visdecode` may come before or after `import matplotlib.pyplot` — the
patch is applied to the module object, which is unique per process.

Warnings are written to `stderr`, so in Jupyter they appear below the figure in
the same cell. Figures drawn by `%matplotlib inline` **without** an explicit
`plt.show()` are covered too.

### Turning it on and off

```python
visdecode.set_enabled(False)   # silence
visdecode.set_enabled(True)    # re-enable
visdecode.uninstall()          # restore the original plt.show
```

## The rules

| Code | What it catches |
|---|---|
| `missing_title` | Chart without a descriptive title |
| `missing_axis_label` | Axis whose variable cannot be identified |
| `bar_axis_excludes_zero` | Bar chart whose quantitative axis is truncated |
| `bar_mark_excludes_zero` | Bars that do not share a common baseline |
| `line_x_not_monotonic` | Line folding back on X, implying a false sequence |
| `scatter_overplot` | Coincident points hiding multiplicity |
| `indistinguishable_line_color` | Series that must be told apart sharing a color |

Each rule cites its source in
[`design_rules.lp`](https://github.com/LIA-DiTella/visdecode-tvcg2026/blob/main/visdecode/design_rules.lp)
— Kosslyn (2006), Cleveland (1994), Cleveland & McGill (1984), Mayorga &
Gleicher (2013), Ware (2019). Adding a rule means adding a clause, not writing
Python.

## Programmatic API

The automatic hook is a convenience. The pipeline is available directly:

| Call | Returns |
|---|---|
| `recommend(fig)` | Flat list of recommendations |
| `recommend_by_panel(fig)` | Recommendations grouped per panel |
| `format_recommendations(panels)` | The text report |
| `recommend(fig, return_details=True)` | Also the spec, ASP facts and raw atoms |
| `figure_to_dict(fig)` | `Figure` → JSON-safe dict |
| `dict_to_facts(spec)` | dict → ASP facts |
| `check_facts(facts)` | facts → `warning/2` atoms |

Every recommendation is a dict with `code`, `message`, `target`, `atom`,
`axes_id`, `panel` and `panel_title`.

```python
for r in visdecode.recommend(fig):
    print(r["code"], "->", r["message"])
```

## How it works

`plt.show` is an attribute of a Python module, so it can be replaced at runtime
with a function that runs the check and then calls the original one. That
replacement happens inside the package's `__init__.py`, which is why importing
is all it takes.

A callback is also registered on IPython's `post_execute` event, ahead of
`flush_figures`, to cover figures Jupyter renders at the end of a cell without
going through `plt.show()`.

Neither mechanism touches Matplotlib's source.

---

Developed at **LIA**, Universidad Torcuato Di Tella.
Full documentation, notebooks and design notes:
[github.com/LIA-DiTella/visdecode-tvcg2026](https://github.com/LIA-DiTella/visdecode-tvcg2026)

Released under the MIT License.
