:- lib(svg). % for calling this as a script
:- lib(os_lib).
:- lib(options).
:- lib(stoics_lib:io_lines/2).
svg_legend_defaults( Defs ) :-
Defs = [
adds_lines(svg_add_lines),
cache(false),
% theme(stack),
dir('.'),
postfix(leg),
placement(bottom),
place_with_space(200),
place_with_decrement(20),
place_with_space_min(100),
debug(false)
].
/** svg_legend( +FilesAndOpts )
Adds legend to svg files.
Atoms in Files and Opts are taken to be the files, and everything else is options.
See option atoms/1 in options_append/4.
If no Files are given, then svgs from current directory are used.
If one or more svg files with postfix fclr and a single svg with
postfix (os_postfix/2) exist, then those are taken as the inputs,
else all svgs are considered.
Opts
* adds_lines(Gal=svg_add_lines)
goal that will produce 1. lines to add and the 2. dimensions of legend
* debug(Dbg=false)
use _true_ to turn debug messaging on
* cache(Cache=false)
whether to cache the intermediary (distance) matrices to files
* theme(Theme=)
Theme that will be passed as 1st arg to Gal.
* dir(Dir='.')
working directory for when no files are given as inputs
* postfix(Psfx=leg)
postfix for new svg
* placement(Plc=bottom)
legend placement strategy. also implemented:
* at
see place_at_*
* on_rhs
extends the svg canvas by ??? and adds the legened at bottom right of extension (unimplemented as yet)
* bottom
Bottom right if there is space, or bottom within canvas if possible, else near last node and extends canvas
* place_at_x(X)
known to placement: at. mandatory for this placement. pos values work from top, neg from bottom
* place_at_y(y)
known to placement: at. mandatory for this placement. pos values work from left, neg from right
* place_with_space(Spc=200)
space parameter for strategies (known to bottom_right) (fixme:)
* place_with_space_min(SpcMin=100)
mimimum space required (fixme:)
* has_postfix(Psfx)
postfix that svg files must match to be processed (multiple can be given)
==
?- svg_legend( ['rea_smp-e10_fclr.svg',theme=edges(['E32636'-red_edges,'8DB600'-green_edges]) ] ).
==
@author nicos angelopoulos
@version 0.1 2016/12/10
@version 0.2 2017/8/21, added placement: at (by passing distances, more or less properly)
@version 0.3 2018/2/27, major restructuring disentangle from gbn_svg_legend.pl
@see gbn_svg_legend/1
@tbd provide some generic legend generator from say clr-text paired inputs
*/
svg_legend( Args ) :-
% spy( options_def_append ),
options_append( svg_legend, Args, Opts, [process(debug),atoms(Files)] ),
( Files = [] ->
svg_legend_dir( Opts )
;
maplist( svg_legend_file(Opts), Files )
).
svg_legend_dir( Opts ) :-
options( dir(Dir), Opts ),
working_directory( Old, Dir ),
os_sel( os_files, ext(svg), Svgs ),
findall( SvgsL, (
member(has_postfix(Psfx),Opts),
include(os_postfix(Psfx),Svgs,SvgsL)
),
ToLegNest ),
flatten( ToLegNest, ToLegPrv ),
( ToLegPrv == [] ->
ToLeg = Svgs
;
ToLeg = ToLegPrv
),
% include( svg_legend_file(Opts), ToLeg, _Successes ),
maplist( svg_legend_file(Opts), ToLeg ),
working_directory( _, Old ).
/** svg_legend_file( +Opts, -File )
Create legend svg file for File according to options Opts.
@see svg_legend/1
*/
svg_legend_file( Opts, File ) :-
debug( svg_legend, 'Svg input file: ~p', File ),
options( placement(Plc), Opts ),
svg_legend_placement( Plc, File, Xi, Yi, Opts ),
svg_legend_place( Opts, File, Xi, Yi ).
/** svg_legend_placement( +Method, +File, -Xi, -Yi, +Opts )
Locate the Xi and Yi for legend.
The current logic of the caller of this, is now flawed. As legends can be of variable height,
(and to some extend width, something that is currently ignored), we cannot really pinpoint
The work around for the most modern method (bottom) is to call the placer and get the resulting height
(height rather than depth as bottom stacks them upwards).
@author nicos angelopoulos
@version 0.2 2018/02/21
*/
svg_legend_placement( bottom, File, Xi, -1, Opts ) :-
svg( File, Svg ),
svg_size( Svg, W, H ), % probably move to parent
svg_tag_coords( Svg, text, true, Coords ),
debug( svg_legend, 'Text coordinates: ~w', [Coords] ),
options( theme(Theme), Opts ),
% get the dimensions (height- can extend to width, in future) of the legend
options( adds_lines(GalTerm), Opts ),
( GalTerm = ModGal:Gal -> true; Gal = GalTerm, ModGal = svg ),
Goal =.. [Gal,Theme,0,0,File,_Add,Xb,Yb],
call( ModGal:Goal ),
% svg_add_lines( Theme, 0, 0, false, File, _Add, Xb, Yb ), % fixme: push Add upwards?
svg_legend_abs_x_coord( Yb, H, AbsYb ),
debug( svg_legend, 'Called: ~w', [svg_legend_abs_x_coord(Yb,H,AbsYb)] ),
include( svg_legend_y_chop(AbsYb), Coords, BotCoords ),
debug( svg_legend, 'Bottom coordinates: ~w', [BotCoords] ),
maplist( arg(1), BotCoords, BotXCoords ),
append( BotXCoords, [W], BotXsb ),
sort( 0, @>, BotXsb, BotXs ),
% Gap is 200,
Gap is Xb + 20,
debug( svg_legend, 'Augmented bottom Xs: ~w', [BotXs] ),
svg_legend_placement_bottom_locate_gap( BotXs, Gap, Xi ),
debug( svg_legend, 'bottom placement selected Xi:~f', Xi ).
% svg_legend_placement( at, _File, _Mtx, _W, _H, _Y, _X, Xi, Yi, Opts ) :-
svg_legend_placement( at, _File, Xi, Yi, Opts ) :-
options( place_at_x(Xi), Opts ),
options( place_at_y(Yi), Opts ).
svg_legend_place( Opts, File, XiPrv, NegYiPrv ) :-
Xi is integer(XiPrv),
NegYi is integer(NegYiPrv),
debug( svg_legend, 'Placing at: ~w,~w', [Xi,NegYi] ),
options( postfix(Psfx), Opts ),
os_postfix( Psfx, File, Lile ),
io_lines( File, Lines ),
once( append(Unchanged,[Penu,Last],Lines) ),
options( theme(Theme), Opts ),
options( adds_lines(GalTerm), Opts ),
( GalTerm = ModGal:Gal -> true; Gal = GalTerm, ModGal = svg ),
Goal =.. [Gal,Theme,Xi,NegYi,File,AddLines,Xb,_Yb],
call( ModGal:Goal ),
% svg_add_lines( Theme, Xi, NegYi, UpW, File, AddLines, Xb, _ ),
append( AddLines, [Penu,Last], NewTail ),
append( Unchanged, NewTail, New ),
io_lines( Lile, New ),
svg_size( Lile, NewW, _NewH ),
LimW is Xi + Xb + 20,
( LimW > NewW ->
svg_change_dim( Lile, width, LimW, Lile )
;
true
),
debug( svg_legend, 'Wrote file: ~p', Lile ).
svg_legend_placement_bottom_locate_gap( [Xb,X1|_], Gap, Xt ) :-
% 1. try the right most of the second post (recall first post is notional)
% this is just a special case of 3, that doesn't share the distance... maybe it can be removed.
Xb - X1 - 40 > Gap,
!,
debug( svg_legend, 'applying rule 1', [] ),
% Xt is X1 + 60 + integer(((Xb - X1)/ 2 )).
Xt is min( integer( X1 + ((Xb - X1)/ 2) - (Gap / 2) ), 2 * X1 ).
% Xt is X1 + 60. % canvas will be fixed by caller if total is > Width
svg_legend_placement_bottom_locate_gap( BotXs, Gap, Xt ) :-
% 2. try the left most, last, post
% this could also be covered by adding X to BotXs
last( BotXs, Xn ),
Xn - 40 > Gap,
!,
debug( svg_legend, 'applying rule 2', [] ),
% Xt is ( Xn - Gap ) / 2.
Xt is 10. % place it near the margin rather than the node
svg_legend_placement_bottom_locate_gap( BotXs, Gap, Xt ) :-
% 3. travel leftwards until you find a gap
svg_legend_placement_bottom_locate_gap_slide( BotXs, Gap, Xt ),
!,
debug( svg_legend, 'applying rule 3', [] ).
svg_legend_placement_bottom_locate_gap( [_,X1|_], _Gap, Xt ) :-
% 4. use the second post regardless if it goes off edge (caller will fix the length of the canvas)
debug( svg_legend, 'applying rule 4', [] ),
Xt is 60 + X1.
svg_legend_placement_bottom_locate_gap_slide( [Xj,Xi|_Xs], Gap, Xt ) :-
Xj - Xi - 40 > Gap,
!,
Xt is Xi + (((Xj - Xi) - Gap ) / 2).
svg_legend_placement_bottom_locate_gap_slide( [_X|Xs], Gap, Xt ) :-
svg_legend_placement_bottom_locate_gap_slide( Xs, Gap, Xt ).
svg_legend_y_chop( Lim, _X-Y ) :-
Lim =< Y.
svg_legend_abs_x_coord( Rel, Edge, Abs ) :-
Rel < 0,
!,
Abs is Edge + Rel.
svg_legend_abs_x_coord( Rel, _Edge, Abs ) :-
Abs is Rel.
svg_legend_abs_y_coord( Rel, _Edge, Abs ) :-
Rel < 0,
!,
Abs is Rel.
svg_legend_abs_y_coord( Rel, Edge, Abs ) :-
Abs is Rel - Edge.
svg_add_lines( edges(ClrTxtPrs), Xi, Yi, _File, Add, MaX, MaY ) :-
svg_line_segs_as( A1, A2, A3, A4 ),
svg_line_segs_bs( B1, B2, B3 ),
svg_add_lines_edge_colors( ClrTxtPrs, A1/A2/A3/A4, B1/B2/B3, Xi, Xi, Yi, AddAtms, MaX, MaY ),
maplist( atom_codes, AddAtms, Add ).
svg_add_lines_edge_colors( [], _As, _Bs, _Xt, MaX, MaY, [], MaX, MaY ).
svg_add_lines_edge_colors( [Clr-Txt|T], As, Bs, Xe, Xi, Yi, [Ln1,Ln2|Lns], MaX, MaY ) :-
As = A1/A2/A3/A4,
Bs = B1/B2/B3,
Xt is Xe + 30,
atomic_list_concat( [A1,Xt,A2,Yi,A3,Txt,A4], '', Ln1 ),
Ya is Yi - 5,
NxX is max(Xi,200), % fixme: proportionate to the text
% Clr = '35978F'
atomic_list_concat( [B1,Clr,B2,Xe,',',Ya,B3], Ln2 ),
NxY is Yi - 20,
svg_add_lines_edge_colors( T, As, Bs, Xe, NxX, NxY, Lns, MaX, MaY ).
svg_line_segs_as( A1, A2, A3, A4 ) :-
A1 = '',
A4 = ''.
svg_line_segs_bs( B1, B2, B3 ) :-
B1 = ''.