From 2547f921e2cd602776458015489be642d32bdba4 Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Tue, 21 May 2024 11:19:20 +0000 Subject: [PATCH 01/34] issue 49 done --- src/plot_circular_topoplots.jl | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/src/plot_circular_topoplots.jl b/src/plot_circular_topoplots.jl index e0690a61..edc8e5e5 100644 --- a/src/plot_circular_topoplots.jl +++ b/src/plot_circular_topoplots.jl @@ -89,7 +89,7 @@ function plot_circular_topoplots!( height = @lift Fixed($(pixelarea(ax.scene)).widths[2]) ) plot_topo_plots!( - ax, + f[1,1], data[:, config.mapping.y], positions, predictor_values, @@ -191,26 +191,15 @@ function plot_topo_plots!( for g in gp i += 1 bbox = calculate_BBox([0, 0], [1, 1], g.p[1], predictor_bounds) - - # convert BBox to rect - rect = ( - Float64.([ - bbox.origin[1], - bbox.origin[1] + bbox.widths[1], - bbox.origin[2], - bbox.origin[2] + bbox.widths[2], - ])..., - ) - - b = rel_to_abs_bbox(f.scene.viewport[], rect) + eeg_axis = Axis( - get_figure(f); + f,#get_figure(f)[1,1]; # this creates an axis at the same grid-location of the current axis aspect = 1, - width = Relative(0.99), - height = Relative(0.99), - halign = b.origin[1], - valign = b.origin[2], - backgroundcolor = :white, + width = Relative(0.2), # size of bboxes + height = Relative(0.2), + halign = bbox.origin[1]+ bbox.widths[1]/2, # coordinates + valign = bbox.origin[2]+ bbox.widths[2]/2, + #backgroundcolor = nothing, ) if !isnothing(labels) @@ -233,7 +222,7 @@ function calculate_BBox(origin, widths, predictor_value, bounds) minwidth = minimum(widths) predictor_ratio = (predictor_value - bounds[1]) / (bounds[2] - bounds[1]) - radius = (minwidth * 0.7) / 2 + radius = (minwidth * 0.8) / 2 # radius of the circ-topoplot position circle size_of_BBox = minwidth / 5 # the middle point of the circle for the topoplot positions # has to be moved a bit into the direction of the longer axis From ccf48fbd85628c98a3db130a721e1e7e71d6c5bc Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Tue, 21 May 2024 11:20:17 +0000 Subject: [PATCH 02/34] implementation of line break in faceting --- src/eeg_series.jl | 3 +++ src/plot_topoplotseries.jl | 33 +++++++++++++++++++++++++++++++++ test/test_toposeries.jl | 20 +++++++++++++++++--- 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/eeg_series.jl b/src/eeg_series.jl index 0350ce51..97ae0197 100644 --- a/src/eeg_series.jl +++ b/src/eeg_series.jl @@ -215,6 +215,9 @@ function eeg_topoplot_series!( end + end + if isempty(to_value(d_vec)) + continue end h_topo = eeg_topoplot!(ax, d_vec, labels; topoplot_attributes...) @debug typeof(h_topo) typeof(ax) diff --git a/src/plot_topoplotseries.jl b/src/plot_topoplotseries.jl index d1c8342c..a55e932b 100644 --- a/src/plot_topoplotseries.jl +++ b/src/plot_topoplotseries.jl @@ -67,6 +67,8 @@ function plot_topoplotseries!( eltype(to_value(data)[!, config.mapping.col]) <: Number ? "cont" : "cat" if cat_or_cont_columns == "cat" # overwrite Time windows [s] default if categorical + + config_kwargs!(config; axis = (; xlabel = string(config.mapping.col))) config_kwargs!(config; kwargs...) # add the user specified once more, just if someone specifies the xlabel manually # overkll as we would only need to check the xlabel ;) @@ -75,7 +77,38 @@ function plot_topoplotseries!( positions = get_topo_positions(; positions = positions, labels = labels) chan_or_label = "label" ∉ names(to_value(data)) ? :channel : :label + @debug "hellooooo" keys(config.mapping) + if :layout ∈ keys(config.mapping) + @debug "hello layout!!" + data = deepcopy(to_value(data)) + + un_layout = unique(data[:,config.mapping.layout]) + ix = findall.(isequal.(un_layout), [data[:,config.mapping.layout]]) +@debug ix[1][1:5] size(ix) size(ix[1]) + n_topoplots = length(un_layout) + + + + n_cols = Int(ceil(sqrt(n_topoplots))) + n_rows = Int(ceil(n_topoplots/n_cols)) + + _col = repeat(1:n_cols,outer=n_rows)[1:n_topoplots] + _row = repeat(1:n_rows,inner=n_cols)[1:n_topoplots] + data._col .= 0 + data._row .= 0 + for topo = 1:n_topoplots + data._col[ix[topo]] .= _col[topo] + data._row[ix[topo]] .= _row[topo] + end + #return data + config_kwargs!(config; mapping = (; row = :_row,col=:_col)) + + + end + + + ftopo, axlist = eeg_topoplot_series!( f, data, diff --git a/test/test_toposeries.jl b/test/test_toposeries.jl index 4bc06270..1e142c6d 100644 --- a/test/test_toposeries.jl +++ b/test/test_toposeries.jl @@ -130,6 +130,23 @@ end f end +@testset "facetting by layout" begin + df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, 200:1:240, 1], string.(1:length(positions))) + + f = Figure(size = (600, 500)) + plot_topoplotseries!( + f[1:2, 1:2], + df, + Δbin; + col_labels = true, + mapping = (; layout = :time), + positions = positions, + visual = (label_scatter = false,), + layout = (; use_colorbar = true), + ) + f +end + @testset "toposeries with xlabel" begin plot_topoplotseries(df, Δbin; positions = positions, axis = (; xlabel = "test")) end @@ -229,11 +246,8 @@ end layout = (; use_colorbar = true), interactive_scatter = obs_tuple, ) - - end - #= using CSV tmp = CSV.read("dev/UnfoldMakie/test/output2.csv", DataFrame) From 2bd7dd556fee40054ae837d6f1641d1a67654003 Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Tue, 21 May 2024 11:20:54 +0000 Subject: [PATCH 03/34] highlights in docs --- docs/src/index.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index 4a9615c6..9def7216 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -4,17 +4,19 @@ ``` -This is the documentation of the UnfoldMakie.jl module (aka library) for the Julia programming language. +This is the documentation of the UnfoldMakie.jl package for the Julia programming language. -## Benefits of UnfoldMakie.jl +## Highlights of UnfoldMakie.jl - **10 plot functions for displaying ERPs.** Each plot emphasizes certain dimensions while collapsing others. +- **Speed** +Plot one figure with 20 topoplots in 1 second? No problemo! - **Highly adaptable.** -The module is based on the [Unfold](https://github.com/unfoldtoolbox/unfold.jl/) and [Makie](https://makie.juliaplots.org/stable/) modules, so you can use configurations from these modules to add new features to your figures. +The package is primarily based on [Unfold.jl](https://github.com/unfoldtoolbox/unfold.jl/) and [Makie.jl](https://makie.juliaplots.org/stable/). - **Many usage examples** -Here in documentation you can find user-friendly examples of how to use plots. +Here in the documentation you can find many user-friendly examples of how to use and adapt the plots. - **Scientific colormaps as default** -According to our study (Mikheev, 2024), 40% of EEG researchers do not know about the issue of scientific color maps. To protect the scientific integrity, we used `Reverse(:RdBu)` and `Roma` as default color maps. +According to our study (Mikheev, 2024), 40% of EEG researchers do not know about the issue of scientific color maps. By default, we use `Reverse(:RdBu)` (based on colorbrewer) and `Roma` (based on Sceintific Colormaps by Fabiano Cramerie) as default color maps. - **Interactivity** -Several plots use Observables and have interactive mode so you can click on them and change their layout. Check `plot_topoplotseries` and `plot_erpimage`. \ No newline at end of file +Several plots make use of `Observables.jl` which allows for rapid updating of the underlying data. Several plots already have predfined interactive features, allowing you to click on e.g. labels to activate / deactivate them. As examples check out `plot_topoplotseries` and `plot_erpimage`. From ad725afafed8ff0b33cbda2248b2e6f0100169a1 Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Mon, 27 May 2024 15:05:01 +0000 Subject: [PATCH 04/34] format --- src/eeg_series.jl | 2 -- src/plot_circular_topoplots.jl | 16 +++++++--------- src/plot_topoplotseries.jl | 25 ++++++++----------------- 3 files changed, 15 insertions(+), 28 deletions(-) diff --git a/src/eeg_series.jl b/src/eeg_series.jl index 97ae0197..51fea2f2 100644 --- a/src/eeg_series.jl +++ b/src/eeg_series.jl @@ -163,7 +163,6 @@ function eeg_topoplot_series!( ) # do the col/row plot - select_col = isnothing(col) ? 1 : unique(to_value(data_mean)[:, col]) select_row = isnothing(row) ? 1 : unique(to_value(data_mean)[:, row]) @@ -197,7 +196,6 @@ function eeg_topoplot_series!( # pos = @lift topoplot_attributes[:positions][highlight_scatter] strokecolor = Observable(repeat([:black], length(to_value(d_vec)))) - highlight_feature = (; strokecolor = strokecolor) if :label_scatter ∈ keys(topoplot_attributes) topoplot_attributes = merge( diff --git a/src/plot_circular_topoplots.jl b/src/plot_circular_topoplots.jl index edc8e5e5..80a709bd 100644 --- a/src/plot_circular_topoplots.jl +++ b/src/plot_circular_topoplots.jl @@ -25,8 +25,6 @@ Plot a circular EEG topoplot. $(_docstring(:circtopos)) - - **Return Value:** `Figure` displaying the Circular topoplot series. """ @@ -89,7 +87,7 @@ function plot_circular_topoplots!( height = @lift Fixed($(pixelarea(ax.scene)).widths[2]) ) plot_topo_plots!( - f[1,1], + f[1, 1], data[:, config.mapping.y], positions, predictor_values, @@ -191,14 +189,14 @@ function plot_topo_plots!( for g in gp i += 1 bbox = calculate_BBox([0, 0], [1, 1], g.p[1], predictor_bounds) - + eeg_axis = Axis( - f,#get_figure(f)[1,1]; # this creates an axis at the same grid-location of the current axis + f, # this creates an axis at the same grid location of the current axis aspect = 1, width = Relative(0.2), # size of bboxes - height = Relative(0.2), - halign = bbox.origin[1]+ bbox.widths[1]/2, # coordinates - valign = bbox.origin[2]+ bbox.widths[2]/2, + height = Relative(0.2), # size of bboxes + halign = bbox.origin[1] + bbox.widths[1] / 2, # coordinates + valign = bbox.origin[2] + bbox.widths[2] / 2, #backgroundcolor = nothing, ) @@ -222,7 +220,7 @@ function calculate_BBox(origin, widths, predictor_value, bounds) minwidth = minimum(widths) predictor_ratio = (predictor_value - bounds[1]) / (bounds[2] - bounds[1]) - radius = (minwidth * 0.8) / 2 # radius of the circ-topoplot position circle + radius = (minwidth * 0.8) / 2 # radius of the position circle of a circular topoplot size_of_BBox = minwidth / 5 # the middle point of the circle for the topoplot positions # has to be moved a bit into the direction of the longer axis diff --git a/src/plot_topoplotseries.jl b/src/plot_topoplotseries.jl index a55e932b..d6b9caec 100644 --- a/src/plot_topoplotseries.jl +++ b/src/plot_topoplotseries.jl @@ -67,8 +67,6 @@ function plot_topoplotseries!( eltype(to_value(data)[!, config.mapping.col]) <: Number ? "cont" : "cat" if cat_or_cont_columns == "cat" # overwrite Time windows [s] default if categorical - - config_kwargs!(config; axis = (; xlabel = string(config.mapping.col))) config_kwargs!(config; kwargs...) # add the user specified once more, just if someone specifies the xlabel manually # overkll as we would only need to check the xlabel ;) @@ -82,18 +80,16 @@ function plot_topoplotseries!( @debug "hello layout!!" data = deepcopy(to_value(data)) - un_layout = unique(data[:,config.mapping.layout]) - ix = findall.(isequal.(un_layout), [data[:,config.mapping.layout]]) -@debug ix[1][1:5] size(ix) size(ix[1]) + un_layout = unique(data[:, config.mapping.layout]) + ix = findall.(isequal.(un_layout), [data[:, config.mapping.layout]]) + @debug ix[1][1:5] size(ix) size(ix[1]) n_topoplots = length(un_layout) - - n_cols = Int(ceil(sqrt(n_topoplots))) - n_rows = Int(ceil(n_topoplots/n_cols)) - - _col = repeat(1:n_cols,outer=n_rows)[1:n_topoplots] - _row = repeat(1:n_rows,inner=n_cols)[1:n_topoplots] + n_rows = Int(ceil(n_topoplots / n_cols)) + + _col = repeat(1:n_cols, outer = n_rows)[1:n_topoplots] + _row = repeat(1:n_rows, inner = n_cols)[1:n_topoplots] data._col .= 0 data._row .= 0 for topo = 1:n_topoplots @@ -101,14 +97,9 @@ function plot_topoplotseries!( data._row[ix[topo]] .= _row[topo] end #return data - config_kwargs!(config; mapping = (; row = :_row,col=:_col)) - - + config_kwargs!(config; mapping = (; row = :_row, col = :_col)) end - - - ftopo, axlist = eeg_topoplot_series!( f, data, From 13db522736f9d480fd79cd5e4a3e60b1f68b43ea Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Mon, 27 May 2024 15:22:26 +0000 Subject: [PATCH 05/34] format 2 --- test/test_toposeries.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/test_toposeries.jl b/test/test_toposeries.jl index 1e142c6d..fb961fd0 100644 --- a/test/test_toposeries.jl +++ b/test/test_toposeries.jl @@ -131,7 +131,10 @@ end end @testset "facetting by layout" begin - df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, 200:1:240, 1], string.(1:length(positions))) + df = UnfoldMakie.eeg_matrix_to_dataframe( + dat[:, 200:1:240, 1], + string.(1:length(positions)), + ) f = Figure(size = (600, 500)) plot_topoplotseries!( @@ -187,7 +190,6 @@ end ) end - @testset "observables" begin f = Figure() df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) From bb02a35a4f57b1c7a4d3789f01584c9b07588f20 Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Mon, 27 May 2024 15:52:03 +0000 Subject: [PATCH 06/34] bbox fitting --- src/plot_circular_topoplots.jl | 13 ++++++++----- test/test_circular_topoplots.jl | 12 ++++++------ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/plot_circular_topoplots.jl b/src/plot_circular_topoplots.jl index 80a709bd..8d5a959c 100644 --- a/src/plot_circular_topoplots.jl +++ b/src/plot_circular_topoplots.jl @@ -20,6 +20,8 @@ Plot a circular EEG topoplot. Positions of the [`plot_topoplot`](@ref topo_vis). - `center_label::String = ""`\\ The text in the center of the cricle. +- `plot_radius::String = 0.8`\\ + The radius of the circular topoplot series plot calucalted by formula: `radius = (minwidth * plot_radius) / 2`. - `labels::Vector{String} = nothing`\\ Labels for the [`plot_topoplots`](@ref topo_vis). @@ -40,6 +42,7 @@ function plot_circular_topoplots!( positions = nothing, labels = nothing, center_label = "", + plot_radius = 0.8, kwargs..., ) config = PlotConfig(:circtopos) @@ -95,6 +98,7 @@ function plot_circular_topoplots!( min, max, labels, + plot_radius, ) apply_layout_settings!(config; ax = ax) @@ -117,7 +121,6 @@ end function plot_circular_axis!(ax, predictor_bounds, center_label) # The axis position is always the middle of the screen # It uses the GridLayout's full size - lines!( ax, 1 * cos.(LinRange(0, 2 * pi, 500)), @@ -182,14 +185,14 @@ function plot_topo_plots!( globalmin, globalmax, labels, + plot_radius, ) df = DataFrame(:e => data, :p => predictor_values) gp = groupby(df, :p) i = 0 for g in gp i += 1 - bbox = calculate_BBox([0, 0], [1, 1], g.p[1], predictor_bounds) - + bbox = calculate_BBox([0, 0], [1, 1], g.p[1], predictor_bounds, plot_radius) eeg_axis = Axis( f, # this creates an axis at the same grid location of the current axis aspect = 1, @@ -216,11 +219,11 @@ function plot_topo_plots!( end end -function calculate_BBox(origin, widths, predictor_value, bounds) +function calculate_BBox(origin, widths, predictor_value, bounds, plot_radius) minwidth = minimum(widths) predictor_ratio = (predictor_value - bounds[1]) / (bounds[2] - bounds[1]) - radius = (minwidth * 0.8) / 2 # radius of the position circle of a circular topoplot + radius = (minwidth * plot_radius) / 2 # radius of the position circle of a circular topoplot size_of_BBox = minwidth / 5 # the middle point of the circle for the topoplot positions # has to be moved a bit into the direction of the longer axis diff --git a/test/test_circular_topoplots.jl b/test/test_circular_topoplots.jl index 8856a3b2..f364d247 100644 --- a/test/test_circular_topoplots.jl +++ b/test/test_circular_topoplots.jl @@ -56,12 +56,12 @@ end end @testset "testing calculate_BBox" begin - @test UnfoldMakie.calculate_BBox([0, 0], [1000, 1000], 180, [0, 360]) == - BBox(50.0, 250.0, 400.0, 600.0) - @test UnfoldMakie.calculate_BBox([0, 0], [1000, 1000], -45, [0, 360]) == - BBox(647.48737, 847.48737, 152.51262, 352.51262) - @test UnfoldMakie.calculate_BBox([0, 0], [1000, 1000], -180, [-180, 180]) == - BBox(750.0, 950.0, 400.0, 600.0) + @test UnfoldMakie.calculate_BBox([0, 0], [1000, 1000], 180, [0, 360], 0.8) == + BBox(0.0, 200.0, 400.0, 600.0) + @test UnfoldMakie.calculate_BBox([0, 0], [1000, 1000], -45, [0, 360], 0.8) == + BBox(682.842712474619, 882.842712474619, 117.15728752538104, 317.15728752538104) + @test UnfoldMakie.calculate_BBox([0, 0], [1000, 1000], -180, [-180, 180], 0.8) == + BBox(800.0, 1000.0, 400.0, 600.0) end @testset "circularplot plot basic" begin From 742f0c1e611b351160591fc2090544d8cc42dff5 Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Mon, 27 May 2024 16:10:26 +0000 Subject: [PATCH 07/34] error test for erpimage --- test/test_erpimage.jl | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/test/test_erpimage.jl b/test/test_erpimage.jl index 81008003..a6b00435 100644 --- a/test/test_erpimage.jl +++ b/test/test_erpimage.jl @@ -125,12 +125,19 @@ end sortval = Observable(evts_e.continuous) end +@testset "check error of empty sortvalues" begin + err1 = nothing + t() = error(plot_erpimage( + times, + dat_e; + show_sortval = true, + )) + try + t() + catch err1 + end + + @test err1 == ErrorException("`show_sortval` needs non-empty `sortvalues` argument") +end -#= @testset "ERP image with show_sortval" begin - plot_erpimage( - times, - dat_e; - show_sortval = true, - ) #should learn that it must be Error -=# From 871f426af2ed85a3600ce2199692b5cdd6d7ed2c Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Mon, 27 May 2024 16:16:52 +0000 Subject: [PATCH 08/34] small changes --- docs/src/index.md | 4 ++-- test/test_erpimage.jl | 8 +------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index 9def7216..6dbac1fc 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -16,7 +16,7 @@ Plot one figure with 20 topoplots in 1 second? No problemo! The package is primarily based on [Unfold.jl](https://github.com/unfoldtoolbox/unfold.jl/) and [Makie.jl](https://makie.juliaplots.org/stable/). - **Many usage examples** Here in the documentation you can find many user-friendly examples of how to use and adapt the plots. -- **Scientific colormaps as default** +- **Scientific colormaps by default** According to our study (Mikheev, 2024), 40% of EEG researchers do not know about the issue of scientific color maps. By default, we use `Reverse(:RdBu)` (based on colorbrewer) and `Roma` (based on Sceintific Colormaps by Fabiano Cramerie) as default color maps. - **Interactivity** -Several plots make use of `Observables.jl` which allows for rapid updating of the underlying data. Several plots already have predfined interactive features, allowing you to click on e.g. labels to activate / deactivate them. As examples check out `plot_topoplotseries` and `plot_erpimage`. +Several plots make use of `Observables.jl` which allows fast updating of the underlying data. Several plots already have predfined interactive features, e.g. you can click on labels to enable / disable them. See `plot_topoplotseries` and `plot_erpimage` for examples. diff --git a/test/test_erpimage.jl b/test/test_erpimage.jl index a6b00435..bfb914f7 100644 --- a/test/test_erpimage.jl +++ b/test/test_erpimage.jl @@ -127,11 +127,7 @@ end @testset "check error of empty sortvalues" begin err1 = nothing - t() = error(plot_erpimage( - times, - dat_e; - show_sortval = true, - )) + t() = error(plot_erpimage(times, dat_e; show_sortval = true)) try t() catch err1 @@ -139,5 +135,3 @@ end @test err1 == ErrorException("`show_sortval` needs non-empty `sortvalues` argument") end - - From cb3b60b405a08393382e39904bd3d7a5bdaef430 Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Wed, 29 May 2024 14:12:27 +0000 Subject: [PATCH 09/34] defineing topoplotseries by number of bin width --- docs/literate/how_to/mult_vis_in_fig.jl | 8 +- docs/literate/intro/plot_types.jl | 2 +- docs/literate/tutorials/topoplotseries.jl | 20 +-- docs/src/index.md | 6 +- src/eeg_series.jl | 91 +++++++------ src/layout_helper.jl | 8 -- src/plot_topoplotseries.jl | 28 ++-- src/plotconfig.jl | 2 - test/test_complexplots.jl | 12 +- test/test_toposeries.jl | 153 ++++++++-------------- 10 files changed, 147 insertions(+), 183 deletions(-) diff --git a/docs/literate/how_to/mult_vis_in_fig.jl b/docs/literate/how_to/mult_vis_in_fig.jl index 27f1c949..1a02cbc5 100644 --- a/docs/literate/how_to/mult_vis_in_fig.jl +++ b/docs/literate/how_to/mult_vis_in_fig.jl @@ -83,8 +83,8 @@ plot_designmatrix!(f[2, 3], designmatrix(uf)) plot_topoplot!(f[3, 1], data[:, 150, 1]; positions = positions) plot_topoplotseries!( f[4, 1:3], - d_topo, - 0.1; + d_topo; + Δbin = 0.1, positions = positions, mapping = (; label = :channel), ) @@ -166,8 +166,8 @@ plot_topoplot!(gc, data[:, 340, 1]; positions = positions, axis = (; xlabel = "[ plot_topoplotseries!( gd, - df, - 80; + df; + Δbin = 80, positions = positions, visual = (label_scatter = false,), layout = (; use_colorbar = true), diff --git a/docs/literate/intro/plot_types.jl b/docs/literate/intro/plot_types.jl index 851f8e88..3a5fa0dc 100644 --- a/docs/literate/intro/plot_types.jl +++ b/docs/literate/intro/plot_types.jl @@ -1,7 +1,7 @@ # # The Dilemma of Multidimensionality # !!! note -# Please read the paper [The Art of Brainwaves](https://apertureneuro.org/article/116386-the-art-of-brainwaves-a-survey-on-event-related-potential-visualization-practices), if you want to know more about how we come up with these plot types. +# Please read the paper [The Art of Brainwaves](https://apertureneuro.org/article/116386-the-art-of-brainwaves-a-survey-on-event-related-potential-visualization-practices), if you want to know how we come up with these plot types. #= EEG – multidimensional data and could be presented differently. diff --git a/docs/literate/tutorials/topoplotseries.jl b/docs/literate/tutorials/topoplotseries.jl index e51fac1b..966cfe55 100644 --- a/docs/literate/tutorials/topoplotseries.jl +++ b/docs/literate/tutorials/topoplotseries.jl @@ -24,13 +24,13 @@ nothing #hide # # Plotting Δbin = 80 -plot_topoplotseries(df, Δbin; positions = positions) +plot_topoplotseries(df; Δbin, positions = positions) # # Additional features # ## Disabling colorbar -plot_topoplotseries(df, Δbin; positions = positions, layout = (; use_colorbar = false)) +plot_topoplotseries(df; Δbin, positions = positions, layout = (; use_colorbar = false)) # ## Aggregating functions # In this example `combinefun` is specified by `mean`, `median` and `std`. @@ -38,24 +38,24 @@ plot_topoplotseries(df, Δbin; positions = positions, layout = (; use_colorbar = f = Figure(size = (500, 500)) plot_topoplotseries!( f[1, 1], - df, - Δbin; + df; + Δbin, positions = positions, combinefun = mean, axis = (; title = "combinefun = mean"), ) plot_topoplotseries!( f[2, 1], - df, - Δbin; + df; + Δbin, positions = positions, combinefun = median, axis = (; title = "combinefun = median"), ) plot_topoplotseries!( f[3, 1], - df, - Δbin; + df; + Δbin, positions = positions, combinefun = std, axis = (; title = "combinefun = std"), @@ -83,8 +83,8 @@ f = Figure(size = (600, 500)) plot_topoplotseries!( f[1:2, 1:2], - df1, - Δbin; + df1; + Δbin, col_labels = true, mapping = (; row = :condition), axis = (; ylabel = "Conditions"), diff --git a/docs/src/index.md b/docs/src/index.md index 6dbac1fc..0aae8e04 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -10,13 +10,13 @@ This is the documentation of the UnfoldMakie.jl package for the Julia programmin - **10 plot functions for displaying ERPs.** Each plot emphasizes certain dimensions while collapsing others. -- **Speed** +- **Fast plotting** Plot one figure with 20 topoplots in 1 second? No problemo! - **Highly adaptable.** The package is primarily based on [Unfold.jl](https://github.com/unfoldtoolbox/unfold.jl/) and [Makie.jl](https://makie.juliaplots.org/stable/). - **Many usage examples** -Here in the documentation you can find many user-friendly examples of how to use and adapt the plots. +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), 40% of EEG researchers do not know about the issue of scientific color maps. By default, we use `Reverse(:RdBu)` (based on colorbrewer) and `Roma` (based on Sceintific Colormaps by Fabiano Cramerie) 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. 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. - **Interactivity** Several plots make use of `Observables.jl` which allows fast updating of the underlying data. Several plots already have predfined 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/eeg_series.jl b/src/eeg_series.jl index 51fea2f2..c4685a1d 100644 --- a/src/eeg_series.jl +++ b/src/eeg_series.jl @@ -16,9 +16,10 @@ end """ eeg_topoplot_series(data::DataFrame, - fig, - data::DataFrame, - Δbin; + f, + data::DataFrame; + Δbin, + num_bin, y = :erp, label = :label, col = :time, @@ -35,7 +36,7 @@ end Plot a series of topoplots. The function automatically takes the `combinefun = mean` over the `:time` column of `data` in `Δbin` steps. -- `fig` \\ +- `f` \\ Figure object. \\ - `data::DataFrame`\\ Needs the columns `:time` and `y(=:erp)`, and `label(=:label)`. \\ @@ -61,36 +62,47 @@ eeg_topoplot_series(df, 5; positions = pos) **Return Value:** `Tuple{Figure, Vector{Any}}`. """ function eeg_topoplot_series( - data::Union{<:Observable,<:DataFrame,<:AbstractMatrix}, - Δbin; + data::Union{<:Observable,<:DataFrame,<:AbstractMatrix}; figure = NamedTuple(), kwargs..., ) - return eeg_topoplot_series!(Figure(; figure...), data, Δbin; kwargs...) + return eeg_topoplot_series!(Figure(; figure...), data; kwargs...) end # allow to specify Δbin as an keyword for nicer readability -eeg_topoplot_series( + +eeg_topoplot_series(data::Union{<:Observable,<:DataFrame,<:AbstractMatrix}; kwargs...) = + eeg_topoplot_series(data; kwargs...) +# AbstractMatrix +function eeg_topoplot_series!( + fig, data::Union{<:Observable,<:DataFrame,<:AbstractMatrix}; - Δ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...) +) + return eeg_topoplot_series!(fig, data, string.(1:size(data, 1)); 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...) +function eeg_topoplot_series( + data::Union{<:Observable,<:DataFrame,<:AbstractMatrix}, + labels; + kwargs..., +) + return eeg_topoplot_series(eeg_matrix_to_dataframe(data, labels); 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...) +function eeg_topoplot_series!( + fig, + data::Union{<:Observable,<:DataFrame,<:AbstractMatrix}, + labels; + kwargs..., +) + return eeg_topoplot_series!(fig, eeg_matrix_to_dataframe(data, labels); kwargs...) end function eeg_topoplot_series!( fig, - data::Union{<:Observable{<:DataFrame},<:DataFrame}, - Δbin; + data::Union{<:Observable{<:DataFrame},<:DataFrame}; + Δbin = nothing, + num_bin = nothing, y = :erp, label = :label, col = :time, @@ -102,12 +114,12 @@ function eeg_topoplot_series!( xlim_topo = (-0.25, 1.25), ylim_topo = (-0.25, 1.25), interactive_scatter = nothing, - highlight_scatter = false,#Observable([0]), + highlight_scatter = false, #Observable([0]), topoplot_attributes..., ) # cannot be made easier right now, but Simon promised a simpler solution "soonish" - axisOptions = ( + axis_options = ( aspect = 1, xgridvisible = false, xminorgridvisible = false, @@ -138,11 +150,11 @@ function eeg_topoplot_series!( data = _as_observable(data) if eltype(to_value(data)[!, col]) <: Number - data_mean = @lift( df_timebin( - $data, - Δbin; + $data; + Δbin = Δbin, + num_bin = num_bin, col_y = y, fun = combinefun, grouping = [label, col, row], @@ -170,11 +182,10 @@ function eeg_topoplot_series!( @assert isa(interactive_scatter, Observable) end - axlist = [] for r = 1:length(select_row) for c = 1:length(select_col) - ax = Axis(fig[:, :][r, c]; axisOptions...) + ax = Axis(fig[:, :][r, c]; axis_options...) # select one topoplot sel = 1 .== ones(size(to_value(data_mean), 1)) # select all if !isnothing(col) @@ -192,10 +203,7 @@ function eeg_topoplot_series!( d_vec = @lift($df_single[:, y]) # plot it if highlight_scatter != false || interactive_scatter != nothing - - # pos = @lift topoplot_attributes[:positions][highlight_scatter] strokecolor = Observable(repeat([:black], length(to_value(d_vec)))) - highlight_feature = (; strokecolor = strokecolor) if :label_scatter ∈ keys(topoplot_attributes) topoplot_attributes = merge( @@ -211,15 +219,11 @@ function eeg_topoplot_series!( topoplot_attributes = merge(topoplot_attributes, (; label_scatter = highlight_feature)) end - - end if isempty(to_value(d_vec)) continue end h_topo = eeg_topoplot!(ax, d_vec, labels; topoplot_attributes...) - @debug typeof(h_topo) typeof(ax) - if rasterize_heatmaps h_topo.plots[1].plots[1].rasterize = true end @@ -228,13 +232,11 @@ function eeg_topoplot_series!( ax.xlabelvisible = true end if c == 1 && length(select_row) > 1 && row_labels - #@show df_single ax.ylabel = string(to_value(df_single)[1, row]) ax.ylabelvisible = true end if interactive_scatter != false - on(events(h_topo).mousebutton) do event if event.button == Mouse.left && event.action == Mouse.press plt, p = pick(h_topo) @@ -280,18 +282,29 @@ Arguments: **Return Value:** `DataFrame`. """ -function df_timebin(df, Δbin; col_y = :erp, fun = mean, grouping = []) +function df_timebin( + df; + Δbin = nothing, + num_bin = nothing, + col_y = :erp, + fun = mean, + grouping = [], +) + @assert !(isnothing(Δbin) && isnothing(num_bin)) tmin = minimum(df.time) tmax = maximum(df.time) - bins = range(; start = tmin, step = Δbin, stop = tmax) + if isnothing(Δbin) + bins = range(; start = tmin, length = num_bin + 1, stop = tmax) + else + bins = range(; start = tmin, step = Δbin, stop = tmax) + end 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 + 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/layout_helper.jl b/src/layout_helper.jl index 1aeb9fc0..183e27f6 100644 --- a/src/layout_helper.jl +++ b/src/layout_helper.jl @@ -53,14 +53,6 @@ function apply_layout_settings!( if :hidedecorations ∈ keys(config.layout) && !isnothing(config.layout.hidedecorations) hidedecorations!(ax; config.layout.hidedecorations...) end - - # automatic labels - #if !isnothing(config.layout.xlabelFromMapping) - # ax.xlabel = string(config.mapping[config.layout.xlabelFromMapping]) - #end - #if !isnothing(config.layout.ylabelFromMapping) - # ax.ylabel = string(config.mapping[config.layout.ylabelFromMapping]) - #end end Makie.hidedecorations!(ax::Matrix{AxisEntries}; kwargs...) = Makie.hidedecorations!.(ax; kwargs...) diff --git a/src/plot_topoplotseries.jl b/src/plot_topoplotseries.jl index d6b9caec..f40cb9a5 100644 --- a/src/plot_topoplotseries.jl +++ b/src/plot_topoplotseries.jl @@ -1,6 +1,6 @@ """ - plot_topoplotseries(f::Union{GridPosition, GridLayout, Figure}, data::DataFrame, Δbin::Real; kwargs...) - plot_topoplotseries!(data::DataFrame, Δbin::Real; kwargs...) + plot_topoplotseries(f::Union{GridPosition, GridLayout, Figure}, data::DataFrame; kwargs...) + plot_topoplotseries!(data::DataFrame; kwargs...) Multiple miniature topoplots in regular distances. ## Arguments @@ -37,13 +37,14 @@ $(_docstring(:topoplotseries)) **Return Value:** `Figure` displaying the Topoplot series. """ -plot_topoplotseries(data::DataFrame, Δbin::Real; kwargs...) = - plot_topoplotseries!(Figure(), data, Δbin; kwargs...) +plot_topoplotseries(data::DataFrame; kwargs...) = + plot_topoplotseries!(Figure(), data; kwargs...) function plot_topoplotseries!( f::Union{GridPosition,GridLayout,Figure,GridLayoutBase.GridSubposition}, - data::Union{<:Observable{<:DataFrame},DataFrame}, - Δbin; + data::Union{<:Observable{<:DataFrame},DataFrame}; + Δbin = nothing, + num_bin = nothing, positions = nothing, labels = nothing, combinefun = mean, @@ -75,14 +76,12 @@ function plot_topoplotseries!( positions = get_topo_positions(; positions = positions, labels = labels) chan_or_label = "label" ∉ names(to_value(data)) ? :channel : :label - @debug "hellooooo" keys(config.mapping) if :layout ∈ keys(config.mapping) - @debug "hello layout!!" data = deepcopy(to_value(data)) un_layout = unique(data[:, config.mapping.layout]) ix = findall.(isequal.(un_layout), [data[:, config.mapping.layout]]) - @debug ix[1][1:5] size(ix) size(ix[1]) + n_topoplots = length(un_layout) n_cols = Int(ceil(sqrt(n_topoplots))) @@ -90,6 +89,7 @@ function plot_topoplotseries!( _col = repeat(1:n_cols, outer = n_rows)[1:n_topoplots] _row = repeat(1:n_rows, inner = n_cols)[1:n_topoplots] + data._col .= 0 data._row .= 0 for topo = 1:n_topoplots @@ -102,8 +102,9 @@ function plot_topoplotseries!( ftopo, axlist = eeg_topoplot_series!( f, - data, - Δbin; + data; + Δbin = Δbin, + num_bin = num_bin, y = config.mapping.y, label = chan_or_label, col = config.mapping.col, @@ -123,8 +124,9 @@ function plot_topoplotseries!( else data_mean = if cat_or_cont_columns == "cont" df_timebin( - to_value(data), - Δbin; + to_value(data); + Δbin, + num_bin, col_y = config.mapping.y, fun = combinefun, grouping = [chan_or_label, config.mapping.col, config.mapping.row], diff --git a/src/plotconfig.jl b/src/plotconfig.jl index 52c913dc..413a458d 100644 --- a/src/plotconfig.jl +++ b/src/plotconfig.jl @@ -120,8 +120,6 @@ function PlotConfig(T::Val{:topoplot}) cfg; layout = ( show_legend = true, - xlabelFromMapping = nothing, - ylabelFromMapping = nothing, use_colorbar = true, hidespines = (), hidedecorations = (Dict(:label => false)), diff --git a/test/test_complexplots.jl b/test/test_complexplots.jl index bedb17ed..2f3fafd3 100644 --- a/test/test_complexplots.jl +++ b/test/test_complexplots.jl @@ -48,8 +48,8 @@ plot_topoplotseries!( gd, - df, - 80; + df; + Δbin = 80, positions = positions, visual = (label_scatter = false,), layout = (; use_colorbar = true), @@ -130,8 +130,8 @@ end plot_topoplot!(f[2, 1], data[:, 150, 1]; positions = positions) plot_topoplotseries!( f[2, 2], - d_topo, - 0.1; + d_topo; + Δbin = 0.1, positions = positions, visual = (label_scatter = false,), layout = (; use_colorbar = true), @@ -200,8 +200,8 @@ end plot_topoplot!(f[3, 1], data[:, 150, 1]; positions = positions) plot_topoplotseries!( f[4, 1:3], - d_topo, - 0.1; + d_topo; + Δbin = 0.1, positions = positions, mapping = (; label = :channel), ) diff --git a/test/test_toposeries.jl b/test/test_toposeries.jl index fb961fd0..332d6ffc 100644 --- a/test/test_toposeries.jl +++ b/test/test_toposeries.jl @@ -2,18 +2,22 @@ dat, positions = TopoPlots.example_data() df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) Δbin = 80 -@testset "toposeries basic" begin - plot_topoplotseries(df, Δbin; positions = positions) +@testset "toposeries basic with Δbin" begin + plot_topoplotseries(df; Δbin, positions = positions) +end + +@testset "toposeries basic with num_bin" begin + plot_topoplotseries(df; num_bin = 5, positions = positions) end @testset "toposeries basic with channel names" begin - plot_topoplotseries(df, Δbin; positions = positions, labels = raw_ch_names) + plot_topoplotseries(df; Δbin, positions = positions, labels = raw_ch_names) end @testset "toposeries with xlabel" begin f = Figure() ax = Axis(f[1, 1]) - plot_topoplotseries!(f[1, 1], df, Δbin; positions = positions) + plot_topoplotseries!(f[1, 1], df; Δbin, positions = positions) text!(ax, 0, 0, text = "Time [ms] ", align = (:center, :center), offset = (0, -120)) hidespines!(ax) # delete unnecessary spines (lines) hidedecorations!(ax, label = false) @@ -21,31 +25,31 @@ end end @testset "toposeries for one time point" begin - plot_topoplotseries(df, Δbin; positions = positions, combinefun = x -> x[end÷2]) + plot_topoplotseries(df; Δbin, positions = positions, combinefun = x -> x[end÷2]) end @testset "toposeries with differend comb functions " begin f = Figure(size = (500, 500)) plot_topoplotseries!( f[1, 1], - df, - Δbin; + df; + Δbin, positions = positions, combinefun = mean, axis = (; title = "combinefun = mean"), ) plot_topoplotseries!( f[2, 1], - df, - Δbin; + df; + Δbin, positions = positions, combinefun = median, axis = (; title = "combinefun = median"), ) plot_topoplotseries!( f[3, 1], - df, - Δbin; + df; + Δbin, positions = positions, combinefun = std, axis = (; title = "combinefun = std"), @@ -54,7 +58,7 @@ end end @testset "toposeries without colorbar" begin - plot_topoplotseries(df, Δbin; positions = positions, layout = (; use_colorbar = false)) + plot_topoplotseries(df; Δbin, positions = positions, layout = (; use_colorbar = false)) end @testset "GridPosition with a title" begin @@ -66,8 +70,8 @@ end Δbin = 80 a = plot_topoplotseries!( f[1:2, 1:5], - df, - Δbin; + df; + Δbin, positions = positions, layout = (; use_colorbar = true), ) @@ -84,8 +88,8 @@ end Δbin = 30 plot_topoplotseries!( f[1, 1:5], - df, - Δbin; + df; + Δbin, positions = positions, visual = (label_scatter = false,), ) @@ -100,8 +104,8 @@ end plot_topoplotseries!( f[1:2, 1:2], - df, - Δbin; + df; + Δbin, col_labels = true, mapping = (; row = :condition), positions = positions, @@ -118,8 +122,8 @@ end f = Figure(size = (600, 500)) plot_topoplotseries!( f[1:2, 1:2], - df, - Δbin; + df; + Δbin, col_labels = true, mapping = (; row = :condition), axis = (; ylabel = "Conditions"), @@ -132,15 +136,15 @@ end @testset "facetting by layout" begin df = UnfoldMakie.eeg_matrix_to_dataframe( - dat[:, 200:1:240, 1], + dat[:, 200:1:205, 1], string.(1:length(positions)), ) f = Figure(size = (600, 500)) plot_topoplotseries!( f[1:2, 1:2], - df, - Δbin; + df; + Δbin = 1, col_labels = true, mapping = (; layout = :time), positions = positions, @@ -151,21 +155,23 @@ end end @testset "toposeries with xlabel" begin - plot_topoplotseries(df, Δbin; positions = positions, axis = (; xlabel = "test")) + plot_topoplotseries(df; Δbin, positions = positions, axis = (; xlabel = "test")) end + @testset "toposeries with adjustable colorrange" begin plot_topoplotseries( - df, - Δbin; + df; + Δbin, positions = positions, colorbar = (; colorrange = (-1, 1)), ) end + @testset "toposeries with xlabel" begin - plot_topoplotseries(df, Δbin; positions = positions, axis = (; ylim_topo = (0, 0.7))) + plot_topoplotseries(df; Δbin, positions = positions, axis = (; ylim_topo = (0, 0.7))) end -@testset "basic eeg_topoplot_series" begin +#= @testset "basic eeg_topoplot_series" begin df = DataFrame( :erp => repeat(1:63, 100), :time => repeat(1:20, 5 * 63), @@ -175,22 +181,22 @@ end b = [(1:63) ./ 63 .* a (1:63) ./ 63 .* cos.(range(-2 * pi, 2 * pi, 63))] pos = b .* 0.5 .+ 0.5 # simulated electrode positions pos = [Point2.(pos[k, 1], pos[k, 2]) for k = 1:size(pos, 1)] - UnfoldMakie.eeg_topoplot_series(df, 5; positions = pos) -end + UnfoldMakie.eeg_topoplot_series(df; Δbin = 5, positions = pos) +end =# @testset "toposeries with GridSubposition" begin f = Figure(size = (500, 500)) plot_topoplotseries!( f[2, 1][1, 1], - df, - Δbin; + df; + Δbin, positions = positions, combinefun = mean, axis = (; title = "combinefun = mean"), ) end -@testset "observables" begin +@testset "interactive data" begin f = Figure() df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) df.condition = repeat(["A", "B"], size(df, 1) ÷ 2) @@ -199,8 +205,8 @@ end plot_topoplotseries!( f[1:2, 1:2], - df_obs, - Δbin; + df_obs; + Δbin, col_labels = true, mapping = (; row = :condition), positions = positions, @@ -213,84 +219,37 @@ end df_obs[] = df end -@testset "categorical cols" begin - f = Figure() +@testset "interactive scatter markers" begin df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, 1:2, 1], string.(1:length(positions))) df.condition = repeat(["A", "B"], size(df, 1) ÷ 2) - plot_topoplotseries!( - f[1:2, 1:2], - df, - 0; + obs_tuple = Observable((0, 0, 0)) + plot_topoplotseries( + df; + Δbin = 0, col_labels = true, mapping = (; col = :condition), positions = positions, - visual = (label_scatter = false,), + visual = (label_scatter = (markersize = 15, strokewidth = 2),), layout = (; use_colorbar = true), + interactive_scatter = obs_tuple, ) - f - end -@testset "observables on scatterpoints" begin - ##-- +@testset "categorical columns" begin + f = Figure() df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, 1:2, 1], string.(1:length(positions))) df.condition = repeat(["A", "B"], size(df, 1) ÷ 2) - obs_tuple = Observable((0, 0, 0)) - plot_topoplotseries( - df, - 0; + plot_topoplotseries!( + f[1:2, 1:2], + df; + Δbin = 0, col_labels = true, mapping = (; col = :condition), positions = positions, - visual = (label_scatter = (markersize = 15, strokewidth = 2),), + visual = (label_scatter = false,), layout = (; use_colorbar = true), - interactive_scatter = obs_tuple, ) + f end - -#= -using CSV -tmp = CSV.read("dev/UnfoldMakie/test/output2.csv", DataFrame) -tmp = stack(tmp, 1:21) -rename!(tmp, :variable => :condition, :value => :estimate) -tmp.time = 1:nrow(tmp) - -Δbin = 140 - -tmp2 = filter(x -> x.condition == "type" || x.condition == "duration", tmp) - -plot_topoplotseries( - tmp2, - Δbin; - positions = rand(Point2f, 128), - combinefun = x -> x, - mapping = (; :col => :condition), -) - - -tmp3 = filter(x -> x.condition != "type", tmp) -n = size(tmp3, 1) ÷ 4 -tmp3.row = vcat(fill("A", n), fill("B", n), fill("C", n), fill("D", n)) - -Δbin = 134 -plot_topoplotseries( - tmp3, - Δbin; - positions = rand(Point2f, 128), - combinefun = x -> x, - mapping = (; :col => :condition, :row => :row), -) - -using Images - -function filt(img) - filtered_data = UnfoldMakie.imfilter(img, UnfoldMakie.Kernel.gaussian((1, max(30, 0)))) -end - -Images.entropy((dat[:, :, shuffle(1:end)])) - - -map((x) -> Images.entropy((dat[x, :, shuffle(1:end)])) * 2, 1:size(dat, 1)) - =# From d566737a919c527b4cb16862927d83e4ccfbb2ed Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Wed, 29 May 2024 14:18:01 +0000 Subject: [PATCH 10/34] transparency examples --- .../{reference => explanations}/positions.jl | 0 docs/literate/how_to/position2color.jl | 16 ++++++++++++++++ docs/make.jl | 4 ++-- 3 files changed, 18 insertions(+), 2 deletions(-) rename docs/literate/{reference => explanations}/positions.jl (100%) diff --git a/docs/literate/reference/positions.jl b/docs/literate/explanations/positions.jl similarity index 100% rename from docs/literate/reference/positions.jl rename to docs/literate/explanations/positions.jl diff --git a/docs/literate/how_to/position2color.jl b/docs/literate/how_to/position2color.jl index bdb05f0b..247d3a1f 100644 --- a/docs/literate/how_to/position2color.jl +++ b/docs/literate/how_to/position2color.jl @@ -54,3 +54,19 @@ plot_butterfly( positions = positions, topopositions_to_color = x -> Colors.RGB(0.5), ) + +# Transparency +# Unlike RGB, RGBA has a fourth channel, alpha, which is responsible for transparency. +# Here are two examples of how to manipulate it. + +plot_butterfly( + results; + positions = positions, + topopositions_to_color = x -> (RGBA(UnfoldMakie.pos_to_color_RomaO(x), 1)), +) + +plot_butterfly( + results; + positions = positions, + topopositions_to_color = x -> (GrayA(UnfoldMakie.pos_to_color_RomaO(x), 0.5)), +) diff --git a/docs/make.jl b/docs/make.jl index bb67fb2e..1db37467 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -56,8 +56,8 @@ makedocs(; "Hide Decorations and Axis Spines" => "generated/how_to/hide_deco.md", "Include multiple figures in one" => "generated/how_to/mult_vis_in_fig.md", ], - "Reference" => [ - "Convert electrode positions from 3D to 2D" => "generated/reference/positions.md", + "Explanations" => [ + "Convert electrode positions from 3D to 2D" => "generated/explanations/positions.md", ], "API / DocStrings" => "api.md", "Utilities" => "helper.md", From 807698ed83ec6e1668c2787a60528fc436f06b5e Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Wed, 29 May 2024 14:34:10 +0000 Subject: [PATCH 11/34] docs bugs --- docs/make.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/make.jl b/docs/make.jl index 1db37467..01e932b5 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -15,7 +15,7 @@ using Glob GENERATED = joinpath(@__DIR__, "src", "generated") SOURCE = joinpath(@__DIR__, "literate") -for subfolder ∈ ["how_to", "intro", "tutorials", "reference"] +for subfolder ∈ ["how_to", "intro", "tutorials", "explanations"] local SOURCE_FILES = Glob.glob(subfolder * "/*.jl", SOURCE) foreach(fn -> Literate.markdown(fn, GENERATED * "/" * subfolder), SOURCE_FILES) end From 1cdb741a98d795e7a734fd16fd1a92ab7dc744e4 Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Wed, 29 May 2024 15:53:46 +0000 Subject: [PATCH 12/34] error managment for df_timebins --- src/eeg_series.jl | 12 +++++++++--- src/plot_topoplotseries.jl | 14 ++++++++++---- src/plotconfig.jl | 2 +- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/eeg_series.jl b/src/eeg_series.jl index c4685a1d..308e4d80 100644 --- a/src/eeg_series.jl +++ b/src/eeg_series.jl @@ -271,8 +271,10 @@ Split or combine `DataFrame` according to equally spaced time bins. Arguments: - `df::AbstractTable`\\ With columns `:time` and `col_y` (default `:erp`), and all columns in `grouping`; -- `Δbin`\\ - Bin size in `:time` units; +- `Δbin::Real = nothing`\\ + Bin width in `:time` units; +- `num_bin::Real = nothing`\\ + Number of topoplots; - `col_y = :erp` \\ The column to combine over (with `fun`); - `fun = mean()`\\ @@ -290,7 +292,11 @@ function df_timebin( fun = mean, grouping = [], ) - @assert !(isnothing(Δbin) && isnothing(num_bin)) + if (Δbin != nothing && num_bin != nothing) + error("Ambigious parameters: specify only `Δbin` or `num_bin`.") + elseif (isnothing(Δbin) && isnothing(num_bin)) + error("You haven't specified `Δbin` or `num_bin`. Such option is available only with categorical `mapping.col` or `mapping.row`.") + end tmin = minimum(df.time) tmax = maximum(df.time) diff --git a/src/plot_topoplotseries.jl b/src/plot_topoplotseries.jl index f40cb9a5..b6c120ab 100644 --- a/src/plot_topoplotseries.jl +++ b/src/plot_topoplotseries.jl @@ -9,12 +9,15 @@ Multiple miniature topoplots in regular distances. `Figure`, `GridLayout`, `GridPosition`, or GridLayoutBase.GridSubposition to draw the plot. - `data::Union{<:Observable{<:DataFrame},DataFrame}`\\ DataFrame with data or Observable DataFrame. Requires a `time` column. -- `Δbin::Real`\\ - A number for how large one time bin should be.\\ - `Δbin` is in units of the `data.time` column.\\ - Should be `0` if `mapping.col` or `mapping.row` are categorical. ## Keyword arguments (kwargs) +- `Δbin::Real = nothing`\\ + Number specifing the width of time bin.\\ + `Δbin` is in units of the `data.time` column.\\ +- `num_bin::Real = nothing`\\ + Number of topoplots.\\ + Either `Δbin`, or `num_bin` should be specified. Error if they are both specified\\ + If `mapping.col` or `mapping.row` are categorical `Δbin` and `num_bin` should be `nothing`. - `combinefun::Function = mean`\\ Specify how the samples within `Δbin` are summarised.\\ Example functions: `mean`, `median`, `std`. @@ -32,6 +35,9 @@ Multiple miniature topoplots in regular distances. Enable interactive mode. \\ If you create `obs_tuple = Observable((0, 0, 0))` and pass it into `interactive_scatter` you can change observable indecies by clicking topopplot markers.\\ `(0, 0, 0)` corresponds to the indecies of row of topoplot layout, column of topoplot layout and channell. +- `mapping.layout = nothing`\\ + When equals `:time` arrange topoplots by rows. + $(_docstring(:topoplotseries)) diff --git a/src/plotconfig.jl b/src/plotconfig.jl index 413a458d..02cab70d 100644 --- a/src/plotconfig.jl +++ b/src/plotconfig.jl @@ -175,7 +175,7 @@ function PlotConfig(T::Val{:topoplotseries}) enlarge = 1, label_scatter = false, ), - mapping = (; col = (:time,), row = (nothing,)), + mapping = (; col = :time, row = nothing, layout = nothing), ) return cfg end From eef23dc6b3de54921de94c50fb9dcb56284817eb Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Thu, 30 May 2024 13:37:55 +0000 Subject: [PATCH 13/34] bug --- src/plotconfig.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plotconfig.jl b/src/plotconfig.jl index 02cab70d..f467fd1d 100644 --- a/src/plotconfig.jl +++ b/src/plotconfig.jl @@ -175,7 +175,8 @@ function PlotConfig(T::Val{:topoplotseries}) enlarge = 1, label_scatter = false, ), - mapping = (; col = :time, row = nothing, layout = nothing), + mapping = (; col = :time, row = nothing, #layout = nothing + ), ) return cfg end From d7de5f4e5ad674530526d40eb7c85d137f4791e4 Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Thu, 30 May 2024 16:18:34 +0000 Subject: [PATCH 14/34] two args renamed, more user-frienlz facetting (buggy for categorical)@ --- Project.toml | 1 + docs/literate/how_to/mult_vis_in_fig.jl | 4 +- docs/literate/tutorials/topoplotseries.jl | 14 +-- src/UnfoldMakie.jl | 1 + src/eeg_series.jl | 75 +++++++++------- src/plot_topoplotseries.jl | 105 +++++++++++++++------- test/test_complexplots.jl | 6 +- test/test_toposeries.jl | 85 +++++++++++------- 8 files changed, 178 insertions(+), 113 deletions(-) diff --git a/Project.toml b/Project.toml index b362d471..36dedc28 100644 --- a/Project.toml +++ b/Project.toml @@ -23,6 +23,7 @@ MakieThemes = "e296ed71-da82-5faf-88ab-0034a9761098" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" TopoPlots = "2bdbdf9c-dbd8-403f-947b-1a4e0dd41a7a" Unfold = "181c99d8-e21b-4ff3-b70b-c233eddec679" diff --git a/docs/literate/how_to/mult_vis_in_fig.jl b/docs/literate/how_to/mult_vis_in_fig.jl index 1a02cbc5..05cd535f 100644 --- a/docs/literate/how_to/mult_vis_in_fig.jl +++ b/docs/literate/how_to/mult_vis_in_fig.jl @@ -84,7 +84,7 @@ plot_topoplot!(f[3, 1], data[:, 150, 1]; positions = positions) plot_topoplotseries!( f[4, 1:3], d_topo; - Δbin = 0.1, + bin_width = 0.1, positions = positions, mapping = (; label = :channel), ) @@ -167,7 +167,7 @@ plot_topoplot!(gc, data[:, 340, 1]; positions = positions, axis = (; xlabel = "[ plot_topoplotseries!( gd, df; - Δbin = 80, + bin_width = 80, positions = positions, visual = (label_scatter = false,), layout = (; use_colorbar = true), diff --git a/docs/literate/tutorials/topoplotseries.jl b/docs/literate/tutorials/topoplotseries.jl index 966cfe55..a4016735 100644 --- a/docs/literate/tutorials/topoplotseries.jl +++ b/docs/literate/tutorials/topoplotseries.jl @@ -23,14 +23,14 @@ nothing #hide # # Plotting -Δbin = 80 -plot_topoplotseries(df; Δbin, positions = positions) +bin_width = 80 +plot_topoplotseries(df; bin_width, positions = positions) # # Additional features # ## Disabling colorbar -plot_topoplotseries(df; Δbin, positions = positions, layout = (; use_colorbar = false)) +plot_topoplotseries(df; bin_width, positions = positions, layout = (; use_colorbar = false)) # ## Aggregating functions # In this example `combinefun` is specified by `mean`, `median` and `std`. @@ -39,7 +39,7 @@ f = Figure(size = (500, 500)) plot_topoplotseries!( f[1, 1], df; - Δbin, + bin_width, positions = positions, combinefun = mean, axis = (; title = "combinefun = mean"), @@ -47,7 +47,7 @@ plot_topoplotseries!( plot_topoplotseries!( f[2, 1], df; - Δbin, + bin_width, positions = positions, combinefun = median, axis = (; title = "combinefun = median"), @@ -55,7 +55,7 @@ plot_topoplotseries!( plot_topoplotseries!( f[3, 1], df; - Δbin, + bin_width, positions = positions, combinefun = std, axis = (; title = "combinefun = std"), @@ -84,7 +84,7 @@ f = Figure(size = (600, 500)) plot_topoplotseries!( f[1:2, 1:2], df1; - Δbin, + bin_width, col_labels = true, mapping = (; row = :condition), axis = (; ylabel = "Conditions"), diff --git a/src/UnfoldMakie.jl b/src/UnfoldMakie.jl index 39298277..b12cb2c4 100644 --- a/src/UnfoldMakie.jl +++ b/src/UnfoldMakie.jl @@ -25,6 +25,7 @@ using DataFrames using SparseArrays using CategoricalArrays # for cut for TopoPlotSeries using StaticArrays +using StatsBase using CoordinateTransformations # for 3D positions to 2D diff --git a/src/eeg_series.jl b/src/eeg_series.jl index 308e4d80..c30404a4 100644 --- a/src/eeg_series.jl +++ b/src/eeg_series.jl @@ -18,8 +18,8 @@ end eeg_topoplot_series(data::DataFrame, f, data::DataFrame; - Δbin, - num_bin, + bin_width, + bin_num, y = :erp, label = :label, col = :time, @@ -32,16 +32,16 @@ end ylim_topo, topoplot_attributes..., ) - eeg_topoplot_series!(fig, data::DataFrame, Δbin; kwargs..) + eeg_topoplot_series!(fig, data::DataFrame, bin_width; kwargs..) Plot a series of topoplots. -The function automatically takes the `combinefun = mean` over the `:time` column of `data` in `Δbin` steps. +The function automatically takes the `combinefun = mean` over the `:time` column of `data` in `bin_width` steps. - `f` \\ Figure object. \\ - `data::DataFrame`\\ 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 = :time` \\ +- `bin_width = :time` \\ 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`. - `col`, `row = :time` \\ @@ -68,7 +68,7 @@ function eeg_topoplot_series( ) return eeg_topoplot_series!(Figure(; figure...), data; kwargs...) end -# allow to specify Δbin as an keyword for nicer readability +# allow to specify bin_width as an keyword for nicer readability eeg_topoplot_series(data::Union{<:Observable,<:DataFrame,<:AbstractMatrix}; kwargs...) = eeg_topoplot_series(data; kwargs...) @@ -101,8 +101,8 @@ end function eeg_topoplot_series!( fig, data::Union{<:Observable{<:DataFrame},<:DataFrame}; - Δbin = nothing, - num_bin = nothing, + bin_width = nothing, + bin_num = nothing, y = :erp, label = :label, col = :time, @@ -153,8 +153,8 @@ function eeg_topoplot_series!( data_mean = @lift( df_timebin( $data; - Δbin = Δbin, - num_bin = num_bin, + bin_width = bin_width, + bin_num = bin_num, col_y = y, fun = combinefun, grouping = [label, col, row], @@ -223,25 +223,32 @@ function eeg_topoplot_series!( if isempty(to_value(d_vec)) continue end - h_topo = eeg_topoplot!(ax, d_vec, labels; topoplot_attributes...) + single_topoplot = eeg_topoplot!(ax, d_vec, labels; topoplot_attributes...) if rasterize_heatmaps - h_topo.plots[1].plots[1].rasterize = true + single_topoplot.plots[1].plots[1].rasterize = true end - if r == length(select_row) && col_labels - ax.xlabel = string(to_value(df_single)[1, col]) + + # to put column and row labels + if col_labels == true + if r == length(select_row) && col_labels + ax.xlabel = string(to_value(df_single)[1, col]) + ax.xlabelvisible = true + end + if c == 1 && length(select_row) > 1 && row_labels + ax.ylabel = string(to_value(df_single)[1, row]) + ax.ylabelvisible = true + end + else ax.xlabelvisible = true - end - if c == 1 && length(select_row) > 1 && row_labels - ax.ylabel = string(to_value(df_single)[1, row]) - ax.ylabelvisible = true + ax.xlabel = string(to_value(df_single).time[1, :][]) end if interactive_scatter != false - on(events(h_topo).mousebutton) do event + on(events(single_topoplot).mousebutton) do event if event.button == Mouse.left && event.action == Mouse.press - plt, p = pick(h_topo) + plt, p = pick(single_topoplot) - if isa(plt, Makie.Scatter) && plt == h_topo.plots[1].plots[3] + if isa(plt, Makie.Scatter) && plt == single_topoplot.plots[1].plots[3] plt.strokecolor[] .= :black plt.strokecolor[][p] = :white @@ -265,15 +272,15 @@ function eeg_topoplot_series!( end """ - df_timebin(df, Δbin; col_y = :erp, fun = mean, grouping = []) + df_timebin(df, bin_width; col_y = :erp, fun = mean, grouping = []) Split or combine `DataFrame` according to equally spaced time bins. Arguments: - `df::AbstractTable`\\ With columns `:time` and `col_y` (default `:erp`), and all columns in `grouping`; -- `Δbin::Real = nothing`\\ +- `bin_width::Real = nothing`\\ Bin width in `:time` units; -- `num_bin::Real = nothing`\\ +- `bin_num::Real = nothing`\\ Number of topoplots; - `col_y = :erp` \\ The column to combine over (with `fun`); @@ -286,31 +293,31 @@ Arguments: """ function df_timebin( df; - Δbin = nothing, - num_bin = nothing, + bin_width = nothing, + bin_num = nothing, col_y = :erp, fun = mean, grouping = [], ) - if (Δbin != nothing && num_bin != nothing) - error("Ambigious parameters: specify only `Δbin` or `num_bin`.") - elseif (isnothing(Δbin) && isnothing(num_bin)) - error("You haven't specified `Δbin` or `num_bin`. Such option is available only with categorical `mapping.col` or `mapping.row`.") + if (bin_width != nothing && bin_num != nothing) + error("Ambigious parameters: specify only `bin_width` or `bin_num`.") + elseif (isnothing(bin_width) && isnothing(bin_num)) + error("You haven't specified `bin_width` or `bin_num`. Such option is available only with categorical `mapping.col` or `mapping.row`.") end tmin = minimum(df.time) tmax = maximum(df.time) - if isnothing(Δbin) - bins = range(; start = tmin, length = num_bin + 1, stop = tmax) + if isnothing(bin_width) + bins = range(; start = tmin, length = bin_num + 1, stop = tmax) else - bins = range(; start = tmin, step = Δbin, stop = tmax) + bins = range(; start = tmin, step = bin_width, stop = tmax) end 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) 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_topoplotseries.jl b/src/plot_topoplotseries.jl index b6c120ab..ef4f0088 100644 --- a/src/plot_topoplotseries.jl +++ b/src/plot_topoplotseries.jl @@ -11,22 +11,22 @@ Multiple miniature topoplots in regular distances. DataFrame with data or Observable DataFrame. Requires a `time` column. ## Keyword arguments (kwargs) -- `Δbin::Real = nothing`\\ +- `bin_width::Real = nothing`\\ Number specifing the width of time bin.\\ - `Δbin` is in units of the `data.time` column.\\ -- `num_bin::Real = nothing`\\ + `bin_width` is in units of the `data.time` column.\\ +- `bin_num::Real = nothing`\\ Number of topoplots.\\ - Either `Δbin`, or `num_bin` should be specified. Error if they are both specified\\ - If `mapping.col` or `mapping.row` are categorical `Δbin` and `num_bin` should be `nothing`. + Either `bin_width`, or `bin_num` should be specified. Error if they are both specified\\ + If `mapping.col` or `mapping.row` are categorical `bin_width` and `bin_num` should be `nothing`. - `combinefun::Function = mean`\\ - Specify how the samples within `Δbin` are summarised.\\ + Specify how the samples within `bin_width` are summarised.\\ Example functions: `mean`, `median`, `std`. - `rasterize_heatmaps::Bool = true`\\ Force rasterization of the plot heatmap when saving in `svg` format.\\ Except for the interpolated heatmap, all lines/points are vectors.\\ This is typically what you want, otherwise you get ~128x128 vectors per topoplot, which makes everything super slow. - `col_labels::Bool`, `row_labels::Bool = true`\\ - Shows column and row labels. + Shows column and row labels for categorical values (?). - `labels::Vector{String} = nothing`\\ Show labels for each electrode. - `positions::Vector{Point{2, Float32}} = nothing`\\ @@ -49,12 +49,13 @@ plot_topoplotseries(data::DataFrame; kwargs...) = function plot_topoplotseries!( f::Union{GridPosition,GridLayout,Figure,GridLayoutBase.GridSubposition}, data::Union{<:Observable{<:DataFrame},DataFrame}; - Δbin = nothing, - num_bin = nothing, + bin_width = nothing, + bin_num = nothing, positions = nothing, - labels = nothing, + nrows = 1, + labels = nothing, # rename to channel_labels? combinefun = mean, - col_labels = true, + col_labels = false, row_labels = true, rasterize_heatmaps = true, interactive_scatter = nothing, @@ -62,7 +63,6 @@ function plot_topoplotseries!( ) data = _as_observable(data) - config = PlotConfig(:topoplotseries) # overwrite all defaults by user specified values config_kwargs!(config; kwargs...) @@ -82,35 +82,52 @@ function plot_topoplotseries!( positions = get_topo_positions(; positions = positions, labels = labels) chan_or_label = "label" ∉ names(to_value(data)) ? :channel : :label - if :layout ∈ keys(config.mapping) - data = deepcopy(to_value(data)) - un_layout = unique(data[:, config.mapping.layout]) - ix = findall.(isequal.(un_layout), [data[:, config.mapping.layout]]) + # arrangment of topoplots by rows and cols + n_topoplots = number_of_topoplots(to_value(data); bin_width, bin_num) + data = deepcopy(to_value(data)) + tmin = minimum(data.time) + tmax = maximum(data.time) - n_topoplots = length(un_layout) + if isnothing(bin_width) + bins = range(; start = tmin, length = bin_num + 1, stop = tmax) + else + bins = range(; start = tmin, step = bin_width, stop = tmax) + end + data.timecuts = cut(data.time, bins; extend = true) + un_layout = unique(data.timecuts) + ix = findall.(isequal.(un_layout), [data.timecuts]) + #n_topoplots = length(un_layout) # this is just wrong! + if :layout ∈ keys(config.mapping) n_cols = Int(ceil(sqrt(n_topoplots))) n_rows = Int(ceil(n_topoplots / n_cols)) - - _col = repeat(1:n_cols, outer = n_rows)[1:n_topoplots] - _row = repeat(1:n_rows, inner = n_cols)[1:n_topoplots] - - data._col .= 0 - data._row .= 0 - for topo = 1:n_topoplots - data._col[ix[topo]] .= _col[topo] - data._row[ix[topo]] .= _row[topo] - end - #return data - config_kwargs!(config; mapping = (; row = :_row, col = :_col)) + else + n_rows = nrows + n_cols = Int(ceil(n_topoplots / nrows)) + end + _col = repeat(1:n_cols, outer = n_rows)[1:n_topoplots] + _row = repeat(1:n_rows, inner = n_cols)[1:n_topoplots] + data._col .= 1 + data._row .= 1 + for topo = 1:n_topoplots + data._col[ix[topo]] .= _col[topo] + data._row[ix[topo]] .= _row[topo] end + data.colrow = map(x->join(x,":"),zip(data._col,string.(data._row))) + #= @show ix[1] + @show _col + @show _row + @show data._col + @show data._row + @show StatsBase.countmap(data.colrow) =# + config_kwargs!(config; mapping = (; row = :_row, col = :_col)) ftopo, axlist = eeg_topoplot_series!( f, data; - Δbin = Δbin, - num_bin = num_bin, + bin_width = bin_width, + bin_num = bin_num, y = config.mapping.y, label = chan_or_label, col = config.mapping.col, @@ -131,8 +148,8 @@ function plot_topoplotseries!( data_mean = if cat_or_cont_columns == "cont" df_timebin( to_value(data); - Δbin, - num_bin, + bin_width, + bin_num, col_y = config.mapping.y, fun = combinefun, grouping = [chan_or_label, config.mapping.col, config.mapping.row], @@ -172,3 +189,25 @@ function plot_topoplotseries!( return f end + +function number_of_topoplots( + df::DataFrame; + bin_width = nothing, + bin_num = nothing, +) + if (bin_width != nothing && bin_num != nothing) + error("Ambigious parameters: specify only `bin_width` or `bin_num`.") + elseif (isnothing(bin_width) && isnothing(bin_num)) + error("You haven't specified `bin_width` or `bin_num`. Such option is available only with categorical `mapping.col` or `mapping.row`.") + end + tmin = minimum(df.time) + tmax = maximum(df.time) + + if isnothing(bin_width) + bins = range(; start = tmin, length = bin_num + 1, stop = tmax) + else + bins = range(; start = tmin, step = bin_width, stop = tmax) + end + time_new = cut(df.time, bins; extend = true) + return length(unique(time_new)) +end \ No newline at end of file diff --git a/test/test_complexplots.jl b/test/test_complexplots.jl index 2f3fafd3..1561f1aa 100644 --- a/test/test_complexplots.jl +++ b/test/test_complexplots.jl @@ -49,7 +49,7 @@ plot_topoplotseries!( gd, df; - Δbin = 80, + bin_width = 80, positions = positions, visual = (label_scatter = false,), layout = (; use_colorbar = true), @@ -131,7 +131,7 @@ end plot_topoplotseries!( f[2, 2], d_topo; - Δbin = 0.1, + bin_width = 0.1, positions = positions, visual = (label_scatter = false,), layout = (; use_colorbar = true), @@ -201,7 +201,7 @@ end plot_topoplotseries!( f[4, 1:3], d_topo; - Δbin = 0.1, + bin_width = 0.1, positions = positions, mapping = (; label = :channel), ) diff --git a/test/test_toposeries.jl b/test/test_toposeries.jl index 332d6ffc..6aac7638 100644 --- a/test/test_toposeries.jl +++ b/test/test_toposeries.jl @@ -1,23 +1,43 @@ dat, positions = TopoPlots.example_data() df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) -Δbin = 80 +bin_width = 80 -@testset "toposeries basic with Δbin" begin - plot_topoplotseries(df; Δbin, positions = positions) +@testset "toposeries basic with bin_width" begin + plot_topoplotseries(df; bin_width, positions = positions) end -@testset "toposeries basic with num_bin" begin - plot_topoplotseries(df; num_bin = 5, positions = positions) +@testset "toposeries basic with bin_num" begin + plot_topoplotseries(df; bin_num = 5, positions = positions) +end + +@testset "error checking: bin_width and bin_num specified" begin + err1 = nothing + t() = error(plot_topoplotseries(df; bin_width, bin_num = 5, positions = positions)) + try + t() + catch err1 + end + @test err1 == ErrorException("Ambigious parameters: specify only `bin_width` or `bin_num`.") +end + +@testset "error checking: bin_width and bin_num not specified" begin + err1 = nothing + t() = error(plot_topoplotseries(df; positions = positions)) + try + t() + catch err1 + end + @test err1 == ErrorException("You haven't specified `bin_width` or `bin_num`. Such option is available only with categorical `mapping.col` or `mapping.row`.") end @testset "toposeries basic with channel names" begin - plot_topoplotseries(df; Δbin, positions = positions, labels = raw_ch_names) + plot_topoplotseries(df; bin_width, positions = positions, labels = raw_ch_names) end @testset "toposeries with xlabel" begin f = Figure() ax = Axis(f[1, 1]) - plot_topoplotseries!(f[1, 1], df; Δbin, positions = positions) + plot_topoplotseries!(f[1, 1], df; bin_width, positions = positions) text!(ax, 0, 0, text = "Time [ms] ", align = (:center, :center), offset = (0, -120)) hidespines!(ax) # delete unnecessary spines (lines) hidedecorations!(ax, label = false) @@ -25,7 +45,7 @@ end end @testset "toposeries for one time point" begin - plot_topoplotseries(df; Δbin, positions = positions, combinefun = x -> x[end÷2]) + plot_topoplotseries(df; bin_width, positions = positions, combinefun = x -> x[end÷2]) end @testset "toposeries with differend comb functions " begin @@ -33,7 +53,7 @@ end plot_topoplotseries!( f[1, 1], df; - Δbin, + bin_width, positions = positions, combinefun = mean, axis = (; title = "combinefun = mean"), @@ -41,7 +61,7 @@ end plot_topoplotseries!( f[2, 1], df; - Δbin, + bin_width, positions = positions, combinefun = median, axis = (; title = "combinefun = median"), @@ -49,7 +69,7 @@ end plot_topoplotseries!( f[3, 1], df; - Δbin, + bin_width, positions = positions, combinefun = std, axis = (; title = "combinefun = std"), @@ -58,7 +78,7 @@ end end @testset "toposeries without colorbar" begin - plot_topoplotseries(df; Δbin, positions = positions, layout = (; use_colorbar = false)) + plot_topoplotseries(df; bin_width, positions = positions, layout = (; use_colorbar = false)) end @testset "GridPosition with a title" begin @@ -67,11 +87,11 @@ end df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) - Δbin = 80 + bin_width = 80 a = plot_topoplotseries!( f[1:2, 1:5], df; - Δbin, + bin_width, positions = positions, layout = (; use_colorbar = true), ) @@ -81,22 +101,21 @@ end f end - @testset "14 topoplots and GridPosition" begin # horrific f = Figure() df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) - Δbin = 30 + bin_width = 30 plot_topoplotseries!( f[1, 1:5], df; - Δbin, + bin_width, positions = positions, - visual = (label_scatter = false,), + visual = (; label_scatter = false), + mapping = (; layout = :time), ) f end - @testset "row faceting, 2 conditions" begin f = Figure() df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) @@ -105,7 +124,7 @@ end plot_topoplotseries!( f[1:2, 1:2], df; - Δbin, + bin_width, col_labels = true, mapping = (; row = :condition), positions = positions, @@ -123,7 +142,7 @@ end plot_topoplotseries!( f[1:2, 1:2], df; - Δbin, + bin_width, col_labels = true, mapping = (; row = :condition), axis = (; ylabel = "Conditions"), @@ -136,7 +155,7 @@ end @testset "facetting by layout" begin df = UnfoldMakie.eeg_matrix_to_dataframe( - dat[:, 200:1:205, 1], + dat[:, 200:1:206, 1], string.(1:length(positions)), ) @@ -144,8 +163,8 @@ end plot_topoplotseries!( f[1:2, 1:2], df; - Δbin = 1, - col_labels = true, + bin_width = 1, + col_labels = false, mapping = (; layout = :time), positions = positions, visual = (label_scatter = false,), @@ -154,21 +173,21 @@ end f end -@testset "toposeries with xlabel" begin - plot_topoplotseries(df; Δbin, positions = positions, axis = (; xlabel = "test")) +@testset "toposeries with specified xlabel" begin + plot_topoplotseries(df; bin_width, positions = positions, axis = (; xlabel = "test")) end @testset "toposeries with adjustable colorrange" begin plot_topoplotseries( df; - Δbin, + bin_width, positions = positions, colorbar = (; colorrange = (-1, 1)), ) end -@testset "toposeries with xlabel" begin - plot_topoplotseries(df; Δbin, positions = positions, axis = (; ylim_topo = (0, 0.7))) +@testset "toposeries with adjusted ylim_topo" begin + plot_topoplotseries(df; bin_width, positions = positions, axis = (; ylim_topo = (0, 0.7))) end #= @testset "basic eeg_topoplot_series" begin @@ -181,7 +200,7 @@ end b = [(1:63) ./ 63 .* a (1:63) ./ 63 .* cos.(range(-2 * pi, 2 * pi, 63))] pos = b .* 0.5 .+ 0.5 # simulated electrode positions pos = [Point2.(pos[k, 1], pos[k, 2]) for k = 1:size(pos, 1)] - UnfoldMakie.eeg_topoplot_series(df; Δbin = 5, positions = pos) + UnfoldMakie.eeg_topoplot_series(df; bin_width = 5, positions = pos) end =# @testset "toposeries with GridSubposition" begin @@ -189,7 +208,7 @@ end =# plot_topoplotseries!( f[2, 1][1, 1], df; - Δbin, + bin_width, positions = positions, combinefun = mean, axis = (; title = "combinefun = mean"), @@ -206,7 +225,7 @@ end plot_topoplotseries!( f[1:2, 1:2], df_obs; - Δbin, + bin_width, col_labels = true, mapping = (; row = :condition), positions = positions, @@ -226,7 +245,6 @@ end obs_tuple = Observable((0, 0, 0)) plot_topoplotseries( df; - Δbin = 0, col_labels = true, mapping = (; col = :condition), positions = positions, @@ -244,7 +262,6 @@ end plot_topoplotseries!( f[1:2, 1:2], df; - Δbin = 0, col_labels = true, mapping = (; col = :condition), positions = positions, From 3c7f69a37f23971460dcb958542a2bd55400a321 Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Fri, 31 May 2024 14:42:01 +0000 Subject: [PATCH 15/34] dividing topopploteseries testing --- test/runtests.jl | 8 +- test/test_toposeries1.jl | 168 +++++++++++++++++++++++++++++++++++++++ test/test_toposeries2.jl | 149 ++++++++++++++++++++++++++++++++++ 3 files changed, 323 insertions(+), 2 deletions(-) create mode 100644 test/test_toposeries1.jl create mode 100644 test/test_toposeries2.jl diff --git a/test/runtests.jl b/test/runtests.jl index 41566155..9d9ed5c1 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -26,8 +26,12 @@ end include("test_topoplot.jl") end -@testset "Topoplot series" begin - include("test_toposeries.jl") +@testset "Topoplot series simple" begin + include("test_toposeries1.jl") +end + +@testset "Topoplot series advanced" begin + include("test_toposeries2.jl") end @testset "Parallel coordinates plot" begin diff --git a/test/test_toposeries1.jl b/test/test_toposeries1.jl new file mode 100644 index 00000000..df897e4d --- /dev/null +++ b/test/test_toposeries1.jl @@ -0,0 +1,168 @@ +# simple checks + +dat, positions = TopoPlots.example_data() +df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) +bin_width = 80 + +@testset "toposeries basic with bin_width" begin + plot_topoplotseries(df; bin_width, positions = positions) +end + +@testset "toposeries basic with bin_num" begin + plot_topoplotseries(df; bin_num = 5, positions = positions) +end + +@testset "toposeries basic with nrows specified" begin + plot_topoplotseries(df; bin_num = 5, nrows = 2, positions = positions) +end + +@testset "error checking: bin_width and bin_num specified" begin + err1 = nothing + t() = error(plot_topoplotseries(df; bin_width, bin_num = 5, positions = positions)) + try + t() + catch err1 + end + @test err1 == + ErrorException("Ambigious parameters: specify only `bin_width` or `bin_num`.") +end + +@testset "error checking: bin_width and bin_num not specified" begin + err1 = nothing + t() = error(plot_topoplotseries(df; positions = positions)) + try + t() + catch err1 + end + @test err1 == ErrorException( + "You haven't specified `bin_width` or `bin_num`. Such option is available only with categorical `mapping.col` or `mapping.row`.", + ) +end + +@testset "toposeries basic with channel names" begin + plot_topoplotseries( + df; + bin_width, + positions = positions, + labels = raw_ch_names, + ) +end # doesnt work rn + +@testset "toposeries with xlabel" begin + f = Figure() + ax = Axis(f[1, 1]) + plot_topoplotseries!(f[1, 1], df; bin_width, positions = positions) + text!(ax, 0, 0, text = "Time [ms] ", align = (:center, :center), offset = (0, -120)) + hidespines!(ax) # delete unnecessary spines (lines) + hidedecorations!(ax, label = false) + f +end + +@testset "toposeries for one time point (?)" begin + plot_topoplotseries(df; bin_width, positions = positions, combinefun = x -> x[end÷2]) +end + +@testset "toposeries with differend comb functions " begin + f = Figure(size = (500, 500)) + plot_topoplotseries!( + f[1, 1], + df; + bin_width, + positions = positions, + combinefun = mean, + axis = (; title = "combinefun = mean"), + ) + plot_topoplotseries!( + f[2, 1], + df; + bin_width, + positions = positions, + combinefun = median, + axis = (; title = "combinefun = median"), + ) + plot_topoplotseries!( + f[3, 1], + df; + bin_width, + positions = positions, + combinefun = std, + axis = (; title = "combinefun = std"), + ) + f +end + +@testset "toposeries without colorbar" begin + plot_topoplotseries( + df; + bin_width, + positions = positions, + layout = (; use_colorbar = false), + ) +end + +@testset "GridPosition with a title" begin + f = Figure() + ax = Axis(f[1:2, 1:5], aspect = DataAspect(), title = "Just a title") + + df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) + + bin_width = 80 + a = plot_topoplotseries!( + f[1:2, 1:5], + df; + bin_width, + positions = positions, + layout = (; use_colorbar = true), + ) + hidespines!(ax) + hidedecorations!(ax, label = false) + + f +end + +@testset "toposeries with specified xlabel" begin + plot_topoplotseries(df; bin_width, positions = positions, axis = (; xlabel = "test")) +end + +@testset "toposeries with adjustable colorrange" begin + plot_topoplotseries( + df; + bin_width, + positions = positions, + colorbar = (; colorrange = (-1, 1)), + ) +end + +@testset "toposeries with adjusted ylim_topo" begin + plot_topoplotseries( + df; + bin_width, + positions = positions, + axis = (; ylim_topo = (0, 0.7)), + ) +end + +#= @testset "basic eeg_topoplot_series" begin + df = DataFrame( + :erp => repeat(1:63, 100), + :time => repeat(1:20, 5 * 63), + :label => repeat(1:63, 100), + ) # simulated data + a = (sin.(range(-2 * pi, 2 * pi, 63))) + b = [(1:63) ./ 63 .* a (1:63) ./ 63 .* cos.(range(-2 * pi, 2 * pi, 63))] + pos = b .* 0.5 .+ 0.5 # simulated electrode positions + pos = [Point2.(pos[k, 1], pos[k, 2]) for k = 1:size(pos, 1)] + UnfoldMakie.eeg_topoplot_series(df; bin_width = 5, positions = pos) +end =# + +@testset "toposeries with GridSubposition" begin + f = Figure(size = (500, 500)) + plot_topoplotseries!( + f[2, 1][1, 1], + df; + bin_width, + positions = positions, + combinefun = mean, + axis = (; title = "combinefun = mean"), + ) +end diff --git a/test/test_toposeries2.jl b/test/test_toposeries2.jl new file mode 100644 index 00000000..5750c7ef --- /dev/null +++ b/test/test_toposeries2.jl @@ -0,0 +1,149 @@ +# advanced features: facetting and interactivity + +dat, positions = TopoPlots.example_data() +df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) +bin_width = 80 + +@testset "14 topoplots and GridPosition" begin # horrific + f = Figure() + df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) + bin_width = 30 + plot_topoplotseries!( + f[1, 1:5], + df; + bin_width, + nrows = 14, + positions = positions, + visual = (; label_scatter = false), + ) + f +end + +@testset "14 topoplots and GridPosition" begin # horrific + f = Figure() + df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) + bin_width = 30 + plot_topoplotseries!( + f[1, 1:5], + df; + bin_width, + nrows = -1, + positions = positions, + visual = (; label_scatter = false), + ) + f +end + +@testset "row faceting, 2 conditions" begin + f = Figure() + df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) + df.condition = repeat(["A", "B"], size(df, 1) ÷ 2) + + plot_topoplotseries!( + f[1:2, 1:2], + df; + bin_width, + col_labels = true, + mapping = (; row = :condition), + positions = positions, + visual = (; label_scatter = false), + layout = (; use_colorbar = true), + ) + f +end + +@testset "row faceting, 5 conditions" begin + df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) + df.condition = repeat(["A", "B", "C", "D", "E"], size(df, 1) ÷ 5) + + f = Figure(size = (600, 500)) + plot_topoplotseries!( + f[1:2, 1:2], + df; + bin_width, + col_labels = true, + mapping = (; row = :condition), + axis = (; ylabel = "Conditions"), + positions = positions, + visual = (label_scatter = false,), + layout = (; use_colorbar = true), + ) + f +end + +@testset "facetting by layout" begin # could be changed to nrwos = "auto" + df = UnfoldMakie.eeg_matrix_to_dataframe( + dat[:, 200:1:206, 1], + string.(1:length(positions)), + ) + + f = Figure(size = (600, 500)) + plot_topoplotseries!( + f[1:2, 1:2], + df; + bin_width = 1, + col_labels = false, + mapping = (; layout = :time), + positions = positions, + visual = (label_scatter = false,), + layout = (; use_colorbar = true), + ) + f +end + + +@testset "interactive data" begin + f = Figure() + df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) + df.condition = repeat(["A", "B"], size(df, 1) ÷ 2) + + df_obs = Observable(df) + + plot_topoplotseries!( + f[1:2, 1:2], + df_obs; + bin_width, + col_labels = true, + mapping = (; row = :condition), + positions = positions, + visual = (label_scatter = false,), + layout = (; use_colorbar = true), + ) + f + df = to_value(df_obs) + df.estimate .= rand(length(df.estimate)) + df_obs[] = df +end + +@testset "interactive scatter markers" begin + df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, 1:2, 1], string.(1:length(positions))) + df.condition = repeat(["A", "B"], size(df, 1) ÷ 2) + + obs_tuple = Observable((0, 0, 0)) + plot_topoplotseries( + df; + col_labels = true, + mapping = (; col = :condition), + positions = positions, + visual = (label_scatter = (markersize = 15, strokewidth = 2),), + layout = (; use_colorbar = true), + interactive_scatter = obs_tuple, + ) +end + +@testset "categorical columns" begin + f = Figure() + df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, 1:2, 1], string.(1:length(positions))) + df.condition = repeat(["A", "B"], size(df, 1) ÷ 2) + + plot_topoplotseries!( + f[1:2, 1:2], + df; + col_labels = true, + mapping = (; col = :condition), + positions = positions, + visual = (label_scatter = false,), + layout = (; use_colorbar = true), + ) + f +end From af47f211c84fbd46641a6a2e67141ecee0ee97da Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Mon, 10 Jun 2024 15:08:37 +0000 Subject: [PATCH 16/34] better separation of cat and contionous toposeries --- src/eeg_series.jl | 59 ++----------- src/plot_topoplotseries.jl | 164 ++++++++++++++++++++++++------------- src/plotconfig.jl | 12 +-- test/test_toposeries1.jl | 19 +++-- test/test_toposeries2.jl | 109 +++++++++++------------- 5 files changed, 177 insertions(+), 186 deletions(-) diff --git a/src/eeg_series.jl b/src/eeg_series.jl index c30404a4..803f9443 100644 --- a/src/eeg_series.jl +++ b/src/eeg_series.jl @@ -117,7 +117,7 @@ function eeg_topoplot_series!( highlight_scatter = false, #Observable([0]), topoplot_attributes..., ) - + @show topoplot_attributes[:label_scatter] # cannot be made easier right now, but Simon promised a simpler solution "soonish" axis_options = ( aspect = 1, @@ -205,6 +205,9 @@ function eeg_topoplot_series!( if highlight_scatter != false || interactive_scatter != nothing strokecolor = Observable(repeat([:black], length(to_value(d_vec)))) highlight_feature = (; strokecolor = strokecolor) + @show highlight_feature + + @show topoplot_attributes[:label_scatter] if :label_scatter ∈ keys(topoplot_attributes) topoplot_attributes = merge( topoplot_attributes, @@ -248,7 +251,8 @@ function eeg_topoplot_series!( if event.button == Mouse.left && event.action == Mouse.press plt, p = pick(single_topoplot) - if isa(plt, Makie.Scatter) && plt == single_topoplot.plots[1].plots[3] + if isa(plt, Makie.Scatter) && + plt == single_topoplot.plots[1].plots[3] plt.strokecolor[] .= :black plt.strokecolor[][p] = :white @@ -270,54 +274,3 @@ function eeg_topoplot_series!( return fig, axlist end - -""" - df_timebin(df, bin_width; col_y = :erp, fun = mean, grouping = []) -Split or combine `DataFrame` according to equally spaced time bins. - -Arguments: -- `df::AbstractTable`\\ - With columns `:time` and `col_y` (default `:erp`), and all columns in `grouping`; -- `bin_width::Real = nothing`\\ - Bin width in `:time` units; -- `bin_num::Real = nothing`\\ - Number of topoplots; -- `col_y = :erp` \\ - The column to combine over (with `fun`); -- `fun = mean()`\\ - Function to combine. -- `grouping = []`\\ - Vector of symbols or strings, columns to group the data by before aggregation. Values of `nothing` are ignored. - -**Return Value:** `DataFrame`. -""" -function df_timebin( - df; - bin_width = nothing, - bin_num = nothing, - col_y = :erp, - fun = mean, - grouping = [], -) - if (bin_width != nothing && bin_num != nothing) - error("Ambigious parameters: specify only `bin_width` or `bin_num`.") - elseif (isnothing(bin_width) && isnothing(bin_num)) - error("You haven't specified `bin_width` or `bin_num`. Such option is available only with categorical `mapping.col` or `mapping.row`.") - end - tmin = minimum(df.time) - tmax = maximum(df.time) - - if isnothing(bin_width) - bins = range(; start = tmin, length = bin_num + 1, stop = tmax) - else - bins = range(; start = tmin, step = bin_width, stop = tmax) - end - 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) - 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_topoplotseries.jl b/src/plot_topoplotseries.jl index ef4f0088..2154b834 100644 --- a/src/plot_topoplotseries.jl +++ b/src/plot_topoplotseries.jl @@ -63,65 +63,58 @@ function plot_topoplotseries!( ) data = _as_observable(data) + positions = get_topo_positions(; positions = positions, labels = labels) + chan_or_label = "label" ∉ names(to_value(data)) ? :channel : :label + config = PlotConfig(:topoplotseries) + @show config.visual # overwrite all defaults by user specified values config_kwargs!(config; kwargs...) # resolve columns with data config.mapping = resolve_mappings(to_value(data), config.mapping) - + @show config.visual cat_or_cont_columns = eltype(to_value(data)[!, config.mapping.col]) <: Number ? "cont" : "cat" + + @show cat_or_cont_columns + data = deepcopy(to_value(data)) if cat_or_cont_columns == "cat" # overwrite Time windows [s] default if categorical config_kwargs!(config; axis = (; xlabel = string(config.mapping.col))) config_kwargs!(config; kwargs...) # add the user specified once more, just if someone specifies the xlabel manually - # overkll as we would only need to check the xlabel ;) - end - - positions = get_topo_positions(; positions = positions, labels = labels) - - chan_or_label = "label" ∉ names(to_value(data)) ? :channel : :label - - # arrangment of topoplots by rows and cols - n_topoplots = number_of_topoplots(to_value(data); bin_width, bin_num) - data = deepcopy(to_value(data)) - tmin = minimum(data.time) - tmax = maximum(data.time) + # overkll as we would only need to check the xlabel ;) + else + # arrangment of topoplots by rows and cols + bins = bins_estimation(data.time; bin_width, bin_num, cat_or_cont_columns) + n_topoplots = number_of_topoplots(data; bin_width, bin_num, bins, config.mapping) - if isnothing(bin_width) - bins = range(; start = tmin, length = bin_num + 1, stop = tmax) + data.timecuts = cut(data.time, bins; extend = true) + unique_cuts = unique(data.timecuts) + ix = findall.(isequal.(unique_cuts), [data.timecuts]) + if :layout ∈ keys(config.mapping) + n_cols = Int(ceil(sqrt(n_topoplots))) + n_rows = Int(ceil(n_topoplots / n_cols)) else - bins = range(; start = tmin, step = bin_width, stop = tmax) + n_rows = nrows + if 0 > n_topoplots / nrows + @warn "Impossible number of rows, set to 1 row" + n_rows = 1 + elseif n_topoplots / nrows < 1 + @warn "Impossible number of rows, set to $(n_topoplots) rows" + end + n_cols = Int(ceil(n_topoplots / n_rows)) end - data.timecuts = cut(data.time, bins; extend = true) - un_layout = unique(data.timecuts) - ix = findall.(isequal.(un_layout), [data.timecuts]) - - #n_topoplots = length(un_layout) # this is just wrong! - if :layout ∈ keys(config.mapping) - n_cols = Int(ceil(sqrt(n_topoplots))) - n_rows = Int(ceil(n_topoplots / n_cols)) - else - n_rows = nrows - n_cols = Int(ceil(n_topoplots / nrows)) - end - _col = repeat(1:n_cols, outer = n_rows)[1:n_topoplots] - _row = repeat(1:n_rows, inner = n_cols)[1:n_topoplots] - data._col .= 1 - data._row .= 1 - for topo = 1:n_topoplots - data._col[ix[topo]] .= _col[topo] - data._row[ix[topo]] .= _row[topo] + _col = repeat(1:n_cols, outer = n_rows)[1:n_topoplots] + _row = repeat(1:n_rows, inner = n_cols)[1:n_topoplots] + data._col .= 1 + data._row .= 1 + for topo = 1:n_topoplots + data._col[ix[topo]] .= _col[topo] + data._row[ix[topo]] .= _row[topo] + end + config_kwargs!(config; mapping = (; row = :_row, col = :_col)) end - data.colrow = map(x->join(x,":"),zip(data._col,string.(data._row))) - #= @show ix[1] - @show _col - @show _row - @show data._col - @show data._row - @show StatsBase.countmap(data.colrow) =# - config_kwargs!(config; mapping = (; row = :_row, col = :_col)) ftopo, axlist = eeg_topoplot_series!( f, @@ -186,28 +179,85 @@ function plot_topoplotseries!( yrectzoom = config.axis.yrectzoom, ) apply_layout_settings!(config; fig = f, ax = ax) + return f end -function number_of_topoplots( - df::DataFrame; - bin_width = nothing, - bin_num = nothing, -) - if (bin_width != nothing && bin_num != nothing) +function bins_estimation(time; bin_width = nothing, bin_num = nothing, cat_or_cont_columns) + tmin = minimum(time) + tmax = maximum(time) + if (!isnothing(bin_width) && !isnothing(bin_num)) error("Ambigious parameters: specify only `bin_width` or `bin_num`.") - elseif (isnothing(bin_width) && isnothing(bin_num)) - error("You haven't specified `bin_width` or `bin_num`. Such option is available only with categorical `mapping.col` or `mapping.row`.") + elseif (isnothing(bin_width) && isnothing(bin_num) && cat_or_cont_columns == "cont") + error( + "You haven't specified `bin_width` or `bin_num`. Such option is available only with categorical `mapping.col` or `mapping.row`.", + ) end - tmin = minimum(df.time) - tmax = maximum(df.time) - if isnothing(bin_width) bins = range(; start = tmin, length = bin_num + 1, stop = tmax) else bins = range(; start = tmin, step = bin_width, stop = tmax) end - time_new = cut(df.time, bins; extend = true) - return length(unique(time_new)) -end \ No newline at end of file + return bins +end + +function number_of_topoplots( + df::DataFrame; + bin_width = nothing, + bin_num = nothing, + bins, + mapping = config.mapping, +) + if !isnothing(bin_width) + time_new = cut(df.time, bins; extend = true) + n = length(unique(time_new)) + elseif !isnothing(bin_num) + time_new = cut(df.time, bins; extend = true) + n = length(unique(time_new)) + else + n = unique(df[:, mapping.col]) + end + return n +end + + +""" + df_timebin(df, bin_width; col_y = :erp, fun = mean, grouping = []) +Split or combine `DataFrame` according to equally spaced time bins. + +Arguments: +- `df::AbstractTable`\\ + With columns `:time` and `col_y` (default `:erp`), and all columns in `grouping`; +- `bin_width::Real = nothing`\\ + Bin width in `:time` units; +- `bin_num::Real = nothing`\\ + Number of topoplots; +- `col_y = :erp` \\ + The column to combine over (with `fun`); +- `fun = mean()`\\ + Function to combine. +- `grouping = []`\\ + Vector of symbols or strings, columns to group the data by before aggregation. Values of `nothing` are ignored. + +**Return Value:** `DataFrame`. +""" +function df_timebin( + df; + bin_width = nothing, + bin_num = nothing, + col_y = :erp, + fun = mean, + grouping = [], +) + bins = bins_estimation(df.time; bin_width, bin_num, cat_or_cont_columns = "cont") + @show bins + 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) + 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/plotconfig.jl b/src/plotconfig.jl index f467fd1d..f1c1201c 100644 --- a/src/plotconfig.jl +++ b/src/plotconfig.jl @@ -105,14 +105,6 @@ function PlotConfig(T::Val{:circtopos}) return cfg end - -function PlotConfig(T::Val{:topoarray}) - cfg = PlotConfig(:erp) - - config_kwargs!(cfg; layout = (;), colorbar = (;), mapping = (;), axis = (;)) - return cfg -end - function PlotConfig(T::Val{:topoplot}) cfg = PlotConfig() @@ -175,7 +167,9 @@ function PlotConfig(T::Val{:topoplotseries}) enlarge = 1, label_scatter = false, ), - mapping = (; col = :time, row = nothing, #layout = nothing + mapping = (; + col = :time, + row = nothing, #layout = nothing ), ) return cfg diff --git a/test/test_toposeries1.jl b/test/test_toposeries1.jl index df897e4d..5cec63d7 100644 --- a/test/test_toposeries1.jl +++ b/test/test_toposeries1.jl @@ -16,6 +16,14 @@ end plot_topoplotseries(df; bin_num = 5, nrows = 2, positions = positions) end +@testset "toposeries basic with nrows specified" begin + plot_topoplotseries(df; bin_num = 5, nrows = 3, positions = positions) +end + +@testset "toposeries basic with nrows specified" begin + plot_topoplotseries(df; bin_num = 5, nrows = -6, positions = positions) +end + @testset "error checking: bin_width and bin_num specified" begin err1 = nothing t() = error(plot_topoplotseries(df; bin_width, bin_num = 5, positions = positions)) @@ -40,12 +48,7 @@ end end @testset "toposeries basic with channel names" begin - plot_topoplotseries( - df; - bin_width, - positions = positions, - labels = raw_ch_names, - ) + plot_topoplotseries(df; bin_width, positions = positions, labels = raw_ch_names) end # doesnt work rn @testset "toposeries with xlabel" begin @@ -70,7 +73,7 @@ end bin_width, positions = positions, combinefun = mean, - axis = (; title = "combinefun = mean"), + axis = (; xlabel = "", title = "combinefun = mean"), ) plot_topoplotseries!( f[2, 1], @@ -78,7 +81,7 @@ end bin_width, positions = positions, combinefun = median, - axis = (; title = "combinefun = median"), + axis = (; xlabel = "", title = "combinefun = median"), ) plot_topoplotseries!( f[3, 1], diff --git a/test/test_toposeries2.jl b/test/test_toposeries2.jl index 5750c7ef..750b23f8 100644 --- a/test/test_toposeries2.jl +++ b/test/test_toposeries2.jl @@ -4,94 +4,105 @@ dat, positions = TopoPlots.example_data() df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) bin_width = 80 -@testset "14 topoplots and GridPosition" begin # horrific +@testset "14 topoplots, 4 rows" begin # horrific f = Figure() df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) bin_width = 30 plot_topoplotseries!( f[1, 1:5], df; - bin_width, - nrows = 14, + bin_num = 14, + nrows = 4, positions = positions, visual = (; label_scatter = false), ) f end -@testset "14 topoplots and GridPosition" begin # horrific - f = Figure() - df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) - bin_width = 30 +@testset "facetting by layout" begin # could be changed to nrwos = "auto" + df = UnfoldMakie.eeg_matrix_to_dataframe( + dat[:, 200:1:206, 1], + string.(1:length(positions)), + ) + + f = Figure(size = (600, 500)) plot_topoplotseries!( - f[1, 1:5], + f[1:2, 1:2], df; - bin_width, - nrows = -1, + bin_width = 1, + mapping = (; layout = :time), positions = positions, - visual = (; label_scatter = false), ) f end -@testset "row faceting, 2 conditions" begin +@testset "categorical columns" begin f = Figure() - df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) + df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, 1:2, 1], string.(1:length(positions))) df.condition = repeat(["A", "B"], size(df, 1) ÷ 2) plot_topoplotseries!( - f[1:2, 1:2], + f[1, 1], df; - bin_width, col_labels = true, - mapping = (; row = :condition), + mapping = (; col = :condition), positions = positions, - visual = (; label_scatter = false), - layout = (; use_colorbar = true), ) f end -@testset "row faceting, 5 conditions" begin - df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) - df.condition = repeat(["A", "B", "C", "D", "E"], size(df, 1) ÷ 5) +@testset "4 conditions" begin + df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, 1:4, 1], string.(1:length(positions))) + df.condition = repeat(["A", "B", "C", "D"], size(df, 1) ÷ 4) f = Figure(size = (600, 500)) plot_topoplotseries!( - f[1:2, 1:2], + f[1, 1], df; - bin_width, + positions = positions, col_labels = true, - mapping = (; row = :condition), axis = (; ylabel = "Conditions"), - positions = positions, - visual = (label_scatter = false,), - layout = (; use_colorbar = true), + mapping = (; col = :condition), ) f end -@testset "facetting by layout" begin # could be changed to nrwos = "auto" - df = UnfoldMakie.eeg_matrix_to_dataframe( - dat[:, 200:1:206, 1], - string.(1:length(positions)), - ) +@testset "4 conditions in 2 rows" begin # TBD + df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, 1:4, 1], string.(1:length(positions))) + df.condition = repeat(["A", "B", "C", "D"], size(df, 1) ÷ 4) f = Figure(size = (600, 500)) plot_topoplotseries!( - f[1:2, 1:2], + f[1, 1], df; - bin_width = 1, - col_labels = false, - mapping = (; layout = :time), + nrows = 2, + positions = positions, + col_labels = true, + axis = (; ylabel = "Conditions"), + mapping = (; col = :condition), + ) + f +end + +@testset "change xlabel" begin + f = Figure() + df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, 1:2, 1], string.(1:length(positions))) + df.condition = repeat(["A", "B"], size(df, 1) ÷ 2) + + plot_topoplotseries!( + f[1, 1], + df; + col_labels = true, + mapping = (; col = :condition), + axis = (; xlabel = "test"), positions = positions, - visual = (label_scatter = false,), - layout = (; use_colorbar = true), ) f end + + @testset "interactive data" begin f = Figure() df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) @@ -106,8 +117,6 @@ end col_labels = true, mapping = (; row = :condition), positions = positions, - visual = (label_scatter = false,), - layout = (; use_colorbar = true), ) f df = to_value(df_obs) @@ -125,25 +134,7 @@ end col_labels = true, mapping = (; col = :condition), positions = positions, - visual = (label_scatter = (markersize = 15, strokewidth = 2),), - layout = (; use_colorbar = true), + visual = (label_scatter = (markersize = 15, strokewidth = 2)), interactive_scatter = obs_tuple, ) end - -@testset "categorical columns" begin - f = Figure() - df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, 1:2, 1], string.(1:length(positions))) - df.condition = repeat(["A", "B"], size(df, 1) ÷ 2) - - plot_topoplotseries!( - f[1:2, 1:2], - df; - col_labels = true, - mapping = (; col = :condition), - positions = positions, - visual = (label_scatter = false,), - layout = (; use_colorbar = true), - ) - f -end From d9e3ab66342c7f715b4b1474e91d0bec0a5fd85f Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Mon, 10 Jun 2024 15:18:17 +0000 Subject: [PATCH 17/34] deleting old test file --- test/test_toposeries.jl | 272 ---------------------------------------- 1 file changed, 272 deletions(-) delete mode 100644 test/test_toposeries.jl diff --git a/test/test_toposeries.jl b/test/test_toposeries.jl deleted file mode 100644 index 6aac7638..00000000 --- a/test/test_toposeries.jl +++ /dev/null @@ -1,272 +0,0 @@ -dat, positions = TopoPlots.example_data() -df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) -bin_width = 80 - -@testset "toposeries basic with bin_width" begin - plot_topoplotseries(df; bin_width, positions = positions) -end - -@testset "toposeries basic with bin_num" begin - plot_topoplotseries(df; bin_num = 5, positions = positions) -end - -@testset "error checking: bin_width and bin_num specified" begin - err1 = nothing - t() = error(plot_topoplotseries(df; bin_width, bin_num = 5, positions = positions)) - try - t() - catch err1 - end - @test err1 == ErrorException("Ambigious parameters: specify only `bin_width` or `bin_num`.") -end - -@testset "error checking: bin_width and bin_num not specified" begin - err1 = nothing - t() = error(plot_topoplotseries(df; positions = positions)) - try - t() - catch err1 - end - @test err1 == ErrorException("You haven't specified `bin_width` or `bin_num`. Such option is available only with categorical `mapping.col` or `mapping.row`.") -end - -@testset "toposeries basic with channel names" begin - plot_topoplotseries(df; bin_width, positions = positions, labels = raw_ch_names) -end - -@testset "toposeries with xlabel" begin - f = Figure() - ax = Axis(f[1, 1]) - plot_topoplotseries!(f[1, 1], df; bin_width, positions = positions) - text!(ax, 0, 0, text = "Time [ms] ", align = (:center, :center), offset = (0, -120)) - hidespines!(ax) # delete unnecessary spines (lines) - hidedecorations!(ax, label = false) - f -end - -@testset "toposeries for one time point" begin - plot_topoplotseries(df; bin_width, positions = positions, combinefun = x -> x[end÷2]) -end - -@testset "toposeries with differend comb functions " begin - f = Figure(size = (500, 500)) - plot_topoplotseries!( - f[1, 1], - df; - bin_width, - positions = positions, - combinefun = mean, - axis = (; title = "combinefun = mean"), - ) - plot_topoplotseries!( - f[2, 1], - df; - bin_width, - positions = positions, - combinefun = median, - axis = (; title = "combinefun = median"), - ) - plot_topoplotseries!( - f[3, 1], - df; - bin_width, - positions = positions, - combinefun = std, - axis = (; title = "combinefun = std"), - ) - f -end - -@testset "toposeries without colorbar" begin - plot_topoplotseries(df; bin_width, positions = positions, layout = (; use_colorbar = false)) -end - -@testset "GridPosition with a title" begin - f = Figure() - ax = Axis(f[1:2, 1:5], aspect = DataAspect(), title = "Just a title") - - df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) - - bin_width = 80 - a = plot_topoplotseries!( - f[1:2, 1:5], - df; - bin_width, - positions = positions, - layout = (; use_colorbar = true), - ) - hidespines!(ax) - hidedecorations!(ax, label = false) - - f -end - -@testset "14 topoplots and GridPosition" begin # horrific - f = Figure() - df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) - bin_width = 30 - plot_topoplotseries!( - f[1, 1:5], - df; - bin_width, - positions = positions, - visual = (; label_scatter = false), - mapping = (; layout = :time), - ) - f -end - -@testset "row faceting, 2 conditions" begin - f = Figure() - df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) - df.condition = repeat(["A", "B"], size(df, 1) ÷ 2) - - plot_topoplotseries!( - f[1:2, 1:2], - df; - bin_width, - col_labels = true, - mapping = (; row = :condition), - positions = positions, - visual = (label_scatter = false,), - layout = (; use_colorbar = true), - ) - f -end - -@testset "row faceting, 5 conditions" begin - df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) - df.condition = repeat(["A", "B", "C", "D", "E"], size(df, 1) ÷ 5) - - f = Figure(size = (600, 500)) - plot_topoplotseries!( - f[1:2, 1:2], - df; - bin_width, - col_labels = true, - mapping = (; row = :condition), - axis = (; ylabel = "Conditions"), - positions = positions, - visual = (label_scatter = false,), - layout = (; use_colorbar = true), - ) - f -end - -@testset "facetting by layout" begin - df = UnfoldMakie.eeg_matrix_to_dataframe( - dat[:, 200:1:206, 1], - string.(1:length(positions)), - ) - - f = Figure(size = (600, 500)) - plot_topoplotseries!( - f[1:2, 1:2], - df; - bin_width = 1, - col_labels = false, - mapping = (; layout = :time), - positions = positions, - visual = (label_scatter = false,), - layout = (; use_colorbar = true), - ) - f -end - -@testset "toposeries with specified xlabel" begin - plot_topoplotseries(df; bin_width, positions = positions, axis = (; xlabel = "test")) -end - -@testset "toposeries with adjustable colorrange" begin - plot_topoplotseries( - df; - bin_width, - positions = positions, - colorbar = (; colorrange = (-1, 1)), - ) -end - -@testset "toposeries with adjusted ylim_topo" begin - plot_topoplotseries(df; bin_width, positions = positions, axis = (; ylim_topo = (0, 0.7))) -end - -#= @testset "basic eeg_topoplot_series" begin - df = DataFrame( - :erp => repeat(1:63, 100), - :time => repeat(1:20, 5 * 63), - :label => repeat(1:63, 100), - ) # simulated data - a = (sin.(range(-2 * pi, 2 * pi, 63))) - b = [(1:63) ./ 63 .* a (1:63) ./ 63 .* cos.(range(-2 * pi, 2 * pi, 63))] - pos = b .* 0.5 .+ 0.5 # simulated electrode positions - pos = [Point2.(pos[k, 1], pos[k, 2]) for k = 1:size(pos, 1)] - UnfoldMakie.eeg_topoplot_series(df; bin_width = 5, positions = pos) -end =# - -@testset "toposeries with GridSubposition" begin - f = Figure(size = (500, 500)) - plot_topoplotseries!( - f[2, 1][1, 1], - df; - bin_width, - positions = positions, - combinefun = mean, - axis = (; title = "combinefun = mean"), - ) -end - -@testset "interactive data" begin - f = Figure() - df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) - df.condition = repeat(["A", "B"], size(df, 1) ÷ 2) - - df_obs = Observable(df) - - plot_topoplotseries!( - f[1:2, 1:2], - df_obs; - bin_width, - col_labels = true, - mapping = (; row = :condition), - positions = positions, - visual = (label_scatter = false,), - layout = (; use_colorbar = true), - ) - f - df = to_value(df_obs) - df.estimate .= rand(length(df.estimate)) - df_obs[] = df -end - -@testset "interactive scatter markers" begin - df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, 1:2, 1], string.(1:length(positions))) - df.condition = repeat(["A", "B"], size(df, 1) ÷ 2) - - obs_tuple = Observable((0, 0, 0)) - plot_topoplotseries( - df; - col_labels = true, - mapping = (; col = :condition), - positions = positions, - visual = (label_scatter = (markersize = 15, strokewidth = 2),), - layout = (; use_colorbar = true), - interactive_scatter = obs_tuple, - ) -end - -@testset "categorical columns" begin - f = Figure() - df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, 1:2, 1], string.(1:length(positions))) - df.condition = repeat(["A", "B"], size(df, 1) ÷ 2) - - plot_topoplotseries!( - f[1:2, 1:2], - df; - col_labels = true, - mapping = (; col = :condition), - positions = positions, - visual = (label_scatter = false,), - layout = (; use_colorbar = true), - ) - f -end From 14e7bd02d9404f79666415f13d9fa3643469c9b6 Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Tue, 11 Jun 2024 11:45:35 +0000 Subject: [PATCH 18/34] compat with new Mkie version for pp --- src/plot_parallelcoordinates.jl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/plot_parallelcoordinates.jl b/src/plot_parallelcoordinates.jl index 614de497..e5323f2b 100644 --- a/src/plot_parallelcoordinates.jl +++ b/src/plot_parallelcoordinates.jl @@ -185,7 +185,7 @@ function parallelcoordinates( 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 a single unique value found in specified color-vec" + @warn "The only a single unique value found in the specified color vector" color = cgrad(colormap, 2)[color_ix] else color = cgrad(colormap, length(un_c))[color_ix] @@ -259,20 +259,19 @@ function parallelcoordinates( ax_pcp = Makie.LineAxis( scene; limits = limits, + dim_convert = Makie.NoDimConversion(), ticks = PCPTicks(), endpoints = axis_endpoints, tickformat = tickformater, axesOptions..., ) + pcp_title!( scene, ax_pcp.attributes.endpoints, ax_labels[i]; titlegap = def[:titlegap], ) - - - append!(axlist, [ax_pcp]) end From 7b4849f58e5f24389e88574e309b76539143f31d Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Tue, 11 Jun 2024 11:54:01 +0000 Subject: [PATCH 19/34] refactoring topoplotseries --- src/eeg_series.jl | 199 ++++++++++++++++++++++--------------- src/plot_topoplotseries.jl | 26 +---- test/setup.jl | 2 +- test/test_toposeries2.jl | 15 ++- 4 files changed, 130 insertions(+), 112 deletions(-) diff --git a/src/eeg_series.jl b/src/eeg_series.jl index 803f9443..85fc4a3a 100644 --- a/src/eeg_series.jl +++ b/src/eeg_series.jl @@ -70,8 +70,8 @@ function eeg_topoplot_series( end # allow to specify bin_width as an keyword for nicer readability -eeg_topoplot_series(data::Union{<:Observable,<:DataFrame,<:AbstractMatrix}; kwargs...) = - eeg_topoplot_series(data; kwargs...) +#eeg_topoplot_series(data::Union{<:Observable,<:DataFrame,<:AbstractMatrix}; kwargs...) = +# eeg_topoplot_series(data; kwargs...) # AbstractMatrix function eeg_topoplot_series!( fig, @@ -117,7 +117,6 @@ function eeg_topoplot_series!( highlight_scatter = false, #Observable([0]), topoplot_attributes..., ) - @show topoplot_attributes[:label_scatter] # cannot be made easier right now, but Simon promised a simpler solution "soonish" axis_options = ( aspect = 1, @@ -169,7 +168,7 @@ function eeg_topoplot_series!( ( colorrange = (q_min, q_max), interp_resolution = (128, 128), - contours = (levels = range(q_min, q_max; length = 7),), + contours = (levels = range(q_min, q_max; length = 7)), ), topoplot_attributes, ) @@ -185,92 +184,134 @@ function eeg_topoplot_series!( axlist = [] for r = 1:length(select_row) for c = 1:length(select_col) - ax = Axis(fig[:, :][r, c]; axis_options...) - # select one topoplot - sel = 1 .== ones(size(to_value(data_mean), 1)) # select all - if !isnothing(col) - sel = sel .&& (to_value(data_mean)[:, col] .== select_col[c]) # subselect - end - if !isnothing(row) - sel = sel .&& (to_value(data_mean)[:, row] .== select_row[r]) # subselect - end - - df_single = @lift($data_mean[sel, :]) + ax = single_topoplot( + fig, + r, + c, + row, + col, + select_row, + select_col, + y, + label, + axis_options, + data_mean, + highlight_scatter, + interactive_scatter, + topoplot_attributes, + col_labels, + row_labels, + rasterize_heatmaps, + ) + push!(axlist, ax) - # select labels - labels = to_value(df_single)[:, label] - # select data - d_vec = @lift($df_single[:, y]) - # plot it - if highlight_scatter != false || interactive_scatter != nothing - strokecolor = Observable(repeat([:black], length(to_value(d_vec)))) - highlight_feature = (; strokecolor = strokecolor) - @show highlight_feature + end + end + if typeof(fig) != GridLayout && typeof(fig) != GridLayoutBase.GridSubposition + colgap!(fig.layout, 0) + end - @show topoplot_attributes[:label_scatter] - if :label_scatter ∈ keys(topoplot_attributes) - topoplot_attributes = merge( - topoplot_attributes, - (; - label_scatter = merge( - topoplot_attributes[:label_scatter], - highlight_feature, - ) - ), - ) - else - topoplot_attributes = - merge(topoplot_attributes, (; label_scatter = highlight_feature)) - end - end - if isempty(to_value(d_vec)) - continue - end - single_topoplot = eeg_topoplot!(ax, d_vec, labels; topoplot_attributes...) - if rasterize_heatmaps - single_topoplot.plots[1].plots[1].rasterize = true - end + return fig, axlist +end - # to put column and row labels - if col_labels == true - if r == length(select_row) && col_labels - ax.xlabel = string(to_value(df_single)[1, col]) - ax.xlabelvisible = true - end - if c == 1 && length(select_row) > 1 && row_labels - ax.ylabel = string(to_value(df_single)[1, row]) - ax.ylabelvisible = true - end - else - ax.xlabelvisible = true - ax.xlabel = string(to_value(df_single).time[1, :][]) - end - if interactive_scatter != false - on(events(single_topoplot).mousebutton) do event - if event.button == Mouse.left && event.action == Mouse.press - plt, p = pick(single_topoplot) +function single_topoplot( + fig, + r, + c, + row, + col, + select_row, + select_col, + y, + label, + axis_options, + data_mean, + highlight_scatter, + interactive_scatter, + topoplot_attributes, + col_labels, + row_labels, + rasterize_heatmaps, +) + ax = Axis(fig[:, :][r, c]; axis_options...) + # select one topoplot + sel = 1 .== ones(size(to_value(data_mean), 1)) # select all + if !isnothing(col) + sel = sel .&& (to_value(data_mean)[:, col] .== select_col[c]) # subselect + end + if !isnothing(row) + sel = sel .&& (to_value(data_mean)[:, row] .== select_row[r]) # subselect + end - if isa(plt, Makie.Scatter) && - plt == single_topoplot.plots[1].plots[3] - plt.strokecolor[] .= :black - plt.strokecolor[][p] = :white - notify(plt.strokecolor) # not sure why this is necessary, but oh well.. + df_single = @lift($data_mean[sel, :]) - interactive_scatter[] = (r, c, p) - end + # select labels + labels = to_value(df_single)[:, label] + # select data + d_vec = @lift($df_single[:, y]) + # plot it + if highlight_scatter != false || interactive_scatter != nothing + strokecolor = Observable(repeat([:black], length(to_value(d_vec)))) + highlight_feature = (; strokecolor = strokecolor) + if :label_scatter ∈ keys(topoplot_attributes) + topoplot_attributes = merge( + topoplot_attributes, + (; + label_scatter = if isa(topoplot_attributes[:label_scatter], NamedTuple) + merge(topoplot_attributes[:label_scatter], highlight_feature) + else + highlight_feature end - end - end + ), + ) - push!(axlist, ax) + else + topoplot_attributes = + merge(topoplot_attributes, (; label_scatter = highlight_feature)) end end - if typeof(fig) != GridLayout && typeof(fig) != GridLayoutBase.GridSubposition - colgap!(fig.layout, 0) + if isempty(to_value(d_vec)) + return + end + single_topoplot = eeg_topoplot!(ax, d_vec, labels; topoplot_attributes...) + if rasterize_heatmaps + single_topoplot.plots[1].plots[1].rasterize = true end - return fig, axlist -end + # to put column and row labels + if col_labels == true + if r == length(select_row) && col_labels + ax.xlabel = string(to_value(df_single)[1, col]) + ax.xlabelvisible = true + end + if c == 1 && length(select_row) > 1 && row_labels + ax.ylabel = string(to_value(df_single)[1, row]) + ax.ylabelvisible = true + end + else + ax.xlabelvisible = true + ax.xlabel = string(to_value(df_single).time[1, :][]) + end + + if interactive_scatter != false + on(events(single_topoplot).mousebutton) do event + if event.button == Mouse.left && event.action == Mouse.press + plt, p = pick(single_topoplot) + + if isa(plt, Makie.Scatter) && plt == single_topoplot.plots[1].plots[3] + + plt.strokecolor[] .= :black + plt.strokecolor[][p] = :white + notify(plt.strokecolor) # not sure why this is necessary, but oh well.. + + interactive_scatter[] = (r, c, p) + end + + end + end + end + return ax +end \ No newline at end of file diff --git a/src/plot_topoplotseries.jl b/src/plot_topoplotseries.jl index 2154b834..8b47d3b1 100644 --- a/src/plot_topoplotseries.jl +++ b/src/plot_topoplotseries.jl @@ -67,23 +67,19 @@ function plot_topoplotseries!( chan_or_label = "label" ∉ names(to_value(data)) ? :channel : :label config = PlotConfig(:topoplotseries) - @show config.visual # overwrite all defaults by user specified values config_kwargs!(config; kwargs...) # resolve columns with data config.mapping = resolve_mappings(to_value(data), config.mapping) - @show config.visual cat_or_cont_columns = eltype(to_value(data)[!, config.mapping.col]) <: Number ? "cont" : "cat" - - @show cat_or_cont_columns data = deepcopy(to_value(data)) if cat_or_cont_columns == "cat" # overwrite Time windows [s] default if categorical config_kwargs!(config; axis = (; xlabel = string(config.mapping.col))) config_kwargs!(config; kwargs...) # add the user specified once more, just if someone specifies the xlabel manually - # overkll as we would only need to check the xlabel ;) + # overkll as we would only need to check the xlabel ;) else # arrangment of topoplots by rows and cols bins = bins_estimation(data.time; bin_width, bin_num, cat_or_cont_columns) @@ -161,27 +157,12 @@ function plot_topoplotseries!( if !config.layout.use_colorbar config_kwargs!(config, layout = (; use_colorbar = false, show_legend = false)) end - ax = Axis( - f[1, 1], - xlabel = config.axis.xlabel, - ylabel = config.axis.ylabel, - title = config.axis.title, - titlesize = config.axis.titlesize, - titlefont = config.axis.titlefont, - ylabelpadding = config.axis.ylabelpadding, - xlabelpadding = config.axis.xlabelpadding, - xpanlock = config.axis.xpanlock, - ypanlock = config.axis.ypanlock, - xzoomlock = config.axis.xzoomlock, - yzoomlock = config.axis.yzoomlock, - xrectzoom = config.axis.xrectzoom, - yrectzoom = config.axis.yrectzoom, + f[1, 1]; + (p for p in pairs(config.axis) if p[1] != :xlim_topo && p[1] != :ylim_topo)..., ) apply_layout_settings!(config; fig = f, ax = ax) - return f - end function bins_estimation(time; bin_width = nothing, bin_num = nothing, cat_or_cont_columns) @@ -251,7 +232,6 @@ function df_timebin( grouping = [], ) bins = bins_estimation(df.time; bin_width, bin_num, cat_or_cont_columns = "cont") - @show bins df = deepcopy(df) # cut seems to change stuff inplace df.time = cut(df.time, bins; extend = true) diff --git a/test/setup.jl b/test/setup.jl index bee46077..821377de 100644 --- a/test/setup.jl +++ b/test/setup.jl @@ -1,6 +1,6 @@ +using CairoMakie using UnfoldSim using Test -using CairoMakie using GeometryBasics using DataFrames using TopoPlots diff --git a/test/test_toposeries2.jl b/test/test_toposeries2.jl index 750b23f8..e7caef4d 100644 --- a/test/test_toposeries2.jl +++ b/test/test_toposeries2.jl @@ -100,22 +100,19 @@ end f end - - - +# use with WGlMakie @testset "interactive data" begin - f = Figure() - df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) + df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, 1:2, 1], string.(1:length(positions))) df.condition = repeat(["A", "B"], size(df, 1) ÷ 2) df_obs = Observable(df) + f = Figure() plot_topoplotseries!( - f[1:2, 1:2], + f[1, 1], df_obs; - bin_width, col_labels = true, - mapping = (; row = :condition), + mapping = (; col = :condition), positions = positions, ) f @@ -134,7 +131,7 @@ end col_labels = true, mapping = (; col = :condition), positions = positions, - visual = (label_scatter = (markersize = 15, strokewidth = 2)), + visual = (; label_scatter = (markersize = 15, strokewidth = 2)), interactive_scatter = obs_tuple, ) end From 604e2d404542edf0dbfc10824008d19bce6c5bcb Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Tue, 11 Jun 2024 15:28:16 +0000 Subject: [PATCH 20/34] compat for butterfly --- src/plot_erp.jl | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/plot_erp.jl b/src/plot_erp.jl index 62a5b334..bee8dd1a 100644 --- a/src/plot_erp.jl +++ b/src/plot_erp.jl @@ -183,7 +183,7 @@ function plot_erp!( end # remove x / y - mappingOthers = deleteKeys(config.mapping, [:x, :y]) + mappingOthers = deleteKeys(config.mapping, [:x, :y, :positions, :lables]) xy_mapp = AlgebraOfGraphics.mapping(config.mapping.x, config.mapping.y; mappingOthers...) @@ -202,7 +202,7 @@ function plot_erp!( basic = basic + addPvalues(plot_data, pvalue, config) end - plotEquation = basic * mapp + plot_equation = basic * mapp f_grid = f[1, 1] # butterfly plot is drawn slightly different @@ -227,19 +227,20 @@ function plot_erp!( allPositions, ) end + if isnothing(colors) - drawing = draw!(f_grid, plotEquation; axis = config.axis) + drawing = draw!(f_grid, plot_equation; axis = config.axis) else drawing = draw!( f_grid, - plotEquation; + plot_equation; axis = config.axis, palettes = (color = colors,), ) end else # draw a normal ERP lineplot - drawing = draw!(f_grid, plotEquation; axis = config.axis) + drawing = draw!(f_grid, plot_equation; axis = config.axis) end apply_layout_settings!(config; fig = f, ax = drawing, drawing = drawing) return f From cf35797fce94c28bcedf33190b1d41a91e999cb8 Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Tue, 11 Jun 2024 15:28:55 +0000 Subject: [PATCH 21/34] small reformatting --- src/eeg_series.jl | 75 +++++++++++++++++++--------------------- src/plot_designmatrix.jl | 56 ++++++++++++++++++++++++------ 2 files changed, 82 insertions(+), 49 deletions(-) diff --git a/src/eeg_series.jl b/src/eeg_series.jl index 85fc4a3a..1d65eed0 100644 --- a/src/eeg_series.jl +++ b/src/eeg_series.jl @@ -114,36 +114,10 @@ function eeg_topoplot_series!( xlim_topo = (-0.25, 1.25), ylim_topo = (-0.25, 1.25), interactive_scatter = nothing, - highlight_scatter = false, #Observable([0]), + highlight_scatter = false, topoplot_attributes..., ) - # cannot be made easier right now, but Simon promised a simpler solution "soonish" - axis_options = ( - 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, - xpanlock = true, - ypanlock = true, - xzoomlock = true, - yzoomlock = true, - xrectzoom = false, - yrectzoom = false, - limits = (xlim_topo, ylim_topo), - ) + axis_options = create_axis_options(xlim_topo, ylim_topo) # aggregate the data over time bins # using same colormap + contour levels for all plots @@ -180,7 +154,7 @@ function eeg_topoplot_series!( if interactive_scatter != nothing @assert isa(interactive_scatter, Observable) end - + axlist = [] for r = 1:length(select_row) for c = 1:length(select_col) @@ -210,11 +184,9 @@ function eeg_topoplot_series!( if typeof(fig) != GridLayout && typeof(fig) != GridLayoutBase.GridSubposition colgap!(fig.layout, 0) end - return fig, axlist end - function single_topoplot( fig, r, @@ -243,8 +215,6 @@ function single_topoplot( if !isnothing(row) sel = sel .&& (to_value(data_mean)[:, row] .== select_row[r]) # subselect end - - df_single = @lift($data_mean[sel, :]) # select labels @@ -267,7 +237,6 @@ function single_topoplot( end ), ) - else topoplot_attributes = merge(topoplot_attributes, (; label_scatter = highlight_feature)) @@ -295,23 +264,51 @@ function single_topoplot( ax.xlabelvisible = true ax.xlabel = string(to_value(df_single).time[1, :][]) end + interctive_toposeries(interactive_scatter, single_topoplot) + return ax +end +function interctive_toposeries(interactive_scatter, single_topoplot) if interactive_scatter != false on(events(single_topoplot).mousebutton) do event if event.button == Mouse.left && event.action == Mouse.press plt, p = pick(single_topoplot) - if isa(plt, Makie.Scatter) && plt == single_topoplot.plots[1].plots[3] - plt.strokecolor[] .= :black plt.strokecolor[][p] = :white notify(plt.strokecolor) # not sure why this is necessary, but oh well.. - interactive_scatter[] = (r, c, p) end - end end end - return ax +end + +function create_axis_options(xlim_topo, ylim_topo) + return( + 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, + xpanlock = true, + ypanlock = true, + xzoomlock = true, + yzoomlock = true, + xrectzoom = false, + yrectzoom = false, + limits = (xlim_topo, ylim_topo), + ) end \ No newline at end of file diff --git a/src/plot_designmatrix.jl b/src/plot_designmatrix.jl index c605efd5..0c936e82 100644 --- a/src/plot_designmatrix.jl +++ b/src/plot_designmatrix.jl @@ -48,7 +48,7 @@ function plot_designmatrix!( ) config = PlotConfig(:designmat) config_kwargs!(config; kwargs...) - designmat = UnfoldMakie.modelmatrices(data) + designmat = UnfoldMakie.modelmatrix(data) if standardize_data designmat = designmat ./ std(designmat, dims = 1) designmat[isinf.(designmat)] .= 1.0 @@ -59,15 +59,33 @@ function plot_designmatrix!( @warn "Sorting does not make sense for time-expanded designmatrices. sort_data has been set to `false`" sort_data = false end - designmat = Matrix(designmat[end÷2-2000:end÷2+2000, :]) + designmat = Matrix(designmat[end÷2-2000:end÷2+2000, :]) # needs a size(designmat) of at least 4000 x Any end if sort_data designmat = Base.sortslices(designmat, dims = 1) end - labels = Unfold.get_coefnames(data) - - lLength = length(labels) + labels0 = replace(Unfold.get_coefnames(data), r"\s*:" => ":") + println(labels0) + if length(split(labels0[1], ": ")) > 1 + labels = map(x -> join(split(x, ": ")[3]), labels0) + labels_top1 = map(x -> join(split(x, ": ")[2]), labels0) + unique_names = String[] + labels_top2 = String[] + for el in labels_top1 + if !in(el, unique_names) + push!(unique_names, el) + push!(labels_top2, el) + else + push!(labels_top2, "") + end + end + println(labels) + println(labels_top1) + println(labels_top2) + println(unique_names) + end + lLength = length(labels0) # only change xticks if we want less then all if (xticks !== nothing && xticks < lLength) @assert(xticks >= 0, "xticks shouldn't be negative") @@ -77,7 +95,7 @@ function plot_designmatrix!( # first tick. Empty if 0 ticks if xticks >= 1 - push!(newLabels, labels[1]) + push!(newLabels, labels0[1]) else push!(newLabels, "") end @@ -86,7 +104,7 @@ function plot_designmatrix!( 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 < ((xticks - 1) * sectionSize) - push!(newLabels, labels[i+1]) + push!(newLabels, labels0[i+1]) else push!(newLabels, "") end @@ -94,14 +112,26 @@ function plot_designmatrix!( # last tick at the end if xticks >= 2 - push!(newLabels, labels[lLength-1]) + push!(newLabels, labels0[lLength-1]) else push!(newLabels, "") end - labels = newLabels + labels0 = newLabels + end + if length(split(labels0[1], ": ")) > 1 + ax2 = Axis( + f[1, 1], + xticklabelcolor = :red, + xaxisposition = :top; + xticks = (1:length(labels_top2), labels_top2), + ) + hidespines!(ax2) + hidexdecorations!(ax2, ticklabels = false, ticks = false) + hm = heatmap!(ax2, designmat'; config.visual...) + else + labels = labels0 end - # plot Designmatrix config.axis = merge(config.axis, (; xticks = (1:length(labels), labels))) @@ -112,7 +142,13 @@ function plot_designmatrix!( ax.yreversed = true end + + + apply_layout_settings!(config; fig = f, hm = hm) return f end +# Unfold.extract_coef_info.(Unfold.get_coefnames.(designmatrix(td)),3) +# use it! +# vcat(Unfold.extract_coef_info.(Unfold.get_coefnames.(designmatrix(td)),3)...) From 8248d2e63aefcf8c50b50a83df74a2d3675c658c Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Tue, 11 Jun 2024 15:46:29 +0000 Subject: [PATCH 22/34] bug --- test/test_butterfly.jl | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/test/test_butterfly.jl b/test/test_butterfly.jl index b59ef198..58e2de93 100644 --- a/test/test_butterfly.jl +++ b/test/test_butterfly.jl @@ -2,15 +2,26 @@ include("../docs/example_data.jl") df, pos = example_data("TopoPlots.jl") -@testset "butterfly basic" begin - plot_butterfly(df; positions = pos) +@testset "butterfly default" begin + plot_butterfly(df; positions = pos, visual = (; transparency = true)) + #save("dev/UnfoldMakie/default_butterfly.png", f) end -@testset "butterfly basic with GridLayout" begin +@testset "butterfly default with GridLayout" begin f = Figure() plot_butterfly!(f[1, 1], df; positions = pos) end +@testset "butterfly basic" begin + plot_butterfly( + df; + positions = pos, + topopositions_to_color = x -> Colors.RGB(0.1), + topolegend = false, + ) + #save("dev/UnfoldMakie/basic_butterfly.png", f) +end + @testset "butterfly with change of topomarkersize" begin plot_butterfly( df; @@ -153,7 +164,7 @@ end #TO DO # not working -@testset "butterfly with two size highlighted channels" begin +#= @testset "butterfly with two size highlighted channels" begin df.highlight = in.(df.channel, Ref([10, 12])) plot_butterfly(df; positions = pos, mapping = (; linesize = :highlight)) -end +end =# From 3a7a6a3ac017e8584de34f0b40bc04f99e50c189 Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Thu, 13 Jun 2024 10:52:05 +0000 Subject: [PATCH 23/34] last bug from incompatibility --- src/eeg_series.jl | 6 +++--- src/plot_circular_topoplots.jl | 4 +++- src/plot_topoplotseries.jl | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/eeg_series.jl b/src/eeg_series.jl index 1d65eed0..48489fa3 100644 --- a/src/eeg_series.jl +++ b/src/eeg_series.jl @@ -154,7 +154,7 @@ function eeg_topoplot_series!( if interactive_scatter != nothing @assert isa(interactive_scatter, Observable) end - + axlist = [] for r = 1:length(select_row) for c = 1:length(select_col) @@ -285,7 +285,7 @@ function interctive_toposeries(interactive_scatter, single_topoplot) end function create_axis_options(xlim_topo, ylim_topo) - return( + return ( aspect = 1, xgridvisible = false, xminorgridvisible = false, @@ -311,4 +311,4 @@ function create_axis_options(xlim_topo, ylim_topo) yrectzoom = false, limits = (xlim_topo, ylim_topo), ) -end \ No newline at end of file +end diff --git a/src/plot_circular_topoplots.jl b/src/plot_circular_topoplots.jl index 8d5a959c..a22f4452 100644 --- a/src/plot_circular_topoplots.jl +++ b/src/plot_circular_topoplots.jl @@ -220,7 +220,6 @@ function plot_topo_plots!( end function calculate_BBox(origin, widths, predictor_value, bounds, plot_radius) - minwidth = minimum(widths) predictor_ratio = (predictor_value - bounds[1]) / (bounds[2] - bounds[1]) radius = (minwidth * plot_radius) / 2 # radius of the position circle of a circular topoplot @@ -241,6 +240,9 @@ function calculate_BBox(origin, widths, predictor_value, bounds, plot_radius) # right point of the axis. This means that you have to # move the bbox to the bottom left by size_of_bbox/2 to move # the center of the axis to a point. + if abs(y) < 1 + y = round(y, digits = 2) + end return BBox( (origin[1] + widths[1]) / 2 - size_of_BBox / 2 + x, (origin[1] + widths[1]) / 2 + size_of_BBox - size_of_BBox / 2 + x, diff --git a/src/plot_topoplotseries.jl b/src/plot_topoplotseries.jl index 8b47d3b1..871d9d0c 100644 --- a/src/plot_topoplotseries.jl +++ b/src/plot_topoplotseries.jl @@ -79,7 +79,7 @@ function plot_topoplotseries!( # overwrite Time windows [s] default if categorical config_kwargs!(config; axis = (; xlabel = string(config.mapping.col))) config_kwargs!(config; kwargs...) # add the user specified once more, just if someone specifies the xlabel manually - # overkll as we would only need to check the xlabel ;) + # overkll as we would only need to check the xlabel ;) else # arrangment of topoplots by rows and cols bins = bins_estimation(data.time; bin_width, bin_num, cat_or_cont_columns) From 9631a93467c5122c21cf1287d6491af61eeac30a Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Thu, 13 Jun 2024 11:12:39 +0000 Subject: [PATCH 24/34] adjusting docs for toposeries --- docs/literate/tutorials/topoplotseries.jl | 56 +++++++++++++++-------- test/test_butterfly.jl | 1 - test/test_circular_topoplots.jl | 2 +- test/test_toposeries2.jl | 1 - 4 files changed, 38 insertions(+), 22 deletions(-) diff --git a/docs/literate/tutorials/topoplotseries.jl b/docs/literate/tutorials/topoplotseries.jl index a4016735..8326decc 100644 --- a/docs/literate/tutorials/topoplotseries.jl +++ b/docs/literate/tutorials/topoplotseries.jl @@ -42,7 +42,7 @@ plot_topoplotseries!( bin_width, positions = positions, combinefun = mean, - axis = (; title = "combinefun = mean"), + axis = (; xlabel = "", title = "combinefun = mean"), ) plot_topoplotseries!( f[2, 1], @@ -50,7 +50,7 @@ plot_topoplotseries!( bin_width, positions = positions, combinefun = median, - axis = (; title = "combinefun = median"), + axis = (; xlabel = "", title = "combinefun = median"), ) plot_topoplotseries!( f[3, 1], @@ -66,36 +66,54 @@ f #= If you need to plot many topoplots, you should display them in multiple rows. - -Here you can specify: -- Grouping condition using `mapping.row`. -- Label the y-axis with `axis.ylabel`. -- Hide electrode markers with `visual.label_scatter`. -- Change the color map with `visual.colormap`. The default is `Reverse(:RdBu)`. -- Adjust the limits of the topoplot boxes with `axis.xlim_topo` and `axis.ylim_topo`. By default both are `(-0.25, 1.25)`. -- Adjust the size of the figure with `Figure(size = (x, y))`. -- Adjust the padding between topoplot labels and axis labels using `xlabelpadding` and `ylabelpadding`. =# + +f = Figure() df1 = UnfoldMakie.eeg_matrix_to_dataframe(data[:, :, 1], string.(1:length(positions))) -df1.condition = repeat(["A", "B", "C", "D", "E"], size(df, 1) ÷ 5) +plot_topoplotseries!( + f[1, 1:5], + df1; + bin_num = 14, + nrows = 4, + positions = positions, + visual = (; label_scatter = false), +) +f + +# ## Categorical topoplots + +#= +If you decide to use categorical values instead of time intvervals for sepration of topoplots do this: +- Do not specify bin_width or bin_num +- Put categorical value in mapping.col +=# + +df2 = UnfoldMakie.eeg_matrix_to_dataframe(data[:, 1:5, 1], string.(1:length(positions))) +df2.condition = repeat(["A", "B", "C", "D", "E"], size(df1, 1) ÷ 5) f = Figure(size = (600, 500)) plot_topoplotseries!( - f[1:2, 1:2], - df1; - bin_width, + f[1, 1], + df2; col_labels = true, - mapping = (; row = :condition), - axis = (; ylabel = "Conditions"), + mapping = (; col = :condition), + axis = (; xlabel = "Conditions"), positions = positions, - visual = (label_scatter = false,), - layout = (; use_colorbar = true), ) f # # Configurations of Topoplot series +#= +Also you can specify: +- Label the x-axis with `axis.xlabel`. +- Hide electrode markers with `visual.label_scatter`. +- Change the color map with `visual.colormap`. The default is `Reverse(:RdBu)`. +- Adjust the limits of the topoplot boxes with `axis.xlim_topo` and `axis.ylim_topo`. By default both are `(-0.25, 1.25)`. +- Adjust the size of the figure with `Figure(size = (x, y))`. +- Adjust the padding between topoplot labels and axis labels using `xlabelpadding` and `ylabelpadding`. +=# # ```@docs # plot_topoplotseries # ``` diff --git a/test/test_butterfly.jl b/test/test_butterfly.jl index 58e2de93..79e427ae 100644 --- a/test/test_butterfly.jl +++ b/test/test_butterfly.jl @@ -162,7 +162,6 @@ end end #TO DO - # not working #= @testset "butterfly with two size highlighted channels" begin df.highlight = in.(df.channel, Ref([10, 12])) diff --git a/test/test_circular_topoplots.jl b/test/test_circular_topoplots.jl index f364d247..26f6e1f4 100644 --- a/test/test_circular_topoplots.jl +++ b/test/test_circular_topoplots.jl @@ -57,7 +57,7 @@ end @testset "testing calculate_BBox" begin @test UnfoldMakie.calculate_BBox([0, 0], [1000, 1000], 180, [0, 360], 0.8) == - BBox(0.0, 200.0, 400.0, 600.0) + BBox(0.0, 200.0, 400.0, 600) @test UnfoldMakie.calculate_BBox([0, 0], [1000, 1000], -45, [0, 360], 0.8) == BBox(682.842712474619, 882.842712474619, 117.15728752538104, 317.15728752538104) @test UnfoldMakie.calculate_BBox([0, 0], [1000, 1000], -180, [-180, 180], 0.8) == diff --git a/test/test_toposeries2.jl b/test/test_toposeries2.jl index e7caef4d..83a0acf8 100644 --- a/test/test_toposeries2.jl +++ b/test/test_toposeries2.jl @@ -7,7 +7,6 @@ bin_width = 80 @testset "14 topoplots, 4 rows" begin # horrific f = Figure() df = UnfoldMakie.eeg_matrix_to_dataframe(dat[:, :, 1], string.(1:length(positions))) - bin_width = 30 plot_topoplotseries!( f[1, 1:5], df; From 5ce397efa9c603f63365cd364704826c13d3ff12 Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Thu, 13 Jun 2024 11:30:25 +0000 Subject: [PATCH 25/34] bug --- docs/literate/tutorials/topoplotseries.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/literate/tutorials/topoplotseries.jl b/docs/literate/tutorials/topoplotseries.jl index 8326decc..0e96d21d 100644 --- a/docs/literate/tutorials/topoplotseries.jl +++ b/docs/literate/tutorials/topoplotseries.jl @@ -89,7 +89,7 @@ If you decide to use categorical values instead of time intvervals for sepration =# df2 = UnfoldMakie.eeg_matrix_to_dataframe(data[:, 1:5, 1], string.(1:length(positions))) -df2.condition = repeat(["A", "B", "C", "D", "E"], size(df1, 1) ÷ 5) +df2.condition = repeat(["A", "B", "C", "D", "E"], size(df2, 1) ÷ 5) f = Figure(size = (600, 500)) From 81a034594e8dae29f2209d5e1b48af3f492ab404 Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Thu, 13 Jun 2024 11:42:19 +0000 Subject: [PATCH 26/34] issue 182 --- docs/literate/how_to/hide_deco.jl | 4 ++-- docs/literate/how_to/mult_vis_in_fig.jl | 2 +- docs/literate/intro/code_principles.jl | 1 + docs/literate/tutorials/butterfly.jl | 2 +- src/plot_erp.jl | 8 ++++---- test/test_butterfly.jl | 4 ++-- test/test_complexplots.jl | 2 +- test/test_dm.jl | 9 +++++++-- 8 files changed, 19 insertions(+), 13 deletions(-) create mode 100644 docs/literate/intro/code_principles.jl diff --git a/docs/literate/how_to/hide_deco.jl b/docs/literate/how_to/hide_deco.jl index 4f9f7dbd..86b26b6a 100644 --- a/docs/literate/how_to/hide_deco.jl +++ b/docs/literate/how_to/hide_deco.jl @@ -28,7 +28,7 @@ plot_butterfly!( data; positions = pos, topomarkersize = 10, - topoheigth = 0.4, + topoheight = 0.4, topowidth = 0.4, axis = (; title = "With decorations"), ) @@ -37,7 +37,7 @@ plot_butterfly!( data; positions = pos, topomarkersize = 10, - topoheigth = 0.4, + topoheight = 0.4, topowidth = 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 05cd535f..31a32118 100644 --- a/docs/literate/how_to/mult_vis_in_fig.jl +++ b/docs/literate/how_to/mult_vis_in_fig.jl @@ -157,7 +157,7 @@ plot_butterfly!( d_topo; positions = pos, topomarkersize = 10, - topoheigth = 0.4, + topoheight = 0.4, topowidth = 0.4, ) hlines!(0, color = :gray, linewidth = 1) diff --git a/docs/literate/intro/code_principles.jl b/docs/literate/intro/code_principles.jl new file mode 100644 index 00000000..381e4d77 --- /dev/null +++ b/docs/literate/intro/code_principles.jl @@ -0,0 +1 @@ +#TBD \ No newline at end of file diff --git a/docs/literate/tutorials/butterfly.jl b/docs/literate/tutorials/butterfly.jl index 11c6256e..c8af4dcb 100644 --- a/docs/literate/tutorials/butterfly.jl +++ b/docs/literate/tutorials/butterfly.jl @@ -38,7 +38,7 @@ plot_butterfly(df; positions = pos) # You want to change size of topomarkers and size of topoplot: -plot_butterfly(df; positions = pos, topomarkersize = 10, topoheigth = 0.4, topowidth = 0.4) +plot_butterfly(df; positions = pos, topomarkersize = 10, topoheight = 0.4, topowidth = 0.4) # You want to add vline and hline: diff --git a/src/plot_erp.jl b/src/plot_erp.jl index bee8dd1a..a0d555cc 100644 --- a/src/plot_erp.jl +++ b/src/plot_erp.jl @@ -56,7 +56,7 @@ Plot a Butterfly plot. Change the size of the electrode markers in topoplot. - `topowidth::Real = 0.25` \\ Change the width of inlay topoplot. -- `topoheigth::Real = 0.25` \\ +- `topoheight::Real = 0.25` \\ Change the height of inlay topoplot. - `topopositions_to_color::x -> pos_to_color_RomaO(x)`\\ Change the line colors. @@ -80,7 +80,7 @@ plot_butterfly!( topolegend = true, topomarkersize = 10, topowidth = 0.25, - topoheigth = 0.25, + topoheight = 0.25, topopositions_to_color = x -> pos_to_color_RomaO(x), kwargs..., ) @@ -98,7 +98,7 @@ function plot_erp!( topolegend = nothing, topomarkersize = nothing, topowidth = nothing, - topoheigth = nothing, + topoheight = nothing, topopositions_to_color = nothing, mapping = (;), kwargs..., @@ -213,7 +213,7 @@ function plot_erp!( topoAxis = Axis( f_grid, width = Relative(topowidth), - height = Relative(topoheigth), + height = Relative(topoheight), halign = 0.05, valign = 0.95, aspect = 1, diff --git a/test/test_butterfly.jl b/test/test_butterfly.jl index 79e427ae..c4734e6d 100644 --- a/test/test_butterfly.jl +++ b/test/test_butterfly.jl @@ -27,7 +27,7 @@ end df; positions = pos, topomarkersize = 10, - topoheigth = 0.4, + topoheight = 0.4, topowidth = 0.4, ) end @@ -58,7 +58,7 @@ end df; positions = pos, topomarkersize = 10, - topoheigth = 0.4, + topoheight = 0.4, topowidth = 0.4, layout = (; hidedecorations = (:label => true, :ticks => true, :ticklabels => true) diff --git a/test/test_complexplots.jl b/test/test_complexplots.jl index 1561f1aa..cb74ad4d 100644 --- a/test/test_complexplots.jl +++ b/test/test_complexplots.jl @@ -34,7 +34,7 @@ d_topo; positions = pos, topomarkersize = 10, - topoheigth = 0.4, + topoheight = 0.4, topowidth = 0.4, ) hlines!(0, color = :gray, linewidth = 1) diff --git a/test/test_dm.jl b/test/test_dm.jl index 867022a7..5586fbf0 100644 --- a/test/test_dm.jl +++ b/test/test_dm.jl @@ -24,9 +24,14 @@ end @testset "hierarchical labels (bugged)" begin df, evts = UnfoldSim.predef_eeg() f = @formula 0 ~ 1 + condition + continuous - #basisfunction = firbasis(τ = (-0.4, 0.8), sfreq = 100, name = "stimulus") - basisfunction = firbasis(τ = (-0.4, -0.3), sfreq = 10, name = "") + basisfunction = firbasis(τ = (-0.4, 0.8), sfreq = 5, name = "stimulus") + #basisfunction = firbasis(τ = (-0.4, -0.3), sfreq = 10, name = "") bfDict = [Any => (f, basisfunction)] td = fit(UnfoldModel, bfDict, evts, df) plot_designmatrix(designmatrix(td)) end + + +#julia > Unfold.SimpleTraits.istrait(Unfold.ContinuousTimeTrait{typeof(td)}) + +#julia > Unfold.SimpleTraits.istrait(Unfold.ContinuousTimeTrait{typeof(uf)}) From 54cf06f3c0f500f8bb1b75bb8d68213b947f0a26 Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Thu, 13 Jun 2024 12:11:01 +0000 Subject: [PATCH 27/34] issue 183 --- docs/literate/intro/code_principles.jl | 2 +- src/plot_erp.jl | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/literate/intro/code_principles.jl b/docs/literate/intro/code_principles.jl index 381e4d77..77fc024a 100644 --- a/docs/literate/intro/code_principles.jl +++ b/docs/literate/intro/code_principles.jl @@ -1 +1 @@ -#TBD \ No newline at end of file +#TBD diff --git a/src/plot_erp.jl b/src/plot_erp.jl index a0d555cc..6af876ac 100644 --- a/src/plot_erp.jl +++ b/src/plot_erp.jl @@ -81,6 +81,9 @@ plot_butterfly!( topomarkersize = 10, topowidth = 0.25, topoheight = 0.25, + topohalign = 0.05, + topovalign = 0.95, + topoaspect = 1, topopositions_to_color = x -> pos_to_color_RomaO(x), kwargs..., ) @@ -207,16 +210,15 @@ function plot_erp!( f_grid = f[1, 1] # butterfly plot is drawn slightly different if butterfly - # no extra legend # add topolegend if (topolegend) topoAxis = Axis( f_grid, width = Relative(topowidth), height = Relative(topoheight), - halign = 0.05, - valign = 0.95, - aspect = 1, + halign = topohalign, + valign = topovalign, + aspect = topoaspect, ) ix = unique(i -> plot_data[:, config.mapping.group[1]][i], 1:size(plot_data, 1)) topoplotLegend( @@ -227,7 +229,6 @@ function plot_erp!( allPositions, ) end - if isnothing(colors) drawing = draw!(f_grid, plot_equation; axis = config.axis) else From 9c2b2218f0c0fde6780b0b4d9e9ed37ff07d21f6 Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Thu, 13 Jun 2024 12:29:12 +0000 Subject: [PATCH 28/34] bug --- src/plot_erp.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/plot_erp.jl b/src/plot_erp.jl index 6af876ac..66ac1841 100644 --- a/src/plot_erp.jl +++ b/src/plot_erp.jl @@ -103,6 +103,9 @@ function plot_erp!( topowidth = nothing, topoheight = nothing, topopositions_to_color = nothing, + topohalign = 0.05, + topovalign = 0.95, + topoaspect = 1, mapping = (;), kwargs..., ) From bfe5628fef132c4ac93057e0bf1242b24e3cfd39 Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Thu, 13 Jun 2024 14:10:08 +0000 Subject: [PATCH 29/34] code principles (draft) --- docs/literate/intro/code_principles.jl | 11 ++++++++++- docs/make.jl | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/literate/intro/code_principles.jl b/docs/literate/intro/code_principles.jl index 77fc024a..28ded972 100644 --- a/docs/literate/intro/code_principles.jl +++ b/docs/literate/intro/code_principles.jl @@ -1 +1,10 @@ -#TBD +# # Code principles +#= 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 +=# diff --git a/docs/make.jl b/docs/make.jl index 01e932b5..015759cf 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -38,6 +38,7 @@ makedocs(; "Intro" => [ "Installation" => "generated/intro/installation.md", "Plot types" => "generated/intro/plot_types.md", + "Code principles" => "generated/intro/code_principles.md", ], "Visualization Types" => [ "ERP plot" => "generated/tutorials/erp.md", From 5f24e32a1035b3639588f542a8204140d586cdaa Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Thu, 13 Jun 2024 17:16:00 +0000 Subject: [PATCH 30/34] i hate this stupid buggy package --- docs/literate/intro/code_principles.jl | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/literate/intro/code_principles.jl b/docs/literate/intro/code_principles.jl index 28ded972..3ac8df4b 100644 --- a/docs/literate/intro/code_principles.jl +++ b/docs/literate/intro/code_principles.jl @@ -1,10 +1,11 @@ # # Code principles -#= 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 -=# + +# 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 From 218cb664e20f80ae123a856543ab2b867ec5a15a Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Thu, 13 Jun 2024 18:06:52 +0000 Subject: [PATCH 31/34] text clarification --- docs/literate/tutorials/butterfly.jl | 2 +- docs/literate/tutorials/channel_image.jl | 2 +- docs/literate/tutorials/circ_topo.jl | 5 +++++ docs/literate/tutorials/erp.jl | 2 +- docs/literate/tutorials/erp_grid.jl | 4 ++++ docs/literate/tutorials/erpimage.jl | 2 +- docs/literate/tutorials/parallelcoordinates.jl | 2 +- docs/literate/tutorials/topoplot.jl | 2 +- docs/literate/tutorials/topoplotseries.jl | 6 +++--- 9 files changed, 18 insertions(+), 9 deletions(-) diff --git a/docs/literate/tutorials/butterfly.jl b/docs/literate/tutorials/butterfly.jl index c8af4dcb..3e30912f 100644 --- a/docs/literate/tutorials/butterfly.jl +++ b/docs/literate/tutorials/butterfly.jl @@ -1,5 +1,5 @@ # # [Butterfly Plot](@id bfp_vis) -# Butterfly plot is a plot type for visualisation of Event-related potentials. +# **Butterfly plot** is a plot type for visualisation of Event-related potentials. # It can fully represent time and channels dimensions using lines. With addition of topoplot inset it can also represent location of channels. # It called "butterfly" because the envelope of channels reminds butterfly wings🦋. diff --git a/docs/literate/tutorials/channel_image.jl b/docs/literate/tutorials/channel_image.jl index 9a56a357..c73a9a7b 100644 --- a/docs/literate/tutorials/channel_image.jl +++ b/docs/literate/tutorials/channel_image.jl @@ -1,6 +1,6 @@ # # Channel image -# Channel image is a plot type for visualizing EEG activity for all channels. +# **Channel image** is a plot type for visualizing EEG activity for all channels. # It can fully represent time and channel dimensions using a heatmap. # Y-axis represents all channels, x-axis represents time, while color represents voltage. diff --git a/docs/literate/tutorials/circ_topo.jl b/docs/literate/tutorials/circ_topo.jl index f6b34d6a..f8be30f5 100644 --- a/docs/literate/tutorials/circ_topo.jl +++ b/docs/literate/tutorials/circ_topo.jl @@ -1,5 +1,10 @@ # # Circular Topoplots +# **Circular topoplot series** is a plot type for visualizing EEG activity in relation to some continous variable arranged on a circluar line. +# It can fully represent channel and channel location dimensions using contour lines. It can also partially represent the varaible dimension. +# Variable could be for instance accadic amplitude or degrees of visual angle. +# Basically, it is a series of Topoplots arranged on a circle. + # # Setup # ## Package loading diff --git a/docs/literate/tutorials/erp.jl b/docs/literate/tutorials/erp.jl index 41d39d12..721063ba 100644 --- a/docs/literate/tutorials/erp.jl +++ b/docs/literate/tutorials/erp.jl @@ -1,6 +1,6 @@ # # [ERP Plot](@id erp_vis) -# ERP plot is plot type for visualisation of [Event-related potentials](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3016705/). +# **ERP plot** is plot type for visualisation of [Event-related potentials](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3016705/). # It can fully represent time and experimental condition dimensions using lines. # # Setup diff --git a/docs/literate/tutorials/erp_grid.jl b/docs/literate/tutorials/erp_grid.jl index 9e36b1e4..c0004e19 100644 --- a/docs/literate/tutorials/erp_grid.jl +++ b/docs/literate/tutorials/erp_grid.jl @@ -1,4 +1,8 @@ # # ERP grid +# **ERP grid** is a plot type for visualisation of Event-related potentials. +# It can fully represent time, channel, and layout dimensions using lines. It can also partially represent condition dimensions. +# Lines are displayed on a grid. The location of each axis represents the location of the electrode. +# This plot type is not as popular because it is too cluttered. # # Setup # ## Package loading diff --git a/docs/literate/tutorials/erpimage.jl b/docs/literate/tutorials/erpimage.jl index c8488bfe..9af55605 100644 --- a/docs/literate/tutorials/erpimage.jl +++ b/docs/literate/tutorials/erpimage.jl @@ -1,6 +1,6 @@ # # ERP image -# ERP image is a plot type for visualizing EEG activity for all trials. +# **ERP image** is a plot type for visualizing EEG activity for all trials. # It can fully represent time and trial dimensions using a heatmap. # Y-axis represents all trials, x-axis represents time, while color represents voltage. # The ERP image can also be sorted by specific experimental variables, which helps to reveal important correlations. diff --git a/docs/literate/tutorials/parallelcoordinates.jl b/docs/literate/tutorials/parallelcoordinates.jl index f6241b66..87f3f461 100644 --- a/docs/literate/tutorials/parallelcoordinates.jl +++ b/docs/literate/tutorials/parallelcoordinates.jl @@ -1,6 +1,6 @@ # # Parallel Coordinates -# Parallel Coordinates Plot (PCP) is a plot type used to visualize EEG activity for some channels. +# **Parallel Coordinates Plot** (PCP) is a plot type used to visualize EEG activity for some channels. # It can fully represent state and channel dimensions using lines. It can also partially represent time or trials # Y-axis represents time points, vertical axes represent channels, while lines show voltage changes. diff --git a/docs/literate/tutorials/topoplot.jl b/docs/literate/tutorials/topoplot.jl index 7a87fc1e..8eab2c91 100644 --- a/docs/literate/tutorials/topoplot.jl +++ b/docs/literate/tutorials/topoplot.jl @@ -1,5 +1,5 @@ # # [Topoplot](@id topo_vis) -# Topoplot (aka topography plot) is a plot type for visualisation of EEG activity in a specific time stemp or time interval. +# **Topoplot** (aka topography plot) is a plot type for visualisation of EEG activity in a specific time stemp or time interval. # It can fully represent channel and channel location dimensions using contour lines. # The topoplot is a 2D projection and interpolation of the 3D distributed sensor activity. The name stems from physical geography, but instead of height, the contour lines represent voltage levels. diff --git a/docs/literate/tutorials/topoplotseries.jl b/docs/literate/tutorials/topoplotseries.jl index 0e96d21d..03d9f9fb 100644 --- a/docs/literate/tutorials/topoplotseries.jl +++ b/docs/literate/tutorials/topoplotseries.jl @@ -1,6 +1,6 @@ # # Topoplot Series -# Topoplot Series is a plot type for visualizing EEG activity in a given time frame or time interval. +# **Topoplot series** is a plot type for visualizing EEG activity in a given time frame or time interval. # It can fully represent channel and channel location dimensions using contour lines. It can also partially represent the time dimension. # Basically, it is a series of Topoplots. @@ -84,8 +84,8 @@ f #= If you decide to use categorical values instead of time intvervals for sepration of topoplots do this: -- Do not specify bin_width or bin_num -- Put categorical value in mapping.col +- Do not specify `bin_width` or `bin_num` +- Put categorical value in `mapping.col` =# df2 = UnfoldMakie.eeg_matrix_to_dataframe(data[:, 1:5, 1], string.(1:length(positions))) From fb2d3a02acfb44b0b0dcca13547e697d92c2d6d3 Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Mon, 17 Jun 2024 12:52:25 +0000 Subject: [PATCH 32/34] after review --- docs/literate/tutorials/erp_grid.jl | 2 +- src/plot_designmatrix.jl | 12 ++++-------- src/plot_topoplotseries.jl | 12 ++++++++---- src/plotconfig.jl | 5 +---- test/test_dm.jl | 5 ++--- test/test_toposeries1.jl | 8 ++++++++ 6 files changed, 24 insertions(+), 20 deletions(-) diff --git a/docs/literate/tutorials/erp_grid.jl b/docs/literate/tutorials/erp_grid.jl index c0004e19..0ebfaccc 100644 --- a/docs/literate/tutorials/erp_grid.jl +++ b/docs/literate/tutorials/erp_grid.jl @@ -1,6 +1,6 @@ # # ERP grid # **ERP grid** is a plot type for visualisation of Event-related potentials. -# It can fully represent time, channel, and layout dimensions using lines. It can also partially represent condition dimensions. +# It can fully represent time, channel, and layout (channel locations) dimensions using lines. It can also partially represent condition dimensions. # Lines are displayed on a grid. The location of each axis represents the location of the electrode. # This plot type is not as popular because it is too cluttered. diff --git a/src/plot_designmatrix.jl b/src/plot_designmatrix.jl index 0c936e82..aa0e6ab8 100644 --- a/src/plot_designmatrix.jl +++ b/src/plot_designmatrix.jl @@ -65,13 +65,13 @@ function plot_designmatrix!( if sort_data designmat = Base.sortslices(designmat, dims = 1) end - labels0 = replace(Unfold.get_coefnames(data), r"\s*:" => ":") - println(labels0) + labels0 = replace.(Unfold.get_coefnames(data), r"\s*:" => ":") + if length(split(labels0[1], ": ")) > 1 labels = map(x -> join(split(x, ": ")[3]), labels0) - labels_top1 = map(x -> join(split(x, ": ")[2]), labels0) + labels_top1 = Unfold.extract_coef_info(Unfold.get_coefnames(data), 2) unique_names = String[] - labels_top2 = String[] + labels_top2 = String[""] for el in labels_top1 if !in(el, unique_names) push!(unique_names, el) @@ -80,10 +80,6 @@ function plot_designmatrix!( push!(labels_top2, "") end end - println(labels) - println(labels_top1) - println(labels_top2) - println(unique_names) end lLength = length(labels0) # only change xticks if we want less then all diff --git a/src/plot_topoplotseries.jl b/src/plot_topoplotseries.jl index 871d9d0c..bb7de4ee 100644 --- a/src/plot_topoplotseries.jl +++ b/src/plot_topoplotseries.jl @@ -8,7 +8,7 @@ Multiple miniature topoplots in regular distances. - `f::Union{GridPosition, GridLayout, GridLayoutBase.GridSubposition, Figure}`\\ `Figure`, `GridLayout`, `GridPosition`, or GridLayoutBase.GridSubposition to draw the plot. - `data::Union{<:Observable{<:DataFrame},DataFrame}`\\ - DataFrame with data or Observable DataFrame. Requires a `time` column. + DataFrame with data or Observable DataFrame. Requires a `time` column, but could be also specified in mapping.x. ## Keyword arguments (kwargs) - `bin_width::Real = nothing`\\ @@ -26,7 +26,7 @@ Multiple miniature topoplots in regular distances. Except for the interpolated heatmap, all lines/points are vectors.\\ This is typically what you want, otherwise you get ~128x128 vectors per topoplot, which makes everything super slow. - `col_labels::Bool`, `row_labels::Bool = true`\\ - Shows column and row labels for categorical values (?). + Shows column and row labels for categorical values. - `labels::Vector{String} = nothing`\\ Show labels for each electrode. - `positions::Vector{Point{2, Float32}} = nothing`\\ @@ -35,6 +35,8 @@ Multiple miniature topoplots in regular distances. Enable interactive mode. \\ If you create `obs_tuple = Observable((0, 0, 0))` and pass it into `interactive_scatter` you can change observable indecies by clicking topopplot markers.\\ `(0, 0, 0)` corresponds to the indecies of row of topoplot layout, column of topoplot layout and channell. +- `mapping.x = :time`\\ + Specification of x value, could be any contionous variable. - `mapping.layout = nothing`\\ When equals `:time` arrange topoplots by rows. @@ -46,6 +48,8 @@ $(_docstring(:topoplotseries)) plot_topoplotseries(data::DataFrame; kwargs...) = plot_topoplotseries!(Figure(), data; kwargs...) +#@deprecate plot_topoplotseries(data::DataFrame, Δbin; kwargs...) plot_topoplotseries(data::DataFrame; bin_width, kwargs...) + function plot_topoplotseries!( f::Union{GridPosition,GridLayout,Figure,GridLayoutBase.GridSubposition}, data::Union{<:Observable{<:DataFrame},DataFrame}; @@ -74,7 +78,7 @@ function plot_topoplotseries!( config.mapping = resolve_mappings(to_value(data), config.mapping) cat_or_cont_columns = eltype(to_value(data)[!, config.mapping.col]) <: Number ? "cont" : "cat" - data = deepcopy(to_value(data)) + data = (to_value(data)) if cat_or_cont_columns == "cat" # overwrite Time windows [s] default if categorical config_kwargs!(config; axis = (; xlabel = string(config.mapping.col))) @@ -165,7 +169,7 @@ function plot_topoplotseries!( return f end -function bins_estimation(time; bin_width = nothing, bin_num = nothing, cat_or_cont_columns) +function bins_estimation(time; bin_width = nothing, bin_num = nothing, cat_or_cont_columns = "cont") tmin = minimum(time) tmax = maximum(time) if (!isnothing(bin_width) && !isnothing(bin_num)) diff --git a/src/plotconfig.jl b/src/plotconfig.jl index f1c1201c..aaefc906 100644 --- a/src/plotconfig.jl +++ b/src/plotconfig.jl @@ -167,10 +167,7 @@ function PlotConfig(T::Val{:topoplotseries}) enlarge = 1, label_scatter = false, ), - mapping = (; - col = :time, - row = nothing, #layout = nothing - ), + mapping = (; col = (:time,), row = (nothing,)), ) return cfg end diff --git a/test/test_dm.jl b/test/test_dm.jl index 5586fbf0..5fe401e2 100644 --- a/test/test_dm.jl +++ b/test/test_dm.jl @@ -32,6 +32,5 @@ end end -#julia > Unfold.SimpleTraits.istrait(Unfold.ContinuousTimeTrait{typeof(td)}) - -#julia > Unfold.SimpleTraits.istrait(Unfold.ContinuousTimeTrait{typeof(uf)}) +#Unfold.SimpleTraits.istrait(Unfold.ContinuousTimeTrait{typeof(td)}) +#Unfold.SimpleTraits.istrait(Unfold.ContinuousTimeTrait{typeof(uf)}) diff --git a/test/test_toposeries1.jl b/test/test_toposeries1.jl index 5cec63d7..86e802f0 100644 --- a/test/test_toposeries1.jl +++ b/test/test_toposeries1.jl @@ -12,6 +12,14 @@ end plot_topoplotseries(df; bin_num = 5, positions = positions) end +@testset "toposeries basic: checking mapping" begin + plot_topoplotseries(df; bin_num = 5, positions = positions, mapping = (; x = df.time)) +end + +#= @testset "toposeries with Δbin deprecated" begin #fail + plot_topoplotseries(df, Δbin; positions = positions) +end =# + @testset "toposeries basic with nrows specified" begin plot_topoplotseries(df; bin_num = 5, nrows = 2, positions = positions) end From 351c93a9a9befb9b87306e8f4952221cb24ad0f9 Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Mon, 17 Jun 2024 13:27:39 +0000 Subject: [PATCH 33/34] formatting and library --- Project.toml | 1 - src/plot_topoplotseries.jl | 7 ++++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 36dedc28..b362d471 100644 --- a/Project.toml +++ b/Project.toml @@ -23,7 +23,6 @@ MakieThemes = "e296ed71-da82-5faf-88ab-0034a9761098" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" -StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" TopoPlots = "2bdbdf9c-dbd8-403f-947b-1a4e0dd41a7a" Unfold = "181c99d8-e21b-4ff3-b70b-c233eddec679" diff --git a/src/plot_topoplotseries.jl b/src/plot_topoplotseries.jl index bb7de4ee..612b3102 100644 --- a/src/plot_topoplotseries.jl +++ b/src/plot_topoplotseries.jl @@ -169,7 +169,12 @@ function plot_topoplotseries!( return f end -function bins_estimation(time; bin_width = nothing, bin_num = nothing, cat_or_cont_columns = "cont") +function bins_estimation( + time; + bin_width = nothing, + bin_num = nothing, + cat_or_cont_columns = "cont", +) tmin = minimum(time) tmax = maximum(time) if (!isnothing(bin_width) && !isnothing(bin_num)) From f5598b0241b6b19d17280cb8e22cf6db8f12606c Mon Sep 17 00:00:00 2001 From: Vladimir Mikheev Date: Mon, 17 Jun 2024 14:00:32 +0000 Subject: [PATCH 34/34] bug@ --- src/UnfoldMakie.jl | 1 - src/plot_designmatrix.jl | 20 ++++++++++---------- src/plot_erp.jl | 20 ++++++++++---------- src/plot_erpimage.jl | 1 - 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/UnfoldMakie.jl b/src/UnfoldMakie.jl index b12cb2c4..39298277 100644 --- a/src/UnfoldMakie.jl +++ b/src/UnfoldMakie.jl @@ -25,7 +25,6 @@ using DataFrames using SparseArrays using CategoricalArrays # for cut for TopoPlotSeries using StaticArrays -using StatsBase using CoordinateTransformations # for 3D positions to 2D diff --git a/src/plot_designmatrix.jl b/src/plot_designmatrix.jl index aa0e6ab8..bea1fb62 100644 --- a/src/plot_designmatrix.jl +++ b/src/plot_designmatrix.jl @@ -86,34 +86,34 @@ function plot_designmatrix!( if (xticks !== nothing && xticks < lLength) @assert(xticks >= 0, "xticks shouldn't be negative") # sections between xticks - sectionSize = (lLength - 2) / (xticks - 1) - newLabels = [] + section_size = (lLength - 2) / (xticks - 1) + new_labels = [] # first tick. Empty if 0 ticks if xticks >= 1 - push!(newLabels, labels0[1]) + push!(new_labels, labels0[1]) else - push!(newLabels, "") + push!(new_labels, "") end # fill in ticks in the middle 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 < ((xticks - 1) * sectionSize) - push!(newLabels, labels0[i+1]) + if i % section_size < 1 && i < ((xticks - 1) * section_size) + push!(new_labels, labels0[i+1]) else - push!(newLabels, "") + push!(new_labels, "") end end # last tick at the end if xticks >= 2 - push!(newLabels, labels0[lLength-1]) + push!(new_labels, labels0[lLength-1]) else - push!(newLabels, "") + push!(new_labels, "") end - labels0 = newLabels + labels0 = new_labels end if length(split(labels0[1], ": ")) > 1 ax2 = Axis( diff --git a/src/plot_erp.jl b/src/plot_erp.jl index 66ac1841..573e0b30 100644 --- a/src/plot_erp.jl +++ b/src/plot_erp.jl @@ -146,13 +146,13 @@ function plot_erp!( topolegend = false colors = nothing else - allPositions = get_topo_positions(; positions = positions, labels = labels) + all_positions = get_topo_positions(; positions = positions, labels = labels) if (config.visual.colormap !== nothing) colors = config.visual.colormap un = length(unique(plot_data[:, config.mapping.color])) colors = cgrad(config.visual.colormap, un, categorical = true) else - colors = get_topo_color(allPositions, topopositions_to_color) + colors = get_topo_color(all_positions, topopositions_to_color) end end end @@ -229,7 +229,7 @@ function plot_erp!( topomarkersize, plot_data[ix, config.mapping.color[1]], colors, - allPositions, + all_positions, ) end if isnothing(colors) @@ -263,10 +263,10 @@ function eegHeadMatrix(positions, center, radius) end # topopositions_to_color = colors? -function topoplotLegend(axis, topomarkersize, unique_val, colors, allPositions) - allPositions = unique(allPositions) +function topoplotLegend(axis, topomarkersize, unique_val, colors, all_positions) + all_positions = unique(all_positions) - topoMatrix = eegHeadMatrix(allPositions, (0.5, 0.5), 0.5) + topoMatrix = eegHeadMatrix(all_positions, (0.5, 0.5), 0.5) un = unique(unique_val) specialColors = ColorScheme( @@ -277,11 +277,11 @@ function topoplotLegend(axis, topomarkersize, unique_val, colors, allPositions) ylims!(low = -0.2, high = 1.2) topoplot = eeg_topoplot!( axis, - 1:length(allPositions), # go from 1:npos - string.(1:length(allPositions)); - positions = allPositions, + 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 specialColorsmap - colorrange = (0, length(allPositions)), # add the 0 for the white-first color + colorrange = (0, length(all_positions)), # add the 0 for the white-first color colormap = specialColors, head = (color = :black, linewidth = 1, model = topoMatrix), label_scatter = (markersize = topomarkersize, strokewidth = 0.5), diff --git a/src/plot_erpimage.jl b/src/plot_erpimage.jl index 7d6af6ef..f0637aef 100644 --- a/src/plot_erpimage.jl +++ b/src/plot_erpimage.jl @@ -125,7 +125,6 @@ function plot_erpimage!( xlabel = "Time [s]", xlabelpadding = 0, xautolimitmargin = (0, 0), - #xticks = @lift([round(minimum($sortvalues), digits=2), round(mean($sortvalues), digits=2), round(maximum($sortvalues), digits=2)]), limits = @lift(( minimum($times), maximum($times),