Skip to content

Commit 38e393c

Browse files
authored
Merge pull request #1978 from GenericMappingTools/colorset
Add a colorset! function to set color is Vec Datasets.
2 parents 103733e + 852b520 commit 38e393c

7 files changed

Lines changed: 327 additions & 246 deletions

File tree

src/GMT.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ export
144144
segy, segy!, segyz, segyz!, segy2grd, stereonet, stereonet!, add_opt, isgeog, numel, scan_opt, extrema_nan, parse_RIr,
145145
close_PS_file, getsize, meshgrid, parse_B, parse_BJR, parse_I,
146146
parse_J, parse_R, ressurectGDAL, CPTaliases, isJLL, POSTMAN, PSname, mgd77magref, magref, find_in_dict, find_in_kwargs,
147-
mbimport, mbgetdata, mbsvplist, mblevitus, blendimg!, lonlat2xy, xy2lonlat, df2ds, mat2ds, mat2grid, mat2img, slicecube,
148-
cubeslice, linspace, logspace, fileparts,
147+
mbimport, mbgetdata, mbsvplist, mblevitus, blendimg!, lonlat2xy, xy2lonlat, df2ds, setcolors!, mat2ds, mat2grid, mat2img,
148+
slicecube, cubeslice, linspace, logspace, fileparts,
149149
tests, fields, flipud, fliplr, flipdim, flipdim!, grdinterpolate, pow, tic, toc, theme, tern2cart, geodetic2enu, cpt4dcw,
150150
getregion, getattribs, getattrib, getres, gd2gmt, gmt2gd, gdalread, gdalshade, gdalwrite, gadm, xyzw2cube,
151151
coastlinesproj, graticules, orbits, orbits!, plotgrid!, leepacific, worldrectangular, worldrectgrid, togglemask,

src/legend_funs.jl

Lines changed: 58 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -621,10 +621,18 @@ function _best_label_pos(D::Vector{<:GMTdataset}, labels::Vector{String}, nc::In
621621
nd = hypot(nx_d, ny_d)
622622
nx_d /= nd; ny_d /= nd
623623
half_d = min(xmax - xmin, ymax - ymin) * 0.015
624-
result[i,1] = clamp(cx_dat - nx_d * half_d, xmin, xmax)
625-
result[i,2] = clamp(cy_dat - ny_d * half_d, ymin, ymax)
626-
result[i,3] = clamp(cx_dat + nx_d * half_d, xmin, xmax)
627-
result[i,4] = clamp(cy_dat + ny_d * half_d, ymin, ymax)
624+
s_neg, s_pos = half_d, half_d
625+
if nx_d > 0 s_neg = min(s_neg, (cx_dat - xmin) / nx_d); s_pos = min(s_pos, (xmax - cx_dat) / nx_d)
626+
elseif nx_d < 0 s_neg = min(s_neg, (xmax - cx_dat) / -nx_d); s_pos = min(s_pos, (cx_dat - xmin) / -nx_d)
627+
end
628+
if ny_d > 0 s_neg = min(s_neg, (cy_dat - ymin) / ny_d); s_pos = min(s_pos, (ymax - cy_dat) / ny_d)
629+
elseif ny_d < 0 s_neg = min(s_neg, (ymax - cy_dat) / -ny_d); s_pos = min(s_pos, (cy_dat - ymin) / -ny_d)
630+
end
631+
s_neg = max(s_neg, 1e-6); s_pos = max(s_pos, 1e-6)
632+
result[i,1] = cx_dat - nx_d * s_neg
633+
result[i,2] = cy_dat - ny_d * s_neg
634+
result[i,3] = cx_dat + nx_d * s_pos
635+
result[i,4] = cy_dat + ny_d * s_pos
628636
end
629637
return result
630638
end
@@ -669,30 +677,31 @@ function _label_pos_at_vals(D::Vector{<:GMTdataset}, nc::Int, xvals::Vector{Floa
669677

670678
!found && error("Value $(target) not found on curve $i")
671679

672-
# Build short perpendicular segment in data coordinates
673-
# Use a small delta based on data range
674-
dx = data[end,1] - data[1,1]
675-
dy = data[end,2] - data[1,2]
676-
scale = max(abs(dx), abs(dy)) * 0.005
680+
# Build short perpendicular segment in data coordinates, guaranteed to stay inside the plot region.
681+
if CTRL.limits[7] != 0
682+
xmin, xmax, ymin, ymax = CTRL.limits[7:10]
683+
else
684+
xmin, xmax, ymin, ymax = getregion(D)
685+
end
686+
scale = max(xmax - xmin, ymax - ymin) * 0.005
677687
scale = max(scale, 1e-6)
678688
nx, ny = -sin(best_ang), cos(best_ang)
679-
result[i,1] = best_px - nx * scale
680-
result[i,2] = best_py - ny * scale
681-
result[i,3] = best_px + nx * scale
682-
result[i,4] = best_py + ny * scale
683-
end
684689

685-
# Clamp all insertion points to the plot region so GMT doesn't skip labels outside it
686-
if CTRL.limits[7] != 0
687-
xmin, xmax, ymin, ymax = CTRL.limits[7:10]
688-
else
689-
xmin, xmax, ymin, ymax = getregion(D)
690-
end
691-
for i in 1:nc
692-
result[i,1] = clamp(result[i,1], xmin, xmax)
693-
result[i,2] = clamp(result[i,2], ymin, ymax)
694-
result[i,3] = clamp(result[i,3], xmin, xmax)
695-
result[i,4] = clamp(result[i,4], ymin, ymax)
690+
# Asymmetric limits: each endpoint limited independently so the segment stays inside
691+
# but extends as far as possible in the direction that has room.
692+
s_neg, s_pos = scale, scale # scale for (center - n*s) and (center + n*s)
693+
if nx > 0 s_neg = min(s_neg, (best_px - xmin) / nx); s_pos = min(s_pos, (xmax - best_px) / nx)
694+
elseif nx < 0 s_neg = min(s_neg, (xmax - best_px) / -nx); s_pos = min(s_pos, (best_px - xmin) / -nx)
695+
end
696+
if ny > 0 s_neg = min(s_neg, (best_py - ymin) / ny); s_pos = min(s_pos, (ymax - best_py) / ny)
697+
elseif ny < 0 s_neg = min(s_neg, (ymax - best_py) / -ny); s_pos = min(s_pos, (best_py - ymin) / -ny)
698+
end
699+
s_neg = max(s_neg, 1e-6); s_pos = max(s_pos, 1e-6)
700+
701+
result[i,1] = best_px - nx * s_neg
702+
result[i,2] = best_py - ny * s_neg
703+
result[i,3] = best_px + nx * s_pos
704+
result[i,4] = best_py + ny * s_pos
696705
end
697706
return result
698707
end
@@ -881,15 +890,25 @@ end
881890
function _outside_label_data(D::Vector{<:GMTdataset}, labels::Vector{String}, fontsize::Int)
882891
nc = length(D)
883892

884-
# Get plot region limits from CTRL.pocket_R e.g. " -R0/10/-1.5/1.5"
885-
ymin, ymax = CTRL.limits[9], CTRL.limits[10]
893+
# Get plot region limits
894+
if CTRL.limits[7] != 0
895+
ymin, ymax = CTRL.limits[9], CTRL.limits[10]
896+
else
897+
_, _, ymin, ymax = getregion(D)
898+
end
899+
(ymax - ymin) < 1e-10 && (ymin -= 0.5; ymax += 0.5) # degenerate range fallback
886900

887-
# x positions: at plot region edge if right axis is drawn, otherwise at each curve's last point
901+
# x positions: at the right edge of the plot region so labels start just beyond the axis
902+
if CTRL.limits[7] != 0
903+
xmax = CTRL.limits[8]
904+
else
905+
_, xmax, _, _ = getregion(D)
906+
end
888907
xs = Vector{Float64}(undef, nc)
889908
ys = Vector{Float64}(undef, nc)
890909
colors = Vector{String}(undef, nc)
891910
for k in 1:nc
892-
xs[k] = D[k].data[end, 1]
911+
xs[k] = xmax
893912
ys[k] = D[k].data[end, 2]
894913
colors[k] = _extract_W_color(D[k].header)
895914
end
@@ -918,12 +937,18 @@ function _outside_label_data(D::Vector{<:GMTdataset}, labels::Vector{String}, fo
918937
end
919938

920939
# Extract the color component from a -W option in a GMT header string.
921-
# -W can have forms like: -W0.5,red -W,blue -W1p,red,dash -W0.5,200/100/50
922-
# The color is the second comma-separated field after -W.
940+
# -W can have forms like: -W0.5,red -W,blue -W1p,red,dash -W0.5,200/100/50 -W#0072BD -Wred
941+
# The color is the second comma-separated field, or the only field if it looks like a color (not a pen width).
923942
function _extract_W_color(header::AbstractString)::String
924943
m = match(r"-W([^, ]*),([^, ]+)", header)
925-
m === nothing && return ""
926-
return String(m.captures[2])
944+
m !== nothing && return String(m.captures[2])
945+
# No comma: -W<something> where <something> might be just a color (e.g. -W#0072BD, -Wred)
946+
m2 = match(r"-W([^ ]+)", header)
947+
m2 === nothing && return ""
948+
s = m2.captures[1]
949+
# It's a color if it starts with # (hex), contains / (r/g/b), or is not a pure number/pen-width
950+
(startswith(s, "#") || contains(s, "/") || match(r"^[\d.]+[cipmn]?$", s) === nothing) && return String(s)
951+
return ""
927952
end
928953

929954
# --------------------------------------------------------------------------------------------------

src/psxy.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,14 @@ function _common_plot_xyz(w::wrapDatasets, caller::String, O::Bool, K::Bool, is3
231231
(@warn("Can't plot the connector when 'bar' is already a nested call."); pocket_call[][3] = nothing))
232232
end
233233

234+
if (isa(arg1, Vector{<:GMTdataset}) && (val = find_in_dict(d, [:setcolors :colorset])[1]) !== nothing)
235+
if (val === true || val == 1) setcolors!(arg1)
236+
elseif isa(val, Symbol) setcolors!(arg1; colorset=val)
237+
elseif isa(val, Vector{String}) setcolors!(arg1; colorset=val)
238+
elseif isa(val, NamedTuple) setcolors!(arg1; val...)
239+
end
240+
end
241+
234242
haskey(d, :labellines) && (arg1 = add_labellines!(arg1, d, _cmd))
235243

236244
(!IamModern[]) && put_in_legend_bag(d, _cmd, arg1, O, opt_l)

src/utils_types.jl

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -251,11 +251,6 @@ function _mat2ds(@nospecialize(mat::Array{<:Real}), txt::Union{String,Vector{Str
251251
_fill::Vector{String} = helper_ds_fill(d)
252252

253253
# --- Here we deal with line colors and line thickness.
254-
#if ((val = find_in_dict(d, [:lt :linethick :linethickness])[1]) !== nothing)
255-
#if (isa(val, AbstractString)) _lt::Vector{Float64} = [size_unit(val)]
256-
#elseif (isa(val, Vector{String})) _lt = size_unit(val)
257-
#else _lt = isa(val, Real) ? [val] : vec(Float64.(val))
258-
#end
259254
_lt = hlp_desnany_vfloat(d, [:lt, :linethick, :linethickness])
260255
if (!isempty(_lt))
261256
_lts::Vector{String} = Vector{String}(undef, n_ds)
@@ -265,7 +260,6 @@ function _mat2ds(@nospecialize(mat::Array{<:Real}), txt::Union{String,Vector{Str
265260
end
266261
else
267262
theW = (color_cycle || get(d, :ls, nothing) !== nothing || get(d, :linestyle, nothing) !== nothing || get(d, :pen, nothing) !== nothing) ? " -W" : ""
268-
#theW = (color_cycle || haskey(d, :ls) || haskey(d, :linestyle) || haskey(d, :pen)) ? " -W" : ""
269263
_lts = fill(theW, n_ds) # If no pen setting no need to set -W
270264
end
271265

@@ -714,7 +708,59 @@ const matlab_cycle_colors = ["#0072BD", "#D95319", "#EDB120", "#7E2F8E", "#77AC3
714708
const alphabet_colors = ["#2BCE48", "#4C005C", "#005C31", "#5EF1F2", "#8F7C00", "#9DCC00", "#0075DC", "#94FFB5", "#740AFF", "#993F00", "#00998F", "#003380", "#191919", "#426600", "#808080", "#990000", "#C20088", "#E0FF66", "#F0A3FF", "#FF0010", "#FF5005", "#FFA8BB", "#FFA405", "#FFCC99", "#FFE100", "#FFFF80"]
715709
# https://sashamaps.net/docs/resources/20-colors/
716710
const simple_distinct = ["#e6194b", "#3cb44b", "#ffe119", "#4363d8", "#f58231", "#911eb4", "#46f0f0", "#f032e6", "#bcf60c", "#fabebe", "#008080", "#e6beff", "#9a6324", "#fffac8", "#800000", "#aaffc3", "#808000", "#ffd8b1", "#000075", "#808080"]
717-
711+
712+
# ---------------------------------------------------------------------------------------------------
713+
"""
714+
setcolors!(D::Vector{<:GMTdataset}; colorset=:auto, fill=false) -> D
715+
716+
Assign cycling line colors (`-W`) and optionally fill colors (`-G`) to a vector of datasets.
717+
718+
### Args
719+
- `D`: Vector of GMTdataset to modify in-place.
720+
721+
### Kwargs
722+
- `colorset`: Color palette to use. One of `:auto` (default: `:matlab` for ≤7 curves, `:distinct` otherwise),
723+
`:matlab`, `:distinct`, or `:alphabet`. Can also be a `Vector{String}` of custom colors.
724+
- `fill`: If `true`, also add `-G<color>` fill to each header.
725+
726+
If a `-W` option already exists in the header, the color is inserted or replaced while preserving
727+
pen width and style. If no `-W` exists, one is added with the color only.
728+
"""
729+
function setcolors!(D::Vector{<:GMTdataset}; colorset::Union{Symbol,Vector{String}}=:auto, fill::Bool=false)
730+
nc = length(D)
731+
if isa(colorset, Vector{String})
732+
colors = colorset
733+
else
734+
colors = (colorset == :matlab) ? matlab_cycle_colors :
735+
(colorset == :distinct) ? simple_distinct :
736+
(colorset == :alphabet) ? alphabet_colors :
737+
(nc <= 7) ? matlab_cycle_colors : simple_distinct # :auto
738+
end
739+
n_colors = length(colors)
740+
741+
for k in 1:nc
742+
c = colors[mod1(k, n_colors)]
743+
hdr = D[k].header::String
744+
m = match(r"-W([^ ]*)", hdr)
745+
if (m !== nothing) # Parse existing -W: could be "-W1.5" or "-W1.5,red" or "-W1.5,red,dash" or "-W,blue"
746+
parts = split(m.captures[1], ',')
747+
if (length(parts) == 1) # No color yet, e.g. "-W1.5" → "-W1.5,<color>"
748+
new_W = "-W" * parts[1] * "," * c
749+
elseif (length(parts) == 2) # Has color, e.g. "-W1.5,red" → "-W1.5,<color>"
750+
new_W = "-W" * parts[1] * "," * c
751+
else # Has color+style, e.g. "-W1.5,red,dash" → "-W1.5,<color>,dash"
752+
new_W = "-W" * parts[1] * "," * c * "," * join(parts[3:end], ",")
753+
end
754+
hdr = replace(hdr, m.match => new_W)
755+
else
756+
hdr = hdr * " -W" * c
757+
end
758+
fill && (hdr = replace(hdr, r" -G[^ ]+" => "") * " -G" * c)
759+
D[k].header = hdr
760+
end
761+
return D
762+
end
763+
718764
# ---------------------------------------------------------------------------------------------------
719765
"""
720766
FV = fv2fv(F, V; proj="", proj4="", wkt="", epsg=0) -> GMTfv

test/runtests.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ using InteractiveUtils
9494
include("test_GRDs.jl")
9595
println(" Entering: test_views.jl")
9696
include("test_views.jl")
97+
println(" Entering: test_labellines.jl")
98+
include("test_labellines.jl")
9799
println(" Entering: test_PSs.jl")
98100
include("test_PSs.jl")
99101
include("test_modern.jl")

0 commit comments

Comments
 (0)