Repository: nilearn/nilearn

Issue:
[BUG] In plot_to_surf vmin is set to 0 despite not setting a threshold
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Operating system

- [ ] Linux
- [X] Mac
- [ ] Windows

### Operating system version

Mac OS Version 13.4.1 "Ventura"

### Python version

- [ ] 3.11
- [ ] 3.10
- [ ] 3.9
- [X] 3.8
- [ ] 3.7

### nilearn version

Dev version in #3942 

### Expected behavior

The minimum value in the colorbar should be set by `vmin` when the user does not provide a `threshold`.

### Current behavior & error messages

This is what I got:

```python
# Paste the error message here
UserWarning:

choosing both vmin and a threshold is not allowed; setting vmin to 0
```

I assume `threshold` has a numerical value by default (rather than `None`) and that's why `vmin` is set to 0.

### Steps and code to reproduce bug

```python
# Paste your code here
cmap = hm_viridis_onesided
# cmap = "viridis"
vmax = 4
# vmin = vmax/data.shape[1] * 60
vmin = 2
event_count = masker.transform(op.join(out_dir, "data_event_count_avg.nii.gz"))
event_count = np.squeeze(event_count)


texture = vol_to_surf(utils.color_rois(out_dir, mask, event_count), fsaverage.pial_left,interpolation='nearest',radius =1, n_samples=1)
surf_plot1=plotting.plot_surf_roi(fsaverage.infl_left, texture, hemi='left', cmap = cmap, colorbar=True, vmin = vmin, vmax = vmax, bg_map=fsaverage.sulc_left, engine="plotly")
```



Discussion:
Thx for reporting.
This probably a side effect of making vmin explicit, which was not done in the original implementation.
It looks like a bug for the plotly engine. Works as it should for matplotlib. Here's a code to more quickly reproduce the warning and buggy behaviorr:

```python
from nilearn import datasets
from nilearn import plotting

destrieux_atlas = datasets.fetch_atlas_surf_destrieux()
parcellation = destrieux_atlas['map_left']

fsaverage = datasets.fetch_surf_fsaverage()
plot_1 = plotting.plot_surf_roi(
            fsaverage.infl_left,
            parcellation,
            hemi="left",
            cmap="viridis_r",
            vmin=2,
            vmax=50,
            colorbar=True,
            bg_map=fsaverage.sulc_left,
            engine="plotly",
         )
plot_1.show()
```

I'll follow up with a PR to fix

Resolving patch:
diff --git a/nilearn/plotting/js_plotting_utils.py b/nilearn/plotting/js_plotting_utils.py
--- a/nilearn/plotting/js_plotting_utils.py
+++ b/nilearn/plotting/js_plotting_utils.py
@@ -80,11 +80,6 @@ def colorscale(cmap, values, threshold=None, symmetric_cmap=True,
     if symmetric_cmap and vmin is not None:
         warnings.warn('vmin cannot be chosen when cmap is symmetric')
         vmin = None
-    if threshold is not None:
-        if vmin is not None:
-            warnings.warn('choosing both vmin and a threshold is not allowed; '
-                          'setting vmin to 0')
-        vmin = 0
     if vmax is None:
         vmax = abs_values.max()
     # cast to float to avoid TypeError if vmax is a numpy boolean


Test patch:
diff --git a/nilearn/plotting/tests/test_js_plotting_utils.py b/nilearn/plotting/tests/test_js_plotting_utils.py
--- a/nilearn/plotting/tests/test_js_plotting_utils.py
+++ b/nilearn/plotting/tests/test_js_plotting_utils.py
@@ -121,16 +121,13 @@ def test_colorscale_symmetric_cmap(vmin, vmax):
 
 
 @pytest.fixture
-def expected_vmin_vmax(values, vmax, vmin, threshold):
+def expected_vmin_vmax(values, vmax, vmin):
     """Returns expected vmin and vmax."""
-    if threshold is None:
-        if vmax is None:
-            return (min(values), max(values))
-        if min(values) < 0:
-            return (-vmax, vmax)
-        return (min(values), vmax) if vmin is None else (vmin, vmax)
-    else:
-        return (0, vmax)
+    if vmax is None:
+        return (min(values), max(values))
+    if min(values) < 0:
+        return (-vmax, vmax)
+    return (min(values), vmax) if vmin is None else (vmin, vmax)
 
 
 @pytest.mark.parametrize(
diff --git a/nilearn/plotting/tests/test_surf_plotting.py b/nilearn/plotting/tests/test_surf_plotting.py
--- a/nilearn/plotting/tests/test_surf_plotting.py
+++ b/nilearn/plotting/tests/test_surf_plotting.py
@@ -811,6 +811,28 @@ def test_plot_surf_roi_error(engine):
         plot_surf_roi(mesh, roi_map=roi_idx, engine=engine)
 
 
+@pytest.mark.parametrize(
+    "kwargs", [{"vmin": 2}, {"vmin": 2, "threshold": 5}, {"threshold": 5}]
+)
+def test_plot_surf_roi_colorbar_vmin_equal_across_engines(kwargs):
+    """See issue https://github.com/nilearn/nilearn/issues/3944"""
+    if not PLOTLY_INSTALLED:
+        pytest.skip("Plotly is not installed; required for this test.")
+    mesh = generate_surf()
+    roi_map = np.arange(0, len(mesh[0]))
+
+    mpl_plot = plot_surf_roi(
+        mesh, roi_map=roi_map, colorbar=True, engine="matplotlib", **kwargs
+    )
+    plotly_plot = plot_surf_roi(
+        mesh, roi_map=roi_map, colorbar=True, engine="plotly", **kwargs
+    )
+    assert (
+        int(mpl_plot.axes[-1].get_yticklabels()[0].get_text())
+        == plotly_plot.figure.data[1]["cmin"]
+    )
+
+
 def test_plot_img_on_surf_hemispheres_and_orientations(img_3d_mni):
     nii = img_3d_mni
     # Check that all combinations of 1D or 2D hemis and orientations work.