1 | | % (c) 2009-2025 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, |
2 | | % Heinrich Heine Universitaet Duesseldorf |
3 | | % This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) |
4 | | |
5 | | :- module(dot_graph_generator, [gen_dot_graph/3, gen_dot_graph/5, gen_dot_graph/6, |
6 | | dot_no_same_rank/1, dot_no_subgraph/3, |
7 | | print_graph_header/1,print_graph_footer/0, |
8 | | use_new_dot_attr_pred/7, % annotation for new-style dot predicate |
9 | | get_dot_cluster_name/2, |
10 | | translate_bvalue_to_colour/2, try_translate_bvalue_to_colour/2, |
11 | | translate_int_col/2]). |
12 | | |
13 | | :- meta_predicate(gen_dot_graph(*,6,5)). |
14 | | :- meta_predicate(gen_dot_graph(*,6,5,1,3)). |
15 | | :- meta_predicate(gen_dot_graph(*,*,6,5,1,3)). |
16 | | :- meta_predicate(node_id(6,*)). |
17 | | :- meta_predicate(node_predicate_call(6,*,*,*)). |
18 | | :- meta_predicate(trans_predicate_call(:,*,*,*,*,*)). % adds 3 or 5 args |
19 | | :- meta_predicate(merged_call5(5,*,*,*,*,*)). % adds 5 args |
20 | | :- meta_predicate(same_rank_call(1,*)). |
21 | | :- meta_predicate(subgraph_call(3,*,*,*,*,*)). |
22 | | :- meta_predicate(fgen_dot_graph(*,*,6,5,1,3)). |
23 | | :- meta_predicate(print_nodes(*,6,3)). |
24 | | :- meta_predicate(print_nodes2(*,*,6)). |
25 | | :- meta_predicate(print_transitions(*,*,*,5)). |
26 | | |
27 | | :- use_module(probsrc(module_information)). |
28 | | :- module_info(group,dot). |
29 | | :- module_info(description,'This a few tools for generating dot graphs.'). |
30 | | |
31 | | :- use_module(library(lists)). |
32 | | :- use_module(probsrc(preferences)). |
33 | | |
34 | | :- use_module(probsrc(debug)). |
35 | | :- use_module(probsrc(self_check)). |
36 | | :- use_module(probsrc(error_manager),[add_internal_error/2, add_message/3]). |
37 | | |
38 | | /* --------------------------------------------------- */ |
39 | | /* MAIN ENTRY POINTS FOR TCL */ |
40 | | /* --------------------------------------------------- */ |
41 | | |
42 | | gen_dot_graph(F,NodePredicate,TransPredicate) :- |
43 | | gen_dot_graph(F,NodePredicate,TransPredicate,dot_no_same_rank,dot_no_subgraph). |
44 | | gen_dot_graph(F,NodePredicate,TransPredicate,SameRankPred,SubGraphPred) :- |
45 | | gen_dot_graph(F,[],NodePredicate,TransPredicate,SameRankPred,SubGraphPred). |
46 | | |
47 | | gen_dot_graph(F,GraphAttrs,NodePredicate,TransPredicate,SameRankPred,SubGraphPred) :- |
48 | | formatsilent('% Generating Dot File: ~w~n',[F]), |
49 | | reset_ids, |
50 | | (get_preference(dot_horizontal_layout,true), |
51 | | \+ has_attribute(rankdir,_,GraphAttrs) -> Opts0 = [rankdir/'LR'|GraphAttrs] ; Opts0 = GraphAttrs), |
52 | | (select(no_page_size,Opts0,Opts) -> true |
53 | | ; get_preference(dot_with_page_size,true), |
54 | | \+ has_attribute(page,_,GraphAttrs), |
55 | | \+ has_attribute(size,_,GraphAttrs) |
56 | | -> Opts = [with_page_size|Opts0] |
57 | | ; Opts = Opts0), |
58 | | open(F,write,FStream,[encoding(utf8)]), |
59 | | (fgen_dot_graph(FStream,Opts,NodePredicate,TransPredicate,SameRankPred,SubGraphPred) -> true ; true), |
60 | | close(FStream), |
61 | | formatsilent('% Done~n',[]). |
62 | | |
63 | | |
64 | | % Placeholder predicates for when caller doesn't need SameRankPred and/or SubGraphPred |
65 | | dot_no_same_rank(_) :- fail. |
66 | | dot_no_subgraph(_, _, _) :- fail. |
67 | | |
68 | | |
69 | | node_id(P,TNodeID) :- |
70 | ? | call(P,NodeID,_,_,_,_,_), |
71 | | translate_id(NodeID,TNodeID). |
72 | | |
73 | | node_predicate_call(Pred,TNodeID,SubGraph,Attributes) :- |
74 | | get_new_style_node_pred(Pred,NodePredicate),!, |
75 | ? | call(NodePredicate,NodeID,SubGraph,Attributes), % new style has only 3 arguments with flexible attribute list |
76 | | translate_id(NodeID,TNodeID). |
77 | | node_predicate_call(NodePredicate,TNodeID,SubGraph,[shape/Shape|Attrs]) :- |
78 | ? | call(NodePredicate,NodeID,SubGraph,NodeDesc,Shape,Style,Color), % has 6 arguments |
79 | | translate_id(NodeID,TNodeID), |
80 | | (Style = none -> Attrs = Attrs2 ; Attrs = [style/Style|Attrs2]), |
81 | | (Color = none -> Attrs2 = Attrs3 ; Attrs2 = [color/Color|Attrs3]), |
82 | | %preference(dot_print_node_ids,true) -> we are not interested in internal ids of this module, are we?? |
83 | | Attrs3 = [label/NodeDesc]. |
84 | | |
85 | | get_new_style_node_pred(use_new_dot_attr_pred(NodePredicate),NodePredicate). |
86 | | get_new_style_node_pred(_:use_new_dot_attr_pred(NodePredicate),NodePredicate). |
87 | | |
88 | | % convert a new style Node Predicate which returns a flexible list of attributes |
89 | | % dummy call: real conversion is done in node_predicate_call |
90 | | use_new_dot_attr_pred(NodePredicate,NodeID,SubGraph,none,none,none,none) :- |
91 | ? | call(NodePredicate,NodeID,SubGraph,_Attributes). |
92 | | |
93 | | |
94 | | % ------- |
95 | | |
96 | | trans_predicate_call(Pred,_Opts,TNodeID,TSuccNodeID,Style,Attrs2) :- |
97 | | get_new_style_node_pred(Pred,NodePredicate),!, |
98 | ? | call(NodePredicate,NodeID,SuccNodeID,Attrs), % new style has only 3 arguments with flexible attribute list |
99 | | % TODO: allow to merge_transitions with same Attrs except label |
100 | ? | (select_attribute(style,Style,Attrs,Attrs2) -> true ; Style=solid,Attrs2=Attrs), |
101 | | translate_id_for_transition(NodeID,TNodeID), |
102 | | translate_id_for_transition(SuccNodeID,TSuccNodeID). |
103 | | trans_predicate_call(P,Opts,TNodeID,TSuccNodeID,Style,[color/Color,label/Label]) :- |
104 | | (has_attribute(merge_transitions,true,Opts) |
105 | ? | -> merged_call5(P,NodeID,Label,SuccNodeID,Color,Style) |
106 | ? | ; call(P,NodeID,Label,SuccNodeID,Color,Style) |
107 | | ), |
108 | | translate_id_for_transition(NodeID,TNodeID), |
109 | | translate_id_for_transition(SuccNodeID,TSuccNodeID). |
110 | | |
111 | | :- use_module(probsrc(tools_strings),[ajoin_with_sep/3]). |
112 | | :- use_module(probsrc(tools_meta),[setof4/4]). |
113 | | % merge all labels with the same origin, destination, color and style |
114 | | merged_call5(P,NodeID,Label,SuccNodeID,Color,Style) :- |
115 | | % we use setof4 rather than setof, because test 1033 fails on Intel platforms due to different order of sols |
116 | ? | setof4(Lbl,[NodeID,SuccNodeID,Color,Style],call(P,NodeID,Lbl,SuccNodeID,Color,Style), Labels), |
117 | | ajoin_with_sep(Labels,',',Label). |
118 | | |
119 | | |
120 | | same_rank_call(_:dot_no_same_rank,_) :- !,fail. |
121 | | same_rank_call(P,TNodes) :- |
122 | | call(P,Nodes), |
123 | | Nodes \= [], % empty list provides no information |
124 | | maplist(translate_id,Nodes,TNodes). |
125 | | /* should succeed once for every set of NodeIDs which should be of same rank */ |
126 | | |
127 | | % should succeed once for every subgraph and generate a subgraphID which is passed to the node predicate |
128 | | subgraph_call(_:dot_no_subgraph,_,_,_,_,_) :- !, fail. |
129 | | subgraph_call(P,SubGraphID,Style,Color,Label,OtherAttrs) :- |
130 | | call(P,ID,Style,Color), |
131 | | extract_new_attrs(ID,SubGraphID,Attrs), |
132 | | (select_attribute(label,Label,Attrs,OtherAttrs) -> true |
133 | | ; OtherAttrs=Attrs, Label=SubGraphID). |
134 | | /* Notes: SubGraphID: should be none if not in a subgraph; Style and Color can be none */ |
135 | | |
136 | | extract_new_attrs(sub_graph_with_attributes(ID,Attrs),SubGraphID,OtherAttrs) :- |
137 | | !, % new style subgraph predicate: additional info |
138 | | SubGraphID=ID, OtherAttrs=Attrs. |
139 | | extract_new_attrs(ID,ID,[]). % old style subgraph predicate: ID is just an atom |
140 | | |
141 | | :- dynamic stored_id/2, additional_id/2. |
142 | | :- dynamic next_id/1. |
143 | | next_id(0). |
144 | | |
145 | | reset_ids :- |
146 | | retractall(stored_id(_,_)), retractall(next_id(_)), |
147 | | retractall(additional_id(_,_)), |
148 | | assertz(next_id(0)). |
149 | | |
150 | | % translate ids to atoms; ensure that dot can deal with them |
151 | | translate_id(ID,TransID) :- |
152 | | (number(ID) -> TransID=ID |
153 | | ; var(ID) -> add_internal_error('Illegal variable identifier for dot:',translate_id(ID,TransID)), |
154 | | TransID is -1 |
155 | | ; stored_id(ID,SID) -> TransID=SID |
156 | | ; gen_new_node_id(NewAtom), |
157 | | assertz(stored_id(ID,NewAtom)), |
158 | | TransID = NewAtom |
159 | | ). |
160 | | |
161 | | gen_new_node_id(NewAtom) :- |
162 | | retract(next_id(NextId)), N1 is NextId+1, |
163 | | assertz(next_id(N1)), |
164 | | number_codes(NextId,NC), |
165 | | append("dot_node_",NC,AC), atom_codes(NewAtom,AC). |
166 | | |
167 | | % called in transition creation: we can no longer add new nodes: |
168 | | translate_id_for_transition(ID,TransID) :- |
169 | | (number(ID) -> TransID=ID |
170 | | ; var(ID) -> add_internal_error('Illegal variable identifier for dot:',translate_id(ID,TransID)), |
171 | | TransID is -1 |
172 | | ; stored_id(ID,SID) -> TransID=SID |
173 | | ; add_message(dot_graph_generator,'Unknown node: ',ID), |
174 | | gen_new_node_id(NewAtom), |
175 | | assertz(stored_id(ID,NewAtom)), |
176 | | assertz(additional_id(NewAtom,ID)), |
177 | | TransID=NewAtom |
178 | | ). |
179 | | |
180 | | print_additional_nodes(FStream) :- |
181 | | additional_id(DotAtom,OriginalID), |
182 | | format(FStream,' ~w [label="~w"];~n',[DotAtom,OriginalID]), |
183 | | fail. |
184 | | print_additional_nodes(_). |
185 | | |
186 | | /* ---------------------------------------------------------------------- */ |
187 | | |
188 | | |
189 | | fgen_dot_graph(FStream,GraphAttrs,NodePredicate,TransPredicate,SameRankPred,SubGraphPred) :- |
190 | | print_graph_header(FStream,prob_graph,GraphAttrs), |
191 | ? | (node_id(NodePredicate,_) -> true |
192 | | ; (format(user_error,"No nodes in gen_dot_graph: ~w, ~w, ~w, ~w.~n~n", |
193 | | [NodePredicate,TransPredicate,SameRankPred,SubGraphPred]),fail)), |
194 | | print_nodes(FStream,NodePredicate,SubGraphPred), |
195 | | fail. |
196 | | fgen_dot_graph(FStream,_GraphAttrs,_NodePredicate,_TransPredicate,SameRankPred,_SubGraphPred) :- |
197 | | same_rank_call(SameRankPred,Nodes), |
198 | | print_same_ranks(FStream,Nodes), |
199 | | fail. |
200 | | fgen_dot_graph(FStream,GraphAttrs,_NodePredicate,TransPredicate,_SameRankPred,_SubGraphPred) :- |
201 | | print_transitions(FStream,_NodeID,GraphAttrs,TransPredicate), |
202 | | fail. |
203 | | fgen_dot_graph(FStream,_,_,_,_,_) :- |
204 | | print_additional_nodes(FStream), |
205 | | print_graph_footer(FStream). |
206 | | |
207 | | /* ---------------------------------------------------------------------- */ |
208 | | |
209 | | print_graph_header(Type) :- print_graph_header(user_output,Type,[with_page_size]). |
210 | | print_graph_header(FStream,Type,Opts) :- |
211 | ? | (select(with_page_size,Opts,Opts1) |
212 | | -> Opts2 = [page/'8.5, 11',ratio/fill,size/'7.5,10'|Opts1] |
213 | | ; Opts2=Opts), |
214 | | (select(directed/'FALSE',Opts2,Opts3) -> GRAPH = 'graph' |
215 | | ; Opts3=Opts2, GRAPH = 'digraph' % directed graph |
216 | | ), |
217 | | (select(strict/'TRUE',Opts3,Opts4) -> STRICT = 'strict ' |
218 | | ; Opts4=Opts3, STRICT = '' |
219 | | ), |
220 | | format(FStream,'~w~w ~w {~n graph [',[STRICT,GRAPH,Type]), |
221 | | % graph [AttrList] sets default attributes |
222 | | print_dot_attrs(Opts4,FStream), |
223 | | format(FStream,'];~n',[]). |
224 | | % print('graph [orientation=landscape, page="8.5, 11",ratio=fill,size="7.5,10"];'),nl, |
225 | | |
226 | | print_graph_footer :- print_graph_footer(user_output). |
227 | | print_graph_footer(FStream) :- format(FStream,'}~n',[]). |
228 | | |
229 | | is_undirected_graph(GraphAttrs) :- member(directed/FALSE,GraphAttrs),!, FALSE='FALSE'. |
230 | | |
231 | | /* ---------------------------------------------------------------------- */ |
232 | | |
233 | | :- use_module(probsrc(tools_strings),[ajoin/2]). |
234 | | % get the dot name of a logical cluster, also relevant or lhead/ltail edge attributes |
235 | | get_dot_cluster_name(SubGraphID,CN) :- ajoin(['cluster_',SubGraphID],CN). |
236 | | |
237 | | % write dot nodes to a stream |
238 | | print_nodes(FStream,NodePredicate,SubGraphPred) :- |
239 | | subgraph_call(SubGraphPred,SubGraphID,Style,Color,Label,OtherAttrs), |
240 | | get_dot_cluster_name(SubGraphID,CN), |
241 | | format(FStream,' subgraph "~w" {~n',[CN]), |
242 | | (Style = none -> true ; format(FStream,' style="~w";~n',[Style])), |
243 | | (Color = none -> true ; format(FStream,' color="~w";~n',[Color])), |
244 | | simple_dot_string_escape(Label,ESID), % simple escape of unescaped ", avoid disturbing records |
245 | | format(FStream,' label="~w";~n',[ESID]), |
246 | | write(FStream, ' '), |
247 | | print_dot_attrs1(OtherAttrs,';\n ',';\n ',FStream), |
248 | | print_nodes2(FStream,SubGraphID,NodePredicate), |
249 | | write(FStream,'}'),nl(FStream), |
250 | | fail. |
251 | | print_nodes(FStream,NodePredicate,_SubGraphPred) :- |
252 | | print_nodes2(FStream,none,NodePredicate), |
253 | | nl(FStream). |
254 | | |
255 | | % If SubGraph can either be none or an ID generated by the subgraph_call |
256 | | print_nodes2(FStream,SubGraph,NodePredicate) :- |
257 | ? | node_predicate_call(NodePredicate,NodeID,SubGraph,Attrs), |
258 | | print_node(FStream,NodeID,Attrs), |
259 | | fail. |
260 | | print_nodes2(FStream,_Subgraph,_) :- nl(FStream). |
261 | | |
262 | | % print an individual DOT node with attributes as list |
263 | | print_node(FStream,NodeID,Attributes) :- |
264 | | format(FStream,' ~w [',[NodeID]), |
265 | | get_preference(dot_node_font_size,FSize), |
266 | | opt_add_attribute(fontsize,FSize,Attributes,Attrs2), |
267 | | print_dot_attrs(Attrs2,FStream), |
268 | | format(FStream,'];~n',[]). |
269 | | |
270 | | :- use_module(probsrc(tools),[simple_dot_string_escape/2]). |
271 | | % print attributes of a node or edge |
272 | | print_dot_attrs(List,FStream) :- exclude(is_meta_attribute,List,List1), |
273 | | print_dot_attrs1(List1,', ','',FStream). |
274 | | print_dot_attrs1([],_,_,_FStream) :- !. |
275 | | print_dot_attrs1([H|T],Sep,Term,FStream) :- get_attribute(H,Attr,Val),!, |
276 | | (compound(Val) -> EVal=Val % we hope no " inside the compound term |
277 | | ; simple_dot_string_escape(Val,EVal) % simple escape of unescaped " |
278 | | ), |
279 | | (T=[] |
280 | | -> format(FStream,'~w="~w"~w',[Attr,EVal,Term]) |
281 | | ; no_need_to_quote(Val) |
282 | | -> format(FStream,'~w=~w~w',[Attr,EVal,Sep]), |
283 | | print_dot_attrs1(T,Sep,Term,FStream) |
284 | | ; format(FStream,'~w="~w"~w',[Attr,EVal,Sep]), |
285 | | print_dot_attrs1(T,Sep,Term,FStream)). |
286 | | print_dot_attrs1(Err,Separator,Terminator,_) :- |
287 | | add_internal_error('Could not print attr:',print_dot_attrs1(Err,Separator,Terminator)). |
288 | | |
289 | | get_attribute(Attr/Val,Attr,Val). |
290 | | get_attribute(Attr=Val,Attr,Val). |
291 | | |
292 | | |
293 | | no_need_to_quote(Val) :- number(Val). |
294 | | no_need_to_quote(record). % mainly for test 1033 |
295 | | |
296 | ? | has_attribute(Name,Val,Attrs) :- member(H,Attrs), get_attribute(H,Name,Val). |
297 | ? | select_attribute(Name,Val,Attrs,Rest) :- select(H,Attrs,Rest), get_attribute(H,Name,Val). |
298 | | |
299 | | is_meta_attribute(H) :- get_attribute(H,Name,_), |
300 | | meta_argument(Name). % attributed not meant for dot, just for controlling the dot graph generator |
301 | | |
302 | | meta_argument(deals_with_pref). |
303 | | meta_argument(merge_transitions). |
304 | | |
305 | | opt_add_attribute(Name,_,Attrs,NewAttrs) :- has_attribute(Name,_,Attrs),!, |
306 | | NewAttrs = Attrs. |
307 | | opt_add_attribute(Name,Val,Attrs,[Name/Val|Attrs]). |
308 | | |
309 | | % -------------------------- |
310 | | |
311 | | |
312 | | print_transitions(FStream,NodeID,GraphAttrs,TransPredicate) :- |
313 | | (is_undirected_graph(GraphAttrs) -> DotArrow = '--' ; DotArrow = '->'), |
314 | ? | trans_predicate_call(TransPredicate,GraphAttrs,NodeID,SuccID,Style,Attributes), |
315 | | |
316 | | (NodeID=root -> preference(dot_print_root,true) ; true), |
317 | | |
318 | | (NodeID \= SuccID -> true |
319 | | ; preference(dot_print_self_loops,true) -> true |
320 | ? | ; has_attribute(deals_with_pref,dot_print_self_loops,GraphAttrs) -> true % the command itself processes the pref. |
321 | | ), |
322 | | |
323 | | format(FStream,' ~w ~w ~w [',[NodeID,DotArrow,SuccID]), |
324 | | |
325 | | (get_preference(dot_edge_penwidth,PenSize),PenSize \= 1 |
326 | | -> opt_add_attribute(penwidth,PenSize,Attributes,Attrs1) |
327 | | ; Attrs1=Attributes |
328 | | ), |
329 | | get_preference(dot_edge_font_size,FSize), |
330 | | opt_add_attribute(fontsize,FSize,Attrs1,Attrs2), |
331 | | (preference(dot_print_arc_colors,false), |
332 | | select_attribute(color,_,Attrs2,Attrs3) |
333 | | -> true % remove color attribute |
334 | | ; Attrs3=Attrs2 |
335 | | ), |
336 | | % acceptable styles Style ::= solid, bold, dotted, dashed, invis, arrowhead(none,Style), arrowtail(none,Style) |
337 | | print_style(Style,FStream), |
338 | | |
339 | | print_dot_attrs(Attrs3,FStream), |
340 | | format(FStream,'];~n',[]), % Note: we may have a trailing comma; but dotty accepts it |
341 | | fail. |
342 | | print_transitions(FStream,_NodeID,_,_) :- nl(FStream). |
343 | | |
344 | | % style term from old-style transition predicates |
345 | | print_style(solid,_) :- !. |
346 | | print_style(arrowhead(AS,S),FStream) :- !, format(FStream,'arrowhead=~w,',[AS]), print_style(S,FStream). |
347 | | print_style(arrowtail(AS,S),FStream) :- !, format(FStream,'dir=both,arrowtail=~w,',[AS]), print_style(S,FStream). |
348 | | print_style(Style,FStream) :- format(FStream,'style="~w",',[Style]). |
349 | | |
350 | | print_same_ranks(_,[]) :- !, |
351 | | add_message(dot_graph_generator,'Empty same rank result',''). |
352 | | print_same_ranks(_,[ID]) :- !, |
353 | | add_message(dot_graph_generator,'Singleton same rank result: ',ID). |
354 | | print_same_ranks(FStream,L) :- |
355 | | write(FStream,' { rank=same; '), |
356 | | print_same_ranks2(FStream,L), |
357 | | write(FStream,' }'),nl(FStream). |
358 | | |
359 | | print_same_ranks2(_FStream,[]). |
360 | | print_same_ranks2(FStream,[H|T]) :- |
361 | | write(FStream,H), write(FStream,'; '), |
362 | | print_same_ranks2(FStream,T). |
363 | | |
364 | | |
365 | | % utilities for converting values into colours: |
366 | | |
367 | | |
368 | | |
369 | | translate_bvalue_to_colour(Val,Col) :- |
370 | | (try_translate_bvalue_to_colour(Val,C) -> Col=C; Col=lightgray). |
371 | | |
372 | | try_translate_bvalue_to_colour(int(X),Res) :- !, (translate_int_col(X,Colour) -> Res=Colour ; Res=black). |
373 | | try_translate_bvalue_to_colour(string(X),Colour) :- is_of_type(X,rgb_color),!, X=Colour. |
374 | | try_translate_bvalue_to_colour(fd(X,GS),Colour) :- !, translate_fd_col(X,GS,Colour). |
375 | | try_translate_bvalue_to_colour(pred_true,Colour) :- !, Colour=olivedrab2. |
376 | | try_translate_bvalue_to_colour(pred_false,Colour) :- !, Colour=tomato. |
377 | | try_translate_bvalue_to_colour((A,_),Colour) :- !, try_translate_bvalue_to_colour(A,Colour). |
378 | | |
379 | | % TO DO : add string conversions, rgb values , ... |
380 | | :- use_module(probsrc(b_global_sets),[is_b_global_constant_hash/3]). |
381 | | |
382 | | translate_fd_col(X,GS,Res) :- |
383 | | is_b_global_constant_hash(GS,X,Colour), |
384 | | is_of_type(Colour,rgb_color),!, |
385 | | Res=Colour. |
386 | | translate_fd_col(X,_,Res) :- number(X), translate_int_col(X,Colour),!, Res=Colour. |
387 | | translate_fd_col(_,_,lightgray). |
388 | | |
389 | | translate_int_col(-1,Colour) :- !,Colour=tomato. % special case |
390 | | translate_int_col(Int,Colour) :- |
391 | | Y is abs(Int) mod 15, translate_int_col_aux(Y,Colour). |
392 | | translate_int_col_aux(0,gray95). |
393 | | translate_int_col_aux(1,blue). |
394 | | translate_int_col_aux(2,red). |
395 | | translate_int_col_aux(3,green). |
396 | | translate_int_col_aux(4,lightgray). |
397 | | translate_int_col_aux(5,orange). |
398 | | translate_int_col_aux(6,yellow). |
399 | | translate_int_col_aux(7,brown). |
400 | | translate_int_col_aux(8,violet). |
401 | | translate_int_col_aux(9,tomato). |
402 | | translate_int_col_aux(10,darkslateblue). |
403 | | translate_int_col_aux(11,maroon2). |
404 | | translate_int_col_aux(12,olivedrab2). |
405 | | translate_int_col_aux(13,chartreuse3). |
406 | | translate_int_col_aux(14,grey20). |
407 | | % red,green,blue,yellow,orange,black,white,gray,brown,violet,darkred,tomato,darkblue, |
408 | | % 'DarkGray',darkviolet,darkslateblue,lightblue,lightgray,maroon2,olivedrab2, |
409 | | % steelblue,chartreuse3,chartreuse4 |