-
Notifications
You must be signed in to change notification settings - Fork 34
Expand file tree
/
Copy pathsubplot.jl
More file actions
247 lines (224 loc) · 12.1 KB
/
subplot.jl
File metadata and controls
247 lines (224 loc) · 12.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
"""
subplot(fim=nothing; kwargs...)
Manage figure subplot configuration and selection.
Parameters
----------
- **grid** :: [Type => Str | Tuple | Array]
Specifies the number of rows and columns of subplots. Ex grid=(2,3)
- **F** | **dims** | **dimensions** | **size** | **sizes** :: [Type => Str | Tuple, NamedTuple]
Specify the dimensions of the figure.
- **A** | **autolabel** | **fixedlabel** :: [Type => Str | number]
Specify automatic tagging of each subplot. This sets the tag of the first, top-left subplot and others follow sequentially.
- $(_opt_B)
- **C** | **clearance** :: [Type => Str | number]
Reserve a space of dimension clearance between the margin and the subplot on the specified side. Settings specified under **begin** directive apply to all panels.
- $(_opt_J)
- **M** | **margin** | **margins** :: [Type => Str]
The margin space that is added around each subplot beyond the automatic space allocated for tick marks, annotations, and labels.
- $(_opt_R)
- **SC** | **SR** | **col_axes** | **row_axes** :: [Type => Str | NamedTuple]
Set subplot layout for shared axes. Set separately for rows (SR) and columns (SC).
- **T** | **title** :: [Type => Str]
While individual subplots can have titles, the entire figure may also have a overarching title.
- $(opt_V)
- $(opt_X)
- $(opt_Y)
"""
function subplot(fim::StrSymb=""; stop=false, kwargs...)
d = init_module(false, kwargs...)[1] # Also checks if the user wants ONLY the HELP mode
subplot(lowercase(string(fim)), stop == 1, d)
end
function subplot(fim::String, stop::Bool, d::Dict{Symbol, Any})
FirstModern[] = true # To know if we need to compute -R in plot. Due to a GMT6.0 BUG
has_options = (length(d) > 0)
# In case :title exists we must use and delete it to avoid double parsing
cmd = ((val = find_in_dict(d, [:T :title])[1]) !== nothing) ? " -T\"" * val * "\"" : ""
val_grid = find_in_dict(d, [:grid])[1] # Must fish this one right now because parse_B also looks for (another) :grid
cmd, = parse_BJR(d, cmd, "", false, " ")
cmd, = parse_common_opts(d, cmd, [:V_params], first=true)
cmd = parse_these_opts(cmd, d, [[:M :margin :margins], [:D :noframes]])
cmd = add_opt(d, cmd, "A", [:A :autolabel],
(Anchor=("+J", arg2str), anchor=("+j", arg2str), label="", clearance=("+c", arg2str), fill=("+g", add_opt_fill), pen=("+p", add_opt_pen), offset=("+o", arg2str), roman="_+r", Roman="_+R", vertical="_+v"))
cmd = add_opt(d, cmd, "SC", [:SC :Sc :col_axes :colaxes :sharex],
(top=("t", nothing, 1), bott=("b", nothing, 1), bottom=("b", nothing, 1), label="+l", grid=("+w", add_opt_pen)))
cmd = add_opt(d, cmd, "SR", [:SR :Sr :row_axes :rowaxes :sharey],
(left=("l", nothing, 1), right=("r", nothing, 1), label="+l", parallel="_+p", row_title="_+t", top_row_title="_+tc", grid=("+w", add_opt_pen)))
opt_C = add_opt(d, "", "", [:C :clearance],
(left=(" -Cw", arg2str), right=(" -Ce", arg2str), bott=(" -Cs", arg2str), bottom=(" -Cs", arg2str), top=(" -Cn", arg2str)))
(opt_C != "" && !startswith(opt_C, " -C")) && (opt_C = " -C" * opt_C) # When C="str" was passed.
cmd = add_opt(d, cmd, "Fs", [:Fs :panels_size :panel_size :panel_sizes])
cmd = add_opt(d, cmd, "Ff", [:Ff :splot_size])
if ((val = find_in_dict(d, [:F :dims :dimensions :size :sizes], false)[1]) !== nothing || SHOW_KWARGS[])
if (isa(val, NamedTuple) && haskey(nt2dict(val), :width)) # Preferred way
cmd *= " -F" * helper_sub_F(val) # VAL = (width=x, height=x, fwidth=(...), fheight=(...))
delete!(d, [:F, :dims, :dimensions, :size, :sizes])
else
cmd = add_opt(d, cmd, "F", [:F :dims :dimensions :size :sizes],
(panels=("-s", helper_sub_F, 1), figsize=("_f", helper_sub_F, 1), size=("", helper_sub_F, 2), sizes=("", helper_sub_F, 2), frac=("+f", helper_sub_F), fractions=("+f", helper_sub_F), clearance=("+c", arg2str), outine=("+p", add_opt_pen), fill=("+g", add_opt_fill), divlines=("+w", add_opt_pen)))
end
end
do_set = false; do_show = false
if (fim !== "")
if (fim == "end" || fim == "stop") stop = true
elseif (fim == "show") stop, do_show = true, true
elseif (fim == "set") do_set = true
end
elseif (haskey(d, :show)) # Let this form work too
do_show = (d[:show] != 0)
stop = true
else
if (!stop && !has_options) stop = true end # To account for the subplot() call case
end
# ------------------------------ End parsing inputs --------------------------------
if (!stop && !do_set)
(val_grid === nothing) && error("SUBPLOT: 'grid' keyword is mandatory")
cmd = arg2str(val_grid, 'x') * " " * cmd * opt_C # Also add the eventual global -C clearance option
cmd = guess_panels_size(cmd, val_grid) # For limitted grid dims, guess panel sizes if not provided
(dbg_print_cmd(d, cmd) !== nothing) && return cmd # Vd=2 cause this return
isJupyter[] = isdefined(Main, :IJulia) # show fig relies on this and at time of "gmt begin" this must be known.
!isJupyter[] && (isJupyter[] = (isdefined(Main, :VSCodeServer) && get(ENV, "DISPLAY_IN_VSC", "") != ""))
if (!IamModern[]) # If we are not in modern mode, issue a gmt("begin") first
# Default name (GMTplot.ps) is set in gmt_main()
fname = ((val_ = find_in_dict(d, [:fmt])[1]) !== nothing) ? string("GMTplot ", val_) : ""
if ((val_ = find_in_dict(d, [:figname :name :savefig])[1]) !== nothing)
fname = get_format(string(val_), nothing, d) # Get the fig name and format.
end
gmt("begin " * fname)
IamModernBySubplot[] = true # We need this to know if subplot(:end) should call gmtend() or not
end
try
gmt("subplot begin " * cmd);
# This is the most strange. For some reason the annot font of first panel is slightly different from the
# others WHEN the first plot does not have the -c option (iplicitly -c0). If it has, then they are equal
# but equal to others than first. Oddly, running the gmtset, which should be doing nothing because the
# map type is already 'fancy' by default, seems to solve this issue.
gmt("gmtset MAP_FRAME_TYPE fancy")
catch; resetGMT()
end
remove_map_origin() # Remove MAP_ORIGIN_X and MAP_ORIGIN_Y from the gmt.conf.0.subplot file (see comments in function)
IamSubplot[], IamModern[] = true, true
elseif (do_set)
(!IamSubplot[]) && error("Cannot call subplot(set, ...) before setting dimensions")
_, pane = parse_c(d, cmd)
cmd = pane * cmd # Here we don't want the "-c" part
cmd = add_opt(d, cmd, "A", [:fixedlabel]) * opt_C # Also add the eventual this panel -C clearance option
if (dbg_print_cmd(d, cmd) !== nothing) return cmd end # Vd=2 cause this return
gmt("subplot set " * cmd)
else
if (IamModernBySubplot[])
IamModernBySubplot[] = false
show = (do_show) ? " show" : ""
helper_showfig4modern(show)
else
gmt("subplot end"); IamSubplot[] = false
end
end
CTRL.pocket_d[1] = d # Store d that may be not empty with members to use in other modules (e.g., cornerplot)
return nothing
end
# --------------------------------------------------------------------------
function guess_panels_size(cmd, opt)
(contains(cmd, " -F")) && return cmd # Ok, panel sizes provided
if (isa(opt, String))
((ind = findfirst('x', opt)) === nothing) && error("'grid' option does not have the form NxM (misses the 'x'")
n_rows, n_cols = parse(Int, opt[1:ind-1]), parse(Int, opt[ind+1:end])
else
n_rows, n_cols = opt[1], opt[2]
end
F = ""
if (n_cols == 3) F = " -Fs6/6"; M = " -M0.2c/0.2c"
elseif (n_cols > 3 && n_cols == n_rows) F = " -Fs$(20/n_cols)"; M = " -M0.15c/0.15c"
elseif (n_rows == 2 && n_cols == 2) F = " -Fs8/8"; M = " -M0.3c/0.2c"
elseif (n_rows == 1)
if (n_cols == 2) F = " -Fs8/8"; M = " -M0.3c"
elseif (n_cols == 3) F = " -Fs6/6"; M = " -M0.3c"
elseif (n_cols == 4) F = " -Fs5/5"; M = " -M0.2c"
end
elseif (n_cols == 1)
if (n_rows == 2) F = " -Fs12/8"; M = " -M0.3c"
elseif (n_rows == 3) F = " -Fs12/6"; M = " -M0.3c"
elseif (n_rows == 4) F = " -Fs12/5"; M = " -M0.2c"
elseif (5 <= n_rows <= 7) F = " -Fs12/4"; M = " -M0.15c"
end
end
(F == "") && error("No panels/fig size provided and the grid panels dimension is not within the subset that we guess for")
!contains(cmd, " -M") && (cmd *= M) # No margins provided. Use our poor guess.
cmd *= F
end
# --------------------------------------------------------------------------
function helper_sub_F(arg, dumb=nothing)::String
# dims=(1,2)
# dims=(panels=(1,2), frac=((2,3),(3,4,5)))
# dims=(width=xx, height=yy, fwidth=(), fheight=(), fill=:red, outline=(3,:red))
out::String = ""
if (isa(arg, String))
out = arg2str(arg)
elseif (isa(arg, NamedTuple) || isa(arg, Dict) || isa(arg, Tuple{Tuple, Tuple}) || isa(arg, Tuple{Tuple, Number}))
d = mura_arg(arg)
if ((val = find_in_dict(d, [:panels])[1]) !== nothing)
if (isa(val, Tuple{Tuple, Tuple})) # ex: dims=(panels=((2,4),(2.5,5,1.25)),)
out *= arg2str(val[1], ',') * '/' * arg2str(val[2], ',')
else
out = arg2str(val)
end
end
if ((val = find_in_dict(d, [:frac :fractions])[1]) !== nothing) # ex: dims=(frac=((2,3),(3,4,5)))
!isa(val, Tuple{Tuple, Tuple}) && error("'frac' option must be a tuple(tuple, tuple)")
out *= arg2str(val[1], ',') * '/' * arg2str(val[2], ',')
end
if (haskey(d, :width))
out *= string(d[:width], '/')
out = (!haskey(d, :height)) ? string(out, d[:width]) : string(out, d[:height])
end
if (haskey(d, :fwidth))
out *= "+f" * arg2str(d[:fwidth], ',')
(!haskey(d, :fheight)) && error("SUBPLOT: when using 'fwidth' must also set 'fheight'")
out *= '/' * arg2str(d[:fheight], ',')
end
if ((val = find_in_dict(d, [:fill], false)[1]) !== nothing) out *= "+g" * add_opt_fill(val) end
if ((val = find_in_dict(d, [:clearance], false)[1]) !== nothing) out *= "+c" * arg2str(val) end
if (haskey(d, :outline)) out *= "+p" * add_opt_pen(d, [:outline]) end
if (haskey(d, :divlines)) out *= "+w" * add_opt_pen(d, [:divlines]) end
elseif (isa(arg, Tuple)) # Hopefully only for the "dims=(panels=(xsize, ysize),)"
out = arg2str(arg)
end
(out == "") && error("SUBPLOT: garbage in DIMS option")
return out
end
# --------------------------------------------------------------------------
function mura_arg(arg)::Dict
# Barrier function to contain a possible type instability
if (isa(arg, Tuple{Tuple, Real})) arg = (arg[1], (arg[2],)) end # This looks terribly type instable
# Need first case because for example dims=(panels=((2,4),(2.5,5,1.25)),) shows up here only as
# arg = ((2, 4), (2.5, 5, 1.25)) because this function was called from within add_opt()
if (isa(arg, Tuple{Tuple, Tuple})) d = Dict{Symbol,Any}(:panels => arg)
else d = (isa(arg, NamedTuple)) ? nt2dict(arg) : arg
end
d
end
# --------------------------------------------------------------------------
function remove_map_origin()
# Remove MAP_ORIGIN_X and MAP_ORIGIN_Y from the gmt.conf.0.subplot file
# The problem: GMT CLI when it creates the gmt.conf.0.subplot file, does not set MAP_ORIGIN_X and MAP_ORIGIN_Y
# but because we fiddle a lot with themes and make the classic mode behave like modern, we end up setting them
# and that has a drastic effect of burning permanently in the GMT_keyword_updated[] internal variable, AND THERE
# IS NO WAY TO UNSET things inside GMT_keyword_updated[] from externals. When gmt_begin() is called, the
# gmt.conf.0.subplot file is created and the MAP_ORIGIN_X and MAP_ORIGIN_Y are set. But this SCREWS the location
# of the colorbars when we have subplots because they all overlap each other. The only solution found is to remove
# the MAP_ORIGIN_X and MAP_ORIGIN_Y from the gmt.conf.0.subplot file.
# WARNING: If we ever implement the 'figure' case, this function will need to be modified to account for the
# figure number. As it is, the "0" in gmt.conf.0.subplot stands for figure 0.
API = unsafe_load(convert(Ptr{GMTAPI_CTRL}, G_API[]))
session_dir = unsafe_string(API.gwf_dir)
fname = session_dir * filesep * "gmt.conf.0.subplot"
!isfile(fname) && return
lines = readlines(fname)
filtered = filter(l -> !startswith(l, "MAP_ORIGIN_"), lines)
length(filtered) == length(lines) && return nothing # Nothing to remove
open(fname, "w") do io
for l in filtered
println(io, l)
end
end
return nothing
end