code: def plot_evoked_joint(
      evoked,
      times="peaks",
      title="",
      picks=None,
      exclude=None,
      show=True,
      ts_args=None,
      topomap_args=None,
  ):
      """Plot evoked data as butterfly plot and add topomaps for time points.
  
      .. note:: Axes to plot in can be passed by the user through ``ts_args`` or
                ``topomap_args``. In that case both ``ts_args`` and
                ``topomap_args`` axes have to be used. Be aware that when the
                axes are provided, their position may be slightly modified.
  
      Parameters
      ----------
      evoked : instance of Evoked
          The evoked instance.
      times : float | array of float | "auto" | "peaks"
          The time point(s) to plot. If ``"auto"``, 5 evenly spaced topographies
          between the first and last time instant will be shown. If ``"peaks"``,
          finds time points automatically by checking for 3 local maxima in
          Global Field Power. Defaults to ``"peaks"``.
      title : str | None
          The title. If ``None``, suppress printing channel type title. If an
          empty string, a default title is created. Defaults to ''. If custom
          axes are passed make sure to set ``title=None``, otherwise some of your
          axes may be removed during placement of the title axis.
      %(picks_all)s
      exclude : None | list of str | 'bads'
          Channels names to exclude from being shown. If ``'bads'``, the
          bad channels are excluded. Defaults to ``None``.
      show : bool
          Show figure if ``True``. Defaults to ``True``.
      ts_args : None | dict
          A dict of ``kwargs`` that are forwarded to :meth:`mne.Evoked.plot` to
          style the butterfly plot. If they are not in this dict, the following
          defaults are passed: ``spatial_colors=True``, ``zorder='std'``.
          ``show`` and ``exclude`` are illegal.
          If ``None``, no customizable arguments will be passed.
          Defaults to ``None``.
      topomap_args : None | dict
          A dict of ``kwargs`` that are forwarded to
          :meth:`mne.Evoked.plot_topomap` to style the topomaps.
          If it is not in this dict, ``outlines='head'`` will be passed.
          ``show``, ``times``, ``colorbar`` are illegal.
          If ``None``, no customizable arguments will be passed.
          Defaults to ``None``.
  
      Returns
      -------
      fig : instance of matplotlib.figure.Figure | list
          The figure object containing the plot. If ``evoked`` has multiple
          channel types, a list of figures, one for each channel type, is
          returned.
  
      Notes
      -----
      .. versionadded:: 0.12.0
      """
      from matplotlib.patches import ConnectionPatch
  
      if ts_args is not None and not isinstance(ts_args, dict):
          raise TypeError(
              "ts_args must be dict or None, got type %s"
              % (type(ts_args),)
          )
      ts_args = dict() if ts_args is None else ts_args.copy()
      ts_args["time_unit"], _ = _check_time_unit(
          ts_args.get("time_unit", "s"), evoked.times
      )
      topomap_args = (
          dict() if topomap_args is None else topomap_args.copy()
      )
  
      got_axes = False
      illegal_args = {"show", "times", "exclude"}
      for args in (ts_args, topomap_args):
          if any(x in args for x in illegal_args):
              raise ValueError(
                  "Don't pass any of {} as *_args.".format(
                      ", ".join(list(illegal_args))
                  )
              )
      if ("axes" in ts_args) or ("axes" in topomap_args):
          if not (("axes" in ts_args) and ("axes" in topomap_args)):
              raise ValueError(
                  "If one of `ts_args` and `topomap_args` contains "
                  "'axes', the other must, too."
              )
          _validate_if_list_of_axes([ts_args["axes"]], 1)
  
          if times in (None, "peaks"):
              n_topomaps = 3 + 1
          else:
              assert not isinstance(times, str)
              n_topomaps = len(times) + 1
  
          _validate_if_list_of_axes(
              list(topomap_args["axes"]), n_topomaps
          )
          got_axes = True
  
      # channel selection
      # simply create a new evoked object with the desired channel selection
      # Need to deal with proj before picking to avoid bad projections
      proj = topomap_args.get("proj", True)
      proj_ts = ts_args.get("proj", True)
      if proj_ts != proj:
          raise ValueError(
              f'topomap_args["proj"] (default True, got {proj}) must match '
              f'ts_args["proj"] (default True, got {proj_ts})'
          )
      _check_option(
          'topomap_args["proj"]', proj, (True, False, "reconstruct")
      )
      evoked = evoked.copy()
      if proj:
          evoked.apply_proj()
          if proj == "reconstruct":
              evoked._reconstruct_proj()
      topomap_args["proj"] = ts_args["proj"] = False  # don't reapply
      evoked.pick(picks, exclude=exclude)
      info = evoked.info
      ch_types = info.get_channel_types(unique=True, only_data_chs=True)
  
      # if multiple sensor types: one plot per channel type, recursive call
      if len(ch_types) > 1:
          if got_axes:
              raise NotImplementedError(
                  "Currently, passing axes manually (via `ts_args` or "
                  "`topomap_args`) is not supported for multiple channel types."
              )
          figs = list()
          for (
              this_type
          ) in ch_types:  # pick only the corresponding channel type
              ev_ = evoked.copy().pick(
                  [
                      info["ch_names"][idx]
                      for idx in range(info["nchan"])
                      if channel_type(info, idx) == this_type
                  ]
              )
              if len(ev_.info.get_channel_types(unique=True)) > 1:
                  raise RuntimeError(
                      "Possibly infinite loop due to channel "
                      "selection problem. This should never "
                      "happen! Please check your channel types."
                  )
              figs.append(
                  plot_evoked_joint(
                      ev_,
                      times=times,
                      title=title,
                      show=show,
                      ts_args=ts_args,
                      exclude=list(),
                      topomap_args=topomap_args,
                  )
              )
          return figs
  
      # set up time points to show topomaps for
      times_sec = _process_times(evoked, times, few=True)
      del times
      _, times_ts = _check_time_unit(ts_args["time_unit"], times_sec)
  
      # prepare axes for topomap
      if not got_axes:
          fig, ts_ax, map_ax = _prepare_joint_axes(
              len(times_sec), figsize=(8.0, 4.2)
          )
          cbar_ax = None
      else:
          ts_ax = ts_args["axes"]
          del ts_args["axes"]
          map_ax = topomap_args["axes"][:-1]
          cbar_ax = topomap_args["axes"][-1]
          del topomap_args["axes"]
          fig = cbar_ax.figure
  
      # butterfly/time series plot
      # most of this code is about passing defaults on demand
      ts_args_def = dict(
          picks=None,
          unit=True,
          ylim=None,
          xlim="tight",
          proj=False,
          hline=None,
          units=None,
          scalings=None,
          titles=None,
          gfp=False,
          window_title=None,
          spatial_colors=True,
          zorder="std",
          sphere=None,
          draw=False,
      )
      ts_args_def.update(ts_args)
      _plot_evoked(
          evoked,
          axes=ts_ax,
          show=False,
          plot_type="butterfly",
          exclude=[],
          **ts_args_def,
      )
  
      # handle title
      # we use a new axis for the title to handle scaling of plots
      old_title = ts_ax.get_title()
      ts_ax.set_title("")
  
      if title is not None:
          if title == "":
              title = old_title
          fig.suptitle(title)
  
      # topomap
      contours = topomap_args.get("contours", 6)
      ch_type = ch_types.pop()  # set should only contain one element
      # Since the data has all the ch_types, we get the limits from the plot.
      vmin, vmax = ts_ax.get_ylim()
      norm = ch_type == "grad"
      vmin = 0 if norm else vmin
      vmin, vmax = _setup_vmin_vmax(evoked.data, vmin, vmax, norm)
      if not isinstance(contours, (list, np.ndarray)):
          locator, contours = _set_contour_locator(vmin, vmax, contours)
      else:
          locator = None
  
      topomap_args_pass = (
          dict(extrapolate="local") if ch_type == "seeg" else dict()
      )
      topomap_args_pass.update(topomap_args)
      topomap_args_pass["outlines"] = topomap_args.get(
          "outlines", "head"
      )
      topomap_args_pass["contours"] = contours
      evoked.plot_topomap(
          times=times_sec,
          axes=map_ax,
          show=False,
          colorbar=False,
          **topomap_args_pass,
      )
  
      if topomap_args.get("colorbar", True):
          from matplotlib import ticker
  
          cbar = fig.colorbar(
              map_ax[0].images[0], ax=map_ax, cax=cbar_ax, shrink=0.8
          )
          cbar.ax.grid(
              False
          )  # auto-removal deprecated as of 2021/10/05
          if isinstance(contours, (list, np.ndarray)):
              cbar.set_ticks(contours)
          else:
              if locator is None:
                  locator = ticker.MaxNLocator(nbins=5)
              cbar.locator = locator
          cbar.update_ticks()
  
      # connection lines
      # draw the connection lines between time series and topoplots
      for timepoint, map_ax_ in zip(times_ts, map_ax):
          con = ConnectionPatch(
              xyA=[timepoint, ts_ax.get_ylim()[1]],
              xyB=[0.5, 0],
              coordsA="data",
              coordsB="axes fraction",
              axesA=ts_ax,
              axesB=map_ax_,
              color="grey",
              linestyle="-",
              linewidth=1.5,
              alpha=0.66,
              zorder=1,
              clip_on=False,
          )
          fig.add_artist(con)
  
      # mark times in time series plot
      for timepoint in times_ts:
          ts_ax.axvline(
              timepoint,
              color="grey",
              linestyle="-",
              linewidth=1.5,
              alpha=0.66,
              zorder=0,
          )
  
      # show and return it
      plt_show(show)
      return fig
  
imports: from copy import deepcopy
  from functools import partial
  from itertools import cycle
  from numbers import Integral
  import numpy
  from .._fiff.pick import _DATA_CH_TYPES_SPLIT, _PICK_TYPES_DATA_DICT, _VALID_CHANNEL_TYPES, _picks_to_idx, channel_indices_by_type, channel_type, pick_info
  from ..defaults import _handle_default
  from ..fixes import _is_last_row
  from ..utils import _check_ch_locs, _check_if_nan, _clean_names, _is_numeric, _pl, _to_rgb, _validate_type, fill_doc, logger, verbose, warn
  from .topo import _plot_evoked_topo
  from .topomap import _check_sphere, _draw_outlines, _get_pos_outlines, _make_head_outlines, _prepare_topomap, _prepare_topomap_plot, _set_contour_locator, plot_topomap
  from .utils import DraggableColorbar, _check_cov, _check_delayed_ssp, _check_option, _check_time_unit, _draw_proj_checkbox, _get_cmap, _get_color_list, _make_combine_callable, _plot_masked_image, _prepare_joint_axes, _process_times, _prop_kw, _set_title_multiple_electrodes, _set_window_title, _setup_ax_spines, _setup_cmap, _setup_plot_projector, _setup_vmin_vmax, _triage_rank_sss, _trim_ticks, _validate_if_list_of_axes, plt_show
label_desc: Use image node_id 19 for calling a global function with example usage: plot_evoked_joint(evoked, times, title, picks, exclude, show, ts_args, topomap_args) and returns: fig, figs