@@ -841,117 +841,165 @@ function _get_axis_label(base_label::String, exponent::Int, scale_func::Function
841841end
842842
843843function _build_plot! (fig_ctx, ctx, axis, spec:: LineParametersPlotSpec )
844- # Set initial axis scales from the spec. These will be the source of truth.
844+ # ---- Axis title & initial labels ----------------------------------------
845+ axis. title = spec. title
846+ axis. xlabel = _get_axis_label (spec. xlabel, spec. x_exp, spec. xscale[])
847+ axis. ylabel = _get_axis_label (spec. ylabel, spec. y_exp, spec. yscale[])
848+
849+ # ---- Helpers ------------------------------------------------------------
850+ sanitize_log! (v:: AbstractVector , is_log:: Bool ) =
851+ (is_log && ! isempty (v)) ? (v[v .<= 0 ] .= NaN ; v) : v
852+
853+ _x_data_for (scale) = begin
854+ xd = _get_axis_data (spec. raw_freqs, spec. freqs, scale)
855+ sanitize_log! (xd. values, scale == Makie. log10)
856+ xd
857+ end
845858
846- axis. title = spec. title
859+ _y_data_for (i:: Int , scale) = begin
860+ yd = _get_axis_data (spec. raw_curves[i], spec. curves[i], scale)
861+ sanitize_log! (yd. values, scale == Makie. log10)
862+ yd
863+ end
847864
848- # This helper function redraws everything based on the current axis scales.
849- function _draw_axis! ()
850- # 1. Clear the axis completely
851- empty! (axis)
852- is_empty = true
865+ # safe max(abs(.)) ignoring non-finite
866+ _finite_max_abs (v) = begin
867+ buf = (x -> abs (x)) . ( value .(v))
868+ any (isfinite, buf) ? maximum (x for x in buf if isfinite (x)) : 0.0
869+ end
853870
854- # 2. Get data and labels based on the current scale stored in the axis
855- axis. xlabel = _get_axis_label (spec. xlabel, spec. x_exp, spec. xscale[])
856- axis. ylabel = _get_axis_label (spec. ylabel, spec. y_exp, spec. yscale[])
857- x_data = _get_axis_data (spec. raw_freqs, spec. freqs, spec. xscale[])
871+ # ---- Select active (non-noise) curves by EPS -------------------------------
872+ ncurves = length (spec. curves)
873+ active_idx = Int[]
858874
859- # Sanitize x-axis data for log scale if needed
860- if spec. xscale[] == Makie. log10
861- x_data. values[x_data. values .<= 0 ] .= NaN
875+ @inbounds for i in 1 : ncurves
876+ # max magnitude of raw curve; works for Real, Complex, and Measurement types
877+ maxmag = maximum (value .(abs .(spec. raw_curves[i])))
878+ if maxmag > eps (Float64) # keep only if anything rises above machine eps
879+ push! (active_idx, i)
862880 end
881+ end
863882
864- palette = Makie. wong_colors ()
865- ncolors = length (palette)
866-
867-
868- # 3. Draw all lines and error bars from scratch
869- for (idx, (raw_curve, scaled_curve)) in enumerate (zip (spec. raw_curves, spec. curves))
870-
871- # If all raw values are below the threshold,
872- # consider it numerical noise and skip plotting this curve entirely.
873- if ! any (abs .(value .(raw_curve)) .> eps ())
874- continue
875- end
876- is_empty = false
877-
878- color = palette[mod1 (idx, ncolors)]
879- label = spec. labels[idx]
880- y_data = _get_axis_data (raw_curve, scaled_curve, spec. yscale[])
881-
882- # LOG-SCALE SANITIZATION: Only for log plots, replace non-positive
883- # values with NaN to create gaps.
884- if spec. yscale[] == Makie. log10
885- y_data. values[y_data. values .<= 0 ] .= NaN
886- end
887-
888- lines! (
889- axis,
890- x_data. values,
891- y_data. values;
892- color = color,
893- label = label,
894- linewidth = 2 ,
883+ any_real_curve = ! isempty (active_idx)
884+
885+ # ---- Initial data (x) ---------------------------------------------------
886+ x_init = _x_data_for (spec. xscale[])
887+ x_vals_obs = Observable (copy (x_init. values))
888+ x_errs_obs = x_init. errors === nothing ? nothing : Observable (copy (x_init. errors))
889+
890+ # ---- Per-curve allocs only for active curves ---------------------------
891+ palette = Makie. wong_colors ()
892+ ncolors = length (palette)
893+ nact = length (active_idx)
894+
895+ y_vals_obs = Vector {Observable} (undef, nact)
896+ y_errs_obs = Vector {Union{Nothing, Observable}} (undef, nact)
897+ line_plots = Vector {Any} (undef, nact)
898+ yerr_plots = Vector {Any} (undef, nact)
899+ xerr_plots = Vector {Any} (undef, nact)
900+
901+ # ---- Draw active curves -------------------------------------------------
902+ for k in 1 : nact
903+ i = active_idx[k]
904+ color = palette[mod1 (k, ncolors)] # color by active order
905+ label = spec. labels[i]
906+
907+ yd = _y_data_for (i, spec. yscale[])
908+
909+ y_vals_obs[k] = Observable (copy (yd. values))
910+ y_errs_obs[k] = yd. errors === nothing ? nothing : Observable (copy (yd. errors))
911+
912+ # line
913+ ln = lines! (
914+ axis,
915+ x_vals_obs,
916+ y_vals_obs[k];
917+ color = color,
918+ label = label,
919+ linewidth = 2 ,
920+ )
921+ line_plots[k] = ln
922+
923+ # Y errorbars: stems only + caps; both follow the line’s visibility
924+ if y_errs_obs[k] != = nothing
925+ yerr_plots[k] = errorbars! (
926+ axis, x_vals_obs, y_vals_obs[k], y_errs_obs[k];
927+ color = color, direction = :y , whiskerwidth = 3 ,
928+ visible = lift (identity, ln. visible),
895929 )
930+ else
931+ yerr_plots[k] = nothing
932+ end
896933
897- if y_data. errors != = nothing
898- errorbars! (
899- axis,
900- x_data. values,
901- y_data. values,
902- y_data. errors;
903- color = color,
904- whiskerwidth = 5 ,
905- direction = :y ,
906- )
907- end
908- if x_data. errors != = nothing
909- errorbars! (
910- axis,
911- x_data. values,
912- y_data. values,
913- x_data. errors;
914- color = color,
915- whiskerwidth = 5 ,
916- direction = :x ,
917- )
918- end
934+ # X errorbars: stems only + caps
935+ if x_errs_obs != = nothing
936+ xerr_plots[k] = errorbars! (
937+ axis, x_vals_obs, y_vals_obs[k], x_errs_obs;
938+ color = color, direction = :x , whiskerwidth = 3 ,
939+ visible = lift (identity, ln. visible),
940+ )
941+ else
942+ xerr_plots[k] = nothing
919943 end
920944
921- # 4. Handle the "no data" case internally
922- if is_empty
923- lines! (axis, [NaN ], [NaN ]; color = :transparent , label = " No data" )
945+ end
946+
947+ # If nothing to draw, add transparent dummy without legend entry
948+ if ! any_real_curve
949+ lines! (axis, [NaN ], [NaN ]; color = :transparent , label = " No data" )
950+ end
951+
952+ # ---- Apply initial scales safely ---------------------------------------
953+ try
954+ axis. xscale[] = spec. xscale[]
955+ axis. yscale[] = spec. yscale[]
956+ catch
957+ axis. xscale[] = Makie. identity
958+ axis. yscale[] = Makie. identity
959+ @warn " Failed to set axis scale; reverted to linear scale."
960+ end
961+ Makie. autolimits! (axis)
962+
963+ # ---- Refreshers (update Observables only) ------------------------------
964+ function _refresh_x! (scale)
965+ spec. xscale[] = scale
966+ axis. xscale[] = scale
967+ axis. xlabel = _get_axis_label (spec. xlabel, spec. x_exp, scale)
968+
969+ xd = _x_data_for (scale)
970+ x_vals_obs[] = xd. values
971+ if x_errs_obs != = nothing
972+ x_errs_obs[] = xd. errors
924973 end
974+ Makie. autolimits! (axis)
975+ nothing
976+ end
925977
926- # 5. Reset limits
927- try
928- axis. xscale[] = spec. xscale[]
929- axis. yscale[] = spec. yscale[]
930- catch
931- # If setting the scale fails (e.g., log scale with non-positive values),
932- # revert to linear scale and notify the user.
933- axis. xscale[] = Makie. identity
934- axis. yscale[] = Makie. identity
935- @warn " Failed to set axis scale; reverted to linear scale."
978+ function _refresh_y! (scale)
979+ spec. yscale[] = scale
980+ axis. yscale[] = scale
981+ axis. ylabel = _get_axis_label (spec. ylabel, spec. y_exp, scale)
982+
983+ @inbounds for k in 1 : nact
984+ i = active_idx[k]
985+ yd = _y_data_for (i, scale)
986+ y_vals_obs[k][] = yd. values
987+ if y_errs_obs[k] != = nothing
988+ y_errs_obs[k][] = yd. errors
989+ end
936990 end
937991 Makie. autolimits! (axis)
938- return is_empty
992+ nothing
939993 end
940994
941- # Initial drawing
942- is_empty_axis = _draw_axis! ()
995+ # ---- Buttons ------------------------------------------------------------
943996 buttons =
944- is_empty_axis ? [] :
997+ any_real_curve ?
945998 [
946999 ControlButtonSpec (
947- (_ctx, _btn) -> begin
948- Makie. reset_limits! (axis)
949- return nothing
950- end ;
1000+ (_ctx, _btn) -> (Makie. reset_limits! (axis); nothing );
9511001 icon = MI_REFRESH,
952- on_success = ControlReaction (
953- status_string = " Axis limits reset" ,
954- ),
1002+ on_success = ControlReaction (status_string = " Axis limits reset" ),
9551003 ),
9561004 ControlButtonSpec (
9571005 (_ctx, _btn) -> _save_plot_export (spec, axis);
@@ -960,70 +1008,57 @@ function _build_plot!(fig_ctx, ctx, axis, spec::LineParametersPlotSpec)
9601008 status_string = path -> string (" Saved SVG to " , basename (path)),
9611009 ),
9621010 ),
963- ]
964-
965- # --- Define control toggles ---
966- # The callbacks are now trivial: just update the scale and redraw.
1011+ ] : Any[]
9671012
1013+ # ---- Toggles ------------------------------------------------------------
9681014 toggles =
969- is_empty_axis ? [] :
1015+ any_real_curve ?
9701016 [
9711017 ControlToggleSpec (
972- # Action when toggled ON (log scale)
973- (_ctx, _toggle) -> begin
974- spec. xscale[] = Makie. log10
975- _draw_axis! ()
976- end ,
977- # Action when toggled OFF (linear scale)
978- (_ctx, _toggle) -> begin
979- spec. xscale[] = Makie. identity
980- _draw_axis! ()
981- end ;
1018+ (_ctx, _t) -> _refresh_x! (Makie. log10),
1019+ (_ctx, _t) -> _refresh_x! (Makie. identity);
9821020 label = " log x-axis" ,
9831021 start_active = spec. xscale[] == Makie. log10,
9841022 on_success_on = ControlReaction (status_string = " x-axis scale set to log" ),
9851023 on_success_off = ControlReaction (
9861024 status_string = " x-axis scale set to linear" ,
9871025 ),
988- on_failure = ControlReaction (
989- status_string = err_msg -> err_msg,
990- ),
1026+ on_failure = ControlReaction (status_string = err -> err),
9911027 ),
9921028 ControlToggleSpec (
993- # Action when toggled ON (log scale)
994- (_ctx, _toggle) -> begin
995- spec. yscale[] = Makie. log10
996- _draw_axis! ()
997- end ,
998- # Action when toggled OFF (linear scale)
999- (_ctx, _toggle) -> begin
1000- spec. yscale[] = Makie. identity
1001- _draw_axis! ()
1002- end ;
1029+ (_ctx, _t) -> _refresh_y! (Makie. log10),
1030+ (_ctx, _t) -> _refresh_y! (Makie. identity);
10031031 label = " log y-axis" ,
10041032 start_active = spec. yscale[] == Makie. log10,
10051033 on_success_on = ControlReaction (status_string = " y-axis scale set to log" ),
10061034 on_success_off = ControlReaction (
10071035 status_string = " y-axis scale set to linear" ,
10081036 ),
1009- on_failure = ControlReaction (
1010- status_string = err_msg -> err_msg,
1011- ),
1037+ on_failure = ControlReaction (status_string = err -> err),
10121038 ),
1013- ]
1014-
1015- legend_builder = parent -> Makie. Legend (parent, axis; orientation = :vertical )
1039+ ] : Any[]
1040+
1041+ # ---- Legend -------------------------------------------------------------
1042+ legend_builder =
1043+ parent ->
1044+ Makie. Legend (
1045+ parent,
1046+ axis;
1047+ orientation = :vertical ,
1048+ )
10161049
10171050 return PlotBuildArtifacts (
1018- axis = axis,
1019- legends = legend_builder,
1020- colorbars = Any[],
1051+ axis = axis,
1052+ legends = legend_builder,
1053+ colorbars = Any[],
10211054 control_buttons = buttons,
10221055 control_toggles = toggles,
1023- status_message = nothing ,
1056+ status_message = nothing ,
10241057 )
10251058end
10261059
1060+
1061+
10271062function _display! (backend_ctx, fig:: Makie.Figure ; title:: AbstractString = " " )
10281063 if backend_ctx. interactive && backend_ctx. window != = nothing
10291064 display (backend_ctx. window, fig)
0 commit comments