orbix.viz.orbit
===============

.. py:module:: orbix.viz.orbit

.. autoapi-nested-parse::

   Sky-plane and 3D orbit plots: extract, delegate to eyepiece, decorate.

   orbix supplies only what eyepiece cannot know: how to turn an orbit into
   tracks (propagation, projection, units), which axis is RA (and that
   astronomers draw it increasing to the left), and where periapsis and the
   nodes sit. The rendering itself is eyepiece's ``sky_fan`` and ``trail``,
   so figures drawn here match every other figure built on eyepiece.

   Every function that accepts an orbit also accepts bare track arrays, for
   consumers holding precomputed coordinates (a CSV of posterior traces, a
   cached ephemeris) rather than a live ``KeplerianOrbit``. On the bare-array
   door the units belong to the caller, so no axis labels are set.



Functions
---------

.. autoapisummary::

   orbix.viz.orbit._resolve_color
   orbix.viz.orbit._neutral
   orbix.viz.orbit._orbit_look
   orbix.viz.orbit.size_by_radius
   orbix.viz.orbit._per_track
   orbix.viz.orbit._sky_tracks
   orbix.viz.orbit.plot_sky_track
   orbix.viz.orbit._positions
   orbix.viz.orbit._nu_to_trig_E
   orbix.viz.orbit._mark_geometry
   orbix.viz.orbit.plot_orbit


Module Contents
---------------

.. py:function:: _resolve_color(ep, style)

   Resolve ``style`` (color, SourceStyles entry, or None) to a color.

   None resolves to the active palette's first color through the public
   ``SourceStyles`` mechanism, at call time, so a mode switch between two
   calls is honored.


.. py:function:: _neutral(level)

   A tone ``level`` of the way from the axes facecolor to the text color.

   Resolved from the active rcParams at call time, so the same level is a
   light gray on a dark background and a dark gray on a light one -- the
   scenery inverts with the mode instead of freezing one gray for both.


.. py:function:: _orbit_look(style, ep, depth)

   The 3D orbit rendering defaults: marker color and path-line kwargs.

   With no ``style`` and ``depth="markers"``, the look is the classic
   star-chart one: markers in the mode's text color (white dots on a dark
   background) over a transparent dashed gray path. Under any other depth
   cue there are no markers, so the path itself has to be the visible
   element and takes the text color rather than a dim gray -- otherwise an
   unstyled orbit renders as a barely-there gray line. A ``style`` opts out
   into that source's color either way.


.. py:function:: size_by_radius(radius_Rearth, *, ms_range=(3.0, 9.0), radius_range_Rearth=(0.38, 11.2))

   Marker diameters encoding planet radii, geometrically interpolated.

   The base marker size is the anchor the depth cue swells around, so it
   is the place physical meaning lives. This maps radii onto diameters
   the same way the original hand-tuned renders mapped mass onto marker
   size: geometrically between ``ms_range`` across
   ``radius_range_Rearth`` (Mercury to Jupiter by default), so a
   super-Earth reads visibly larger than a sub-Earth without Jupiter
   dwarfing everything. Radii outside the range clip to its ends.

   The result is a set of marker DIAMETERS in points, which is what
   ``animate_orbit(base_ms=...)`` takes (matplotlib's ``ms``). Do NOT pass
   it to ``plot_orbit(marker_scale=...)``: that reaches ``scatter(s=...)``,
   an AREA in points squared, so the diameters would be read as areas and
   the encoding silently square-rooted -- an 11.2-Earth-radius planet drawn
   1.48x an Earth instead of 2.19x. Square the result first if you need an
   area.

   :param radius_Rearth: Planet radii in Earth radii, scalar or ``(K,)``.
   :param ms_range: Marker diameters in points at the two ends of the
                    radius range.
   :param radius_range_Rearth: The radii mapped onto ``ms_range``'s ends.

   :returns: Marker diameters in points, shape ``(K,)``.


.. py:function:: _per_track(values, n_tracks, name)

   Broadcast a scalar or length-K sequence to one float per track.


.. py:function:: _sky_tracks(orbit_or_radec, t_jd, Ms_kg, dist_pc, trig_solver)

   Normalize the input to ``(ra, dec)`` arrays of shape ``(K, T)``.

   :returns: ``(ra, dec, from_orbit)`` where ``from_orbit`` records whether the
             tracks were propagated here (and are therefore known to be in
             arcsec) or handed in raw.


.. py:function:: plot_sky_track(orbit_or_radec, t_jd=None, *, Ms_kg=None, dist_pc=None, trig_solver=None, ax=None, style=None, colors=None, weights=None, data=None, iwa=None, invert_ra=True, fan_kw=None)

   Draw sky-plane orbit tracks: one orbit, or a fan of K candidates.

   A ``(K,)``-batched orbit (posterior draws through
   ``KeplerianOrbit.from_period``, for instance) becomes a fan of K
   tracks faded by ``weights``; a single orbit becomes one solid track.
   Delegation is to ``eyepiece.sky_fan``, which owns the equal aspect,
   the central-star marker, the optional inner-working-angle disk, and
   the optional observed-epoch errorbars.

   :param orbit_or_radec: An ``AbstractOrbit`` (propagated here, requiring
                          ``t_jd``, ``Ms_kg``, ``dist_pc``), or bare tracks: an
                          ``(ra, dec)`` pair of ``(T,)`` or ``(K, T)`` arrays, or an
                          array shaped ``(2, T)`` or ``(K, 2, T)``. Bare tracks are in
                          whatever units the caller made them, so no axis labels are
                          set on that door.
   :param t_jd: Times in Julian Days, shape ``(T,)``. Orbit door only.
   :param Ms_kg: Stellar mass in kg. Orbit door only.
   :param dist_pc: Distance to the star in parsecs. Orbit door only.
   :param trig_solver: Optional Kepler solver forwarded to the orbit; None
                       uses orbix's default.
   :param ax: Axes to draw into. None creates a new figure and axes.
   :param style: A color, or a ``SourceStyles`` entry, applied to every
                 track -- a fan of candidates for one planet is one source, so
                 it takes one color rather than cycling the palette. None uses
                 the active palette's first color.
   :param colors: Optional per-track color list (length K), for a fan whose
                  tracks carry distinct meaning (period-alias families, for
                  instance). Overrides ``style`` for the track colors.
   :param weights: Optional per-track weights (length K), fading each
                   track's alpha; typically posterior mass per draw.
   :param data: Optional ``(ra, dec, err)`` tuple of observed epochs, drawn
                as errorbars by ``sky_fan``.
   :param iwa: Optional inner-working-angle radius, in the same units as
               the tracks (arcsec on the orbit door), drawn as a shaded disk.
   :param invert_ra: Invert the x axis so RA offset increases to the left
                     (the astronomer's convention; orbix's own frame maps x to RA
                     with no flip). An already-inverted axis is left alone, so
                     overplotting onto the same axes does not flip it back.
   :param fan_kw: Extra kwargs for each track's ``ax.plot`` call, routed
                  through ``sky_fan`` and applied last. For a single track the
                  base alpha defaults to 0.75 (a solid line) instead of
                  ``sky_fan``'s fan default.

   :returns: artists ``"lines"``
             (one ``Line2D`` per track), plus ``"ellipse"`` when ``iwa`` is
             given and ``"collection"`` when ``data`` is given.
   :rtype: The ``eyepiece.PlotResult`` from ``sky_fan``


.. py:function:: _positions(orbit_or_xyz, t_jd, Ms_kg, trig_solver)

   Normalize the input to positions of shape ``(K, T, 3)``.

   :returns: ``(positions, orbit_or_none)`` -- the orbit comes back so exact
             mark geometry can be computed from its elements rather than
             re-derived from the sampled track.


.. py:function:: _nu_to_trig_E(nu, e)

   Convert a true anomaly to ``(sinE, cosE)`` of the eccentric anomaly.


.. py:function:: _mark_geometry(orbit)

   Exact periapsis and node positions per orbit, from the elements.

   Positions come through the same ``A (cosE - e) + B sinE`` propagation
   form the orbit itself uses, so a mark sits exactly on the drawn track
   rather than on a re-derived approximation. The ascending node is
   identified analytically: z along the orbit is
   ``A_z (cosE - e) + B_z sinE``, so its derivative in E is
   ``-A_z sinE + B_z cosE`` and the node with a positive derivative is
   ascending (E increases monotonically with time).

   :returns: ``(periapsis, ascending, descending)``, each ``(K, 3)`` in AU.


.. py:function:: plot_orbit(orbit_or_xyz, t_jd=None, *, Ms_kg=None, trig_solver=None, ax=None, style=None, marks=None, depth=None, marker_scale=25.0, trail_kw=None)

   Draw one or more orbits in 3D, star-centric AU, via ``eyepiece.trail``.

   The trajectory rendering -- connected path, depth-cued marker sizes,
   camera-aware layering -- is ``trail``'s; orbix adds the propagation,
   the star at the origin, symmetric axis limits so the orbit is not
   distorted, AU labels, and the optional exact periapsis/node marks.

   ``trail`` bakes its depth cues from the camera at call time, so set
   the view first (``ax.view_init(...)`` before calling this function)
   and keep the camera well off the orbit normal -- a near-face-on view
   collapses the marker-size depth cue.

   :param orbit_or_xyz: An ``AbstractOrbit`` (propagated here, requiring
                        ``t_jd`` and ``Ms_kg``), or bare positions shaped ``(T, 3)``
                        or ``(K, T, 3)``. Bare positions are in whatever units the
                        caller made them, so no axis labels are set on that door.
   :param t_jd: Times in Julian Days, shape ``(T,)``. Orbit door only.
   :param Ms_kg: Stellar mass in kg. Orbit door only.
   :param trig_solver: Optional Kepler solver forwarded to the orbit; None
                       uses orbix's default.
   :param ax: A ``projection="3d"`` axes to draw into. None creates one.
   :param style: A color, or a ``SourceStyles`` entry (which also sets the
                 track marker), applied to every track: solid path and markers
                 in that color. None gives the star-chart default -- markers in
                 the mode's text color (white dots on a dark background) over
                 a transparent dashed gray path.
   :param marks: Optional set drawn from ``{"periapsis", "nodes"}``. Orbit
                 door only -- exact mark geometry needs the elements, so bare
                 xyz tracks raise if marks are requested. Periapsis is a
                 diamond in the track color; the nodes are up/down triangles
                 joined by a dashed line of nodes through the origin.
   :param depth: Forwarded to ``eyepiece.trail``: how the path shows which
                 half faces the camera. ``None`` takes trail's own default,
                 the hidden-line convention -- the whole orbit dashed and dim
                 with the near half overdrawn solid. ``"markers"`` restores the
                 older per-point markers, which also restores the star-chart
                 look when no ``style`` is given. ``"none"`` drops the cue.
   :param marker_scale: Forwarded to ``trail``: marker AREA in points
                        squared (matplotlib's ``scatter(s=)``) at full illumination,
                        for the per-point depth cue a still figure needs. Pass ``0.0``
                        for a bare line -- an animation does this, since its moving
                        head carries the depth cue instead. Note the unit: this is an
                        area, while ``size_by_radius`` returns diameters, so the two
                        do not compose directly. Note also that ``trail``'s still
                        depth law takes marker area to zero on the far side, so a
                        per-track ``marker_scale`` encoding a physical quantity is
                        unrecoverable there; encode physical size on an animation's
                        ``base_ms`` instead, whose law is anchored at the base size.
   :param trail_kw: Extra kwargs for the connecting-line ``ax.plot`` call,
                    forwarded to ``trail`` and applied last.

   :returns: An ``eyepiece.PlotResult``. For a single track the artists are
             ``trail``'s ``"line"`` and ``"scatter"``; for K tracks they are
             ``"lines"`` and ``"scatter"`` lists in track order. ``"scatter"``
             holds one depth-cue artist per track, whichever the mode drew: a
             ``PathCollection`` under ``depth="markers"``, the solid near-half
             ``Line3D`` under the default hidden-line cue, and nothing at all
             under ``depth="none"``. Mark artists are appended to ``"scatter"``
             (periapsis first, then nodes) and the line of nodes to ``"lines"``,
             after the per-track entries.


