DynamicMap colorbar ticks will not update when parameter changes

We have a Panel app that has two linked DynamicMaps that have gridded data with point observation data overlayed. The data has 3 different output fields from the model (severity, probability, and SLD) and we have a parameter to cycle between these. We’ve also defined the colors, tick labels, and ticks for the colorbar for each field. However, when we run the app, all of the fields change appropriately and the colorbar changes to have the correct colors, but it will not change the ticks or tick labels.

COLORS = {
“Severity”: [“#FFFFFF”, “#CCFFFF”, “#98CCFF”, “#6699FE”, “#3333FE”],
“Probability”: [
#FFFFFF”,
#CCFFFF”,
#99FFCC”,
#99FF66”,
#CCFF66”,
#FFFF00”,
#FFCC00”,
#FF9900”,
#FF6600”,
],
“SLD”: [“#FFFFFF”, “#FFA500”, “#FF0000”],
}
BOUNDS = {
“Severity”: [0, 1, 2, 3, 4, 5],
“Probability”: [0.0, 0.05, 0.15, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 1.0],
“SLD”: [0.0, 0.05, 0.25, 1.0],
}
CLIMS = {
“Severity”: (0, 5),
“Probability”: (0, 1),
“SLD”: (0, 1),
}
CTICKS = {
“Severity”: [0.5, 1.5, 2.5, 3.5, 4.5],
“Probability”: [0, 0.05, 0.15, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 1],
“SLD”: [0, 0.05, 0.25, 1],
}

field = param.ObjectSelector(
default=“Severity”, objects=[“Severity”, “Probability”, “SLD”]
)
lead = param.ObjectSelector(default=3, objects=[3, 6, 9, 12, 15, 18])
map_time = param.Selector(default=datetime.datetime(2024, 10, 28, 21))
level = param.Selector(
default=6000, objects=list(np.arange(1000, 31000, 1000, dtype=int))
)
deps = [“field”, “lead”, “map_time”, “level”]

def __init__(self, **params):
    """
    View product fields
    """
    super().__init__(**params)
    # self.colorbar_opts = self.get_colorbar_opts()
    self.plot = self.create_plot()
def get_colorbar_opts(self):
    """Dynamically generate colorbar options based on selected field."""
    field = self.field
    ct = CTICKS[field]

    if field == "Severity":
        ticks = [
            (0.5, "No Icing"),
            (1.5, "Trace"),
            (2.5, "Light"),
            (3.5, "Moderate"),
            (4.5, "Heavy"),
        ]
        print(ticks)
        return ticks
        # return {
        #     "major_label_overrides": {
        #         0.5: "No Icing",
        #         1.5: "Trace",
        #         2.5: "Light",
        #         3.5: "Moderate",
        #         4.5: "Heavy",
        #     },
        #     "ticker": FixedTicker(ticks=ct),
        # }
    else:
        ticks = [(tick, f"{tick}") for tick in ct]
        print(ticks)
        return ticks
        # return {
        #     "major_label_overrides": {tick: f"{tick}" for tick in ct},
        #     "ticker": FixedTicker(ticks=ct),
        # }

@param.depends(*deps, watch=True)
def dynamic_plot(self):
    """
    The mapping function for the dynamic plots.
    """

    def plot_limits(plot):
        """
        plot limits
        """
        plot.handles["x_range"].min_interval = 10
        plot.handles["x_range"].max_interval = 1299
        plot.handles["y_range"].min_interval = 10
        plot.handles["y_range"].max_interval = 919

    valid = self.map_time
    level = self.level
    field = self.field
    lead = self.lead
    ds1, ds2 = self.prepare_data(field, lead)

    colorbar_opts = self.get_colorbar_opts()

    img1 = hv.Image(ds1).opts(hv.opts.Image(aspect="equal"))
    img1.opts(
        width=WIDTH,
        cmap=COLORS[field],
        color_levels=BOUNDS[field],
        clim=CLIMS[field],
        colorbar=True,
        # cticks=colorbar_opts,
        # colorbar_opts=colorbar_opts,
        colorbar_position="top",
    )

    img2 = hv.Image(ds2).opts(hv.opts.Image(aspect="equal"))
    img2.opts(
        width=WIDTH,
        cmap=COLORS[field],
        color_levels=BOUNDS[field],
        clim=CLIMS[field],
        colorbar=True,
        # cticks=colorbar_opts,
        colorbar_position="top",
        # colorbar_opts=colorbar_opts,
        hooks=[plot_limits],
    )

    # load and plot coastline paths
    paths = list(
        np.load(
            "user-path",
            allow_pickle=True,
        )
    )
    coasts = hv.Path(paths).opts(
        xlim=(0, 1299), ylim=(0, 919), aspect="equal", color="black"
    )

    subtitle = f"Valid Time: {valid} | Lead: {lead} | Level: {level:,} ft"
    pirep_overlay, tamdar_overlay, metars_overlay = self.get_obs()
    plot1 = img1 * coasts * pirep_overlay * metars_overlay * tamdar_overlay
    plot1.opts(
        title=f"IPA-F {field}\n{subtitle}",
        xlabel="",
        ylabel="",
        fontsize=FONTSIZE,
        xticks=0,
        yticks=0,
        legend_position="bottom",
        legend_opts={"click_policy": "hide", "ncols": 5},
    )
    plot2 = img2 * coasts * pirep_overlay * metars_overlay * tamdar_overlay
    plot2.opts(
        title=f"FIP2-AK {field}\n{subtitle}",
        xlabel="",
        ylabel="",
        fontsize=FONTSIZE,
        xticks=0,
        yticks=0,
        legend_position="bottom",
        legend_opts={"click_policy": "hide", "ncols": 5},
    )
    return plot1 + plot2

def create_plot(self):
    """
    Create the map
    """
    return hv.DynamicMap(self.dynamic_plot, kdims=[])

Some of the code is commented out, representing our attempts to make the updated colorbar use the ticks and labels we want. But so far, only the first load has the correct labels for the severity field. When we change the “field” parameter, the colorbar will change to the right colors and spacing, but will retain the first set of ticks and labels (ex: 0.5 on probability is labeled “No Icing”, there are no other labels or ticks present).

All help appreciated.

Try dropping hv.DynamicMap and only keeping depends.


def create_plot(self):
    """
    Create the map
    """
    return self.dynamic_plot

This does make the ticks behave as desired. The problem now is that when a parameter changes, the maps reset to the default zoom extents. The desired behavior is that the map extents don’t change when parameters update.

Might be related to Plot options not applied when changed interactively · Issue #2982 · holoviz/holoviews · GitHub

You may have to manually capture and update the x_range/y_range