فهرست منبع

Fix potential matplotlib warning when using ui.line_plot.push (#4192)

## Description

When trying to use `ui.line_plot.push` with x or y values that are all
equal, you get the following matplotlib warning:
```
lib/python3.12/site-packages/nicegui/elements/line_plot.py:72: UserWarning: Attempting to set identical low and high ylims makes transformation singular; automatically expanding.
  self.fig.gca().set_ylim(min_y - pad_y, max_y + pad_y)
```

This PR changes 2 things:

1. It will not update the x and/or y limits if their respective data
points are all the same value.
2. Add `update_y_lims` and `update_x_lims` parameters to `push` that
will disable the limits updating for that push. This will also allow for
custom limits to be set (and not reset every call to push).

## Example

This example will have a warning on the current version of nicegui.

```python
from nicegui import ui

lp = ui.line_plot(n=1)

i = 0
def update_lp():
    global i
    lp.push([i], [[0]])
    # lp.push([i], [[0]], update_y_lims=False)

    i += 1
ui.timer(1, update_lp)
ui.run()
```

---------

Co-authored-by: Falko Schindler <falko@zauberzeug.com>
Daniel Kramer 4 ماه پیش
والد
کامیت
95fb61a716
2فایلهای تغییر یافته به همراه30 افزوده شده و 12 حذف شده
  1. 29 11
      nicegui/elements/line_plot.py
  2. 1 1
      website/documentation/content/line_plot_documentation.py

+ 29 - 11
nicegui/elements/line_plot.py

@@ -1,4 +1,4 @@
-from typing import Any, List
+from typing import Any, List, Literal, Tuple, Union
 
 from .pyplot import Pyplot
 
@@ -42,11 +42,19 @@ class LinePlot(Pyplot):
         self._convert_to_html()
         return self
 
-    def push(self, x: List[float], Y: List[List[float]]) -> None:
+    def push(self,
+             x: List[float],
+             Y: List[List[float]],
+             *,
+             x_limits: Union[None, Literal['auto'], Tuple[float, float]] = 'auto',
+             y_limits: Union[None, Literal['auto'], Tuple[float, float]] = 'auto',
+             ) -> None:
         """Push new data to the plot.
 
         :param x: list of x values
         :param Y: list of lists of y values (one list per line)
+        :param x_limits: new x limits (tuple of floats, or "auto" to fit the data points, or ``None`` to leave unchanged)
+        :param y_limits: new y limits (tuple of floats, or "auto" to fit the data points, or ``None`` to leave unchanged)
         """
         self.push_counter += 1
 
@@ -61,15 +69,25 @@ class LinePlot(Pyplot):
             line.set_xdata(self.x)
             line.set_ydata(self.Y[i])
 
-        flat_y = [y_i for y in self.Y for y_i in y]
-        min_x = min(self.x)
-        max_x = max(self.x)
-        min_y = min(flat_y)
-        max_y = max(flat_y)
-        pad_x = 0.01 * (max_x - min_x)
-        pad_y = 0.01 * (max_y - min_y)
-        self.fig.gca().set_xlim(min_x - pad_x, max_x + pad_x)
-        self.fig.gca().set_ylim(min_y - pad_y, max_y + pad_y)
+        if isinstance(x_limits, tuple):
+            self.fig.gca().set_xlim(*x_limits)
+        elif x_limits == 'auto':
+            min_x = min(self.x)
+            max_x = max(self.x)
+            if min_x != max_x:
+                pad_x = 0.01 * (max_x - min_x)
+                self.fig.gca().set_xlim(min_x - pad_x, max_x + pad_x)
+
+        if isinstance(y_limits, tuple):
+            self.fig.gca().set_ylim(*y_limits)
+        elif y_limits == 'auto':
+            flat_y = [y_i for y in self.Y for y_i in y]
+            min_y = min(flat_y)
+            max_y = max(flat_y)
+            if min_y != max_y:
+                pad_y = 0.01 * (max_y - min_y)
+                self.fig.gca().set_ylim(min_y - pad_y, max_y + pad_y)
+
         self._convert_to_html()
         self.update()
 

+ 1 - 1
website/documentation/content/line_plot_documentation.py

@@ -16,7 +16,7 @@ def main_demo() -> None:
         x = now.timestamp()
         y1 = math.sin(x)
         y2 = math.cos(x)
-        line_plot.push([now], [[y1], [y2]])
+        line_plot.push([now], [[y1], [y2]], y_limits=(-1.5, 1.5))
 
     line_updates = ui.timer(0.1, update_line_plot, active=False)
     line_checkbox = ui.checkbox('active').bind_value(line_updates, 'active')