@@ -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
630638end
@@ -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
698707end
@@ -881,15 +890,25 @@ end
881890function _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
918937end
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) .
923942function _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 " "
927952end
928953
929954# --------------------------------------------------------------------------------------------------
0 commit comments