Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 7 additions & 12 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,21 @@ on:
jobs:
pytest:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]

steps:
- name: Check out repo
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Set up python
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: 3.8
cache: pip
cache-dependency-path: setup.py

- uses: conda-incubator/setup-miniconda@v2
with:
python-version: 3.8
python-version: ${{ matrix.python-version }}

- name: Install molplotly + dependencies
run: |
pip install .[test]
pip install rdkit-pypi
run: pip install .[test]

- name: Run tests
run: pytest --cov molplotly
4 changes: 2 additions & 2 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ authors:
given-names: "Rokas"
orcid: "https://orcid.org/0000-0001-6397-0002"
title: "molplotly"
version: 1.1.1
date-released: 2022-03-01
version: 2.0.0
date-released: 2026-03-17
url: "https://github.com/wjm41/molplotly"
118 changes: 118 additions & 0 deletions PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# molplotly Cleanup & Modernization Plan

## Summary

molplotly is a single-module package (~520 lines) that adds interactive molecule hover tooltips to plotly figures using RDKit and Dash. The core idea is solid but the package is broken on PyPI, relies on a deprecated dependency (`JupyterDash`), has fragile packaging, minimal tests, and several unaddressed user issues.

---

## Phase 1: Critical Fixes (Get it working again)

### 1.1 Replace `JupyterDash` with `Dash`
- **Why**: `JupyterDash` is deprecated and causes doubled plots + port errors on Dash >= 2.10 (issue #31). This is the #1 breakage.
- **What**: Adopt the approach from PR #32 (open since Dec 2023, never reviewed) — replace `jupyter-dash` imports with standard `dash.Dash`. Modern Dash (>=2.11) natively supports Jupyter without `JupyterDash`.
- **Files**: `molplotly/main.py`, `tests/test_add_molecules.py`

### 1.2 Modernize packaging — migrate to `pyproject.toml`
- **Why**: `setup.py` is legacy, `setup_pip.py` uses the removed `distutils`, and the current PyPI release (1.1.8) has mismatched version metadata making it uninstallable (issue #35).
- **What**:
- Create `pyproject.toml` with all metadata, dependencies, and build config
- Delete `setup.py` and `setup_pip.py`
- Set a correct, bumped version (e.g. 2.0.0 given the breaking `JupyterDash` removal)
- Pin minimum dependency versions sensibly: `dash>=2.11.0`, `plotly>=5.0.0`, `rdkit`, `pandas`
- Remove `jupyter-dash`, `werkzeug`, `ipykernel`, `nbformat` from required deps (no longer needed without JupyterDash; Dash pulls in werkzeug transitively)
- **Files**: new `pyproject.toml`, delete `setup.py`, delete `setup_pip.py`

### 1.3 Update CI/CD
- **Why**: CI uses Python 3.8 (EOL), `rdkit-pypi` (renamed), and `actions/setup-python@v2` (outdated).
- **What**:
- Bump to Python 3.10+ (or test matrix 3.10/3.11/3.12)
- Use `actions/checkout@v4`, `actions/setup-python@v5`
- Remove miniconda setup (rdkit installs fine via pip now)
- Install via `pip install .[test]` (which will use pyproject.toml)
- **Files**: `.github/workflows/test.yml`

---

## Phase 2: Code Quality

### 2.1 Clean up `main.py`
- **Bare except** (line ~369): Catch specific `Exception` or `ValueError` instead of bare `except:`
- **Unused imports / dead code**: Audit and remove
- **Type hints**: Add missing type hints to `test_groups`, `find_correct_column_order`, `find_grouping`
- **Docstrings**: Add/improve docstrings for the public `add_molecules` function and helpers
- **Files**: `molplotly/main.py`

### 2.2 Improve `__init__.py`
- **Why**: `from .main import *` exports everything including internal helpers
- **What**: Define `__all__` to only export `add_molecules` (the public API), or use explicit imports
- **Files**: `molplotly/__init__.py`

### 2.3 Add `__version__`
- Use `importlib.metadata` to expose `__version__` from the installed package metadata (single source of truth from `pyproject.toml`)
- **Files**: `molplotly/__init__.py`

---

## Phase 3: Testing

### 3.1 Expand test coverage
- **Why**: Currently 1 test that only checks `isinstance(app, JupyterDash)` — no functional validation
- **What**: Add tests for:
- Basic scatter plot with molecules
- Color column grouping
- Symbol column grouping
- Facet column support
- Multiple SMILES columns
- Reaction SMILES drawing
- Edge cases: missing SMILES, invalid SMILES, empty dataframe
- Caption formatting functions
- The `find_grouping` logic (unit test the helper directly)
- **Files**: `tests/test_add_molecules.py` (or split into multiple test files)

---

## Phase 4: Documentation & Metadata

### 4.1 Update README
- Update installation instructions
- Note the `JupyterDash` → `Dash` migration (breaking change for users who import JupyterDash type)
- Update the "known issues" section (remove stale items, add current limitations)
- Update badges if any

### 4.2 Update CITATION.cff
- **Why**: Shows version 1.1.1 and date 2022-03-01, both stale
- **What**: Bump version and date to match the new release

### 4.3 Update example notebooks
- Verify notebooks still run with the updated code
- Remove/fix any `JupyterDash`-specific patterns

---

## Phase 5: Address Open Issues (nice-to-haves / future work)

These are lower priority but worth tracking:

| Issue | Description | Effort |
|-------|-------------|--------|
| #34 | Streamlit integration | Medium — would need a different rendering approach |
| #29 | Embed in existing Dash app | Medium — return a Dash component instead of a full app |
| #26 | Support for stacked bar charts / graph_objects | Small-Medium |
| #4 | Export as standalone HTML | Hard — fundamental limitation of needing a Dash server |
| #20 | Usage question (resolved) | Can be closed |
| #21 | make_subplots support | Medium |

---

## Suggested Execution Order

1. **Phase 1.1** — Replace JupyterDash (unblocks everything)
2. **Phase 1.2** — pyproject.toml migration (fixes packaging)
3. **Phase 2.1-2.3** — Code cleanup (while we're in the code)
4. **Phase 1.3** — Update CI (validates the above changes)
5. **Phase 3** — Expand tests
6. **Phase 4** — Docs & metadata
7. **Phase 5** — Feature work (separate PRs)

Phases 1-4 could reasonably be done as a single "v2.0.0" release given the breaking JupyterDash change.
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![Powered by RDKit](https://img.shields.io/static/v1?label=Powered%20by&message=RDKit&color=3838ff&style=flat&logo=data:image/x-icon;base64,AAABAAEAEBAQAAAAAABoAwAAFgAAACgAAAAQAAAAIAAAAAEAGAAAAAAAAAMAABILAAASCwAAAAAAAAAAAADc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nz/FBT/FBT/FBT/FBT/FBT/FBTc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nz/FBT/PBT/PBT/PBT/PBT/PBT/PBT/FBTc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nz/FBT/PBT/ZGT/ZGT/ZGT/ZGT/ZGT/ZGT/PBT/FBTc3Nzc3Nzc3Nzc3Nzc3Nz/FBT/PBT/ZGT/ZGT/ZGT/ZGT/ZGT/ZGT/ZGT/ZGT/PBT/FBTc3Nzc3Nzc3Nz/FBT/PBT/ZGT/ZGT/ZGT/jIz/jIz/jIz/jIz/ZGT/ZGT/ZGT/PBT/FBTc3Nzc3Nz/FBT/PBT/ZGT/ZGT/jIz/jIz/jIz/jIz/jIz/jIz/ZGT/ZGT/PBT/FBTc3Nzc3Nz/FBT/PBT/ZGT/ZGT/jIz/jIz/tLT/tLT/jIz/jIz/ZGT/ZGT/PBT/FBTc3Nzc3Nz/FBT/PBT/ZGT/ZGT/jIz/jIz/tLT/tLT/jIz/jIz/ZGT/ZGT/PBT/FBTc3Nzc3Nz/FBT/PBT/ZGT/ZGT/jIz/jIz/jIz/jIz/jIz/jIz/ZGT/ZGT/PBT/FBTc3Nzc3Nz/FBT/PBT/ZGT/ZGT/ZGT/jIz/jIz/jIz/jIz/ZGT/ZGT/ZGT/PBT/FBTc3Nzc3Nzc3Nz/FBT/PBT/ZGT/ZGT/ZGT/ZGT/ZGT/ZGT/ZGT/ZGT/PBT/FBTc3Nzc3Nzc3Nzc3Nzc3Nz/FBT/PBT/ZGT/ZGT/ZGT/ZGT/ZGT/ZGT/PBT/FBTc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nz/FBT/PBT/PBT/PBT/PBT/PBT/PBT/FBTc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nz/FBT/FBT/FBT/FBT/FBT/FBTc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nz/////+B////AP///gB///wAP//4AB//+AAf//gAH//4AB//+AAf//gAH//8AD///gB///8A////gf////////)](https://www.rdkit.org/)
[![PyPI version](https://img.shields.io/pypi/v/molplotly)](https://pypi.python.org/pypi/molplotly)
[![PyPI Downloads](https://pepy.tech/badge/molplotly)](https://pepy.tech/project/molplotly)
[![This project supports Python 3.8+](https://img.shields.io/badge/Python-3.8+-blue.svg)](https://python.org/downloads)
[![This project supports Python 3.10+](https://img.shields.io/badge/Python-3.10+-blue.svg)](https://python.org/downloads)

`molplotly` is an add-on to `plotly` built on RDKit which allows 2D images of molecules to be shown in `plotly` figures when hovering over the data points.

Expand Down Expand Up @@ -44,7 +44,7 @@ app = molplotly.add_molecules(fig=fig,
)

# run Dash app inline in notebook (or in an external server)
app.run_server(mode='inline', port=8700, height=1000)
app.run(port=8700, height=1000, jupyter_mode='inline')
```

### Input parameters
Expand Down Expand Up @@ -72,15 +72,14 @@ app.run_server(mode='inline', port=8700, height=1000)

#### Output parameters

by default a JupyterDash `app` is returned which can be run inline in a jupyter notebook or deployed on a server via `app.run_server()`
by default a Dash `app` is returned which can be run inline in a jupyter notebook or deployed on a server via `app.run()`

- The recommended `height` of the app is `50+(height of the plotly figure)`.
- For the `port` of the app, make sure you don't pick the same `port` as another `molplotly` plot otherwise the tooltips will clash with each other. Also, apparently on windows port numbers below `8700` are used by other processes so for safety processes keep to numbers above that.

## Can I run this in colab?

JupyterDash is supposed to have support for Google Colab but at some point that seems to have broken.. Keep an eye on the raised issue [here](https://github.com/plotly/jupyter-dash/issues/10)!
Update (1st March 2022): The plots seem to be running again but the hoverboxes are not showing so I don't think it has been fully fixed - I will keep an eye on it in the meantime.
Modern Dash (>=2.11) has native support for running in Jupyter notebooks and Google Colab without any extra dependencies.

## Can I save these plots?

Expand Down
11 changes: 10 additions & 1 deletion molplotly/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
from .main import *
from importlib.metadata import PackageNotFoundError, version

from .main import add_molecules

__all__ = ["add_molecules"]

try:
__version__ = version("molplotly")
except PackageNotFoundError:
__version__ = "unknown"
23 changes: 10 additions & 13 deletions molplotly/main.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
from __future__ import annotations

import base64
import itertools
import re
import textwrap
from io import BytesIO
from typing import Callable
import itertools
import re

import pandas as pd
import numpy as np
from dash import Input, Output, dcc, html, no_update
from jupyter_dash import JupyterDash
import pandas as pd
import plotly.graph_objects as go
from dash import Dash, Input, Output, dcc, html, no_update
from pandas.core.groupby import DataFrameGroupBy
from plotly.graph_objects import Figure
import plotly.graph_objects as go

from rdkit import Chem
from rdkit.Chem.Draw import rdMolDraw2D
from rdkit.Chem.rdChemReactions import ReactionFromSmarts
from rdkit.Chem.rdchem import Mol
from rdkit.Chem.Draw import rdMolDraw2D


def str2bool(v: str) -> bool:
Expand Down Expand Up @@ -162,12 +160,11 @@ def add_molecules(
fontfamily: str = "Arial",
fontsize: int = 12,
reaction: bool = False,
) -> JupyterDash:
) -> Dash:
"""
A function that takes a plotly figure and a dataframe with molecular SMILES
and returns a dash app that dynamically generates an image of molecules in the hover box
and returns a Dash app that dynamically generates an image of molecules in the hover box
when hovering the mouse over datapoints.
...

Attributes
----------
Expand Down Expand Up @@ -251,7 +248,7 @@ def add_molecules(
if not svg_width:
svg_width = svg_size

app = JupyterDash(__name__)
app = Dash(__name__)
if smiles_col is None and mol_col is None:
raise ValueError("Either smiles_col or mol_col has to be specified!")

Expand Down Expand Up @@ -368,7 +365,7 @@ def display_hover(hoverData, value):
if reaction:
try:
d2d.DrawReaction(ReactionFromSmarts(smiles, useSmiles=True))
except:
except Exception:
d2d.DrawMolecule(Chem.MolFromSmiles(smiles))
else:
d2d.DrawMolecule(Chem.MolFromSmiles(smiles))
Expand Down
43 changes: 43 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
[build-system]
requires = ["setuptools>=64"]
build-backend = "setuptools.build_meta"

[project]
name = "molplotly"
version = "2.0.0"
description = "plotly add-on to render molecule images on mouseover"
readme = "README.md"
license = "Apache-2.0"
requires-python = ">=3.10"
authors = [
{ name = "William McCorkindale", email = "wjm41@cam.ac.uk" },
]
keywords = ["science", "chemistry", "cheminformatics"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Science/Research",
"Topic :: Scientific/Engineering :: Chemistry",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]
dependencies = [
"dash>=2.11.0",
"plotly>=5.0.0",
"rdkit",
"pandas",
"numpy",
]

[project.optional-dependencies]
test = ["pytest", "pytest-cov"]

[project.urls]
Homepage = "https://github.com/wjm41/molplotly"
Repository = "https://github.com/wjm41/molplotly"
Issues = "https://github.com/wjm41/molplotly/issues"

[tool.setuptools.packages.find]
include = ["molplotly*"]
34 changes: 0 additions & 34 deletions setup.py

This file was deleted.

40 changes: 0 additions & 40 deletions setup_pip.py

This file was deleted.

Loading
Loading