Add hatched fill for GR and PyPlot (#3107)

This commit is contained in:
Pearl Li 2021-08-26 07:55:56 -07:00 committed by GitHub
parent 854d5ba5c9
commit ded808477d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 149 additions and 74 deletions

View File

@ -14,6 +14,7 @@ const _arg_desc = KW(
:fillcolor => "Color Type. Color of the filled area of path or bar types. `:match` will take the value from `:seriescolor`.", :fillcolor => "Color Type. Color of the filled area of path or bar types. `:match` will take the value from `:seriescolor`.",
:fillalpha => "Number in [0,1]. The alpha/opacity override for the fill area. `nothing` (the default) means it will take the alpha value of fillcolor.", :fillalpha => "Number in [0,1]. The alpha/opacity override for the fill area. `nothing` (the default) means it will take the alpha value of fillcolor.",
:markershape => "Symbol, Shape, or AbstractVector. Choose from $(_allMarkers).", :markershape => "Symbol, Shape, or AbstractVector. Choose from $(_allMarkers).",
:fillstyle => "Symbol. Style of the fill area. `nothing` (the default) means solid fill. Choose from :/, :\\, :|, :-, :+, :x",
:markercolor => "Color Type. Color of the interior of the marker or shape. `:match` will take the value from `:seriescolor`.", :markercolor => "Color Type. Color of the interior of the marker or shape. `:match` will take the value from `:seriescolor`.",
:markeralpha => "Number in [0,1]. The alpha/opacity override for the marker interior. `nothing` (the default) means it will take the alpha value of markercolor.", :markeralpha => "Number in [0,1]. The alpha/opacity override for the marker interior. `nothing` (the default) means it will take the alpha value of markercolor.",
:markersize => "Number or AbstractVector. Size (radius pixels) of the markers", :markersize => "Number or AbstractVector. Size (radius pixels) of the markers",

View File

@ -348,6 +348,7 @@ const _series_defaults = KW(
:fillrange => nothing, # ribbons, areas, etc :fillrange => nothing, # ribbons, areas, etc
:fillcolor => :match, :fillcolor => :match,
:fillalpha => nothing, :fillalpha => nothing,
:fillstyle => nothing,
:markershape => :none, :markershape => :none,
:markercolor => :match, :markercolor => :match,
:markeralpha => nothing, :markeralpha => nothing,
@ -1117,6 +1118,7 @@ function processLineArg(plotattributes::AKW, arg)
arg.color == :auto ? :auto : plot_color(arg.color) arg.color == :auto ? :auto : plot_color(arg.color)
) )
arg.alpha === nothing || (plotattributes[:fillalpha] = arg.alpha) arg.alpha === nothing || (plotattributes[:fillalpha] = arg.alpha)
arg.style === nothing || (plotattributes[:fillstyle] = arg.style)
elseif typeof(arg) <: Arrow || arg in (:arrow, :arrows) elseif typeof(arg) <: Arrow || arg in (:arrow, :arrows)
plotattributes[:arrow] = arg plotattributes[:arrow] = arg
@ -1188,6 +1190,7 @@ function processFillArg(plotattributes::AKW, arg)
arg.color == :auto ? :auto : plot_color(arg.color) arg.color == :auto ? :auto : plot_color(arg.color)
) )
arg.alpha === nothing || (plotattributes[:fillalpha] = arg.alpha) arg.alpha === nothing || (plotattributes[:fillalpha] = arg.alpha)
arg.style === nothing || (plotattributes[:fillstyle] = arg.style)
elseif typeof(arg) <: Bool elseif typeof(arg) <: Bool
plotattributes[:fillrange] = arg ? 0 : nothing plotattributes[:fillrange] = arg ? 0 : nothing

View File

@ -136,6 +136,23 @@ gr_set_arrowstyle(s::Symbol) = GR.setarrowstyle(
), ),
) )
gr_set_fillstyle(::Nothing) = GR.setfillintstyle(GR.INTSTYLE_SOLID)
function gr_set_fillstyle(s::Symbol)
GR.setfillintstyle(GR.INTSTYLE_HATCH)
GR.setfillstyle(get(
(
(/) = 9,
(\) = 10,
(|) = 7,
(-) = 8,
(+) = 11,
(x) = 6,
),
s,
9),
)
end
# -------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------
# draw line segments, splitting x/y into contiguous/finite segments # draw line segments, splitting x/y into contiguous/finite segments
@ -1058,7 +1075,9 @@ function gr_add_legend(sp, leg, viewport_plotarea)
series[:ribbon] === nothing series[:ribbon] === nothing
) )
fc = get_fillcolor(series, clims) fc = get_fillcolor(series, clims)
gr_set_fill(fc) #, series[:fillalpha]) gr_set_fill(fc)
fs = get_fillstyle(series, i)
gr_set_fillstyle(fs)
l, r = xpos - leg.width_factor * 3.5, xpos - leg.width_factor / 2 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 b, t = ypos - 0.4 * leg.dy, ypos + 0.4 * leg.dy
x = [l, r, r, l, l] x = [l, r, r, l, l]
@ -1824,6 +1843,8 @@ function gr_draw_segments(series, x, y, fillrange, clims)
i, rng = segment.attr_index, segment.range i, rng = segment.attr_index, segment.range
fc = get_fillcolor(series, clims, i) fc = get_fillcolor(series, clims, i)
gr_set_fillcolor(fc) gr_set_fillcolor(fc)
fs = get_fillstyle(series, i)
gr_set_fillstyle(fs)
fx = _cycle(x, vcat(rng, reverse(rng))) fx = _cycle(x, vcat(rng, reverse(rng)))
fy = vcat(_cycle(fr_from, rng), _cycle(fr_to, reverse(rng))) fy = vcat(_cycle(fr_from, rng), _cycle(fr_to, reverse(rng)))
gr_set_transparency(fc, get_fillalpha(series, i)) gr_set_transparency(fc, get_fillalpha(series, i))
@ -1912,6 +1933,8 @@ function gr_draw_shapes(series, clims)
# draw the interior # draw the interior
fc = get_fillcolor(series, clims, i) fc = get_fillcolor(series, clims, i)
gr_set_fill(fc) gr_set_fill(fc)
fs = get_fillstyle(series, i)
gr_set_fillstyle(fs)
gr_set_transparency(fc, get_fillalpha(series, i)) gr_set_transparency(fc, get_fillalpha(series, i))
GR.fillarea(xseg, yseg) GR.fillarea(xseg, yseg)

View File

@ -162,6 +162,9 @@ function py_fillstepstyle(seriestype::Symbol)
return nothing return nothing
end end
py_fillstyle(::Nothing) = nothing
py_fillstyle(fillstyle::Symbol) = string(fillstyle)
# # untested... return a FontProperties object from a Plots.Font # # untested... return a FontProperties object from a Plots.Font
# function py_font(font::Font) # function py_font(font::Font)
# pyfont["FontProperties"]( # pyfont["FontProperties"](
@ -709,24 +712,45 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
for segment in series_segments(series) for segment in series_segments(series)
i, rng = segment.attr_index, segment.range i, rng = segment.attr_index, segment.range
if length(rng) > 1 if length(rng) > 1
lc = get_linecolor(series, clims, i)
la = get_linealpha(series, i)
ls = get_linestyle(series, i)
fc = get_fillcolor(series, clims, i)
fa = get_fillalpha(series, i)
fs = get_fillstyle(series, i)
has_fs = !isnothing(fs)
path = pypath."Path"(hcat(x[rng], y[rng])) path = pypath."Path"(hcat(x[rng], y[rng]))
# shape outline (and potentially solid fill)
patches = pypatches."PathPatch"( patches = pypatches."PathPatch"(
path; path;
label = series[:label], label = series[:label],
zorder = series[:series_plotindex], zorder = series[:series_plotindex],
edgecolor = py_color( edgecolor = py_color(lc, la),
get_linecolor(series, clims, i), facecolor = py_color(fc, has_fs ? 0 : fa),
get_linealpha(series, i),
),
facecolor = py_color(
get_fillcolor(series, clims, i),
get_fillalpha(series, i),
),
linewidth = py_thickness_scale(plt, get_linewidth(series, i)), linewidth = py_thickness_scale(plt, get_linewidth(series, i)),
linestyle = py_linestyle(st, get_linestyle(series, i)), linestyle = py_linestyle(st, ls),
fill = true, fill = !has_fs,
) )
push!(handle, ax."add_patch"(patches)) push!(handle, ax."add_patch"(patches))
# shape hatched fill
# hatch color/alpha are controlled by edge (not face) color/alpha
if has_fs
patches = pypatches."PathPatch"(
path;
label = "",
zorder = series[:series_plotindex],
edgecolor = py_color(fc, fa),
facecolor = py_color(fc, 0), # don't fill with solid background
hatch = py_fillstyle(fs),
linewidth = 0, # don't replot shape outline (doesn't affect hatch linewidth)
linestyle = py_linestyle(st, ls),
fill = false,
)
push!(handle, ax."add_patch"(patches))
end
end end
end end
push!(handles, handle) push!(handles, handle)
@ -754,17 +778,24 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
dim1, _cycle(fillrange[1], rng), _cycle(fillrange[2], rng) dim1, _cycle(fillrange[1], rng), _cycle(fillrange[2], rng)
end end
la = get_linealpha(series, i)
fc = get_fillcolor(series, clims, i)
fa = get_fillalpha(series, i)
fs = get_fillstyle(series, i)
has_fs = !isnothing(fs)
handle = getproperty(ax, f)( handle = getproperty(ax, f)(
args..., args...,
trues(n), trues(n),
false, false,
py_fillstepstyle(st); py_fillstepstyle(st);
zorder = series[:series_plotindex], zorder = series[:series_plotindex],
facecolor = py_color( # hatch color/alpha are controlled by edge (not face) color/alpha
get_fillcolor(series, clims, i), # if has_fs, set edge color/alpha <- fill color/alpha and face alpha <- 0
get_fillalpha(series, i), edgecolor = py_color(fc, has_fs ? fa : la),
), facecolor = py_color(fc, has_fs ? 0 : fa),
linewidths = 0, hatch = py_fillstyle(fs),
linewidths = 0
) )
push!(handles, handle) push!(handles, handle)
end end
@ -1455,32 +1486,47 @@ function py_add_legend(plt::Plot, sp::Subplot, ax)
if should_add_to_legend(series) if should_add_to_legend(series)
clims = get_clims(sp, series) clims = get_clims(sp, series)
# add a line/marker and a label # add a line/marker and a label
push!(
handles,
if series[:seriestype] == :shape || series[:fillrange] !== nothing if series[:seriestype] == :shape || series[:fillrange] !== nothing
pypatches."Patch"( lc = get_linecolor(series, clims)
edgecolor = py_color( la = get_linealpha(series)
single_color(get_linecolor(series, clims)), ls = get_linestyle(series)
get_linealpha(series), fc = get_fillcolor(series, clims)
), fa = get_fillalpha(series)
facecolor = py_color( fs = get_fillstyle(series)
single_color(get_fillcolor(series, clims)), has_fs = !isnothing(fs)
get_fillalpha(series),
), # line (and potentially solid fill)
linewidth = py_thickness_scale( line_handle = pypatches."Patch"(
plt, edgecolor = py_color(single_color(lc), la),
clamp(get_linewidth(series), 0, 5), facecolor = py_color(single_color(fc), has_fs ? 0 : fa),
), linewidth = py_thickness_scale(plt, clamp(get_linewidth(series), 0, 5)),
linestyle = py_linestyle( linestyle = py_linestyle(series[:seriestype], ls),
series[:seriestype],
get_linestyle(series),
),
capstyle = "butt", capstyle = "butt",
) )
# hatched fill
# hatch color/alpha are controlled by edge (not face) color/alpha
if has_fs
fill_handle = pypatches."Patch"(
edgecolor = py_color(single_color(fc), fa),
facecolor = py_color(single_color(fc), 0), # don't fill with solid background
hatch = py_fillstyle(fs),
linewidth = 0, # don't replot shape outline (doesn't affect hatch linewidth)
linestyle = py_linestyle(series[:seriestype], ls),
capstyle = "butt",
)
# plot two handles on top of each other by passing in a tuple
# https://matplotlib.org/stable/tutorials/intermediate/legend_guide.html
push!(handles, (line_handle, fill_handle))
else
# plot line handle (which includes solid fill) only
push!(handles, line_handle)
end
elseif series[:seriestype] in elseif series[:seriestype] in
(:path, :straightline, :scatter, :steppre, :stepmid, :steppost) (:path, :straightline, :scatter, :steppre, :stepmid, :steppost)
hasline = get_linewidth(series) > 0 hasline = get_linewidth(series) > 0
PyPlot.plt."Line2D"( handle = PyPlot.plt."Line2D"(
(0, 1), (0, 1),
(0, 0), (0, 0),
color = py_color( color = py_color(
@ -1512,10 +1558,10 @@ function py_add_legend(plt::Plot, sp::Subplot, ax)
first(series[:markersize]), first(series[:markersize]),
), # retain the markersize/markerstroke ratio from the markers on the plot ), # retain the markersize/markerstroke ratio from the markers on the plot
) )
push!(handles, handle)
else else
series[:serieshandle][1] push!(handles, series[:serieshandle][1])
end, end
)
push!(labels, series[:label]) push!(labels, series[:label])
end end
end end

View File

@ -538,6 +538,7 @@ get_gradient(cp::ColorPalette) = cgrad(cp, categorical = true)
get_linewidth(series, i::Int = 1) = _cycle(series[:linewidth], i) get_linewidth(series, i::Int = 1) = _cycle(series[:linewidth], i)
get_linestyle(series, i::Int = 1) = _cycle(series[:linestyle], i) get_linestyle(series, i::Int = 1) = _cycle(series[:linestyle], i)
get_fillstyle(series, i::Int = 1) = _cycle(series[:fillstyle], i)
function get_markerstrokecolor(series, i::Int = 1) function get_markerstrokecolor(series, i::Int = 1)
msc = series[:markerstrokecolor] msc = series[:markerstrokecolor]
@ -556,6 +557,7 @@ const _segmenting_vector_attributes = (
:linestyle, :linestyle,
:fillcolor, :fillcolor,
:fillalpha, :fillalpha,
:fillstyle,
:markercolor, :markercolor,
:markeralpha, :markeralpha,
:markersize, :markersize,