311.4. Enhancing JSON-based visualizations#
311.4. Enhancing JSON-based visualizations¶
For the Rubin Science Platform at data.lsst.cloud.
Data Release: Data Preview 1
Container Size: large
LSST Science Pipelines version: r29.2.0
Last verified to run: 2026-05-29
Repository: github.com/lsst/tutorial-notebooks
DOI: 10.11578/rubin/dc.20250909.20
Learning objective: Reconstruct a full Plotly object from a JSON file, enhance plot clarity, and save the modified result.
LSST data products: None.
Packages: json, plotly
Credit: Originally developed by the Rubin Community Science team. Please consider acknowledging them if this notebook is used for the preparation of journal articles, software releases, or other notebooks.
Get Support: Everyone is encouraged to ask questions or raise issues in the Support Category of the Rubin Community Forum. Rubin staff will respond to all questions posted there.
1. Introduction¶
In astronomical data exploration, complex visualizations can be saved as JSON-serialized files. Unlike a static image (like a PNG), a JSON file is a complete "snapshot" of a plot’s state containing not just the raw data points, but also the coordinate systems, axis scaling, and interactive metadata used at the time of creation. The RSP Portal offers a convenient way to save exploratory plots in JSON format. However, fine-tuning these figures for final publication often requires more flexibility than a web interface can provide.
This tutorial demonstrates how to reconstruct a full, live serialized figure object from such a JSON file and perform advanced programmatic modifications in a Notebook environment, using a Polty module called plotly.io. This module is specifically desgined to parse JSON files, and recognizes keys like "data", "layout", and "frames" and maps them directly to Python objects. This tutorial specifically utilizes the JSON file generated from a related Portal tutorial.
Related tutorials: The 100-level Portal tutorial demonstrating how to save a plot as JSON file.
import plotly.io as pio
1.2. Define parameters and functions¶
Define the JSON file path.
data_path = '/rubin/cst_repos/tutorial-notebooks-data/data/dp1_311_4_CMD.json'
2. Reconstructthe Figure from JSON¶
Read the JSON file and plot the original color-magnitude diagram (CMD) generated from a relevant Portal tutorial.
fig = pio.read_json(data_path)
The variable fig is a Plotly figure object. Instead of being a simple dictionary, it is a live, hierarchical data structure. Because it is a reconstructed class object, it is fully actionable. Call fig.show() to render it.
fig.show()
Figure 1: Reconstructed CMD using serialized JSON output from the RSP Portal tutorial. The figure maintains 1:1 visual fidelity with the original Portal visualization while preserving all native interactive features, including WebGL-accelerated panning, zooming, and hover-tooltips.
3. Modify the data (called "Trace").¶
The method update_traces is used to change how the actual data is visualized. This targets the "traces" - the heatmap pixels in this example. It controls the look of the data itself, including marker color, marker size, line thickness, color maps, etc. You can use the selector argument to target specific types of traces (e.g., heatmaps) without affecting others such as scatter points or histogram bars.
As a demonstration of programmatic styling edit, this section updates the heatmap trace to use the Viridis colorscale, customize the colorbar’s title and dimensions, and implement a custom hovertemplate for enhanced data inspection.
fig.update_traces(
colorscale='Viridis',
colorbar=dict(
title="Nstar",
len=1.0,
thickness=15,
x=1.1
),
hovertemplate=(
"<b>Object Info</b><br>" +
"Color (g-r): %{x:.3f}<br>" +
"Mag (r): %{y:.3f}<br>" +
"Coords: %{text}" +
"<extra></extra>"
),
selector=dict(type='heatmap')
)
fig.show()
Figure 2: The heatmap trace has been modified with a perceptually uniform Viridis colorscale and a resized colorbar labeled 'Nstar.' A custom hovertemplate has also been applied.
4. Modify the canvas (called "Layout").¶
The method update_layout is used to change the environment where the data lives. This targets the "Layout". It controls the global figure settings or specific axis objects, including figure title, axis titles, axis ranges, plot background color, figure size, font settings, legend position, etc.
As a demonstration of programmatic styling edit, this section updates the heatmap layout to refine the figure's title and size, to apply the "plotly_dark" template for a high-contrast aesthetic, to update the axis labels, and to customize the hover labels with a white background and the Rockwell font family.
fig.update_layout(
title={
'text': "47 Tuc Field",
'y': 0.98,
'x': 0.5
},
height=400,
width=400,
template="plotly_dark",
xaxis_title="g - r",
yaxis_title="r (Magnitude)",
hoverlabel=dict(
bgcolor="white",
font_family="Rockwell"
)
)
fig.show()
Figure 3: The CMD following global layout refinements. The visualization now utilizes the "plotly_dark" theme with a centered title and a reasonable size. Axis labels have been updated for scientific clarity, and hover labels have been styled with custom fonts and high-contrast backgrounds to ensure legibility during interactive data exploration.
5. Add annotation¶
In Plotly, fig.add_annotation() is the primary tool for adding descriptive text, labels, or pointers to your visualization. Unlike titles or axis labels, annotations allow you to place text exactly where you need it to highlight specific scientific features.
Add text to indicate which system each stellar population belongs to in the CMD: 47 Tuc vs. Small Magellanic Cloud (SMC) vs. Milky Way (MW).
fig.add_annotation(
x=0.2,
y=17.5,
text="47 Tuc",
showarrow=True,
arrowhead=2,
arrowcolor="red",
ax=-50,
ay=-20,
font=dict(size=14, color="red"),
bgcolor="white",
)
fig.add_annotation(
x=-0.2,
y=22,
text="SMC",
showarrow=True,
arrowhead=2,
arrowcolor="red",
ax=-40,
ay=-20,
font=dict(size=14, color="red"),
bgcolor="white",
)
fig.add_annotation(
x=1.5,
y=20,
text="MW",
showarrow=True,
arrowhead=2,
arrowcolor="red",
ax=40,
ay=0,
font=dict(size=14, color="red"),
bgcolor="white",
)
fig.show()
Figure 4: Final annotated CMD identifying the distinct stellar populations within the field of view. Red arrows and labels delineate the 47 Tucanae globular cluster, characterized by its remarkably narrow lower main sequence; the SMC, which exhibits a broader main-sequence turn-off and a well-developed red giant branch; and the MW foreground, visible as a prominent vertical structure at $g−r \approx$ 1.2. These annotations are anchored to data coordinates, providing a scientific reference for the various stellar populations present in the dataset.
Option to clear the annotations by setting the list to empty [].
# fig.layout.annotations = []
6. Save the final plot (optional)¶
Downloading the displayed figure above as a PNG for static use is an option. It is also possible to export the final plot as a JSON file to maintain its interactive capabilities for future programmatic modification.
Define the output directory and save your final plot.
# import os
# home_dir = os.getenv("HOME")
# tmp_dir = os.path.join(home_dir, "tmpdir")
# file_path = os.path.join(tmp_dir, "updated_CMD.json")
# if not os.path.exists(tmp_dir):
# os.makedirs(tmp_dir)
# print(f"Created directory: {tmp_dir}")
# pio.write_json(fig, file_path)