diff --git a/Project.toml b/Project.toml index d27ac0ac6..d65c797c9 100644 --- a/Project.toml +++ b/Project.toml @@ -1,10 +1,11 @@ name = "UnfoldMakie" uuid = "69a5ce3b-64fb-4f22-ae69-36dd4416af2a" authors = ["Vladimir Mikheev", "Daniel Baumgartner", "SΓΆren DΓΆring", "Niklas GΓ€rtner", "Furkan Lokman", "Benedikt Ehinger"] -version = "0.5.7" +version = "0.5.8" [deps] AlgebraOfGraphics = "cbdf2221-f076-402e-a563-3d30da359d67" +BSplineKit = "093aae92-e908-43d7-9660-e50ee39d5a0a" CategoricalArrays = "324d7699-5711-5eae-9e2f-1d82baa6b597" ColorSchemes = "35d6a980-a343-548e-a6ea-1d62b119f2f4" ColorTypes = "3da002f7-5984-5a60-b8a6-cbb66c0b333f" @@ -34,6 +35,7 @@ UnfoldMakiePyMNEExt = "PyMNE" [compat] AlgebraOfGraphics = "0.7, 0.8" +BSplineKit = "0.16, 0.17" CategoricalArrays = "0.10" ColorSchemes = "3" ColorTypes = "0.11" diff --git a/README.md b/README.md index 9bb492988..8a85c4828 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,15 @@ | | ||||| A toolbox for visualizations of EEG/ERP data and Unfold.jl models. -Based on the [Unfold](https://github.com/unfoldtoolbox/unfold.jl/) and [Makie](https://makie.juliaplots.org/stable/), it grants users high performance, and highly customizable plots. -We currently support: +Based on three libraries +- [Unfold](https://github.com/unfoldtoolbox/unfold.jl/) - for performing deconvolution regression; +- [Makie](https://makie.juliaplots.org/stable/) - very flexible visualisation library ([Maki-e](https://en.wikipedia.org/wiki/Maki-e) means "visualisation" on Japanese); +- [Algebra of Graphics](https://github.com/MakieOrg/AlgebraOfGraphics.jl) - Makie-based visualisation library, allowing flexible mapping. + +This grants users high performance, and highly customizable plots. + +We currently support 9 general ERP plots: - ![icon_erpplot_20px](https://github.com/unfoldtoolbox/UnfoldMakie.jl/assets/10183650/22c8472d-df78-46d7-afe8-e1e4e7b04313) ERP plots @@ -37,9 +43,11 @@ ERP images Channel images - ![icon_parallel_20px](https://github.com/unfoldtoolbox/UnfoldMakie.jl/assets/10183650/dab097c3-bcd6-4405-a44b-71cbe3e5fac9) Parallel coordinates -- Design matrices - Circular topoplots +And 2 Unfold-specific plots: +- Design matrices +- Splines plot ## Install @@ -113,8 +121,8 @@ If you use these visualizations, please cite: Benedikt Ehinger
Benedikt Ehinger

πŸ› πŸ’» πŸ“– πŸ€” πŸš‡ 🚧 πŸ’¬ πŸ‘€ ⚠️ βœ… - Quantum
Daniel Baumgartner

πŸ’» πŸ“– Vladimir Mikheev
Vladimir Mikheev

πŸ› πŸ’» πŸ“– πŸ€” 🚧 πŸ‘€ ⚠️ βœ… + Quantum
Daniel Baumgartner

πŸ’» πŸ“– NiklasMGaertner
Niklas GΓ€rtner

πŸ’» πŸ“– SorenDoring
Soren Doring

πŸ’» πŸ“– lokmanfl
Fadil Furkan Lokman

πŸ’» πŸ“– diff --git a/docs/Project.toml b/docs/Project.toml index 7064ee367..b07f03b59 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,5 +1,6 @@ [deps] AlgebraOfGraphics = "cbdf2221-f076-402e-a563-3d30da359d67" +BSplineKit = "093aae92-e908-43d7-9660-e50ee39d5a0a" CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" ColorSchemes = "35d6a980-a343-548e-a6ea-1d62b119f2f4" @@ -23,4 +24,5 @@ UnfoldSim = "ed8ae6d2-84d3-44c6-ab46-0baf21700804" XML2_jll = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a" [compat] -AlgebraOfGraphics = "0.7, 0.8" \ No newline at end of file +AlgebraOfGraphics = "0.7, 0.8" +BSplineKit = "0.16, 0.17" diff --git a/docs/example_data.jl b/docs/example_data.jl index 082bae000..e76b123c7 100644 --- a/docs/example_data.jl +++ b/docs/example_data.jl @@ -6,24 +6,34 @@ using Random """ example_data(String) -Creates example data. Currently, 7 datasets are available. -- `TopoPlots.jl` (default) - tidy DataFrame from `TopoPlots.jl` with 2 conditions, 64 channels and 800 ms time range. -- `UnfoldLinearModel` -- `UnfoldLinearModelMultiChannel` -- `UnfoldLinearModelContinuousTime` -- `7channels` -- `UnfoldTimeExpanded` -- `sort_data` - includes DataFrame EEG recordings `dat` and `evts` with event variables occured during experiment. `evts` could be used for sorting EEG data in ERP image. +Creates example data or model. Currently, 3 datasets and 6 models are available. + +Datasets: +- `TopoPlots.jl` (default) - 2 DataFrames from `TopoPlots.jl`:\\ + - DataFrame with estimate, time, 64 channels, topopositions, sterror and pvalue and 800 ms time range.\\ + - Posiions for 64 electrodes. +- `UnfoldLinearModelMultiChannel` - DataFrame with 5 channels, 3 coefnames, sterror, time and estimate. +- `sort_data` - 2 DataFrames: + - `dat` for EEG recordings and `evts` with event variables occured during experiment.\\ + - `evts` could be used for sorting EEG data in ERP image. + +Models: +- `UnfoldLinearModel` - Model with formula `1 + condition + continuous`. +- `UnfoldLinearModelContinuousTime` - Model with formula `timeexpand(1 + condition + continuous)` for times [0.0, 0.01 ... 0.5]. +- `UnfoldLinearModelwith1Spline` - Model with formula `1 + condition + spl(continuous, 4)`. +- `UnfoldLinearModelwith2Splines` - Model with formula ` 1 + condition + spl(continuous, 4) + spl(continuous2, 6)`. +- `7channels` - Model with formula `timeexpand(1 + condA)` for times [-0.1, -0.09 ... 0.5]. +- `UnfoldTimeExpanded` - Model with formula `timeexpand(1 + condition + continuous)` for times [-0.4, -0.39 ... 0.8]. + **Return Value:** `DataFrame`. """ function example_data(example = "TopoPlots.jl") - if example == "UnfoldLinearModel" # load and generate a simulated Unfold Design data, evts = UnfoldSim.predef_eeg(; noiselevel = 12, return_epoched = true) data = reshape(data, (1, size(data)...)) f = @formula 0 ~ 1 + condition + continuous - # generate ModelStruct + # generate ModelStruct se_solver = (x, y) -> Unfold.solver_default(x, y, stderror = true) return fit( UnfoldModel, @@ -72,6 +82,38 @@ function example_data(example = "TopoPlots.jl") basis = firbasis([0, 0.5], 100) # generate ModelStruct return fit(UnfoldModel, [Any => (f, basis)], evts, data) + elseif example == "UnfoldLinearModelwith1Spline" + # load and generate a simulated Unfold Design + data, evts = UnfoldSim.predef_eeg(; noiselevel = 12, return_epoched = true) + data = reshape(data, (1, size(data)...)) + evts.continuous2 .= + log10.(6 .+ rand(MersenneTwister(1), length(evts.continuous))) .^ 2 + f = @formula 0 ~ 1 + condition + spl(continuous, 4) + # generate ModelStruct + se_solver = (x, y) -> Unfold.solver_default(x, y, stderror = true) + return fit( + UnfoldModel, + [Any => (f, range(0, length = size(data, 2), step = 1 / 100))], + evts, + data; + solver = se_solver, + ) + elseif example == "UnfoldLinearModelwith2Splines" + # load and generate a simulated Unfold Design + data, evts = UnfoldSim.predef_eeg(; noiselevel = 12, return_epoched = true) + data = reshape(data, (1, size(data)...)) + evts.continuous2 .= + log10.(6 .+ rand(MersenneTwister(1), length(evts.continuous))) .^ 2 + f = @formula 0 ~ 1 + condition + spl(continuous, 4) + spl(continuous2, 6) + # generate ModelStruct + se_solver = (x, y) -> Unfold.solver_default(x, y, stderror = true) + return fit( + UnfoldModel, + [Any => (f, range(0, length = size(data, 2), step = 1 / 100))], + evts, + data; + solver = se_solver, + ) elseif example == "7channels" design = SingleSubjectDesign(conditions = Dict(:condA => ["levelA", "levelB"])) |> @@ -89,7 +131,6 @@ function example_data(example = "TopoPlots.jl") f = @formula 0 ~ 1 + condA bf_dict = [Any => (f, basisfunction)] return fit(UnfoldModel, bf_dict, evnts, df) - elseif example == "UnfoldTimeExpanded" df, evts = UnfoldSim.predef_eeg() f = @formula 0 ~ 1 + condition + continuous diff --git a/docs/literate/explanations/positions.jl b/docs/literate/explanations/positions.jl index f1623215b..c927b62c7 100644 --- a/docs/literate/explanations/positions.jl +++ b/docs/literate/explanations/positions.jl @@ -4,7 +4,7 @@ using UnfoldMakie using CairoMakie using TopoPlots -using PyMNE +using PyMNE; # # Get positions from MNE diff --git a/docs/literate/how_to/hide_deco.jl b/docs/literate/how_to/hide_deco.jl index 99d8c67d8..02eb0f1b2 100644 --- a/docs/literate/how_to/hide_deco.jl +++ b/docs/literate/how_to/hide_deco.jl @@ -32,8 +32,7 @@ plot_butterfly!( data; positions = pos, topomarkersize = 10, - topoheight = 0.4, - topowidth = 0.4, + topo_axis = (; height = Relative(0.4), width = Relative(0.4)), axis = (; title = "With decorations"), ) plot_butterfly!( @@ -41,8 +40,7 @@ plot_butterfly!( data; positions = pos, topomarkersize = 10, - topoheight = 0.4, - topowidth = 0.4, + topo_axis = (; height = Relative(0.4), width = Relative(0.4)), axis = (; title = "Without decorations"), layout = (; hidedecorations = (:label => true, :ticks => true, :ticklabels => true)), ) diff --git a/docs/literate/how_to/mult_vis_in_fig.jl b/docs/literate/how_to/mult_vis_in_fig.jl index 079339dc0..bfc65d2bd 100644 --- a/docs/literate/how_to/mult_vis_in_fig.jl +++ b/docs/literate/how_to/mult_vis_in_fig.jl @@ -69,14 +69,7 @@ pvals = DataFrame( to = [0.2, 0.5], # if coefname not specified, line should be black coefname = ["(Intercept)", "category: face"], ) -plot_erp!( - f[2, 1:2], - results, - categorical_color = false, - categorical_group = false, - significance = pvals, - stderror = true, -) +plot_erp!(f[2, 1:2], results, significance = pvals, stderror = true) plot_designmatrix!(f[2, 3], designmatrix(uf)) @@ -94,18 +87,14 @@ res_effects = effects(Dict(:continuous => -5:0.5:5), uf_deconv) plot_erp!( f[2, 4:5], res_effects; - categorical_color = false, - categorical_group = true, - mapping = (; y = :yhat, color = :continuous, group = :continuous), + mapping = (; y = :yhat, color = :continuous, group = :continuous => nonnumeric), legend = (; nbanks = 2), - layout = (; show_legend = true, legend_position = :right), ) plot_parallelcoordinates( f[3, 2:3], uf_5chan; mapping = (; color = :coefname), - layout = (; legend_position = :right), ) plot_erpimage!(f[1, 4:5], times, d_singletrial) @@ -157,8 +146,7 @@ plot_butterfly!( d_topo; positions = pos, topomarkersize = 10, - topoheight = 0.4, - topowidth = 0.4, + topo_axis = (; height = Relative(0.4), width = Relative(0.4)), ) hlines!(0, color = :gray, linewidth = 1) vlines!(0, color = :gray, linewidth = 1) diff --git a/docs/literate/how_to/position2color.jl b/docs/literate/how_to/position2color.jl index 87ac447f4..3d3448005 100644 --- a/docs/literate/how_to/position2color.jl +++ b/docs/literate/how_to/position2color.jl @@ -2,6 +2,7 @@ # You want to change the colors of the lines and markers on the inserted topoplot. # To do that you need to change the color scheme (aka color map) of the butterfly plot. +# You can find th elist of colormaps for Makie [here](https://docs.makie.org/v0.21/explanations/colors). # # Setup @@ -19,15 +20,15 @@ plot_butterfly(results; positions = positions) # # Color schemes # ## MNE style -#= -We can change the color scale by specifying a function that maps from an `(x, y)` tuple to a color. UnfoldMakie currently provides three different color scales: -- `pos2colorRGB` (same as MNE-Python), -- `pos2colorHSV` (HSV color space), -- `pos2colorRomaO`. +# We can change the color scale by specifying a function that maps from an `(x, y)` tuple to a color. +# `UnfoldMakie` currently provides three different color scales: +# - `pos2colorRGB` (same as MNE-Python), +# - `pos2colorHSV` (HSV color space), +# - `pos2colorRomaO`. + +# While `RGB` & `HSV` have the advantage of being 2D color maps, `Roma0` has the advantage of being perceptually uniform. +# Also you can specify a uniform color. -While `RGB` & `HSV` have the advantage of being 2D color maps, `Roma0` has the advantage of being perceptually uniform. -Also you can specify a uniform color. -=# plot_butterfly( results; diff --git a/docs/literate/intro/code_principles.jl b/docs/literate/intro/code_principles.jl index 3ac8df4b5..6f15e4aaf 100644 --- a/docs/literate/intro/code_principles.jl +++ b/docs/literate/intro/code_principles.jl @@ -3,9 +3,9 @@ # Here we will write about principles which we developed through our publication. -#- Code should be clear and concise -#- Variables inside the code should have meaningful names -#- Every function exposed to the user should have documentation that specifies all parameters, types, input and output arguments. -#- Most people will not look at the defaults, so it is very important to nudge users to show important details with a picture or text. -#- Function naming should be based on some theory and naming conventions. -#- You should avoid functions longer 50 lines +# - Code should be clear and concise. +# - Variables inside the code should have meaningful names. +# - Every function exposed to the user should have documentation that specifies all parameters, types, input and output arguments. +# - Most people will not look at the defaults, so it is very important to nudge users to label important details of the plot. +# - Function naming should be based on some theory and naming conventions. +# - You should avoid functions longer 50 lines. diff --git a/docs/literate/tutorials/butterfly.jl b/docs/literate/tutorials/butterfly.jl index edf54b478..043170874 100644 --- a/docs/literate/tutorials/butterfly.jl +++ b/docs/literate/tutorials/butterfly.jl @@ -38,7 +38,12 @@ plot_butterfly(df; positions = pos) # You want to change size of topomarkers and size of topoplot: -plot_butterfly(df; positions = pos, topomarkersize = 10, topoheight = 0.4, topowidth = 0.4) +plot_butterfly( + df; + positions = pos, + topomarkersize = 10, + topo_axis = (; height = Relative(0.4), width = Relative(0.4)), +) # You want to add vline and hline: diff --git a/docs/literate/tutorials/designmatrix.jl b/docs/literate/tutorials/designmatrix.jl index b6552e800..d6a34dc4b 100644 --- a/docs/literate/tutorials/designmatrix.jl +++ b/docs/literate/tutorials/designmatrix.jl @@ -11,7 +11,7 @@ using CairoMakie # Data include("../../../example_data.jl") -uf = example_data("UnfoldLinearModel") +uf = example_data("UnfoldLinearModel"); # # Plot Designmatrices diff --git a/docs/literate/tutorials/erp.jl b/docs/literate/tutorials/erp.jl index 9f3f8a9ea..684564687 100644 --- a/docs/literate/tutorials/erp.jl +++ b/docs/literate/tutorials/erp.jl @@ -13,7 +13,7 @@ using CairoMakie using DataFramesMeta using UnfoldSim using UnfoldMakie -include("../../../example_data.jl") +include("../../../example_data.jl"); # Data generation @@ -36,6 +36,9 @@ res_effects = effects(Dict(:continuous => -5:0.5:5), m); # ## Figure plotting plot_erp(results) +# To change legend title use `mapping.color`: +plot_erp(results, mapping = (; color = :coefname => "Conditions")) + # # Additional features # ## Effect plot @@ -53,9 +56,7 @@ plot_erp(results) plot_erp( res_effects; mapping = (; y = :yhat, color = :continuous, group = :continuous), - layout = (; show_legend = false), - categorical_color = false, # perceives color (here: continuous) as contionus - categorical_group = true, # separates lines, if `false` all lines will be connected + layout = (; use_colorbar = false), ) # ## Significance lines @@ -68,8 +69,8 @@ plot_erp( m = example_data("UnfoldLinearModel") results = coeftable(m) significancevalues = DataFrame( - from = [0.1, 0.3], - to = [0.5, 0.7], + from = [0.01, 0.2], + to = [0.3, 0.4], coefname = ["(Intercept)", "condition: face"], # if coefname not specified, line should be black ) plot_erp(results; :significance => significancevalues) @@ -110,7 +111,7 @@ text!(0.98, 0.2, text = "* Confidence\nintervals", align = (:right, :top)) f # There are two ways to implement it. -# First is using `:stderror = true' after `;`. +# First is using `:stderror = true` after `;`. results.se_low = results.estimate .- 0.5 results.se_high = results.estimate .+ 0.15 diff --git a/docs/literate/tutorials/splines.jl b/docs/literate/tutorials/splines.jl new file mode 100644 index 000000000..7ae9e57d8 --- /dev/null +++ b/docs/literate/tutorials/splines.jl @@ -0,0 +1,31 @@ +# # [Spline plot](@id spline_vis) +# **Spline plot** is a plot type for visualisation of terms in an UnfoldModel. +# Two subplots are generated for each spline term: 1) the basis function of the spline; 2) the density of the underlying covariate. + +# Multiple spline terms are arranged across columns. +# Dashed lines indicate spline knots. + +# # Setup +# Package and data loading + +using Unfold, UnfoldMakie +using BSplineKit, DataFrames + + +include("../../../example_data.jl") +df, pos = example_data("TopoPlots.jl") +m1 = example_data("UnfoldLinearModelwith1Spline"); +m2 = example_data("UnfoldLinearModelwith2Splines"); + + +# Spline plot with one spline term: +plot_splines(m1) + +# Spline plot with two spline terms: +plot_splines(m2) + +# # Configurations of Spline plot + +# ```@docs +# plot_splines +# ``` diff --git a/docs/literate/tutorials/topoplot.jl b/docs/literate/tutorials/topoplot.jl index abb67b0c2..2e62370d7 100644 --- a/docs/literate/tutorials/topoplot.jl +++ b/docs/literate/tutorials/topoplot.jl @@ -16,7 +16,7 @@ using DataFrames # Data loading -dat, positions = TopoPlots.example_data() +dat, positions = TopoPlots.example_data(); # The size of `data` is 64Γ—400Γ—3. This means: # - 64 channels; @@ -91,8 +91,11 @@ plot_topoplot!( ) f +# # Highlighting channels +plot_topoplot(dat[:, 50, 1]; positions, high_chan = [1, 2]) + # # Configurations of Topoplot # ```@docs # plot_topoplot -# ``` \ No newline at end of file +# ``` diff --git a/docs/literate/tutorials/topoplotseries.jl b/docs/literate/tutorials/topoplotseries.jl index 9c12cb578..410b30735 100644 --- a/docs/literate/tutorials/topoplotseries.jl +++ b/docs/literate/tutorials/topoplotseries.jl @@ -25,11 +25,21 @@ nothing #hide # `bin_width` - specify the interval between topoplots bin_width = 80 -plot_topoplotseries(df; bin_width, positions = positions) +plot_topoplotseries( + df; + bin_width, + positions = positions, + axis = (; xlabel = "Time windows [s]"), +) # `bin_num` - specify the number of topoplots -plot_topoplotseries(df; bin_num = 5, positions = positions) +plot_topoplotseries( + df; + bin_num = 5, + positions = positions, + axis = (; xlabel = "Time windows [s]"), +) # # Categorical and contionous x-values # By deafult x-value is `time`, but it could be any contionous (i.g. saccade amplitude) or categorical (any experimental variable) value. diff --git a/docs/make.jl b/docs/make.jl index 43a140fe4..b73e949d9 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -40,7 +40,7 @@ makedocs(; "Plot types" => "generated/intro/plot_types.md", "Code principles" => "generated/intro/code_principles.md", ], - "Visualization Types" => [ + "ERP Visualizations" => [ "ERP plot" => "generated/tutorials/erp.md", "Butterfly plot" => "generated/tutorials/butterfly.md", "Topoplot" => "generated/tutorials/topoplot.md", @@ -49,9 +49,12 @@ makedocs(; "ERP image" => "generated/tutorials/erpimage.md", "Channel image" => "generated/tutorials/channel_image.md", "Parallel coordinates" => "generated/tutorials/parallelcoordinates.md", - "Design matrix" => "generated/tutorials/designmatrix.md", "Circular topoplots" => "generated/tutorials/circ_topo.md", ], + "Unfold-specific Visualisations" => [ + "Design matrix" => "generated/tutorials/designmatrix.md", + "Spline plot" => "generated/tutorials/splines.md", + ], "How To" => [ "Change colormap of Butterfly plot" => "generated/how_to/position2color.md", "Hide decorations and axis spines" => "generated/how_to/hide_deco.md", diff --git a/docs/src/index.md b/docs/src/index.md index 2b5c9834b..1a6d58890 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -8,7 +8,7 @@ This is the documentation of the UnfoldMakie.jl package for the Julia programmin ## Highlights of UnfoldMakie.jl -- **10 plot functions for displaying ERPs.** +- **11 plot functions for displaying ERPs.** Each plot emphasizes certain dimensions while collapsing others. - **Fast plotting** Plot one figure with 20 topoplots in 1 second? No problemo! @@ -17,6 +17,6 @@ The package is primarily based on [Unfold.jl](https://github.com/unfoldtoolbox/u - **Many usage examples** You can find many user-friendly examples of how to use and adapt the plots in this documentation. - **Scientific colormaps by default** -According to our study [(Mikheev, 2024)](https://apertureneuro.org/article/116386-the-art-of-brainwaves-a-survey-on-event-related-potential-visualization-practices), 40% of EEG researchers do not know about the issue of scientific color maps. By default, we use `Reverse(:RdBu)` (based on [colorbrewer](https://colorbrewer2.org/#type=sequential&scheme=BuGn&n=3)) and `Roma` (based on [Sceintific Colormaps](https://www.fabiocrameri.ch/colourmaps/) by Fabio Crameri) as default color maps. +According to our study [(Mikheev, 2024)](https://apertureneuro.org/article/116386-the-art-of-brainwaves-a-survey-on-event-related-potential-visualization-practices), 40% of EEG researchers do not know about the issue of scientific color maps. As default color maps we use `Reverse(:RdBu)` (based on [colorbrewer](https://colorbrewer2.org/#type=sequential&scheme=BuGn&n=3)) and `Roma` (based on [Sceintific Colormaps](https://www.fabiocrameri.ch/colourmaps/) by Fabio Crameri). - **Interactivity** Several plots make use of `Observables.jl` which allows fast updating of the underlying data. Several plots already have predefined interactive features, e.g. you can click on labels to enable / disable them. See `plot_topoplotseries` and `plot_erpimage` for examples. diff --git a/src/UnfoldMakie.jl b/src/UnfoldMakie.jl index 596419461..4336b863b 100644 --- a/src/UnfoldMakie.jl +++ b/src/UnfoldMakie.jl @@ -48,6 +48,7 @@ end include("plotconfig.jl") include("docstring_template.jl") +include("supportive_defaults.jl") include("eeg_series.jl") include("plot_topoplotseries.jl") @@ -55,6 +56,7 @@ include("plot_topoplotseries.jl") include("plot_erp.jl") include("plot_butterfly.jl") include("plot_designmatrix.jl") +include("plot_splines.jl") include("plot_topoplot.jl") include("plot_erpimage.jl") include("plot_parallelcoordinates.jl") @@ -71,6 +73,8 @@ export PlotConfig export plot_designmatrix export plot_designmatrix! +export plot_splines +export plot_splines! export plot_erp export plot_erp! export plot_erpimage @@ -95,4 +99,5 @@ export to_positions export eeg_array_to_dataframe export eeg_topoplot_series export nonnumeric # reexport from AoG + end diff --git a/src/docstring_template.jl b/src/docstring_template.jl index 93572e216..86848601d 100644 --- a/src/docstring_template.jl +++ b/src/docstring_template.jl @@ -14,6 +14,7 @@ function _docstring(cfg_symb::Symbol) :circtopos => `Topoplot.eeg_topoplot`, :topoplot => `Topoplot.eeg_topoplot`, :topoplotseries => `Topoplot.eeg_topoplot`, + :splines => `Makie.series`, ) visuallink2 = Dict( :erp => "https://docs.makie.org/stable/reference/plots/lines/", @@ -26,6 +27,7 @@ function _docstring(cfg_symb::Symbol) :circtopos => "https://makieorg.github.io/TopoPlots.jl/stable/eeg/", :topoplot => "https://makieorg.github.io/TopoPlots.jl/stable/eeg/", :topoplotseries => "https://makieorg.github.io/TopoPlots.jl/stable/eeg/", + :splines => "https://docs.makie.org/stable/reference/plots/series", ) cbarstring = (cfg_symb == :erp || cfg_symb == :butterfly) ? @@ -35,8 +37,10 @@ function _docstring(cfg_symb::Symbol) :figure => "use `kwargs...` of [`Makie.Figure`](https://docs.makie.org/stable/explanations/figure/)", :axis => "use `kwargs...` of [`Makie.Axis`](https://docs.makie.org/stable/reference/blocks/axis/)", :legend => "use `kwargs...` of [`Makie.Legend`](https://docs.makie.org/stable/reference/blocks/legend/)", - :colorbar => "use `kwargs...` of $cbarstring", + :layout => "check this [page](https://unfoldtoolbox.github.io/UnfoldMakie.jl/dev/generated/how_to/hide_deco/)", + :mapping => "use any mapping from [`AlgebraOfGraphics`](https://aog.makie.org/stable/layers/mapping/)", :visual => "use `kwargs...` of [$(visuallink[cfg_symb])]($(visuallink2[cfg_symb]))", + :colorbar => "use `kwargs...` of $cbarstring", ) for k = 1:length(fn) namedtpl = string(Base.getfield(cfg, fn[k])) @@ -51,7 +55,7 @@ function _docstring(cfg_symb::Symbol) return """## Shared plot configuration options The shared plot options can be used as follows: `type = (; key = value, ...))`.\\ - For example, `plot_x(...; layout = (; show_legend = true, legend_position = :right))`.\\ + For example, `plot_x(...; colorbar = (; vertical = true, label = "Test"))`.\\ Multiple defaults will be cycled until match. Placing `;` is important! @@ -59,12 +63,3 @@ function _docstring(cfg_symb::Symbol) $(out) """ end -#= -""" - $(TYPEDSIGNATURES) -$(_docstring(:erp)) - -""" -function plot_new() - return "b" -end =# diff --git a/src/layout_helper.jl b/src/layout_helper.jl index 628bf8267..f6a67f228 100644 --- a/src/layout_helper.jl +++ b/src/layout_helper.jl @@ -9,43 +9,12 @@ function apply_layout_settings!( hm = nothing, drawing = nothing, ax = nothing, - plotArea = (1, 1), + plot_area = (1, 1), ) if isnothing(ax) ax = current_axis() end - if (config.layout.show_legend) - if isnothing(fig) - @error "Legend needs `Figure` parameter" - else - # set f[] position depending on legend_position - legend_position = - config.layout.legend_position == :right ? - fig[1:plotArea[1], plotArea[2]+1] : fig[plotArea[1]+1, 1:plotArea[2]] - if isnothing(drawing) - if (config.layout.use_colorbar) #not sure this line is useful - if isnothing(hm) - Colorbar( - legend_position; - colormap = config.visual.colormap, - config.colorbar..., - ) - else - Colorbar(legend_position, hm; config.colorbar...) - end - else # for PCP - title_pcp = getproperty.(Ref(config.legend), :title) # pop title - config.legend = dropnames(config.legend, (:title,)) # delete title - Legend(legend_position, ax, title_pcp; config.legend...) - end - else - legend!(legend_position, drawing; config.legend...) - colorbar!(legend_position, drawing; config.colorbar...) - end - end - end - if :hidespines ∈ keys(config.layout) && !isnothing(config.layout.hidespines) Makie.hidespines!(ax, config.layout.hidespines...) end @@ -58,6 +27,4 @@ Makie.hidedecorations!(ax::Matrix{AxisEntries}; kwargs...) = Makie.hidedecorations!.(ax; kwargs...) Makie.hidespines!(ax::Matrix{AxisEntries}, args...) = Makie.hidespines!.(ax, args...) -#hidedecorations!(ax::AxisEntries;kwargs...) = Makie.hidedecorations!.(ax.axis;kwargs...) Makie.hidespines!(ax::AxisEntries, args...) = Makie.hidespines!.(ax.axis, args...) -#hidespinses!(ax:Axis,args...) = hiespines!.(Ref(ax),args...) diff --git a/src/plot_butterfly.jl b/src/plot_butterfly.jl index 6346eac0b..33508f4af 100644 --- a/src/plot_butterfly.jl +++ b/src/plot_butterfly.jl @@ -26,12 +26,15 @@ Plot a Butterfly plot. Show an inlay topoplot with corresponding electrodes. Requires `positions`. - `topomarkersize::Real = 10` \\ Change the size of the electrode markers in topoplot. -- `topowidth::Real = 0.25` \\ - Change the width of inlay topoplot. -- `topoheight::Real = 0.25` \\ - Change the height of inlay topoplot. - `topopositions_to_color::x -> pos_to_color_RomaO(x)`\\ Change the line colors. +- `topo_axis::NamedTuple = (;)`\\ + Here you can flexibly change configurations of the topoplot axis.\\ + To see all options just type `?Axis` in REPL.\\ + Defaults: $(supportive_defaults(:topo_default)) +- `mapping = (;)`\\ + For highlighting specific channels.\\ + Example: `mapping = (; color = :highlight))`, where `:highlight` is variable with appopriate mapping. **Return Value:** `Figure` displaying Butterfly plot. @@ -48,12 +51,8 @@ function plot_butterfly!( labels = nothing, topolegend = true, topomarkersize = 10, - topowidth = 0.35, - topoheight = 0.35, - topohalign = 0.05, - topovalign = 0.95, - topoaspect = 1, topopositions_to_color = x -> pos_to_color_RomaO(x), + topo_axis = (;), mapping = (;), kwargs..., ) @@ -93,6 +92,7 @@ function plot_butterfly!( colors = get_topo_color(all_positions, topopositions_to_color) end end + # Categorical mapping # convert color column into string to prevent wrong grouping if (:group ∈ keys(config.mapping)) @@ -111,15 +111,12 @@ function plot_butterfly!( end mapp = AlgebraOfGraphics.mapping() - - if (:color ∈ keys(config.mapping)) - mapp = mapp * AlgebraOfGraphics.mapping(; config.mapping.color) - end - - if (:group ∈ keys(config.mapping)) - mapp = mapp * AlgebraOfGraphics.mapping(; config.mapping.group) + for i in [:color, :group] + if (i ∈ keys(config.mapping)) + tmp = getindex(config.mapping, i) + mapp = mapp * AlgebraOfGraphics.mapping(; i => tmp) + end end - # remove x / y mapping_others = deleteKeys(config.mapping, [:x, :y, :positions, :lables]) xy_mapp = @@ -131,25 +128,17 @@ function plot_butterfly!( f_grid = f[1, 1] if (topolegend) - topoAxis = Axis( - f_grid, - width = Relative(topowidth), - height = Relative(topoheight), - halign = topohalign, - valign = topovalign, - aspect = topoaspect, - ) + topo_axis = update_axis(supportive_defaults(:topo_default); topo_axis...) ix = unique(i -> plot_data[:, config.mapping.group[1]][i], 1:size(plot_data, 1)) topoplot_legend( - topoAxis, + Axis(f_grid; topo_axis...), topomarkersize, plot_data[ix, config.mapping.color[1]], colors, all_positions, ) end - if isnothing(colors) drawing = draw!(f_grid, plot_equation; axis = config.axis) else @@ -163,3 +152,56 @@ function plot_butterfly!( apply_layout_settings!(config; fig = f, ax = drawing, drawing = drawing) return f end + + +# topopositions_to_color = colors? +function topoplot_legend(axis, topomarkersize, unique_val, colors, all_positions) + all_positions = unique(all_positions) + topo_matrix = eeg_head_matrix(all_positions, (0.5, 0.5), 0.5) + + un = unique(unique_val) + special_colors = + ColorScheme(vcat(RGB(1, 1, 1.0), colors[map(x -> findfirst(x .== un), unique_val)])) + + xlims!(low = -0.2, high = 1.2) + ylims!(low = -0.2, high = 1.2) + + topoplot = eeg_topoplot!( + axis, + 1:length(all_positions), # go from 1:npos + string.(1:length(all_positions)); + positions = all_positions, + interpolation = NullInterpolator(), # inteprolator that returns only 0, which is put to white in the special_colorsmap + colorrange = (0, length(all_positions)), # add the 0 for the white-first color + colormap = special_colors, + head = (color = :black, linewidth = 1, model = topo_matrix), + label_scatter = (markersize = topomarkersize, strokewidth = 0.5), + ) + hidespines!(axis) + hidedecorations!(axis) + return topoplot +end + +function eeg_head_matrix(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, + ) +end diff --git a/src/plot_circular_topoplots.jl b/src/plot_circular_topoplots.jl index 635a8bc14..05f2744f7 100644 --- a/src/plot_circular_topoplots.jl +++ b/src/plot_circular_topoplots.jl @@ -83,10 +83,9 @@ function plot_circular_topoplots!( # setting the colorbar to the bottom right of the box. # Relative values got determined by checking what subjectively looks best Colorbar( - f[1, 2], - colormap = config.colorbar.colormap, + f[1, 2]; colorrange = (min, max), - label = config.colorbar.label, + config.colorbar..., height = @lift Fixed($(pixelarea(ax.scene)).widths[2]) ) plot_topo_plots!( @@ -102,7 +101,6 @@ function plot_circular_topoplots!( ) apply_layout_settings!(config; ax = ax) - # set the scene's background color according to config #set_theme!(Theme(backgroundcolor = config.axisData.backgroundcolor)) return f diff --git a/src/plot_erp.jl b/src/plot_erp.jl index 63bcfc17c..c1ba6e8be 100644 --- a/src/plot_erp.jl +++ b/src/plot_erp.jl @@ -19,10 +19,6 @@ Plot an ERP plot. ## Keyword arguments (kwargs) -- `categorical_color::Bool = true`\\ - Treat `:color` as categorical variable in case of numeric `:color` column. -- `categorical_group::Bool = true`\\ - Treat `:group` as categorical variable in case of numeric `:group` column. - `stderror::Bool = false`\\ Add an error ribbon, with lower and upper limits based on the `:stderror` column. - `significance::DataFrame = nothing`\\ @@ -55,13 +51,18 @@ function plot_erp!( plot_data::Union{DataFrame,AbstractMatrix,AbstractVector{<:Number}}; positions = nothing, labels = nothing, - categorical_color = true, - categorical_group = true, + categorical_color = nothing, + categorical_group = nothing, stderror = false, # XXX if it exists, should be plotted significance = nothing, mapping = (;), kwargs..., ) + if !(isnothing(categorical_color) && isnothing(categorical_group)) + @warn "categorical_color and categorical_group have been deprecated. + To switch to categorical colors, please use `mapping(..., color = :mycolorcolum => nonnumeric)`. + `group` is now automatically cast to nonnumeric." + end config = PlotConfig(:erp) config_kwargs!(config; mapping, kwargs...) plot_data = deepcopy(plot_data) @@ -84,41 +85,40 @@ function plot_erp!( plot_data.group = plot_data.group .|> a -> isnothing(a) ? :fixef : a end - # check if stderror values exist and create new columns with high and low band - if "stderror" ∈ names(plot_data) && stderror - plot_data.stderror = plot_data.stderror .|> a -> isnothing(a) ? 0.0 : a - plot_data[!, :se_low] = plot_data[:, config.mapping.y] .- plot_data.stderror - plot_data[!, :se_high] = plot_data[:, config.mapping.y] .+ plot_data.stderror - end - # Categorical mapping - # convert color column into string to prevent wrong grouping - if categorical_color && (:color ∈ keys(config.mapping)) - config.mapping = - merge(config.mapping, (; color = config.mapping.color => nonnumeric)) - end - - # converts group column into string - if categorical_group && (:group ∈ keys(config.mapping)) - config.mapping = - merge(config.mapping, (; group = config.mapping.group => nonnumeric)) - end + # automatically convert col & group to nonnumeric if ( :col ∈ keys(config.mapping) && + !isa(config.mapping.col, Pair) && typeof(plot_data[:, config.mapping.col]) <: AbstractVector{<:Number} ) config.mapping = merge(config.mapping, (; col = config.mapping.col => nonnumeric)) end - mapp = AlgebraOfGraphics.mapping() - - if (:color ∈ keys(config.mapping)) - mapp = mapp * AlgebraOfGraphics.mapping(; config.mapping.color) + if ( + :group ∈ keys(config.mapping) && + !isa(config.mapping.group, Pair) && + typeof(plot_data[:, config.mapping.group]) <: AbstractVector{<:Number} + ) + config.mapping = + merge(config.mapping, (; group = config.mapping.group => nonnumeric)) end - if (:group ∈ keys(config.mapping)) - mapp = mapp * AlgebraOfGraphics.mapping(; config.mapping.group) + # check if stderror values exist and create new columns with high and low band + if "stderror" ∈ names(plot_data) && stderror + plot_data.stderror = plot_data.stderror .|> a -> isnothing(a) ? 0.0 : a + plot_data[!, :se_low] = plot_data[:, config.mapping.y] .- plot_data.stderror + plot_data[!, :se_high] = plot_data[:, config.mapping.y] .+ plot_data.stderror end + mapp = AlgebraOfGraphics.mapping() + + # mapping for stderrors + for i in [:color, :group, :col, :row, :layout] + if (i ∈ keys(config.mapping)) + tmp = getindex(config.mapping, i) + mapp = mapp * AlgebraOfGraphics.mapping(; i => tmp) + end + end # remove x / y mapping_others = deleteKeys(config.mapping, [:x, :y, :positions, :lables]) @@ -126,6 +126,7 @@ function plot_erp!( AlgebraOfGraphics.mapping(config.mapping.x, config.mapping.y; mapping_others...) basic = visual(Lines; config.visual...) * xy_mapp # add band of sdterrors + if stderror m_se = AlgebraOfGraphics.mapping(config.mapping.x, :se_low, :se_high) basic = basic + visual(Band, alpha = 0.5) * m_se @@ -140,78 +141,21 @@ function plot_erp!( plot_equation = basic * mapp - f_grid = f[1, 1:4] - - # draw a standart ERP lineplot + f_grid = f[1, 1] = GridLayout() drawing = draw!(f_grid, plot_equation; axis = config.axis) if config.layout.show_legend == true config_kwargs!(config; mapping, layout = (; show_legend = false)) if config.layout.use_legend == true - legend!(f[:, 5], drawing; config.legend...) + legend!(f_grid[:, end+1], drawing; config.legend...) end if config.layout.use_colorbar == true - N = config.layout.use_legend == false ? 5 : 6 - colorbar!(f[:, N], drawing; config.colorbar...) + colorbar!(f_grid[:, end+1], drawing; config.colorbar...) end end apply_layout_settings!(config; fig = f, ax = drawing, drawing = drawing) return f end -function eeg_head_matrix(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, - ) -end - -# topopositions_to_color = colors? -function topoplot_legend(axis, topomarkersize, unique_val, colors, all_positions) - all_positions = unique(all_positions) - - topo_matrix = eeg_head_matrix(all_positions, (0.5, 0.5), 0.5) - - un = unique(unique_val) - special_colors = - ColorScheme(vcat(RGB(1, 1, 1.0), colors[map(x -> findfirst(x .== un), unique_val)])) - - xlims!(low = -0.2, high = 1.2) - ylims!(low = -0.2, high = 1.2) - topoplot = eeg_topoplot!( - axis, - 1:length(all_positions), # go from 1:npos - string.(1:length(all_positions)); - positions = all_positions, - interpolation = NullInterpolator(), # inteprolator that returns only 0, which is put to white in the special_colorsmap - colorrange = (0, length(all_positions)), # add the 0 for the white-first color - colormap = special_colors, - head = (color = :black, linewidth = 1, model = topo_matrix), - label_scatter = (markersize = topomarkersize, strokewidth = 0.5), - ) - - hidedecorations!(current_axis()) - hidespines!(current_axis()) - - return topoplot -end - function add_significance(plot_data, significance, config) p = deepcopy(significance) diff --git a/src/plot_erpgrid.jl b/src/plot_erpgrid.jl index ef06456ac..fa994f9ff 100644 --- a/src/plot_erpgrid.jl +++ b/src/plot_erpgrid.jl @@ -13,6 +13,22 @@ Plot an ERP image. Electrode positions. - `ch_names::Vector{String}`\\ Vector with channel names. +- `hlines_grid_axis::NamedTuple = (;)`\\ + Here you can flexibly change configurations of the hlines on all subaxes.\\ + To see all options just type `?hlines` in REPL.\\ + Defaults: $(supportive_defaults(:hlines_grid_default)) +- `vlines_grid_axis::NamedTuple = (;)`\\ + Here you can flexibly change configurations of the vlines on all subaxes.\\ + To see all options just type `?vlines` in REPL.\\ + Defaults: $(supportive_defaults(:vlines_grid_default)) +- `lines_grid_axis::NamedTuple = (;)`\\ + Here you can flexibly change configurations of the lines on all subaxes.\\ + To see all options just type `?lines` in REPL.\\ + Defaults: $(supportive_defaults(:lines_grid_default)) +- `labels_grid_axis::NamedTuple = (;)`\\ + Here you can flexibly change configurations of the labels on all subaxes.\\ + To see all options just type `?text` in REPL.\\ + Defaults: $(supportive_defaults(:labels_grid_default)) ## Keyword arguments (kwargs) - `drawlabels::Bool = false`\\ @@ -48,6 +64,10 @@ function plot_erpgrid!( ch_names::Vector{String}; drawlabels = false, times = -1:size(data, 2)-2, #arbitrary + labels_grid_axis = (;), + hlines_grid_axis = (;), + vlines_grid_axis = (;), + lines_grid_axis = (;), kwargs..., ) config = PlotConfig(:erpgrid) @@ -72,6 +92,8 @@ function plot_erpgrid!( axlist = [] rel_zeropoint = argmin(abs.(times)) ./ length(times) + labels_grid_axis = + update_axis(supportive_defaults(:labels_grid_default); labels_grid_axis...) for (ix, p) in enumerate(eachcol(positions)) x = p[1] y = p[2] @@ -83,29 +105,26 @@ function plot_erpgrid!( valign = y, ) if drawlabels - text!( - ax, - rel_zeropoint + 0.1, - 1, - color = :gray, - fontsize = 12, - text = ch_names[ix], - align = (:left, :top), - space = :relative, - ) + text!(ax, rel_zeropoint + 0.1, 1; text = ch_names[ix], labels_grid_axis...) end # todo: add label if not nothing push!(axlist, ax) end - # todo: make optional + be able to specify the linewidth + color - hlines!.(axlist, Ref([0.0]), color = :gray, linewidth = 0.5) - vlines!.(axlist, Ref([0.0]), color = :gray, linewidth = 0.5) + hlines_grid_axis = + update_axis(supportive_defaults(:hlines_grid_default); hlines_grid_axis...) + vlines_grid_axis = + update_axis(supportive_defaults(:vlines_grid_default); vlines_grid_axis...) + lines_grid_axis = + update_axis(supportive_defaults(:lines_grid_default); lines_grid_axis...) + + hlines!.(axlist, Ref([0.0]); hlines_grid_axis...) + vlines!.(axlist, Ref([0.0]); vlines_grid_axis...) times = isnothing(times) ? (1:size(data, 2)) : times # todo: add customizable kwargs - h = lines!.(axlist, Ref(times), eachrow(data)) + h = lines!.(axlist, Ref(times), eachrow(data); lines_grid_axis...) linkaxes!(axlist...) hidedecorations!.(axlist) @@ -119,14 +138,20 @@ function plot_erpgrid!( xstart = [Point2f(0), Point2f(0)] xdir = [Vec2f(0, 0.1), Vec2f(0.1, 0)] arrows!(xstart, xdir, arrowsize = 10) - text!(0.02, 0, text = config.axis.xlabel, align = (:left, :top), fontsize = 12) + text!( + 0.02, + 0, + text = config.axis.xlabel, + fontsize = config.axis.fontsize, + align = (:left, :top), + ) text!( -0.008, 0.01, text = config.axis.ylabel, + fontsize = config.axis.fontsize, align = (:left, :baseline), rotation = Ο€ / 2, - fontsize = 12, ) f end diff --git a/src/plot_erpimage.jl b/src/plot_erpimage.jl index 5cf4d1abb..e08cb9388 100644 --- a/src/plot_erpimage.jl +++ b/src/plot_erpimage.jl @@ -33,10 +33,12 @@ Plot an ERP image. If `sortvalues = true` the default text will change to "Sorted trials", but it could be changed to any values specified manually. - `meanplot_axis::NamedTuple = (;)`\\ Here you can flexibly change configurations of meanplot.\\ - To see all options just type `?Axis` in REPL. + To see all options just type `?Axis` in REPL.\\ + Defaults: $(supportive_defaults(:meanplot_default)) - `sortplot_axis::NamedTuple = (;)`\\ Here you can flexibly change configurations of meanplot.\\ - To see all options just type `?Axis` in REPL. + To see all options just type `?Axis` in REPL.\\ + Defaults: $(supportive_defaults(:sortplot_default)) $(_docstring(:erpimage)) @@ -125,7 +127,7 @@ function plot_erpimage!( ) end hidespines!(ax, :r, :t) - apply_layout_settings!(config; fig = f, hm = hm, ax = ax, plotArea = (4, 1)) + apply_layout_settings!(config; fig = f, hm = hm, ax = ax, plot_area = (4, 1)) return f end @@ -134,13 +136,11 @@ function ei_meanplot(ax, data, config, f, ga, times, meanplot_axis) ax.xticklabelsvisible = false trace = @lift(mean($data, dims = 2)[:, 1]) + meanplot_axis = update_axis(supportive_defaults(:meanplot_default); meanplot_axis...) + axbottom = Axis( ga[5, 1:4]; - height = 100, ylabel = config.colorbar.label === nothing ? "" : config.colorbar.label, - xlabel = "Time [s]", - xlabelpadding = 0, - xautolimitmargin = (0, 0), limits = @lift(( minimum($times), maximum($times), @@ -163,11 +163,10 @@ function ei_sortvalue(sortvalues, f, ax, hm, config, sortval_xlabel, sortplot_ax error("`show_sortval` can not take `sortvalues` with all NaN-values") end gb = f[1, 3] = GridLayout() + sortplot_axis = update_axis(supportive_defaults(:sortplot_default); sortplot_axis...) axleft = Axis( gb[1:4, 1:5]; xlabel = sortval_xlabel, - ylabelvisible = true, - yticklabelsvisible = false, #xautolimitmargin = (-1, 1), #yautolimitmargin = (1, 100), xticks = @lift([ @@ -188,7 +187,6 @@ function ei_sortvalue(sortvalues, f, ax, hm, config, sortval_xlabel, sortplot_ax hidedecorations!(axempty) hidespines!(axempty) hidespines!(axleft, :r, :t) - #scatter!(axleft, xs, ys) lines!(axleft, xs, ys) if config.layout.use_colorbar != false Colorbar( diff --git a/src/plot_parallelcoordinates.jl b/src/plot_parallelcoordinates.jl index 9192148c1..72d822324 100644 --- a/src/plot_parallelcoordinates.jl +++ b/src/plot_parallelcoordinates.jl @@ -8,7 +8,6 @@ Plot a PCP (parallel coordinates plot).\\ Dimensions: conditions, channels, time, trials. ## Arguments: - - `f::Union{GridPosition, GridLayout, Figure}` `Figure`, `GridLayout`, or `GridPosition` to draw the plot. - `data::Union{DataFrame, AbstractMatrix}`\\ @@ -101,7 +100,7 @@ function plot_parallelcoordinates( end UnfoldMakie.config_kwargs!(config; visual = (; color = c)) - f, ax, axlist, hlines = parallelcoordinates( + f1, ax, axlist, hlines = parallelcoordinates( f, d5; normalize = normalize, @@ -119,7 +118,10 @@ function plot_parallelcoordinates( fontsize = 20, font = :bold, ) - apply_layout_settings!(config; fig = f, ax = ax) + if config.layout.show_legend + Legend(f[1, 2], ax, config.legend.title; config.legend...) + end + apply_layout_settings!(config; fig = f1, ax = ax) return isa(f, Figure) ? Makie.FigureAxisPlot(f, [ax, axlist], hlines[1]) : Makie.AxisPlot([ax, axlist], hlines[1]) @@ -165,7 +167,6 @@ function parallelcoordinates( minlist = minimum(plotdata) maxlist = maximum(plotdata) end - # @debug plotdata # edge bending / bundling if !bend @@ -175,7 +176,10 @@ function parallelcoordinates( x_plotdata = range(1, x_pos[end], step = 0.05) plotdata_int = Array{Float64}(undef, length(x_plotdata), size(plotdata, 2)) for k = 1:size(plotdata, 2) - itp = interpolate(plotdata[:, k], BSpline(Cubic(Interpolations.Line(OnGrid())))) + itp = Interpolations.interpolate( + plotdata[:, k], + BSpline(Cubic(Interpolations.Line(OnGrid()))), + ) plotdata_int[:, k] = itp.(x_plotdata) end end @@ -189,14 +193,12 @@ function parallelcoordinates( # categorical colors un_c = unique(color) color_ix = [findfirst(un_c .== c) for c in color] - #@assert length(un_c) == 1 "Only single color found, please don't specify color, " if length(un_c) == 1 - @warn "Only single unique value found in the specified color vector" - color = cgrad(colormap, 2)[color_ix] + @warn "Only single unique value found in the specified color vector." + color = cgrad(colormap, 2)[color_ix] # color gradient else color = cgrad(colormap, length(un_c))[color_ix] end - #crange = [1,length(unique(color))] else # continuous color crange = [minimum(color), maximum(color)] @@ -206,7 +208,6 @@ function parallelcoordinates( # plot the lines - this way it will be easy to curve them too hlines = [] for (ix, r) in enumerate(eachcol(plotdata_int)) - h = lines!( ax, x_plotdata, @@ -345,7 +346,6 @@ Used to inject extrema ticks and round them if necessary. struct PCPTicks end function Makie.get_ticks(ticks::PCPTicks, scale, formatter, vmin, vmax) - #@debug "get_ticks custom",vmin,vmax tickvalues = Makie.get_tickvalues(Makie.WilkinsonTicks(5), scale, vmin, vmax) ticklabels_without = Makie.get_ticklabels(formatter, tickvalues) diff --git a/src/plot_splines.jl b/src/plot_splines.jl new file mode 100644 index 000000000..7041ad3a2 --- /dev/null +++ b/src/plot_splines.jl @@ -0,0 +1,106 @@ +using BSplineKit, Unfold + +""" + plot_splines(m::UnfoldModel; kwargs...) + plot_splines!(f::Union{GridPosition, GridLayout, Figure}, m::UnfoldModel; kwargs...) + +Visualization of spline terms in an UnfoldModel. Two subplots are generated for each spline term:\\ +1) the basis function of the spline; 2) the density of the underlying covariate.\\ + +Multiple spline terms are arranged across columns.\\ +Dashed lines indicate spline knots. + +## Arguments: + +- `f::Union{GridPosition, GridLayout, Figure}` + `Figure`, `GridLayout`, or `GridPosition` to draw the plot. +- `m::UnfoldModel`\\ + UnfoldModel with splines. +- `spline_axis::NamedTuple = (;)`\\ + Here you can flexibly change configurations of spline subplots.\\ + To see all options just type `?Axis` in REPL.\\ + Defaults: $(supportive_defaults(:spline_default)) +- `density_axis::NamedTuple = (;)`\\ + Here you can flexibly change configurations of density subplots.\\ + To see all options just type `?Axis` in REPL.\\ + Defaults: $(supportive_defaults(:density_default)) +- `superlabel_config::NamedTuple = (;)`\\ + Here you can flexibly change configurations of the Label on the top of the plot.\\ + To see all options just type `?Label` in REPL.\\ + Defaults: $(supportive_defaults(:superlabel_default)) + +$(_docstring(:splines)) + +**Return Value:** `Figure` with splines and their density for basis functions. +""" +plot_splines(m::UnfoldModel; kwargs...) = plot_splines(Figure(), m; kwargs...) + +function plot_splines( + f::Union{GridPosition,GridLayout,Figure}, + m::UnfoldModel; + spline_axis = (;), + density_axis = (;), + superlabel_config = (;), + kwargs..., +) + config = PlotConfig(:splines) + config_kwargs!(config; kwargs...) + spline_axis, density_axis, superlabel_config = + supportive_axes_management(spline_axis, density_axis, superlabel_config) + + ga = f[1, 1] = GridLayout() + + terms = Unfold.formulas(m)[1].rhs.terms + spl_title = join(terms, " + ") + + splFunction = Base.get_extension(Unfold, :UnfoldBSplineKitExt).splFunction + spl_ix = findall(isa.(terms, Unfold.AbstractSplineTerm)) + @assert !isempty(spl_ix) "No spline term is found in UnfoldModel. Does your UnfoldModel really have a `spl(...)` or other `AbstractSplineTerm`?" + + spline_terms = [terms[i] for i in spl_ix] + subplot_id = 1 + for spline_term in spline_terms + x_range = range( + spline_term.breakpoints[1], + stop = spline_term.breakpoints[end], + length = 100, + ) + basis_set = splFunction(x_range, spline_term) + + if subplot_id > 1 + spline_axis = update_axis(spline_axis; ylabelvisible = false) + density_axis = update_axis(density_axis; ylabelvisible = false) + end + a1 = Axis(ga[1, subplot_id]; title = string(spline_term), spline_axis...) + series!( + x_range, + basis_set', + color = resample_cmap(config.visual.colormap, size(basis_set')[1]), + ) # continuous color map used + vlines!( + spline_term.breakpoints; + ymin = extrema(basis_set')[1], + ymax = extrema(basis_set')[2], + linestyle = :dash, + ) + a2 = Axis(ga[2, subplot_id]; xlabel = string(spline_term.term.sym), density_axis...) + density!( + Unfold.events(designmatrix(m))[1][:, spline_term.term.sym]; + color = :transparent, + strokecolor = :black, + strokewidth = 1, + ) + linkxaxes!(a1, a2) + subplot_id = subplot_id + 1 + end + Label(ga[1, 1:end, Top()], spl_title; superlabel_config...) + f +end + +function supportive_axes_management(spline_axis, density_axis, superlabel_config) + spline_axis = update_axis(supportive_defaults(:spline_default); spline_axis...) + density_axis = update_axis(supportive_defaults(:density_default); density_axis...) + superlabel_config = + update_axis(supportive_defaults(:superlabel_default); superlabel_config...) + return spline_axis, density_axis, superlabel_config +end diff --git a/src/plot_topoplot.jl b/src/plot_topoplot.jl index 0747b2d61..4df4551b6 100644 --- a/src/plot_topoplot.jl +++ b/src/plot_topoplot.jl @@ -1,5 +1,6 @@ """ plot_topoplot!(f::Union{GridPosition, GridLayout, Figure}, data::Union{<:AbstractDataFrame,<:AbstractVector}; positions::Vector, labels = nothing, kwargs...) +using Interpolations: positions plot_topoplot(data::Union{<:AbstractDataFrame,<:AbstractVector}; position::Vector, labels = nothing, kwargs...) Plot a topoplot. @@ -12,6 +13,8 @@ Plot a topoplot. Positions used if `data` is not a `DataFrame`. Positions are generated from `labels` if `positions = nothing`. - `labels::Vector{String} = nothing`\\ Labels used if `data` is not a DataFrame. +- `high_chan = nothing` - channnel(s) to highlight by color. +- `high_color = :darkgreen` - color for highlighting. $(_docstring(:topoplot)) @@ -25,6 +28,8 @@ function plot_topoplot!( data::Union{<:AbstractDataFrame,<:AbstractVector}; labels = nothing, positions = nothing, + high_chan = nothing, + high_color = :darkgreen, kwargs..., ) config = PlotConfig(:topoplot) @@ -41,14 +46,35 @@ function plot_topoplot!( positions = TopoPlots.labels2positions(labels) end positions = get_topo_positions(; positions = positions, labels = labels) - eeg_topoplot!(axis, data, labels; positions, config.visual...) + if isa(high_chan, Int) || isa(high_chan, Vector{Int64}) + x = zeros(length(positions)) + isa(high_chan, Int) ? x[high_chan] = 1 : x[high_chan] .= 1 + clist = [:gray, high_color][Int.(x .+ 1)] #color for highlighting + eeg_topoplot!( + axis, + data, + labels; + positions, + config.visual..., + label_scatter = (; + color = clist, + markersize = ((x .+ 0.25) .* 40) ./ 5, # make adjustabel for users + ), + ) + else + eeg_topoplot!(axis, data, labels; positions, config.visual...) + end + + if config.layout.use_colorbar == true + Colorbar(f[1, 2]; colormap = config.visual.colormap, config.colorbar...) + end clims = (min(data...), max(data...)) if clims[1] β‰ˆ clims[2] @warn """The min and max of the value represented by the color are the same, it seems that the data values are identical. We disable the color bar in this figure. Note: The identical min and max may cause an interpolation error when plotting the topoplot.""" - config_kwargs!(config, layout = (; use_colorbar = false, show_legend = false)) + config_kwargs!(config, layout = (; use_colorbar = false)) else config_kwargs!(config, colorbar = (; limits = clims)) end diff --git a/src/plot_topoplotseries.jl b/src/plot_topoplotseries.jl index ea2335e7d..494c11489 100644 --- a/src/plot_topoplotseries.jl +++ b/src/plot_topoplotseries.jl @@ -42,9 +42,12 @@ Multiple miniature topoplots in regular distances. `mapping.col` - specify x-value, can be any continuous or categorical variable.\\ `mapping.row` - specify y-value, can be any continuous or categorical variable (not implemented yet).\\ `mapping.layout` - arranges topoplots by rows when equals `:time`.\\ -- `visual.colorrange::2-element Vector{Int64}`, `colorbar.colorrange::2-element Vector{Int64}`\\ - First is resposnible for colorrange in topoplots, second - in colorbars. Ideally they should be the same. +- `visual.colorrange::2-element Vector{Int64}`\\ + Resposnible for colorrange in topoplots and in colorbar. + +Code description: +- $(_docstring(:topoplotseries)) **Return Value:** `Figure` displaying the Topoplot series. @@ -77,13 +80,14 @@ function plot_topoplotseries!( config = PlotConfig(:topoplotseries) # overwrite all defaults by user specified values config_kwargs!(config; kwargs...) + # resolve columns with data config.mapping = resolve_mappings(to_value(data), config.mapping) data_copy = deepcopy(to_value(data)) # deepcopy prevents overwriting initial data cat_or_cont_columns = eltype(data_copy[!, config.mapping.col]) <: Number ? "cont" : "cat" if cat_or_cont_columns == "cat" - # overwrite Time windows [s] default if categorical + # overwrite 'Time windows [s]' default if categorical n_topoplots = number_of_topoplots(data_copy; bin_width, bin_num, bins = 0, config.mapping) ix = @@ -132,22 +136,19 @@ function plot_topoplotseries!( config.visual..., positions, ) - if (config.colorbar.colorrange !== nothing) - config_kwargs!(config) - else - config_kwargs!( - config, - visual = (; colorrange = colorrange), - colorbar = (; colorrange = colorrange), - ) - end - if !config.layout.use_colorbar - config_kwargs!(config, layout = (; use_colorbar = false, show_legend = false)) - end + config_kwargs!( + config, + visual = (; colorrange = colorrange), + colorbar = (; colorrange = colorrange), + ) + ax = Axis( f[1, 1]; (p for p in pairs(config.axis) if p[1] != :xlim_topo && p[1] != :ylim_topo)..., ) + if config.layout.use_colorbar == true + Colorbar(f[1, 2]; colormap = config.visual.colormap, config.colorbar...) + end apply_layout_settings!(config; fig = f, ax = ax) return f end diff --git a/src/plotconfig.jl b/src/plotconfig.jl index 7d895a44f..ac1961aad 100644 --- a/src/plotconfig.jl +++ b/src/plotconfig.jl @@ -28,8 +28,7 @@ function PlotConfig()# defaults (;), # axis (; # layout show_legend = true, - legend_position = :right, - use_colorbar = false, # ideally should be deleted + use_colorbar = true, ), (#maping x = (:time,), @@ -42,47 +41,18 @@ function PlotConfig()# defaults orientation = :vertical, tellwidth = true, tellheight = false, + halign = :right, + valign = :center, ), (;#colorbar vertical = true, tellwidth = true, tellheight = false, + labelrotation = -Ο€ / 2, ), ) end -""" - config_kwargs!(cfg::PlotConfig; kwargs...) -Takes named tuple of `Key => NamedTuple` as kwargs and merges the fields with the defaults. -""" -function config_kwargs!(cfg::PlotConfig; kwargs...) - - is_namedtuple = [isa(t, NamedTuple) for t in values(kwargs)] - @assert( - all(is_namedtuple), - """ Keyword argument specification (kwargs...). Specified config groups must be from `NamedTuple`, but $(keys(kwargs)[.!is_namedtuple]) was not. - - Maybe you forgot the semicolon (;) at the beginning of your specification? Compare these strings: - - plot_example(...; layout = (; use_colorbar = true)) - - plot_example(...; layout = (use_colorbar = true)) - - The first is correct and creates a `NamedTuple` as needed. The second is incorrect and its call is ignored.""" - ) - list = fieldnames(PlotConfig) #[:layout, :visual, :mapping, :legend, :colorbar, :axis] - - keyList = collect(keys(kwargs)) - :extra ∈ keyList ? - @warn( - "Extra is deprecated in 0.4, and extra keyword arguments must be used directly as keyword arguments." - ) : "" - applyTo = keyList[in.(keyList, Ref(list))] - for k ∈ applyTo - setfield!(cfg, k, merge(getfield(cfg, k), kwargs[k])) - end -end - PlotConfig(T::Symbol) = PlotConfig(Val{T}()) @@ -92,7 +62,11 @@ function PlotConfig(T::Val{:circtopos}) config_kwargs!( cfg; layout = (; show_legend = false), - colorbar = (; label = "Voltage [Β΅V]", colormap = Reverse(:RdBu)), + colorbar = (; + labelrotation = -Ο€ / 2, + label = "Voltage [Β΅V]", + colormap = Reverse(:RdBu), + ), mapping = (;), axis = (; label = "" @@ -108,7 +82,6 @@ function PlotConfig(T::Val{:topoplot}) config_kwargs!( cfg; layout = ( - show_legend = true, use_colorbar = true, hidespines = (), hidedecorations = (Dict(:label => false)), @@ -126,7 +99,7 @@ function PlotConfig(T::Val{:topoplot}) positions = (:pos, :positions, :position, nothing), # Point / Array / Tuple labels = (:labels, :label, :sensor, nothing), # String ), - colorbar = (; flipaxis = true, labelrotation = -Ο€ / 2, label = "Voltage [Β΅V]"), + colorbar = (; flipaxis = true, label = "Voltage [Β΅V]"), axis = (; xlabel = "", aspect = DataAspect()), ) return cfg @@ -151,12 +124,7 @@ function PlotConfig(T::Val{:topoplotseries}) yrectzoom = false, ), layout = (; use_colorbar = true), - colorbar = (; - flipaxis = true, - labelrotation = -Ο€ / 2, - label = "Voltage [Β΅V]", - colorrange = nothing, - ), + colorbar = (; flipaxis = true, label = "Voltage [Β΅V]", colorrange = nothing), visual = (; label_text = false, # true doesnt work again colormap = Reverse(:RdBu), @@ -178,7 +146,19 @@ function PlotConfig(T::Val{:designmat}) ylabel = "Trials", xticklabelrotation = round(pi / 8, digits = 2), ), - colorbar = (; flipaxis = true, labelrotation = -Ο€ / 2, label = ""), + colorbar = (; flipaxis = true, label = ""), + ) + return cfg +end + +function PlotConfig(T::Val{:splines}) + cfg = PlotConfig() + config_kwargs!( + cfg; + layout = (;), + axis = (;), + visual = (; colormap = :viridis), + legend = (; title = "Splines", framevisible = false), ) return cfg end @@ -225,19 +205,14 @@ function PlotConfig(T::Val{:erp}) :ticklabels => false, )), ), - legend = (; - tellwidth = false, - halign = :right, - valign = :center, - framevisible = false, - ), + legend = (; framevisible = false), axis = ( xlabel = "Time [s]", ylabel = "Voltage [Β΅V]", yticklabelsize = 14, xtickformat = "{:.1f}", ), - colorbar = (; label = "", flipaxis = true, labelrotation = -Ο€ / 2), + colorbar = (; label = "", flipaxis = true), ) return cfg @@ -255,6 +230,7 @@ function PlotConfig(T::Val{:erpgrid}) ylabel = "Voltage [Β΅V]", xlim = [-0.04, 1], ylim = [-0.04, 1], + fontsize = 12, ), ) return cfg @@ -265,7 +241,7 @@ function PlotConfig(T::Val{:channelimage}) config_kwargs!( cfg; #layout = (; use_colorbar = true), - colorbar = (; label = "Voltage [Β΅V]", labelrotation = -Ο€ / 2), + colorbar = (; label = "Voltage [Β΅V]"), axis = (xlabel = "Time [s]", ylabel = "Channels", yticklabelsize = 14), visual = (; colormap = Reverse("RdBu")), #cork ) @@ -275,8 +251,8 @@ function PlotConfig(T::Val{:erpimage}) cfg = PlotConfig() config_kwargs!( cfg; - layout = (; use_colorbar = true, show_legend = false), - colorbar = (; label = "Voltage [Β΅V]", labelrotation = -Ο€ / 2), + layout = (; use_colorbar = true), + colorbar = (; label = "Voltage [Β΅V]"), axis = (xlabel = "Time [s]", ylabel = "Trials"), visual = (; colormap = Reverse("RdBu")), ) @@ -294,6 +270,7 @@ function PlotConfig(T::Val{:paracoord}) axis = (; xlabel = "Channels", ylabel = "Time", title = ""), legend = (; title = "Conditions", merge = true, framevisible = false), # fontsize = 14), mapping = (; x = :channel), + layout = (; show_legend = true), ) return cfg end @@ -325,3 +302,34 @@ function resolve_mappings(plot_data, mapping_data) # check mapping_data in PlotC end return (; mapping_dict...) end + +""" + config_kwargs!(cfg::PlotConfig; kwargs...) +Takes NamedTuple of `Key => NamedTuple` as kwargs and merges the fields with the defaults. +""" +function config_kwargs!(cfg::PlotConfig; kwargs...) + + is_namedtuple = [isa(t, NamedTuple) for t in values(kwargs)] + @assert( + all(is_namedtuple), + """ Keyword argument specification (kwargs...). Specified config groups must be from `NamedTuple`, but $(keys(kwargs)[.!is_namedtuple]) was not. + + Maybe you forgot the semicolon (;) at the beginning of your specification? Compare these strings: + + plot_example(...; layout = (; use_colorbar = true)) + + plot_example(...; layout = (use_colorbar = true)) + + The first is correct and creates a `NamedTuple` as needed. The second is incorrect and its call is ignored.""" + ) + field_list = fieldnames(PlotConfig) #[:layout, :visual, :mapping, :legend, :colorbar, :axis] + key_list = collect(keys(kwargs)) + :extra ∈ key_list ? + @warn( + "Extra is deprecated in 0.4, and extra keyword arguments must be used directly as keyword arguments." + ) : "" + apply_to = key_list[in.(key_list, Ref(field_list))] + for k ∈ apply_to + setfield!(cfg, k, merge(getfield(cfg, k), kwargs[k])) + end +end diff --git a/src/supportive_defaults.jl b/src/supportive_defaults.jl new file mode 100644 index 000000000..f6427141c --- /dev/null +++ b/src/supportive_defaults.jl @@ -0,0 +1,63 @@ + +""" + supportive_defaults(cfg_symb::Symbol) + +Default configurations for the supporting axis. Similar to PlotConfig, but these configurations are not shared by all plots.\\ +Such supporting axes allow users to flexibly see defaults in docstrings and manipulate them using corresponding axes. + +For developers: to make them updateable in the function, use `update_axis`. +**Return value:** `NamedTuple`. +""" +function supportive_defaults(cfg_symb::Symbol) + # plot_splines + if cfg_symb == :spline_default + return (; + ylabel = "Spline value", + xlabelvisible = false, + xticklabelsvisible = false, + ylabelvisible = true, + ) + elseif cfg_symb == :density_default + return (; xautolimitmargin = (0, 0), ylabel = "Density value") + elseif cfg_symb == :superlabel_default + return (; fontsize = 20, padding = (0, 0, 40, 0)) + # plot_butterfly + elseif cfg_symb == :topo_default + return (; + width = Relative(0.35), + height = Relative(0.35), + halign = 0.05, + valign = 0.95, + aspect = 1, + ) + # plot_erpimage + elseif cfg_symb == :meanplot_default + return (; + height = 100, + xlabel = "Time [s]", + xlabelpadding = 0, + xautolimitmargin = (0, 0), + ) + elseif cfg_symb == :sortplot_default + return (; ylabelvisible = true, yticklabelsvisible = false) + # plot_erpgrid + elseif cfg_symb == :hlines_grid_default + return (; color = :gray, linewidth = 0.5) + elseif cfg_symb == :vlines_grid_default + return (; color = :gray, linewidth = 0.5, ymin = 0.2, ymax = 0.8) + elseif cfg_symb == :lines_grid_default + return (; color = :deepskyblue3) + elseif cfg_symb == :labels_grid_default + return (; color = :gray, fontsize = 12, align = (:left, :top), space = :relative) + end +end + +""" + update_axis(support_axis::NamedTuple; kwargs...) +Update values of `NamedTuple{key = value}`.\\ +Used for supportive axes to make users be able to flexibly change them. +""" +function update_axis(support_axis::NamedTuple; kwargs...) + support_axis = (; support_axis..., kwargs...) + return support_axis +end diff --git a/test/Project.toml b/test/Project.toml index 51872b03f..181e9f34a 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,5 +1,6 @@ [deps] AlgebraOfGraphics = "cbdf2221-f076-402e-a563-3d30da359d67" +BSplineKit = "093aae92-e908-43d7-9660-e50ee39d5a0a" CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" @@ -10,3 +11,7 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TopoPlots = "2bdbdf9c-dbd8-403f-947b-1a4e0dd41a7a" Unfold = "181c99d8-e21b-4ff3-b70b-c233eddec679" UnfoldSim = "ed8ae6d2-84d3-44c6-ab46-0baf21700804" + +[compat] +AlgebraOfGraphics = "0.7, 0.8" +BSplineKit = "0.16, 0.17" diff --git a/test/runtests.jl b/test/runtests.jl index 976f86f0c..ceafcb803 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -54,6 +54,10 @@ end include("test_dm.jl") end +@testset "Spline plots" begin + include("test_splines.jl") +end + @testset "Complex plots" begin include("test_complexplots.jl") end diff --git a/test/test_butterfly.jl b/test/test_butterfly.jl index 8115eafcc..0d8d162ef 100644 --- a/test/test_butterfly.jl +++ b/test/test_butterfly.jl @@ -25,7 +25,7 @@ end plot_butterfly!(f[1, 1], mat; positions = pos) end -@testset "butterfly: witout topolegend" begin +@testset "butterfly: without topolegend" begin plot_butterfly( df; positions = pos, @@ -39,9 +39,8 @@ end plot_butterfly( df; positions = pos, - topomarkersize = 10, - topoheight = 0.4, - topowidth = 0.4, + topomarkersize = 70, + topo_axis = (; height = Relative(0.4), width = Relative(0.4)), ) end @@ -70,8 +69,7 @@ end f[1, 1], df; positions = pos, - topoheight = 0.4, - topowidth = 0.4, + topo_axis = (; height = Relative(0.4), width = Relative(0.4)), layout = (; hidedecorations = (:label => true, :ticks => true, :ticklabels => true) ), diff --git a/test/test_complexplots.jl b/test/test_complexplots.jl index 82ca44a53..4837419bf 100644 --- a/test/test_complexplots.jl +++ b/test/test_complexplots.jl @@ -34,8 +34,7 @@ d_topo; positions = pos, topomarkersize = 10, - topoheight = 0.4, - topowidth = 0.4, + topo_axis = (; height = Relative(0.4), width = Relative(0.4)), ) hlines!(0, color = :gray, linewidth = 1) vlines!(0, color = :gray, linewidth = 1) @@ -110,21 +109,13 @@ end uf_5chan = example_data("UnfoldLinearModelMultiChannel") d_singletrial, _ = UnfoldSim.predef_eeg(; return_epoched = true) - pvals = DataFrame( from = [0.1, 0.15], to = [0.2, 0.5], # if coefname not specified, line should be black coefname = ["(Intercept)", "category: face"], ) - plot_erp!( - f[1, 1], - results, - categorical_color = false, - categorical_group = false, - significance = pvals, - stderror = true, - ) + plot_erp!(f[1, 1], results, significance = pvals, stderror = true) plot_butterfly!(f[1, 2], d_topo, positions = positions) plot_topoplot!(f[2, 1], data[:, 150, 1]; positions = positions) @@ -181,14 +172,7 @@ end # if coefname not specified, line should be black coefname = ["(Intercept)", "category: face"], ) - plot_erp!( - f[2, 1:2], - results, - categorical_color = false, - categorical_group = false, - significance = pvals, - stderror = true, - ) + plot_erp!(f[2, 1:2], results, significance = pvals, stderror = true) plot_designmatrix!(f[2, 3], designmatrix(uf)) @@ -206,10 +190,7 @@ end plot_erp!( f[2, 4:5], res_effects; - categorical_color = false, - categorical_group = true, - mapping = (; y = :yhat, color = :continuous, group = :continuous), - layout = (; show_legend = true), + mapping = (; y = :yhat, color = :continuous, group = :continuous => nonnumeric), ) plot_parallelcoordinates(f[3, 2:3], uf_5chan; mapping = (; color = :coefname)) diff --git a/test/test_erp.jl b/test/test_erp.jl index 5f19b9bf8..b48a0a843 100644 --- a/test/test_erp.jl +++ b/test/test_erp.jl @@ -1,11 +1,16 @@ +using Unfold: stderror using AlgebraOfGraphics: group include("../docs/example_data.jl") m = example_data("UnfoldLinearModel") + results = coeftable(m) res_effects = effects(Dict(:continuous => -5:0.5:5), m) res_effects2 = effects(Dict(:condition => ["car", "face"], :continuous => -5:5), m) dat, positions = TopoPlots.example_data() +m7 = example_data("7channels") +results7 = coeftable(m7) + @testset "ERP plot: DataFrame data" begin plot_erp(results) end @@ -18,19 +23,27 @@ end plot_erp(dat[1, :, 1]) end +@testset "ERP plot: rename xlabel" begin + plot_erp(results; axis = (; xlabel = "test")) +end + +@testset "ERP plot: xlabelvisible" begin + plot_erp(results; axis = (; xlabelvisible = false, xticklabelsvisible = false)) +end + @testset "ERP plot: Array data with times vector" begin times = range(0, step = 100, length = size(dat, 2)) plot_erp(times, dat[1, :, 1], axis = (; xtickformat = "{:d}")) end @testset "ERP plot: stderror error" begin - plot_erp(results; :stderror => true) + plot_erp(results; stderror = true) end @testset "ERP plot: standart errors in GridLayout" begin f = Figure(size = (1200, 1400)) ga = f[1, 1] = GridLayout() - plot_erp!(ga, results; :stderror => true) + plot_erp!(ga, results; stderror = true) f end @@ -40,24 +53,31 @@ end plot_erp(results; mapping = (; col = :group)) end +@testset "ERP plot: faceting by two columns with stderror" begin + results = coeftable(m) + results.group = push!(repeat(["A", "B"], inner = 67), "A") + plot_erp(results; mapping = (; col = :group), stderror = true) +end + @testset "ERP plot: with and withour error ribbons" begin results = coeftable(m) - f = Figure() results.coefname = replace(results.coefname, "condition: face" => "face", "(Intercept)" => "car") results = filter(row -> row.coefname != "continuous", results) + + f = Figure() plot_erp!( f[1, 1], results; - axis = (title = "Bad example", titlegap = 12), - :stderror => false, + axis = (; title = "Bad example", titlegap = 12), + stderror = false, mapping = (; color = :coefname => "Conditions"), ) plot_erp!( f[2, 1], results; axis = (title = "Good example", titlegap = 12), - :stderror => true, + stderror = true, mapping = (; color = :coefname => "Conditions"), ) @@ -81,50 +101,23 @@ end # if coefname not specified, line should be black coefname = ["(Intercept)", "category: face"], ) - plot_erp!( - ga, - results; - categorical_color = false, - categorical_group = false, - significance = pvals, - stderror = true, - ) + plot_erp!(ga, results; significance = pvals, stderror = true) f end @testset "ERP plot with significance" begin pvals = DataFrame( - from = [0.1, 0.3], - to = [0.5, 0.7], + from = [0.01, 0.2], + to = [0.3, 0.4], coefname = ["(Intercept)", "condition: face"], # if coefname not specified, line should be black ) plot_erp(results; :significance => pvals) end @testset "ERP plot: 7 channels faceted" begin - m7 = example_data("7channels") - results7 = coeftable(m7) plot_erp(results7, mapping = (; col = :channel, group = :channel)) end - -@testset "ERP plot: with colorbar and legend" begin - plot_erp( - res_effects2; - mapping = (; color = :continuous, linestyle = :condition, group = :continuous), - categorical_color = false, - ) -end - -#= @testset "ERP plot: with colorbar and legend 2" begin - plot_erp( - res_effects2; - mapping = (; color = :continuous, group = :continuous), - categorical_color = false, - categorical_group = false, - ) -end =# - @testset "ERP plot: rename legend" begin f = Figure() results = coeftable(m) @@ -132,11 +125,50 @@ end =# replace(results.coefname, "condition: face" => "face", "(Intercept)" => "car") results = filter(row -> row.coefname != "continuous", results) plot_erp!( - f[1, 1], + f, results; axis = (title = "Bad example", titlegap = 12), - :stderror => false, mapping = (; color = :coefname => "Conditions"), ) f end + +@testset "ERP plot: Facet sorting" begin + data, evts = UnfoldSim.predef_eeg() + + m = fit( + UnfoldModel, + [ + "car" => (@formula(0 ~ 1 + continuous), firbasis((-0.1, 1), 100)), + "face" => (@formula(0 ~ 1 + continuous), firbasis((-0.1, 1), 100)), + ], + evts, + data; + eventcolumn = :condition, + ) + eff = effects(Dict(:continuous => 75:20:300), m) + + sorting1 = ["face", "car"] # check + sorting2 = ["car", "face"] + + f = Figure() + plot_erp!( + f[1, 1], + eff; + mapping = (; + col = :eventname => sorter(sorting1), + color = :continuous, + group = :continuous, + ), + ) + plot_erp!( + f[2, 1], + eff; + mapping = (; + col = :eventname => sorter(sorting2), + color = :continuous, + group = :continuous, + ), + ) + f +end diff --git a/test/test_erp_effects.jl b/test/test_erp_effects.jl index 59eb817eb..dc8b2e17d 100644 --- a/test/test_erp_effects.jl +++ b/test/test_erp_effects.jl @@ -5,62 +5,48 @@ m = example_data("UnfoldLinearModel") res_effects = effects(Dict(:continuous => -5:0.5:5), m) res_effects2 = effects(Dict(:condition => ["car", "face"], :continuous => -5:5), m) -@testset "Effect plot" begin #where is legend here?? - plot_erp( - res_effects; - mapping = (; y = :yhat, color = :continuous, group = :continuous), - legend = (; nbanks = 2), - categorical_color = false, - categorical_group = true, - ) +@testset "Effect plot" begin + plot_erp(res_effects; mapping = (; y = :yhat, color = :continuous, group = :continuous)) end -@testset "Effect plot: faceted" begin #bug +@testset "Effect plot: faceted" begin res_effects = effects(Dict(:continuous => -5:0.5:5), m) res_effects.channel = push!(repeat(["1", "2"], 472), "1") - plot_erp(res_effects; mapping = (; y = :yhat, color = :continuous, col = :channel)) + plot_erp( + res_effects; + mapping = (; y = :yhat, color = :continuous => nonnumeric, col = :channel), + legend = (; nbanks = 2), + ) end @testset "Effect plot: faceted channels" begin #bug res_effects = effects(Dict(:continuous => -5:0.5:5), m) res_effects.channel = push!(repeat(["1", "2"], 472), "1") - #res_effects.channel = float.(push!(repeat([1, 2], 472), 1)) - plot_erp(res_effects; mapping = (; y = :yhat, group = :channel, col = :channel)) -end - -@testset "Effect plot: no colorbar and legend" begin plot_erp( - res_effects2; - mapping = (; color = :continuous, linestyle = :condition, group = :continuous), - layout = (; show_legend = true, use_legend = false, use_colorbar = false), - categorical_color = false, + res_effects; + mapping = (; + y = :yhat, + color = :continuous => nonnumeric, + group = :channel, + col = :channel => nonnumeric, + ), + legend = (; nbanks = 2), ) end -@testset "Effect plot: no colorbar" begin +@testset "Effect plot: no colorbar and yes legend" begin plot_erp( res_effects2; mapping = (; color = :continuous, linestyle = :condition, group = :continuous), layout = (; use_legend = true, use_colorbar = false), - categorical_color = false, ) end -@testset "Effect plot: no legend" begin +@testset "Effect plot: yes colorbar and no legend" begin plot_erp( res_effects2; mapping = (; color = :continuous, linestyle = :condition, group = :continuous), layout = (; use_legend = false, use_colorbar = true), - categorical_color = false, - ) -end - -@testset "Effect plot: no colorbar and no legend" begin - plot_erp( - res_effects2; - mapping = (; color = :continuous, linestyle = :condition, group = :continuous), - layout = (; use_legend = false, use_colorbar = false), - categorical_color = false, ) end @@ -69,26 +55,22 @@ end res_effects2; mapping = (; color = :continuous, linestyle = :condition, group = :continuous), layout = (; use_legend = true, use_colorbar = true), - categorical_color = false, ) end - -@testset "Effect plot: should be no gap instead of legend" begin +@testset "Effect plot: no colorbar and no legend" begin plot_erp( res_effects2; - mapping = (; color = :continuous, group = :continuous), - categorical_color = false, + mapping = (; color = :continuous, linestyle = :condition, group = :continuous), + layout = (; use_legend = false, use_colorbar = false), ) end - @testset "Effect plot: move legend" begin plot_erp( res_effects2; mapping = (; color = :continuous, linestyle = :condition, group = :continuous), - legend = (; valign = :bottom, halign = :right, tellwidth = false), - categorical_color = false, + legend = (; valign = :bottom, halign = :right), axis = ( title = "Marginal effects", titlegap = 12, @@ -104,14 +86,18 @@ end @testset "Effect plot: xlabelvisible is not working" begin eff_same = effects(Dict(:condition => ["car", "face"], :duration => 200), m) + + plot_erp(results; axis = (; xlabelvisible = false, xticklabelsvisible = false)) plot_erp( - res_effects2; - mapping = (; col = :eventname),#, color = :condition), why it doesn't work??? + eff_same; + mapping = (; col = :condition, color = :time), axis = (; + xlabel = "test", titlevisible = false, xlabelvisible = false, ylabelvisible = false, yticklabelsvisible = false, + xticklabelsvisible = false, ), ) end diff --git a/test/test_erpgrid.jl b/test/test_erpgrid.jl index a7caf4fbd..d574a361e 100644 --- a/test/test_erpgrid.jl +++ b/test/test_erpgrid.jl @@ -25,6 +25,35 @@ end plot_erpgrid(data[1:6, :], pos[1:6], raw_ch_names[1:6]; drawlabels = true) end +@testset "erpgrid: customizable labels" begin + plot_erpgrid( + data[1:6, :], + pos[1:6], + raw_ch_names[1:6]; + drawlabels = true, + labels_grid_axis = (; color = :red), + ) +end + +@testset "erpgrid: customizable vlines and hlines" begin + plot_erpgrid( + data[1:6, :], + pos[1:6], + raw_ch_names[1:6]; + hlines_grid_axis = (; color = :red), + vlines_grid_axis = (; color = :green), + ) +end + +@testset "erpgrid: customizable lines" begin + plot_erpgrid( + data[1:6, :], + pos[1:6], + raw_ch_names[1:6]; + lines_grid_axis = (; color = :red), + ) +end + @testset "erpgrid: GridPosition" begin f = Figure() plot_erpgrid!(f[1, 1], data, pos) diff --git a/test/test_pcp.jl b/test/test_pcp.jl index 84a3063b8..bdf13257e 100644 --- a/test/test_pcp.jl +++ b/test/test_pcp.jl @@ -1,5 +1,6 @@ include("../docs/example_data.jl") # we need more specified example data results_plot, positions = example_data() + @testset "PCP: data input DataFrame" begin plot_parallelcoordinates(results_plot) end @@ -15,6 +16,14 @@ end plot_parallelcoordinates(results_plot; mapping = (color = :coefname, y = :estimate)) end +@testset "PCP: show_legend = false" begin + plot_parallelcoordinates( + results_plot; + mapping = (color = :coefname, y = :estimate), + layout = (; show_legend = false), + ) +end + @testset "PCP: Figure, 64 channels, 1 condition, bigger size" begin plot_parallelcoordinates( Figure(size = (1200, 800)), @@ -71,7 +80,7 @@ end plot_parallelcoordinates( uf_5chan; mapping = (; color = :coefname), - layout = (; legend_position = :right), + legend = (; valign = :center), ) end @@ -170,7 +179,6 @@ end f[1, 1], uf_5chan; mapping = (; color = :coefname), - layout = (; legend_position = :right), visual = (; alpha = 0.1), axis = (; title = "alpha = 0.1"), ) @@ -178,7 +186,6 @@ end f[2, 1], uf_5chan, mapping = (; color = :coefname), - layout = (; legend_position = :right), visual = (; alpha = 0.9), axis = (; title = "alpha = 0.9"), ) @@ -202,22 +209,3 @@ end ) f end - -begin - f = Figure() - plot_parallelcoordinates( - f[1, 1], - subset(results_plot, :channel => x -> x .< 10); - mapping = (; color = :coefname), - axis = (; title = "normalize = nothing"), - ) - plot_parallelcoordinates( - f[2, 1], - subset(results_plot, :channel => x -> x .< 10); - mapping = (; color = :coefname), - normalize = :minmax, - axis = (; title = "normalize = :minmax"), - ) - f - -end diff --git a/test/test_splines.jl b/test/test_splines.jl new file mode 100644 index 000000000..e6e7c6a85 --- /dev/null +++ b/test/test_splines.jl @@ -0,0 +1,36 @@ +using BSplineKit, Unfold +m0 = example_data("UnfoldLinearModel") +m1 = example_data("UnfoldLinearModelwith1Spline") +m2 = example_data("UnfoldLinearModelwith2Splines") + +@testset "Spline plot: no splines" begin + err1 = nothing + t() = error(plot_splines(m0)) + try + t() + catch err1 + end + @test err1 == AssertionError( + "No spline term is found in UnfoldModel. Does your UnfoldModel really have a `spl(...)` or other `AbstractSplineTerm`?", + ) +end + +@testset "Spline plot: basic" begin + plot_splines(m1) +end + +@testset "Spline plot: two spline terms" begin + plot_splines(m2) +end + +@testset "Spline plot: spline_axis check" begin + plot_splines(m2; spline_axis = (; ylabel = "test")) +end + +@testset "Spline plot: density_axis check" begin + plot_splines(m2, density_axis = (; ylabel = "test")) +end + +@testset "Spline plot: superlabel_axis check" begin + plot_splines(m2; superlabel_config = (; fontsize = 60)) +end diff --git a/test/test_topoplot.jl b/test/test_topoplot.jl index 76508b89c..f9518fcb3 100644 --- a/test/test_topoplot.jl +++ b/test/test_topoplot.jl @@ -25,8 +25,16 @@ end plot_topoplot(d1; positions = rand(Point2f, 10)) end +@testset "topoplot: highliht an electrode" begin + plot_topoplot(dat[:, 50, 1]; positions, high_chan = 2) +end + +@testset "topoplot: highliht several electrodes" begin + plot_topoplot(dat[:, 50, 1]; positions, high_chan = [1, 2]) +end + @testset "topoplot: no legend" begin - plot_topoplot(dat[:, 50, 1]; positions = positions, layout = (; show_legend = false)) + plot_topoplot(dat[:, 50, 1]; positions = positions, layout = (; use_colorbar = false)) end @testset "topoplot: xlabel" begin diff --git a/test/test_toposeries1.jl b/test/test_toposeries1.jl index 775ee60d2..ead15dc40 100644 --- a/test/test_toposeries1.jl +++ b/test/test_toposeries1.jl @@ -12,6 +12,10 @@ end plot_topoplotseries(df; bin_num = 5, positions = positions) end +@testset "toposeries: bin_num" begin + plot_topoplotseries(df; bin_num = 5, positions = positions, axis = (; xlabel = "test")) +end + @testset "toposeries: checking other y value" begin df.cont = df.time .* 3 plot_topoplotseries(df; bin_num = 5, positions = positions, mapping = (; col = :cont)) @@ -145,7 +149,7 @@ end df; bin_width, positions = positions, - colorbar = (; colorrange = (-3, 3)), + visual = (; colorrange = (-3, 3)), ) end diff --git a/test/test_toposeries2.jl b/test/test_toposeries2.jl index f15d5597f..e6b84d4d2 100644 --- a/test/test_toposeries2.jl +++ b/test/test_toposeries2.jl @@ -29,7 +29,6 @@ end ) end - @testset "error checking: bin_width and bin_num specified" begin err1 = nothing t() = error(plot_topoplotseries(df; bin_width = 80, bin_num = 5, positions = positions)) @@ -99,17 +98,19 @@ end mapping = (; col = :condition), ) end - -#= @testset "4 condtions in rows" begin # TBD +#= +@testset "4 condtions in rows" begin # TBD df = UnfoldMakie.eeg_array_to_dataframe(dat[:, 1:4, 1], string.(1:length(positions))) df.condition = repeat(["A", "B", "C", "D"], size(df, 1) Γ· 4) plot_topoplotseries( df; + bin_num = 3, positions = positions, mapping = (; row = :condition), ) -end =# +end +=# @testset "topoplot axes configuration" begin # TBD df = UnfoldMakie.eeg_array_to_dataframe(dat[:, 1:4, 1], string.(1:length(positions))) @@ -129,7 +130,6 @@ end =# ) end - @testset "change xlabel" begin df = UnfoldMakie.eeg_array_to_dataframe(dat[:, 1:2, 1], string.(1:length(positions))) df.condition = repeat(["A", "B"], size(df, 1) Γ· 2) @@ -177,3 +177,10 @@ end interactive_scatter = obs_tuple, ) end +#= +@testset "interactive data in eeg_array_to_dataframe" begin + data_obs3 = Observable(UnfoldMakie.eeg_array_to_dataframe(rand(10, 20))) + plot_topoplotseries!(Figure(), data_obs3; bin_num = 5, positions = rand(Point2f, 10)) + data_obs3[] = UnfoldMakie.eeg_array_to_dataframe(rand(10, 20)) +end +=#