Plots.jl
Plots.jl copied to clipboard
Customize errorbars (in particular their caps)
Is it possible to change size or completely remove the errorbar caps? Currently the defaults are ok, but they do not work great for all plots.
errorbar caps are controlled by the marker group of arguments. markerstrokecolor = :transparent and markersize = 2 should be relevant things to try.
But markerstrokecolor=:transparent removes both the cap and the error line itself (under GR)... Is it supposed to do that?
under the pyplot the caps have the color of marker, not the stroke,

pyplot() # the caps would be red in gr()
plot(rand(10), yerr=rand(10), lab="data", l=nothing, m=(4,stroke(:red, 1.5)))
It would be nice to be able to customize the caps.
You have another option of setting ms=0 that defaults to no marker, which I think looks better
@Krastanov
Another thing that, as far as I know, is not possible it to change the vertical thickness of the line. That would be very helpful...
I think lw, msw or some other thing does it
hm, does not look like it.
(So, msw changes the thickness of the vertical line, but not the thickness of the horizontal one)
using Plots
y = rand(10); yerr=0.2rand(10)
p1 = plot(y, yerr=yerr)
p2 = plot(y, yerr=yerr, msw=5, ms=20)
p3 = plot(y, yerr=yerr, ms=20)
p4 = plot(y, yerr=yerr, lw=10)
plot(p1,p2,p3,p4, layout=(2,2))

What backend are you using
The issue is that the horizontal bar is just a _ marker and not all backends support it
pyplot
I found a solution that works in my case but does (so far) not cover all cases.

The problem is that the currently the caps are drawn using a markershape=:hline.
This can be problematic, see https://github.com/JuliaPlots/Plots.jl/issues/2823
And you can not specify length and thickness separately.
My code modifies the yerror recipe to compute the caps as small line segements
that are plotted along with and in the style of the vertical lines.
It also takes an extra keyword argument specifying the length of the caps.
function errorcap_coords(errorbar, errordata, otherdata; capsize)
ed = Vector{Plots.float_extended_type(errordata)}(undef, 0)
od = Vector{Plots.float_extended_type(otherdata)}(undef, 0)#[Vector{float_extended_type(odi)}(undef, 0) for odi in otherdata]
for (j, (edi, odj)) in enumerate(zip(errordata, otherdata))
#for (i, edi) in enumerate(errordata)
#odi = _cycle(odj, i)
e1, e2 = Plots.error_tuple(Plots._cycle(errorbar, j))
Plots.nanappend!(ed, [edi - e1, edi - e1])
Plots.nanappend!(ed, [edi + e2, edi + e2])
Plots.nanappend!(od, [odj-capsize/2, odj+capsize/2])
Plots.nanappend!(od, [odj-capsize/2, odj+capsize/2])
#end
end
return (ed, od)
end
@recipe function f(::Type{Val{:yerror}}, x, y, z)
Plots.error_style!(plotattributes)
#markershape := :hline
yerr = Plots.error_zipit(plotattributes[:yerror])
if z === nothing
plotattributes[:y], plotattributes[:x] = Plots.error_coords(yerr, y, x)
errcapy, errcapx = errorcap_coords(yerr, y, x; capsize=get(plotattributes, :capsize, 0.1))
Plots.nanappend!(plotattributes[:y], errcapy)
Plots.nanappend!(plotattributes[:x], errcapx)
else
plotattributes[:y], plotattributes[:x], plotattributes[:z] =
Plots.error_coords(yerr, y, x, z)
end
()
end
How would you decide on the width of these lines?
Try commenting out https://github.com/JuliaPlots/Plots.jl/blob/709a8a96093b512693b8703d195b5a5da0636a13/src/backends/pyplot.jl#L128 and seeing the results. Perhaps that would be exactly what you want
If marker is unsupported by a backend, then it is provided by Plots using lower level recipes. In that case using msw, ms, lw, markercolor, linecolor can give you exactly what you want.
One reason to have it as it is right now is that we can start allowing custom marker shapes for errorbar plots fairly easy. But I could not figure out a way to invert those markers easily for bottom/top errorbar. Caps being markers (instead of linse) can give good customizability I think
thank you, for your suggestions.
I tried redefining the above mentioned function in my script but I can't seem to figure out where _marker is defined.
128 line in pyplot.jl
Please let me know your feedback with this
Thank you for your help.
128 line in pyplot.jl this was a misunderstanding. I found that bit. I was just wondering whether it's possible to redefine this function outside of plots and instead in my plot-script so that I don't have to rely on my custom branch of plots.jl (when executing on another machine or so)
Here's my result: As promised the thickness can now be varied but for some reason the caps are rounded on one side and not on the other.

What about using m=:circle
I think pyplot got better at marker centering at the latest version
so - is there a way to remove error bar caps without removing the error bars in GR?
so - is there a way to remove error bar caps without removing the error bars in GR?
With https://github.com/JuliaPlots/Plots.jl/pull/4362 that would be
using Plots
plot(1:5, yerror=fill(0.2,5), markershape = :none)
I cannot remove error bar caps with markershape = :none i.e. running
using Plots
plot(1:5, yerror=fill(0.2,5), markershape = :none)
gives me this
On the other hand, running
function errorcap_coords(errorbar, errordata, otherdata; capsize)
ed = Vector{Plots.float_extended_type(errordata)}(undef, 0)
od = Vector{Plots.float_extended_type(otherdata)}(undef, 0)#[Vector{float_extended_type(odi)}(undef, 0) for odi in otherdata]
for (j, (edi, odj)) in enumerate(zip(errordata, otherdata))
#for (i, edi) in enumerate(errordata)
#odi = _cycle(odj, i)
e1, e2 = Plots.error_tuple(Plots._cycle(errorbar, j))
Plots.nanappend!(ed, [edi - e1, edi - e1])
Plots.nanappend!(ed, [edi + e2, edi + e2])
Plots.nanappend!(od, [odj-capsize/2, odj+capsize/2])
Plots.nanappend!(od, [odj-capsize/2, odj+capsize/2])
#end
end
return (ed, od)
end
@recipe function f(::Type{Val{:yerror}}, x, y, z)
Plots.error_style!(plotattributes)
#markershape := :hline
yerr = Plots.error_zipit(plotattributes[:yerror])
if z === nothing
plotattributes[:y], plotattributes[:x] = Plots.error_coords(yerr, y, x)
errcapy, errcapx = errorcap_coords(yerr, y, x; capsize=get(plotattributes, :capsize, 0.1))
Plots.nanappend!(plotattributes[:y], errcapy)
Plots.nanappend!(plotattributes[:x], errcapx)
else
plotattributes[:y], plotattributes[:x], plotattributes[:z] =
Plots.error_coords(yerr, y, x, z)
end
()
end
plot(1:5, yerror=fill(0.2,5), capsize = 0)
gives me this