Skip to content

Commit 5e55ddf

Browse files
rmaloufclaude
andcommitted
Add displaCy visualization support for dependency trees
Adds functions to visualize Tree objects using spaCy's displaCy library. Changes: - Add to_displacy() function to convert Tree to displaCy format - Add render() function to generate SVG visualizations - Add optional viz dependency group (pip install treesearch-ud[viz]) - Make functions available as Tree methods (tree.render(), tree.to_displacy()) - Update documentation in README, API.md, and docs/api.md - Update CHANGELOG with unreleased features 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 0a61b9a commit 5e55ddf

File tree

8 files changed

+214
-1
lines changed

8 files changed

+214
-1
lines changed

API.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,69 @@ for tree, match in ts.search_trees(trees, pattern):
361361
print(f"Found: {verb.form}")
362362
```
363363

364+
### Visualization Functions
365+
366+
#### `to_displacy(tree: Tree) -> dict`
367+
368+
Convert a Tree to displaCy's manual rendering format.
369+
370+
**Parameters:**
371+
- `tree` (Tree): A Tree object to convert
372+
373+
**Returns:**
374+
- `dict`: Dictionary with 'words' and 'arcs' keys in displaCy format
375+
376+
```python
377+
tree = next(ts.trees("corpus.conllu"))
378+
data = ts.to_displacy(tree)
379+
# Returns: {'words': [{'text': '...', 'tag': '...'}, ...], 'arcs': [...]}
380+
381+
# Use with spaCy's displacy
382+
from spacy import displacy
383+
displacy.render(data, style="dep", manual=True)
384+
```
385+
386+
#### `render(tree: Tree, **options) -> str`
387+
388+
Render a Tree as an SVG dependency visualization using displaCy.
389+
390+
**Requirements:** Requires spaCy to be installed (`pip install treesearch-ud[viz]` or `pip install spacy`)
391+
392+
**Parameters:**
393+
- `tree` (Tree): A Tree object to render
394+
- `**options`: Additional options passed to displacy.render()
395+
- `jupyter` (bool): Return HTML for Jupyter display (default: auto-detect)
396+
- `compact` (bool): Use compact visualization mode
397+
- `word_spacing` (int): Spacing between words
398+
- `distance` (int): Distance between dependency arcs
399+
400+
**Returns:**
401+
- `str`: SVG markup string (or displays in Jupyter if jupyter=True)
402+
403+
**Raises:**
404+
- `ImportError`: If spaCy is not installed
405+
406+
```python
407+
# Basic usage
408+
tree = next(ts.trees("corpus.conllu"))
409+
svg = ts.render(tree)
410+
print(svg) # SVG markup
411+
412+
# Save to file
413+
with open("tree.svg", "w") as f:
414+
f.write(svg)
415+
416+
# In Jupyter notebook (displays inline)
417+
ts.render(tree, jupyter=True)
418+
419+
# Compact mode with custom spacing
420+
svg = ts.render(tree, compact=True, word_spacing=50)
421+
422+
# Also available as Tree methods
423+
svg = tree.render()
424+
data = tree.to_displacy()
425+
```
426+
364427
### Data Classes
365428

366429
#### `Tree`

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased]
9+
10+
### Added
11+
- Tree visualization with displaCy via `render()` and `to_displacy()` functions
12+
- Optional `viz` extras for spaCy dependency
13+
814
## [0.1.0] - 2025-12-29
915

1016
Initial release.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "treesearch-ud"
3-
version = "0.1.0"
3+
version = "0.2.0-dev"
44
edition = "2024"
55
authors = ["Rob Malouf"]
66
description = "High-performance toolkit for querying linguistic dependency parses"

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ Requires Python 3.12+.
1818

1919
```bash
2020
pip install treesearch-ud
21+
22+
# Optional: Install with visualization support (displaCy)
23+
pip install treesearch-ud[viz]
2124
```
2225

2326
### From Source

docs/api.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,24 @@ for tree, match in ts.search_trees(tree, pattern):
5858
...
5959
```
6060

61+
### to_displacy(tree) → dict
62+
63+
Convert a Tree to displaCy format for visualization.
64+
65+
```python
66+
data = ts.to_displacy(tree)
67+
# Returns: {'words': [...], 'arcs': [...]}
68+
```
69+
70+
### render(tree, **options) → str
71+
72+
Render a Tree as SVG using displaCy. Requires spaCy (`pip install treesearch-ud[viz]`).
73+
74+
```python
75+
svg = ts.render(tree)
76+
ts.render(tree, jupyter=True) # Display in Jupyter
77+
```
78+
6179
## Treebank
6280

6381
Collection of trees from one or more files.
@@ -89,6 +107,8 @@ A dependency tree (parsed sentence).
89107
- `tree.word(id) → Word` - Get word by ID (0-indexed). Raises `IndexError` if out of range.
90108
- `tree[id] → Word` - Same as `word(id)`
91109
- `len(tree) → int` - Number of words
110+
- `tree.to_displacy() → dict` - Convert to displaCy format
111+
- `tree.render(**options) → str` - Render as SVG (requires spaCy)
92112

93113
## Word
94114

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ docs = [
4545
"mkdocs>=1.5.0",
4646
"mkdocs-material>=9.0.0",
4747
]
48+
viz = [
49+
"spacy>=3.0.0",
50+
]
4851

4952
[tool.maturin]
5053
features = ["pyo3/extension-module"]

python/treesearch/__init__.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
"trees",
4646
"search",
4747
"search_trees",
48+
"to_displacy",
49+
"render",
4850
]
4951

5052

@@ -157,3 +159,91 @@ def search_trees(
157159
else:
158160
source = list(source)
159161
return py_search_trees(source, query)
162+
163+
164+
def to_displacy(tree: Tree) -> dict:
165+
"""Convert a Tree to displaCy's manual rendering format.
166+
167+
Args:
168+
tree: A Tree object to convert
169+
170+
Returns:
171+
Dictionary in displaCy format with 'words' and 'arcs' keys
172+
173+
Example:
174+
>>> tree = next(treesearch.trees("corpus.conllu"))
175+
>>> data = treesearch.to_displacy(tree)
176+
>>> from spacy import displacy
177+
>>> displacy.render(data, style="dep", manual=True)
178+
"""
179+
words = []
180+
arcs = []
181+
182+
for i in range(len(tree)):
183+
word = tree.word(i)
184+
words.append({"text": word.form, "tag": word.upos})
185+
186+
if word.head is not None:
187+
head_idx = word.head
188+
dep_idx = word.id
189+
if head_idx < dep_idx:
190+
arcs.append(
191+
{
192+
"start": head_idx,
193+
"end": dep_idx,
194+
"label": word.deprel,
195+
"dir": "right",
196+
}
197+
)
198+
else:
199+
arcs.append(
200+
{
201+
"start": dep_idx,
202+
"end": head_idx,
203+
"label": word.deprel,
204+
"dir": "left",
205+
}
206+
)
207+
return {"words": words, "arcs": arcs}
208+
209+
210+
def render(tree: Tree, **options) -> str:
211+
"""Render a Tree as an SVG dependency visualization using displaCy.
212+
213+
Requires spaCy to be installed.
214+
215+
Args:
216+
tree: A Tree object to render
217+
**options: Additional options passed to displacy.render()
218+
Common options include:
219+
- jupyter: bool - Return HTML for Jupyter display (default: auto-detect)
220+
- compact: bool - Use compact visualization mode
221+
- word_spacing: int - Spacing between words
222+
- distance: int - Distance between dependency arcs
223+
224+
Returns:
225+
SVG markup string (or displays in Jupyter if jupyter=True)
226+
227+
Raises:
228+
ImportError: If spaCy is not installed
229+
230+
Example:
231+
>>> tree = next(treesearch.trees("corpus.conllu"))
232+
>>> svg = treesearch.render(tree)
233+
>>> with open("tree.svg", "w") as f:
234+
... f.write(svg)
235+
236+
# In Jupyter notebook:
237+
>>> treesearch.render(tree, jupyter=True)
238+
"""
239+
try:
240+
from spacy import displacy
241+
except ImportError:
242+
raise ImportError("spaCy is required for rendering. Install it with: pip install spacy")
243+
244+
data = to_displacy(tree)
245+
return displacy.render(data, style="dep", manual=True, **options)
246+
247+
248+
Tree.to_displacy = to_displacy
249+
Tree.render = render

python/treesearch/treesearch.pyi

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,3 +238,31 @@ def py_search_trees(trees: list[Tree], pattern: Pattern | str) -> MatchIterator:
238238
Iterator over (Tree, match_dict) tuples from all trees
239239
"""
240240
...
241+
242+
def to_displacy(tree: Tree) -> dict[str, list]:
243+
"""Convert a Tree to displaCy's manual rendering format.
244+
245+
Args:
246+
tree: A Tree object to convert
247+
248+
Returns:
249+
Dictionary with 'words' and 'arcs' keys for displaCy rendering
250+
"""
251+
...
252+
253+
def render(tree: Tree, **options) -> str:
254+
"""Render a Tree as an SVG dependency visualization using displaCy.
255+
256+
Requires spaCy to be installed.
257+
258+
Args:
259+
tree: A Tree object to render
260+
**options: Additional options passed to displacy.render()
261+
262+
Returns:
263+
SVG markup string
264+
265+
Raises:
266+
ImportError: If spaCy is not installed
267+
"""
268+
...

0 commit comments

Comments
 (0)