From 6a03da5837e791879532b887a381ccae36079584 Mon Sep 17 00:00:00 2001 From: Daniel Schwabeneder Date: Wed, 2 Sep 2020 21:27:51 +0200 Subject: [PATCH] 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 (