Is it possible to rotate curves independently of their axis' values in a graph? I made this test file:
import pyqtgraph as pg
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QSlider
from PyQt5.QtCore import Qt, QPointF
from PyQt5.QtGui import QPen, QTransform
import numpy as np
class CurveViewer(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Curve Viewer")
self.central_widget = QWidget(self)
self.setCentralWidget(self.central_widget)
self.layout = QVBoxLayout(self.central_widget)
# Create a GraphicsLayoutWidget to hold the plot
self.graph_widget = pg.GraphicsLayoutWidget()
self.layout.addWidget(self.graph_widget)
# Create sliders for controlling the selected curve
self.slider_x = QSlider(Qt.Horizontal)
self.slider_x.setRange(-100, 100)
self.layout.addWidget(self.slider_x)
self.slider_y = QSlider(Qt.Vertical)
self.slider_y.setRange(-100, 100)
self.layout.addWidget(self.slider_y)
self.slider_rotation = QSlider(Qt.Horizontal)
self.slider_rotation.setRange(-180, 180) # Angle range in degrees
self.layout.addWidget(self.slider_rotation)
# Create a single PlotItem to hold all curves
self.plot_item = self.graph_widget.addPlot()
self.curves = []
self.curve_data = [] # Store initial curve data
self.offsets = [] # Store offsets for each curve
self.rotations = [] # Store rotations for each curve
# Create example plot data with multiple curves
num_curves = 3
for i in range(num_curves):
x_data = np.linspace(-2, 2, 100)
y_data = np.sin(x_data) + i
pen = pg.mkPen(color=pg.intColor(i), width=1.0)
curve_item = self.plot_item.plot(x_data, y_data, pen=pen, clickable=True)
curve_item.setCurveClickable(True)
curve_item.sigClicked.connect(self.__handle_curve_click)
self.curves.append(curve_item)
self.curve_data.append((x_data, y_data))
self.offsets.append((0, 0)) # Initialize offsets to zero
self.rotations.append(0) # Initialize rotations to zero
# Store the currently clicked curve
self.clicked_curve = None
# Copy the original data for all curves
self.original_curve_data = self.curve_data.copy()
# Connect slider valueChanged signals to slot functions
self.slider_x.valueChanged.connect(self.update_curve_position)
self.slider_y.valueChanged.connect(self.update_curve_position)
self.slider_rotation.valueChanged.connect(self.update_curve_rotation)
def __handle_curve_click(self, curve_item):
# Reset the pen properties of all curves to the default thickness
for curve in self.curves:
pen = curve.opts['pen']
pen = pg.mkPen(pen)
default_pen = pg.mkPen(color=pen.color(), width=1.0)
curve.setPen(default_pen)
# Modify the pen properties of the selected curve to make it thicker
pen = curve_item.opts['pen']
pen = pg.mkPen(pen)
selected_pen = pg.mkPen(color=pen.color(), width=2.0)
curve_item.setPen(selected_pen)
self.clicked_curve = curve_item
# Set slider values to match the current offset and rotation of the clicked curve
x_offset, y_offset = self.offsets[self.curves.index(self.clicked_curve)]
rotation = self.rotations[self.curves.index(self.clicked_curve)]
self.slider_x.setValue(int(x_offset))
self.slider_y.setValue(int(y_offset))
self.slider_rotation.setValue(int(rotation))
def update_curve_position(self):
if self.clicked_curve is not None:
x_offset = self.slider_x.value()
y_offset = self.slider_y.value()
index = self.curves.index(self.clicked_curve)
x_data, y_data = self.original_curve_data[index]
rotation_degrees = self.rotations[index]
# Update the offset for the clicked curve
self.offsets[index] = (x_offset, y_offset)
# Apply the rotation transformation first
rotation_matrix = QTransform().rotate(rotation_degrees)
rotated_x_data, rotated_y_data = [], []
for x, y in zip(x_data, y_data):
rotated_point = rotation_matrix.map(QPointF(x, y))
rotated_x_data.append(rotated_point.x())
rotated_y_data.append(rotated_point.y())
# Then apply the translation (offset) transformation
translated_x_data = [x + x_offset for x in rotated_x_data]
translated_y_data = [y + y_offset for y in rotated_y_data]
# Update the data for the clicked curve
self.clicked_curve.setData(translated_x_data, translated_y_data)
def update_curve_rotation(self):
if self.clicked_curve is not None:
rotation_degrees = self.slider_rotation.value()
index = self.curves.index(self.clicked_curve)
x_data, y_data = self.original_curve_data[index]
x_offset, y_offset = self.offsets[index]
# Update the rotation for the clicked curve
self.rotations[index] = rotation_degrees
# Apply both the translation (offset) and rotation transformations
rotation_matrix = QTransform().rotate(rotation_degrees)
rotated_x_data, rotated_y_data = [], []
for x, y in zip(x_data, y_data):
rotated_point = rotation_matrix.map(QPointF(x, y))
rotated_x_data.append(rotated_point.x())
rotated_y_data.append(rotated_point.y())
translated_x_data = [x + x_offset for x in rotated_x_data]
translated_y_data = [y + y_offset for y in rotated_y_data]
# Update the data for the clicked curve
self.clicked_curve.setData(translated_x_data, translated_y_data)
if __name__ == "__main__":
app = QApplication([])
viewer = CurveViewer()
viewer.show()
app.exec_()
In this test file I have 3 lines/curves which I can select. By selecting a line it gets thicker such that the user knows it's selected. Then I can move and rotate the selected curve with sliders. I didn't have an issue with rotating the curves in the test file because it was only integer values, but in my original program I have different values on the axis'.
Let's say I have a graph with y-axis range(1000, 6000) and x-axis range(0, 0.00008)
On x-axis I obviously have tiny values. The movement I fixed with scaling factors but I don't know how to solve the rotation. Also didn't find any proper solution in the web. So is it possible to just 'ignore' the values of the axis and still being able to rotate the curve?
You can scale the points by each axis' visible size before applying the rotation. This applies it in an artificial space where the screen is a square of side 1. We then rescale to your points' actual space before plotting them.
The effect of these transformations is to make the curves rotate onscreen without being deformed (to check it, you can manually unzoom then zoom, which will disable axis auto-scaling).
The code is below:
# Apply both the translation (offset) and rotation transformations
rotation_matrix = QTransform().rotate(rotation_degrees)
rotated_x_data, rotated_y_data = [], []
x_range, y_range = self.plot_item.getViewBox().viewRange()
x_size = x_range[1] - x_range[0]
y_size = y_range[1] - y_range[0]
for x, y in zip(x_data / x_size, y_data / y_size):
rotated_point = rotation_matrix.map(QPointF(x, y))
rotated_x_data.append(rotated_point.x() * x_size)
rotated_y_data.append(rotated_point.y() * y_size)
Note: This should probably be made into a function, as the code is currently duplicated for translation and rotation.