From 9df962fccea23e2380b449e5d0dc13cb2030dcee Mon Sep 17 00:00:00 2001 From: Daniel Schwabeneder Date: Wed, 2 Sep 2020 18:52:12 +0200 Subject: [PATCH 1/6] clean up gr_set_tickfont --- src/backends/gr.jl | 47 ++++++++++++++++------------------------------ 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/src/backends/gr.jl b/src/backends/gr.jl index 1f5f1520..8dd53d4e 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -316,7 +316,7 @@ function gr_draw_marker(series, xi, yi, clims, i, msize, strokewidth, shape::Sha GR.selntran(1) end -function nominal_size(s) +function gr_nominal_size(s) w, h = get_size(s) min(w, h) / 500 end @@ -328,7 +328,7 @@ function gr_draw_marker(series, xi, yi, clims, i, msize, strokewidth, shape::Sym gr_set_markercolor(get_markercolor(series, clims, i)); gr_set_transparency(get_markeralpha(series, i)) GR.setmarkertype(gr_markertype(shape)) - GR.setmarkersize(0.3msize / nominal_size(series)) + GR.setmarkersize(0.3msize / gr_nominal_size(series)) GR.polymarker([xi], [yi]) end @@ -366,12 +366,11 @@ end function gr_set_line(lw, style, c, s) # s can be Subplot or Series GR.setlinetype(gr_linetype(style)) - GR.setlinewidth(get_thickness_scaling(s) * max(0, lw / nominal_size(s))) + GR.setlinewidth(get_thickness_scaling(s) * max(0, lw / gr_nominal_size(s))) gr_set_linecolor(c) end - function gr_set_fill(c) #, a) gr_set_fillcolor(c) #, a) GR.setfillintstyle(GR.INTSTYLE_SOLID) @@ -684,31 +683,15 @@ function gr_display(plt::Plot, fmt="") end -function gr_set_xticks_font(sp) - flip = sp[:yaxis][:flip] - mirror = sp[:xaxis][:mirror] +function gr_set_ticks_font(sp, asym) + mirror = sp[asym][:mirror] gr_set_font( - tickfont(sp[:xaxis]), + tickfont(sp[asym]), sp, - halign = (:left, :hcenter, :right)[sign(sp[:xaxis][:rotation]) + 2], + halign = (:left, :hcenter, :right)[sign(sp[asym][:rotation]) + 2], valign = (mirror ? :bottom : :top), - rotation = sp[:xaxis][:rotation], + rotation = sp[asym][:rotation], ) - return flip, mirror -end - - -function gr_set_yticks_font(sp) - flip = sp[:xaxis][:flip] - mirror = sp[:yaxis][:mirror] - gr_set_font( - tickfont(sp[:yaxis]), - sp, - halign = (mirror ? :left : :right), - valign = (:top, :vcenter, :bottom)[sign(sp[:yaxis][:rotation]) + 2], - rotation = sp[:yaxis][:rotation], - ) - return flip, mirror end function gr_text_size(str) @@ -889,17 +872,17 @@ function _update_min_padding!(sp::Subplot{GRBackend}) # Add margin for x and y ticks xticks, yticks = get_ticks(sp, sp[:xaxis]), get_ticks(sp, sp[:yaxis]) if !(xticks in (nothing, false, :none)) - flip, mirror = gr_set_xticks_font(sp) + gr_set_ticks_font(sp, :xaxis) l = 0.01 + last(gr_get_ticks_size(xticks, sp[:xaxis][:rotation])) h = 1mm + get_size(sp)[2] * l * px - if mirror + if sp[:xaxis][:mirror] toppad += h else bottompad += h end end if !(yticks in (nothing, false, :none)) - flip, mirror = gr_set_yticks_font(sp) + gr_set_ticks_font(sp, :yaxis) l = 0.01 + first(gr_get_ticks_size(yticks, sp[:yaxis][:rotation])) w = 1mm + get_size(sp)[1] * l * px if mirror @@ -1460,7 +1443,8 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) # tick marks if !(xticks in (:none, nothing, false)) && xaxis[:showaxis] # x labels - flip, mirror = gr_set_xticks_font(sp) + flip, mirror = yaxis[:flip], xaxis[:mirror] + gr_set_ticks_font(sp, :xaxis) for (cv, dv) in zip(xticks...) xi, yi = GR.wctondc(cv, sp[:framestyle] == :origin ? 0 : xor(flip, mirror) ? ymax : ymin) gr_text(xi, yi + (mirror ? 1 : -1) * 8e-3 * (xaxis[:tick_direction] == :out ? 1.5 : 1.0), dv) @@ -1469,7 +1453,8 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) if !(yticks in (:none, nothing, false)) && yaxis[:showaxis] # y labels - flip, mirror = gr_set_yticks_font(sp) + flip, mirror = xaxis[:flip], yaxis[:mirror] + gr_set_ticks_font(sp, :yaxis) for (cv, dv) in zip(yticks...) xi, yi = GR.wctondc(sp[:framestyle] == :origin ? 0 : xor(flip, mirror) ? xmax : xmin, cv) gr_text(xi + (mirror ? 1 : -1) * 1.5e-2 * (yaxis[:tick_direction] == :out ? 1.5 : 1.0), @@ -1675,7 +1660,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) elseif st == :contour GR.setspace(clims[1], clims[2], 0, 90) GR.setlinetype(gr_linetype(get_linestyle(series))) - GR.setlinewidth(max(0, get_linewidth(series)) / nominal_size(sp)) + GR.setlinewidth(max(0, get_linewidth(series)) / gr_nominal_size(sp)) is_lc_black = let black=plot_color(:black) plot_color(series[:linecolor]) in (black,[black]) end From 6a03da5837e791879532b887a381ccae36079584 Mon Sep 17 00:00:00 2001 From: Daniel Schwabeneder Date: Wed, 2 Sep 2020 21:27:51 +0200 Subject: [PATCH 2/6] extract legend functions and viewport updating from gr_display --- src/backends/gr.jl | 429 +++++++++++++++++++++++++-------------------- 1 file changed, 237 insertions(+), 192 deletions(-) diff --git a/src/backends/gr.jl b/src/backends/gr.jl index 8dd53d4e..b1914ecf 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -567,11 +567,9 @@ end gr_view_xcenter(viewport_plotarea) = 0.5 * (viewport_plotarea[1] + viewport_plotarea[2]) gr_view_ycenter(viewport_plotarea) = 0.5 * (viewport_plotarea[3] + viewport_plotarea[4]) -function gr_legend_pos(sp::Subplot, w, h, viewport_plotarea) - legend_leftw, legend_rightw, legend_textw, x_legend_offset = w - legend_dy, legendh, y_legend_offset = h +function gr_legend_pos(sp::Subplot, leg, viewport_plotarea) s = sp[:legend] - typeof(s) <: Symbol || return gr_legend_pos(s, w, h, viewport_plotarea) + typeof(s) <: Symbol || return gr_legend_pos(s, viewport_plotarea) str = string(s) if str == "best" str = "topright" @@ -584,39 +582,39 @@ function gr_legend_pos(sp::Subplot, w, h, viewport_plotarea) if occursin("right", str) if occursin("outer", str) # As per https://github.com/jheinen/GR.jl/blob/master/src/jlgr.jl#L525 - xpos = viewport_plotarea[2] + x_legend_offset + legend_leftw + ymirror * gr_axis_width(sp, sp[:yaxis]) + xpos = viewport_plotarea[2] + leg.xoffset + leg.leftw + ymirror * gr_axis_width(sp, sp[:yaxis]) else - xpos = viewport_plotarea[2] - legend_rightw - legend_textw - x_legend_offset + xpos = viewport_plotarea[2] - leg.rightw - leg.textw - leg.xoffset end elseif occursin("left", str) if occursin("outer", str) - xpos = viewport_plotarea[1] - !ymirror * gr_axis_width(sp, sp[:yaxis]) - x_legend_offset * 2 - legend_rightw - legend_textw + xpos = viewport_plotarea[1] - !ymirror * gr_axis_width(sp, sp[:yaxis]) - leg.xoffset * 2 - leg.rightw - leg.textw else - xpos = viewport_plotarea[1] + legend_leftw + x_legend_offset + xpos = viewport_plotarea[1] + leg.leftw + leg.xoffset end else - xpos = (viewport_plotarea[2]-viewport_plotarea[1])/2 + viewport_plotarea[1] + legend_leftw - legend_rightw - legend_textw - x_legend_offset * 2 + xpos = (viewport_plotarea[2] - viewport_plotarea[1]) / 2 + viewport_plotarea[1] + leg.leftw - leg.rightw - leg.textw - leg.xoffset * 2 end if occursin("top", str) if s == :outertop - ypos = viewport_plotarea[4] + y_legend_offset + legendh + xmirror * gr_axis_height(sp, sp[:xaxis]) + ypos = viewport_plotarea[4] + leg.yoffset + leg.h + xmirror * gr_axis_height(sp, sp[:xaxis]) else - ypos = viewport_plotarea[4] - y_legend_offset - legend_dy + ypos = viewport_plotarea[4] - leg.yoffset - leg.dy end elseif occursin("bottom", str) if s == :outerbottom - ypos = viewport_plotarea[3] - y_legend_offset - legendh - !xmirror * gr_axis_height(sp, sp[:xaxis]) + ypos = viewport_plotarea[3] - leg.yoffset - leg.h - !xmirror * gr_axis_height(sp, sp[:xaxis]) else - ypos = viewport_plotarea[3] + legendh + y_legend_offset + ypos = viewport_plotarea[3] + leg.yoffset + leg.h end else # Adding min y to shift legend pos to correct graph (#2377) - ypos = (viewport_plotarea[4]-viewport_plotarea[3])/2 + legendh/2 + viewport_plotarea[3] + ypos = (viewport_plotarea[4] - viewport_plotarea[3] + leg.h) / 2 + viewport_plotarea[3] end - (xpos,ypos) + return xpos, ypos end -function gr_legend_pos(v::Tuple{S,T}, w, h, viewport_plotarea) where {S<:Real, T<:Real} +function gr_legend_pos(v::Tuple{S,T}, viewport_plotarea) where {S<:Real, T<:Real} xpos = v[1] * (viewport_plotarea[2] - viewport_plotarea[1]) + viewport_plotarea[1] ypos = v[2] * (viewport_plotarea[4] - viewport_plotarea[3]) + viewport_plotarea[3] (xpos,ypos) @@ -682,15 +680,24 @@ function gr_display(plt::Plot, fmt="") GR.updatews() end - -function gr_set_ticks_font(sp, asym) - mirror = sp[asym][:mirror] +function gr_set_xtickfont(sp) + mirror = gr_set_font( - tickfont(sp[asym]), + tickfont(sp[:xaxis]), sp, - halign = (:left, :hcenter, :right)[sign(sp[asym][:rotation]) + 2], - valign = (mirror ? :bottom : :top), - rotation = sp[asym][:rotation], + halign = (:left, :hcenter, :right)[sign(sp[:xaxis][:rotation]) + 2], + valign = (sp[:xaxis][:mirror] ? :bottom : :top), + rotation = sp[:xaxis][:rotation], + ) +end + +function gr_set_ytickfont(sp) + gr_set_font( + tickfont(sp[:yaxis]), + sp, + halign = (sp[:yaxis][:mirror] ? :left : :right), + valign = (:top, :vcenter, :bottom)[sign(sp[:yaxis][:rotation]) + 2], + rotation = sp[:yaxis][:rotation], ) end @@ -872,7 +879,7 @@ function _update_min_padding!(sp::Subplot{GRBackend}) # Add margin for x and y ticks xticks, yticks = get_ticks(sp, sp[:xaxis]), get_ticks(sp, sp[:yaxis]) if !(xticks in (nothing, false, :none)) - gr_set_ticks_font(sp, :xaxis) + gr_set_xtickfont(sp) l = 0.01 + last(gr_get_ticks_size(xticks, sp[:xaxis][:rotation])) h = 1mm + get_size(sp)[2] * l * px if sp[:xaxis][:mirror] @@ -882,10 +889,10 @@ function _update_min_padding!(sp::Subplot{GRBackend}) end end if !(yticks in (nothing, false, :none)) - gr_set_ticks_font(sp, :yaxis) + gr_set_ytickfont(sp) l = 0.01 + first(gr_get_ticks_size(yticks, sp[:yaxis][:rotation])) w = 1mm + get_size(sp)[1] * l * px - if mirror + if sp[:yaxis][:mirror] rightpad += w else leftpad += w @@ -938,94 +945,15 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) # the viewports for this subplot viewport_subplot = gr_viewport_from_bbox(sp, bbox(sp), w, h, viewport_canvas) viewport_plotarea = gr_viewport_from_bbox(sp, plotarea(sp), w, h, viewport_canvas) - # get data limits - data_lims = gr_xy_axislims(sp) - xy_lims = data_lims - ratio = get_aspect_ratio(sp) - if ratio != :none - if ratio == :equal - ratio = 1 - end - viewport_ratio = (viewport_plotarea[2] - viewport_plotarea[1]) / (viewport_plotarea[4] - viewport_plotarea[3]) - window_ratio = (data_lims[2] - data_lims[1]) / (data_lims[4] - data_lims[3]) / ratio - if window_ratio < viewport_ratio - viewport_center = 0.5 * (viewport_plotarea[1] + viewport_plotarea[2]) - viewport_size = (viewport_plotarea[2] - viewport_plotarea[1]) * window_ratio / viewport_ratio - viewport_plotarea[1] = viewport_center - 0.5 * viewport_size - viewport_plotarea[2] = viewport_center + 0.5 * viewport_size - elseif window_ratio > viewport_ratio - viewport_center = 0.5 * (viewport_plotarea[3] + viewport_plotarea[4]) - viewport_size = (viewport_plotarea[4] - viewport_plotarea[3]) * viewport_ratio / window_ratio - viewport_plotarea[3] = viewport_center - 0.5 * viewport_size - viewport_plotarea[4] = viewport_center + 0.5 * viewport_size - end - end - - # calculate legend size - # has to be done now due to a potential adjustment to the plotarea given an outer legend. - legendn = 0 - legendw = 0 - if sp[:legend] != :none - GR.savestate() - GR.selntran(0) - GR.setscale(0) - if sp[:legendtitle] !== nothing - gr_set_font(legendtitlefont(sp), sp) - tbx, tby = gr_inqtext(0, 0, string(sp[:legendtitle])) - legendw = tbx[3] - tbx[1] - legendn += 1 - end - gr_set_font(legendfont(sp), sp) - for series in series_list(sp) - should_add_to_legend(series) || continue - legendn += 1 - lab = series[:label] - tbx, tby = gr_inqtext(0, 0, string(lab)) - legendw = max(legendw, tbx[3] - tbx[1]) # Holds text width right now - end - - GR.setscale(1) - GR.selntran(1) - GR.restorestate() - end - - legend_width_factor = (viewport_plotarea[2] - viewport_plotarea[1]) / 45 # Determines the width of legend box - legend_textw = legendw - legend_rightw = legend_width_factor - legend_leftw = legend_width_factor * 4 - total_legendw = legend_textw + legend_leftw + legend_rightw - - x_legend_offset = (viewport_plotarea[2] - viewport_plotarea[1]) / 30 - y_legend_offset = (viewport_plotarea[4] - viewport_plotarea[3]) / 30 - - dy = gr_point_mult(sp) * sp[:legendfontsize] * 1.75 - legendh = dy * legendn - leg_str = string(sp[:legend]) - - xaxis, yaxis = sp[:xaxis], sp[:yaxis] - xmirror = xaxis[:guide_position] == :top || (xaxis[:guide_position] == :auto && xaxis[:mirror] == true) - ymirror = yaxis[:guide_position] == :right || (yaxis[:guide_position] == :auto && yaxis[:mirror] == true) - - if occursin("outer", leg_str) - if occursin("right", leg_str) - viewport_plotarea[2] -= total_legendw + x_legend_offset - elseif occursin("left", leg_str) - viewport_plotarea[1] += total_legendw + x_legend_offset + !ymirror * gr_axis_width(sp, sp[:yaxis]) - elseif occursin("top", leg_str) - viewport_plotarea[4] -= legendh + dy + y_legend_offset - elseif occursin("bottom", leg_str) - viewport_plotarea[3] += legendh + dy + y_legend_offset + !xmirror * gr_axis_height(sp, sp[:xaxis]) - end - end - if sp[:legend] == :inline - if sp[:yaxis][:mirror] - viewport_plotarea[1] += legendw - else - viewport_plotarea[2] -= legendw - end - end + # update plotarea + gr_update_viewport_ratio!(viewport_plotarea, sp) + leg = gr_get_legend_geometry(viewport_plotarea, sp) + gr_update_viewport_legend!(viewport_plotarea, sp, leg) + # TODO daschw is this required here? + data_lims = xy_lims = gr_xy_axislims(sp) + # fill in the plot area background bg = plot_color(sp[:background_color_inside]) RecipesPipeline.is3d(sp) || gr_fill_viewport(viewport_plotarea, bg) @@ -1444,7 +1372,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) if !(xticks in (:none, nothing, false)) && xaxis[:showaxis] # x labels flip, mirror = yaxis[:flip], xaxis[:mirror] - gr_set_ticks_font(sp, :xaxis) + gr_set_xtickfont(sp) for (cv, dv) in zip(xticks...) xi, yi = GR.wctondc(cv, sp[:framestyle] == :origin ? 0 : xor(flip, mirror) ? ymax : ymin) gr_text(xi, yi + (mirror ? 1 : -1) * 8e-3 * (xaxis[:tick_direction] == :out ? 1.5 : 1.0), dv) @@ -1454,7 +1382,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) if !(yticks in (:none, nothing, false)) && yaxis[:showaxis] # y labels flip, mirror = xaxis[:flip], yaxis[:mirror] - gr_set_ticks_font(sp, :yaxis) + gr_set_ytickfont(sp) for (cv, dv) in zip(yticks...) xi, yi = GR.wctondc(sp[:framestyle] == :origin ? 0 : xor(flip, mirror) ? xmax : xmin, cv) gr_text(xi + (mirror ? 1 : -1) * 1.5e-2 * (yaxis[:tick_direction] == :out ? 1.5 : 1.0), @@ -1829,84 +1757,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) hascolorbar(sp) && gr_draw_colorbar(cbar, sp, get_clims(sp), viewport_plotarea) # add the legend - if !(sp[:legend] in(:none, :inline)) - GR.savestate() - GR.selntran(0) - GR.setscale(0) - gr_set_font(legendfont(sp), sp) - w = legendw # This is the legend text width - n = legendn - if w > 0 - dy = gr_point_mult(sp) * sp[:legendfontsize] * 1.75 - h = dy*n - xpos, ypos = gr_legend_pos(sp, [legend_leftw, legend_rightw, legend_textw, x_legend_offset, y_legend_offset], [dy, h, y_legend_offset], viewport_plotarea) # Passing legend width components instead of legend text width - GR.setfillintstyle(GR.INTSTYLE_SOLID) - gr_set_fillcolor(sp[:background_color_legend]) - GR.fillrect(xpos - legend_leftw, xpos + legend_textw + legend_rightw, ypos + dy, ypos - dy * n) # Allocating white space for actual legend width here - gr_set_line(1, :solid, sp[:foreground_color_legend], sp) - GR.drawrect(xpos - legend_leftw, xpos + legend_textw + legend_rightw, ypos + dy, ypos - dy * n) # Drawing actual legend width here - i = 0 - if sp[:legendtitle] !== nothing - GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_HALF) - gr_set_font(legendtitlefont(sp), sp) - gr_text(xpos - 0.03 + 0.5*w, ypos, string(sp[:legendtitle])) - ypos -= dy - gr_set_font(legendfont(sp), sp) - end - for series in series_list(sp) - clims = get_clims(sp, series) - should_add_to_legend(series) || continue - st = series[:seriestype] - lc = get_linecolor(series, clims) - gr_set_line(sp[:legendfontsize] / 8, get_linestyle(series), lc, sp) - - if (st == :shape || series[:fillrange] !== nothing) && series[:ribbon] === nothing - fc = get_fillcolor(series, clims) - gr_set_fill(fc) #, series[:fillalpha]) - l, r = xpos - legend_width_factor * 3.5, xpos - legend_width_factor / 2 - b, t = ypos-0.4dy, ypos+0.4dy - x = [l, r, r, l, l] - y = [b, b, t, t, b] - gr_set_transparency(fc, get_fillalpha(series)) - gr_polyline(x, y, GR.fillarea) - lc = get_linecolor(series, clims) - gr_set_transparency(lc, get_linealpha(series)) - gr_set_line(get_linewidth(series), get_linestyle(series), lc, sp) - st == :shape && gr_polyline(x, y) - end - - if st in (:path, :straightline, :path3d) - gr_set_transparency(lc, get_linealpha(series)) - if series[:fillrange] === nothing || series[:ribbon] !== nothing - GR.polyline([xpos - legend_width_factor * 3.5, xpos - legend_width_factor / 2], [ypos, ypos]) - else - GR.polyline([xpos - legend_width_factor * 3.5, xpos - legend_width_factor / 2], [ypos+0.4dy, ypos+0.4dy]) - end - end - - if series[:markershape] != :none - ms = first(series[:markersize]) - msw = first(series[:markerstrokewidth]) - s, sw = if ms > 0 - 0.8 * sp[:legendfontsize], 0.8 * sp[:legendfontsize] * msw / ms - else - 0, 0.8 * sp[:legendfontsize] * msw / 8 - end - gr_draw_markers( - series, xpos - legend_width_factor * 2, ypos, clims, s, sw - ) - end - - lab = series[:label] - GR.settextalign(GR.TEXT_HALIGN_LEFT, GR.TEXT_VALIGN_HALF) - gr_set_textcolor(plot_color(sp[:legendfontcolor])) - gr_text(xpos, ypos, string(lab)) - ypos -= dy - end - end - GR.selntran(1) - GR.restorestate() - end + gr_add_legend(sp, leg, viewport_plotarea) # add annotations GR.savestate() @@ -1933,6 +1784,200 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) end +## Legend + +function gr_add_legend(sp, leg, plotarea) + if !(sp[:legend] in(:none, :inline)) + GR.savestate() + GR.selntran(0) + GR.setscale(0) + gr_set_font(legendfont(sp), sp) + if leg.w > 0 + xpos, ypos = gr_legend_pos(sp, leg, plotarea) # Passing legend width components instead of legend text width + GR.setfillintstyle(GR.INTSTYLE_SOLID) + gr_set_fillcolor(sp[:background_color_legend]) + GR.fillrect( + xpos - leg.leftw, xpos + leg.textw + leg.rightw, + ypos + leg.dy, ypos - leg.h + ) # Allocating white space for actual legend width here + gr_set_line(1, :solid, sp[:foreground_color_legend], sp) + GR.drawrect( + xpos - leg.leftw, xpos + leg.textw + leg.rightw, + ypos + leg.dy, ypos - leg.h + ) # Drawing actual legend width here + i = 0 + if sp[:legendtitle] !== nothing + GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_HALF) + gr_set_font(legendtitlefont(sp), sp) + gr_text(xpos - 0.03 + 0.5 * leg.w, ypos, string(sp[:legendtitle])) + ypos -= leg.dy + gr_set_font(legendfont(sp), sp) + end + for series in series_list(sp) + clims = get_clims(sp, series) + should_add_to_legend(series) || continue + st = series[:seriestype] + lc = get_linecolor(series, clims) + gr_set_line(sp[:legendfontsize] / 8, get_linestyle(series), lc, sp) + + if (st == :shape || series[:fillrange] !== nothing) && series[:ribbon] === nothing + fc = get_fillcolor(series, clims) + gr_set_fill(fc) #, series[:fillalpha]) + l, r = xpos - leg.width_factor * 3.5, xpos - leg.width_factor / 2 + b, t = ypos - 0.4 * leg.dy, ypos + 0.4 * leg.dy + x = [l, r, r, l, l] + y = [b, b, t, t, b] + gr_set_transparency(fc, get_fillalpha(series)) + gr_polyline(x, y, GR.fillarea) + lc = get_linecolor(series, clims) + gr_set_transparency(lc, get_linealpha(series)) + gr_set_line(get_linewidth(series), get_linestyle(series), lc, sp) + st == :shape && gr_polyline(x, y) + end + + if st in (:path, :straightline, :path3d) + gr_set_transparency(lc, get_linealpha(series)) + if series[:fillrange] === nothing || series[:ribbon] !== nothing + GR.polyline( + [xpos - leg.width_factor * 3.5, xpos - leg.width_factor / 2], + [ypos, ypos], + ) + else + GR.polyline( + [xpos - leg.width_factor * 3.5, xpos - leg.width_factor / 2], + [ypos + 0.4 * leg.dy, ypos + 0.4 * leg.dy], + ) + end + end + + if series[:markershape] != :none + ms = first(series[:markersize]) + msw = first(series[:markerstrokewidth]) + s, sw = if ms > 0 + 0.8 * sp[:legendfontsize], 0.8 * sp[:legendfontsize] * msw / ms + else + 0, 0.8 * sp[:legendfontsize] * msw / 8 + end + gr_draw_markers(series, xpos - leg.width_factor * 2, ypos, clims, s, sw) + end + + lab = series[:label] + GR.settextalign(GR.TEXT_HALIGN_LEFT, GR.TEXT_VALIGN_HALF) + gr_set_textcolor(plot_color(sp[:legendfontcolor])) + gr_text(xpos, ypos, string(lab)) + ypos -= leg.dy + end + end + GR.selntran(1) + GR.restorestate() + end +end + +function gr_get_legend_geometry(plotarea, sp) + legendn = 0 + legendw = 0 + if sp[:legend] != :none + GR.savestate() + GR.selntran(0) + GR.setscale(0) + if sp[:legendtitle] !== nothing + gr_set_font(legendtitlefont(sp), sp) + tbx, tby = gr_inqtext(0, 0, string(sp[:legendtitle])) + legendw = tbx[3] - tbx[1] + legendn += 1 + end + gr_set_font(legendfont(sp), sp) + for series in series_list(sp) + should_add_to_legend(series) || continue + legendn += 1 + lab = series[:label] + tbx, tby = gr_inqtext(0, 0, string(lab)) + legendw = max(legendw, tbx[3] - tbx[1]) # Holds text width right now + end + + GR.setscale(1) + GR.selntran(1) + GR.restorestate() + end + + legend_width_factor = (plotarea[2] - plotarea[1]) / 45 # Determines the width of legend box + legend_textw = legendw + legend_rightw = legend_width_factor + legend_leftw = legend_width_factor * 4 + total_legendw = legend_textw + legend_leftw + legend_rightw + + x_legend_offset = (plotarea[2] - plotarea[1]) / 30 + y_legend_offset = (plotarea[4] - plotarea[3]) / 30 + + dy = gr_point_mult(sp) * sp[:legendfontsize] * 1.75 + legendh = dy * legendn + + return ( + w = legendw, + h = legendh, + dy = dy, + leftw = legend_leftw, + textw = legend_textw, + rightw = legend_rightw, + xoffset = x_legend_offset, + yoffset = y_legend_offset, + width_factor = legend_width_factor, + ) +end + +function gr_update_viewport_legend!(plotarea, sp, leg) + leg_str = string(sp[:legend]) + + xaxis, yaxis = sp[:xaxis], sp[:yaxis] + xmirror = xaxis[:guide_position] == :top || (xaxis[:guide_position] == :auto && xaxis[:mirror] == true) + ymirror = yaxis[:guide_position] == :right || (yaxis[:guide_position] == :auto && yaxis[:mirror] == true) + + if occursin("outer", leg_str) + if occursin("right", leg_str) + plotarea[2] -= leg.leftw + leg.textw + leg.rightw + leg.xoffset + elseif occursin("left", leg_str) + plotarea[1] += leg.leftw + leg.textw + leg.rightw + leg.xoffset + !ymirror * gr_axis_width(sp, sp[:yaxis]) + elseif occursin("top", leg_str) + plotarea[4] -= leg.h + leg.dy + leg.yoffset + elseif occursin("bottom", leg_str) + plotarea[3] += leg.h + leg.dy + leg.yoffset + !xmirror * gr_axis_height(sp, sp[:xaxis]) + end + end + if sp[:legend] == :inline + if sp[:yaxis][:mirror] + plotarea[1] += leg.w + else + plotarea[2] -= leg.w + end + end +end + + +## Aspect ratio + +function gr_update_viewport_ratio!(plotarea, sp) + ratio = get_aspect_ratio(sp) + if ratio != :none + data_lims = gr_xy_axislims(sp) + if ratio == :equal + ratio = 1 + end + viewport_ratio = (plotarea[2] - plotarea[1]) / (plotarea[4] - plotarea[3]) + window_ratio = (data_lims[2] - data_lims[1]) / (data_lims[4] - data_lims[3]) / ratio + if window_ratio < viewport_ratio + viewport_center = 0.5 * (plotarea[1] + plotarea[2]) + viewport_size = (plotarea[2] - plotarea[1]) * window_ratio / viewport_ratio + plotarea[1] = viewport_center - 0.5 * viewport_size + plotarea[2] = viewport_center + 0.5 * viewport_size + elseif window_ratio > viewport_ratio + viewport_center = 0.5 * (plotarea[3] + plotarea[4]) + viewport_size = (plotarea[4] - plotarea[3]) * viewport_ratio / window_ratio + plotarea[3] = viewport_center - 0.5 * viewport_size + plotarea[4] = viewport_center + 0.5 * viewport_size + end + end +end + # ---------------------------------------------------------------- for (mime, fmt) in ( From 1e536dda4b9ee989aa8b08635f7bdbb96e1add5d Mon Sep 17 00:00:00 2001 From: Daniel Schwabeneder Date: Fri, 4 Sep 2020 13:43:51 +0200 Subject: [PATCH 3/6] reorganize axes drawing --- src/axes.jl | 594 +++++++++++---------------- src/backends/gr.jl | 983 ++++++++++++++++++--------------------------- 2 files changed, 627 insertions(+), 950 deletions(-) diff --git a/src/axes.jl b/src/axes.jl index 683c62d2..a6729a0d 100644 --- a/src/axes.jl +++ b/src/axes.jl @@ -578,391 +578,269 @@ end # ------------------------------------------------------------------------- # compute the line segments which should be drawn for this axis -function axis_drawing_info(sp::Subplot) - xaxis, yaxis = sp[:xaxis], sp[:yaxis] - xmin, xmax = axis_limits(sp, :x) - ymin, ymax = axis_limits(sp, :y) - xticks = get_ticks(sp, xaxis) - yticks = get_ticks(sp, yaxis) - xminorticks = get_minor_ticks(sp, xaxis, xticks) - yminorticks = get_minor_ticks(sp, yaxis, yticks) - xaxis_segs = Segments(2) - yaxis_segs = Segments(2) - xtick_segs = Segments(2) - ytick_segs = Segments(2) - xgrid_segs = Segments(2) - ygrid_segs = Segments(2) - xminorgrid_segs = Segments(2) - yminorgrid_segs = Segments(2) - xborder_segs = Segments(2) - yborder_segs = Segments(2) +function axis_drawing_info(sp, letter) + # find out which axis we are dealing with + asym = Symbol(letter, :axis) + isy = letter === :y + oletter = isy ? :x : :y + oasym = Symbol(oletter, :axis) + + # get axis objects, ticks and minor ticks + ax, oax = sp[asym], sp[oasym] + amin, amax = axis_limits(sp, letter) + oamin, oamax = axis_limits(sp, oletter) + ticks = get_ticks(sp, ax) + minor_ticks = get_minor_ticks(sp, ax, ticks) + + # initialize the segments + segments = Segments(2) + tick_segments = Segments(2) + grid_segments = Segments(2) + minorgrid_segments = Segments(2) + border_segments = Segments(2) if sp[:framestyle] != :none - # xaxis - y1, y2 = if sp[:framestyle] in (:origin, :zerolines) + oa1, oa2 = if sp[:framestyle] in (:origin, :zerolines) 0.0, 0.0 else - xor(xaxis[:mirror], yaxis[:flip]) ? (ymax, ymin) : (ymin, ymax) + xor(ax[:mirror], oax[:flip]) ? (oamax, oamin) : (oamin, oamax) end - if xaxis[:showaxis] + if ax[:showaxis] if sp[:framestyle] != :grid - push!(xaxis_segs, (xmin, y1), (xmax, y1)) + push!(segments, reverse_if((amin, oa1), isy), reverse_if((amax, oa1), isy)) # don't show the 0 tick label for the origin framestyle - if sp[:framestyle] == :origin && !(xticks in (:none, nothing, false)) && length(xticks) > 1 - showticks = xticks[1] .!= 0 - xticks = (xticks[1][showticks], xticks[2][showticks]) + if sp[:framestyle] == :origin && !(ticks in (:none, nothing, false)) && length(ticks) > 1 + i = findfirst(==(0), ticks[1]) + if i !== nothing + deleteat!(ticks[1], i) + deleteat!(ticks[2], i) + end end end - sp[:framestyle] in (:semi, :box) && push!(xborder_segs, (xmin, y2), (xmax, y2)) # top spine + if sp[:framestyle] in (:semi, :box) # top spine + push!( + border_segments, + reverse_if((amin, oa2), isy), + reverse_if((amax, oa2), isy), + ) + end end - if !(xaxis[:ticks] in (:none, nothing, false)) - f = RecipesPipeline.scale_func(yaxis[:scale]) - invf = RecipesPipeline.inverse_scale_func(yaxis[:scale]) + if !(ax[:ticks] in (:none, nothing, false)) + f = RecipesPipeline.scale_func(oax[:scale]) + invf = RecipesPipeline.inverse_scale_func(oax[:scale]) tick_start, tick_stop = if sp[:framestyle] == :origin - t = invf(f(0) + 0.012 * (f(ymax) - f(ymin))) + t = invf(f(0) + 0.012 * (f(oamax) - f(oamin))) (-t, t) else - ticks_in = xaxis[:tick_direction] == :out ? -1 : 1 - t = invf(f(y1) + 0.012 * (f(y2) - f(y1)) * ticks_in) - (y1, t) + ticks_in = ax[:tick_direction] == :out ? -1 : 1 + t = invf(f(oa1) + 0.012 * (f(oa2) - f(oa1)) * ticks_in) + (oa1, t) end - for xtick in xticks[1] - if xaxis[:showaxis] - push!(xtick_segs, (xtick, tick_start), (xtick, tick_stop)) # bottom tick + for tick in ticks[1] + if ax[:showaxis] + push!( + tick_segments, + reverse_if((tick, tick_start), isy), + reverse_if((tick, tick_stop), isy), + ) + end + if ax[:grid] + push!( + grid_segments, + reverse_if((tick, oamin), isy), + reverse_if((tick, oamax), isy), + ) end - xaxis[:grid] && push!(xgrid_segs, (xtick, ymin), (xtick, ymax)) # vertical grid end - if !(xaxis[:minorticks] in (:none, nothing, false)) || xaxis[:minorgrid] + if !(ax[:minorticks] in (:none, nothing, false)) || ax[:minorgrid] tick_start, tick_stop = if sp[:framestyle] == :origin - t = invf(f(0) + 0.006 * (f(ymax) - f(ymin))) + t = invf(f(0) + 0.006 * (f(oamax) - f(oamin))) (-t, t) else - t = invf(f(y1) + 0.006 * (f(y2) - f(y1)) * ticks_in) - (y1, t) + t = invf(f(oa1) + 0.006 * (f(oa2) - f(oa1)) * ticks_in) + (oa1, t) end - for xtick in xminorticks - if xaxis[:showaxis] - push!(xtick_segs, (xtick, tick_start), (xtick, tick_stop)) # bottom tick + for tick in minor_ticks + if ax[:showaxis] + push!( + tick_segments, + reverse_if((tick, tick_start), isy), + reverse_if((tick, tick_stop), isy), + ) end - xaxis[:minorgrid] && push!(xminorgrid_segs, (xtick, ymin), (xtick, ymax)) # vertical grid - end - end - end - - - # yaxis - x1, x2 = if sp[:framestyle] in (:origin, :zerolines) - 0.0, 0.0 - else - xor(yaxis[:mirror], xaxis[:flip]) ? (xmax, xmin) : (xmin, xmax) - end - if yaxis[:showaxis] - if sp[:framestyle] != :grid - push!(yaxis_segs, (x1, ymin), (x1, ymax)) - # don't show the 0 tick label for the origin framestyle - if sp[:framestyle] == :origin && !(yticks in (:none, nothing,false)) && length(yticks) > 1 - showticks = yticks[1] .!= 0 - yticks = (yticks[1][showticks], yticks[2][showticks]) - end - end - sp[:framestyle] in (:semi, :box) && push!(yborder_segs, (x2, ymin), (x2, ymax)) # right spine - end - if !(yaxis[:ticks] in (:none, nothing, false)) - f = RecipesPipeline.scale_func(xaxis[:scale]) - invf = RecipesPipeline.inverse_scale_func(xaxis[:scale]) - tick_start, tick_stop = if sp[:framestyle] == :origin - t = invf(f(0) + 0.012 * (f(xmax) - f(xmin))) - (-t, t) - else - ticks_in = yaxis[:tick_direction] == :out ? -1 : 1 - t = invf(f(x1) + 0.012 * (f(x2) - f(x1)) * ticks_in) - (x1, t) - end - - for ytick in yticks[1] - if yaxis[:showaxis] - push!(ytick_segs, (tick_start, ytick), (tick_stop, ytick)) # left tick - end - yaxis[:grid] && push!(ygrid_segs, (xmin, ytick), (xmax, ytick)) # horizontal grid - end - - if !(yaxis[:minorticks] in (:none, nothing, false)) || yaxis[:minorgrid] - tick_start, tick_stop = if sp[:framestyle] == :origin - t = invf(f(0) + 0.006 * (f(xmax) - f(xmin))) - (-t, t) - else - t = invf(f(x1) + 0.006 * (f(x2) - f(x1)) * ticks_in) - (x1, t) - end - for ytick in yminorticks - if yaxis[:showaxis] - push!(ytick_segs, (tick_start, ytick), (tick_stop, ytick)) # left tick - end - yaxis[:minorgrid] && push!(yminorgrid_segs, (xmin, ytick), (xmax, ytick)) # horizontal grid - end - end - end - end - - xticks, yticks, xaxis_segs, yaxis_segs, xtick_segs, ytick_segs, xgrid_segs, ygrid_segs, xminorgrid_segs, yminorgrid_segs, xborder_segs, yborder_segs -end - - -function axis_drawing_info_3d(sp::Subplot) - xaxis, yaxis, zaxis = sp[:xaxis], sp[:yaxis], sp[:zaxis] - xmin, xmax = axis_limits(sp, :x) - ymin, ymax = axis_limits(sp, :y) - zmin, zmax = axis_limits(sp, :z) - xticks = get_ticks(sp, xaxis) - yticks = get_ticks(sp, yaxis) - zticks = get_ticks(sp, zaxis) - xminorticks = get_minor_ticks(sp, xaxis, xticks) - yminorticks = get_minor_ticks(sp, yaxis, yticks) - zminorticks = get_minor_ticks(sp, zaxis, zticks) - xaxis_segs = Segments(3) - yaxis_segs = Segments(3) - zaxis_segs = Segments(3) - xtick_segs = Segments(3) - ytick_segs = Segments(3) - ztick_segs = Segments(3) - xgrid_segs = Segments(3) - ygrid_segs = Segments(3) - zgrid_segs = Segments(3) - xminorgrid_segs = Segments(3) - yminorgrid_segs = Segments(3) - zminorgrid_segs = Segments(3) - xborder_segs = Segments(3) - yborder_segs = Segments(3) - zborder_segs = Segments(3) - - if sp[:framestyle] != :none - - # xaxis - y1, y2 = if sp[:framestyle] in (:origin, :zerolines) - 0.0, 0.0 - else - xor(xaxis[:mirror], yaxis[:flip]) ? (ymax, ymin) : (ymin, ymax) - end - z1, z2 = if sp[:framestyle] in (:origin, :zerolines) - 0.0, 0.0 - else - xor(xaxis[:mirror], zaxis[:flip]) ? (zmax, zmin) : (zmin, zmax) - end - if xaxis[:showaxis] - if sp[:framestyle] != :grid - push!(xaxis_segs, (xmin, y1, z1), (xmax, y1, z1)) - # don't show the 0 tick label for the origin framestyle - if sp[:framestyle] == :origin && !(xticks in (:none, nothing, false)) && length(xticks) > 1 - showticks = xticks[1] .!= 0 - xticks = (xticks[1][showticks], xticks[2][showticks]) - end - end - sp[:framestyle] in (:semi, :box) && push!(xborder_segs, (xmin, y2, z2), (xmax, y2, z2)) # top spine - end - if !(xaxis[:ticks] in (:none, nothing, false)) - f = RecipesPipeline.scale_func(yaxis[:scale]) - invf = RecipesPipeline.inverse_scale_func(yaxis[:scale]) - tick_start, tick_stop = if sp[:framestyle] == :origin - t = invf(f(0) + 0.012 * (f(ymax) - f(ymin))) - (-t, t) - else - ticks_in = xaxis[:tick_direction] == :out ? -1 : 1 - t = invf(f(y1) + 0.012 * (f(y2) - f(y1)) * ticks_in) - (y1, t) - end - - for xtick in xticks[1] - if xaxis[:showaxis] - push!(xtick_segs, (xtick, tick_start, z1), (xtick, tick_stop, z1)) # bottom tick - end - if xaxis[:grid] - if sp[:framestyle] in (:origin, :zerolines) - push!(xgrid_segs, (xtick, ymin, 0.0), (xtick, ymax, 0.0)) - push!(xgrid_segs, (xtick, 0.0, zmin), (xtick, 0.0, zmax)) - else - push!(xgrid_segs, (xtick, y1, z1), (xtick, y2, z1)) - push!(xgrid_segs, (xtick, y2, z1), (xtick, y2, z2)) - end - end - end - - if !(xaxis[:minorticks] in (:none, nothing, false)) || xaxis[:minorgrid] - tick_start, tick_stop = if sp[:framestyle] == :origin - t = invf(f(0) + 0.006 * (f(ymax) - f(ymin))) - (-t, t) - else - t = invf(f(y1) + 0.006 * (f(y2) - f(y1)) * ticks_in) - (y1, t) - end - for xtick in xminorticks - if xaxis[:showaxis] - push!(xtick_segs, (xtick, tick_start, z1), (xtick, tick_stop, z1)) # bottom tick - end - if xaxis[:minorgrid] - if sp[:framestyle] in (:origin, :zerolines) - push!(xminorgrid_segs, (xtick, ymin, 0.0), (xtick, ymax, 0.0)) - push!(xminorgrid_segs, (xtick, 0.0, zmin), (xtick, 0.0, zmax)) - else - push!(xminorgrid_segs, (xtick, y1, z1), (xtick, y2, z1)) - push!(xminorgrid_segs, (xtick, y2, z1), (xtick, y2, z2)) - end - end - end - end - end - - - # yaxis - x1, x2 = if sp[:framestyle] in (:origin, :zerolines) - 0.0, 0.0 - else - xor(yaxis[:mirror], xaxis[:flip]) ? (xmin, xmax) : (xmax, xmin) - end - z1, z2 = if sp[:framestyle] in (:origin, :zerolines) - 0.0, 0.0 - else - xor(yaxis[:mirror], zaxis[:flip]) ? (zmax, zmin) : (zmin, zmax) - end - if yaxis[:showaxis] - if sp[:framestyle] != :grid - push!(yaxis_segs, (x1, ymin, z1), (x1, ymax, z1)) - # don't show the 0 tick label for the origin framestyle - if sp[:framestyle] == :origin && !(yticks in (:none, nothing,false)) && length(yticks) > 1 - showticks = yticks[1] .!= 0 - yticks = (yticks[1][showticks], yticks[2][showticks]) - end - end - sp[:framestyle] in (:semi, :box) && push!(yborder_segs, (x2, ymin, z2), (x2, ymax, z2)) # right spine - end - if !(yaxis[:ticks] in (:none, nothing, false)) - f = RecipesPipeline.scale_func(xaxis[:scale]) - invf = RecipesPipeline.inverse_scale_func(xaxis[:scale]) - tick_start, tick_stop = if sp[:framestyle] == :origin - t = invf(f(0) + 0.012 * (f(xmax) - f(xmin))) - (-t, t) - else - ticks_in = yaxis[:tick_direction] == :out ? -1 : 1 - t = invf(f(x1) + 0.012 * (f(x2) - f(x1)) * ticks_in) - (x1, t) - end - - for ytick in yticks[1] - if yaxis[:showaxis] - push!(ytick_segs, (tick_start, ytick, z1), (tick_stop, ytick, z1)) # left tick - end - if yaxis[:grid] - if sp[:framestyle] in (:origin, :zerolines) - push!(ygrid_segs, (xmin, ytick, 0.0), (xmax, ytick, 0.0)) - push!(ygrid_segs, (0.0, ytick, zmin), (0.0, ytick, zmax)) - else - push!(ygrid_segs, (x1, ytick, z1), (x2, ytick, z1)) - push!(ygrid_segs, (x2, ytick, z1), (x2, ytick, z2)) - end - end - end - - if !(yaxis[:minorticks] in (:none, nothing, false)) || yaxis[:minorgrid] - tick_start, tick_stop = if sp[:framestyle] == :origin - t = invf(f(0) + 0.006 * (f(xmax) - f(xmin))) - (-t, t) - else - t = invf(f(x1) + 0.006 * (f(x2) - f(x1)) * ticks_in) - (x1, t) - end - for ytick in yminorticks - if yaxis[:showaxis] - push!(ytick_segs, (tick_start, ytick, z1), (tick_stop, ytick, z1)) # left tick - end - if yaxis[:minorgrid] - if sp[:framestyle] in (:origin, :zerolines) - push!(yminorgrid_segs, (xmin, ytick, 0.0), (xmax, ytick, 0.0)) - push!(yminorgrid_segs, (0.0, ytick, zmin), (0.0, ytick, zmax)) - else - push!(yminorgrid_segs, (x1, ytick, z1), (x2, ytick, z1)) - push!(yminorgrid_segs, (x2, ytick, z1), (x2, ytick, z2)) - end - end - end - end - end - - - # zaxis - x1, x2 = if sp[:framestyle] in (:origin, :zerolines) - 0.0, 0.0 - else - xor(zaxis[:mirror], xaxis[:flip]) ? (xmax, xmin) : (xmin, xmax) - end - y1, y2 = if sp[:framestyle] in (:origin, :zerolines) - 0.0, 0.0 - else - xor(zaxis[:mirror], yaxis[:flip]) ? (ymax, ymin) : (ymin, ymax) - end - if zaxis[:showaxis] - if sp[:framestyle] != :grid - push!(zaxis_segs, (x1, y1, zmin), (x1, y1, zmax)) - # don't show the 0 tick label for the origin framestyle - if sp[:framestyle] == :origin && !(zticks in (:none, nothing,false)) && length(zticks) > 1 - showticks = zticks[1] .!= 0 - zticks = (zticks[1][showticks], zticks[2][showticks]) - end - end - sp[:framestyle] in (:semi, :box) && push!(zborder_segs, (x2, y2, zmin), (x2, y2, zmax)) - end - if !(zaxis[:ticks] in (:none, nothing, false)) - f = RecipesPipeline.scale_func(xaxis[:scale]) - invf = RecipesPipeline.inverse_scale_func(xaxis[:scale]) - tick_start, tick_stop = if sp[:framestyle] == :origin - t = invf(f(0) + 0.012 * (f(ymax) - f(ymin))) - (-t, t) - else - ticks_in = zaxis[:tick_direction] == :out ? -1 : 1 - t = invf(f(y1) + 0.012 * (f(y2) - f(y1)) * ticks_in) - (y1, t) - end - - for ztick in zticks[1] - if zaxis[:showaxis] - push!(ztick_segs, (x1, tick_start, ztick), (x1, tick_stop, ztick)) # left tick - end - if zaxis[:grid] - if sp[:framestyle] in (:origin, :zerolines) - push!(zgrid_segs, (xmin, 0.0, ztick), (xmax, 0.0, ztick)) - push!(ygrid_segs, (0.0, ymin, ztick), (0.0, ymax, ztick)) - else - push!(ygrid_segs, (x1, y1, ztick), (x1, y2, ztick)) - push!(ygrid_segs, (x1, y2, ztick), (x2, y2, ztick)) - end - end - end - - if !(zaxis[:minorticks] in (:none, nothing, false)) || zaxis[:minorgrid] - tick_start, tick_stop = if sp[:framestyle] == :origin - t = invf(f(0) + 0.006 * (f(ymax) - f(ymin))) - (-t, t) - else - t = invf(f(y1) + 0.006 * (f(y2) - f(y1)) * ticks_in) - (y1, t) - end - for ztick in zminorticks - if zaxis[:showaxis] - push!(ztick_segs, (x1, tick_start, ztick), (x1, tick_stop, ztick)) # left tick - end - if zaxis[:minorgrid] - if sp[:framestyle] in (:origin, :zerolines) - push!(zminorgrid_segs, (xmin, 0.0, ztick), (xmax, 0.0, ztick)) - push!(zminorgrid_segs, (0.0, ymin, ztick), (0.0, ymax, ztick)) - else - push!(zminorgrid_segs, (x1, y1, ztick), (x1, y2, ztick)) - push!(zminorgrid_segs, (x1, y2, ztick), (x2, y2, ztick)) - end + if ax[:minorgrid] + push!( + minorgrid_segments, + reverse_if((tick, oamin), isy), + reverse_if((tick, oamax), isy), + ) end end end end end - xticks, yticks, zticks, xaxis_segs, yaxis_segs, zaxis_segs, xtick_segs, ytick_segs, ztick_segs, xgrid_segs, ygrid_segs, zgrid_segs, xminorgrid_segs, yminorgrid_segs, zminorgrid_segs, xborder_segs, yborder_segs, zborder_segs + return ( + ticks = ticks, + segments = segments, + tick_segments = tick_segments, + grid_segments = grid_segments, + minorgrid_segments = minorgrid_segments, + border_segments = border_segments + ) end + +function sort_3d_axes(a, b, c, letter) + if letter === :x + a, b, c + elseif letter === :y + b, a, c + else + c, b, a + end +end + +function axis_drawing_info_3d(sp, letter) + near_letter = letter in (:x, :z) ? :y : :x + far_letter = letter in (:x, :y) ? :z : :x + + ax = sp[Symbol(letter, :axis)] + nax = sp[Symbol(near_letter, :axis)] + fax = sp[Symbol(far_letter, :axis)] + + amin, amax = axis_limits(sp, letter) + namin, namax = axis_limits(sp, near_letter) + famin, famax = axis_limits(sp, far_letter) + + ticks = get_ticks(sp, ax) + minor_ticks = get_minor_ticks(sp, ax, ticks) + + # initialize the segments + segments = Segments(3) + tick_segments = Segments(3) + grid_segments = Segments(3) + minorgrid_segments = Segments(3) + border_segments = Segments(3) + + + if sp[:framestyle] != :none# && letter === :x + na0, na1 = if sp[:framestyle] in (:origin, :zerolines) + 0, 0 + else + # reverse_if((namin, namax), xor(ax[:mirror], nax[:flip])) + reverse_if(reverse_if((namin, namax), letter === :y), xor(ax[:mirror], nax[:flip])) + end + fa0, fa1 = if sp[:framestyle] in (:origin, :zerolines) + 0, 0 + else + reverse_if((famin, famax), xor(ax[:mirror], fax[:flip])) + end + if ax[:showaxis] + if sp[:framestyle] != :grid + push!( + segments, + sort_3d_axes(amin, na0, fa0, letter), + sort_3d_axes(amax, na0, fa0, letter), + ) + # don't show the 0 tick label for the origin framestyle + if sp[:framestyle] == :origin && !(ticks in (:none, nothing, false)) && length(ticks) > 1 + i0 = findfirst(==(0), ticks[1]) + if ind !== nothing + deleteat!(ticks[1], i0) + deleteat!(ticks[2], i0) + end + end + end + if sp[:framestyle] in (:semi, :box) + push!( + border_segments, + sort_3d_axes(amin, na1, fa1, letter), + sort_3d_axes(amax, na1, fa1, letter), + ) + end + end + # TODO this can be simplified, we do almost the same thing twice for grid and minorgrid + if !(ax[:ticks] in (:none, nothing, false)) + f = RecipesPipeline.scale_func(nax[:scale]) + invf = RecipesPipeline.inverse_scale_func(nax[:scale]) + tick_start, tick_stop = if sp[:framestyle] == :origin + t = invf(f(0) + 0.012 * (f(namax) - f(namin))) + (-t, t) + else + ticks_in = ax[:tick_direction] == :out ? -1 : 1 + t = invf(f(na0) + 0.012 * (f(na1) - f(na0)) * ticks_in) + (na0, t) + end + + ga0, ga1 = sp[:framestyle] in (:origin, :zerolines) ? (namin, namax) : (na0, na1) + for tick in ticks[1] + if ax[:showaxis] + push!( + tick_segments, + sort_3d_axes(tick, tick_start, fa0, letter), + sort_3d_axes(tick, tick_stop, fa0, letter), + ) + end + if ax[:grid] + push!( + grid_segments, + sort_3d_axes(tick, ga0, fa0, letter), + sort_3d_axes(tick, ga1, fa0, letter), + ) + push!( + grid_segments, + sort_3d_axes(tick, ga1, fa0, letter), + sort_3d_axes(tick, ga1, fa1, letter), + ) + end + end + + if !(ax[:minorticks] in (:none, nothing, false)) || ax[:minorgrid] + tick_start, tick_stop = if sp[:framestyle] == :origin + t = invf(f(0) + 0.006 * (f(namax) - f(namin))) + (-t, t) + else + t = invf(f(na0) + 0.006 * (f(na1) - f(na0)) * ticks_in) + (na0, t) + end + for tick in minorticks + if ax[:showaxis] + push!( + tick_segments, + sort_3d_axes(tick, tick_start, fa0, letter), + sort_3d_axes(tick, tick_stop, fa0, letter), + ) + end + if ax[:minorgrid] + push!( + minorgrid_segments, + sort_3d_axes(tick, ga0, fa0, letter), + sort_3d_axes(tick, ga1, fa0, letter), + ) + push!( + minorgrid_segments, + sort_3d_axes(tick, ga1, fa0, letter), + sort_3d_axes(tick, ga1, fa1, letter), + ) + end + end + end + end + end + + return ( + ticks = ticks, + segments = segments, + tick_segments = tick_segments, + grid_segments = grid_segments, + minorgrid_segments = minorgrid_segments, + border_segments = border_segments + ) +end + +reverse_if(x, cond) = cond ? reverse(x) : x +axis_tuple(x, y, letter) = reverse_if((x, y), letter === :y) + +axes_shift(t, i) = i % 3 == 0 ? t : i % 3 == 1 ? (t[3], t[1], t[2]) : (t[2], t[3], t[1]) \ No newline at end of file diff --git a/src/backends/gr.jl b/src/backends/gr.jl index b1914ecf..266e28b0 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -567,58 +567,6 @@ end gr_view_xcenter(viewport_plotarea) = 0.5 * (viewport_plotarea[1] + viewport_plotarea[2]) gr_view_ycenter(viewport_plotarea) = 0.5 * (viewport_plotarea[3] + viewport_plotarea[4]) -function gr_legend_pos(sp::Subplot, leg, viewport_plotarea) - s = sp[:legend] - typeof(s) <: Symbol || return gr_legend_pos(s, viewport_plotarea) - str = string(s) - if str == "best" - str = "topright" - end - if occursin("outer", str) - xaxis, yaxis = sp[:xaxis], sp[:yaxis] - xmirror = xaxis[:guide_position] == :top || (xaxis[:guide_position] == :auto && xaxis[:mirror] == true) - ymirror = yaxis[:guide_position] == :right || (yaxis[:guide_position] == :auto && yaxis[:mirror] == true) - end - if occursin("right", str) - if occursin("outer", str) - # As per https://github.com/jheinen/GR.jl/blob/master/src/jlgr.jl#L525 - xpos = viewport_plotarea[2] + leg.xoffset + leg.leftw + ymirror * gr_axis_width(sp, sp[:yaxis]) - else - xpos = viewport_plotarea[2] - leg.rightw - leg.textw - leg.xoffset - end - elseif occursin("left", str) - if occursin("outer", str) - xpos = viewport_plotarea[1] - !ymirror * gr_axis_width(sp, sp[:yaxis]) - leg.xoffset * 2 - leg.rightw - leg.textw - else - xpos = viewport_plotarea[1] + leg.leftw + leg.xoffset - end - else - xpos = (viewport_plotarea[2] - viewport_plotarea[1]) / 2 + viewport_plotarea[1] + leg.leftw - leg.rightw - leg.textw - leg.xoffset * 2 - end - if occursin("top", str) - if s == :outertop - ypos = viewport_plotarea[4] + leg.yoffset + leg.h + xmirror * gr_axis_height(sp, sp[:xaxis]) - else - ypos = viewport_plotarea[4] - leg.yoffset - leg.dy - end - elseif occursin("bottom", str) - if s == :outerbottom - ypos = viewport_plotarea[3] - leg.yoffset - leg.h - !xmirror * gr_axis_height(sp, sp[:xaxis]) - else - ypos = viewport_plotarea[3] + leg.yoffset + leg.h - end - else - # Adding min y to shift legend pos to correct graph (#2377) - ypos = (viewport_plotarea[4] - viewport_plotarea[3] + leg.h) / 2 + viewport_plotarea[3] - end - return xpos, ypos -end - -function gr_legend_pos(v::Tuple{S,T}, viewport_plotarea) where {S<:Real, T<:Real} - xpos = v[1] * (viewport_plotarea[2] - viewport_plotarea[1]) + viewport_plotarea[1] - ypos = v[2] * (viewport_plotarea[4] - viewport_plotarea[3]) + viewport_plotarea[3] - (xpos,ypos) -end # -------------------------------------------------------------------------------------- @@ -680,24 +628,22 @@ function gr_display(plt::Plot, fmt="") GR.updatews() end -function gr_set_xtickfont(sp) - mirror = +function gr_set_tickfont(sp, letter) + axis = sp[Symbol(letter, :axis)] + if letter === :x || (RecipesPipeline.is3d(sp) && letter === :y) + halign = (:left, :hcenter, :right)[sign(axis[:rotation]) + 2] + valign = (axis[:mirror] ? :bottom : :top) + else + halign = (axis[:mirror] ? :left : :right) + valign = (:top, :vcenter, :bottom)[sign(axis[:rotation]) + 2] + end gr_set_font( - tickfont(sp[:xaxis]), + tickfont(axis), sp, - halign = (:left, :hcenter, :right)[sign(sp[:xaxis][:rotation]) + 2], - valign = (sp[:xaxis][:mirror] ? :bottom : :top), - rotation = sp[:xaxis][:rotation], - ) -end - -function gr_set_ytickfont(sp) - gr_set_font( - tickfont(sp[:yaxis]), - sp, - halign = (sp[:yaxis][:mirror] ? :left : :right), - valign = (:top, :vcenter, :bottom)[sign(sp[:yaxis][:rotation]) + 2], - rotation = sp[:yaxis][:rotation], + halign = halign, + valign = valign, + rotation = axis[:rotation], + color = axis[:tickfontcolor], ) end @@ -879,7 +825,7 @@ function _update_min_padding!(sp::Subplot{GRBackend}) # Add margin for x and y ticks xticks, yticks = get_ticks(sp, sp[:xaxis]), get_ticks(sp, sp[:yaxis]) if !(xticks in (nothing, false, :none)) - gr_set_xtickfont(sp) + gr_set_tickfont(sp, :x) l = 0.01 + last(gr_get_ticks_size(xticks, sp[:xaxis][:rotation])) h = 1mm + get_size(sp)[2] * l * px if sp[:xaxis][:mirror] @@ -889,7 +835,7 @@ function _update_min_padding!(sp::Subplot{GRBackend}) end end if !(yticks in (nothing, false, :none)) - gr_set_ytickfont(sp) + gr_set_tickfont(sp, :y) l = 0.01 + first(gr_get_ticks_size(yticks, sp[:yaxis][:rotation])) w = 1mm + get_size(sp)[1] * l * px if sp[:yaxis][:mirror] @@ -946,7 +892,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) viewport_subplot = gr_viewport_from_bbox(sp, bbox(sp), w, h, viewport_canvas) viewport_plotarea = gr_viewport_from_bbox(sp, plotarea(sp), w, h, viewport_canvas) - # update plotarea + # update viewport_plotarea gr_update_viewport_ratio!(viewport_plotarea, sp) leg = gr_get_legend_geometry(viewport_plotarea, sp) gr_update_viewport_legend!(viewport_plotarea, sp, leg) @@ -955,8 +901,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) data_lims = xy_lims = gr_xy_axislims(sp) # fill in the plot area background - bg = plot_color(sp[:background_color_inside]) - RecipesPipeline.is3d(sp) || gr_fill_viewport(viewport_plotarea, bg) + gr_fill_plotarea(sp, viewport_plotarea) # reduced from before... set some flags based on the series in this subplot # TODO: can these be generic flags? @@ -968,7 +913,6 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) for series in series_list(sp) st = series[:seriestype] if st in (:heatmap, :image) - outside_ticks = true x, y = heatmap_edges(series[:x], sp[:xaxis][:scale], series[:y], sp[:yaxis][:scale], size(series[:z])) xy_lims = x[1], x[end], y[1], y[end] expand_extrema!(sp[:xaxis], x) @@ -988,423 +932,10 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) zaxis = sp[:zaxis] # set the scale flags and window - xmin, xmax, ymin, ymax = data_lims - scaleop = 0 - xtick, ytick = 1, 1 - if xmax > xmin && ymax > ymin - # NOTE: for log axes, the major_x and major_y - if non-zero (omit labels) - control the minor grid lines (1 = draw 9 minor grid lines, 2 = no minor grid lines) - # NOTE: for log axes, the x_tick and y_tick - if non-zero (omit axes) - only affect the output appearance (1 = nomal, 2 = scientiic notation) - xaxis[:scale] == :log10 && (scaleop |= GR.OPTION_X_LOG) - yaxis[:scale] == :log10 && (scaleop |= GR.OPTION_Y_LOG) - xaxis[:flip] && (scaleop |= GR.OPTION_FLIP_X) - yaxis[:flip] && (scaleop |= GR.OPTION_FLIP_Y) - if scaleop & GR.OPTION_X_LOG == 0 - majorx = 1 #5 - xtick = GR.tick(xmin, xmax) / majorx - else - # log axis - xtick = 2 # scientific notation - majorx = 2 # no minor grid lines - end - if scaleop & GR.OPTION_Y_LOG == 0 - majory = 1 #5 - ytick = GR.tick(ymin, ymax) / majory - else - # log axis - ytick = 2 # scientific notation - majory = 2 # no minor grid lines - end - - # NOTE: setwindow sets the "data coordinate" limits of the current "viewport" - GR.setwindow(xmin, xmax, ymin, ymax) - GR.setscale(scaleop) - end + gr_set_window(sp) # draw the axes - gr_set_font(tickfont(xaxis), sp) - GR.setlinewidth(sp.plt[:thickness_scaling]) - - if RecipesPipeline.is3d(sp) - zmin, zmax = axis_limits(sp, :z) - GR.setspace(zmin, zmax, round.(Int, sp[:camera])...) - - xticks, yticks, zticks, xaxis_segs, yaxis_segs, zaxis_segs, xtick_segs, ytick_segs, ztick_segs, xgrid_segs, ygrid_segs, zgrid_segs, xminorgrid_segs, yminorgrid_segs, zminorgrid_segs, xborder_segs, yborder_segs, zborder_segs = axis_drawing_info_3d(sp) - - # fill the plot area - gr_set_fill(sp[:background_color_inside]) - plot_area_x = [xmin, xmin, xmin, xmax, xmax, xmax, xmin] - plot_area_y = [ymin, ymin, ymax, ymax, ymax, ymin, ymin] - plot_area_z = [zmin, zmax, zmax, zmax, zmin, zmin, zmin] - x_bg, y_bg = RecipesPipeline.unzip(GR.wc3towc.(plot_area_x, plot_area_y, plot_area_z)) - GR.fillarea(x_bg, y_bg) - - # draw the grid lines - if xaxis[:grid] - gr_set_line( - xaxis[:gridlinewidth], - xaxis[:gridstyle], - xaxis[:foreground_color_grid], - sp - ) - gr_set_transparency(xaxis[:foreground_color_grid], xaxis[:gridalpha]) - gr_polyline3d(coords(xgrid_segs)...) - end - if yaxis[:grid] - gr_set_line( - yaxis[:gridlinewidth], - yaxis[:gridstyle], - yaxis[:foreground_color_grid], - sp - ) - gr_set_transparency(yaxis[:foreground_color_grid], yaxis[:gridalpha]) - gr_polyline3d(coords(ygrid_segs)...) - end - if zaxis[:grid] - gr_set_line( - zaxis[:gridlinewidth], - zaxis[:gridstyle], - zaxis[:foreground_color_grid], - sp - ) - gr_set_transparency(zaxis[:foreground_color_grid], zaxis[:gridalpha]) - gr_polyline3d(coords(zgrid_segs)...) - end - - if xaxis[:minorgrid] - gr_set_line( - xaxis[:minorgridlinewidth], - xaxis[:minorgridstyle], - xaxis[:foreground_color_minor_grid], - sp - ) - gr_set_transparency(xaxis[:foreground_color_minor_grid], xaxis[:minorgridalpha]) - gr_polyline3d(coords(xminorgrid_segs)...) - end - if yaxis[:minorgrid] - gr_set_line( - yaxis[:minorgridlinewidth], - yaxis[:minorgridstyle], - yaxis[:foreground_color_minor_grid], - sp - ) - gr_set_transparency(yaxis[:foreground_color_minor_grid], yaxis[:minorgridalpha]) - gr_polyline3d(coords(yminorgrid_segs)...) - end - if zaxis[:minorgrid] - gr_set_line( - zaxis[:minorgridlinewidth], - zaxis[:minorgridstyle], - zaxis[:foreground_color_minor_grid], - sp - ) - gr_set_transparency(zaxis[:foreground_color_minor_grid], zaxis[:minorgridalpha]) - gr_polyline3d(coords(zminorgrid_segs)...) - end - gr_set_transparency(1.0) - - # axis lines - if xaxis[:showaxis] - gr_set_line(1, :solid, xaxis[:foreground_color_border], sp) - GR.setclip(0) - gr_polyline3d(coords(xaxis_segs)...) - end - if yaxis[:showaxis] - gr_set_line(1, :solid, yaxis[:foreground_color_border], sp) - GR.setclip(0) - gr_polyline3d(coords(yaxis_segs)...) - end - if zaxis[:showaxis] - gr_set_line(1, :solid, zaxis[:foreground_color_border], sp) - GR.setclip(0) - gr_polyline3d(coords(zaxis_segs)...) - end - GR.setclip(1) - - # axis ticks - if xaxis[:showaxis] - if sp[:framestyle] in (:zerolines, :grid) - gr_set_line(1, :solid, xaxis[:foreground_color_grid], sp) - gr_set_transparency( - xaxis[:foreground_color_grid], - xaxis[:tick_direction] == :out ? xaxis[:gridalpha] : 0 - ) - else - gr_set_line(1, :solid, xaxis[:foreground_color_axis], sp) - end - GR.setclip(0) - gr_polyline3d(coords(xtick_segs)...) - end - if yaxis[:showaxis] - if sp[:framestyle] in (:zerolines, :grid) - gr_set_line(1, :solid, yaxis[:foreground_color_grid], sp) - gr_set_transparency( - yaxis[:foreground_color_grid], - yaxis[:tick_direction] == :out ? yaxis[:gridalpha] : 0 - ) - else - gr_set_line(1, :solid, yaxis[:foreground_color_axis], sp) - end - GR.setclip(0) - gr_polyline3d(coords(ytick_segs)...) - end - if zaxis[:showaxis] - if sp[:framestyle] in (:zerolines, :grid) - gr_set_line(1, :solid, zaxis[:foreground_color_grid], sp) - gr_set_transparency( - zaxis[:foreground_color_grid], - zaxis[:tick_direction] == :out ? zaxis[:gridalpha] : 0 - ) - else - gr_set_line(1, :solid, zaxis[:foreground_color_axis], sp) - end - GR.setclip(0) - gr_polyline3d(coords(ztick_segs)...) - end - GR.setclip(1) - - # tick marks - if !(xticks in (:none, nothing, false)) && xaxis[:showaxis] - # x labels - gr_set_font( - tickfont(xaxis), - halign = (:left, :hcenter, :right)[sign(xaxis[:rotation]) + 2], - valign = (xaxis[:mirror] ? :bottom : :top), - rotation = xaxis[:rotation], - color = xaxis[:tickfontcolor], - sp - ) - yt = if sp[:framestyle] == :origin - 0 - elseif xor(xaxis[:mirror], yaxis[:flip]) - ymax - else - ymin - end - zt = if sp[:framestyle] == :origin - 0 - elseif xor(xaxis[:mirror], zaxis[:flip]) - zmax - else - zmin - end - for (cv, dv) in zip(xticks...) - xi, yi = gr_w3tondc(cv, yt, zt) - xi += (yaxis[:mirror] ? 1 : -1) * 1.5e-2 * (xaxis[:tick_direction] == :out ? 1.5 : 1.0) - yi += (xaxis[:mirror] ? 1 : -1) * 8e-3 * (xaxis[:tick_direction] == :out ? 1.5 : 1.0) - gr_text(xi, yi, dv) - end - end - - if !(yticks in (:none, nothing, false)) && yaxis[:showaxis] - # y labels - gr_set_font( - tickfont(yaxis), - halign = (:left, :hcenter, :right)[sign(yaxis[:rotation]) + 2], - valign = (yaxis[:mirror] ? :bottom : :top), - rotation = yaxis[:rotation], - color = yaxis[:tickfontcolor], - sp - ) - xt = if sp[:framestyle] == :origin - 0 - elseif xor(yaxis[:mirror], xaxis[:flip]) - xmin - else - xmax - end - zt = if sp[:framestyle] == :origin - 0 - elseif xor(yaxis[:mirror], zaxis[:flip]) - zmax - else - zmin - end - for (cv, dv) in zip(yticks...) - xi, yi = gr_w3tondc(xt, cv, zt) - gr_text(xi + (yaxis[:mirror] ? -1 : 1) * 1.5e-2 * (yaxis[:tick_direction] == :out ? 1.5 : 1.0), - yi + (yaxis[:mirror] ? 1 : -1) * 8e-3 * (yaxis[:tick_direction] == :out ? 1.5 : 1.0), - dv) - end - end - - if !(zticks in (:none, nothing, false)) && zaxis[:showaxis] - # z labels - gr_set_font( - tickfont(zaxis), - halign = (zaxis[:mirror] ? :left : :right), - valign = (:top, :vcenter, :bottom)[sign(zaxis[:rotation]) + 2], - rotation = zaxis[:rotation], - color = zaxis[:tickfontcolor], - sp - ) - xt = if sp[:framestyle] == :origin - 0 - elseif xor(zaxis[:mirror], xaxis[:flip]) - xmax - else - xmin - end - yt = if sp[:framestyle] == :origin - 0 - elseif xor(zaxis[:mirror], yaxis[:flip]) - ymax - else - ymin - end - for (cv, dv) in zip(zticks...) - xi, yi = gr_w3tondc(xt, yt, cv) - gr_text(xi + (zaxis[:mirror] ? 1 : -1) * 1.5e-2 * (zaxis[:tick_direction] == :out ? 1.5 : 1.0), - yi, dv) - end - end - # - # # border - # intensity = sp[:framestyle] == :semi ? 0.5 : 1.0 - # if sp[:framestyle] in (:box, :semi) - # gr_set_line(intensity, :solid, xaxis[:foreground_color_border], sp) - # gr_set_transparency(xaxis[:foreground_color_border], intensity) - # gr_polyline3d(coords(xborder_segs)...) - # gr_set_line(intensity, :solid, yaxis[:foreground_color_border], sp) - # gr_set_transparency(yaxis[:foreground_color_border], intensity) - # gr_polyline3d(coords(yborder_segs)...) - # end - - elseif ispolar(sp) - r = gr_set_viewport_polar(viewport_plotarea) - #rmin, rmax = GR.adjustrange(ignorenan_minimum(r), ignorenan_maximum(r)) - rmin, rmax = axis_limits(sp, :y) - gr_polaraxes(rmin, rmax, sp) - - elseif draw_axes - if xmax > xmin && ymax > ymin - GR.setwindow(xmin, xmax, ymin, ymax) - end - - xticks, yticks, xspine_segs, yspine_segs, xtick_segs, ytick_segs, xgrid_segs, ygrid_segs, xminorgrid_segs, yminorgrid_segs, xborder_segs, yborder_segs = axis_drawing_info(sp) - - # draw the grid lines - if xaxis[:grid] - # GR.grid(xtick, ytick, 0, 0, majorx, majory) - gr_set_line( - xaxis[:gridlinewidth], - xaxis[:gridstyle], - xaxis[:foreground_color_grid], - sp - ) - gr_set_transparency(xaxis[:foreground_color_grid], xaxis[:gridalpha]) - gr_polyline(coords(xgrid_segs)...) - end - if yaxis[:grid] - gr_set_line( - yaxis[:gridlinewidth], - yaxis[:gridstyle], - yaxis[:foreground_color_grid], - sp - ) - gr_set_transparency(yaxis[:foreground_color_grid], yaxis[:gridalpha]) - gr_polyline(coords(ygrid_segs)...) - end - if xaxis[:minorgrid] - gr_set_line( - xaxis[:minorgridlinewidth], - xaxis[:minorgridstyle], - xaxis[:foreground_color_minor_grid], - sp - ) - gr_set_transparency(xaxis[:foreground_color_minor_grid], xaxis[:minorgridalpha]) - gr_polyline(coords(xminorgrid_segs)...) - end - if yaxis[:minorgrid] - gr_set_line( - yaxis[:minorgridlinewidth], - yaxis[:minorgridstyle], - yaxis[:foreground_color_minor_grid], - sp - ) - gr_set_transparency(yaxis[:foreground_color_minor_grid], yaxis[:minorgridalpha]) - gr_polyline(coords(yminorgrid_segs)...) - end - gr_set_transparency(1.0) - - # axis lines - if xaxis[:showaxis] - gr_set_line(1, :solid, xaxis[:foreground_color_border], sp) - GR.setclip(0) - gr_polyline(coords(xspine_segs)...) - end - if yaxis[:showaxis] - gr_set_line(1, :solid, yaxis[:foreground_color_border], sp) - GR.setclip(0) - gr_polyline(coords(yspine_segs)...) - end - GR.setclip(1) - - # axis ticks - if xaxis[:showaxis] - if sp[:framestyle] in (:zerolines, :grid) - gr_set_line(1, :solid, xaxis[:foreground_color_grid], sp) - gr_set_transparency( - xaxis[:foreground_color_grid], - xaxis[:tick_direction] == :out ? xaxis[:gridalpha] : 0 - ) - else - gr_set_line(1, :solid, xaxis[:foreground_color_axis], sp) - end - GR.setclip(0) - gr_polyline(coords(xtick_segs)...) - end - if yaxis[:showaxis] - if sp[:framestyle] in (:zerolines, :grid) - gr_set_line(1, :solid, yaxis[:foreground_color_grid], sp) - gr_set_transparency( - yaxis[:foreground_color_grid], - yaxis[:tick_direction] == :out ? yaxis[:gridalpha] : 0 - ) - else - gr_set_line(1, :solid, yaxis[:foreground_color_axis], sp) - end - GR.setclip(0) - gr_polyline(coords(ytick_segs)...) - end - GR.setclip(1) - - # tick marks - if !(xticks in (:none, nothing, false)) && xaxis[:showaxis] - # x labels - flip, mirror = yaxis[:flip], xaxis[:mirror] - gr_set_xtickfont(sp) - for (cv, dv) in zip(xticks...) - xi, yi = GR.wctondc(cv, sp[:framestyle] == :origin ? 0 : xor(flip, mirror) ? ymax : ymin) - gr_text(xi, yi + (mirror ? 1 : -1) * 8e-3 * (xaxis[:tick_direction] == :out ? 1.5 : 1.0), dv) - end - end - - if !(yticks in (:none, nothing, false)) && yaxis[:showaxis] - # y labels - flip, mirror = xaxis[:flip], yaxis[:mirror] - gr_set_ytickfont(sp) - for (cv, dv) in zip(yticks...) - xi, yi = GR.wctondc(sp[:framestyle] == :origin ? 0 : xor(flip, mirror) ? xmax : xmin, cv) - gr_text(xi + (mirror ? 1 : -1) * 1.5e-2 * (yaxis[:tick_direction] == :out ? 1.5 : 1.0), - yi, dv) - - end - end - - # border - intensity = sp[:framestyle] == :semi ? 0.5 : 1 - if sp[:framestyle] in (:box, :semi) - GR.setclip(0) - gr_set_line(intensity, :solid, xaxis[:foreground_color_border], sp) - gr_set_transparency(xaxis[:foreground_color_border], intensity) - gr_polyline(coords(xborder_segs)...) - gr_set_line(intensity, :solid, yaxis[:foreground_color_border], sp) - gr_set_transparency(yaxis[:foreground_color_border], intensity) - gr_polyline(coords(yborder_segs)...) - GR.setclip(1) - end - end - # end + gr_draw_axes(sp, viewport_plotarea) # add the guides GR.savestate() @@ -1424,85 +955,9 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) GR.settextalign(halign, GR.TEXT_VALIGN_TOP) gr_text(xpos, viewport_subplot[4], sp[:title]) end - if RecipesPipeline.is3d(sp) - if xaxis[:guide] != "" - gr_set_font( - guidefont(xaxis), - halign = (:left, :hcenter, :right)[sign(xaxis[:rotation]) + 2], - valign = (xaxis[:mirror] ? :bottom : :top), - rotation = xaxis[:rotation], - sp - ) - yg = xor(xaxis[:mirror], yaxis[:flip]) ? ymax : ymin - zg = xor(xaxis[:mirror], zaxis[:flip]) ? zmax : zmin - xg = (xmin + xmax) / 2 - xndc, yndc = gr_w3tondc(xg, yg, zg) - h = gr_axis_height(sp, xaxis) - gr_text(xndc - h, yndc - h, xaxis[:guide]) - end - - if yaxis[:guide] != "" - gr_set_font( - guidefont(yaxis), - halign = (:left, :hcenter, :right)[sign(yaxis[:rotation]) + 2], - valign = (yaxis[:mirror] ? :bottom : :top), - rotation = yaxis[:rotation], - sp - ) - xg = xor(yaxis[:mirror], xaxis[:flip]) ? xmin : xmax - yg = (ymin + ymax) / 2 - zg = xor(yaxis[:mirror], zaxis[:flip]) ? zmax : zmin - xndc, yndc = gr_w3tondc(xg, yg, zg) - h = gr_axis_height(sp, yaxis) - gr_text(xndc + h, yndc - h, yaxis[:guide]) - end - - if zaxis[:guide] != "" - gr_set_font( - guidefont(zaxis), - halign = (:left, :hcenter, :right)[sign(zaxis[:rotation]) + 2], - valign = (zaxis[:mirror] ? :bottom : :top), - rotation = zaxis[:rotation], - sp - ) - xg = xor(zaxis[:mirror], xaxis[:flip]) ? xmax : xmin - yg = xor(zaxis[:mirror], yaxis[:flip]) ? ymax : ymin - zg = (zmin + zmax) / 2 - xndc, yndc = gr_w3tondc(xg, yg, zg) - w = gr_axis_width(sp, zaxis) - GR.setcharup(-1, 0) - gr_text(xndc - w, yndc, zaxis[:guide]) - end - else - if xaxis[:guide] != "" - h = 0.01 + gr_axis_height(sp, xaxis) - gr_set_font(guidefont(xaxis), sp) - if xaxis[:guide_position] == :top || (xaxis[:guide_position] == :auto && xaxis[:mirror] == true) - GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_TOP) - gr_text(gr_view_xcenter(viewport_plotarea), viewport_plotarea[4] + h, xaxis[:guide]) - else - GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_BOTTOM) - gr_text(gr_view_xcenter(viewport_plotarea), viewport_plotarea[3] - h, xaxis[:guide]) - end - end - - if yaxis[:guide] != "" - w = 0.02 + gr_axis_width(sp, yaxis) - gr_set_font(guidefont(yaxis), sp) - GR.setcharup(-1, 0) - if yaxis[:guide_position] == :right || (yaxis[:guide_position] == :auto && yaxis[:mirror] == true) - GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_BOTTOM) - gr_text(viewport_plotarea[2] + w, gr_view_ycenter(viewport_plotarea), yaxis[:guide]) - else - GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_TOP) - gr_text(viewport_plotarea[1] - w, gr_view_ycenter(viewport_plotarea), yaxis[:guide]) - end - end - end + GR.restorestate() - gr_set_font(tickfont(xaxis), sp) - # this needs to be here to point the colormap to the right indices GR.setcolormap(1000 + GR.COLORMAP_COOLWARM) @@ -1537,6 +992,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) if typeof(z) <: Surface z = vec(transpose_z(series, z.surf, false)) elseif ispolar(sp) + rmin, rmax = axis_limits(sp, :y) if frng !== nothing _, frng = convert_to_polar(x, frng, (rmin, rmax)) end @@ -1786,14 +1242,14 @@ end ## Legend -function gr_add_legend(sp, leg, plotarea) +function gr_add_legend(sp, leg, viewport_plotarea) if !(sp[:legend] in(:none, :inline)) GR.savestate() GR.selntran(0) GR.setscale(0) gr_set_font(legendfont(sp), sp) if leg.w > 0 - xpos, ypos = gr_legend_pos(sp, leg, plotarea) # Passing legend width components instead of legend text width + xpos, ypos = gr_legend_pos(sp, leg, viewport_plotarea) GR.setfillintstyle(GR.INTSTYLE_SOLID) gr_set_fillcolor(sp[:background_color_legend]) GR.fillrect( @@ -1873,7 +1329,60 @@ function gr_add_legend(sp, leg, plotarea) end end -function gr_get_legend_geometry(plotarea, sp) +function gr_legend_pos(sp::Subplot, leg, viewport_plotarea) + s = sp[:legend] + typeof(s) <: Symbol || return gr_legend_pos(s, viewport_plotarea) + str = string(s) + if str == "best" + str = "topright" + end + if occursin("outer", str) + xaxis, yaxis = sp[:xaxis], sp[:yaxis] + xmirror = xaxis[:guide_position] == :top || (xaxis[:guide_position] == :auto && xaxis[:mirror] == true) + ymirror = yaxis[:guide_position] == :right || (yaxis[:guide_position] == :auto && yaxis[:mirror] == true) + end + if occursin("right", str) + if occursin("outer", str) + # As per https://github.com/jheinen/GR.jl/blob/master/src/jlgr.jl#L525 + xpos = viewport_plotarea[2] + leg.xoffset + leg.leftw + ymirror * gr_axis_width(sp, sp[:yaxis]) + else + xpos = viewport_plotarea[2] - leg.rightw - leg.textw - leg.xoffset + end + elseif occursin("left", str) + if occursin("outer", str) + xpos = viewport_plotarea[1] - !ymirror * gr_axis_width(sp, sp[:yaxis]) - leg.xoffset * 2 - leg.rightw - leg.textw + else + xpos = viewport_plotarea[1] + leg.leftw + leg.xoffset + end + else + xpos = (viewport_plotarea[2] - viewport_plotarea[1]) / 2 + viewport_plotarea[1] + leg.leftw - leg.rightw - leg.textw - leg.xoffset * 2 + end + if occursin("top", str) + if s == :outertop + ypos = viewport_plotarea[4] + leg.yoffset + leg.h + xmirror * gr_axis_height(sp, sp[:xaxis]) + else + ypos = viewport_plotarea[4] - leg.yoffset - leg.dy + end + elseif occursin("bottom", str) + if s == :outerbottom + ypos = viewport_plotarea[3] - leg.yoffset - leg.h - !xmirror * gr_axis_height(sp, sp[:xaxis]) + else + ypos = viewport_plotarea[3] + leg.yoffset + leg.h + end + else + # Adding min y to shift legend pos to correct graph (#2377) + ypos = (viewport_plotarea[4] - viewport_plotarea[3] + leg.h) / 2 + viewport_plotarea[3] + end + return xpos, ypos +end + +function gr_legend_pos(v::Tuple{S,T}, viewport_plotarea) where {S<:Real, T<:Real} + xpos = v[1] * (viewport_plotarea[2] - viewport_plotarea[1]) + viewport_plotarea[1] + ypos = v[2] * (viewport_plotarea[4] - viewport_plotarea[3]) + viewport_plotarea[3] + (xpos,ypos) +end + +function gr_get_legend_geometry(viewport_plotarea, sp) legendn = 0 legendw = 0 if sp[:legend] != :none @@ -1900,14 +1409,14 @@ function gr_get_legend_geometry(plotarea, sp) GR.restorestate() end - legend_width_factor = (plotarea[2] - plotarea[1]) / 45 # Determines the width of legend box + legend_width_factor = (viewport_plotarea[2] - viewport_plotarea[1]) / 45 # Determines the width of legend box legend_textw = legendw legend_rightw = legend_width_factor legend_leftw = legend_width_factor * 4 total_legendw = legend_textw + legend_leftw + legend_rightw - x_legend_offset = (plotarea[2] - plotarea[1]) / 30 - y_legend_offset = (plotarea[4] - plotarea[3]) / 30 + x_legend_offset = (viewport_plotarea[2] - viewport_plotarea[1]) / 30 + y_legend_offset = (viewport_plotarea[4] - viewport_plotarea[3]) / 30 dy = gr_point_mult(sp) * sp[:legendfontsize] * 1.75 legendh = dy * legendn @@ -1925,7 +1434,10 @@ function gr_get_legend_geometry(plotarea, sp) ) end -function gr_update_viewport_legend!(plotarea, sp, leg) + +## Viewport, window and scale + +function gr_update_viewport_legend!(viewport_plotarea, sp, leg) leg_str = string(sp[:legend]) xaxis, yaxis = sp[:xaxis], sp[:yaxis] @@ -1934,50 +1446,337 @@ function gr_update_viewport_legend!(plotarea, sp, leg) if occursin("outer", leg_str) if occursin("right", leg_str) - plotarea[2] -= leg.leftw + leg.textw + leg.rightw + leg.xoffset + viewport_plotarea[2] -= leg.leftw + leg.textw + leg.rightw + leg.xoffset elseif occursin("left", leg_str) - plotarea[1] += leg.leftw + leg.textw + leg.rightw + leg.xoffset + !ymirror * gr_axis_width(sp, sp[:yaxis]) + viewport_plotarea[1] += leg.leftw + leg.textw + leg.rightw + leg.xoffset + !ymirror * gr_axis_width(sp, sp[:yaxis]) elseif occursin("top", leg_str) - plotarea[4] -= leg.h + leg.dy + leg.yoffset + viewport_plotarea[4] -= leg.h + leg.dy + leg.yoffset elseif occursin("bottom", leg_str) - plotarea[3] += leg.h + leg.dy + leg.yoffset + !xmirror * gr_axis_height(sp, sp[:xaxis]) + viewport_plotarea[3] += leg.h + leg.dy + leg.yoffset + !xmirror * gr_axis_height(sp, sp[:xaxis]) end end if sp[:legend] == :inline if sp[:yaxis][:mirror] - plotarea[1] += leg.w + viewport_plotarea[1] += leg.w else - plotarea[2] -= leg.w + viewport_plotarea[2] -= leg.w end end end - -## Aspect ratio - -function gr_update_viewport_ratio!(plotarea, sp) +function gr_update_viewport_ratio!(viewport_plotarea, sp) ratio = get_aspect_ratio(sp) if ratio != :none data_lims = gr_xy_axislims(sp) if ratio == :equal ratio = 1 end - viewport_ratio = (plotarea[2] - plotarea[1]) / (plotarea[4] - plotarea[3]) + viewport_ratio = (viewport_plotarea[2] - viewport_plotarea[1]) / (viewport_plotarea[4] - viewport_plotarea[3]) window_ratio = (data_lims[2] - data_lims[1]) / (data_lims[4] - data_lims[3]) / ratio if window_ratio < viewport_ratio - viewport_center = 0.5 * (plotarea[1] + plotarea[2]) - viewport_size = (plotarea[2] - plotarea[1]) * window_ratio / viewport_ratio - plotarea[1] = viewport_center - 0.5 * viewport_size - plotarea[2] = viewport_center + 0.5 * viewport_size + viewport_center = 0.5 * (viewport_plotarea[1] + viewport_plotarea[2]) + viewport_size = (viewport_plotarea[2] - viewport_plotarea[1]) * window_ratio / viewport_ratio + viewport_plotarea[1] = viewport_center - 0.5 * viewport_size + viewport_plotarea[2] = viewport_center + 0.5 * viewport_size elseif window_ratio > viewport_ratio - viewport_center = 0.5 * (plotarea[3] + plotarea[4]) - viewport_size = (plotarea[4] - plotarea[3]) * viewport_ratio / window_ratio - plotarea[3] = viewport_center - 0.5 * viewport_size - plotarea[4] = viewport_center + 0.5 * viewport_size + viewport_center = 0.5 * (viewport_plotarea[3] + viewport_plotarea[4]) + viewport_size = (viewport_plotarea[4] - viewport_plotarea[3]) * viewport_ratio / window_ratio + viewport_plotarea[3] = viewport_center - 0.5 * viewport_size + viewport_plotarea[4] = viewport_center + 0.5 * viewport_size end end end +function gr_set_window(sp) + xmin, xmax, ymin, ymax = gr_xy_axislims(sp) + scaleop = 0 + if xmax > xmin && ymax > ymin + sp[:xaxis][:scale] == :log10 && (scaleop |= GR.OPTION_X_LOG) + sp[:yaxis][:scale] == :log10 && (scaleop |= GR.OPTION_Y_LOG) + sp[:xaxis][:flip] && (scaleop |= GR.OPTION_FLIP_X) + sp[:yaxis][:flip] && (scaleop |= GR.OPTION_FLIP_Y) + # NOTE: setwindow sets the "data coordinate" limits of the current "viewport" + GR.setwindow(xmin, xmax, ymin, ymax) + GR.setscale(scaleop) + end +end + +function gr_fill_plotarea(sp, viewport_plotarea) + if !RecipesPipeline.is3d(sp) + gr_fill_viewport(viewport_plotarea, plot_color(sp[:background_color_inside])) + end +end + +## Axes + +function gr_draw_axes(sp, viewport_plotarea) + GR.setlinewidth(sp.plt[:thickness_scaling]) + + if RecipesPipeline.is3d(sp) + # set space + xmin, xmax, ymin, ymax = gr_xy_axislims(sp) + zmin, zmax = axis_limits(sp, :z) + GR.setspace(zmin, zmax, round.(Int, sp[:camera])...) + + # fill the plot area + gr_set_fill(plot_color(sp[:background_color_inside])) + plot_area_x = [xmin, xmin, xmin, xmax, xmax, xmax, xmin] + plot_area_y = [ymin, ymin, ymax, ymax, ymax, ymin, ymin] + plot_area_z = [zmin, zmax, zmax, zmax, zmin, zmin, zmin] + x_bg, y_bg = RecipesPipeline.unzip(GR.wc3towc.(plot_area_x, plot_area_y, plot_area_z)) + GR.fillarea(x_bg, y_bg) + + for letter in (:x, :y, :z) + gr_draw_axis_3d(sp, letter) + end + elseif ispolar(sp) + r = gr_set_viewport_polar(viewport_plotarea) + #rmin, rmax = GR.adjustrange(ignorenan_minimum(r), ignorenan_maximum(r)) + rmin, rmax = axis_limits(sp, :y) + gr_polaraxes(rmin, rmax, sp) + elseif sp[:framestyle] != :none + for letter in (:x, :y) + gr_draw_axis(sp, letter, viewport_plotarea) + end + end +end + +function gr_draw_axis(sp, letter, viewport_plotarea) + ax = axis_drawing_info(sp, letter) + axis = sp[Symbol(letter, :axis)] + + # draw segments + gr_draw_grid(sp, axis, ax.grid_segments) + gr_draw_minorgrid(sp, axis, ax.minorgrid_segments) + gr_draw_spine(sp, axis, ax.segments) + gr_draw_border(sp, axis, ax.border_segments) + gr_draw_ticks(sp, axis, ax.tick_segments) + + # labels + gr_label_ticks(sp, letter, ax.ticks) + gr_label_axis(sp, letter, viewport_plotarea) +end + +function gr_draw_axis_3d(sp, letter) + ax = axis_drawing_info_3d(sp, letter) + axis = sp[Symbol(letter, :axis)] + + # draw segments + gr_draw_grid(sp, axis, ax.grid_segments, gr_polyline3d) + gr_draw_minorgrid(sp, axis, ax.minorgrid_segments, gr_polyline3d) + gr_draw_spine(sp, axis, ax.segments, gr_polyline3d) + gr_draw_border(sp, axis, ax.border_segments, gr_polyline3d) + gr_draw_ticks(sp, axis, ax.tick_segments, gr_polyline3d) + + # labels + gr_label_ticks_3d(sp, letter, ax.ticks) + gr_label_axis_3d(sp, letter) +end + +function gr_draw_grid(sp, axis, segments, func = gr_polyline) + if axis[:grid] + gr_set_line( + axis[:gridlinewidth], + axis[:gridstyle], + axis[:foreground_color_grid], + sp + ) + gr_set_transparency(axis[:foreground_color_grid], axis[:gridalpha]) + func(coords(segments)...) + end +end + +function gr_draw_minorgrid(sp, axis, segments, func = gr_polyline) + if axis[:grid] + gr_set_line( + axis[:minorgridlinewidth], + axis[:minorgridstyle], + axis[:foreground_color_minor_grid], + sp + ) + gr_set_transparency(axis[:foreground_color_minor_grid], axis[:minorgridalpha]) + func(coords(segments)...) + end +end + +function gr_draw_spine(sp, axis, segments, func = gr_polyline) + if axis[:showaxis] + gr_set_line(1, :solid, axis[:foreground_color_border], sp) + gr_set_transparency(1.0) + GR.setclip(0) + func(coords(segments)...) + GR.setclip(1) + end +end + +function gr_draw_border(sp, axis, segments, func = gr_polyline) + intensity = sp[:framestyle] == :semi ? 0.5 : 1 + if sp[:framestyle] in (:box, :semi) + GR.setclip(0) + gr_set_line(intensity, :solid, axis[:foreground_color_border], sp) + gr_set_transparency(axis[:foreground_color_border], intensity) + func(coords(segments)...) + GR.setclip(1) + end +end + +function gr_draw_ticks(sp, axis, segments, func = gr_polyline) + if axis[:showaxis] + if sp[:framestyle] in (:zerolines, :grid) + gr_set_line(1, :solid, axis[:foreground_color_grid], sp) + gr_set_transparency( + axis[:foreground_color_grid], + axis[:tick_direction] == :out ? axis[:gridalpha] : 0, + ) + else + gr_set_line(1, :solid, axis[:foreground_color_axis], sp) + end + GR.setclip(0) + func(coords(segments)...) + GR.setclip(1) + end +end + +function gr_label_ticks(sp, letter, ticks) + axis = sp[Symbol(letter, :axis)] + isy = letter === :y + oletter = isy ? :x : :y + oaxis = sp[Symbol(oletter, :axis)] + oamin, oamax = axis_limits(sp, oletter) + gr_set_tickfont(sp, letter) + out_factor = ifelse(axis[:tick_direction] === :out, 1.5, 1) + x_offset = isy ? (axis[:mirror] ? 1 : -1) * 1e-2 * out_factor : 0 + y_offset = isy ? 0 : (axis[:mirror] ? 1 : -1) * 6.7e-3 * out_factor + + ov = sp[:framestyle] == :origin ? 0 : xor(oaxis[:flip], axis[:mirror]) ? oamax : oamin + for (cv, dv) in zip(ticks...) + x, y = GR.wctondc(reverse_if((cv, ov), isy)...) + gr_text(x + x_offset, y + y_offset, dv) + end +end + +function gr_label_ticks_3d(sp, letter, ticks) + near_letter = letter in (:x, :z) ? :y : :x + far_letter = letter in (:x, :y) ? :z : :x + + ax = sp[Symbol(letter, :axis)] + nax = sp[Symbol(near_letter, :axis)] + fax = sp[Symbol(far_letter, :axis)] + + amin, amax = axis_limits(sp, letter) + namin, namax = axis_limits(sp, near_letter) + famin, famax = axis_limits(sp, far_letter) + n0, n1 = letter === :y ? (namax, namin) : (namin, namax) + + + # find out which axes we are dealing with + i = findfirst(==(letter), (:x, :y, :z)) + letters = axes_shift((:x, :y, :z), 1 - i) + asyms = Symbol.(letters, :axis) + + # get axis objects, ticks and minor ticks + # regardless of the `letter` we now use the convention that `x` in variable names refer to + # the first axesm `y` to the second, etc ... + ylims, zlims = axis_limits.(Ref(sp), letters[2:3]) + xax, yax, zax = getindex.(Ref(sp), asyms) + + gr_set_tickfont(sp, letter) + nt = sp[:framestyle] == :origin ? 0 : xor(ax[:mirror], nax[:flip]) ? n1 : n0 + ft = sp[:framestyle] == :origin ? 0 : xor(ax[:mirror], fax[:flip]) ? famax : famin + + xoffset = if letter === :x + (sp[:yaxis][:mirror] ? 1 : -1) * 1e-2 * (sp[:xaxis][:tick_direction] == :out ? 1.5 : 1) + elseif letter === :y + (sp[:yaxis][:mirror] ? -1 : 1) * 1e-2 * (sp[:yaxis][:tick_direction] == :out ? 1.5 : 1) + else + (sp[:zaxis][:mirror] ? 1 : -1) * 1e-2 * (sp[:zaxis][:tick_direction] == :out ? 1.5 : 1) + end + yoffset = if letter === :x + (sp[:xaxis][:mirror] ? 1 : -1) * 1e-2 * (sp[:xaxis][:tick_direction] == :out ? 1.5 : 1) + elseif letter === :y + (sp[:yaxis][:mirror] ? 1 : -1) * 1e-2 * (sp[:yaxis][:tick_direction] == :out ? 1.5 : 1) + else + 0 + end + + for (cv, dv) in zip(ticks...) + xi, yi = gr_w3tondc(sort_3d_axes(cv, nt, ft, letter)...) + gr_text(xi + xoffset, yi + yoffset, dv) + end +end + +function gr_label_axis(sp, letter, viewport_plotarea) + axis = sp[Symbol(letter, :axis)] + # guide + if axis[:guide] != "" + isy = letter === :y + GR.savestate() + gr_set_font(guidefont(axis), sp) + guide_position = axis[:guide_position] + if isy + w = 0.02 + gr_axis_width(sp, axis) + GR.setcharup(-1, 0) + if guide_position == :right || (guide_position == :auto && axis[:mirror]) + GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_BOTTOM) + gr_text(viewport_plotarea[2] + w, gr_view_ycenter(viewport_plotarea), axis[:guide]) + else + GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_TOP) + gr_text(viewport_plotarea[1] - w, gr_view_ycenter(viewport_plotarea), axis[:guide]) + end + else + h = 0.01 + gr_axis_height(sp, axis) + if guide_position == :top || (guide_position == :auto && axis[:mirror]) + GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_TOP) + gr_text(gr_view_xcenter(viewport_plotarea), viewport_plotarea[4] + h, axis[:guide]) + else + GR.settextalign(GR.TEXT_HALIGN_CENTER, GR.TEXT_VALIGN_BOTTOM) + gr_text(gr_view_xcenter(viewport_plotarea), viewport_plotarea[3] - h, axis[:guide]) + end + end + GR.restorestate() + end +end + +function gr_label_axis_3d(sp, letter) + ax = sp[Symbol(letter, :axis)] + if ax[:guide] != "" + near_letter = letter in (:x, :z) ? :y : :x + far_letter = letter in (:x, :y) ? :z : :x + + nax = sp[Symbol(near_letter, :axis)] + fax = sp[Symbol(far_letter, :axis)] + + amin, amax = axis_limits(sp, letter) + namin, namax = axis_limits(sp, near_letter) + famin, famax = axis_limits(sp, far_letter) + n0, n1 = letter === :y ? (namax, namin) : (namin, namax) + + GR.savestate() + gr_set_font( + guidefont(ax), + sp, + halign = (:left, :hcenter, :right)[sign(ax[:rotation]) + 2], + valign = ax[:mirror] ? :bottom : :top, + rotation = ax[:rotation], + # color = ax[:guidefontcolor], + ) + ag = (amin + amax) / 2 + ng = xor(ax[:mirror], nax[:flip]) ? n1 : n0 + fg = xor(ax[:mirror], fax[:flip]) ? famax : famin + x, y = gr_w3tondc(sort_3d_axes(ag, ng, fg, letter)...) + if letter in (:x, :y) + h = gr_axis_height(sp, ax) + x_offset = letter === :x ? -h : h + y_offset = -h + else + x_offset = -gr_axis_width(sp, ax) + y_offset = 0 + end + gr_text(x + x_offset, y - y_offset, ax[:guide]) + GR.restorestate() + end +end + # ---------------------------------------------------------------- for (mime, fmt) in ( From 44bd1812e0d673f55fcaeb1ed19770c1e7a6cd6d Mon Sep 17 00:00:00 2001 From: Daniel Schwabeneder Date: Fri, 4 Sep 2020 16:18:40 +0200 Subject: [PATCH 4/6] don't run optimize_ticks multiple times --- src/axes.jl | 83 ++++++++++++++++++++++++---------------------- src/backends/gr.jl | 40 +++++++++------------- 2 files changed, 59 insertions(+), 64 deletions(-) diff --git a/src/axes.jl b/src/axes.jl index a6729a0d..944d724a 100644 --- a/src/axes.jl +++ b/src/axes.jl @@ -213,48 +213,51 @@ function optimal_ticks_and_labels(sp::Subplot, axis::Axis, ticks = nothing) end # return (continuous_values, discrete_values) for the ticks on this axis -function get_ticks(sp::Subplot, axis::Axis) - ticks = _transform_ticks(axis[:ticks]) - ticks in (:none, nothing, false) && return nothing +function get_ticks(sp::Subplot, axis::Axis; update = true) + if update || !haskey(axis.plotattributes, :optimized_ticks) + ticks = _transform_ticks(axis[:ticks]) + if ticks in (:none, nothing, false) + axis.plotattributes[:optimized_ticks] = nothing + else + # treat :native ticks as :auto + ticks = ticks == :native ? :auto : ticks - # treat :native ticks as :auto - ticks = ticks == :native ? :auto : ticks - - dvals = axis[:discrete_values] - cv, dv = if typeof(ticks) <: Symbol - if !isempty(dvals) - # discrete ticks... - n = length(dvals) - rng = if ticks == :auto - Int[round(Int,i) for i in range(1, stop=n, length=min(n,15))] - else # if ticks == :all - 1:n + dvals = axis[:discrete_values] + cv, dv = if typeof(ticks) <: Symbol + if !isempty(dvals) + # discrete ticks... + n = length(dvals) + rng = if ticks == :auto + Int[round(Int,i) for i in range(1, stop=n, length=min(n,15))] + else # if ticks == :all + 1:n + end + axis[:continuous_values][rng], dvals[rng] + elseif ispolar(axis.sps[1]) && axis[:letter] == :x + #force theta axis to be full circle + (collect(0:pi/4:7pi/4), string.(0:45:315)) + else + # compute optimal ticks and labels + optimal_ticks_and_labels(sp, axis) + end + elseif typeof(ticks) <: Union{AVec, Int} + if !isempty(dvals) && typeof(ticks) <: Int + rng = Int[round(Int,i) for i in range(1, stop=length(dvals), length=ticks)] + axis[:continuous_values][rng], dvals[rng] + else + # override ticks, but get the labels + optimal_ticks_and_labels(sp, axis, ticks) + end + elseif typeof(ticks) <: NTuple{2, Any} + # assuming we're passed (ticks, labels) + ticks + else + error("Unknown ticks type in get_ticks: $(typeof(ticks))") end - axis[:continuous_values][rng], dvals[rng] - elseif ispolar(axis.sps[1]) && axis[:letter] == :x - #force theta axis to be full circle - (collect(0:pi/4:7pi/4), string.(0:45:315)) - else - # compute optimal ticks and labels - optimal_ticks_and_labels(sp, axis) + axis.plotattributes[:optimized_ticks] = (cv, dv) end - elseif typeof(ticks) <: Union{AVec, Int} - if !isempty(dvals) && typeof(ticks) <: Int - rng = Int[round(Int,i) for i in range(1, stop=length(dvals), length=ticks)] - axis[:continuous_values][rng], dvals[rng] - else - # override ticks, but get the labels - optimal_ticks_and_labels(sp, axis, ticks) - end - elseif typeof(ticks) <: NTuple{2, Any} - # assuming we're passed (ticks, labels) - ticks - else - error("Unknown ticks type in get_ticks: $(typeof(ticks))") end - # @show ticks dvals cv dv - - return cv, dv + axis.plotattributes[:optimized_ticks] end _transform_ticks(ticks) = ticks @@ -589,7 +592,7 @@ function axis_drawing_info(sp, letter) ax, oax = sp[asym], sp[oasym] amin, amax = axis_limits(sp, letter) oamin, oamax = axis_limits(sp, oletter) - ticks = get_ticks(sp, ax) + ticks = get_ticks(sp, ax, update = false) minor_ticks = get_minor_ticks(sp, ax, ticks) # initialize the segments @@ -714,7 +717,7 @@ function axis_drawing_info_3d(sp, letter) namin, namax = axis_limits(sp, near_letter) famin, famax = axis_limits(sp, far_letter) - ticks = get_ticks(sp, ax) + ticks = get_ticks(sp, ax, update = false) minor_ticks = get_minor_ticks(sp, ax, ticks) # initialize the segments diff --git a/src/backends/gr.jl b/src/backends/gr.jl index 266e28b0..9f02810d 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -215,7 +215,7 @@ function gr_polaraxes(rmin::Real, rmax::Real, sp::Subplot) a = α .+ 90 sinf = sind.(a) cosf = cosd.(a) - rtick_values, rtick_labels = get_ticks(sp, yaxis) + rtick_values, rtick_labels = get_ticks(sp, yaxis, update = false) #draw angular grid if xaxis[:grid] @@ -692,7 +692,7 @@ end function gr_axis_height(sp, axis) GR.savestate() - ticks = get_ticks(sp, axis) + ticks = get_ticks(sp, axis, update = false) gr_set_font(tickfont(axis), sp) h = (ticks in (nothing, false, :none) ? 0 : last(gr_get_ticks_size(ticks, axis[:rotation]))) if axis[:guide] != "" @@ -705,7 +705,7 @@ end function gr_axis_width(sp, axis) GR.savestate() - ticks = get_ticks(sp, axis) + ticks = get_ticks(sp, axis, update = false) gr_set_font(tickfont(axis), sp) w = (ticks in (nothing, false, :none) ? 0 : first(gr_get_ticks_size(ticks, axis[:rotation]))) if axis[:guide] != "" @@ -896,29 +896,22 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) gr_update_viewport_ratio!(viewport_plotarea, sp) leg = gr_get_legend_geometry(viewport_plotarea, sp) gr_update_viewport_legend!(viewport_plotarea, sp, leg) - - # TODO daschw is this required here? - data_lims = xy_lims = gr_xy_axislims(sp) # fill in the plot area background gr_fill_plotarea(sp, viewport_plotarea) - # reduced from before... set some flags based on the series in this subplot - # TODO: can these be generic flags? - outside_ticks = false cbar = GRColorbar() draw_axes = sp[:framestyle] != :none # axes_2d = true for series in series_list(sp) - st = series[:seriestype] - if st in (:heatmap, :image) - x, y = heatmap_edges(series[:x], sp[:xaxis][:scale], series[:y], sp[:yaxis][:scale], size(series[:z])) - xy_lims = x[1], x[end], y[1], y[end] - expand_extrema!(sp[:xaxis], x) - expand_extrema!(sp[:yaxis], y) - data_lims = gr_xy_axislims(sp) - end + # st = series[:seriestype] + # if st in (:heatmap, :image) + # x, y = heatmap_edges(series[:x], sp[:xaxis][:scale], series[:y], sp[:yaxis][:scale], size(series[:z])) + # xy_lims = x[1], x[end], y[1], y[end] + # expand_extrema!(sp[:xaxis], x) + # expand_extrema!(sp[:yaxis], y) + # end gr_update_colorbar!(cbar,series) end @@ -973,7 +966,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) if ispolar(sp) gr_set_viewport_polar(viewport_plotarea) else - xmin, xmax, ymin, ymax = data_lims + xmin, xmax, ymin, ymax = gr_xy_axislims(sp) if xmax > xmin && ymax > ymin GR.setwindow(xmin, xmax, ymin, ymax) end @@ -1107,9 +1100,8 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) else phimin, phimax = 0.0, 360.0 # nonuniform polar array is not yet supported in GR.jl nx, ny = length(series[:x]), length(series[:y]) - xmin, xmax, ymin, ymax = xy_lims - rmax = data_lims[4] - GR.setwindow(-rmax, rmax, -rmax, rmax) + xmin, xmax, ymin, ymax = gr_xy_axislims(sp) + GR.setwindow(-ymax, ymax, -ymax, ymax) if ymin > 0 @warn "'ymin[1] > 0' (rmin) is not yet supported." end @@ -1221,7 +1213,7 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) if ispolar(sp) gr_set_viewport_polar(viewport_plotarea) else - xmin, xmax, ymin, ymax = data_lims + xmin, xmax, ymin, ymax = gr_xy_axislims(sp) if xmax > xmin && ymax > ymin GR.setwindow(xmin, xmax, ymin, ymax) end @@ -1467,12 +1459,12 @@ end function gr_update_viewport_ratio!(viewport_plotarea, sp) ratio = get_aspect_ratio(sp) if ratio != :none - data_lims = gr_xy_axislims(sp) + xmin, xmax, ymin, ymax = gr_xy_axislims(sp) if ratio == :equal ratio = 1 end viewport_ratio = (viewport_plotarea[2] - viewport_plotarea[1]) / (viewport_plotarea[4] - viewport_plotarea[3]) - window_ratio = (data_lims[2] - data_lims[1]) / (data_lims[4] - data_lims[3]) / ratio + window_ratio = (xmax - xmin) / (ymax - ymin) / ratio if window_ratio < viewport_ratio viewport_center = 0.5 * (viewport_plotarea[1] + viewport_plotarea[2]) viewport_size = (viewport_plotarea[2] - viewport_plotarea[1]) * window_ratio / viewport_ratio From 51527705dee1d974f30d82f2363f939f94811882 Mon Sep 17 00:00:00 2001 From: Daniel Schwabeneder Date: Tue, 8 Sep 2020 16:24:00 +0200 Subject: [PATCH 5/6] extract gr_draw_ functions --- src/backends/gr.jl | 665 ++++++++++++++++++++++----------------------- src/components.jl | 1 + 2 files changed, 328 insertions(+), 338 deletions(-) diff --git a/src/backends/gr.jl b/src/backends/gr.jl index 9f02810d..ec62f168 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -333,35 +333,6 @@ function gr_draw_marker(series, xi, yi, clims, i, msize, strokewidth, shape::Sym end -# draw the markers, one at a time -function gr_draw_markers( - series::Series, - x, - y, - clims, - msize = series[:markersize], - strokewidth = series[:markerstrokewidth], -) - - isempty(x) && return - GR.setfillintstyle(GR.INTSTYLE_SOLID) - - shapes = series[:markershape] - if shapes != :none - for (i, rng) in enumerate(iter_segments(series, :scatter)) - rng = intersect(eachindex(x), rng) - if !isempty(rng) - ms = get_thickness_scaling(series) * _cycle(msize, i) - msw = get_thickness_scaling(series) * _cycle(strokewidth, i) - shape = _cycle(shapes, i) - for j in rng - gr_draw_marker(series, _cycle(x, j), _cycle(y, j), clims, i, ms, msw, shape) - end - end - end - end -end - # --------------------------------------------------------- function gr_set_line(lw, style, c, s) # s can be Subplot or Series @@ -900,305 +871,25 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) # fill in the plot area background gr_fill_plotarea(sp, viewport_plotarea) - cbar = GRColorbar() - - draw_axes = sp[:framestyle] != :none - # axes_2d = true - for series in series_list(sp) - # st = series[:seriestype] - # if st in (:heatmap, :image) - # x, y = heatmap_edges(series[:x], sp[:xaxis][:scale], series[:y], sp[:yaxis][:scale], size(series[:z])) - # xy_lims = x[1], x[end], y[1], y[end] - # expand_extrema!(sp[:xaxis], x) - # expand_extrema!(sp[:yaxis], y) - # end - - gr_update_colorbar!(cbar,series) - end - # set our plot area view GR.setviewport(viewport_plotarea...) - # these are the Axis objects, which hold scale, lims, etc - xaxis = sp[:xaxis] - yaxis = sp[:yaxis] - zaxis = sp[:zaxis] - # set the scale flags and window - gr_set_window(sp) + gr_set_window(sp, viewport_plotarea) # draw the axes gr_draw_axes(sp, viewport_plotarea) - - # add the guides - GR.savestate() - if sp[:title] != "" - gr_set_font(titlefont(sp), sp) - loc = sp[:titlelocation] - if loc == :left - xpos = viewport_plotarea[1] - halign = GR.TEXT_HALIGN_LEFT - elseif loc == :right - xpos = viewport_plotarea[2] - halign = GR.TEXT_HALIGN_RIGHT - else - xpos = gr_view_xcenter(viewport_plotarea) - halign = GR.TEXT_HALIGN_CENTER - end - GR.settextalign(halign, GR.TEXT_VALIGN_TOP) - gr_text(xpos, viewport_subplot[4], sp[:title]) - end - - GR.restorestate() + gr_add_title(sp, viewport_plotarea, viewport_subplot) # this needs to be here to point the colormap to the right indices GR.setcolormap(1000 + GR.COLORMAP_COOLWARM) - for (idx, series) in enumerate(series_list(sp)) - st = series[:seriestype] + # init the colorbar + cbar = GRColorbar() - # update the current stored gradient - gr_set_gradient(series) - - GR.savestate() - - # update the bounding window - if ispolar(sp) - gr_set_viewport_polar(viewport_plotarea) - else - xmin, xmax, ymin, ymax = gr_xy_axislims(sp) - if xmax > xmin && ymax > ymin - GR.setwindow(xmin, xmax, ymin, ymax) - end - end - - x, y, z = series[:x], series[:y], series[:z] - frng = series[:fillrange] - - clims = get_clims(sp, series) - - # add custom frame shapes to markershape? - series_annotations_shapes!(series) - # ------------------------------------------------------- - - # recompute data - if typeof(z) <: Surface - z = vec(transpose_z(series, z.surf, false)) - elseif ispolar(sp) - rmin, rmax = axis_limits(sp, :y) - if frng !== nothing - _, frng = convert_to_polar(x, frng, (rmin, rmax)) - end - x, y = convert_to_polar(x, y, (rmin, rmax)) - end - - if st == :straightline - x, y = straightline_data(series) - end - - if st in (:path, :scatter, :straightline) - if x !== nothing && length(x) > 1 - lz = series[:line_z] - segments = iter_segments(series, st) - # do area fill - if frng !== nothing - GR.setfillintstyle(GR.INTSTYLE_SOLID) - fr_from, fr_to = (is_2tuple(frng) ? frng : (y, frng)) - for (i, rng) in enumerate(segments) - fc = get_fillcolor(series, clims, i) - gr_set_fillcolor(fc) - fx = _cycle(x, vcat(rng, reverse(rng))) - fy = vcat(_cycle(fr_from,rng), _cycle(fr_to,reverse(rng))) - gr_set_transparency(fc, get_fillalpha(series, i)) - GR.fillarea(fx, fy) - end - end - - # draw the line(s) - if st in (:path, :straightline) - for (i, rng) in enumerate(segments) - lc = get_linecolor(series, clims, i) - gr_set_line( - get_linewidth(series, i), get_linestyle(series, i), lc, sp - ) - arrowside = isa(series[:arrow], Arrow) ? series[:arrow].side : :none - arrowstyle = isa(series[:arrow], Arrow) ? series[:arrow].style : :simple - gr_set_fillcolor(lc) - gr_set_transparency(lc, get_linealpha(series, i)) - gr_polyline(x[rng], y[rng]; arrowside = arrowside, arrowstyle = arrowstyle) - end - end - end - - if series[:markershape] != :none - gr_draw_markers(series, x, y, clims) - end - - elseif st == :contour - GR.setspace(clims[1], clims[2], 0, 90) - GR.setlinetype(gr_linetype(get_linestyle(series))) - GR.setlinewidth(max(0, get_linewidth(series)) / gr_nominal_size(sp)) - is_lc_black = let black=plot_color(:black) - plot_color(series[:linecolor]) in (black,[black]) - end - h = gr_contour_levels(series, clims) - if series[:fillrange] !== nothing - if series[:fillcolor] != series[:linecolor] && !is_lc_black - @warn("GR: filled contour only supported with black contour lines") - end - GR.contourf(x, y, h, z, series[:contour_labels] == true ? 1 : 0) - else - coff = is_lc_black ? 0 : 1000 - GR.contour(x, y, h, z, coff + (series[:contour_labels] == true ? 1 : 0)) - end - - elseif st in [:surface, :wireframe] - if st == :surface - if length(x) == length(y) == length(z) - GR.trisurface(x, y, z) - else - try - GR.gr3.surface(x, y, z, GR.OPTION_COLORED_MESH) - catch - GR.surface(x, y, z, GR.OPTION_COLORED_MESH) - end - end - else - GR.setfillcolorind(0) - GR.surface(x, y, z, GR.OPTION_FILLED_MESH) - end - - elseif st == :volume - sp[:legend] = :none - GR.gr3.clear() - dmin, dmax = GR.gr3.volume(y.v, 0) - - elseif st == :heatmap - zmin, zmax = clims - fillgrad = _as_gradient(series[:fillcolor]) - if !ispolar(sp) - GR.setspace(clims..., 0, 90) - x, y = heatmap_edges(series[:x], sp[:xaxis][:scale], series[:y], sp[:yaxis][:scale], size(series[:z])) - w, h = length(x) - 1, length(y) - 1 - if is_uniformly_spaced(x) && is_uniformly_spaced(y) - # For uniformly spaced data use GR.drawimage, which can be - # much faster than GR.nonuniformcellarray, especially for - # pdf output, and also supports alpha values. - # Note that drawimage draws uniformly spaced data correctly - # even on log scales, where it is visually non-uniform. - colors = plot_color.(get(fillgrad, z, clims), series[:fillalpha]) - rgba = gr_color.(colors) - GR.drawimage(first(x), last(x), last(y), first(y), w, h, rgba) - else - if something(series[:fillalpha],1) < 1 - @warn "GR: transparency not supported in non-uniform heatmaps. Alpha values ignored." - end - z_normalized = get_z_normalized.(z, zmin, zmax) - rgba = Int32[round(Int32, 1000 + _i * 255) for _i in z_normalized] - GR.nonuniformcellarray(x, y, w, h, rgba) - end - else - phimin, phimax = 0.0, 360.0 # nonuniform polar array is not yet supported in GR.jl - nx, ny = length(series[:x]), length(series[:y]) - xmin, xmax, ymin, ymax = gr_xy_axislims(sp) - GR.setwindow(-ymax, ymax, -ymax, ymax) - if ymin > 0 - @warn "'ymin[1] > 0' (rmin) is not yet supported." - end - if series[:y][end] != ny - @warn "Right now only the maximum value of y (r) is taken into account." - end - # colors = get(fillgrad, z, clims) - # GR.polarcellarray(0, 0, phimin, phimax, ymin, ymax, nx, ny, colors) - z_normalized = get_z_normalized.(z, zmin, zmax) - # z_normalized = map(c -> c == invisible() ? 256/255 : PlotUtils.getinverse(fillgrad.colors, c), colors) - rgba = Int32[round(Int32, 1000 + _i * 255) for _i in z_normalized] - GR.polarcellarray(0, 0, phimin, phimax, 0, ymax, nx, ny, rgba) - # Right now only the maximum value of y (r) is taken into account. - # This is certainly not perfect but nonuniform polar array is not yet supported in GR.jl - end - - elseif st in (:path3d, :scatter3d) - # draw path - if st == :path3d - if length(x) > 1 - lz = series[:line_z] - segments = iter_segments(series, st) - for (i, rng) in enumerate(segments) - lc = get_linecolor(series, clims, i) - gr_set_line( - get_linewidth(series, i), get_linestyle(series, i), lc, sp - ) - gr_set_transparency(lc, get_linealpha(series, i)) - GR.polyline3d(x[rng], y[rng], z[rng]) - end - end - end - - # draw markers - if st == :scatter3d || series[:markershape] != :none - x2, y2 = RecipesPipeline.unzip(map(GR.wc3towc, x, y, z)) - gr_draw_markers(series, x2, y2, clims) - end - - elseif st == :shape - x, y = shape_data(series) - for (i,rng) in enumerate(iter_segments(x, y)) - if length(rng) > 1 - # connect to the beginning - rng = vcat(rng, rng[1]) - - # get the segments - xseg, yseg = x[rng], y[rng] - - # draw the interior - fc = get_fillcolor(series, clims, i) - gr_set_fill(fc) - gr_set_transparency(fc, get_fillalpha(series, i)) - GR.fillarea(xseg, yseg) - - # draw the shapes - lc = get_linecolor(series, clims, i) - gr_set_line(get_linewidth(series, i), get_linestyle(series, i), lc, sp) - gr_set_transparency(lc, get_linealpha(series, i)) - GR.polyline(xseg, yseg) - end - end - - - elseif st == :image - z = transpose_z(series, series[:z].surf, true)' - x, y = heatmap_edges(series[:x], sp[:xaxis][:scale], series[:y], sp[:yaxis][:scale], size(z)) - w, h = size(z) - xmin, xmax = ignorenan_extrema(x) - ymin, ymax = ignorenan_extrema(y) - rgba = gr_color.(z) - GR.drawimage(xmin, xmax, ymax, ymin, w, h, rgba) - end - - # this is all we need to add the series_annotations text - anns = series[:series_annotations] - for (xi,yi,str,fnt) in EachAnn(anns, x, y) - gr_set_font(fnt, sp) - gr_text(GR.wctondc(xi, yi)..., str) - end - - if sp[:legend] == :inline && should_add_to_legend(series) - gr_set_font(legendfont(sp), sp) - gr_set_textcolor(plot_color(sp[:legendfontcolor])) - if sp[:yaxis][:mirror] - (_,i) = sp[:xaxis][:flip] ? findmax(x) : findmin(x) - GR.settextalign(GR.TEXT_HALIGN_RIGHT, GR.TEXT_VALIGN_HALF) - offset = -0.01 - else - (_,i) = sp[:xaxis][:flip] ? findmin(x) : findmax(x) - GR.settextalign(GR.TEXT_HALIGN_LEFT, GR.TEXT_VALIGN_HALF) - offset = 0.01 - end - (x_l,y_l) = GR.wctondc(x[i],y[i]) - gr_text(x_l+offset,y_l,series[:label]) - end - GR.restorestate() + for series in series_list(sp) + gr_add_series(sp, series) + gr_update_colorbar!(cbar, series) end # draw the colorbar @@ -1208,16 +899,6 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) gr_add_legend(sp, leg, viewport_plotarea) # add annotations - GR.savestate() - # update the bounding window - if ispolar(sp) - gr_set_viewport_polar(viewport_plotarea) - else - xmin, xmax, ymin, ymax = gr_xy_axislims(sp) - if xmax > xmin && ymax > ymin - GR.setwindow(xmin, xmax, ymin, ymax) - end - end for ann in sp[:annotations] x, y, val = locate_annotation(sp, ann...) x, y = if RecipesPipeline.is3d(sp) @@ -1228,7 +909,6 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) gr_set_font(val.font, sp) gr_text(x, y, val.str) end - GR.restorestate() end @@ -1479,17 +1159,21 @@ function gr_update_viewport_ratio!(viewport_plotarea, sp) end end -function gr_set_window(sp) - xmin, xmax, ymin, ymax = gr_xy_axislims(sp) - scaleop = 0 - if xmax > xmin && ymax > ymin - sp[:xaxis][:scale] == :log10 && (scaleop |= GR.OPTION_X_LOG) - sp[:yaxis][:scale] == :log10 && (scaleop |= GR.OPTION_Y_LOG) - sp[:xaxis][:flip] && (scaleop |= GR.OPTION_FLIP_X) - sp[:yaxis][:flip] && (scaleop |= GR.OPTION_FLIP_Y) - # NOTE: setwindow sets the "data coordinate" limits of the current "viewport" - GR.setwindow(xmin, xmax, ymin, ymax) - GR.setscale(scaleop) +function gr_set_window(sp, viewport_plotarea) + if ispolar(sp) + gr_set_viewport_polar(viewport_plotarea) + else + xmin, xmax, ymin, ymax = gr_xy_axislims(sp) + scaleop = 0 + if xmax > xmin && ymax > ymin + sp[:xaxis][:scale] == :log10 && (scaleop |= GR.OPTION_X_LOG) + sp[:yaxis][:scale] == :log10 && (scaleop |= GR.OPTION_Y_LOG) + sp[:xaxis][:flip] && (scaleop |= GR.OPTION_FLIP_X) + sp[:yaxis][:flip] && (scaleop |= GR.OPTION_FLIP_Y) + # NOTE: setwindow sets the "data coordinate" limits of the current "viewport" + GR.setwindow(xmin, xmax, ymin, ymax) + GR.setscale(scaleop) + end end end @@ -1499,6 +1183,7 @@ function gr_fill_plotarea(sp, viewport_plotarea) end end + ## Axes function gr_draw_axes(sp, viewport_plotarea) @@ -1769,6 +1454,310 @@ function gr_label_axis_3d(sp, letter) end end +function gr_add_title(sp, viewport_plotarea, viewport_subplot) + if sp[:title] != "" + GR.savestate() + gr_set_font(titlefont(sp), sp) + loc = sp[:titlelocation] + if loc == :left + xpos = viewport_plotarea[1] + halign = GR.TEXT_HALIGN_LEFT + elseif loc == :right + xpos = viewport_plotarea[2] + halign = GR.TEXT_HALIGN_RIGHT + else + xpos = gr_view_xcenter(viewport_plotarea) + halign = GR.TEXT_HALIGN_CENTER + end + GR.settextalign(halign, GR.TEXT_VALIGN_TOP) + gr_text(xpos, viewport_subplot[4], sp[:title]) + GR.restorestate() + end +end + + +## Series + +function gr_add_series(sp, series) + st = series[:seriestype] + + # update the current stored gradient + gr_set_gradient(series) + + GR.savestate() + + x, y, z = series[:x], series[:y], series[:z] + xscale, yscale = sp[:xaxis][:scale], sp[:yaxis][:scale] + frng = series[:fillrange] + + # recompute data + if typeof(z) <: Surface + z = transpose_z(series, z.surf, false) + elseif ispolar(sp) + rmin, rmax = axis_limits(sp, :y) + if frng !== nothing + _, frng = convert_to_polar(x, frng, (rmin, rmax)) + end + x, y = convert_to_polar(x, y, (rmin, rmax)) + end + + clims = get_clims(sp, series) + + # add custom frame shapes to markershape? + series_annotations_shapes!(series) + # ------------------------------------------------------- + + # draw the series + if st in (:path, :scatter, :straightline) + if st === :straightline + x, y = straightline_data(series) + end + gr_draw_segments(series, x, y, frng, clims) + if series[:markershape] !== :none + gr_draw_markers(series, x, y, clims) + end + elseif st === :shape + gr_draw_shapes(series, x, y, clims) + elseif st in (:path3d, :scatter3d) + gr_draw_segments_3d(series, x, y, z, clims) + if st === :scatter3d || series[:markershape] !== :none + # TODO: Do we need to transform to 2d coordinates here? + x2, y2 = RecipesPipeline.unzip(map(GR.wc3towc, x, y, z)) + gr_draw_markers(series, x2, y2, clims) + end + elseif st === :contour + gr_draw_contour(series, x, y, z, clims) + elseif st in (:surface, :wireframe) + gr_draw_surface(series, x, y, z, clims) + elseif st === :volume + sp[:legend] = :none + GR.gr3.clear() + dmin, dmax = GR.gr3.volume(y.v, 0) + elseif st in (:heatmap, :image) + if !ispolar(series) + x, y = heatmap_edges(x, xscale, y, yscale, size(z)) + end + if st === :heatmap + gr_draw_heatmap(series, x, y, z, clims) + else + gr_draw_image(series, x, y, z, clims) + end + end + + # this is all we need to add the series_annotations text + anns = series[:series_annotations] + for (xi,yi,str,fnt) in EachAnn(anns, x, y) + gr_set_font(fnt, sp) + gr_text(GR.wctondc(xi, yi)..., str) + end + + if sp[:legend] == :inline && should_add_to_legend(series) + gr_set_font(legendfont(sp), sp) + gr_set_textcolor(plot_color(sp[:legendfontcolor])) + if sp[:yaxis][:mirror] + (_,i) = sp[:xaxis][:flip] ? findmax(x) : findmin(x) + GR.settextalign(GR.TEXT_HALIGN_RIGHT, GR.TEXT_VALIGN_HALF) + offset = -0.01 + else + (_,i) = sp[:xaxis][:flip] ? findmin(x) : findmax(x) + GR.settextalign(GR.TEXT_HALIGN_LEFT, GR.TEXT_VALIGN_HALF) + offset = 0.01 + end + (x_l,y_l) = GR.wctondc(x[i],y[i]) + gr_text(x_l+offset,y_l,series[:label]) + end + GR.restorestate() +end + +function gr_draw_segments(series, x, y, fillrange, clims) + st = series[:seriestype] + if x !== nothing && length(x) > 1 + segments = iter_segments(series, st) + # do area fill + if fillrange !== nothing + GR.setfillintstyle(GR.INTSTYLE_SOLID) + fr_from, fr_to = (is_2tuple(fillrange) ? fillrange : (y, fillrange)) + for (i, rng) in enumerate(segments) + fc = get_fillcolor(series, clims, i) + gr_set_fillcolor(fc) + fx = _cycle(x, vcat(rng, reverse(rng))) + fy = vcat(_cycle(fr_from, rng), _cycle(fr_to, reverse(rng))) + gr_set_transparency(fc, get_fillalpha(series, i)) + GR.fillarea(fx, fy) + end + end + + # draw the line(s) + if st in (:path, :straightline) + for (i, rng) in enumerate(segments) + lc = get_linecolor(series, clims, i) + gr_set_line( + get_linewidth(series, i), get_linestyle(series, i), lc, series + ) + arrowside = isa(series[:arrow], Arrow) ? series[:arrow].side : :none + arrowstyle = isa(series[:arrow], Arrow) ? series[:arrow].style : :simple + gr_set_fillcolor(lc) + gr_set_transparency(lc, get_linealpha(series, i)) + gr_polyline(x[rng], y[rng]; arrowside = arrowside, arrowstyle = arrowstyle) + end + end + end +end + +function gr_draw_segments_3d(series, x, y, z, clims) + if series[:seriestype] === :path3d && length(x) > 1 + lz = series[:line_z] + segments = iter_segments(series, :path3d) + for (i, rng) in enumerate(segments) + lc = get_linecolor(series, clims, i) + gr_set_line( + get_linewidth(series, i), get_linestyle(series, i), lc, series + ) + gr_set_transparency(lc, get_linealpha(series, i)) + GR.polyline3d(x[rng], y[rng], z[rng]) + end + end +end + +function gr_draw_markers( + series::Series, + x, + y, + clims, + msize = series[:markersize], + strokewidth = series[:markerstrokewidth], +) + + isempty(x) && return + GR.setfillintstyle(GR.INTSTYLE_SOLID) + + shapes = series[:markershape] + if shapes != :none + for (i, rng) in enumerate(iter_segments(series, :scatter)) + rng = intersect(eachindex(x), rng) + if !isempty(rng) + ms = get_thickness_scaling(series) * _cycle(msize, i) + msw = get_thickness_scaling(series) * _cycle(strokewidth, i) + shape = _cycle(shapes, i) + for j in rng + gr_draw_marker(series, _cycle(x, j), _cycle(y, j), clims, i, ms, msw, shape) + end + end + end + end +end + +function gr_draw_shapes(series, x, y, clims) + x, y = shape_data(series) + for (i,rng) in enumerate(iter_segments(x, y)) + if length(rng) > 1 + # connect to the beginning + rng = vcat(rng, rng[1]) + + # get the segments + xseg, yseg = x[rng], y[rng] + + # draw the interior + fc = get_fillcolor(series, clims, i) + gr_set_fill(fc) + gr_set_transparency(fc, get_fillalpha(series, i)) + GR.fillarea(xseg, yseg) + + # draw the shapes + lc = get_linecolor(series, clims, i) + gr_set_line(get_linewidth(series, i), get_linestyle(series, i), lc, series) + gr_set_transparency(lc, get_linealpha(series, i)) + GR.polyline(xseg, yseg) + end + end +end + +function gr_draw_contour(series, x, y, z, clims) + GR.setspace(clims[1], clims[2], 0, 90) + gr_set_line(get_linewidth(series), get_linestyle(series), get_linecolor(series), series) + is_lc_black = let black=plot_color(:black) + plot_color(series[:linecolor]) in (black,[black]) + end + h = gr_contour_levels(series, clims) + if series[:fillrange] !== nothing + if series[:fillcolor] != series[:linecolor] && !is_lc_black + @warn("GR: filled contour only supported with black contour lines") + end + GR.contourf(x, y, h, z, series[:contour_labels] == true ? 1 : 0) + else + coff = is_lc_black ? 0 : 1000 + GR.contour(x, y, h, z, coff + (series[:contour_labels] == true ? 1 : 0)) + end +end + +function gr_draw_surface(series, x, y, z, clims) + if series[:seriestype] === :surface + if length(x) == length(y) == length(z) + GR.trisurface(x, y, z) + else + try + GR.gr3.surface(x, y, z, GR.OPTION_COLORED_MESH) + catch + GR.surface(x, y, z, GR.OPTION_COLORED_MESH) + end + end + else # wireframe + GR.setfillcolorind(0) + GR.surface(x, y, z, GR.OPTION_FILLED_MESH) + end +end + +function gr_draw_heatmap(series, x, y, z, clims) + fillgrad = _as_gradient(series[:fillcolor]) + if !ispolar(series) + GR.setspace(clims..., 0, 90) + w, h = length(x) - 1, length(y) - 1 + if is_uniformly_spaced(x) && is_uniformly_spaced(y) + # For uniformly spaced data use GR.drawimage, which can be + # much faster than GR.nonuniformcellarray, especially for + # pdf output, and also supports alpha values. + # Note that drawimage draws uniformly spaced data correctly + # even on log scales, where it is visually non-uniform. + colors = plot_color.(get(fillgrad, z, clims), series[:fillalpha]) + rgba = gr_color.(colors) + GR.drawimage(first(x), last(x), last(y), first(y), w, h, rgba) + else + if something(series[:fillalpha], 1) < 1 + @warn "GR: transparency not supported in non-uniform heatmaps. Alpha values ignored." + end + z_normalized = get_z_normalized.(z, clims...) + rgba = Int32[round(Int32, 1000 + _i * 255) for _i in z_normalized] + GR.nonuniformcellarray(x, y, w, h, rgba) + end + else + phimin, phimax = 0.0, 360.0 # nonuniform polar array is not yet supported in GR.jl + nx, ny = length(series[:x]), length(series[:y]) + xmin, xmax, ymin, ymax = gr_xy_axislims(sp) + GR.setwindow(-ymax, ymax, -ymax, ymax) + if ymin > 0 + @warn "'ymin[1] > 0' (rmin) is not yet supported." + end + if series[:y][end] != ny + @warn "Right now only the maximum value of y (r) is taken into account." + end + z_normalized = get_z_normalized.(z, clims...) + rgba = Int32[round(Int32, 1000 + _i * 255) for _i in z_normalized] + GR.polarcellarray(0, 0, phimin, phimax, 0, ymax, nx, ny, rgba) + # Right now only the maximum value of y (r) is taken into account. + # This is certainly not perfect but nonuniform polar array is not yet supported in GR.jl + end +end + +function gr_draw_image(series, x, y, z, clims) + z = transpose_z(series, series[:z].surf, true)' + w, h = size(z) + xmin, xmax = ignorenan_extrema(x) + ymin, ymax = ignorenan_extrema(y) + rgba = gr_color.(z) + GR.drawimage(xmin, xmax, ymax, ymin, w, h, rgba) +end + + # ---------------------------------------------------------------- for (mime, fmt) in ( diff --git a/src/components.jl b/src/components.jl index 71894343..8b55bd8b 100644 --- a/src/components.jl +++ b/src/components.jl @@ -659,6 +659,7 @@ function locate_annotation(sp::Subplot, pos::Symbol, lab::PlotText) (x, y, lab) end locate_annotation(sp::Subplot, x, y, label::PlotText) = (x, y, label) +locate_annotation(sp::Subplot, x, y, z, label::PlotText) = (x, y, z, label) # ----------------------------------------------------------------------- "type which represents z-values for colors and sizes (and anything else that might come up)" From 363c842ab4be39a685bf5a7afbe05ba420df67e9 Mon Sep 17 00:00:00 2001 From: Daniel Schwabeneder Date: Fri, 18 Sep 2020 10:23:12 +0200 Subject: [PATCH 6/6] redo #2988 --- src/backends/gr.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backends/gr.jl b/src/backends/gr.jl index ec62f168..f426868a 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -1322,8 +1322,8 @@ function gr_label_ticks(sp, letter, ticks) oamin, oamax = axis_limits(sp, oletter) gr_set_tickfont(sp, letter) out_factor = ifelse(axis[:tick_direction] === :out, 1.5, 1) - x_offset = isy ? (axis[:mirror] ? 1 : -1) * 1e-2 * out_factor : 0 - y_offset = isy ? 0 : (axis[:mirror] ? 1 : -1) * 6.7e-3 * out_factor + x_offset = isy ? (axis[:mirror] ? 1 : -1) * 1.5e-2 * out_factor : 0 + y_offset = isy ? 0 : (axis[:mirror] ? 1 : -1) * 8e-3 * out_factor ov = sp[:framestyle] == :origin ? 0 : xor(oaxis[:flip], axis[:mirror]) ? oamax : oamin for (cv, dv) in zip(ticks...)