Repository: nilearn/nilearn

Issue:
plot_surf_roi Colorbar values are in scientific format 
Hi all,

I am plotting a `roi_map` and I get colorbar values in scientific format. I would expect to have only integers values since my `roi_map` contains integers.
The parcellation file contains only integers and can be found here: https://gofile.io/?c=ePmDcg

Additionally, I see `format='%.2g'` in https://github.com/nilearn/nilearn/blob/0941897aca6f3073221fcd3483afe91b8305b27c/nilearn/plotting/surf_plotting.py#L293. Maybe this is the culprit?

Shouldn't be `format='%.1f'` ?


Here is a small example:

```
import numpy as np
from nilearn import plotting, datasets

parcellation = np.load("parcellation.npy")
surface = datasets.fetch_surf_fsaverage('fsaverage') # the high resolution file - plot will take time

plotting.plot_surf_roi(surface["pial_left"], roi_map = parcellation, \
		hemi = 'left', view = 'lateral', bg_map = surface["sulc_left"], \
		bg_on_data = True, darkness = 0.6, \
		cmap = 'jet', colorbar = True)

```
![Figure_1](https://user-images.githubusercontent.com/25902670/69094532-d2e36200-0a50-11ea-9a2a-cc2a14b3c39d.png)

P.S: even by manually adding `vmin=0` and `vmax=181` the problem remains.




Discussion:
> Additionally, I see `format='%.2g'` in
> 
> https://github.com/nilearn/nilearn/blob/0941897aca6f3073221fcd3483afe91b8305b27c/nilearn/plotting/surf_plotting.py#L293

probably, but this function (`plot_surf`) is also used to plot stat maps, so it would need to expose this parameter or choose the format based on the type of the data. 

I think the easier solution for you is to change the tick formatter of the matplotlib axis containing the colorbar
Makes sense. Any idea how to do this? I have tried creating a figure and passing it to `figure=myfigure` argument but still no luck. Thanks for the reply

can you try
```python
from matplotlib import pyplot as plt
from matplotlib import ticker

plt.gcf().axes[0].yaxis.set_major_formatter(ticker.FormatStrFormatter("%i"))
```

it may be `axes[1]` instead of `axes[0]`

Even when I pass the `fig` and `ax`, it seems that the resulted figure does not obey my settings.
I think that the  `fig` and `ax` are not taken into account at all.

```
import numpy as np
from nilearn import plotting, datasets
from matplotlib import pyplot as plt
from matplotlib import ticker

parcellation = np.load("parcellation.npy")
surface = datasets.fetch_surf_fsaverage('fsaverage') # the high resolution file - plot will take time

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
plt.gcf().axes[0].yaxis.set_major_formatter(ticker.FormatStrFormatter("%i"))

plotting.plot_surf_roi(surface["pial_left"], roi_map = parcellation, \
		hemi = 'left', view = 'lateral', bg_map = surface["sulc_left"], \
		bg_on_data = True, darkness = 0.6, \
		cmap = 'jet', colorbar = True, axes= ax, figure=fig)

```
the formatter should be changed for the colorbar axis, which is created by the plotting function. the axes you create will contain the brain plot, not the colorbar. can you try this?

```python
import numpy as np
from nilearn import plotting, datasets
from matplotlib import pyplot as plt
from matplotlib import ticker

parcellation = np.load("parcellation.npy")
surface = datasets.fetch_surf_fsaverage('fsaverage') # the high resolution file - plot will take time

plotting.plot_surf_roi(surface["pial_left"], roi_map = parcellation, \
    hemi = 'left', view = 'lateral', bg_map = surface["sulc_left"], \
    bg_on_data = True, darkness = 0.6, \
    cmap = 'jet', colorbar = True)

for ax in plt.gcf().axes:
    ax.yaxis.set_major_formatter(ticker.FormatStrFormatter("%i"))

plt.show()
```
there does seem to be a problem, when formatted as ints I get 0 and 1, and the scientific notation gets truncated as in the plot you show.

for some reason `plot_surf` does not use the same code as other plotting functions to add the colorbar
> there does seem to be a problem, when formatted as ints I get 0 and 1, and the scientific notation gets truncated as in the plot you show.
> 
> for some reason `plot_surf` does not use the same code as other plotting functions to add the colorbar

Exactly. I have tried more than 10 things to make it work but no luck. It's a bug indeed. 
yes, thanks for reporting!
I'll look at it.
Has there been any solution found for this issue? I am having the same issue using plot_anat.
> Has there been any solution found for this issue? I am having the same issue using plot_anat.

Nothing new — still an issue 
I'm afraid that nobody can take time on that atm. would you try and identify the bug more precisely ?
Best,

My issue is exactly the same as that described above, and trying to use [this](https://github.com/nilearn/nilearn/issues/2220#issuecomment-555506339) solution produced the same output as described [here](https://github.com/nilearn/nilearn/issues/2220#issuecomment-555514604), although it seems that the issue is not limited to plot_surf.
A little update on this:

- The link to download the parcellation file in the first message is not working anymore
- Even if it doesn't make much sense, the following code gives a colorbar with scientific notation (which indeed gets cropped): 

```python
import matplotlib.pyplot as plt
from nilearn import plotting, datasets

destrieux_atlas = datasets.fetch_atlas_surf_destrieux()
parcellation = destrieux_atlas['map_left']
surface = datasets.fetch_surf_fsaverage()

plotting.plot_surf_roi(surface["pial_left"], roi_map = parcellation,
        hemi = 'left', view = 'lateral', bg_map = surface["sulc_left"],
        bg_on_data = True, darkness = 0.6,
        cmap = 'jet', colorbar = True, 
        vmin=1, vmax=210) # force values larger than 99
plt.show()
```

With `matplotlib>=3.2.0` there is no longer a normalization between 0 and 1 (see this [link](https://matplotlib.org/3.2.1/api/prev_api_changes/api_changes_3.2.0.html)). So the following code proposed by @jeromedockes now works with recent versions of `matplotlib` (colorbar should display integer values from 1 to 210...):

```python
import matplotlib.pyplot as plt
from matplotlib import ticker
from nilearn import plotting, datasets

destrieux_atlas = datasets.fetch_atlas_surf_destrieux()
parcellation = destrieux_atlas['map_left']
surface = datasets.fetch_surf_fsaverage()

plotting.plot_surf_roi(surface["pial_left"], roi_map = parcellation,
        hemi = 'left', view = 'lateral', bg_map = surface["sulc_left"],
        bg_on_data = True, darkness = 0.6,
        cmap = 'jet', colorbar = True, 
        vmin=1, vmax=210) # force values larger than 99
for ax in plt.gcf().axes:
    ax.yaxis.set_major_formatter(ticker.FormatStrFormatter("%i"))
plt.show()
```

At this point I see two possibilities:

- consider that the user is responsible for manually formatting if scientific notation isn't wanted (as shown above). In this case, I think there is nothing to do and we can close this.
- enable the user to control this by adding an argument `cbar_tick_format` to `plot_surf` which will be used here:

https://github.com/nilearn/nilearn/blob/6c040cb98fe7e70b026522be5d404e5894348a9d/nilearn/plotting/surf_plotting.py#L297-L301

I'm OK with both but wondering what others think...


> At this point I see two possibilities:
>
> - consider that the user is responsible for manually formatting if scientific notation isn't wanted (as shown above). In this case, I think there is nothing to do and we can close this.
> - enable the user to control this by adding an argument `cbar_tick_format` to `plot_surf` 

I think I prefer the second one; it adds a parameter to plot_surf but
most users don't use it directly but through plot_surf_stat_map or
plot_surf_roi. These functions would not expose the parameter to the
user but use appropriate values for the kind of data they are meant for
(floats for stat_map and ints for roi)


Resolving patch:
diff --git a/nilearn/plotting/surf_plotting.py b/nilearn/plotting/surf_plotting.py
--- a/nilearn/plotting/surf_plotting.py
+++ b/nilearn/plotting/surf_plotting.py
@@ -30,7 +30,7 @@ def plot_surf(surf_mesh, surf_map=None, bg_map=None,
               hemi='left', view='lateral', cmap=None, colorbar=False,
               avg_method='mean', threshold=None, alpha='auto',
               bg_on_data=False, darkness=1, vmin=None, vmax=None,
-              cbar_vmin=None, cbar_vmax=None,
+              cbar_vmin=None, cbar_vmax=None, cbar_tick_format='%.2g',
               title=None, output_file=None, axes=None, figure=None, **kwargs):
     """ Plotting of surfaces with optional background and data
 
@@ -98,8 +98,17 @@ def plot_surf(surf_mesh, surf_map=None, bg_map=None,
         .5 indicates the background values are reduced by half before being
         applied.
 
-    vmin, vmax: lower / upper bound to plot surf_data values
-        If None , the values will be set to min/max of the data
+    vmin, vmax: float, float, lower / upper bound to plot surf_data values
+        If None, the values will be set to min/max of the data
+
+    cbar_vmin, cbar_vmax: float, float, lower / upper bounds for the colorbar, optional.
+        If None, the values will be set from the data.
+        Default values are None.
+
+    cbar_tick_format: str, optional,
+        Default is '%.2g' for scientific notation.
+        Controls how to format the tick labels of the colorbar.
+        Ex: use "%i" to display as integers.
 
     title : str, optional
         Figure title.
@@ -129,6 +138,7 @@ def plot_surf(surf_mesh, surf_map=None, bg_map=None,
     nilearn.plotting.plot_surf_stat_map : for plotting statistical maps on
         brain surfaces.
     """
+    _default_figsize = [6, 4]
 
     # load mesh and derive axes limits
     mesh = load_surf_mesh(surf_mesh)
@@ -186,15 +196,20 @@ def plot_surf(surf_mesh, surf_map=None, bg_map=None,
         if isinstance(cmap, str):
             cmap = plt.cm.get_cmap(cmap)
 
+    figsize = _default_figsize
+    # Leave space for colorbar
+    if colorbar:
+        figsize[0] += .7
     # initiate figure and 3d axes
     if axes is None:
         if figure is None:
-            figure = plt.figure()
+            figure = plt.figure(figsize=figsize)
         axes = Axes3D(figure, rect=[0, 0, 1, 1],
                       xlim=limits, ylim=limits)
     else:
         if figure is None:
             figure = axes.get_figure()
+            figure.set_size_inches(*figsize)
         axes.set_xlim(*limits)
         axes.set_ylim(*limits)
     axes.view_init(elev=elev, azim=azim)
@@ -255,7 +270,7 @@ def plot_surf(surf_mesh, surf_map=None, bg_map=None,
         if vmax is None:
             vmax = np.nanmax(surf_map_faces)
 
-        # treshold if inidcated
+        # treshold if indicated
         if threshold is None:
             kept_indices = np.arange(surf_map_faces.shape[0])
         else:
@@ -275,10 +290,16 @@ def plot_surf(surf_mesh, surf_map=None, bg_map=None,
             our_cmap = get_cmap(cmap)
             norm = Normalize(vmin=vmin, vmax=vmax)
 
+            # Default number of ticks is 5...
             nb_ticks = 5
-            ticks = np.linspace(vmin, vmax, nb_ticks)
+            # ...unless we are dealing with integers with a small range
+            # in this case, we reduce the number of ticks
+            if cbar_tick_format == "%i" and vmax - vmin < nb_ticks:
+                ticks = np.arange(vmin, vmax + 1)
+                nb_ticks = len(ticks)
+            else:
+                ticks = np.linspace(vmin, vmax, nb_ticks)
             bounds = np.linspace(vmin, vmax, our_cmap.N)
-
             if threshold is not None:
                 cmaplist = [our_cmap(i) for i in range(our_cmap.N)]
                 # set colors to grey for absolute values < threshold
@@ -297,7 +318,7 @@ def plot_surf(surf_mesh, surf_map=None, bg_map=None,
             cbar = figure.colorbar(
                 proxy_mappable, cax=cax, ticks=ticks,
                 boundaries=bounds, spacing='proportional',
-                format='%.2g', orientation='vertical')
+                format=cbar_tick_format, orientation='vertical')
             _crop_colorbar(cbar, cbar_vmin, cbar_vmax)
 
         p3dcollec.set_facecolors(face_colors)
@@ -824,8 +845,8 @@ def plot_img_on_surf(stat_map, surf_mesh='fsaverage5', mask_img=None,
 def plot_surf_roi(surf_mesh, roi_map, bg_map=None,
                   hemi='left', view='lateral', threshold=1e-14,
                   alpha='auto', vmin=None, vmax=None, cmap='gist_ncar',
-                  bg_on_data=False, darkness=1, title=None,
-                  output_file=None, axes=None, figure=None, **kwargs):
+                  cbar_tick_format="%i", bg_on_data=False, darkness=1, 
+                  title=None, output_file=None, axes=None, figure=None, **kwargs):
     """ Plotting ROI on a surface mesh with optional background
 
     .. versionadded:: 0.3
@@ -868,6 +889,11 @@ def plot_surf_roi(surf_mesh, roi_map, bg_map=None,
         To use for plotting of the rois. Either a string which is a name
         of a matplotlib colormap, or a matplotlib colormap object.
 
+    cbar_tick_format: str, optional,
+        Default is '%i' for integers.
+        Controls how to format the tick labels of the colorbar.
+        Ex: use "%.2g" to display using scientific notation.
+
     alpha : float, default is 'auto'
         Alpha level of the mesh (not the stat_map). If default,
         alpha will default to .5 when no bg_map is passed
@@ -931,7 +957,8 @@ def plot_surf_roi(surf_mesh, roi_map, bg_map=None,
 
     display = plot_surf(mesh, surf_map=roi, bg_map=bg_map,
                         hemi=hemi, view=view, avg_method='median',
-                        threshold=threshold, cmap=cmap, alpha=alpha,
+                        threshold=threshold, cmap=cmap, 
+                        cbar_tick_format=cbar_tick_format, alpha=alpha,
                         bg_on_data=bg_on_data, darkness=darkness,
                         vmin=vmin, vmax=vmax, title=title,
                         output_file=output_file, axes=axes,


Test patch:
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
@@ -33,6 +33,8 @@ def test_plot_surf():
 
     # Plot with colorbar
     plot_surf(mesh, bg_map=bg, colorbar=True)
+    plot_surf(mesh, bg_map=bg, colorbar=True, cbar_vmin=0,
+              cbar_vmax=150, cbar_tick_format="%i")
 
     # Save execution time and memory
     plt.close()
@@ -96,6 +98,10 @@ def test_plot_surf_stat_map():
                        bg_on_data=True, darkness=0.5,
                        threshold=0.3)
 
+    # Change colorbar tick format
+    plot_surf_stat_map(mesh, stat_map=data, bg_map=bg, colorbar=True,
+                       bg_on_data=True, darkness=0.5, cbar_tick_format="%.2g")
+
     # Change vmax
     plot_surf_stat_map(mesh, stat_map=data, vmax=5)
     plot_surf_stat_map(mesh, stat_map=data, vmax=5, colorbar=True)
@@ -175,12 +181,21 @@ def test_plot_surf_roi():
     cbar = img.axes[-1]
     cbar_vmin = float(cbar.get_yticklabels()[0].get_text())
     cbar_vmax = float(cbar.get_yticklabels()[-1].get_text())
+    assert cbar_vmin == 1.0
+    assert cbar_vmax == 8.0
+    img2 = plot_surf_roi(mesh, roi_map=roi_map, vmin=1.2,
+                         vmax=8.9, colorbar=True, cbar_tick_format="%.2g")
+    img2.canvas.draw()
+    cbar = img2.axes[-1]
+    cbar_vmin = float(cbar.get_yticklabels()[0].get_text())
+    cbar_vmax = float(cbar.get_yticklabels()[-1].get_text())
     assert cbar_vmin == 1.2
     assert cbar_vmax == 8.9
 
     # plot parcellation
     plot_surf_roi(mesh, roi_map=parcellation)
     plot_surf_roi(mesh, roi_map=parcellation, colorbar=True)
+    plot_surf_roi(mesh, roi_map=parcellation, colorbar=True, cbar_tick_fomat="%f")
 
     # plot to axes
     plot_surf_roi(mesh, roi_map=roi_map, ax=None, figure=plt.gcf())