Repository: aeye-lab/pymovements
Pull request #339 (2023-04-27 07:51:27)

feat(plot): Use GazeDataFrame in traceplot()

codecov-commenter: ## [Codecov](https://codecov.io/gh/aeye-lab/pymovements/pull/339?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) Report
> :exclamation: No coverage uploaded for pull request base (`main@3104661`). [Click here to learn what that means](https://docs.codecov.io/docs/error-reference?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#section-missing-base-commit).
> Patch coverage: 90.00% of modified lines in pull request are covered.

> :exclamation: Current head 469fc90 differs from pull request most recent head 30fce72. Consider uploading reports for the commit 30fce72 to get more accurate results

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main     #339   +/-   ##
=======================================
  Coverage        ?   99.87%           
=======================================
  Files           ?       44           
  Lines           ?     1633           
  Branches        ?      354           
=======================================
  Hits            ?     1631           
  Misses          ?        1           
  Partials        ?        1           
```


| [Impacted Files](https://codecov.io/gh/aeye-lab/pymovements/pull/339?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab) | Coverage Δ | |
|---|---|---|
| [src/pymovements/plotting/traceplot.py](https://codecov.io/gh/aeye-lab/pymovements/pull/339?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab#diff-c3JjL3B5bW92ZW1lbnRzL3Bsb3R0aW5nL3RyYWNlcGxvdC5weQ==) | `97.05% <90.00%> (ø)` | |


</details>

[:umbrella: View full report in Codecov by Sentry](https://codecov.io/gh/aeye-lab/pymovements/pull/339?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aeye-lab).


Patch:
diff --git a/src/pymovements/plotting/traceplot.py b/src/pymovements/plotting/traceplot.py
index c9d01c2..6d543f4 100644
--- a/src/pymovements/plotting/traceplot.py
+++ b/src/pymovements/plotting/traceplot.py
@@ -28,6 +28,9 @@ import numpy as np
 from matplotlib import colors
 from matplotlib.collections import LineCollection
 
+from pymovements.gaze.gaze_dataframe import GazeDataFrame
+
+
 DEFAULT_SEGMENTDATA = {
     'red': [
         [0.0, 0.0, 0.0],
@@ -71,8 +74,9 @@ DEFAULT_SEGMENTDATA_TWOSLOPE = {
 
 
 def traceplot(
-        x: np.ndarray,
-        y: np.ndarray,
+        gaze: GazeDataFrame,
+        x: str,
+        y: str,
         cval: np.ndarray | None = None,
         cmap: colors.Colormap | None = None,
         cmap_norm: colors.Normalize | str | None = None,
@@ -91,10 +95,12 @@ def traceplot(
 
     Parameters
     ----------
-    x: np.ndarray
-        x-coordinates
-    y: np.ndarray
-        y-coordinates
+    gaze: GazeDataFrame
+        The GazeDataFrame to plot.
+    x: str
+        Column name of x-coordinates
+    y: str
+        Column name of y-coordinates
     cval: np.ndarray
         line color values.
     cmap: matplotlib.colors.Colormap, optional
@@ -126,13 +132,10 @@ def traceplot(
         If length of x and y coordinates do not match or if ``cmap_norm`` is unknown.
 
     """
+    x_signal = gaze.frame[x]
+    y_signal = gaze.frame[y]
 
-    if len(x) != len(y):
-        raise ValueError(
-            'x and y do not share same length '
-            f'({len(x)} != {len(y)})',
-        )
-    n = len(x)
+    n = len(x_signal)
 
     fig = plt.figure(figsize=figsize)
     ax = fig.gca()
@@ -188,7 +191,7 @@ def traceplot(
     # This creates the points as a N x 1 x 2 array so that we can stack points
     # together easily to get the segments. The segments array for line collection
     # needs to be (numlines) x (points per line) x 2 (for x and y)
-    points = np.array([x, y]).T.reshape((-1, 1, 2))
+    points = np.array([x_signal, y_signal]).T.reshape((-1, 1, 2))
     segments = np.concatenate([points[:-1], points[1:]], axis=1)
 
     # Create a continuous norm to map from data points to colors
@@ -199,14 +202,14 @@ def traceplot(
     line = ax.add_collection(line_collection)
 
     if padding is None:
-        x_pad = (np.nanmax(x) - np.nanmin(x)) * pad_factor
-        y_pad = (np.nanmax(y) - np.nanmin(y)) * pad_factor
+        x_pad = (np.nanmax(x_signal) - np.nanmin(x_signal)) * pad_factor
+        y_pad = (np.nanmax(y_signal) - np.nanmin(y_signal)) * pad_factor
     else:
         x_pad = padding
         y_pad = padding
 
-    ax.set_xlim(np.nanmin(x) - x_pad, np.nanmax(x) + x_pad)
-    ax.set_ylim(np.nanmin(y) - y_pad, np.nanmax(y) + y_pad)
+    ax.set_xlim(np.nanmin(x_signal) - x_pad, np.nanmax(x_signal) + x_pad)
+    ax.set_ylim(np.nanmin(y_signal) - y_pad, np.nanmax(y_signal) + y_pad)
 
     if show_cbar:
         # sm = matplotlib.cm.ScalarMappable(cmap=cmap, norm=cmap_norm)


Test patch:
diff --git a/tests/plotting/traceplot_test.py b/tests/plotting/traceplot_test.py
index e58065b..c99c273 100644
--- a/tests/plotting/traceplot_test.py
+++ b/tests/plotting/traceplot_test.py
@@ -26,76 +26,116 @@ import numpy as np
 import pytest
 from matplotlib import figure
 
-from pymovements.plotting import traceplot
+import pymovements as pm
 
 
-@pytest.fixture(name='args')
-def args_fixture():
+@pytest.fixture(name='gaze')
+def gaze_fixture():
     x = np.arange(-100, 100)
     y = np.arange(-100, 100)
+    arr = np.column_stack((x, y)).transpose()
 
-    return x, y
+    experiment = pm.Experiment(
+        screen_width_px=1280,
+        screen_height_px=1024,
+        screen_width_cm=38,
+        screen_height_cm=30,
+        distance_cm=68,
+        origin='lower left',
+        sampling_rate=1000.0,
+    )
+
+    gaze = pm.gaze.from_numpy(
+        data=arr,
+        schema=['x_pix', 'y_pix'],
+        experiment=experiment,
+    )
+
+    gaze.pix2deg()
+    gaze.pos2vel()
+
+    return gaze
 
 
 @pytest.mark.parametrize(
     'kwargs',
     [
-        pytest.param({}, id='no_kwargs'),
-        pytest.param({'cval': np.arange(-100, 100)}, id='cval_array'),
+        pytest.param({'x': 'x_pix', 'y': 'y_pix', 'cval': np.arange(-100, 100)}, id='cval_array'),
         pytest.param(
-            {'cval': np.arange(-100, 100), 'cmap_norm': 'twoslope'},
+            {'x': 'x_pix', 'y': 'y_pix', 'cval': np.arange(-100, 100), 'cmap_norm': 'twoslope'},
             id='cmap_norm_twoslope',
         ),
         pytest.param(
-            {'cval': np.arange(0, 200), 'cmap_norm': 'nonorm'},
+            {'x': 'x_pix', 'y': 'y_pix', 'cval': np.arange(0, 200), 'cmap_norm': 'nonorm'},
             id='cmap_norm_nonorm',
         ),
         pytest.param(
-            {'cval': np.arange(0, 200)},
+            {'x': 'x_pix', 'y': 'y_pix', 'cval': np.arange(0, 200)},
             id='cmap_norm_nonorm_implicit',
         ),
         pytest.param(
-            {'cval': np.arange(-100, 100), 'cmap_norm': 'normalize'},
+            {'x': 'x_pix', 'y': 'y_pix', 'cval': np.arange(-100, 100), 'cmap_norm': 'normalize'},
             id='cmap_norm_normalize',
         ),
         pytest.param(
-            {'cval': np.arange(0, 200), 'cmap_norm': 'linear'},
+            {'x': 'x_pix', 'y': 'y_pix', 'cval': np.arange(0, 200), 'cmap_norm': 'linear'},
             id='cmap_norm_linear',
         ),
         pytest.param(
-            {'cval': np.arange(0, 200), 'cmap_norm': matplotlib.colors.NoNorm()},
+            {
+                'x': 'x_pix', 'y': 'y_pix', 'cval': np.arange(0, 200),
+                'cmap_norm': matplotlib.colors.NoNorm(),
+            },
             id='cmap_norm_class',
         ),
         pytest.param(
-            {'cmap': matplotlib.colors.LinearSegmentedColormap(name='test', segmentdata={})},
+            {
+                'x': 'x_pix', 'y': 'y_pix',
+                'cmap': matplotlib.colors.LinearSegmentedColormap(name='test', segmentdata={}),
+            },
             id='cmap_class',
         ),
-        pytest.param({'cmap_segmentdata': {}}, id='cmap_segmentdata'),
-        pytest.param({'padding': 0.1}, id='padding'),
-        pytest.param({'cval': np.arange(0, 200), 'show_cbar': True}, id='show_cbar_true'),
-        pytest.param({'cval': np.arange(0, 200), 'show_cbar': False}, id='show_cbar_false'),
+        pytest.param({'x': 'x_pix', 'y': 'y_pix', 'cmap_segmentdata': {}}, id='cmap_segmentdata'),
+        pytest.param({'x': 'x_pix', 'y': 'y_pix', 'padding': 0.1}, id='padding'),
+        pytest.param(
+            {'x': 'x_pix', 'y': 'y_pix', 'cval': np.arange(0, 200), 'show_cbar': True},
+            id='show_cbar_true',
+        ),
+        pytest.param(
+            {'x': 'x_pix', 'y': 'y_pix', 'cval': np.arange(0, 200), 'show_cbar': False},
+            id='show_cbar_false',
+        ),
     ],
 )
-def test_traceplot_show(args, kwargs, monkeypatch):
+def test_traceplot_show(gaze, kwargs, monkeypatch):
     mock = Mock()
     monkeypatch.setattr(plt, 'show', mock)
-    traceplot(*args, **kwargs)
+    pm.plotting.traceplot(gaze=gaze, **kwargs)
     plt.close()
     mock.assert_called_once()
 
 
-def test_traceplot_noshow(args, monkeypatch):
+def test_traceplot_noshow(gaze, monkeypatch):
     mock = Mock()
     monkeypatch.setattr(plt, 'show', mock)
-    traceplot(*args, show=False)
+    pm.plotting.traceplot(gaze=gaze, x='x_pix', y='y_pix', show=False)
     plt.close()
     mock.assert_not_called()
 
 
-def test_traceplot_save(args, monkeypatch, tmp_path):
+def test_traceplot_save(gaze, monkeypatch, tmp_path):
     mock = Mock()
     monkeypatch.setattr(figure.Figure, 'savefig', mock)
-    traceplot(*args, show=False, savepath=str(tmp_path / 'test.svg'))
+    pm.plotting.traceplot(
+        gaze=gaze,
+        x='x_pix',
+        y='y_pix',
+        show=False,
+        savepath=str(
+            tmp_path /
+            'test.svg',
+        ),
+    )
     plt.close()
     mock.assert_called_once()
 
@@ -103,15 +143,8 @@ def test_traceplot_save(args, monkeypatch, tmp_path):
 @pytest.mark.parametrize(
     ('kwargs', 'exception'),
     [
-        pytest.param(
-            {'x': np.arange(100), 'y': np.arange(11)},
-            ValueError,
-            id='different_lengths',
-        ),
         pytest.param(
             {
-                'x': np.arange(-100, 100),
-                'y': np.arange(-100, 100),
                 'cval': np.arange(0, 200),
                 'cmap_norm': 'invalid',
             },
@@ -120,9 +153,9 @@ def test_traceplot_save(args, monkeypatch, tmp_path):
         ),
     ],
 )
-def test_traceplot_exceptions(kwargs, exception, monkeypatch):
+def test_traceplot_exceptions(gaze, kwargs, exception, monkeypatch):
     mock = Mock()
     monkeypatch.setattr(plt, 'show', mock)
 
     with pytest.raises(exception):
-        traceplot(**kwargs)
+        pm.plotting.traceplot(gaze=gaze, x='x_pix', y='y_pix', **kwargs)