保存来自 Dash/Flask 的 matplotlib 图表
Save matplotlib chart from Dash/Flask
我有一个 Dash 应用程序 运行ning,它调用创建和保存图表的函数。
应用程序 运行 因绘制图表而超时。
Matplotlib 打印警告:
Starting a Matplotlib GUI outside of the main thread will likely fail.
据我所知,Dash 应用程序由 运行 不同线程的 Flask 托管。这似乎是 matploblib 的问题,因为它不是线程保存。我 运行 应用程序带有 threaded=False
参数,但问题仍然存在。调试应用程序时,Flask 似乎仍在 运行 多线程。
matplotlib website 提出的解决方案不是使用 pyplot,而是使用 OOP 方法。
有人知道解决问题的另一种方法吗?
示例代码:
import dash
from dash import html
from dash.dependencies import Output, Input
import matplotlib.pyplot as plt
app = dash.Dash(__name__)
app.layout = html.Div([html.Button("Button", id="btn"), html.Div(id="placeholder")])
def draw_figure():
fig, ax = plt.subplots(1, 1)
@app.callback(
Output("placeholder", "children"),
Input("btn", "n_clicks"),
prevent_initial_call=True,
)
def func(_):
for _ in range(20):
draw_figure()
return ""
if __name__ == "__main__":
app.run_server(debug=True, threaded=False)
从 pyplot
切换并不算太糟糕,幸运的是,在 Dash 的 dcc
库中有一个 非常 有用的新组件(dcc.Download
)! (以前,您必须使用 flask
和 werkzeug
并使用 Url 破折号组件,这可能会非常浪费时间;现在简单多了)
基本示例
import os
from time import time_ns
import dash
import matplotlib
import matplotlib as mpl
matplotlib.use("agg")
import numpy as np
from dash import dcc
from dash import html
from dash.dependencies import Input
from dash.dependencies import Output
from matplotlib.backends.backend_agg import FigureCanvasAgg
from matplotlib.figure import Figure
app = dash.Dash(__name__)
app.layout = html.Div(
[
html.Button(
"Generate plot",
id="generate-plot",
style={
"margin": "10% 40% 10% 40%",
"width": "20%",
"fontSize": "1.1rem",
},
),
dcc.Download(id="download-image"),
]
)
def draw_figure():
fig = Figure()
ax = fig.add_subplot(111)
# Random dist plot
scaled_y = np.random.randint(20, 30)
random_data = np.random.poisson(scaled_y, 100)
ax.hist(random_data, bins=12, fc=(0, 0, 0, 0), lw=0.75, ec="b")
# Axes label properties
ax.set_title("Figure Title", size=26)
ax.set_xlabel("X Label", size=14)
ax.set_ylabel("Y Label", size=14)
# NOTE:
# Save figure ~
# * BUT DO NOT USE PYLAB *
# Write figure to output file (png|pdf).
# Make the PNG
canvas = FigureCanvasAgg(fig)
# The size * the dpi gives the final image size
# a4"x4" image * 80 dpi ==> 320x320 pixel image
fig_path = f"rand-poiss-hist_{time_ns()}.png"
canvas.print_figure(fig_path, dpi=150, bbox_inches="tight")
return fig_path
@app.callback(
Output("download-image", "data"),
Input("generate-plot", "n_clicks"),
prevent_initial_call=True,
)
def generate_downloadable_figure(n_clicks):
if n_clicks > 1:
fig_path = draw_figure()
return dcc.send_file(fig_path)
if __name__ == "__main__":
app.run_server(debug=True, dev_tools_hot_reload=True, host="0.0.0.0")
我生成了一个随机图,仅用于演示目的,但希望这足以让您清楚地了解您需要做什么。诀窍是您不能使用 pyplot,而必须使用“agg”mpl 后端并使用所谓的 FigureCanvasAgg
。该图不会显示(尽管您可以根据需要另外编写代码),它只会在单击按钮时下载。
↓点击按钮,然后..
在 Dash 中生成和下载高吞吐量的子图(不使用 任何 mpl.pyplot
)
在前面代码的这个扩展示例中,我添加了一个数字输入组件(dcc.Input
w/ type='number', max=1200, step=1
;当然你也可以只输入任何数字 1 <= n <= 1200
),因此,当您单击下载按钮时,您获得的文件是一个 pdf 文件,其中可能已生成数百或数千个图。
import os
import random
from time import time_ns
import dash
import matplotlib
import matplotlib as mpl
matplotlib.use("agg")
import numpy as np
from dash import dcc
from dash import html
from dash.dependencies import Input
from dash.dependencies import Output
from dash.dependencies import State
from matplotlib.backends.backend_pdf import PdfPages
from matplotlib.figure import Figure
import seaborn as sns
sns.set(
font_scale=0.2
) # this erases labels for any blank plots on the last page
ctheme = [
"k", "gray", "magenta", "fuchsia", "#be03fd", "#1e488f",
(0.443_137_254_901_960_76, 0.443_137_254_901_960_76,
0.886_274_509_803_921_53, ), "#75bbfd", "teal", "lime", "g",
(0.666_667_4, 0.666_666_3, 0.290_780_141_843_971_38), "y",
"#f1da7a", "tan", "orange", "maroon", "r"
] # colors to blend to any scalar-spread palette form
def new_page(m, n):
fig = Figure()
axarr = fig.subplots(m, n, sharex="all", sharey="all")
arr_ij = [(x, y) for x, y in np.ndindex(m, n)]
subplots = [axarr[index] for index in arr_ij]
return (fig, subplots)
def generate_figures(n_plots, m=6, n=5):
fig_path = f"rand-poiss-hist_N={n_plots}_{time_ns()}.pdf"
colors = sns.blend_palette(ctheme, n_plots)
x = 0
with PdfPages(fig_path) as pdf:
for _ in range((n_plots // (m * n)) + 1):
fig, subplots = new_page(m, n)
fig.subplots_adjust(wspace=0.5, hspace=0.5)
for i in range(m * n): # Random dist plots
ax = subplots[i]
x += 1
if x <= n_plots:
scaled_y = np.random.randint(20, 30)
random_data = np.random.poisson(scaled_y, 100)
ax.hist(
random_data,
bins=12,
fc=(0, 0, 0, 0),
lw=0.75,
ec=colors.pop(),
)
# Axes label properties
ax.set_title(f"fig.{x}", size=6)
if ax.is_last_row() or ((n_plots - x) <= n):
ax.set_xlabel("X Label", size=4)
if ax.is_first_col():
ax.set_ylabel("Y Label", size=4)
# ax.set_xmargin(2)
# ax.set_ymargin(2)
# NOTE:
# Save figure ~
# * BUT DO NOT USE PYLAB *
# Write figure to output file (png|pdf).
pdf.savefig(fig)
return fig_path
app = dash.Dash(__name__)
app.layout = html.Div(
[
html.Button(
"Generate plots",
id="generate-plot",
style={
"width": "30%",
"fontSize": "1.1rem",
},
),
html.Br(),
html.Code("Enter number of plots to generate:"),
html.Br(),
dcc.Input(id="range", type="number", min=1, max=1200, step=1),
dcc.Download(id="download-image"),
],
style={"margin": "10% 40% 10% 40%"}
)
@app.callback(
Output("download-image", "data"),
Input("generate-plot", "n_clicks"),
State("range", "value"),
prevent_initial_call=True,
)
def generate_downloadable_figure(n_clicks, n_plots):
if n_clicks > 0:
fig_path = generate_figures(n_plots)
return dcc.send_file(fig_path)
if __name__ == "__main__":
app.run_server(debug=True, dev_tools_hot_reload=True, host="0.0.0.0")
→ Clicking button downloads multiple (as applicable) page PDF of subplots
N=60 地块
N=231 地块
(Took about ten-twenty seconds..)
我有一个 Dash 应用程序 运行ning,它调用创建和保存图表的函数。
应用程序 运行 因绘制图表而超时。
Matplotlib 打印警告:
Starting a Matplotlib GUI outside of the main thread will likely fail.
据我所知,Dash 应用程序由 运行 不同线程的 Flask 托管。这似乎是 matploblib 的问题,因为它不是线程保存。我 运行 应用程序带有 threaded=False
参数,但问题仍然存在。调试应用程序时,Flask 似乎仍在 运行 多线程。
matplotlib website 提出的解决方案不是使用 pyplot,而是使用 OOP 方法。
有人知道解决问题的另一种方法吗?
示例代码:
import dash
from dash import html
from dash.dependencies import Output, Input
import matplotlib.pyplot as plt
app = dash.Dash(__name__)
app.layout = html.Div([html.Button("Button", id="btn"), html.Div(id="placeholder")])
def draw_figure():
fig, ax = plt.subplots(1, 1)
@app.callback(
Output("placeholder", "children"),
Input("btn", "n_clicks"),
prevent_initial_call=True,
)
def func(_):
for _ in range(20):
draw_figure()
return ""
if __name__ == "__main__":
app.run_server(debug=True, threaded=False)
从 pyplot
切换并不算太糟糕,幸运的是,在 Dash 的 dcc
库中有一个 非常 有用的新组件(dcc.Download
)! (以前,您必须使用 flask
和 werkzeug
并使用 Url 破折号组件,这可能会非常浪费时间;现在简单多了)
基本示例
import os
from time import time_ns
import dash
import matplotlib
import matplotlib as mpl
matplotlib.use("agg")
import numpy as np
from dash import dcc
from dash import html
from dash.dependencies import Input
from dash.dependencies import Output
from matplotlib.backends.backend_agg import FigureCanvasAgg
from matplotlib.figure import Figure
app = dash.Dash(__name__)
app.layout = html.Div(
[
html.Button(
"Generate plot",
id="generate-plot",
style={
"margin": "10% 40% 10% 40%",
"width": "20%",
"fontSize": "1.1rem",
},
),
dcc.Download(id="download-image"),
]
)
def draw_figure():
fig = Figure()
ax = fig.add_subplot(111)
# Random dist plot
scaled_y = np.random.randint(20, 30)
random_data = np.random.poisson(scaled_y, 100)
ax.hist(random_data, bins=12, fc=(0, 0, 0, 0), lw=0.75, ec="b")
# Axes label properties
ax.set_title("Figure Title", size=26)
ax.set_xlabel("X Label", size=14)
ax.set_ylabel("Y Label", size=14)
# NOTE:
# Save figure ~
# * BUT DO NOT USE PYLAB *
# Write figure to output file (png|pdf).
# Make the PNG
canvas = FigureCanvasAgg(fig)
# The size * the dpi gives the final image size
# a4"x4" image * 80 dpi ==> 320x320 pixel image
fig_path = f"rand-poiss-hist_{time_ns()}.png"
canvas.print_figure(fig_path, dpi=150, bbox_inches="tight")
return fig_path
@app.callback(
Output("download-image", "data"),
Input("generate-plot", "n_clicks"),
prevent_initial_call=True,
)
def generate_downloadable_figure(n_clicks):
if n_clicks > 1:
fig_path = draw_figure()
return dcc.send_file(fig_path)
if __name__ == "__main__":
app.run_server(debug=True, dev_tools_hot_reload=True, host="0.0.0.0")
我生成了一个随机图,仅用于演示目的,但希望这足以让您清楚地了解您需要做什么。诀窍是您不能使用 pyplot,而必须使用“agg”mpl 后端并使用所谓的 FigureCanvasAgg
。该图不会显示(尽管您可以根据需要另外编写代码),它只会在单击按钮时下载。
↓点击按钮,然后..
在 Dash 中生成和下载高吞吐量的子图(不使用 任何 mpl.pyplot
)
在前面代码的这个扩展示例中,我添加了一个数字输入组件(dcc.Input
w/ type='number', max=1200, step=1
;当然你也可以只输入任何数字 1 <= n <= 1200
),因此,当您单击下载按钮时,您获得的文件是一个 pdf 文件,其中可能已生成数百或数千个图。
import os
import random
from time import time_ns
import dash
import matplotlib
import matplotlib as mpl
matplotlib.use("agg")
import numpy as np
from dash import dcc
from dash import html
from dash.dependencies import Input
from dash.dependencies import Output
from dash.dependencies import State
from matplotlib.backends.backend_pdf import PdfPages
from matplotlib.figure import Figure
import seaborn as sns
sns.set(
font_scale=0.2
) # this erases labels for any blank plots on the last page
ctheme = [
"k", "gray", "magenta", "fuchsia", "#be03fd", "#1e488f",
(0.443_137_254_901_960_76, 0.443_137_254_901_960_76,
0.886_274_509_803_921_53, ), "#75bbfd", "teal", "lime", "g",
(0.666_667_4, 0.666_666_3, 0.290_780_141_843_971_38), "y",
"#f1da7a", "tan", "orange", "maroon", "r"
] # colors to blend to any scalar-spread palette form
def new_page(m, n):
fig = Figure()
axarr = fig.subplots(m, n, sharex="all", sharey="all")
arr_ij = [(x, y) for x, y in np.ndindex(m, n)]
subplots = [axarr[index] for index in arr_ij]
return (fig, subplots)
def generate_figures(n_plots, m=6, n=5):
fig_path = f"rand-poiss-hist_N={n_plots}_{time_ns()}.pdf"
colors = sns.blend_palette(ctheme, n_plots)
x = 0
with PdfPages(fig_path) as pdf:
for _ in range((n_plots // (m * n)) + 1):
fig, subplots = new_page(m, n)
fig.subplots_adjust(wspace=0.5, hspace=0.5)
for i in range(m * n): # Random dist plots
ax = subplots[i]
x += 1
if x <= n_plots:
scaled_y = np.random.randint(20, 30)
random_data = np.random.poisson(scaled_y, 100)
ax.hist(
random_data,
bins=12,
fc=(0, 0, 0, 0),
lw=0.75,
ec=colors.pop(),
)
# Axes label properties
ax.set_title(f"fig.{x}", size=6)
if ax.is_last_row() or ((n_plots - x) <= n):
ax.set_xlabel("X Label", size=4)
if ax.is_first_col():
ax.set_ylabel("Y Label", size=4)
# ax.set_xmargin(2)
# ax.set_ymargin(2)
# NOTE:
# Save figure ~
# * BUT DO NOT USE PYLAB *
# Write figure to output file (png|pdf).
pdf.savefig(fig)
return fig_path
app = dash.Dash(__name__)
app.layout = html.Div(
[
html.Button(
"Generate plots",
id="generate-plot",
style={
"width": "30%",
"fontSize": "1.1rem",
},
),
html.Br(),
html.Code("Enter number of plots to generate:"),
html.Br(),
dcc.Input(id="range", type="number", min=1, max=1200, step=1),
dcc.Download(id="download-image"),
],
style={"margin": "10% 40% 10% 40%"}
)
@app.callback(
Output("download-image", "data"),
Input("generate-plot", "n_clicks"),
State("range", "value"),
prevent_initial_call=True,
)
def generate_downloadable_figure(n_clicks, n_plots):
if n_clicks > 0:
fig_path = generate_figures(n_plots)
return dcc.send_file(fig_path)
if __name__ == "__main__":
app.run_server(debug=True, dev_tools_hot_reload=True, host="0.0.0.0")
→ Clicking button downloads multiple (as applicable) page PDF of subplots
N=60 地块
N=231 地块
(Took about ten-twenty seconds..)