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.
- pyside6plot-0.0.1/.gitignore +164 -0
- pyside6plot-0.0.1/PKG-INFO +37 -0
- pyside6plot-0.0.1/PySide6Plot/__init__.py +187 -0
- pyside6plot-0.0.1/PySide6Plot/compoents/__init__.py +3 -0
- pyside6plot-0.0.1/PySide6Plot/compoents/average_line.py +232 -0
- pyside6plot-0.0.1/PySide6Plot/compoents/draw_line.py +624 -0
- pyside6plot-0.0.1/PySide6Plot/compoents/frame_recorder.py +213 -0
- pyside6plot-0.0.1/PySide6Plot/compoents/zoom_move.py +160 -0
- pyside6plot-0.0.1/PySide6Plot/libs/__init__.py +3 -0
- pyside6plot-0.0.1/PySide6Plot/libs/constant.py +19 -0
- pyside6plot-0.0.1/PySide6Plot/libs/data_handler.py +230 -0
- pyside6plot-0.0.1/PySide6Plot/libs/helpers.py +401 -0
- pyside6plot-0.0.1/PySide6Plot/libs/plot_item.py +233 -0
- pyside6plot-0.0.1/PySide6Plot/libs/style.py +119 -0
- pyside6plot-0.0.1/PySide6Plot/resources/icons/ChevronLeft_black.svg +44 -0
- pyside6plot-0.0.1/PySide6Plot/resources/icons/ChevronLeft_white.svg +44 -0
- pyside6plot-0.0.1/PySide6Plot/resources/qss/dark/navigation_view_interface.qss +16 -0
- pyside6plot-0.0.1/PySide6Plot/resources/qss/light/navigation_view_interface.qss +16 -0
- pyside6plot-0.0.1/PySide6Plot/widgets/__init__.py +3 -0
- pyside6plot-0.0.1/PySide6Plot/widgets/colorful_toggle_button.py +131 -0
- pyside6plot-0.0.1/PySide6Plot/widgets/fluent_scroller.py +450 -0
- pyside6plot-0.0.1/PySide6Plot/widgets/line_card.py +138 -0
- pyside6plot-0.0.1/PySide6Plot/widgets/navigation_widget.py +49 -0
- pyside6plot-0.0.1/PySide6Plot/widgets/q_plot_widget.py +750 -0
- pyside6plot-0.0.1/PySide6Plot/widgets/removable_table.py +259 -0
- pyside6plot-0.0.1/PySide6Plot/widgets/transparent_Line_edit.py +69 -0
- pyside6plot-0.0.1/PySide6Plot/widgets/transparent_selector.py +208 -0
- pyside6plot-0.0.1/PySide6Plot/widgets/value_select_box.py +369 -0
- pyside6plot-0.0.1/PySide6Plot/widgets/zoom_bar.py +136 -0
- pyside6plot-0.0.1/PySide6Plot.egg-info/PKG-INFO +37 -0
- pyside6plot-0.0.1/PySide6Plot.egg-info/SOURCES.txt +38 -0
- pyside6plot-0.0.1/PySide6Plot.egg-info/dependency_links.txt +1 -0
- pyside6plot-0.0.1/PySide6Plot.egg-info/requires.txt +17 -0
- pyside6plot-0.0.1/PySide6Plot.egg-info/top_level.txt +1 -0
- pyside6plot-0.0.1/README.md +7 -0
- pyside6plot-0.0.1/examples/price_volume_plotter.py +16 -0
- pyside6plot-0.0.1/examples/sample_stock_data.h5 +0 -0
- pyside6plot-0.0.1/examples/single_plotter.py +19 -0
- pyside6plot-0.0.1/pyproject.toml +63 -0
- 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,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()
|