Search code examples
pythonpyqtgraph

How to plot the Earth orbit with pyqtgraph?


Say, I have a set of data of consists of the time sequence and the coordinates of X, Y, Z thousands points (sample data as below). I want to plot the data in 3D, in a way like the Earth orbiting the Sun, or the Earth orbiting the Sun with a trail of 10 faded Earth.

By following the 3D example code, currently I am able to plot all points together with fading at the same time. But I don't know how to iterate the points and update the plot with the timer start/stop properly.

(orbit.csv)

TimeDiff,X,Y,Z
0.0,0.00,0.27,-8.28
0.04,-0.96,0.27,-11.46
0.17,-0.96,-0.18,-13.81
0.36,-2.69,-0.18,-16.34
0.5,-2.69,-0.77,-17.69
0.73,-4.82,-0.77,-19.20
0.83,-4.82,-0.99,-19.43
1.04,-6.10,-0.99,-19.68
1.18,-6.10,-0.84,-18.87
1.39,-4.20,-0.84,-17.49
1.5,-4.20,-0.65,-15.63
1.72,-2.09,-0.65,-13.38
1.83,-2.09,-0.31,-10.49
2.05,-0.92,-0.31,-7.47
2.18,-0.92,0.09,-4.10
2.36,-0.12,0.09,-0.58
2.52,-0.12,0.20,2.71
2.7,-0.05,0.20,6.11
2.86,-0.05,0.11,9.12
3.03,-0.89,0.11,12.30
3.18,-0.89,-0.29,14.46

or in markdown format:

| Time     | X     | Y     | Z      |
| -------- | ----- | ----- | ------ |
| 0        | 0     | 0.27  | -8.28  |
| 0.04     | -0.96 | 0.27  | -11.46 |
| 0.17     | -0.96 | -0.18 | -13.81 |
| 0.36     | -2.69 | -0.18 | -16.34 |
| 0.5      | -2.69 | -0.77 | -17.69 |
| 0.73     | -4.82 | -0.77 | -19.2  |
| 0.83     | -4.82 | -0.99 | -19.43 |
| 1.04     | -6.1  | -0.99 | -19.68 |
| 1.18     | -6.1  | -0.84 | -18.87 |
| 1.39     | -4.2  | -0.84 | -17.49 |
| 1.5      | -4.2  | -0.65 | -15.63 |
| 1.72     | -2.09 | -0.65 | -13.38 |
| 1.83     | -2.09 | -0.31 | -10.49 |
| 2.05     | -0.92 | -0.31 | -7.47  |
| 2.18     | -0.92 | 0.09  | -4.1   |
| 2.36     | -0.12 | 0.09  | -0.58  |
| 2.52     | -0.12 | 0.2   | 2.71   |

enter image description here

from pyqtgraph.Qt import QtCore, QtGui, QtWidgets
import pyqtgraph.opengl as gl
from pyqtgraph import functions as fn
import numpy as np
import pandas as pd
import os


app = QtWidgets.QApplication([])
w = gl.GLViewWidget()
w.opts['distance'] = 500
w.show()
w.setWindowTitle('pyqtgraph example: GLVolumeItem')

ax = gl.GLAxisItem()
ax.setSize(900, 900, 900)
w.addItem(ax)

f = "Orbit.csv"
df = pd.read_csv(f)
timeList = df['TimeDiff'].tolist()
comp_x = df["X"].tolist()
comp_y = df["Y"].tolist()
comp_z = df["Z"].tolist()

w.setWindowTitle('pyqtgraph example: DateAxisItem')

speed = 1
measureTimeList = [x / speed for x in timeList]

line_x = [x for x in comp_x]
line_y = [x for x in comp_y]
line_z = [x for x in comp_z]

pos = np.empty((len(line_x), 3))
size = np.empty(len(line_x))
color = np.empty((len(line_x), 4))

pos[2] = (0, 0, 1);
size[2] = 1. / 1.0;
color[2] = (0.0, 1.0, 0.0, 1)


def update(): ## update volume colors
    global phase, sp2, d2
    s = -np.cos(d2*2+phase)
    color = np.empty((len(line_x), 4), dtype=np.float32)
    color[:, 3] = fn.clip_array(s * 0.1, 0., 1.)
    color[:, 0] = fn.clip_array(s * 3.0, 0., 1.)
    color[:, 1] = fn.clip_array(s * 1.0, 0., 1.)
    color[:, 2] = fn.clip_array(s ** 3, 0., 1.)
    sp2.setData(color=color)
    phase -= 0.1

t = QtCore.QTimer()
t.timeout.connect(update)
t.start(20)


color = np.ones((pos.shape[0], 4))
for i in range(len(line_x)):
    pos[i] = (line_z[i], line_x[i], line_y[i])

d2 = 2
phase = 90.
size = 2
s = -np.cos(d2 * 2 + phase)
color[:, 3] = fn.clip_array(s * 0.1, 0., 1.)
color[:, 0] = fn.clip_array(s * 3.0, 0., 1.)
color[:, 1] = fn.clip_array(s * 1.0, 0., 1.)
color[:, 2] = fn.clip_array(s ** 3, 0., 1.)
sp2 = gl.GLScatterPlotItem(pos=pos, color=(1, 1, 1, 1), size=size)
w.addItem(sp2)


## Start Qt event loop unless running in interactive mode.
if __name__ == '__main__':
    import sys

    if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
        QtWidgets.QApplication.instance().exec_()

Solution

  • Unfortunatly, I do not see an orbit in your data. Nevertheless, here's my approach:

    Set all colors to fully transparent. Then create a "transparency ramp" from 0 to 1 and let this ramp "roll" over your positions for each update. This way, it looks like a moving fading object:

    enter image description here

    from itertools import cycle
    
    import pyqtgraph.opengl as gl
    import numpy as np
    import pandas as pd
    
    import pyqtgraph as pg
    from PySide6.QtTest import QTest
    
    app = pg.mkQApp()
    
    w = gl.GLViewWidget()
    w.opts["distance"] = 100
    w.show()
    
    ax = gl.GLAxisItem()
    ax.setSize(900, 900, 900)
    w.addItem(ax)
    
    df = pd.read_csv("Orbit.csv")
    
    pos = [(z, x, y) for z, x, y in zip(df["Z"], df["X"], df["Y"])]
    
    sp2 = gl.GLScatterPlotItem(pos=pos, color=(1, 1, 1, 1), size=5)
    w.addItem(sp2)
    
    delta_times = np.diff(df["TimeDiff"])
    # repeat the last time step for loop-around
    delta_times = np.append(delta_times, delta_times[-1])
    
    # create transparency ramp
    n_display = 6
    color = np.ones((sp2.pos.shape[0], 4), dtype=np.float32)
    color[:, 3] = 0
    color[: n_display + 1, 3] = np.linspace(0, 1, n_display + 1)
    
    # endless cycle over delta times
    for dt in cycle(delta_times):
        # move the ramp over the array by one
        color = np.roll(color, 1, axis=0)
        sp2.setData(color=color)
        # delay application
        QTest.qWait(int(dt * 1000))