code: def plt_annotate_series(
      df,
      x_col,
      value_col,
      label_col,
      annotate_labels=None,
      keep_cols=None,
      fill_cols=None,
      title=None,
  ):
      """A function which plots a values given in ``value_col`` of a dataframe
      ``df`` against x-axis values given in ``x_col`` and adds annotations based
      on labels in ``label_col``.
  
      Parameters
      ----------
      df : `pandas.DataFrame`
          Data frame with ``x_col``, ``value_col`` and ``label_col``.
          If ``keep_cols`` is not None ``df`` is supposed to have the columns
          in ``keep_cols`` as well.
      x_col: `str`
          Which column to plot on the x-axis.
      value_col : `str`
          The column with the values for the series.
      label_col : `str`
          The column which includes the labels for which we want to annotate the
          series given in ``value_col``.
      annotate_labels : `List` [`str`] or None, default None
          If not None, the annotations will only be added for the labels given in
          this list.
      keep_cols: `List` [`str`] or None, default None
          Extra columns to be plotted if not None.
      fill_cols : `List` [ `List` [`str`] ]
          Fill between each list of columns in ``fill_cols``.
      title : `str` or None, default None
          Plot title. If None, default is based on axis labels.
  
      Returns
      -------
      result : `dict` with following items:
  
          - "fig" : `plotly.graph_objects.Figure`
              Interactive plotly graph of ``value_col`` and potentially columns
              in specified in ``keep_cols`` (if not None)
              in ``df`` against ``x_col`` with added annotations.
  
          - "df": `pandas.DataFrame`
              A dataframe which is an expansion of ``df`` and includes extra columns
              with new value columns for each label in ``annotate_labels`` or all labels
              if ``annotate_labels`` is None. The new column for each label is NA if
              that label is not activated and equals ``value_col`` otherwise.
      """
      df = df.copy()
      if keep_cols is None:
          keep_cols = []
  
      y_col_style_dict = {}
      # The columns to be filled. They will be handled separately from ``keep_cols``.
      all_fill_cols = []
      if fill_cols is not None and len(fill_cols) > 0:
          if not isinstance(fill_cols[0], list):
              raise ValueError(
                  f"fill_cols must be a list of lists of strings."
              )
          all_fill_cols = [c for c_list in fill_cols for c in c_list]
          # Removes a col in keep_cols if it is already in fill_cols.
          keep_cols = [
              col for col in keep_cols if col not in all_fill_cols
          ]
  
      df = df[[x_col, value_col, label_col] + keep_cols + all_fill_cols]
  
      if annotate_labels is None:
          annotate_labels = df[label_col].unique()
  
      for col in [value_col] + keep_cols:
          y_col_style_dict[col] = {"mode": "lines"}
  
      for label in annotate_labels:
          new_col = f"{value_col}_label_{label}"
          df[new_col] = df[value_col]
          df.loc[~(df[label_col] == label), new_col] = None
          y_col_style_dict[new_col] = {"mode": "markers"}
  
      y_col_style_dict[value_col] = {
          "mode": "lines",
          "line": {"color": "rgba(0, 0, 255, 0.3)", "dash": "solid"},
      }
  
      del df[label_col]
  
      # Instead of calling plot_multivariate, we make the figure manually to avoid the column order to be
      # flipped due to the fact that dictionary is unordered. This may affect the fill between certain columns.
      data = []
      # Plots fill_cols first to make them in the bottom layer.
      if fill_cols is not None:
          first_fill_color = "rgba(142, 215, 246, 0.3)"  # blue
  
          for g, col_list in enumerate(fill_cols):
              for i, col in enumerate(col_list):
                  line = go.Scatter(
                      x=df[x_col],
                      y=df[col],
                      name=col,
                      legendgroup=f"{g}",
                      mode="lines",
                      line=dict(
                          color=(first_fill_color if g == 0 else None)
                      ),
                      fill=("tonexty" if i != 0 else None),
                      fillcolor=(first_fill_color if g == 0 else None),
                  )
                  data.append(line)
  
      for column, style_dict in y_col_style_dict.items():
          style_dict = dict(name=column, **style_dict)
          line = go.Scatter(x=df[x_col], y=df[column], **style_dict)
          data.append(line)
  
      layout = go.Layout(
          xaxis=dict(title=x_col),
          yaxis=dict(title=value_col),
          title=title,
          title_x=0.5,
          showlegend=True,
      )
      fig = go.Figure(data=data, layout=layout)
  
      return {"fig": fig, "annotated_df": df}
  
file_path: greykite/greykite/common/viz/timeseries_annotate.py
imports: import pandas
  import plotly.graph_objects
  from greykite.common.constants import ACTUAL_COL
  from greykite.common.constants import ANOMALY_COL
  from greykite.common.constants import END_TIME_COL
  from greykite.common.constants import PREDICTED_ANOMALY_COL
  from greykite.common.constants import PREDICTED_COL
  from greykite.common.constants import START_TIME_COL
  from greykite.common.constants import TIME_COL
  from greykite.common.features.adjust_anomalous_data import label_anomalies_multi_metric
  from greykite.common.viz.colors_utils import get_distinct_colors
  from greykite.common.viz.timeseries_plotting import plot_forecast_vs_actual
label_desc: Use image node_id 1 for calling a global function with example usage: plt_annotate_series(df, x_col, value_col, label_col, annotate_labels, keep_cols, fill_cols, title) and returns: dict