Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PREOPS-4817: Integrate key and map with colourblind-friendly plot elements. #63

Merged
merged 1 commit into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 23 additions & 179 deletions schedview/app/scheduler_dashboard/scheduler_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -911,6 +911,21 @@
)
self._sky_map_base.plot.add_layout(color_bar, "below")
self._sky_map_base.plot.below[1].visible = False
self._sky_map_base.plot.toolbar.autohide = True # show toolbar only when mouseover plot
self._sky_map_base.plot.title.text = "" # remove 'Horizon' title
self._sky_map_base.plot.legend.propagate_hover = True # hover tool works over in-plot legend
self._sky_map_base.plot.legend.title = "Key"
self._sky_map_base.plot.legend.title_text_font_style = "bold"
self._sky_map_base.plot.legend.border_line_color = "#048b8c"
self._sky_map_base.plot.legend.border_line_width = 3
self._sky_map_base.plot.legend.border_line_alpha = 1
self._sky_map_base.plot.legend.label_standoff = 10 # gap between images and text
self._sky_map_base.plot.legend.padding = 15 # space around inside edge
self._sky_map_base.plot.legend.title_standoff = 10 # space between title and items
self._sky_map_base.plot.legend.click_policy = "hide" # hide elements when clicked
self._sky_map_base.plot.add_layout(self._sky_map_base.plot.legend[0], "right")
self._sky_map_base.plot.right[0].location = "center_right"

Check warning on line 927 in schedview/app/scheduler_dashboard/scheduler_dashboard.py

View check run for this annotation

Codecov / codecov/patch

schedview/app/scheduler_dashboard/scheduler_dashboard.py#L914-L927

Added lines #L914 - L927 were not covered by tests

self._debugging_message = "Finished creating sky map base."

except Exception:
Expand Down Expand Up @@ -1313,178 +1328,6 @@
self.param["scheduler_fname"].update(path=f"{data_dir}/*scheduler*.p*")


# --------------------------------------------------------------- Key functions


def generate_array_for_key(number_of_columns=4):
"""Create a dictionary of data for the key.

Parameters
----------
number_of_columns : 'int', optional
The number of columns to display key objects in. The default is 4.

Returns
-------
data : 'dict'
Coordinate, styling and text data for key.
"""
return {
# x,y coordinates for glyphs (lines, circles and text).
"x_title": np.array([7]), # Title x coord
"y_title": np.array([5.75]), # Title y coord
"x_circles": np.tile(8, number_of_columns), # Circle centre coords
"x_text_1": np.tile(2.5, number_of_columns), # Text in colunn 1
"x_text_2": np.tile(8.75, number_of_columns), # Text in column 2
"x0_lines": np.tile(0.75, number_of_columns), # Start lines
"x1_lines": np.tile(2, number_of_columns), # End lines
"y": np.arange(number_of_columns, 0, -1), # y coords for all items except title
# Colours and sizes of images.
"line_colours": np.array(["black", "red", "#1f8f20", "#110cff"]),
"circle_colours": np.array(["#ffa500", "brown", "black", "#1f8f20"]),
"circle_fill_alpha": np.array([1, 1, 0, 0.5]),
"circle_sizes": np.tile(10, number_of_columns),
# Text for title and key items.
"title_text": np.array(["Key"]),
"text_1": np.array(["Horizon", "ZD=70 degrees", "Ecliptic", "Galactic plane"]),
"text_2": np.array(["Moon position", "Sun position", "Survey field(s)", "Telescope pointing"]),
}


def generate_key():
"""Populate a Bokeh plot with glyphs of key objects.

Returns
-------
key : 'bokeh.models.Plot'
A key for the dashboard map.
"""
data_array = generate_array_for_key()

# Assign data to relevant data source (to be used in glyph creation below).
title_source = bokeh.models.ColumnDataSource(
dict(
x=data_array["x_title"],
y=data_array["y_title"],
text=data_array["title_text"],
)
)
text1_source = bokeh.models.ColumnDataSource(
dict(
x=data_array["x_text_1"],
y=data_array["y"],
text=data_array["text_1"],
)
)
text2_source = bokeh.models.ColumnDataSource(
dict(
x=data_array["x_text_2"],
y=data_array["y"],
text=data_array["text_2"],
)
)
circle_source = bokeh.models.ColumnDataSource(
dict(
x=data_array["x_circles"],
y=data_array["y"],
sizes=data_array["circle_sizes"],
colours=data_array["circle_colours"],
fill_alpha=data_array["circle_fill_alpha"],
)
)
line_source = bokeh.models.ColumnDataSource(
dict(
x0=data_array["x0_lines"],
y0=data_array["y"],
x1=data_array["x1_lines"],
y1=data_array["y"],
colours=data_array["line_colours"],
)
)

# Create glyphs.
border_glyph = bokeh.models.Rect(
x=7,
y=3.25,
width=14,
height=6.5,
line_color="#048b8c",
fill_color=None,
line_width=3,
)
header_glyph = bokeh.models.Rect(
x=7,
y=5.75,
width=14,
height=1.5,
line_color=None,
fill_color="#048b8c",
)
title_glyph = bokeh.models.Text(
x="x",
y="y",
text="text",
text_font_size="15px",
text_color="white",
text_baseline="middle",
text_font={"value": "verdana"},
text_align="center",
)
text1_glyph = bokeh.models.Text(
x="x",
y="y",
text="text",
text_font_size="10px",
text_color="black",
text_baseline="middle",
text_font={"value": "verdana"},
)
text2_glyph = bokeh.models.Text(
x="x",
y="y",
text="text",
text_font_size="10px",
text_color="black",
text_baseline="middle",
text_font={"value": "verdana"},
)
circle_glyph = bokeh.models.Circle(
x="x",
y="y",
size="sizes",
line_color="colours",
fill_color="colours",
fill_alpha="fill_alpha",
)
line_glyph = bokeh.models.Segment(
x0="x0",
y0="y0",
x1="x1",
y1="y1",
line_color="colours",
line_width=2,
line_cap="round",
)

key = bokeh.models.Plot(
title=None,
width=300,
height=150,
min_border=0,
toolbar_location=None,
)

key.add_glyph(border_glyph)
key.add_glyph(header_glyph)
key.add_glyph(title_source, title_glyph)
key.add_glyph(text1_source, text1_glyph)
key.add_glyph(text2_source, text2_glyph)
key.add_glyph(circle_source, circle_glyph)
key.add_glyph(line_source, line_glyph)

return key


# ------------------------------------------------------------ Create dashboard


Expand Down Expand Up @@ -1623,24 +1466,25 @@
pn.Spacer(width=10),
)
# Map display and header.
sched_app[8:59, 67:100] = pn.Column(
sched_app[8:67, 67:100] = pn.Column(

Check warning on line 1469 in schedview/app/scheduler_dashboard/scheduler_dashboard.py

View check run for this annotation

Codecov / codecov/patch

schedview/app/scheduler_dashboard/scheduler_dashboard.py#L1469

Added line #L1469 was not covered by tests
pn.Spacer(height=10),
pn.Row(
scheduler.map_title,
styles={"background": "#048b8c"},
),
pn.param.ParamMethod(scheduler.publish_sky_map, loading_indicator=True),
)
# Key.
sched_app[66:87, 67:87] = pn.Column(
pn.Spacer(height=32),
pn.pane.Bokeh(generate_key()),
)
# Map display parameters (map, nside, color palette).
sched_app[66:87, 87:100] = pn.Param(
sched_app[74:87, 67:100] = pn.Param(

Check warning on line 1478 in schedview/app/scheduler_dashboard/scheduler_dashboard.py

View check run for this annotation

Codecov / codecov/patch

schedview/app/scheduler_dashboard/scheduler_dashboard.py#L1478

Added line #L1478 was not covered by tests
scheduler,
widgets={
"survey_map": {"type": pn.widgets.Select, "width": 250},
"nside": {"type": pn.widgets.Select, "width": 150},
"color_palette": {"type": pn.widgets.Select, "width": 100},
},
parameters=["survey_map", "nside", "color_palette"],
show_name=False,
default_layout=pn.Row,
)
# Debugging collapsable card.
sched_app[87:100, :] = pn.Card(
Expand Down
119 changes: 67 additions & 52 deletions schedview/plot/survey.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,36 +98,86 @@
name="hpix_renderer",
)

# Add the hovertool
hpix_datasource = sky_map.plot.select(name="hpix_ds")[0]
hpix_renderer = sky_map.plot.select(name="hpix_renderer")[0]
shown_hpix_data = dict(hpix_datasource.data)
shown_hpids = shown_hpix_data["hpid"]
tooltips = [

Check warning on line 106 in schedview/plot/survey.py

View check run for this annotation

Codecov / codecov/patch

schedview/plot/survey.py#L102-L106

Added lines #L102 - L106 were not covered by tests
(f"hpid (nside={nside})", "@hpid"),
("R. A. (deg)", "@center_ra"),
("Declination (deg)", "@center_decl"),
]

sky_map.add_horizon_graticules()
sky_map.add_ecliptic()
sky_map.add_galactic_plane()

# If we have alt/az coordinates, include them
if "x_hz" in shown_hpix_data.keys() and "y_hz" in shown_hpix_data.keys():
x_hz = np.mean(np.array(shown_hpix_data["x_hz"]), axis=1)
y_hz = np.mean(np.array(shown_hpix_data["y_hz"]), axis=1)

Check warning on line 117 in schedview/plot/survey.py

View check run for this annotation

Codecov / codecov/patch

schedview/plot/survey.py#L116-L117

Added lines #L116 - L117 were not covered by tests

zd = np.degrees(np.sqrt(x_hz**2 + y_hz**2))
alt = 90 - zd
az = np.degrees(np.arctan2(-x_hz, y_hz))

Check warning on line 121 in schedview/plot/survey.py

View check run for this annotation

Codecov / codecov/patch

schedview/plot/survey.py#L119-L121

Added lines #L119 - L121 were not covered by tests

# Calculate airmass using Kirstensen (1998), good to the horizon.
# Models the atmosphere with a uniform spherical shell with a height
# 1/470 of the radius of the earth.
# https://doi.org/10.1002/asna.2123190313
a_cos_zd = 470 * np.cos(np.radians(zd))
airmass = np.sqrt(a_cos_zd**2 + 941) - a_cos_zd

Check warning on line 128 in schedview/plot/survey.py

View check run for this annotation

Codecov / codecov/patch

schedview/plot/survey.py#L127-L128

Added lines #L127 - L128 were not covered by tests

if "zd" not in shown_hpix_data and "zd" not in hpix_data:
shown_hpix_data["zd"] = zd
tooltips.append(("Zenith Distance (deg)", "@zd"))

Check warning on line 132 in schedview/plot/survey.py

View check run for this annotation

Codecov / codecov/patch

schedview/plot/survey.py#L131-L132

Added lines #L131 - L132 were not covered by tests

if "alt" not in shown_hpix_data and "alt" not in hpix_data:
shown_hpix_data["alt"] = alt
tooltips.append(("Altitude (deg)", "@alt"))

Check warning on line 136 in schedview/plot/survey.py

View check run for this annotation

Codecov / codecov/patch

schedview/plot/survey.py#L135-L136

Added lines #L135 - L136 were not covered by tests

if "azimuth" not in shown_hpix_data and "azimuth" not in hpix_data:
shown_hpix_data["azimuth"] = az
tooltips.append(("Azimuth (deg)", "@azimuth"))

Check warning on line 140 in schedview/plot/survey.py

View check run for this annotation

Codecov / codecov/patch

schedview/plot/survey.py#L139-L140

Added lines #L139 - L140 were not covered by tests

if "airmass" not in shown_hpix_data and "airmass" not in hpix_data:
shown_hpix_data["airmass"] = airmass
tooltips.append(("airmass", "@airmass"))

Check warning on line 144 in schedview/plot/survey.py

View check run for this annotation

Codecov / codecov/patch

schedview/plot/survey.py#L143-L144

Added lines #L143 - L144 were not covered by tests

sky_map.add_horizon(

Check warning on line 146 in schedview/plot/survey.py

View check run for this annotation

Codecov / codecov/patch

schedview/plot/survey.py#L146

Added line #L146 was not covered by tests
89.99, line_kwargs={"color": "#000000", "line_width": 2, "legend_label": "Horizon"}
)
sky_map.add_horizon(

Check warning on line 149 in schedview/plot/survey.py

View check run for this annotation

Codecov / codecov/patch

schedview/plot/survey.py#L149

Added line #L149 was not covered by tests
70, line_kwargs={"color": "#D55E00", "line_width": 2, "legend_label": "ZD = 70 degrees"}
)

# "line_dash": "dashed"
sky_map.add_ecliptic(legend_label="Ecliptic", line_width=2, color="#009E73", line_dash="dashed")
sky_map.add_galactic_plane(

Check warning on line 155 in schedview/plot/survey.py

View check run for this annotation

Codecov / codecov/patch

schedview/plot/survey.py#L154-L155

Added lines #L154 - L155 were not covered by tests
legend_label="Galactic plane", color="#0072B2", line_width=2, line_dash="dotted"
)

sky_map.add_marker(
sun_coords.ra.deg,
sun_coords.dec.deg,
name="Sun",
glyph_size=15,
circle_kwargs={"color": "brown"},
circle_kwargs={
"color": "brown",
"legend_label": "Sun position",
},
)
sky_map.add_marker(
moon_coords.ra.deg,
moon_coords.dec.deg,
name="Moon",
glyph_size=15,
circle_kwargs={"color": "orange"},
circle_kwargs={
"color": "orange",
"legend_label": "Moon position",
"tags": ["circle", "orange"],
},
)

# Add the hovertool
hpix_datasource = sky_map.plot.select(name="hpix_ds")[0]
hpix_renderer = sky_map.plot.select(name="hpix_renderer")[0]
shown_hpix_data = dict(hpix_datasource.data)
shown_hpids = shown_hpix_data["hpid"]
tooltips = [
(f"hpid (nside={nside})", "@hpid"),
("R. A. (deg)", "@center_ra"),
("Declination (deg)", "@center_decl"),
]

if conditions is not None and conditions.tel_az is not None and conditions.tel_alt is not None:
telescope_marker_data_source = bokeh.models.ColumnDataSource(
data={
Expand All @@ -142,7 +192,7 @@
sky_map.add_marker(
data_source=telescope_marker_data_source,
name="telescope_pointing_marker",
circle_kwargs={"color": "green", "fill_alpha": 0.5},
circle_kwargs={"color": "green", "fill_alpha": 0.5, "legend_label": "Telescope pointing"},
)

if survey is not None:
Expand All @@ -159,46 +209,11 @@
sky_map.add_marker(
data_source=survey_field_data_source,
name="survey_field_marker",
circle_kwargs={"color": "black", "fill_alpha": 0},
circle_kwargs={"color": "black", "fill_alpha": 0, "legend_label": "Survey field(s)"},
)
except AttributeError:
pass

# If we have alt/az coordinates, include them
if "x_hz" in shown_hpix_data.keys() and "y_hz" in shown_hpix_data.keys():
x_hz = np.mean(np.array(shown_hpix_data["x_hz"]), axis=1)
y_hz = np.mean(np.array(shown_hpix_data["y_hz"]), axis=1)

zd = np.degrees(np.sqrt(x_hz**2 + y_hz**2))
alt = 90 - zd
az = np.degrees(np.arctan2(-x_hz, y_hz))

# Calculate airmass using Kirstensen (1998), good to the horizon.
# Models the atmosphere with a uniform spherical shell with a height
# 1/470 of the radius of the earth.
# https://doi.org/10.1002/asna.2123190313
a_cos_zd = 470 * np.cos(np.radians(zd))
airmass = np.sqrt(a_cos_zd**2 + 941) - a_cos_zd

if "zd" not in shown_hpix_data and "zd" not in hpix_data:
shown_hpix_data["zd"] = zd
tooltips.append(("Zenith Distance (deg)", "@zd"))

if "alt" not in shown_hpix_data and "alt" not in hpix_data:
shown_hpix_data["alt"] = alt
tooltips.append(("Altitude (deg)", "@alt"))

if "azimuth" not in shown_hpix_data and "azimuth" not in hpix_data:
shown_hpix_data["azimuth"] = az
tooltips.append(("Azimuth (deg)", "@azimuth"))

if "airmass" not in shown_hpix_data and "airmass" not in hpix_data:
shown_hpix_data["airmass"] = airmass
tooltips.append(("airmass", "@airmass"))

sky_map.add_horizon(89.99, line_kwargs={"color": "black", "line_width": 2})
sky_map.add_horizon(70, line_kwargs={"color": "red", "line_width": 2})

for key in hpix_data:
column_name = key.replace(" ", "_").replace(".", "_").replace("@", "_")
shown_hpix_data[column_name] = hpix_data[key][shown_hpids]
Expand Down
Loading