diff --git a/docs/src/makielayout/axis.md b/docs/src/makielayout/axis.md index 60fc6d410..fd9a49d6c 100755 --- a/docs/src/makielayout/axis.md +++ b/docs/src/makielayout/axis.md @@ -282,19 +282,18 @@ Take care that the axis limits always stay inside the limits appropriate for the ```@example using CairoMakie -data = sort(10.0 .^ randn(100)) +data = LinRange(0.01, 0.99, 200) f = Figure(resolution = (1000, 1000), fontsize = 14) -for (i, scale) in enumerate([identity, log10, log2, log, sqrt]) - +for (i, scale) in enumerate([identity, log10, log2, log, sqrt, AbstractPlotting.logit]) + row, col = fldmod1(i, 2) Axis(f[row, col], yscale = scale, title = string(scale), yminorticksvisible = true, yminorgridvisible = true, yminorticks = IntervalsBetween(8)) - - lines!(data, color = :blue) + lines!(data, color = :blue) end f diff --git a/src/layouting/transformation.jl b/src/layouting/transformation.jl index 8cf072889..d777e4790 100644 --- a/src/layouting/transformation.jl +++ b/src/layouting/transformation.jl @@ -318,4 +318,8 @@ inverse_transform(::typeof(log10)) = exp10 inverse_transform(::typeof(log)) = exp inverse_transform(::typeof(log2)) = exp2 inverse_transform(::typeof(sqrt)) = x -> x ^ 2 -inverse_transform(F::Tuple) = map(inverse_transform, F) \ No newline at end of file +inverse_transform(F::Tuple) = map(inverse_transform, F) + +logit(x) = log(x / (1 - x)) +expit(x) = 1 / (1 + exp(-x)) +inverse_transform(::typeof(logit)) = expit \ No newline at end of file diff --git a/src/makielayout/MakieLayout.jl b/src/makielayout/MakieLayout.jl index 625f1b497..d6a2056ac 100644 --- a/src/makielayout/MakieLayout.jl +++ b/src/makielayout/MakieLayout.jl @@ -6,6 +6,7 @@ import ..AbstractPlotting: IRect2D using ..AbstractPlotting.Keyboard using ..AbstractPlotting.Mouse using ..AbstractPlotting: ispressed, is_mouseinside, get_scene, FigureLike +using ..AbstractPlotting: OpenInterval, Interval using Observables: onany import Observables import Formatting diff --git a/src/makielayout/layoutables/axis.jl b/src/makielayout/layoutables/axis.jl index b51f5ee5e..123de1922 100644 --- a/src/makielayout/layoutables/axis.jl +++ b/src/makielayout/layoutables/axis.jl @@ -485,17 +485,15 @@ function validate_limits_for_scales(lims::Rect, xsc, ysc) ylims = (mi[2], ma[2]) if !validate_limits_for_scale(xlims, xsc) - error("Invalid x-limits $xlims for scale $xsc") + error("Invalid x-limits $xlims for scale $xsc which is defined on the interval $(defined_interval(xsc))") end if !validate_limits_for_scale(ylims, ysc) - error("Invalid y-limits $ylims for scale $ysc") + error("Invalid y-limits $ylims for scale $ysc which is defined on the interval $(defined_interval(ysc))") end nothing end -validate_limits_for_scale(lims, ::typeof(identity)) = true -validate_limits_for_scale(lims, ::Union{typeof(log2), typeof(log10), typeof(log)}) = all(>(0), lims) -validate_limits_for_scale(lims, ::typeof(sqrt)) = all(>=(0), lims) +validate_limits_for_scale(lims, scale) = all(x -> x in defined_interval(scale), lims) function AbstractPlotting.plot!( la::Axis, P::AbstractPlotting.PlotFunc, @@ -666,7 +664,7 @@ function xautolimits(ax::Axis) # but if we have limits, then expand them with the auto margins if !isnothing(xlims) if !validate_limits_for_scale(xlims, ax.xscale[]) - error("Found invalid x-limits $xlims for scale $(ax.xscale[])") + error("Found invalid x-limits $xlims for scale $(ax.xscale[]) which is defined on the interval $(defined_interval(ax.xscale[]))") end xlims = expandlimits(xlims, ax.attributes.xautolimitmargin[][1], @@ -696,7 +694,7 @@ function yautolimits(ax) # but if we have limits, then expand them with the auto margins if !isnothing(ylims) if !validate_limits_for_scale(ylims, ax.yscale[]) - error("Found invalid direct y-limits $ylims for scale $(ax.yscale[])") + error("Found invalid direct y-limits $ylims for scale $(ax.yscale[]) which is defined on the interval $(defined_interval(ax.yscale[]))") end ylims = expandlimits(ylims, ax.attributes.yautolimitmargin[][1], @@ -1094,4 +1092,10 @@ defaultlimits(::typeof(log10)) = (1.0, 1000.0) defaultlimits(::typeof(log2)) = (1.0, 8.0) defaultlimits(::typeof(log)) = (1.0, exp(3.0)) defaultlimits(::typeof(identity)) = (0.0, 10.0) -defaultlimits(::typeof(sqrt)) = (0.0, 100.0) \ No newline at end of file +defaultlimits(::typeof(sqrt)) = (0.0, 100.0) +defaultlimits(::typeof(AbstractPlotting.logit)) = (0.01, 0.99) + +defined_interval(::typeof(identity)) = OpenInterval(-Inf, Inf) +defined_interval(::Union{typeof(log2), typeof(log10), typeof(log)}) = OpenInterval(0.0, Inf) +defined_interval(::typeof(sqrt)) = Interval{:closed,:open}(0, Inf) +defined_interval(::typeof(AbstractPlotting.logit)) = OpenInterval(0.0, 1.0) \ No newline at end of file diff --git a/src/makielayout/lineaxis.jl b/src/makielayout/lineaxis.jl index f848a15ca..f2cae031d 100644 --- a/src/makielayout/lineaxis.jl +++ b/src/makielayout/lineaxis.jl @@ -476,18 +476,50 @@ _logbase(::typeof(log2)) = "2" _logbase(::typeof(log)) = "e" # log ticks just use the normal pipeline but with log'd limits, then transform the labels -function get_ticks(x, scale::Union{typeof(log10), typeof(log2), typeof(log)}, y, vmin, vmax) +function get_ticks(x, scale::Union{typeof(log10), typeof(log2), typeof(log)}, any_formatter, vmin, vmax) ticks_scaled = get_tickvalues(x, identity, scale(vmin), scale(vmax)) ticks = AbstractPlotting.inverse_transform(scale).(ticks_scaled) - if y === AbstractPlotting.automatic + if any_formatter === AbstractPlotting.automatic # here we assume that the labels are normal numbers, and we just superscript them labels_scaled = get_ticklabels(AbstractPlotting.automatic, ticks_scaled) labels = _logbase(scale) .* AbstractPlotting.UnicodeFun.to_superscript.(labels_scaled) else # otherwise the formatter has to handle the real tick numbers - labels = get_ticklabels(y, ticks) + labels = get_ticklabels(any_formatter, ticks) + end + + (ticks, labels) +end + +# logit ticks +function get_ticks(x, scale::typeof(AbstractPlotting.logit), any_formatter, vmin, vmax) + + logit_10(x) = log10(x / (1 - x)) + expit_10(x) = 1 / (1 + exp10(-x)) + ticks_scaled = get_tickvalues(x, identity, logit_10(vmin), logit_10(vmax)) + + ticks = expit_10.(ticks_scaled) + + if any_formatter === AbstractPlotting.automatic + base_labels = get_ticklabels(AbstractPlotting.automatic, ticks_scaled) + + labels = map(ticks_scaled, base_labels) do t, bl + if t == 0 + "¹/₂" + elseif t < 0 + "10" * AbstractPlotting.UnicodeFun.to_superscript(bl) + else + "1-10" * AbstractPlotting.UnicodeFun.to_superscript("-" * bl) + end + end + # # here we assume that the labels are normal numbers, and we just superscript them + # labels_scaled = get_ticklabels(AbstractPlotting.automatic, ticks_scaled) + # labels = _logbase(scale) .* AbstractPlotting.UnicodeFun.to_superscript.(labels_scaled) + else + # otherwise the formatter has to handle the real tick numbers + labels = get_ticklabels(any_formatter, ticks) end (ticks, labels) @@ -590,7 +622,7 @@ function get_minor_tickvalues(i::IntervalsBetween, scale, tickvalues, vmin, vmax end # for log scales, we need to step in log steps at the edges -function get_minor_tickvalues(i::IntervalsBetween, scale::Union{typeof(log), typeof(log2), typeof(log10)}, tickvalues, vmin, vmax) +function get_minor_tickvalues(i::IntervalsBetween, scale::Union{typeof(log), typeof(log2), typeof(log10), typeof(AbstractPlotting.logit)}, tickvalues, vmin, vmax) vals = Float32[] length(tickvalues) < 2 && return vals n = i.n diff --git a/src/makielayout/ticklocators/wilkinson.jl b/src/makielayout/ticklocators/wilkinson.jl index 18bec9895..b41b3aa63 100644 --- a/src/makielayout/ticklocators/wilkinson.jl +++ b/src/makielayout/ticklocators/wilkinson.jl @@ -14,8 +14,9 @@ function WilkinsonTicks(k_ideal::Int; k_min = 2, k_max = 10, coverage_weight, niceness_weight, min_px_dist) end +get_tickvalues(ticks::WilkinsonTicks, vmin, vmax) = get_tickvalues(ticks, Float64(vmin), Float64(vmax)) -function get_tickvalues(ticks::WilkinsonTicks, vmin, vmax) +function get_tickvalues(ticks::WilkinsonTicks, vmin::Float64, vmax::Float64) tickvalues, _ = PlotUtils.optimize_ticks(vmin, vmax; extend_ticks = false, strict_span=true, span_buffer = nothing,