Plot the evaluation results from `eval_results`. `eval_results` must be a sequence containing `(param, values)`
    tuples, where `param` is the parameter value to appear on the x axis and `values` can be a dict structure
    containing the metric values. `eval_results` can be created using the `results_by_parameter` function from the
    `topicmod.common` module.
    Set `metric` to plot only a specific metric.
    Set `xaxislabel` for a label on the x-axis.
    Set `yaxislabel` for a label on the y-axis.
    Set `title` for a plot title.
    Options in a dict `subplots_opts` will be passed to `plt.subplots(...)`.
    Options in a dict `subplots_adjust_opts` will be passed to `fig.subplots_adjust(...)`.
    `figsize` can be set to a tuple `(width, height)` or to `"auto"` (default) which will set the size to
    `(8, 2 * <num. of metrics>)`.

def plot_eval_results(eval_results, metric=None, xaxislabel=None, yaxislabel=None,
                      title=None, title_fontsize='x-large', axes_title_fontsize='large',
                      show_metric_direction=True, metric_direction_font_size='large',
                      subplots_opts=None, subplots_adjust_opts=None, figsize='auto',
                      **fig_kwargs):
    """
    Plot the evaluation results from `eval_results`. `eval_results` must be a sequence containing `(param, values)`
    tuples, where `param` is the parameter value to appear on the x axis and `values` can be a dict structure
    containing the metric values. `eval_results` can be created using the `results_by_parameter` function from the
    `topicmod.common` module.
    Set `metric` to plot only a specific metric.
    Set `xaxislabel` for a label on the x-axis.
    Set `yaxislabel` for a label on the y-axis.
    Set `title` for a plot title.
    Options in a dict `subplots_opts` will be passed to `plt.subplots(...)`.
    Options in a dict `subplots_adjust_opts` will be passed to `fig.subplots_adjust(...)`.
    `figsize` can be set to a tuple `(width, height)` or to `"auto"` (default) which will set the size to
    `(8, 2 * <num. of metrics>)`.
    """
    if type(eval_results) not in (list, tuple) or not eval_results:
        raise ValueError('`eval_results` must be a list or tuple with at least one element')

    if type(eval_results[0]) not in (list, tuple) or len(eval_results[0]) != 2:
        raise ValueError('`eval_results` must be a list or tuple containing a (param, values) tuple. '
                         'Maybe `eval_results` must be converted with `results_by_parameter`.')

    if metric is not None and type(metric) not in (list, tuple):
        metric = [metric]
    elif metric is None:
        # remove special evaluation result 'model': the calculated model itself
        metric = list(set(next(iter(eval_results))[1].keys()) - {'model'})

    metric = sorted(metric)

    metric_direction = []
    for m in metric:
        if m == 'perplexity':
            metric_direction.append('minimize')
        else:
            m_fn_name = 'metric_%s' % (m[:16] if m.startswith('coherence_gensim') else m)
            m_fn = getattr(evaluate, m_fn_name, None)
            if m_fn:
                metric_direction.append(getattr(m_fn, 'direction', 'unknown'))
            else:
                metric_direction.append('unknown')

    n_metrics = len(metric)

    assert n_metrics == len(metric_direction)

    metrics_ordered = []
    for m_dir in sorted(set(metric_direction), reverse=True):
        metrics_ordered.extend([(m, d) for m, d in zip(metric, metric_direction) if d == m_dir])

    assert n_metrics == len(metrics_ordered)

    # get figure and subplots (axes)
    if figsize == 'auto':
        figsize = (8, 2*n_metrics)

    subplots_kwargs = dict(nrows=n_metrics, ncols=1, sharex=True, constrained_layout=True, figsize=figsize)
    subplots_kwargs.update(subplots_opts or {})
    subplots_kwargs.update(fig_kwargs)

    fig, axes = plt.subplots(**subplots_kwargs)

    # set title
    if title:
        fig.suptitle(title, fontsize=title_fontsize)

    x = list(zip(*eval_results))[0]

    # set adjustments
    if title:
        subplots_adjust_kwargs = dict(top=0.9, hspace=0.3)
    else:
        subplots_adjust_kwargs = {}

    subplots_adjust_kwargs.update(subplots_adjust_opts or {})

    if subplots_adjust_kwargs:
        fig.subplots_adjust(**subplots_adjust_kwargs)

    # draw subplot for each metric
    axes_pos_per_dir = defaultdict(list)
    for i, (ax, (m, m_dir)) in enumerate(zip(axes.flatten(), metrics_ordered)):
        if show_metric_direction:
            axes_pos_per_dir[m_dir].append(ax.get_position())

        y = [metric_res[m] for _, metric_res in eval_results]
        ax.plot(x, y, label=m)

        ax.set_title(m, fontsize=axes_title_fontsize)

        # set axis labels
        if xaxislabel and i == len(metric)-1:
            ax.set_xlabel(xaxislabel)
        if yaxislabel:
            ax.set_ylabel(yaxislabel)

    # show grouped metric direction on the left
    if axes_pos_per_dir:   # = if show_metric_direction
        left_xs = []
        ys = []
        for m_dir, bboxes in axes_pos_per_dir.items():
            left_xs.append(min(bb.x0 for bb in bboxes))
            min_y = min(bb.y0 for bb in bboxes)
            max_y = max(bb.y1 for bb in bboxes)
            ys.append((min_y, max_y))

        left_x = min(left_xs) / 2.5

        fig.lines = []
        for (min_y, max_y), m_dir in zip(ys, axes_pos_per_dir.keys()):
            center_y = min_y + (max_y - min_y) / 2

            fig.lines.append(Line2D((left_x, left_x), (min_y, max_y), transform=fig.transFigure, linewidth=5,
                                    color='lightgray'))

            fig.text(left_x / 1.5, center_y, m_dir, fontsize=metric_direction_font_size, rotation='vertical',
                     horizontalalignment='right', verticalalignment='center')

    return fig, axes