Skip to content

Commit

Permalink
support for functions in aes
Browse files Browse the repository at this point in the history
  • Loading branch information
rdboyes committed Apr 4, 2024
1 parent 3efca51 commit 254a41b
Show file tree
Hide file tree
Showing 12 changed files with 102 additions and 31 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "TidierPlots"
uuid = "337ecbd1-5042-4e2a-ae6f-ca776f97570a"
authors = ["Randall Boyes <[email protected]> and contributors"]
version = "0.6.2"
version = "0.6.3"

[deps]
CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
Expand Down
5 changes: 5 additions & 0 deletions src/TidierPlots.jl
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ export scale_colour_continuous, scale_color_continuous
export scale_colour_discrete, scale_color_discrete
export scale_colour_manual, scale_color_manual

# transforms

export cat_inseq, cat_inorder, number_on_axis, as_is, discard, verbatim, kernel_density_2d
export as_color

const plot_log = Ref{Bool}(true)
const plot_show = Ref{Bool}(true)
const plot_pluto = Ref{Bool}(true)
Expand Down
31 changes: 28 additions & 3 deletions src/aes.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,32 @@
function aes(args...; kwargs...)
col_transforms = Dict()
aes_args = Symbol[]
aes_kwargs = Dict{String, Symbol}()

for arg in args
if arg isa Pair
push!(col_transforms, arg)
push!(aes_args, arg[1])
else
push!(aes_args, Symbol(arg))
end
end

d = Dict(kwargs)
return Aesthetics(Symbol.([args...]),
Dict([String(key) => Symbol(d[key]) for key in keys(d)]))

for (k, v) in d
if v isa Pair
push!(col_transforms, v)
push!(aes_kwargs, String(k) => Symbol(v[1]))
else
push!(aes_kwargs, String(k) => Symbol(v))
end
end

return Aesthetics(
aes_args,
aes_kwargs,
col_transforms)
end

macro aes(exprs...)
Expand All @@ -22,7 +47,7 @@ macro aes(exprs...)
push!(positional, Symbol(aes_ex))
end
end
return Aesthetics(positional, aes_dict)
return Aesthetics(positional, aes_dict, Dict())
end

@eval const $(Symbol("@es")) = $(Symbol("@aes"))
13 changes: 12 additions & 1 deletion src/extract_aes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ function make_aes_extractor(required_aes)
return function extract_aes(args, kwargs)
aes_dict = Dict{String, Symbol}()
args_dict = Dict{String, Any}()
transforms = nothing

for arg in args
if arg isa DataFrame
Expand All @@ -21,13 +22,23 @@ function make_aes_extractor(required_aes)
end
end
aes_dict = merge(aes_dict, arg.named)
transforms = arg.column_transformations
end
end

if !isnothing(transforms)
rev_aes_dict = Dict([v => k for (k, v) in aes_dict])
transforms = Dict([Symbol(rev_aes_dict[k]) => [Symbol(rev_aes_dict[k])] => v for (k, v) in transforms])
println(transforms)
else
println("TRANSFORMS EMPTY")
transforms = Dict{Symbol, Pair{Vector{Symbol}, AesTransform}}()
end

d = Dict(kwargs)
args_dict = merge(args_dict, Dict([String(key) => d[key] for key in keys(d)]))

return (aes_dict, args_dict)
return (aes_dict, args_dict, transforms)
end
end

Expand Down
2 changes: 1 addition & 1 deletion src/geoms/geom_contour.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ end
geom_tile = geom_template("geom_tile", ["x", "y", "z"], :Heatmap)
geom_contour = geom_template("geom_contour", ["x", "y"], :Contour;
aes_function = stat_density_2d,
column_transformations = Dict{Symbol, Pair{Vector{Symbol}, Function}}(
column_transformations = Dict{Symbol, Pair{Vector{Symbol}, AesTransform}}(
:x => [:x]=>discard,
:y => [:y]=>discard,
:z => [:x, :y]=>kernel_density_2d))
8 changes: 4 additions & 4 deletions src/geoms/geom_errorbar.jl
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
function geom_errorbar(args...; kwargs...)
aes_dict, args_dict = extract_aes(args, kwargs)
aes_dict, args_dict, transforms = extract_aes(args, kwargs)

args_dict["geom_name"] = "geom_errorbar"

return build_geom(aes_dict, args_dict,
["x", "ymin", "ymax"], # required aesthetics
:Rangebars, # function for visual layer
do_nothing,
Dict{Symbol, Pair{Vector{Symbol}, Function}}();
transforms;
special_aes = Dict("width" => "whiskerwidth"))
end

function geom_errorbarh(args...; kwargs...)
aes_dict, args_dict = extract_aes(args, kwargs)
aes_dict, args_dict, transforms = extract_aes(args, kwargs)

args_dict["geom_name"] = "geom_errorbarh"
args_dict["errorbar_direction"] = :x
Expand All @@ -21,7 +21,7 @@ function geom_errorbarh(args...; kwargs...)
["y", "xmin", "xmax"], # required aesthetics
:Rangebars, # function for visual layer
do_nothing,
Dict{Symbol, Pair{Vector{Symbol}, Function}}();
transforms;
special_aes = Dict("width" => "whiskerwidth"))

end
Expand Down
8 changes: 5 additions & 3 deletions src/geoms/geom_hvline.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
function geom_hline(args...; kwargs...)
aes_dict, args_dict = extract_aes(args, kwargs)
aes_dict, args_dict, transforms = extract_aes(args, kwargs)

args_dict["geom_name"] = "geom_hline"

Expand All @@ -11,7 +11,8 @@ function geom_hline(args...; kwargs...)
return build_geom(aes_dict, args_dict,
["yintercept"], # required aesthetics
:HLines,
do_nothing, Dict{Symbol, Pair{Vector{Symbol}, Function}}()) # function for visual layer
do_nothing,
transforms) # function for visual layer
end

function geom_vline(args...; kwargs...)
Expand All @@ -27,7 +28,8 @@ function geom_vline(args...; kwargs...)
return build_geom(aes_dict, args_dict,
["xintercept"], # required aesthetics
:VLines,
do_nothing, Dict{Symbol, Pair{Vector{Symbol}, Function}}()) # function for visual layer
do_nothing,
transforms) # function for visual layer
end

function geom_hline(plot::GGPlot, args...; kwargs...)
Expand Down
8 changes: 4 additions & 4 deletions src/geoms/geom_smooth.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function geom_smooth(plot::GGPlot, args...; kwargs...)
end

function geom_smooth(args...; kwargs...)
aes_dict, args_dict = extract_aes(args, kwargs)
aes_dict, args_dict, transforms = extract_aes(args, kwargs)
args_dict["geom_name"] = "geom_smooth"

if haskey(args_dict, "method")
Expand All @@ -41,13 +41,13 @@ function geom_smooth(args...; kwargs...)
["x", "y"],
:Lines,
stat_linear,
Dict{Symbol, Pair{Vector{Symbol}, Function}}()),
transforms),
build_geom(aes_dict,
args_dict,
["x", "lower", "upper"],
:Band,
stat_linear,
Dict{Symbol, Pair{Vector{Symbol}, Function}}())]
transforms)]
end
end

Expand All @@ -56,7 +56,7 @@ function geom_smooth(args...; kwargs...)
["x", "y"],
:Lines,
stat_loess,
Dict{Symbol, Pair{Vector{Symbol}, Function}}())
transforms)
end

function stat_loess(aes_dict::Dict{String, Symbol},
Expand Down
6 changes: 3 additions & 3 deletions src/geoms/geom_template.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@ function geom_template(name::AbstractString,
required_aes::AbstractArray,
spec_api_function::Symbol;
aes_function::Function = do_nothing,
column_transformations::Dict{Symbol, Pair{Vector{Symbol}, Function}} = Dict{Symbol, Pair{Vector{Symbol}, Function}}(),
column_transformations::Dict{Symbol, Pair{Vector{Symbol}, AesTransform}} = Dict{Symbol, Pair{Vector{Symbol}, AesTransform}}(),
extra_args::Dict = Dict())

extract_geom_aes = make_aes_extractor(required_aes)

function geom_function(args...; kwargs...)
aes_dict, args_dict = extract_geom_aes(args, kwargs)
aes_dict, args_dict, transforms = extract_geom_aes(args, kwargs)
args_dict["geom_name"] = name
args_dict = merge(args_dict, extra_args)

return build_geom(aes_dict, args_dict,
required_aes,
spec_api_function,
aes_function,
column_transformations)
merge(transforms, column_transformations))
end

function geom_function(plot::GGPlot, args...; kwargs...)
Expand Down
4 changes: 2 additions & 2 deletions src/geoms/geom_text.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
geom_text = geom_template("geom_text", ["x", "y"], :Text;
column_transformations = Dict{Symbol, Pair{Vector{Symbol}, Function}}(:text => [:text]=>verbatim))
column_transformations = Dict{Symbol, Pair{Vector{Symbol}, AesTransform}}(:text => [:text]=>verbatim))
geom_label = geom_template("geom_label", ["x", "y"], :Text;
column_transformations = Dict{Symbol, Pair{Vector{Symbol}, Function}}(:text => [:text]=>verbatim))
column_transformations = Dict{Symbol, Pair{Vector{Symbol}, AesTransform}}(:text => [:text]=>verbatim))
1 change: 1 addition & 0 deletions src/structs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ end
struct Aesthetics
positional::AbstractArray
named::Dict
column_transformations::Dict
end

struct AxisOptions
Expand Down
45 changes: 36 additions & 9 deletions src/transforms.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,39 @@ struct PlottableData
label_function::Any
end

# AesTransform acts like a class of functions

struct AesTransform
fn::Function
end

# When called with the standard signature, an AesFunction object
# calls its internal function with those arguments

(at::AesTransform)(target::Symbol, source::Vector{Symbol}, data::DataFrame) = at.fn(target, source, data)

# When called with a string or symbol, as would happen in an aes() call
# returns a dict of the same type used in column_transformations

(at::AesTransform)(sym::Symbol) = sym => at
(at::AesTransform)(str::String) = Symbol(str) => at

# simplest one is as_is, which just gets a column
# exactly as it is in the DataFrame

function as_is(target::Symbol, source::Vector{Symbol}, data::DataFrame)
function as_is_fn(target::Symbol, source::Vector{Symbol}, data::DataFrame)
return Dict{Symbol, PlottableData}(
target => PlottableData(
data[!, source[1]], # get the column out of the dataframe
data[!, source[1]], # get the column out of the dataframe
identity, # do nothing to it
nothing,
nothing
)
)
end

as_is = AesTransform(as_is_fn)

# verbatim has a similar goal, but for String columns

function convert_to(type::Type)
Expand All @@ -43,9 +62,9 @@ function convert_to(type::Type)
end
end

verbatim = convert_to(String)
verbatim = AesTransform(convert_to(String))

function number_on_axis(target::Symbol, source::Vector{Symbol}, data::DataFrame)
function number_on_axis_fn(target::Symbol, source::Vector{Symbol}, data::DataFrame)
return Dict{Symbol, PlottableData}(
target => PlottableData(
data[!, source[1]], # get the column out of the dataframe
Expand All @@ -56,11 +75,11 @@ function number_on_axis(target::Symbol, source::Vector{Symbol}, data::DataFrame)
)
end


number_on_axis = AesTransform(number_on_axis_fn)

# categorical array handling options for String columns

function cat_inorder(target::Symbol, source::Vector{Symbol}, data::DataFrame)
function cat_inorder_fn(target::Symbol, source::Vector{Symbol}, data::DataFrame)
cat_column = data[!, source[1]]
cat_array = CategoricalArray(cat_column,
levels = unique(cat_column),
Expand All @@ -80,7 +99,9 @@ function cat_inorder(target::Symbol, source::Vector{Symbol}, data::DataFrame)
)
end

function cat_inseq(target::Symbol, source::Vector{Symbol}, data::DataFrame)
cat_inorder = AesTransform(cat_inorder_fn)

function cat_inseq_fn(target::Symbol, source::Vector{Symbol}, data::DataFrame)
cat_array = CategoricalArray(data[!, source[1]])

label_target = target == :x ? :xticks :
Expand All @@ -97,9 +118,11 @@ function cat_inseq(target::Symbol, source::Vector{Symbol}, data::DataFrame)
)
end

cat_inseq = AesTransform(cat_inseq_fn)

# kernel density estimation for geom_contour

function kernel_density_2d(target::Symbol, source::Vector{Symbol}, data::DataFrame)
function kernel_density_2d_fn(target::Symbol, source::Vector{Symbol}, data::DataFrame)

k = kde((data[!, source[1]], data[!, source[2]]))

Expand All @@ -115,12 +138,16 @@ function kernel_density_2d(target::Symbol, source::Vector{Symbol}, data::DataFra
return return_dict
end

kernel_density_2d = AesTransform(kernel_density_2d_fn)

# returns nothing, removing aes from graph

function discard(target::Symbol, source::Vector{Symbol}, data::DataFrame)
function discard_fn(target::Symbol, source::Vector{Symbol}, data::DataFrame)
return Dict{Symbol, PlottableData}()
end

discard = AesTransform(discard_fn)

# tweaks
# takes an existing PlottableData object and modifies the makie_function

Expand Down

0 comments on commit 254a41b

Please sign in to comment.