Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dodge in scatter,lines #393

Closed
behinger opened this issue May 20, 2022 · 8 comments · Fixed by #558
Closed

Dodge in scatter,lines #393

behinger opened this issue May 20, 2022 · 8 comments · Fixed by #558

Comments

@behinger
Copy link

Problem description

I often encountered the issue, that there is no way to dodge datapoints in a scatter (or less important imho line) plot.

That is going from here:
image
To here:
image

Something similar is asked here #230 I think

Proposed solution

I dont understand enough of the internals, but as I understand Makie.jl implements the dodge feature for violin & histograms.

In ggplot2 dodging is possible by specifing a position-function (position = position_dodge(width=0.2)). I was expecting something like
data(df) * mapping(x,y,color="categoryA",dodge="categoryA") * visual(Scatter) to "just work", by doding the colored datapoints.

Let's just hope I missed something ;-)

@behinger
Copy link
Author

behinger commented Feb 5, 2023

ok - I needed this again and couldnt find a solution. I implemented a new Makie Recipe Dodge, which allows you to do exactly this.

Please find the gist here: https://gist.github.com/behinger/8df41a3e051979a8e8ee0068f1aac6b8

I don't really know whether @recipe Dodge should be rather in Makie.jl @SimonDanisch what do you think?

I also dont know if this is the nicest way, but one can dodge all kinds of plots. tried with scatter and errorbars. dodging in y instead of x is supported as well.
image

MWE

begin
	using Makie
	using CairoMakie
	using HTTP
HTTP.download("https://gist.githubusercontent.com/behinger/8df41a3e051979a8e8ee0068f1aac6b8/raw/4c6f44603d4432b19c8abe2955eab4962a8ae45f/MakieDodge.jl",tempdir()) |>include
	
	#----
	x = [1,2,3,  1,2,3.,  3,2,1]
	y = [1.1,1.9,2.8,1,2,3,3.05,2.05,1.05]
	c = ["red","red","red","green","green","green","blue","blue","blue"]
	low,high = repeat([0.2],length(x))
	dodge(x,y,low,high;plot_fun=errorbars!,color=c)
	
	dodge!(x,y;color=c, plot_fun=scatter!)
	current_figure()
	

end

@behinger
Copy link
Author

behinger commented Feb 5, 2023

ah and of course with AoG
image

let
		d = DataFrame(:x=>["x2","x1","x2","x1","x3","x3","x4"],:y=>[2.,1.1,2.1,1.,3.,3.1,4],:c=>["a","a","b","b","b","b","b"])
	d.high = repeat([0.1],nrow(d))
	d.low = repeat([0.1],nrow(d))
	
	data(d)* 
		mapping(color=:c,dodge=:c) *
		(
			mapping(:x,:y,:low,:high,)* visual(Dodge,plot_fun=errorbars!) +
	 		mapping(:x,:y)			  * visual(Dodge)
		) |> draw
	
end

@pdeffebach
Copy link

@jkrumbiegel I would be super interested in this feature! Not sure how to add it. But if it could go higher on the priority list I would appreciate it!

@jkrumbiegel
Copy link
Member

I'm not sure, yet, either, how exactly this could be implemented. But in principle one can add arbitrary keywords to the aesthetic mapping of a plotting function, which just have to be removed before everything is transferred to Makie. Maybe one could add some sort of generic logic to to_entry which detects the dodge keyword and modifies the data belonging to the X or Y aesthetic accordingly. The generic to_entry functions are here

function to_entry(p::ProcessedLayer, categoricalscales::Dictionary, continuousscales::Dictionary)
entry = to_entry(p.plottype, p, categoricalscales, continuousscales)
insert!(entry.named, :cycle, nothing)
for key in (:group, :layout, :row, :col)
if haskey(entry.named, key)
delete!(entry.named, key)
end
end
return entry
end
function to_entry(P, p::ProcessedLayer, categoricalscales::Dictionary, continuousscales::Dictionary)
aes_mapping = aesthetic_mapping(p)
scale_mapping = p.scale_mapping
positional = map(eachindex(p.positional)) do i
full_rescale(p.positional[i], i, aes_mapping, scale_mapping, categoricalscales, continuousscales)
end
named = map(pairs(p.named)) do (key, value)
full_rescale(value, key, aes_mapping, scale_mapping, categoricalscales, continuousscales)
end
primary = map(pairs(p.primary)) do (key, values)
if key in (:group, :layout, :col, :row)
return values
end
# seems that there can be vectors here for concatenated layers, but for unconcatenated these should
# be scalars
if values isa AbstractVector
full_rescale(values, key, aes_mapping, scale_mapping, categoricalscales, continuousscales)
else
values = [values]
rescaled = full_rescale(values, key, aes_mapping, scale_mapping, categoricalscales, continuousscales)
rescaled[]
end
end
Entry(P, positional, merge(p.attributes, named, primary))
end
. Then the plotting functions wouldn't need to know about dodging at all, they would just receive modified x and y data.

@pdeffebach
Copy link

I think the issue is that Makie itself has no concept of dodge for certain things like lines and errorbars. So this has to be fixed upstream, right?

@jkrumbiegel
Copy link
Member

Well as I wrote above, if you consider dodging in data space, then you don't really have to add it to plotting functions. You could, but you don't have to, depending on what exactly is plotted and whether there's some advanced interactions like in barplots where width needs to change as well.

@pdeffebach
Copy link

Yes sorry, my particular application is bar plots with error bars on the top.

@pdeffebach
Copy link

As a concrete example, here is what I want to be able to do with AoG:

julia> df = DataFrame(
           g = ["a", "b", "c", "a", "b", "c"],  
           country = ["X", "X", "X", "Y", "Y", "Y"],
           val = [1.0, 1.5, 2.0, 4.3, 3.6, 2.9], 
           err = [0.2, 0.4, 0.1, 0.3, 0.1, 0.4]);

julia> data(df) * (mapping(:g, :val, color = :country, dodge = :country) * visual(BarPlot) + mapping(:g, :val, :err; dodge = :country) * visual(Errorbars)) |> draw

I get

ERROR: ArgumentError: ProcessedLayer with plot type Plot{Makie.errorbars} did not have :dodge in its AestheticMapping. The mapping was {1 = AlgebraOfGraphics.AesX, 2 = AlgebraOfGraphics.AesY, 3 = AlgebraOfGraphics.AesDeltaY, :color = AlgebraOfGraphics.AesColor}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Done
Development

Successfully merging a pull request may close this issue.

3 participants