Skip to content

Michael Borregaard beeswarm

julia
export MKBorregaardBeeswarm

struct MKBorregaardBeeswarm <: BeeswarmAlgorithm end

function calculate!(buffer::AbstractVector{<: Point2}, alg::MKBorregaardBeeswarm, positions::AbstractVector{<: Point2}, markersize, side::Symbol)
    x, y = beeswarm_coords(last.(positions), :both, markersize)
    buffer .= Point2f.(x .+ first.(positions), y)
end


function beeswarm_coords(olda, side::Symbol, cellsize)

what should be the new y position of a point given it's x and it's closest point

julia
    getypos(x, ptx, pty, cellsize) = pty + sqrt(cellsize^2-(ptx-x)^2)

find the minimum y position for a given point

julia
    function minypos(xind, shift_signs = false)
           neighbors = potential_interactions(xind)
           length(neighbors) == 0 && return NaN
           yvals, xvals = view(ys, neighbors), view(a, neighbors)
           tmpyvals = shift_signs ? -yvals : yvals
           limit = maximum(tmpyvals) - cellsize

           ypos = zeros(length(xvals))
           for i in eachindex(xvals)
                  if tmpyvals[i] > limit
                         ypos[i] = getypos(a[xind], xvals[i], tmpyvals[i], cellsize)
                  end
           end
           ret = maximum(ypos) # Using maxmimum here is not an error - it is the closest point that determines the result
           shift_signs ? -ret : ret
    end

    function update_ypos!(n_ypos, inds, shift_signs = false)
           n_ypos[inds] .= minypos.(inds, shift_signs)
    end

    function potential_interactions(freeind)
           outside_range(x) = abs(a[freeind] - a[x]) > cellsize
           lo_ind = findprev(outside_range, LinearIndices(a), freeind)
           lo = isnothing(lo_ind) ? 0 : lo_ind + 1
           hi_ind = findnext(outside_range, LinearIndices(a), freeind)
           hi = isnothing(hi_ind) ? 0 : hi_ind - 1
           if lo != 0 && hi != 0 && hi >= lo
                  included[freeind] ? (lo:hi)[.!(included[lo:hi])] : (lo:hi)[included[lo:hi]]
           else
                  Int[]
           end
    end

estimate a pointsize

julia
    a = sort(shuffle(olda))
cellsize = (diff([extrema(a)...])/(length(a)^0.6))[1]

vectors to hold return values

julia
    included, ys = falses(axes(a)), zeros(length(a))

fill the first points, that are going to be along the middle line

julia
    ind = firstindex(a)
    nearest_ypos = fill(NaN, length(a))
    while !isnothing(ind)
           included[ind] = true
           ind = findfirst(v -> v > (a[ind] + cellsize), a)
    end

    @show sum(included)

now fill the rest in turn

julia
    update_ypos!(nearest_ypos, findall(.!(included)))
    innow = sum(included)

    if side in (:left, :right)
           while innow < length(a)
                  newy, placenext = findmin(nearest_ypos)
                  included[placenext] = true
                  ys[placenext] = newy
                  update_ypos!(nearest_ypos, potential_interactions(placenext))
                  nearest_ypos[placenext] = NaN
                  innow += 1
           end
    elseif side == :both
           nearest_ypos_left = -copy(nearest_ypos)
           nearest_ypos_right = nearest_ypos

           while innow < length(a)
                  newyleft, placenext_left = findmax(nearest_ypos_left)
                  newyright, placenext_right = findmin(nearest_ypos_right)

                  if -newyleft < newyright
                         placenext = placenext_left
                         included[placenext] = true
                         ys[placenext] = newyleft
                         update_ypos!(nearest_ypos_left, potential_interactions(placenext), true)
                  else
                         placenext = placenext_right
                         included[placenext] = true
                         ys[placenext] = newyright
                         update_ypos!(nearest_ypos_right, potential_interactions(placenext))
                  end

                  nearest_ypos_right[placenext] = NaN
                  nearest_ypos_left[placenext] = NaN
                  innow += 1
           end
    else
           error("side must be :left, :right, or :both")
    end

    @show ys

    rety = zeros(length(olda))
    rety[sortperm(olda)] .= ys

    if side == :left
           rety .*= -1
    end
    rety, olda
end
julia
using DataFrames, RDatasets
using SwarmMakie, CairoMakie

iris = dataset("datasets", "iris")

x,y = SwarmMakie.beeswarm_coords(collect(iris[!, :SepalLength]), :both, 7)
Makie.scatter(x,y, color = iris[!, :Species].refs, markersize = 7, axis = (; aspect = DataAspect()))

This page was generated using Literate.jl.