xaxis(args...; kw...) = Axis(:x, args...; kw...) yaxis(args...; kw...) = Axis(:y, args...; kw...) zaxis(args...; kw...) = Axis(:z, args...; kw...) function Axis(letter::Symbol, args...; kw...) # init with values from _plot_defaults d = KW( :letter => letter, :extrema => (Inf, -Inf), :discrete_map => Dict(), # map discrete values to continuous plot values :discrete_values => Tuple{Float64,Any}[], :use_minor => false, :show => true, # show or hide the axis? (useful for linked subplots) ) merge!(d, _axis_defaults) # update the defaults update!(Axis(d), args...; kw...) end function process_axis_arg!(d::KW, arg, letter = "") T = typeof(arg) arg = get(_scaleAliases, arg, arg) if typeof(arg) <: Font d[symbol(letter,:tickfont)] = arg d[symbol(letter,:guidefont)] = arg elseif arg in _allScales d[symbol(letter,:scale)] = arg elseif arg in (:flip, :invert, :inverted) d[symbol(letter,:flip)] = true elseif T <: @compat(AbstractString) d[symbol(letter,:guide)] = arg # xlims/ylims elseif (T <: Tuple || T <: AVec) && length(arg) == 2 sym = typeof(arg[1]) <: Number ? :lims : :ticks d[symbol(letter,sym)] = arg # xticks/yticks elseif T <: AVec d[symbol(letter,:ticks)] = arg elseif arg == nothing d[symbol(letter,:ticks)] = [] elseif typeof(arg) <: Number d[symbol(letter,:rotation)] = arg else warn("Skipped $(letter)axis arg $arg") end end # update an Axis object with magic args and keywords function update!(a::Axis, args...; kw...) # first process args d = a.d for arg in args process_axis_arg!(d, arg) end # then override for any keywords... only those keywords that already exists in d for (k,v) in kw # sym = symbol(string(k)[2:end]) if haskey(d, k) d[k] = v end end a end Base.show(io::IO, a::Axis) = dumpdict(a.d, "Axis", true) Base.getindex(a::Axis, k::Symbol) = getindex(a.d, k) Base.setindex!(a::Axis, v, ks::Symbol...) = setindex!(a.d, v, ks...) Base.haskey(a::Axis, k::Symbol) = haskey(a.d, k) Base.extrema(a::Axis) = a[:extrema] # get discrete ticks, or not function get_ticks(a::Axis) ticks = a[:ticks] dvals = a[:discrete_values] if !isempty(dvals) && ticks == :auto vals, labels = unzip(dvals) else ticks end end function expand_extrema!(a::Axis, v::Number) emin, emax = a[:extrema] a[:extrema] = (min(v, emin), max(v, emax)) end function expand_extrema!{MIN<:Number,MAX<:Number}(a::Axis, v::Tuple{MIN,MAX}) emin, emax = a[:extrema] a[:extrema] = (min(v[1], emin), max(v[2], emax)) end function expand_extrema!{N<:Number}(a::Axis, v::AVec{N}) if !isempty(v) emin, emax = a[:extrema] a[:extrema] = (min(minimum(v), emin), max(maximum(v), emax)) end a[:extrema] end # these methods track the discrete values which correspond to axis continuous values (cv) # whenever we have discrete values, we automatically set the ticks to match. # we return the plot value function discrete_value!(a::Axis, v) cv = get(a[:discrete_map], v, NaN) if isnan(cv) emin, emax = a[:extrema] cv = max(0.5, emax + 1.0) expand_extrema!(a, cv) a[:discrete_map][v] = cv push!(a[:discrete_values], (cv, v)) end cv end # add the discrete value for each item function discrete_value!(a::Axis, v::AVec) Float64[discrete_value!(a, vi) for vi=v] end