Repository: Qiskit/qiskit

Issue:
Better use for the `figsize` argument of `plot_bloch_multivector`
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues to confirm this idea does not exist. -->

### What is the expected enhancement?

The `figsize` argument of `plot_bloch_multivector` is currently not used by the visualisation, so it is impossible to change its size (e.g. to shrink it for single-qubit states). The following tiny change to its code would use `figsize` as a hint for individual bloch ball sizes, enhancing the usability of this visualization:

```py
# Original source code snippet:
...
width, height = plt.figaspect(1 / num)
...

# Modified source code snippet:
...
if figsize is not None:
    width, height = figsize
    width *= num
else:
    width, height = plt.figaspect(1 / num)
...
```

Comparison of a single-qubit bloch sphere visualisation, original (left) vs scaled version available in proposed enhancement (right):

![image](https://user-images.githubusercontent.com/13587235/141653535-f9639067-7e00-4ed0-a5ea-781aa1f97735.png)

Three-qubit bloch spheres, original (top) vs scaled version available in proposed enhancement (bottom):

![image](https://user-images.githubusercontent.com/13587235/141653589-4021d1d2-bf80-4e8a-9918-dd1240810422.png)

Three-qubit bloch spheres, advanced scaled version available in proposed enhancement, leaving additional space between spheres:

![image](https://user-images.githubusercontent.com/13587235/141653623-f53d69b4-f76c-454d-a989-0de9ceb48b0d.png)

Full source code for reference:

```py
def plot_bloch_multivector(state, title="", figsize=None, *, rho=None, reverse_bits=False):
    if not HAS_MATPLOTLIB:
        raise MissingOptionalLibraryError(
            libname="Matplotlib",
            name="plot_bloch_multivector",
            pip_install="pip install matplotlib",
        )
    from matplotlib import pyplot as plt
    bloch_data = (
        _bloch_multivector_data(state)[::-1] if reverse_bits else _bloch_multivector_data(state)
    )
    num = len(bloch_data)
    if figsize is not None:
        width, height = figsize
        width *= num
    else:
        width, height = plt.figaspect(1 / num)
    fig = plt.figure(figsize=(width, height))
    for i in range(num):
        pos = num - 1 - i if reverse_bits else i
        ax = fig.add_subplot(1, num, i + 1, projection="3d")
        plot_bloch_vector(bloch_data[i], "qubit " + str(pos), ax=ax, figsize=figsize)
    fig.suptitle(title, fontsize=16)
    matplotlib_close_if_inline(fig)
    return fig
```




Discussion:
Hi @sg495 ! Do you think you can turn this issue into a PR?
Sure thing, I'll do that tonight 👍 

Resolving patch:
diff --git a/qiskit/visualization/bloch.py b/qiskit/visualization/bloch.py
--- a/qiskit/visualization/bloch.py
+++ b/qiskit/visualization/bloch.py
@@ -149,7 +149,9 @@ class Bloch:
             Positions of +z and -z labels respectively.
     """
 
-    def __init__(self, fig=None, axes=None, view=None, figsize=None, background=False):
+    def __init__(
+        self, fig=None, axes=None, view=None, figsize=None, background=False, font_size=20
+    ):
 
         # Figure and axes
         self._ext_fig = False
@@ -194,7 +196,7 @@ def __init__(self, fig=None, axes=None, view=None, figsize=None, background=Fals
         # Color of fonts, default = 'black'
         self.font_color = plt.rcParams["axes.labelcolor"]
         # Size of fonts, default = 20
-        self.font_size = 20
+        self.font_size = font_size
 
         # ---vector options---
         # List of colors for Bloch vectors, default = ['b','g','r','y']
diff --git a/qiskit/visualization/state_visualization.py b/qiskit/visualization/state_visualization.py
--- a/qiskit/visualization/state_visualization.py
+++ b/qiskit/visualization/state_visualization.py
@@ -187,7 +187,9 @@ def plot_state_hinton(
 
 
 @_optionals.HAS_MATPLOTLIB.require_in_call
-def plot_bloch_vector(bloch, title="", ax=None, figsize=None, coord_type="cartesian"):
+def plot_bloch_vector(
+    bloch, title="", ax=None, figsize=None, coord_type="cartesian", font_size=None
+):
     """Plot the Bloch sphere.
 
     Plot a Bloch sphere with the specified coordinates, that can be given in both
@@ -204,6 +206,7 @@ def plot_bloch_vector(bloch, title="", ax=None, figsize=None, coord_type="cartes
         figsize (tuple): Figure size in inches. Has no effect is passing ``ax``.
         coord_type (str): a string that specifies coordinate type for bloch
             (Cartesian or spherical), default is Cartesian
+        font_size (float): Font size.
 
     Returns:
         Figure: A matplotlib figure instance if ``ax = None``.
@@ -234,7 +237,7 @@ def plot_bloch_vector(bloch, title="", ax=None, figsize=None, coord_type="cartes
 
     if figsize is None:
         figsize = (5, 5)
-    B = Bloch(axes=ax)
+    B = Bloch(axes=ax, font_size=font_size)
     if coord_type == "spherical":
         r, theta, phi = bloch[0], bloch[1], bloch[2]
         bloch[0] = r * np.sin(theta) * np.cos(phi)
@@ -253,7 +256,16 @@ def plot_bloch_vector(bloch, title="", ax=None, figsize=None, coord_type="cartes
 @deprecate_arguments({"rho": "state"}, since="0.15.1")
 @_optionals.HAS_MATPLOTLIB.require_in_call
 def plot_bloch_multivector(
-    state, title="", figsize=None, *, rho=None, reverse_bits=False, filename=None
+    state,
+    title="",
+    figsize=None,
+    *,
+    rho=None,
+    reverse_bits=False,
+    filename=None,
+    font_size=None,
+    title_font_size=None,
+    title_pad=1,
 ):
     r"""Plot a Bloch sphere for each qubit.
 
@@ -266,8 +278,11 @@ def plot_bloch_multivector(
     Args:
         state (Statevector or DensityMatrix or ndarray): an N-qubit quantum state.
         title (str): a string that represents the plot title
-        figsize (tuple): Has no effect, here for compatibility only.
+        figsize (tuple): size of each individual Bloch sphere figure, in inches.
         reverse_bits (bool): If True, plots qubits following Qiskit's convention [Default:False].
+        font_size (float): Font size for the Bloch ball figures.
+        title_font_size (float): Font size for the title.
+        title_pad (float): Padding for the title (suptitle `y` position is `y=1+title_pad/100`).
 
     Returns:
         matplotlib.Figure:
@@ -324,13 +339,21 @@ def plot_bloch_multivector(
         _bloch_multivector_data(state)[::-1] if reverse_bits else _bloch_multivector_data(state)
     )
     num = len(bloch_data)
-    width, height = plt.figaspect(1 / num)
+    if figsize is not None:
+        width, height = figsize
+        width *= num
+    else:
+        width, height = plt.figaspect(1 / num)
+    default_title_font_size = font_size if font_size is not None else 16
+    title_font_size = title_font_size if title_font_size is not None else default_title_font_size
     fig = plt.figure(figsize=(width, height))
     for i in range(num):
         pos = num - 1 - i if reverse_bits else i
         ax = fig.add_subplot(1, num, i + 1, projection="3d")
-        plot_bloch_vector(bloch_data[i], "qubit " + str(pos), ax=ax, figsize=figsize)
-    fig.suptitle(title, fontsize=16, y=1.01)
+        plot_bloch_vector(
+            bloch_data[i], "qubit " + str(pos), ax=ax, figsize=figsize, font_size=font_size
+        )
+    fig.suptitle(title, fontsize=title_font_size, y=1.0 + title_pad / 100)
     matplotlib_close_if_inline(fig)
     if filename is None:
         return fig


Test patch:
diff --git a/test/ipynb/mpl/graph/references/bloch_multivector_figsize_improvements.png b/test/ipynb/mpl/graph/references/bloch_multivector_figsize_improvements.png
new file mode 100644
Binary files /dev/null and b/test/ipynb/mpl/graph/references/bloch_multivector_figsize_improvements.png differ
diff --git a/test/ipynb/mpl/graph/test_graph_matplotlib_drawer.py b/test/ipynb/mpl/graph/test_graph_matplotlib_drawer.py
--- a/test/ipynb/mpl/graph/test_graph_matplotlib_drawer.py
+++ b/test/ipynb/mpl/graph/test_graph_matplotlib_drawer.py
@@ -370,6 +370,31 @@ def test_plot_coupling_map(self):
             filename="coupling_map.png",
         )
 
+    def test_plot_bloch_multivector_figsize_improvements(self):
+        """test bloch sphere figsize, font_size, title_font_size and title_pad
+        See https://github.com/Qiskit/qiskit-terra/issues/7263
+        and https://github.com/Qiskit/qiskit-terra/pull/7264.
+        """
+        circuit = QuantumCircuit(3)
+        circuit.h(1)
+        circuit.sxdg(2)
+
+        # getting the state using backend
+        backend = BasicAer.get_backend("statevector_simulator")
+        result = execute(circuit, backend).result()
+        state = result.get_statevector(circuit)
+
+        self.graph_state_drawer(
+            state=state,
+            output="bloch",
+            figsize=(3, 2),
+            font_size=10,
+            title="|0+R> state",
+            title_font_size=14,
+            title_pad=8,
+            filename="bloch_multivector_figsize_improvements.png",
+        )
+
 
 if __name__ == "__main__":
     unittest.main(verbosity=1)