Skip to content

Simple beeswarm

julia
export SimpleBeeswarm

This is a simple beeswarm implementation as used in Matplotlib.

julia
"""
    SimpleBeeswarm()

A simple implementation like Matplotlib's algorithm.

This algorithm dodges in `x` but preserves the exact `y` coordinate of each point.
If you don't want to preserve the y coordinate, check out `WilkinsonBeeswarm`.
"""
struct SimpleBeeswarm <: BeeswarmAlgorithm
end



function calculate!(buffer::AbstractVector{<: Point2}, alg::SimpleBeeswarm, positions::AbstractVector{<: Point2}, markersize, side::Symbol)
    @debug "Calculating..."
    ys = last.(positions)
    xs = first.(positions)
    for x_val in unique(xs)
        group = findall(==(x_val), xs)
        view_ys = view(ys, group)
        if isempty(view_ys)
            continue
        else
            ms = if markersize isa Number
                markersize
            else
                view_ms = view(markersize, group)
                maximum(view_ms)
            end
            xs[group] .= simple_xs(view_ys, ms, side)
        end
    end

    buffer .= Point2f.(xs .+ first.(positions), last.(positions))
end


function simple_xs(ys, markersize, side)
    n_points = length(ys)
    ymin, ymax = extrema(ys)
    nbins = round(Int, (ymax - ymin) ÷ markersize)
    if nbins  2
        nbins = 3
    end
    dy = markersize
    ybins = LinRange(ymin+dy, ymax-dy, nbins-1) # this is a center list of bins
    idxs = eachindex(ys)
    bin_idxs = Vector{Vector{Int}}()
    bin_vals = Vector{Vector{eltype(ys)}}()

    for (j, ybin) in enumerate(ybins)
        mask = ys .< ybin
        push!(bin_idxs, idxs[mask])
        push!(bin_vals, ys[mask])

Remove the points that are already in the bin

julia
        mask .= (!).(mask)
        idxs = idxs[mask]
        ys = ys[mask]
    end

Add the remaning elements to the last bin

julia
    push!(bin_idxs, idxs)
    push!(bin_vals, ys)

nmax = maximum(length, bin_idxs)

julia
    xs = zeros(eltype(ys), n_points)

    for (b_idxs, b_vals) in zip(bin_idxs, bin_vals)
        if length(idxs) < 1 # if only 1 element exists, continue
            continue
        else

            j = length(b_idxs) % 2

Resort the indices in the bin by y-value, which allows us to ensure that all markers in the bin are monotonically increasing in the y direction as they go farther from the center.

julia
            resorted_b_idxs = b_idxs[sortperm(b_vals)]
            if side == :both

Split the bin in two parts, evenly split.

julia
                a = resorted_b_idxs[begin:2:end]
                b = resorted_b_idxs[(begin+1):2:end]

Populate the x-array.

julia
                xs[a] .= ((1:length(a))) .* markersize .- markersize/2
                xs[b] .= ((1:length(b))) .* (-markersize) .+ markersize/2
            elseif side == :left

Populate the x-array.

julia
                xs[resorted_b_idxs] .= ((1:length(resorted_b_idxs))) .* markersize .- markersize/2
            elseif side == :right

Populate the x-array.

julia
                xs[resorted_b_idxs] .= ((1:length(resorted_b_idxs))) .* (-markersize) .+ markersize/2
            end

        end
    end
    return xs
end

This page was generated using Literate.jl.