qplotter 1.6.0b3__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.
Files changed (119) hide show
  1. qplot/__init__.py +46 -0
  2. qplot/__main__.py +232 -0
  3. qplot/_auxiliary_paths.py +63 -0
  4. qplot/_metadata.py +12 -0
  5. qplot/_repair.py +14 -0
  6. qplot/_shutdown_supervisor.py +4049 -0
  7. qplot/_version.py +12 -0
  8. qplot/_windows_shutdown_job.py +1166 -0
  9. qplot/configuration/__init__.py +3 -0
  10. qplot/configuration/config.py +573 -0
  11. qplot/configuration/config_schema.json +319 -0
  12. qplot/configuration/scripts.py +255 -0
  13. qplot/configuration/themes/__init__.py +13 -0
  14. qplot/configuration/themes/_base.py +579 -0
  15. qplot/configuration/themes/blank.py +17 -0
  16. qplot/configuration/themes/dark.py +54 -0
  17. qplot/configuration/themes/light.py +54 -0
  18. qplot/datahandling/LoadFromDB.py +224 -0
  19. qplot/datahandling/__init__.py +47 -0
  20. qplot/datahandling/_trusted_live_helper.py +812 -0
  21. qplot/datahandling/_trusted_live_protocol.py +1745 -0
  22. qplot/datahandling/database.py +2330 -0
  23. qplot/datahandling/dimensions.py +43 -0
  24. qplot/datahandling/file_identity.py +461 -0
  25. qplot/datahandling/parameter_data.py +149 -0
  26. qplot/datahandling/plot_progress.py +17 -0
  27. qplot/datahandling/qcodes_cache.py +277 -0
  28. qplot/datahandling/qcodes_compat.py +14 -0
  29. qplot/datahandling/readSQL.py +2442 -0
  30. qplot/datahandling/readonly.py +2561 -0
  31. qplot/datahandling/source_activity.py +21 -0
  32. qplot/datahandling/trusted_array_preview.py +127 -0
  33. qplot/datahandling/trusted_derived_cache.py +1605 -0
  34. qplot/datahandling/trusted_derived_rendering.py +868 -0
  35. qplot/datahandling/trusted_heatmap.py +241 -0
  36. qplot/datahandling/trusted_live.py +2808 -0
  37. qplot/datahandling/trusted_live_queries.py +5907 -0
  38. qplot/datahandling/trusted_live_service.py +1894 -0
  39. qplot/datahandling/trusted_live_supervisor.py +2161 -0
  40. qplot/datahandling/trusted_plot.py +283 -0
  41. qplot/datahandling/trusted_presentation.py +1065 -0
  42. qplot/datahandling/trusted_snapshot.py +1132 -0
  43. qplot/datahandling/trusted_work_coordinator.py +2157 -0
  44. qplot/datahandling/trusted_work_scheduler.py +1204 -0
  45. qplot/diagnostics.py +241 -0
  46. qplot/resources/testdata/qplot_test_db_01_10mb.csv +11 -0
  47. qplot/resources/testdata/qplot_test_db_02_25mb.csv +14 -0
  48. qplot/resources/testdata/qplot_test_db_03_50mb.csv +17 -0
  49. qplot/resources/testdata/qplot_test_db_04_100mb.csv +20 -0
  50. qplot/resources/testdata/qplot_test_db_05_250mb.csv +23 -0
  51. qplot/resources/testdata/qplot_test_db_06_500mb.csv +26 -0
  52. qplot/resources/testdata/qplot_test_db_07_1gb.csv +29 -0
  53. qplot/resources/testdata/qplot_test_db_08_5gb.csv +32 -0
  54. qplot/resources/testdata/qplot_test_db_09_10gb.csv +35 -0
  55. qplot/resources/testdata/qplot_test_db_10_30gb.csv +38 -0
  56. qplot/testdata.py +1733 -0
  57. qplot/tools/__init__.py +13 -0
  58. qplot/tools/finite_means.py +114 -0
  59. qplot/tools/general.py +134 -0
  60. qplot/tools/heatmap_geometry.py +381 -0
  61. qplot/tools/operation_registry.py +179 -0
  62. qplot/tools/plot_tools.py +625 -0
  63. qplot/tools/sample_statistics.py +56 -0
  64. qplot/tools/worker.py +2787 -0
  65. qplot/windows/__init__.py +9 -0
  66. qplot/windows/_colorbar.py +422 -0
  67. qplot/windows/_commands.py +523 -0
  68. qplot/windows/_config_persistence.py +123 -0
  69. qplot/windows/_database_actions.py +4904 -0
  70. qplot/windows/_dataset_handle.py +221 -0
  71. qplot/windows/_dragdrop.py +71 -0
  72. qplot/windows/_export_paths.py +1167 -0
  73. qplot/windows/_help.py +169 -0
  74. qplot/windows/_native_averaging.py +173 -0
  75. qplot/windows/_native_transforms.py +389 -0
  76. qplot/windows/_plot1d_snap.py +608 -0
  77. qplot/windows/_plot1d_traces.py +2183 -0
  78. qplot/windows/_plot2d_colorbar.py +1126 -0
  79. qplot/windows/_plot2d_colorbar_dialog.py +786 -0
  80. qplot/windows/_plot2d_layers.py +2197 -0
  81. qplot/windows/_plot2d_sweeps.py +712 -0
  82. qplot/windows/_plotWin.py +1993 -0
  83. qplot/windows/_plot_actions.py +2695 -0
  84. qplot/windows/_plot_appearance.py +276 -0
  85. qplot/windows/_plot_axis_scaling.py +1485 -0
  86. qplot/windows/_plot_export.py +1445 -0
  87. qplot/windows/_plot_feedback.py +110 -0
  88. qplot/windows/_plot_marquee.py +656 -0
  89. qplot/windows/_plot_refresh.py +942 -0
  90. qplot/windows/_plot_state.py +239 -0
  91. qplot/windows/_plot_transform_labels.py +60 -0
  92. qplot/windows/_preferences.py +635 -0
  93. qplot/windows/_refresh_interval.py +70 -0
  94. qplot/windows/_run_controls.py +828 -0
  95. qplot/windows/_shortcuts.py +47 -0
  96. qplot/windows/_subplots/__init__.py +8 -0
  97. qplot/windows/_subplots/subplot1d.py +531 -0
  98. qplot/windows/_subplots/subplot2d.py +918 -0
  99. qplot/windows/_svg_export.py +209 -0
  100. qplot/windows/_trusted_derived_qt.py +1759 -0
  101. qplot/windows/_widgets/__init__.py +23 -0
  102. qplot/windows/_widgets/_run_formatting.py +387 -0
  103. qplot/windows/_widgets/details_tables.py +1119 -0
  104. qplot/windows/_widgets/dropbox.py +290 -0
  105. qplot/windows/_widgets/operations.py +460 -0
  106. qplot/windows/_widgets/preview.py +2997 -0
  107. qplot/windows/_widgets/run_list_items.py +488 -0
  108. qplot/windows/_widgets/toolbar.py +111 -0
  109. qplot/windows/_widgets/treeWidgets.py +2572 -0
  110. qplot/windows/_window_controls.py +413 -0
  111. qplot/windows/main.py +1635 -0
  112. qplot/windows/plot1d.py +311 -0
  113. qplot/windows/plot2d.py +1901 -0
  114. qplotter-1.6.0b3.dist-info/METADATA +318 -0
  115. qplotter-1.6.0b3.dist-info/RECORD +119 -0
  116. qplotter-1.6.0b3.dist-info/WHEEL +5 -0
  117. qplotter-1.6.0b3.dist-info/entry_points.txt +4 -0
  118. qplotter-1.6.0b3.dist-info/licenses/LICENSE +21 -0
  119. qplotter-1.6.0b3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,209 @@
1
+ """SVG export using Qt's SVG 1.1 paint engine, including native clipping.
2
+
3
+ PyQtGraph 0.14's SVG coordinate rewriter cannot read Qt 6.11's closed
4
+ paths. Keep Qt's geometry, transforms, clip paths and gradient definitions
5
+ instead of reconstructing the scene with that rewriter.
6
+ """
7
+
8
+ import math
9
+ import re
10
+ import xml.dom.minidom as xml
11
+ from typing import Any
12
+
13
+ from PyQt6 import QtCore, QtGui, QtSvg
14
+ from pyqtgraph.exporters import SVGExporter
15
+
16
+ _NUMBER = r"[-+]?(?:\d*\.\d+|\d+\.?\d*)(?:[eE][-+]?\d+)?"
17
+ _TOKEN = re.compile(rf"[A-Za-z]|{_NUMBER}")
18
+
19
+
20
+ def svg_bytes(exporter: SVGExporter) -> bytes:
21
+ """Render the selected scene rectangle with all SVG dialog options."""
22
+ width, height = (float(exporter.params[name]) for name in ("width", "height"))
23
+ if not all(math.isfinite(v) and 0 < v <= 2_147_483_647 for v in (width, height)):
24
+ raise ValueError("SVG dimensions must be finite, positive Qt device dimensions.")
25
+ source = QtCore.QRectF(exporter.getSourceRect())
26
+ if source.isEmpty():
27
+ raise ValueError("The selected SVG source rectangle is empty.")
28
+ target = QtCore.QRectF(0, 0, width, height)
29
+ data = QtCore.QByteArray()
30
+ buffer = QtCore.QBuffer(data)
31
+ if not buffer.open(QtCore.QIODevice.OpenModeFlag.WriteOnly):
32
+ raise RuntimeError("Could not open the SVG rendering buffer.")
33
+ # SVG Tiny (the generator default) does not support clipping. SVG 1.1
34
+ # does, including ViewBoxes, secondary axes and image/colorbar items.
35
+ generator = QtSvg.QSvgGenerator(QtSvg.QSvgGenerator.SvgVersion.Svg11)
36
+ generator.setOutputDevice(buffer)
37
+ generator.setSize(QtCore.QSize(math.ceil(width), math.ceil(height)))
38
+ generator.setViewBox(target)
39
+ generator.setResolution(96) # one SVG user unit is one CSS pixel
40
+ generator.setTitle("qPlot plot")
41
+ generator.setDescription("Exported from qPlot")
42
+ painter = QtGui.QPainter()
43
+ try:
44
+ if not painter.begin(generator):
45
+ raise RuntimeError("Could not start the SVG painter.")
46
+ painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing)
47
+ painter.setRenderHint(QtGui.QPainter.RenderHint.TextAntialiasing)
48
+ painter.fillRect(target, exporter.params["background"])
49
+ try:
50
+ exporter.setExportMode(True, {
51
+ "painter": painter,
52
+ "antialias": True,
53
+ "background": exporter.params["background"],
54
+ "resolutionScale": width / exporter.getTargetRect().width(),
55
+ })
56
+ exporter.getScene().render(
57
+ painter, target, source,
58
+ QtCore.Qt.AspectRatioMode.IgnoreAspectRatio,
59
+ )
60
+ finally:
61
+ exporter.setExportMode(False)
62
+ if not painter.end():
63
+ raise RuntimeError("Could not finish the SVG painter.")
64
+ finally:
65
+ if painter.isActive():
66
+ painter.end()
67
+ buffer.close()
68
+ result = bytes(data.data())
69
+ if exporter.params["scaling stroke"]:
70
+ result = _scaling_strokes(result)
71
+ return result
72
+
73
+
74
+ def write_svg_stage(filename: str, exporter: SVGExporter) -> bool:
75
+ """Write only the private staging path supplied by qPlot's transaction."""
76
+ data = svg_bytes(exporter)
77
+ with open(filename, "wb") as output:
78
+ output.write(data)
79
+ return True
80
+
81
+
82
+ def _matrix(transform: QtGui.QTransform) -> str:
83
+ return "matrix(" + ",".join(format(v, ".17g") for v in (
84
+ transform.m11(), transform.m12(), transform.m21(), transform.m22(),
85
+ transform.dx(), transform.dy(),
86
+ )) + ")"
87
+
88
+
89
+ def _transform(value: str) -> QtGui.QTransform:
90
+ if not value:
91
+ return QtGui.QTransform()
92
+ # Qt emits only matrix transforms. Reject unexpected output rather than
93
+ # publishing geometry with a partially interpreted transform.
94
+ match = re.fullmatch(r"matrix\(([^()]*)\)", value.strip())
95
+ if match is None:
96
+ raise ValueError("Unexpected Qt SVG transform.")
97
+ values = [float(v) for v in re.split(r"[\s,]+", match[1].strip())]
98
+ if len(values) != 6 or not all(map(math.isfinite, values)):
99
+ raise ValueError("Invalid Qt SVG transform.")
100
+ return QtGui.QTransform(*values)
101
+
102
+
103
+ def _map_pairs(values: list[str], transform: QtGui.QTransform) -> list[str]:
104
+ if len(values) % 2:
105
+ raise ValueError("Invalid Qt SVG coordinate pair.")
106
+ result = []
107
+ for index in range(0, len(values), 2):
108
+ point = transform.map(QtCore.QPointF(float(values[index]), float(values[index + 1])))
109
+ result.append(f"{point.x():.17g},{point.y():.17g}")
110
+ return result
111
+
112
+
113
+ def _map_path(value: str, transform: QtGui.QTransform) -> str:
114
+ """Map Qt's absolute M/L/C paths, preserving curves and closing Z commands.
115
+
116
+ Qt serializes QPainterPath with this subset of SVG commands (quadratics
117
+ become cubics). Tokenize SVG numbers, including exponents and adjacent
118
+ signed numbers; never assume whitespace-delimited tokens are pairs.
119
+ """
120
+ tokens = _TOKEN.findall(value)
121
+ if re.sub(r"[\s,]", "", _TOKEN.sub("", value)):
122
+ raise ValueError("Invalid Qt SVG path.")
123
+ result: list[str] = []
124
+ index = 0
125
+ while index < len(tokens):
126
+ command = tokens[index]
127
+ index += 1
128
+ if command == "Z":
129
+ result.append("Z")
130
+ continue
131
+ arity = {"M": 2, "L": 2, "C": 6}.get(command)
132
+ if arity is None:
133
+ raise ValueError(f"Unexpected Qt SVG path command: {command}")
134
+ end = index
135
+ while end < len(tokens) and not tokens[end].isalpha():
136
+ end += 1
137
+ values = tokens[index:end]
138
+ if not values or len(values) % arity:
139
+ raise ValueError("Invalid Qt SVG path coordinates.")
140
+ result.append(command + " ".join(_map_pairs(values, transform)))
141
+ index = end
142
+ return " ".join(result)
143
+
144
+
145
+ def _shape_path(node: Any) -> str:
146
+ """Convert Qt's rect/ellipse primitives to the same absolute path subset."""
147
+ path = QtGui.QPainterPath()
148
+ if node.tagName == "rect":
149
+ path.addRoundedRect(QtCore.QRectF(*(
150
+ float(node.getAttribute(key) or 0) for key in ("x", "y", "width", "height")
151
+ )), float(node.getAttribute("rx") or 0), float(node.getAttribute("ry") or 0))
152
+ else:
153
+ cx, cy = (float(node.getAttribute(key) or 0) for key in ("cx", "cy"))
154
+ rx = float(node.getAttribute("rx") or node.getAttribute("r"))
155
+ ry = float(node.getAttribute("ry") or node.getAttribute("r"))
156
+ path.addEllipse(QtCore.QRectF(cx - rx, cy - ry, 2 * rx, 2 * ry))
157
+ parts = []
158
+ for i in range(path.elementCount()):
159
+ element = path.elementAt(i)
160
+ command = {
161
+ QtGui.QPainterPath.ElementType.MoveToElement: "M",
162
+ QtGui.QPainterPath.ElementType.LineToElement: "L",
163
+ QtGui.QPainterPath.ElementType.CurveToElement: "C",
164
+ QtGui.QPainterPath.ElementType.CurveToDataElement: "",
165
+ }[element.type]
166
+ parts.append(f"{command}{element.x:.17g},{element.y:.17g}")
167
+ return " ".join(parts) + " Z"
168
+
169
+
170
+ def _scaling_strokes(data: bytes) -> bytes:
171
+ """Make cosmetic strokes scale on SVG resize, without changing geometry.
172
+
173
+ Simply removing vector-effect would multiply widths by the data-to-view
174
+ transform (often anisotropic). Move those primitives to document coordinates
175
+ first, cancelling their inherited transform. Leave noncosmetic pens, text,
176
+ images, gradients and clipping definitions as Qt generated them.
177
+ """
178
+ document = xml.parseString(data)
179
+
180
+ def visit(node: Any, parent_transform: QtGui.QTransform) -> None:
181
+ transform = _transform(node.getAttribute("transform")) * parent_transform
182
+ if node.getAttribute("vector-effect") == "non-scaling-stroke":
183
+ inverse, invertible = parent_transform.inverted()
184
+ if not invertible:
185
+ raise ValueError("Cannot scale strokes under a singular SVG transform.")
186
+ if node.tagName in ("polyline", "polygon"):
187
+ node.setAttribute("points", " ".join(_map_pairs(
188
+ re.findall(_NUMBER, node.getAttribute("points")), transform,
189
+ )))
190
+ elif node.tagName in ("path", "rect", "ellipse", "circle"):
191
+ value = node.getAttribute("d") if node.tagName == "path" else _shape_path(node)
192
+ node.tagName = "path"
193
+ node.setAttribute("d", _map_path(value, transform))
194
+ for name in ("x", "y", "width", "height", "rx", "ry", "cx", "cy", "r"):
195
+ if node.hasAttribute(name):
196
+ node.removeAttribute(name)
197
+ else:
198
+ raise ValueError(f"Unexpected Qt SVG cosmetic primitive: {node.tagName}")
199
+ node.removeAttribute("vector-effect")
200
+ node.setAttribute("transform", _matrix(inverse))
201
+ for child in node.childNodes:
202
+ if isinstance(child, xml.Element) and child.tagName != "defs":
203
+ visit(child, transform)
204
+
205
+ try:
206
+ visit(document.documentElement, QtGui.QTransform())
207
+ return document.toxml(encoding="utf-8")
208
+ finally:
209
+ document.unlink()