Skip to content

Commit

Permalink
adds multi-legend support, support for size legend
Browse files Browse the repository at this point in the history
  • Loading branch information
rdboyes committed Feb 14, 2025
1 parent d9fe21a commit bfc6afe
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 86 deletions.
2 changes: 2 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
Parquet2 = "98572fba-bba0-415d-956f-fa77e587d26d"
PooledArrays = "2dfb63ee-cc39-5dd5-95bd-886bf059d720"
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
TidierData = "fe2206b3-d496-4ee9-a338-6a095c4ece80"

Expand All @@ -42,6 +43,7 @@ Makie = "0.22"
Parquet2 = "0.2"
PooledArrays = "1.4"
Reexport = "1.2"
Statistics = "1.10"
Test = "1.10"
TidierData = "0.16"
julia = "1.10"
Expand Down
6 changes: 6 additions & 0 deletions src/TidierPlots.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ using Loess
# for geom_contour fits
using KernelDensity

# for size scales
using Statistics

include("structs.jl")

include("addplots.jl")
Expand Down Expand Up @@ -127,6 +130,9 @@ export scale_colour_continuous, scale_colour_discrete, scale_colour_manual, scal
export scale_color_continuous, scale_color_discrete, scale_color_manual, scale_color_binned
export scale_fill_continuous, scale_fill_discrete, scale_fill_manual, scale_fill_binned
export scale_alpha, scale_alpha_continuous
export scale_size
export scale_shape
export scale_linewidth

# default options

Expand Down
107 changes: 38 additions & 69 deletions src/draw.jl
Original file line number Diff line number Diff line change
Expand Up @@ -265,67 +265,21 @@ function as_GridLayout(plot::GGPlot)

push!(optional_aes_data, Symbol(a) => data)

palette_function = get(plot.axis_options.palette, :color, nothing)

isnothing(palette_function) && continue
!haskey(_legend_geom_symbols,
geom.args["geom_name"]) && continue
!haskey(_legend_geom_elements,
geom.args["geom_name"]) && continue

labels_for_this_aes = subset(labels_aes_df,
:col_name => ByRow(x -> x == a))

if haskey(plot.axis_options.legend_options, :color)
l_type = get(plot.axis_options.legend_options[:color], :type, "na")
legend_title = get(plot.axis_options.legend_options[:color], :name, " ")
draw_colorbar = get(plot.axis_options.legend_options[:color], :guide, :auto)
if draw_colorbar == :auto
if l_type in ["continuous", "binned"]
draw_colorbar = :colorbar
elseif l_type in ["discrete", "manual"]
draw_colorbar = :legend
end
end
else
draw_colorbar = :none
legend_title = " "
end

if draw_colorbar == :colorbar

colorbar_kwargs[:colormap] =
plot.axis_options.legend_options[:color][:type] == "continuous" ? Symbol(plot.axis_options.legend_options[:color][:palette]) :
cgrad(Symbol(plot.axis_options.legend_options[:color][:palette]), 5, categorical=true)

colorbar_lowlim = min(
minimum(labels_for_this_aes.original_value), colorbar_lowlim)

colorbar_highlim = max(
maximum(labels_for_this_aes.original_value), colorbar_highlim)

colorbar = true
elseif draw_colorbar == :legend
append!(legend,
sort(DataFrame(
labels=labels_for_this_aes.original_value,
colors=labels_for_this_aes.new_value,
options=get(
_legend_geom_symbols,
geom.args["geom_name"],
Dict(:marker => :circle, :markersize => 12)
),
element=get(
_legend_geom_elements,
geom.args["geom_name"],
MarkerElement
),
title=legend_title
),
:labels)
)
end

colorbar_highlim,
colorbar_lowlim,
colorbar_kwargs,
colorbar,
legend = update_legend(
legend,
plot.axis_options,
geom.args["geom_name"],
a,
labels_aes_df,
colorbar_highlim,
colorbar_lowlim,
colorbar_kwargs,
colorbar
)
end

args = Tuple([geom.visual, required_aes_data...])
Expand Down Expand Up @@ -381,20 +335,35 @@ function as_GridLayout(plot::GGPlot)
if nrow(legend) == 0 && !colorbar
l = nothing
elseif nrow(legend) != 0
labels = String[]
elems = Any[]

title = legend_title
labels_list = []
elems_list = []
titles_list = []

for t in unique(legend.title)

labels = String[]
elems = Any[]

sublegend = subset(legend, :title => ByRow(x -> x == t))

title = first(sublegend.title)

for l in eachrow(sublegend)
push!(elems, l.element(color=l.colors; l.options...))
push!(labels, string(l.labels))
end

legend = subset(legend, :colors => ByRow(x -> typeof(x) <: Colorant))
push!(labels_list, labels)
push!(elems_list, elems)
push!(titles_list, title)

for (k, v) in pairs(groupby(legend, :labels))
push!(elems, [l.element(color=l.colors; l.options...) for l in eachrow(v)])
push!(labels, string(v.labels[1]))
end

l = (1, 2) => Makie.SpecApi.GridLayout(
Makie.SpecApi.Legend(elems, labels, title))
Makie.SpecApi.Legend([e for e in elems_list],
[l for l in labels_list],
[t for t in titles_list]))
else
title = get(plot.axis_options.legend_options[:color], :name, " ")
l = (1, 2) => Makie.SpecApi.GridLayout(
Expand Down
6 changes: 3 additions & 3 deletions src/labs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ function labs(args...; kwargs...)
)

return AxisOptions(
Dict(ggplot_to_makie[k] => v for (k, v) in args_dict),
Dict(ggplot_to_makie[k] => v for (k, v) in args_dict if k in keys(ggplot_to_makie)),
Dict(),
Dict()
Dict(Symbol(k) => Dict(:name => v) for (k, v) in args_dict if !(k in keys(ggplot_to_makie)))
)
end

Expand All @@ -22,7 +22,7 @@ end
function lims(args...; kwargs...)
aes_dict, args_dict = extract_aes(args, kwargs)

lims_dict = Dict{Symbol, Any}()
lims_dict = Dict{Symbol,Any}()

if haskey(args_dict, "x")
if haskey(args_dict, "y")
Expand Down
92 changes: 92 additions & 0 deletions src/legend.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,95 @@ function guides(args...; kwargs...)
Dict(k => Dict(:guide => Symbol(v)) for (k, v) in Dict(kwargs))
)
end

function update_legend(legend,
plot_axis_options,
geom_name,
a,
labels_aes_df,
colorbar_highlim,
colorbar_lowlim,
colorbar_kwargs,
colorbar)

palette_function = get(plot_axis_options.palette, Symbol(a), nothing)

isnothing(palette_function) && return (colorbar_highlim, colorbar_lowlim, colorbar_kwargs, colorbar, legend)
!haskey(_legend_geom_symbols,
geom_name) && return (colorbar_highlim, colorbar_lowlim, colorbar_kwargs, colorbar, legend)
!haskey(_legend_geom_elements,
geom_name) && return (colorbar_highlim, colorbar_lowlim, colorbar_kwargs, colorbar, legend)

labels_for_this_aes = subset(labels_aes_df,
:col_name => ByRow(x -> x == a))

if haskey(plot_axis_options.legend_options, Symbol(a))
l_type = get(plot_axis_options.legend_options[Symbol(a)], :type, "na")
legend_title = get(plot_axis_options.legend_options[Symbol(a)], :name, a)
draw_colorbar = get(plot_axis_options.legend_options[Symbol(a)], :guide, :auto)
if draw_colorbar == :auto
if l_type in ["continuous", "binned"]
draw_colorbar = :colorbar
elseif l_type in ["discrete", "manual"]
draw_colorbar = :legend
end
end
else
draw_colorbar = :none
legend_title = a
end

if draw_colorbar == :colorbar
colorbar_kwargs[:colormap] =
plot_axis_options.legend_options[Symbol(a)][:type] == "continuous" ? Symbol(plot_axis_options.legend_options[Symbol(a)][:palette]) :
cgrad(Symbol(plot_axis_options.legend_options[Symbol(a)][:palette]), 5, categorical=true)

colorbar_lowlim = min(
minimum(labels_for_this_aes.original_value), colorbar_lowlim)

colorbar_highlim = max(
maximum(labels_for_this_aes.original_value), colorbar_highlim)

colorbar = true
elseif draw_colorbar == :legend
append!(legend,
sort(DataFrame(
labels=labels_for_this_aes.original_value,
colors=labels_for_this_aes.new_value,
options=get(
_legend_geom_symbols,
geom_name,
Dict(:marker => :circle, :markersize => 12)
),
element=get(
_legend_geom_elements,
geom_name,
MarkerElement
),
title=legend_title
),
:labels), promote=true
)
elseif draw_colorbar == :legend_size
o = labels_for_this_aes.original_value
n = labels_for_this_aes.new_value

Check warning on line 82 in src/legend.jl

View check run for this annotation

Codecov / codecov/patch

src/legend.jl#L81-L82

Added lines #L81 - L82 were not covered by tests

ro = quantile(o, [0, .25, .5, .75, 1])
rn = quantile(n, [0, .25, .5, .75, 1])

Check warning on line 85 in src/legend.jl

View check run for this annotation

Codecov / codecov/patch

src/legend.jl#L84-L85

Added lines #L84 - L85 were not covered by tests

append!(legend,

Check warning on line 87 in src/legend.jl

View check run for this annotation

Codecov / codecov/patch

src/legend.jl#L87

Added line #L87 was not covered by tests
sort(DataFrame(
labels=ro,
colors=RGB(0,0,0),
options=[Dict(:marker => :circle, :markersize => n) for n in rn],
element=MarkerElement,
title=legend_title
),
:labels), promote=true
)
end

return (
colorbar_highlim, colorbar_lowlim, colorbar_kwargs, colorbar, legend
)
end
23 changes: 9 additions & 14 deletions src/scales/size.jl
Original file line number Diff line number Diff line change
@@ -1,30 +1,25 @@
"""
Internal function. Takes a Dict and makes a function that maps a numeric vector to the range specfied in the Dict's :range key, or to [0.1, 1.0] if not specified.
Internal function. Converts args_dict to AxisOptions.
"""
function make_size_lookup_continuous(args_dict::Dict)
r = get(args_dict, :range, [1, 6])
function size_scale_to_ggoptions(args_dict::Dict)
palette = Dict{Symbol,Function}()
r = get(args_dict, :range, [1, 5])

Check warning on line 6 in src/scales/size.jl

View check run for this annotation

Codecov / codecov/patch

src/scales/size.jl#L4-L6

Added lines #L4 - L6 were not covered by tests

function size_lookup_continuous(input)
return r[1] .+ (r[2] - r[1]) .* (input ./ maximum(input))
return r[1] .+ (r[2] - r[1]) .*

Check warning on line 9 in src/scales/size.jl

View check run for this annotation

Codecov / codecov/patch

src/scales/size.jl#L9

Added line #L9 was not covered by tests
((input .- minimum(input)) ./ maximum(input))
end
return size_lookup_continuous
end

"""
Internal function. Converts args_dict to AxisOptions.
"""
function size_scale_to_ggoptions(args_dict::Dict)
palette = Dict{Symbol,Function}()
palette[:linewidth] = make_size_lookup_continuous(args_dict)
palette[:markersize] = size_lookup_continuous

Check warning on line 13 in src/scales/size.jl

View check run for this annotation

Codecov / codecov/patch

src/scales/size.jl#L13

Added line #L13 was not covered by tests

return AxisOptions(
Dict(),
palette,
Dict(:linewidth => args_dict)
Dict(:markersize => merge(Dict(:guide => :legend_size), args_dict))
)
end

scale_linewidth = color_scale_template(
scale_size = color_scale_template(
"size",
size_scale_to_ggoptions,
"continuous"
Expand Down

0 comments on commit bfc6afe

Please sign in to comment.