将 Matplotlib 图形添加到 KivyMD 中的小部件
Add a Matplotlib Graph to a Widget in KivyMD
我目前正在使用 KivyMD 创建一个移动应用程序,用于管理差旅费用请求。用户将在 MDTextField 上为不同类型的费用输入所需的请求金额。我想将使用 patplotlib 制作的 donut 图添加到 MDBoxLayout 中。此类图表应在请求填满时自动更新。 (为清楚起见,我将包括一个屏幕截图。红色方块是我的图表所需的位置)。
我创建了一个名为 update_method_graph 的方法并使用了固定数字,我可以成功地创建一个 Plot,但是我没有成功地在应用程序上添加这样的图表。一旦我可以成功地将图表添加到我的应用程序中,我将 link 这些值添加到用户添加的请求中。现在我关心的是正确添加图形。当然完成的代码不会包含 plt.show() 行,图表应该直接在应用程序上更新。
至于现在,当我关闭图表的 window 时,我的代码在
中显示错误
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__'`
在 expense_graph.
中出现密钥错误
我已尝试使用 an answer to a similar question and with matplotlib.use('module://kivy.garden.matplotlib.backend_kivy')
, as done in examples of use in garden.matplotlib 中建议的 from kivy.garden.matplotlib.backend_kivyagg import FigureCanvasKivyAgg
,但我仍然无法让我的应用程序正常工作。
最小可重现示例的代码
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()
KV代码:
<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
对我的代码提出任何建议或更正将不胜感激。非常感谢。
编辑
我设法 link MDTextFields 到图中的数据值。因此,图表将随着值的输入而更新。每次添加一个值时,都会出现一个更新的图表,以便您自己查看(最小可重现示例的代码已经更新)。尽管如此,我仍然无法将图表添加到我的应用程序中。我将非常感谢你的帮助。非常感谢!
编辑 #2
我改变了方法,我决定将图形转换为图像,并将图像添加到 MDBoxLayout。 (如果第一种方法更好,请告诉我)。代码已经更新。但是我得到一个错误:
self.ids.expense_graph.add_widget(updated_graph)
AttributeError: 'str' object has no attribute 'ids'
我在网上搜索了针对此错误的不同解决方案,但我无法解决这个问题。
编辑 3
所以我终于能够解决 EDIT 2 中描述的错误代码。我能够将我的图表正确添加到应用程序中。然而,该图表没有更新新的费用(尽管文件确实更新并且 plt.show() 代码行确实显示了更新的图表)。知道为什么应用程序中的图表无法更新吗?最小可重现示例的代码已更新。
我认为你只需要在每次更改时重建剧情即可。尝试将您的 update_donut_graph()
更改为:
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()))
感谢@John Anderson 的回复。一如既往,你帮了大忙。
但是我遇到了一个简单的问题。我的图形大小已修改,它小得多,但图例的大小保持不变。因此,图例现在没有按照我的意愿显示图表,而是掩盖了图表。
有什么办法可以防止这种情况发生吗?我一直在想,也许一种使图表占据整个 canvas 大小的方法是合适的。正如您在 donut_graph_image.png 屏幕截图中所见,图表周围有很多空白。或者可以帮助我保持图形大小的命令?我尝试使用 Axes.set_aspect 但这确实有效。
再次感谢。
我目前正在使用 KivyMD 创建一个移动应用程序,用于管理差旅费用请求。用户将在 MDTextField 上为不同类型的费用输入所需的请求金额。我想将使用 patplotlib 制作的 donut 图添加到 MDBoxLayout 中。此类图表应在请求填满时自动更新。 (为清楚起见,我将包括一个屏幕截图。红色方块是我的图表所需的位置)。
我创建了一个名为 update_method_graph 的方法并使用了固定数字,我可以成功地创建一个 Plot,但是我没有成功地在应用程序上添加这样的图表。一旦我可以成功地将图表添加到我的应用程序中,我将 link 这些值添加到用户添加的请求中。现在我关心的是正确添加图形。当然完成的代码不会包含 plt.show() 行,图表应该直接在应用程序上更新。
至于现在,当我关闭图表的 window 时,我的代码在
中显示错误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__'`
在 expense_graph.
中出现密钥错误我已尝试使用 an answer to a similar question and with matplotlib.use('module://kivy.garden.matplotlib.backend_kivy')
, as done in examples of use in garden.matplotlib 中建议的 from kivy.garden.matplotlib.backend_kivyagg import FigureCanvasKivyAgg
,但我仍然无法让我的应用程序正常工作。
最小可重现示例的代码
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()
KV代码:
<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
对我的代码提出任何建议或更正将不胜感激。非常感谢。
编辑 我设法 link MDTextFields 到图中的数据值。因此,图表将随着值的输入而更新。每次添加一个值时,都会出现一个更新的图表,以便您自己查看(最小可重现示例的代码已经更新)。尽管如此,我仍然无法将图表添加到我的应用程序中。我将非常感谢你的帮助。非常感谢!
编辑 #2
我改变了方法,我决定将图形转换为图像,并将图像添加到 MDBoxLayout。 (如果第一种方法更好,请告诉我)。代码已经更新。但是我得到一个错误:
self.ids.expense_graph.add_widget(updated_graph)
AttributeError: 'str' object has no attribute 'ids'
我在网上搜索了针对此错误的不同解决方案,但我无法解决这个问题。
编辑 3
所以我终于能够解决 EDIT 2 中描述的错误代码。我能够将我的图表正确添加到应用程序中。然而,该图表没有更新新的费用(尽管文件确实更新并且 plt.show() 代码行确实显示了更新的图表)。知道为什么应用程序中的图表无法更新吗?最小可重现示例的代码已更新。
我认为你只需要在每次更改时重建剧情即可。尝试将您的 update_donut_graph()
更改为:
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()))
感谢@John Anderson 的回复。一如既往,你帮了大忙。 但是我遇到了一个简单的问题。我的图形大小已修改,它小得多,但图例的大小保持不变。因此,图例现在没有按照我的意愿显示图表,而是掩盖了图表。
有什么办法可以防止这种情况发生吗?我一直在想,也许一种使图表占据整个 canvas 大小的方法是合适的。正如您在 donut_graph_image.png 屏幕截图中所见,图表周围有很多空白。或者可以帮助我保持图形大小的命令?我尝试使用 Axes.set_aspect 但这确实有效。
再次感谢。