@@ -130,6 +130,300 @@ JupyterLab cells — no extra code needed:
130130![ Approval machine on JupyterLab] ( images/lab_approval_machine_accepted.png )
131131
132132
133+ ## Visual showcase
134+
135+ This section demonstrates how each state machine feature is rendered in
136+ diagrams. Each example shows both the ** class** diagram (no active state) and
137+ the ** instance** diagram (with the current state highlighted).
138+
139+ ``` py
140+ >> > # This showcase will only run on automated tests if dot is present
141+ >> > getfixture(" requires_dot_installed" )
142+
143+ >> > from statemachine import State, StateChart, HistoryState
144+ >> > from statemachine.contrib.diagram import DotGraphMachine
145+
146+ ```
147+
148+ ### Simple states
149+
150+ A minimal state machine with three atomic states and linear transitions.
151+
152+ ``` py
153+ >> > class SimpleSC (StateChart ):
154+ ... idle = State(initial = True )
155+ ... running = State()
156+ ... done = State(final = True )
157+ ... start = idle.to(running)
158+ ... finish = running.to(done)
159+
160+ >> > DotGraphMachine(SimpleSC)().write_png(" docs/images/showcase_simple_class.png" )
161+
162+ >> > sm = SimpleSC()
163+ >> > DotGraphMachine(sm)().write_png(" docs/images/showcase_simple_initial.png" )
164+
165+ >> > sm.start()
166+ >> > DotGraphMachine(sm)().write_png(" docs/images/showcase_simple_running.png" )
167+
168+ >> > sm.finish()
169+ >> > DotGraphMachine(sm)().write_png(" docs/images/showcase_simple_done.png" )
170+
171+ ```
172+
173+ | Class | Initial | Running | Done (final) |
174+ | :---:| :---:| :---:| :---:|
175+ | ![ ] ( images/showcase_simple_class.png ) | ![ ] ( images/showcase_simple_initial.png ) | ![ ] ( images/showcase_simple_running.png ) | ![ ] ( images/showcase_simple_done.png ) |
176+
177+
178+ ### Entry and exit actions
179+
180+ States can declare ` entry ` / ` exit ` callbacks, shown in the state label.
181+
182+ ``` py
183+ >> > class ActionsSC (StateChart ):
184+ ... off = State(initial = True )
185+ ... on = State()
186+ ... done = State(final = True )
187+ ... power_on = off.to(on)
188+ ... shutdown = on.to(done)
189+ ... def on_exit_off (self ): ...
190+ ... def on_enter_on (self ): ...
191+ ... def on_exit_on (self ): ...
192+ ... def on_enter_done (self ): ...
193+
194+ >> > DotGraphMachine(ActionsSC)().write_png(" docs/images/showcase_actions_class.png" )
195+
196+ >> > sm = ActionsSC()
197+ >> > sm.power_on()
198+ >> > DotGraphMachine(sm)().write_png(" docs/images/showcase_actions_on.png" )
199+
200+ ```
201+
202+ | Class | Active: On |
203+ | :---:| :---:|
204+ | ![ ] ( images/showcase_actions_class.png ) | ![ ] ( images/showcase_actions_on.png ) |
205+
206+
207+ ### Guard conditions
208+
209+ Transitions can have ` cond ` guards, shown in brackets on the edge label.
210+
211+ ``` py
212+ >> > class GuardSC (StateChart ):
213+ ... pending = State(initial = True )
214+ ... approved = State(final = True )
215+ ... rejected = State(final = True )
216+ ... def is_valid (self ): return True
217+ ... def is_invalid (self ): return False
218+ ... review = pending.to(approved, cond = " is_valid" ) | pending.to(rejected, cond = " is_invalid" )
219+
220+ >> > DotGraphMachine(GuardSC)().write_png(" docs/images/showcase_guards_class.png" )
221+
222+ >> > sm = GuardSC()
223+ >> > DotGraphMachine(sm)().write_png(" docs/images/showcase_guards_pending.png" )
224+
225+ ```
226+
227+ | Class | Active: Pending |
228+ | :---:| :---:|
229+ | ![ ] ( images/showcase_guards_class.png ) | ![ ] ( images/showcase_guards_pending.png ) |
230+
231+
232+ ### Self-transitions
233+
234+ A transition from a state back to itself.
235+
236+ ``` py
237+ >> > class SelfTransitionSC (StateChart ):
238+ ... counting = State(initial = True )
239+ ... done = State(final = True )
240+ ... increment = counting.to.itself()
241+ ... stop = counting.to(done)
242+
243+ >> > DotGraphMachine(SelfTransitionSC)().write_png(" docs/images/showcase_self_class.png" )
244+
245+ >> > sm = SelfTransitionSC()
246+ >> > DotGraphMachine(sm)().write_png(" docs/images/showcase_self_active.png" )
247+
248+ ```
249+
250+ | Class | Active: Counting |
251+ | :---:| :---:|
252+ | ![ ] ( images/showcase_self_class.png ) | ![ ] ( images/showcase_self_active.png ) |
253+
254+
255+ ### Internal transitions
256+
257+ Internal transitions execute actions without exiting/entering the state.
258+
259+ ``` py
260+ >> > class InternalSC (StateChart ):
261+ ... monitoring = State(initial = True )
262+ ... done = State(final = True )
263+ ... def log_status (self ): ...
264+ ... check = monitoring.to.itself(internal = True , on = " log_status" )
265+ ... stop = monitoring.to(done)
266+
267+ >> > DotGraphMachine(InternalSC)().write_png(" docs/images/showcase_internal_class.png" )
268+
269+ >> > sm = InternalSC()
270+ >> > DotGraphMachine(sm)().write_png(" docs/images/showcase_internal_active.png" )
271+
272+ ```
273+
274+ | Class | Active: Monitoring |
275+ | :---:| :---:|
276+ | ![ ] ( images/showcase_internal_class.png ) | ![ ] ( images/showcase_internal_active.png ) |
277+
278+
279+ ### Compound states
280+
281+ A compound state contains child states. Entering the compound activates
282+ its initial child.
283+
284+ ``` py
285+ >> > class CompoundSC (StateChart ):
286+ ... class active (State .Compound , name = " Active" ):
287+ ... idle = State(initial = True )
288+ ... working = State()
289+ ... begin = idle.to(working)
290+ ...
291+ ... off = State(initial = True )
292+ ... done = State(final = True )
293+ ... turn_on = off.to(active)
294+ ... turn_off = active.to(done)
295+
296+ >> > DotGraphMachine(CompoundSC)().write_png(" docs/images/showcase_compound_class.png" )
297+
298+ >> > sm = CompoundSC()
299+ >> > DotGraphMachine(sm)().write_png(" docs/images/showcase_compound_off.png" )
300+
301+ >> > sm.turn_on()
302+ >> > DotGraphMachine(sm)().write_png(" docs/images/showcase_compound_idle.png" )
303+
304+ >> > sm.begin()
305+ >> > DotGraphMachine(sm)().write_png(" docs/images/showcase_compound_working.png" )
306+
307+ ```
308+
309+ | Class | Off | Active/Idle | Active/Working |
310+ | :---:| :---:| :---:| :---:|
311+ | ![ ] ( images/showcase_compound_class.png ) | ![ ] ( images/showcase_compound_off.png ) | ![ ] ( images/showcase_compound_idle.png ) | ![ ] ( images/showcase_compound_working.png ) |
312+
313+
314+ ### Parallel states
315+
316+ A parallel state activates all its regions simultaneously.
317+
318+ ``` py
319+ >> > class ParallelSC (StateChart ):
320+ ... class both (State .Parallel , name = " Both" ):
321+ ... class left (State .Compound , name = " Left" ):
322+ ... l1 = State(initial = True )
323+ ... l2 = State(final = True )
324+ ... go_l = l1.to(l2)
325+ ... class right (State .Compound , name = " Right" ):
326+ ... r1 = State(initial = True )
327+ ... r2 = State(final = True )
328+ ... go_r = r1.to(r2)
329+ ...
330+ ... start = State(initial = True )
331+ ... end = State(final = True )
332+ ... enter = start.to(both)
333+ ... done_state_both = both.to(end)
334+
335+ >> > DotGraphMachine(ParallelSC)().write_png(" docs/images/showcase_parallel_class.png" )
336+
337+ >> > sm = ParallelSC()
338+ >> > sm.enter()
339+ >> > DotGraphMachine(sm)().write_png(" docs/images/showcase_parallel_active.png" )
340+
341+ >> > sm.go_l()
342+ >> > DotGraphMachine(sm)().write_png(" docs/images/showcase_parallel_l_done.png" )
343+
344+ ```
345+
346+ | Class | Both active | Left done |
347+ | :---:| :---:| :---:|
348+ | ![ ] ( images/showcase_parallel_class.png ) | ![ ] ( images/showcase_parallel_active.png ) | ![ ] ( images/showcase_parallel_l_done.png ) |
349+
350+
351+ ### History states (shallow)
352+
353+ A history pseudo-state remembers the last active child of a compound state.
354+
355+ ``` py
356+ >> > class HistorySC (StateChart ):
357+ ... class process (State .Compound , name = " Process" ):
358+ ... step1 = State(initial = True )
359+ ... step2 = State()
360+ ... advance = step1.to(step2)
361+ ... h = HistoryState()
362+ ...
363+ ... paused = State(initial = True )
364+ ... pause = process.to(paused)
365+ ... resume = paused.to(process.h)
366+ ... begin = paused.to(process)
367+
368+ >> > DotGraphMachine(HistorySC)().write_png(" docs/images/showcase_history_class.png" )
369+
370+ >> > sm = HistorySC()
371+ >> > sm.begin()
372+ >> > sm.advance()
373+ >> > DotGraphMachine(sm)().write_png(" docs/images/showcase_history_step2.png" )
374+
375+ >> > sm.pause()
376+ >> > DotGraphMachine(sm)().write_png(" docs/images/showcase_history_paused.png" )
377+
378+ >> > sm.resume()
379+ >> > DotGraphMachine(sm)().write_png(" docs/images/showcase_history_resumed.png" )
380+
381+ ```
382+
383+ | Class | Step2 | Paused | Resumed (→Step2) |
384+ | :---:| :---:| :---:| :---:|
385+ | ![ ] ( images/showcase_history_class.png ) | ![ ] ( images/showcase_history_step2.png ) | ![ ] ( images/showcase_history_paused.png ) | ![ ] ( images/showcase_history_resumed.png ) |
386+
387+
388+ ### Deep history
389+
390+ Deep history remembers the exact leaf state across nested compounds.
391+
392+ ``` py
393+ >> > class DeepHistorySC (StateChart ):
394+ ... class outer (State .Compound , name = " Outer" ):
395+ ... class inner (State .Compound , name = " Inner" ):
396+ ... a = State(initial = True )
397+ ... b = State()
398+ ... go = a.to(b)
399+ ... start = State(initial = True )
400+ ... enter_inner = start.to(inner)
401+ ... h = HistoryState(type = " deep" )
402+ ...
403+ ... away = State(initial = True )
404+ ... dive = away.to(outer)
405+ ... leave = outer.to(away)
406+ ... restore = away.to(outer.h)
407+
408+ >> > DotGraphMachine(DeepHistorySC)().write_png(" docs/images/showcase_deep_history_class.png" )
409+
410+ >> > sm = DeepHistorySC()
411+ >> > sm.dive()
412+ >> > sm.enter_inner()
413+ >> > sm.go()
414+ >> > DotGraphMachine(sm)().write_png(" docs/images/showcase_deep_history_inner_b.png" )
415+
416+ >> > sm.leave()
417+ >> > sm.restore()
418+ >> > DotGraphMachine(sm)().write_png(" docs/images/showcase_deep_history_restored.png" )
419+
420+ ```
421+
422+ | Class | Inner/B | Restored (→Inner/B) |
423+ | :---:| :---:| :---:|
424+ | ![ ] ( images/showcase_deep_history_class.png ) | ![ ] ( images/showcase_deep_history_inner_b.png ) | ![ ] ( images/showcase_deep_history_restored.png ) |
425+
426+
133427## Online generation (QuickChart)
134428
135429If you prefer not to install Graphviz locally, you can generate diagrams
0 commit comments