11module TikzGraph
2- export rgbcolor!, Node, Line, BoundingBox, Mesh, Canvas, >> , command, canvas, generate_standalone, StringElement, PlainText
2+ export rgbcolor!, Node, Line, BoundingBox, Mesh, Canvas, >> , command, canvas, generate_standalone, StringElement, PlainText, uselib!
3+ export Cycle, Controls, annotate, Annotate, autoid!, vizgraph!
34
45const instance_counter = Ref (0 )
56abstract type AbstractTikzElement end
67
78struct Canvas
89 header:: String
10+ libs:: Vector{String}
911 colors:: Dict{String, Tuple{Int,Int,Int}}
1012 contents:: Vector{AbstractTikzElement}
1113 props:: Dict{String,String}
1214end
1315
14- function canvas (f; header= " " , colors= Dict {String,Tuple{Int,Int,Int}} (), props= Dict {String,String} ())
15- canvas = Canvas (header, colors, AbstractTikzElement[], props)
16+ function canvas (f; header= " " , libs = String[], colors= Dict {String,Tuple{Int,Int,Int}} (), props= Dict {String,String} ())
17+ canvas = Canvas (header, libs, colors, AbstractTikzElement[], props)
1618 f (canvas)
1719 return canvas
1820end
1921
20- Base.:(>> )(element:: AbstractTikzElement , canvas:: Canvas ) = push! (canvas. contents, element)
21- Base.:(>> )(element:: String , canvas:: Canvas ) = push! (canvas. contents, StringElement (element))
22+ Base.:(>> )(element:: AbstractTikzElement , canvas:: Canvas ) = ( push! (canvas. contents, element); element)
23+ Base.:(>> )(element:: String , canvas:: Canvas ) = ( push! (canvas. contents, StringElement (element)); element )
2224
25+ function uselib! (canvas:: Canvas , lib:: String )
26+ push! (canvas. libs, lib)
27+ return lib
28+ end
2329function rgbcolor! (canvas:: Canvas , red:: Int , green:: Int , blue:: Int )
24- instance_counter[] += 1
25- colorname = " color$(instance_counter[]) "
30+ colorname = " color$(autoid! ()) "
2631 canvas. colors[colorname] = (red,green,blue)
2732 return colorname
2833end
5661
5762function Node (x, y;
5863 shape:: String = " circle" ,
59- id = string ((instance_counter[] += 1 ; instance_counter[])),
64+ id = autoid! (),
6065 text:: String = " " ,
6166 fill = " none" ,
6267 draw = " black" ,
@@ -73,12 +78,13 @@ function Node(x, y;
7378 kwargs... )
7479 return Node (x, y, shape, id, text, props)
7580end
81+ autoid! () = string ((instance_counter[] += 1 ; instance_counter[]))
7682function build_props (; kwargs... )
7783 Dict ([replace (string (k), " _" => " " )=> string (v) for (k,v) in kwargs])
7884end
7985
8086function command (node:: Node )
81- return " \\ node[$(node. shape) , $( command ( node. props)) ] at ($(node. x) , $(node. y) ) ($(node. id) ) {$(node. text) };"
87+ return " \\ node[$(parse_args ([ string ( node. shape)], node. props)) ] at ($(node. x) , $(node. y) ) ($(node. id) ) {$(node. text) };"
8288end
8389
8490struct Mesh <: AbstractTikzElement
@@ -93,28 +99,52 @@ function Mesh(xmin, xmax, ymin, ymax; step="1.0cm", draw="gray", line_width=0.03
9399 Mesh (xmin, xmax, ymin, ymax, build_props (; step= step, draw= draw, line_width= line_width, kwargs... ))
94100end
95101function command (grid:: Mesh )
96- return " \\ draw[$(command ( grid. props)) ] ($(grid. xmin) ,$(grid. ymin) ) grid ($(grid. xmax) ,$(grid. ymax) );"
102+ return " \\ draw[$(parse_args (String[], grid. props)) ] ($(grid. xmin) ,$(grid. ymin) ) grid ($(grid. xmax) ,$(grid. ymax) );"
97103end
98104
99- struct Line <: AbstractTikzElement
100- src:: String
101- dst:: String
102- controls:: Vector{Int}
103- props:: Dict{String,String}
105+ struct Cycle end
106+ struct Controls
107+ start:: String
108+ controls:: Vector{String}
109+ stop:: String
110+ Controls (start, c1, stop) = new (parse_path (start), [parse_path (c1)], parse_path (stop))
111+ Controls (start, c1, c2, stop) = new (parse_path (start), [parse_path (c1), parse_path (c2)], parse_path (stop))
104112end
105113
106- function Line (src, dst; controls= Int[], line_width = " 0.03" , kwargs... )
107- Line (string (src), string (dst), controls, build_props (; line_width= line_width, kwargs... ))
114+ struct Annotate
115+ args:: Vector{String} # e.g. "[midway, above]"
116+ id:: String
117+ text:: String
108118end
109- Line (src :: Node , dst :: Node ; kwargs ... ) = Line (src . id, dst . id )
119+ Base . isempty (ann :: Annotate ) = isempty (ann . text )
110120
121+ # arrow styles: https://latexdraw.com/exploring-tikz-arrows/
122+ # line styles: https://tex.stackexchange.com/questions/45275/tikz-get-values-for-predefined-dash-patterns
123+ struct Line <: AbstractTikzElement
124+ path:: Vector{String}
125+ arrow:: String
126+ line_style:: String
127+ annotate:: Annotate
128+ props:: Dict{String,String}
129+ end
130+ function Line (path... ; annotate:: Union{String,Annotate} = " " , arrow:: String = " " , line_width = " 0.03" , line_style= " " , kwargs... )
131+ ann = annotate isa String ? Annotate ([" midway" , " above" , " sloped" ], " " , annotate) : annotate
132+ Line (collect (parse_path .(path)), arrow, line_style, ann, build_props (; line_width= line_width, kwargs... ))
133+ end
134+ parse_path (t:: Tuple ) = " $(t) "
135+ parse_path (n:: Node ) = " ($(n. id) )"
136+ parse_path (s:: String ) = " ($s )"
137+ parse_path (s:: Cycle ) = " cycle"
138+ function parse_path (c:: Controls )
139+ " $(c. start) .. controls $(join ([" $c " for c in c. controls], " and " )) .. $(c. stop) "
140+ end
111141function command (edge:: Line )
112- head = " \\ draw[$(command ( edge. props)) ]"
113- if isempty (edge. controls )
114- return " $head ( $( edge. src) ) -- ( $(edge . dst) ); "
115- else
116- return " $head ( $(edge . src) ) .. controls $( join ([ " ( $c ) " for c in edge . controls], " and " )) .. ( $(edge . dst) ); "
117- end
142+ head = " \\ draw[$(parse_args ([edge . arrow, edge . line_style], edge. props)) ]"
143+ path = join (edge. path, " -- " )
144+ ann = edge. annotate
145+ isempty (ann) && return " $head $path ; "
146+ annotate = " node [ $( parse_args (ann . args, Dict {String,String} ())) ] ( $(ann . id ) ) { $(ann . text) } "
147+ return " $head $path $annotate ; "
118148end
119149
120150struct PlainText <: AbstractTikzElement
@@ -127,28 +157,47 @@ function PlainText(x, y, text; kwargs...)
127157 PlainText (x, y, text, build_props (; kwargs... ))
128158end
129159function command (text:: PlainText )
130- " \\ node[$(command ( text. props)) ] at ($(text. x) , $(text. y) ) {$(text. text) };"
160+ " \\ node[$(parse_args (String[], text. props)) ] at ($(text. x) , $(text. y) ) {$(text. text) };"
131161end
132162
133- function command (node:: Dict ) # properties
134- return join ([" $k =$v " for (k,v) in node], " , " )
163+ annotate (node:: Node , text; offsetx= 0 , offsety= 0 , kwargs... ) = PlainText (node. x+ offsetx, node. y+ offsety, text; kwargs... )
164+
165+ function parse_args (args:: Vector , kwargs:: Dict ) # properties
166+ return join (filter (! isempty, [args... , [" $k =$v " for (k,v) in kwargs if ! isempty (v)]. .. ]), " , " )
135167end
136168
137- function generate_standalone (header:: String , props:: Dict , content:: String )
169+ function generate_standalone (libs :: Vector , header:: String , props:: Dict , content:: String )
138170 return """
139171\\ documentclass[crop,tikz]{standalone}
172+ $(join ([" \\ usepgflibrary{$lib }" for lib in libs], " \n " ))
140173$(header)
141174\\ begin{document}
142- \\ begin{tikzpicture}[$(command ( props)) ]
175+ \\ begin{tikzpicture}[$(parse_args (String[], props)) ]
143176$content
144177\\ end{tikzpicture}
145178\\ end{document}
146179"""
147180end
148- generate_standalone (canvas:: Canvas ) = generate_standalone (canvas. header, canvas. props, join ([[generate_rgbcolor (k,v... ) for (k,v) in canvas. colors]. .. , command .(canvas. contents)... ], " \n " ))
181+ generate_standalone (canvas:: Canvas ) = generate_standalone (canvas. libs, canvas . header, canvas. props, join ([[generate_rgbcolor (k,v... ) for (k,v) in canvas. colors]. .. , command .(canvas. contents)... ], " \n " ))
149182
150183function Base. write (io:: IO , canvas:: Canvas )
151184 write (io, generate_standalone (canvas))
152185end
153186
154- end
187+ function vizgraph! (c:: Canvas , locations:: AbstractVector , edges; fills= fill (" black" , length (locations)),
188+ texts= fill (" " , length (locations)), ids= [autoid! () for i= 1 : length (locations)], minimum_size= " 0.4cm" ,
189+ draw= " " , line_width= " 1pt" , edgecolors= fill (" black" , length (edges)))
190+ nodes = Node[]
191+ lines = Line[]
192+ for i= 1 : length (locations)
193+ n = Node (locations[i]. .. ; fill= fills[i], minimum_size= minimum_size, draw= draw, id= ids[i], text= texts[i]) >> c
194+ push! (nodes, n)
195+ end
196+ for (k, (i, j)) in enumerate (edges)
197+ l = Line (nodes[i], nodes[j], line_width= line_width, draw= edgecolors[k]) >> c
198+ push! (lines, l)
199+ end
200+ return nodes, lines
201+ end
202+
203+ end
0 commit comments