Python 将Matplotlib图添加到KivyMD中的小部件
我目前正在使用KivyMD创建一个移动应用程序,用于管理差旅费用申请。用户将在MDTEXT字段中输入不同类型费用的所需请求金额。我想将使用patplotlib制作的圆环图添加到MDBoxLayout中。当请求被填写时,这样的图表应该自动更新。(为了清晰起见,我将包括一个屏幕截图。红色的正方形是我的图形所需的位置) 我创建了一个名为update_method_graph的方法,并使用了固定的数字,我可以成功地创建一个绘图,但是我没有成功地在应用程序中添加这样的图形。一旦我能成功地将图表添加到我的应用程序中,我会将这些值链接到用户添加的请求。现在我关心的是正确地添加图表。当然,完成的代码不包括plt.show()行,图形应该直接在应用程序上更新 现在,当我关闭图形窗口时,我的代码在Python 将Matplotlib图添加到KivyMD中的小部件,python,matplotlib,kivy,kivymd,matplotlib-widget,Python,Matplotlib,Kivy,Kivymd,Matplotlib Widget,我目前正在使用KivyMD创建一个移动应用程序,用于管理差旅费用申请。用户将在MDTEXT字段中输入不同类型费用的所需请求金额。我想将使用patplotlib制作的圆环图添加到MDBoxLayout中。当请求被填写时,这样的图表应该自动更新。(为了清晰起见,我将包括一个屏幕截图。红色的正方形是我的图形所需的位置) 我创建了一个名为update_method_graph的方法,并使用了固定的数字,我可以成功地创建一个绘图,但是我没有成功地在应用程序中添加这样的图形。一旦我能成功地将图表添加到我的
self.ids.expense_graph.add_widget(FigureCanvasKivyAgg(plt.gcf()))
File "kivy\properties.pyx", line 863, in kivy.properties.ObservableDict.__getattr__
AttributeError: 'super' object has no attribute '__getattr__'`
费用图中的关键错误
我已尝试使用kivy.garden.matplotlib.backend中的_kivyaggimport figure canvaskivyagg
以及matplotlib.use('module://kivy.garden.matplotlib.backend_kivy)
,如中所述,但我仍然无法使我的应用程序正常运行
最小可重复示例代码
Python代码:
from kivy.properties import ObjectProperty
from kivy.uix.screenmanager import ScreenManager, Screen
from kivymd.app import MDApp
from kivymd.uix.expansionpanel import MDExpansionPanel, MDExpansionPanelOneLine
from kivy.uix.boxlayout import BoxLayout
import matplotlib.pyplot as plt
from kivy.garden.matplotlib.backend_kivyagg import FigureCanvasKivyAgg
from kivy.uix.image import Image
class MyContentAliment(BoxLayout):
monto_alimento = 0
def apply_currency_format(self):
# if len <= 3
if len(self.ids.monto_aliment_viaje.text) <= 3 and self.ids.monto_aliment_viaje.text.isnumeric():
self.ids.monto_aliment_viaje.text = "$" + self.ids.monto_aliment_viaje.text + '.00'
# n,nnn
elif len(self.ids.monto_aliment_viaje.text) == 4 and self.ids.monto_aliment_viaje.text.isnumeric():
self.ids.monto_aliment_viaje.text = "$" + self.ids.monto_aliment_viaje.text[0] + "," + \
self.ids.monto_aliment_viaje.text[1:] + '.00'
# nn,nnn
elif len(self.ids.monto_aliment_viaje.text) == 5 and self.ids.monto_aliment_viaje.text.isnumeric():
self.ids.monto_aliment_viaje.text = "$" + self.ids.monto_aliment_viaje.text[:2] + "," + \
self.ids.monto_aliment_viaje.text[2:] + '.00'
def limit_currency(self):
if len(self.ids.monto_aliment_viaje.text) > 5 and self.ids.monto_aliment_viaje.text.startswith('$') == False:
self.ids.monto_aliment_viaje.text = self.ids.monto_aliment_viaje.text[:-1]
def sumar_gasto(self):
if self.ids.monto_aliment_viaje.text == "":
pass
elif self.ids.monto_aliment_viaje.text.startswith('$'):
pass
else:
travel_manager = MDApp.get_running_app().root.get_screen('travelManager')
monto_total = float(travel_manager.ids.suma_solic_viaje.text[2:])
monto_total += float(self.ids.monto_aliment_viaje.text)
travel_manager.ids.suma_solic_viaje.text = "$ " + str(monto_total)
self.apply_currency_format()
# USE THIS METHOD TO UPDATE THE VALUE OF ALIMENTOS (donut)
def update_requested_value(self):
MyContentAliment.monto_alimento = 0
if len(self.ids.monto_aliment_viaje.text) > 0:
MyContentAliment.monto_alimento = self.ids.monto_aliment_viaje.text
else:
MyContentAliment.monto_alimento = 0
TravelManagerWindow.update_donut_graph(MyContentAliment.monto_alimento)
class MyContentCasetas(BoxLayout):
monto_casetas = 0
def apply_currency_format(self):
# if len <= 3
if len(self.ids.monto_casetas_viaje.text) <= 3 and self.ids.monto_casetas_viaje.text.isnumeric():
self.ids.monto_casetas_viaje.text = "$" + self.ids.monto_casetas_viaje.text + '.00'
# n,nnn
elif len(self.ids.monto_casetas_viaje.text) == 4 and self.ids.monto_casetas_viaje.text.isnumeric():
self.ids.monto_casetas_viaje.text = "$" + self.ids.monto_casetas_viaje.text[0] + "," + \
self.ids.monto_casetas_viaje.text[1:] + '.00'
# nn,nnn
elif len(self.ids.monto_casetas_viaje.text) == 5 and self.ids.monto_casetas_viaje.text.isnumeric():
self.ids.monto_casetas_viaje.text = "$" + self.ids.monto_casetas_viaje.text[:2] + "," + \
self.ids.monto_casetas_viaje.text[2:] + '.00'
def limit_currency(self):
if len(self.ids.monto_casetas_viaje.text) > 5 and self.ids.monto_casetas_viaje.text.startswith('$') == False:
self.ids.monto_casetas_viaje.text = self.ids.monto_casetas_viaje.text[:-1]
def sumar_gasto(self):
if self.ids.monto_casetas_viaje.text == "":
pass
elif self.ids.monto_casetas_viaje.text.startswith('$'):
pass
else:
travel_manager = MDApp.get_running_app().root.get_screen('travelManager')
monto_total = float(travel_manager.ids.suma_solic_viaje.text[2:])
monto_total += float(self.ids.monto_casetas_viaje.text)
travel_manager.ids.suma_solic_viaje.text = "$ " + str(monto_total)
self.apply_currency_format()
# USE THIS METHOD TO UPDATE THE VALUE OF CASETAS (donut)
def update_requested_value(self):
MyContentCasetas.monto_casetas = 0
if len(self.ids.monto_casetas_viaje.text) > 0:
MyContentCasetas.monto_casetas = self.ids.monto_casetas_viaje.text
else:
MyContentCasetas.monto_casetas = 0
TravelManagerWindow.update_donut_graph(MyContentCasetas.monto_casetas)
class MyContentGasolina(BoxLayout):
monto_gasolina = 0
def apply_currency_format(self):
# if len <= 3
if len(self.ids.monto_gas_viaje.text) <= 3 and self.ids.monto_gas_viaje.text.isnumeric():
self.ids.monto_gas_viaje.text = "$" + self.ids.monto_gas_viaje.text + '.00'
# n,nnn
elif len(self.ids.monto_gas_viaje.text) == 4 and self.ids.monto_gas_viaje.text.isnumeric():
self.ids.monto_gas_viaje.text = "$" + self.ids.monto_gas_viaje.text[0] + "," + \
self.ids.monto_gas_viaje.text[1:] + '.00'
# nn,nnn
elif len(self.ids.monto_gas_viaje.text) == 5 and self.ids.monto_gas_viaje.text.isnumeric():
self.ids.monto_gas_viaje.text = "$" + self.ids.monto_gas_viaje.text[:2] + "," + \
self.ids.monto_gas_viaje.text[2:] + '.00'
def limit_currency(self):
if len(self.ids.monto_gas_viaje.text) > 5 and self.ids.monto_gas_viaje.text.startswith('$') == False:
self.ids.monto_gas_viaje.text = self.ids.monto_gas_viaje.text[:-1]
def sumar_gasto(self):
if self.ids.monto_gas_viaje.text == "":
pass
elif self.ids.monto_gas_viaje.text.startswith('$'):
pass
else:
travel_manager = MDApp.get_running_app().root.get_screen('travelManager')
monto_total = float(travel_manager.ids.suma_solic_viaje.text[2:])
monto_total += float(self.ids.monto_gas_viaje.text)
travel_manager.ids.suma_solic_viaje.text = "$ " + str(monto_total)
self.apply_currency_format()
# USE THIS METHOD TO UPDATE THE VALUE OF GASOLINA (donut)
def update_requested_value(self):
MyContentGasolina.monto_gasolina = 0
if len(self.ids.monto_gas_viaje.text) > 0:
MyContentGasolina.monto_gasolina = self.ids.monto_gas_viaje.text
else:
MyContentGasolina.monto_gasolina = 0
TravelManagerWindow.update_donut_graph \
(MyContentGasolina.monto_gasolina)
class LoginWindow(Screen):
pass
class TravelManagerWindow(Screen):
panel_container = ObjectProperty(None)
expense_graph = ObjectProperty(None)
# EXPANSION PANEL PARA SOLICITAR GV
def set_expansion_panel(self):
self.ids.panel_container.clear_widgets()
# FOOD PANEL
self.ids.panel_container.add_widget(MDExpansionPanel(icon="food", content=MyContentAliment(),
panel_cls=MDExpansionPanelOneLine(text="Alimentacion")))
# CASETAS PANEL
self.ids.panel_container.add_widget(MDExpansionPanel(icon="food", content=MyContentCasetas(),
panel_cls=MDExpansionPanelOneLine(text="Casetas")))
# GAS PANEL
self.ids.panel_container.add_widget(MDExpansionPanel(icon="food", content=MyContentGasolina(),
panel_cls=MDExpansionPanelOneLine(text="Gasolina")))
def update_donut_graph(self):
travel_manager = MDApp.get_running_app().root.get_screen('travelManager')
travel_manager.ids.expense_graph.clear_widgets()
# create data
names = 'Alimentación', 'Casetas', 'Gasolina',
data_values = [MyContentAliment.monto_alimento, MyContentCasetas.monto_casetas,
MyContentGasolina.monto_gasolina]
# Create a white circle for the center of the plot
my_circle = plt.Circle((0, 0), 0.65, color='white')
# Create graph, add and place percentage labels
# Add spaces to separate elements from the donut
explode = (0.05, 0.05, 0.05)
plt.pie(data_values, autopct="%.1f%%", startangle=0, pctdistance=0.80, labeldistance=1.2, explode=explode)
p = plt.gcf()
p.gca().add_artist(my_circle)
# Create and place legend of the graph
plt.legend(labels=names, loc="center")
# Add graph to Kivy App
plt.show()
# THE DESIRED RESULT IS TO ADD THE GRAPH TO THE APP WITH THE LINE OF CODE BELOW, INSTEAD OF THE plt.show() line
travel_manager.ids.expense_graph.add_widget(Image(source='donut_graph_image.png'))
# WINDOW MANAGER ################################
class WindowManager(ScreenManager):
pass
class ReprodExample3(MDApp):
travel_manager_window = TravelManagerWindow()
def build(self):
self.theme_cls.primary_palette = "Teal"
return WindowManager()
if __name__ == "__main__":
ReprodExample3().run()
<WindowManager>:
LoginWindow:
TravelManagerWindow:
<LoginWindow>:
name: 'login'
MDRaisedButton:
text: 'Enter'
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
size_hint: None, None
on_release:
root.manager.transition.direction = 'up'
root.manager.current = 'travelManager'
<TravelManagerWindow>:
name:'travelManager'
on_pre_enter: root.set_expansion_panel()
MDRaisedButton:
text: 'Back'
pos_hint: {'center_x': 0.5, 'center_y': 0.85}
size_hint: None, None
on_release:
root.manager.transition.direction = 'down'
root.manager.current = 'login'
BoxLayout:
orientation: 'vertical'
size_hint:1,0.85
pos_hint: {"center_x": 0.5, "center_y":0.37}
adaptive_height:True
height: self.minimum_height
ScrollView:
adaptive_height:True
GridLayout:
size_hint_y: None
cols: 1
row_default_height: root.height*0.10
height: self.minimum_height
BoxLayout:
adaptive_height: True
orientation: 'horizontal'
GridLayout:
id: panel_container
size_hint_x: 0.6
cols: 1
adaptive_height: True
BoxLayout:
size_hint_x: 0.05
MDCard:
id: resumen_solicitud
size_hint: None, None
size: "250dp", "350dp"
pos_hint: {"top": 0.9, "center_x": .5}
elevation: 0.1
BoxLayout:
orientation: 'vertical'
canvas.before:
Color:
rgba: 0.8, 0.8, 0.8, 1
Rectangle:
pos: self.pos
size: self.size
MDLabel:
text: 'Monto Total Solicitado'
font_style: 'Button'
halign: 'center'
font_size: (root.width**2 + root.height**2) / 15.5**4
size_hint_y: 0.2
MDSeparator:
height: "1dp"
MDTextField:
id: suma_solic_viaje
text: "$ 0.00"
bold: True
line_color_normal: app.theme_cls.primary_color
halign: "center"
size_hint_x: 0.8
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
MDSeparator:
height: "1dp"
# DESIRED LOCATION FOR THE MATPLOTLIB GRAPH
MDBoxLayout:
id: expense_graph
<MyContentAliment>:
adaptive_height: True
MDBoxLayout:
orientation:'horizontal'
adaptive_height:True
size_hint_x:self.width
pos_hint: {"center_x":0.5, "center_y":0.5}
spacing: dp(10)
padding_horizontal: dp(10)
MDLabel:
text: 'Monto:'
multiline: 'True'
halign: 'center'
pos_hint: {"x":0, "top":0.5}
size_hint_x: 0.15
font_style: 'Button'
font_size: 19
MDTextField:
id: monto_aliment_viaje
hint_text: 'Monto a solicitar'
pos_hint: {"x":0, "top":0.5}
halign: 'left'
size_hint_x: 0.3
helper_text: 'Ingresar el monto a solicitar'
helper_text_mode: 'on_focus'
write_tab: False
required: True
on_text: root.limit_currency()
MDRaisedButton:
id: boton_aliment_viaje
pos_hint: {"x":0, "top":0.5}
text:'Ingresar Gasto'
on_press:
root.update_requested_value()
on_release:
root.sumar_gasto()
### CASETAS
<MyContentCasetas>:
adaptive_height: True
MDBoxLayout:
orientation:'horizontal'
adaptive_height:True
size_hint_x:self.width
pos_hint: {"center_x":0.5, "center_y":0.5}
spacing: dp(10)
padding_horizontal: dp(10)
MDLabel:
text: 'Monto:'
multiline: 'True'
halign: 'center'
pos_hint: {"x":0, "top":0.5}
size_hint_x: 0.15
font_style: 'Button'
font_size: 19
MDTextField:
id: monto_casetas_viaje
hint_text: 'Monto a solicitar'
pos_hint: {"x":0, "top":0.5}
halign: 'left'
size_hint_x: 0.3
helper_text: 'Ingresar el monto a solicitar'
helper_text_mode: 'on_focus'
write_tab: False
#input_filter: 'float'
required: True
on_text: root.limit_currency()
MDRaisedButton:
id: boton_casetas_viaje
pos_hint: {"x":0, "top":0.5}
text:'Ingresar Gasto'
on_press:
root.update_requested_value()
on_release:
root.sumar_gasto()
BoxLayout:
size_hint_x: 0.05
### GASOLINA
<MyContentGasolina>:
adaptive_height: True
MDBoxLayout:
orientation:'horizontal'
adaptive_height:True
size_hint_x:self.width
pos_hint: {"center_x":0.5, "center_y":0.5}
spacing: dp(10)
padding_horizontal: dp(10)
MDLabel:
text: 'Monto:'
multiline: 'True'
halign: 'center'
pos_hint: {"x":0, "top":0.5}
size_hint_x: 0.15
font_style: 'Button'
font_size: 19
MDTextField:
id: monto_gas_viaje
hint_text: 'Monto a solicitar'
pos_hint: {"x":0, "top":0.5}
halign: 'left'
size_hint_x: 0.3
helper_text: 'Ingresar el monto a solicitar'
helper_text_mode: 'on_focus'
write_tab: False
required: True
on_text: root.limit_currency()
MDRaisedButton:
id: boton_gas_viaje
pos_hint: {"x":0, "top":0.5}
text:'Ingresar Gasto'
on_press:
root.update_requested_value()
on_release:
root.sumar_gasto()
BoxLayout:
size_hint_x: 0.05
我已经在网上搜索了这个错误的不同解决方案,但是我无法修复这个问题
编辑3
因此,我终于能够解决Edit2中描述的错误代码。我能够将我的图表正确添加到应用程序中。但是,图形不会用新的费用更新(尽管文件会更新,并且plt.show()代码行会显示更新的图形)。你知道为什么应用程序中的图表无法更新吗?最小可复制示例的代码已更新
我认为您只需要根据每次更改重新生成绘图。尝试将
更新甜甜圈图()
更改为:
def update_donut_graph(self):
plt.clf() # clear the plot
travel_manager = MDApp.get_running_app().root.get_screen('travelManager')
travel_manager.ids.expense_graph.clear_widgets()
# create data
names = 'Alimentación', 'Casetas', 'Gasolina',
data_values = [MyContentAliment.monto_alimento, MyContentCasetas.monto_casetas,
MyContentGasolina.monto_gasolina]
# Create a white circle for the center of the plot
my_circle = plt.Circle((0, 0), 0.65, color='white')
# Create graph, add and place percentage labels
# Add spaces to separate elements from the donut
explode = (0.05, 0.05, 0.05)
plt.pie(data_values, autopct="%.1f%%", startangle=0, pctdistance=0.80, labeldistance=1.2, explode=explode)
p = plt.gcf()
p.gca().add_artist(my_circle)
# Create and place legend of the graph
plt.legend(labels=names, loc="center")
# Add graph to Kivy App
# plt.show()
# THE DESIRED RESULT IS TO ADD THE GRAPH TO THE APP WITH THE LINE OF CODE BELOW, INSTEAD OF THE plt.show() line
travel_manager.ids.expense_graph.add_widget(FigureCanvasKivyAgg(figure=plt.gcf()))
感谢您的回复@johnanderson。一如既往,你帮了大忙。 然而,我遇到了一个简单的问题。我的图表的大小被修改,它相当小,但图例的大小保持不变。因此,图例不再显示我所希望的图形,而是覆盖了图形 有什么办法可以防止这种情况发生吗?我一直在想,也许一种让图形占据整个画布大小的方法是合适的。正如您在donut_graph_image.png屏幕截图中所看到的,图形周围有很多空白。或者是一个可以帮助我维护图形大小的命令?我尝试了Axis.set_方面,但这确实有效 再次感谢
您可以通过使用
fontsize
参数,如plt.legend(labels=names,loc=“center”,fontsize='xx-small')
使图形的容器变大,或使图例变小。最后,我使用了较小的字体大小,删除了图例的框架,并使用了自动缩放(tight=True)以及子批次调整,并给出wspace=0和hspace=0。看起来正是我想要的。再次非常感谢你,约翰,祝你今天愉快。
def update_donut_graph(self):
plt.clf() # clear the plot
travel_manager = MDApp.get_running_app().root.get_screen('travelManager')
travel_manager.ids.expense_graph.clear_widgets()
# create data
names = 'Alimentación', 'Casetas', 'Gasolina',
data_values = [MyContentAliment.monto_alimento, MyContentCasetas.monto_casetas,
MyContentGasolina.monto_gasolina]
# Create a white circle for the center of the plot
my_circle = plt.Circle((0, 0), 0.65, color='white')
# Create graph, add and place percentage labels
# Add spaces to separate elements from the donut
explode = (0.05, 0.05, 0.05)
plt.pie(data_values, autopct="%.1f%%", startangle=0, pctdistance=0.80, labeldistance=1.2, explode=explode)
p = plt.gcf()
p.gca().add_artist(my_circle)
# Create and place legend of the graph
plt.legend(labels=names, loc="center")
# Add graph to Kivy App
# plt.show()
# THE DESIRED RESULT IS TO ADD THE GRAPH TO THE APP WITH THE LINE OF CODE BELOW, INSTEAD OF THE plt.show() line
travel_manager.ids.expense_graph.add_widget(FigureCanvasKivyAgg(figure=plt.gcf()))