QT-PyQt-PySide-Custom-Widgets-Pro 1.1.0__py3-none-any.whl
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.
- custom_widgets_pro/__init__.py +73 -0
- custom_widgets_pro/_catalog.py +25 -0
- custom_widgets_pro/_cli.py +132 -0
- custom_widgets_pro/_license.py +419 -0
- custom_widgets_pro/_version.py +14 -0
- custom_widgets_pro/datatable/__init__.py +17 -0
- custom_widgets_pro/datatable/datatable_pro.py +319 -0
- custom_widgets_pro/datatable/export.py +143 -0
- custom_widgets_pro/datatable/frozen_view.py +112 -0
- custom_widgets_pro/datatable/grouping.py +309 -0
- custom_widgets_pro/datatable/pivot.py +100 -0
- custom_widgets_pro/datatable/provider.py +98 -0
- custom_widgets_pro/datatable/virtual_model.py +451 -0
- custom_widgets_pro/widgets/__init__.py +7 -0
- custom_widgets_pro/widgets/charts/QCustomBeeswarm.py +317 -0
- custom_widgets_pro/widgets/charts/QCustomBubbleChart.py +655 -0
- custom_widgets_pro/widgets/charts/QCustomCandlestickChart.py +522 -0
- custom_widgets_pro/widgets/charts/QCustomDivergingBarChart.py +430 -0
- custom_widgets_pro/widgets/charts/QCustomDotMatrix.py +266 -0
- custom_widgets_pro/widgets/charts/QCustomFunnelChart.py +474 -0
- custom_widgets_pro/widgets/charts/QCustomGanttChart.py +376 -0
- custom_widgets_pro/widgets/charts/QCustomHeatmap.py +555 -0
- custom_widgets_pro/widgets/charts/QCustomRadarChart.py +600 -0
- custom_widgets_pro/widgets/charts/QCustomRadialBars.py +441 -0
- custom_widgets_pro/widgets/charts/QCustomRadialLines.py +554 -0
- custom_widgets_pro/widgets/charts/QCustomRangeBarChart.py +504 -0
- custom_widgets_pro/widgets/charts/QCustomSankey.py +539 -0
- custom_widgets_pro/widgets/charts/QCustomScatterChart.py +623 -0
- custom_widgets_pro/widgets/charts/__init__.py +1 -0
- custom_widgets_pro/widgets/data/QCustomCodeEditor.py +391 -0
- custom_widgets_pro/widgets/data/QCustomNodeGraph.py +1030 -0
- custom_widgets_pro/widgets/data/QCustomRichTextEditor.py +189 -0
- custom_widgets_pro/widgets/data/QCustomTableToolbar.py +566 -0
- custom_widgets_pro/widgets/data/__init__.py +1 -0
- custom_widgets_pro/widgets/media/QCustomImageViewer.py +300 -0
- custom_widgets_pro/widgets/media/QCustomMediaGrid.py +197 -0
- custom_widgets_pro/widgets/media/QCustomMediaTimeline.py +607 -0
- custom_widgets_pro/widgets/media/QCustomVideoPlayer.py +326 -0
- custom_widgets_pro/widgets/media/__init__.py +1 -0
- qt_pyqt_pyside_custom_widgets_pro-1.1.0.dist-info/METADATA +112 -0
- qt_pyqt_pyside_custom_widgets_pro-1.1.0.dist-info/RECORD +45 -0
- qt_pyqt_pyside_custom_widgets_pro-1.1.0.dist-info/WHEEL +5 -0
- qt_pyqt_pyside_custom_widgets_pro-1.1.0.dist-info/entry_points.txt +2 -0
- qt_pyqt_pyside_custom_widgets_pro-1.1.0.dist-info/licenses/LICENSE +91 -0
- qt_pyqt_pyside_custom_widgets_pro-1.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
########################################################################
|
|
2
|
+
## SPINN DESIGN CODE
|
|
3
|
+
# YOUTUBE: (SPINN TV) https://www.youtube.com/@SpinnTV
|
|
4
|
+
# WEBSITE: customwidgets.org
|
|
5
|
+
########################################################################
|
|
6
|
+
|
|
7
|
+
########################################################################
|
|
8
|
+
## QCustomCandlestickChart - a painted OHLC / candlestick price chart.
|
|
9
|
+
##
|
|
10
|
+
## Each candle is one period: a filled body spanning open->close and a wick
|
|
11
|
+
## spanning low->high. Candles are coloured by direction - up when close >= open,
|
|
12
|
+
## down otherwise - which is the whole point of the form.
|
|
13
|
+
##
|
|
14
|
+
## Rendered entirely with QPainter and NO QtCharts dependency. That is
|
|
15
|
+
## deliberate and load-bearing: Qt Charts is GPLv3-or-commercial with no LGPL
|
|
16
|
+
## option, so anything built on it cannot ship inside a proprietary wheel. This
|
|
17
|
+
## widget stays clean.
|
|
18
|
+
##
|
|
19
|
+
## Data goes in with setData([(open, high, low, close), ...]) in code, or the
|
|
20
|
+
## ohlcCsv property in Qt Designer ("o,h,l,c;o,h,l,c;..."), following the
|
|
21
|
+
## valuesCsv convention used by the other painted charts.
|
|
22
|
+
##
|
|
23
|
+
## Emits candleHovered(int) and candleClicked(int); -1 means "nothing".
|
|
24
|
+
########################################################################
|
|
25
|
+
from qtpy.QtCore import Qt, Signal, Property, QRectF, QSize, QPointF
|
|
26
|
+
from qtpy.QtGui import QColor, QPainter, QPen, QBrush, QFontMetrics
|
|
27
|
+
from qtpy.QtWidgets import QWidget, QSizePolicy
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class QCustomCandlestickChart(QWidget):
|
|
31
|
+
candleHovered = Signal(int)
|
|
32
|
+
candleClicked = Signal(int)
|
|
33
|
+
|
|
34
|
+
WIDGET_ICON = "components/icons/candlestick_chart.png"
|
|
35
|
+
WIDGET_TOOLTIP = "A painted OHLC / candlestick price chart"
|
|
36
|
+
WIDGET_MODULE = "Custom_Widgets.QCustomCandlestickChart"
|
|
37
|
+
WIDGET_DOM_XML = """
|
|
38
|
+
<ui language='c++'>
|
|
39
|
+
<widget class='QCustomCandlestickChart' name='customCandlestickChart'>
|
|
40
|
+
<property name='geometry'><rect><x>0</x><y>0</y><width>520</width><height>280</height></rect></property>
|
|
41
|
+
<property name='ohlcCsv'><string>26,28,25,27;27,30,26,29;29,29.5,27,27.5;27.5,31,27,30.5;30.5,32,30,31</string></property>
|
|
42
|
+
</widget>
|
|
43
|
+
</ui>
|
|
44
|
+
"""
|
|
45
|
+
__catalog__ = {
|
|
46
|
+
"name": "QCustomCandlestickChart",
|
|
47
|
+
"props": {"ohlcCsv": {"type": "string", "default": ""},
|
|
48
|
+
"labelsCsv": {"type": "string", "default": ""},
|
|
49
|
+
"upColor": {"type": "color", "default": "#16a34a"},
|
|
50
|
+
"downColor": {"type": "color", "default": "#dc2626"},
|
|
51
|
+
"wickColor": {"type": "color", "default": "#64748b"},
|
|
52
|
+
"gridColor": {"type": "color", "default": "#e2e8f0"},
|
|
53
|
+
"axisTextColor": {"type": "color", "default": "#64748b"},
|
|
54
|
+
"showGrid": {"type": "bool", "default": True},
|
|
55
|
+
"showPriceAxis": {"type": "bool", "default": True},
|
|
56
|
+
"showLabels": {"type": "bool", "default": True},
|
|
57
|
+
"showTooltip": {"type": "bool", "default": True},
|
|
58
|
+
"hollowUpCandles": {"type": "bool", "default": False},
|
|
59
|
+
"candleWidthRatio": {"type": "float", "default": 0.62},
|
|
60
|
+
"gridLines": {"type": "int", "default": 4},
|
|
61
|
+
"pricePrecision": {"type": "int", "default": 2}},
|
|
62
|
+
"signals": ["candleHovered", "candleClicked"],
|
|
63
|
+
"tokens_used": ["success", "destructive", "outline", "on-surface"],
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# Rich editors for the Designer "Custom Properties" dock (see
|
|
67
|
+
# DesignerTools.CustomPropertiesDock).
|
|
68
|
+
DESIGNER_CUSTOM_PROPS = [
|
|
69
|
+
{"name": "ohlcCsv", "kind": "str", "group": "General"},
|
|
70
|
+
{"name": "labelsCsv", "kind": "str", "group": "General"},
|
|
71
|
+
{"name": "upColor", "kind": "color", "group": "Colors"},
|
|
72
|
+
{"name": "downColor", "kind": "color", "group": "Colors"},
|
|
73
|
+
{"name": "wickColor", "kind": "color", "group": "Colors"},
|
|
74
|
+
{"name": "gridColor", "kind": "color", "group": "Colors"},
|
|
75
|
+
{"name": "axisTextColor", "kind": "color", "group": "Colors"},
|
|
76
|
+
{"name": "showGrid", "kind": "bool", "group": "General"},
|
|
77
|
+
{"name": "showPriceAxis", "kind": "bool", "group": "General"},
|
|
78
|
+
{"name": "showLabels", "kind": "bool", "group": "General"},
|
|
79
|
+
{"name": "showTooltip", "kind": "bool", "group": "General"},
|
|
80
|
+
{"name": "hollowUpCandles", "kind": "bool", "group": "General"},
|
|
81
|
+
{"name": "candleWidthRatio", "kind": "float", "group": "General"},
|
|
82
|
+
{"name": "gridLines", "kind": "int", "group": "General"},
|
|
83
|
+
{"name": "pricePrecision", "kind": "int", "group": "General"},
|
|
84
|
+
{"name": "tooltipBackgroundColor", "kind": "color", "group": "Colors"},
|
|
85
|
+
{"name": "tooltipTextColor", "kind": "color", "group": "Colors"},
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
_PAD_L_NO_AXIS = 8
|
|
89
|
+
_PAD_R = 8
|
|
90
|
+
_PAD_T = 10
|
|
91
|
+
_PAD_B_NO_LABELS = 8
|
|
92
|
+
|
|
93
|
+
def __init__(self, parent=None, data=None, labels=None):
|
|
94
|
+
super().__init__(parent)
|
|
95
|
+
self.setObjectName("QCustomCandlestickChart")
|
|
96
|
+
self._data = [] # list of (open, high, low, close)
|
|
97
|
+
self._labels = []
|
|
98
|
+
self._rects = [] # per-candle hit rect, parallel to _data
|
|
99
|
+
self._hover = -1
|
|
100
|
+
|
|
101
|
+
self._up = QColor("#16a34a")
|
|
102
|
+
self._down = QColor("#dc2626")
|
|
103
|
+
self._wick = QColor("#64748b")
|
|
104
|
+
self._grid = QColor("#e2e8f0")
|
|
105
|
+
self._axisText = QColor("#64748b")
|
|
106
|
+
self._tooltipBg = QColor("#0f172a")
|
|
107
|
+
self._tooltipText = QColor("#f8fafc")
|
|
108
|
+
|
|
109
|
+
self._showGrid = True
|
|
110
|
+
self._showPriceAxis = True
|
|
111
|
+
self._showLabels = True
|
|
112
|
+
self._showTooltip = True
|
|
113
|
+
self._hollowUp = False
|
|
114
|
+
self._widthRatio = 0.62
|
|
115
|
+
self._gridLines = 4
|
|
116
|
+
self._precision = 2
|
|
117
|
+
|
|
118
|
+
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
|
119
|
+
self.setMouseTracking(True)
|
|
120
|
+
self.setCursor(Qt.CrossCursor)
|
|
121
|
+
if data:
|
|
122
|
+
self.setData(data, labels)
|
|
123
|
+
elif labels:
|
|
124
|
+
self._labels = [str(x) for x in labels]
|
|
125
|
+
|
|
126
|
+
# ------------------------------------------------------------------ #
|
|
127
|
+
## Data
|
|
128
|
+
# ------------------------------------------------------------------ #
|
|
129
|
+
@staticmethod
|
|
130
|
+
def _coerce(candle):
|
|
131
|
+
"""Accept (o,h,l,c), [o,h,l,c] or a dict with those keys."""
|
|
132
|
+
if isinstance(candle, dict):
|
|
133
|
+
vals = (candle.get("open"), candle.get("high"),
|
|
134
|
+
candle.get("low"), candle.get("close"))
|
|
135
|
+
else:
|
|
136
|
+
try:
|
|
137
|
+
vals = tuple(candle)[:4]
|
|
138
|
+
except TypeError: # None, a bare number, any non-iterable
|
|
139
|
+
return None
|
|
140
|
+
if len(vals) != 4 or any(v is None for v in vals):
|
|
141
|
+
return None
|
|
142
|
+
try:
|
|
143
|
+
o, h, l, c = (float(v) for v in vals)
|
|
144
|
+
except (TypeError, ValueError):
|
|
145
|
+
return None
|
|
146
|
+
# Tolerate high/low that do not actually bound the body rather than
|
|
147
|
+
# dropping the candle: a feed that swaps them should still render.
|
|
148
|
+
hi = max(o, h, l, c)
|
|
149
|
+
lo = min(o, h, l, c)
|
|
150
|
+
return (o, hi, lo, c)
|
|
151
|
+
|
|
152
|
+
def setData(self, data, labels=None):
|
|
153
|
+
"""Replace every candle. Each item is (open, high, low, close)."""
|
|
154
|
+
out = []
|
|
155
|
+
for candle in data or []:
|
|
156
|
+
coerced = self._coerce(candle)
|
|
157
|
+
if coerced is not None:
|
|
158
|
+
out.append(coerced)
|
|
159
|
+
self._data = out
|
|
160
|
+
if labels is not None:
|
|
161
|
+
self._labels = [str(x) for x in labels]
|
|
162
|
+
self._hover = -1
|
|
163
|
+
self.updateGeometry()
|
|
164
|
+
self.update()
|
|
165
|
+
|
|
166
|
+
def data(self):
|
|
167
|
+
return list(self._data)
|
|
168
|
+
|
|
169
|
+
def count(self):
|
|
170
|
+
return len(self._data)
|
|
171
|
+
|
|
172
|
+
def setLabels(self, labels):
|
|
173
|
+
self._labels = [str(x) for x in (labels or [])]
|
|
174
|
+
self.update()
|
|
175
|
+
|
|
176
|
+
def labels(self):
|
|
177
|
+
return list(self._labels)
|
|
178
|
+
|
|
179
|
+
def priceRange(self):
|
|
180
|
+
"""(low, high) across every candle, or (0.0, 1.0) when empty."""
|
|
181
|
+
if not self._data:
|
|
182
|
+
return (0.0, 1.0)
|
|
183
|
+
lo = min(c[2] for c in self._data)
|
|
184
|
+
hi = max(c[1] for c in self._data)
|
|
185
|
+
if hi == lo: # flat series: give it room to draw
|
|
186
|
+
return (lo - 0.5, hi + 0.5)
|
|
187
|
+
return (lo, hi)
|
|
188
|
+
|
|
189
|
+
# ------------------------------------------------------------------ #
|
|
190
|
+
## Geometry
|
|
191
|
+
# ------------------------------------------------------------------ #
|
|
192
|
+
def sizeHint(self):
|
|
193
|
+
return QSize(520, 280)
|
|
194
|
+
|
|
195
|
+
def minimumSizeHint(self):
|
|
196
|
+
return QSize(160, 90)
|
|
197
|
+
|
|
198
|
+
def _priceAxisWidth(self):
|
|
199
|
+
if not self._showPriceAxis:
|
|
200
|
+
return self._PAD_L_NO_AXIS
|
|
201
|
+
lo, hi = self.priceRange()
|
|
202
|
+
fm = QFontMetrics(self.font())
|
|
203
|
+
widest = max(fm.horizontalAdvance(self._fmtPrice(v)) for v in (lo, hi))
|
|
204
|
+
return widest + 12
|
|
205
|
+
|
|
206
|
+
def _labelStripHeight(self):
|
|
207
|
+
if not (self._showLabels and self._labels):
|
|
208
|
+
return self._PAD_B_NO_LABELS
|
|
209
|
+
return QFontMetrics(self.font()).height() + 6
|
|
210
|
+
|
|
211
|
+
def _plotRect(self):
|
|
212
|
+
left = self._priceAxisWidth()
|
|
213
|
+
bottom = self._labelStripHeight()
|
|
214
|
+
return QRectF(left, self._PAD_T,
|
|
215
|
+
max(1.0, self.width() - left - self._PAD_R),
|
|
216
|
+
max(1.0, self.height() - self._PAD_T - bottom))
|
|
217
|
+
|
|
218
|
+
def _fmtPrice(self, v):
|
|
219
|
+
return "%.*f" % (self._precision, v)
|
|
220
|
+
|
|
221
|
+
# ------------------------------------------------------------------ #
|
|
222
|
+
## Painting
|
|
223
|
+
# ------------------------------------------------------------------ #
|
|
224
|
+
def paintEvent(self, e):
|
|
225
|
+
p = QPainter(self)
|
|
226
|
+
p.setRenderHint(QPainter.Antialiasing)
|
|
227
|
+
plot = self._plotRect()
|
|
228
|
+
lo, hi = self.priceRange()
|
|
229
|
+
span = (hi - lo) or 1.0
|
|
230
|
+
|
|
231
|
+
def y_for(price):
|
|
232
|
+
return plot.bottom() - (price - lo) / span * plot.height()
|
|
233
|
+
|
|
234
|
+
if self._showGrid or self._showPriceAxis:
|
|
235
|
+
self._paintGrid(p, plot, lo, span, y_for)
|
|
236
|
+
|
|
237
|
+
self._rects = []
|
|
238
|
+
if not self._data:
|
|
239
|
+
return
|
|
240
|
+
|
|
241
|
+
slot = plot.width() / len(self._data)
|
|
242
|
+
body_w = max(1.0, slot * self._widthRatio)
|
|
243
|
+
|
|
244
|
+
for i, (o, h, l, c) in enumerate(self._data):
|
|
245
|
+
cx = plot.left() + slot * (i + 0.5)
|
|
246
|
+
up = c >= o
|
|
247
|
+
colour = self._up if up else self._down
|
|
248
|
+
|
|
249
|
+
# wick first so the body paints over it
|
|
250
|
+
p.setPen(QPen(self._wick, 1))
|
|
251
|
+
p.drawLine(QPointF(cx, y_for(h)), QPointF(cx, y_for(l)))
|
|
252
|
+
|
|
253
|
+
top, bottom = y_for(max(o, c)), y_for(min(o, c))
|
|
254
|
+
# A doji (open == close) has zero height; give it a visible line.
|
|
255
|
+
if bottom - top < 1.0:
|
|
256
|
+
bottom = top + 1.0
|
|
257
|
+
body = QRectF(cx - body_w / 2.0, top, body_w, bottom - top)
|
|
258
|
+
|
|
259
|
+
if i == self._hover:
|
|
260
|
+
p.setPen(Qt.NoPen)
|
|
261
|
+
p.setBrush(QBrush(QColor(colour.red(), colour.green(),
|
|
262
|
+
colour.blue(), 38)))
|
|
263
|
+
p.drawRect(QRectF(cx - slot / 2.0, plot.top(), slot, plot.height()))
|
|
264
|
+
|
|
265
|
+
if self._hollowUp and up:
|
|
266
|
+
p.setPen(QPen(colour, 1.4))
|
|
267
|
+
p.setBrush(Qt.NoBrush)
|
|
268
|
+
else:
|
|
269
|
+
p.setPen(QPen(colour, 1))
|
|
270
|
+
p.setBrush(QBrush(colour))
|
|
271
|
+
p.drawRect(body)
|
|
272
|
+
|
|
273
|
+
self._rects.append(QRectF(cx - slot / 2.0, plot.top(),
|
|
274
|
+
slot, plot.height()))
|
|
275
|
+
|
|
276
|
+
if self._showLabels and self._labels:
|
|
277
|
+
self._paintLabels(p, plot, slot)
|
|
278
|
+
|
|
279
|
+
if self._showTooltip and 0 <= self._hover < len(self._data):
|
|
280
|
+
self._paintTooltip(p, plot, slot)
|
|
281
|
+
|
|
282
|
+
def _paintGrid(self, p, plot, lo, span, y_for):
|
|
283
|
+
lines = max(1, self._gridLines)
|
|
284
|
+
fm = QFontMetrics(self.font())
|
|
285
|
+
p.setFont(self.font())
|
|
286
|
+
for i in range(lines + 1):
|
|
287
|
+
price = lo + span * i / lines
|
|
288
|
+
y = y_for(price)
|
|
289
|
+
if self._showGrid:
|
|
290
|
+
p.setPen(QPen(self._grid, 1))
|
|
291
|
+
p.drawLine(QPointF(plot.left(), y), QPointF(plot.right(), y))
|
|
292
|
+
if self._showPriceAxis:
|
|
293
|
+
p.setPen(QPen(self._axisText))
|
|
294
|
+
text = self._fmtPrice(price)
|
|
295
|
+
p.drawText(QRectF(0, y - fm.height() / 2.0,
|
|
296
|
+
plot.left() - 6, fm.height()),
|
|
297
|
+
int(Qt.AlignRight | Qt.AlignVCenter), text)
|
|
298
|
+
|
|
299
|
+
def _paintLabels(self, p, plot, slot):
|
|
300
|
+
fm = QFontMetrics(self.font())
|
|
301
|
+
p.setPen(QPen(self._axisText))
|
|
302
|
+
p.setFont(self.font())
|
|
303
|
+
# Thin the labels out so they never overlap: draw every nth that fits.
|
|
304
|
+
widest = max((fm.horizontalAdvance(s) for s in self._labels), default=1)
|
|
305
|
+
step = max(1, int((widest + 10) // max(1.0, slot)) + (0 if slot >= widest + 10 else 1))
|
|
306
|
+
for i, text in enumerate(self._labels[:len(self._data)]):
|
|
307
|
+
if i % step:
|
|
308
|
+
continue
|
|
309
|
+
cx = plot.left() + slot * (i + 0.5)
|
|
310
|
+
p.drawText(QRectF(cx - slot, plot.bottom() + 2, slot * 2, fm.height()),
|
|
311
|
+
int(Qt.AlignHCenter | Qt.AlignVCenter), text)
|
|
312
|
+
|
|
313
|
+
def _paintTooltip(self, p, plot, slot):
|
|
314
|
+
o, h, l, c = self._data[self._hover]
|
|
315
|
+
label = (self._labels[self._hover]
|
|
316
|
+
if self._hover < len(self._labels) else "#%d" % self._hover)
|
|
317
|
+
rows = ["%s" % label,
|
|
318
|
+
"O %s H %s" % (self._fmtPrice(o), self._fmtPrice(h)),
|
|
319
|
+
"L %s C %s" % (self._fmtPrice(l), self._fmtPrice(c))]
|
|
320
|
+
fm = QFontMetrics(self.font())
|
|
321
|
+
w = max(fm.horizontalAdvance(r) for r in rows) + 16
|
|
322
|
+
row_h = fm.height()
|
|
323
|
+
box_h = row_h * len(rows) + 12
|
|
324
|
+
|
|
325
|
+
cx = plot.left() + slot * (self._hover + 0.5)
|
|
326
|
+
x = cx + 10
|
|
327
|
+
if x + w > plot.right(): # flip to the left near the edge
|
|
328
|
+
x = cx - 10 - w
|
|
329
|
+
x = max(plot.left(), min(x, plot.right() - w))
|
|
330
|
+
y = max(plot.top(), plot.top() + 6)
|
|
331
|
+
|
|
332
|
+
p.setPen(Qt.NoPen)
|
|
333
|
+
p.setBrush(QBrush(QColor(self._tooltipBg.red(), self._tooltipBg.green(),
|
|
334
|
+
self._tooltipBg.blue(), 235)))
|
|
335
|
+
p.drawRoundedRect(QRectF(x, y, w, box_h), 6, 6)
|
|
336
|
+
p.setPen(QPen(self._tooltipText))
|
|
337
|
+
p.setFont(self.font())
|
|
338
|
+
for i, text in enumerate(rows):
|
|
339
|
+
p.drawText(QRectF(x + 8, y + 6 + i * row_h, w - 16, row_h),
|
|
340
|
+
int(Qt.AlignLeft | Qt.AlignVCenter), text)
|
|
341
|
+
|
|
342
|
+
# ------------------------------------------------------------------ #
|
|
343
|
+
## Interaction
|
|
344
|
+
# ------------------------------------------------------------------ #
|
|
345
|
+
def candleAt(self, pos):
|
|
346
|
+
"""Index of the candle under a point, or -1."""
|
|
347
|
+
for i, rect in enumerate(self._rects):
|
|
348
|
+
if rect.contains(QPointF(pos)):
|
|
349
|
+
return i
|
|
350
|
+
return -1
|
|
351
|
+
|
|
352
|
+
def mouseMoveEvent(self, e):
|
|
353
|
+
i = self.candleAt(e.pos())
|
|
354
|
+
if i != self._hover:
|
|
355
|
+
self._hover = i
|
|
356
|
+
self.candleHovered.emit(i)
|
|
357
|
+
self.update()
|
|
358
|
+
super().mouseMoveEvent(e)
|
|
359
|
+
|
|
360
|
+
def leaveEvent(self, e):
|
|
361
|
+
if self._hover != -1:
|
|
362
|
+
self._hover = -1
|
|
363
|
+
self.candleHovered.emit(-1)
|
|
364
|
+
self.update()
|
|
365
|
+
super().leaveEvent(e)
|
|
366
|
+
|
|
367
|
+
def mouseReleaseEvent(self, e):
|
|
368
|
+
if e.button() == Qt.LeftButton:
|
|
369
|
+
i = self.candleAt(e.pos())
|
|
370
|
+
if i >= 0:
|
|
371
|
+
self.candleClicked.emit(i)
|
|
372
|
+
super().mouseReleaseEvent(e)
|
|
373
|
+
|
|
374
|
+
# ------------------------------------------------------------------ #
|
|
375
|
+
## Designer properties
|
|
376
|
+
# ------------------------------------------------------------------ #
|
|
377
|
+
@Property(str)
|
|
378
|
+
def ohlcCsv(self):
|
|
379
|
+
return ";".join(",".join("%g" % v for v in candle) for candle in self._data)
|
|
380
|
+
|
|
381
|
+
@ohlcCsv.setter
|
|
382
|
+
def ohlcCsv(self, text):
|
|
383
|
+
candles = []
|
|
384
|
+
for chunk in str(text).split(";"):
|
|
385
|
+
parts = [tok.strip() for tok in chunk.split(",") if tok.strip()]
|
|
386
|
+
if len(parts) < 4:
|
|
387
|
+
continue
|
|
388
|
+
try:
|
|
389
|
+
candles.append(tuple(float(v) for v in parts[:4]))
|
|
390
|
+
except ValueError:
|
|
391
|
+
continue
|
|
392
|
+
self.setData(candles)
|
|
393
|
+
|
|
394
|
+
@Property(str)
|
|
395
|
+
def labelsCsv(self):
|
|
396
|
+
return ",".join(self._labels)
|
|
397
|
+
|
|
398
|
+
@labelsCsv.setter
|
|
399
|
+
def labelsCsv(self, text):
|
|
400
|
+
self.setLabels([tok.strip() for tok in str(text).replace(";", ",").split(",")
|
|
401
|
+
if tok.strip()])
|
|
402
|
+
|
|
403
|
+
@Property(bool)
|
|
404
|
+
def showGrid(self):
|
|
405
|
+
return self._showGrid
|
|
406
|
+
|
|
407
|
+
@showGrid.setter
|
|
408
|
+
def showGrid(self, v):
|
|
409
|
+
self._showGrid = bool(v); self.update()
|
|
410
|
+
|
|
411
|
+
@Property(bool)
|
|
412
|
+
def showPriceAxis(self):
|
|
413
|
+
return self._showPriceAxis
|
|
414
|
+
|
|
415
|
+
@showPriceAxis.setter
|
|
416
|
+
def showPriceAxis(self, v):
|
|
417
|
+
self._showPriceAxis = bool(v); self.update()
|
|
418
|
+
|
|
419
|
+
@Property(bool)
|
|
420
|
+
def showLabels(self):
|
|
421
|
+
return self._showLabels
|
|
422
|
+
|
|
423
|
+
@showLabels.setter
|
|
424
|
+
def showLabels(self, v):
|
|
425
|
+
self._showLabels = bool(v); self.update()
|
|
426
|
+
|
|
427
|
+
@Property(bool)
|
|
428
|
+
def showTooltip(self):
|
|
429
|
+
return self._showTooltip
|
|
430
|
+
|
|
431
|
+
@showTooltip.setter
|
|
432
|
+
def showTooltip(self, v):
|
|
433
|
+
self._showTooltip = bool(v); self.update()
|
|
434
|
+
|
|
435
|
+
@Property(bool)
|
|
436
|
+
def hollowUpCandles(self):
|
|
437
|
+
return self._hollowUp
|
|
438
|
+
|
|
439
|
+
@hollowUpCandles.setter
|
|
440
|
+
def hollowUpCandles(self, v):
|
|
441
|
+
self._hollowUp = bool(v); self.update()
|
|
442
|
+
|
|
443
|
+
@Property(float)
|
|
444
|
+
def candleWidthRatio(self):
|
|
445
|
+
return self._widthRatio
|
|
446
|
+
|
|
447
|
+
@candleWidthRatio.setter
|
|
448
|
+
def candleWidthRatio(self, v):
|
|
449
|
+
self._widthRatio = max(0.05, min(1.0, float(v))); self.update()
|
|
450
|
+
|
|
451
|
+
@Property(int)
|
|
452
|
+
def gridLines(self):
|
|
453
|
+
return self._gridLines
|
|
454
|
+
|
|
455
|
+
@gridLines.setter
|
|
456
|
+
def gridLines(self, v):
|
|
457
|
+
self._gridLines = max(1, int(v)); self.update()
|
|
458
|
+
|
|
459
|
+
@Property(int)
|
|
460
|
+
def pricePrecision(self):
|
|
461
|
+
return self._precision
|
|
462
|
+
|
|
463
|
+
@pricePrecision.setter
|
|
464
|
+
def pricePrecision(self, v):
|
|
465
|
+
self._precision = max(0, int(v)); self.update()
|
|
466
|
+
|
|
467
|
+
# -- colours (from tokens via qproperty) --
|
|
468
|
+
@Property(QColor)
|
|
469
|
+
def upColor(self):
|
|
470
|
+
return self._up
|
|
471
|
+
|
|
472
|
+
@upColor.setter
|
|
473
|
+
def upColor(self, c):
|
|
474
|
+
self._up = QColor(c); self.update()
|
|
475
|
+
|
|
476
|
+
@Property(QColor)
|
|
477
|
+
def downColor(self):
|
|
478
|
+
return self._down
|
|
479
|
+
|
|
480
|
+
@downColor.setter
|
|
481
|
+
def downColor(self, c):
|
|
482
|
+
self._down = QColor(c); self.update()
|
|
483
|
+
|
|
484
|
+
@Property(QColor)
|
|
485
|
+
def wickColor(self):
|
|
486
|
+
return self._wick
|
|
487
|
+
|
|
488
|
+
@wickColor.setter
|
|
489
|
+
def wickColor(self, c):
|
|
490
|
+
self._wick = QColor(c); self.update()
|
|
491
|
+
|
|
492
|
+
@Property(QColor)
|
|
493
|
+
def gridColor(self):
|
|
494
|
+
return self._grid
|
|
495
|
+
|
|
496
|
+
@gridColor.setter
|
|
497
|
+
def gridColor(self, c):
|
|
498
|
+
self._grid = QColor(c); self.update()
|
|
499
|
+
|
|
500
|
+
@Property(QColor)
|
|
501
|
+
def axisTextColor(self):
|
|
502
|
+
return self._axisText
|
|
503
|
+
|
|
504
|
+
@axisTextColor.setter
|
|
505
|
+
def axisTextColor(self, c):
|
|
506
|
+
self._axisText = QColor(c); self.update()
|
|
507
|
+
|
|
508
|
+
@Property(QColor)
|
|
509
|
+
def tooltipBackgroundColor(self):
|
|
510
|
+
return self._tooltipBg
|
|
511
|
+
|
|
512
|
+
@tooltipBackgroundColor.setter
|
|
513
|
+
def tooltipBackgroundColor(self, c):
|
|
514
|
+
self._tooltipBg = QColor(c); self.update()
|
|
515
|
+
|
|
516
|
+
@Property(QColor)
|
|
517
|
+
def tooltipTextColor(self):
|
|
518
|
+
return self._tooltipText
|
|
519
|
+
|
|
520
|
+
@tooltipTextColor.setter
|
|
521
|
+
def tooltipTextColor(self, c):
|
|
522
|
+
self._tooltipText = QColor(c); self.update()
|