code: def plot_lines_markers(
      df,
      x_col,
      line_cols=None,
      marker_cols=None,
      band_cols=None,
      band_cols_dict=None,
      line_colors=None,
      marker_colors=None,
      band_colors=None,
      title=None,
  ):
      """A lightweight, easy-to-use function to create a plotly figure of given
  
      - lines (curves)
      - markers (points)
      - filled bands (e.g. error bands)
  
      from the columns of a dataframe with a legend which matches the column names.
  
      This can be used for example to annotate multiple curves,  markers and bands
      with an easy function call.
  
      Parameters
      ----------
      df : `pandas.DataFrame`
          Data frame with ``x_col`` and value columns specified in ``line_cols`` and ``marker_cols``.
      x_col : `str`
          The column used for the x-axis.
      line_cols : `list` [`str`] or None, default None
          The list of y-axis variables to be plotted as lines / curves.
      marker_cols : `list` [`str`] or None, default None
          The list of y-axis variables to be plotted as markers / points.
      band_cols : `list` [`str`] or None, default None
          The list of y-axis variables to be plotted as bands.
          Each column is expected to have tuples, each of which denote the upper
          and lower bounds.
      band_cols_dict : `dict` [`str`: [`str`]] or None, default None
          This is another way to specify bands.
          In this case:
  
              - each key will be the name for the band
              - the value contains the two bound colums of `df` for the band.
  
          For example `{
              "forecast": ["forecast_upper", "forecast_lower"],
              "w": ["w1", "w2"]}`
          Specifies two bands, one is based on the forecast prediction intervals
          and one is based on a variables denoted by "w" which has two corresponding
          columns in df: `"w1"` and `"w2"`.
  
      line_colors : `list` [`str`] or None, default None
          The list of colors to be used for each corresponding line column given in ``line_cols``.
      marker_colors : `list` [`str`] or None, default None
          The list of colors to be used for each corresponding marker column given in ``line_cols``.
      band_colors : `list` [`str`] or None, default None
          The list of colors to be used for each corresponding band column given in ``band_cols``.
          Each of these colors are used as filler for each band.
      title : `str` or None, default None
          Plot title. If None, no title will appear.
  
      Returns
      -------
      fig : `plotly.graph_objects.Figure`
          Interactive plotly graph of one or more columns in ``df`` against ``x_col``.
      """
  
      if line_colors is not None and line_cols is not None:
          if len(line_colors) < len(line_cols):
              raise ValueError(
                  "If `line_colors` is passed, its length must be at least `len(line_cols)`"
              )
  
      if marker_colors is not None and marker_cols is not None:
          if len(marker_colors) < len(marker_cols):
              raise ValueError(
                  "If `marker_colors` is passed, its length must be at least `len(marker_cols)`"
              )
  
      if band_colors is not None and band_cols is not None:
          if len(band_colors) < len(band_cols):
              raise ValueError(
                  "If `band_colors` is passed, its length must be at least `len(band_cols)`"
              )
  
      if band_colors is not None and band_cols_dict is not None:
          if len(band_colors) < len(band_cols_dict):
              raise ValueError(
                  "If `band_colors` is passed, its length must be at least `len(band_cols_dict)`"
              )
  
      if (
          line_cols is None
          and marker_cols is None
          and band_cols is None
          and band_cols_dict is None
      ):
          raise ValueError(
              "At least one of `line_cols` or `marker_cols` or `band_cols`"
              " or `band_cols_dict` must be passed as a list (not None)."
          )
  
      fig = go.Figure()
      # Below we count the number of figure components to assign proper labels to legends.
      count_fig_data = -1
      if line_cols is not None:
          for i, col in enumerate(line_cols):
              if line_colors is not None:
                  line = go.scatter.Line(color=line_colors[i])
              else:
                  line = go.scatter.Line()
  
              fig.add_trace(
                  go.Scatter(
                      x=df[x_col],
                      y=df[col],
                      mode="lines",
                      line=line,
                      showlegend=True,
                  )
              )
              count_fig_data += 1
              fig["data"][count_fig_data]["name"] = col
  
      if marker_cols is not None:
          for i, col in enumerate(marker_cols):
              if marker_colors is not None:
                  marker = go.scatter.Marker(color=marker_colors[i])
              else:
                  marker = go.scatter.Marker()
              fig.add_trace(
                  go.Scatter(
                      x=df[x_col],
                      y=df[col],
                      mode="markers",
                      marker=marker,
                      showlegend=True,
                  )
              )
              count_fig_data += 1
              fig["data"][count_fig_data]["name"] = col
  
      if band_cols is not None:
          if band_colors is None:
              band_colors = get_distinct_colors(
                  num_colors=len(band_cols), opacity=0.2
              )
  
          for i, col in enumerate(band_cols):
              fig.add_traces(
                  [
                      go.Scatter(
                          x=df[x_col],
                          y=df[col].map(lambda b: b[1]),
                          mode="lines",
                          line=line,
                          line_color="rgba(0, 0, 0, 0)",
                          showlegend=True,
                      ),
                      go.Scatter(
                          x=df[x_col],
                          y=df[col].map(lambda b: b[0]),
                          mode="lines",
                          line_color="rgba(0, 0, 0, 0)",
                          line=line,
                          fill="tonexty",
                          fillcolor=band_colors[i],
                          showlegend=True,
                      ),
                  ]
              )
  
              # The code below adds legend for each band.
              # We increment the count by two this time because each band comes with the
              # inner filling and lines around it.
              # In this case, we have made the lines around each band to be invisible.
              # However, they do appear in the figure data and we want to only include
              # one legend for each band.
              count_fig_data += 2
              # This adds the legend corresponding to the band filler color.
              fig["data"][len(fig["data"]) - 1]["name"] = col
              # The name for this added data is the empty string,
              # because we do not want to add a legend for the empty lines
              # around the bands.
              fig["data"][len(fig["data"]) - 2]["name"] = ""
  
      if band_cols_dict is not None:
          if band_colors is None:
              band_colors = get_distinct_colors(
                  num_colors=len(band_cols_dict), opacity=0.2
              )
  
          for i, name in enumerate(band_cols_dict):
              col1 = band_cols_dict[name][0]
              col2 = band_cols_dict[name][1]
              fig.add_traces(
                  [
                      go.Scatter(
                          x=df[x_col],
                          y=df[col2],
                          mode="lines",
                          line=line,
                          line_color="rgba(0, 0, 0, 0)",
                          showlegend=True,
                      ),
                      go.Scatter(
                          x=df[x_col],
                          y=df[col1],
                          mode="lines",
                          line_color="rgba(0, 0, 0, 0)",
                          line=line,
                          fill="tonexty",
                          fillcolor=band_colors[i],
                          showlegend=True,
                      ),
                  ]
              )
  
              count_fig_data += 2
              fig["data"][len(fig["data"]) - 1]["name"] = name
              fig["data"][len(fig["data"]) - 2]["name"] = ""
  
      fig.update_layout(title=title)
      return fig
  
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 3 for calling a global function with example usage: plot_lines_markers(df, x_col, line_cols, marker_cols, band_cols, band_cols_dict, line_colors, marker_colors, band_colors, title) and returns: fig