Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/GMT.jl
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export
density, density!, boxplot, boxplot!, cornerplot, cornerplot!, cubeplot, cubeplot!,
ecdfplot, ecdfplot!, fill_between, fill_between!, funcurve, marginalhist, marginalhist!, parallelplot, parallelplot!,
piechart, piechart!, plotlinefit, plotlinefit!, qqplot, qqplot!, qqnorm, qqnorm!, remotegrid, sealand, squeeze, terramar,
violin, violin!, viz, vizpdf, windbarbs, whereami, maregrams, pastplates, seismicity, ecmwf, era5time, era5vars,
symlog, isymlog, violin, violin!, viz, vizpdf, windbarbs, whereami, maregrams, pastplates, seismicity, ecmwf, era5time, era5vars,
listecmwfvars, meteostat, weather, wmsinfo, wmstest, wmsread, VSdisp, mad, info, kmeans, pca, mosaic, quadbounds, quadkey,
geocoder, getprovider, zscores, bwhitmiss, binarize, bwareaopen, bwconncomp, bwdist, bwdist_idx, bwlabel, bwperim,
bwskell, cc2bw, graydist, isodata, padarray, rgb2gray, rgb2lab, rgb2YCbCr, rgb2ycbcr, grid2img, img2grid, grays2cube, grays2rgb, imclose,
Expand Down Expand Up @@ -315,6 +315,7 @@ include("splitxyz.jl")
include("streamlines.jl")
include("surface.jl")
include("subplot.jl")
include("symlog.jl")
include("PrettyTables.jl")
include("show_pretty_datasets.jl")
include("solids.jl")
Expand Down
4 changes: 2 additions & 2 deletions src/gdal_utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -358,9 +358,9 @@ function gd2gmt(dataset::Gdal.AbstractDataset)
end

# -------------------------------------------------------------------------------------------------------------
# This function is made apart because XLS and CSVs have geometries and the calling functio, as is,
# This function is made apart because XLS and CSVs have geometries and the calling function, as is,
# would not be able to extract the data from the 'dataset'
function helper_read_XLSCSV(dataset::Gdal.AbstractDataset)::GDtype
function helper_read_XLSCSV(@nospecialize(dataset))::GDtype
if (get(POSTMAN[], "meteostat", "") != "")
delete!(POSTMAN[], "meteostat")
return read_meteostat(dataset)
Expand Down
1 change: 1 addition & 0 deletions src/gmt_types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ mutable struct wrapDatasets
td, tvd, tcpt, tfv = GMTdataset(), Vector{GMTdataset}(), GMTcpt(), GMTfv()
if (arg1 !== "") new(arg1, td, tvd, tfv, tcpt)
elseif (isa(arg2, Matrix{<:Real})) new("", mat2ds(arg2), tvd, tfv, tcpt)
elseif (isa(arg2, Vector{<:Real})) new("", mat2ds(arg2), tvd, tfv, tcpt)
elseif (isa(arg2, GMTdataset)) new("", arg2, tvd, tfv, tcpt)
elseif (isa(arg2, Vector{<:GMTdataset})) new("", td, arg2, tfv, tcpt)
elseif (isa(arg2, GMTfv)) new("", td, tvd, arg2, tcpt)
Expand Down
5 changes: 3 additions & 2 deletions src/makecpt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,10 @@ To see the full documentation type: ``@? makecpt``
makecpt(cmd0::Symbol; kwargs...) = makecpt(""; C=string(cmd0), kwargs...) # Ex: makecpt(:gray)
function makecpt(cmd0::String="", arg1=nothing; kwargs...)::Union{String, GMTcpt}
d = init_module(false, kwargs...)[1] # Also checks if the user wants ONLY the HELP mode
makecpt(cmd0, arg1, d)
makecpt(wrapDatasets(cmd0, arg1), d)
end
function makecpt(cmd0::String, arg1, d::Dict)::Union{String, GMTcpt}
function makecpt(w::wrapDatasets, d::Dict)::Union{String, GMTcpt}
cmd0, arg1 = unwrapDatasets(w::wrapDatasets)

cmd = parse_V_params(d, "")

Expand Down
8 changes: 5 additions & 3 deletions src/psscale.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,15 @@ To see the full documentation type: ``@? colorbar``
"""
function colorbar(arg1::Union{Nothing, GMTcpt}=nothing; first=true, kwargs...)
d, K, O = init_module(first==1, kwargs...) # Also checks if the user wants ONLY the HELP mode
dbg_cmd, d, cmd, arg1 = colorbar_parser(isa(arg1, Nothing) ? GMTcpt() : arg1, O, d)
dbg_cmd, d, cmd, arg1 = colorbar_parser(wrapDatasets("", arg1), O, d)
(dbg_cmd !== nothing) && return dbg_cmd
r = prep_and_call_finish_PS_module(d, cmd, "", K, O, true, arg1)
(!isa(r,String)) && gmt("destroy") # Probably because of the rasters in cpt
end

function colorbar_parser(arg1::Union{Nothing, GMTcpt}, O::Bool, d::Dict{Symbol, Any})
function colorbar_parser(w::wrapDatasets, O::Bool, d::Dict{Symbol, Any})
arg1 = unwrapDatasets(w::wrapDatasets)[2]

gmt_proggy = (IamModern[]) ? "colorbar " : "psscale "

parse_paper(d) # See if user asked to temporarily pass into paper mode coordinates
Expand All @@ -73,7 +75,7 @@ function colorbar_parser(arg1::Union{Nothing, GMTcpt}, O::Bool, d::Dict{Symbol,

(!isempty(opt_D)) && (!contains(opt_D, "DJ") && !contains(opt_D, "Dj") && !contains(opt_D, "Dg")) && (cmd = replace(cmd, "-R " => ""))
cmd *= opt_D
cmd, arg1, = add_opt_cpt(d, cmd, CPTaliases, 'C', 0, isempty(arg1) ? nothing : arg1)
cmd, arg1, = add_opt_cpt(d, cmd, CPTaliases, 'C', 0, arg1)
if (opt_D === "" && ((val = hlp_desnany_str(d, [:triangles])) !== "")) # User asked for triangles but did not set pos
(val == "true") && (val = "tri") # Means just triangles
anc_tris = colorbar_triangles(val) # Returns anchor+triangles string
Expand Down
163 changes: 163 additions & 0 deletions src/symlog.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# Written by Claude.ai

"""
symlog(x, y; axis=:y, linthresh=1, linscale=1, base=10, kwargs...)
symlog(D::GMTdataset; axis=:y, linthresh=1, linscale=1, base=10, kwargs...)

Plot data with a symmetric logarithmic scale, like matplotlib's symlog.
Linear in [-linthresh, linthresh], logarithmic beyond.

- `axis`: which axis to transform: `:y` (default), `:x`, or `:xy` for both
- `linthresh`: threshold below which the scale is linear (must be > 0)
- `linscale`: scale factor for the linear region
- `base`: logarithm base (default 10)
- All other kwargs are forwarded to `plot()`

### Example
```julia
x = 1:100
y = @. 10.0^(x/20) - 10.0^((101-x)/20)
symlog(x, y, linthresh=10, lw=1)
```
"""
function symlog(x::AbstractVector{<:Real}, y::AbstractVector{<:Real}; axis::Symbol=:y,
linthresh::Real=1, linscale::Real=1, base::Real=10, first::Bool=true, kwargs...)
d = KW(kwargs)
lt, ls, b = Float64(linthresh), Float64(linscale), Float64(base)
_symlog_plot(Float64.(x), Float64.(y), axis, lt, ls, b, first, d)
end

function symlog(D::GMTdataset; axis::Symbol=:y,
linthresh::Real=1, linscale::Real=1, base::Real=10, first::Bool=true, kwargs...)
d = KW(kwargs)
lt, ls, b = Float64(linthresh), Float64(linscale), Float64(base)
_symlog_plot(Float64.(view(D.data,:,1)), Float64.(view(D.data,:,2)), axis, lt, ls, b, first, d)
end

function symlog(D::Vector{<:GMTdataset}; axis::Symbol=:y,
linthresh::Real=1, linscale::Real=1, base::Real=10, first::Bool=true, kwargs...)
d = KW(kwargs)
lt, ls, b = Float64(linthresh), Float64(linscale), Float64(base)
# Transform each dataset and plot; first one creates, rest overlay
for (i, Di) in enumerate(D)
xx, yy = Float64.(view(Di.data,:,1)), Float64.(view(Di.data,:,2))
_symlog_plot(xx, yy, axis, lt, ls, b, i == 1 && first, d)
(i == 1) && (d = Dict{Symbol,Any}()) # Only pass user opts on first call
end
end

function _symlog_plot(x::Vector{Float64}, y::Vector{Float64}, axis::Symbol,
lt::Float64, ls::Float64, b::Float64, first::Bool, d::Dict)

# Transform the data
xt = (axis == :x || axis == :xy) ? [_symlog(v, lt, ls, b) for v in x] : x
yt = (axis == :y || axis == :xy) ? [_symlog(v, lt, ls, b) for v in y] : y

# Build custom tick annotations for the transformed axis/axes
if (axis == :y || axis == :xy)
tpos, tlab = _symlog_ticks(y, lt, ls, b)
typ = ["a " * l for l in tlab]
d[:yaxis] = (custom = (pos=tpos, type=typ),)
end
if (axis == :x || axis == :xy)
tpos, tlab = _symlog_ticks(x, lt, ls, b)
typ = ["a " * l for l in tlab]
d[:xaxis] = (custom = (pos=tpos, type=typ),)
end

# Ensure the non-transformed axis gets default annotations
if (axis == :y && !haskey(d, :xaxis) && !haskey(d, :frame) && !haskey(d, :B))
d[:xaxis] = (annot=:auto,)
elseif (axis == :x && !haskey(d, :yaxis) && !haskey(d, :frame) && !haskey(d, :B))
d[:yaxis] = (annot=:auto,)
end

plot(mat2ds(hcat(xt, yt)); first=first, d...)
end

# ---------------------------------------------------------------------------
# Core transform (single compilation, no type specialization)
# ---------------------------------------------------------------------------
function _symlog(x::Float64, linthresh::Float64, linscale::Float64, base::Float64)::Float64
log_base = log(base)
linscale_adj = linscale / (1.0 - 1.0 / base)
abs_x = abs(x)
if abs_x <= linthresh
return x * linscale_adj / linthresh
else
return sign(x) * (linscale_adj + linscale * log(abs_x / linthresh) / log_base)
end
end

"""
isymlog(y; linthresh=1, linscale=1, base=10)

Inverse of the symlog transform. Converts transformed values back to original scale.
"""
function isymlog(y::Real; linthresh::Real=1, linscale::Real=1, base::Real=10)
_isymlog(Float64(y), Float64(linthresh), Float64(linscale), Float64(base))
end

function isymlog(v::AbstractArray{<:Real}; linthresh::Real=1, linscale::Real=1, base::Real=10)
lt, ls, b = Float64(linthresh), Float64(linscale), Float64(base)
[_isymlog(Float64(y), lt, ls, b) for y in v]
end

function _isymlog(y::Float64, linthresh::Float64, linscale::Float64, base::Float64)::Float64
log_base = log(base)
linscale_adj = linscale / (1.0 - 1.0 / base)
if abs(y) <= linscale_adj
return y * linthresh / linscale_adj
else
return sign(y) * linthresh * exp((abs(y) - linscale_adj) * log_base / linscale)
end
end

# ---------------------------------------------------------------------------
# Tick generation — produces positions in transformed space + original-value labels
# ---------------------------------------------------------------------------
function _symlog_ticks(data::Vector{Float64}, lt::Float64, ls::Float64, b::Float64)
vmin, vmax = extrema(data)
ticks = Float64[]
labels = String[]

function add_tick!(val::Float64)
push!(ticks, _symlog(val, lt, ls, b))
push!(labels, _fmt_tick(val))
end

(vmin <= 0 <= vmax) && add_tick!(0.0)

# +/- linthresh boundaries
(vmin <= -lt && vmax >= -lt) && add_tick!(-lt)
(vmax >= lt && vmin <= lt) && add_tick!(lt)

# Positive log ticks
if vmax > lt
for e in floor(Int, log(b, lt)):ceil(Int, log(b, vmax))
val = Float64(b^e)
(val >= lt && val <= vmax) && add_tick!(val)
end
end

# Negative log ticks
if vmin < -lt
for e in floor(Int, log(b, lt)):ceil(Int, log(b, abs(vmin)))
val = -Float64(b^e)
(val <= -lt && val >= vmin) && add_tick!(val)
end
end

perm = sortperm(ticks)
return ticks[perm], labels[perm]
end

function _fmt_tick(v::Float64)::String
if v == 0
return "0"
elseif abs(v) >= 1 && abs(v) == round(abs(v))
return string(Int(v))
else
return @sprintf("%.4g", v)
end
end
11 changes: 8 additions & 3 deletions src/utils_types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ function mat2ds(mat::Array{T,N}, txt::Union{String,Vector{String}}=String[];
d = KW(kwargs)
_mat2ds(mat, txt, isa(hdr, String) ? [hdr] : vec(hdr), Int(geom), d)
end
function _mat2ds(mat::Array{T,N}, txt::Union{String,Vector{String}}, hdr::Vector{String}, geom::Int, d::Dict)::GDtype where {T<:Real, N}
function _mat2ds(@nospecialize(mat::Array{<:Real}), txt::Union{String,Vector{String}}, hdr::Vector{String}, geom::Int, d::Dict)::GDtype

coln = hlp_desnany_vstr(d, [:colnames])
(!isempty(txt)) && return text_record(mat, txt, hdr)
Expand Down Expand Up @@ -1292,7 +1292,7 @@ function mat2img16(mat::AbstractArray{<:Unsigned}; x=Float64[], y=Float64[], v=F
d = KW(kw)
helper_mat2img(mat, vec(Float64.(x)), vec(Float64.(y)), vec(Float64.(v)), vec(Float64.(hdr)), proj4, wkt, cmap, is_transposed, d)
end
function helper_mat2img(mat, x::Vector{Float64}, y::Vector{Float64}, v::Vector{Float64}, hdr::Vector{Float64},
function helper_mat2img(@nospecialize(mat), x::Vector{Float64}, y::Vector{Float64}, v::Vector{Float64}, hdr::Vector{Float64},
proj4::String, wkt::String, cmap::GMTcpt, is_transposed::Bool, d::Dict)
nx = size(mat, 2); ny = size(mat, 1);
if (is_transposed) nx, ny = ny, nx end
Expand Down Expand Up @@ -2443,9 +2443,14 @@ end

# ---------------------------------------------------------------------------------------------------
function grdimg_hdr_xy(mat, reg, hdr, x=Float64[], y=Float64[], is_transposed=false)
# Thin wrapper: convert to concrete types, then call the single-compilation implementation
_grdimg_hdr_xy(mat, Int(reg)::Int, Float64.(vec(hdr)), Float64.(vec(x)), Float64.(vec(y)), is_transposed == 1)
end

function _grdimg_hdr_xy(@nospecialize(mat), reg::Int, hdr::Vector{Float64}, x::Vector{Float64}, y::Vector{Float64}, is_transposed::Bool)
# Generate x,y coords array and compute/update header plus increments for grids/images
# Arrays coming from GDAL are often scanline so they are transposed. In that case is_transposed should be true
row_dim, col_dim = (is_transposed) ? (2,1) : (1,2)
row_dim, col_dim = (is_transposed) ? (2,1) : (1,2)
nx = size(mat, col_dim); ny = size(mat, row_dim);
one_or_zero = reg == 0 ? 1 : 0

Expand Down
Loading