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,317 @@
|
|
|
1
|
+
########################################################################
|
|
2
|
+
## SPINN DESIGN CODE
|
|
3
|
+
# YOUTUBE: (SPINN TV) https://www.youtube.com/@SpinnTV
|
|
4
|
+
# WEBSITE: customwidgets.org
|
|
5
|
+
########################################################################
|
|
6
|
+
|
|
7
|
+
########################################################################
|
|
8
|
+
## QCustomBeeswarm - a column beeswarm / bubble-stack chart.
|
|
9
|
+
##
|
|
10
|
+
## Each COLUMN is a thin vertical guide line carrying a vertical stack of
|
|
11
|
+
## rounded "pill" bubbles. Every bubble shows a VALUE (its number) and a
|
|
12
|
+
## CATEGORY (a colour from `colorsCsv`); the pill's height scales with the
|
|
13
|
+
## value between `minSize`..`maxSize`. This is the "check-box product" viz:
|
|
14
|
+
## numbered capsules in columns, coloured by Resources / Valid / Invalid.
|
|
15
|
+
## Painted (no QtCharts) so it stays crisp and needs no external axis.
|
|
16
|
+
##
|
|
17
|
+
## Give data in code with setData(columns) where `columns` is a list of
|
|
18
|
+
## columns and each column is a list of (value, category) pairs, or in Qt
|
|
19
|
+
## Designer with `dataCsv` (columns separated by ';', items by ',', each item
|
|
20
|
+
## "value:category" — category is the 1-based colour index, default 1).
|
|
21
|
+
## Colours / the number-text colour rules are qproperties so a theme flips
|
|
22
|
+
## them on switch. The legend + total are best drawn as sibling labels in the
|
|
23
|
+
## card (reuse), not by this widget.
|
|
24
|
+
########################################################################
|
|
25
|
+
from qtpy.QtCore import Qt, Property, QRectF
|
|
26
|
+
from qtpy.QtGui import QColor, QPainter, QBrush, QPen, QFont, QFontMetrics
|
|
27
|
+
from qtpy.QtWidgets import QWidget, QSizePolicy
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class QCustomBeeswarm(QWidget):
|
|
31
|
+
|
|
32
|
+
WIDGET_ICON = "components/icons/bar_chart.png"
|
|
33
|
+
WIDGET_TOOLTIP = "A column beeswarm / numbered bubble-stack chart"
|
|
34
|
+
WIDGET_MODULE = "Custom_Widgets.QCustomBeeswarm"
|
|
35
|
+
WIDGET_DOM_XML = """
|
|
36
|
+
<ui language='c++'>
|
|
37
|
+
<widget class='QCustomBeeswarm' name='customBeeswarm'>
|
|
38
|
+
<property name='geometry'><rect><x>0</x><y>0</y><width>560</width><height>300</height></rect></property>
|
|
39
|
+
</widget>
|
|
40
|
+
</ui>
|
|
41
|
+
"""
|
|
42
|
+
__catalog__ = {
|
|
43
|
+
"name": "QCustomBeeswarm",
|
|
44
|
+
"props": {"dataCsv": {"type": "string", "default": ""},
|
|
45
|
+
"colorsCsv": {"type": "string", "default": "#ffffff,#8fe36b,#f6912b"},
|
|
46
|
+
"textColorsCsv": {"type": "string", "default": "#1c1c20,#1c1c20,#ffffff"},
|
|
47
|
+
"lineColor": {"type": "color", "default": "#3a3a40"},
|
|
48
|
+
"minSize": {"type": "int", "default": 34},
|
|
49
|
+
"maxSize": {"type": "int", "default": 58},
|
|
50
|
+
"bubbleWidth": {"type": "int", "default": 42},
|
|
51
|
+
"gap": {"type": "int", "default": 8},
|
|
52
|
+
"showValues": {"type": "bool", "default": True},
|
|
53
|
+
"jitter": {"type": "int", "default": 0}},
|
|
54
|
+
"signals": [],
|
|
55
|
+
# Intentionally no tokens_used: this widget resolves no token roles.
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# Rich editors for the Designer "Custom Properties" dock (see
|
|
59
|
+
# DesignerTools.CustomPropertiesDock).
|
|
60
|
+
DESIGNER_CUSTOM_PROPS = [
|
|
61
|
+
{"name": "dataCsv", "kind": "str", "group": "General"},
|
|
62
|
+
{"name": "colorsCsv", "kind": "str", "group": "Colors"},
|
|
63
|
+
{"name": "textColorsCsv", "kind": "str", "group": "Colors"},
|
|
64
|
+
{"name": "lineColor", "kind": "color", "group": "Colors"},
|
|
65
|
+
{"name": "minSize", "kind": "int", "group": "General"},
|
|
66
|
+
{"name": "maxSize", "kind": "int", "group": "General"},
|
|
67
|
+
{"name": "bubbleWidth", "kind": "int", "group": "General"},
|
|
68
|
+
{"name": "gap", "kind": "int", "group": "General"},
|
|
69
|
+
{"name": "showValues", "kind": "bool", "group": "General"},
|
|
70
|
+
{"name": "jitter", "kind": "int", "group": "General"},
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
# deterministic demo columns: list of (value, category) per column
|
|
74
|
+
_DEMO = [
|
|
75
|
+
[(52, 0), (81, 2)],
|
|
76
|
+
[(96, 1), (25, 0)],
|
|
77
|
+
[(48, 1), (51, 0)],
|
|
78
|
+
[(80, 1), (49, 2)],
|
|
79
|
+
[(34, 2), (67, 1)],
|
|
80
|
+
[(92, 1), (28, 0)],
|
|
81
|
+
[(58, 1), (20, 2)],
|
|
82
|
+
[(84, 2), (39, 1)],
|
|
83
|
+
[(36, 0), (72, 2)],
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
def __init__(self, parent=None, columns=None):
|
|
87
|
+
super().__init__(parent)
|
|
88
|
+
self.setObjectName("QCustomBeeswarm")
|
|
89
|
+
self._cols = [[(float(v), int(c)) for v, c in col] for col in columns] if columns else []
|
|
90
|
+
self._colors = [QColor("#ffffff"), QColor("#8fe36b"), QColor("#f6912b")]
|
|
91
|
+
self._text_colors = [QColor("#1c1c20"), QColor("#1c1c20"), QColor("#ffffff")]
|
|
92
|
+
self._line = QColor("#3a3a40")
|
|
93
|
+
self._min_size = 34
|
|
94
|
+
self._max_size = 58
|
|
95
|
+
self._bubble_w = 42
|
|
96
|
+
self._gap = 8
|
|
97
|
+
self._show_values = True
|
|
98
|
+
self._jitter = 0
|
|
99
|
+
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
|
100
|
+
self.setMinimumSize(160, 120)
|
|
101
|
+
if not self._cols:
|
|
102
|
+
self._cols = [[(float(v), int(c)) for v, c in col] for col in self._DEMO]
|
|
103
|
+
|
|
104
|
+
# ------------------------------------------------------------------ #
|
|
105
|
+
## Data API
|
|
106
|
+
# ------------------------------------------------------------------ #
|
|
107
|
+
def setData(self, columns):
|
|
108
|
+
"""`columns` = list of columns; each column = list of (value, category)."""
|
|
109
|
+
self._cols = [[(float(v), int(c)) for v, c in col] for col in (columns or [])]
|
|
110
|
+
self.update()
|
|
111
|
+
|
|
112
|
+
def data(self):
|
|
113
|
+
return [list(col) for col in self._cols]
|
|
114
|
+
|
|
115
|
+
def setColors(self, fills, texts=None):
|
|
116
|
+
self._colors = [QColor(c) for c in fills if c]
|
|
117
|
+
if texts:
|
|
118
|
+
self._text_colors = [QColor(c) for c in texts if c]
|
|
119
|
+
self.update()
|
|
120
|
+
|
|
121
|
+
def _value_range(self):
|
|
122
|
+
vals = [v for col in self._cols for (v, _c) in col]
|
|
123
|
+
if not vals:
|
|
124
|
+
return 0.0, 1.0
|
|
125
|
+
return min(vals), max(vals)
|
|
126
|
+
|
|
127
|
+
def _fill(self, cat):
|
|
128
|
+
return QColor(self._colors[cat % len(self._colors)]) if self._colors else QColor("#ffffff")
|
|
129
|
+
|
|
130
|
+
def _text(self, cat):
|
|
131
|
+
if self._text_colors:
|
|
132
|
+
return QColor(self._text_colors[cat % len(self._text_colors)])
|
|
133
|
+
return QColor("#1c1c20")
|
|
134
|
+
|
|
135
|
+
# ------------------------------------------------------------------ #
|
|
136
|
+
## Painting
|
|
137
|
+
# ------------------------------------------------------------------ #
|
|
138
|
+
def paintEvent(self, e):
|
|
139
|
+
if not self._cols:
|
|
140
|
+
return
|
|
141
|
+
p = QPainter(self)
|
|
142
|
+
p.setRenderHint(QPainter.Antialiasing, True)
|
|
143
|
+
w, h = self.width(), self.height()
|
|
144
|
+
n = len(self._cols)
|
|
145
|
+
if n == 0:
|
|
146
|
+
p.end(); return
|
|
147
|
+
|
|
148
|
+
lo, hi = self._value_range()
|
|
149
|
+
rng = (hi - lo) or 1.0
|
|
150
|
+
col_w = w / n
|
|
151
|
+
bw = float(min(self._bubble_w, col_w * 0.82))
|
|
152
|
+
gap = float(self._gap)
|
|
153
|
+
|
|
154
|
+
vfont = QFont(self.font())
|
|
155
|
+
vfont.setBold(True)
|
|
156
|
+
|
|
157
|
+
for i, col in enumerate(self._cols):
|
|
158
|
+
cx = col_w * (i + 0.5)
|
|
159
|
+
if self._jitter:
|
|
160
|
+
# deterministic left/right nudge off the guide line
|
|
161
|
+
cx += (self._jitter if i % 2 else -self._jitter)
|
|
162
|
+
|
|
163
|
+
# pill heights for this column
|
|
164
|
+
heights = []
|
|
165
|
+
for (v, _c) in col:
|
|
166
|
+
t = (v - lo) / rng
|
|
167
|
+
heights.append(self._min_size + t * (self._max_size - self._min_size))
|
|
168
|
+
stack_h = sum(heights) + gap * (len(col) - 1)
|
|
169
|
+
# centre the stack vertically with a little breathing room
|
|
170
|
+
top = (h - stack_h) / 2.0
|
|
171
|
+
|
|
172
|
+
# guide line spanning the stack
|
|
173
|
+
if len(col) >= 1:
|
|
174
|
+
pen = QPen(self._line, 2)
|
|
175
|
+
pen.setCapStyle(Qt.RoundCap)
|
|
176
|
+
p.setPen(pen)
|
|
177
|
+
p.drawLine(int(cx), int(max(6, top - 8)),
|
|
178
|
+
int(cx), int(min(h - 6, top + stack_h + 8)))
|
|
179
|
+
|
|
180
|
+
y = top
|
|
181
|
+
for (v, c), bh in zip(col, heights):
|
|
182
|
+
fill = self._fill(c)
|
|
183
|
+
rect = QRectF(cx - bw / 2.0, y, bw, bh)
|
|
184
|
+
r = bw / 2.0
|
|
185
|
+
p.setPen(Qt.NoPen)
|
|
186
|
+
p.setBrush(QBrush(fill))
|
|
187
|
+
p.drawRoundedRect(rect, r, r)
|
|
188
|
+
if self._show_values:
|
|
189
|
+
p.setPen(self._text(c))
|
|
190
|
+
# fit the number: shrink font for short pills
|
|
191
|
+
ps = max(8, min(13, int(bh * 0.34)))
|
|
192
|
+
vfont.setPointSize(ps)
|
|
193
|
+
p.setFont(vfont)
|
|
194
|
+
p.drawText(rect, Qt.AlignCenter, ("%g" % v))
|
|
195
|
+
y += bh + gap
|
|
196
|
+
p.end()
|
|
197
|
+
|
|
198
|
+
# ------------------------------------------------------------------ #
|
|
199
|
+
## Designer properties
|
|
200
|
+
# ------------------------------------------------------------------ #
|
|
201
|
+
@Property(str)
|
|
202
|
+
def dataCsv(self):
|
|
203
|
+
return ";".join(
|
|
204
|
+
",".join("%g:%d" % (v, c) for (v, c) in col) for col in self._cols)
|
|
205
|
+
|
|
206
|
+
@dataCsv.setter
|
|
207
|
+
def dataCsv(self, text):
|
|
208
|
+
cols = []
|
|
209
|
+
for chunk in str(text).replace("|", ";").split(";"):
|
|
210
|
+
chunk = chunk.strip()
|
|
211
|
+
if not chunk:
|
|
212
|
+
continue
|
|
213
|
+
col = []
|
|
214
|
+
for tok in chunk.split(","):
|
|
215
|
+
tok = tok.strip()
|
|
216
|
+
if not tok:
|
|
217
|
+
continue
|
|
218
|
+
if ":" in tok:
|
|
219
|
+
a, b = tok.split(":", 1)
|
|
220
|
+
try:
|
|
221
|
+
col.append((float(a), int(float(b))))
|
|
222
|
+
except ValueError:
|
|
223
|
+
pass
|
|
224
|
+
else:
|
|
225
|
+
try:
|
|
226
|
+
col.append((float(tok), 0))
|
|
227
|
+
except ValueError:
|
|
228
|
+
pass
|
|
229
|
+
if col:
|
|
230
|
+
cols.append(col)
|
|
231
|
+
if cols:
|
|
232
|
+
self.setData(cols)
|
|
233
|
+
|
|
234
|
+
@Property(str)
|
|
235
|
+
def colorsCsv(self):
|
|
236
|
+
return ",".join(c.name() for c in self._colors)
|
|
237
|
+
|
|
238
|
+
@colorsCsv.setter
|
|
239
|
+
def colorsCsv(self, text):
|
|
240
|
+
cols = [t.strip() for t in str(text).replace(";", ",").split(",") if t.strip()]
|
|
241
|
+
if cols:
|
|
242
|
+
self._colors = [QColor(c) for c in cols]
|
|
243
|
+
self.update()
|
|
244
|
+
|
|
245
|
+
@Property(str)
|
|
246
|
+
def textColorsCsv(self):
|
|
247
|
+
return ",".join(c.name() for c in self._text_colors)
|
|
248
|
+
|
|
249
|
+
@textColorsCsv.setter
|
|
250
|
+
def textColorsCsv(self, text):
|
|
251
|
+
cols = [t.strip() for t in str(text).replace(";", ",").split(",") if t.strip()]
|
|
252
|
+
if cols:
|
|
253
|
+
self._text_colors = [QColor(c) for c in cols]
|
|
254
|
+
self.update()
|
|
255
|
+
|
|
256
|
+
@Property(QColor)
|
|
257
|
+
def lineColor(self):
|
|
258
|
+
return self._line
|
|
259
|
+
|
|
260
|
+
@lineColor.setter
|
|
261
|
+
def lineColor(self, c):
|
|
262
|
+
self._line = QColor(c)
|
|
263
|
+
self.update()
|
|
264
|
+
|
|
265
|
+
@Property(int)
|
|
266
|
+
def minSize(self):
|
|
267
|
+
return self._min_size
|
|
268
|
+
|
|
269
|
+
@minSize.setter
|
|
270
|
+
def minSize(self, v):
|
|
271
|
+
self._min_size = max(6, int(v))
|
|
272
|
+
self.update()
|
|
273
|
+
|
|
274
|
+
@Property(int)
|
|
275
|
+
def maxSize(self):
|
|
276
|
+
return self._max_size
|
|
277
|
+
|
|
278
|
+
@maxSize.setter
|
|
279
|
+
def maxSize(self, v):
|
|
280
|
+
self._max_size = max(self._min_size, int(v))
|
|
281
|
+
self.update()
|
|
282
|
+
|
|
283
|
+
@Property(int)
|
|
284
|
+
def bubbleWidth(self):
|
|
285
|
+
return self._bubble_w
|
|
286
|
+
|
|
287
|
+
@bubbleWidth.setter
|
|
288
|
+
def bubbleWidth(self, v):
|
|
289
|
+
self._bubble_w = max(8, int(v))
|
|
290
|
+
self.update()
|
|
291
|
+
|
|
292
|
+
@Property(int)
|
|
293
|
+
def gap(self):
|
|
294
|
+
return self._gap
|
|
295
|
+
|
|
296
|
+
@gap.setter
|
|
297
|
+
def gap(self, v):
|
|
298
|
+
self._gap = max(0, int(v))
|
|
299
|
+
self.update()
|
|
300
|
+
|
|
301
|
+
@Property(bool)
|
|
302
|
+
def showValues(self):
|
|
303
|
+
return self._show_values
|
|
304
|
+
|
|
305
|
+
@showValues.setter
|
|
306
|
+
def showValues(self, v):
|
|
307
|
+
self._show_values = bool(v)
|
|
308
|
+
self.update()
|
|
309
|
+
|
|
310
|
+
@Property(int)
|
|
311
|
+
def jitter(self):
|
|
312
|
+
return self._jitter
|
|
313
|
+
|
|
314
|
+
@jitter.setter
|
|
315
|
+
def jitter(self, v):
|
|
316
|
+
self._jitter = max(0, int(v))
|
|
317
|
+
self.update()
|