Skip to content

Commit c910189

Browse files
committed
allow multiple substructure and text matches
1 parent 7e63f86 commit c910189

File tree

2 files changed

+69
-9
lines changed

2 files changed

+69
-9
lines changed

molhighlighter/highlight.py

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,28 @@
11
from rdkit import Chem
22

33
class Highlight:
4-
def __init__(self, indices, text=None, color=None, fill_ring=False):
4+
def __init__(self, indices, text=None, color=None, fill_ring=False, same_color=False):
5+
"""Creates a highlight from a list of atom indices
6+
7+
Parameters
8+
----------
9+
indices : list[int] or list[list[int]]
10+
Indices of atoms to highlight
11+
text : str or None
12+
Text to match on the molecule's label
13+
color : str, tuple[float] or None
14+
Color of the highlight, as a hex code or an RGB tuple. Use `None`
15+
for automatic color assignment
16+
fill_ring : bool
17+
Fill the entire ring or simply highlight the ring atoms
18+
same_color : bool
19+
If a nested list is given, assign the same color to all matches
20+
"""
521
self.indices = indices
622
self.text = text
723
self.color = color
824
self.fill_ring = fill_ring
25+
self.same_color = same_color
926

1027
def __repr__(self):
1128
ix = self.indices
@@ -15,12 +32,28 @@ def __repr__(self):
1532
return f'<Highlight({ix=}, {text=}, {color=}, {fill_ring=})>'
1633

1734
@classmethod
18-
def from_smarts(cls, mol, smarts, text=None, color=None, fill_ring=False):
35+
def from_smarts(cls, mol, smarts, text=None, color=None, fill_ring=False,
36+
same_color=False, single_match=True):
37+
"""Creates a highlight from a SMARTS string
38+
39+
Parameters
40+
----------
41+
mol : RDKit.Chem.rdchem.Mol
42+
RDKit mol on which the substructure query is performed
43+
smarts : str
44+
SMARTS string for the substructure query
45+
single_match : bool
46+
Restrict query matches to a single one
47+
"""
1948
qmol = Chem.MolFromSmarts(smarts)
20-
indices = mol.GetSubstructMatch(qmol)
49+
if single_match:
50+
indices = mol.GetSubstructMatch(qmol)
51+
else:
52+
indices = mol.GetSubstructMatches(qmol)
2153
if not indices:
22-
raise ValueError(f"Not match found for {smarts!r}")
23-
return cls(indices=indices, text=text, color=color, fill_ring=fill_ring)
54+
raise ValueError(f"No match found for {smarts!r}")
55+
return cls(indices=indices, text=text, color=color, fill_ring=fill_ring,
56+
same_color=same_color)
2457

2558
@property
2659
def color(self):

molhighlighter/molhighlighter.py

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from rdkit.Chem import AllChem, Draw
55
from .utils import (Substitution, requires_config,
66
sequential_palette, get_auto_palette)
7+
from .highlight import Highlight
78
try:
89
from IPython.display import display_svg, display_html, Javascript
910
from ipywidgets import ColorPicker
@@ -73,15 +74,41 @@ def configure(self, size=(-1, -1), black_font=True, fill_rings=None,
7374
if self.label and any(h.text is None for h in self.highlights):
7475
raise ValueError("Cannot use empty highlight text if label is set")
7576

77+
# extend highlights if indices is nested list
78+
extended = []
79+
for h in self.highlights:
80+
if isinstance(h.indices[0], (list, tuple)) and not h.same_color:
81+
ext = [Highlight(ix, h.text, h.color, h.fill_ring)
82+
for ix in h.indices]
83+
extended.extend(ext)
84+
else:
85+
extended.append(h)
86+
self.highlights = extended
87+
7688
# automatic colors if not set
77-
if all(h.color is None for h in self.highlights):
89+
if any(h.color is None for h in self.highlights):
7890
num_hl = len(self.highlights)
7991
if num_hl > 5:
8092
palette = get_auto_palette(num_hl)
8193
else:
8294
palette = sequential_palette
83-
for highlight, color in zip(self.highlights, palette):
84-
highlight.color = color
95+
for h, color in zip(self.highlights, palette):
96+
h.color = h.color if h.color else color
97+
98+
# extend highlights if indices is nested list or text is list
99+
extended = []
100+
for h in self.highlights:
101+
if isinstance(h.indices[0], (list, tuple)):
102+
ext = [Highlight(ix, h.text, h.color, h.fill_ring)
103+
for ix in h.indices]
104+
extended.extend(ext)
105+
elif isinstance(h.text, (list, tuple)):
106+
ext = [Highlight(h.indices, txt, h.color, h.fill_ring)
107+
for txt in h.text]
108+
extended.extend(ext)
109+
else:
110+
extended.append(h)
111+
self.highlights = extended
85112

86113
# MolDrawOptions defaults
87114
opts = Draw.MolDrawOptions()
@@ -212,7 +239,7 @@ def generate_label(self):
212239
# find start index of text in label
213240
start = self._find_text(text, 0, starts)
214241
if start < 0:
215-
warnings.warn(f"No match found in label for {highlight}")
242+
warnings.warn(f"{highlight.text!r} unmatched or already found in label")
216243
continue
217244
end = start + size
218245
# create substitution string

0 commit comments

Comments
 (0)