Drawing stripped dodged bar charts in Makie

I tried to create a dodged stripped barplot where bars from each plot have different strip directions.

After consulting the documentation about dodged bars and stripped bars, I came up with the following short example:

using CairoMakie

cats = repeat(Float64[1, 2], inner=2)
heights = repeat(Float64[1, 2], outer=2)
group = repeat([1, 2], outer=2)

directions = [[1.0, 1.0], [-1.0, 1.0]]
patterns = Any[Makie.LinePattern(background_color=(:red, 0.5), direction=direction) for direction in directions]

fig = Figure()
barplot(fig[1, 1], cats, heights, dodge=group, color=patterns[group])
fig

However, I got the following error message:

ERROR: MethodError: Cannot `convert` an object of type Makie.LinePattern to an object of type Float32

The individual line patterns seem to be valid, as the following code that uses just one line pattern for every bar works fine:

fig2 = Figure()
barplot(fig2[1, 1], cats, heights, dodge=group, color=patterns[1])
fig2

I wonder where my code went wrong. Thank you very much for the help!

@jules I am currently also stack with this. Is there an easy work around ?
(maybe sorry for the audacity to ping directly :sweat_smile:)

Patterns currently only work when there’s one per plot type. So you can’t use that with stacking, you’d have to create multiple barplots and make the stacking yourself using offset. But it’s not so difficult if you make one matrix of heights and then cumsum it along one dimension

1 Like

Some code that does that

using CairoMakie, Random

# data
begin
	n = 10
	k = 3
	rng = Random.MersenneTwister(0);
	
	ys = [rand(rng, n) for _ in 1:k]
	xs = [1:n for _ in 1:k]
end;

# transform data
begin
	ys_flat = reduce(vcat, ys)
	xs_flat = reduce(vcat, xs)
	groups = reduce(vcat, [fill(i, length(x)) for (i,x) in enumerate(xs)])
end;

# plot data
begin
	colors = Makie.wong_colors()
	
	patterns = let
		patternsymbols = ["/", "x", "-"]
	[Pattern(pat, background_color=col, linecolor = :black) for (col,pat) in zip(colors, patternsymbols)]
	end

	offsets = let
		ys_cumsum = accumulate(+, ys[1:end-1])
		pushfirst!(ys_cumsum, zero(ys_cumsum[1]))
	end
end;

# get Makie dodge x values
function compute_x(x, dodge; width=1, gap=0.2, dodge_gap=0.03)
    scale_width(dodge_gap, n_dodge) = (1 - (n_dodge - 1) * dodge_gap) / n_dodge
    function shift_dodge(i, dodge_width, dodge_gap)
        (dodge_width - 1) / 2 + (i - 1) * (dodge_width + dodge_gap)
    end
    width *= 1 - gap
    n_dodge = maximum(dodge)
    dodge_width = scale_width(dodge_gap, n_dodge)
    shifts = shift_dodge.(dodge, dodge_width, dodge_gap)
    return x .+ width .* shifts
end
# Normal dodge barplot
let
	barplot(xs_flat, ys_flat; dodge=groups, color=colors[groups])
end

# Dodge barplot with patterns
let
	fig = Figure()

	a = Axis(fig[1,1])

	uniquegroups = unique(groups)
	
	dodge_gap = 0.03 #Makie default
	gap = 0.2 #Makie default
	width = 1 #Makie default	
	widthplot = width / (length(uniquegroups) + 0.2) # work around
	
	xs_explicit_flat = compute_x(xs_flat,  groups; width, gap, dodge_gap)

	for (g,pat) in zip(uniquegroups, patterns)
		indices = findall(==(g), groups)
		ys_g = ys_flat[indices]
		xs_g = xs_explicit_flat[indices]
		barplot!(a, xs_g, ys_g; color=pat, dodge_gap, gap, width=widthplot)
	end
	
	fig
end

# Stacked barplot with pattern
let
	fig = Figure()

	a = Axis(fig[1,1])

	uniquegroups = unique(groups)
	
	dodge_gap = 0.03 #Makie default
	gap = 0.2 #Makie default
	width = 1 #Makie default	
	widthplot = width / (length(uniquegroups) + 0.2) # work around

	for (g,pat,offs) in zip(uniquegroups, patterns, offsets)
		indices = findall(==(g), groups)
		ys_g = ys_flat[indices]
		xs_g = xs_flat[indices]
		barplot!(a, xs_g, ys_g; color=pat, offset=offs, gap, width=widthplot)
	end
	
	fig
end

Credits to Errorbars for grouped barplots · Issue #3300 · MakieOrg/Makie.jl · GitHub for the compute_x function. @jules do you think if the compute_x data could ever be provided by Makie directly ? Plus I had to use widthplot = width/3.2 instead of width for plotting. These both look very hacky and it would be nice if Makie could provide some interfaces/funcionalities here.