Skip to content

Plot refactor#415

Open
eshelDror wants to merge 26 commits into
sblunt:mainfrom
eshelDror:plot_refactor
Open

Plot refactor#415
eshelDror wants to merge 26 commits into
sblunt:mainfrom
eshelDror:plot_refactor

Conversation

@eshelDror

Copy link
Copy Markdown
Collaborator

No description provided.

@eshelDror
eshelDror marked this pull request as ready for review July 19, 2026 19:32
Comment thread orbitize/plot.py
cmap = colors.LinearSegmentedColormap.from_list(
"trunc({n},{a:.2f},{b:.2f})".format(n=cmap.name, a=0.0, b=0.7),
cmap(np.linspace(0.0, 0.7, 1000)),
)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to define cmap here and below? Can we reduce it to just one def, either at the top here or in the Plotter class?

Comment thread orbitize/plot.py
def __init__(
self,
results,
object_to_plot=1, # TODO: Support multiplanet

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noting that we should resolve this todo before merge (I'm sure this is already on your radar).

Comment thread orbitize/plot.py
self.results = results
self.system = results.system

if default_cmap is not None:

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure the logic works here to define the colormap as we expect

Comment thread orbitize/plot.py
self.cmap = default_cmap

if start is None:
start = getattr(Time(np.min(self.system.data_table['epoch'])-365*3, format="mjd"), time_format)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm tempted to automatically set the start and end times for each dataset being plotted, and asking the user to manually override these after the Figure object is returned. If we make this change, we should include an example of manually setting axis limits in the plotting tutorial. Also, if there is no data, but the user still wants to plot the prediction, we can use the "first epoch (of any kind) - 3 years" as the start date, and analagous for end date.

Comment thread orbitize/plot.py
cbar_param=None,
rv_time_series=None,
rv_time_series2=None,
):

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add docstring

Comment thread orbitize/plot.py
)


def legacy_plot_orbits(

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decided you can remove the legacy code. It will be in the git history if we want to refer back to it.

Comment thread orbitize/plot.py
fig=None,
):
# TODO: Improve message
print("The orbitize.plot.plot_orbits function is deprecated. Instead, initialize an orbitize.plot.Plotter and use the plot_orbits method.")

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice. Just writing down what we talked about over zoom for posterity that we should raise a DeprecationWarning (or similar) here

Comment thread orbitize/plot.py
self.sep_data, self.sep_err, self.pa_data, self.pa_err, self.ra_data, self.ra_err, self.dec_data, self.dec_err = self._calc_seppa_radec(self.data)
self.astr_raoff, self.astr_deoff, self.astr_vz, self.astr_epochs = self._calc_astr_orbits(self.standard_post, self.num_orbits_to_plot, self.object_to_plot)

def _calc_seppa_radec(self, all_data):

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add docstrings for all these private functions

Comment thread orbitize/plot.py
self.astr_raoff, self.astr_deoff, self.astr_vz, self.astr_epochs = self._calc_astr_orbits(self.standard_post, self.num_orbits_to_plot, self.object_to_plot)

def _calc_seppa_radec(self, all_data):
data = all_data[all_data["quant_type"]!="rv"]

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you calculate all of these data type masks outside of these private methods? It would be nice to see them in one place (maybe in set_params above)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I disagree with myself now that I went through the whole refactor. I like it as is.

Comment thread orbitize/plot.py
# TODO: vectorize
for i in np.arange(num_orbits_to_plot):
# Calculate ra/dec offsets for all epochs of this orbit
raoff0, deoff0, vz0 = self.system.compute_all_orbits(

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note we just changed the return type of compute_all_orbits in v3.4.0, so you will need to add raoff0, deoff0, vz0, _ here (and elsewhere this method is called)

Comment thread orbitize/plot.py

return raoff, deoff, vz, astr_epochs

def _calc_rv(self, object):

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should change object to something else since object is a special variable in python. It should work fine as is but might be confusing.

Comment thread orbitize/plot.py
return rv_data, insts, gams, labels, gam_idx, inds, sig_idx

def _get_standard_post(self, num_orbits_to_plot):
# TODO: Replace random with results.downsample

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good call to replace with results.downsample!

Comment thread orbitize/plot.py
plot_astrometry_insts=False,
rv_time_series=False,
rv_time_series2=False,
# plot_errorbars=True,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove commented out code

Comment thread orbitize/plot.py
warnings.simplefilter("ignore", ErfaWarning)

# Before starting to plot rv data, make sure rv data exists:
rv_indices = np.where(self.data["quant_type"] == "rv")

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we replace this bit of code with the self.rv_data variable you defined above?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I think we should raise this warning in self.set_params.

Comment thread orbitize/plot.py
else:
ax = plt.subplot2grid((2, 14), (0, 0), rowspan=2, colspan=6)

astr_inds = np.where((~np.isnan(self.data["quant1"])) & (~np.isnan(self.data["quant2"])))

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use the variables you defined above rather than recalculating this mapping here.

Comment thread orbitize/plot.py

# For plotting different astrometry instruments
if plot_astrometry_insts:
astr_colors = ("#FF7F11", "#11FFE3", "#14FF11", "#7A11FF", "#FF1919")

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

define these in the same place you define the RV data colors and symbols above.

Comment thread orbitize/plot.py

return fig

def _plot_full_orbits(self, ax, plot_astrometry, square_plot, fontsize, cmap, plot_astrometry_insts, astr_insts=None, astr_inst_inds=None, astr_colors=None, astr_symbols=None):

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest putting all the private methods together in the beginning

Comment thread orbitize/plot.py
plt.sca(ax3)

# scale back to primary RV semi amplitude
vz0 = self.vz1[i] * (-(mtot[i] - m0[i]) / np.median(m0[i])) # TODO: vectorize

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should use the output of calc_all_orbits here rather than doing the math ourselves

Comment thread orbitize/plot.py
else:
plt.sca(ax3)

# scale back to primary RV semi amplitude

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this comment isn't relevant anymore; we can remove it

Comment thread orbitize/plot.py
plt.plot(yr_epochs, pas, color=sep_pa_color)

def _plot_rv_data(self, ax3, ax4, rv_time_series, rv_time_series2, sep_pa_color):
# plot RV orbits here

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's call these _plot_rv_model, _plot_sep_pa_model, etc, since the data is being plotted in a separate place

Comment thread orbitize/plot.py
epochs = inst_data["epoch"]
epochs = Time(epochs, format="mjd").decimalyear
inst_err = inst_data["quant1_err"]
gam_err2 = np.sqrt(np.square(inst_err)+np.square(med_sigma[i]))

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these (gam_err1, gam_err2, gam_err_2) could use more informative variable names

Comment thread orbitize/plot.py
s=30,
marker=next(ax3_symbols),
c=c,
label=name,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the name is "defrv" (our default when the user doesn't provide an instrument name) we should make the label something more informative like "RV data"

Comment thread orbitize/plot.py
epochs = Time(epochs, format="mjd").decimalyear
inst_err = inst_data["quant1_err"]
gam_err2 = np.sqrt(np.square(inst_err)+np.square(med_sigma[i]))
gam_err_1 = np.sqrt(gam_err2)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

subtle point here-- each orbit may have its own fitted value of jitter (sigma). We want to use that value, rather than the median across all orbits.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I think gam_err_1 is unneeded. Below, gam_err_2 should be np.sqrt(gam_err2**2 + stddev_ga[i]**2)

Comment thread orbitize/plot.py
plt.errorbar(
x=epochs2,
y=rvs2-med_ga2[i],
yerr=stddev_ga2[i],

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice job working through all the differences here!

Comment thread orbitize/plot.py
ecolor=c,
zorder=5,
ls="none",
label="{} gamma err".format(name2)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd probably suggest writing these captions out in latex to make them as clear as possible: std(rv_{inst name} - gamma_{inst name}); sigma_{inst name}2 + sigma2 (and same for the other case below)

Comment thread orbitize/plot.py
deoff = self.astr_deoff
seps = []
pas = []
raoff_100 = self.raoff1

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use your defined variables throughout rather than redefining them (for clarity)

Comment thread orbitize/plot.py

# for j in range(len(astr_epochs)):

# seps0, pas0 = orbitize.system.radec2seppa(raoff[i][j], deoff[i][j], mod180=mod180)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove commented out code

Comment thread orbitize/plot.py
arcs with PAs that cross 360 deg during observations (default: False)

Return:
``matplotlib.pyplot.Figure``: the residual plots

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use the same logic for plotting residuals as we used above for plotting the gammas for RVs. Offer two options for plotting the uncertainties, exactly the same way you did above.

Comment thread orbitize/plot.py
timestep = epoch_in_yr[1] - epoch_in_yr[0]
# dra/dt and ddec/dt
ddec_b = np.gradient(deoff[i, :], timestep) # in mas/yr
dec_b_radian = (

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's use astropy.units to do these conversions for clarity

@sblunt

sblunt commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Looks great! Excellent job @eshelDror. A few small overall comments:

  1. Please push the changes to the notebooks without outputs (i.e. run. "clear output" before pushing). That makes the diff a little cleaner. The plots will be autogenerated by nbconvert when the documentation is building, and you'll see the plots on orbitize.info.
  2. In general, it would be very good to use a consistent set of mask variables (e.g. self.rv_data) throughout, rather than redefining them in each subfunction. This is definitely orbitize's fault, not yours, but since you're refactoring the plot code, I think you should be the one to fix it. :D I left a few in-line comments with suggested places to make these changes, but I don't think I got all of them.

Comment thread orbitize/plot.py

if plot_priors:
axes = figure.axes
num_params = len(param_indices)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can avoid much of this normalization code by passing hist2d_kwargs = {"density":True} into corner.corner.

Comment thread orbitize/plot.py
for i, param_i in enumerate(param_indices):
prior = results.system.sys_priors[param_i]
if not hasattr(prior, "compute_lnprob"):
continue

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should never happen; Prior objects by definition (by means of the abstract base class) must have a compute_lnprob method, so I think you can safely remove this check

@sblunt

sblunt commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Also, see aesthetic suggestions in #378 and please implement the ones you like! I think the main things I'd like to see here based on those suggestions are:

  1. by default using circles instead of stars for astrometric data
  2. using minor ticks and mirror ticks on all panels
  3. using a (slightly) bigger font size
  4. I also think the default red astrometry is a little jarring. Play around with the default color scheme and see what you can figure out that you like!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants