QuickGraphLib 0.1.0a0__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.
- QuickGraphLib/AntialiasingContainer.qml +18 -0
- QuickGraphLib/Axis.qml +227 -0
- QuickGraphLib/GraphArea.qml +54 -0
- QuickGraphLib/GraphItems/AxHLine.qml +25 -0
- QuickGraphLib/GraphItems/AxHSpan.qml +28 -0
- QuickGraphLib/GraphItems/AxVLine.qml +25 -0
- QuickGraphLib/GraphItems/AxVSpan.qml +28 -0
- QuickGraphLib/GraphItems/BasicLegend.qml +29 -0
- QuickGraphLib/GraphItems/BasicLegendItem.qml +54 -0
- QuickGraphLib/GraphItems/Contour.qml +33 -0
- QuickGraphLib/GraphItems/GraphItemDragHandler.qml +39 -0
- QuickGraphLib/GraphItems/Grid.qml +58 -0
- QuickGraphLib/GraphItems/Histogram.qml +50 -0
- QuickGraphLib/GraphItems/Line.qml +58 -0
- QuickGraphLib/GraphItems/Marker.qml +28 -0
- QuickGraphLib/GraphItems/qmldir +16 -0
- QuickGraphLib/Helpers.js +167 -0
- QuickGraphLib/PreFabs/XYAxes.qml +103 -0
- QuickGraphLib/PreFabs/qmldir +5 -0
- QuickGraphLib/ScalingContainer.qml +38 -0
- QuickGraphLib/ZoomPanHandler.qml +78 -0
- QuickGraphLib/qmldir +10 -0
- QuickGraphLib-0.1.0a0.dist-info/LICENCE +21 -0
- QuickGraphLib-0.1.0a0.dist-info/METADATA +73 -0
- QuickGraphLib-0.1.0a0.dist-info/RECORD +32 -0
- QuickGraphLib-0.1.0a0.dist-info/WHEEL +5 -0
- QuickGraphLib-0.1.0a0.dist-info/top_level.txt +2 -0
- quickgraphlib_helpers/__init__.py +4 -0
- quickgraphlib_helpers/consts.py +6 -0
- quickgraphlib_helpers/contours.py +79 -0
- quickgraphlib_helpers/export.py +167 -0
- quickgraphlib_helpers/py.typed +0 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2024 Matthew Joyce and other QuickGraphLib contributors
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
from typing import Sequence
|
|
5
|
+
|
|
6
|
+
from PySide6 import QtCore, QtQml
|
|
7
|
+
|
|
8
|
+
from .consts import ( # pylint: disable=unused-import
|
|
9
|
+
QML_IMPORT_MAJOR_VERSION,
|
|
10
|
+
QML_IMPORT_MINOR_VERSION,
|
|
11
|
+
QML_IMPORT_NAME,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
import contourpy
|
|
16
|
+
import numpy as np
|
|
17
|
+
except ModuleNotFoundError:
|
|
18
|
+
CONTOURPY_AVAILABLE = False
|
|
19
|
+
else:
|
|
20
|
+
CONTOURPY_AVAILABLE = True
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def contour_line(
|
|
24
|
+
x: Sequence[Sequence[float]],
|
|
25
|
+
y: Sequence[Sequence[float]],
|
|
26
|
+
z: Sequence[Sequence[float]],
|
|
27
|
+
height: float,
|
|
28
|
+
) -> Sequence[Sequence[QtCore.QPointF]]:
|
|
29
|
+
gen = contourpy.contour_generator(
|
|
30
|
+
x=x, y=y, z=z, line_type=contourpy.LineType.Separate
|
|
31
|
+
)
|
|
32
|
+
result = []
|
|
33
|
+
for loop in gen.lines(height):
|
|
34
|
+
assert isinstance(loop, np.ndarray), "Loop is not an array" # For mypy
|
|
35
|
+
points = [QtCore.QPointF(x, y) for x, y in loop]
|
|
36
|
+
result.append(points)
|
|
37
|
+
return result
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def contour_fill(
|
|
41
|
+
x: Sequence[Sequence[float]],
|
|
42
|
+
y: Sequence[Sequence[float]],
|
|
43
|
+
z: Sequence[Sequence[float]],
|
|
44
|
+
heights: tuple[float, float],
|
|
45
|
+
) -> Sequence[Sequence[QtCore.QPointF]]:
|
|
46
|
+
gen = contourpy.contour_generator(
|
|
47
|
+
x=x, y=y, z=z, fill_type=contourpy.FillType.OuterOffset
|
|
48
|
+
)
|
|
49
|
+
result = []
|
|
50
|
+
for loop, offsets in zip(*gen.filled(*heights)):
|
|
51
|
+
for start, end in zip(offsets, offsets[1:]):
|
|
52
|
+
points = [QtCore.QPointF(x, y) for x, y in loop[start:end]]
|
|
53
|
+
result.append(points)
|
|
54
|
+
return result
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@QtQml.QmlElement
|
|
58
|
+
@QtQml.QmlSingleton
|
|
59
|
+
class ContourHelper(QtCore.QObject):
|
|
60
|
+
@QtCore.Slot(list, list, list, float, result=list)
|
|
61
|
+
def contourLine(
|
|
62
|
+
self,
|
|
63
|
+
x: Sequence[Sequence[float]],
|
|
64
|
+
y: Sequence[Sequence[float]],
|
|
65
|
+
z: Sequence[Sequence[float]],
|
|
66
|
+
height: float,
|
|
67
|
+
) -> Sequence[Sequence[QtCore.QPointF]]:
|
|
68
|
+
return contour_line(x, y, z, height)
|
|
69
|
+
|
|
70
|
+
@QtCore.Slot(list, list, list, float, float, result=list)
|
|
71
|
+
def contourFill(
|
|
72
|
+
self,
|
|
73
|
+
x: Sequence[Sequence[float]],
|
|
74
|
+
y: Sequence[Sequence[float]],
|
|
75
|
+
z: Sequence[Sequence[float]],
|
|
76
|
+
h_min: float,
|
|
77
|
+
h_max: float,
|
|
78
|
+
) -> Sequence[Sequence[QtCore.QPointF]]:
|
|
79
|
+
return contour_fill(x, y, z, (h_min, h_max))
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2024 Matthew Joyce and other QuickGraphLib contributors
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import math
|
|
5
|
+
import pathlib
|
|
6
|
+
from typing import Any, Mapping
|
|
7
|
+
|
|
8
|
+
from PySide6 import QtCore, QtGui, QtQml, QtSvg
|
|
9
|
+
|
|
10
|
+
from .consts import ( # pylint: disable=unused-import
|
|
11
|
+
QML_IMPORT_MAJOR_VERSION,
|
|
12
|
+
QML_IMPORT_MINOR_VERSION,
|
|
13
|
+
QML_IMPORT_NAME,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _export_child_to_painter(data: Mapping[str, Any], painter: QtGui.QPainter) -> None:
|
|
18
|
+
if "x" in data: # An Item
|
|
19
|
+
if not data["visible"]:
|
|
20
|
+
return
|
|
21
|
+
painter.save()
|
|
22
|
+
painter.translate(data["x"], data["y"])
|
|
23
|
+
painter.setOpacity(data["opacity"])
|
|
24
|
+
rect = QtCore.QRectF(0, 0, data["width"], data["height"])
|
|
25
|
+
if data["clip"]:
|
|
26
|
+
# Note: doesn't work for SVGs until 6.7
|
|
27
|
+
painter.setClipRect(rect)
|
|
28
|
+
|
|
29
|
+
childrenZSorted = sorted(data["children"], key=lambda c: c.get("z", 0))
|
|
30
|
+
|
|
31
|
+
for tr in data["transform"][::-1]:
|
|
32
|
+
if tr is None:
|
|
33
|
+
pass
|
|
34
|
+
elif tr["type"] == "translate":
|
|
35
|
+
painter.translate(tr["x"], tr["y"])
|
|
36
|
+
elif tr["type"] == "rotation":
|
|
37
|
+
painter.translate(tr["origin"].x(), tr["origin"].y())
|
|
38
|
+
painter.rotate(tr["angle"])
|
|
39
|
+
painter.translate(-tr["origin"].x(), -tr["origin"].y())
|
|
40
|
+
elif tr["type"] == "scale":
|
|
41
|
+
painter.translate(tr["origin"].x(), tr["origin"].y())
|
|
42
|
+
painter.scale(tr["xScale"], tr["yScale"])
|
|
43
|
+
painter.translate(-tr["origin"].x(), -tr["origin"].y())
|
|
44
|
+
elif tr["type"] == "matrix4x4":
|
|
45
|
+
painter.setTransform(tr["matrix"].toTransform())
|
|
46
|
+
|
|
47
|
+
for c in childrenZSorted:
|
|
48
|
+
if c.get("z", 0) < 0:
|
|
49
|
+
_export_child_to_painter(c, painter)
|
|
50
|
+
|
|
51
|
+
if data["type"] == "text":
|
|
52
|
+
painter.save()
|
|
53
|
+
font = QtGui.QFont(data["fontFamily"], 1, data["fontWeight"])
|
|
54
|
+
font.setPixelSize(data["fontSize"])
|
|
55
|
+
painter.setFont(font)
|
|
56
|
+
painter.setPen(data["color"])
|
|
57
|
+
painter.drawText(rect, data["text"])
|
|
58
|
+
painter.restore()
|
|
59
|
+
if data["type"] == "rectangle":
|
|
60
|
+
painter.save()
|
|
61
|
+
if data["border_width"] > 0:
|
|
62
|
+
pen = QtGui.QPen(data["border_color"])
|
|
63
|
+
pen.setWidthF(data["border_width"])
|
|
64
|
+
else:
|
|
65
|
+
pen = QtGui.QPen(QtCore.Qt.PenStyle.NoPen)
|
|
66
|
+
painter.setPen(pen)
|
|
67
|
+
painter.setBrush(data["color"])
|
|
68
|
+
painter.drawRoundedRect(rect, data["radius"], data["radius"])
|
|
69
|
+
painter.restore()
|
|
70
|
+
|
|
71
|
+
for c in childrenZSorted:
|
|
72
|
+
if c.get("z", 0) >= 0:
|
|
73
|
+
_export_child_to_painter(c, painter)
|
|
74
|
+
painter.restore()
|
|
75
|
+
|
|
76
|
+
elif data["type"] == "shape_path":
|
|
77
|
+
painter.save()
|
|
78
|
+
pen = QtGui.QPen(data["strokeColor"])
|
|
79
|
+
pen.setWidth(data["strokeWidth"])
|
|
80
|
+
painter.setPen(pen)
|
|
81
|
+
if data["fillGradient"] is None:
|
|
82
|
+
brush = QtGui.QBrush(data["fillColor"])
|
|
83
|
+
elif data["fillGradient"]["type"] == "lineargradient":
|
|
84
|
+
lingrad = QtGui.QLinearGradient(
|
|
85
|
+
data["fillGradient"]["x1"],
|
|
86
|
+
data["fillGradient"]["y1"],
|
|
87
|
+
data["fillGradient"]["x2"],
|
|
88
|
+
data["fillGradient"]["y2"],
|
|
89
|
+
)
|
|
90
|
+
lingrad.setStops(
|
|
91
|
+
[(s["position"], s["color"]) for s in data["fillGradient"]["stops"]]
|
|
92
|
+
)
|
|
93
|
+
brush = QtGui.QBrush(lingrad)
|
|
94
|
+
painter.setBrush(brush)
|
|
95
|
+
for c in data["elements"]:
|
|
96
|
+
_export_child_to_painter(c, painter)
|
|
97
|
+
painter.restore()
|
|
98
|
+
|
|
99
|
+
elif data["type"] == "polyline":
|
|
100
|
+
brush = painter.brush()
|
|
101
|
+
pen = painter.pen()
|
|
102
|
+
painter.setPen(QtCore.Qt.PenStyle.NoPen)
|
|
103
|
+
painter.drawPolygon(data["path"])
|
|
104
|
+
painter.setPen(pen)
|
|
105
|
+
painter.setBrush(QtCore.Qt.BrushStyle.NoBrush)
|
|
106
|
+
painter.drawPolyline(data["path"])
|
|
107
|
+
painter.setBrush(brush)
|
|
108
|
+
|
|
109
|
+
elif data["type"] == "multiline":
|
|
110
|
+
brush = painter.brush()
|
|
111
|
+
pen = painter.pen()
|
|
112
|
+
fill_path = QtGui.QPainterPath()
|
|
113
|
+
for path in data["paths"]:
|
|
114
|
+
fill_path.addPolygon(QtGui.QPolygonF(path))
|
|
115
|
+
fill_path.closeSubpath()
|
|
116
|
+
|
|
117
|
+
painter.setPen(QtCore.Qt.PenStyle.NoPen)
|
|
118
|
+
painter.drawPath(fill_path)
|
|
119
|
+
painter.setPen(pen)
|
|
120
|
+
painter.setBrush(QtCore.Qt.BrushStyle.NoBrush)
|
|
121
|
+
for path in data["paths"]:
|
|
122
|
+
painter.drawPolyline(path)
|
|
123
|
+
painter.setBrush(brush)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def export_to_painter(data: Mapping[str, Any], painter: QtGui.QPainter) -> None:
|
|
127
|
+
painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing, True)
|
|
128
|
+
painter.setRenderHint(QtGui.QPainter.RenderHint.TextAntialiasing, True)
|
|
129
|
+
_export_child_to_painter(data, painter)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def export_to_paintdevice(data: Mapping[str, Any], device: QtGui.QPaintDevice) -> None:
|
|
133
|
+
painter = QtGui.QPainter()
|
|
134
|
+
painter.begin(device)
|
|
135
|
+
try:
|
|
136
|
+
export_to_painter(data, painter)
|
|
137
|
+
finally:
|
|
138
|
+
painter.end()
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def export_to_svg(data: Mapping[str, Any], path: pathlib.Path) -> None:
|
|
142
|
+
device = QtSvg.QSvgGenerator()
|
|
143
|
+
device.setFileName(str(path))
|
|
144
|
+
export_to_paintdevice(data, device)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def export_to_png(data: Mapping[str, Any], path: pathlib.Path, dpi: int = 96) -> None:
|
|
148
|
+
device = QtGui.QPixmap(
|
|
149
|
+
math.ceil((data["width"] + data["x"]) * dpi / 96),
|
|
150
|
+
math.ceil((data["height"] + data["y"]) * dpi / 96),
|
|
151
|
+
)
|
|
152
|
+
device.fill(QtCore.Qt.GlobalColor.transparent)
|
|
153
|
+
device.setDevicePixelRatio(dpi / 96)
|
|
154
|
+
export_to_paintdevice(data, device)
|
|
155
|
+
device.save(str(path))
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@QtQml.QmlElement
|
|
159
|
+
@QtQml.QmlSingleton
|
|
160
|
+
class ExportHelper(QtCore.QObject):
|
|
161
|
+
@QtCore.Slot(dict, QtCore.QUrl)
|
|
162
|
+
def exportToSvg(self, data: Mapping[str, Any], path: QtCore.QUrl) -> None:
|
|
163
|
+
export_to_svg(data, pathlib.Path(path.toLocalFile()))
|
|
164
|
+
|
|
165
|
+
@QtCore.Slot(dict, QtCore.QUrl)
|
|
166
|
+
def exportToPng(self, data: Mapping[str, Any], path: QtCore.QUrl) -> None:
|
|
167
|
+
export_to_png(data, pathlib.Path(path.toLocalFile()))
|
|
File without changes
|