or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

actions-callbacks.mdcore-statemachine.mddiagrams.mdevents-transitions.mdexceptions.mdindex.mdmixins-integration.mdutilities.md

diagrams.mddocs/

0

# Diagram Generation

1

2

Graphical representation and visualization features including Graphviz diagram generation, state machine visualization, and diagram customization options.

3

4

## Capabilities

5

6

### DotGraphMachine Class

7

8

Main class for generating graphical representations of state machines using Graphviz DOT format.

9

10

```python { .api }

11

class DotGraphMachine:

12

"""

13

Generates graphical representations of state machines using Graphviz.

14

15

Provides customizable visualization with support for different layouts,

16

colors, fonts, and styling options.

17

18

Class Attributes:

19

- graph_rankdir: Direction of the graph layout ("LR" for left-right, "TB" for top-bottom)

20

- font_name: Font face name for graph text

21

- state_font_size: Font size for state labels in points

22

- state_active_penwidth: Line width for active state borders

23

- state_active_fillcolor: Fill color for active states

24

- transition_font_size: Font size for transition labels in points

25

"""

26

27

graph_rankdir: str = "LR"

28

"""Direction of the graph. Defaults to "LR" (option "TB" for top bottom)."""

29

30

font_name: str = "Arial"

31

"""Graph font face name."""

32

33

state_font_size: str = "10"

34

"""State font size in points."""

35

36

state_active_penwidth: int = 2

37

"""Active state external line width."""

38

39

state_active_fillcolor: str = "turquoise"

40

"""Active state fill color."""

41

42

transition_font_size: str = "9"

43

"""Transition font size in points."""

44

45

def __init__(self, machine: StateMachine):

46

"""

47

Initialize diagram generator for a state machine.

48

49

Parameters:

50

- machine: StateMachine instance to visualize

51

"""

52

53

def get_graph(self) -> pydot.Dot:

54

"""

55

Generate and return the Graphviz digraph object.

56

57

Returns:

58

pydot.Dot object that can be rendered to various formats

59

"""

60

61

def create_digraph(self) -> pydot.Dot:

62

"""

63

Create a Graphviz digraph object representing the state machine.

64

65

Returns:

66

pydot.Dot object that can be rendered to various formats

67

"""

68

69

def __call__(self) -> pydot.Dot:

70

"""Alias for get_graph() - allows calling instance as function."""

71

72

def write(self, filename: str, format: str = "png", prog: str = "dot"):

73

"""

74

Write diagram to file.

75

76

Parameters:

77

- filename: Output file path

78

- format: Output format (png, svg, pdf, etc.)

79

- prog: Graphviz layout program (dot, neato, fdp, etc.)

80

"""

81

82

# Built-in diagram generation method

83

class StateMachine:

84

def _graph(self) -> DotGraphMachine:

85

"""Get DotGraphMachine instance for this state machine."""

86

```

87

88

### Diagram Utility Functions

89

90

Standalone functions for diagram generation and export.

91

92

```python { .api }

93

def quickchart_write_svg(sm: StateMachine, path: str):

94

"""

95

Write state machine diagram as SVG using QuickChart service.

96

97

Generates diagram without requiring local Graphviz installation

98

by using the QuickChart.io online service.

99

100

Parameters:

101

- sm: StateMachine instance to visualize

102

- path: Output SVG file path

103

"""

104

105

def write_image(qualname: str, out: str):

106

"""

107

Write state machine diagram to file by class qualname.

108

109

Parameters:

110

- qualname: Fully qualified name of StateMachine class

111

- out: Output file path

112

"""

113

114

def import_sm(qualname: str):

115

"""

116

Import StateMachine class by fully qualified name.

117

118

Parameters:

119

- qualname: Fully qualified class name

120

121

Returns:

122

StateMachine class

123

"""

124

```

125

126

### Command Line Interface

127

128

Command line tools for diagram generation.

129

130

```python { .api }

131

def main(argv=None):

132

"""

133

Main entry point for command line diagram generation.

134

135

Supports generating diagrams from command line using:

136

python -m statemachine.contrib.diagram <qualname> <output_file>

137

"""

138

```

139

140

## Usage Examples

141

142

### Basic Diagram Generation

143

144

```python

145

from statemachine import StateMachine, State

146

from statemachine.contrib.diagram import DotGraphMachine

147

148

class TrafficLight(StateMachine):

149

green = State(initial=True)

150

yellow = State()

151

red = State()

152

153

cycle = (

154

green.to(yellow)

155

| yellow.to(red)

156

| red.to(green)

157

)

158

159

# Create state machine and generate diagram

160

traffic = TrafficLight()

161

162

# Method 1: Using built-in _graph() method

163

graph = traffic._graph()

164

graph.write("traffic_light.png")

165

166

# Method 2: Using DotGraphMachine directly

167

diagram = DotGraphMachine(traffic)

168

diagram.write("traffic_light_custom.png", format="png")

169

170

# Method 3: Generate SVG

171

diagram.write("traffic_light.svg", format="svg")

172

173

# Method 4: Generate PDF

174

diagram.write("traffic_light.pdf", format="pdf")

175

```

176

177

### Custom Diagram Styling

178

179

```python

180

class CustomStyledDiagram(DotGraphMachine):

181

# Customize appearance

182

graph_rankdir = "TB" # Top to bottom layout

183

font_name = "Helvetica"

184

state_font_size = "12"

185

state_active_penwidth = 3

186

state_active_fillcolor = "lightblue"

187

transition_font_size = "10"

188

189

class OrderWorkflow(StateMachine):

190

pending = State("Pending Order", initial=True)

191

paid = State("Payment Received")

192

shipped = State("Order Shipped")

193

delivered = State("Delivered", final=True)

194

cancelled = State("Cancelled", final=True)

195

196

pay = pending.to(paid)

197

ship = paid.to(shipped)

198

deliver = shipped.to(delivered)

199

cancel = (

200

pending.to(cancelled)

201

| paid.to(cancelled)

202

| shipped.to(cancelled)

203

)

204

205

# Create workflow and generate custom styled diagram

206

workflow = OrderWorkflow()

207

custom_diagram = CustomStyledDiagram(workflow)

208

custom_diagram.write("order_workflow_custom.png")

209

210

# Set current state and regenerate to show active state

211

workflow.send("pay")

212

workflow.send("ship")

213

active_diagram = CustomStyledDiagram(workflow)

214

active_diagram.write("order_workflow_active.png")

215

```

216

217

### Online Diagram Generation (No Local Graphviz)

218

219

```python

220

from statemachine.contrib.diagram import quickchart_write_svg

221

222

class SimpleSwitch(StateMachine):

223

off = State("Off", initial=True)

224

on = State("On")

225

226

turn_on = off.to(on)

227

turn_off = on.to(off)

228

229

# Generate diagram using online service (no local Graphviz required)

230

switch = SimpleSwitch()

231

quickchart_write_svg(switch, "switch_diagram.svg")

232

233

print("Diagram generated using QuickChart service")

234

```

235

236

### Complex State Machine Visualization

237

238

```python

239

class ComplexWorkflow(StateMachine):

240

# Define multiple states with descriptive names

241

draft = State("Draft Document", initial=True)

242

review = State("Under Review")

243

revision = State("Needs Revision")

244

approved = State("Approved")

245

published = State("Published", final=True)

246

rejected = State("Rejected", final=True)

247

archived = State("Archived", final=True)

248

249

# Define complex transition network

250

submit = draft.to(review)

251

approve = review.to(approved)

252

request_revision = review.to(revision)

253

resubmit = revision.to(review)

254

reject = review.to(rejected) | approved.to(rejected)

255

publish = approved.to(published)

256

archive = (

257

draft.to(archived)

258

| rejected.to(archived)

259

| published.to(archived)

260

)

261

262

# Add transition conditions for better diagram labeling

263

urgent_publish = approved.to(published).cond("is_urgent")

264

regular_publish = approved.to(published).unless("is_urgent")

265

266

def is_urgent(self, priority: str = "normal"):

267

return priority == "urgent"

268

269

# Generate comprehensive diagram

270

workflow = ComplexWorkflow()

271

272

# Create diagram with custom settings for complex visualization

273

class ComplexDiagram(DotGraphMachine):

274

graph_rankdir = "LR"

275

font_name = "Arial"

276

state_font_size = "11"

277

transition_font_size = "9"

278

279

def create_digraph(self):

280

"""Override to add custom graph attributes."""

281

dot = super().create_digraph()

282

283

# Add graph-level styling

284

dot.set_graph_defaults(

285

fontname=self.font_name,

286

fontsize="14",

287

labelloc="t",

288

label="Document Workflow State Machine"

289

)

290

291

# Add node-level styling

292

dot.set_node_defaults(

293

shape="box",

294

style="rounded,filled",

295

fillcolor="lightyellow",

296

fontname=self.font_name

297

)

298

299

# Add edge-level styling

300

dot.set_edge_defaults(

301

fontname=self.font_name,

302

fontsize=self.transition_font_size

303

)

304

305

return dot

306

307

complex_diagram = ComplexDiagram(workflow)

308

complex_diagram.write("complex_workflow.png", format="png")

309

complex_diagram.write("complex_workflow.svg", format="svg")

310

```

311

312

### Diagram Generation in Different Formats

313

314

```python

315

class MultiFormatExample(StateMachine):

316

start = State("Start", initial=True)

317

process = State("Processing")

318

complete = State("Complete", final=True)

319

error = State("Error", final=True)

320

321

begin = start.to(process)

322

finish = process.to(complete)

323

fail = process.to(error)

324

retry = error.to(process)

325

326

# Generate diagrams in multiple formats

327

example = MultiFormatExample()

328

diagram = DotGraphMachine(example)

329

330

# Common formats

331

formats = {

332

"png": "Portable Network Graphics",

333

"svg": "Scalable Vector Graphics",

334

"pdf": "Portable Document Format",

335

"jpg": "JPEG Image",

336

"gif": "Graphics Interchange Format",

337

"dot": "DOT Source Code"

338

}

339

340

for fmt, description in formats.items():

341

filename = f"multi_format_example.{fmt}"

342

try:

343

diagram.write(filename, format=fmt)

344

print(f"Generated {description}: {filename}")

345

except Exception as e:

346

print(f"Failed to generate {fmt}: {e}")

347

```

348

349

### Command Line Usage Examples

350

351

```python

352

# Command line diagram generation examples:

353

354

# Basic usage:

355

# python -m statemachine.contrib.diagram myapp.machines.OrderMachine order_diagram.png

356

357

# Generate SVG:

358

# python -m statemachine.contrib.diagram myapp.machines.OrderMachine order_diagram.svg

359

360

# Example of programmatic command line invocation

361

import subprocess

362

import sys

363

364

def generate_diagram_cli(machine_qualname: str, output_path: str):

365

"""Generate diagram using command line interface."""

366

try:

367

result = subprocess.run([

368

sys.executable, "-m", "statemachine.contrib.diagram",

369

machine_qualname, output_path

370

], capture_output=True, text=True, check=True)

371

372

print(f"Diagram generated successfully: {output_path}")

373

return True

374

except subprocess.CalledProcessError as e:

375

print(f"Error generating diagram: {e.stderr}")

376

return False

377

378

# Usage

379

if __name__ == "__main__":

380

# This would work if the machine class is importable

381

success = generate_diagram_cli(

382

"myproject.machines.WorkflowMachine",

383

"workflow_diagram.png"

384

)

385

```

386

387

### Integration with Jupyter Notebooks

388

389

```python

390

# Jupyter notebook integration example

391

try:

392

from IPython.display import Image, SVG, display

393

import tempfile

394

import os

395

396

def display_state_machine(machine: StateMachine, format: str = "svg"):

397

"""Display state machine diagram in Jupyter notebook."""

398

# Create temporary file

399

with tempfile.NamedTemporaryFile(suffix=f".{format}", delete=False) as tmp:

400

temp_path = tmp.name

401

402

try:

403

# Generate diagram

404

diagram = DotGraphMachine(machine)

405

diagram.write(temp_path, format=format)

406

407

# Display in notebook

408

if format.lower() == "svg":

409

with open(temp_path, 'r') as f:

410

display(SVG(f.read()))

411

else:

412

display(Image(temp_path))

413

414

finally:

415

# Clean up temporary file

416

if os.path.exists(temp_path):

417

os.unlink(temp_path)

418

419

# Usage in Jupyter notebook

420

class NotebookExample(StateMachine):

421

idle = State("Idle", initial=True)

422

working = State("Working")

423

done = State("Done", final=True)

424

425

start = idle.to(working)

426

complete = working.to(done)

427

428

notebook_machine = NotebookExample()

429

430

# Display the diagram in notebook

431

display_state_machine(notebook_machine, "svg")

432

433

# Show state after transition

434

notebook_machine.send("start")

435

display_state_machine(notebook_machine, "svg")

436

437

except ImportError:

438

print("IPython not available - skipping Jupyter notebook example")

439

```

440

441

### Animated State Diagrams

442

443

```python

444

import time

445

from pathlib import Path

446

447

def create_animated_sequence(machine: StateMachine, events: list, output_dir: str = "animation_frames"):

448

"""Create sequence of diagrams showing state transitions."""

449

Path(output_dir).mkdir(exist_ok=True)

450

451

frames = []

452

453

# Initial state

454

diagram = DotGraphMachine(machine)

455

frame_path = f"{output_dir}/frame_000_initial.png"

456

diagram.write(frame_path)

457

frames.append(frame_path)

458

459

# Process each event

460

for i, event in enumerate(events, 1):

461

try:

462

machine.send(event)

463

diagram = DotGraphMachine(machine)

464

frame_path = f"{output_dir}/frame_{i:03d}_{event}.png"

465

diagram.write(frame_path)

466

frames.append(frame_path)

467

print(f"Generated frame {i}: {event} -> {machine.current_state.id}")

468

except Exception as e:

469

print(f"Error processing event {event}: {e}")

470

break

471

472

return frames

473

474

# Create animated sequence

475

class AnimatedExample(StateMachine):

476

state1 = State("State 1", initial=True)

477

state2 = State("State 2")

478

state3 = State("State 3")

479

final = State("Final", final=True)

480

481

next1 = state1.to(state2)

482

next2 = state2.to(state3)

483

finish = state3.to(final)

484

reset = (state2.to(state1) | state3.to(state1))

485

486

animated_machine = AnimatedExample()

487

event_sequence = ["next1", "next2", "reset", "next1", "next2", "finish"]

488

489

frames = create_animated_sequence(animated_machine, event_sequence)

490

print(f"Generated {len(frames)} animation frames")

491

492

# Note: To create actual animated GIF, you would need additional tools like PIL or ImageIO

493

# Example with PIL (if available):

494

try:

495

from PIL import Image

496

497

def create_gif(frame_paths: list, output_path: str, duration: int = 1000):

498

"""Create animated GIF from frame images."""

499

images = []

500

for path in frame_paths:

501

images.append(Image.open(path))

502

503

images[0].save(

504

output_path,

505

save_all=True,

506

append_images=images[1:],

507

duration=duration,

508

loop=0

509

)

510

print(f"Animated GIF created: {output_path}")

511

512

# create_gif(frames, "state_machine_animation.gif", duration=1500)

513

514

except ImportError:

515

print("PIL not available - cannot create animated GIF")

516

```