From a0ac70be3caed932c482e99184033b7fa91de21c Mon Sep 17 00:00:00 2001 From: Thomas Breloff Date: Thu, 14 Jul 2016 15:46:44 -0400 Subject: [PATCH] axes log scales, colors, ticks, and more; proper 2D axis drawing in GR --- src/axes.jl | 106 +++++++++++++++++++++++++++++++++++++-------- src/backends/gr.jl | 34 +++++++++------ 2 files changed, 108 insertions(+), 32 deletions(-) diff --git a/src/axes.jl b/src/axes.jl index 3c4efffe..ba3edadc 100644 --- a/src/axes.jl +++ b/src/axes.jl @@ -117,17 +117,77 @@ Base.setindex!(axis::Axis, v, ks::Symbol...) = setindex!(axis.d, v, ks...) Base.haskey(axis::Axis, k::Symbol) = haskey(axis.d, k) Base.extrema(axis::Axis) = (ex = axis[:extrema]; (ex.emin, ex.emax)) + +const _scale_funcs = Dict{Symbol,Function}( + :log10 => log10, + :log2 => log2, + :ln => log, +) +const _inv_scale_funcs = Dict{Symbol,Function}( + :log10 => exp10, + :log2 => exp2, + :ln => exp, +) + + +scalefunc(scale::Symbol) = get(_scale_funcs, scale, identity) +invscalefunc(scale::Symbol) = get(_inv_scale_funcs, scale, identity) + +function optimal_ticks_and_labels(axis::Axis, ticks = nothing) + lims = axis_limits(axis) + + # scale the limits + scale = axis[:scale] + scaled_lims = map(scalefunc(scale), lims) + # scaled_lims = if scale == :log10 # TODO: log10(xmin), log10(xmax) + # map(log10, lims) + # elseif scale == :log2 + # map(log2, lims) + # elseif scale == :ln + # map(log, lims) + # else + # lims + # end + + # get a list of well-laid-out ticks + cv = if ticks == nothing + optimize_ticks(scaled_lims...)[1] + else + ticks + end + + # rescale and return values and labels + tickvals = map(invscalefunc(scale), cv) + basestr = scale == :log10 ? "10^" : scale == :log2 ? "2^" : scale == :ln ? "e^" : "" + tickvals, ["$basestr$cvi" for cvi in cv] + # if scale == :log10 + # map(exp10, cv), ["10^$cvi" for cvi in cv] + # elseif scale == :log2 + # map(exp2, cv), ["2^$cvi" for cvi in cv] + # elseif scale == :ln + # map(exp, cv), ["e^$cvi" for cvi in cv] + # else + # cv, map(string, cv) + # end +end + # return (continuous_values, discrete_values) for the ticks on this axis function get_ticks(axis::Axis) ticks = axis[:ticks] + ticks in (nothing, false) && return nothing + dvals = axis[:discrete_values] cv, dv = if !isempty(dvals) && ticks == :auto # discrete ticks... axis[:continuous_values], dvals elseif ticks == :auto - cv = optimize_ticks(map(Float64, axis_limits(axis))..., k_max=5)[1] - cv, cv + # compute optimal ticks and labels + optimal_ticks_and_labels(axis) + elseif typeof(ticks) <: AVec + # override ticks, but get the labels + optimal_ticks_and_labels(axis, ticks) elseif typeof(ticks) <: NTuple{2} + # assuming we're passed (ticks, labels) ticks else error("Unknown ticks type in get_ticks: $(typeof(ticks))") @@ -360,24 +420,34 @@ function axis_drawing_info(sp::Subplot) spine_segs = Segments(2) grid_segs = Segments(2) - # x axis - ticksz = 0.015 * (ymax - ymin) - push!(spine_segs, (xmin,ymin), (xmax,ymin)) # bottom spine - push!(spine_segs, (xmin,ymax), (xmax,ymax)) # top spine - for xtick in xticks[1] - push!(spine_segs, (xtick, ymin), (xtick, ymin+ticksz)) # bottom tick - push!(grid_segs, (xtick, ymin+ticksz), (xtick, ymax-ticksz)) # vertical grid - push!(spine_segs, (xtick, ymax), (xtick, ymax-ticksz)) # top tick + if !(xaxis[:ticks] in (nothing, false)) + f = scalefunc(yaxis[:scale]) + invf = invscalefunc(yaxis[:scale]) + t1 = invf(f(ymin) + 0.015*(f(ymax)-f(ymin))) + t2 = invf(f(ymax) - 0.015*(f(ymax)-f(ymin))) + + push!(spine_segs, (xmin,ymin), (xmax,ymin)) # bottom spine + push!(spine_segs, (xmin,ymax), (xmax,ymax)) # top spine + for xtick in xticks[1] + push!(spine_segs, (xtick, ymin), (xtick, t1)) # bottom tick + push!(grid_segs, (xtick, t1), (xtick, t2)) # vertical grid + push!(spine_segs, (xtick, ymax), (xtick, t2)) # top tick + end end - # y axis - ticksz = 0.015 * (xmax - xmin) - push!(spine_segs, (xmin,ymin), (xmin,ymax)) # left spine - push!(spine_segs, (xmax,ymin), (xmax,ymax)) # right spine - for ytick in yticks[1] - push!(spine_segs, (xmin, ytick), (xmin+ticksz, ytick)) # left tick - push!(grid_segs, (xmin+ticksz, ytick), (xmax-ticksz, ytick)) # horizontal grid - push!(spine_segs, (xmax, ytick), (xmax-ticksz, ytick)) # right tick + if !(yaxis[:ticks] in (nothing, false)) + f = scalefunc(xaxis[:scale]) + invf = invscalefunc(xaxis[:scale]) + t1 = invf(f(xmin) + 0.015*(f(xmax)-f(xmin))) + t2 = invf(f(xmax) - 0.015*(f(xmax)-f(xmin))) + + push!(spine_segs, (xmin,ymin), (xmin,ymax)) # left spine + push!(spine_segs, (xmax,ymin), (xmax,ymax)) # right spine + for ytick in yticks[1] + push!(spine_segs, (xmin, ytick), (t1, ytick)) # left tick + push!(grid_segs, (t1, ytick), (t2, ytick)) # horizontal grid + push!(spine_segs, (xmax, ytick), (t2, ytick)) # right tick + end end xticks, yticks, spine_segs, grid_segs diff --git a/src/backends/gr.jl b/src/backends/gr.jl index 30465121..cc81f90b 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -328,14 +328,14 @@ end const _gr_point_mult = zeros(1) # set the font attributes... assumes _gr_point_mult has been populated already -function gr_set_font(f::Font; halign = f.halign, valign = f.valign) +function gr_set_font(f::Font; halign = f.halign, valign = f.valign, color = f.color) family = lowercase(f.family) GR.setcharheight(_gr_point_mult[1] * f.pointsize) GR.setcharup(sin(f.rotation), cos(f.rotation)) if haskey(gr_font_family, family) GR.settextfontprec(100 + gr_font_family[family], GR.TEXT_PRECISION_STRING) end - gr_set_textcolor(f.color) + gr_set_textcolor(color) GR.settextalign(gr_halign[halign], gr_valign[valign]) end @@ -582,20 +582,26 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) gr_set_line(1, :solid, sp[:xaxis][:foreground_color_axis]) gr_polyline(coords(spine_segs)...) - # x labels - gr_set_font(sp[:xaxis][:tickfont], valign = :top) - for (cv, dv) in zip(xticks...) - xi, yi = GR.wctondc(cv, ymin) - # @show cv dv ymin xi yi - gr_text(xi, yi-0.01, string(dv)) + if !(xticks in (nothing, false)) + # x labels + flip = sp[:yaxis][:flip] + gr_set_font(sp[:xaxis][:tickfont], valign = :top, color = sp[:xaxis][:foreground_color_axis]) + for (cv, dv) in zip(xticks...) + xi, yi = GR.wctondc(cv, flip ? ymax : ymin) + # @show cv dv ymin xi yi + gr_text(xi, yi-0.01, string(dv)) + end end - # y labels - gr_set_font(sp[:yaxis][:tickfont], halign = :right) - for (cv, dv) in zip(yticks...) - xi, yi = GR.wctondc(xmin, cv) - # @show cv dv xmin xi yi - gr_text(xi-0.01, yi, string(dv)) + if !(yticks in (nothing, false)) + # y labels + flip = sp[:xaxis][:flip] + gr_set_font(sp[:yaxis][:tickfont], halign = :right, color = sp[:yaxis][:foreground_color_axis]) + for (cv, dv) in zip(yticks...) + xi, yi = GR.wctondc(flip ? xmax : xmin, cv) + # @show cv dv xmin xi yi + gr_text(xi-0.01, yi, string(dv)) + end end # window_diag = sqrt(gr_view_xdiff()^2 + gr_view_ydiff()^2)