Repository: dask/distributed

Issue:
Fine Performance Metrics Dashboard is broken on Bokeh 3
- Part of #7665 
- Follow-up to #7679

The new dashboard view for Fine Performance Metrics (#7725) does not work with Bokeh 3.1.
Everything looks OK with Bokeh 2.4.


Discussion:

Resolving patch:
diff --git a/distributed/dashboard/components/scheduler.py b/distributed/dashboard/components/scheduler.py
--- a/distributed/dashboard/components/scheduler.py
+++ b/distributed/dashboard/components/scheduler.py
@@ -3405,20 +3405,12 @@ def handle_selector_chng(attr, old, new):
         self.unit_selector = Select(title="Unit selection", options=[])
         self.unit_selector.on_change("value", handle_selector_chng)
         self.unit_selected = "seconds"
-        self.task_exec_by_activity_chart = figure()
-        self.task_exec_by_prefix_chart = figure()
-        self.senddata_by_activity_chart = figure()
+        self.task_exec_by_activity_chart = None
+        self.task_exec_by_prefix_chart = None
+        self.senddata_by_activity_chart = None
         self.root = column(
             self.function_selector,
             self.unit_selector,
-            row(
-                [
-                    self.task_exec_by_prefix_chart,
-                    self.task_exec_by_activity_chart,
-                    self.senddata_by_activity_chart,
-                ],
-                sizing_mode="stretch_width",
-            ),
             sizing_mode="scale_width",
         )
 
@@ -3504,26 +3496,29 @@ def update(self):
             f"Filter by function ({len(self.function_selector.options)}):"
         )
 
-        task_exec_piechart = self._build_task_execution_by_activity_chart(
-            self.task_exec_data_limited.copy()
-        )
-        task_exec_barchart = self._build_task_execution_by_prefix_chart(
-            self.task_exec_data_limited.copy()
-        )
-        senddata_piechart = self._build_senddata_chart(self.senddata.copy())
+        needs_figures_row = self.task_exec_by_activity_chart is None
+        self._build_task_execution_by_activity_chart(self.task_exec_data_limited.copy())
+        self._build_task_execution_by_prefix_chart(self.task_exec_data_limited.copy())
+        self._build_senddata_chart(self.senddata.copy())
 
-        # Replacing the child causes small blips if done every iteration vs updating
-        # renderers, but it's needed when new functions and/or activities show up to
-        # rerender plot
-        if self.substantial_change:
-            self.root.children[-1].children[0] = task_exec_barchart
-            self.root.children[-1].children[1] = task_exec_piechart
-            self.root.children[-1].children[2] = senddata_piechart
+        if self.substantial_change or needs_figures_row:
             self.substantial_change = False
-        else:
-            self.task_exec_by_prefix_chart.renderers = task_exec_piechart.renderers
-            self.task_exec_by_activity_chart.renderers = task_exec_barchart.renderers
-            self.senddata_by_activity_chart.renderers = senddata_piechart.renderers
+
+            figures_row = row(
+                children=[
+                    self.task_exec_by_prefix_chart,
+                    self.task_exec_by_activity_chart,
+                    self.senddata_by_activity_chart,
+                ],
+                sizing_mode="stretch_width",
+            )
+
+            if needs_figures_row:
+                # First iteration, initial assignment of figures row
+                self.root.children.append(figures_row)
+            else:
+                # Otherwise needs forced refresh by replacing the figures row
+                self.root.children[-1] = figures_row
 
     def _build_task_execution_by_activity_chart(
         self, task_exec_data: defaultdict[str, list]
@@ -3553,30 +3548,31 @@ def _build_task_execution_by_activity_chart(
         piechart_data["activity"] = self.task_activities
         self.task_exec_by_activity_src.data = piechart_data
 
-        piechart = figure(
-            height=500,
-            sizing_mode="scale_both",
-            title="Task execution, by activity",
-            tools="hover",
-            tooltips="@{activity}: @text",
-            x_range=(-0.5, 1.0),
-        )
-        piechart.axis.axis_label = None
-        piechart.axis.visible = False
-        piechart.grid.grid_line_color = None
-
-        piechart.wedge(
-            x=0,
-            y=1,
-            radius=0.4,
-            start_angle=cumsum("angle", include_zero=True),
-            end_angle=cumsum("angle"),
-            line_color="white",
-            fill_color="color",
-            legend_field="activity",
-            source=self.task_exec_by_activity_src,
-        )
-        return piechart
+        if self.task_exec_by_activity_chart is None:
+            piechart = figure(
+                height=500,
+                sizing_mode="scale_both",
+                title="Task execution, by activity",
+                tools="hover",
+                tooltips="@{activity}: @text",
+                x_range=(-0.5, 1.0),
+            )
+            piechart.axis.axis_label = None
+            piechart.axis.visible = False
+            piechart.grid.grid_line_color = None
+
+            piechart.wedge(
+                x=0,
+                y=1,
+                radius=0.4,
+                start_angle=cumsum("angle", include_zero=True),
+                end_angle=cumsum("angle"),
+                line_color="white",
+                fill_color="color",
+                legend_field="activity",
+                source=self.task_exec_by_activity_src,
+            )
+            self.task_exec_by_activity_chart = piechart
 
     def _build_task_execution_by_prefix_chart(
         self, task_exec_data: defaultdict[str, list]
@@ -3621,7 +3617,7 @@ def _build_task_execution_by_prefix_chart(
 
             self.task_exec_by_prefix_src.data = dict(task_exec_data)
             barchart.renderers = renderers
-        return barchart
+        self.task_exec_by_prefix_chart = barchart
 
     def _build_senddata_chart(self, senddata: defaultdict[str, list]) -> figure:
         piedata = {}
@@ -3643,29 +3639,31 @@ def _build_senddata_chart(self, senddata: defaultdict[str, list]) -> figure:
         piedata["color"] = small_palettes["YlGnBu"].get(len(piedata["activity"]), [])
 
         self.sendsrc.data = piedata
-        senddata_piechart = figure(
-            height=500,
-            sizing_mode="scale_both",
-            title="Send data, by activity",
-            tools="hover",
-            tooltips="@{activity}: @text",
-            x_range=(-0.5, 1.0),
-        )
-        senddata_piechart.wedge(
-            x=0,
-            y=1,
-            radius=0.4,
-            start_angle=cumsum("angle", include_zero=True),
-            end_angle=cumsum("angle"),
-            line_color="white",
-            fill_color="color",
-            legend_field="activity",
-            source=self.sendsrc,
-        )
-        senddata_piechart.axis.axis_label = None
-        senddata_piechart.axis.visible = False
-        senddata_piechart.grid.grid_line_color = None
-        return senddata_piechart
+
+        if self.senddata_by_activity_chart is None:
+            senddata_piechart = figure(
+                height=500,
+                sizing_mode="scale_both",
+                title="Send data, by activity",
+                tools="hover",
+                tooltips="@{activity}: @text",
+                x_range=(-0.5, 1.0),
+            )
+            senddata_piechart.wedge(
+                x=0,
+                y=1,
+                radius=0.4,
+                start_angle=cumsum("angle", include_zero=True),
+                end_angle=cumsum("angle"),
+                line_color="white",
+                fill_color="color",
+                legend_field="activity",
+                source=self.sendsrc,
+            )
+            senddata_piechart.axis.axis_label = None
+            senddata_piechart.axis.visible = False
+            senddata_piechart.grid.grid_line_color = None
+            self.senddata_by_activity_chart = senddata_piechart
 
 
 class Contention(DashboardComponent):


Test patch: