From e5049309c455cbc0d35b53eab0cd97c9a8694358 Mon Sep 17 00:00:00 2001
From: he weilin <heweilin19971212@163.com>
Date: Fri, 27 Sep 2024 11:18:09 +0800
Subject: [PATCH 01/14] Add new feature to plot each series's component
 separately

---
 darts/ad/anomaly_model/anomaly_model.py  |   2 +
 darts/ad/anomaly_model/forecasting_am.py |   2 +
 darts/ad/scorers/scorers.py              |   4 +
 darts/ad/utils.py                        | 315 +++++++++++++++++------
 4 files changed, 244 insertions(+), 79 deletions(-)

diff --git a/darts/ad/anomaly_model/anomaly_model.py b/darts/ad/anomaly_model/anomaly_model.py
index be66758a0f..f93ef3301e 100644
--- a/darts/ad/anomaly_model/anomaly_model.py
+++ b/darts/ad/anomaly_model/anomaly_model.py
@@ -250,6 +250,7 @@ def show_anomalies(
         names_of_scorers: Union[str, Sequence[str]] = None,
         title: str = None,
         metric: Optional[Literal["AUC_ROC", "AUC_PR"]] = None,
+        multivariate_plot: bool = False,
         **score_kwargs,
     ):
         """Plot the results of the anomaly model.
@@ -313,6 +314,7 @@ def show_anomalies(
             names_of_scorers=names_of_scorers,
             title=title,
             metric=metric,
+            multivariate_plot=multivariate_plot,
         )
 
     @property
diff --git a/darts/ad/anomaly_model/forecasting_am.py b/darts/ad/anomaly_model/forecasting_am.py
index fd3eb9a33a..42dee0960c 100644
--- a/darts/ad/anomaly_model/forecasting_am.py
+++ b/darts/ad/anomaly_model/forecasting_am.py
@@ -447,6 +447,7 @@ def show_anomalies(
         names_of_scorers: Union[str, Sequence[str]] = None,
         title: str = None,
         metric: Optional[Literal["AUC_ROC", "AUC_PR"]] = None,
+        multivariate_plot: bool = False,
         **score_kwargs,
     ):
         """Plot the results of the anomaly model.
@@ -535,6 +536,7 @@ def show_anomalies(
             names_of_scorers=names_of_scorers,
             title=title,
             metric=metric,
+            multivariate_plot=multivariate_plot,
             **score_kwargs,
         )
 
diff --git a/darts/ad/scorers/scorers.py b/darts/ad/scorers/scorers.py
index 1afae77d21..ba573229bb 100644
--- a/darts/ad/scorers/scorers.py
+++ b/darts/ad/scorers/scorers.py
@@ -175,6 +175,7 @@ def show_anomalies_from_prediction(
         anomalies: TimeSeries = None,
         title: str = None,
         metric: Optional[Literal["AUC_ROC", "AUC_PR"]] = None,
+        multivariate_plot: bool = False,
     ):
         """Plot the results of the scorer.
 
@@ -229,6 +230,7 @@ def show_anomalies_from_prediction(
             names_of_scorers=scorer_name,
             title=title,
             metric=metric,
+            multivariate_plot=multivariate_plot,
         )
 
     @property
@@ -579,6 +581,7 @@ def show_anomalies(
         scorer_name: str = None,
         title: str = None,
         metric: Optional[Literal["AUC_ROC", "AUC_PR"]] = None,
+        multivariate_plot: bool = False,
     ):
         """Plot the results of the scorer.
 
@@ -632,6 +635,7 @@ def show_anomalies(
             names_of_scorers=scorer_name,
             title=title,
             metric=metric,
+            multivariate_plot=multivariate_plot,
         )
 
     @property
diff --git a/darts/ad/utils.py b/darts/ad/utils.py
index 0d5260b1f4..0ee00ca6f7 100644
--- a/darts/ad/utils.py
+++ b/darts/ad/utils.py
@@ -310,6 +310,7 @@ def show_anomalies_from_scores(
     names_of_scorers: Union[str, Sequence[str]] = None,
     title: str = None,
     metric: Optional[Literal["AUC_ROC", "AUC_PR"]] = None,
+    multivariate_plot: bool = False,
 ):
     """Plot the results generated by an anomaly model.
 
@@ -352,6 +353,7 @@ def show_anomalies_from_scores(
         Only effective when `pred_scores` is not `None`.
         Default: "AUC_ROC".
     """
+
     series = _check_input(
         series,
         name="series",
@@ -362,6 +364,7 @@ def show_anomalies_from_scores(
         title = "Anomaly results"
 
     nbr_plots = 1
+
     if anomalies is not None:
         nbr_plots = nbr_plots + 1
     elif metric is not None:
@@ -421,105 +424,259 @@ def show_anomalies_from_scores(
 
         nbr_plots = nbr_plots + len(set(window))
 
-    fig, axs = plt.subplots(
-        nbr_plots,
-        figsize=(8, 4 + 2 * (nbr_plots - 1)),
-        sharex=True,
-        gridspec_kw={"height_ratios": [2] + [1] * (nbr_plots - 1)},
-        squeeze=False,
-    )
+    if multivariate_plot:
+        series = _check_input(
+            series,
+            name="series",
+            check_multivariate=True,
+        )[0]
+        series_width = series.n_components
+
+        if pred_series is not None:
+            pred_series = _check_input(
+                pred_series,
+                name="pred_series",
+                width_expected=series.width,
+                check_multivariate=True,
+            )[0]
+
+        if anomalies is not None:
+            anomalies = _check_input(
+                anomalies,
+                name="anomalies",
+                width_expected=series.width,
+                check_multivariate=True,
+            )[0]
+
+        if pred_scores is not None:
+            for pred_score in pred_scores:
+                pred_score = _check_input(
+                    pred_score,
+                    name="pred_score",
+                    width_expected=series.width,
+                    check_multivariate=True,
+                )[0]
+
+        series_width = series.n_components
+        fig, axs = plt.subplots(
+            nbr_plots * series_width,
+            figsize=(8, 4 + 2 * (nbr_plots * series_width - 1)),
+            sharex=True,
+            gridspec_kw={"height_ratios": [2] + [1] * (nbr_plots * series_width - 1)},
+            squeeze=False,
+        )
 
-    index_ax = 0
+        for i in range(series_width):
+            index_ax = i * nbr_plots
 
-    _plot_series(series=series, ax_id=axs[index_ax][0], linewidth=0.5, label_name="")
+            _plot_series(
+                series=series[series.components[i]],
+                ax_id=axs[index_ax][0],
+                linewidth=0.5,
+                label_name="",
+            )
 
-    if pred_series is not None:
-        _plot_series(
-            series=pred_series,
-            ax_id=axs[index_ax][0],
-            linewidth=0.5,
-            label_name="model output",
-        )
+            if pred_series[pred_series.components[i]] is not None:
+                _plot_series(
+                    series=pred_series[pred_series.components[i]],
+                    ax_id=axs[index_ax][0],
+                    linewidth=0.5,
+                    label_name=pred_series.components[i] + " model_output",
+                )
 
-    axs[index_ax][0].set_title("")
+            axs[index_ax][0].set_title("")
 
-    if anomalies is not None or pred_scores is not None:
-        axs[index_ax][0].set_xlabel("")
+            if anomalies is not None or pred_scores is not None:
+                axs[index_ax][0].set_xlabel("")
 
-    axs[index_ax][0].legend(loc="upper center", bbox_to_anchor=(0.5, 1.1), ncol=2)
+            axs[index_ax][0].legend(
+                loc="upper center", bbox_to_anchor=(0.5, 1.1), ncol=2
+            )
 
-    if pred_scores is not None:
-        dict_input = {}
-
-        for idx, (score, w) in enumerate(zip(pred_scores, window)):
-            dict_input[idx] = {"series_score": score, "window": w, "name_id": idx}
-
-        for index, elem in enumerate(
-            sorted(dict_input.items(), key=lambda x: x[1]["window"])
-        ):
-            if index == 0:
-                current_window = elem[1]["window"]
-                index_ax = index_ax + 1
-
-            idx = elem[1]["name_id"]
-            w = elem[1]["window"]
-
-            if w != current_window:
-                current_window = w
-                index_ax = index_ax + 1
-
-            if metric is not None:
-                value = round(
-                    eval_metric_from_scores(
-                        anomalies=anomalies,
-                        pred_scores=pred_scores[idx],
-                        window=w,
-                        metric=metric,
-                    ),
-                    3,
+            if pred_scores is not None:
+                dict_input = {}
+
+                for idx, (score, w) in enumerate(zip(pred_scores, window)):
+                    dict_input[idx] = {
+                        "series_score": score,
+                        "window": w,
+                        "name_id": idx,
+                    }
+
+                for index, elem in enumerate(
+                    sorted(dict_input.items(), key=lambda x: x[1]["window"])
+                ):
+                    if index == 0:
+                        current_window = elem[1]["window"]
+                        index_ax = index_ax + 1
+
+                    idx = elem[1]["name_id"]
+                    w = elem[1]["window"]
+
+                    if w != current_window:
+                        current_window = w
+                        index_ax = index_ax + 1
+
+                    if metric is not None:
+                        value = round(
+                            eval_metric_from_scores(
+                                anomalies=anomalies[anomalies.components[i]],
+                                pred_scores=pred_scores[idx][
+                                    pred_scores[idx].components[i]
+                                ],
+                                window=w,
+                                metric=metric,
+                            ),
+                            3,
+                        )
+                    else:
+                        value = None
+
+                    if names_of_scorers is not None:
+                        label = (
+                            names_of_scorers[idx] + [f" ({value})", ""][value is None]
+                        )
+                    else:
+                        label = f"score_{str(idx)}" + [f" ({value})", ""][value is None]
+
+                    _plot_series(
+                        series=elem[1]["series_score"][
+                            elem[1]["series_score"].components[i]
+                        ],
+                        ax_id=axs[index_ax][0],
+                        linewidth=0.5,
+                        label_name=label,
+                    )
+
+                    axs[index_ax][0].legend(
+                        loc="upper center", bbox_to_anchor=(0.5, 1.19), ncol=2
+                    )
+                    axs[index_ax][0].set_title(f"Window: {str(w)}", loc="left")
+                    axs[index_ax][0].set_title("")
+                    axs[index_ax][0].set_xlabel("")
+
+            if anomalies is not None:
+                _plot_series(
+                    series=anomalies[anomalies.components[i]],
+                    ax_id=axs[index_ax + 1][0],
+                    linewidth=1,
+                    label_name=anomalies.components[i],
+                    color="red",
                 )
-            else:
-                value = None
 
-            if names_of_scorers is not None:
-                label = names_of_scorers[idx] + [f" ({value})", ""][value is None]
+                axs[index_ax + 1][0].set_title("")
+                axs[index_ax + 1][0].set_ylim([-0.1, 1.1])
+                axs[index_ax + 1][0].set_yticks([0, 1])
+                axs[index_ax + 1][0].set_yticklabels(["no", "yes"])
+                axs[index_ax + 1][0].legend(
+                    loc="upper center", bbox_to_anchor=(0.5, 1.2), ncol=2
+                )
             else:
-                label = f"score_{str(idx)}" + [f" ({value})", ""][value is None]
+                axs[index_ax][0].set_xlabel("timestamp")
+
+        fig.suptitle(title)
+    else:
+        fig, axs = plt.subplots(
+            nbr_plots,
+            figsize=(8, 4 + 2 * (nbr_plots - 1)),
+            sharex=True,
+            gridspec_kw={"height_ratios": [2] + [1] * (nbr_plots - 1)},
+            squeeze=False,
+        )
 
+        index_ax = 0
+
+        _plot_series(
+            series=series, ax_id=axs[index_ax][0], linewidth=0.5, label_name=""
+        )
+
+        if pred_series is not None:
             _plot_series(
-                series=elem[1]["series_score"],
+                series=pred_series,
                 ax_id=axs[index_ax][0],
                 linewidth=0.5,
-                label_name=label,
+                label_name="model output",
             )
 
-            axs[index_ax][0].legend(
-                loc="upper center", bbox_to_anchor=(0.5, 1.19), ncol=2
-            )
-            axs[index_ax][0].set_title(f"Window: {str(w)}", loc="left")
-            axs[index_ax][0].set_title("")
+        axs[index_ax][0].set_title("")
+
+        if anomalies is not None or pred_scores is not None:
             axs[index_ax][0].set_xlabel("")
 
-    if anomalies is not None:
-        _plot_series(
-            series=anomalies,
-            ax_id=axs[index_ax + 1][0],
-            linewidth=1,
-            label_name="anomalies",
-            color="red",
-        )
+        axs[index_ax][0].legend(loc="upper center", bbox_to_anchor=(0.5, 1.1), ncol=2)
+
+        if pred_scores is not None:
+            dict_input = {}
+
+            for idx, (score, w) in enumerate(zip(pred_scores, window)):
+                dict_input[idx] = {"series_score": score, "window": w, "name_id": idx}
+
+            for index, elem in enumerate(
+                sorted(dict_input.items(), key=lambda x: x[1]["window"])
+            ):
+                if index == 0:
+                    current_window = elem[1]["window"]
+                    index_ax = index_ax + 1
+
+                idx = elem[1]["name_id"]
+                w = elem[1]["window"]
+
+                if w != current_window:
+                    current_window = w
+                    index_ax = index_ax + 1
+
+                if metric is not None:
+                    value = round(
+                        eval_metric_from_scores(
+                            anomalies=anomalies,
+                            pred_scores=pred_scores[idx],
+                            window=w,
+                            metric=metric,
+                        ),
+                        3,
+                    )
+                else:
+                    value = None
+
+                if names_of_scorers is not None:
+                    label = names_of_scorers[idx] + [f" ({value})", ""][value is None]
+                else:
+                    label = f"score_{str(idx)}" + [f" ({value})", ""][value is None]
+
+                _plot_series(
+                    series=elem[1]["series_score"],
+                    ax_id=axs[index_ax][0],
+                    linewidth=0.5,
+                    label_name=label,
+                )
 
-        axs[index_ax + 1][0].set_title("")
-        axs[index_ax + 1][0].set_ylim([-0.1, 1.1])
-        axs[index_ax + 1][0].set_yticks([0, 1])
-        axs[index_ax + 1][0].set_yticklabels(["no", "yes"])
-        axs[index_ax + 1][0].legend(
-            loc="upper center", bbox_to_anchor=(0.5, 1.2), ncol=2
-        )
-    else:
-        axs[index_ax][0].set_xlabel("timestamp")
+                axs[index_ax][0].legend(
+                    loc="upper center", bbox_to_anchor=(0.5, 1.19), ncol=2
+                )
+                axs[index_ax][0].set_title(f"Window: {str(w)}", loc="left")
+                axs[index_ax][0].set_title("")
+                axs[index_ax][0].set_xlabel("")
+
+        if anomalies is not None:
+            _plot_series(
+                series=anomalies,
+                ax_id=axs[index_ax + 1][0],
+                linewidth=1,
+                label_name="anomalies",
+                color="red",
+            )
+
+            axs[index_ax + 1][0].set_title("")
+            axs[index_ax + 1][0].set_ylim([-0.1, 1.1])
+            axs[index_ax + 1][0].set_yticks([0, 1])
+            axs[index_ax + 1][0].set_yticklabels(["no", "yes"])
+            axs[index_ax + 1][0].legend(
+                loc="upper center", bbox_to_anchor=(0.5, 1.2), ncol=2
+            )
+        else:
+            axs[index_ax][0].set_xlabel("timestamp")
 
-    fig.suptitle(title)
+        fig.suptitle(title)
 
 
 def _assert_binary(series: TimeSeries, name: str):

From b05eb8a2bc241cbc8e6bbbb439cd046527ee1bd2 Mon Sep 17 00:00:00 2001
From: he weilin <heweilin19971212@163.com>
Date: Tue, 31 Dec 2024 09:06:50 +0800
Subject: [PATCH 02/14] Update the docstring

---
 darts/ad/anomaly_model/anomaly_model.py  | 2 ++
 darts/ad/anomaly_model/forecasting_am.py | 2 ++
 darts/ad/scorers/scorers.py              | 2 ++
 3 files changed, 6 insertions(+)

diff --git a/darts/ad/anomaly_model/anomaly_model.py b/darts/ad/anomaly_model/anomaly_model.py
index 49ebe63ba9..d75ad18ab0 100644
--- a/darts/ad/anomaly_model/anomaly_model.py
+++ b/darts/ad/anomaly_model/anomaly_model.py
@@ -284,6 +284,8 @@ def show_anomalies(
             Default: "AUC_ROC".
         score_kwargs
             parameters for the `score()` method.
+        multivariate_plot
+            If True, it will separately plot each component in multivariate series.
         """
         series = _check_input(series, name="series", num_series_expected=1)[0]
         predict_kwargs = predict_kwargs if predict_kwargs is not None else {}
diff --git a/darts/ad/anomaly_model/forecasting_am.py b/darts/ad/anomaly_model/forecasting_am.py
index 348e025e9f..7fee356cb6 100644
--- a/darts/ad/anomaly_model/forecasting_am.py
+++ b/darts/ad/anomaly_model/forecasting_am.py
@@ -507,6 +507,8 @@ def show_anomalies(
             Optionally, the name of the metric function to use. Must be one of "AUC_ROC" (Area Under the
             Receiver Operating Characteristic Curve) and "AUC_PR" (Average Precision from scores).
             Default: "AUC_ROC".
+        multivariate_plot
+            If True, it will separately plot each component in multivariate series.
         score_kwargs
             parameters for the `score()` method.
         """
diff --git a/darts/ad/scorers/scorers.py b/darts/ad/scorers/scorers.py
index d797c93212..66f01b6692 100644
--- a/darts/ad/scorers/scorers.py
+++ b/darts/ad/scorers/scorers.py
@@ -209,6 +209,8 @@ def show_anomalies_from_prediction(
             Optionally, the name of the metric function to use. Must be one of "AUC_ROC" (Area Under the
             Receiver Operating Characteristic Curve) and "AUC_PR" (Average Precision from scores).
             Default: "AUC_ROC".
+        multivariate_plot
+            If True, it will separately plot each component in multivariate series.
         """
         series = _check_input(series, name="series", num_series_expected=1)[0]
         pred_series = _check_input(

From 33006d44e3967f9d2e59e61d7a8367af45dde9ff Mon Sep 17 00:00:00 2001
From: he weilin <heweilin19971212@163.com>
Date: Tue, 31 Dec 2024 10:19:18 +0800
Subject: [PATCH 03/14] refactor the show_anomalies_from_scores()

---
 darts/ad/utils.py | 347 +++++++++++++++++++++-------------------------
 1 file changed, 155 insertions(+), 192 deletions(-)

diff --git a/darts/ad/utils.py b/darts/ad/utils.py
index 510ef3f924..6f2821a656 100644
--- a/darts/ad/utils.py
+++ b/darts/ad/utils.py
@@ -469,112 +469,23 @@ def show_anomalies_from_scores(
 
         for i in range(series_width):
             index_ax = i * nbr_plots
-
-            _plot_series(
+            _plot_series_and_anomalies(
                 series=series[series.components[i]],
-                ax_id=axs[index_ax][0],
-                linewidth=0.5,
-                label_name="",
-            )
-
-            if pred_series[pred_series.components[i]] is not None:
-                _plot_series(
-                    series=pred_series[pred_series.components[i]],
-                    ax_id=axs[index_ax][0],
-                    linewidth=0.5,
-                    label_name=pred_series.components[i] + " model_output",
-                )
-
-            axs[index_ax][0].set_title("")
-
-            if anomalies is not None or pred_scores is not None:
-                axs[index_ax][0].set_xlabel("")
-
-            axs[index_ax][0].legend(
-                loc="upper center", bbox_to_anchor=(0.5, 1.1), ncol=2
+                anomalies=anomalies[anomalies.components[i]]
+                if anomalies is not None
+                else None,
+                pred_series=pred_series[pred_series.components[i]]
+                if pred_series is not None
+                else None,
+                pred_scores=pred_scores,
+                window=window,
+                names_of_scorers=names_of_scorers,
+                metric=metric,
+                axs=axs,
+                index_ax=index_ax,
+                nbr_plots=nbr_plots,
             )
 
-            if pred_scores is not None:
-                dict_input = {}
-
-                for idx, (score, w) in enumerate(zip(pred_scores, window)):
-                    dict_input[idx] = {
-                        "series_score": score,
-                        "window": w,
-                        "name_id": idx,
-                    }
-
-                for index, elem in enumerate(
-                    sorted(dict_input.items(), key=lambda x: x[1]["window"])
-                ):
-                    if index == 0:
-                        current_window = elem[1]["window"]
-                        index_ax = index_ax + 1
-
-                    idx = elem[1]["name_id"]
-                    w = elem[1]["window"]
-
-                    if w != current_window:
-                        current_window = w
-                        index_ax = index_ax + 1
-
-                    if metric is not None:
-                        value = round(
-                            eval_metric_from_scores(
-                                anomalies=anomalies[anomalies.components[i]],
-                                pred_scores=pred_scores[idx][
-                                    pred_scores[idx].components[i]
-                                ],
-                                window=w,
-                                metric=metric,
-                            ),
-                            3,
-                        )
-                    else:
-                        value = None
-
-                    if names_of_scorers is not None:
-                        label = (
-                            names_of_scorers[idx] + [f" ({value})", ""][value is None]
-                        )
-                    else:
-                        label = f"score_{str(idx)}" + [f" ({value})", ""][value is None]
-
-                    _plot_series(
-                        series=elem[1]["series_score"][
-                            elem[1]["series_score"].components[i]
-                        ],
-                        ax_id=axs[index_ax][0],
-                        linewidth=0.5,
-                        label_name=label,
-                    )
-
-                    axs[index_ax][0].legend(
-                        loc="upper center", bbox_to_anchor=(0.5, 1.19), ncol=2
-                    )
-                    axs[index_ax][0].set_title(f"Window: {str(w)}", loc="left")
-                    axs[index_ax][0].set_title("")
-                    axs[index_ax][0].set_xlabel("")
-
-            if anomalies is not None:
-                _plot_series(
-                    series=anomalies[anomalies.components[i]],
-                    ax_id=axs[index_ax + 1][0],
-                    linewidth=1,
-                    label_name=anomalies.components[i],
-                    color="red",
-                )
-
-                axs[index_ax + 1][0].set_title("")
-                axs[index_ax + 1][0].set_ylim([-0.1, 1.1])
-                axs[index_ax + 1][0].set_yticks([0, 1])
-                axs[index_ax + 1][0].set_yticklabels(["no", "yes"])
-                axs[index_ax + 1][0].legend(
-                    loc="upper center", bbox_to_anchor=(0.5, 1.2), ncol=2
-                )
-            else:
-                axs[index_ax][0].set_xlabel("timestamp")
-
         fig.suptitle(title)
     else:
         fig, axs = plt.subplots(
@@ -586,97 +497,19 @@ def show_anomalies_from_scores(
         )
 
         index_ax = 0
-
-        _plot_series(
-            series=series, ax_id=axs[index_ax][0], linewidth=0.5, label_name=""
+        _plot_series_and_anomalies(
+            series=series,
+            anomalies=anomalies,
+            pred_series=pred_series,
+            pred_scores=pred_scores,
+            window=window,
+            names_of_scorers=names_of_scorers,
+            metric=metric,
+            axs=axs,
+            index_ax=index_ax,
+            nbr_plots=nbr_plots,
         )
 
-        if pred_series is not None:
-            _plot_series(
-                series=pred_series,
-                ax_id=axs[index_ax][0],
-                linewidth=0.5,
-                label_name="model output",
-            )
-
-        axs[index_ax][0].set_title("")
-
-        if anomalies is not None or pred_scores is not None:
-            axs[index_ax][0].set_xlabel("")
-
-        axs[index_ax][0].legend(loc="upper center", bbox_to_anchor=(0.5, 1.1), ncol=2)
-
-        if pred_scores is not None:
-            dict_input = {}
-
-            for idx, (score, w) in enumerate(zip(pred_scores, window)):
-                dict_input[idx] = {"series_score": score, "window": w, "name_id": idx}
-
-            for index, elem in enumerate(
-                sorted(dict_input.items(), key=lambda x: x[1]["window"])
-            ):
-                if index == 0:
-                    current_window = elem[1]["window"]
-                    index_ax = index_ax + 1
-
-                idx = elem[1]["name_id"]
-                w = elem[1]["window"]
-
-                if w != current_window:
-                    current_window = w
-                    index_ax = index_ax + 1
-
-                if metric is not None:
-                    value = round(
-                        eval_metric_from_scores(
-                            anomalies=anomalies,
-                            pred_scores=pred_scores[idx],
-                            window=w,
-                            metric=metric,
-                        ),
-                        3,
-                    )
-                else:
-                    value = None
-
-                if names_of_scorers is not None:
-                    label = names_of_scorers[idx] + [f" ({value})", ""][value is None]
-                else:
-                    label = f"score_{str(idx)}" + [f" ({value})", ""][value is None]
-
-                _plot_series(
-                    series=elem[1]["series_score"],
-                    ax_id=axs[index_ax][0],
-                    linewidth=0.5,
-                    label_name=label,
-                )
-
-                axs[index_ax][0].legend(
-                    loc="upper center", bbox_to_anchor=(0.5, 1.19), ncol=2
-                )
-                axs[index_ax][0].set_title(f"Window: {str(w)}", loc="left")
-                axs[index_ax][0].set_title("")
-                axs[index_ax][0].set_xlabel("")
-
-        if anomalies is not None:
-            _plot_series(
-                series=anomalies,
-                ax_id=axs[index_ax + 1][0],
-                linewidth=1,
-                label_name="anomalies",
-                color="red",
-            )
-
-            axs[index_ax + 1][0].set_title("")
-            axs[index_ax + 1][0].set_ylim([-0.1, 1.1])
-            axs[index_ax + 1][0].set_yticks([0, 1])
-            axs[index_ax + 1][0].set_yticklabels(["no", "yes"])
-            axs[index_ax + 1][0].legend(
-                loc="upper center", bbox_to_anchor=(0.5, 1.2), ncol=2
-            )
-        else:
-            axs[index_ax][0].set_xlabel("timestamp")
-
         fig.suptitle(title)
 
 
@@ -937,3 +770,133 @@ def _assert_fit_called(fit_called: bool, name: str):
             ),
             logger=logger,
         )
+
+
+def _plot_series_and_anomalies(
+    series: TimeSeries,
+    anomalies: TimeSeries,
+    pred_series: TimeSeries,
+    pred_scores: Sequence[TimeSeries],
+    window: Sequence[int],
+    names_of_scorers: Sequence[str],
+    metric: str,
+    axs: plt.Axes,
+    index_ax: int,
+    nbr_plots: int,
+):
+    """Helper function to plot series and anomalies.
+
+    Parameters
+    ----------
+    series
+        The actual series to visualize anomalies from.
+    anomalies
+        The ground truth of the anomalies (1 if it is an anomaly and 0 if not).
+    pred_series
+        Output of the model given as input the `series` (can be stochastic).
+    pred_scores
+        Output of the scorers given the output of the model and `series`.
+    window
+        Window parameter for each anomaly scores.
+    names_of_scorers
+        Name of the scores.
+    metric
+        The name of the metric function to use.
+    axs
+        The axes to plot on.
+    index_ax
+        The index of the current axis.
+    nbr_plots
+        The number of plots.
+    """
+    _plot_series(series=series, ax_id=axs[index_ax][0], linewidth=0.5, label_name="")
+
+    if pred_series is not None:
+        _plot_series(
+            series=pred_series,
+            ax_id=axs[index_ax][0],
+            linewidth=0.5,
+            label_name="model output",
+        )
+
+    axs[index_ax][0].set_title("")
+
+    if anomalies is not None or pred_scores is not None:
+        axs[index_ax][0].set_xlabel("")
+
+    axs[index_ax][0].legend(loc="upper center", bbox_to_anchor=(0.5, 1.1), ncol=2)
+
+    if pred_scores is not None:
+        dict_input = {}
+
+        for idx, (score, w) in enumerate(zip(pred_scores, window)):
+            dict_input[idx] = {"series_score": score, "window": w, "name_id": idx}
+
+        for index, elem in enumerate(
+            sorted(dict_input.items(), key=lambda x: x[1]["window"])
+        ):
+            if index == 0:
+                current_window = elem[1]["window"]
+                index_ax = index_ax + 1
+
+            idx = elem[1]["name_id"]
+            w = elem[1]["window"]
+
+            if w != current_window:
+                current_window = w
+                index_ax = index_ax + 1
+
+            if metric is not None:
+                value = round(
+                    eval_metric_from_scores(
+                        anomalies=anomalies,
+                        pred_scores=pred_scores[idx][
+                            pred_scores[idx].components[index_ax // nbr_plots]
+                        ],
+                        window=w,
+                        metric=metric,
+                    ),
+                    3,
+                )
+            else:
+                value = None
+
+            if names_of_scorers is not None:
+                label = names_of_scorers[idx] + [f" ({value})", ""][value is None]
+            else:
+                label = f"score_{str(idx)}" + [f" ({value})", ""][value is None]
+
+            _plot_series(
+                series=elem[1]["series_score"][
+                    elem[1]["series_score"].components[index_ax // nbr_plots]
+                ],
+                ax_id=axs[index_ax][0],
+                linewidth=0.5,
+                label_name=label,
+            )
+
+            axs[index_ax][0].legend(
+                loc="upper center", bbox_to_anchor=(0.5, 1.19), ncol=2
+            )
+            axs[index_ax][0].set_title(f"Window: {str(w)}", loc="left")
+            axs[index_ax][0].set_title("")
+            axs[index_ax][0].set_xlabel("")
+
+    if anomalies is not None:
+        _plot_series(
+            series=anomalies,
+            ax_id=axs[index_ax + 1][0],
+            linewidth=1,
+            label_name="anomalies",
+            color="red",
+        )
+
+        axs[index_ax + 1][0].set_title("")
+        axs[index_ax + 1][0].set_ylim([-0.1, 1.1])
+        axs[index_ax + 1][0].set_yticks([0, 1])
+        axs[index_ax + 1][0].set_yticklabels(["no", "yes"])
+        axs[index_ax + 1][0].legend(
+            loc="upper center", bbox_to_anchor=(0.5, 1.2), ncol=2
+        )
+    else:
+        axs[index_ax][0].set_xlabel("timestamp")

From e6b68545bd10a2d904242da97ea520850ce878b4 Mon Sep 17 00:00:00 2001
From: he weilin <heweilin19971212@163.com>
Date: Tue, 31 Dec 2024 10:55:24 +0800
Subject: [PATCH 04/14] Update changelog

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index cf805cad84..8439668f75 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@ but cannot always guarantee backwards compatibility. Changes that may **break co
 **Improved**
 
 - New model: `StatsForecastAutoTBATS`. This model offers the [AutoTBATS](https://nixtlaverse.nixtla.io/statsforecast/src/core/models.html#autotbats) model from Nixtla's `statsforecasts` library. [#2611](https://github.com/unit8co/darts/pull/2611) by [He Weilin](https://github.com/cnhwl).
+- Added `multivariate_plot` parameter in `show_anomalies()` to separately plot each component in multivariate series. [#2544](https://github.com/unit8co/darts/pull/2544) by [He Weilin](https://github.com/cnhwl).
 
 **Fixed**
 

From 8468f6d851d9ce4c1d94473414c045bf9f169b09 Mon Sep 17 00:00:00 2001
From: he weilin <heweilin19971212@163.com>
Date: Tue, 31 Dec 2024 17:21:59 +0800
Subject: [PATCH 05/14] Improve code in utils.py

---
 darts/ad/utils.py | 60 +++++++++++++++++++++--------------------------
 1 file changed, 27 insertions(+), 33 deletions(-)

diff --git a/darts/ad/utils.py b/darts/ad/utils.py
index 6f2821a656..672a3a818a 100644
--- a/darts/ad/utils.py
+++ b/darts/ad/utils.py
@@ -353,13 +353,23 @@ def show_anomalies_from_scores(
         Receiver Operating Characteristic Curve) and "AUC_PR" (Average Precision from scores).
         Only effective when `pred_scores` is not `None`.
         Default: "AUC_ROC".
+    multivariate_plot
+        If True, it will separately plot each component in multivariate series.
     """
 
-    series = _check_input(
-        series,
-        name="series",
-        num_series_expected=1,
-    )[0]
+    series = (
+        _check_input(
+            series,
+            name="series",
+            check_multivariate=True,
+        )[0]
+        if multivariate_plot
+        else _check_input(
+            series,
+            name="series",
+            num_series_expected=1,
+        )[0]
+    )
 
     if title is None and pred_scores is not None:
         title = "Anomaly results"
@@ -424,15 +434,17 @@ def show_anomalies_from_scores(
             )
 
         nbr_plots = nbr_plots + len(set(window))
-
-    if multivariate_plot:
-        series = _check_input(
-            series,
-            name="series",
-            check_multivariate=True,
-        )[0]
         series_width = series.n_components
+        plots_per_ts = nbr_plots * series_width if multivariate_plot else nbr_plots
+        fig, axs = plt.subplots(
+            plots_per_ts,
+            figsize=(8, 4 + 2 * (plots_per_ts - 1)),
+            sharex=True,
+            gridspec_kw={"height_ratios": [2] + [1] * (plots_per_ts - 1)},
+            squeeze=False,
+        )
 
+    if multivariate_plot:
         if pred_series is not None:
             pred_series = _check_input(
                 pred_series,
@@ -446,6 +458,7 @@ def show_anomalies_from_scores(
                 anomalies,
                 name="anomalies",
                 width_expected=series.width,
+                check_binary=True,
                 check_multivariate=True,
             )[0]
 
@@ -458,17 +471,7 @@ def show_anomalies_from_scores(
                     check_multivariate=True,
                 )[0]
 
-        series_width = series.n_components
-        fig, axs = plt.subplots(
-            nbr_plots * series_width,
-            figsize=(8, 4 + 2 * (nbr_plots * series_width - 1)),
-            sharex=True,
-            gridspec_kw={"height_ratios": [2] + [1] * (nbr_plots * series_width - 1)},
-            squeeze=False,
-        )
-
         for i in range(series_width):
-            index_ax = i * nbr_plots
             _plot_series_and_anomalies(
                 series=series[series.components[i]],
                 anomalies=anomalies[anomalies.components[i]]
@@ -482,21 +485,12 @@ def show_anomalies_from_scores(
                 names_of_scorers=names_of_scorers,
                 metric=metric,
                 axs=axs,
-                index_ax=index_ax,
+                index_ax=i * nbr_plots,
                 nbr_plots=nbr_plots,
             )
 
         fig.suptitle(title)
     else:
-        fig, axs = plt.subplots(
-            nbr_plots,
-            figsize=(8, 4 + 2 * (nbr_plots - 1)),
-            sharex=True,
-            gridspec_kw={"height_ratios": [2] + [1] * (nbr_plots - 1)},
-            squeeze=False,
-        )
-
-        index_ax = 0
         _plot_series_and_anomalies(
             series=series,
             anomalies=anomalies,
@@ -506,7 +500,7 @@ def show_anomalies_from_scores(
             names_of_scorers=names_of_scorers,
             metric=metric,
             axs=axs,
-            index_ax=index_ax,
+            index_ax=0,
             nbr_plots=nbr_plots,
         )
 

From da1e644d9ac3c3937417819ab2b2c2ed3d16d6f9 Mon Sep 17 00:00:00 2001
From: he weilin <heweilin19971212@163.com>
Date: Thu, 2 Jan 2025 09:21:43 +0800
Subject: [PATCH 06/14] Improve code in utils.py

---
 darts/ad/utils.py | 72 ++++++++++++++++++++++-------------------------
 1 file changed, 33 insertions(+), 39 deletions(-)

diff --git a/darts/ad/utils.py b/darts/ad/utils.py
index 672a3a818a..8bba465551 100644
--- a/darts/ad/utils.py
+++ b/darts/ad/utils.py
@@ -357,25 +357,17 @@ def show_anomalies_from_scores(
         If True, it will separately plot each component in multivariate series.
     """
 
-    series = (
-        _check_input(
-            series,
-            name="series",
-            check_multivariate=True,
-        )[0]
-        if multivariate_plot
-        else _check_input(
-            series,
-            name="series",
-            num_series_expected=1,
-        )[0]
-    )
+    series = _check_input(
+        series,
+        name="series",
+        num_series_expected=1,
+        check_multivariate=multivariate_plot,
+    )[0]
 
     if title is None and pred_scores is not None:
         title = "Anomaly results"
 
     nbr_plots = 1
-
     if anomalies is not None:
         nbr_plots = nbr_plots + 1
     elif metric is not None:
@@ -433,7 +425,7 @@ def show_anomalies_from_scores(
                 logger=logger,
             )
 
-        nbr_plots = nbr_plots + len(set(window))
+        nbr_plots += len(set(window))
         series_width = series.n_components
         plots_per_ts = nbr_plots * series_width if multivariate_plot else nbr_plots
         fig, axs = plt.subplots(
@@ -444,33 +436,36 @@ def show_anomalies_from_scores(
             squeeze=False,
         )
 
-    if multivariate_plot:
-        if pred_series is not None:
-            pred_series = _check_input(
-                pred_series,
-                name="pred_series",
-                width_expected=series.width,
-                check_multivariate=True,
-            )[0]
+    if pred_series is not None:
+        pred_series = _check_input(
+            pred_series,
+            name="pred_series",
+            width_expected=series.width,
+            num_series_expected=1,
+            check_multivariate=True,
+        )[0]
 
-        if anomalies is not None:
-            anomalies = _check_input(
-                anomalies,
-                name="anomalies",
+    if anomalies is not None:
+        anomalies = _check_input(
+            anomalies,
+            name="anomalies",
+            width_expected=series.width,
+            num_series_expected=1,
+            check_binary=True,
+            check_multivariate=True,
+        )[0]
+
+    if pred_scores is not None:
+        for pred_score in pred_scores:
+            pred_score = _check_input(
+                pred_score,
+                name="pred_score",
                 width_expected=series.width,
-                check_binary=True,
+                num_series_expected=1,
                 check_multivariate=True,
             )[0]
 
-        if pred_scores is not None:
-            for pred_score in pred_scores:
-                pred_score = _check_input(
-                    pred_score,
-                    name="pred_score",
-                    width_expected=series.width,
-                    check_multivariate=True,
-                )[0]
-
+    if multivariate_plot:
         for i in range(series_width):
             _plot_series_and_anomalies(
                 series=series[series.components[i]],
@@ -489,7 +484,6 @@ def show_anomalies_from_scores(
                 nbr_plots=nbr_plots,
             )
 
-        fig.suptitle(title)
     else:
         _plot_series_and_anomalies(
             series=series,
@@ -504,7 +498,7 @@ def show_anomalies_from_scores(
             nbr_plots=nbr_plots,
         )
 
-        fig.suptitle(title)
+    fig.suptitle(title)
 
 
 def _assert_binary(series: TimeSeries, name: str):

From 8e51e2c57d80be72da4126d653dc716a8286c3ae Mon Sep 17 00:00:00 2001
From: he weilin <heweilin19971212@163.com>
Date: Thu, 2 Jan 2025 09:59:13 +0800
Subject: [PATCH 07/14] make check_multivariate depend on multivariate_plot

---
 darts/ad/utils.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/darts/ad/utils.py b/darts/ad/utils.py
index 8bba465551..a0a4dfc40a 100644
--- a/darts/ad/utils.py
+++ b/darts/ad/utils.py
@@ -442,7 +442,7 @@ def show_anomalies_from_scores(
             name="pred_series",
             width_expected=series.width,
             num_series_expected=1,
-            check_multivariate=True,
+            check_multivariate=multivariate_plot,
         )[0]
 
     if anomalies is not None:
@@ -452,7 +452,7 @@ def show_anomalies_from_scores(
             width_expected=series.width,
             num_series_expected=1,
             check_binary=True,
-            check_multivariate=True,
+            check_multivariate=multivariate_plot,
         )[0]
 
     if pred_scores is not None:
@@ -462,7 +462,7 @@ def show_anomalies_from_scores(
                 name="pred_score",
                 width_expected=series.width,
                 num_series_expected=1,
-                check_multivariate=True,
+                check_multivariate=multivariate_plot,
             )[0]
 
     if multivariate_plot:

From f3e0db21206547bb42447f8163a283ad3c00342d Mon Sep 17 00:00:00 2001
From: dennisbader <dennis.bader@gmx.ch>
Date: Fri, 3 Jan 2025 13:55:01 +0100
Subject: [PATCH 08/14] update utils

---
 darts/ad/scorers/scorers.py |  2 +
 darts/ad/utils.py           | 87 ++++++++++++++++++-------------------
 2 files changed, 44 insertions(+), 45 deletions(-)

diff --git a/darts/ad/scorers/scorers.py b/darts/ad/scorers/scorers.py
index 66f01b6692..7e935c56dc 100644
--- a/darts/ad/scorers/scorers.py
+++ b/darts/ad/scorers/scorers.py
@@ -615,6 +615,8 @@ def show_anomalies(
             Optionally, the name of the metric function to use. Must be one of "AUC_ROC" (Area Under the
             Receiver Operating Characteristic Curve) and "AUC_PR" (Average Precision from scores).
             Default: "AUC_ROC".
+        multivariate_plot
+            If True, it will separately plot each component in multivariate series.
         """
         series = _check_input(series, name="series", num_series_expected=1)[0]
         pred_scores = self.score(series)
diff --git a/darts/ad/utils.py b/darts/ad/utils.py
index a0a4dfc40a..26a2a6f42f 100644
--- a/darts/ad/utils.py
+++ b/darts/ad/utils.py
@@ -356,7 +356,6 @@ def show_anomalies_from_scores(
     multivariate_plot
         If True, it will separately plot each component in multivariate series.
     """
-
     series = _check_input(
         series,
         name="series",
@@ -426,78 +425,80 @@ def show_anomalies_from_scores(
             )
 
         nbr_plots += len(set(window))
-        series_width = series.n_components
-        plots_per_ts = nbr_plots * series_width if multivariate_plot else nbr_plots
-        fig, axs = plt.subplots(
-            plots_per_ts,
-            figsize=(8, 4 + 2 * (plots_per_ts - 1)),
-            sharex=True,
-            gridspec_kw={"height_ratios": [2] + [1] * (plots_per_ts - 1)},
-            squeeze=False,
-        )
 
+    series_width = series.n_components
     if pred_series is not None:
         pred_series = _check_input(
             pred_series,
             name="pred_series",
-            width_expected=series.width,
+            width_expected=series_width,
             num_series_expected=1,
             check_multivariate=multivariate_plot,
         )[0]
 
-    if anomalies is not None:
+    if anomalies is not None and multivariate_plot:
         anomalies = _check_input(
             anomalies,
             name="anomalies",
-            width_expected=series.width,
+            width_expected=series_width,
             num_series_expected=1,
             check_binary=True,
             check_multivariate=multivariate_plot,
         )[0]
 
-    if pred_scores is not None:
+    if pred_scores is not None and multivariate_plot:
         for pred_score in pred_scores:
-            pred_score = _check_input(
+            _ = _check_input(
                 pred_score,
                 name="pred_score",
-                width_expected=series.width,
+                width_expected=series_width,
                 num_series_expected=1,
                 check_multivariate=multivariate_plot,
             )[0]
 
-    if multivariate_plot:
-        for i in range(series_width):
-            _plot_series_and_anomalies(
-                series=series[series.components[i]],
-                anomalies=anomalies[anomalies.components[i]]
-                if anomalies is not None
-                else None,
-                pred_series=pred_series[pred_series.components[i]]
+    plots_per_ts = nbr_plots * series_width if multivariate_plot else nbr_plots
+    fig, axs = plt.subplots(
+        plots_per_ts,
+        figsize=(8, 4 + 2 * (plots_per_ts - 1)),
+        sharex=True,
+        gridspec_kw={"height_ratios": [2] + [1] * (plots_per_ts - 1)},
+        squeeze=False,
+    )
+
+    for i in range(series_width if multivariate_plot else 1):
+        if multivariate_plot:
+            series_ = series[series.components[i]]
+            anomalies_ = (
+                anomalies[anomalies.components[i]] if anomalies is not None else None
+            )
+            pred_series_ = (
+                pred_series[pred_series.components[i]]
                 if pred_series is not None
-                else None,
-                pred_scores=pred_scores,
-                window=window,
-                names_of_scorers=names_of_scorers,
-                metric=metric,
-                axs=axs,
-                index_ax=i * nbr_plots,
-                nbr_plots=nbr_plots,
+                else None
+            )
+            pred_scores_ = (
+                [pc[pc.components[i]] for pc in pred_scores]
+                if pred_scores is not None
+                else None
             )
+        else:
+            series_ = series
+            anomalies_ = anomalies
+            pred_series_ = pred_series
+            pred_scores_ = pred_scores
 
-    else:
         _plot_series_and_anomalies(
-            series=series,
-            anomalies=anomalies,
-            pred_series=pred_series,
-            pred_scores=pred_scores,
+            series=series_,
+            anomalies=anomalies_,
+            pred_series=pred_series_,
+            pred_scores=pred_scores_,
             window=window,
             names_of_scorers=names_of_scorers,
             metric=metric,
             axs=axs,
-            index_ax=0,
+            index_ax=i * nbr_plots,
             nbr_plots=nbr_plots,
         )
-
     fig.suptitle(title)
 
 
@@ -838,9 +839,7 @@ def _plot_series_and_anomalies(
                 value = round(
                     eval_metric_from_scores(
                         anomalies=anomalies,
-                        pred_scores=pred_scores[idx][
-                            pred_scores[idx].components[index_ax // nbr_plots]
-                        ],
+                        pred_scores=pred_scores[idx],
                         window=w,
                         metric=metric,
                     ),
@@ -855,9 +854,7 @@ def _plot_series_and_anomalies(
                 label = f"score_{str(idx)}" + [f" ({value})", ""][value is None]
 
             _plot_series(
-                series=elem[1]["series_score"][
-                    elem[1]["series_score"].components[index_ax // nbr_plots]
-                ],
+                series=elem[1]["series_score"],
                 ax_id=axs[index_ax][0],
                 linewidth=0.5,
                 label_name=label,

From 27156f467cdaf04b0428ec850846a0a05fb9c48b Mon Sep 17 00:00:00 2001
From: he weilin <heweilin19971212@163.com>
Date: Mon, 6 Jan 2025 11:02:21 +0800
Subject: [PATCH 09/14] improve the spacing between suptitle and axes

---
 darts/ad/utils.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/darts/ad/utils.py b/darts/ad/utils.py
index 26a2a6f42f..d0e3fb4a3b 100644
--- a/darts/ad/utils.py
+++ b/darts/ad/utils.py
@@ -463,6 +463,7 @@ def show_anomalies_from_scores(
         sharex=True,
         gridspec_kw={"height_ratios": [2] + [1] * (plots_per_ts - 1)},
         squeeze=False,
+        constrained_layout=True,
     )
 
     for i in range(series_width if multivariate_plot else 1):

From 1fa81e2b66b02c4b4d3dee99d75cd04be076f555 Mon Sep 17 00:00:00 2001
From: he weilin <heweilin19971212@163.com>
Date: Mon, 6 Jan 2025 14:55:39 +0800
Subject: [PATCH 10/14] update utils

---
 darts/ad/scorers/scorers.py |  2 +
 darts/ad/utils.py           | 87 ++++++++++++++++++-------------------
 2 files changed, 44 insertions(+), 45 deletions(-)

diff --git a/darts/ad/scorers/scorers.py b/darts/ad/scorers/scorers.py
index 66f01b6692..7e935c56dc 100644
--- a/darts/ad/scorers/scorers.py
+++ b/darts/ad/scorers/scorers.py
@@ -615,6 +615,8 @@ def show_anomalies(
             Optionally, the name of the metric function to use. Must be one of "AUC_ROC" (Area Under the
             Receiver Operating Characteristic Curve) and "AUC_PR" (Average Precision from scores).
             Default: "AUC_ROC".
+        multivariate_plot
+            If True, it will separately plot each component in multivariate series.
         """
         series = _check_input(series, name="series", num_series_expected=1)[0]
         pred_scores = self.score(series)
diff --git a/darts/ad/utils.py b/darts/ad/utils.py
index a0a4dfc40a..26a2a6f42f 100644
--- a/darts/ad/utils.py
+++ b/darts/ad/utils.py
@@ -356,7 +356,6 @@ def show_anomalies_from_scores(
     multivariate_plot
         If True, it will separately plot each component in multivariate series.
     """
-
     series = _check_input(
         series,
         name="series",
@@ -426,78 +425,80 @@ def show_anomalies_from_scores(
             )
 
         nbr_plots += len(set(window))
-        series_width = series.n_components
-        plots_per_ts = nbr_plots * series_width if multivariate_plot else nbr_plots
-        fig, axs = plt.subplots(
-            plots_per_ts,
-            figsize=(8, 4 + 2 * (plots_per_ts - 1)),
-            sharex=True,
-            gridspec_kw={"height_ratios": [2] + [1] * (plots_per_ts - 1)},
-            squeeze=False,
-        )
 
+    series_width = series.n_components
     if pred_series is not None:
         pred_series = _check_input(
             pred_series,
             name="pred_series",
-            width_expected=series.width,
+            width_expected=series_width,
             num_series_expected=1,
             check_multivariate=multivariate_plot,
         )[0]
 
-    if anomalies is not None:
+    if anomalies is not None and multivariate_plot:
         anomalies = _check_input(
             anomalies,
             name="anomalies",
-            width_expected=series.width,
+            width_expected=series_width,
             num_series_expected=1,
             check_binary=True,
             check_multivariate=multivariate_plot,
         )[0]
 
-    if pred_scores is not None:
+    if pred_scores is not None and multivariate_plot:
         for pred_score in pred_scores:
-            pred_score = _check_input(
+            _ = _check_input(
                 pred_score,
                 name="pred_score",
-                width_expected=series.width,
+                width_expected=series_width,
                 num_series_expected=1,
                 check_multivariate=multivariate_plot,
             )[0]
 
-    if multivariate_plot:
-        for i in range(series_width):
-            _plot_series_and_anomalies(
-                series=series[series.components[i]],
-                anomalies=anomalies[anomalies.components[i]]
-                if anomalies is not None
-                else None,
-                pred_series=pred_series[pred_series.components[i]]
+    plots_per_ts = nbr_plots * series_width if multivariate_plot else nbr_plots
+    fig, axs = plt.subplots(
+        plots_per_ts,
+        figsize=(8, 4 + 2 * (plots_per_ts - 1)),
+        sharex=True,
+        gridspec_kw={"height_ratios": [2] + [1] * (plots_per_ts - 1)},
+        squeeze=False,
+    )
+
+    for i in range(series_width if multivariate_plot else 1):
+        if multivariate_plot:
+            series_ = series[series.components[i]]
+            anomalies_ = (
+                anomalies[anomalies.components[i]] if anomalies is not None else None
+            )
+            pred_series_ = (
+                pred_series[pred_series.components[i]]
                 if pred_series is not None
-                else None,
-                pred_scores=pred_scores,
-                window=window,
-                names_of_scorers=names_of_scorers,
-                metric=metric,
-                axs=axs,
-                index_ax=i * nbr_plots,
-                nbr_plots=nbr_plots,
+                else None
+            )
+            pred_scores_ = (
+                [pc[pc.components[i]] for pc in pred_scores]
+                if pred_scores is not None
+                else None
             )
+        else:
+            series_ = series
+            anomalies_ = anomalies
+            pred_series_ = pred_series
+            pred_scores_ = pred_scores
 
-    else:
         _plot_series_and_anomalies(
-            series=series,
-            anomalies=anomalies,
-            pred_series=pred_series,
-            pred_scores=pred_scores,
+            series=series_,
+            anomalies=anomalies_,
+            pred_series=pred_series_,
+            pred_scores=pred_scores_,
             window=window,
             names_of_scorers=names_of_scorers,
             metric=metric,
             axs=axs,
-            index_ax=0,
+            index_ax=i * nbr_plots,
             nbr_plots=nbr_plots,
         )
-
     fig.suptitle(title)
 
 
@@ -838,9 +839,7 @@ def _plot_series_and_anomalies(
                 value = round(
                     eval_metric_from_scores(
                         anomalies=anomalies,
-                        pred_scores=pred_scores[idx][
-                            pred_scores[idx].components[index_ax // nbr_plots]
-                        ],
+                        pred_scores=pred_scores[idx],
                         window=w,
                         metric=metric,
                     ),
@@ -855,9 +854,7 @@ def _plot_series_and_anomalies(
                 label = f"score_{str(idx)}" + [f" ({value})", ""][value is None]
 
             _plot_series(
-                series=elem[1]["series_score"][
-                    elem[1]["series_score"].components[index_ax // nbr_plots]
-                ],
+                series=elem[1]["series_score"],
                 ax_id=axs[index_ax][0],
                 linewidth=0.5,
                 label_name=label,

From 8aea4c07dccc6aed2ac1bb4138b79d1f4d81d930 Mon Sep 17 00:00:00 2001
From: he weilin <heweilin19971212@163.com>
Date: Mon, 6 Jan 2025 15:49:23 +0800
Subject: [PATCH 11/14] improve the spacing between suptitle and axes

---
 darts/ad/utils.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/darts/ad/utils.py b/darts/ad/utils.py
index 26a2a6f42f..d0e3fb4a3b 100644
--- a/darts/ad/utils.py
+++ b/darts/ad/utils.py
@@ -463,6 +463,7 @@ def show_anomalies_from_scores(
         sharex=True,
         gridspec_kw={"height_ratios": [2] + [1] * (plots_per_ts - 1)},
         squeeze=False,
+        constrained_layout=True,
     )
 
     for i in range(series_width if multivariate_plot else 1):

From c6b799e03bfb103f789c833c3baf42af35013a06 Mon Sep 17 00:00:00 2001
From: he weilin <heweilin19971212@163.com>
Date: Mon, 6 Jan 2025 17:10:21 +0800
Subject: [PATCH 12/14] Fix the height_ratios of each subplot

---
 darts/ad/utils.py | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/darts/ad/utils.py b/darts/ad/utils.py
index d0e3fb4a3b..bc3588cb77 100644
--- a/darts/ad/utils.py
+++ b/darts/ad/utils.py
@@ -459,11 +459,13 @@ def show_anomalies_from_scores(
     plots_per_ts = nbr_plots * series_width if multivariate_plot else nbr_plots
     fig, axs = plt.subplots(
         plots_per_ts,
-        figsize=(8, 4 + 2 * (plots_per_ts - 1)),
+        figsize=(8, 4 * (plots_per_ts // nbr_plots) + 2 * (nbr_plots - 1)),
         sharex=True,
-        gridspec_kw={"height_ratios": [2] + [1] * (plots_per_ts - 1)},
+        gridspec_kw={
+            "height_ratios": ([2] + [1] * (nbr_plots - 1)) * (plots_per_ts // nbr_plots)
+        },
         squeeze=False,
-        constrained_layout=True,
+        layout="constrained",
     )
 
     for i in range(series_width if multivariate_plot else 1):

From 78baeec4c4e89eeae88d6541e75934449568dd5a Mon Sep 17 00:00:00 2001
From: he weilin <heweilin19971212@163.com>
Date: Mon, 6 Jan 2025 17:18:38 +0800
Subject: [PATCH 13/14] Change "multivariate_plot" parameter name to
 "component_wise"

---
 darts/ad/anomaly_model/anomaly_model.py  |  6 +++---
 darts/ad/anomaly_model/forecasting_am.py |  6 +++---
 darts/ad/scorers/scorers.py              | 12 ++++++------
 darts/ad/utils.py                        | 22 +++++++++++-----------
 4 files changed, 23 insertions(+), 23 deletions(-)

diff --git a/darts/ad/anomaly_model/anomaly_model.py b/darts/ad/anomaly_model/anomaly_model.py
index d75ad18ab0..1b13d0553a 100644
--- a/darts/ad/anomaly_model/anomaly_model.py
+++ b/darts/ad/anomaly_model/anomaly_model.py
@@ -247,7 +247,7 @@ def show_anomalies(
         names_of_scorers: Union[str, Sequence[str]] = None,
         title: str = None,
         metric: Optional[Literal["AUC_ROC", "AUC_PR"]] = None,
-        multivariate_plot: bool = False,
+        component_wise: bool = False,
         **score_kwargs,
     ):
         """Plot the results of the anomaly model.
@@ -284,7 +284,7 @@ def show_anomalies(
             Default: "AUC_ROC".
         score_kwargs
             parameters for the `score()` method.
-        multivariate_plot
+        component_wise
             If True, it will separately plot each component in multivariate series.
         """
         series = _check_input(series, name="series", num_series_expected=1)[0]
@@ -313,7 +313,7 @@ def show_anomalies(
             names_of_scorers=names_of_scorers,
             title=title,
             metric=metric,
-            multivariate_plot=multivariate_plot,
+            component_wise=component_wise,
         )
 
     @property
diff --git a/darts/ad/anomaly_model/forecasting_am.py b/darts/ad/anomaly_model/forecasting_am.py
index 7fee356cb6..318fe3361a 100644
--- a/darts/ad/anomaly_model/forecasting_am.py
+++ b/darts/ad/anomaly_model/forecasting_am.py
@@ -440,7 +440,7 @@ def show_anomalies(
         names_of_scorers: Union[str, Sequence[str]] = None,
         title: str = None,
         metric: Optional[Literal["AUC_ROC", "AUC_PR"]] = None,
-        multivariate_plot: bool = False,
+        component_wise: bool = False,
         **score_kwargs,
     ):
         """Plot the results of the anomaly model.
@@ -507,7 +507,7 @@ def show_anomalies(
             Optionally, the name of the metric function to use. Must be one of "AUC_ROC" (Area Under the
             Receiver Operating Characteristic Curve) and "AUC_PR" (Average Precision from scores).
             Default: "AUC_ROC".
-        multivariate_plot
+        component_wise
             If True, it will separately plot each component in multivariate series.
         score_kwargs
             parameters for the `score()` method.
@@ -530,7 +530,7 @@ def show_anomalies(
             names_of_scorers=names_of_scorers,
             title=title,
             metric=metric,
-            multivariate_plot=multivariate_plot,
+            component_wise=component_wise,
             **score_kwargs,
         )
 
diff --git a/darts/ad/scorers/scorers.py b/darts/ad/scorers/scorers.py
index 7e935c56dc..f5887c8314 100644
--- a/darts/ad/scorers/scorers.py
+++ b/darts/ad/scorers/scorers.py
@@ -176,7 +176,7 @@ def show_anomalies_from_prediction(
         anomalies: TimeSeries = None,
         title: str = None,
         metric: Optional[Literal["AUC_ROC", "AUC_PR"]] = None,
-        multivariate_plot: bool = False,
+        component_wise: bool = False,
     ):
         """Plot the results of the scorer.
 
@@ -209,7 +209,7 @@ def show_anomalies_from_prediction(
             Optionally, the name of the metric function to use. Must be one of "AUC_ROC" (Area Under the
             Receiver Operating Characteristic Curve) and "AUC_PR" (Average Precision from scores).
             Default: "AUC_ROC".
-        multivariate_plot
+        component_wise
             If True, it will separately plot each component in multivariate series.
         """
         series = _check_input(series, name="series", num_series_expected=1)[0]
@@ -233,7 +233,7 @@ def show_anomalies_from_prediction(
             names_of_scorers=scorer_name,
             title=title,
             metric=metric,
-            multivariate_plot=multivariate_plot,
+            component_wise=component_wise,
         )
 
     @property
@@ -584,7 +584,7 @@ def show_anomalies(
         scorer_name: str = None,
         title: str = None,
         metric: Optional[Literal["AUC_ROC", "AUC_PR"]] = None,
-        multivariate_plot: bool = False,
+        component_wise: bool = False,
     ):
         """Plot the results of the scorer.
 
@@ -615,7 +615,7 @@ def show_anomalies(
             Optionally, the name of the metric function to use. Must be one of "AUC_ROC" (Area Under the
             Receiver Operating Characteristic Curve) and "AUC_PR" (Average Precision from scores).
             Default: "AUC_ROC".
-        multivariate_plot
+        component_wise
             If True, it will separately plot each component in multivariate series.
         """
         series = _check_input(series, name="series", num_series_expected=1)[0]
@@ -640,7 +640,7 @@ def show_anomalies(
             names_of_scorers=scorer_name,
             title=title,
             metric=metric,
-            multivariate_plot=multivariate_plot,
+            component_wise=component_wise,
         )
 
     @property
diff --git a/darts/ad/utils.py b/darts/ad/utils.py
index bc3588cb77..f338d54b79 100644
--- a/darts/ad/utils.py
+++ b/darts/ad/utils.py
@@ -311,7 +311,7 @@ def show_anomalies_from_scores(
     names_of_scorers: Union[str, Sequence[str]] = None,
     title: str = None,
     metric: Optional[Literal["AUC_ROC", "AUC_PR"]] = None,
-    multivariate_plot: bool = False,
+    component_wise: bool = False,
 ):
     """Plot the results generated by an anomaly model.
 
@@ -353,14 +353,14 @@ def show_anomalies_from_scores(
         Receiver Operating Characteristic Curve) and "AUC_PR" (Average Precision from scores).
         Only effective when `pred_scores` is not `None`.
         Default: "AUC_ROC".
-    multivariate_plot
+    component_wise
         If True, it will separately plot each component in multivariate series.
     """
     series = _check_input(
         series,
         name="series",
         num_series_expected=1,
-        check_multivariate=multivariate_plot,
+        check_multivariate=component_wise,
     )[0]
 
     if title is None and pred_scores is not None:
@@ -433,30 +433,30 @@ def show_anomalies_from_scores(
             name="pred_series",
             width_expected=series_width,
             num_series_expected=1,
-            check_multivariate=multivariate_plot,
+            check_multivariate=component_wise,
         )[0]
 
-    if anomalies is not None and multivariate_plot:
+    if anomalies is not None and component_wise:
         anomalies = _check_input(
             anomalies,
             name="anomalies",
             width_expected=series_width,
             num_series_expected=1,
             check_binary=True,
-            check_multivariate=multivariate_plot,
+            check_multivariate=component_wise,
         )[0]
 
-    if pred_scores is not None and multivariate_plot:
+    if pred_scores is not None and component_wise:
         for pred_score in pred_scores:
             _ = _check_input(
                 pred_score,
                 name="pred_score",
                 width_expected=series_width,
                 num_series_expected=1,
-                check_multivariate=multivariate_plot,
+                check_multivariate=component_wise,
             )[0]
 
-    plots_per_ts = nbr_plots * series_width if multivariate_plot else nbr_plots
+    plots_per_ts = nbr_plots * series_width if component_wise else nbr_plots
     fig, axs = plt.subplots(
         plots_per_ts,
         figsize=(8, 4 * (plots_per_ts // nbr_plots) + 2 * (nbr_plots - 1)),
@@ -468,8 +468,8 @@ def show_anomalies_from_scores(
         layout="constrained",
     )
 
-    for i in range(series_width if multivariate_plot else 1):
-        if multivariate_plot:
+    for i in range(series_width if component_wise else 1):
+        if component_wise:
             series_ = series[series.components[i]]
             anomalies_ = (
                 anomalies[anomalies.components[i]] if anomalies is not None else None

From 95dd225faa91bef62595ec0066ec65e21ded9d68 Mon Sep 17 00:00:00 2001
From: dennisbader <dennis.bader@gmx.ch>
Date: Wed, 8 Jan 2025 14:40:38 +0100
Subject: [PATCH 14/14] make title fit better

---
 CHANGELOG.md                             |  2 +-
 darts/ad/anomaly_model/anomaly_model.py  |  2 +-
 darts/ad/anomaly_model/forecasting_am.py |  2 +-
 darts/ad/scorers/scorers.py              |  4 +-
 darts/ad/utils.py                        | 63 +++++++++++-------------
 5 files changed, 34 insertions(+), 39 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 500c10a09f..1e2ac21663 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,7 +12,7 @@ but cannot always guarantee backwards compatibility. Changes that may **break co
 **Improved**
 
 - New model: `StatsForecastAutoTBATS`. This model offers the [AutoTBATS](https://nixtlaverse.nixtla.io/statsforecast/src/core/models.html#autotbats) model from Nixtla's `statsforecasts` library. [#2611](https://github.com/unit8co/darts/pull/2611) by [He Weilin](https://github.com/cnhwl).
-- Added `multivariate_plot` parameter in `show_anomalies()` to separately plot each component in multivariate series. [#2544](https://github.com/unit8co/darts/pull/2544) by [He Weilin](https://github.com/cnhwl).
+- Added parameter `component_wise` to `show_anomalies()` to separately plot each component in multivariate series. [#2544](https://github.com/unit8co/darts/pull/2544) by [He Weilin](https://github.com/cnhwl).
 
 **Fixed**
 - Fixed a bug when performing optimized historical forecasts with `stride=1` using a `RegressionModel` with `output_chunk_shift>=1` and `output_chunk_length=1`, where the forecast time index was not properly shifted. [#2634](https://github.com/unit8co/darts/pull/2634) by [Mattias De Charleroy](https://github.com/MattiasDC).
diff --git a/darts/ad/anomaly_model/anomaly_model.py b/darts/ad/anomaly_model/anomaly_model.py
index 1b13d0553a..63655db40c 100644
--- a/darts/ad/anomaly_model/anomaly_model.py
+++ b/darts/ad/anomaly_model/anomaly_model.py
@@ -285,7 +285,7 @@ def show_anomalies(
         score_kwargs
             parameters for the `score()` method.
         component_wise
-            If True, it will separately plot each component in multivariate series.
+            If True, will separately plot each component in case of multivariate anomaly detection.
         """
         series = _check_input(series, name="series", num_series_expected=1)[0]
         predict_kwargs = predict_kwargs if predict_kwargs is not None else {}
diff --git a/darts/ad/anomaly_model/forecasting_am.py b/darts/ad/anomaly_model/forecasting_am.py
index 318fe3361a..8b4339cd9c 100644
--- a/darts/ad/anomaly_model/forecasting_am.py
+++ b/darts/ad/anomaly_model/forecasting_am.py
@@ -508,7 +508,7 @@ def show_anomalies(
             Receiver Operating Characteristic Curve) and "AUC_PR" (Average Precision from scores).
             Default: "AUC_ROC".
         component_wise
-            If True, it will separately plot each component in multivariate series.
+            If True, will separately plot each component in case of multivariate anomaly detection.
         score_kwargs
             parameters for the `score()` method.
         """
diff --git a/darts/ad/scorers/scorers.py b/darts/ad/scorers/scorers.py
index f5887c8314..3fadee463a 100644
--- a/darts/ad/scorers/scorers.py
+++ b/darts/ad/scorers/scorers.py
@@ -210,7 +210,7 @@ def show_anomalies_from_prediction(
             Receiver Operating Characteristic Curve) and "AUC_PR" (Average Precision from scores).
             Default: "AUC_ROC".
         component_wise
-            If True, it will separately plot each component in multivariate series.
+            If True, will separately plot each component in case of multivariate anomaly detection.
         """
         series = _check_input(series, name="series", num_series_expected=1)[0]
         pred_series = _check_input(
@@ -616,7 +616,7 @@ def show_anomalies(
             Receiver Operating Characteristic Curve) and "AUC_PR" (Average Precision from scores).
             Default: "AUC_ROC".
         component_wise
-            If True, it will separately plot each component in multivariate series.
+            If True, will separately plot each component in case of multivariate anomaly detection.
         """
         series = _check_input(series, name="series", num_series_expected=1)[0]
         pred_scores = self.score(series)
diff --git a/darts/ad/utils.py b/darts/ad/utils.py
index f338d54b79..4395afdfeb 100644
--- a/darts/ad/utils.py
+++ b/darts/ad/utils.py
@@ -354,7 +354,7 @@ def show_anomalies_from_scores(
         Only effective when `pred_scores` is not `None`.
         Default: "AUC_ROC".
     component_wise
-        If True, it will separately plot each component in multivariate series.
+        If True, will separately plot each component in case of multivariate anomaly detection.
     """
     series = _check_input(
         series,
@@ -457,15 +457,13 @@ def show_anomalies_from_scores(
             )[0]
 
     plots_per_ts = nbr_plots * series_width if component_wise else nbr_plots
+    height_ratios = ([2] + [1] * (nbr_plots - 1)) * (plots_per_ts // nbr_plots)
+    height_total = 2 * sum(height_ratios)
     fig, axs = plt.subplots(
-        plots_per_ts,
-        figsize=(8, 4 * (plots_per_ts // nbr_plots) + 2 * (nbr_plots - 1)),
+        nrows=plots_per_ts,
+        figsize=(8, height_total),
         sharex=True,
-        gridspec_kw={
-            "height_ratios": ([2] + [1] * (nbr_plots - 1)) * (plots_per_ts // nbr_plots)
-        },
-        squeeze=False,
-        layout="constrained",
+        gridspec_kw={"height_ratios": height_ratios},
     )
 
     for i in range(series_width if component_wise else 1):
@@ -500,9 +498,13 @@ def show_anomalies_from_scores(
             metric=metric,
             axs=axs,
             index_ax=i * nbr_plots,
-            nbr_plots=nbr_plots,
         )
-    fig.suptitle(title)
+    # make title fit nicely on plot
+    title_height = 0.1
+    title_y = 1 - title_height / height_total
+
+    fig.suptitle(title, y=title_y)
+    fig.tight_layout()
 
 
 def _assert_binary(series: TimeSeries, name: str):
@@ -774,7 +776,6 @@ def _plot_series_and_anomalies(
     metric: str,
     axs: plt.Axes,
     index_ax: int,
-    nbr_plots: int,
 ):
     """Helper function to plot series and anomalies.
 
@@ -798,25 +799,23 @@ def _plot_series_and_anomalies(
         The axes to plot on.
     index_ax
         The index of the current axis.
-    nbr_plots
-        The number of plots.
     """
-    _plot_series(series=series, ax_id=axs[index_ax][0], linewidth=0.5, label_name="")
+    _plot_series(series=series, ax_id=axs[index_ax], linewidth=0.5, label_name="")
 
     if pred_series is not None:
         _plot_series(
             series=pred_series,
-            ax_id=axs[index_ax][0],
+            ax_id=axs[index_ax],
             linewidth=0.5,
             label_name="model output",
         )
 
-    axs[index_ax][0].set_title("")
+    axs[index_ax].set_title("")
 
     if anomalies is not None or pred_scores is not None:
-        axs[index_ax][0].set_xlabel("")
+        axs[index_ax].set_xlabel("")
 
-    axs[index_ax][0].legend(loc="upper center", bbox_to_anchor=(0.5, 1.1), ncol=2)
+    axs[index_ax].legend(loc="upper center", bbox_to_anchor=(0.5, 1.1), ncol=2)
 
     if pred_scores is not None:
         dict_input = {}
@@ -858,33 +857,29 @@ def _plot_series_and_anomalies(
 
             _plot_series(
                 series=elem[1]["series_score"],
-                ax_id=axs[index_ax][0],
+                ax_id=axs[index_ax],
                 linewidth=0.5,
                 label_name=label,
             )
 
-            axs[index_ax][0].legend(
-                loc="upper center", bbox_to_anchor=(0.5, 1.19), ncol=2
-            )
-            axs[index_ax][0].set_title(f"Window: {str(w)}", loc="left")
-            axs[index_ax][0].set_title("")
-            axs[index_ax][0].set_xlabel("")
+            axs[index_ax].legend(loc="upper center", bbox_to_anchor=(0.5, 1.19), ncol=2)
+            axs[index_ax].set_title(f"Window: {str(w)}", loc="left")
+            axs[index_ax].set_title("")
+            axs[index_ax].set_xlabel("")
 
     if anomalies is not None:
         _plot_series(
             series=anomalies,
-            ax_id=axs[index_ax + 1][0],
+            ax_id=axs[index_ax + 1],
             linewidth=1,
             label_name="anomalies",
             color="red",
         )
 
-        axs[index_ax + 1][0].set_title("")
-        axs[index_ax + 1][0].set_ylim([-0.1, 1.1])
-        axs[index_ax + 1][0].set_yticks([0, 1])
-        axs[index_ax + 1][0].set_yticklabels(["no", "yes"])
-        axs[index_ax + 1][0].legend(
-            loc="upper center", bbox_to_anchor=(0.5, 1.2), ncol=2
-        )
+        axs[index_ax + 1].set_title("")
+        axs[index_ax + 1].set_ylim([-0.1, 1.1])
+        axs[index_ax + 1].set_yticks([0, 1])
+        axs[index_ax + 1].set_yticklabels(["no", "yes"])
+        axs[index_ax + 1].legend(loc="upper center", bbox_to_anchor=(0.5, 1.2), ncol=2)
     else:
-        axs[index_ax][0].set_xlabel("timestamp")
+        axs[index_ax].set_xlabel("timestamp")