PySide6Plot 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. pyside6plot-0.0.1/.gitignore +164 -0
  2. pyside6plot-0.0.1/PKG-INFO +37 -0
  3. pyside6plot-0.0.1/PySide6Plot/__init__.py +187 -0
  4. pyside6plot-0.0.1/PySide6Plot/compoents/__init__.py +3 -0
  5. pyside6plot-0.0.1/PySide6Plot/compoents/average_line.py +232 -0
  6. pyside6plot-0.0.1/PySide6Plot/compoents/draw_line.py +624 -0
  7. pyside6plot-0.0.1/PySide6Plot/compoents/frame_recorder.py +213 -0
  8. pyside6plot-0.0.1/PySide6Plot/compoents/zoom_move.py +160 -0
  9. pyside6plot-0.0.1/PySide6Plot/libs/__init__.py +3 -0
  10. pyside6plot-0.0.1/PySide6Plot/libs/constant.py +19 -0
  11. pyside6plot-0.0.1/PySide6Plot/libs/data_handler.py +230 -0
  12. pyside6plot-0.0.1/PySide6Plot/libs/helpers.py +401 -0
  13. pyside6plot-0.0.1/PySide6Plot/libs/plot_item.py +233 -0
  14. pyside6plot-0.0.1/PySide6Plot/libs/style.py +119 -0
  15. pyside6plot-0.0.1/PySide6Plot/resources/icons/ChevronLeft_black.svg +44 -0
  16. pyside6plot-0.0.1/PySide6Plot/resources/icons/ChevronLeft_white.svg +44 -0
  17. pyside6plot-0.0.1/PySide6Plot/resources/qss/dark/navigation_view_interface.qss +16 -0
  18. pyside6plot-0.0.1/PySide6Plot/resources/qss/light/navigation_view_interface.qss +16 -0
  19. pyside6plot-0.0.1/PySide6Plot/widgets/__init__.py +3 -0
  20. pyside6plot-0.0.1/PySide6Plot/widgets/colorful_toggle_button.py +131 -0
  21. pyside6plot-0.0.1/PySide6Plot/widgets/fluent_scroller.py +450 -0
  22. pyside6plot-0.0.1/PySide6Plot/widgets/line_card.py +138 -0
  23. pyside6plot-0.0.1/PySide6Plot/widgets/navigation_widget.py +49 -0
  24. pyside6plot-0.0.1/PySide6Plot/widgets/q_plot_widget.py +750 -0
  25. pyside6plot-0.0.1/PySide6Plot/widgets/removable_table.py +259 -0
  26. pyside6plot-0.0.1/PySide6Plot/widgets/transparent_Line_edit.py +69 -0
  27. pyside6plot-0.0.1/PySide6Plot/widgets/transparent_selector.py +208 -0
  28. pyside6plot-0.0.1/PySide6Plot/widgets/value_select_box.py +369 -0
  29. pyside6plot-0.0.1/PySide6Plot/widgets/zoom_bar.py +136 -0
  30. pyside6plot-0.0.1/PySide6Plot.egg-info/PKG-INFO +37 -0
  31. pyside6plot-0.0.1/PySide6Plot.egg-info/SOURCES.txt +38 -0
  32. pyside6plot-0.0.1/PySide6Plot.egg-info/dependency_links.txt +1 -0
  33. pyside6plot-0.0.1/PySide6Plot.egg-info/requires.txt +17 -0
  34. pyside6plot-0.0.1/PySide6Plot.egg-info/top_level.txt +1 -0
  35. pyside6plot-0.0.1/README.md +7 -0
  36. pyside6plot-0.0.1/examples/price_volume_plotter.py +16 -0
  37. pyside6plot-0.0.1/examples/sample_stock_data.h5 +0 -0
  38. pyside6plot-0.0.1/examples/single_plotter.py +19 -0
  39. pyside6plot-0.0.1/pyproject.toml +63 -0
  40. pyside6plot-0.0.1/setup.cfg +4 -0
@@ -0,0 +1,164 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # poetry
98
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102
+ #poetry.lock
103
+
104
+ # pdm
105
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106
+ #pdm.lock
107
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108
+ # in version control.
109
+ # https://pdm.fming.dev/#use-with-ide
110
+ .pdm.toml
111
+
112
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113
+ __pypackages__/
114
+
115
+ # Celery stuff
116
+ celerybeat-schedule
117
+ celerybeat.pid
118
+
119
+ # SageMath parsed files
120
+ *.sage.py
121
+
122
+ # Environments
123
+ .env
124
+ .venv
125
+ env/
126
+ venv/
127
+ ENV/
128
+ env.bak/
129
+ venv.bak/
130
+
131
+ # Spyder project settings
132
+ .spyderproject
133
+ .spyproject
134
+
135
+ # Rope project settings
136
+ .ropeproject
137
+
138
+ # mkdocs documentation
139
+ /site
140
+
141
+ # mypy
142
+ .mypy_cache/
143
+ .dmypy.json
144
+ dmypy.json
145
+
146
+ # Pyre type checker
147
+ .pyre/
148
+
149
+ # pytype static type analyzer
150
+ .pytype/
151
+
152
+ # Cython debug symbols
153
+ cython_debug/
154
+
155
+ # PyCharm
156
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
159
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
160
+ .idea/
161
+ /pic_edit
162
+ /build
163
+ /dist
164
+ /qstockplotter.egg-info
@@ -0,0 +1,37 @@
1
+ Metadata-Version: 2.4
2
+ Name: PySide6Plot
3
+ Version: 0.0.1
4
+ Summary: Scientific/Financial plot utilities with PySide6+pyqtgraph.
5
+ Author-email: YQ Cui <qianyun210603@hotmail.com>
6
+ License-Expression: MIT
7
+ Keywords: visualization,plotting,pyside6,pyqtgraph,financial,scientific
8
+ Classifier: Development Status :: 2 - Pre-Alpha
9
+ Classifier: Operating System :: POSIX :: Linux
10
+ Classifier: Operating System :: Microsoft :: Windows
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Topic :: Scientific/Engineering :: Visualization
15
+ Classifier: Programming Language :: Python :: Implementation :: CPython
16
+ Requires-Python: >=3.11
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: numpy
19
+ Requires-Dist: pandas
20
+ Requires-Dist: pyaml>=23.10.0
21
+ Requires-Dist: PySide6!=6.9.1,>=6.9.0; python_version >= "3.12"
22
+ Requires-Dist: PySide6>=6.9.0; python_version <= "3.12"
23
+ Requires-Dist: pyqtgraph>=0.13.3
24
+ Requires-Dist: PySide6-Fluent-Widgets[full]>=1.5.1
25
+ Provides-Extra: dev
26
+ Requires-Dist: ruff>=0.0.263; extra == "dev"
27
+ Requires-Dist: setuptools_scm>=8; extra == "dev"
28
+ Requires-Dist: setuptools>=64; extra == "dev"
29
+ Requires-Dist: wheel; extra == "dev"
30
+
31
+ <h1 align="center">
32
+ <br>PySide6Plot<br>
33
+ </h1>
34
+
35
+ -----
36
+
37
+ A PySide6-based data visualization tool.
@@ -0,0 +1,187 @@
1
+ from PySide6.QtGui import QResizeEvent
2
+ from PySide6.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout
3
+ from PySide6.QtWidgets import QGridLayout, QSizePolicy
4
+ from qfluentwidgets import TransparentToggleToolButton, FluentIcon
5
+ from .widgets.q_plot_widget import QPlotWidget
6
+ from .widgets.navigation_widget import SegmentedInterface
7
+ from .compoents.zoom_move import (
8
+ StockWidgetZoomBar,
9
+ StockWidgetHorizontalScroller,
10
+ StockWidgetVerticalScroller,
11
+ )
12
+ from .compoents.draw_line import DrawLineComponent
13
+ from .compoents.frame_recorder import FrameRecorderComponent
14
+ from .compoents.average_line import AverageLineComponent
15
+ from .libs.plot_item import get_plot_item, AdaptiveGraphObject
16
+ from .libs.style import QStockIcon, set_background_with_theme, qconfig
17
+ from .libs.data_handler import PricesDataFrame, VolumeDataFrame, TradeData
18
+
19
+
20
+ class QStockPlotter(QWidget):
21
+ def __init__(self, show_zoom_bar=True, parent=None) -> None:
22
+ super().__init__(parent)
23
+
24
+ self.main_item: AdaptiveGraphObject | None = None
25
+
26
+ self.setMinimumSize(450, 200)
27
+ self.main_plotter = QPlotWidget(self)
28
+
29
+ self.y_scroller = StockWidgetVerticalScroller(self.main_plotter, parent=self)
30
+ self.x_scroller = StockWidgetHorizontalScroller(self.main_plotter, parent=self)
31
+ if show_zoom_bar:
32
+ self.zoom_bar = StockWidgetZoomBar(self.main_plotter, parent=self)
33
+
34
+ self.draw_line_component = DrawLineComponent(self.main_plotter, parent=self)
35
+ self.draw_line_command_bar, self.draw_line_table = self.draw_line_component.get_widget()
36
+ self.draw_line_table.show()
37
+
38
+ self.frame_recorder_component = FrameRecorderComponent(self.main_plotter, parent=self)
39
+ self.saved_frame_table = self.frame_recorder_component.get_widget()
40
+ self.saved_frame_table.show()
41
+
42
+ self.average_line_component = AverageLineComponent(self.main_plotter, parent=self)
43
+ self.average_line_command_bar = self.average_line_component.get_widget()
44
+
45
+ # self.navigation_widget = PivotInterface(parent=self)
46
+ self.navigation_widget = SegmentedInterface(parent=self)
47
+ self.navigation_widget.addSubInterface(self.draw_line_table, "saved_line_table", "Lines")
48
+ self.navigation_widget.addSubInterface(self.saved_frame_table, "saved_frame_table", "Frames")
49
+ self.navigation_widget.hide()
50
+ self.show_up_button = TransparentToggleToolButton(QStockIcon.CHEVRON_LEFT)
51
+ self.show_up_button.setFixedWidth(10)
52
+ self.show_up_button.clicked.connect(self.__on_show_up_button_clicked)
53
+
54
+ self.main_plotter.insert_context_menu(self.frame_recorder_component.jump_to_menu)
55
+ self.main_plotter.insert_context_menu(self.frame_recorder_component.record_current_frame_action)
56
+ self.main_plotter.insert_context_menu(self.draw_line_component.menu_action)
57
+
58
+ self.main_layout = QHBoxLayout(self)
59
+ self.main_layout.setContentsMargins(0, 0, 0, 0)
60
+ self.main_layout.setSpacing(0)
61
+ self.setLayout(self.main_layout)
62
+ self.inner_layout = QVBoxLayout()
63
+ self.inner_layout.setContentsMargins(0, 0, 0, 0)
64
+ self.inner_layout.setSpacing(0)
65
+ self.plotter_grid_layout = QGridLayout()
66
+ self.plotter_grid_layout.setContentsMargins(0, 0, 0, 0)
67
+ self.plotter_grid_layout.setSpacing(0)
68
+
69
+ self.main_layout.addLayout(self.inner_layout)
70
+ self.main_layout.addWidget(self.show_up_button)
71
+ self.main_layout.addWidget(self.navigation_widget)
72
+ self.inner_layout.addWidget(self.draw_line_command_bar)
73
+ self.inner_layout.addLayout(self.plotter_grid_layout)
74
+ self.inner_layout.addWidget(self.average_line_command_bar)
75
+ self.plotter_grid_layout.addWidget(self.main_plotter, 0, 0, 1, 1)
76
+ self.plotter_grid_layout.addWidget(self.y_scroller, 0, 1, 1, 1)
77
+ self.plotter_grid_layout.addWidget(self.x_scroller, 1, 0, 1, 2)
78
+
79
+ for m in [
80
+ "update_plot",
81
+ "move_y_loc",
82
+ "set_zoom_model",
83
+ "set_y_loc_model",
84
+ "set_full_range_enabled",
85
+ "set_x_range",
86
+ "move_to_end",
87
+ "move_to_start",
88
+ "full_range",
89
+ ]:
90
+ setattr(self, m, getattr(self.main_plotter, m))
91
+
92
+ set_background_with_theme(self)
93
+
94
+ qconfig.themeChanged.connect(lambda theme: set_background_with_theme(self, theme))
95
+
96
+ def add_main_item(self, plot_item, x_ticks=None, y_ticks=None):
97
+ if self.main_item is not None:
98
+ raise Exception("Main item already exists. There can only be one main item.")
99
+ self.main_item = plot_item
100
+ self.main_plotter.add_item(plot_item, x_ticks, y_ticks)
101
+ # self.average_line_component.add_default_average_lines()
102
+
103
+ def remove_main_item(self):
104
+ if self.main_item is None:
105
+ raise Exception("No main item exists.")
106
+ self.main_plotter.remove_item(self.main_item)
107
+ self.main_item = None
108
+
109
+ def __on_show_up_button_clicked(self):
110
+ if self.navigation_widget.isHidden():
111
+ self.navigation_widget.show()
112
+ self.show_up_button.setIcon(FluentIcon.CHEVRON_RIGHT)
113
+ else:
114
+ self.navigation_widget.hide()
115
+ self.show_up_button.setIcon(QStockIcon.CHEVRON_LEFT)
116
+
117
+ def resizeEvent(self, a0: QResizeEvent) -> None:
118
+ self.navigation_widget.setFixedWidth(min(int(self.width() * 0.3), 350))
119
+ return super().resizeEvent(a0)
120
+
121
+ def update_plot(self, x_loc: float | None = None, x_range: float | None = None):
122
+ self.main_plotter.update_plot(x_loc, x_range)
123
+
124
+
125
+ class PriceVolumePlotter(QWidget):
126
+ def __init__(self, parent=None):
127
+ super().__init__(parent)
128
+ self.main_layout = QVBoxLayout(self)
129
+ self.main_layout.setContentsMargins(8, 8, 8, 8)
130
+ self.main_layout.setSpacing(8)
131
+ self.setLayout(self.main_layout)
132
+
133
+ self.price_plotter = QStockPlotter(show_zoom_bar=True)
134
+ self.volume_plotter = QStockPlotter(show_zoom_bar=True)
135
+
136
+ self.price_plotter.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
137
+ self.volume_plotter.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
138
+ self.main_layout.addWidget(self.price_plotter, stretch=3)
139
+ self.main_layout.addWidget(self.volume_plotter, stretch=1)
140
+
141
+ self.price_plotter.main_plotter.sigViewChanged.connect(
142
+ lambda: self.__on_view_changed(self.price_plotter.main_plotter)
143
+ )
144
+ self.volume_plotter.main_plotter.sigViewChanged.connect(
145
+ lambda: self.__on_view_changed(self.volume_plotter.main_plotter)
146
+ )
147
+
148
+ set_background_with_theme(self)
149
+
150
+ qconfig.themeChanged.connect(lambda theme: set_background_with_theme(self, theme))
151
+
152
+ def __on_view_changed(self, active_plot_widget):
153
+ if active_plot_widget == self.price_plotter.main_plotter:
154
+ self.volume_plotter.update_plot(
155
+ x_loc=active_plot_widget.viewRect().left(),
156
+ x_range=active_plot_widget.viewRect().width(),
157
+ )
158
+ else:
159
+ self.price_plotter.update_plot(
160
+ x_loc=active_plot_widget.viewRect().left(),
161
+ x_range=active_plot_widget.viewRect().width(),
162
+ )
163
+
164
+ def plot_price_volume(self, price_data: PricesDataFrame, volume_data: VolumeDataFrame):
165
+ price_item = get_plot_item(price_data)
166
+ self.price_plotter.add_main_item(price_item, x_ticks=price_item.get_x_ticks())
167
+ volume_item = get_plot_item(volume_data)
168
+ self.volume_plotter.add_main_item(volume_item, x_ticks=volume_item.get_x_ticks())
169
+
170
+ def plot_trade_data(self, trade_data: TradeData):
171
+ self.plot_price_volume(trade_data.prices, trade_data.volume)
172
+
173
+ def update_plot(self, x_loc: float | None = None, x_range: float | None = None):
174
+ self.price_plotter.update_plot(x_loc, x_range)
175
+
176
+ def set_x_range(self, x_loc: float | None = None, x_range: float | None = None):
177
+ self.price_plotter.set_x_range(x_loc, x_range)
178
+ self.volume_plotter.set_x_range(x_loc, x_range)
179
+
180
+ def move_to_end(self):
181
+ self.price_plotter.move_to_end()
182
+
183
+ def move_to_start(self):
184
+ self.price_plotter.move_to_start()
185
+
186
+ def full_range(self):
187
+ self.price_plotter.full_range()
@@ -0,0 +1,3 @@
1
+ """
2
+ Functional components for plotters.
3
+ """
@@ -0,0 +1,232 @@
1
+ import numpy as np
2
+ from PySide6.QtGui import QContextMenuEvent, QColor, QIcon
3
+ from PySide6.QtCore import Qt, Signal
4
+ from PySide6.QtWidgets import QWidget
5
+ from qfluentwidgets import CommandBar, FluentIcon, Action, FluentIconBase, qconfig, RoundMenu
6
+ from qfluentwidgets.common.overload import singledispatchmethod
7
+ import pyqtgraph as pg
8
+ from pyqtgraph import PlotCurveItem
9
+ from ..widgets.q_plot_widget import QPlotWidget
10
+ from ..widgets.colorful_toggle_button import ColorfulToggleButton
11
+ from ..widgets.value_select_box import NewAverageLineBox
12
+
13
+
14
+ class AverageLineItem(PlotCurveItem):
15
+ """
16
+ A class representing an average line item on a plot.
17
+
18
+ Attributes:
19
+ num_average_data (int): The number of data points to use for calculating the average.
20
+ """
21
+
22
+ def __init__(self, data, num_average_data: int, color: QColor, line_width: float):
23
+ """
24
+ Initializes an AverageLineItem object.
25
+
26
+ Args:
27
+ data (array-like): The data points for which the average line is calculated.
28
+ num_average_data (int): The number of data points to use for calculating the average.
29
+ color (str): The color of the average line.
30
+ line_width (float): The width of the average line.
31
+ """
32
+ super().__init__(
33
+ pen=pg.mkPen(color, width=line_width),
34
+ clickable=False,
35
+ x=np.arange(num_average_data - 1, len(data)),
36
+ y=np.asarray([np.mean(data[i : i + num_average_data]) for i in range(len(data) - num_average_data + 1)]),
37
+ )
38
+ self.num_average_data = num_average_data
39
+
40
+ def get_local_plot_range(self, start: float, end: float):
41
+ """
42
+ Returns the minimum and maximum values of the average line within the specified range.
43
+
44
+ Args:
45
+ start (float): The start value of the range.
46
+ end (float): The end value of the range.
47
+
48
+ Returns:
49
+ tuple: A tuple containing the minimum and maximum values of the average line within the range.
50
+ If there are no data points within the range, returns None.
51
+ """
52
+ _, ys = self.getData()
53
+ ys = ys[max(0, int(start - self.num_average_data)) : max(0, int(end - self.num_average_data))]
54
+ if len(ys) > 0:
55
+ return np.min(ys), np.max(ys)
56
+ else:
57
+ return None
58
+
59
+
60
+ class AverageLineButton(ColorfulToggleButton):
61
+ """
62
+ A button widget that represents an average line.
63
+
64
+ This button provides functionality for removing the average line and displays a context menu when right-clicked.
65
+
66
+ Signals:
67
+ - sigRemoveClicked: Emitted when the remove action is triggered.
68
+
69
+ Args:
70
+ parent (QWidget): The parent widget.
71
+
72
+ Overloaded Methods:
73
+ - __init__(self, parent: QWidget = None)
74
+ - __init__(self, color: QColor, text: str, parent: QWidget = None, icon: Union[QIcon, FluentIconBase, str] = None)
75
+ - __init__(self, icon: QIcon, color: QColor, text: str, parent: QWidget = None)
76
+ - __init__(self, icon: FluentIconBase, color: QColor, text: str, parent: QWidget = None)
77
+ """
78
+
79
+ sigRemoveClicked = Signal()
80
+
81
+ @singledispatchmethod
82
+ def __init__(self, parent: QWidget = None):
83
+ super().__init__(parent)
84
+ self.color = None
85
+ qconfig.themeChangedFinished.connect(self.set_color)
86
+ self.context_menu = RoundMenu("Context Menu")
87
+ self.remove_action = Action("Remove")
88
+ self.remove_action.setIcon(FluentIcon.DELETE)
89
+ self.remove_action.triggered.connect(self.sigRemoveClicked.emit)
90
+ self.context_menu.addAction(self.remove_action)
91
+
92
+ @__init__.register
93
+ def _(self, color: QColor, text: str, parent: QWidget = None, icon: QIcon | FluentIconBase | str = None):
94
+ self.__init__(parent)
95
+ self.setText(text)
96
+ self.setIcon(icon)
97
+ self.color = color
98
+ self.set_color()
99
+
100
+ @__init__.register
101
+ def _(self, icon: QIcon, color: QColor, text: str, parent: QWidget = None):
102
+ self.__init__(color, text, parent, icon)
103
+
104
+ @__init__.register
105
+ def _(self, icon: FluentIconBase, color: QColor, text: str, parent: QWidget = None):
106
+ self.__init__(color, text, parent, icon)
107
+
108
+ def contextMenuEvent(self, a0: QContextMenuEvent) -> None:
109
+ self.context_menu.exec(a0.globalPos())
110
+
111
+
112
+ class AverageLineComponent:
113
+ """
114
+ A component that handles the addition and removal of average lines on a plot.
115
+ """
116
+
117
+ def __init__(self, plot_widget: QPlotWidget, parent=None) -> None:
118
+ """
119
+ Initializes the AverageLineComponent object.
120
+
121
+ Parameters:
122
+ - plot_widget (QPlotWidget): The plot widget where the average lines are added.
123
+ - parent: The parent object of the component.
124
+ """
125
+ self.parent = parent
126
+ self.plot_widget = plot_widget
127
+ self.plot_items_bar = CommandBar(parent=self.parent)
128
+ self.plot_items_bar.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
129
+ self.plot_items_bar.setFixedHeight(25)
130
+ self.plot_items_bar.addSeparator()
131
+ self.add_line_action = Action("Add average line")
132
+ self.add_line_action.setIcon(FluentIcon.ADD)
133
+ self.add_line_action.triggered.connect(self.__on_add_line_action_clicked)
134
+ self.plot_items_bar.addAction(self.add_line_action)
135
+ self.show_all_line_action = Action("Show all average lines")
136
+ self.show_all_line_action.setIcon(FluentIcon.VIEW)
137
+ self.show_all_line_action.triggered.connect(self.show_all_average_lines)
138
+ self.plot_items_bar.addAction(self.show_all_line_action)
139
+ self.hide_all_line_action = Action("Hide all average lines")
140
+ self.hide_all_line_action.setIcon(FluentIcon.HIDE)
141
+ self.hide_all_line_action.triggered.connect(self.hide_all_average_lines)
142
+ self.plot_items_bar.addAction(self.hide_all_line_action)
143
+
144
+ self.average_lines = {}
145
+
146
+ def __on_add_line_action_clicked(self):
147
+ """
148
+ Handles the event when the add line action is clicked.
149
+ Shows a dialog box for selecting the number of data points and color for the average line.
150
+ Adds the average line to the plot.
151
+ """
152
+ msg = NewAverageLineBox(
153
+ num_data=len(self.parent.main_item.get_feature_value()),
154
+ existed_values=self.average_lines.keys(),
155
+ parent=self.parent,
156
+ )
157
+ if msg.exec():
158
+ self.add_average_line(int(msg.value_edit.text()), msg.color_selector.current_item)
159
+
160
+ def get_widget(self):
161
+ """
162
+ Returns the plot_items_bar widget.
163
+
164
+ Returns:
165
+ - QWidget: The plot_items_bar widget.
166
+ """
167
+ return self.plot_items_bar
168
+
169
+ def add_average_line(self, num_average_data, color):
170
+ """
171
+ Adds an average line to the plot.
172
+
173
+ Parameters:
174
+ - num_average_data (int): The number of data points to calculate the average.
175
+ - color: The color of the average line.
176
+ """
177
+ if not hasattr(self.parent, "main_item"):
178
+ raise ValueError("The parent plotter has no main item")
179
+ if self.parent.main_item is None:
180
+ raise ValueError("The main item of the parent plotter is None")
181
+ data = self.parent.main_item.get_feature_value()
182
+ style = self.parent.main_item.style
183
+ average_line = AverageLineItem(data, num_average_data, color, style.line_width)
184
+ self.average_lines[num_average_data] = average_line
185
+ self.plot_widget.add_item(average_line)
186
+ toggle_button = AverageLineButton(color=color, parent=self.plot_items_bar, text=f"MA{num_average_data}")
187
+ toggle_button.setFixedHeight(25)
188
+ toggle_button.setChecked(True)
189
+
190
+ def on_toggle_button_clicked():
191
+ if toggle_button.isChecked():
192
+ self.plot_widget.add_item(average_line)
193
+ self.average_lines[num_average_data] = average_line
194
+ else:
195
+ self.plot_widget.remove_item(average_line)
196
+ self.average_lines.pop(num_average_data)
197
+
198
+ def on_remove_button_clicked():
199
+ self.plot_widget.remove_item(average_line)
200
+ self.average_lines.pop(num_average_data)
201
+ self.plot_items_bar.removeWidget(toggle_button)
202
+ toggle_button.deleteLater()
203
+
204
+ toggle_button.clicked.connect(on_toggle_button_clicked)
205
+ toggle_button.sigRemoveClicked.connect(on_remove_button_clicked)
206
+ self.plot_items_bar._insertWidgetToLayout(len(self.plot_items_bar._widgets) - 4, toggle_button)
207
+
208
+ def add_default_average_lines(self):
209
+ """
210
+ Adds the default average lines based on the style of the main item.
211
+ """
212
+ style = self.parent.main_item.style
213
+ for num_average_data in style.average_line_color.keys():
214
+ self.add_average_line(num_average_data, style.average_line_color[num_average_data])
215
+
216
+ def show_all_average_lines(self):
217
+ """
218
+ Shows all the average lines.
219
+ """
220
+ for item in self.plot_items_bar._widgets:
221
+ if isinstance(item, AverageLineButton):
222
+ if not item.isChecked():
223
+ item.click()
224
+
225
+ def hide_all_average_lines(self):
226
+ """
227
+ Hides all the average lines.
228
+ """
229
+ for item in self.plot_items_bar._widgets:
230
+ if isinstance(item, AverageLineButton):
231
+ if item.isChecked():
232
+ item.click()