orbix.viz.anim#

Orbit animation: a ghost of the full path, a growing trail, a moving head.

Built on eyepiece.animate in update mode: the figure is drawn once (the full track as a faint ghost, via the static plot functions, so an animated figure’s ghost matches its still counterpart’s path), the animated artists are created empty, and each frame is a set_data – nothing is cleared and no artist is created inside the frame loop.

The history vocabulary carries over from the key-strategy idea in earlier orbit-animation code: "all" accumulates the trail from the first epoch, an int keeps a trailing window of that many frames, and "none" moves the head marker alone.

Functions#

_history_slice(i, history)

The [start, stop) trail slice for frame i under history.

_track_alphas(n_tracks, weights)

Per-track alpha for the animated artists: opaque, faded by weight.

_auto_camera(tracks, n_frames, roll_deg[, tilt_deg, ...])

A sweep that turns the orbit without changing how big it is drawn.

depth_size(depth)

Map a [0, 1] depth factor onto a marker-diameter multiplier.

depth_scale(positions, azim_deg, elev_deg)

Per-point head-marker scale in [0, 1] from the camera geometry.

animate_orbit(orbit_or_tracks[, t_jd, Ms_kg, dist_pc, ...])

Animate one or more orbits and return a lazy eyepiece.Animation.

_single(orbit_or_tracks)

Whether the input is a single track (K == 1), cheaply and safely.

Module Contents#

orbix.viz.anim._history_slice(i, history)[source]#

The [start, stop) trail slice for frame i under history.

orbix.viz.anim._track_alphas(n_tracks, weights)[source]#

Per-track alpha for the animated artists: opaque, faded by weight.

orbix.viz.anim._auto_camera(tracks, n_frames, roll_deg, tilt_deg=55.0, sweep_deg=40.0)[source]#

A sweep that turns the orbit without changing how big it is drawn.

Sweeping raw azimuth from wherever the axes happened to be pointing changes the projected area of a fixed orbit – from matplotlib’s default camera this orbit’s drawn area swings by a factor of six, which a reader reads as a change in the orbit. It is the same failure as an axis that rescales between frames, with a rotation matrix in front of it.

A camera on a cone about the orbit normal cannot do that: the projected area of a planar ellipse is pi * a * b * cos(tilt), so holding the tilt fixed holds the drawn size fixed exactly, and the sweep turns the ellipse in the page plane instead of inflating it. The parallax that makes the geometry legible is unchanged.

Parameters:
  • tracks – Positions, shape (K, T, 3), in the plot’s own units.

  • n_frames – Number of frames to generate angles for.

  • roll_deg – Camera roll, held fixed across the sweep.

  • tilt_deg – Angle between the camera and the orbit normal. Well away from 0 (face-on, where the depth cue flattens to nothing) and from 90 (edge-on, where the orbit collapses to a line).

  • sweep_deg – How far around the cone to travel.

Returns:

Dict with "azim", "elev" and "roll" arrays.

orbix.viz.anim.depth_size(depth)[source]#

Map a [0, 1] depth factor onto a marker-diameter multiplier.

The tuning is inherited from the original hand-tuned orbit renders: the marker’s scatter AREA grew additively by at most half its base on the near side, which in diameter terms is sqrt(1 + 0.5 * d) – a swell that peaks at about 22 percent. The far side is the anchor at exactly the base size, so the resting size stays free to encode physical meaning such as a planet radius; depth reads as a brief near-side swell, not a shrink.

Callers styling a scatter artist (whose s is an area) should square this factor to stay in area units.

orbix.viz.anim.depth_scale(positions, azim_deg, elev_deg)[source]#

Per-point head-marker scale in [0, 1] from the camera geometry.

The same viewer-angle cue eyepiece.trail bakes into its per-point markers – (1 + cos(angle)) / 2 between each position vector and the position-to-viewer vector, with the viewer far along the camera direction – but here it drives the one moving head marker instead of beads along the whole path: in an animation the head can carry the depth cue itself, so the path stays a clean line.

orbix.viz.anim.animate_orbit(orbit_or_tracks, t_jd=None, *, Ms_kg=None, dist_pc=None, trig_solver=None, kind='sky', history='all', rotate='auto', fps=10, style=None, base_ms=6.0, weights=None, data=None, iwa=None, marks=None)[source]#

Animate one or more orbits and return a lazy eyepiece.Animation.

Nothing renders until .save, .jshtml, or .video is called on the result, so one animation can go to several sinks in one pass and the test path never needs ffmpeg.

Parameters:
  • orbit_or_tracks – An AbstractOrbit, or bare tracks in the same forms the static functions accept ((ra, dec) arrays or (2, T) / (K, 2, T) for kind="sky"; (T, 3) / (K, T, 3) for kind="3d").

  • t_jd – Times in Julian Days, shape (T,). Required on the orbit door; on the bare-track door it is optional and used only for the elapsed-time label (length must match the track).

  • Ms_kg – Stellar mass in kg. Orbit door only.

  • dist_pc – Distance in parsecs. Orbit door with kind="sky" only.

  • trig_solver – Optional Kepler solver; None uses orbix’s default.

  • kind – "sky" (sky-plane arcsec, via plot_sky_track) or "3d" (star-centric AU, via plot_orbit).

  • history – "all" grows the trail from the first epoch, an int keeps a trailing window of that many frames, "none" moves the head marker alone.

  • rotate – Camera sweep, kind="3d" only. The default "auto" travels 40 degrees around a cone about the ORBIT NORMAL, at a fixed tilt to it, so azimuth and elevation both vary. That is deliberate: holding the tilt fixed holds the projected area pi * a * b * cos(tilt) fixed, where a plain azimuth sweep about z at held elevation changes the tilt and so swings the drawn size – by 5.7x on the three-planet system in the docs, which a reader misreads as the orbit growing. None holds the camera still. A dict maps any of "azim", "elev", "roll" to a (start_deg, stop_deg) pair interpolated linearly across the frames, for full control. The head’s depth cue tracks the moving camera frame by frame. Ignored for kind="sky" unless a dict is passed, which raises.

  • fps – Default playback rate carried by the returned animation.

  • style – A color or SourceStyles entry for the tracks, forwarded to the static function and used for the animated artists. For kind="3d", None gives the star-chart default: heads in the mode’s text color (white dots on a dark background) over transparent dashed gray trails; a style opts into that source’s solid color instead.

  • base_ms – Head-marker diameter in points, a scalar or one value per track. The base size is the anchor the depth cue swells around, so it is where physical meaning lives – pass size_by_radius(radii) to encode planet radii.

  • weights – Optional per-track weights (length K), fading both the ghost fan and the animated trails.

  • data – Optional (ra, dec, err) observed epochs (sky only).

  • iwa – Optional inner-working-angle disk radius (sky only).

  • marks – Optional {"periapsis", "nodes"} (3d orbit door only).

Returns:

An eyepiece.Animation bound to the built figure, with one frame per epoch of the track.

Raises:

ValueError – If kind or history is not one of the documented values, or if a labeled t_jd does not match the track length.

orbix.viz.anim._single(orbit_or_tracks)[source]#

Whether the input is a single track (K == 1), cheaply and safely.

Only tunes the ghost’s alpha, so an exotic AbstractOrbit without element arrays to inspect just keeps the fan default.