From b25d4df422824e21c2c837e2b593d54541489fe4 Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev <33777074+vladdez@users.noreply.github.com> Date: Tue, 24 Oct 2023 15:37:00 +0000 Subject: [PATCH] Erp plot documentation (#86) * Update plot_erp.jl * new tests, combined plot in docs * del * additional docs problems * little detail * dead links * docstring warnings * cross-refs again * test * pp_plot fixed * docs errors should be resolved * Update hide_deco.md * colorbar docs * liting * add Voltage label * linting + name consistency * formatter * no extra for circular * no extra toposeries * test for design matrix and no extra * test * no extra for erp * del extra in erpimage and parplot * g * citability --- CITATION.cff | 28 +++ docs/src/literate/tutorials/circTopo.jl | 28 ++- docs/src/literate/tutorials/circTopo.md | 28 ++- docs/src/literate/tutorials/erp.jl | 47 ++--- docs/src/literate/tutorials/erp.md | 47 +++-- docs/src/tutorials/butterfly.md | 10 +- docs/src/tutorials/designmatrix.md | 10 +- docs/src/tutorials/erpimage.md | 6 +- src/UnfoldMakie.jl | 2 +- src/eeg_series.jl | 195 +++++++++++++++++++ src/plot_circulareegtopoplot.jl | 158 ++++++++++----- src/plot_designmatrix.jl | 54 +++--- src/plot_erp.jl | 166 +++++++++++----- src/plot_erpimage.jl | 63 ++++-- src/plot_parallelcoordinates.jl | 105 ++++++---- src/plot_topoplot.jl | 20 +- src/plot_topoplotseries.jl | 93 ++++++--- src/plotconfig.jl | 243 +++++++++++------------- src/topo_color.jl | 4 +- test/test_all.jl | 9 +- test/test_butterfly.jl | 15 +- test/test_dm.jl | 11 ++ test/test_erp.jl | 48 +++++ test/test_erpimage.jl | 23 ++- test/test_plot_circulareegtopoplot.jl | 2 +- test/test_toposeries.jl | 35 +++- 26 files changed, 1025 insertions(+), 425 deletions(-) create mode 100644 CITATION.cff create mode 100644 src/eeg_series.jl create mode 100644 test/test_dm.jl create mode 100644 test/test_erp.jl diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 000000000..436cfb1da --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,28 @@ +# This CITATION.cff file was generated with cffinit. +# Visit https://bit.ly/cffinit to generate yours today! + +cff-version: 1.2.0 +title: UnfoldMakie +message: 'If you use this page, please cite it as below.' +type: software +authors: + - given-names: Benedikt + family-names: Ehinger + orcid: 'https://orcid.org/0000-0002-6276-3332' + - given-names: Sören + family-names: Döring + - given-names: Niklas + family-names: Gärtner + - given-names: Vladimir + family-names: Mikheev + orcid: 'https://orcid.org/0000-0002-4738-6655' + - given-names: René + family-names: Skukies + orcid: 'https://orcid.org/0000-0002-4124-4584' + +identifiers: + - type: doi + value: 10.5281/zenodo.6531996 +repository-code: 'https://github.com/unfoldtoolbox/UnfoldMakie.jl' +version: 0.3.5 +date-released: '2023-10-24' diff --git a/docs/src/literate/tutorials/circTopo.jl b/docs/src/literate/tutorials/circTopo.jl index f1858e6e8..1c1c56310 100644 --- a/docs/src/literate/tutorials/circTopo.jl +++ b/docs/src/literate/tutorials/circTopo.jl @@ -11,18 +11,30 @@ using DataFrames # # Generate data # Generate a Dataframe. We need to specify the TopoPlot Positions either via position, or via labels (according to TopoPlots.jl) -data,pos = TopoPlots.example_data(); +data, pos = TopoPlots.example_data(); dat = data[:, 240, 1] -df= DataFrame( - :estimate=>eachcol(Float64.(data[:,100:40:300,1])), - :circularVariable=>[0,50,80,120,180,210], - :time=>100:40:300) -df = flatten(df,:estimate); +df = DataFrame( + :estimate => eachcol(Float64.(data[:, 100:40:300, 1])), + :circularVariable => [0, 50, 80, 120, 180, 210], + :time => 100:40:300, +) +df = flatten(df, :estimate); # # Our first plot! # note how the plots are at the angles of circularVariable` -plot_circulareegtopoplot(df;positions=pos,axis=(;label="Sac Incoming"),predictor=:circularVariable) +plot_circulareegtopoplot( + df; + positions = pos, + axis = (; label = "Sac Incoming"), + predictor = :circularVariable, +) # In case the bounding variable is not between 0 and 360, as here we use actually time, we have to specify it. e.g. -plot_circulareegtopoplot(df;positions=pos,axis=(;label="Time?!"),predictor=:time,extra=(;predictorBounds=[80,320])) +plot_circulareegtopoplot( + df; + positions = pos, + axis = (; label = "Time?!"), + predictor = :time, + predictorBounds = [80, 320], +) diff --git a/docs/src/literate/tutorials/circTopo.md b/docs/src/literate/tutorials/circTopo.md index 69c3b3da5..4d641e741 100644 --- a/docs/src/literate/tutorials/circTopo.md +++ b/docs/src/literate/tutorials/circTopo.md @@ -16,13 +16,14 @@ using DataFrames Generate a Dataframe. We need to specify the TopoPlot Positions either via position, or via labels (according to TopoPlots.jl) ````@example circTopo -data,pos = TopoPlots.example_data(); +data, pos = TopoPlots.example_data(); dat = data[:, 240, 1] -df= DataFrame( - :estimate=>eachcol(Float64.(data[:,100:40:300,1])), - :circularVariable=>[0,50,80,120,180,210], - :time=>100:40:300) -df = flatten(df,:estimate); +df = DataFrame( + :estimate => eachcol(Float64.(data[:, 100:40:300, 1])), + :circularVariable => [0, 50, 80, 120, 180, 210], + :time => 100:40:300, +) +df = flatten(df, :estimate); nothing #hide ```` @@ -30,13 +31,24 @@ nothing #hide note how the plots are at the angles of circularVariable` ````@example circTopo -plot_circulareegtopoplot(df;positions=pos,axis=(;label="Sac Incoming"),predictor=:circularVariable) +plot_circulareegtopoplot( + df; + positions = pos, + axis = (; label = "Sac Incoming"), + predictor = :circularVariable, +) ```` In case the bounding variable is not between 0 and 360, as here we use actually time, we have to specify it. e.g. ````@example circTopo -plot_circulareegtopoplot(df;positions=pos,axis=(;label="Time?!"),predictor=:time,extra=(;predictorBounds=[80,320])) +plot_circulareegtopoplot( + df; + positions = pos, + axis = (; label = "Time?!"), + predictor = :time, + predictorBounds = [80, 320], +) ```` --- diff --git a/docs/src/literate/tutorials/erp.jl b/docs/src/literate/tutorials/erp.jl index 6371f80d1..6bd168d81 100644 --- a/docs/src/literate/tutorials/erp.jl +++ b/docs/src/literate/tutorials/erp.jl @@ -16,17 +16,23 @@ using UnfoldMakie # ## Setup # Let's generate some data and fit a model of a 2-level categorical and a continuous predictor with interaction. -data,evts = UnfoldSim.predef_eeg(;noiselevel=12,return_epoched=true) -data = reshape(data,(1,size(data)...)) -f = @formula 0 ~ 1+condition+continuous -se_solver =(x,y)->Unfold.solver_default(x,y,stderror=true); - -m = fit(UnfoldModel, Dict(Any=>(f,range(0,step=1/100,length=size(data,2)))), evts, data,solver=se_solver) +data, evts = UnfoldSim.predef_eeg(; noiselevel = 12, return_epoched = true) +data = reshape(data, (1, size(data)...)) +f = @formula 0 ~ 1 + condition + continuous +se_solver = (x, y) -> Unfold.solver_default(x, y, stderror = true); + +m = fit( + UnfoldModel, + Dict(Any => (f, range(0, step = 1 / 100, length = size(data, 2)))), + evts, + data, + solver = se_solver, +) results = coeftable(m) -res_effects = effects(Dict(:continuous=>-5:0.5:5),m); +res_effects = effects(Dict(:continuous => -5:0.5:5), m); # ## Plot the results -plot_erp(results; extra=(:stderror=>true,)) +plot_erp(results; :stderror => true,) @@ -42,8 +48,8 @@ plot_erp(results; extra=(:stderror=>true,)) # # Configuration for Line Plots -# ## extra -# `plot_erp(...;extra=(;=,...)`. +# ## key values +# `plot_erp(...; =,...)`. # - categoricalColor (boolean, true) - in case of numeric `:color` column, is color a continuous or categorical variable? # - categoricalGroup (boolean, true) - in case of numeric `:group` column, treat `:group` as categorical variable by default # - stderror (boolean, false) - add an error-ribbon based on the `:stderror` column @@ -51,13 +57,13 @@ plot_erp(results; extra=(:stderror=>true,)) # Using some general configurations we can pretty up the default visualization. Here we use the following configuration: -plot_erp(res_effects; - mapping = (;y=:yhat,color=:continuous, group=:continuous), - extra=(;showLegend=true, - categoricalColor=false, - categoricalGroup=true), - legend = (;nbanks=2), - layout = (;legendPosition=:right)) +plot_erp( + res_effects; + mapping = (; y = :yhat, color = :continuous, group = :continuous), + legend = (; nbanks = 2), + layout = (; legendPosition = :right), + showLegend = true, categoricalColor = false, categoricalGroup = true, +) @@ -78,7 +84,7 @@ plot_erp(res_effects; # coefname=["(Intercept)","condition: face"] # if coefname not specified, line should be black # ) # -# plot_erp(results;extra= (;:pvalue=>pvals)) +# plot_erp(results; :pvalue=>pvals) # ### stderror (boolean) # Indicating whether the plot should show a colored band showing lower and higher estimates based on the stderror. # Default is `false`. @@ -86,10 +92,7 @@ plot_erp(res_effects; # previously we showed `:stderror`- but low/high is possible as well` results.se_low = results.estimate .- 0.5 results.se_high = results.estimate .+ 0.15 -plot_erp(select(results,Not(:stderror));extra= (;stderror=true)) +plot_erp(select(results, Not(:stderror)); stderror = true) # !!! note # as in the above code,`:stderror` has precedence over `:se_low`/`:se_high` - - - diff --git a/docs/src/literate/tutorials/erp.md b/docs/src/literate/tutorials/erp.md index a8ff8aa3d..fe66d42de 100644 --- a/docs/src/literate/tutorials/erp.md +++ b/docs/src/literate/tutorials/erp.md @@ -24,28 +24,34 @@ using UnfoldMakie Let's generate some data and fit a model of a 2-level categorical and a continuous predictor with interaction. ````@example erp -data,evts = UnfoldSim.predef_eeg(;noiselevel=12,return_epoched=true) -data = reshape(data,(1,size(data)...)) -f = @formula 0 ~ 1+condition+continuous -se_solver =(x,y)->Unfold.solver_default(x,y,stderror=true); - -m = fit(UnfoldModel, Dict(Any=>(f,range(0,step=1/100,length=size(data,2)))), evts, data,solver=se_solver) +data, evts = UnfoldSim.predef_eeg(; noiselevel = 12, return_epoched = true) +data = reshape(data, (1, size(data)...)) +f = @formula 0 ~ 1 + condition + continuous +se_solver = (x, y) -> Unfold.solver_default(x, y, stderror = true); + +m = fit( + UnfoldModel, + Dict(Any => (f, range(0, step = 1 / 100, length = size(data, 2)))), + evts, + data, + solver = se_solver, +) results = coeftable(m) -res_effects = effects(Dict(:continuous=>-5:0.5:5),m); +res_effects = effects(Dict(:continuous => -5:0.5:5), m); nothing #hide ```` ## Plot the results ````@example erp -plot_erp(results; extra=(:stderror=>true,)) +plot_erp(results; :stderror=>true,) ```` ## Column Mappings for Line Plots `plot_erp` use a `DataFrame` as an input, the library needs to know the names of the columns used for plotting. There are multiple default values, that are checked in that order if they exist in the `DataFrame`, a custom name can be chosen using -`plot_erp(...;mapping=(; :y=:myEstimate)` +`plot_erp(...; mapping=(; :y=:myEstimate)` :x Default is `(:x, :time)`. :y Default is `(:y, :estimate, :yhat)`. @@ -53,8 +59,8 @@ There are multiple default values, that are checked in that order if they exist # Configuration for Line Plots -## extra -`plot_erp(...;extra=(;=,...)`. +## key variables +`plot_erp(...; =,...)`. - categoricalColor (boolean, true) - in case of numeric `:color` column, is color a continuous or categorical variable? - categoricalGroup (boolean, true) - in case of numeric `:group` column, treat `:group` as categorical variable by default - stderror (boolean, false) - add an error-ribbon based on the `:stderror` column @@ -63,13 +69,13 @@ There are multiple default values, that are checked in that order if they exist Using some general configurations we can pretty up the default visualization. Here we use the following configuration: ````@example erp -plot_erp(res_effects; - mapping = (;y=:yhat,color=:continuous, group=:continuous), - extra=(;showLegend=true, - categoricalColor=false, - categoricalGroup=true), - legend = (;nbanks=2), - layout = (;legendPosition=:right)) +plot_erp( + res_effects; + mapping = (; y = :yhat, color = :continuous, group = :continuous), + legend = (; nbanks = 2), + layout = (; legendPosition = :right), + showLegend = true, categoricalColor = false, categoricalGroup = true, +) ```` In the following we will use this "pretty" line plot as a basis for looking into configuration options. @@ -91,7 +97,8 @@ pvals = DataFrame( # ) ```` -plot_erp(results;extra= (;:pvalue=>pvals)) +plot_erp(results; :pvalue=>pvals) + ### stderror (boolean) Indicating whether the plot should show a colored band showing lower and higher estimates based on the stderror. Default is `false`. @@ -101,7 +108,7 @@ previously we showed `:stderror`- but low/high is possible as well` ````@example erp results.se_low = results.estimate .- 0.5 results.se_high = results.estimate .+ 0.15 -plot_erp(select(results,Not(:stderror));extra= (;stderror=true)) +plot_erp(select(results, Not(:stderror)); stderror=true) ```` !!! note diff --git a/docs/src/tutorials/butterfly.md b/docs/src/tutorials/butterfly.md index dc3f286b2..b7dc499ff 100644 --- a/docs/src/tutorials/butterfly.md +++ b/docs/src/tutorials/butterfly.md @@ -19,7 +19,7 @@ We filter the data to make it more clearly represented: ```@example main include("../../example_data.jl") df, pos = example_data("TopoPlots.jl") -first(df,3) +first(df, 3) ``` ## Plot Butterfly Plots @@ -37,7 +37,7 @@ plot_butterfly(df; positions=pos) ## Column Mappings for Butterfly Plots -Since butterfly plots use a `DataFrame` as input, the library needs to know the names of the columns used for plotting. You can set these mapping values by calling `plot_butterfly(...; mapping=(;:x=:time,))`, that is, by specifying a `NamedTuple` (note the `;` right after the opening parentheses). +Since butterfly plots use a `DataFrame` as input, the library needs to know the names of the columns used for plotting. You can set these mapping values by calling `plot_butterfly(...; mapping=(; :x=:time,))`, that is, by specifying a `NamedTuple` (note the `;` right after the opening parentheses). For more information on mapping values, see the [Mapping Data](@ref config_mapping) section of the documentation. @@ -56,8 +56,10 @@ Default is `(:labels, :label, :topoLabels, :sensor, :nothing)` ## Configurations for Butterfly Plots -Here we look into possible options for configuring the butterfly plot visualization using `(...; extra=(=, ...)`. -This is the list of unique configuration (extraData): + +Here we look into possible options for configuring the butterfly plot visualization using `(...; =, ...)`. +This is the list of unique configuration (key values): + - topoLegend (boolean) diff --git a/docs/src/tutorials/designmatrix.md b/docs/src/tutorials/designmatrix.md index d194eebc2..155741133 100644 --- a/docs/src/tutorials/designmatrix.md +++ b/docs/src/tutorials/designmatrix.md @@ -28,20 +28,20 @@ The following code will result in the default configuration. plot_designmatrix(designmatrix(uf)) ``` -# `plot_designmatrix(...; extra=(=, ...)`. +# kwargs `plot_designmatrix(...; ...)`. + - sortData (boolean,false) - Indicating whether the data is sorted; using sortslices() of Base Julia. In order to make the designmatrix easier to read, you may want to sort it. ``` -plot_designmatrix(designmatrix(uf);extra=(;sortData=true)) +plot_designmatrix(designmatrix(uf); sortData=true) ``` -- standardizeData (boolean,false) - Indicating whether the data is standardized, mapping the values between 0 and 1. - +- standardizeData (boolean, false) - Indicating whether the data is standardized, mapping the values between 0 and 1. +- xTicks (number, nothing) -- xTicks (number,nothing) Indicating the number of labels on the x-axis. Behavior if specified in configuration: - xTicks = 0: no labels are placed. - xTicks = 1: first possible label is placed. diff --git a/docs/src/tutorials/erpimage.md b/docs/src/tutorials/erpimage.md index d0a482a18..a8b058908 100644 --- a/docs/src/tutorials/erpimage.md +++ b/docs/src/tutorials/erpimage.md @@ -29,14 +29,14 @@ Since ERP images use a `Matrix` as an input, the library does not need any infor ## extra=(;) - erpBlur (number, 10) - Is a number indicating how much blur is applied to the image; using Gaussian blur of the ImageFiltering module. Negative values deactivate the blur. -- sortData (boolean, false) - Indicating whether the data is sorted; using sortperm() of Base Julia +- sortvalues - Indicating whether the data is sorted; using sortperm() of Base Julia (sortperm() computes a permutation of the array's indices that puts the array into sorted order). -- ploterp (bool, false) - Indicating whether the plot should add a line plot below the ERP image, showing the mean of the data. If limits are set in the axis values both plots will be aligned. +- meanPlot (bool, false) - Indicating whether the plot should add a line plot below the ERP image, showing the mean of the data. If limits are set in the axis values both plots will be aligned. ```@example main plot_erpimage(data; - extra = (ploterp = true,), + meanPlot = true, colorbar = (label = "Voltage [µV]",), visual = (colormap = :viridis, colorrange = (-40, 40))) diff --git a/src/UnfoldMakie.jl b/src/UnfoldMakie.jl index 2f3bd8acf..4468022c4 100644 --- a/src/UnfoldMakie.jl +++ b/src/UnfoldMakie.jl @@ -30,7 +30,7 @@ import AlgebraOfGraphics.hidedecorations! include("plotconfig.jl") -include("eeg-series.jl") +include("eeg_series.jl") include("plot_topoplotseries.jl") include("plot_erp.jl") diff --git a/src/eeg_series.jl b/src/eeg_series.jl new file mode 100644 index 000000000..c02824f36 --- /dev/null +++ b/src/eeg_series.jl @@ -0,0 +1,195 @@ +# Note: This is copied from https://github.com/MakieOrg/TopoPlots.jl/pull/3 because they apparently cannot do a review in ~9month... + +""" +Helper function converting a matrix (channel x times) to a tidy dataframe + with columns :estimate, :time and :label +""" +function eeg_matrix_to_dataframe(data, label) + df = DataFrame(data', label) + df[!, :time] .= 1:nrow(df) + df = stack(df, Not([:time]); variable_name = :label, value_name = "estimate") + return df +end + +""" +function eeg_topoplot_series(data::DataFrame, + Δbin; + y=:estimate, + label=:label, + col=:time, + row=nothing, + figure = NamedTuple(), + combinefun=mean, + row_labels = false, + col_labels = false, + topoplot_attributes... + ) + +Plot a series of topoplots. The function automatically takes the `combinefun=mean` over the `:time` column of `data` in `Δbin` steps. +- The data frame `data` needs the columns `:time` and `y(=:erp)`, and `label(=:label)`. If `data` is a matrix, it is automatically cast to a dataframe, time bins are in samples, labels are `string.(1:size(data,1))`. + +- Δbin in `:time` units, specifying the time steps. All other keyword arguments are passed to the EEG_TopoPlot recipe. In most cases, the user should specify the electrode positions with `positions=pos`. +- The `col` and `row` arguments specify the field to be divided into columns and rows. The default is `col=:time` to split by the time field and `row=nothing`. Useful +to split by a condition, e.g. `...(..., col=:time, row=:condition)` would result in multiple (as many as different values in df.condition) rows of topoplot series. +- The `figure` option allows you to include information for plotting the figure. Alternatively, you can pass a fig object `eeg_topoplot_series!(fig, data::DataFrame, Δbin; kwargs..)`. +- `row_labels` and `col_labels` indicate whether there should be labels in the plots in the first column to indicate the row value and in the last row to indicate the time (typically timerange). + +# Examples +Desc +```julia-repl +julia > df = DataFrame(:erp => repeat(1:63, 100), :time => repeat(1:20, 5 * 63), :label => repeat(1:63, 100)) # fake data +julia > pos = [(1:63) ./ 63 .* (sin.(range(-2 * pi, 2 * pi, 63))) (1:63) ./ 63 .* cos.(range(-2 * pi, 2 * pi, 63))] .* 0.5 .+ 0.5 # fake electrode positions +julia > pos = [Point2.(pos[k, 1], pos[k, 2]) for k in 1:size(pos, 1)] +julia > eeg_topoplot_series(df, 5; positions=pos) +``` +""" +function eeg_topoplot_series(data::DataFrame, Δbin; figure = NamedTuple(), kwargs...) + return eeg_topoplot_series!(Figure(; figure...), data, Δbin; kwargs...) +end +function eeg_topoplot_series(data::AbstractMatrix, Δbin; figure = NamedTuple(), kwargs...) + return eeg_topoplot_series!(Figure(; figure...), data, Δbin; kwargs...) +end +# allow to specify Δbin as an keyword for nicer readability +eeg_topoplot_series(data::DataFrame; Δbin, kwargs...) = + eeg_topoplot_series(data, Δbin; kwargs...) +AbstractMatrix +function eeg_topoplot_series!(fig, data::AbstractMatrix, Δbin; kwargs...) + return eeg_topoplot_series!(fig, data, string.(1:size(data, 1)), Δbin; kwargs...) +end + +# convert a 2D Matrix to the dataframe +function eeg_topoplot_series(data::AbstractMatrix, labels, Δbin; kwargs...) + return eeg_topoplot_series(eeg_matrix_to_dataframe(data, labels), Δbin; kwargs...) +end +function eeg_topoplot_series!(fig, data::AbstractMatrix, labels, Δbin; kwargs...) + return eeg_topoplot_series!(fig, eeg_matrix_to_dataframe(data, labels), Δbin; kwargs...) +end + +""" +eeg_topoplot_series!(fig, data::DataFrame, Δbin; kwargs..) +In place plotting of topoplot series +see eeg_topoplot_series(data, Δbin) for help +""" +function eeg_topoplot_series!( + fig, + data::DataFrame, + Δbin; + y = :erp, + label = :label, + col = :time, + row = nothing, + combinefun = mean, + col_labels = false, + row_labels = false, + rasterize_heatmap = true, + topoplot_attributes..., +) + + # cannot be made easier right now, but Simon promised a simpler solution "soonish" + axisOptions = ( + aspect = 1, + xgridvisible = false, + xminorgridvisible = false, + xminorticksvisible = false, + xticksvisible = false, + xticklabelsvisible = false, + xlabelvisible = false, + ygridvisible = false, + yminorgridvisible = false, + yminorticksvisible = false, + yticksvisible = false, + yticklabelsvisible = false, + ylabelvisible = false, + leftspinevisible = false, + rightspinevisible = false, + topspinevisible = false, + bottomspinevisible = false, + limits = ((-0.25, 1.25), (-0.25, 1.25)), + ) + + # aggregate the data over time bins + data_mean = + df_timebin(data, Δbin; col_y = y, fun = combinefun, grouping = [label, col, row]) + + # using same colormap + contour levels for all plots + (q_min, q_max) = Statistics.quantile(data_mean[:, y], [0.001, 0.999]) + # make them symmetrical + q_min = q_max = max(abs(q_min), abs(q_max)) + q_min = -q_min + + topoplot_attributes = merge( + ( + colorrange = (q_min, q_max), + contours = (levels = range(q_min, q_max; length = 7),), + ), + topoplot_attributes, + ) + + # do the col/row plot + + select_col = isnothing(col) ? 1 : unique(data_mean[:, col]) + select_row = isnothing(row) ? 1 : unique(data_mean[:, row]) + + for r = 1:length(select_row) + for c = 1:length(select_col) + ax = Axis(fig[r, c]; axisOptions...) + # select one topoplot + sel = 1 .== ones(size(data_mean, 1)) # select all + if !isnothing(col) + sel = sel .&& (data_mean[:, col] .== select_col[c]) # subselect + end + if !isnothing(row) + sel = sel .&& (data_mean[:, row] .== select_row[r]) # subselect + end + df_single = data_mean[sel, :] + + # select labels + labels = df_single[:, label] + # select data + d_vec = df_single[:, y] + # plot it + ax2 = eeg_topoplot!(ax, d_vec, labels; topoplot_attributes...) + + if rasterize_heatmap + ax2.plots[1].plots[1].rasterize = true + end + if r == length(select_row) && col_labels + ax.xlabel = string(df_single.time[1]) + ax.xlabelvisible = true + end + if c == 1 && length(select_row) > 1 && row_labels + #@show df_single + ax.ylabel = string(df_single.row[1]) + ax.ylabelvisible = true + end + end + end + colgap!(fig.layout, 0) + + return fig +end + +""" +function df_timebin(df, Δbin; col_y=:erp, fun=mean, grouping=[]) +Split or combine dataframe according to equally spaced time bins +- `df` AbstractTable with columns `:time` and `col_y` (default `:erp`), and all columns in `grouping`; +- `Δbin` bin size in `:time` units; +- `col_y` default :erp, the column to combine over (with `fun`); +- `fun` function to combine, default is `mean`; +- `grouping` (vector of symbols/strings) default empty vector, columns to group the data by before aggregating. Values of `nothing` are ignored. +""" +function df_timebin(df, Δbin; col_y = :erp, fun = mean, grouping = []) + tmin = minimum(df.time) + tmax = maximum(df.time) + + bins = range(; start = tmin, step = Δbin, stop = tmax) + df = deepcopy(df) # cut seems to change stuff inplace + df.time = cut(df.time, bins; extend = true) + + grouping = grouping[.!isnothing.(grouping)] + + df_m = combine(groupby(df, unique([:time, grouping...])), col_y => fun) + #df_m = combine(groupby(df, Not(y)), y=>fun) + rename!(df_m, names(df_m)[end] => col_y) # remove the _fun part of the new column + return df_m +end diff --git a/src/plot_circulareegtopoplot.jl b/src/plot_circulareegtopoplot.jl index 2c5442f67..cdca61379 100644 --- a/src/plot_circulareegtopoplot.jl +++ b/src/plot_circulareegtopoplot.jl @@ -11,13 +11,9 @@ Plot a circular EEG topoplot. - `figlike::Union{GridPosition, Figure}`: Figure or GridPosition that the plot should be drawn into - `plotData::DataFrame`: Dataframe with keys for data (looks for `:y,:yhat, :estimate`, and :position (looks for `:pos, :positions, :position`), - `predictor` (optional; default :predictor) the circular predictor value, defines position of topoplot, is mapped around `predictorBounds` +- `predictorBounds`: Default: `[0,360]` - The bounds of the predictor. This is relevant for the axis labels. - `kwargs...`: Additional styling behavior. -## Extra Data Behavior (...; extra=(; [key]=value)): - -`predictorBounds`: Default: `[0,360]` - The bounds of the predictor. This is relevant for the axis labels. - - ## Axis Data Behavior (...; axis=(; [key]=value)): `label`: default "", the text in the center of the cricle @@ -27,48 +23,80 @@ Plot a circular EEG topoplot. A figure containing the circular topoplot at given layout position """ -plot_circulareegtopoplot(plotData::DataFrame; kwargs...) = plot_circulareegtopoplot!(Figure(), plotData; kwargs...) -plot_circulareegtopoplot!(f, plotData::DataFrame; kwargs...) = plot_circulareegtopoplot!(f, plotData; kwargs...) -function plot_circulareegtopoplot!(f::Union{GridPosition,Figure}, plotData::DataFrame, ; predictor=:predictor, positions=nothing, labels=nothing, kwargs...) +plot_circulareegtopoplot(plotData::DataFrame; kwargs...) = + plot_circulareegtopoplot!(Figure(), plotData; kwargs...) +plot_circulareegtopoplot!(f, plotData::DataFrame; kwargs...) = + plot_circulareegtopoplot!(f, plotData; kwargs...) +function plot_circulareegtopoplot!( + f::Union{GridPosition,Figure}, + plotData::DataFrame, + ; + predictor = :predictor, + positions = nothing, + labels = nothing, + predictorBounds = [0, 360], + kwargs..., +) config = PlotConfig(:circeegtopo) config_kwargs!(config; kwargs...) config.mapping = resolveMappings(plotData, config.mapping) - positions = getTopoPositions(; positions=positions, labels=labels) + positions = getTopoPositions(; positions = positions, labels = labels) # moving the values of the predictor to a different array to perform boolean queries on them predictorValues = plotData[:, predictor] - if (length(config.extra.predictorBounds) != 2) - error("config.extra.predictorBounds needs exactly two values") + if (length(predictorBounds) != 2) + error("predictorBounds needs exactly two values") end - if (config.extra.predictorBounds[1] >= config.extra.predictorBounds[2]) - error("config.extra.predictorBounds[1] needs to be smaller than config.extra.predictorBounds[2]") + if (predictorBounds[1] >= predictorBounds[2]) + error( + "predictorBounds[1] needs to be smaller than predictorBounds[2]", + ) end - if ((length(predictorValues[predictorValues.config.extra.predictorBounds[2]]) != 0)) - error("all values in the plotData's effect column have to be within the config.extra.predictorBounds range") + if ( + (length(predictorValues[predictorValues.predictorBounds[2]]) != 0) + ) + error( + "all values in the plotData's effect column have to be within the predictorBounds range", + ) end if (all(predictorValues .<= 2 * pi)) - @warn "insert the predictor values in degrees instead of radian, or change config.extra.predictorBounds" + @warn "insert the predictor values in degrees instead of radian, or change predictorBounds" end - ax = Axis(f[1, 1]; aspect=1) + ax = Axis(f[1, 1]; aspect = 1) hidedecorations!(ax) hidespines!(ax) - plotCircularAxis!(ax, config.extra.predictorBounds, config.axis.label) + plotCircularAxis!(ax, predictorBounds, config.axis.label) limits!(ax, -3.5, 3.5, -3.5, 3.5) min, max = calculateGlobalMaxValues(plotData[:, config.mapping.y], predictorValues) - positions = getTopoPositions(; positions=positions, labels=labels) - plotTopoPlots!(ax, plotData[:, config.mapping.y], positions, predictorValues, config.extra.predictorBounds, min, max) + positions = getTopoPositions(; positions = positions, labels = labels) + plotTopoPlots!( + ax, + plotData[:, config.mapping.y], + positions, + predictorValues, + predictorBounds, + min, + max, + ) # setting the colorbar to the bottom right of the box. # Relative values got determined by checking what subjectively # looks best #RelativeAxis(ax,(0.85,0.95,0.06,0.25)) - Colorbar(f[1, 2], colormap=config.colorbar.colormap, colorrange=(min, max), label=config.colorbar.label, height=@lift Fixed($(pixelarea(ax.scene)).widths[2])) - applyLayoutSettings!(config; ax=ax) + Colorbar( + f[1, 2], + colormap = config.colorbar.colormap, + colorrange = (min, max), + label = config.colorbar.label, + height = @lift Fixed($(pixelarea(ax.scene)).widths[2]) + ) + applyLayoutSettings!(config; ax = ax) # set the scene's background color according to config #set_theme!(Theme(backgroundcolor = config.axisData.backgroundcolor)) @@ -77,7 +105,10 @@ end function calculateGlobalMaxValues(plotData, predictor) - x = combine(groupby(DataFrame(:e => plotData, :p => predictor), :p), :e => (x -> maximum(abs.(quantile!(x, [0.01, 0.99])))) => :localMaxVal) + x = combine( + groupby(DataFrame(:e => plotData, :p => predictor), :p), + :e => (x -> maximum(abs.(quantile!(x, [0.01, 0.99])))) => :localMaxVal, + ) globalMaxVal = maximum(x.localMaxVal) @@ -91,28 +122,36 @@ function plotCircularAxis!(ax, predictorBounds, label) #xlims!(-9,9) #ylims!(-9,9) - lines!(ax, 1 * cos.(LinRange(0, 2 * pi, 500)), 1 * sin.(LinRange(0, 2 * pi, 500)), color=(:black, 0.5), linewidth=3) + lines!( + ax, + 1 * cos.(LinRange(0, 2 * pi, 500)), + 1 * sin.(LinRange(0, 2 * pi, 500)), + color = (:black, 0.5), + linewidth = 3, + ) #minsize = minimum([origin[1]+widths[1],origin[2]+widths[2]]) # labels and label lines for the circle - circlepoints_lines = [(1.1 * cos(a), 1.1 * sin(a)) for a in LinRange(0, 2pi, 5)[1:end-1]] - circlepoints_labels = [(1.3 * cos(a), 1.3 * sin(a)) for a in LinRange(0, 2pi, 5)[1:end-1]] + circlepoints_lines = + [(1.1 * cos(a), 1.1 * sin(a)) for a in LinRange(0, 2pi, 5)[1:end-1]] + circlepoints_labels = + [(1.3 * cos(a), 1.3 * sin(a)) for a in LinRange(0, 2pi, 5)[1:end-1]] text!( circlepoints_lines, # using underscores as lines around the circular axis - text=["_", "_", "_", "_"], - rotation=LinRange(0, 2pi, 5)[1:end-1], - align=(:right, :baseline), + text = ["_", "_", "_", "_"], + rotation = LinRange(0, 2pi, 5)[1:end-1], + align = (:right, :baseline), #textsize = round(minsize*0.03) ) text!( circlepoints_labels, - text=calculateAxisLabels(predictorBounds), - align=(:center, :center), + text = calculateAxisLabels(predictorBounds), + align = (:center, :center), #textsize = round(minsize*0.03) ) - text!(ax, 0, 0, text=label, align=(:center, :center))#,textsize = round(minsize*0.04)) + text!(ax, 0, 0, text = label, align = (:center, :center))#,textsize = round(minsize*0.04)) end @@ -121,24 +160,50 @@ function calculateAxisLabels(predictorBounds) nonboundlabels = quantile(predictorBounds, [0.25, 0.5, 0.75]) # third label is on the left and it tends to cover the circle # so added some blank spaces to tackle that - return [string(trunc(Int, predictorBounds[1])), string(trunc(Int, nonboundlabels[1])), string(trunc(Int, nonboundlabels[2]), " "), string(trunc(Int, nonboundlabels[3]))] + return [ + string(trunc(Int, predictorBounds[1])), + string(trunc(Int, nonboundlabels[1])), + string(trunc(Int, nonboundlabels[2]), " "), + string(trunc(Int, nonboundlabels[3])), + ] end -function plotTopoPlots!(f, data, positions, predictorValues, predictorBounds, globalmin, globalmax) +function plotTopoPlots!( + f, + data, + positions, + predictorValues, + predictorBounds, + globalmin, + globalmax, +) #for (index, datapoints) in enumerate(data) df = DataFrame(:e => data, :p => predictorValues) gp = groupby(df, :p) - for g = gp + for g in gp bbox = calculateBBox([0, 0], [1, 1], g.p[1], predictorBounds) # convet BBox to rect - rect = (Float64.([bbox.origin[1], bbox.origin[1] + bbox.widths[1], bbox.origin[2], bbox.origin[2] + bbox.widths[2]])...,) - - - eegaxis = RelativeAxis(f, rect; aspect=1) - - TopoPlots.eeg_topoplot!(eegaxis, g.e; positions=positions, colorrange=(globalmin, globalmax), enlarge=1) + rect = ( + Float64.([ + bbox.origin[1], + bbox.origin[1] + bbox.widths[1], + bbox.origin[2], + bbox.origin[2] + bbox.widths[2], + ])..., + ) + + + eegaxis = RelativeAxis(f, rect; aspect = 1) + + TopoPlots.eeg_topoplot!( + eegaxis, + g.e; + positions = positions, + colorrange = (globalmin, globalmax), + enlarge = 1, + ) hidedecorations!(eegaxis) hidespines!(eegaxis) @@ -155,7 +220,10 @@ function calculateBBox(origin, widths, predictorValue, bounds) # the middle point of the circle for the topoplot positions # has to be moved a bit into the direction of the longer axis # to be centered on a scene that's not shaped like a square - resShift = [((origin[1] + widths[1]) - widths[1]) / 2, ((origin[2] + widths[2]) - widths[2]) / 2] + resShift = [ + ((origin[1] + widths[1]) - widths[1]) / 2, + ((origin[2] + widths[2]) - widths[2]) / 2, + ] resShift[resShift.<0] .= 0 x = radius * cos(predictorRatio * 2 * pi) + resShift[1] @@ -166,10 +234,12 @@ function calculateBBox(origin, widths, predictorValue, bounds) # right point of the axis. This means that you have to # move the bbox to the bottom left by sizeofbbox/2 to move # the center of the axis to a point - return BBox((origin[1] + widths[1]) / 2 - sizeOfBBox / 2 + x, + return BBox( + (origin[1] + widths[1]) / 2 - sizeOfBBox / 2 + x, (origin[1] + widths[1]) / 2 + sizeOfBBox - sizeOfBBox / 2 + x, (origin[2] + widths[2]) / 2 - sizeOfBBox / 2 + y, - (origin[2] + widths[2]) / 2 + sizeOfBBox - sizeOfBBox / 2 + y) + (origin[2] + widths[2]) / 2 + sizeOfBBox - sizeOfBBox / 2 + y, + ) end diff --git a/src/plot_designmatrix.jl b/src/plot_designmatrix.jl index 8049fe36e..43bdf325c 100644 --- a/src/plot_designmatrix.jl +++ b/src/plot_designmatrix.jl @@ -6,12 +6,10 @@ Plot a designmatrix. ## Arguments: - `plotData::Unfold.DesignMatrix`: Data for the plot visualization. -## Extra Data Behavior plot_designmatrix(...;extra=(;[key]=value)) -`standardizeData`: (bool,`true`) - Indicating whether the data is standardized by pointwise division of the data with its sampled standard deviation. - -`sortData`: (bool, `true`) - Indicating whether the data is sorted; using sortslices() of Base Julia. - -`xTicks`: (`nothing`) +## kwargs +- `standardizeData`: (bool,`true`) - Indicating whether the data is standardized by pointwise division of the data with its sampled standard deviation. +- `sortData`: (bool, `true`) - Indicating whether the data is sorted; using sortslices() of Base Julia. +- `xTicks`: (`nothing`) Indicating the number of labels on the x-axis. Behavior if specified in configuration: @@ -24,49 +22,57 @@ Behavior if specified in configuration: ## Return Value: A figure displaying the designmatrix. """ -plot_designmatrix(plotData::Unfold.DesignMatrix; kwargs...) = plot_designmatrix!(Figure(), plotData; kwargs...) -function plot_designmatrix!(f::Union{GridPosition,Figure}, plotData::Unfold.DesignMatrix; kwargs...) +plot_designmatrix(plotData::Unfold.DesignMatrix; kwargs...) = + plot_designmatrix!(Figure(), plotData; kwargs...) +function plot_designmatrix!( + f::Union{GridPosition,Figure}, + plotData::Unfold.DesignMatrix; + xTicks = nothing, + sortData = false, + standardizeData = false, + kwargs..., +) config = PlotConfig(:designmat) config_kwargs!(config; kwargs...) designmat = Unfold.get_Xs(plotData) - if config.extra.standardizeData - designmat = designmat ./ std(designmat, dims=1) + if standardizeData + designmat = designmat ./ std(designmat, dims = 1) designmat[isinf.(designmat)] .= 1.0 end if isa(designmat, SparseMatrixCSC) - if config.extra.sortData + if sortData @warn "Sorting does not make sense for timeexpanded designmatrices. sortData has been set to `false`" - config.setExtraValues!(sortData=false) + config.setExtraValues!(sortData = false) end designmat = Matrix(designmat[end÷2-2000:end÷2+2000, :]) end - if config.extra.sortData - designmat = Base.sortslices(designmat, dims=1) + if sortData + designmat = Base.sortslices(designmat, dims = 1) end labels = Unfold.get_coefnames(plotData) lLength = length(labels) # only change xTicks if we want less then all - if (config.extra.xTicks !== nothing && config.extra.xTicks < lLength) - @assert(config.extra.xTicks >= 0, "xTicks shouldn't be negative") + if (xTicks !== nothing && xTicks < lLength) + @assert(xTicks >= 0, "xTicks shouldn't be negative") # sections between xTicks - sectionSize = (lLength - 2) / (config.extra.xTicks - 1) + sectionSize = (lLength - 2) / (xTicks - 1) newLabels = [] # first tick. Empty if 0 ticks - if config.extra.xTicks >= 1 + if xTicks >= 1 push!(newLabels, labels[1]) else push!(newLabels, "") end # fill in ticks in the middle - for i in 1:(lLength-2) + for i = 1:(lLength-2) # checks if we're at the end of a section, but NO tick on the very last section - if i % sectionSize < 1 && i < ((config.extra.xTicks - 1) * sectionSize) + if i % sectionSize < 1 && i < ((xTicks - 1) * sectionSize) push!(newLabels, labels[i+1]) else push!(newLabels, "") @@ -74,7 +80,7 @@ function plot_designmatrix!(f::Union{GridPosition,Figure}, plotData::Unfold.Desi end # last tick at the end - if config.extra.xTicks >= 2 + if xTicks >= 2 push!(newLabels, labels[lLength-1]) else push!(newLabels, "") @@ -85,7 +91,7 @@ function plot_designmatrix!(f::Union{GridPosition,Figure}, plotData::Unfold.Desi # plot Designmatrix - config.axis = merge(config.axis, (; xticks=(1:length(labels), labels))) + config.axis = merge(config.axis, (; xticks = (1:length(labels), labels))) ax = Axis(f[1, 1]; config.axis...) hm = heatmap!(ax, designmat'; config.visual...) @@ -93,7 +99,7 @@ function plot_designmatrix!(f::Union{GridPosition,Figure}, plotData::Unfold.Desi ax.yreversed = true end - applyLayoutSettings!(config; fig=f, hm=hm) + applyLayoutSettings!(config; fig = f, hm = hm) return f -end \ No newline at end of file +end diff --git a/src/plot_erp.jl b/src/plot_erp.jl index 0bf52d472..f779d0559 100644 --- a/src/plot_erp.jl +++ b/src/plot_erp.jl @@ -13,7 +13,7 @@ Plot an ERP plot. - `plotData::DataFrame`: Data for the line plot visualization. - `kwargs...`: Additional styling behavior. Often used: `plot_erp(df; mapping=(; color=:coefname, col=:conditionA))` -## Extra Data Behavior (...; extra = (; [key]=value)): +## kwargs (...; ...): - `categoricalColor` (bool, `true`) - Indicates whether the column referenced in mapping.color should be used nonnumerically. - `categoricalGroup` (bool, `true`) - Indicates whether the column referenced in mapping.group should be used nonnumerically. @@ -34,22 +34,51 @@ Plot Butterfly See `plot_erp` for all specifications -## Extra Data Behavior (...; extra=(; [key]=value)): +## kwargs: (...; ...): `markersize` (Real, `10`) - change the size of the markers, topoplot-inlay electrodes `topowidth` (Real, `0.25`) - change the size of the inlay topoplot width `topoheigth` (Real, `0.25`) - change the size of the inlay topoplot height """ -plot_butterfly(plotData::DataFrame; kwargs...) = plot_butterfly!(Figure(), plotData; kwargs...) -plot_butterfly!(f::Union{GridPosition,<:Figure}, plotData::DataFrame; extra=(;), kwargs...) = plot_erp!(f, plotData, ; extra=merge((; butterfly=true), extra), kwargs...) - - - -function plot_erp!(f::Union{GridPosition,Figure}, plotData::DataFrame; positions=nothing, labels=nothing, kwargs...) +plot_butterfly(plotData::DataFrame; kwargs...) = + plot_butterfly!(Figure(), plotData; kwargs...) + +plot_butterfly!(f::Union{GridPosition,<:Figure}, plotData::DataFrame; kwargs...) = + plot_erp!( + f, + plotData; + butterfly = true, + topoLegend = true, + markersize = 10, + topowidth = 0.25, + topoheigth = 0.25, + topoPositionToColorFunction = x -> posToColorRomaO(x), + kwargs..., + ) + + + +function plot_erp!( + f::Union{GridPosition,Figure}, + plotData::DataFrame; + positions = nothing, + labels = nothing, + categoricalColor = true, + categoricalGroup = true, + stderror = false, # XXX if it exists, should be plotted + pvalue = [], + butterfly = false, + topoLegend = nothing, + markersize = nothing, + topowidth = nothing, + topoheigth = nothing, + topoPositionToColorFunction = nothing, + kwargs..., +) config = PlotConfig(:erp) config_kwargs!(config; kwargs...) - if config.extra.butterfly + if butterfly config = PlotConfig(:butterfly) config_kwargs!(config; kwargs...) end @@ -60,8 +89,12 @@ function plot_erp!(f::Union{GridPosition,Figure}, plotData::DataFrame; positions # resolve columns with data config.mapping = resolveMappings(plotData, config.mapping) #remove mapping values with `nothing` - deleteKeys(nt::NamedTuple{names}, keys) where {names} = NamedTuple{filter(x -> x ∉ keys, names)}(nt) - config.mapping = deleteKeys(config.mapping, keys(config.mapping)[findall(isnothing.(values(config.mapping)))]) + deleteKeys(nt::NamedTuple{names}, keys) where {names} = + NamedTuple{filter(x -> x ∉ keys, names)}(nt) + config.mapping = deleteKeys( + config.mapping, + keys(config.mapping)[findall(isnothing.(values(config.mapping)))], + ) # turn "nothing" from group columns into :fixef @@ -70,35 +103,37 @@ function plot_erp!(f::Union{GridPosition,Figure}, plotData::DataFrame; positions end # check if stderror values exist and create new collumsn with high and low band - if "stderror" ∈ names(plotData) && config.extra.stderror + if "stderror" ∈ names(plotData) && stderror plotData.stderror = plotData.stderror .|> a -> isnothing(a) ? 0.0 : a plotData[!, :se_low] = plotData[:, config.mapping.y] .- plotData.stderror plotData[!, :se_high] = plotData[:, config.mapping.y] .+ plotData.stderror end # Get topocolors for butterfly - if (config.extra.butterfly) + if (butterfly) if isnothing(positions) && isnothing(labels) - config.extra = merge(config.extra, (; topoLegend=false)) - #colors =config.visual.colormap# get(colorschemes[config.visual.colormap],range(0,1,length=nrow(plotData))) + topoLegend = false + #colors = config.visual.colormap# get(colorschemes[config.visual.colormap],range(0,1,length=nrow(plotData))) colors = nothing #config.mapping = merge(config.mapping,(;color=config.)) else - allPositions = getTopoPositions(; positions=positions, labels=labels) - colors = getTopoColor(allPositions, config) + allPositions = getTopoPositions(; positions = positions, labels = labels) + colors = getTopoColor(allPositions, topoPositionToColorFunction) end end # Categorical mapping # convert color column into string, so no wrong grouping happens - if config.extra.categoricalColor && (:color ∈ keys(config.mapping)) - config.mapping = merge(config.mapping, (; color=config.mapping.color => nonnumeric)) + if categoricalColor && (:color ∈ keys(config.mapping)) + config.mapping = + merge(config.mapping, (; color = config.mapping.color => nonnumeric)) end # converts group column into string - if config.extra.categoricalGroup && (:group ∈ keys(config.mapping)) - config.mapping = merge(config.mapping, (; group=config.mapping.group => nonnumeric)) + if categoricalGroup && (:group ∈ keys(config.mapping)) + config.mapping = + merge(config.mapping, (; group = config.mapping.group => nonnumeric)) end #@show colors mapp = mapping() @@ -122,15 +157,15 @@ function plot_erp!(f::Union{GridPosition,Figure}, plotData::DataFrame; positions basic = visual(Lines; config.visual...) * xy_mapp # add band of sdterrors - if config.extra.stderror + if stderror m_se = mapping(config.mapping.x, :se_low, :se_high) - basic = basic + visual(Band, alpha=0.5) * m_se + basic = basic + visual(Band, alpha = 0.5) * m_se end basic = basic * data(plotData) # add the pvalues - if !isempty(config.extra.pvalue) + if !isempty(pvalue) basic = basic + addPvalues(plotData, config) end @@ -138,30 +173,37 @@ function plot_erp!(f::Union{GridPosition,Figure}, plotData::DataFrame; positions f_grid = f[1, 1] # butterfly plot is drawn slightly different - if config.extra.butterfly + if butterfly # add topoLegend - if (config.extra.topoLegend) - topoAxis = Axis(f_grid, width=Relative(config.extra.topowidth), height=Relative(config.extra.topoheigth), halign=0.05, valign=0.95, aspect=1) - topoplotLegend(config, topoAxis, allPositions) + if (topoLegend) + topoAxis = Axis( + f_grid, + width = Relative(topowidth), + height = Relative(topoheigth), + halign = 0.05, + valign = 0.95, + aspect = 1, + ) + topoplotLegend(topoAxis, markersize, topoPositionToColorFunction, allPositions) end # no extra legend mainAxis = Axis(f_grid; config.axis...) - hidedecorations!(mainAxis, label=false, ticks=false, ticklabels=false) + hidedecorations!(mainAxis, label = false, ticks = false, ticklabels = false) if isnothing(colors) drawing = draw!(mainAxis, plotEquation) else - drawing = draw!(mainAxis, plotEquation; palettes=(color=colors,)) + drawing = draw!(mainAxis, plotEquation; palettes = (color = colors,)) end else # normal lineplot draw #drawing = draw!(Axis(f[1,1]; config.axisData...),plotEquation) - drawing = draw!(f_grid, plotEquation; axis=config.axis) + drawing = draw!(f_grid, plotEquation; axis = config.axis) end - applyLayoutSettings!(config; fig=f, ax=drawing, drawing=drawing)#, drawing = drawing) + applyLayoutSettings!(config; fig = f, ax = drawing, drawing = drawing)#, drawing = drawing) return f @@ -173,30 +215,49 @@ function eegHeadMatrix(positions, center, radius) oldCenter = mean(positions) oldRadius, _ = findmax(x -> norm(x .- oldCenter), positions) radF = radius / oldRadius - return Makie.Mat4f(radF, 0, 0, 0, - 0, radF, 0, 0, - 0, 0, 1, 0, - center[1] - oldCenter[1] * radF, center[2] - oldCenter[2] * radF, 0, 1) + return Makie.Mat4f( + radF, + 0, + 0, + 0, + 0, + radF, + 0, + 0, + 0, + 0, + 1, + 0, + center[1] - oldCenter[1] * radF, + center[2] - oldCenter[2] * radF, + 0, + 1, + ) end -function topoplotLegend(config, axis, allPositions) +function topoplotLegend(axis, markersize, topoPositionToColorFunction, allPositions) allPositions = unique(allPositions) topoMatrix = eegHeadMatrix(allPositions, (0.5, 0.5), 0.5) # colorscheme where first entry is 0, and exactly length(positions)+1 entries - specialColors = ColorScheme(vcat(RGB(1, 1, 1.0), [config.extra.topoPositionToColorFunction(pos) for pos in allPositions]...)) - - xlims!(low=-0.2, high=1.2) - ylims!(low=-0.2, high=1.2) - topoplot = eeg_topoplot!(axis, 1:length(allPositions), # go from 1:npos + specialColors = ColorScheme( + vcat(RGB(1, 1, 1.0), [topoPositionToColorFunction(pos) for pos in allPositions]...), + ) + + xlims!(low = -0.2, high = 1.2) + ylims!(low = -0.2, high = 1.2) + topoplot = eeg_topoplot!( + axis, + 1:length(allPositions), # go from 1:npos string.(1:length(allPositions)); - positions=allPositions, - interpolation=NullInterpolator(), # inteprolator that returns only 0, which is put to white in the specialColorsmap - colorrange=(0, length(allPositions)), # add the 0 for the white-first color - colormap=specialColors, - head=(color=:black, linewidth=1, model=topoMatrix), - label_scatter=(markersize=config.extra.markersize, strokewidth=0.5,)) + positions = allPositions, + interpolation = NullInterpolator(), # inteprolator that returns only 0, which is put to white in the specialColorsmap + colorrange = (0, length(allPositions)), # add the 0 for the white-first color + colormap = specialColors, + head = (color = :black, linewidth = 1, model = topoMatrix), + label_scatter = (markersize = markersize, strokewidth = 0.5), + ) hidedecorations!(current_axis()) hidespines!(current_axis()) @@ -205,7 +266,7 @@ function topoplotLegend(config, axis, allPositions) end function addPvalues(plotData, config) - p = deepcopy(config.extra.pvalue) + p = deepcopy(pvalue) # for now, add them to the fixed effect if "group" ∉ names(p) @@ -234,6 +295,11 @@ function addPvalues(plotData, config) posY = stepY * -0.05 + scaleY[1] Δt = diff(plotData.time[1:2])[1] Δy = 0.01 - p[!, :segments] = [Makie.Rect(Makie.Vec(x, posY + stepY * (Δy * (n - 1))), Makie.Vec(y - x + Δt, 0.5 * Δy * stepY)) for (x, y, n) in zip(p.from, p.to, p.sigindex)] + p[!, :segments] = [ + Makie.Rect( + Makie.Vec(x, posY + stepY * (Δy * (n - 1))), + Makie.Vec(y - x + Δt, 0.5 * Δy * stepY), + ) for (x, y, n) in zip(p.from, p.to, p.sigindex) + ] return (data(p) * mapping(:segments) * visual(Poly)) end diff --git a/src/plot_erpimage.jl b/src/plot_erpimage.jl index 1471e2ff5..519a3e845 100644 --- a/src/plot_erpimage.jl +++ b/src/plot_erpimage.jl @@ -11,28 +11,48 @@ Plot an ERP image. - `plotData::Matrix{Float64}`: Data for the plot visualization ## Keyword Arguments -`blurwidth` (Number, `10`) - Number indicating how much blur is applied to the image; using Gaussian blur of the ImageFiltering module. +`erpBlur` (Number, `10`) - Number indicating how much blur is applied to the image; using Gaussian blur of the ImageFiltering module. Non-Positive values deactivate the blur. -`sortData` (bool, `false`) - Indicating whether the data is sorted; using sortperm() of Base Julia +`sortvalues` (bool, `false`) - parameter over which plot will be sorted. Using sortperm() of Base Julia (sortperm() computes a permutation of the array's indices that puts the array into sorted order). -`ploterp`: (bool, `false`) - Indicating whether the plot should add a line plot below the ERP image, showing the mean of the data. +`meanPlot`: (bool, `false`) - Indicating whether the plot should add a line plot below the ERP image, showing the mean of the data. ## Return Value: The input `f` """ # no times + no figure? -plot_erpimage(plotData::Matrix{<:Real}; kwargs...) = plot_erpimage!(Figure(), plotData; kwargs...) +plot_erpimage(plotData::Matrix{<:Real}; kwargs...) = + plot_erpimage!(Figure(), plotData; kwargs...) # no times? -plot_erpimage!(f::Figure, plotData::Matrix{<:Real}; kwargs...) = plot_erpimage!(f, 1:size(plotData, 1), plotData; kwargs...) +plot_erpimage!(f::Union{GridPosition,Figure}, plotData::Matrix{<:Real}; kwargs...) = + plot_erpimage!(f, 1:size(plotData, 1), plotData; kwargs...) -# no figure? -plot_erpimage(times::AbstractVector, plotData::Matrix{<:Real}; kwargs...) = plot_erpimage!(Figure(), times, plotData; kwargs...) -function plot_erpimage!(f::Union{GridPosition,Figure}, times::AbstractVector, plotData::Matrix{<:Real}; sortvalues=nothing, sortix=nothing, kwargs...) +# no figure? +plot_erpimage(times::AbstractVector, plotData::Matrix{<:Real}; kwargs...) = plot_erpimage!( + Figure(), + times, + plotData; + sortix = nothing, + meanPlot = false, + erpBlur = 10, + kwargs..., +) + +function plot_erpimage!( + f::Union{GridPosition,Figure}, + times::AbstractVector, + plotData::Matrix{<:Real}; + sortvalues = nothing, + sortix = nothing, + meanPlot = false, + erpBlur = 10, + kwargs..., +) config = PlotConfig(:erpimage) UnfoldMakie.config_kwargs!(config; kwargs...) @@ -48,7 +68,10 @@ function plot_erpimage!(f::Union{GridPosition,Figure}, times::AbstractVector, pl end end - filtered_data = UnfoldMakie.imfilter(plotData[:, sortix], UnfoldMakie.Kernel.gaussian((0, max(config.extra.erpBlur, 0)))) + filtered_data = UnfoldMakie.imfilter( + plotData[:, sortix], + UnfoldMakie.Kernel.gaussian((0, max(erpBlur, 0))), + ) yvals = 1:size(filtered_data, 2) if !isnothing(sortvalues) @@ -57,25 +80,27 @@ function plot_erpimage!(f::Union{GridPosition,Figure}, times::AbstractVector, pl hm = heatmap!(ax, times, yvals, filtered_data; config.visual...) - UnfoldMakie.applyLayoutSettings!(config; fig=f, hm=hm, ax=ax, plotArea=(4, 1)) + UnfoldMakie.applyLayoutSettings!(config; fig = f, hm = hm, ax = ax, plotArea = (4, 1)) - if config.extra.meanPlot + if meanPlot # UserInput subConfig = deepcopy(config) - config_kwargs!(subConfig; layout=(; - showLegend=false + config_kwargs!( + subConfig; + layout = (; showLegend = false), + axis = (; + ylabel = config.colorbar.label === nothing ? "" : config.colorbar.label ), - axis=(; - ylabel=config.colorbar.label === nothing ? "" : config.colorbar.label)) + ) - axisOffset = (config.layout.showLegend && config.layout.legendPosition == :bottom) ? 1 : 0 + axisOffset = + (config.layout.showLegend && config.layout.legendPosition == :bottom) ? 1 : 0 subAxis = Axis(f[5+axisOffset, 1]; subConfig.axis...) - lines!(subAxis, mean(plotData, dims=2)[:, 1]) - applyLayoutSettings!(subConfig; fig=f, ax=subAxis) + lines!(subAxis, mean(plotData, dims = 2)[:, 1]) + applyLayoutSettings!(subConfig; fig = f, ax = subAxis) end return f end - diff --git a/src/plot_parallelcoordinates.jl b/src/plot_parallelcoordinates.jl index 06ddf8e34..958410dd1 100644 --- a/src/plot_parallelcoordinates.jl +++ b/src/plot_parallelcoordinates.jl @@ -28,19 +28,31 @@ By adapting the padding, aspect ratio and tick label size in px for a new use ca ## Return Value: The input `f` """ -plot_parallelcoordinates(plotData::DataFrame, channels::Vector{Int64}; kwargs...) = plot_parallelcoordinates!(Figure(), plotData, channels; kwargs...) -function plot_parallelcoordinates!(f::Union{GridPosition,Figure}, plotData::DataFrame, channels::Vector{Int64}; kwargs...) +plot_parallelcoordinates(plotData::DataFrame, channels::Vector{Int64}; kwargs...) = + plot_parallelcoordinates!(Figure(), plotData, channels; kwargs...) +function plot_parallelcoordinates!( + f::Union{GridPosition,Figure}, + plotData::DataFrame, + channels::Vector{Int64}; + pc_aspect_ratio = 0.55, + pc_right_padding = 15, + pc_left_padding = 25, + pc_top_padding = 26, + pc_bottom_padding = 16, + pc_tick_label_size = 14, + kwargs..., +) config = PlotConfig(:paracoord) config_kwargs!(config; kwargs...) # We didn't find a good formula to set these automatically # have to be set manually for now # if size of the plot-area changes the padding gets weird - aspect_ratio = config.extra.pc_aspect_ratio - right_padding = config.extra.pc_right_padding - left_padding = config.extra.pc_left_padding - top_padding = config.extra.pc_top_padding - bottom_padding = config.extra.pc_bottom_padding - tick_label_size = config.extra.pc_tick_label_size + aspect_ratio = pc_aspect_ratio + right_padding = pc_right_padding + left_padding = pc_left_padding + top_padding = pc_top_padding + bottom_padding = pc_bottom_padding + tick_label_size = pc_tick_label_size # have to be set now to reduce weird behaviour width = 500 @@ -65,7 +77,11 @@ function plot_parallelcoordinates!(f::Union{GridPosition,Figure}, plotData::Data # height of the upper labels y_values = fill(height, chaLeng) - colormap = cgrad(config.visual.colormap, (catLeng < 2) ? 2 + (bord * 2) : catLeng + (bord * 2), categorical=true) + colormap = cgrad( + config.visual.colormap, + (catLeng < 2) ? 2 + (bord * 2) : catLeng + (bord * 2), + categorical = true, + ) colors = Dict{String,RGBA{Float64}}() @@ -94,25 +110,27 @@ function plot_parallelcoordinates!(f::Union{GridPosition,Figure}, plotData::Data end # Draw vertical line for each channel - for i in 1:n + for i = 1:n x = (i - 1) / (n - 1) * width if i == 1 switch = true else switch = false end - Makie.LineAxis(ax.scene; - limits=limits[i], - spinecolor=:black, - labelfont="Arial", - ticklabelfont="Arial", - spinevisible=true, - labelrotation=0.0, - ticklabelsize=tick_label_size, - minorticks=IntervalsBetween(2), - endpoints=Point2f[(x_values[i], bottom_padding), (x_values[i], y_values[i])], - ticklabelalign=(:right, :center), - labelvisible=false) + Makie.LineAxis( + ax.scene; + limits = limits[i], + spinecolor = :black, + labelfont = "Arial", + ticklabelfont = "Arial", + spinevisible = true, + labelrotation = 0.0, + ticklabelsize = tick_label_size, + minorticks = IntervalsBetween(2), + endpoints = Point2f[(x_values[i], bottom_padding), (x_values[i], y_values[i])], + ticklabelalign = (:right, :center), + labelvisible = false, + ) end # Draw colored line through all channels for each time entry @@ -130,9 +148,13 @@ function plot_parallelcoordinates!(f::Union{GridPosition,Figure}, plotData::Data values = map(1:n, dfInOrder[:, config.mapping.y], limits) do q, d, l # axes, data, limis x = (q - 1) / (n - 1) * width - Point2f(x_values[q], (d - l[1]) ./ (l[2] - l[1]) * (y_values[q] - bottom_padding) + bottom_padding) + Point2f( + x_values[q], + (d - l[1]) ./ (l[2] - l[1]) * (y_values[q] - bottom_padding) + + bottom_padding, + ) end - lines!(ax.scene, values; color=colors[cat], config.visual...) + lines!(ax.scene, values; color = colors[cat], config.visual...) end end @@ -140,23 +162,38 @@ function plot_parallelcoordinates!(f::Union{GridPosition,Figure}, plotData::Data # helper, because without them they wouldn#t have an entry in legend for cat in color - lines!(ax, 1, 1, 1, label=cat, color=colors[cat]) + lines!(ax, 1, 1, 1, label = cat, color = colors[cat]) end # labels - text!(x_values, y_values, text=channelNames, align=(:center, :center), - offset=(0, ch_label_offset * 2), - color=:blue) + text!( + x_values, + y_values, + text = channelNames, + align = (:center, :center), + offset = (0, ch_label_offset * 2), + color = :blue, + ) # lower limit text - text!(x_values, fill(0, chaLeng), align=(:right, :bottom), text=string.(round.(l_low, digits=1))) + text!( + x_values, + fill(0, chaLeng), + align = (:right, :bottom), + text = string.(round.(l_low, digits = 1)), + ) # upper limit text - text!(x_values, y_values, align=(:right, :bottom), text=string.(round.(l_up, digits=1))) - Makie.xlims!(low=0, high=width + right_padding) - Makie.ylims!(low=0, high=height + top_padding) + text!( + x_values, + y_values, + align = (:right, :bottom), + text = string.(round.(l_up, digits = 1)), + ) + Makie.xlims!(low = 0, high = width + right_padding) + Makie.ylims!(low = 0, high = height + top_padding) - applyLayoutSettings!(config; fig=f, ax=ax) + applyLayoutSettings!(config; fig = f, ax = ax) # ensures the axis numbers aren't squished #ax.aspect = DataAspect() return f -end \ No newline at end of file +end diff --git a/src/plot_topoplot.jl b/src/plot_topoplot.jl index 723937f20..26134d829 100644 --- a/src/plot_topoplot.jl +++ b/src/plot_topoplot.jl @@ -1,10 +1,11 @@ """ function plot_topoplot!(f::Union{GridPosition, Figure}, plotData, ; positions=nothing, labels=nothing, kwargs...) + function plot_topoplot(plotData,; positions=nothing, labels=nothing, kwargs...) Plot a topo plot. ## Arguments: -- `f::Union{GridPosition, Figure}`: Figure or GridPosition (e.g. f[2,3]) that the plot should be drawn into. new axis is created +- `f::Union{GridPosition, Figure}`: Figure or GridPosition (e.g. f[2, 3]) that the plot should be drawn into. New axis is created. - `plotData::Union{DataFrame, Vector{Float32}}`: Data for the plot visualization. - `positions::Vector{Point{2, Float32}}=nothing`: positions used if `plotData` is no DataFrame. If this is the case and `positions=nothing` then positions is generated from `labels`. - `labels::Vector{String}=nothing`: labels used if `plotData` is no DataFrame. @@ -15,8 +16,15 @@ None ## Return Value: A figure displaying the topo plot. """ -plot_topoplot(plotData::Union{DataFrame,Vector{Float32}}; kwargs...) = plot_topoplot!(Figure(), plotData; kwargs...) -function plot_topoplot!(f::Union{GridPosition,Figure}, plotData::Union{DataFrame,<:AbstractVector}; positions=nothing, labels=nothing, kwargs...) +plot_topoplot(plotData::Union{DataFrame,Vector{Float32}}; kwargs...) = + plot_topoplot!(Figure(), plotData; kwargs...) +function plot_topoplot!( + f::Union{GridPosition,Figure}, + plotData::Union{DataFrame,<:AbstractVector}; + positions = nothing, + labels = nothing, + kwargs..., +) config = PlotConfig(:topoplot) config_kwargs!(config; kwargs...) # potentially should be combined @@ -28,12 +36,12 @@ function plot_topoplot!(f::Union{GridPosition,Figure}, plotData::Union{DataFrame plotData = plotData[:, config.mapping.y] end - positions = getTopoPositions(; positions=positions, labels=labels) + positions = getTopoPositions(; positions = positions, labels = labels) eeg_topoplot!(axis, plotData, labels; positions, config.visual...) - config_kwargs!(config, colorbar=(; limits=(min(plotData...), max(plotData...)))) - applyLayoutSettings!(config; fig=f) + config_kwargs!(config, colorbar = (; limits = (min(plotData...), max(plotData...)))) + applyLayoutSettings!(config; fig = f) return f end diff --git a/src/plot_topoplotseries.jl b/src/plot_topoplotseries.jl index 28e775df7..92490b711 100644 --- a/src/plot_topoplotseries.jl +++ b/src/plot_topoplotseries.jl @@ -9,23 +9,35 @@ Plot a Topoplot Series. - `plotData::DataFrame`: DataFrame with data, needs a `time` column - `Δbin::Real`: A number for how large one bin should be. Δbin is in units of the `plotData.time` column - `useColorbar`: (default `true`) - show colorbar. -- `kwargs...`: Additional styling behavior. Often used: -`plot_topoplotseries(df; mapping=(;col=:time, row=:conditionA))` -## Extra Data Behavior (...; extra=(; [key]=value)): -`combinefun` (default `mean`) can be used to specify how the samples within `Δbin` are combined. -`bin_labels` (default `true`) - plot the time-window bin size as xlabels at the last row of the plot -`rasterize_heatmaps` (deault `true`) - when saving a svg - enforce rasterization of the plot heatmap. This has the benefit that all lines/points are vectors, except the interpolated heatmap. This is typically what you want, because else you get ~500x500 vectors per topoplot, which makes everything super slow... +### key arguments +- `combinefun` (default `mean`) can be used to specify how the samples within `Δbin` are combined. +- `rasterize_heatmaps` (deault `true`) - enforce rasterization of the plot heatmap when saving in svg format. + This has the benefit that all lines/points are vectors, except the interpolated heatmap. + This is typically what you want, because else you get ~500x500 vectors per topoplot, which makes everything super slow. +- `col_labels`, `row_labels` - shows column and row labels. ## Return Value: The input `f` """ -plot_topoplotseries(plotData::DataFrame, Δbin::Real; kwargs...) = plot_topoplotseries!(Figure(), plotData, Δbin; kwargs...) - - -function plot_topoplotseries!(f::Union{GridPosition,Figure}, plotData::DataFrame, Δbin; positions=nothing, labels=nothing, kwargs...) +plot_topoplotseries(plotData::DataFrame, Δbin::Real; kwargs...) = + plot_topoplotseries!(Figure(), plotData, Δbin; kwargs...) + + +function plot_topoplotseries!( + f::Union{GridPosition,Figure}, + plotData::DataFrame, + Δbin; + positions = nothing, + labels = nothing, + combinefun = mean, + col_labels = true, + row_labels = true, + rasterize_heatmaps = true, + kwargs..., +) config = PlotConfig(:topoplotseries) config_kwargs!(config; kwargs...) @@ -33,32 +45,61 @@ function plot_topoplotseries!(f::Union{GridPosition,Figure}, plotData::DataFrame # resolve columns with data config.mapping = resolveMappings(plotData, config.mapping) - - positions = getTopoPositions(; positions=positions, labels=labels) + positions = getTopoPositions(; positions = positions, labels = labels) if "label" ∉ names(plotData) plotData.label = plotData.channel end - ftopo = eeg_topoplot_series!(f, plotData, Δbin; - y=config.mapping.y, - label=:label, - col=config.mapping.col, - row=config.mapping.row, - col_labels=config.extra.col_labels, - row_labels=config.extra.row_labels, - rasterize_heatmaps=config.extra.rasterize_heatmaps, - combinefun=config.extra.combinefun, - positions=positions, - config.visual... + + ftopo = eeg_topoplot_series!( + f, + plotData, + Δbin; + y = config.mapping.y, + label = :label, + col = config.mapping.col, + row = config.mapping.row, + col_labels = col_labels, + row_labels = row_labels, + rasterize_heatmaps = rasterize_heatmaps, + combinefun = combinefun, + positions = positions, + config.visual..., ) if config.layout.useColorbar + #println(fieldnames(typeof(ftopo.layout.content[5].content.content[2].content))) @show "colorbar" - d = ftopo.content[1].scene.plots[1] - - Colorbar(f[1, end+1], colormap=d.colormap, colorrange=d.colorrange, height=100, flipaxis=false, label="Voltage [µV]") + if typeof(ftopo) == Figure + + d = ftopo.content[1].scene.plots[1] + #println(d.colorrange) + #println(d.colormap) + Colorbar( + f[1, end+1], + colormap = d.colormap, + colorrange = d.colorrange, + height = 100, + flipaxis = false, + label = "Voltage [µV]", + ) + else # temporal + if length(ftopo.layout.content) > 2 + d = ftopo.layout.content[5].content.content[2].content.scene.plots[1].attributes + else + d = ftopo.layout.content[2].content.content[1].content.scene.plots[1].plots[1].attributes + end + Colorbar( + f[1, ftopo.layout.size[2]+1], + colormap = d.colormap, + colorrange = d.colorrange, + height = 100, + flipaxis = false, + label = "Voltage [µV]", + ) # why end is not working???? + end end return f diff --git a/src/plotconfig.jl b/src/plotconfig.jl index 2b6106fcd..027c9af60 100644 --- a/src/plotconfig.jl +++ b/src/plotconfig.jl @@ -28,30 +28,31 @@ function PlotConfig()# defaults PlotConfig( (;), #figure (; # layout - showLegend=true, - legendPosition=:right, - xlabelFromMapping=:x, - ylabelFromMapping=:y, - useColorbar=false + showLegend = true, + legendPosition = :right, + xlabelFromMapping = :x, + ylabelFromMapping = :y, + useColorbar = false, ), (;), # axis (#maping - x=(:time,), - y=(:estimate, :yhat, :y,), + x = (:time,), + y = (:estimate, :yhat, :y), ), (; # visual - colormap=:roma - ), (;#legend - orientation=:vertical, - tellwidth=true, - tellheight=false + colormap = :roma + ), + (;#legend + orientation = :vertical, + tellwidth = true, + tellheight = false, ), (;#colorbar - vertical=true, - tellwidth=true, - tellheight=false + vertical = true, + tellwidth = true, + tellheight = false, ), - (;) + (;), ) end @@ -78,18 +79,16 @@ PlotConfig(T::Symbol) = PlotConfig(Val{T}()) function PlotConfig(T::Val{:circeegtopo}) cfg = PlotConfig(:topoplot) - config_kwargs!(cfg; extra=(; - predictorBounds=[0, 360] - ), layout=(; - showLegend=false - ), colorbar=(; - label="Voltage [µV]", - colormap=Reverse(:RdBu) - ), mapping=(; - ), axis=(; - label="" + config_kwargs!( + cfg; + layout = (; showLegend = false), + colorbar = (; label = "Voltage [µV]", colormap = Reverse(:RdBu)), + mapping = (;), + axis = (; + label = "" #backgroundcolor = RGB(0.98, 0.98, 0.98), - )) + ), + ) return cfg end @@ -97,12 +96,14 @@ end function PlotConfig(T::Val{:topoarray}) cfg = PlotConfig(:erp) - config_kwargs!(cfg; extra=(; - ), layout=(; - ), colorbar=(; - ), mapping=(; - ), axis=(; - )) + config_kwargs!( + cfg; + extra = (;), + layout = (;), + colorbar = (;), + mapping = (;), + axis = (;), + ) return cfg end @@ -111,132 +112,104 @@ end function PlotConfig(T::Val{:topoplot}) cfg = PlotConfig() - config_kwargs!(cfg; layout=( - showLegend=true, - xlabelFromMapping=nothing, - ylabelFromMapping=nothing, - useColorbar=true, - hidespines=(), - hidedecorations=() - ), visual=(; - contours=(color=:white, linewidth=2), - label_scatter=true, - label_text=true, - bounding_geometry=Circle, - colormap=Reverse(:RdBu) - ), mapping=(; - x=(nothing,), - positions=(:pos, :positions, :position, nothing), # Point / Array / Tuple - labels=(:labels, :label, :sensor, nothing) # String - ), axis=(; - aspect=DataAspect() - )) + config_kwargs!( + cfg; + layout = ( + showLegend = true, + xlabelFromMapping = nothing, + ylabelFromMapping = nothing, + useColorbar = true, + hidespines = (), + hidedecorations = (), + ), + visual = (; + contours = (color = :white, linewidth = 2), + label_scatter = true, + label_text = true, + bounding_geometry = Circle, + colormap = Reverse(:RdBu), + ), + mapping = (; + x = (nothing,), + positions = (:pos, :positions, :position, nothing), # Point / Array / Tuple + labels = (:labels, :label, :sensor, nothing), # String + ), + axis = (; aspect = DataAspect()), + ) return cfg end function PlotConfig(T::Val{:topoplotseries}) cfg = PlotConfig(:topoplot) - config_kwargs!(cfg, extra=( - combinefun=mean, - col_labels=true, - row_labels=true, - rasterize_heatmaps=true - ), layout=( - useColorbar=true, - ), visual=(; label_text=false # true doesnt work again - ), mapping=( - col=(:time,), - row=(nothing,) - )) + config_kwargs!( + cfg, + layout = (useColorbar = true,), + visual = (; + label_text = false # true doesnt work again + ), + mapping = (col = (:time,), row = (nothing,)), + ) return cfg end function PlotConfig(T::Val{:designmat}) cfg = PlotConfig() - config_kwargs!(cfg; layout=(; - useColorbar=true, - xlabelFromMapping=nothing, - ylabelFromMapping=nothing - ), axis=(; - xticklabelrotation=pi / 8 - ), extra=(; - xTicks=nothing, - sortData=false, - standardizeData=false - ) + config_kwargs!( + cfg; + layout = (; + useColorbar = true, + xlabelFromMapping = nothing, + ylabelFromMapping = nothing, + ), + axis = (; xticklabelrotation = pi / 8), ) return cfg end function PlotConfig(T::Val{:butterfly}) cfg = PlotConfig(:erp) - config_kwargs!(cfg; - layout=(; - showLegend=false - ), extra=(; - topoLegend=true, - markersize=10, - topowidth=0.25, - topoheigth=0.25, - topoPositionToColorFunction=x -> posToColorRomaO(x) - ), mapping=(; - color=(:channel, :channels, :trial, :trials,), - positions=(:pos, :positions, :position, :topoPositions, :x, nothing), - labels=(:labels, :label, :topoLabels, :sensor, nothing))) + config_kwargs!( + cfg; + layout = (; showLegend = false), + mapping = (; + color = (:channel, :channels, :trial, :trials), + positions = (:pos, :positions, :position, :topoPositions, :x, nothing), + labels = (:labels, :label, :topoLabels, :sensor, nothing), + ), + ) return cfg end function PlotConfig(T::Val{:erp}) cfg = PlotConfig() - config_kwargs!(cfg; mapping=(; - color=(:color, :coefname, nothing) - ), layout=(; - showLegend=true, - hidespines=(:r, :t) - ), extra=(; - butterfly=false, - categoricalColor=true, - categoricalGroup=true, - stderror=false, # XXX if it exists, should be plotted - pvalue=[] - )) + config_kwargs!( + cfg; + mapping = (; color = (:color, :coefname, nothing)), + layout = (; showLegend = true, hidespines = (:r, :t)), + ) return cfg end function PlotConfig(T::Val{:erpimage}) cfg = PlotConfig() - config_kwargs!(cfg; extra=(; - sortData=true, - meanPlot=false, - erpBlur=10 - ), layout=(; - useColorbar=true - ), colorbar=(; - label="Voltage [µV]" - ), axis=( - xlabel="Time", - ylabel="Sorted trials"), visual=(; - colormap=Reverse("RdBu") - )) + config_kwargs!( + cfg; + layout = (; useColorbar = true), + colorbar = (; label = "Voltage [µV]"), + axis = (xlabel = "Time", ylabel = "Sorted trials"), + visual = (; colormap = Reverse("RdBu")), + ) return cfg end function PlotConfig(T::Val{:paracoord}) cfg = PlotConfig() - config_kwargs!(cfg; layout=(; - xlabelFromMapping=:channel, - ylabelFromMapping=:y, - hidespines=(), - hidedecorations=(; label=false) - ), mapping=(; - channel=:channel, - category=:category, - time=:time - ), extra=(; - # paracoord fix-values - pc_aspect_ratio=0.55, - pc_right_padding=15, - pc_left_padding=25, - pc_top_padding=26, - pc_bottom_padding=16, - pc_tick_label_size=14 - )) + config_kwargs!( + cfg; + layout = (; + xlabelFromMapping = :channel, + ylabelFromMapping = :y, + hidespines = (), + hidedecorations = (; label = false), + ), + mapping = (; channel = :channel, category = :category, time = :time), + ) return cfg end @@ -254,7 +227,9 @@ function resolveMappings(plotData, mappingData) else return (nothing ∈ collect(choices)) ? # is it allowed to return nothing? nothing : - @error("default columns for $key = $choices not found, user must provide one by using plot_plotname(...;mapping=(; $key=:yourColumnName))") + @error( + "default columns for $key = $choices not found, user must provide one by using plot_plotname(...;mapping=(; $key=:yourColumnName))" + ) end end # have to use Dict here because NamedTuples break when trying to map them with keys/indices @@ -273,4 +248,4 @@ end """ Val{:bu}() to => :bu """ -valType_to_symbol(T) = Symbol(split(string(T), [':', '}'])[2]) \ No newline at end of file +valType_to_symbol(T) = Symbol(split(string(T), [':', '}'])[2]) diff --git a/src/topo_color.jl b/src/topo_color.jl index 8585894b5..c41ebca4d 100644 --- a/src/topo_color.jl +++ b/src/topo_color.jl @@ -8,9 +8,7 @@ function getTopoPositions(; labels=nothing, positions=nothing) return positions .|> (p -> Point2f(p[1], p[2])) end -function getTopoColor(positions, config) - posToColor = config.extra.topoPositionToColorFunction - # positions = getTopoPositions(plotData,config) +function getTopoColor(positions, posToColor) if isnothing(positions) return nothing end diff --git a/test/test_all.jl b/test/test_all.jl index d8e728b7d..b7b6b7298 100644 --- a/test/test_all.jl +++ b/test/test_all.jl @@ -27,8 +27,7 @@ plot_designmatrix!(f[2, 3], designmatrix(uf)) plot_topoplot!(f[3, 1], collect(1:64); positions=positions, visual=(; colormap=:viridis)) - plot_topoplotseries!(f[4, 1:3], d_topo, 0.1; positions=positions, mapping=(; label=:channel)) - + plot_topoplotseries!(f[4, 1:3], d_topo, 0.1; positions=positions, layout = (; useColorbar=true)) res_effects = effects(Dict(:continuous => -5:0.5:5), uf_deconv) @@ -48,7 +47,9 @@ plot_erpimage!(f[1, 4:5], times, d_singletrial) plot_circulareegtopoplot!(f[3:4, 4:5], d_topo[in.(d_topo.time, Ref(-0.3:0.1:0.5)), :]; - positions=positions, predictor=:time, extra=(; predictorBounds=[-0.3, 0.5])) + positions=positions, predictor=:time, predictorBounds=[-0.3, 0.5]) f -end \ No newline at end of file + #save("test.png", f) +end + diff --git a/test/test_butterfly.jl b/test/test_butterfly.jl index eb5a3d085..1cfde5417 100644 --- a/test/test_butterfly.jl +++ b/test/test_butterfly.jl @@ -2,5 +2,18 @@ @testset "markersize change" begin include("../docs/example_data.jl") data, pos = example_data("TopoPlots.jl") - plot_butterfly(data; positions=pos, extra=(markersize = 10, topoheigth=0.4, topowidth=0.4)) + plot_butterfly( + data; + positions = pos, + markersize = 10, + topoheigth = 0.4, + topowidth = 0.4, + ) +end + + +@testset "markersize change" begin + include("../docs/example_data.jl") + data, pos = example_data("TopoPlots.jl") + plot_butterfly(data; positions = pos) end diff --git a/test/test_dm.jl b/test/test_dm.jl new file mode 100644 index 000000000..516189ab1 --- /dev/null +++ b/test/test_dm.jl @@ -0,0 +1,11 @@ +@testset "basic" begin + include("../docs/example_data.jl") + uf = example_data("UnfoldLinearModel") + plot_designmatrix(designmatrix(uf)) +end + +@testset "sort data" begin + include("../docs/example_data.jl") + uf = example_data("UnfoldLinearModel") + plot_designmatrix(designmatrix(uf); sortData = true) +end diff --git a/test/test_erp.jl b/test/test_erp.jl new file mode 100644 index 000000000..a4aa38c07 --- /dev/null +++ b/test/test_erp.jl @@ -0,0 +1,48 @@ +@testset "testing standart" begin + data, evts = UnfoldSim.predef_eeg(; noiselevel = 12, return_epoched = true) + data = reshape(data, (1, size(data)...)) + f = @formula 0 ~ 1 + condition + continuous + se_solver = (x, y) -> Unfold.solver_default(x, y, stderror = true) + + m = fit( + UnfoldModel, + Dict(Any => (f, range(0, step = 1 / 100, length = size(data, 2)))), + evts, + data, + solver = se_solver, + ) + results = coeftable(m) + res_effects = effects(Dict(:continuous => -5:0.5:5), m) + + # ## Plot the results + plot_erp(results; :stderror => true) +end + + +@testset "testing standart with more arguments" begin + data, evts = UnfoldSim.predef_eeg(; noiselevel = 12, return_epoched = true) + data = reshape(data, (1, size(data)...)) + f = @formula 0 ~ 1 + condition + continuous + se_solver = (x, y) -> Unfold.solver_default(x, y, stderror = true) + + m = fit( + UnfoldModel, + Dict(Any => (f, range(0, step = 1 / 100, length = size(data, 2)))), + evts, + data, + solver = se_solver, + ) + results = coeftable(m) + res_effects = effects(Dict(:continuous => -5:0.5:5), m) + + # ## Plot the results + plot_erp( + res_effects; + mapping = (; y = :yhat, color = :continuous, group = :continuous), + legend = (; nbanks = 2), + layout = (; legendPosition = :right), + showLegend = true, + categoricalColor = false, + categoricalGroup = true, + ) +end diff --git a/test/test_erpimage.jl b/test/test_erpimage.jl index 7a956da5e..1bd17b9b0 100644 --- a/test/test_erpimage.jl +++ b/test/test_erpimage.jl @@ -1,19 +1,28 @@ #include("setup.jl") @testset "testing no bottom erp plot" begin - data, evts = UnfoldSim.predef_eeg(; noiselevel=10, return_epoched=true) - plot_erpimage(data; ploterp=false) + data, evts = UnfoldSim.predef_eeg(; noiselevel = 10, return_epoched = true) + plot_erpimage(data;) end @testset "testing with bottom erp plot" begin - data, evts = UnfoldSim.predef_eeg(; noiselevel=10, return_epoched=true) - plot_erpimage(data; ploterp=true) + data, evts = UnfoldSim.predef_eeg(; noiselevel = 10, return_epoched = true) + plot_erpimage(data; meanPlot = true) end @testset "testing no bottom erp plot in extra mode" begin - data, evts = UnfoldSim.predef_eeg(; noiselevel=10, return_epoched=true) - plot_erpimage(data; extra=(ploterp=true,)) + data, evts = UnfoldSim.predef_eeg(; noiselevel = 10, return_epoched = true) + plot_erpimage(data; meanPlot = true) end + +@testset "testing no bottom erp plot in extra mode" begin + f = Figure() + data, evts = UnfoldSim.predef_eeg(; noiselevel = 10, return_epoched = true) + plot_erpimage!(f[1, 1], data; meanPlot = true) + #save("erpimage.eps", f) +end + + #= @testset "testing better sorting" begin @@ -38,4 +47,4 @@ end plot_erpimage!(f[2, 2], times, d_nan; sortvalues=evts_fix.sac_amplitude, visual=v) f end -=# \ No newline at end of file +=# diff --git a/test/test_plot_circulareegtopoplot.jl b/test/test_plot_circulareegtopoplot.jl index 6bcd3f36f..08e31807c 100644 --- a/test/test_plot_circulareegtopoplot.jl +++ b/test/test_plot_circulareegtopoplot.jl @@ -18,7 +18,7 @@ predictor = [70,80,90], ) - @test_throws ErrorException plot_circulareegtopoplot(testdf; extra=(;predictorBounds=[0,100,360]), positions = [Point(1.0,2.0), Point(1.0,2.0), Point(1.0,2.0)],) + @test_throws ErrorException plot_circulareegtopoplot(testdf; predictorBounds=[0,100,360], positions = [Point(1.0,2.0), Point(1.0,2.0), Point(1.0,2.0)],) end end diff --git a/test/test_toposeries.jl b/test/test_toposeries.jl index 93bbee8ef..d494e760d 100644 --- a/test/test_toposeries.jl +++ b/test/test_toposeries.jl @@ -12,4 +12,37 @@ end Δbin=80 UnfoldMakie.plot_topoplotseries(df, Δbin; positions=positions, layout = (; useColorbar=true)) -end \ No newline at end of file + +end + +@testset "testing with colorbar" begin + data, positions = TopoPlots.example_data() + df = UnfoldMakie.eeg_matrix_to_dataframe(data[:, :, 1], string.(1:length(positions))); + Δbin=80 + UnfoldMakie.plot_topoplotseries(df, Δbin; positions=positions, layout = (; useColorbar=true)) + +end + +@testset "testing with colorbar and Figure" begin + f = Figure() + ax = Axis(f[2, 1:5], aspect=DataAspect()) + + data, positions = TopoPlots.example_data() + df = UnfoldMakie.eeg_matrix_to_dataframe(data[:, :, 1], string.(1:length(positions))) + + Δbin = 80 + chaLeng = 5 + x = Array(55:120:600) + t = Array(-0.3:0.18:0.5) + + xlims!(low=0, high=600) + ylims!(low=0, high=110) + + hidespines!(ax) + hidedecorations!(ax, label=false) + plot_topoplotseries!(f[1:2, 1:5], df, Δbin; positions=positions, visual=(label_scatter=false,), layout = (; useColorbar = true)) + + + f + +en