Repository: ManimCommunity/manim
Pull request: Fixed ``get_axis_labels`` for :class:`.Axes` and :class:`.ThreeDAxes`
<!-- Thank you for contributing to Manim! Learn more about the process in our contributing guidelines: https://docs.manim.community/en/latest/contributing.html -->

## Overview: What does this pull request change?
<!-- If there is more information than the PR title that should be added to our release changelog, add it in the following changelog section. This is optional, but recommended for larger pull requests. -->
<!--changelog-start-->
<!--changelog-end-->
Moves `~.get_axis_labels()` out of `CoordinateSystem` and into `Axes` and `ThreeDAxes`.  This allows for `~.get_axis_labels()` to be used with both 2D and 3D axes.  Fixed some documentation in the process.

## Motivation and Explanation: Why and how do your changes improve the library?
<!-- Optional for bugfixes, small enhancements, and documentation-related PRs. Otherwise, please give a short reasoning for your changes. -->
Closes #2853 

## Links to added or changed documentation pages
<!-- Please add links to the affected documentation pages (edit the description after opening the PR). The link to the documentation for your PR is https://manimce--####.org.readthedocs.build/en/####/, where #### represents the PR number. -->
https://manimce--3095.org.readthedocs.build/en/3095/

## Further Information and Comments
<!-- If applicable, put further comments for the reviewers here. -->



<!-- Thank you again for contributing! Do not modify the lines below, they are for reviewers. -->
## Reviewer Checklist
- [ ] The PR title is descriptive enough for the changelog, and the PR is labeled correctly
- [ ] If applicable: newly added non-private functions and classes have a docstring including a short summary and a PARAMETERS section
- [ ] If applicable: newly added functions and classes are tested


Problem statement:
`~.get_axis_labels()` implemented for `Axes`, but not `ThreeDAxes`
## Description of bug / unexpected behavior
<!-- Add a clear and concise description of the problem you encountered. -->
Shouldn't the method `~.get_axis_labels()` work for both `Axes` and `ThreeDAxes`?  At the moment it only works for `Axes` and returns an error when trying to use with `ThreeDAxes`.  This doesn't make sense, it should work for both.

## Expected behavior
<!-- Add a clear and concise description of what you expected to happen. -->
The method `~.get_axis_labels()` should work the same for both `Axes` and `ThreeDAxes`.

## How to reproduce the issue
<!-- Provide a piece of code illustrating the undesired behavior. -->

<details><summary>Code for reproducing the problem</summary>

```py
class Test(ThreeDScene):
    def construct(self):
        axes = ThreeDAxes()
        axes_labels = axes.get_axis_labels(Tex("x"), Tex("y"), Tex("z"))
        self.add(VGroup(axes, axes_labels))
```

</details>


## Logs
<details><summary>Terminal output</summary>
<!-- Add "-v DEBUG" when calling manim to generate more detailed logs -->

```
Manim Community v0.15.2

╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│                                                                                                  │
│ /home/alexlembcke/.local/lib/python3.10/site-packages/manim/cli/render/commands.py:118 in render │
│                                                                                                  │
│   115 │   │   for SceneClass in scene_classes_from_file(file):                                   │
│   116 │   │   │   try:                                                                           │
│   117 │   │   │   │   scene = SceneClass()                                                       │
│ ❱ 118 │   │   │   │   scene.render()                                                             │
│   119 │   │   │   except Exception:                                                              │
│   120 │   │   │   │   error_console.print_exception()                                            │
│   121 │   │   │   │   sys.exit(1)                                                                │
│ /home/alexlembcke/.local/lib/python3.10/site-packages/manim/scene/scene.py:222 in render         │
│                                                                                                  │
│    219 │   │   """                                                                               │
│    220 │   │   self.setup()                                                                      │
│    221 │   │   try:                                                                              │
│ ❱  222 │   │   │   self.construct()                                                              │
│    223 │   │   except EndSceneEarlyException:                                                    │
│    224 │   │   │   pass                                                                          │
│    225 │   │   except RerunSceneException as e:                                                  │
│                                                                                                  │
│ /home/alexlembcke/video.py:877 in construct                     │
│                                                                                                  │
│   874 class Test(ThreeDScene):                                                                   │
│   875 │   def construct(self):                                                                   │
│   876 │   │   axes = ThreeDAxes()                                                                │
│ ❱ 877 │   │   axes_labels = axes.get_axis_labels(Tex("x"), Tex("y"), Tex("z"))                  │
│   878 │   │   self.add(VGroup(axes, axes_labels))                                                │
│   879                                                                                            │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
TypeError: CoordinateSystem.get_axis_labels() takes from 1 to 3 positional arguments but 4 were given
```

<!-- Insert screenshots here (only when absolutely necessary, we prefer copy/pasted output!) -->

</details>


## System specifications

<details><summary>System Details</summary>

- OS (with version, e.g., Windows 10 v2004 or macOS 10.15 (Catalina)):
- RAM:
- Python version (`python/py/python3 --version`):
- Installed modules (provide output from `pip list`):
```
PASTE HERE
```
</details>

<details><summary>LaTeX details</summary>

+ LaTeX distribution (e.g. TeX Live 2020):
+ Installed LaTeX packages:
<!-- output of `tlmgr list --only-installed` for TeX Live or a screenshot of the Packages page for MikTeX -->
</details>

<details><summary>FFMPEG</summary>

Output of `ffmpeg -version`:

```
PASTE HERE
```
</details>

## Additional comments
<!-- Add further context that you think might be relevant for this issue here. -->

Hints from issue discussion:
Yes and no, there is a degree of freedom associated with labels in 3D space. Do you want them always facing the camera, aligned with the axis itself. At the end of the axis or above that you can always read it no matter the orientation.

In 2D it's simply just putting it beside the axis. So unless you propose a system which is sticking to the original function behavior but also solves the problem, sure we can add it!
I would say there should be reasonable defaults for those parameters.  You can already add numbers to the axes by passing `axis_config = {"include_numbers": True}` when creating `ThreeDAxes`, so why not use the defaults used by the numbers?

Commit message: Fix ~.get_axis_labels() for ThreeDAxes

Patch to review:
diff --git a/manim/mobject/graphing/coordinate_systems.py b/manim/mobject/graphing/coordinate_systems.py
index 703d879d7c..3604c13bd4 100644
--- a/manim/mobject/graphing/coordinate_systems.py
+++ b/manim/mobject/graphing/coordinate_systems.py
@@ -364,52 +364,8 @@ def _get_axis_label(
         label.shift_onto_screen(buff=MED_SMALL_BUFF)
         return label
 
-    def get_axis_labels(
-        self,
-        x_label: float | str | Mobject = "x",
-        y_label: float | str | Mobject = "y",
-    ) -> VGroup:
-        """Defines labels for the x_axis and y_axis of the graph.
-
-        For increased control over the position of the labels,
-        use :meth:`get_x_axis_label` and :meth:`get_y_axis_label`.
-
-        Parameters
-        ----------
-        x_label
-            The label for the x_axis. Defaults to :class:`~.MathTex` for ``str`` and ``float`` inputs.
-        y_label
-            The label for the y_axis. Defaults to :class:`~.MathTex` for ``str`` and ``float`` inputs.
-
-        Returns
-        -------
-        :class:`~.VGroup`
-            A :class:`~.Vgroup` of the labels for the x_axis and y_axis.
-
-
-        .. seealso::
-            :class:`get_x_axis_label`
-            :class:`get_y_axis_label`
-
-        Examples
-        --------
-        .. manim:: GetAxisLabelsExample
-            :save_last_frame:
-
-            class GetAxisLabelsExample(Scene):
-                def construct(self):
-                    ax = Axes()
-                    labels = ax.get_axis_labels(
-                        Tex("x-axis").scale(0.7), Text("y-axis").scale(0.45)
-                    )
-                    self.add(ax, labels)
-        """
-
-        self.axis_labels = VGroup(
-            self.get_x_axis_label(x_label),
-            self.get_y_axis_label(y_label),
-        )
-        return self.axis_labels
+    def get_axis_labels(self):
+        raise NotImplementedError()
 
     def add_coordinates(
         self,
@@ -2124,6 +2080,53 @@ def get_axes(self) -> VGroup:
         """
         return self.axes
 
+    def get_axis_labels(
+        self,
+        x_label: float | str | Mobject = "x",
+        y_label: float | str | Mobject = "y",
+    ) -> VGroup:
+        """Defines labels for the x_axis and y_axis of the graph.
+
+        For increased control over the position of the labels,
+        use :meth:`get_x_axis_label` and :meth:`get_y_axis_label`.
+
+        Parameters
+        ----------
+        x_label
+            The label for the x_axis. Defaults to :class:`~.MathTex` for ``str`` and ``float`` inputs.
+        y_label
+            The label for the y_axis. Defaults to :class:`~.MathTex` for ``str`` and ``float`` inputs.
+
+        Returns
+        -------
+        :class:`~.VGroup`
+            A :class:`~.Vgroup` of the labels for the x_axis and y_axis.
+
+
+        .. seealso::
+            :class:`get_x_axis_label`
+            :class:`get_y_axis_label`
+
+        Examples
+        --------
+        .. manim:: GetAxisLabelsExample
+            :save_last_frame:
+
+            class GetAxisLabelsExample(Scene):
+                def construct(self):
+                    ax = Axes()
+                    labels = ax.get_axis_labels(
+                        Tex("x-axis").scale(0.7), Text("y-axis").scale(0.45)
+                    )
+                    self.add(ax, labels)
+        """
+
+        self.axis_labels = VGroup(
+            self.get_x_axis_label(x_label),
+            self.get_y_axis_label(y_label),
+        )
+        return self.axis_labels
+
     def plot_line_graph(
         self,
         x_values: Iterable[float],
@@ -2361,6 +2364,57 @@ def make_func(axis):
                 submob.get_unit_normal = lambda a: np.ones(3)
                 submob.set_sheen(0.2)
 
+    def get_y_axis_label(
+        self,
+        label: float | str | Mobject,
+        edge: Sequence[float] = UR,
+        direction: Sequence[float] = UR,
+        buff: float = SMALL_BUFF,
+        rotation=PI / 2,
+        rotation_axis=OUT,
+        **kwargs,
+    ) -> Mobject:
+        """Generate a y-axis label.
+
+        Parameters
+        ----------
+        label
+            The label. Defaults to :class:`~.MathTex` for ``str`` and ``float`` inputs.
+        edge
+            The edge of the y-axis to which the label will be added, by default ``UR``.
+        direction
+            Allows for further positioning of the label from an edge, by default ``UR``.
+        buff
+            The distance of the label from the line, by default ``SMALL_BUFF``.
+        rotation
+            The angle at which to rotate the label, by default ``PI/2``.
+        rotation_axis
+            The axis about which to rotate the label, by default ``OUT``.
+
+        Returns
+        -------
+        :class:`~.Mobject`
+            The positioned label.
+
+        Examples
+        --------
+        .. manim:: GetYAxisLabelExample
+            :save_last_frame:
+
+            class GetYAxisLabelExample(ThreeDScene):
+                def construct(self):
+                    ax = ThreeDAxes()
+                    lab = ax.get_y_axis_label(Tex("$y$-label"))
+                    self.set_camera_orientation(phi=2*PI/5, theta=PI/5)
+                    self.add(ax, lab)
+        """
+
+        positioned_label = self._get_axis_label(
+            label, self.get_y_axis(), edge, direction, buff=buff, **kwargs
+        )
+        positioned_label.rotate(rotation, axis=rotation_axis)
+        return positioned_label
+
     def get_z_axis_label(
         self,
         label: float | str | Mobject,
@@ -2378,11 +2432,11 @@ def get_z_axis_label(
         label
             The label. Defaults to :class:`~.MathTex` for ``str`` and ``float`` inputs.
         edge
-            The edge of the x-axis to which the label will be added, by default ``UR``.
+            The edge of the z-axis to which the label will be added, by default ``OUT``.
         direction
-            Allows for further positioning of the label from an edge, by default ``UR``.
+            Allows for further positioning of the label from an edge, by default ``RIGHT``.
         buff
-            The distance of the label from the line.
+            The distance of the label from the line, by default ``SMALL_BUFF``.
         rotation
             The angle at which to rotate the label, by default ``PI/2``.
         rotation_axis
@@ -2412,6 +2466,59 @@ def construct(self):
         positioned_label.rotate(rotation, axis=rotation_axis)
         return positioned_label
 
+    def get_axis_labels(
+        self,
+        x_label: float | str | Mobject = "x",
+        y_label: float | str | Mobject = "y",
+        z_label: float | str | Mobject = "z",
+    ) -> VGroup:
+        """Defines labels for the x_axis and y_axis of the graph.
+
+        For increased control over the position of the labels,
+        use :meth:`get_x_axis_label` and :meth:`get_y_axis_label`.
+
+        Parameters
+        ----------
+        x_label
+            The label for the x_axis. Defaults to :class:`~.MathTex` for ``str`` and ``float`` inputs.
+        y_label
+            The label for the y_axis. Defaults to :class:`~.MathTex` for ``str`` and ``float`` inputs.
+        z_label
+            The label for the z_axis. Defaults to :class:`~.MathTex` for ``str`` and ``float`` inputs.
+
+        Returns
+        -------
+        :class:`~.VGroup`
+            A :class:`~.Vgroup` of the labels for the x_axis, y_axis and z_axis.
+
+
+        .. seealso::
+            :class:`get_x_axis_label`
+            :class:`get_y_axis_label`
+            :class:`get_z_axis_label`
+
+        Examples
+        --------
+        .. manim:: GetAxisLabelsExample
+            :save_last_frame:
+
+            class GetAxisLabelsExample(ThreeDScene):
+                def construct(self):
+                    self.set_camera_orientation(phi=2*PI/5, theta=PI/5)
+                    axes = ThreeDAxes()
+                    labels = axes.get_axis_labels(
+                        Tex("x-axis").scale(0.7), Text("y-axis").scale(0.45), Text("z-axis").scale(0.45)
+                    )
+                    self.add(axes, labels)
+        """
+
+        self.axis_labels = VGroup(
+            self.get_x_axis_label(x_label),
+            self.get_y_axis_label(y_label),
+            self.get_z_axis_label(z_label),
+        )
+        return self.axis_labels
+
 
 class NumberPlane(Axes):
     """Creates a cartesian plane with background lines.


Code review comments:
diff_hunk: @@ -2412,6 +2466,59 @@ def construct(self):
         positioned_label.rotate(rotation, axis=rotation_axis)
         return positioned_label
 
+    def get_axis_labels(
+        self,
+        x_label: float | str | Mobject = "x",
+        y_label: float | str | Mobject = "y",
+        z_label: float | str | Mobject = "z",
+    ) -> VGroup:
+        """Defines labels for the x_axis and y_axis of the graph.
+
+        For increased control over the position of the labels,
+        use :meth:`get_x_axis_label` and :meth:`get_y_axis_label`.
+
+        Parameters
+        ----------
+        x_label
+            The label for the x_axis. Defaults to :class:`~.MathTex` for ``str`` and ``float`` inputs.
+        y_label
+            The label for the y_axis. Defaults to :class:`~.MathTex` for ``str`` and ``float`` inputs.
+        z_label
+            The label for the z_axis. Defaults to :class:`~.MathTex` for ``str`` and ``float`` inputs.
+
+        Returns
+        -------
+        :class:`~.VGroup`
+            A :class:`~.Vgroup` of the labels for the x_axis, y_axis and z_axis.
path: manim/mobject/graphing/coordinate_systems.py
text: @user1:
The Oxford comma is debatable, but the spelling of VGroup should definitely be corrected :-)

```suggestion
            A :class:`~.VGroup` of the labels for the x_axis, y_axis, and z_axis.
```

diff_hunk: @@ -2124,6 +2080,53 @@ def get_axes(self) -> VGroup:
         """
         return self.axes
 
+    def get_axis_labels(
+        self,
+        x_label: float | str | Mobject = "x",
+        y_label: float | str | Mobject = "y",
+    ) -> VGroup:
+        """Defines labels for the x_axis and y_axis of the graph.
+
+        For increased control over the position of the labels,
+        use :meth:`get_x_axis_label` and :meth:`get_y_axis_label`.
+
+        Parameters
+        ----------
+        x_label
+            The label for the x_axis. Defaults to :class:`~.MathTex` for ``str`` and ``float`` inputs.
+        y_label
+            The label for the y_axis. Defaults to :class:`~.MathTex` for ``str`` and ``float`` inputs.
+
+        Returns
+        -------
+        :class:`~.VGroup`
+            A :class:`~.Vgroup` of the labels for the x_axis and y_axis.
+
+
+        .. seealso::
+            :class:`get_x_axis_label`
+            :class:`get_y_axis_label`
path: manim/mobject/graphing/coordinate_systems.py
text: @user1:
```suggestion
            :meth:`.get_x_axis_label`
            :meth:`.get_y_axis_label`
```

diff_hunk: @@ -2124,6 +2080,53 @@ def get_axes(self) -> VGroup:
         """
         return self.axes
 
+    def get_axis_labels(
+        self,
+        x_label: float | str | Mobject = "x",
+        y_label: float | str | Mobject = "y",
+    ) -> VGroup:
+        """Defines labels for the x_axis and y_axis of the graph.
+
+        For increased control over the position of the labels,
+        use :meth:`get_x_axis_label` and :meth:`get_y_axis_label`.
+
+        Parameters
+        ----------
+        x_label
+            The label for the x_axis. Defaults to :class:`~.MathTex` for ``str`` and ``float`` inputs.
+        y_label
+            The label for the y_axis. Defaults to :class:`~.MathTex` for ``str`` and ``float`` inputs.
+
+        Returns
+        -------
+        :class:`~.VGroup`
+            A :class:`~.Vgroup` of the labels for the x_axis and y_axis.
path: manim/mobject/graphing/coordinate_systems.py
text: @user1:
```suggestion
            A :class:`~.VGroup` of the labels for the x_axis and y_axis.
```

diff_hunk: @@ -2412,6 +2466,59 @@ def construct(self):
         positioned_label.rotate(rotation, axis=rotation_axis)
         return positioned_label
 
+    def get_axis_labels(
+        self,
+        x_label: float | str | Mobject = "x",
+        y_label: float | str | Mobject = "y",
+        z_label: float | str | Mobject = "z",
+    ) -> VGroup:
+        """Defines labels for the x_axis and y_axis of the graph.
+
+        For increased control over the position of the labels,
+        use :meth:`get_x_axis_label` and :meth:`get_y_axis_label`.
+
+        Parameters
+        ----------
+        x_label
+            The label for the x_axis. Defaults to :class:`~.MathTex` for ``str`` and ``float`` inputs.
+        y_label
+            The label for the y_axis. Defaults to :class:`~.MathTex` for ``str`` and ``float`` inputs.
+        z_label
+            The label for the z_axis. Defaults to :class:`~.MathTex` for ``str`` and ``float`` inputs.
+
+        Returns
+        -------
+        :class:`~.VGroup`
+            A :class:`~.Vgroup` of the labels for the x_axis, y_axis and z_axis.
+
+
+        .. seealso::
+            :class:`get_x_axis_label`
+            :class:`get_y_axis_label`
+            :class:`get_z_axis_label`
path: manim/mobject/graphing/coordinate_systems.py
text: @user1:
```suggestion
            :meth:`.get_x_axis_label`
            :meth:`.get_y_axis_label`
            :meth:`.get_z_axis_label`
```

diff_hunk: @@ -2412,6 +2466,59 @@ def construct(self):
         positioned_label.rotate(rotation, axis=rotation_axis)
         return positioned_label
 
+    def get_axis_labels(
+        self,
+        x_label: float | str | Mobject = "x",
+        y_label: float | str | Mobject = "y",
+        z_label: float | str | Mobject = "z",
+    ) -> VGroup:
+        """Defines labels for the x_axis and y_axis of the graph.
+
+        For increased control over the position of the labels,
+        use :meth:`get_x_axis_label` and :meth:`get_y_axis_label`.
path: manim/mobject/graphing/coordinate_systems.py
text: @user1:
```suggestion
        use :meth:`.get_x_axis_label` and :meth:`.get_y_axis_label`.
```