Python 3.x matplotlib对figure.canvas.draw()和figure.savefig()抛出错误:“No No No modHFGen.dll;ValueError:应为二维数组,得到1“;

Python 3.x matplotlib对figure.canvas.draw()和figure.savefig()抛出错误:“No No No modHFGen.dll;ValueError:应为二维数组,得到1“;,python-3.x,matplotlib,canvas,draw,figure,Python 3.x,Matplotlib,Canvas,Draw,Figure,我想画一个数字,我需要比较图例和数字高度。 所需的输出如下所示: 以前它像一个符咒一样工作,现在我无法绘制存储在变量fig中的图形 为此,有必要在真实绘图之前绘制图形画布,因为这样可以获得真实的最终图形扩展 fig.canvas.draw() # Get the extensions/dimensions of the current axis and legend ax_height = ax.get_window_extent().height ax_width = ax.get_win

我想画一个数字,我需要比较图例和数字高度。 所需的输出如下所示:

以前它像一个符咒一样工作,现在我无法绘制存储在变量
fig
中的图形

为此,有必要在真实绘图之前绘制图形画布,因为这样可以获得真实的最终图形扩展

fig.canvas.draw()

# Get the extensions/dimensions of the current axis and legend
ax_height = ax.get_window_extent().height
ax_width = ax.get_window_extent().width
leg_height = legend.get_window_extent().height
leg_width = legend.get_window_extent().width
至少在以前,
fig.canvas.draw()
这一行工作得很完美,但这次抛出了以下错误(包括整个回溯):

但是,通过
figure.savefig()
保存图形时,会出现类似错误,因此我无法避免解决此问题:

fig.savefig(filename,
            dpi=dpi,
            bbox_inches=bbox_inches,
            transparent=transparent)
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/figure.py", line 2311, in savefig
    self.canvas.print_figure(fname, **kwargs)
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/backend_bases.py", line 2210, in print_figure
    result = print_method(
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/backend_bases.py", line 1639, in wrapper
    return func(*args, **kwargs)
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/backends/backend_agg.py", line 509, in print_png
    FigureCanvasAgg.draw(self)
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/backends/backend_agg.py", line 407, in draw
    self.figure.draw(self.renderer)
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/artist.py", line 41, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/figure.py", line 1863, in draw
    mimage._draw_list_compositing_images(
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/image.py", line 132, in _draw_list_compositing_images
    a.draw(renderer)
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/artist.py", line 41, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/cbook/deprecation.py", line 411, in wrapper
    return func(*inner_args, **inner_kwargs)
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/axes/_base.py", line 2748, in draw
    mimage._draw_list_compositing_images(renderer, self, artists)
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/image.py", line 132, in _draw_list_compositing_images
    a.draw(renderer)
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/artist.py", line 41, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/collections.py", line 931, in draw
    Collection.draw(self, renderer)
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/artist.py", line 41, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/collections.py", line 406, in draw
    renderer.draw_path_collection(
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/backends/backend_agg.py", line 172, in draw_path_collection
    return self._renderer.draw_path_collection(
ValueError: Expected 2-dimensional array, got 1
fig.savefig(文件名,
dpi=dpi,
bbox_英寸=bbox_英寸,
透明=透明)
回溯(最近一次呼叫最后一次):
文件“”,第1行,在
savefig中的文件“/home/linuxbrew/.linuxbrew/lib/python3.8/site packages/matplotlib/figure.py”,第2311行
self.canvas.print_图(fname,**kwargs)
文件“/home/linuxbrew/.linuxbrew/lib/python3.8/site packages/matplotlib/backend_base.py”,第2210行,如图所示
结果=打印方法(
包装器中的文件“/home/linuxbrew/.linuxbrew/lib/python3.8/site packages/matplotlib/backend_base.py”,第1639行
返回函数(*args,**kwargs)
文件“/home/linuxbrew/.linuxbrew/lib/python3.8/site packages/matplotlib/backends/backend_agg.py”,第509行,打印格式为png
图CAVASAGG.draw(自绘制)
文件“/home/linuxbrew/.linuxbrew/lib/python3.8/site packages/matplotlib/backends/backend_agg.py”,绘图中第407行
self.figure.draw(self.renderer)
文件“/home/linuxbrew/.linuxbrew/lib/python3.8/site packages/matplotlib/artist.py”,第41行,在draw_包装中
返回绘制(艺术家、渲染器、*args、**kwargs)
文件“/home/linuxbrew/.linuxbrew/lib/python3.8/site packages/matplotlib/figure.py”,绘图中第1863行
mimage.\u绘制\u列表\u合成\u图像(
文件“/home/linuxbrew/.linuxbrew/lib/python3.8/site packages/matplotlib/image.py”,第132行,在“绘制”列表“合成”图像中
a、 绘制(渲染器)
文件“/home/linuxbrew/.linuxbrew/lib/python3.8/site packages/matplotlib/artist.py”,第41行,在draw_包装中
返回绘制(艺术家、渲染器、*args、**kwargs)
包装器中的文件“/home/linuxbrew/.linuxbrew/lib/python3.8/site packages/matplotlib/cbook/deprecation.py”,第411行
返回函数(*内部参数,**内部参数)
文件“/home/linuxbrew/.linuxbrew/lib/python3.8/site packages/matplotlib/axes/_base.py”,绘图中第2748行
mimage.\u绘制\u列表\u合成\u图像(渲染器、自身、艺术家)
文件“/home/linuxbrew/.linuxbrew/lib/python3.8/site packages/matplotlib/image.py”,第132行,在“绘制”列表“合成”图像中
a、 绘制(渲染器)
文件“/home/linuxbrew/.linuxbrew/lib/python3.8/site packages/matplotlib/artist.py”,第41行,在draw_包装中
返回绘制(艺术家、渲染器、*args、**kwargs)
文件“/home/linuxbrew/.linuxbrew/lib/python3.8/site packages/matplotlib/collections.py”,绘图中第931行
Collection.draw(自绘制、渲染器)
文件“/home/linuxbrew/.linuxbrew/lib/python3.8/site packages/matplotlib/artist.py”,第41行,在draw_包装中
返回绘制(艺术家、渲染器、*args、**kwargs)
文件“/home/linuxbrew/.linuxbrew/lib/python3.8/site packages/matplotlib/collections.py”,第406行,在绘图中
renderer.draw_路径_集合(
文件“/home/linuxbrew/.linuxbrew/lib/python3.8/site packages/matplotlib/backends/backend\u agg.py”,第172行,在draw\u path\u集合中
返回self.\u renderer.draw\u path\u集合(
ValueError:应为二维数组,得到1
我在本文中使用的最内层打印功能是:

def plot_point_estimator_with_CI(
    df_plot_list=None,
    colname=None,
    estimator="mean",
    ci_color=[1, 0, 0, 0.15],
    CI=0.95,
    hor_line=None,
    strftime_str=None,
    outer_index=None,
    all_outer_indexes_subplot=False,
    groupby_freq=None,
    ax_title_pos=None,
    savepath=None,
    filtered_gps=None):

# Check for existence of the savepath
if savepath is not None and not os.path.exists(savepath):
    os.makedirs(savepath)

# * Create a dummy date for later combination with pure time-values (i.e. HH:MM:SS)
# NOTE on scope: this is needed for being able to be plotted on a matplotlib.ax since datetime.time()-arrays will throw errors
# NOTE on implementation: use an extra-weird date in order to make clear that this is certainly not a real date
dummy_date = datetime.date(1000, 10, 10)

if not all_outer_indexes_subplot:
    loop2_list = filtered_gps
else:
    loop2_list = outer_index

## ** SUBPLOTS
"""Prepare the figure dimensions:
# NOTE: the figure size should be decided according to the dimensions of the grid
# Syntax from the documentation under: https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.figure.html
# (float, float), optional, default: None
# width, height in inches. If not provided, defaults to rcParams["figure.figsize"] = [6.4, 4.8]
# -> the relation is 4:3, which will be maintained throughout this
# CAUTION: even when cols=rows, the figure needs to be wider than high."""
if cols >= rows:
    width = 10
    height = width * (3 / 4)
else:  # cols < rows:
    height = 10
    width = height * (3 / 4)

# * Instantiate figure
fig, axs = plt.subplots(rows, cols, figsize=(width, height))

# * Turn off possible extant subplot axes
dim_subplot = rows * cols
if len(loop2_list) < dim_subplot and len(loop2_list) > 1:
    subplot_ax_overshoot = True
    # Could happen when uneven number of subplots, such as "5", is passed, and the rectangular grid
    # has extant subplot axis, e.g. in the case of a 2*3 = 6 grid
    for i in range(len(loop2_list), dim_subplot):
        axs.ravel()[i].axis('off')
else:
    subplot_ax_overshoot = False

# Adapt the fontsize of the axis title strings according to the column number of the subplot
if cols < 3:  # should be the "maximum max_cols_per_row"
    ax_title_fontsize = 9
    if not particular_ax_label_fontsize:
        particular_ax_label_fontsize = 9
    legend_font_size = 8.25
    # NOTE on linewidths: the more columns/subplot graphic number, the finer the lines should be
    if cols < 2:
        linewidth = None  # standard seems ok
    else:
        linewidth = 1.25
else:
    ax_title_fontsize = 7
    if not particular_ax_label_fontsize:
        particular_ax_label_fontsize = 8
    linewidth = 1
    legend_font_size = 7

# NOTE: this is paramount in order to display the CI-band in the end as a part of the legend, even though the last subplot didn't contain it
add_handles_labels = None

# * Loop 2 - Create the output graphics
for i, elem in enumerate(loop2_list):
    if groupby_freq is not None:
        if groupby_freq.lower() == "y":
            # Extract the grouped-by unit of the current pd.Timestamp
            coord_kw = int(elem[0].strftime(
                strftime_str))  # could be year, month, ...
            # Assign a time unit name to the title string of the current sub-plot axis
            current_ax_title = coord_kw
        elif groupby_freq.lower() == "m":
            # Extract the grouped-by unit of the current pd.Timestamp
            coord_kw = int(elem[0].strftime(
                strftime_str))  # could be year, month, ...
            # Assign a time unit name to the title string of the current sub-plot axis
            current_ax_title = calendar.month_name[coord_kw]
        else:
            string = """\nERROR: if the time-groupingby-frequency is neither "Y" nor "M",
it hasn't been implemented yet. Won't execute the plotting code (for this iteration).\nCurrent groupby_freq: '{}'""".format(
                    groupby_freq)
                tools.except_print(string)
                break
        elif all_outer_indexes_subplot:
            # The current outer index / directory / location name of the given meteo or gas measuring data
            current_ax_title = elem
            coord_kw = elem

    # Define a temporary variable for the current sub-set dataframe
    sub_df = df_plot_list[i]
    if type(sub_df) == pd.DataFrame:
        sub_df.dropna(axis=1, how="all", inplace=True)

    # Assign the row and column to the current month accordingly - from dictionary
    # NOTE: it should be ordered from January upper-left to December bottom-right
    row, col = row_col_coords[coord_kw]
    # Pass the info to the axis-dummy variable for the following plotting commands
    if len(loop2_list) > 1:
        # Discerning is paramount for avoiding "IndexError: too many indices for array"
        if rows > 1 and cols > 1:
            ax = axs[row, col]
        elif rows > 1:  # cols = 1
            ax = axs[row]
        else:  # rows = 1
            ax = axs[col]
    # Case of single plot: rows = cols = 1
    else:
        # Avoid error: "is not subscriptable" when it's not a real subplot but only 1 plot
        ax = axs

    # * Check for datetime.time - format of index (overlay of days or similar)
    if isinstance(sub_df.index[0], datetime.time):
        # NOTE on implementation:
        # - Replaced df_dummy.index.time with [datetime.datetime.combine(dummy_date, t) for t in df_dummy.index.time] since datetime.time(22, 35)-objects can't be processed well with ax.plot()
        # Docs: https://stackoverflow.com/questions/24757178/how-plot-datetime-time-in-matplotlib
        sub_df.index = [
            datetime.datetime.combine(dummy_date, t) for t in sub_df.index
        ]
        update_ticks_to_full_fledged_datetime = True
    else:
        update_ticks_to_full_fledged_datetime = False

    # * Create plot on the designated axis based on the current sub-df
    if type(sub_df) == pd.DataFrame:  # contains CI-bands
        # Caution: Colnames are uppercase conventionally
        # NOTE: optionally another kwarg could be added: label=str_man.uppercase(estimator)
        line1, = ax.plot(
            sub_df.loc[:, str_man.uppercase(estimator)],
            label=str_man.uppercase(estimator),
            linewidth=linewidth)  # matplotlib.lines.Line2D object
        # Confidence interval of fit (higher resolution due to generated fit-vals with higher density)
        # NOTE: the comma "," needs to be left out, otherwise: "TypeError: cannot unpack non-iterable PolyCollection object"
        line2_label = "{}% CI".format(str(round(CI * 100)))
        line2 = ax.fill_between(sub_df.index,
                                sub_df.loc[:, "Lower_bound"],
                                sub_df.loc[:, "Upper_bound"],
                                color=ci_color,
                                edgecolor="",
                                label=line2_label)
        if not hor_line or i == len(loop2_list) - 1:
            # NOTE on handles: could also be without "handles=.."
            legend = aux_plot.set_legend_with_sorted_labels(
                fig=fig,
                ax=ax,
                handles=[line1, line2],
                return_legend_n_its_position=True)[0]
            if i == len(loop2_list) - 1:
                # Set to None, as the CI-band (line2) has just been plotted in the last subplot axis
                add_handles_labels = None
        else:
            # NOTE: this is paramount in order to display the CI-band in the end as a part of the legend, even though the last subplot didn't contain it
            if not add_handles_labels:
                add_handles_labels = [line2, line2_label]

    # Contains only the aggregated values of the statistical summary estimator, i.e. type(df) == pd.Series
    else:
        # pd.Series doesn't need any other kwargs to be passed
        line1, = ax.plot(sub_df,
                         label=str_man.uppercase(estimator),
                         linewidth=linewidth)
        if not hor_line or i == len(loop2_list) - 1:
            # NOTE on handles: could also be without "handles=.."
            legend = aux_plot.set_legend_with_sorted_labels(
                fig=fig,
                ax=ax,
                handles=[line1],
                return_legend_n_its_position=True)[0]

    # * SET X-TICKS and -LABELS
    # Create a grid for the times on the x-axis
    times_grid, time_ax_vals, ax_tick_labels, sub_ordinated_unit = dt_man.time_range_grid_and_vals(
        step=step, cols=cols)
    # Delete superfluous variables
    del time_ax_vals

    # * Set the ticks and their associated labels
    if update_ticks_to_full_fledged_datetime:
        # NOTE on implementation: need to adapted with the dummy-date to the current axis
        # -> The date doesn't matter since the ticks will be labeled separately with the "HH:MM:SS" - strings (pure times)
        ax.set_xticks(
            [datetime.datetime.combine(dummy_date, t) for t in times_grid])
    else:
        ax.set_xticks(times_grid)
    # Tick labels remain untouched by the ticks-setting above
    ax.set_xticklabels(ax_tick_labels,
                       fontsize=particular_ax_label_fontsize)

    # * SET Y-TICKS and -LABELS
    y_tick_labels = [str(yt) for yt in list(ax.get_yticks())]
    if decimal_formatter:
        y_tick_labels = [
            decimal_formatter % Decimal(float(t)) for t in y_tick_labels
        ]
    y_tick_labels = tools.round_long_floats_with_many_zeros(
        number_list=y_tick_labels,
        decimal_sep=".",
        undesired_char="0",
        limit_consec_undesired_chars=1)
    ax.set_yticklabels(y_tick_labels,
                       fontsize=particular_ax_label_fontsize)
    ax.grid(which='both', alpha=1)

    # Inserts horizontal line into plot adapted by its value in comparison to the data's values
    # NOTE: only the last element, which is true in case of a subplot or a single plot
    if hor_line is not None:
        remove_legend_from_current_axis = i != len(loop2_list) - 1
        # Loop over all horizontal lines provided
        for hor in hor_line:
            if hor[0].lower() in colname.lower(
            ):  # hor[0] contains the variable name (or vice versa)
                # hor[1] contains tuples (triples)
                for info, val, linestyle in hor[1]:
                    leg_label = str_man.uppercase(info)

                    # Caution 1: In order to obtain pd.Datetime-vals from matplotlib's ax.get_xlim() -> conversion necessary
                    # Caution 2: Set vertical=False since horizontal lines are desired
                    # NOTE on previous implementation of other_ax_vals:
                    # i) time_ax_vals
                    # ii) [datetime.datetime.combine(dummy_date, t) for t in time_ax_vals]
                    ax, legend = aux_plot.add_hor_vert_line_n_legend(
                        ax=ax,
                        compare_ax_vals=ax.get_ylim(),
                        add_handles_labels=add_handles_labels,
                        other_ax_vals=ax.get_xlim(),
                        val=val,
                        alpha=1,
                        linewidth=linewidth,
                        leg_label=leg_label,
                        vertical=False,
                        linestyle=linestyle,
                        remove_legend_from_current_axis=
                        remove_legend_from_current_axis)[:2]

    # * X-axis label
    # NOTE: otherwise, it appears always the string "time"
    ax.set_xlabel("")

    # * Title of current axis
    # Set title with the calendar month above every subplot
    # [x_coord, y_coord] -> slightly above the top and centered
    ax.set_title(current_ax_title,
                 position=ax_title_pos,
                 fontsize=ax_title_fontsize)

# * AFTER 2nd LOOP
# 0.0) Assign global axis label shifts
global_X_ax_label_shift = global_X_ax_label_shift_dict[rows]
global_Y_ax_label_shift = global_Y_ax_label_shift_dict[rows]

# 0.1) Create final and global legend object
if hor_line is not None and dim_subplot > 1:
    # Obtain legend handles and labels from passed axis object
    handles, labels = ax.get_legend_handles_labels()
    # NOTE: sometimes it is necessary to pass former handles and labels from an already plotted axis
    # TIPP: the problem is that when the current axis is accessed for retrieving the handles and labels, these added
    # handles and labels won't be in there since it was nothing plotted in the current axis, but in a former one
    if add_handles_labels:
        # Extract the additional handles and labels from another/former axis
        add_handles, add_labels = add_handles_labels
        # Add these accordingly to the current axis' handles..
        if type(add_handles) != list:
            handles += [add_handles]
        else:
            handles += add_handles
        # .. and labels
        if type(add_labels) != list:
            labels += [add_labels]
        else:
            labels += add_labels

    # CAUTION: Prevent the legend's appearance in the last selected axis of the subplot
    ax.get_legend().remove()

    legend_font_size = 8.25
    # NOTE on the nomenclature of this legend:
    # - bbox_to_anchor = (x, y), alternatively, if a size needs to be determined: (x, y, width, height)
    # - loc == 9 -> upper center, (0, 0) seems to stand for the lower/upper left corner of the legend box
    # - ncol : The number of columns that the legend has. Default is 1.
    # - mode: If mode is set to "expand" the legend will be horizontally expanded to fill the axes area
    # (or bbox_to_anchor if defines the legend's size, which is the case if a 4-tuple was passed to bbox_to_anchor like (x, y, width, height))
    if not subplot_ax_overshoot:
        aux_plot.set_legend_with_sorted_labels(fig=fig,
                                               handles=handles,
                                               labels=labels,
                                               loc="lower center",
                                               ncol=5,
                                               fontsize=legend_font_size)

        global_X_ax_label_shift += 0.01  # shift higher to make room for the footnote-legend
    # * OTHERWISE, use the last free axis to plot the legend
    else:
        legend_ax = axs[rows - 1, cols - 1]
        # Set sorted legend on specific axis
        aux_plot.set_legend_with_sorted_labels(ax=legend_ax,
                                               handles=handles,
                                               labels=labels,
                                               loc="upper left",
                                               ncol=1,
                                               fontsize=legend_font_size)

# 0.2) Set global X- and Y-axis labels
xaxstr = "Time ({})".format(sub_ordinated_unit)
if yaxstr is None:
    yaxstr = str_man.uppercase(estimator)
if len(df_plot_list) == 1:
    ax.set_xlabel(xaxstr, fontsize=ax_label_font_size)
    ax.set_ylabel(yaxstr, fontsize=ax_label_font_size)
else:

    fig.text(0.5,
             global_X_ax_label_shift,
             xaxstr,
             ha='center',
             rotation='horizontal',
             fontsize=ax_label_font_size)  # general x-axis label
    fig.text(global_Y_ax_label_shift,
             0.5,
             yaxstr,
             va='center',
             rotation='vertical',
             fontsize=ax_label_font_size)  # general y-axis label

# 1) Generate a unique title string
# ...

# 2) Set title
if len(df_plot_list) == 1:
    # NOTE: overlapping title strings with the standard .set_title()-function can be fought via the y-kwarg
    ax.set_title(titlestr,
                 y=1.0 + add_to_ycoord / 2,
                 fontsize=title_font_size,
                 weight="bold")
    fig.tight_layout()
else:
    # Now, alter the y-coord of the superior title as a function of the lines the suptitlestring comprises
    y_coord_suptitle = y_coord_suptitle_dict[rows]
    y_coord_suptitle += add_to_ycoord

    # Finally, set the suptitle
    plt.suptitle(titlestr,
                 x=0.5,
                 y=y_coord_suptitle,
                 fontsize=title_font_size,
                 weight="bold")
    # NOTE: Tight layout often produces nice results, but requires the title to be spaced accordingly
    fig.tight_layout()

    if global_Y_ax_label_shift:
        # ...

        fig.subplots_adjust(top=y_coord_suptitle + sub_top_shift -
                            add_to_ycoord,
                            bottom=global_X_ax_label_shift + 0.04,
                            left=global_Y_ax_label_shift + add_left_shift)
    else:
        fig.subplots_adjust(top=y_coord_suptitle + sub_top_shift -
                            add_to_ycoord,
                            bottom=global_X_ax_label_shift + 0.1)

# NOTE: Tight layout often produces nice results but requires the title to be spaced accordingly
# CAUTION: as far as this function (windrose-subplotting) is concerned, it hasn't been necessary (status: 17-08-2019)
if len(df_plot_list) == 1:
    pass
else:  # in case of subplots
    # * FINALLY, set legend to None due to the subplots character
    legend = None

## ** Finally, either show or save the current plot/figure **
aux_plot.show_or_save_plot(fig=fig,
                           path=savepath,
                           basename=titlestr,
                           file_extensions=['.png', '.pdf'],
                           legend=legend)
def plot_point_估计器_与_CI(
df_绘图_列表=无,
colname=None,
估计器=“平均值”,
ci_color=[1,0,0,0.15],
CI=0.95,
水平线=无,
strftime_str=None,
外部索引=无,
所有外部索引子批次=False,
groupby_freq=无,
ax_title_pos=无,
savepath=None,
过滤(gps=无):
#检查保存路径是否存在
如果savepath不是None且不是os.path.exists(savepath):
os.makedirs(保存路径)
#*为以后与纯时间值(即HH:MM:SS)的组合创建虚拟日期
#关于作用域的注意事项:这是自datetime.time()以来能够在matplotlib.ax上打印所必需的-数组将抛出错误
#实施注意事项:使用一个额外的奇怪日期,以明确这肯定不是一个真实的日期
dummy_date=datetime.date(1000,10,10)
如果不是所有外部索引子批次:
loop2\u列表=过滤的\u gps
其他:
loop2\u list=外部索引
##**小地块
“”“准备图形尺寸:
#注:图形尺寸应根据网格尺寸确定
#以下文档中的语法:https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.figure.html
#(浮动,浮动),可选,默认值:无
#宽度、高度(以英寸为单位)。如果未提供,则默认为rcParams[“figure.figsize”]=[6.4,4.8]
#->关系为4:3,将在整个过程中保持
#注意:即使cols=行,图形也需要比high更宽
如果cols>=行:
宽度=10
高度=宽度*(3/4)
其他:#cols<行:
高度=10
宽度=高度*(3/4)
#*实例化图形
图,axs=plt.子批次(行、列、图尺寸=(宽度、高度))
#*关闭可能存在的子地块轴
dim_子批次=行*列
如果len(loop2_列表)1:
子批次最大超调量=真
#当通过不均匀数量的子批次(如“5”)时,可能会发生矩形网格
#具有现有子地块轴,例如在2*3=6网格的情况下
对于范围内的i(len(loop2\u列表)、dim\u子批次):
axs.ravel()[i].axis('off')
其他:
子批次最大超调量=错误
#根据列号调整轴标题字符串的字体大小
# NOTE on scope of drawing the figure canvas:
# Crucial in order to get real legend extent afterwards
try:
    fig.canvas.draw()
except Exception as e:
    tools.except_print(f"The exception thrown opon executing fig.canvas.draw was:\n{e}\nExecute the rest of this function nevertheless.")
fig.savefig(filename,
            dpi=dpi,
            bbox_inches=bbox_inches,
            transparent=transparent)
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/figure.py", line 2311, in savefig
    self.canvas.print_figure(fname, **kwargs)
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/backend_bases.py", line 2210, in print_figure
    result = print_method(
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/backend_bases.py", line 1639, in wrapper
    return func(*args, **kwargs)
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/backends/backend_agg.py", line 509, in print_png
    FigureCanvasAgg.draw(self)
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/backends/backend_agg.py", line 407, in draw
    self.figure.draw(self.renderer)
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/artist.py", line 41, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/figure.py", line 1863, in draw
    mimage._draw_list_compositing_images(
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/image.py", line 132, in _draw_list_compositing_images
    a.draw(renderer)
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/artist.py", line 41, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/cbook/deprecation.py", line 411, in wrapper
    return func(*inner_args, **inner_kwargs)
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/axes/_base.py", line 2748, in draw
    mimage._draw_list_compositing_images(renderer, self, artists)
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/image.py", line 132, in _draw_list_compositing_images
    a.draw(renderer)
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/artist.py", line 41, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/collections.py", line 931, in draw
    Collection.draw(self, renderer)
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/artist.py", line 41, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/collections.py", line 406, in draw
    renderer.draw_path_collection(
  File "/home/linuxbrew/.linuxbrew/lib/python3.8/site-packages/matplotlib/backends/backend_agg.py", line 172, in draw_path_collection
    return self._renderer.draw_path_collection(
ValueError: Expected 2-dimensional array, got 1
def plot_point_estimator_with_CI(
    df_plot_list=None,
    colname=None,
    estimator="mean",
    ci_color=[1, 0, 0, 0.15],
    CI=0.95,
    hor_line=None,
    strftime_str=None,
    outer_index=None,
    all_outer_indexes_subplot=False,
    groupby_freq=None,
    ax_title_pos=None,
    savepath=None,
    filtered_gps=None):

# Check for existence of the savepath
if savepath is not None and not os.path.exists(savepath):
    os.makedirs(savepath)

# * Create a dummy date for later combination with pure time-values (i.e. HH:MM:SS)
# NOTE on scope: this is needed for being able to be plotted on a matplotlib.ax since datetime.time()-arrays will throw errors
# NOTE on implementation: use an extra-weird date in order to make clear that this is certainly not a real date
dummy_date = datetime.date(1000, 10, 10)

if not all_outer_indexes_subplot:
    loop2_list = filtered_gps
else:
    loop2_list = outer_index

## ** SUBPLOTS
"""Prepare the figure dimensions:
# NOTE: the figure size should be decided according to the dimensions of the grid
# Syntax from the documentation under: https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.figure.html
# (float, float), optional, default: None
# width, height in inches. If not provided, defaults to rcParams["figure.figsize"] = [6.4, 4.8]
# -> the relation is 4:3, which will be maintained throughout this
# CAUTION: even when cols=rows, the figure needs to be wider than high."""
if cols >= rows:
    width = 10
    height = width * (3 / 4)
else:  # cols < rows:
    height = 10
    width = height * (3 / 4)

# * Instantiate figure
fig, axs = plt.subplots(rows, cols, figsize=(width, height))

# * Turn off possible extant subplot axes
dim_subplot = rows * cols
if len(loop2_list) < dim_subplot and len(loop2_list) > 1:
    subplot_ax_overshoot = True
    # Could happen when uneven number of subplots, such as "5", is passed, and the rectangular grid
    # has extant subplot axis, e.g. in the case of a 2*3 = 6 grid
    for i in range(len(loop2_list), dim_subplot):
        axs.ravel()[i].axis('off')
else:
    subplot_ax_overshoot = False

# Adapt the fontsize of the axis title strings according to the column number of the subplot
if cols < 3:  # should be the "maximum max_cols_per_row"
    ax_title_fontsize = 9
    if not particular_ax_label_fontsize:
        particular_ax_label_fontsize = 9
    legend_font_size = 8.25
    # NOTE on linewidths: the more columns/subplot graphic number, the finer the lines should be
    if cols < 2:
        linewidth = None  # standard seems ok
    else:
        linewidth = 1.25
else:
    ax_title_fontsize = 7
    if not particular_ax_label_fontsize:
        particular_ax_label_fontsize = 8
    linewidth = 1
    legend_font_size = 7

# NOTE: this is paramount in order to display the CI-band in the end as a part of the legend, even though the last subplot didn't contain it
add_handles_labels = None

# * Loop 2 - Create the output graphics
for i, elem in enumerate(loop2_list):
    if groupby_freq is not None:
        if groupby_freq.lower() == "y":
            # Extract the grouped-by unit of the current pd.Timestamp
            coord_kw = int(elem[0].strftime(
                strftime_str))  # could be year, month, ...
            # Assign a time unit name to the title string of the current sub-plot axis
            current_ax_title = coord_kw
        elif groupby_freq.lower() == "m":
            # Extract the grouped-by unit of the current pd.Timestamp
            coord_kw = int(elem[0].strftime(
                strftime_str))  # could be year, month, ...
            # Assign a time unit name to the title string of the current sub-plot axis
            current_ax_title = calendar.month_name[coord_kw]
        else:
            string = """\nERROR: if the time-groupingby-frequency is neither "Y" nor "M",
it hasn't been implemented yet. Won't execute the plotting code (for this iteration).\nCurrent groupby_freq: '{}'""".format(
                    groupby_freq)
                tools.except_print(string)
                break
        elif all_outer_indexes_subplot:
            # The current outer index / directory / location name of the given meteo or gas measuring data
            current_ax_title = elem
            coord_kw = elem

    # Define a temporary variable for the current sub-set dataframe
    sub_df = df_plot_list[i]
    if type(sub_df) == pd.DataFrame:
        sub_df.dropna(axis=1, how="all", inplace=True)

    # Assign the row and column to the current month accordingly - from dictionary
    # NOTE: it should be ordered from January upper-left to December bottom-right
    row, col = row_col_coords[coord_kw]
    # Pass the info to the axis-dummy variable for the following plotting commands
    if len(loop2_list) > 1:
        # Discerning is paramount for avoiding "IndexError: too many indices for array"
        if rows > 1 and cols > 1:
            ax = axs[row, col]
        elif rows > 1:  # cols = 1
            ax = axs[row]
        else:  # rows = 1
            ax = axs[col]
    # Case of single plot: rows = cols = 1
    else:
        # Avoid error: "is not subscriptable" when it's not a real subplot but only 1 plot
        ax = axs

    # * Check for datetime.time - format of index (overlay of days or similar)
    if isinstance(sub_df.index[0], datetime.time):
        # NOTE on implementation:
        # - Replaced df_dummy.index.time with [datetime.datetime.combine(dummy_date, t) for t in df_dummy.index.time] since datetime.time(22, 35)-objects can't be processed well with ax.plot()
        # Docs: https://stackoverflow.com/questions/24757178/how-plot-datetime-time-in-matplotlib
        sub_df.index = [
            datetime.datetime.combine(dummy_date, t) for t in sub_df.index
        ]
        update_ticks_to_full_fledged_datetime = True
    else:
        update_ticks_to_full_fledged_datetime = False

    # * Create plot on the designated axis based on the current sub-df
    if type(sub_df) == pd.DataFrame:  # contains CI-bands
        # Caution: Colnames are uppercase conventionally
        # NOTE: optionally another kwarg could be added: label=str_man.uppercase(estimator)
        line1, = ax.plot(
            sub_df.loc[:, str_man.uppercase(estimator)],
            label=str_man.uppercase(estimator),
            linewidth=linewidth)  # matplotlib.lines.Line2D object
        # Confidence interval of fit (higher resolution due to generated fit-vals with higher density)
        # NOTE: the comma "," needs to be left out, otherwise: "TypeError: cannot unpack non-iterable PolyCollection object"
        line2_label = "{}% CI".format(str(round(CI * 100)))
        line2 = ax.fill_between(sub_df.index,
                                sub_df.loc[:, "Lower_bound"],
                                sub_df.loc[:, "Upper_bound"],
                                color=ci_color,
                                edgecolor="",
                                label=line2_label)
        if not hor_line or i == len(loop2_list) - 1:
            # NOTE on handles: could also be without "handles=.."
            legend = aux_plot.set_legend_with_sorted_labels(
                fig=fig,
                ax=ax,
                handles=[line1, line2],
                return_legend_n_its_position=True)[0]
            if i == len(loop2_list) - 1:
                # Set to None, as the CI-band (line2) has just been plotted in the last subplot axis
                add_handles_labels = None
        else:
            # NOTE: this is paramount in order to display the CI-band in the end as a part of the legend, even though the last subplot didn't contain it
            if not add_handles_labels:
                add_handles_labels = [line2, line2_label]

    # Contains only the aggregated values of the statistical summary estimator, i.e. type(df) == pd.Series
    else:
        # pd.Series doesn't need any other kwargs to be passed
        line1, = ax.plot(sub_df,
                         label=str_man.uppercase(estimator),
                         linewidth=linewidth)
        if not hor_line or i == len(loop2_list) - 1:
            # NOTE on handles: could also be without "handles=.."
            legend = aux_plot.set_legend_with_sorted_labels(
                fig=fig,
                ax=ax,
                handles=[line1],
                return_legend_n_its_position=True)[0]

    # * SET X-TICKS and -LABELS
    # Create a grid for the times on the x-axis
    times_grid, time_ax_vals, ax_tick_labels, sub_ordinated_unit = dt_man.time_range_grid_and_vals(
        step=step, cols=cols)
    # Delete superfluous variables
    del time_ax_vals

    # * Set the ticks and their associated labels
    if update_ticks_to_full_fledged_datetime:
        # NOTE on implementation: need to adapted with the dummy-date to the current axis
        # -> The date doesn't matter since the ticks will be labeled separately with the "HH:MM:SS" - strings (pure times)
        ax.set_xticks(
            [datetime.datetime.combine(dummy_date, t) for t in times_grid])
    else:
        ax.set_xticks(times_grid)
    # Tick labels remain untouched by the ticks-setting above
    ax.set_xticklabels(ax_tick_labels,
                       fontsize=particular_ax_label_fontsize)

    # * SET Y-TICKS and -LABELS
    y_tick_labels = [str(yt) for yt in list(ax.get_yticks())]
    if decimal_formatter:
        y_tick_labels = [
            decimal_formatter % Decimal(float(t)) for t in y_tick_labels
        ]
    y_tick_labels = tools.round_long_floats_with_many_zeros(
        number_list=y_tick_labels,
        decimal_sep=".",
        undesired_char="0",
        limit_consec_undesired_chars=1)
    ax.set_yticklabels(y_tick_labels,
                       fontsize=particular_ax_label_fontsize)
    ax.grid(which='both', alpha=1)

    # Inserts horizontal line into plot adapted by its value in comparison to the data's values
    # NOTE: only the last element, which is true in case of a subplot or a single plot
    if hor_line is not None:
        remove_legend_from_current_axis = i != len(loop2_list) - 1
        # Loop over all horizontal lines provided
        for hor in hor_line:
            if hor[0].lower() in colname.lower(
            ):  # hor[0] contains the variable name (or vice versa)
                # hor[1] contains tuples (triples)
                for info, val, linestyle in hor[1]:
                    leg_label = str_man.uppercase(info)

                    # Caution 1: In order to obtain pd.Datetime-vals from matplotlib's ax.get_xlim() -> conversion necessary
                    # Caution 2: Set vertical=False since horizontal lines are desired
                    # NOTE on previous implementation of other_ax_vals:
                    # i) time_ax_vals
                    # ii) [datetime.datetime.combine(dummy_date, t) for t in time_ax_vals]
                    ax, legend = aux_plot.add_hor_vert_line_n_legend(
                        ax=ax,
                        compare_ax_vals=ax.get_ylim(),
                        add_handles_labels=add_handles_labels,
                        other_ax_vals=ax.get_xlim(),
                        val=val,
                        alpha=1,
                        linewidth=linewidth,
                        leg_label=leg_label,
                        vertical=False,
                        linestyle=linestyle,
                        remove_legend_from_current_axis=
                        remove_legend_from_current_axis)[:2]

    # * X-axis label
    # NOTE: otherwise, it appears always the string "time"
    ax.set_xlabel("")

    # * Title of current axis
    # Set title with the calendar month above every subplot
    # [x_coord, y_coord] -> slightly above the top and centered
    ax.set_title(current_ax_title,
                 position=ax_title_pos,
                 fontsize=ax_title_fontsize)

# * AFTER 2nd LOOP
# 0.0) Assign global axis label shifts
global_X_ax_label_shift = global_X_ax_label_shift_dict[rows]
global_Y_ax_label_shift = global_Y_ax_label_shift_dict[rows]

# 0.1) Create final and global legend object
if hor_line is not None and dim_subplot > 1:
    # Obtain legend handles and labels from passed axis object
    handles, labels = ax.get_legend_handles_labels()
    # NOTE: sometimes it is necessary to pass former handles and labels from an already plotted axis
    # TIPP: the problem is that when the current axis is accessed for retrieving the handles and labels, these added
    # handles and labels won't be in there since it was nothing plotted in the current axis, but in a former one
    if add_handles_labels:
        # Extract the additional handles and labels from another/former axis
        add_handles, add_labels = add_handles_labels
        # Add these accordingly to the current axis' handles..
        if type(add_handles) != list:
            handles += [add_handles]
        else:
            handles += add_handles
        # .. and labels
        if type(add_labels) != list:
            labels += [add_labels]
        else:
            labels += add_labels

    # CAUTION: Prevent the legend's appearance in the last selected axis of the subplot
    ax.get_legend().remove()

    legend_font_size = 8.25
    # NOTE on the nomenclature of this legend:
    # - bbox_to_anchor = (x, y), alternatively, if a size needs to be determined: (x, y, width, height)
    # - loc == 9 -> upper center, (0, 0) seems to stand for the lower/upper left corner of the legend box
    # - ncol : The number of columns that the legend has. Default is 1.
    # - mode: If mode is set to "expand" the legend will be horizontally expanded to fill the axes area
    # (or bbox_to_anchor if defines the legend's size, which is the case if a 4-tuple was passed to bbox_to_anchor like (x, y, width, height))
    if not subplot_ax_overshoot:
        aux_plot.set_legend_with_sorted_labels(fig=fig,
                                               handles=handles,
                                               labels=labels,
                                               loc="lower center",
                                               ncol=5,
                                               fontsize=legend_font_size)

        global_X_ax_label_shift += 0.01  # shift higher to make room for the footnote-legend
    # * OTHERWISE, use the last free axis to plot the legend
    else:
        legend_ax = axs[rows - 1, cols - 1]
        # Set sorted legend on specific axis
        aux_plot.set_legend_with_sorted_labels(ax=legend_ax,
                                               handles=handles,
                                               labels=labels,
                                               loc="upper left",
                                               ncol=1,
                                               fontsize=legend_font_size)

# 0.2) Set global X- and Y-axis labels
xaxstr = "Time ({})".format(sub_ordinated_unit)
if yaxstr is None:
    yaxstr = str_man.uppercase(estimator)
if len(df_plot_list) == 1:
    ax.set_xlabel(xaxstr, fontsize=ax_label_font_size)
    ax.set_ylabel(yaxstr, fontsize=ax_label_font_size)
else:

    fig.text(0.5,
             global_X_ax_label_shift,
             xaxstr,
             ha='center',
             rotation='horizontal',
             fontsize=ax_label_font_size)  # general x-axis label
    fig.text(global_Y_ax_label_shift,
             0.5,
             yaxstr,
             va='center',
             rotation='vertical',
             fontsize=ax_label_font_size)  # general y-axis label

# 1) Generate a unique title string
# ...

# 2) Set title
if len(df_plot_list) == 1:
    # NOTE: overlapping title strings with the standard .set_title()-function can be fought via the y-kwarg
    ax.set_title(titlestr,
                 y=1.0 + add_to_ycoord / 2,
                 fontsize=title_font_size,
                 weight="bold")
    fig.tight_layout()
else:
    # Now, alter the y-coord of the superior title as a function of the lines the suptitlestring comprises
    y_coord_suptitle = y_coord_suptitle_dict[rows]
    y_coord_suptitle += add_to_ycoord

    # Finally, set the suptitle
    plt.suptitle(titlestr,
                 x=0.5,
                 y=y_coord_suptitle,
                 fontsize=title_font_size,
                 weight="bold")
    # NOTE: Tight layout often produces nice results, but requires the title to be spaced accordingly
    fig.tight_layout()

    if global_Y_ax_label_shift:
        # ...

        fig.subplots_adjust(top=y_coord_suptitle + sub_top_shift -
                            add_to_ycoord,
                            bottom=global_X_ax_label_shift + 0.04,
                            left=global_Y_ax_label_shift + add_left_shift)
    else:
        fig.subplots_adjust(top=y_coord_suptitle + sub_top_shift -
                            add_to_ycoord,
                            bottom=global_X_ax_label_shift + 0.1)

# NOTE: Tight layout often produces nice results but requires the title to be spaced accordingly
# CAUTION: as far as this function (windrose-subplotting) is concerned, it hasn't been necessary (status: 17-08-2019)
if len(df_plot_list) == 1:
    pass
else:  # in case of subplots
    # * FINALLY, set legend to None due to the subplots character
    legend = None

## ** Finally, either show or save the current plot/figure **
aux_plot.show_or_save_plot(fig=fig,
                           path=savepath,
                           basename=titlestr,
                           file_extensions=['.png', '.pdf'],
                           legend=legend)
fig = plt.figure()
a = [1,2,3,4]
plt.scatter(a,a,edgecolors='')
fig.savefig('test.png')