Re: [TIKZ] Macchina a stati finiti… gerarchica

#79000
Up
0
Down
::


In effetti quei disegni sono orribili… ma occorre anche precisare che con TikZ l’occhio si abitua all’eccellenza. 🙂

In questo intervento si realizzerà una delle figure a cui l’OP faceva riferimento e può essere inteso come tutorial valido per tutti gli schemi in quanto si utilizzano diverse TikZ-tecniche.

Le basi

Quando si inizia è fondamentale fare una scelta di base: quale tecnica usare per posizionare i nodi. I metodi non sono infiniti: posizionamento manuale con le coordinate assolute, posizionamento manuale con le coordinate relative oppure posizionare i nodi su una matrice. Il primo metodo è, in genere, da scartare, a meno che si sia abituati a vedere il proprio disegno su un piano cartesiano e quindi sia abbastanza facile determinare a mente le coordinate.

Per realizzare il disegno e per mostrare insieme un paio di tecniche si utilizzeranno sia i riferimenti relativi che il posizionamento con matrici.

Disegnare rettangoli

Il disegno dei rettangoli non deve spaventare, anzi è una delle cose più semplici da realizzare. Con TikZ è possibile definire stili da applicare ai nodi, le varie label del disegno. Ecco cosa si utilizzerà per la figura:
`
\tikzset{blocco/.style={draw,rounded corners}}
`La definizione degli stili, un consiglio, va preferibilmente inserita nel preambolo.

Creare i collegamenti

Mentre si posizionano i nodi, è consigliabile etichettarli con label, in modo tale da poter utilizzare successivamente le label come riferimenti. Le frecce, quindi, saranno dei segmenti originati da un nodo e diretti verso un altro. Ad esempio, per collegare (a) e (b) sapendo che (b) è a destra di (a) si può scrivere:
`\draw[->](a.east)–(b.west); `in questo modo il segmento è originato dall’ancora posizionata a destra ([tt]east[/tt]) di (a) verso il punto contraddistinto dall’ancora a sinistra ([tt]west[/tt]) di (b). Le ancore strettamente indispensabili sono [tt]north[/tt], [tt]south[/tt], [tt]east[/tt] e [tt]west[/tt].

Posizionamento dei nodi ed allineamenti

Iniziamo a posizionare il primo nodo, lo [tt]start[/tt]. Il codice è semplice:
`\node[blocco] (start) {start}; `Utilizzando i riferimenti relativi non serve definire dove posizionare il primo nodo. Si noti che il nostro nodo è di tipo [tt]blocco[/tt] perciò sarà evidenziato dal rettangolo, come si vuole e il nodo ha come label [tt](start)[/tt], la stessa che deve comparire in figura. Ovviamente, però, la label può essere definita a piacere.

A destra del nodo [tt]start[/tt], nella figura campione sono presenti quattro nodi. Cosa meglio di una matrice per definirli? L’idea base è definire una matrice di questo tipo:
`\matrix (m1) [blocco, right= 2.5cm of start]{
try1 & \node[blocco](fail1){fail};\\
$K_2$ & \node[blocco](ok1){ok};\\
};` Con questa definizione si otterrebbero i quattro nodi delimitati da un rettangolo come, all’interno della matrice, i nodi (fail) e (ok); il problema sarebbe l’allineamento dei nodi stessi: in genere, con matrici, posizionamento dei nodi con riferimenti relativi ecc.. l’allineamento dei nodi va farsi benedire senza un intervento manuale. Inoltre, la spaziatura dei nodi nella matrice è pensata per elementi senza niente altro ed evidenziare con un rettangolo alcuni di essi fa sì che il risultato non sia dei migliori. Entrambi i problemi si risolvono specificando a mano il punto di ancoraggio e la spaziatura dei nodi, ad esempio:
`
\matrix (m1) [matrix of nodes,blocco, right= 2.5cm of start ,column sep=0.25cm,row sep =0.25 cm, every column/.style={anchor=center},minimum height=1ex,]{
try1 & \node[blocco](fail1){fail};\\
$K_2$ & \node[blocco](ok1){ok};\\
};
`Si noti che anche la matrice ha la propria label, (m1), esattamente come gli altri nodi, ma serve solo al posizionamento dei nodi, non per le frecce.

In maniera analoga a questa, andiamo a definire una seconda matrice per il secondo gruppo di nodi:
`
\matrix (m2) [matrix of nodes,blocco, right= 3cm of m1 ,column sep=0.25cm,row sep =0.25 cm, every column/.style={anchor=center},minimum height=1ex,]{
try2 & \node[blocco](fail2){fail};\\
$K_2$ & \node[blocco](ok2){ok};\\
};
` Il posizionamento di (m2) è chiaramente a destra di (m1) come si evince da [tt]right= 3cm of m1[/tt]. Per i nodi [tt]fail[/tt] ed [tt]ok[/tt] si sono usate label diverse rispetto a quelle definite per gli stessi nodi in (m1).

La terza matrice è un po’ diversa rispetto alle due precedenti. In realtà non servirebbe neppure una matrice per definire i nodi (abort) e (success), ma viene comodo utilizzare la spaziatura delle righe per posizionare in maniera fedele alla figura riferimento di due nodi. Ecco la definizione:
`
\matrix (m3) [matrix of nodes, right= 3cm of m2,row sep =1.5 cm, every column/.style={anchor=center},minimum height=3ex,minimum width=4em]{
\node[blocco](abort){abort};\\
\node[blocco](success){success};\\
};
` Si noti che in ogni matrice si è specificato almeno [tt]minimum height[/tt]: la chiave contribuisce, come l’ancoraggio, all’allineamento dei nodi. In genere si usa [tt]text height[/tt], ma in questo caso occorre tenere in considerazione che un nodo può anche avere il rettangolo al proprio bordo.

Posizionamento dei collegamenti

In questa sezione si discute come disegnare effettivamente le frecce da un nodo ad un altro. Un comodo strumento è la libreria [tt]calc[/tt] che permetterà di effettuare calcoli per meglio caratterizzare i segmenti. È comodo definire il tipo di freccia da usare nelle opzioni dell’ambiente [tt]tikzpicture[/tt], in modo tale da non ripeterlo ad ogni passaggio; nel nostro caso:
`\begin{tikzpicture}[-stealth]` che è la mia preferita.

Iniziamo con la freccia a destra del nodo (start):
`\path[draw]($(start.west)-(1,0)$)–(start.west);` Come si può notare, con la sintassi della libreria calc si può disegnare un segmento a partire da un singolo nodo conosciuto: in questo caso il punto a sinistra del nodo (start.west). Il punto iniziale è 1cm a sinistra della coordinata (start.west) infatti la sintassi indica che a (start.west) si sottrae 1cm sull’asse x e 0cm sull’asse y.

Colleghiamo i nodi start e m1 con:
`\path[draw](start.east)–(m1);` mentre con:
`\path[draw](fail1.east)–(fail1.east-|m2.west);` colleghiamo i nodi (fail1) della prima matrice con la seconda matrice. Si noti la sintassi [tt](fail1.east-|m2.west)[/tt]: in questo modo si sta dicendo che la coordinata di arrivo ha lo stesso valore sull’asse x di (fail1) e quello dell’asse y di (m2). Scrivere:
`\path[draw](fail1.east)–(m2.west);` avrebbe disegnato un segmento il cui punto di arrivo sarebbe stato il centro esatto del rettangolo della seconda matrice.

Proseguendo, si commenteranno solo gli altri collegamenti con particolarità evidenti. Ad esempio:
`\path[draw](ok1.east)–($(ok1.east|-success.west)!0.5!(m2.west|-success.west)$)–(success.west);` che collega il punto (ok1) della prima matrice verso (success). La particolarità è il calcolo del punto intermedio [tt]($(ok1.east|-success.west)!0.5!(m2.west|-success.west)$)[/tt] in cui il segmento si divide: partendo da (ok1.east) verso il punto indicato in direzione obliqua e dal punto intermedio verso (success) in direzione orizzontale.

Invece, con:
`
\path[draw](ok2.east)–(success.170);
` è possibile spostare il punto di arrivo (per evitare sovrapposizioni con la freccia già disegnata che parte da (ok1)) riferendosi all’angolo del rettangolo del nodo (success). 170, infatti, rappresentano i gradi. Attenzione: l’uso degli angoli per fare riferimento al bordo di un nodo può essere pericoloso se non si è sicuri al 100% di cosa si fa; gli angoli infatti cambiano rispetto alle dimensioni del nodo, perciò in generale non è vero che per due distinti nodi i riferimenti (x.170) e (y.170) siano uguali (lo so per esperienza perché ci sono cascato come un pollo 😀 – si veda qui).

Il rettangolo in background

Dopo aver posizionato i nodi e disegnato tutti i collegamenti, il tocco finale è rappresentato dalla definizione del rettangolo che comprende tutta la figura. Ho pensato di posizionare tale rettangolo in background; in questo caso è un passaggio superfulo, ma conoscere questa libreria a volte è veramente utile (e qui bisogna ringraziare Liverpool che è stato il primo a suggerirla- si veda qui). La libreria infatti permette di definire vari “livelli” in cui apporre parte dell’immagine, ad esempio colorare un sfondo in modo tale che il contenuto da tenere in primo piano non sia reso opaco. In questo caso, comunque, si è utilizzato il livello “predefinito” [tt]background[/tt] reso già disponibile dalla libreria; in generale l’utilizzo è:

`\begin{pgfonlayer}{background}

\end{pgfonlayer}` Dove i [tt]…[/tt] rappresentano il codice da inserire in background.

Nel nostro caso codice necessario è:
`
\begin{pgfonlayer}{background}
\node[scale=0.02] (mywest) at ($(start.west)-(0.5,0)$){};
\node[scale=0.02] (mynorth) at ($(abort.north)+(0,0.5)$){};
\node[scale=0.02] (myeast) at ($(abort.east)+(0.5,0)$){};
\node[scale=0.02] (mysouth) at ($(success.south)-(0,0.5)$){};

\draw[rounded corners,-] (mywest)|-(mynorth)-|(myeast)|-(mysouth)-| (mywest);
\end{pgfonlayer}
` I primi comandi servono per creare nodi “aiuto”, ovvero dei nodi le cui posizioni, definite a partire dai nodi già esistenti, saranno la base per il disegno del rettangolo. Si consideri, ad esempio:
`\node[scale=0.02] (mywest) at ($(start.west)-(0.5,0)$){};` È molto simile alla sintassi usata per definire la freccia a sinistra del nodo (start) e infatti con questa sintassi si definisce il punto intermedio di quel collegamento. Tutto ciò, ovviamente, perché nella figura campione il rettangolo globale aveva come particolarità il fatto di “tagliare” le frecce entranti ed uscenti dalla figura. I nodi “aiuto” definiti sono stati tutti scalati: questo fa sì che nel disegno del rettangolo non siano visibili “buchi bianchi”. In genere, infatti, ogni nodo, anche se non ha una label, occupa dello spazio; ad esempio, il codice:
`
\node (x) at (1,0) {};
\draw (0,0)–(x)–(2,1);
` non produce un segmento continuo.

Codice completo

Ecco il codice con un esempio compilabile:
`\documentclass{article}

\usepackage{tikz}
\usetikzlibrary{positioning,matrix,calc,backgrounds}

\tikzset{blocco/.style={draw,rounded corners}}

\begin{document}

\begin{tikzpicture}[-stealth,on grid, node distance=1cm,scale=0.95,transform shape]

% Blocchi

\node[blocco] (start) {start};

\matrix (m1) [matrix of nodes,blocco, right= 2.5cm of start ,column sep=0.25cm,row sep =0.25 cm, every column/.style={anchor=center},minimum height=1ex,]{
try1 & \node[blocco](fail1){fail};\\
$K_2$ & \node[blocco](ok1){ok};\\
};

\matrix (m2) [matrix of nodes,blocco, right= 3cm of m1 ,column sep=0.25cm,row sep =0.25 cm, every column/.style={anchor=center},minimum height=1ex,]{
try2 & \node[blocco](fail2){fail};\\
$K_2$ & \node[blocco](ok2){ok};\\
};

\matrix (m3) [matrix of nodes, right= 3cm of m2,row sep =1.5 cm, every column/.style={anchor=center},minimum height=3ex,minimum width=4em]{
\node[blocco](abort){abort};\\
\node[blocco](success){success};\\
};

% Collegamenti
\path[draw]($(start.west)-(1,0)$)–(start.west);
\path[draw](start.east)–(m1);
\path[draw](fail1.east)–(fail1.east-|m2.west);
\path[draw](ok1.east)–($(ok1.east|-success.west)!0.5!(m2.west|-success.west)$)–(success.west);
\path[draw](fail2.east)–(abort.west);
\path[draw](ok2.east)–(success.170);

\path[draw](abort.east)–($(abort.east)+(1,0)$);
\path[draw](success.east)–($(success.east)+(1,0)$);

% background
\begin{pgfonlayer}{background}
\node[scale=0.02] (mywest) at ($(start.west)-(0.5,0)$){};
\node[scale=0.02] (mynorth) at ($(abort.north)+(0,0.5)$){};
\node[scale=0.02] (myeast) at ($(abort.east)+(0.5,0)$){};
\node[scale=0.02] (mysouth) at ($(success.south)-(0,0.5)$){};

\draw[rounded corners,-] (mywest)|-(mynorth)-|(myeast)|-(mysouth)-| (mywest);
\end{pgfonlayer}

\end{tikzpicture}

\end{document}`

Il risultato grafico

[attachment=462]Immagine_2012-09-23.jpg[/attachment]

E ora… il gran premio 😎

Ciao
Claudio

Attachments:
You must be logged in to view attached files.

Go to top