Repository: dask/distributed

Issue:
Improve readability of Task execution, by function plot in Fine Performance Metrics
As mentioned in  https://github.com/dask/distributed/pull/7945#discussion_r1252046791, the x-axis labels on the *Task execution, by function* plot are hardly readable even with a fairly small number of functions:

![image](https://github.com/dask/distributed/assets/2699097/abd12b51-6ec8-4cab-b010-ba0e7972bcc0)

The plot might be more readable if we used a horizontal bar chart instead.


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
@@ -3386,7 +3386,7 @@ class FinePerformanceMetrics(DashboardComponent):
     BASE_TOOLS = ["pan", "wheel_zoom", "box_zoom", "reset"]
     scheduler: Scheduler
     task_exec_by_prefix_src: ColumnDataSource
-    task_exec_by_prefix_ymax: float
+    task_exec_by_prefix_xmax: float
     task_exec_by_activity_src: ColumnDataSource
     get_data_by_activity_src: ColumnDataSource
     substantial_change: bool
@@ -3404,7 +3404,7 @@ class FinePerformanceMetrics(DashboardComponent):
     def __init__(self, scheduler: Scheduler, **kwargs: Any):
         self.scheduler = scheduler
         self.task_exec_by_prefix_src = ColumnDataSource(data={})
-        self.task_exec_by_prefix_ymax = 0.0
+        self.task_exec_by_prefix_xmax = 0.0
         self.task_exec_by_activity_src = ColumnDataSource(data={})
         self.get_data_by_activity_src = ColumnDataSource(data={})
         self.substantial_change = False
@@ -3447,17 +3447,9 @@ def __init__(self, scheduler: Scheduler, **kwargs: Any):
 
     def _handle_change_unit(self, attr: str, old: str, new: str) -> None:
         self.substantial_change = True
-
-        if new == "seconds":
-            yfmt = "00:00:00"
-        elif new == "bytes":
-            yfmt = "0.00b"
-        elif new == "count":
-            yfmt = "0"
-        else:
-            yfmt = "0.000"
         assert self.task_exec_by_prefix_chart
-        self.task_exec_by_prefix_chart.yaxis[0].formatter.format = yfmt
+        fmt = self._bokeh_unit_format()
+        self.task_exec_by_prefix_chart.xaxis[0].formatter.format = fmt
 
     @without_property_validation
     @log_errors
@@ -3485,7 +3477,7 @@ def update(self):
                 title="Send data, by activity",
             )
 
-        self.task_exec_by_prefix_chart.y_range.end = self.task_exec_by_prefix_ymax * 1.1
+        self.task_exec_by_prefix_chart.x_range.end = self.task_exec_by_prefix_xmax * 1.1
 
         if self.substantial_change:
             # Visible activities and/or functions changed
@@ -3556,9 +3548,19 @@ def _update_selectors(self) -> None:
                     f"Filter by span tag ({len(self.span_tag_selector.options)}):"
                 )
 
+    def _bokeh_unit_format(self) -> str:
+        unit = self.unit_selector.value
+        if unit == "seconds":
+            return "00:00:00"
+        elif unit == "bytes":
+            return "0.00b"
+        elif unit == "count":
+            return "0"
+        else:
+            return "0.000"
+
     def _format(self, val: float) -> str:
         unit = self.unit_selector.value
-        assert isinstance(unit, str)
         if unit == "seconds":
             return format_time(val)
         elif unit == "bytes":
@@ -3657,7 +3659,7 @@ def _build_data_sources(self) -> None:
 
         (
             self.task_exec_by_prefix_src.data,
-            self.task_exec_by_prefix_ymax,
+            self.task_exec_by_prefix_xmax,
         ) = self._build_task_execution_by_prefix_data(execute_by_func)
         self.task_exec_by_activity_src.data = self._build_pie_data(execute)
         self.get_data_by_activity_src.data = self._build_pie_data(get_data)
@@ -3773,7 +3775,7 @@ def _build_task_execution_by_prefix_chart(self) -> figure:
         _update_task_execution_by_prefix_chart
         """
         barchart = figure(
-            x_range=[],
+            y_range=[],
             height=500,
             sizing_mode="scale_both",
             title="Task execution, by function",
@@ -3782,9 +3784,9 @@ def _build_task_execution_by_prefix_chart(self) -> figure:
         barchart.yaxis.visible = True
         # As of Bokeh 3.1, DataRange1D (the default) does not work when switching back
         # from bytes (GiBs) to seconds (hundreds). So we need to manually update it.
-        barchart.y_range = Range1d(0, 1)
-        barchart.yaxis[0].formatter = NumeralTickFormatter(format="00:00:00")
-        barchart.xaxis.major_label_orientation = 0.2
+        barchart.x_range = Range1d(0, 1)
+        barchart.xaxis[0].formatter = NumeralTickFormatter(format="00:00:00")
+        barchart.xaxis.major_label_orientation = 0.4
         barchart.grid.grid_line_color = None
         return barchart
 
@@ -3798,7 +3800,7 @@ def _update_task_execution_by_prefix_chart(self) -> None:
         """
         barchart = self.task_exec_by_prefix_chart
         assert barchart is not None
-        barchart.x_range = FactorRange(*self.visible_functions)
+        barchart.y_range = FactorRange(*self.visible_functions)
 
         palette = [
             p
@@ -3806,9 +3808,9 @@ def _update_task_execution_by_prefix_chart(self) -> None:
             if a in self.stacked_chart_visible_activities
         ]
         assert len(palette) == len(self.stacked_chart_visible_activities)
-        renderers = barchart.vbar_stack(
+        renderers = barchart.hbar_stack(
             self.stacked_chart_visible_activities,
-            x="__functions",
+            y="__functions",
             width=0.9,
             source=self.task_exec_by_prefix_src,
             color=palette,


Test patch: