Skip to content
Closed
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
22 changes: 18 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
- [Developer docs: Testing](#developer-docs-testing)

`draw_tree` is a game tree drawing tool for publication-ready extensive form games in Game Theory.
It can generate TikZ code, LaTeX documents, PDFs, and PNGs from game specifications.
It can generate TikZ code, LaTeX documents, PDFs, PNGs, and SVGs from game specifications.

Games can be specified via `.ef` format files which include layout formatting.
These can be created via [Game Theory Explorer](https://gametheoryexplorer-a68c7.web.app/), or by hand, see [specs.pdf](specs.pdf) for details.
Expand All @@ -35,10 +35,11 @@ pip install -e .
- Python 3.10+ (tested on 3.13)
- LaTeX with TikZ (for PDF/PNG generation)
- (optional) ImageMagick or Ghostscript or Poppler (for PNG generation)
- (optional) pdf2svg (for SVG generation)

### Installing LaTeX

Note: PDF and PNG generation require `pdflatex` to be installed and available in PATH. Tested methods have a ✅ next to them. Methods include:
Note: PDF, PNG and SVG generation require `pdflatex` to be installed and available in PATH. Tested methods have a ✅ next to them. Methods include:

- macOS:
- Install [MacTEX](https://www.tug.org/mactex/mactex-download.html) ✅
Expand All @@ -60,6 +61,14 @@ PNG generation will default to using any of ImageMagick or Ghostscript or Popple
- `sudo apt-get install poppler-utils`
- Windows: Install ImageMagick or Ghostscript from their websites

### SVG generation

SVG generation requires `pdflatex` and `pdf2svg`. To install `pdf2svg`:

- macOS: `brew install pdf2svg`
- Ubuntu: `sudo apt install pdf2svg`
- Windows: see [pdf2svg releases](https://github.com/dawbarton/pdf2svg)

## CLI

By default, `draw_tree` generates TikZ code and prints it to standard output.
Expand All @@ -73,20 +82,24 @@ draw_tree games/example.ef --pdf # Creates example.pdf
draw_tree games/example.ef --png # Creates example.png
draw_tree games/example.ef --png --dpi=600 # Creates high-res example.png (72-2400, default: 300)
draw_tree games/example.ef --output=mygame.png scale=0.8 # Creates mygame.png with 0.8 scaling (0.01 to 100)
draw_tree games/example.ef --svg # Creates example.svg
```

## Python API

You can also use `draw_tree` as a Python library:

```python
from draw_tree import generate_tex, generate_pdf, generate_png
from draw_tree import generate_tex, generate_pdf, generate_png, generate_svg
generate_tex('games/example.ef') # Creates example.tex
generate_tex('games/example.ef', save_to='custom') # Creates custom.tex
generate_pdf('games/example.ef') # Creates example.pdf
generate_png('games/example.ef') # Creates example.png
generate_png('games/example.ef', dpi=600) # Creates high-res example.png (72-2400, default: 300)
generate_png('games/example.ef', save_to='mygame', scale_factor=0.8) # Creates mygame.png with 0.8 scaling (0.01 to 100)
generate_svg('games/example.ef') # Creates example.svg
generate_svg('games/example.ef', save_to='custom') # Creates custom.svg
generate_svg('games/example.ef', save_to='mygame', scale_factor=0.8) # Creates mygame.svg with 0.8 scaling
```

### Rendering in Jupyter Notebooks
Expand All @@ -111,12 +124,13 @@ In particular read [Tutorial 4) Creating publication-ready game images](https://
In short, you can do:
```python
import pygambit as gbt
from draw_tree import draw_tree, generate_tex, generate_pdf, generate_png
from draw_tree import draw_tree, generate_tex, generate_pdf, generate_png, generate_svg
g = gbt.read_efg('somegame.efg')
draw_tree(g)
generate_tex(g)
generate_pdf(g)
generate_png(g)
generate_svg(g)
```

> Note: Without setting the `save_to` parameter, the saved file will be based on the title field of the pygambit game object.
Expand Down
2 changes: 2 additions & 0 deletions src/draw_tree/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
generate_tex,
generate_pdf,
generate_png,
generate_svg,
ef_to_tex,
latex_wrapper,
efg_dl_ef
Expand All @@ -26,6 +27,7 @@
"generate_tex",
"generate_pdf",
"generate_png",
"generate_svg",
"ef_to_tex",
"latex_wrapper",
"efg_dl_ef",
Expand Down
87 changes: 87 additions & 0 deletions src/draw_tree/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -2092,6 +2092,93 @@ def generate_png(
raise RuntimeError(f"PNG generation failed: {e}")


def generate_svg(
game: str | "pygambit.gambit.Game",
save_to: Optional[str] = None,
scale_factor: float = 1.0,
level_scaling: int = 1,
sublevel_scaling: int = 1,
width_scaling: int = 1,
hide_action_labels: bool = False,
shared_terminal_depth: bool = False,
show_grid: bool = False,
color_scheme: str = "default",
edge_thickness: float = 1.0,
action_label_position: float = 0.5,
) -> str:
"""
Generate an SVG image directly from an extensive form (.ef) file.

This function creates a PDF first, then converts it to SVG using pdf2svg.

Requires:
- pdflatex
- pdf2svg

Returns:
Path to generated SVG file.
"""
# Determine output filename
if save_to is None:
if isinstance(game, str):
game_path = Path(game)
else:
game_path = Path(game.title + ".ef")
output_svg = game_path.with_suffix(".svg").name
else:
if not save_to.endswith(".svg"):
output_svg = save_to + ".svg"
else:
output_svg = save_to

final_svg_path = Path(output_svg)

with tempfile.TemporaryDirectory() as temp_dir:
temp_pdf_path = Path(temp_dir) / "temp_output.pdf"

try:
# Step 1: Generate PDF explicitly into temp file
generate_pdf(
game=game,
save_to=str(temp_pdf_path.with_suffix("").absolute()),
scale_factor=scale_factor,
level_scaling=level_scaling,
sublevel_scaling=sublevel_scaling,
width_scaling=width_scaling,
hide_action_labels=hide_action_labels,
shared_terminal_depth=shared_terminal_depth,
show_grid=show_grid,
color_scheme=color_scheme,
edge_thickness=edge_thickness,
action_label_position=action_label_position,
)

# Step 2: Convert PDF to SVG
subprocess.run(
["pdf2svg", str(temp_pdf_path), str(final_svg_path)],
check=True,
capture_output=True,
text=True,
)

if final_svg_path.exists():
return str(final_svg_path.absolute())
else:
raise RuntimeError("SVG was not generated successfully.")

except FileNotFoundError:
raise RuntimeError(
"pdf2svg not found. Please install it.\n"
"macOS: brew install pdf2svg\n"
"Ubuntu: sudo apt install pdf2svg\n"
"Windows: Install from official binaries"
)
except subprocess.CalledProcessError as e:
raise RuntimeError(f"SVG conversion failed: {e.stderr}")
except Exception as e:
raise RuntimeError(f"SVG generation failed: {e}")


def efg_dl_ef(efg_file: str) -> str:
"""Convert a Gambit .efg file to the `.ef` format used by generate_tikz.

Expand Down