Source code for opengnc.visualization.attitude


import numpy as np
import plotly.graph_objects as go


[docs] def plot_attitude_sphere( vectors: np.ndarray | list[list[float]], title: str = "Attitude Sphere Visualization" ) -> go.Figure: r""" Directional Attitude Visualization. Projects vectors onto a unit sphere shell ($\|\mathbf{v}\| = 1$). Parameters ---------- vectors : np.ndarray | list[list[float]] Sequence of 3D pointing vectors $(N, 3)$. title : str, optional Plot heading. Returns ------- go.Figure Plotly 3D unit sphere object. """ v_val = np.asarray(vectors) if v_val.ndim != 2 or v_val.shape[1] != 3: raise ValueError("vectors must be a 2D array of shape (N, 3)") # Normalize to unit shell norms = np.linalg.norm(v_val, axis=1, keepdims=True) norms[norms < 1e-12] = 1.0 # Singularity guard v_norm = v_val / norms fig = go.Figure() # 1. Reference Unit Sphere u = np.linspace(0, 2 * np.pi, 40) v = np.linspace(0, np.pi, 40) x = np.outer(np.cos(u), np.sin(v)) y = np.outer(np.sin(u), np.sin(v)) z = np.outer(np.ones(np.size(u)), np.cos(v)) fig.add_trace( go.Surface( x=x, y=y, z=z, colorscale="Greys", opacity=0.15, showscale=False, name="Unit Sphere", hoverinfo="skip", ) ) # 2. Sequential Trace fig.add_trace( go.Scatter3d( x=v_norm[:, 0], y=v_norm[:, 1], z=v_norm[:, 2], mode="lines", line=dict(color="blue", width=4), name="Pointing Trace", ) ) # 3. Radial Pointer (Current State) curr = v_norm[-1] fig.add_trace( go.Scatter3d( x=[0, curr[0]], y=[0, curr[1]], z=[0, curr[2]], mode="lines+markers", line=dict(color="red", width=5), marker=dict(size=4), name="Current Vector", ) ) # 4. Landmarks fig.add_trace( go.Scatter3d( x=[v_norm[0, 0]], y=[v_norm[0, 1]], z=[v_norm[0, 2]], mode="markers", marker=dict(size=6, color="green"), name="Start", ) ) # 5. Scene constraints fig.update_layout( title=title, scene=dict( xaxis=dict(title="X", range=[-1.1, 1.1], gridcolor="lightgray"), yaxis=dict(title="Y", range=[-1.1, 1.1], gridcolor="lightgray"), zaxis=dict(title="Z", range=[-1.1, 1.1], gridcolor="lightgray"), aspectmode="manual", aspectratio=dict(x=1, y=1, z=1), ), margin=dict(r=10, l=10, b=10, t=40), ) return fig