def _init_backend():
    """
    Runs when hypertools is initially imported and sets the matplotlib
    backend used for animated/interactive plots.
    """
    global BACKEND_MAPPING, \
        BACKEND_WARNING, \
        HYPERTOOLS_BACKEND, \
        IPYTHON_INSTANCE, \
        IS_NOTEBOOK, \
        reset_backend, \
        switch_backend

    curr_backend = mpl.get_backend()

    try:
        # `get_ipython()` function exists in the namespace if
        # `hypertools` was imported from an IPython shell or Jupyter
        # notebook
        IPYTHON_INSTANCE = get_ipython()
        assert 'IPKernelApp' in IPYTHON_INSTANCE.config

    # NameError: raised if imported from a script
    # AssertionError: raised if imported from an IPython shell
    except (NameError, AssertionError):
        # see `_block_greedy_completer_execution()` docstring
        _block_greedy_completer_execution()

        IS_NOTEBOOK = False
        # (excluding WebAgg - no way to test in advance if it will work)
        backends = (
            'TkAgg',
            'QtAgg',
            'Qt5Agg',
            'Qt4Agg',
            'GTK4Agg',
            'GTK3Agg',
            'TkAgg',
            'WXAgg'
        )
        if sys.platform == 'darwin':
            # prefer cocoa backend on Mac - pretty much guaranteed to
            # work, appears to be faster, and Mac does NOT like Tkinter
            backends = ('MacOSX', *backends)

        # TODO: document setting environment variable
        # check for configurable environment variable
        env_backend = os.getenv("HYPERTOOLS_BACKEND")
        if env_backend is not None:
            # prefer user-specified backend, if set
            if env_backend.lower() in tuple(map(str.lower, backends)):
                backends = (backends[:backends.index(HYPERTOOLS_BACKEND)],
                            *backends[backends.index(HYPERTOOLS_BACKEND) + 1:])

            backends = (env_backend, *backends)

        for b in backends:
            try:
                mpl.use(b)
                working_backend = b
                break

            except (ImportError, NameError, ValueError):
                # ImportError/NameError:
                #     raised if backend's dependencies aren't installed
                # ValueError:
                #     raised if named backed isn't supported by
                #     installed matplotlib version
                continue

        else:
            BACKEND_WARNING = ("Failed to switch to any interactive backend "
                               f"({', '.join(backends)}). Falling back to 'Agg'.")
            working_backend = 'Agg'

        if env_backend is not None and working_backend.lower() != env_backend.lower():
            # The only time a plotting-related warning should be issued
            # on import rather than on call to hypertools.plot is if
            # $HYPERTOOLS_BACKEND specifies an incompatible backend,
            # since that will have been set manually.
            warnings.warn("failed to set matplotlib backend to backend "
                          f"specified in environment ('{env_backend}'). "
                          f"Falling back to '{working_backend}'")

        switch_backend = reset_backend = _switch_backend_regular

    else:
        IS_NOTEBOOK = True
        # if running in a notebook, the backend to use depends on the
        # user's Jupyter version and frontend. In "classic" Jupyter
        # notebooks (i.e., notebook < 7.0), use `nbAgg` since it's the
        # most stable, best supported, available out-of-the-box, etc.
        # However, that backend no longer works with the new JupyterLab
        # JS frontend (which is also used by notebook >= 7.0), so in
        # those cases, use the "ipympl"/"widget" plotting backend
        # instead.
        #
        # The user can technically override this by setting the
        # HYPERTOOLS_BACKEND environment variable, but for now this is
        # undocumented and the two officially supported methods of
        # changing the backend hypertools uses for interactive plots are
        # to (1) manually set the desired backend after importing
        # hypertools with `hypertools.set_interactive_backend`, or (2)
        # pass the desired backend identifier to the `mpl_backend` kwarg
        # of `hypertools.plot`
        #
        # Note: the "ipympl"/"widget" backend requires the `ipympl`
        # library be installed in both the notebook kernel environment
        # AND the notebook server environment, if the two aren't the
        # same. The former is guaranteed by hypertools's dependencies,
        # but the latter is not, so we must check for it manually.
        notebook_frontend = _get_jupyter_frontend()
        if notebook_frontend == 'lab':
            notebook_backend = 'module://ipympl.backend_nbagg'
        else:
            notebook_backend = 'nbAgg'

        try:
            mpl.use(notebook_backend)
        except (ImportError, ModuleNotFoundError):
            BACKEND_WARNING = (
                "Failed to switch to interactive notebook backend "
                f"('{notebook_backend}'). Falling back to inline static plots."
            )
            working_backend = 'inline'
        else:
            working_backend = notebook_backend
            if notebook_frontend == 'lab':
                # if the notebook uses the JupyterLab JS frontend and
                # the ipympl plotting backend was successfully found in
                # the notebook kernel environment (`try` block above),
                # determine whether the notebook server environment is
                # the same or different
                kernel_python = sys.executable
                server_python = shutil.which('python')
                if server_python != kernel_python:
                    # if they're different, check whether `ipympl` is
                    # installed in the server environment
                    import IPython    # guaranteed to be installed at this point

                    with open(os.devnull, 'w') as devnull, redirect_stdout(devnull):
                        retcode = IPython.utils.process.system('pip show ipympl')
                    if retcode != 0:
                        # NOTE: this is not currently checked, but the
                        #  `ipympl` versions installed in the server and
                        #  kernel environments must also be compatible
                        #  with each other. See
                        #  https://matplotlib.org/ipympl/installing.html#compatibility-table
                        # NOTE: this check is imperfect and will result
                        #  in a false positive if the user has installed
                        #  just the carved-off JS components from
                        #  `ipympl` via the `jupyter-matplotlib`
                        #  extension instead of installing the full
                        #  package. We *could* account for this by
                        #  additionally checking the output of
                        #  `jupyter labextension check jupyter-matplotlib`,
                        #  but then we'd get into differentiating
                        #  the extension not being installed at all vs.
                        #  being installed but not enabled, etc.
                        #  Ultimately this is a pretty minor edge case
                        #  and the cost of a false positive is pretty
                        #  low (a harmless warning message IF the user
                        #  creates an interactive plot), so IMO it's
                        #  not worth the extra overhead of running more
                        #  shell commands every time hypertools is
                        #  imported.
                        BACKEND_WARNING = (
                            "The `ipympl` package is not installed in the "
                            "Jupyter server's environment. Interactive and "
                            "animated plots may not appear. To fix this, "
                            "pip-install `ipympl` and restart the Jupyer "
                            "server."
                        )

        switch_backend = _switch_backend_notebook
        reset_backend = _reset_backend_notebook

    finally:
        # restore backend
        mpl.use(curr_backend)
        BACKEND_MAPPING = BackendMapping(BACKEND_KEYS)
        HYPERTOOLS_BACKEND = HypertoolsBackend(working_backend).normalize()

    Runs when hypertools is initially imported and sets the matplotlib
    backend used for animated/interactive plots.
    
hypertools/plot/backend.py