From f97900a2014457bb4d56a70834a76069ab55d3ec Mon Sep 17 00:00:00 2001 From: Serge Bakharev Date: Fri, 8 May 2026 23:52:49 +1000 Subject: [PATCH] basic monitoring graph --- carveracontroller/CNC.py | 3 +- carveracontroller/Controller.py | 3 + carveracontroller/main.py | 105 + carveracontroller/makera.kv | 456 +-- carveracontroller/ui/graph_widget_twinx.py | 3051 ++++++++++++++++++++ poetry.lock | 996 ++++++- pyproject.toml | 1 + 7 files changed, 4391 insertions(+), 224 deletions(-) create mode 100644 carveracontroller/ui/graph_widget_twinx.py diff --git a/carveracontroller/CNC.py b/carveracontroller/CNC.py index 07421ff6..2e61d010 100644 --- a/carveracontroller/CNC.py +++ b/carveracontroller/CNC.py @@ -222,7 +222,8 @@ class CNC: "st_calibrate" : 0, "st_cover" : 0, "st_tool_sensor" : 0, - "st_e_stop" : 0 + "st_e_stop" : 0, + "spindle_pwm_request": 0 } diff --git a/carveracontroller/Controller.py b/carveracontroller/Controller.py index 0112fb6c..85d73058 100644 --- a/carveracontroller/Controller.py +++ b/carveracontroller/Controller.py @@ -1360,6 +1360,9 @@ def parseBracketAngle(self, line,): if 'H' in d: CNC.vars["halt_reason"] = int(d['H'][0]) + if 'PWM' in d: + CNC.vars["spindle_pwm_request"] = int(d['PWM'][0]) + self.posUpdate = True def parseBigParentheses(self, line): diff --git a/carveracontroller/main.py b/carveracontroller/main.py index cb7f8de3..ad1113af 100644 --- a/carveracontroller/main.py +++ b/carveracontroller/main.py @@ -200,6 +200,17 @@ def ios_webbrowser_open(url, new=None): from .addons.tooltips.Tooltips import Tooltip,ToolTipButton,ToolTipDropDown from .addons.probing.ProbingControls import ProbeButton +import matplotlib +matplotlib.use('Agg') +import matplotlib.pyplot as plt +import matplotlib as mpl +from collections import deque +from .ui.graph_widget_twinx import StaticMatplotFigureTwinx # noqa: F401 – registers widget with Kivy Factory + +mpl.rcParams['path.simplify'] = True +mpl.rcParams['path.simplify_threshold'] = 1.0 +mpl.rcParams['agg.path.chunksize'] = 1000 + def load_halt_translations(tr: translation.Lang): """Loads the appropriate language translation""" HALT_REASON = { @@ -2703,6 +2714,8 @@ class Makera(RelativeLayout): gcode_playing = BooleanProperty(False) gcode_cannot_visualise = BooleanProperty(False) + telemetry_chart = ObjectProperty() + probing_popup = ObjectProperty() coord_config = {} @@ -2912,6 +2925,16 @@ def __init__(self, ctl_version): self.gcode_viewer.set_error_popup_callback(self._on_gcode_cannot_visualise) self.gcode_viewer.time_estimate_progress_callback = self._on_time_estimate_progress + # init telemetry chart + self._telemetry_spindle_temp = deque(maxlen=Makera._TELEMETRY_WINDOW) + self._telemetry_spindle_pwm_request = deque(maxlen=Makera._TELEMETRY_WINDOW) + self._init_telemetry_chart() + # Drive the chart at a fixed 1 Hz, independent of machine connection state. + Clock.schedule_interval(lambda dt: self._update_telemetry_chart(), 1.0) + # Schedule an initial home() for the next frame so the widget has its + # final layout size before the first draw. + Clock.schedule_once(lambda dt: self.telemetry_chart.home(), 0) + # init settings self.config = ConfigParser() self.config_popup = ConfigPopup() @@ -4967,6 +4990,86 @@ def updateCompressProgress(self, value): # Schedule callback with a short delay to ensure decompression is fully complete Clock.schedule_once(lambda dt: callback(), 0.1) + # ----------------------------------------------------------------------- + _TELEMETRY_WINDOW = 300 # seconds / points shown at once + + def _init_telemetry_chart(self): + fs = dp(8) + bg = 18 / 255 + fig, ax1 = plt.subplots(1, 1) + fig.patch.set_facecolor((bg, bg, bg, 1)) + ax1.set_facecolor((bg, bg, bg, 1)) + ax2 = ax1.twinx() + + self._telemetry_line_temp, = ax1.plot([], [], color='#ff6b35', linewidth=1.5) + self._telemetry_line_feed, = ax2.plot([], [], color='#4ec9b0', linewidth=1.5) + + ax1.set_xlabel('Time (s)', color='#888888', fontsize=fs) + ax1.set_ylabel('Temp (°C)', color='#ff6b35', fontsize=fs) + ax2.set_ylabel('Spindle Power Request %', color='#4ec9b0', fontsize=fs) + + for ax in (ax1, ax2): + ax.tick_params(colors='#888888', labelsize=fs) + ax.spines['bottom'].set_color('#444444') + ax.spines['left'].set_color('#444444') + ax.spines['right'].set_color('#444444') + ax.spines['top'].set_visible(False) + + ax1.yaxis.label.set_color('#ff6b35') + ax1.tick_params(axis='y', colors='#ff6b35', labelsize=fs) + ax2.yaxis.label.set_color('#4ec9b0') + ax2.tick_params(axis='y', colors='#4ec9b0', labelsize=fs) + + ax1.set_xlim(-self._TELEMETRY_WINDOW, 0) + ax1.set_ylim(0, 100) + ax2.set_ylim(0, 1000) + + fig.tight_layout(pad=0.5) + + self._telemetry_ax1 = ax1 + self._telemetry_ax2 = ax2 + + # Assign figure and initialise the widget's limit properties so + # home() has the correct anchors from the start. + self.telemetry_chart.figure = fig + self.telemetry_chart.xmin = -self._TELEMETRY_WINDOW + self.telemetry_chart.xmax = 0 + self.telemetry_chart.ymin = 0 + self.telemetry_chart.ymax = 100 + self.telemetry_chart.ymin2 = 0 + self.telemetry_chart.ymax2 = 1000 + + # ----------------------------------------------------------------------- + def _update_telemetry_chart(self): + self._telemetry_spindle_temp.append(CNC.vars["spindletemp"]) + self._telemetry_spindle_pwm_request.append(CNC.vars["spindle_pwm_request"]) + + n = len(self._telemetry_spindle_temp) + # x = 0 is "now"; each previous sample is 1 s earlier → [-n+1, …, -1, 0] + times = list(range(-(n - 1), 1)) + + self._telemetry_line_temp.set_data(times, list(self._telemetry_spindle_temp)) + self._telemetry_line_feed.set_data(times, list(self._telemetry_spindle_pwm_request)) + + # Recompute y limits from current data and write back to widget so + # home() (called below) applies them correctly. + temp_vals = list(self._telemetry_spindle_temp) + feed_vals = list(self._telemetry_spindle_pwm_request) + + if temp_vals: + pad = max((max(temp_vals) - min(temp_vals)) * 0.1, 1.0) + self.telemetry_chart.ymin = min(temp_vals) - pad + self.telemetry_chart.ymax = max(temp_vals) + pad + + if feed_vals: + pad = max((max(feed_vals) - min(feed_vals)) * 0.1, 10.0) + self.telemetry_chart.ymin2 = min(feed_vals) - pad + self.telemetry_chart.ymax2 = max(feed_vals) + pad + + # home() sets axes limits from widget properties and calls draw_idle() + # + flush_events() — the idiomatic live-update pattern from example_live_data. + self.telemetry_chart.home() + # ----------------------------------------------------------------------- def updateStatus(self, *args): try: @@ -5495,6 +5598,8 @@ def updateDiagnose(self, *args): self.diagnose_popup.st_atc_home.state = CNC.vars["st_atc_home"] self.diagnose_popup.st_tool_sensor.state = CNC.vars["st_tool_sensor"] self.diagnose_popup.st_e_stop.state = CNC.vars["st_e_stop"] + + except: logger.error(sys.exc_info()[1]) diff --git a/carveracontroller/makera.kv b/carveracontroller/makera.kv index fffbed13..7673cbe9 100644 --- a/carveracontroller/makera.kv +++ b/carveracontroller/makera.kv @@ -2,6 +2,7 @@ #:import Factory kivy.factory.Factory #:import digitize_v carveracontroller.Utils.digitize_v #:import tr carveracontroller.__main__.tr +#:import StaticMatplotFigureTwinx carveracontroller.ui.graph_widget_twinx #:include addons/tooltips/Tooltips.kv #:include addons/probing/ProbingPopup.kv @@ -3483,6 +3484,7 @@ cmd_manager: cmd_manager gcode_viewer_container: gcode_viewer_container gcode_play_slider: gcode_play_slider + telemetry_chart: telemetry_chart BoxLayout: padding: dp(5) @@ -4117,231 +4119,253 @@ disabled: (app.state != 'Idle' and app.state != 'Pause' and root.allow_mdi_while_machine_running in ['0', 'false', False]) on_release: root.send_cmd() - FloatBox: - id: float_layout - tool_bar: tool_bar - t1: t1 - t2: t2 - t3: t3 - t4: t4 - t5: t5 - t6: t6 - laser: laser - hide_all: hide_all - gcode_ctl_bar: gcode_ctl_bar - BoxLayout: - id: gcode_viewer_container - pos: float_layout.pos - orientation: 'vertical' - BoxLayout: - id: tool_bar - orbit: True - size_hint_x: 1 - size_hint_y: 0.1 if app.show_gcode_ctl_bar else 0 - opacity: 1 if app.show_gcode_ctl_bar else 0 - # pos: float_layout.x, 0 - pos_hint: {'x': 0.0, 'top': 1.0} + BoxLayout: + orientation: 'vertical' + Splitter: + sizable_from: 'bottom' + strip_size: '5dp' canvas.before: Color: - rgba: 10/255, 10/255, 10/255, 0.2 + rgba: 0.6, 0.6, 0.6, 1 Rectangle: - pos: self.pos - size: self.size - BoxLayout: - # tool bar buttons - TransparentButton: - width: '50dp' - active: tool_bar.orbit - icon: 'data/orbit.png' - disabled: app.selected_remote_filename == '' and app.selected_local_filename == '' - on_release: - tool_bar.orbit = True - root.gcode_viewer.set_orbit(True) - TransparentButton: - width: '50dp' - active: not tool_bar.orbit - icon: 'data/pan.png' - disabled: app.selected_remote_filename == '' and app.selected_local_filename == '' - on_release: - tool_bar.orbit = False - root.gcode_viewer.set_orbit(False) - TransparentButton: - width: '50dp' - icon: 'data/zoom_out.png' - disabled: app.selected_remote_filename == '' and app.selected_local_filename == '' - on_release: - root.gcode_viewer.zoom_in() - TransparentButton: - width: '50dp' - icon: 'data/zoom_in.png' - disabled: app.selected_remote_filename == '' and app.selected_local_filename == '' - on_release: - root.gcode_viewer.zoom_out() - TransparentButton: - width: '50dp' - icon: 'data/zoom_fit.png' - disabled: app.selected_remote_filename == '' and app.selected_local_filename == '' - on_release: - root.gcode_viewer.restore_default_view() - Widget: - - # tools - TransparentButton: - id: t1 - width: '44dp' - valign: 'middle' - text: 'T1' - active: app.tool == 1 - disabled: 1 not in root.used_tools - min_icon: 'data/eye.png' - on_release: - self.min_active = not self.min_active - app.root.filter_tool() - TransparentButton: - id: t2 - width: '44dp' - valign: 'middle' - text: 'T2' - active: app.tool == 2 - disabled: 2 not in root.used_tools - min_icon: 'data/eye.png' - on_release: - self.min_active = not self.min_active - app.root.filter_tool() - TransparentButton: - id: t3 - width: '44dp' - valign: 'middle' - text: 'T3' - active: app.tool == 3 - disabled: 3 not in root.used_tools - min_icon: 'data/eye.png' - on_release: - self.min_active = not self.min_active - app.root.filter_tool() - TransparentButton: - id: t4 - width: '44dp' - valign: 'middle' - text: 'T4' - active: app.tool == 4 - disabled: 4 not in root.used_tools - min_icon: 'data/eye.png' - on_release: - self.min_active = not self.min_active - app.root.filter_tool() - TransparentButton: - id: t5 - width: '44dp' - valign: 'middle' - text: 'T5' - active: app.tool == 5 - disabled: 5 not in root.used_tools - min_icon: 'data/eye.png' - on_release: - self.min_active = not self.min_active - app.root.filter_tool() - TransparentButton: - id: t6 - width: '44dp' - valign: 'middle' - text: 'T6' - active: app.tool == 6 - disabled: 6 not in root.used_tools - min_icon: 'data/eye.png' - on_release: - self.min_active = not self.min_active - app.root.filter_tool() - TransparentButton: - id: laser - width: '44dp' - valign: 'middle' - icon: 'data/laser.png' - active: app.lasering == True - disabled: 7 not in root.used_tools - min_icon: 'data/eye.png' - on_release: - self.min_active = not self.min_active - app.root.filter_tool() - TransparentGrayButton: - id: hide_all - width: '50dp' - icon: 'data/eye.png' - disabled: app.selected_remote_filename == '' and app.selected_local_filename == '' - on_release: - self.active = not self.active - if self.active:\ - t1.min_active = True;\ - t2.min_active = True;\ - t3.min_active = True;\ - t4.min_active = True;\ - t5.min_active = True;\ - t6.min_active = True;\ - laser.min_active = True; - else:\ - t1.min_active = False;\ - t2.min_active = False;\ - t3.min_active = False;\ - t4.min_active = False;\ - t5.min_active = False;\ - t6.min_active = False;\ - laser.min_active = False; - app.root.filter_tool() + pos: self.x, self.y + size: self.width, self.strip_size + FloatBox: + id: float_layout + tool_bar: tool_bar + t1: t1 + t2: t2 + t3: t3 + t4: t4 + t5: t5 + t6: t6 + laser: laser + hide_all: hide_all + gcode_ctl_bar: gcode_ctl_bar + BoxLayout: + id: gcode_viewer_container + pos: float_layout.pos + orientation: 'vertical' + BoxLayout: + id: tool_bar + orbit: True + size_hint_x: 1 + size_hint_y: 0.1 if app.show_gcode_ctl_bar else 0 + opacity: 1 if app.show_gcode_ctl_bar else 0 + # pos: float_layout.x, 0 + pos_hint: {'x': 0.0, 'top': 1.0} + canvas.before: + Color: + rgba: 10/255, 10/255, 10/255, 0.2 + Rectangle: + pos: self.pos + size: self.size + BoxLayout: + # tool bar buttons + TransparentButton: + width: '50dp' + active: tool_bar.orbit + icon: 'data/orbit.png' + disabled: app.selected_remote_filename == '' and app.selected_local_filename == '' + on_release: + tool_bar.orbit = True + root.gcode_viewer.set_orbit(True) + TransparentButton: + width: '50dp' + active: not tool_bar.orbit + icon: 'data/pan.png' + disabled: app.selected_remote_filename == '' and app.selected_local_filename == '' + on_release: + tool_bar.orbit = False + root.gcode_viewer.set_orbit(False) + TransparentButton: + width: '50dp' + icon: 'data/zoom_out.png' + disabled: app.selected_remote_filename == '' and app.selected_local_filename == '' + on_release: + root.gcode_viewer.zoom_in() + TransparentButton: + width: '50dp' + icon: 'data/zoom_in.png' + disabled: app.selected_remote_filename == '' and app.selected_local_filename == '' + on_release: + root.gcode_viewer.zoom_out() + TransparentButton: + width: '50dp' + icon: 'data/zoom_fit.png' + disabled: app.selected_remote_filename == '' and app.selected_local_filename == '' + on_release: + root.gcode_viewer.restore_default_view() + Widget: + + # tools + TransparentButton: + id: t1 + width: '44dp' + valign: 'middle' + text: 'T1' + active: app.tool == 1 + disabled: 1 not in root.used_tools + min_icon: 'data/eye.png' + on_release: + self.min_active = not self.min_active + app.root.filter_tool() + TransparentButton: + id: t2 + width: '44dp' + valign: 'middle' + text: 'T2' + active: app.tool == 2 + disabled: 2 not in root.used_tools + min_icon: 'data/eye.png' + on_release: + self.min_active = not self.min_active + app.root.filter_tool() + TransparentButton: + id: t3 + width: '44dp' + valign: 'middle' + text: 'T3' + active: app.tool == 3 + disabled: 3 not in root.used_tools + min_icon: 'data/eye.png' + on_release: + self.min_active = not self.min_active + app.root.filter_tool() + TransparentButton: + id: t4 + width: '44dp' + valign: 'middle' + text: 'T4' + active: app.tool == 4 + disabled: 4 not in root.used_tools + min_icon: 'data/eye.png' + on_release: + self.min_active = not self.min_active + app.root.filter_tool() + TransparentButton: + id: t5 + width: '44dp' + valign: 'middle' + text: 'T5' + active: app.tool == 5 + disabled: 5 not in root.used_tools + min_icon: 'data/eye.png' + on_release: + self.min_active = not self.min_active + app.root.filter_tool() + TransparentButton: + id: t6 + width: '44dp' + valign: 'middle' + text: 'T6' + active: app.tool == 6 + disabled: 6 not in root.used_tools + min_icon: 'data/eye.png' + on_release: + self.min_active = not self.min_active + app.root.filter_tool() + TransparentButton: + id: laser + width: '44dp' + valign: 'middle' + icon: 'data/laser.png' + active: app.lasering == True + disabled: 7 not in root.used_tools + min_icon: 'data/eye.png' + on_release: + self.min_active = not self.min_active + app.root.filter_tool() + TransparentGrayButton: + id: hide_all + width: '50dp' + icon: 'data/eye.png' + disabled: app.selected_remote_filename == '' and app.selected_local_filename == '' + on_release: + self.active = not self.active + if self.active:\ + t1.min_active = True;\ + t2.min_active = True;\ + t3.min_active = True;\ + t4.min_active = True;\ + t5.min_active = True;\ + t6.min_active = True;\ + laser.min_active = True; + else:\ + t1.min_active = False;\ + t2.min_active = False;\ + t3.min_active = False;\ + t4.min_active = False;\ + t5.min_active = False;\ + t6.min_active = False;\ + laser.min_active = False; + app.root.filter_tool() + BoxLayout: + id: gcode_ctl_bar + size_hint_x: 1 + size_hint_y: 0.2 if app.show_gcode_ctl_bar else 0 + opacity: 1 if app.show_gcode_ctl_bar else 0 + orientation: 'vertical' + pos: float_layout.pos + canvas.before: + Color: + rgba: 10/255, 10/255, 10/255, 0.2 + Rectangle: + pos: self.pos + size: self.size + BoxLayout: + Widget: + TransparentButton: + height: '0dp' + icon: 'data/to_start.png' + icon_size: 24 + disabled: (app.selected_remote_filename == '' and app.selected_local_filename == '') or (app.state != 'Idle' and app.state != 'N/A') + on_release: + root.gcode_play_to_start() + TransparentButton: + icon: 'data/backward.png' + icon_size: 24 + disabled: (app.selected_remote_filename == '' and app.selected_local_filename == '') or (app.state != 'Idle' and app.state != 'N/A') + on_release: + root.gcode_play_speed_down() + TransparentButton: + icon: 'data/suspend.png' if root.gcode_playing else 'data/start.png' + icon_size: 26 if root.gcode_playing else 18 + disabled: (app.selected_remote_filename == '' and app.selected_local_filename == '') or (app.state != 'Idle' and app.state != 'N/A') + on_release: + root.gcode_play_toggle() + TransparentButton: + icon: 'data/forward.png' + icon_size: 24 + disabled: (app.selected_remote_filename == '' and app.selected_local_filename == '') or (app.state != 'Idle' and app.state != 'N/A') + on_release: + root.gcode_play_speed_up() + TransparentButton: + icon: 'data/to_end.png' + icon_size: 24 + disabled: (app.selected_remote_filename == '' and app.selected_local_filename == '') or (app.state != 'Idle' and app.state != 'N/A') + on_release: + root.gcode_play_to_end() + + Widget: + GcodePlaySlider: + id: gcode_play_slider + height: '15dp' + min: 0 + max: 1000 + value: 1000 + disabled: app.selected_remote_filename == '' and app.selected_local_filename == '' BoxLayout: - id: gcode_ctl_bar - size_hint_x: 1 - size_hint_y: 0.2 if app.show_gcode_ctl_bar else 0 - opacity: 1 if app.show_gcode_ctl_bar else 0 - orientation: 'vertical' - pos: float_layout.pos + size_hint_y: None + height: '200dp' canvas.before: Color: - rgba: 10/255, 10/255, 10/255, 0.2 + rgba: 20/255, 20/255, 20/255, 1 Rectangle: pos: self.pos size: self.size - BoxLayout: - Widget: - TransparentButton: - height: '0dp' - icon: 'data/to_start.png' - icon_size: 24 - disabled: (app.selected_remote_filename == '' and app.selected_local_filename == '') or (app.state != 'Idle' and app.state != 'N/A') - on_release: - root.gcode_play_to_start() - TransparentButton: - icon: 'data/backward.png' - icon_size: 24 - disabled: (app.selected_remote_filename == '' and app.selected_local_filename == '') or (app.state != 'Idle' and app.state != 'N/A') - on_release: - root.gcode_play_speed_down() - TransparentButton: - icon: 'data/suspend.png' if root.gcode_playing else 'data/start.png' - icon_size: 26 if root.gcode_playing else 18 - disabled: (app.selected_remote_filename == '' and app.selected_local_filename == '') or (app.state != 'Idle' and app.state != 'N/A') - on_release: - root.gcode_play_toggle() - TransparentButton: - icon: 'data/forward.png' - icon_size: 24 - disabled: (app.selected_remote_filename == '' and app.selected_local_filename == '') or (app.state != 'Idle' and app.state != 'N/A') - on_release: - root.gcode_play_speed_up() - TransparentButton: - icon: 'data/to_end.png' - icon_size: 24 - disabled: (app.selected_remote_filename == '' and app.selected_local_filename == '') or (app.state != 'Idle' and app.state != 'N/A') - on_release: - root.gcode_play_to_end() - - Widget: - GcodePlaySlider: - id: gcode_play_slider - height: '15dp' - min: 0 - max: 1000 - value: 1000 - disabled: app.selected_remote_filename == '' and app.selected_local_filename == '' + StaticMatplotFigureTwinx: + id: telemetry_chart SettingPage: name: 'Setting' diff --git a/carveracontroller/ui/graph_widget_twinx.py b/carveracontroller/ui/graph_widget_twinx.py new file mode 100644 index 00000000..0aa72916 --- /dev/null +++ b/carveracontroller/ui/graph_widget_twinx.py @@ -0,0 +1,3051 @@ +"""Based on the example https://github.com/mp-007/kivy_matplotlib_widget/tree/main/examples/example_live_data""" + +import copy +import math +from typing import Any, Optional +from weakref import WeakKeyDictionary + +import matplotlib + +matplotlib.use("Agg") # must be set before importing pyplot or backends +import matplotlib.lines as mlines # noqa: E402 +import matplotlib.pyplot # noqa: E402 +import numpy as np # noqa: E402 +from kivy.clock import Clock # noqa: E402 +from kivy.core.window import Window # noqa: E402 +from kivy.graphics.texture import Texture # noqa: E402 +from kivy.graphics.transformation import Matrix # noqa: E402 +from kivy.lang import Builder # noqa: E402 +from kivy.metrics import dp # noqa: E402 +from kivy.properties import ( # noqa: E402 + AliasProperty, + BooleanProperty, + BoundedNumericProperty, + DictProperty, + ListProperty, + NumericProperty, + ObjectProperty, + OptionProperty, +) +from kivy.uix.widget import Widget # noqa: E402 +from kivy.utils import get_color_from_hex # noqa: E402 +from kivy.vector import Vector # noqa: E402 +from matplotlib import cbook # noqa: E402 +from matplotlib.backend_bases import ResizeEvent # noqa: E402 +from matplotlib.backends.backend_agg import FigureCanvasAgg # noqa: E402 +from matplotlib.colors import to_hex # noqa: E402 + +selector_widgets_available = False +try: + selector_widgets_available = True + from kivy_matplotlib_widget.uix.selector_widget import ( # noqa: E402 + EllipseRelativeLayout, + LassoRelativeLayout, + ResizeRelativeLayout, + SpanRelativeLayout, + ) +except ImportError: + print("Selector widgets are not available") + + +class MatplotlibEvent: + x: Any = None + y: Any = None + pickradius: Any = None + inaxes: Any = None + projection: bool = False + compare_xdata: bool = False + pick_radius_axis: str = "both" + + +class MatplotFigureTwinx(Widget): # type: ignore[misc] + """Widget to show a matplotlib figure in kivy. + The figure is rendered internally in an AGG backend then + the rgba data is obtained and blitted into a kivy texture. + """ + + figure = ObjectProperty(None) + _box_pos = ListProperty([0, 0]) + _box_size = ListProperty([0, 0]) + _img_texture = ObjectProperty(None) + _alpha_box = NumericProperty(0) + _bitmap = None + _pressed = False + do_update = False + figcanvas = ObjectProperty(None) + translation_touches = BoundedNumericProperty(1, min=1) + do_scale = BooleanProperty(True) + scale_min = NumericProperty(0.01) + scale_max = NumericProperty(1e20) + transform = ObjectProperty(Matrix()) + _alpha_hor = NumericProperty(0) + _alpha_ver = NumericProperty(0) + pos_x_rect_hor = NumericProperty(0) + pos_y_rect_hor = NumericProperty(0) + pos_x_rect_ver = NumericProperty(0) + pos_y_rect_ver = NumericProperty(0) + invert_rect_ver = BooleanProperty(False) + invert_rect_hor = BooleanProperty(False) + legend_do_scroll_x = BooleanProperty(True) + legend_do_scroll_y = BooleanProperty(True) + interactive_axis = BooleanProperty(False) + do_pan_x = BooleanProperty(True) + do_pan_y = BooleanProperty(True) + do_zoom_x = BooleanProperty(True) + do_zoom_y = BooleanProperty(True) + fast_draw = BooleanProperty(True) # True will don't draw axis + xsorted = BooleanProperty(False) # to manage x sorted data + minzoom = NumericProperty(dp(20)) + twinx = BooleanProperty(False) + compare_xdata = BooleanProperty(False) + hover_instance = ObjectProperty(None, allownone=True) + nearest_hover_instance = ObjectProperty(None, allownone=True) + compare_hover_instance = ObjectProperty(None, allownone=True) + disable_mouse_scrolling = BooleanProperty(False) + disable_double_tap = BooleanProperty(False) + text_instance = None + min_max_option = BooleanProperty(True) + auto_zoom = BooleanProperty(False) + zoom_angle_detection = NumericProperty(15) # in degree + auto_cursor = BooleanProperty(False) + autoscale_visible_only = BooleanProperty(True) + autoscale_axis = OptionProperty("both", options=["both", "x", "y"]) + autoscale_tight = BooleanProperty(False) + desktop_mode = BooleanProperty(True) # change mouse hover for selector widget + current_selector = OptionProperty("None", options=["None", "rectangle", "lasso", "ellipse", "span", "custom"]) + highlight_hover = BooleanProperty(False) + highlight_prop = DictProperty({}) + highlight_alpha = NumericProperty(0.2) + myevent = MatplotlibEvent() + pick_minimum_radius = NumericProperty(dp(50)) + pick_radius_axis = OptionProperty("both", options=["both", "x", "y"]) + + def on_figure(self, _obj: Any, _value: Any) -> None: + self.figcanvas = _FigureCanvas(self.figure, self) + self.figcanvas._isDrawn = False + _left, _b, w, h = self.figure.bbox.bounds + w = int(math.ceil(w)) + h = int(math.ceil(h)) + self.width = w + self.height = h + + if len(self.figure.axes) > 0 and self.figure.axes[0]: + # add copy patch + ax = self.figure.axes[0] + self.axes = ax + patch_cpy = copy.copy(ax.patch) + patch_cpy.set_visible(False) + for pos in ["right", "top", "bottom", "left"]: + ax.spines[pos].set_zorder(10) + patch_cpy.set_zorder(9) + self.background_patch_copy = ax.add_patch(patch_cpy) + + # set default xmin/xmax and ymin/ymax + self.xmin, self.xmax = ax.get_xlim() + self.ymin, self.ymax = ax.get_ylim() + + if len(self.figure.axes) == 2: + self.twinx = True + ax2 = self.figure.axes[1] + patch_cpy_ax2 = copy.copy(ax2.patch) + patch_cpy_ax2.set_visible(False) + for pos in ["right", "top", "bottom", "left"]: + ax2.spines[pos].set_zorder(10) + patch_cpy_ax2.set_zorder(9) + self.background_ax2_patch_copy = ax2.add_patch(patch_cpy_ax2) + self.ymin2, self.ymax2 = ax.get_ylim() + else: + self.twinx = False + self.background_ax2_patch_copy = None + self.ymin2 = None + self.ymax2 = None + + if self.legend_instance: + # remove all legend_instance from parent widget + for current_legend in self.legend_instance: + current_legend.parent.remove_widget(current_legend) + self.legend_instance: list[Any] = [] + + if self.auto_cursor: + if len(self.figure.axes) == 2: + self.register_lines(list(self.figure.axes[0].lines + self.figure.axes[1].lines)) + elif len(self.figure.axes) > 0: + self.register_lines(list(self.figure.axes[0].lines)) + + # Texture + self._img_texture = Texture.create(size=(w, h)) + + if self.selector and self.axes: + self.selector.resize_wgt.ax = self.axes + + # close last figure in memory (avoid max figure warning) + matplotlib.pyplot.close() + + def __init__(self, **kwargs: Any) -> None: + super().__init__(**kwargs) + + # figure info + self.figure = None + self.axes = None + self.xmin = None + self.xmax = None + self.ymin = None + self.ymax = None + self.ymin2 = None + self.ymax2 = None + self.lines: list[Any] = [] + + # option + self.touch_mode = "pan" + self.hover_on = False + self.cursor_xaxis_formatter = None # used matplotlib formatter to display x cursor value + self.cursor_yaxis_formatter = None # used matplotlib formatter to display y cursor value (left axis) + self.cursor_yaxis2_formatter = None # used matplotlib formatter to display y cursor value (right axis) + + # zoom box coordonnate + self.x0_box = None + self.y0_box = None + self.x1_box = None + self.y1_box = None + + # clear touches on touch up + self._touches: list[Any] = [] + self._last_touch_pos: dict[Any, Any] = {} + + # background + self.background = None + self.background_patch_copy = None + self.background_ax2_patch_copy = None + + # twin x axis + self.twinx = False + + # manage adjust x and y + self.anchor_x: Optional[str] = None + self.anchor_y: Optional[str] = None + + # manage hover data + self.x_hover_data = None + self.y_hover_data = None + + # pan management + self.first_touch_pan: Optional[str] = None + + # trick to manage wrong canvas size on first call (compare_hover) + self.first_call_compare_hover = False + + # cross hair cursor + self.horizontal_line: Any = None + self.vertical_line: Any = None + + # manage cursor update on right axis + self.cursor_last_axis = None + self.cursor_last_y = 0 + + # manage show compare cursor on release + self.show_compare_cursor = False + + # manage back and next event + _stack_cls = cbook._Stack if hasattr(cbook, "_Stack") else cbook.Stack # type: ignore[attr-defined] + self._nav_stack: Any = _stack_cls() + self.set_history_buttons() + + # legend management + self.legend_instance = [] + self.current_legend = None + + # selector management + self.kv_post_done = False + self.selector: Any = None + + # highlight management + self.last_line: Any = None + self.last_line_prop: dict[str, Any] = {} + + self.bind(size=self._on_size) + + def on_kv_post(self, _: Any) -> None: + # if not self.selector: + if self.current_selector != "None" and selector_widgets_available: + if self.current_selector == "rectangle": + self.set_selector(ResizeRelativeLayout) + elif self.current_selector == "lasso": + self.set_selector(LassoRelativeLayout) + elif self.current_selector == "ellipse": + self.set_selector(EllipseRelativeLayout) + elif self.current_selector == "span": + self.set_selector(SpanRelativeLayout) + self.kv_post_done = True + + def transform_eval(self, x: Any, axis: Any) -> Any: + custom_transform = axis.get_transform() + return custom_transform.transform_non_affine(np.array([x]))[0] + + def inv_transform_eval(self, x: Any, axis: Any) -> Any: + inv_custom_transform = axis.get_transform().inverted() + return inv_custom_transform.transform_non_affine(np.array([x]))[0] + + def on_current_selector(self, _instance: Any, value: Any, *_args: Any) -> None: + if self.kv_post_done and selector_widgets_available: + if value == "rectangle": + self.set_selector(ResizeRelativeLayout) + elif value == "lasso": + self.set_selector(LassoRelativeLayout) + elif value == "ellipse": + self.set_selector(EllipseRelativeLayout) + elif self.current_selector == "span": + self.set_selector(SpanRelativeLayout) + elif value == "None": + if self.selector: + Window.unbind(mouse_pos=self.selector.resize_wgt.on_mouse_pos) + self.parent.remove_widget(self.selector) + self.selector = None + + def set_selector(self, selector: Any, *_args: Any) -> None: + selector_collection = None + selector_line = None + callback = None + callback_clear = None + if self.selector: + selector_collection = self.selector.resize_wgt.collection + selector_line = self.selector.resize_wgt.line + callback = self.selector.resize_wgt.callback + callback_clear = self.selector.resize_wgt.callback_clear + Window.unbind(mouse_pos=self.selector.resize_wgt.on_mouse_pos) + self.parent.remove_widget(self.selector) + + self.selector = selector(figure_wgt=self, desktop_mode=self.desktop_mode) + self.selector.resize_wgt.ax = self.axes + if selector_collection: + self.set_collection() + if selector_line: + self.set_line(selector_line) + if callback: + self.set_callback(callback) + if callback_clear: + self.set_callback_clear(callback_clear) + + self.parent.add_widget(self.selector) + + def set_collection(self) -> None: + self.selector.resize_wgt.ax = self.axes + collections = self.figure.axes[0].collections + + if collections: + self.selector.resize_wgt.set_collection(collections[0]) + + def set_line(self, line: Any) -> None: + self.selector.resize_wgt.ax = self.axes + self.selector.resize_wgt.set_line(line) + + def set_callback(self, callback: Any) -> None: + self.selector.resize_wgt.set_callback(callback) + + def set_callback_clear(self, callback: Any) -> None: + self.selector.resize_wgt.set_callback_clear(callback) + + def register_lines(self, lines: list[Any]) -> None: + """register lines method + + Args: + lines (list): list of matplolib line class + + Return: + None + """ + ax = self.figure.axes[0] + # use sel,axes limit to avoid graph rescale + xmin, xmax = ax.get_xlim() + ymin, ymax = ax.get_ylim() + + # create cross hair cursor + self.horizontal_line = ax.axhline(y=self.ymin, color="k", lw=0.8, ls="--", visible=False) + self.vertical_line = ax.axvline(x=self.xmin, color="k", lw=0.8, ls="--", visible=False) + + # register lines + self.lines = lines + + # cursor text + self.text = ax.text(1.0, 1.01, "", transform=ax.transAxes, ha="right") + + def set_cross_hair_visible(self, visible: bool) -> None: + """set curcor visibility + + Args: + visible (bool): make cursor visble or not + + Return: + None + + """ + self.horizontal_line.set_visible(visible) + self.vertical_line.set_visible(visible) + self.text.set_visible(visible) + + def update_cursor(self) -> None: + if self.twinx and self.horizontal_line and self.cursor_last_axis: # noqa: SIM102 + if self.horizontal_line.get_visible() or self.hover_instance: # noqa: SIM102 + if self.cursor_last_axis == self.figure.axes[1]: + if self.hover_instance: + if ( + self.hover_instance.show_cursor + and self.x_hover_data is not None + and self.y_hover_data is not None + ): + self.y_hover_data = self.cursor_last_y + xy_pos = self.figure.axes[1].transData.transform([(self.x_hover_data, self.y_hover_data)]) + self.hover_instance.y_hover_pos = float(xy_pos[0][1]) + self.y + + else: + new_y = self.cursor_last_y + x = self.vertical_line.get_xdata() + trans = self.figure.axes[0].transData.inverted() + xy_pos = self.figure.axes[1].transData.transform([(x, new_y)]) + xdata, ydata = trans.transform_point((xy_pos[0][0], xy_pos[0][1])) + self.horizontal_line.set_ydata( + [ + ydata, + ] + ) + + def clear_line_prop(self) -> None: + """clear attribute line_prop method + + Args: + None + + Return: + None + + """ + if self.last_line_prop: + for key in self.last_line_prop: + set_line_attr = getattr(self.last_line, "set_" + key) + set_line_attr(self.last_line_prop[key]) + self.last_line_prop = {} + self.last_line = None + + def hover(self, event: Any) -> None: + """hover cursor method (cursor to nearest value) + + Args: + event: touch kivy event + + Return: + None + + """ + + # if cursor is set -> hover is on + if self.hover_on: + # transform kivy x,y touch event to x,y data + trans = self.figure.axes[0].transData.inverted() + xdata, ydata = trans.transform_point((event.x - self.pos[0], event.y - self.pos[1])) + + # loop all register lines and find closest x,y data for each valid line + distance = [] + good_line = [] + good_index = [] + for line in self.lines: + # get only visible lines + if line.get_visible(): + # get line x,y datas + self.x_cursor, self.y_cursor = line.get_xydata().T + + # check if line is not empty + if len(self.x_cursor) != 0: + # find closest data index from touch (x axis) + if self.xsorted: + index = min(np.searchsorted(self.x_cursor, xdata), len(self.y_cursor) - 1) + + else: + index = np.argsort(abs(self.x_cursor - xdata))[0] + + # get x data from index + x = self.x_cursor[index] + + if self.compare_xdata: + y = self.y_cursor[index] + + # get distance between line and touch (in pixels) + ax = line.axes + # left axis + if self.twinx: + if ax == self.figure.axes[1]: + # right axis + trans = self.figure.axes[1].transData.inverted() + xdata2, ydata2 = trans.transform_point( + (event.x - self.pos[0], event.y - self.pos[1]) + ) + xy_pixels_mouse = ax.transData.transform(np.vstack([xdata2, ydata2]).T) + else: + # left axis + xy_pixels_mouse = ax.transData.transform([(xdata, ydata)]) + else: + # left axis + xy_pixels_mouse = ax.transData.transform([(xdata, ydata)]) + if np.ma.is_masked(x) or np.ma.is_masked(y) or np.isnan(x) or np.isnan(y): # type: ignore[no-untyped-call] + distance.append(np.nan) + else: + xy_pixels = ax.transData.transform([(x, ydata)]) + dx2 = xy_pixels_mouse[0][0] - xy_pixels[0][0] + distance.append(abs(dx2)) + else: + # find ydata corresponding to xdata + y = self.y_cursor[index] + + # get distance between line and touch (in pixels) + ax = line.axes + if self.twinx: + if ax == self.figure.axes[1]: + # right axis + trans = self.figure.axes[1].transData.inverted() + xdata2, ydata2 = trans.transform_point( + (event.x - self.pos[0], event.y - self.pos[1]) + ) + xy_pixels_mouse = ax.transData.transform(np.vstack([xdata2, ydata2]).T) + else: + # left axis + xy_pixels_mouse = ax.transData.transform([(xdata, ydata)]) + else: + # left axis + xy_pixels_mouse = ax.transData.transform([(xdata, ydata)]) + if np.ma.is_masked(x) or np.ma.is_masked(y): # type: ignore[no-untyped-call] + distance.append(np.nan) + else: + xy_pixels = ax.transData.transform([(x, y)]) + dx2 = (xy_pixels_mouse[0][0] - xy_pixels[0][0]) ** 2 + dy2 = (xy_pixels_mouse[0][1] - xy_pixels[0][1]) ** 2 + + # store distance + if self.pick_radius_axis == "both": + distance.append((dx2 + dy2) ** 0.5) + if self.pick_radius_axis == "x": + distance.append(abs(dx2)) + if self.pick_radius_axis == "y": + distance.append(abs(dy2)) + + # store all best lines and index + good_line.append(line) + good_index.append(index) + + # case if no good line + if len(good_line) == 0: + return + + # if minimum distance if lower than 50 pixels, get line datas with + # minimum distance + if np.nanmin(distance) < self.pick_minimum_radius: + # index of minimum distance + if self.compare_xdata: + if not self.hover_instance or not hasattr(self.hover_instance, "children_list"): + return + + idx_best_list = np.flatnonzero(np.array(distance) == np.nanmin(distance)) + # get datas from closest line + line = good_line[idx_best_list[0]] + self.x_cursor, self.y_cursor = line.get_xydata().T + x = self.x_cursor[good_index[idx_best_list[0]]] + y = self.y_cursor[good_index[idx_best_list[0]]] + + ax = line.axes + xy_pos = ax.transData.transform([(x, y)]) + self.x_hover_data = x + self.y_hover_data = y + self.hover_instance.x_hover_pos = float(xy_pos[0][0]) + self.x + self.hover_instance.y_hover_pos = float(xy_pos[0][1]) + self.y + self.hover_instance.y_touch_pos = float(xy_pixels[0][1]) + self.y + + if self.first_call_compare_hover: + self.hover_instance.show_cursor = True + else: + self.first_call_compare_hover = True + + if len(idx_best_list) > 0: + available_widget = self.hover_instance.children_list + nb_widget = len(available_widget) + index_list = list(range(nb_widget)) + for i, _current_idx_best in enumerate(idx_best_list): + if i > nb_widget - 1: + break + line = good_line[idx_best_list[i]] + line_label = line.get_label() + if line_label in self.hover_instance.children_names: + index = self.hover_instance.children_names.index(line_label) + y_cursor = line.get_ydata() + y = y_cursor[good_index[idx_best_list[i]]] + ax = line.axes + + xy_pos = ax.transData.transform([(x, y)]) + pos_y = float(xy_pos[0][1]) + self.y + + if ( + pos_y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] + and pos_y > self.y + ax.bbox.bounds[1] + ): + available_widget[index].x_hover_pos = float(xy_pos[0][0]) + self.x + available_widget[index].y_hover_pos = float(xy_pos[0][1]) + self.y + available_widget[index].custom_color = get_color_from_hex(to_hex(line.get_color())) + + if self.twinx: + if ax == self.figure.axes[1]: + if self.cursor_yaxis2_formatter: + y = self.cursor_yaxis2_formatter.format_data(y) + else: + y = ax.yaxis.get_major_formatter().format_data_short(y) + else: + if self.cursor_yaxis_formatter: + y = self.cursor_yaxis_formatter.format_data(y) + + else: + if self.cursor_yaxis_formatter: + y = self.cursor_yaxis_formatter.format_data(y) + else: + y = ax.yaxis.get_major_formatter().format_data_short(y) + available_widget[index].label_y_value = f"{y}" + available_widget[index].show_widget = True + index_list.remove(index) + + for ii in index_list: + available_widget[ii].show_widget = False + + if self.cursor_xaxis_formatter: + x = self.cursor_xaxis_formatter.format_data(x) + else: + x = ax.xaxis.get_major_formatter().format_data_short(x) + + self.hover_instance.label_x_value = f"{x}" + + if hasattr(self.hover_instance, "overlap_check"): + self.hover_instance.overlap_check() + + self.hover_instance.xmin_line = float(ax.bbox.bounds[0]) + self.x + self.hover_instance.xmax_line = float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x + self.hover_instance.ymin_line = float(ax.bbox.bounds[1]) + self.y + self.hover_instance.ymax_line = float(ax.bbox.bounds[1] + ax.bbox.bounds[3]) + self.y + + if ( + self.hover_instance.x_hover_pos > self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + or self.hover_instance.x_hover_pos < self.x + ax.bbox.bounds[0] + or len(index_list) == nb_widget + ): + self.hover_instance.hover_outside_bound = True + else: + self.hover_instance.hover_outside_bound = False + + return + + else: + idx_best = np.nanargmin(distance) + + # get datas from closest line + line = good_line[idx_best] + self.x_cursor, self.y_cursor = line.get_xydata().T + x = self.x_cursor[good_index[idx_best]] + y = self.y_cursor[good_index[idx_best]] + + if not self.hover_instance: + self.set_cross_hair_visible(True) + + # update the cursor x,y data + ax = line.axes + self.cursor_last_axis = ax + if self.twinx: + if ax == self.figure.axes[1]: + cur_ylim = self.figure.axes[0].get_ylim() + cur_ylim2 = self.figure.axes[1].get_ylim() + + ratio = (cur_ylim2[1] - cur_ylim2[0]) / (cur_ylim[1] - cur_ylim[0]) + new_y = (y - cur_ylim2[0]) / ratio + cur_ylim[0] + self.horizontal_line.set_ydata( + [ + new_y, + ] + ) + + self.cursor_last_y = new_y + if self.cursor_yaxis2_formatter and not self.hover_instance: + y = self.cursor_yaxis2_formatter.format_data(y) + elif not self.hover_instance: + y = ax.yaxis.get_major_formatter().format_data_short(y) + else: + self.horizontal_line.set_ydata( + [ + y, + ] + ) + if self.cursor_yaxis_formatter and not self.hover_instance: + y = self.cursor_yaxis_formatter.format_data(y) + elif not self.hover_instance: + y = ax.yaxis.get_major_formatter().format_data_short(y) + else: + self.horizontal_line.set_ydata( + [ + y, + ] + ) + if self.cursor_yaxis_formatter and not self.hover_instance: + y = self.cursor_yaxis_formatter.format_data(y) + elif not self.hover_instance: + y = ax.yaxis.get_major_formatter().format_data_short(y) + self.vertical_line.set_xdata( + [ + x, + ] + ) + if self.cursor_xaxis_formatter and not self.hover_instance: + x = self.cursor_xaxis_formatter.format_data(x) + elif not self.hover_instance: + x = ax.xaxis.get_major_formatter().format_data_short(x) + + # x y label + if self.hover_instance: + xy_pos = ax.transData.transform([(x, y)]) + self.x_hover_data = x + self.y_hover_data = y + self.hover_instance.x_hover_pos = float(xy_pos[0][0]) + self.x + self.hover_instance.y_hover_pos = float(xy_pos[0][1]) + self.y + self.hover_instance.show_cursor = True + + if self.twinx: + if ax == self.figure.axes[1]: + if self.cursor_yaxis2_formatter: + y = self.cursor_yaxis2_formatter.format_data(y) + else: + y = ax.yaxis.get_major_formatter().format_data_short(y) + else: + if self.cursor_yaxis_formatter: + y = self.cursor_yaxis_formatter.format_data(y) + else: + y = ax.yaxis.get_major_formatter().format_data_short(y) + + else: + if self.cursor_yaxis_formatter: + y = self.cursor_yaxis_formatter.format_data(y) + else: + y = ax.yaxis.get_major_formatter().format_data_short(y) + + if self.cursor_xaxis_formatter: + x = self.cursor_xaxis_formatter.format_data(x) + else: + x = ax.xaxis.get_major_formatter().format_data_short(x) + + self.hover_instance.label_x_value = f"{x}" + self.hover_instance.label_y_value = f"{y}" + + self.hover_instance.xmin_line = float(ax.bbox.bounds[0]) + self.x + self.hover_instance.xmax_line = float(ax.bbox.bounds[0] + ax.bbox.bounds[2]) + self.x + self.hover_instance.ymin_line = float(ax.bbox.bounds[1]) + self.y + self.hover_instance.ymax_line = float(ax.bbox.bounds[1] + ax.bbox.bounds[3]) + self.y + + self.hover_instance.custom_label = line.get_label() + self.hover_instance.custom_color = get_color_from_hex(to_hex(line.get_color())) + + if ( + self.hover_instance.x_hover_pos + > self.x + self.figure.axes[0].bbox.bounds[2] + self.figure.axes[0].bbox.bounds[0] + or self.hover_instance.x_hover_pos < self.x + self.figure.axes[0].bbox.bounds[0] + or self.hover_instance.y_hover_pos + > self.y + self.figure.axes[0].bbox.bounds[1] + self.figure.axes[0].bbox.bounds[3] + or self.hover_instance.y_hover_pos < self.y + self.figure.axes[0].bbox.bounds[1] + ): + self.hover_instance.hover_outside_bound = True + else: + self.hover_instance.hover_outside_bound = False + + if self.highlight_hover: + self.myevent.x = event.x - self.pos[0] + self.myevent.y = event.y - self.pos[1] + self.myevent.inaxes = self.figure.canvas.inaxes( + (event.x - self.pos[0], event.y - self.pos[1]) + ) + + axes = [a for a in self.figure.canvas.figure.get_axes() if a.in_axes(self.myevent)] + + if not axes or not isinstance(line, mlines.Line2D): + if self.last_line: + self.clear_line_prop() + if self.background: + ax.figure.canvas.restore_region(self.background) + # draw (blit method) + ax.figure.canvas.blit(ax.bbox) + ax.figure.canvas.flush_events() + self.background = None + + return + + # blit method (always use because same visual effect as draw) + if self.background is None: + self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) + if self.last_line is None: + default_alpha = [] + lines_list = self.lines # get all register lines + for current_line in lines_list: + default_alpha.append(current_line.get_alpha()) + current_line.set_alpha(self.highlight_alpha) + + ax.figure.canvas.draw_idle() + ax.figure.canvas.flush_events() + self.background_highlight = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) + self.last_line = line + for i, current_line in enumerate(lines_list): + current_line.set_alpha(default_alpha[i]) + default_alpha[i] + + if self.highlight_prop: + self.last_line_prop = {} + for key in self.highlight_prop: + # if hasattr(line,key): + line_attr = getattr(line, "get_" + key) + self.last_line_prop.update({key: line_attr()}) + set_line_attr = getattr(line, "set_" + key) + set_line_attr(self.highlight_prop[key]) + elif self.last_line_prop: + for key in self.last_line_prop: + set_line_attr = getattr(self.last_line, "set_" + key) + set_line_attr(self.last_line_prop[key]) + self.hover_instance.custom_color = get_color_from_hex(to_hex(line.get_color())) + self.last_line_prop = {} + for key in self.highlight_prop: + line_attr = getattr(line, "get_" + key) + self.last_line_prop.update({key: line_attr()}) + set_line_attr = getattr(line, "set_" + key) + set_line_attr(self.highlight_prop[key]) + self.last_line = line + + ax.figure.canvas.restore_region(self.background_highlight) + ax.draw_artist(line) + + # draw (blit method) + ax.figure.canvas.blit(ax.bbox) + ax.figure.canvas.flush_events() + + return + self.text.set_text(f"x={x}, y={y}") + + # blit method (always use because same visual effect as draw) + if self.background is None: + self.set_cross_hair_visible(False) + self.figure.canvas.draw_idle() + self.figure.canvas.flush_events() + self.background = self.figure.canvas.copy_from_bbox(self.figure.bbox) + self.set_cross_hair_visible(True) + if self.last_line is not None: + self.clear_line_prop() + + self.figure.canvas.restore_region(self.background) + self.figure.axes[0].draw_artist(self.text) + + self.figure.axes[0].draw_artist(self.horizontal_line) + self.figure.axes[0].draw_artist(self.vertical_line) + + # draw (blit method) + self.figure.canvas.blit(self.figure.axes[0].bbox) + self.figure.canvas.flush_events() + + # if touch is too far, hide cross hair cursor + else: + self.set_cross_hair_visible(False) + if self.hover_instance: + self.hover_instance.x_hover_pos = self.x + self.hover_instance.y_hover_pos = self.y + self.hover_instance.show_cursor = False + self.x_hover_data = None + self.y_hover_data = None + + if self.highlight_hover: + self.myevent.x = event.x - self.pos[0] + self.myevent.y = event.y - self.pos[1] + self.myevent.inaxes = self.figure.canvas.inaxes((event.x - self.pos[0], event.y - self.pos[1])) + + axes = [a for a in self.figure.canvas.figure.get_axes() if a.in_axes(self.myevent)] + + if not axes: + if self.last_line: + self.clear_line_prop() + if self.background: + self.figure.canvas.restore_region(self.background) + # draw (blit method) + self.figure.canvas.blit(self.figure.axes[0].bbox) + self.figure.canvas.flush_events() + self.background = None + return + + def autoscale(self) -> None: + if self.disabled: + return + ax = self.figure.axes[0] + ax.relim(visible_only=self.autoscale_visible_only) + ax.autoscale_view( + tight=self.autoscale_tight, + scalex=self.autoscale_axis != "y", + scaley=self.autoscale_axis != "x", + ) + ax.autoscale(axis=self.autoscale_axis, tight=self.autoscale_tight) + if self.twinx: + ax2 = self.figure.axes[1] + if self.autoscale_axis != "x": + ax2.relim(visible_only=self.autoscale_visible_only) + ax2.autoscale_view(tight=self.autoscale_tight, scalex=False, scaley=True) + ax2.autoscale(axis="y", tight=self.autoscale_tight) + + self.ymin2, self.ymax2 = ax2.get_ylim() + ax.figure.canvas.draw_idle() + ax.figure.canvas.flush_events() + ax.set_autoscale_on(False) + + self.xmin, self.xmax = ax.get_xlim() + self.ymin, self.ymax = ax.get_ylim() + + def home(self) -> None: + """reset data axis + + Return: + None + """ + ax = self.figure.axes[0] + xleft, xright = ax.get_xlim() + ybottom, ytop = ax.get_ylim() + + # check inverted data + inverted_x = False + if xleft > xright: + inverted_x = True + inverted_y = False + if ybottom > ytop: + inverted_y = True + + if inverted_x: + ax.set_xlim(right=self.xmin, left=self.xmax) + else: + ax.set_xlim(left=self.xmin, right=self.xmax) + if inverted_y: + ax.set_ylim(top=self.ymin, bottom=self.ymax) + else: + ax.set_ylim(bottom=self.ymin, top=self.ymax) + + if self.twinx: + ax2 = self.figure.axes[1] + ybottom2, ytop2 = ax2.get_ylim() + inverted_y2 = False + if ybottom2 > ytop2: + inverted_y2 = True + if inverted_y2: + ax2.set_ylim(top=self.ymin2, bottom=self.ymax2) + else: + ax2.set_ylim(bottom=self.ymin2, top=self.ymax2) + + self.update_cursor() + + if self.last_line is not None: + self.clear_line_prop() + + ax.figure.canvas.draw_idle() + ax.figure.canvas.flush_events() + + def back(self, *_args: Any) -> None: + """ + Move back up the view lim stack. + For convenience of being directly connected as a GUI callback, which + often get passed additional parameters, this method accepts arbitrary + parameters, but does not use them. + """ + self._nav_stack.back() + self.set_history_buttons() + self._update_view() + + def forward(self, *_args: Any) -> None: + """ + Move forward in the view lim stack. + For convenience of being directly connected as a GUI callback, which + often get passed additional parameters, this method accepts arbitrary + parameters, but does not use them. + """ + self._nav_stack.forward() + self.set_history_buttons() + self._update_view() + + def push_current(self) -> None: + """Push the current view limits and position onto the stack.""" + self._nav_stack.push( + WeakKeyDictionary( + { + ax: ( + ax._get_view(), + # Store both the original and modified positions. + (ax.get_position(True).frozen(), ax.get_position().frozen()), + ) + for ax in self.figure.axes + } + ) + ) + self.set_history_buttons() + + def update(self) -> None: + """Reset the Axes stack.""" + self._nav_stack.clear() + self.set_history_buttons() + + def _update_view(self) -> None: + """ + Update the viewlim and position from the view and position stack for + each Axes. + """ + nav_info = self._nav_stack() + if nav_info is None: + return + # Retrieve all items at once to avoid any risk of GC deleting an Axes + # while in the middle of the loop below. + items = list(nav_info.items()) + for ax, (view, (pos_orig, pos_active)) in items: + ax._set_view(view) + # Restore both the original and modified positions + ax._set_position(pos_orig, "original") + ax._set_position(pos_active, "active") + self.figure.canvas.draw_idle() + self.figure.canvas.flush_events() + + def set_history_buttons(self) -> None: + """Enable or disable the back/forward button.""" + + def reset_touch(self) -> None: + """reset touch + + Return: + None + """ + self._touches = [] + self._last_touch_pos = {} + + def _get_scale(self) -> Any: + """kivy scatter _get_scale method""" + p1 = Vector(*self.to_parent(0, 0)) + p2 = Vector(*self.to_parent(1, 0)) + scale = p1.distance(p2) + + # XXX float calculation are not accurate, and then, scale can be + # throwed again even with only the position change. So to + # prevent anything wrong with scale, just avoid to dispatch it + # if the scale "visually" didn't change. #947 + # Remove this ugly hack when we'll be Python 3 only. + if hasattr(self, "_scale_p") and str(scale) == str(self._scale_p): # type: ignore[has-type] + return self._scale_p # type: ignore[has-type] + + self._scale_p = scale + return scale + + def _set_scale(self, scale: float) -> None: + """kivy scatter _set_scale method""" + rescale = scale * 1.0 / self.scale + self.apply_transform( + Matrix().scale(rescale, rescale, rescale), post_multiply=True, anchor=self.to_local(*self.center) + ) + + scale = AliasProperty(_get_scale, _set_scale, bind=("x", "y", "transform")) + """Scale value of the scatter. + + :attr:`scale` is an :class:`~kivy.properties.AliasProperty` and defaults to + 1.0. + """ + + def _draw_bitmap(self) -> None: + """draw bitmap method. based on kivy scatter method""" + if self._bitmap is None: + print("No bitmap!") + return + self._img_texture = Texture.create(size=(self.bt_w, self.bt_h)) + self._img_texture.blit_buffer(bytes(self._bitmap), colorfmt="rgba", bufferfmt="ubyte") + self._img_texture.flip_vertical() + + self.update_hover() + self.update_selector() + + def transform_with_touch(self, event: Any) -> bool: + """manage touch behaviour. based on kivy scatter method""" + # just do a simple one finger drag + changed = False + + if len(self._touches) == self.translation_touches: + if self.touch_mode == "pan": + if self._nav_stack() is None: + self.push_current() + if self.twinx: + self.apply_pan_twinx(self.figure.axes[0], self.figure.axes[1], event) + else: + self.apply_pan(self.figure.axes[0], event) + + if ( + self.touch_mode == "pan_x" + or self.touch_mode == "pan_y" + or self.touch_mode == "adjust_x" + or self.touch_mode == "adjust_y" + ): + if self._nav_stack() is None: + self.push_current() + if self.twinx: + self.apply_pan_twinx(self.figure.axes[0], self.figure.axes[1], event, mode=self.touch_mode) + else: + self.apply_pan(self.figure.axes[0], event, mode=self.touch_mode) + + elif self.touch_mode == "drag_legend": + if self.legend_instance: + self.apply_drag_legend(self.figure.axes[0], event) + + elif self.touch_mode == "zoombox": + if self._nav_stack() is None: + self.push_current() + _real_x, real_y = event.x - self.pos[0], event.y - self.pos[1] + # in case x_init is not create + if not hasattr(self, "x_init"): + self.x_init = event.x + self.y_init = real_y + self.draw_box(event, self.x_init, self.y_init, event.x, real_y) + + # mode cursor + elif self.touch_mode == "cursor": + self.hover_on = True + self.hover(event) + + changed = True + + # note: avoid zoom in/out on touch mode zoombox + if len(self._touches) == 1: # + return changed + + # we have more than one touch... list of last known pos + points = [Vector(self._last_touch_pos[t]) for t in self._touches if t is not event] + # add current touch last + points.append(Vector(event.pos)) + + # we only want to transform if the touch is part of the two touches + # farthest apart! So first we find anchor, the point to transform + # around as another touch farthest away from current touch's pos + anchor = max(points[:-1], key=lambda p: p.distance(event.pos)) + + # now we find the touch farthest away from anchor, if its not the + # same as touch. Touch is not one of the two touches used to transform + farthest = max(points, key=anchor.distance) + if farthest is not points[-1]: + return changed + + # ok, so we have touch, and anchor, so we can actually compute the + # transformation + old_line = Vector(*event.ppos) - anchor + new_line = Vector(*event.pos) - anchor + if not old_line.length(): # div by zero + return changed + + if self.auto_zoom: + v1 = Vector(0, 10) + angle = v1.angle(new_line) + 180 + if angle < 0 + self.zoom_angle_detection or angle > 360 - self.zoom_angle_detection: + self.do_zoom_x = False + self.do_zoom_y = True + elif angle > 90 - self.zoom_angle_detection and angle < 90 + self.zoom_angle_detection: + self.do_zoom_x = True + self.do_zoom_y = False + elif angle > 180 - self.zoom_angle_detection and angle < 180 + self.zoom_angle_detection: + self.do_zoom_x = False + self.do_zoom_y = True + elif angle > 270 - self.zoom_angle_detection and angle < 270 + self.zoom_angle_detection: + self.do_zoom_x = True + self.do_zoom_y = False + else: + self.do_zoom_x = True + self.do_zoom_y = True + + if self.do_scale: + scale = old_line.length() / new_line.length() + new_scale = scale * self.scale + if new_scale < self.scale_min: + scale = self.scale_min / self.scale + elif new_scale > self.scale_max: + scale = self.scale_max / self.scale + + if self.twinx: + self.apply_zoom_twinx(scale, self.figure.axes[0], self.figure.axes[1], anchor=anchor, new_line=new_line) + else: + self.apply_zoom(scale, self.figure.axes[0], anchor=anchor, new_line=new_line) + + changed = True + return changed + + def on_motion(self, *args: Any) -> None: + """Kivy Event to trigger mouse event on motion + `enter_notify_event`. + """ + if self._pressed or self.disabled: # Do not process this event if there's a touch_move + return + pos = args[1] + newcoord = self.to_widget(pos[0], pos[1]) + x = newcoord[0] + y = newcoord[1] + inside = self.collide_point(x, y) + if inside: # noqa: SIM102 + # will receive all motion events. + if self.figcanvas and self.hover_instance: + # avoid in motion if touch is detected + if len(self._touches) != 0: + return + FakeEventTwinx.x = x + FakeEventTwinx.y = y + self.hover(FakeEventTwinx) + + def get_data_xy(self, _x: Any, _y: Any) -> tuple[Any, Any]: + """manage x y data in navigation bar TODO""" + return None, None + + def on_touch_down(self, event: Any) -> Optional[bool]: + """Manage Mouse/touch press""" + if self.disabled: + return None + + x, y = event.x, event.y + + if self.collide_point(x, y) and self.figure: + self._pressed = True + self.show_compare_cursor = False + if self.legend_instance: + select_legend = False + for current_legend in self.legend_instance: + if current_legend.box.collide_point(x, y): + select_legend = True + self.current_legend = current_legend + break + if select_legend: + if self.touch_mode != "drag_legend": + return False + event.grab(self) + self._touches.append(event) + self._last_touch_pos[event] = event.pos + if len(self._touches) > 1: + # new touch, reset background + self.background = None + + return True + self.current_legend = None + + if event.is_mouse_scrolling: + if not self.disable_mouse_scrolling: + ax = self.figure.axes[0] + if self.twinx: + ax2 = self.figure.axes[1] + self.zoom_factory_twin(event, ax, ax2, base_scale=1.2) + else: + self.zoom_factory(event, ax, base_scale=1.2) + return True + + if event.is_double_tap: + if not self.disable_double_tap and self.touch_mode != "selector": + self.home() + return True + + if self.touch_mode == "cursor": + self.hover_on = True + self.hover(event) + elif self.touch_mode == "zoombox": + _real_x, real_y = x - self.pos[0], y - self.pos[1] + self.x_init = x + self.y_init = real_y + self.draw_box(event, x, real_y, x, real_y) + + elif self.touch_mode == "minmax": + self.min_max(event) + elif self.touch_mode == "selector": + pass + + event.grab(self) + self._touches.append(event) + self._last_touch_pos[event] = event.pos + if len(self._touches) > 1: + # new touch, reset background + self.background = None + + return True + + return False + + def on_touch_move(self, event: Any) -> Optional[bool]: + """Manage Mouse/touch move while pressed""" + if self.disabled: + return None + + x, y = event.x, event.y + + if event.is_double_tap: + if not self.disable_double_tap and self.touch_mode != "selector": + self.home() + return True + + # scale/translate + if event in self._touches and event.grab_current == self: + self.transform_with_touch(event) + self._last_touch_pos[event] = event.pos + + # stop propagating if its within our bounds + if self.collide_point(x, y): + return True + return None + + def on_touch_up(self, event: Any) -> Optional[bool]: + """Manage Mouse/touch release""" + if self.disabled: + return None + + # remove it from our saved touches + if event in self._touches and event.grab_state: + event.ungrab(self) + del self._last_touch_pos[event] + self._touches.remove(event) + if ( + self.touch_mode == "pan" + or self.touch_mode == "zoombox" + or self.touch_mode == "pan_x" + or self.touch_mode == "pan_y" + or self.touch_mode == "adjust_x" + or self.touch_mode == "adjust_y" + or self.touch_mode == "minmax" + ): + self.push_current() + if self.interactive_axis: + if ( + self.touch_mode == "pan_x" + or self.touch_mode == "pan_y" + or self.touch_mode == "adjust_x" + or self.touch_mode == "adjust_y" + ): + self.touch_mode = "pan" + self.first_touch_pan = None + + if self.last_line is not None: + self.clear_line_prop() + + x, y = event.x, event.y + if abs(self._box_size[0]) > 1 or abs(self._box_size[1]) > 1 or self.touch_mode == "zoombox": + self.reset_box() + if not self.collide_point(x, y) and self.do_update: + # update axis lim if zoombox is used and touch outside widget + self.update_lim() + self.figure.canvas.draw_idle() + self.figure.canvas.flush_events() + return True + + # stop propagating if its within our bounds + if self.collide_point(x, y) and self.figure: + self._pressed = False + + if self.do_update: + self.update_lim() + + self.anchor_x = None + self.anchor_y = None + + self.background = None + self.show_compare_cursor = True + self.figure.canvas.draw_idle() + self.figure.canvas.flush_events() + if self.last_line is None or self.touch_mode != "cursor": + self.figure.canvas.draw_idle() + self.figure.canvas.flush_events() + + return True + return None + + def apply_zoom(self, scale_factor: Any, ax: Any, anchor: Any = (0, 0), new_line: Any = None) -> None: + """zoom touch method""" + if self.touch_mode == "selector": + return + + x = anchor[0] - self.pos[0] + y = anchor[1] - self.pos[1] + + trans = ax.transData.inverted() + xdata, ydata = trans.transform_point((x + new_line.x, y + new_line.y)) + + cur_xlim = ax.get_xlim() + cur_ylim = ax.get_ylim() + + scale = ax.get_xscale() + yscale = ax.get_yscale() + + if scale == "linear": + old_min = cur_xlim[0] + old_max = cur_xlim[1] + + else: + min_ = cur_xlim[0] + max_ = cur_xlim[1] + old_min = self.transform_eval(min_, ax.yaxis) + xdata = self.transform_eval(xdata, ax.yaxis) + old_max = self.transform_eval(max_, ax.yaxis) + + if yscale == "linear": + yold_min = cur_ylim[0] + yold_max = cur_ylim[1] + + else: + ymin_ = cur_ylim[0] + ymax_ = cur_ylim[1] + yold_min = self.transform_eval(ymin_, ax.yaxis) + ydata = self.transform_eval(ydata, ax.yaxis) + yold_max = self.transform_eval(ymax_, ax.yaxis) + + new_width = (old_max - old_min) * scale_factor + new_height = (yold_max - yold_min) * scale_factor + + relx = (old_max - xdata) / (old_max - old_min) + rely = (yold_max - ydata) / (yold_max - yold_min) + + if self.do_zoom_x: + if scale == "linear": + ax.set_xlim([xdata - new_width * (1 - relx), xdata + new_width * (relx)]) + else: + new_min = xdata - new_width * (1 - relx) + new_max = xdata + new_width * (relx) + try: + new_min, new_max = ( + self.inv_transform_eval(new_min, ax.yaxis), + self.inv_transform_eval(new_max, ax.yaxis), + ) + except OverflowError: # Limit case + new_min, new_max = min_, max_ + if new_min <= 0.0 or new_max <= 0.0: # Limit case + new_min, new_max = min_, max_ + ax.set_xlim([new_min, new_max]) + + if self.do_zoom_y: + if yscale == "linear": + ax.set_ylim([ydata - new_height * (1 - rely), ydata + new_height * (rely)]) + else: + new_ymin = ydata - new_height * (1 - rely) + new_ymax = ydata + new_height * (rely) + try: + new_ymin, new_ymax = ( + self.inv_transform_eval(new_ymin, ax.yaxis), + self.inv_transform_eval(new_ymax, ax.yaxis), + ) + except OverflowError: # Limit case + new_ymin, new_ymax = ymin_, ymax_ + if new_ymin <= 0.0 or new_ymax <= 0.0: # Limit case + new_ymin, new_ymax = ymin_, ymax_ + ax.set_ylim([new_ymin, new_ymax]) + + if self.fast_draw: + # use blit method + if self.background is None or self.last_line is not None: + self.background_patch_copy.set_visible(True) + ax.figure.canvas.draw_idle() + ax.figure.canvas.flush_events() + self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) + self.background_patch_copy.set_visible(False) + if self.last_line is not None: + self.clear_line_prop() + ax.figure.canvas.restore_region(self.background) + + for line in ax.lines: + ax.draw_artist(line) + ax.figure.canvas.blit(ax.bbox) + ax.figure.canvas.flush_events() + + self.update_hover() + else: + ax.figure.canvas.draw_idle() + ax.figure.canvas.flush_events() + + def apply_zoom_twinx( + self, scale_factor: Any, ax: Any, ax2: Any, anchor: Any = (0, 0), new_line: Any = None + ) -> None: + """twin axis zoom method""" + if self.touch_mode == "selector": + return + x = anchor[0] - self.pos[0] + y = anchor[1] - self.pos[1] + + trans = ax.transData.inverted() + xdata, ydata = trans.transform_point((x + new_line.x, y + new_line.y)) + trans2 = ax2.transData.inverted() + xdata2, ydata2 = trans2.transform_point((x + new_line.x, y + new_line.y)) + + cur_xlim = ax.get_xlim() + cur_ylim = ax.get_ylim() + cur_ylim2 = ax2.get_ylim() + + scale = ax.get_xscale() + yscale = ax.get_yscale() + yscale2 = ax2.get_yscale() + + if scale == "linear": + old_min = cur_xlim[0] + old_max = cur_xlim[1] + + else: + min_ = cur_xlim[0] + max_ = cur_xlim[1] + old_min = self.transform_eval(min_, ax.yaxis) + xdata = self.transform_eval(xdata, ax.yaxis) + old_max = self.transform_eval(max_, ax.yaxis) + + if yscale == "linear": + yold_min = cur_ylim[0] + yold_max = cur_ylim[1] + + else: + ymin_ = cur_ylim[0] + ymax_ = cur_ylim[1] + yold_min = self.transform_eval(ymin_, ax.yaxis) + ydata = self.transform_eval(ydata, ax.yaxis) + yold_max = self.transform_eval(ymax_, ax.yaxis) + + new_width = (old_max - old_min) * scale_factor + new_height = (yold_max - yold_min) * scale_factor + + relx = (old_max - xdata) / (old_max - old_min) + rely = (yold_max - ydata) / (yold_max - yold_min) + + if self.do_zoom_x: + if scale == "linear": + ax.set_xlim([xdata - new_width * (1 - relx), xdata + new_width * (relx)]) + else: + new_min = xdata - new_width * (1 - relx) + new_max = xdata + new_width * (relx) + try: + new_min, new_max = ( + self.inv_transform_eval(new_min, ax.yaxis), + self.inv_transform_eval(new_max, ax.yaxis), + ) + except OverflowError: # Limit case + new_min, new_max = min_, max_ + if new_min <= 0.0 or new_max <= 0.0: # Limit case + new_min, new_max = min_, max_ + ax.set_xlim([new_min, new_max]) + + if self.do_zoom_y: + if yscale == "linear": + ax.set_ylim([ydata - new_height * (1 - rely), ydata + new_height * (rely)]) + else: + new_ymin = ydata - new_height * (1 - rely) + new_ymax = ydata + new_height * (rely) + try: + new_ymin, new_ymax = ( + self.inv_transform_eval(new_ymin, ax.yaxis), + self.inv_transform_eval(new_ymax, ax.yaxis), + ) + except OverflowError: # Limit case + new_ymin, new_ymax = ymin_, ymax_ + if new_ymin <= 0.0 or new_ymax <= 0.0: # Limit case + new_ymin, new_ymax = ymin_, ymax_ + ax.set_ylim([new_ymin, new_ymax]) + + if yscale2 == "linear": + yold2_min = cur_ylim2[0] + yold2_max = cur_ylim2[1] + + else: + ymin2_ = cur_ylim2[0] + ymax2_ = cur_ylim2[1] + yold2_min = self.transform_eval(ymin2_, ax2.yaxis) + ydata2 = self.transform_eval(ydata2, ax2.yaxis) + yold2_max = self.transform_eval(ymax2_, ax2.yaxis) + + new_height2 = (yold2_max - yold2_min) * scale_factor + + rely2 = (yold2_max - ydata2) / (yold2_max - yold2_min) + if self.do_zoom_y: + ax2.set_ylim([ydata2 - new_height2 * (1 - rely2), ydata2 + new_height2 * (rely2)]) + + if yscale2 == "linear": + ax2.set_ylim([ydata2 - new_height2 * (1 - rely2), ydata2 + new_height2 * (rely2)]) + else: + new_ymin2 = ydata2 - new_height2 * (1 - rely2) + new_ymax2 = ydata2 + new_height2 * (rely2) + try: + new_ymin2, new_ymax2 = ( + self.inv_transform_eval(new_ymin2, ax2.yaxis), + self.inv_transform_eval(new_ymax2, ax2.yaxis), + ) + except OverflowError: # Limit case + new_ymin2, new_ymax2 = ymin2_, ymax2_ + if new_ymin2 <= 0.0 or new_ymax2 <= 0.0: # Limit case + new_ymin2, new_ymax2 = ymin2_, ymax2_ + ax2.set_ylim([new_ymin2, new_ymax2]) + + if self.fast_draw: + # use blit method + if self.background is None or self.last_line is not None: + self.background_patch_copy.set_visible(True) + self.background_ax2_patch_copy.set_visible(True) + ax.figure.canvas.draw_idle() + ax.figure.canvas.flush_events() + self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) + self.background_patch_copy.set_visible(False) + self.background_ax2_patch_copy.set_visible(False) + if self.last_line is not None: + self.clear_line_prop() + ax.figure.canvas.restore_region(self.background) + + for line in ax.lines: + if line.get_visible(): + ax.draw_artist(line) + for line in ax2.lines: + if line.get_visible(): + ax2.draw_artist(line) + + ax.figure.canvas.blit(ax.bbox) + ax.figure.canvas.flush_events() + + self.update_hover() + + else: + ax.figure.canvas.draw_idle() + ax.figure.canvas.flush_events() + + def apply_pan(self, ax: Any, event: Any, mode: str = "pan") -> None: + """pan method""" + + trans = ax.transData.inverted() + xdata, ydata = trans.transform_point((event.x - self.pos[0], event.y - self.pos[1])) + xpress, ypress = trans.transform_point( + (self._last_touch_pos[event][0] - self.pos[0], self._last_touch_pos[event][1] - self.pos[1]) + ) + + scale = ax.get_xscale() + yscale = ax.get_yscale() + + if scale == "linear": + dx = xdata - xpress + else: + dx = self.transform_eval(xdata, ax.xaxis) - self.transform_eval(xpress, ax.xaxis) + + if yscale == "linear": + dy = ydata - ypress + else: + dy = self.transform_eval(ydata, ax.yaxis) - self.transform_eval(ypress, ax.yaxis) + + xleft, xright = self.figure.axes[0].get_xlim() + ybottom, ytop = self.figure.axes[0].get_ylim() + + # check inverted data + inverted_x = False + if xleft > xright: + inverted_x = True + cur_xlim = (xright, xleft) + else: + cur_xlim = (xleft, xright) + inverted_y = False + if ybottom > ytop: + inverted_y = True + cur_ylim = (ytop, ybottom) + else: + cur_ylim = (ybottom, ytop) + + if self.interactive_axis and self.touch_mode == "pan" and self.first_touch_pan != "pan": + if (ydata < cur_ylim[0] and not inverted_y) or (ydata > cur_ylim[1] and inverted_y): + left_anchor_zone = (cur_xlim[1] - cur_xlim[0]) * 0.2 + cur_xlim[0] + right_anchor_zone = (cur_xlim[1] - cur_xlim[0]) * 0.8 + cur_xlim[0] + mode = "adjust_x" if xdata < left_anchor_zone or xdata > right_anchor_zone else "pan_x" + self.touch_mode = mode + elif (xdata < cur_xlim[0] and not inverted_x) or (xdata > cur_xlim[1] and inverted_x): + bottom_anchor_zone = (cur_ylim[1] - cur_ylim[0]) * 0.2 + cur_ylim[0] + top_anchor_zone = (cur_ylim[1] - cur_ylim[0]) * 0.8 + cur_ylim[0] + mode = "adjust_y" if ydata < bottom_anchor_zone or ydata > top_anchor_zone else "pan_y" + self.touch_mode = mode + else: + self.touch_mode = "pan" + + if mode != "pan_y" and mode != "adjust_y": + if mode == "adjust_x": + if self.anchor_x is None: + midpoint = (cur_xlim[1] + cur_xlim[0]) / 2 + if xdata > midpoint: + self.anchor_x = "left" + else: + self.anchor_x = "right" + if self.anchor_x == "left": + if xdata > cur_xlim[0]: + if scale == "linear": + cur_xlim -= dx + else: + try: + cur_xlim = [ # type: ignore[assignment] + self.inv_transform_eval( + (self.transform_eval(cur_xlim[0], ax.xaxis) - dx), ax.xaxis + ), + self.inv_transform_eval( + (self.transform_eval(cur_xlim[1], ax.xaxis) - dx), ax.xaxis + ), + ] + except (ValueError, OverflowError): + cur_xlim = cur_xlim # Keep previous limits + if inverted_x: + ax.set_xlim(cur_xlim[1], None) + else: + ax.set_xlim(None, cur_xlim[1]) + else: + if xdata < cur_xlim[1]: + if scale == "linear": + cur_xlim -= dx + else: + try: + cur_xlim = [ # type: ignore[assignment] + self.inv_transform_eval( + (self.transform_eval(cur_xlim[0], ax.xaxis) - dx), ax.xaxis + ), + self.inv_transform_eval( + (self.transform_eval(cur_xlim[1], ax.xaxis) - dx), ax.xaxis + ), + ] + except (ValueError, OverflowError): + cur_xlim = cur_xlim # Keep previous limits + if inverted_x: + ax.set_xlim(None, cur_xlim[0]) + else: + ax.set_xlim(cur_xlim[0], None) + else: + if scale == "linear": + cur_xlim -= dx + else: + try: + cur_xlim = [ # type: ignore[assignment] + self.inv_transform_eval((self.transform_eval(cur_xlim[0], ax.xaxis) - dx), ax.xaxis), + self.inv_transform_eval((self.transform_eval(cur_xlim[1], ax.xaxis) - dx), ax.xaxis), + ] + except (ValueError, OverflowError): + cur_xlim = cur_xlim # Keep previous limits + if inverted_x: + ax.set_xlim(cur_xlim[1], cur_xlim[0]) + else: + ax.set_xlim(cur_xlim) + + if mode != "pan_x" and mode != "adjust_x": + if mode == "adjust_y": + if self.anchor_y is None: + midpoint = (cur_ylim[1] + cur_ylim[0]) / 2 + if ydata > midpoint: + self.anchor_y = "top" + else: + self.anchor_y = "bottom" + + if self.anchor_y == "top": + if ydata > cur_ylim[0]: + if yscale == "linear": + cur_ylim -= dy + + else: + try: + cur_ylim = [ # type: ignore[assignment] + self.inv_transform_eval( + (self.transform_eval(cur_ylim[0], ax.yaxis) - dy), ax.yaxis + ), + self.inv_transform_eval( + (self.transform_eval(cur_ylim[1], ax.yaxis) - dy), ax.yaxis + ), + ] + except (ValueError, OverflowError): + cur_ylim = cur_ylim # Keep previous limits + + if inverted_y: + ax.set_ylim(cur_ylim[1], None) + else: + ax.set_ylim(None, cur_ylim[1]) + else: + if ydata < cur_ylim[1]: + if yscale == "linear": + cur_ylim -= dy + + else: + try: + cur_ylim = [ # type: ignore[assignment] + self.inv_transform_eval( + (self.transform_eval(cur_ylim[0], ax.yaxis) - dy), ax.yaxis + ), + self.inv_transform_eval( + (self.transform_eval(cur_ylim[1], ax.yaxis) - dy), ax.yaxis + ), + ] + except (ValueError, OverflowError): + cur_ylim = cur_ylim # Keep previous limits + if inverted_y: + ax.set_ylim(None, cur_ylim[0]) + else: + ax.set_ylim(cur_ylim[0], None) + else: + if yscale == "linear": + cur_ylim -= dy + + else: + try: + cur_ylim = [ # type: ignore[assignment] + self.inv_transform_eval((self.transform_eval(cur_ylim[0], ax.yaxis) - dy), ax.yaxis), + self.inv_transform_eval((self.transform_eval(cur_ylim[1], ax.yaxis) - dy), ax.yaxis), + ] + except (ValueError, OverflowError): + cur_ylim = cur_ylim # Keep previous limits + if inverted_y: + ax.set_ylim(cur_ylim[1], cur_ylim[0]) + else: + ax.set_ylim(cur_ylim) + + if self.first_touch_pan is None: + self.first_touch_pan = self.touch_mode + + if self.fast_draw: + # use blit method + if self.background is None or self.last_line is not None: + self.background_patch_copy.set_visible(True) + ax.figure.canvas.draw_idle() + ax.figure.canvas.flush_events() + self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) + self.background_patch_copy.set_visible(False) + if self.last_line is not None: + self.clear_line_prop() + ax.figure.canvas.restore_region(self.background) + + for line in ax.lines: + ax.draw_artist(line) + + ax.figure.canvas.blit(ax.bbox) + ax.figure.canvas.flush_events() + + self.update_hover() + + else: + ax.figure.canvas.draw_idle() + ax.figure.canvas.flush_events() + + def apply_pan_twinx(self, ax: Any, ax2: Any, event: Any, mode: str = "pan") -> None: + """twin axis pan method""" + trans = ax.transData.inverted() + xdata, ydata = trans.transform_point((event.x - self.pos[0], event.y - self.pos[1])) + xpress, ypress = trans.transform_point( + (self._last_touch_pos[event][0] - self.pos[0], self._last_touch_pos[event][1] - self.pos[1]) + ) + + scale = ax.get_xscale() + yscale = ax.get_yscale() + yscale2 = ax2.get_yscale() + + update_cursor = False + + if scale == "linear": + dx = xdata - xpress + else: + dx = self.transform_eval(xdata, ax.xaxis) - self.transform_eval(xpress, ax.xaxis) + + if yscale == "linear": + dy = ydata - ypress + else: + dy = self.transform_eval(ydata, ax.yaxis) - self.transform_eval(ypress, ax.yaxis) + + xleft, xright = ax.get_xlim() + ybottom, ytop = ax.get_ylim() + ybottom2, ytop2 = ax2.get_ylim() + + # check inverted data + inverted_x = False + if xleft > xright: + inverted_x = True + cur_xlim = (xright, xleft) + else: + cur_xlim = (xleft, xright) + inverted_y = False + if ybottom > ytop: + inverted_y = True + cur_ylim = (ytop, ybottom) + else: + cur_ylim = (ybottom, ytop) + inverted_y2 = False + if ybottom2 > ytop2: + inverted_y2 = True + cur_ylim2 = (ytop2, ybottom2) + else: + cur_ylim2 = (ybottom2, ytop2) + + ratio = (cur_ylim2[1] - cur_ylim2[0]) / (cur_ylim[1] - cur_ylim[0]) + ydata2 = ydata * ratio + cur_ylim2[0] + ypress2 = ypress * ratio + cur_ylim2[0] + + if yscale2 == "linear": + dy2 = ydata2 - ypress2 + else: + dy2 = self.transform_eval(ydata2, ax2.yaxis) - self.transform_eval(ypress2, ax2.yaxis) + + if self.interactive_axis and self.touch_mode == "pan" and self.first_touch_pan != "pan": + if (ydata < cur_ylim[0] and not inverted_y) or (ydata > cur_ylim[1] and inverted_y): + left_anchor_zone = (cur_xlim[1] - cur_xlim[0]) * 0.2 + cur_xlim[0] + right_anchor_zone = (cur_xlim[1] - cur_xlim[0]) * 0.8 + cur_xlim[0] + mode = "adjust_x" if xdata < left_anchor_zone or xdata > right_anchor_zone else "pan_x" + self.touch_mode = mode + elif ( + (xdata < cur_xlim[0] and not inverted_y) + or (xdata > cur_xlim[1] and inverted_y) + or (xdata > cur_xlim[1] and not inverted_y) + or (xdata < cur_xlim[0] and inverted_y) + ): + bottom_anchor_zone = (cur_ylim[1] - cur_ylim[0]) * 0.2 + cur_ylim[0] + top_anchor_zone = (cur_ylim[1] - cur_ylim[0]) * 0.8 + cur_ylim[0] + mode = "adjust_y" if ydata < bottom_anchor_zone or ydata > top_anchor_zone else "pan_y" + self.touch_mode = mode + + else: + self.touch_mode = "pan" + + if mode != "pan_y" and mode != "adjust_y": + if mode == "adjust_x": + if self.anchor_x is None: + midpoint = (cur_xlim[1] + cur_xlim[0]) / 2 + if xdata > midpoint: + self.anchor_x = "left" + else: + self.anchor_x = "right" + if self.anchor_x == "left": + if xdata > cur_xlim[0]: + if scale == "linear": + cur_xlim -= dx + else: + try: + cur_xlim = [ # type: ignore[assignment] + self.inv_transform_eval( + (self.transform_eval(cur_xlim[0], ax.xaxis) - dx), ax.xaxis + ), + self.inv_transform_eval( + (self.transform_eval(cur_xlim[1], ax.xaxis) - dx), ax.xaxis + ), + ] + except (ValueError, OverflowError): + cur_xlim = cur_xlim # Keep previous limits + if inverted_x: + ax.set_xlim(cur_xlim[1], None) + else: + ax.set_xlim(None, cur_xlim[1]) + + else: + if xdata < cur_xlim[1]: + if scale == "linear": + cur_xlim -= dx + else: + try: + cur_xlim = [ # type: ignore[assignment] + self.inv_transform_eval( + (self.transform_eval(cur_xlim[0], ax.xaxis) - dx), ax.xaxis + ), + self.inv_transform_eval( + (self.transform_eval(cur_xlim[1], ax.xaxis) - dx), ax.xaxis + ), + ] + except (ValueError, OverflowError): + cur_xlim = cur_xlim # Keep previous limits + if inverted_x: + ax.set_xlim(None, cur_xlim[0]) + else: + ax.set_xlim(cur_xlim[0], None) + + else: + if scale == "linear": + cur_xlim -= dx + else: + try: + cur_xlim = [ # type: ignore[assignment] + self.inv_transform_eval((self.transform_eval(cur_xlim[0], ax.xaxis) - dx), ax.xaxis), + self.inv_transform_eval((self.transform_eval(cur_xlim[1], ax.xaxis) - dx), ax.xaxis), + ] + except (ValueError, OverflowError): + cur_xlim = cur_xlim # Keep previous limits + if inverted_x: + ax.set_xlim(cur_xlim[1], cur_xlim[0]) + else: + ax.set_xlim(cur_xlim) + + if mode != "pan_x" and mode != "adjust_x": + if mode == "adjust_y": + trans_ax2 = ax2.transData.inverted() + xdata_ax2, ydata_ax2 = trans_ax2.transform_point((event.x - self.pos[0], event.y - self.pos[1])) + if self.anchor_y is None: + midpoint_x = (cur_xlim[1] + cur_xlim[0]) / 2 + midpoint_ax1 = (cur_ylim[1] + cur_ylim[0]) / 2 + midpoint_ax2 = (cur_ylim2[1] + cur_ylim2[0]) / 2 + if (xdata > midpoint_x and not inverted_x) or (xdata < midpoint_x and inverted_x): + ax_anchor = "right" + else: + ax_anchor = "left" + + if ax_anchor == "left": + if ydata > midpoint_ax1: + self.anchor_y = "top_left" + else: + self.anchor_y = "bottom_left" + else: + if ydata_ax2 > midpoint_ax2: + self.anchor_y = "top_right" + else: + self.anchor_y = "bottom_right" + if self.anchor_y == "top_left": + if ydata > cur_ylim[0]: + if yscale == "linear": + cur_ylim -= dy + + else: + try: + cur_ylim = [ # type: ignore[assignment] + self.inv_transform_eval( + (self.transform_eval(cur_ylim[0], ax.yaxis) - dy), ax.yaxis + ), + self.inv_transform_eval( + (self.transform_eval(cur_ylim[1], ax.yaxis) - dy), ax.yaxis + ), + ] + except (ValueError, OverflowError): + cur_ylim = cur_ylim # Keep previous limits + if inverted_y: + ax.set_ylim(cur_ylim[1], None) + else: + ax.set_ylim(None, cur_ylim[1]) + + update_cursor = True + + elif self.anchor_y == "top_right": + if ydata_ax2 > cur_ylim2[0]: + if yscale2 == "linear": + cur_ylim2 -= dy2 + + else: + try: + cur_ylim2 = [ # type: ignore[assignment] + self.inv_transform_eval( + (self.transform_eval(cur_ylim2[0], ax2.yaxis) - dy2), ax2.yaxis + ), + self.inv_transform_eval( + (self.transform_eval(cur_ylim2[1], ax2.yaxis) - dy2), ax2.yaxis + ), + ] + except (ValueError, OverflowError): + cur_ylim2 = cur_ylim2 # Keep previous limits + + if inverted_y2: + ax2.set_ylim(cur_ylim2[1], None) + else: + ax2.set_ylim(None, cur_ylim2[1]) + + update_cursor = True + + elif self.anchor_y == "bottom_left": + if ydata < cur_ylim[1]: + if yscale == "linear": + cur_ylim -= dy + + else: + try: + cur_ylim = [ # type: ignore[assignment] + self.inv_transform_eval( + (self.transform_eval(cur_ylim[0], ax.yaxis) - dy), ax.yaxis + ), + self.inv_transform_eval( + (self.transform_eval(cur_ylim[1], ax.yaxis) - dy), ax.yaxis + ), + ] + except (ValueError, OverflowError): + cur_ylim = cur_ylim # Keep previous limits + if inverted_y: + ax.set_ylim(None, cur_ylim[0]) + else: + ax.set_ylim(cur_ylim[0], None) + + update_cursor = True + else: + if ydata_ax2 < cur_ylim2[1]: + if yscale2 == "linear": + cur_ylim2 -= dy2 + + else: + try: + cur_ylim2 = [ # type: ignore[assignment] + self.inv_transform_eval( + (self.transform_eval(cur_ylim2[0], ax2.yaxis) - dy2), ax2.yaxis + ), + self.inv_transform_eval( + (self.transform_eval(cur_ylim2[1], ax2.yaxis) - dy2), ax2.yaxis + ), + ] + except (ValueError, OverflowError): + cur_ylim2 = cur_ylim2 # Keep previous limits + if inverted_y2: + ax2.set_ylim(None, cur_ylim2[0]) + else: + ax2.set_ylim(cur_ylim2[0], None) + + update_cursor = True + else: + if yscale == "linear": + cur_ylim -= dy + + else: + try: + cur_ylim = [ # type: ignore[assignment] + self.inv_transform_eval((self.transform_eval(cur_ylim[0], ax.yaxis) - dy), ax.yaxis), + self.inv_transform_eval((self.transform_eval(cur_ylim[1], ax.yaxis) - dy), ax.yaxis), + ] + except (ValueError, OverflowError): + cur_ylim = cur_ylim # Keep previous limits + + if yscale2 == "linear": + cur_ylim2 -= dy2 + + else: + try: + cur_ylim2 = [ # type: ignore[assignment] + self.inv_transform_eval((self.transform_eval(cur_ylim2[0], ax2.yaxis) - dy2), ax2.yaxis), + self.inv_transform_eval((self.transform_eval(cur_ylim2[1], ax2.yaxis) - dy2), ax2.yaxis), + ] + except (ValueError, OverflowError): + cur_ylim2 = cur_ylim2 # Keep previous limits + if inverted_y: + ax.set_ylim(cur_ylim[1], cur_ylim[0]) + else: + ax.set_ylim(cur_ylim) + if inverted_y2: + ax2.set_ylim(cur_ylim2[1], cur_ylim2[0]) + else: + ax2.set_ylim(cur_ylim2) + + if self.first_touch_pan is None: + self.first_touch_pan = self.touch_mode + + if update_cursor: + self.update_cursor() + + if self.fast_draw: + # use blit method + if self.background is None or self.last_line is not None: + self.background_patch_copy.set_visible(True) + self.background_ax2_patch_copy.set_visible(True) + ax.figure.canvas.draw_idle() + ax.figure.canvas.flush_events() + self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) + self.background_patch_copy.set_visible(False) + self.background_ax2_patch_copy.set_visible(False) + if self.last_line is not None: + self.clear_line_prop() + ax.figure.canvas.restore_region(self.background) + + for line in ax.lines: + if line.get_visible(): + ax.draw_artist(line) + for line in ax2.lines: + if line.get_visible(): + ax2.draw_artist(line) + + ax.figure.canvas.blit(ax.bbox) + ax.figure.canvas.flush_events() + + self.update_hover() + + else: + ax.figure.canvas.draw_idle() + ax.figure.canvas.flush_events() + + def update_hover(self) -> None: + """update hover on fast draw (if exist)""" + if self.hover_instance: + if self.compare_xdata and self.hover_instance: + if (self.touch_mode != "cursor" or len(self._touches) > 1) and not self.show_compare_cursor: + self.hover_instance.hover_outside_bound = True + + elif self.show_compare_cursor and self.touch_mode == "cursor": + self.show_compare_cursor = False + else: + self.hover_instance.hover_outside_bound = True + + # update hover pos if needed + elif self.hover_instance.show_cursor and self.x_hover_data is not None and self.y_hover_data is not None: + if self.cursor_last_axis: + xy_pos = self.cursor_last_axis.transData.transform([(self.x_hover_data, self.y_hover_data)]) + else: + xy_pos = self.figure.axes[0].transData.transform([(self.x_hover_data, self.y_hover_data)]) + self.hover_instance.x_hover_pos = float(xy_pos[0][0]) + self.x + self.hover_instance.y_hover_pos = float(xy_pos[0][1]) + self.y + + self.hover_instance.xmin_line = float(self.figure.axes[0].bbox.bounds[0]) + self.x + self.hover_instance.xmax_line = ( + float(self.figure.axes[0].bbox.bounds[0] + self.figure.axes[0].bbox.bounds[2]) + self.x + ) + self.hover_instance.ymin_line = float(self.figure.axes[0].bbox.bounds[1]) + self.y + self.hover_instance.ymax_line = ( + float(self.figure.axes[0].bbox.bounds[1] + self.figure.axes[0].bbox.bounds[3]) + self.y + ) + + if ( + self.hover_instance.x_hover_pos + > self.x + self.figure.axes[0].bbox.bounds[2] + self.figure.axes[0].bbox.bounds[0] + or self.hover_instance.x_hover_pos < self.x + self.figure.axes[0].bbox.bounds[0] + or self.hover_instance.y_hover_pos + > self.y + self.figure.axes[0].bbox.bounds[1] + self.figure.axes[0].bbox.bounds[3] + or self.hover_instance.y_hover_pos < self.y + self.figure.axes[0].bbox.bounds[1] + ): + self.hover_instance.hover_outside_bound = True + else: + self.hover_instance.hover_outside_bound = False + + def update_selector(self, *args: Any) -> None: + """update selector on fast draw (if exist)""" + if self.selector and self.selector.resize_wgt.verts and (len(args) != 0 or self.touch_mode != "selector"): + # update selector pos if needed + resize_wgt = self.selector.resize_wgt + + if hasattr(resize_wgt, "shapes"): + # lasso widget or ellipse + if resize_wgt.shapes: + if hasattr(resize_wgt.shapes[0], "radius_x"): + # ellipse widget + xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[1][0], resize_wgt.verts[1][1])]) + new_pos = resize_wgt.to_widget(*(float(xy_pos[0][0]), float(xy_pos[0][1]))) + pos0 = new_pos[0] + self.x + pos1 = new_pos[1] + self.y + + xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[2][0], resize_wgt.verts[2][1])]) + new_pos2 = resize_wgt.to_widget(*(float(xy_pos2[0][0]), float(xy_pos2[0][1]))) + pos0_2 = new_pos2[0] + self.x + pos1_2 = new_pos2[1] + self.y + + current_shape = resize_wgt.shapes[0] + dataxy1 = current_shape.selection_point_inst.points + dataxy2 = current_shape.selection_point_inst2.points + + # note: the 2 first points are the same in current_shape.points + pos0_old = dataxy1[0] + pos1_old = dataxy1[1] + + pos0_2_old = dataxy2[0] + pos1_2_old = dataxy2[1] + + old_length = np.sqrt((pos0_2_old - pos0_old) ** 2 + (pos1_2_old - pos1_old) ** 2) + new_length = np.sqrt((pos0_2 - pos0) ** 2 + (pos1_2 - pos1) ** 2) + + scale = float(new_length / old_length) + + xy_pos3 = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + new_pos3 = resize_wgt.to_widget(*(float(xy_pos3[0][0]), float(xy_pos3[0][1]))) + pos0_c = new_pos3[0] + self.x + pos1_c = new_pos3[1] + self.y + + for s in resize_wgt.shapes: + s.rescale(scale) + + for s in resize_wgt.shapes: + s.translate(pos=(pos0_c, pos1_c)) + + xmin, xmax, ymin, ymax = resize_wgt.shapes[0].get_min_max() + else: + # lasso widget + xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + new_pos = resize_wgt.to_widget(*(float(xy_pos[0][0]), float(xy_pos[0][1]))) + pos0 = new_pos[0] + self.x + pos1 = new_pos[1] + self.y + + xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[1][0], resize_wgt.verts[1][1])]) + new_pos2 = resize_wgt.to_widget(*(float(xy_pos2[0][0]), float(xy_pos2[0][1]))) + pos0_2 = new_pos2[0] + self.x + pos1_2 = new_pos2[1] + self.y + + current_shape = resize_wgt.shapes[0] + dataxy = np.array(current_shape.points).reshape(-1, 2) + + # note: the 2 first points are the same in current_shape.points + pos0_old = dataxy[1][0] + pos1_old = dataxy[1][1] + + pos0_2_old = dataxy[2][0] + pos1_2_old = dataxy[2][1] + + old_length = np.sqrt((pos0_2_old - pos0_old) ** 2 + (pos1_2_old - pos1_old) ** 2) + new_length = np.sqrt((pos0_2 - pos0) ** 2 + (pos1_2 - pos1) ** 2) + + scale = new_length / old_length + + for s in resize_wgt.shapes: + s.rescale(scale) + + for s in resize_wgt.shapes: + s.translate(pos=(pos0, pos1)) + + xmax, ymax = dataxy.max(axis=0) + xmin, ymin = dataxy.min(axis=0) + + if self.collide_point(*resize_wgt.to_window(xmin, ymin)) and self.collide_point( + *resize_wgt.to_window(xmax, ymax) + ): + resize_wgt.opacity = 1 + else: + resize_wgt.opacity = 0 + + elif self.selector.resize_wgt.verts and (len(args) != 0 or self.touch_mode != "selector"): + resize_wgt = self.selector.resize_wgt + if not (resize_wgt.size[0] > 1 and resize_wgt.size[1] > 1): + return + + # rectangle or spann selector + if hasattr(resize_wgt, "span_orientation"): + # span selector + if resize_wgt.span_orientation == "vertical": + xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + new_pos = resize_wgt.to_widget(*(float(xy_pos[0][0]), float(xy_pos[0][1]))) + resize_wgt.pos[0] = new_pos[0] + self.x + + top_bound = float(self.y + resize_wgt.ax.bbox.bounds[3] + resize_wgt.ax.bbox.bounds[1]) + bottom_bound = float(self.y + resize_wgt.ax.bbox.bounds[1]) + resize_wgt.pos[1] = bottom_bound - self.y + + # recalcul size + xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[3][0], resize_wgt.verts[3][1])]) + resize_wgt.size[0] = float(xy_pos2[0][0] - xy_pos[0][0]) + resize_wgt.size[1] = top_bound - bottom_bound + + elif resize_wgt.span_orientation == "horizontal": + xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + new_pos = resize_wgt.to_widget(*(float(xy_pos[0][0]), float(xy_pos[0][1]))) + left_bound = float(self.x + resize_wgt.ax.bbox.bounds[0]) + right_bound = float(self.x + resize_wgt.ax.bbox.bounds[2] + resize_wgt.ax.bbox.bounds[0]) + + width = right_bound - left_bound + + left_bound, right_bound = resize_wgt.to_widget(left_bound, right_bound) + + resize_wgt.pos[0] = left_bound + resize_wgt.pos[1] = new_pos[1] + self.y + + # recalcul size + xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][1], resize_wgt.verts[1][1])]) + resize_wgt.size[0] = width + resize_wgt.size[1] = float(xy_pos2[0][1] - xy_pos[0][1]) + + else: + # rectangle selector + + # update all selector pts + # recalcul pos + xy_pos = resize_wgt.ax.transData.transform([(resize_wgt.verts[0][0], resize_wgt.verts[0][1])]) + new_pos = resize_wgt.to_widget(*(float(xy_pos[0][0]), float(xy_pos[0][1]))) + resize_wgt.pos[0] = new_pos[0] + self.x + resize_wgt.pos[1] = new_pos[1] + self.y + + # recalcul size + xy_pos2 = resize_wgt.ax.transData.transform([(resize_wgt.verts[2][0], resize_wgt.verts[2][1])]) + resize_wgt.size[0] = float(xy_pos2[0][0] - xy_pos[0][0]) + resize_wgt.size[1] = float(xy_pos2[0][1] - xy_pos[0][1]) + + if self.collide_point( + *resize_wgt.to_window(resize_wgt.pos[0], resize_wgt.pos[1]) + ) and self.collide_point( + *resize_wgt.to_window( + resize_wgt.pos[0] + resize_wgt.size[0], resize_wgt.pos[1] + resize_wgt.size[1] + ) + ): + resize_wgt.opacity = 1 + else: + resize_wgt.opacity = 0 + + def min_max(self, event: Any) -> None: + """manage min/max touch mode""" + ax = self.figure.axes[0] # left axis + xlabelbottom = ax.xaxis._major_tick_kw.get("tick1On") + ylabelleft = ax.yaxis._major_tick_kw.get("tick1On") + ylabelright = ax.yaxis._major_tick_kw.get("tick2On") + + if ( + xlabelbottom + and event.x > self.x + ax.bbox.bounds[0] + and event.x < self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + and event.y < self.y + ax.bbox.bounds[1] + ): + right_lim = self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + left_lim = self.x + ax.bbox.bounds[0] + left_anchor_zone = (right_lim - left_lim) * 0.2 + left_lim + right_anchor_zone = (right_lim - left_lim) * 0.8 + left_lim + + if (event.x < left_anchor_zone or event.x > right_anchor_zone) and self.text_instance: # noqa: SIM102 + if not self.text_instance.show_text: + midpoint = ((self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0]) + (self.x + ax.bbox.bounds[0])) / 2 + if event.x < midpoint: + anchor = "left" + self.text_instance.x_text_pos = float(self.x + ax.bbox.bounds[0]) + self.text_instance.y_text_pos = ( + float(self.y + ax.bbox.bounds[1]) - self.text_instance.text_height + ) + self.text_instance.offset_text = False + else: + anchor = "right" + self.text_instance.x_text_pos = float(self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0]) + self.text_instance.y_text_pos = ( + float(self.y + ax.bbox.bounds[1]) - self.text_instance.text_height + ) + self.text_instance.offset_text = True + + self.text_instance.current_axis = ax + self.text_instance.kind = {"axis": "x", "anchor": anchor} + + self.text_instance.show_text = True + return + + elif ( + ylabelleft + and event.x < self.x + ax.bbox.bounds[0] + and event.y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] + and event.y > self.y + ax.bbox.bounds[1] + ): + top_lim = self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1] + bottom_lim = self.y + ax.bbox.bounds[1] + bottom_anchor_zone = (top_lim - bottom_lim) * 0.2 + bottom_lim + top_anchor_zone = (top_lim - bottom_lim) * 0.8 + bottom_lim + + if (event.y < bottom_anchor_zone or event.y > top_anchor_zone) and self.text_instance: # noqa: SIM102 + if not self.text_instance.show_text: + midpoint = ((self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1]) + (self.y + ax.bbox.bounds[1])) / 2 + if event.y > midpoint: + anchor = "top" + self.text_instance.x_text_pos = float(self.x + ax.bbox.bounds[0]) - dp(40) + self.text_instance.y_text_pos = ( + float(self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3]) - self.text_instance.text_height + ) + self.text_instance.offset_text = False + else: + anchor = "bottom" + self.text_instance.x_text_pos = float(self.x + ax.bbox.bounds[0]) - dp(40) + self.text_instance.y_text_pos = float(self.y + ax.bbox.bounds[1]) - dp(6) + self.text_instance.offset_text = False + self.text_instance.current_axis = ax + self.text_instance.kind = {"axis": "y", "anchor": anchor} + + self.text_instance.show_text = True + return + + elif ( + ylabelright + and event.x > self.x + ax.bbox.bounds[2] + ax.bbox.bounds[0] + and event.y < self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3] + and event.y > self.y + ax.bbox.bounds[1] + ): + top_lim = self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1] + bottom_lim = self.y + ax.bbox.bounds[1] + bottom_anchor_zone = (top_lim - bottom_lim) * 0.2 + bottom_lim + top_anchor_zone = (top_lim - bottom_lim) * 0.8 + bottom_lim + + if (event.y < bottom_anchor_zone or event.y > top_anchor_zone) and self.text_instance: # noqa: SIM102 + if not self.text_instance.show_text: + midpoint = ((self.y + ax.bbox.bounds[3] + ax.bbox.bounds[1]) + (self.y + ax.bbox.bounds[1])) / 2 + if event.y > midpoint: + anchor = "top" + self.text_instance.x_text_pos = float(self.x + ax.bbox.bounds[0] + ax.bbox.bounds[2]) + dp(40) + self.text_instance.y_text_pos = ( + float(self.y + ax.bbox.bounds[1] + ax.bbox.bounds[3]) - self.text_instance.text_height + ) + self.text_instance.offset_text = True + else: + anchor = "bottom" + self.text_instance.x_text_pos = float(self.x + ax.bbox.bounds[0] + ax.bbox.bounds[2]) + dp(40) + self.text_instance.y_text_pos = float(self.y + ax.bbox.bounds[1]) - dp(6) + self.text_instance.offset_text = True + self.text_instance.current_axis = self.figure.axes[1] # left axis + self.text_instance.kind = {"axis": "y", "anchor": anchor} + + self.text_instance.show_text = True + return + + def apply_drag_legend(self, ax: Any, event: Any) -> None: + """drag legend method""" + + dx = event.x - self._last_touch_pos[event][0] + if not self.legend_do_scroll_x: + dx = 0 + dy = event.y - self._last_touch_pos[event][1] + if not self.legend_do_scroll_y: + dy = 0 + legend = None + if self.current_legend: + legend = self.current_legend.legend_instance if self.current_legend.legend_instance else ax.get_legend() + if legend is not None: + bbox = legend.get_window_extent() + legend_x = bbox.xmin + legend_y = bbox.ymin + + loc_in_canvas = legend_x + dx, legend_y + dy + loc_in_norm_axes = legend.parent.transAxes.inverted().transform_point(loc_in_canvas) + legend._loc = tuple(loc_in_norm_axes) + + # use blit method + if self.background is None or self.last_line is not None: + legend.set_visible(False) + ax.figure.canvas.draw_idle() + ax.figure.canvas.flush_events() + self.background = ax.figure.canvas.copy_from_bbox(ax.figure.bbox) + legend.set_visible(True) + if self.last_line is not None: + self.clear_line_prop() + ax.figure.canvas.restore_region(self.background) + + ax.draw_artist(legend) + + ax.figure.canvas.blit(ax.bbox) + ax.figure.canvas.flush_events() + + self.current_legend.update_size() + + def zoom_factory(self, event: Any, ax: Any, base_scale: float = 1.1) -> None: + """zoom with scrolling mouse method""" + + newcoord = self.to_widget(event.x, event.y, relative=True) + x = newcoord[0] + y = newcoord[1] + + trans = ax.transData.inverted() + xdata, ydata = trans.transform_point((x, y)) + + cur_xlim = ax.get_xlim() + cur_ylim = ax.get_ylim() + + scale = ax.get_xscale() + yscale = ax.get_yscale() + + if scale == "linear": + old_min = cur_xlim[0] + old_max = cur_xlim[1] + + else: + min_ = cur_xlim[0] + max_ = cur_xlim[1] + old_min = self.transform_eval(min_, ax.yaxis) + xdata = self.transform_eval(xdata, ax.yaxis) + old_max = self.transform_eval(max_, ax.yaxis) + + if yscale == "linear": + yold_min = cur_ylim[0] + yold_max = cur_ylim[1] + + else: + ymin_ = cur_ylim[0] + ymax_ = cur_ylim[1] + yold_min = self.transform_eval(ymin_, ax.yaxis) + ydata = self.transform_eval(ydata, ax.yaxis) + yold_max = self.transform_eval(ymax_, ax.yaxis) + + if event.button == "scrolldown": + # deal with zoom in + scale_factor = 1 / base_scale + elif event.button == "scrollup": + # deal with zoom out + scale_factor = base_scale + else: + # deal with something that should never happen + scale_factor = 1 + print(event.button) + + new_width = (old_max - old_min) * scale_factor + new_height = (yold_max - yold_min) * scale_factor + + relx = (old_max - xdata) / (old_max - old_min) + rely = (yold_max - ydata) / (yold_max - yold_min) + + if self.do_zoom_x: + if scale == "linear": + ax.set_xlim([xdata - new_width * (1 - relx), xdata + new_width * (relx)]) + else: + new_min = xdata - new_width * (1 - relx) + new_max = xdata + new_width * (relx) + try: + new_min, new_max = ( + self.inv_transform_eval(new_min, ax.yaxis), + self.inv_transform_eval(new_max, ax.yaxis), + ) + except OverflowError: # Limit case + new_min, new_max = min_, max_ + if new_min <= 0.0 or new_max <= 0.0: # Limit case + new_min, new_max = min_, max_ + ax.set_xlim([new_min, new_max]) + + if self.do_zoom_y: + if yscale == "linear": + ax.set_ylim([ydata - new_height * (1 - rely), ydata + new_height * (rely)]) + else: + new_ymin = ydata - new_height * (1 - rely) + new_ymax = ydata + new_height * (rely) + try: + new_ymin, new_ymax = ( + self.inv_transform_eval(new_ymin, ax.yaxis), + self.inv_transform_eval(new_ymax, ax.yaxis), + ) + except OverflowError: # Limit case + new_ymin, new_ymax = ymin_, ymax_ + if new_ymin <= 0.0 or new_ymax <= 0.0: # Limit case + new_ymin, new_ymax = ymin_, ymax_ + ax.set_ylim([new_ymin, new_ymax]) + + if self.last_line is not None: + self.clear_line_prop() + + ax.figure.canvas.draw_idle() + ax.figure.canvas.flush_events() + + def zoom_factory_twin(self, event: Any, ax: Any, ax2: Any, base_scale: float = 2.0) -> None: + """twin axis zoom method from scroll mouse""" + x, y = event.x, event.y + cur_xlim = ax.get_xlim() + cur_ylim = ax.get_ylim() + + cur_ylim2 = ax2.get_ylim() + + trans = ax.transData.inverted() + xdata, ydata = trans.transform_point((x, y)) + trans2 = ax2.transData.inverted() + xdata2, ydata2 = trans2.transform_point((x, y)) + + scale = ax.get_xscale() + yscale = ax.get_yscale() + yscale2 = ax2.get_yscale() + + if scale == "linear": + old_min = cur_xlim[0] + old_max = cur_xlim[1] + + else: + min_ = cur_xlim[0] + max_ = cur_xlim[1] + old_min = self.transform_eval(min_, ax.yaxis) + xdata = self.transform_eval(xdata, ax.yaxis) + old_max = self.transform_eval(max_, ax.yaxis) + + if yscale == "linear": + yold_min = cur_ylim[0] + yold_max = cur_ylim[1] + + else: + ymin_ = cur_ylim[0] + ymax_ = cur_ylim[1] + yold_min = self.transform_eval(ymin_, ax.yaxis) + ydata = self.transform_eval(ydata, ax.yaxis) + yold_max = self.transform_eval(ymax_, ax.yaxis) + + if event.button == "scrolldown": + # deal with zoom in + scale_factor = 1 / base_scale + elif event.button == "scrollup": + # deal with zoom out + scale_factor = base_scale + else: + # deal with something that should never happen + scale_factor = 1 + print(event.button) + + new_width = (old_max - old_min) * scale_factor + new_height = (yold_max - yold_min) * scale_factor + + relx = (old_max - xdata) / (old_max - old_min) + rely = (yold_max - ydata) / (yold_max - yold_min) + + if self.do_zoom_x: + if scale == "linear": + ax.set_xlim([xdata - new_width * (1 - relx), xdata + new_width * (relx)]) + else: + new_min = xdata - new_width * (1 - relx) + new_max = xdata + new_width * (relx) + try: + new_min, new_max = ( + self.inv_transform_eval(new_min, ax.yaxis), + self.inv_transform_eval(new_max, ax.yaxis), + ) + except OverflowError: # Limit case + new_min, new_max = min_, max_ + if new_min <= 0.0 or new_max <= 0.0: # Limit case + new_min, new_max = min_, max_ + ax.set_xlim([new_min, new_max]) + + if self.do_zoom_y: + if yscale == "linear": + ax.set_ylim([ydata - new_height * (1 - rely), ydata + new_height * (rely)]) + else: + new_ymin = ydata - new_height * (1 - rely) + new_ymax = ydata + new_height * (rely) + try: + new_ymin, new_ymax = ( + self.inv_transform_eval(new_ymin, ax.yaxis), + self.inv_transform_eval(new_ymax, ax.yaxis), + ) + except OverflowError: # Limit case + new_ymin, new_ymax = ymin_, ymax_ + if new_ymin <= 0.0 or new_ymax <= 0.0: # Limit case + new_ymin, new_ymax = ymin_, ymax_ + ax.set_ylim([new_ymin, new_ymax]) + + if yscale2 == "linear": + yold2_min = cur_ylim2[0] + yold2_max = cur_ylim2[1] + + else: + ymin2_ = cur_ylim2[0] + ymax2_ = cur_ylim2[1] + yold2_min = self.transform_eval(ymin2_, ax2.yaxis) + ydata2 = self.transform_eval(ydata2, ax2.yaxis) + yold2_max = self.transform_eval(ymax2_, ax2.yaxis) + + new_height2 = (yold2_max - yold2_min) * scale_factor + + rely2 = (yold2_max - ydata2) / (yold2_max - yold2_min) + if self.do_zoom_y: + ax2.set_ylim([ydata2 - new_height2 * (1 - rely2), ydata2 + new_height2 * (rely2)]) + + if yscale2 == "linear": + ax2.set_ylim([ydata2 - new_height2 * (1 - rely2), ydata2 + new_height2 * (rely2)]) + else: + new_ymin2 = ydata2 - new_height2 * (1 - rely2) + new_ymax2 = ydata2 + new_height2 * (rely2) + try: + new_ymin2, new_ymax2 = ( + self.inv_transform_eval(new_ymin2, ax2.yaxis), + self.inv_transform_eval(new_ymax2, ax2.yaxis), + ) + except OverflowError: # Limit case + new_ymin2, new_ymax2 = ymin2_, ymax2_ + if new_ymin2 <= 0.0 or new_ymax2 <= 0.0: # Limit case + new_ymin2, new_ymax2 = ymin2_, ymax2_ + ax2.set_ylim([new_ymin2, new_ymax2]) + + if self.last_line is not None: + self.clear_line_prop() + + ax.figure.canvas.draw_idle() + ax.figure.canvas.flush_events() + + def _on_size(self, _o: Any, size: Any) -> None: + """_onsize method""" + if self.figure is None: + return + # Create a new, correctly sized bitmap + self._width, self._height = size + self._isDrawn = False + + if self._width <= 1 or self._height <= 1: + return + + dpival = self.figure.dpi + winch = self._width / dpival + hinch = self._height / dpival + self.figure.set_size_inches(winch, hinch) + + s = "resize_event" + event = ResizeEvent(s, self.figcanvas) + self.figcanvas.callbacks.process(s, event) + self.figcanvas.draw_idle() + + self.figcanvas.draw() + if self.legend_instance: + for current_legend in self.legend_instance: + current_legend.update_size() + if self.hover_instance: + self.hover_instance.figwidth = self.width + self.hover_instance.figheight = self.height + self.hover_instance.figx = self.x + self.hover_instance.figy = self.y + if self.selector and self.selector.resize_wgt.verts: + # update selector next frame to have correct position + Clock.schedule_once(self.update_selector) + + def update_lim(self) -> None: + """update axis lim if zoombox is used""" + ax = self.figure.axes[0] + ax2 = self.figure.axes[1] + self.do_update = False + + # check if inverted axis + xleft, xright = ax.get_xlim() + ybottom, ytop = ax.get_ylim() + ybottom2, ytop2 = ax2.get_ylim() + + # check inverted data + inverted_x = False + if xleft > xright: + inverted_x = True + else: + pass + inverted_y = False + if ybottom > ytop: + inverted_y = True + cur_ylim = (ytop, ybottom) + else: + cur_ylim = (ybottom, ytop) + inverted_y2 = False + if ybottom2 > ytop2: + inverted_y2 = True + cur_ylim2 = (ytop2, ybottom2) + else: + cur_ylim2 = (ybottom2, ytop2) + + range_old = cur_ylim[1] - cur_ylim[0] + range_old2 = cur_ylim2[1] - cur_ylim2[0] + + ymin2 = (min(self.y0_box, self.y1_box) - cur_ylim[0]) / range_old * range_old2 + cur_ylim2[0] # type: ignore[call-overload] + ymax2 = (max(self.y0_box, self.y1_box) - cur_ylim[0]) / range_old * range_old2 + cur_ylim2[0] # type: ignore[call-overload] + + if inverted_x: + ax.set_xlim(right=min(self.x0_box, self.x1_box), left=max(self.x0_box, self.x1_box)) # type: ignore[call-overload] + else: + ax.set_xlim(left=min(self.x0_box, self.x1_box), right=max(self.x0_box, self.x1_box)) # type: ignore[call-overload] + if inverted_y: + ax.set_ylim(top=min(self.y0_box, self.y1_box), bottom=max(self.y0_box, self.y1_box)) # type: ignore[call-overload] + else: + ax.set_ylim(bottom=min(self.y0_box, self.y1_box), top=max(self.y0_box, self.y1_box)) # type: ignore[call-overload] + if inverted_y2: + ax2.set_ylim(top=ymin2, bottom=ymax2) + else: + ax2.set_ylim(bottom=ymin2, top=ymax2) + + def reset_box(self) -> None: + """reset zoombox and apply zoombox limit if zoombox option if selected""" + if min(abs(self._box_size[0]), abs(self._box_size[1])) > self.minzoom: + trans = self.figure.axes[0].transData.inverted() + self.x0_box, self.y0_box = trans.transform_point( + (self._box_pos[0] - self.pos[0], self._box_pos[1] - self.pos[1]) + ) + self.x1_box, self.y1_box = trans.transform_point( + (self._box_size[0] + self._box_pos[0] - self.pos[0], self._box_size[1] + self._box_pos[1] - self.pos[1]) + ) + self.do_update = True + + self._box_size = 0, 0 + self._box_pos = 0, 0 + self._alpha_box = 0 + + self._pos_x_rect_hor = 0 + self._pos_y_rect_hor = 0 + self._pos_x_rect_ver = 0 + self._pos_y_rect_ver = 0 + self._alpha_hor = 0 + self._alpha_ver = 0 + self.invert_rect_hor = False + self.invert_rect_ver = False + + def draw_box(self, event: Any, x0: Any, y0: Any, x1: Any, y1: Any) -> None: + """Draw zoombox method + + Args: + event: touch kivy event + x0: x coordonnate init + x1: y coordonnate of move touch + y0: y coordonnate init + y1: x coordonnate of move touch + + Return: + None + """ + pos_x, pos_y = self.pos + # Kivy coords + y0 = pos_y + y0 + y1 = pos_y + y1 + + if abs(y1 - y0) > dp(5) or abs(x1 - x0) > dp(5): + self._alpha_box = 0.3 + self._alpha_rect = 0 + + trans = self.figure.axes[0].transData.inverted() + xdata, ydata = trans.transform_point((event.x - pos_x, event.y - pos_y)) + + xleft, xright = self.figure.axes[0].get_xlim() + ybottom, ytop = self.figure.axes[0].get_ylim() + + xmax = max(xleft, xright) + xmin = min(xleft, xright) + ymax = max(ybottom, ytop) + ymin = min(ybottom, ytop) + + # check inverted data + inverted_x = False + if xleft > xright: + inverted_x = True + inverted_y = False + if ybottom > ytop: + inverted_y = True + + x0data, y0data = trans.transform_point((x0 - pos_x, y0 - pos_y)) + if x0data > xmax or x0data < xmin or y0data > ymax or y0data < ymin: + return + + if xdata < xmin: + x1_min = self.figure.axes[0].transData.transform([(xmin, ymin)]) + if (x1 < x0 and not inverted_x) or (x1 > x0 and inverted_x): + x1 = x1_min[0][0] + pos_x + else: + x0 = x1_min[0][0] + + if xdata > xmax: + x0_max = self.figure.axes[0].transData.transform([(xmax, ymin)]) + if (x1 > x0 and not inverted_x) or (x1 < x0 and inverted_x): + x1 = x0_max[0][0] + pos_x + else: + x0 = x0_max[0][0] + + if ydata < ymin: + y1_min = self.figure.axes[0].transData.transform([(xmin, ymin)]) + if (y1 < y0 and not inverted_y) or (y1 > y0 and inverted_y): + y1 = y1_min[0][1] + pos_y + else: + y0 = y1_min[0][1] + pos_y + + if ydata > ymax: + y0_max = self.figure.axes[0].transData.transform([(xmax, ymax)]) + if (y1 > y0 and not inverted_y) or (y1 < y0 and inverted_y): + y1 = y0_max[0][1] + pos_y + else: + y0 = y0_max[0][1] + pos_y + + if abs(x1 - x0) < dp(20) and abs(y1 - y0) > self.minzoom: + self.pos_x_rect_ver = x0 + self.pos_y_rect_ver = y0 + + x1_min = self.figure.axes[0].transData.transform([(xmin, ymin)]) + x0 = x1_min[0][0] + pos_x + + x0_max = self.figure.axes[0].transData.transform([(xmax, ymin)]) + x1 = x0_max[0][0] + pos_x + + self._alpha_ver = 1 + self._alpha_hor = 0 + + elif abs(y1 - y0) < dp(20) and abs(x1 - x0) > self.minzoom: + self.pos_x_rect_hor = x0 + self.pos_y_rect_hor = y0 + + y1_min = self.figure.axes[0].transData.transform([(xmin, ymin)]) + y0 = y1_min[0][1] + pos_y + + y0_max = self.figure.axes[0].transData.transform([(xmax, ymax)]) + y1 = y0_max[0][1] + pos_y + + self._alpha_hor = 1 + self._alpha_ver = 0 + + else: + self._alpha_hor = 0 + self._alpha_ver = 0 + + if x1 > x0: + self.invert_rect_ver = False + else: + self.invert_rect_ver = True + if y1 > y0: + self.invert_rect_hor = False + else: + self.invert_rect_hor = True + + self._box_pos = x0, y0 + self._box_size = x1 - x0, y1 - y0 + + +class _FigureCanvas(FigureCanvasAgg): + """Internal AGG Canvas""" + + def __init__(self, figure: Any, widget: Any, *args: Any, **kwargs: Any) -> None: + self.widget = widget + super().__init__(figure, *args, **kwargs) + + def draw(self) -> None: + """ + Render the figure using agg. + """ + super().draw() # type: ignore[no-untyped-call] + agg = self.get_renderer() # type: ignore[no-untyped-call] + w, h = agg.width, agg.height + self._isDrawn = True + + self.widget.bt_w = w + self.widget.bt_h = h + self.widget._bitmap = agg.buffer_rgba() + self.widget._draw_bitmap() + + def blit(self, _bbox: Any = None) -> None: + """ + Render the figure using agg (blit method). + """ + agg = self.get_renderer() # type: ignore[no-untyped-call] + w, h = agg.width, agg.height + self.widget._bitmap = agg.buffer_rgba() + self.widget.bt_w = w + self.widget.bt_h = h + self.widget._draw_bitmap() + + +class FakeEventTwinx: + x: Any = None + y: Any = None + + +from kivy.factory import Factory # noqa: E402 + +Factory.register("MatplotFigureTwinx", MatplotFigureTwinx) + +Builder.load_string(""" + + canvas: + Color: + rgba: (1, 1, 1, 1) + Rectangle: + pos: self.pos + size: self.size + texture: self._img_texture + Color: + rgba: 0, 0, 1, self._alpha_box + BorderImage: + source: 'border.png' + pos: self._box_pos + size: self._box_size + border: + dp(1) if root.invert_rect_hor else -dp(1), \ + dp(1) if root.invert_rect_ver else -dp(1), \ + dp(1) if root.invert_rect_hor else -dp(1), \ + dp(1) if root.invert_rect_ver else -dp(1) + + canvas.after: + #horizontal rectangle left + Color: + rgba:0, 0, 0, self._alpha_hor + Line: + width: dp(1) + rectangle: + (self.pos_x_rect_hor+dp(1) if root.invert_rect_ver \ + else self.pos_x_rect_hor-dp(4),self.pos_y_rect_hor-dp(20), dp(4),dp(40)) + + #horizontal rectangle right + Color: + rgba:0, 0, 0, self._alpha_hor + Line: + width: dp(1) + rectangle: + (self.pos_x_rect_hor-dp(4)+self._box_size[0] if root.invert_rect_ver \ + else self.pos_x_rect_hor+dp(1)+self._box_size[0], self.pos_y_rect_hor-dp(20), dp(4),dp(40)) + + #vertical rectangle bottom + Color: + rgba:0, 0, 0, self._alpha_ver + Line: + width: dp(1) + rectangle: + (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver+dp(1) if root.invert_rect_hor else \ + self.pos_y_rect_ver-dp(4), dp(40),dp(4)) + + #vertical rectangle top + Color: + rgba:0, 0, 0, self._alpha_ver + Line: + width: dp(1) + rectangle: + (self.pos_x_rect_ver-dp(20),self.pos_y_rect_ver-dp(4)+self._box_size[1] \ + if root.invert_rect_hor else self.pos_y_rect_ver+dp(1)+self._box_size[1], \ + dp(40),dp(4)) + """) + + +class StaticMatplotFigureTwinx(MatplotFigureTwinx): + """Read-only variant of MatplotFigureTwinx — all touch interaction disabled.""" + + def on_touch_down(self, _event: Any) -> Optional[bool]: + return False + + def on_touch_move(self, _event: Any) -> Optional[bool]: + return False + + def on_touch_up(self, _event: Any) -> Optional[bool]: + return False diff --git a/poetry.lock b/poetry.lock index 3629f8dc..1f02fd6d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.3.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. [[package]] name = "altgraph" @@ -320,6 +320,180 @@ type = "legacy" url = "https://pypi.org/simple" reference = "pypi-public" +[[package]] +name = "contourpy" +version = "1.3.0" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.9\"" +files = [ + {file = "contourpy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:880ea32e5c774634f9fcd46504bf9f080a41ad855f4fef54f5380f5133d343c7"}, + {file = "contourpy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76c905ef940a4474a6289c71d53122a4f77766eef23c03cd57016ce19d0f7b42"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92f8557cbb07415a4d6fa191f20fd9d2d9eb9c0b61d1b2f52a8926e43c6e9af7"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36f965570cff02b874773c49bfe85562b47030805d7d8360748f3eca570f4cab"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cacd81e2d4b6f89c9f8a5b69b86490152ff39afc58a95af002a398273e5ce589"}, + {file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69375194457ad0fad3a839b9e29aa0b0ed53bb54db1bfb6c3ae43d111c31ce41"}, + {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a52040312b1a858b5e31ef28c2e865376a386c60c0e248370bbea2d3f3b760d"}, + {file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3faeb2998e4fcb256542e8a926d08da08977f7f5e62cf733f3c211c2a5586223"}, + {file = "contourpy-1.3.0-cp310-cp310-win32.whl", hash = "sha256:36e0cff201bcb17a0a8ecc7f454fe078437fa6bda730e695a92f2d9932bd507f"}, + {file = "contourpy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:87ddffef1dbe5e669b5c2440b643d3fdd8622a348fe1983fad7a0f0ccb1cd67b"}, + {file = "contourpy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fa4c02abe6c446ba70d96ece336e621efa4aecae43eaa9b030ae5fb92b309ad"}, + {file = "contourpy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:834e0cfe17ba12f79963861e0f908556b2cedd52e1f75e6578801febcc6a9f49"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbc4c3217eee163fa3984fd1567632b48d6dfd29216da3ded3d7b844a8014a66"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4865cd1d419e0c7a7bf6de1777b185eebdc51470800a9f42b9e9decf17762081"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:303c252947ab4b14c08afeb52375b26781ccd6a5ccd81abcdfc1fafd14cf93c1"}, + {file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637f674226be46f6ba372fd29d9523dd977a291f66ab2a74fbeb5530bb3f445d"}, + {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76a896b2f195b57db25d6b44e7e03f221d32fe318d03ede41f8b4d9ba1bff53c"}, + {file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e1fd23e9d01591bab45546c089ae89d926917a66dceb3abcf01f6105d927e2cb"}, + {file = "contourpy-1.3.0-cp311-cp311-win32.whl", hash = "sha256:d402880b84df3bec6eab53cd0cf802cae6a2ef9537e70cf75e91618a3801c20c"}, + {file = "contourpy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:6cb6cc968059db9c62cb35fbf70248f40994dfcd7aa10444bbf8b3faeb7c2d67"}, + {file = "contourpy-1.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:570ef7cf892f0afbe5b2ee410c507ce12e15a5fa91017a0009f79f7d93a1268f"}, + {file = "contourpy-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:da84c537cb8b97d153e9fb208c221c45605f73147bd4cadd23bdae915042aad6"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0be4d8425bfa755e0fd76ee1e019636ccc7c29f77a7c86b4328a9eb6a26d0639"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c0da700bf58f6e0b65312d0a5e695179a71d0163957fa381bb3c1f72972537c"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb8b141bb00fa977d9122636b16aa67d37fd40a3d8b52dd837e536d64b9a4d06"}, + {file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3634b5385c6716c258d0419c46d05c8aa7dc8cb70326c9a4fb66b69ad2b52e09"}, + {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0dce35502151b6bd35027ac39ba6e5a44be13a68f55735c3612c568cac3805fd"}, + {file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea348f053c645100612b333adc5983d87be69acdc6d77d3169c090d3b01dc35"}, + {file = "contourpy-1.3.0-cp312-cp312-win32.whl", hash = "sha256:90f73a5116ad1ba7174341ef3ea5c3150ddf20b024b98fb0c3b29034752c8aeb"}, + {file = "contourpy-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b11b39aea6be6764f84360fce6c82211a9db32a7c7de8fa6dd5397cf1d079c3b"}, + {file = "contourpy-1.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3e1c7fa44aaae40a2247e2e8e0627f4bea3dd257014764aa644f319a5f8600e3"}, + {file = "contourpy-1.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:364174c2a76057feef647c802652f00953b575723062560498dc7930fc9b1cb7"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32b238b3b3b649e09ce9aaf51f0c261d38644bdfa35cbaf7b263457850957a84"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d51fca85f9f7ad0b65b4b9fe800406d0d77017d7270d31ec3fb1cc07358fdea0"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:732896af21716b29ab3e988d4ce14bc5133733b85956316fb0c56355f398099b"}, + {file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d73f659398a0904e125280836ae6f88ba9b178b2fed6884f3b1f95b989d2c8da"}, + {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c6c7c2408b7048082932cf4e641fa3b8ca848259212f51c8c59c45aa7ac18f14"}, + {file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f317576606de89da6b7e0861cf6061f6146ead3528acabff9236458a6ba467f8"}, + {file = "contourpy-1.3.0-cp313-cp313-win32.whl", hash = "sha256:31cd3a85dbdf1fc002280c65caa7e2b5f65e4a973fcdf70dd2fdcb9868069294"}, + {file = "contourpy-1.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4553c421929ec95fb07b3aaca0fae668b2eb5a5203d1217ca7c34c063c53d087"}, + {file = "contourpy-1.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:345af746d7766821d05d72cb8f3845dfd08dd137101a2cb9b24de277d716def8"}, + {file = "contourpy-1.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3bb3808858a9dc68f6f03d319acd5f1b8a337e6cdda197f02f4b8ff67ad2057b"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:420d39daa61aab1221567b42eecb01112908b2cab7f1b4106a52caaec8d36973"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d63ee447261e963af02642ffcb864e5a2ee4cbfd78080657a9880b8b1868e18"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:167d6c890815e1dac9536dca00828b445d5d0df4d6a8c6adb4a7ec3166812fa8"}, + {file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:710a26b3dc80c0e4febf04555de66f5fd17e9cf7170a7b08000601a10570bda6"}, + {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:75ee7cb1a14c617f34a51d11fa7524173e56551646828353c4af859c56b766e2"}, + {file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:33c92cdae89ec5135d036e7218e69b0bb2851206077251f04a6c4e0e21f03927"}, + {file = "contourpy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a11077e395f67ffc2c44ec2418cfebed032cd6da3022a94fc227b6faf8e2acb8"}, + {file = "contourpy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e8134301d7e204c88ed7ab50028ba06c683000040ede1d617298611f9dc6240c"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e12968fdfd5bb45ffdf6192a590bd8ddd3ba9e58360b29683c6bb71a7b41edca"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd2a0fc506eccaaa7595b7e1418951f213cf8255be2600f1ea1b61e46a60c55f"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4cfb5c62ce023dfc410d6059c936dcf96442ba40814aefbfa575425a3a7f19dc"}, + {file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68a32389b06b82c2fdd68276148d7b9275b5f5cf13e5417e4252f6d1a34f72a2"}, + {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94e848a6b83da10898cbf1311a815f770acc9b6a3f2d646f330d57eb4e87592e"}, + {file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d78ab28a03c854a873787a0a42254a0ccb3cb133c672f645c9f9c8f3ae9d0800"}, + {file = "contourpy-1.3.0-cp39-cp39-win32.whl", hash = "sha256:81cb5ed4952aae6014bc9d0421dec7c5835c9c8c31cdf51910b708f548cf58e5"}, + {file = "contourpy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:14e262f67bd7e6eb6880bc564dcda30b15e351a594657e55b7eec94b6ef72843"}, + {file = "contourpy-1.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe41b41505a5a33aeaed2a613dccaeaa74e0e3ead6dd6fd3a118fb471644fd6c"}, + {file = "contourpy-1.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca7e17a65f72a5133bdbec9ecf22401c62bcf4821361ef7811faee695799779"}, + {file = "contourpy-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ec4dc6bf570f5b22ed0d7efba0dfa9c5b9e0431aeea7581aa217542d9e809a4"}, + {file = "contourpy-1.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:00ccd0dbaad6d804ab259820fa7cb0b8036bda0686ef844d24125d8287178ce0"}, + {file = "contourpy-1.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ca947601224119117f7c19c9cdf6b3ab54c5726ef1d906aa4a69dfb6dd58102"}, + {file = "contourpy-1.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6ec93afeb848a0845a18989da3beca3eec2c0f852322efe21af1931147d12cb"}, + {file = "contourpy-1.3.0.tar.gz", hash = "sha256:7ffa0db17717a8ffb127efd0c95a4362d996b892c2904db72428d5b52e1938a4"}, +] + +[package.dependencies] +numpy = ">=1.23" + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] +mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.11.1)", "types-Pillow"] +test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] +test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"] + +[package.source] +type = "legacy" +url = "https://pypi.org/simple" +reference = "pypi-public" + +[[package]] +name = "contourpy" +version = "1.3.2" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\"" +files = [ + {file = "contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934"}, + {file = "contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989"}, + {file = "contourpy-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d"}, + {file = "contourpy-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9"}, + {file = "contourpy-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512"}, + {file = "contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631"}, + {file = "contourpy-1.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f"}, + {file = "contourpy-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2"}, + {file = "contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0"}, + {file = "contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a"}, + {file = "contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445"}, + {file = "contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773"}, + {file = "contourpy-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1"}, + {file = "contourpy-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43"}, + {file = "contourpy-1.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab"}, + {file = "contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7"}, + {file = "contourpy-1.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83"}, + {file = "contourpy-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd"}, + {file = "contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f"}, + {file = "contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878"}, + {file = "contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2"}, + {file = "contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15"}, + {file = "contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92"}, + {file = "contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87"}, + {file = "contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415"}, + {file = "contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe"}, + {file = "contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441"}, + {file = "contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e"}, + {file = "contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912"}, + {file = "contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73"}, + {file = "contourpy-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb"}, + {file = "contourpy-1.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08"}, + {file = "contourpy-1.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c"}, + {file = "contourpy-1.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f"}, + {file = "contourpy-1.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85"}, + {file = "contourpy-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841"}, + {file = "contourpy-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422"}, + {file = "contourpy-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef"}, + {file = "contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f"}, + {file = "contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9"}, + {file = "contourpy-1.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f"}, + {file = "contourpy-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739"}, + {file = "contourpy-1.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823"}, + {file = "contourpy-1.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5"}, + {file = "contourpy-1.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532"}, + {file = "contourpy-1.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b"}, + {file = "contourpy-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52"}, + {file = "contourpy-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd"}, + {file = "contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1"}, + {file = "contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69"}, + {file = "contourpy-1.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c"}, + {file = "contourpy-1.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16"}, + {file = "contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad"}, + {file = "contourpy-1.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0"}, + {file = "contourpy-1.3.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5"}, + {file = "contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5"}, + {file = "contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54"}, +] + +[package.dependencies] +numpy = ">=1.23" + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] +mypy = ["bokeh", "contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.15.0)", "types-Pillow"] +test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] +test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"] + +[package.source] +type = "legacy" +url = "https://pypi.org/simple" +reference = "pypi-public" + [[package]] name = "cookiecutter" version = "2.6.0" @@ -347,6 +521,27 @@ type = "legacy" url = "https://pypi.org/simple" reference = "pypi-public" +[[package]] +name = "cycler" +version = "0.12.1" +description = "Composable style cycles" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, + {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, +] + +[package.extras] +docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] +tests = ["pytest", "pytest-cov", "pytest-xdist"] + +[package.source] +type = "legacy" +url = "https://pypi.org/simple" +reference = "pypi-public" + [[package]] name = "cython" version = "3.2.4" @@ -528,6 +723,172 @@ type = "legacy" url = "https://pypi.org/simple" reference = "pypi-public" +[[package]] +name = "fonttools" +version = "4.60.2" +description = "Tools to manipulate font files" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.9\"" +files = [ + {file = "fonttools-4.60.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4e36fadcf7e8ca6e34d490eef86ed638d6fd9c55d2f514b05687622cfc4a7050"}, + {file = "fonttools-4.60.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6e500fc9c04bee749ceabfc20cb4903f6981c2139050d85720ea7ada61b75d5c"}, + {file = "fonttools-4.60.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22efea5e784e1d1cd8d7b856c198e360a979383ebc6dea4604743b56da1cbc34"}, + {file = "fonttools-4.60.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:677aa92d84d335e4d301d8ba04afca6f575316bc647b6782cb0921943fcb6343"}, + {file = "fonttools-4.60.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:edd49d3defbf35476e78b61ff737ff5efea811acff68d44233a95a5a48252334"}, + {file = "fonttools-4.60.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:126839492b69cecc5baf2bddcde60caab2ffafd867bbae2a88463fce6078ca3a"}, + {file = "fonttools-4.60.2-cp310-cp310-win32.whl", hash = "sha256:ffcab6f5537136046ca902ed2491ab081ba271b07591b916289b7c27ff845f96"}, + {file = "fonttools-4.60.2-cp310-cp310-win_amd64.whl", hash = "sha256:9c68b287c7ffcd29dd83b5f961004b2a54a862a88825d52ea219c6220309ba45"}, + {file = "fonttools-4.60.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a2aed0a7931401b3875265717a24c726f87ecfedbb7b3426c2ca4d2812e281ae"}, + {file = "fonttools-4.60.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dea6868e9d2b816c9076cfea77754686f3c19149873bdbc5acde437631c15df1"}, + {file = "fonttools-4.60.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2fa27f34950aa1fe0f0b1abe25eed04770a3b3b34ad94e5ace82cc341589678a"}, + {file = "fonttools-4.60.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:13a53d479d187b09bfaa4a35ffcbc334fc494ff355f0a587386099cb66674f1e"}, + {file = "fonttools-4.60.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fac5e921d3bd0ca3bb8517dced2784f0742bc8ca28579a68b139f04ea323a779"}, + {file = "fonttools-4.60.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:648f4f9186fd7f1f3cd57dbf00d67a583720d5011feca67a5e88b3a491952cfb"}, + {file = "fonttools-4.60.2-cp311-cp311-win32.whl", hash = "sha256:3274e15fad871bead5453d5ce02658f6d0c7bc7e7021e2a5b8b04e2f9e40da1a"}, + {file = "fonttools-4.60.2-cp311-cp311-win_amd64.whl", hash = "sha256:91d058d5a483a1525b367803abb69de0923fbd45e1f82ebd000f5c8aa65bc78e"}, + {file = "fonttools-4.60.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e0164b7609d2b5c5dd4e044b8085b7bd7ca7363ef8c269a4ab5b5d4885a426b2"}, + {file = "fonttools-4.60.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1dd3d9574fc595c1e97faccae0f264dc88784ddf7fbf54c939528378bacc0033"}, + {file = "fonttools-4.60.2-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:98d0719f1b11c2817307d2da2e94296a3b2a3503f8d6252a101dca3ee663b917"}, + {file = "fonttools-4.60.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9d3ea26957dd07209f207b4fff64c702efe5496de153a54d3b91007ec28904dd"}, + {file = "fonttools-4.60.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ee301273b0850f3a515299f212898f37421f42ff9adfc341702582ca5073c13"}, + {file = "fonttools-4.60.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c6eb4694cc3b9c03b7c01d65a9cf35b577f21aa6abdbeeb08d3114b842a58153"}, + {file = "fonttools-4.60.2-cp312-cp312-win32.whl", hash = "sha256:57f07b616c69c244cc1a5a51072eeef07dddda5ebef9ca5c6e9cf6d59ae65b70"}, + {file = "fonttools-4.60.2-cp312-cp312-win_amd64.whl", hash = "sha256:310035802392f1fe5a7cf43d76f6ff4a24c919e4c72c0352e7b8176e2584b8a0"}, + {file = "fonttools-4.60.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2bb5fd231e56ccd7403212636dcccffc96c5ae0d6f9e4721fa0a32cb2e3ca432"}, + {file = "fonttools-4.60.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:536b5fab7b6fec78ccf59b5c59489189d9d0a8b0d3a77ed1858be59afb096696"}, + {file = "fonttools-4.60.2-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6b9288fc38252ac86a9570f19313ecbc9ff678982e0f27c757a85f1f284d3400"}, + {file = "fonttools-4.60.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93fcb420791d839ef592eada2b69997c445d0ce9c969b5190f2e16828ec10607"}, + {file = "fonttools-4.60.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7916a381b094db4052ac284255186aebf74c5440248b78860cb41e300036f598"}, + {file = "fonttools-4.60.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58c8c393d5e16b15662cfc2d988491940458aa87894c662154f50c7b49440bef"}, + {file = "fonttools-4.60.2-cp313-cp313-win32.whl", hash = "sha256:19c6e0afd8b02008caa0aa08ab896dfce5d0bcb510c49b2c499541d5cb95a963"}, + {file = "fonttools-4.60.2-cp313-cp313-win_amd64.whl", hash = "sha256:6a500dc59e11b2338c2dba1f8cf11a4ae8be35ec24af8b2628b8759a61457b76"}, + {file = "fonttools-4.60.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9387c532acbe323bbf2a920f132bce3c408a609d5f9dcfc6532fbc7e37f8ccbb"}, + {file = "fonttools-4.60.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e6f1c824185b5b8fb681297f315f26ae55abb0d560c2579242feea8236b1cfef"}, + {file = "fonttools-4.60.2-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:55a3129d1e4030b1a30260f1b32fe76781b585fb2111d04a988e141c09eb6403"}, + {file = "fonttools-4.60.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b196e63753abc33b3b97a6fd6de4b7c4fef5552c0a5ba5e562be214d1e9668e0"}, + {file = "fonttools-4.60.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:de76c8d740fb55745f3b154f0470c56db92ae3be27af8ad6c2e88f1458260c9a"}, + {file = "fonttools-4.60.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6ba6303225c95998c9fda2d410aa792c3d2c1390a09df58d194b03e17583fa25"}, + {file = "fonttools-4.60.2-cp314-cp314-win32.whl", hash = "sha256:0a89728ce10d7c816fedaa5380c06d2793e7a8a634d7ce16810e536c22047384"}, + {file = "fonttools-4.60.2-cp314-cp314-win_amd64.whl", hash = "sha256:fa8446e6ab8bd778b82cb1077058a2addba86f30de27ab9cc18ed32b34bc8667"}, + {file = "fonttools-4.60.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4063bc81ac5a4137642865cb63dd270e37b3cd1f55a07c0d6e41d072699ccca2"}, + {file = "fonttools-4.60.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ebfdb66fa69732ed604ab8e2a0431e6deff35e933a11d73418cbc7823d03b8e1"}, + {file = "fonttools-4.60.2-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50b10b3b1a72d1d54c61b0e59239e1a94c0958f4a06a1febf97ce75388dd91a4"}, + {file = "fonttools-4.60.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:beae16891a13b4a2ddec9b39b4de76092a3025e4d1c82362e3042b62295d5e4d"}, + {file = "fonttools-4.60.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:522f017fdb3766fd5d2d321774ef351cc6ce88ad4e6ac9efe643e4a2b9d528db"}, + {file = "fonttools-4.60.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:82cceceaf9c09a965a75b84a4b240dd3768e596ffb65ef53852681606fe7c9ba"}, + {file = "fonttools-4.60.2-cp314-cp314t-win32.whl", hash = "sha256:bbfbc918a75437fe7e6d64d1b1e1f713237df1cf00f3a36dedae910b2ba01cee"}, + {file = "fonttools-4.60.2-cp314-cp314t-win_amd64.whl", hash = "sha256:0e5cd9b0830f6550d58c84f3ab151a9892b50c4f9d538c5603c0ce6fff2eb3f1"}, + {file = "fonttools-4.60.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a3c75b8b42f7f93906bdba9eb1197bb76aecbe9a0a7cf6feec75f7605b5e8008"}, + {file = "fonttools-4.60.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0f86c8c37bc0ec0b9c141d5e90c717ff614e93c187f06d80f18c7057097f71bc"}, + {file = "fonttools-4.60.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe905403fe59683b0e9a45f234af2866834376b8821f34633b1c76fb731b6311"}, + {file = "fonttools-4.60.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38ce703b60a906e421e12d9e3a7f064883f5e61bb23e8961f4be33cfe578500b"}, + {file = "fonttools-4.60.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9e810c06f3e79185cecf120e58b343ea5a89b54dd695fd644446bcf8c026da5e"}, + {file = "fonttools-4.60.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:38faec8cc1d12122599814d15a402183f5123fb7608dac956121e7c6742aebc5"}, + {file = "fonttools-4.60.2-cp39-cp39-win32.whl", hash = "sha256:80a45cf7bf659acb7b36578f300231873daba67bd3ca8cce181c73f861f14a37"}, + {file = "fonttools-4.60.2-cp39-cp39-win_amd64.whl", hash = "sha256:c355d5972071938e1b1e0f5a1df001f68ecf1a62f34a3407dc8e0beccf052501"}, + {file = "fonttools-4.60.2-py3-none-any.whl", hash = "sha256:73cf92eeda67cf6ff10c8af56fc8f4f07c1647d989a979be9e388a49be26552a"}, + {file = "fonttools-4.60.2.tar.gz", hash = "sha256:d29552e6b155ebfc685b0aecf8d429cb76c14ab734c22ef5d3dea6fdf800c92c"}, +] + +[package.extras] +all = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\"", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.45.0)", "unicodedata2 (>=17.0.0) ; python_version <= \"3.14\"", "xattr ; sys_platform == \"darwin\"", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\""] +lxml = ["lxml (>=4.0)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.45.0)"] +symfont = ["sympy"] +type1 = ["xattr ; sys_platform == \"darwin\""] +unicode = ["unicodedata2 (>=17.0.0) ; python_version <= \"3.14\""] +woff = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "zopfli (>=0.1.4)"] + +[package.source] +type = "legacy" +url = "https://pypi.org/simple" +reference = "pypi-public" + +[[package]] +name = "fonttools" +version = "4.62.1" +description = "Tools to manipulate font files" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\"" +files = [ + {file = "fonttools-4.62.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ad5cca75776cd453b1b035b530e943334957ae152a36a88a320e779d61fc980c"}, + {file = "fonttools-4.62.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b3ae47e8636156a9accff64c02c0924cbebad62854c4a6dbdc110cd5b4b341a"}, + {file = "fonttools-4.62.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b9e288b4da2f64fd6180644221749de651703e8d0c16bd4b719533a3a7d6e3"}, + {file = "fonttools-4.62.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7bca7a1c1faf235ffe25d4f2e555246b4750220b38de8261d94ebc5ce8a23c23"}, + {file = "fonttools-4.62.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b4e0fcf265ad26e487c56cb12a42dffe7162de708762db951e1b3f755319507d"}, + {file = "fonttools-4.62.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2d850f66830a27b0d498ee05adb13a3781637b1826982cd7e2b3789ef0cc71ae"}, + {file = "fonttools-4.62.1-cp310-cp310-win32.whl", hash = "sha256:486f32c8047ccd05652aba17e4a8819a3a9d78570eb8a0e3b4503142947880ed"}, + {file = "fonttools-4.62.1-cp310-cp310-win_amd64.whl", hash = "sha256:5a648bde915fba9da05ae98856987ca91ba832949a9e2888b48c47ef8b96c5a9"}, + {file = "fonttools-4.62.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:40975849bac44fb0b9253d77420c6d8b523ac4dcdcefeff6e4d706838a5b80f7"}, + {file = "fonttools-4.62.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9dde91633f77fa576879a0c76b1d89de373cae751a98ddf0109d54e173b40f14"}, + {file = "fonttools-4.62.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6acb4109f8bee00fec985c8c7afb02299e35e9c94b57287f3ea542f28bd0b0a7"}, + {file = "fonttools-4.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1c5c25671ce8805e0d080e2ffdeca7f1e86778c5cbfbeae86d7f866d8830517b"}, + {file = "fonttools-4.62.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a5d8825e1140f04e6c99bb7d37a9e31c172f3bc208afbe02175339e699c710e1"}, + {file = "fonttools-4.62.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:268abb1cb221e66c014acc234e872b7870d8b5d4657a83a8f4205094c32d2416"}, + {file = "fonttools-4.62.1-cp311-cp311-win32.whl", hash = "sha256:942b03094d7edbb99bdf1ae7e9090898cad7bf9030b3d21f33d7072dbcb51a53"}, + {file = "fonttools-4.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:e8514f4924375f77084e81467e63238b095abda5107620f49421c368a6017ed2"}, + {file = "fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:90365821debbd7db678809c7491ca4acd1e0779b9624cdc6ddaf1f31992bf974"}, + {file = "fonttools-4.62.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12859ff0b47dd20f110804c3e0d0970f7b832f561630cd879969011541a464a9"}, + {file = "fonttools-4.62.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c125ffa00c3d9003cdaaf7f2c79e6e535628093e14b5de1dccb08859b680936"}, + {file = "fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:149f7d84afca659d1a97e39a4778794a2f83bf344c5ee5134e09995086cc2392"}, + {file = "fonttools-4.62.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0aa72c43a601cfa9273bb1ae0518f1acadc01ee181a6fc60cd758d7fdadffc04"}, + {file = "fonttools-4.62.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:19177c8d96c7c36359266e571c5173bcee9157b59cfc8cb0153c5673dc5a3a7d"}, + {file = "fonttools-4.62.1-cp312-cp312-win32.whl", hash = "sha256:a24decd24d60744ee8b4679d38e88b8303d86772053afc29b19d23bb8207803c"}, + {file = "fonttools-4.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e7863e10b3de72376280b515d35b14f5eeed639d1aa7824f4cf06779ec65e42"}, + {file = "fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c22b1014017111c401469e3acc5433e6acf6ebcc6aa9efb538a533c800971c79"}, + {file = "fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68959f5fc58ed4599b44aad161c2837477d7f35f5f79402d97439974faebfebe"}, + {file = "fonttools-4.62.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef46db46c9447103b8f3ff91e8ba009d5fe181b1920a83757a5762551e32bb68"}, + {file = "fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6706d1cb1d5e6251a97ad3c1b9347505c5615c112e66047abbef0f8545fa30d1"}, + {file = "fonttools-4.62.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e7abd2b1e11736f58c1de27819e1955a53267c21732e78243fa2fa2e5c1e069"}, + {file = "fonttools-4.62.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:403d28ce06ebfc547fbcb0cb8b7f7cc2f7a2d3e1a67ba9a34b14632df9e080f9"}, + {file = "fonttools-4.62.1-cp313-cp313-win32.whl", hash = "sha256:93c316e0f5301b2adbe6a5f658634307c096fd5aae60a5b3412e4f3e1728ab24"}, + {file = "fonttools-4.62.1-cp313-cp313-win_amd64.whl", hash = "sha256:7aa21ff53e28a9c2157acbc44e5b401149d3c9178107130e82d74ceb500e5056"}, + {file = "fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fa1d16210b6b10a826d71bed68dd9ec24a9e218d5a5e2797f37c573e7ec215ca"}, + {file = "fonttools-4.62.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aa69d10ed420d8121118e628ad47d86e4caa79ba37f968597b958f6cceab7eca"}, + {file = "fonttools-4.62.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd13b7999d59c5eb1c2b442eb2d0c427cb517a0b7a1f5798fc5c9e003f5ff782"}, + {file = "fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8d337fdd49a79b0d51c4da87bc38169d21c3abbf0c1aa9367eff5c6656fb6dae"}, + {file = "fonttools-4.62.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d241cdc4a67b5431c6d7f115fdf63335222414995e3a1df1a41e1182acd4bcc7"}, + {file = "fonttools-4.62.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c05557a78f8fa514da0f869556eeda40887a8abc77c76ee3f74cf241778afd5a"}, + {file = "fonttools-4.62.1-cp314-cp314-win32.whl", hash = "sha256:49a445d2f544ce4a69338694cad575ba97b9a75fff02720da0882d1a73f12800"}, + {file = "fonttools-4.62.1-cp314-cp314-win_amd64.whl", hash = "sha256:1eecc128c86c552fb963fe846ca4e011b1be053728f798185a1687502f6d398e"}, + {file = "fonttools-4.62.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1596aeaddf7f78e21e68293c011316a25267b3effdaccaf4d59bc9159d681b82"}, + {file = "fonttools-4.62.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8f8fca95d3bb3208f59626a4b0ea6e526ee51f5a8ad5d91821c165903e8d9260"}, + {file = "fonttools-4.62.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee91628c08e76f77b533d65feb3fbe6d9dad699f95be51cf0d022db94089cdc4"}, + {file = "fonttools-4.62.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f37df1cac61d906e7b836abe356bc2f34c99d4477467755c216b72aa3dc748b"}, + {file = "fonttools-4.62.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92bb00a947e666169c99b43753c4305fc95a890a60ef3aeb2a6963e07902cc87"}, + {file = "fonttools-4.62.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bdfe592802ef939a0e33106ea4a318eeb17822c7ee168c290273cbd5fabd746c"}, + {file = "fonttools-4.62.1-cp314-cp314t-win32.whl", hash = "sha256:b820fcb92d4655513d8402d5b219f94481c4443d825b4372c75a2072aa4b357a"}, + {file = "fonttools-4.62.1-cp314-cp314t-win_amd64.whl", hash = "sha256:59b372b4f0e113d3746b88985f1c796e7bf830dd54b28374cd85c2b8acd7583e"}, + {file = "fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd"}, + {file = "fonttools-4.62.1.tar.gz", hash = "sha256:e54c75fd6041f1122476776880f7c3c3295ffa31962dc6ebe2543c00dca58b5d"}, +] + +[package.extras] +all = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\"", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.45.0)", "unicodedata2 (>=17.0.0) ; python_version <= \"3.14\"", "xattr ; sys_platform == \"darwin\"", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\""] +lxml = ["lxml (>=4.0)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.45.0)"] +symfont = ["sympy"] +type1 = ["xattr ; sys_platform == \"darwin\""] +unicode = ["unicodedata2 (>=17.0.0) ; python_version <= \"3.14\""] +woff = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "zopfli (>=0.1.4)"] + +[package.source] +type = "legacy" +url = "https://pypi.org/simple" +reference = "pypi-public" + [[package]] name = "grimp" version = "3.13" @@ -943,6 +1304,35 @@ type = "legacy" url = "https://pypi.org/simple" reference = "pypi-public" +[[package]] +name = "importlib-resources" +version = "6.5.2" +description = "Read resources from Python packages" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.9\"" +files = [ + {file = "importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec"}, + {file = "importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c"}, +] + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "zipp (>=3.17)"] +type = ["pytest-mypy"] + +[package.source] +type = "legacy" +url = "https://pypi.org/simple" +reference = "pypi-public" + [[package]] name = "iniconfig" version = "2.1.0" @@ -1186,6 +1576,289 @@ url = "https://github.com/Carvera-Community/kivy-ios.git" reference = "pr-643-fix-custom-recipes" resolved_reference = "06b743dfc52a3f1ca51c9dc92d1adda7ec77c459" +[[package]] +name = "kivy-matplotlib-widget" +version = "0.16.0" +description = "A Matplotlib interactive widget for kivy" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "kivy_matplotlib_widget-0.16.0.tar.gz", hash = "sha256:d9563562b832be82d1b1b0161cde50279c0589d006e4dc36f88c85723122bdaf"}, +] + +[package.dependencies] +kivy = ">=2.0.0" +matplotlib = ">=3.5.2" + +[package.source] +type = "legacy" +url = "https://pypi.org/simple" +reference = "pypi-public" + +[[package]] +name = "kiwisolver" +version = "1.4.7" +description = "A fast implementation of the Cassowary constraint solver" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.9\"" +files = [ + {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8a9c83f75223d5e48b0bc9cb1bf2776cf01563e00ade8775ffe13b0b6e1af3a6"}, + {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58370b1ffbd35407444d57057b57da5d6549d2d854fa30249771775c63b5fe17"}, + {file = "kiwisolver-1.4.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa0abdf853e09aff551db11fce173e2177d00786c688203f52c87ad7fcd91ef9"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8d53103597a252fb3ab8b5845af04c7a26d5e7ea8122303dd7a021176a87e8b9"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:88f17c5ffa8e9462fb79f62746428dd57b46eb931698e42e990ad63103f35e6c"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a9ca9c710d598fd75ee5de59d5bda2684d9db36a9f50b6125eaea3969c2599"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f4d742cb7af1c28303a51b7a27aaee540e71bb8e24f68c736f6f2ffc82f2bf05"}, + {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e28c7fea2196bf4c2f8d46a0415c77a1c480cc0724722f23d7410ffe9842c407"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e968b84db54f9d42046cf154e02911e39c0435c9801681e3fc9ce8a3c4130278"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0c18ec74c0472de033e1bebb2911c3c310eef5649133dd0bedf2a169a1b269e5"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8f0ea6da6d393d8b2e187e6a5e3fb81f5862010a40c3945e2c6d12ae45cfb2ad"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:f106407dda69ae456dd1227966bf445b157ccc80ba0dff3802bb63f30b74e895"}, + {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84ec80df401cfee1457063732d90022f93951944b5b58975d34ab56bb150dfb3"}, + {file = "kiwisolver-1.4.7-cp310-cp310-win32.whl", hash = "sha256:71bb308552200fb2c195e35ef05de12f0c878c07fc91c270eb3d6e41698c3bcc"}, + {file = "kiwisolver-1.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:44756f9fd339de0fb6ee4f8c1696cfd19b2422e0d70b4cefc1cc7f1f64045a8c"}, + {file = "kiwisolver-1.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:78a42513018c41c2ffd262eb676442315cbfe3c44eed82385c2ed043bc63210a"}, + {file = "kiwisolver-1.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d2b0e12a42fb4e72d509fc994713d099cbb15ebf1103545e8a45f14da2dfca54"}, + {file = "kiwisolver-1.4.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2a8781ac3edc42ea4b90bc23e7d37b665d89423818e26eb6df90698aa2287c95"}, + {file = "kiwisolver-1.4.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46707a10836894b559e04b0fd143e343945c97fd170d69a2d26d640b4e297935"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef97b8df011141c9b0f6caf23b29379f87dd13183c978a30a3c546d2c47314cb"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab58c12a2cd0fc769089e6d38466c46d7f76aced0a1f54c77652446733d2d02"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:803b8e1459341c1bb56d1c5c010406d5edec8a0713a0945851290a7930679b51"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9a9e8a507420fe35992ee9ecb302dab68550dedc0da9e2880dd88071c5fb052"}, + {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18077b53dc3bb490e330669a99920c5e6a496889ae8c63b58fbc57c3d7f33a18"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6af936f79086a89b3680a280c47ea90b4df7047b5bdf3aa5c524bbedddb9e545"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3abc5b19d24af4b77d1598a585b8a719beb8569a71568b66f4ebe1fb0449460b"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:933d4de052939d90afbe6e9d5273ae05fb836cc86c15b686edd4b3560cc0ee36"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:65e720d2ab2b53f1f72fb5da5fb477455905ce2c88aaa671ff0a447c2c80e8e3"}, + {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3bf1ed55088f214ba6427484c59553123fdd9b218a42bbc8c6496d6754b1e523"}, + {file = "kiwisolver-1.4.7-cp311-cp311-win32.whl", hash = "sha256:4c00336b9dd5ad96d0a558fd18a8b6f711b7449acce4c157e7343ba92dd0cf3d"}, + {file = "kiwisolver-1.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:929e294c1ac1e9f615c62a4e4313ca1823ba37326c164ec720a803287c4c499b"}, + {file = "kiwisolver-1.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:e33e8fbd440c917106b237ef1a2f1449dfbb9b6f6e1ce17c94cd6a1e0d438376"}, + {file = "kiwisolver-1.4.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5360cc32706dab3931f738d3079652d20982511f7c0ac5711483e6eab08efff2"}, + {file = "kiwisolver-1.4.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942216596dc64ddb25adb215c3c783215b23626f8d84e8eff8d6d45c3f29f75a"}, + {file = "kiwisolver-1.4.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:48b571ecd8bae15702e4f22d3ff6a0f13e54d3d00cd25216d5e7f658242065ee"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad42ba922c67c5f219097b28fae965e10045ddf145d2928bfac2eb2e17673640"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:612a10bdae23404a72941a0fc8fa2660c6ea1217c4ce0dbcab8a8f6543ea9e7f"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e838bba3a3bac0fe06d849d29772eb1afb9745a59710762e4ba3f4cb8424483"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22f499f6157236c19f4bbbd472fa55b063db77a16cd74d49afe28992dff8c258"}, + {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693902d433cf585133699972b6d7c42a8b9f8f826ebcaf0132ff55200afc599e"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4e77f2126c3e0b0d055f44513ed349038ac180371ed9b52fe96a32aa071a5107"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:657a05857bda581c3656bfc3b20e353c232e9193eb167766ad2dc58b56504948"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4bfa75a048c056a411f9705856abfc872558e33c055d80af6a380e3658766038"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:34ea1de54beef1c104422d210c47c7d2a4999bdecf42c7b5718fbe59a4cac383"}, + {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:90da3b5f694b85231cf93586dad5e90e2d71b9428f9aad96952c99055582f520"}, + {file = "kiwisolver-1.4.7-cp312-cp312-win32.whl", hash = "sha256:18e0cca3e008e17fe9b164b55735a325140a5a35faad8de92dd80265cd5eb80b"}, + {file = "kiwisolver-1.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:58cb20602b18f86f83a5c87d3ee1c766a79c0d452f8def86d925e6c60fbf7bfb"}, + {file = "kiwisolver-1.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:f5a8b53bdc0b3961f8b6125e198617c40aeed638b387913bf1ce78afb1b0be2a"}, + {file = "kiwisolver-1.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2e6039dcbe79a8e0f044f1c39db1986a1b8071051efba3ee4d74f5b365f5226e"}, + {file = "kiwisolver-1.4.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a1ecf0ac1c518487d9d23b1cd7139a6a65bc460cd101ab01f1be82ecf09794b6"}, + {file = "kiwisolver-1.4.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ab9ccab2b5bd5702ab0803676a580fffa2aa178c2badc5557a84cc943fcf750"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f816dd2277f8d63d79f9c8473a79fe54047bc0467754962840782c575522224d"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf8bcc23ceb5a1b624572a1623b9f79d2c3b337c8c455405ef231933a10da379"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dea0bf229319828467d7fca8c7c189780aa9ff679c94539eed7532ebe33ed37c"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c06a4c7cf15ec739ce0e5971b26c93638730090add60e183530d70848ebdd34"}, + {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913983ad2deb14e66d83c28b632fd35ba2b825031f2fa4ca29675e665dfecbe1"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5337ec7809bcd0f424c6b705ecf97941c46279cf5ed92311782c7c9c2026f07f"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c26ed10c4f6fa6ddb329a5120ba3b6db349ca192ae211e882970bfc9d91420b"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c619b101e6de2222c1fcb0531e1b17bbffbe54294bfba43ea0d411d428618c27"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:073a36c8273647592ea332e816e75ef8da5c303236ec0167196793eb1e34657a"}, + {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3ce6b2b0231bda412463e152fc18335ba32faf4e8c23a754ad50ffa70e4091ee"}, + {file = "kiwisolver-1.4.7-cp313-cp313-win32.whl", hash = "sha256:f4c9aee212bc89d4e13f58be11a56cc8036cabad119259d12ace14b34476fd07"}, + {file = "kiwisolver-1.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:8a3ec5aa8e38fc4c8af308917ce12c536f1c88452ce554027e55b22cbbfbff76"}, + {file = "kiwisolver-1.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:76c8094ac20ec259471ac53e774623eb62e6e1f56cd8690c67ce6ce4fcb05650"}, + {file = "kiwisolver-1.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5d5abf8f8ec1f4e22882273c423e16cae834c36856cac348cfbfa68e01c40f3a"}, + {file = "kiwisolver-1.4.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:aeb3531b196ef6f11776c21674dba836aeea9d5bd1cf630f869e3d90b16cfade"}, + {file = "kiwisolver-1.4.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7d755065e4e866a8086c9bdada157133ff466476a2ad7861828e17b6026e22c"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08471d4d86cbaec61f86b217dd938a83d85e03785f51121e791a6e6689a3be95"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bbfcb7165ce3d54a3dfbe731e470f65739c4c1f85bb1018ee912bae139e263b"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d34eb8494bea691a1a450141ebb5385e4b69d38bb8403b5146ad279f4b30fa3"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9242795d174daa40105c1d86aba618e8eab7bf96ba8c3ee614da8302a9f95503"}, + {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a0f64a48bb81af7450e641e3fe0b0394d7381e342805479178b3d335d60ca7cf"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8e045731a5416357638d1700927529e2b8ab304811671f665b225f8bf8d8f933"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4322872d5772cae7369f8351da1edf255a604ea7087fe295411397d0cfd9655e"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e1631290ee9271dffe3062d2634c3ecac02c83890ada077d225e081aca8aab89"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:edcfc407e4eb17e037bca59be0e85a2031a2ac87e4fed26d3e9df88b4165f92d"}, + {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4d05d81ecb47d11e7f8932bd8b61b720bf0b41199358f3f5e36d38e28f0532c5"}, + {file = "kiwisolver-1.4.7-cp38-cp38-win32.whl", hash = "sha256:b38ac83d5f04b15e515fd86f312479d950d05ce2368d5413d46c088dda7de90a"}, + {file = "kiwisolver-1.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:d83db7cde68459fc803052a55ace60bea2bae361fc3b7a6d5da07e11954e4b09"}, + {file = "kiwisolver-1.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f9362ecfca44c863569d3d3c033dbe8ba452ff8eed6f6b5806382741a1334bd"}, + {file = "kiwisolver-1.4.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e8df2eb9b2bac43ef8b082e06f750350fbbaf2887534a5be97f6cf07b19d9583"}, + {file = "kiwisolver-1.4.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f32d6edbc638cde7652bd690c3e728b25332acbadd7cad670cc4a02558d9c417"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e2e6c39bd7b9372b0be21456caab138e8e69cc0fc1190a9dfa92bd45a1e6e904"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dda56c24d869b1193fcc763f1284b9126550eaf84b88bbc7256e15028f19188a"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79849239c39b5e1fd906556c474d9b0439ea6792b637511f3fe3a41158d89ca8"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e3bc157fed2a4c02ec468de4ecd12a6e22818d4f09cde2c31ee3226ffbefab2"}, + {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3da53da805b71e41053dc670f9a820d1157aae77b6b944e08024d17bcd51ef88"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8705f17dfeb43139a692298cb6637ee2e59c0194538153e83e9ee0c75c2eddde"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:82a5c2f4b87c26bb1a0ef3d16b5c4753434633b83d365cc0ddf2770c93829e3c"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce8be0466f4c0d585cdb6c1e2ed07232221df101a4c6f28821d2aa754ca2d9e2"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:409afdfe1e2e90e6ee7fc896f3df9a7fec8e793e58bfa0d052c8a82f99c37abb"}, + {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5b9c3f4ee0b9a439d2415012bd1b1cc2df59e4d6a9939f4d669241d30b414327"}, + {file = "kiwisolver-1.4.7-cp39-cp39-win32.whl", hash = "sha256:a79ae34384df2b615eefca647a2873842ac3b596418032bef9a7283675962644"}, + {file = "kiwisolver-1.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:cf0438b42121a66a3a667de17e779330fc0f20b0d97d59d2f2121e182b0505e4"}, + {file = "kiwisolver-1.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:764202cc7e70f767dab49e8df52c7455e8de0df5d858fa801a11aa0d882ccf3f"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:94252291e3fe68001b1dd747b4c0b3be12582839b95ad4d1b641924d68fd4643"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b7dfa3b546da08a9f622bb6becdb14b3e24aaa30adba66749d38f3cc7ea9706"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd3de6481f4ed8b734da5df134cd5a6a64fe32124fe83dde1e5b5f29fe30b1e6"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a91b5f9f1205845d488c928e8570dcb62b893372f63b8b6e98b863ebd2368ff2"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fa14dbd66b8b8f470d5fc79c089a66185619d31645f9b0773b88b19f7223c4"}, + {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:eb542fe7933aa09d8d8f9d9097ef37532a7df6497819d16efe4359890a2f417a"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bfa1acfa0c54932d5607e19a2c24646fb4c1ae2694437789129cf099789a3b00"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:eee3ea935c3d227d49b4eb85660ff631556841f6e567f0f7bda972df6c2c9935"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f3160309af4396e0ed04db259c3ccbfdc3621b5559b5453075e5de555e1f3a1b"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a17f6a29cf8935e587cc8a4dbfc8368c55edc645283db0ce9801016f83526c2d"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10849fb2c1ecbfae45a693c070e0320a91b35dd4bcf58172c023b994283a124d"}, + {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ac542bf38a8a4be2dc6b15248d36315ccc65f0743f7b1a76688ffb6b5129a5c2"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8b01aac285f91ca889c800042c35ad3b239e704b150cfd3382adfc9dcc780e39"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:48be928f59a1f5c8207154f935334d374e79f2b5d212826307d072595ad76a2e"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f37cfe618a117e50d8c240555331160d73d0411422b59b5ee217843d7b693608"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599b5c873c63a1f6ed7eead644a8a380cfbdf5db91dcb6f85707aaab213b1674"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:801fa7802e5cfabe3ab0c81a34c323a319b097dfb5004be950482d882f3d7225"}, + {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0c6c43471bc764fad4bc99c5c2d6d16a676b1abf844ca7c8702bdae92df01ee0"}, + {file = "kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.org/simple" +reference = "pypi-public" + +[[package]] +name = "kiwisolver" +version = "1.5.0" +description = "A fast implementation of the Cassowary constraint solver" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\"" +files = [ + {file = "kiwisolver-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32cc0a5365239a6ea0c6ed461e8838d053b57e397443c0ca894dcc8e388d4374"}, + {file = "kiwisolver-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cc0b66c1eec9021353a4b4483afb12dfd50e3669ffbb9152d6842eb34c7e29fd"}, + {file = "kiwisolver-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86e0287879f75621ae85197b0877ed2f8b7aa57b511c7331dce2eb6f4de7d476"}, + {file = "kiwisolver-1.5.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:62f59da443c4f4849f73a51a193b1d9d258dcad0c41bc4d1b8fb2bcc04bfeb22"}, + {file = "kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9190426b7aa26c5229501fa297b8d0653cfd3f5a36f7990c264e157cbf886b3b"}, + {file = "kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c8277104ded0a51e699c8c3aff63ce2c56d4ed5519a5f73e0fd7057f959a2b9e"}, + {file = "kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8f9baf6f0a6e7571c45c8863010b45e837c3ee1c2c77fcd6ef423be91b21fedb"}, + {file = "kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cff8e5383db4989311f99e814feeb90c4723eb4edca425b9d5d9c3fefcdd9537"}, + {file = "kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ebae99ed6764f2b5771c522477b311be313e8841d2e0376db2b10922daebbba4"}, + {file = "kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:d5cd5189fc2b6a538b75ae45433140c4823463918f7b1617c31e68b085c0022c"}, + {file = "kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f42c23db5d1521218a3276bb08666dcb662896a0be7347cba864eca45ff64ede"}, + {file = "kiwisolver-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:94eff26096eb5395136634622515b234ecb6c9979824c1f5004c6e3c3c85ccd2"}, + {file = "kiwisolver-1.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:dd952e03bfbb096cfe2dd35cd9e00f269969b67536cb4370994afc20ff2d0875"}, + {file = "kiwisolver-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9eed0f7edbb274413b6ee781cca50541c8c0facd3d6fd289779e494340a2b85c"}, + {file = "kiwisolver-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c4923e404d6bcd91b6779c009542e5647fef32e4a5d75e115e3bbac6f2335eb"}, + {file = "kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac"}, + {file = "kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27"}, + {file = "kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff710414307fefa903e0d9bdf300972f892c23477829f49504e59834f4195398"}, + {file = "kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6176c1811d9d5a04fa391c490cc44f451e240697a16977f11c6f722efb9041db"}, + {file = "kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50847dca5d197fcbd389c805aa1a1cf32f25d2e7273dc47ab181a517666b68cc"}, + {file = "kiwisolver-1.5.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:01808c6d15f4c3e8559595d6d1fe6411c68e4a3822b4b9972b44473b24f4e679"}, + {file = "kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1f9f4121ec58628c96baa3de1a55a4e3a333c5102c8e94b64e23bf7b2083309"}, + {file = "kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b7d335370ae48a780c6e6a6bbfa97342f563744c39c35562f3f367665f5c1de2"}, + {file = "kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:800ee55980c18545af444d93fdd60c56b580db5cc54867d8cbf8a1dc0829938c"}, + {file = "kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c438f6ca858697c9ab67eb28246c92508af972e114cac34e57a6d4ba17a3ac08"}, + {file = "kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c63c91f95173f9c2a67c7c526b2cea976828a0e7fced9cdcead2802dc10f8a4"}, + {file = "kiwisolver-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b"}, + {file = "kiwisolver-1.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:ad4ae4ffd1ee9cd11357b4c66b612da9888f4f4daf2f36995eda64bd45370cac"}, + {file = "kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9"}, + {file = "kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588"}, + {file = "kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819"}, + {file = "kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f"}, + {file = "kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf"}, + {file = "kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d"}, + {file = "kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083"}, + {file = "kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6"}, + {file = "kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1"}, + {file = "kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0"}, + {file = "kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15"}, + {file = "kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314"}, + {file = "kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9"}, + {file = "kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384"}, + {file = "kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7"}, + {file = "kiwisolver-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09"}, + {file = "kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3"}, + {file = "kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd"}, + {file = "kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3"}, + {file = "kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96"}, + {file = "kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099"}, + {file = "kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8"}, + {file = "kiwisolver-1.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87"}, + {file = "kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23"}, + {file = "kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859"}, + {file = "kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902"}, + {file = "kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167"}, + {file = "kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0"}, + {file = "kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276"}, + {file = "kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53"}, + {file = "kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615"}, + {file = "kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02"}, + {file = "kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e"}, + {file = "kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac"}, + {file = "kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05"}, + {file = "kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd"}, + {file = "kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a"}, + {file = "kiwisolver-1.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554"}, + {file = "kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581"}, + {file = "kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303"}, + {file = "kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9"}, + {file = "kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79"}, + {file = "kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796"}, + {file = "kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e"}, + {file = "kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57"}, + {file = "kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797"}, + {file = "kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203"}, + {file = "kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7"}, + {file = "kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57"}, + {file = "kiwisolver-1.5.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:295d9ffe712caa9f8a3081de8d32fc60191b4b51c76f02f951fd8407253528f4"}, + {file = "kiwisolver-1.5.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:51e8c4084897de9f05898c2c2a39af6318044ae969d46ff7a34ed3f96274adca"}, + {file = "kiwisolver-1.5.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b83af57bdddef03c01a9138034c6ff03181a3028d9a1003b301eb1a55e161a3f"}, + {file = "kiwisolver-1.5.0-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf4679a3d71012a7c2bf360e5cd878fbd5e4fcac0896b56393dec239d81529ed"}, + {file = "kiwisolver-1.5.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:41024ed50e44ab1a60d3fe0a9d15a4ccc9f5f2b1d814ff283c8d01134d5b81bc"}, + {file = "kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ec4c85dc4b687c7f7f15f553ff26a98bfe8c58f5f7f0ac8905f0ba4c7be60232"}, + {file = "kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:12e91c215a96e39f57989c8912ae761286ac5a9584d04030ceb3368a357f017a"}, + {file = "kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be4a51a55833dc29ab5d7503e7bcb3b3af3402d266018137127450005cdfe737"}, + {file = "kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daae526907e262de627d8f70058a0f64acc9e2641c164c99c8f594b34a799a16"}, + {file = "kiwisolver-1.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1"}, + {file = "kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.org/simple" +reference = "pypi-public" + [[package]] name = "librt" version = "0.9.0" @@ -1448,6 +2121,163 @@ type = "legacy" url = "https://pypi.org/simple" reference = "pypi-public" +[[package]] +name = "matplotlib" +version = "3.9.4" +description = "Python plotting package" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.9\"" +files = [ + {file = "matplotlib-3.9.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c5fdd7abfb706dfa8d307af64a87f1a862879ec3cd8d0ec8637458f0885b9c50"}, + {file = "matplotlib-3.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d89bc4e85e40a71d1477780366c27fb7c6494d293e1617788986f74e2a03d7ff"}, + {file = "matplotlib-3.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ddf9f3c26aae695c5daafbf6b94e4c1a30d6cd617ba594bbbded3b33a1fcfa26"}, + {file = "matplotlib-3.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18ebcf248030173b59a868fda1fe42397253f6698995b55e81e1f57431d85e50"}, + {file = "matplotlib-3.9.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:974896ec43c672ec23f3f8c648981e8bc880ee163146e0312a9b8def2fac66f5"}, + {file = "matplotlib-3.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:4598c394ae9711cec135639374e70871fa36b56afae17bdf032a345be552a88d"}, + {file = "matplotlib-3.9.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d4dd29641d9fb8bc4492420c5480398dd40a09afd73aebe4eb9d0071a05fbe0c"}, + {file = "matplotlib-3.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30e5b22e8bcfb95442bf7d48b0d7f3bdf4a450cbf68986ea45fca3d11ae9d099"}, + {file = "matplotlib-3.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bb0030d1d447fd56dcc23b4c64a26e44e898f0416276cac1ebc25522e0ac249"}, + {file = "matplotlib-3.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aca90ed222ac3565d2752b83dbb27627480d27662671e4d39da72e97f657a423"}, + {file = "matplotlib-3.9.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a181b2aa2906c608fcae72f977a4a2d76e385578939891b91c2550c39ecf361e"}, + {file = "matplotlib-3.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:1f6882828231eca17f501c4dcd98a05abb3f03d157fbc0769c6911fe08b6cfd3"}, + {file = "matplotlib-3.9.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dfc48d67e6661378a21c2983200a654b72b5c5cdbd5d2cf6e5e1ece860f0cc70"}, + {file = "matplotlib-3.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:47aef0fab8332d02d68e786eba8113ffd6f862182ea2999379dec9e237b7e483"}, + {file = "matplotlib-3.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fba1f52c6b7dc764097f52fd9ab627b90db452c9feb653a59945de16752e965f"}, + {file = "matplotlib-3.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:173ac3748acaac21afcc3fa1633924609ba1b87749006bc25051c52c422a5d00"}, + {file = "matplotlib-3.9.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320edea0cadc07007765e33f878b13b3738ffa9745c5f707705692df70ffe0e0"}, + {file = "matplotlib-3.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a4a4cfc82330b27042a7169533da7991e8789d180dd5b3daeaee57d75cd5a03b"}, + {file = "matplotlib-3.9.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37eeffeeca3c940985b80f5b9a7b95ea35671e0e7405001f249848d2b62351b6"}, + {file = "matplotlib-3.9.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3e7465ac859ee4abcb0d836137cd8414e7bb7ad330d905abced457217d4f0f45"}, + {file = "matplotlib-3.9.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4c12302c34afa0cf061bea23b331e747e5e554b0fa595c96e01c7b75bc3b858"}, + {file = "matplotlib-3.9.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b8c97917f21b75e72108b97707ba3d48f171541a74aa2a56df7a40626bafc64"}, + {file = "matplotlib-3.9.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0229803bd7e19271b03cb09f27db76c918c467aa4ce2ae168171bc67c3f508df"}, + {file = "matplotlib-3.9.4-cp313-cp313-win_amd64.whl", hash = "sha256:7c0d8ef442ebf56ff5e206f8083d08252ee738e04f3dc88ea882853a05488799"}, + {file = "matplotlib-3.9.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a04c3b00066a688834356d196136349cb32f5e1003c55ac419e91585168b88fb"}, + {file = "matplotlib-3.9.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:04c519587f6c210626741a1e9a68eefc05966ede24205db8982841826af5871a"}, + {file = "matplotlib-3.9.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:308afbf1a228b8b525fcd5cec17f246bbbb63b175a3ef6eb7b4d33287ca0cf0c"}, + {file = "matplotlib-3.9.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddb3b02246ddcffd3ce98e88fed5b238bc5faff10dbbaa42090ea13241d15764"}, + {file = "matplotlib-3.9.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8a75287e9cb9eee48cb79ec1d806f75b29c0fde978cb7223a1f4c5848d696041"}, + {file = "matplotlib-3.9.4-cp313-cp313t-win_amd64.whl", hash = "sha256:488deb7af140f0ba86da003e66e10d55ff915e152c78b4b66d231638400b1965"}, + {file = "matplotlib-3.9.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3c3724d89a387ddf78ff88d2a30ca78ac2b4c89cf37f2db4bd453c34799e933c"}, + {file = "matplotlib-3.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d5f0a8430ffe23d7e32cfd86445864ccad141797f7d25b7c41759a5b5d17cfd7"}, + {file = "matplotlib-3.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bb0141a21aef3b64b633dc4d16cbd5fc538b727e4958be82a0e1c92a234160e"}, + {file = "matplotlib-3.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57aa235109e9eed52e2c2949db17da185383fa71083c00c6c143a60e07e0888c"}, + {file = "matplotlib-3.9.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b18c600061477ccfdd1e6fd050c33d8be82431700f3452b297a56d9ed7037abb"}, + {file = "matplotlib-3.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:ef5f2d1b67d2d2145ff75e10f8c008bfbf71d45137c4b648c87193e7dd053eac"}, + {file = "matplotlib-3.9.4-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:44e0ed786d769d85bc787b0606a53f2d8d2d1d3c8a2608237365e9121c1a338c"}, + {file = "matplotlib-3.9.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:09debb9ce941eb23ecdbe7eab972b1c3e0276dcf01688073faff7b0f61d6c6ca"}, + {file = "matplotlib-3.9.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcc53cf157a657bfd03afab14774d54ba73aa84d42cfe2480c91bd94873952db"}, + {file = "matplotlib-3.9.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ad45da51be7ad02387801fd154ef74d942f49fe3fcd26a64c94842ba7ec0d865"}, + {file = "matplotlib-3.9.4.tar.gz", hash = "sha256:1e00e8be7393cbdc6fedfa8a6fba02cf3e83814b285db1c60b906a023ba41bc3"}, +] + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +importlib-resources = {version = ">=3.2.0", markers = "python_version < \"3.10\""} +kiwisolver = ">=1.3.1" +numpy = ">=1.23" +packaging = ">=20.0" +pillow = ">=8" +pyparsing = ">=2.3.1" +python-dateutil = ">=2.7" + +[package.extras] +dev = ["meson-python (>=0.13.1,<0.17.0)", "numpy (>=1.25)", "pybind11 (>=2.6,!=2.13.3)", "setuptools (>=64)", "setuptools_scm (>=7)"] + +[package.source] +type = "legacy" +url = "https://pypi.org/simple" +reference = "pypi-public" + +[[package]] +name = "matplotlib" +version = "3.10.9" +description = "Python plotting package" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\"" +files = [ + {file = "matplotlib-3.10.9-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77210dce9cb8153dffc967efaae990543392563d5a376d4dd8539bebcb0ed217"}, + {file = "matplotlib-3.10.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1e7698ac9868428e84d2c967424803b2472ff7167d9d6590d4204ed775343c3b"}, + {file = "matplotlib-3.10.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1aa972116abb4c9d201bf245620b433726cb6856f3bef6a78f776a00f5c92d37"}, + {file = "matplotlib-3.10.9-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae2f11957b27ce53497dd4d7b235c4d4f1faf383dfb39d0c5beb833bff883294"}, + {file = "matplotlib-3.10.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b049278ddce116aaa1c1377ebf58adea909132dfce0281cf7e3a1ea9fc2e2c65"}, + {file = "matplotlib-3.10.9-cp310-cp310-win_amd64.whl", hash = "sha256:82834c3c292d24d3a8aae77cd2d20019de69d692a34a970e4fdb8d33e2ea3dda"}, + {file = "matplotlib-3.10.9-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:68cfdcede415f7c8f5577b03303dd94526cdb6d11036cecdc205e08733b2d2bb"}, + {file = "matplotlib-3.10.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfca0129678bd56379db26c52b5d77ed7de314c047492fbdc763aa7501710cfb"}, + {file = "matplotlib-3.10.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e436d155fa8a3399dc62683f8f5d0e2e50d25d0144a73edd73f82eec8f4abfb"}, + {file = "matplotlib-3.10.9-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56fc0bd271b00025c6edfdc7c2dcd247372c8e1544971d62e1dc7c17367e8bf9"}, + {file = "matplotlib-3.10.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5a6104ed666402ba5106d7f36e0e0cdca4e8d7fa4d39708ca88019e2835a2eb"}, + {file = "matplotlib-3.10.9-cp311-cp311-win_amd64.whl", hash = "sha256:d730e984eddf56974c3e72b6129c7ca462ac38dc624338f4b0b23eb23ecba00f"}, + {file = "matplotlib-3.10.9-cp311-cp311-win_arm64.whl", hash = "sha256:51bf0ddbdc598e060d46c16b5590708f81a1624cefbaaf62f6a81bf9285b8c80"}, + {file = "matplotlib-3.10.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f0c3c28d9fbcc1fe7a03be236d73430cf6409c41fb2383a7ac52fe932b072cb1"}, + {file = "matplotlib-3.10.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cb28c2bd769aa3e98322c6ab09854cbcc52ab69d2759d681bba3e327b2b320"}, + {file = "matplotlib-3.10.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ae20801130378b82d647ff5047c07316295b68dc054ca6b3c13519d0ea624285"}, + {file = "matplotlib-3.10.9-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c63ebcd8b4b169eb2f5c200552ae6b8be8999a005b6b507ed76fb8d7d674fe2"}, + {file = "matplotlib-3.10.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d75d11c949914165976c621b2324f9ef162af7ebf4b057ddf95dd1dba7e5edcf"}, + {file = "matplotlib-3.10.9-cp312-cp312-win_amd64.whl", hash = "sha256:d091f9d758b34aaaaa6331d13574bf01891d903b3dec59bfff458ef7551de5d6"}, + {file = "matplotlib-3.10.9-cp312-cp312-win_arm64.whl", hash = "sha256:10cc5ce06d10231c36f40e875f3c7e8050362a4ee8f0ee5d29a6b3277d57bb42"}, + {file = "matplotlib-3.10.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b580440f1ff81a0e34122051a3dfabb7e4b7f9e380629929bde0eff9af72165f"}, + {file = "matplotlib-3.10.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b1b745c489cd1a77a0dc1120a05dc87af9798faebc913601feb8c73d89bf2d1e"}, + {file = "matplotlib-3.10.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8f3bcac1ca5ed000a6f4337d47ba67dfddf37ed6a46c15fd7f014997f7bf865f"}, + {file = "matplotlib-3.10.9-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a8d66a55def891c33147ba3ba9bfcabf0b526a43764c818acbb4525e5ed0838"}, + {file = "matplotlib-3.10.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d843374407c4017a6403b59c6c81606773d136f3259d5b6da3131bc814542cc2"}, + {file = "matplotlib-3.10.9-cp313-cp313-win_amd64.whl", hash = "sha256:f4399f64b3e94cd500195490972ae1ee81170df1636fa15364d157d5bdd7b921"}, + {file = "matplotlib-3.10.9-cp313-cp313-win_arm64.whl", hash = "sha256:ba7b3b8ef09eab7df0e86e9ae086faa433efbfbdb46afcb3aa16aabf779469a8"}, + {file = "matplotlib-3.10.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:09218df8a93712bd6ea133e83a153c755448cf7868316c531cffcc43f69d1cc9"}, + {file = "matplotlib-3.10.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:82368699727bfb7b0182e1aa13082e3c08e092fa1a25d3e1fd92405bff96f6d4"}, + {file = "matplotlib-3.10.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3225f4e1edcb8c86c884ddf79ebe20ecd0a67d30188f279897554ccd8fded4dc"}, + {file = "matplotlib-3.10.9-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de2445a0c6690d21b7eb6ce071cebad6d40a2e9bdf10d039074a96ba19797b99"}, + {file = "matplotlib-3.10.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b2b9516251cb89ff618d757daec0e2ed1bf21248013844a853d87ef85ab3081d"}, + {file = "matplotlib-3.10.9-cp313-cp313t-win_amd64.whl", hash = "sha256:e9fae004b941b23ff2edcf1567a857ed77bafc8086ffa258190462328434faf8"}, + {file = "matplotlib-3.10.9-cp313-cp313t-win_arm64.whl", hash = "sha256:6b63d9c7c769b88ab81e10dc86e4e0607cf56817b9f9e6cf24b2a5f1693b8e38"}, + {file = "matplotlib-3.10.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:172db52c9e683f5d12eaf57f0f54834190e12581fe1cc2a19595a8f5acb4e77d"}, + {file = "matplotlib-3.10.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97e35e8d39ccc85859095e01a53847432ba9a53ddf7986f7a54a11b73d0e143f"}, + {file = "matplotlib-3.10.9-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aba1615dabe83188e19d4f75a253c6a08423e04c1425e64039f800050a69de6b"}, + {file = "matplotlib-3.10.9-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34cf8167e023ad956c15f36302911d5406bd99a9862c1a8499ea6f7c0e015dc2"}, + {file = "matplotlib-3.10.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:59476c6d29d612b8e9bb6ce8c5b631be6ba8f9e3a2421f22a02b192c7dd28716"}, + {file = "matplotlib-3.10.9-cp314-cp314-win_amd64.whl", hash = "sha256:336b9acc64d309063126edcdaca00db9373af3c476bb94388fe9c5a53ad13e6f"}, + {file = "matplotlib-3.10.9-cp314-cp314-win_arm64.whl", hash = "sha256:2dc9477819ffd78ad12a20df1d9d6a6bd4fec6aaa9072681465fddca052f1456"}, + {file = "matplotlib-3.10.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:da4e09638420548f31c354032a6250e473c68e5a4e96899b4844cf39ddea23fe"}, + {file = "matplotlib-3.10.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:345f6f68ecc8da0ca56fad2ea08fde1a115eda530079eca185d50a7bc3e146c6"}, + {file = "matplotlib-3.10.9-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4edcfbd8565339aa62f1cd4012f7180926fdbe71850f7b0d3c379c175cd6b66c"}, + {file = "matplotlib-3.10.9-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6be157fe17fc37cb95ac1d7374cf717ce9259616edec911a78d9d26dae8522d4"}, + {file = "matplotlib-3.10.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4e42042d54db34fda4e95a7bd3e5789c2a995d2dad3eb8850232ee534092fbbf"}, + {file = "matplotlib-3.10.9-cp314-cp314t-win_amd64.whl", hash = "sha256:c27df8b3848f32a83d1767566595e43cfaa4460380974da06f4279a7ec143c39"}, + {file = "matplotlib-3.10.9-cp314-cp314t-win_arm64.whl", hash = "sha256:a49f1eadc84ca85fd72fa4e89e70e61bf86452df6f971af04b12c60761a0772c"}, + {file = "matplotlib-3.10.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1872fb212a05b729e649754a72d5da61d03e0554d76e80303b6f83d1d2c0552b"}, + {file = "matplotlib-3.10.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:985f2238880e2e69093f588f5fe2e46771747febf0649f3cf7f7b7480875317f"}, + {file = "matplotlib-3.10.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6640f75af2c6148293caa0a2b39dd806a492dd66c8a8b04035813e33d0fd2585"}, + {file = "matplotlib-3.10.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:42fb814efabe95c06c1994d8ab5a8385f43a249e23badd3ba931d4308e5bca20"}, + {file = "matplotlib-3.10.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f76e640a5268850bfda54b5131b1b1941cc685e42c5fa98ed9f2d64038308cba"}, + {file = "matplotlib-3.10.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3fc0364dfbe1d07f6d15c5ebd0c5bf89e126916e5a8667dd4a7a6e84c36653d4"}, + {file = "matplotlib-3.10.9.tar.gz", hash = "sha256:fd66508e8c6877d98e586654b608a0456db8d7e8a546eb1e2600efd957302358"}, +] + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +kiwisolver = ">=1.3.1" +numpy = ">=1.23" +packaging = ">=20.0" +pillow = ">=8" +pyparsing = ">=3" +python-dateutil = ">=2.7" + +[package.extras] +dev = ["meson-python (>=0.13.1,<0.17.0)", "pybind11 (>=2.13.2,!=2.13.3)", "setuptools (>=64)", "setuptools_scm (>=7,<10)"] + +[package.source] +type = "legacy" +url = "https://pypi.org/simple" +reference = "pypi-public" + [[package]] name = "mdurl" version = "0.1.2" @@ -1643,6 +2473,138 @@ type = "legacy" url = "https://pypi.org/simple" reference = "pypi-public" +[[package]] +name = "numpy" +version = "2.0.2" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.9\"" +files = [ + {file = "numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece"}, + {file = "numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04"}, + {file = "numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66"}, + {file = "numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b"}, + {file = "numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd"}, + {file = "numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318"}, + {file = "numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8"}, + {file = "numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326"}, + {file = "numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97"}, + {file = "numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131"}, + {file = "numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448"}, + {file = "numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195"}, + {file = "numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57"}, + {file = "numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a"}, + {file = "numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669"}, + {file = "numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951"}, + {file = "numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9"}, + {file = "numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15"}, + {file = "numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4"}, + {file = "numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc"}, + {file = "numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b"}, + {file = "numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e"}, + {file = "numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c"}, + {file = "numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c"}, + {file = "numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692"}, + {file = "numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a"}, + {file = "numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c"}, + {file = "numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded"}, + {file = "numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5"}, + {file = "numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a"}, + {file = "numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c"}, + {file = "numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd"}, + {file = "numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b"}, + {file = "numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729"}, + {file = "numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1"}, + {file = "numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd"}, + {file = "numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d"}, + {file = "numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d"}, + {file = "numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa"}, + {file = "numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73"}, + {file = "numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8"}, + {file = "numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4"}, + {file = "numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c"}, + {file = "numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385"}, + {file = "numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.org/simple" +reference = "pypi-public" + +[[package]] +name = "numpy" +version = "2.2.6" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\"" +files = [ + {file = "numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb"}, + {file = "numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90"}, + {file = "numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163"}, + {file = "numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf"}, + {file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83"}, + {file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915"}, + {file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680"}, + {file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289"}, + {file = "numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d"}, + {file = "numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3"}, + {file = "numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae"}, + {file = "numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a"}, + {file = "numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42"}, + {file = "numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491"}, + {file = "numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a"}, + {file = "numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf"}, + {file = "numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1"}, + {file = "numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab"}, + {file = "numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47"}, + {file = "numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303"}, + {file = "numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff"}, + {file = "numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c"}, + {file = "numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3"}, + {file = "numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282"}, + {file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87"}, + {file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249"}, + {file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49"}, + {file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de"}, + {file = "numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4"}, + {file = "numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2"}, + {file = "numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84"}, + {file = "numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b"}, + {file = "numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d"}, + {file = "numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566"}, + {file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f"}, + {file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f"}, + {file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868"}, + {file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d"}, + {file = "numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd"}, + {file = "numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c"}, + {file = "numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6"}, + {file = "numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda"}, + {file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40"}, + {file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8"}, + {file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f"}, + {file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa"}, + {file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571"}, + {file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1"}, + {file = "numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff"}, + {file = "numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06"}, + {file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d"}, + {file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db"}, + {file = "numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543"}, + {file = "numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00"}, + {file = "numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.org/simple" +reference = "pypi-public" + [[package]] name = "openstep-parser" version = "2.0.1" @@ -1666,7 +2628,7 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["dev", "test"] +groups = ["main", "dev", "test"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, @@ -1764,7 +2726,7 @@ version = "11.3.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.9" -groups = ["ios-dev", "test"] +groups = ["main", "ios-dev", "test"] files = [ {file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"}, {file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"}, @@ -2104,6 +3066,26 @@ type = "legacy" url = "https://pypi.org/simple" reference = "pypi-public" +[[package]] +name = "pyparsing" +version = "3.3.2" +description = "pyparsing - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d"}, + {file = "pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[package.source] +type = "legacy" +url = "https://pypi.org/simple" +reference = "pypi-public" + [[package]] name = "pypiwin32" version = "223" @@ -2224,7 +3206,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["ios-dev"] +groups = ["main", "ios-dev"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -2585,7 +3567,7 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -groups = ["ios-dev"] +groups = ["main", "ios-dev"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -2784,7 +3766,7 @@ version = "3.23.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["main", "dev"] markers = "python_version == \"3.9\"" files = [ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, @@ -2807,4 +3789,4 @@ reference = "pypi-public" [metadata] lock-version = "2.1" python-versions = "<3.14,>=3.9" -content-hash = "4b64174a2ab7c13dce7dbe199adfeb73909b032f4e8aee40f7d7619244d83edd" +content-hash = "e3ecc11f8a0a979ce390dd9e40cd1969a1c6f6df062a1e1c0ce50470bddcbb6c" diff --git a/pyproject.toml b/pyproject.toml index 2fa89a6d..783ba18e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ certifi = ">=2017.4.17" pyserial = "^3.5" pyquicklz = "^1.4.1" hid = "^1.0.7" +kivy-matplotlib-widget = "^0.16.0" [tool.poetry.group.dev.dependencies] pyinstaller = "^6.11.0"