Open wmstack opened 9 months ago
Hi, I would like to work on this, do you mind assigning it to me? I am new to open-source contributions so any guidance on where to locate the relevant code would be really appreciated. Thanks!
I think for a start we can add the handling of this to the bar
recipe here:
https://github.com/JuliaPlots/Plots.jl/blob/fcf2e74258e7eb7dbba36c91182d3e8aa3d7c712/src/recipes.jl#L407-L507
I added this segment of code to recipes.jl below. I am trying to test it to see if it works as intended but every time I test through the Julia app it uses the original code instead of the modified one. If anyone could let me know how to test it or if there are any issues with the code I would really appreciate it. Thanks
# custom bar plot function for named tuples
@recipe function f(::Type{Val{:bar}}, nt::NamedTuple)
names = collect(keys(nt)) # Extract names from the named tuple
values = collect(values(nt)) # Extract values from the named tuple
# Convert names to a format that can be used for plotting
plotnames = map(string, names)
x := plotnames
y := values
seriestype := :bar
# Do I have to call the other bar plot function or would this work?
end
Yeah, such a dispatch mechanism would be nice, but unfortunately that won't work here.
If you insert a @show x, y, z
you will see, how the input gets passed to the recipe function (its likely in y
).
But you probably can add a method to _preprocess_barlike
or an inner function there to do a similiar thing. Alternatively we just add an if
block to the recipe.
Okay thank you for the advice. I think this is the correct implementation, but let me know if anything is incorrect. Also, what is the best way of testing the code? When I test, it uses the official JuliaPlots code instead of my modified one. I looked at the JuliaPlots documentation but could not find much on this. I want to ensure that it works fine before creating a pull request.
Here is the updated function in recipes.jl:
# create a bar plot as a filled step function
@recipe function f(::Type{Val{:bar}}, x, y, z) # COV_EXCL_LINE
# check if 'y' is a named tuple and handle accordingly
if typeof(y) == NamedTuple
names = collect(keys(y))
values = collect(values(y))
plotnames = map(string, names)
x = plotnames
y = values
else
println("original called")
end
ywiden --> false
procx, procy, xscale, yscale, _ = _preprocess_barlike(plotattributes, x, y)
nx, ny = length(procx), length(procy)
axis = plotattributes[:subplot][isvertical(plotattributes) ? :xaxis : :yaxis]
cv = map(xi -> discrete_value!(plotattributes, :x, xi)[1], procx)
procx = if nx == ny
cv
elseif nx == ny + 1
0.5diff(cv) + @view(cv[1:(end - 1)])
else
error(
"bar recipe: x must be same length as y (centers), or one more than y (edges).\n\t\tlength(x)=$(length(x)), length(y)=$(length(y))",
)
end
# compute half-width of bars
bw = plotattributes[:bar_width]
hw = if bw === nothing
0.5_bar_width * if nx > 1
ignorenan_minimum(filter(x -> x > 0, diff(sort(procx))))
else
1
end
else
map(i -> 0.5_cycle(bw, i), eachindex(procx))
end
# make fillto a vector... default fills to 0
if (fillto = plotattributes[:fillrange]) === nothing
fillto = 0
end
if yscale in _logScales && !all(_is_positive, fillto)
# github.com/JuliaPlots/Plots.jl/issues/4502
# https://github.com/JuliaPlots/Plots.jl/issues/4774
T = float(eltype(y))
min_y = NaNMath.minimum(y)
base = _logScaleBases[yscale]
baseline = floor_base(min_y, base)
if min_y == baseline
baseline /= base
end
fillto = map(x -> _is_positive(x) ? T(x) : T(baseline), fillto)
end
xseg, yseg = map(_ -> Segments(), 1:2)
valid_i = isfinite.(procx) .& isfinite.(procy)
for i in 1:ny
valid_i[i] || continue
yi = procy[i]
center = procx[i]
hwi = _cycle(hw, i)
fi = _cycle(fillto, i)
push!(xseg, center - hwi, center - hwi, center + hwi, center + hwi, center - hwi)
push!(yseg, yi, fi, fi, yi, yi)
end
# widen limits out a bit
expand_extrema!(axis, scale_lims(ignorenan_extrema(xseg.pts)..., default_widen_factor))
# switch back
if !isvertical(plotattributes)
xseg, yseg = yseg, xseg
x, y = y, x
end
# reset orientation
orientation := default(:orientation)
# draw the bar shapes
@series begin
seriestype := :shape
series_annotations := nothing
primary := true
x := xseg.pts
y := yseg.pts
# expand attributes to match indices in new series data
for k in _segmenting_vector_attributes ∪ _segmenting_array_attributes
if (v = get(plotattributes, k, nothing)) isa AVec
if eachindex(v) != eachindex(y)
@warn "Indices $(eachindex(v)) of attribute `$k` do not match data indices $(eachindex(y))."
end
# Each segment is 6 elements long, including the NaN separator.
# One segment is created for each non-NaN element of `procy`.
# There is no trailing NaN, so the last repetition is dropped.
plotattributes[k] = @views repeat(v[valid_i]; inner = 6)[1:(end - 1)]
end
end
()
end
# add empty series
primary := false
seriestype := :scatter
markersize := 0
markeralpha := 0
fillrange := nothing
x := procx
y := procy
()
end
To test that code you run:
using Pkg
Pkg.activate(temp=true)
Pkg.develop("Plots")
then you change the code in ~/.julia/dev/Plots
and run
Pkg.activate(joinpath(homedir(), ".julia", "dev", "Plots"))
using Plots
and then your code should get used.
One of the things that I liked about R was the fact that you could very swiftly spin up a bar chart by using named tuples with
c(A = 1, B = 2, C = 3)
etc...This is not possible with Julia's named tuples and this seems like a downgrade. On the one hand, you don't need that
c
function call, on the other hand, they seem less useful. You can try this out with the following code at https://webr.r-wasm.org/latest/