kivy scrollview consisting of maplotlib plots not scrolling - python

I'm currently working on building a digital planner app, where I want to include some statistics-related features. Obviously, matplotlib is an optimal means of doing that, but I have two problems with it when I try to add more than one plot in a a Kivy ScrollView:
Each plot decreases in size so much that you cannot see what's
actually being displayed;
Kivy ScrollView is not scrolling - unfortunately, very common.
I've tried setting ScrollView's height equal to ScrollView.minimum_height and yet I get no result.
Here's a bit of my Python code:
class StatsWindow(FloatLayout, MDTabsBase):
dates_from, dates_to, plot_scroll = [str(datetime.today()).split()[0]], [str(datetime.today()).split()[0]], ObjectProperty(None)
def add_sp_plot(self, date_from, date_to):
# There were too many lines of data handling and plot creating, so I've only left the KivyPart:
# ------- KIVY part ------- KIVY part ------- KIVY part ------- KIVY part ------- #
self.plot_scroll.plot_layout.clear_widgets()
self.plot_scroll.plot_layout.add_widget(FigureCanvasKivyAgg(plt.gcf(), size_hint_y=None))
self.plot_scroll.plot_layout.add_widget(FigureCanvasKivyAgg(plt.gcf(), size_hint_y=None))
self.plot_scroll.plot_layout.add_widget(FigureCanvasKivyAgg(plt.gcf(), size_hint_y=None))
Here's a bit of my Kivy code:
<StatsWindow>:
name: "stats"
text: "STATS"
icon: "thumbs-up-down"
plot_scroll: plot_scroll
choose_date_to: choose_date_to
choose_date_from: choose_date_from
FloatLayout:
MDLabel:
halign: "center"
size_hint: 1, .1
text: "Choose the dates to view your stats"
font_size: self.width / 17
color: app.theme_cls.primary_color
pos_hint: {"top": .98, "center_x": .5}
BoxLayout:
MDFlatButton:
id: choose_date_from
pos_hint: {"center_x": .25, "top": .88}
text: "from"
size_hint: .4, .1
font_size: self.width / 11
on_release: root.open_date_picker(root.dates_from, self)
MDFlatButton:
text: "|"
size_hint: .1, .1
pos_hint: {"center_x": .5, "top": .88}
MDFlatButton:
id: choose_date_to
font_size: self.width / 11
pos_hint: {"center_x": .75, "top": .88}
text: "to"
size_hint: .4, .1
font_size: self.width / 11
on_release:
root.open_date_picker(root.dates_from, self)
BoxLayout:
MDFlatButton:
size_hint: 1, .1
text: "Load the statistics"
pos_hint: {"center_x": .5, "top": .78}
on_release: root.add_sp_plot(choose_date_from.text, choose_date_to.text)
ScrollView:
id: plot_scroll
do_scroll_y: True
do_scroll_x: False
pos_hint: {"top": .68}
plot_layout: plot_layout
size: root.width, root.height
GridLayout:
id: plot_layout
cols: 1
height: self.minimum_height
Here's the picture of what I get when I run the program:

The ScrollView will only scroll if its child (the GridLayout) is larger than the ScrollView. Also, the line:
height: self.minimum_height
will have no effect unless you add the line:
size_hint_y: None
You can increase the size of the plots by specifying a row_default_height for the GridLayout and eliminating the size_hint_y=None for the FigureCanvasKivyAgg. So, I would suggest specifying your ScrollView as:
ScrollView:
id: plot_scroll
do_scroll_y: True
do_scroll_x: False
pos_hint: {"top": .68}
plot_layout: plot_layout
GridLayout:
id: plot_layout
cols: 1
size_hint_y: None
row_default_height: 500 # any default row height that you desire
height: self.minimum_height
Then, add the plots using:
def add_sp_plot(self, date_from, date_to):
# There were too many lines of data handling and plot creating, so I've only left the KivyPart:
# ------- KIVY part ------- KIVY part ------- KIVY part ------- KIVY part ------- #
self.plot_scroll.plot_layout.clear_widgets()
self.plot_scroll.plot_layout.add_widget(FigureCanvasKivyAgg(plt.gcf()))
self.plot_scroll.plot_layout.add_widget(FigureCanvasKivyAgg(plt.gcf()))
self.plot_scroll.plot_layout.add_widget(FigureCanvasKivyAgg(plt.gcf()))
Removing the size_hint_y=None leaves the default value of size_hint_y as 1, so that the plot will take up all the space that the GridLayout allocates to it (the row_default_height).

Related

How To Show Text and Images With a Switch using KivyMD?

I'm trying to make a simple program that, right now, needs to have two functions.
The app has four images displayed at the top and for words in the middle. There are two switches in the bottom left corner, one to toggle text and the other to toggle images.
How would one use the switches to enable/disable the images and the text?
Here's a screenshot of the program. Below is the current code.
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.core.window import Window
Window.size = (1280, 720)
class MyAppApp(MDApp):
height = '185dp'
width = '250dp'
text = 'airplane bicycle boat canoe'
x = 0
def build(self):
return Builder.load_string('''
FloatLayout:
MDLabel:
text: 'Images'
font_size: dp(18)
halign: 'center'
pos_hint: {'center_x': 0.1075, 'center_y': 0.135}
MDLabel:
text: 'Text'
halign: 'center'
pos_hint: {'center_x': 0.098, 'center_y': 0.075}
font_size: dp(18)
MDSwitch:
active: True
width: dp(40)
pos_hint: {'center_x': 0.05, 'center_y': 0.135}
MDSwitch:
active: True
width: dp(40)
pos_hint: {'center_x': 0.05, 'center_y': 0.075}
MDToolbar:
pos_hint: {'top': 1}
md_bg_color: [0, 106/255, 163/255, 1]
title: 'Images'
elevation: 20
FitImage:
size_hint_y: None
size_hint_x: None
height: app.height
width: app.width
radius: 36, 36, 36, 36
elevation: 20
source: 'images/transportation/Airplane.jpg'
pos_hint: {'center_x': 0.2, 'center_y': 0.7}
FitImage:
size_hint_y: None
size_hint_x: None
height: app.height
width: app.width
radius: 36, 36, 36, 36
elevation: 20
source: 'images/transportation/Bicycle.jpg'
pos_hint: {'center_x': 0.4, 'center_y': 0.7}
FitImage:
size_hint_y: None
size_hint_x: None
height: app.height
width: app.width
radius: 36, 36, 36, 36
elevation: 20
source: 'images/transportation/Boat.jpg'
pos_hint: {'center_x': 0.6, 'center_y': 0.7}
FitImage:
size_hint_y: None
size_hint_x: None
height: app.height
width: app.width
radius: 36, 36, 36, 36
elevation: 20
source: 'images/transportation/Canoe.jpg'
pos_hint: {'center_x': 0.8, 'center_y': 0.7}
MDLabel:
text: app.text
halign: 'center'
pos_hint: {'center_y': 0.375}
font_size: dp(56)
'''
)
MyAppApp().run()
Any help would be greatly appreciated. Thanks!
To turn on/off the text, you can define a method in your App class that adjusts the opacity of the MDLabel, like this:
def switch_text(self, switch_instance):
text_widget = self.root.ids.text
if switch_instance.active:
text_widget.opacity = 1
else:
text_widget.opacity = 0
This requires adding an id to your kv:
MDLabel:
id: text # added id
text: app.text
halign: 'center'
pos_hint: {'center_y': 0.375}
font_size: dp(56)
And an on_active: entry for the MDSwitch:
MDSwitch:
active: True
width: dp(40)
pos_hint: {'center_x': 0.05, 'center_y': 0.075}
on_active: app.switch_text(self) # triggers method call when active changes
You can do the same type of thing for the Images, but you may need to add an id for each Image and change the opacity for each.
Using opacity just makes the widget invisible, but it still hold its position in the GUI. You could change the size of those widgets (to (0,0)) instead, if you want to open up the space that they occupy in the GUI.

Appending and removing code elements within Kivy language on button click

I have written a Kivy GUI that consists of various buttons and nested layouts. I want to be able to, on a click of a button, append a section of code n number of times within one of these layouts, specifically the scroll layout. Since I have am very new to Kivy and since I cannot seem to find a tutorial on this matter, I am presenting it here.
Here is the Python code:
import kivy
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.widget import Widget
class Interface(Widget):
pass
class GUI(App):
def build(self):
return Interface()
if __name__ == "__main__":
GUI().run()
And the total Kivy code:
<Interface>:
GridLayout:
padding:0
size: root.width, root.height
cols:1
#Top buttons
GridLayout:
size_hint: 1, 0.07
padding:0
cols:1
Button:
text:"Add Phase"
#Chart areas
ScrollView:
do_scroll_y:False
BoxLayout:
orientation: "vertical"
size_hint_x: None
width: self.minimum_width
GridLayout:
size_hint_x: None
width: self.minimum_width
cols:20
#Phase template
GridLayout:
width: 200
size_hint_x: None
cols:1
TextInput:
size_hint: 1, 0.06
halign: 'center'
hint_text:"Phase"
TextInput:
size_hint: 1, 0.06
halign: 'center'
hint_text:"Step"
Button:
size_hint:1, 0.07
text:"Add Step"
GridLayout:
cols:20
#Step template
GridLayout:
width: 100
size_hint_x: None
cols:1
TextInput:
size_hint: 1, 0.06
halign: 'center'
hint_text:"Var1"
TextInput:
size_hint: 1, 0.06
halign: 'center'
hint_text:"Var2"
Button:
background_normal: ''
background_color: 0.28,0.59,0.72,1
text:"Test"
Button:
size_hint:1, 0.07
text:"Delete"
Button:
background_color: 0.8,0,0,1
size_hint:1, 0.07
text:"Delete"
You will see in the Kivy code that these is a commented secion called #Phase template. Basically on pressing the button Add Phase, this entire section and its children elements should be appended in the immediate parent GridLayout.
Here you can press the Add Phase button:
Which will result in this:
And then finally, pressing the Delete button should remove that specific appended section of code.
Again, no idea how to approach this from the Kivy language, which seems a bit rigid to work with. But I am sure what I want to do can be accomplished.
One way to accomplish that is to create a Phase class, and add a kv rule for building instances of Phase. Then, in kv, you can use Factory.Phase() to create new instances.
Modify your kv as:
#:import Factory kivy.factory.Factory
<Interface>:
GridLayout:
padding:0
size: root.width, root.height
cols:1
#Top buttons
GridLayout:
size_hint: 1, 0.07
padding:0
cols:1
Button:
text:"Add Phase"
on_release: grid.add_widget(Factory.Phase()) # this adds another Phase
#Chart areas
ScrollView:
do_scroll_y:False
BoxLayout:
orientation: "vertical"
size_hint_x: None
width: self.minimum_width
GridLayout:
id: grid # added id to identify where to add new Phase instances
size_hint_x: None
width: self.minimum_width
cols:20
# initial Phase instance
Phase:
#Phase template
<Phase#GridLayout>:
width: 200
size_hint_x: None
cols:1
TextInput:
size_hint: 1, 0.06
halign: 'center'
hint_text:"Phase"
TextInput:
size_hint: 1, 0.06
halign: 'center'
hint_text:"Step"
Button:
size_hint:1, 0.07
text:"Add Step"
GridLayout:
cols:20
#Step template
GridLayout:
width: 100
size_hint_x: None
cols:1
TextInput:
size_hint: 1, 0.06
halign: 'center'
hint_text:"Var1"
TextInput:
size_hint: 1, 0.06
halign: 'center'
hint_text:"Var2"
Button:
background_normal: ''
background_color: 0.28,0.59,0.72,1
text:"Test"
Button:
size_hint:1, 0.07
text:"Delete"
Button:
background_color: 0.8,0,0,1
size_hint:1, 0.07
text:"Delete"
on_release: root.parent.remove_widget(root) # delete this Phase
Key points are the <Phase#GridLayout> rule, the new grid id, and the use of Factory in the Add Phase Button.

Kivy - Referencing IDs of Subclasses (Nested IDs)

I'm creating a kivy app right now. I'm quite new to Kivy and kv lang and there doesn't seam to be much rumour about it although i find it great to seperate code logic and layout, especially after some pygame development.
So to my actuall problem: I have a wiki-style screen for screenmanager, consisting out of a BoxLayout:
Title as Label
Scrollable Label for the Text (later , there shall be displayed a nested kv file)
Buttons for Navigation (scroll up and go back to main screen)
Now I'm recreating the Navigation buttons to be floating type as known from many webpages and apps. Problem is, I suddendly cant reference my ScrollView anymore. Any help or suggestions?
<Wiki>:
name: "wiki"
canvas.before:
Rectangle:
pos: self.pos
size: self.size
source: "picture.jpg"
BoxLayout:
id: box
orientation: "vertical"
padding: 10
spacing: 10
Label:
font_size: 20
size_hint_y: .1
text: root.country_text
ScrollView:
id: scrlvw
BackgroundLabel:
background_color: 220,220,220, 0.5
size_hint_y: None
text_size: self.width, None
height: self.texture_size[1]
halign: "left"
valign: "top"
text: root.wiki_text
FloatLayout:
size_hint_y: .1
Button:
size_hint_x: .2
pos_hint: {"center_x": .25, "center_y": .5}
text: "Back"
on_release:
app.root.current = "main"
root.manager.transition.direction = "right"
FloatButton:
size_hint_x: .2
pos_hint: {"center_x": .75, "center_y": .5}
text: "Up"
on_release:
widget = BoxLayout()
widget.ids.scrlvw.scroll_y = 0
Before, when it worked, it was:
BoxLayout:
FloatLayout:
size_hint_y: .1
Button:
size_hint_x: .2
pos_hint: {"center_x": .25, "center_y": .5}
text: "Back"
on_release:
app.root.current = "main"
root.manager.transition.direction = "right"
Button:
size_hint_x: .2
pos_hint: {"center_x": .75, "center_y": .5}
text: "Up"
on_release:
scrlvw.scroll_y = 0
Well as its just a design question, I guess i temporary have to dismiss the floating design. But I would be so pleased if you could help me understand this language better.
As long as the 'kv' code described as "when it worked" is still in the same <Wiki> rule, it should still work. The newer kv code will never work as you are trying to create a new BoxLayout and reference an id in it. That has two problems:
The newly created BoxLayout is not the BoxLayout instance that appears in your GUI, so any changes to it will have no effect on what appears in the display.
Unless you have a <BoxLayout> rule in your kv, the newly created BoxLayout will not have a scrlvw id.
The ids defined within a kv rule are available for use only within that rule. See the documntation.

How can I position and size my image Button

I am new to kivy, I want to create my basic UI for android application,
and I tried to code down below, I first create my boxlayout to separate my UI into three parts: title part, main part, and icon part,
actually my question is about how to size and position my icon image button
<MyLabel#Label>:
color: .8, .9, 0, 1
font_size: 32
text_size: self.width, None
size_hint_y: None
height: self.texture_size[1]
<MyBoxLayout>:
orientation: 'vertical'
BoxLayout:
size_hint: 1, .1
Label:
text: "Face-Reg"
font_size: 50
color: .8, .9, 0, 1
text_size: self.size
ScrollView:
size_hint: 1, .8
MyLabel:
text: str ('Hello This is My New Project ' * 100)
BoxLayout:
size_hint: 1, .1
Button:
size_hint_x: 0.25
Image:
source: 'icon/server.png'
size:self.texture_size
Button:
size_hint_x: 0.25
Image:
source: 'icon/add.png'
size:self.texture_size
Button:
size_hint_x: 0.25
Image:
source: 'icon/recog.png'
size:self.texture_size
Button:
size_hint_x: 0.25
Image:
source: 'icon/renew.png'
size:self.texture_size
The output screen snapshot is:
1
Four Icon are overlapped together, also the size is not match to the button size
how can I fix that? Thanks
You can adjust the size and pos using those properties of the Image. For example, here is how you might adjust the third Button:
Button:
id: b3
size_hint_x: 0.25
Image:
source: 'icon/recog.png'
allow_stretch: True
keep_ratio: False
size: b3.size
pos: b3.pos
Note the id assigned to the Button. That id is used to assign the same size and position to the Image. The allow_stretch: True property makes the Image stretch to fit the specified size, and the keep_ratio: False allows the image to be stretched unevenly. See the Image documentation for more details.

How do I position widgets and use layouts in Kivy?

This: (https://imgur.com/a/Y9Xwl) (can't format it for some reason) is the user interface I am currently trying to create in Kivy. I am having difficulty recreating this as I do not understand the layout system and I have read a lot of documentation, watched a lot of Youtube videos, tinkered with the code and still I cannot get the desired result. So far this is my code, it has all the widgets that I need within it, they're just not sized/positioned how I want them:
from kivy.app import App
from kivy.uix.label import Label
from kivy.lang import Builder
from kivy.properties import ListProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
class CaloriesScreen(Screen):
pass
class categoriesScreen(Screen):
pass
class loginScreen(Screen):
pass
class registerScreen(Screen):
pass
class shoppingListScreen(Screen):
pass
class theScreenManager(ScreenManager):
pass
root_widget = Builder.load_string('''
#:import FadeTransition kivy.uix.screenmanager.FadeTransition
theScreenManager:
transition: FadeTransition()
CaloriesScreen:
<CaloriesScreen>:
name: 'calories'
BoxLayout:
orientation: 'vertical'
BoxLayout:
orientation: 'horizontal'
size_hint: 1, .3
Button:
text: '<'
size_hint: .1, 1
font_size: 75
background_normal: ""
background_color: 0.18, .5, .92, 1
on_release: app.root.current = 'main'
Label:
text: 'Calories'
halign: 'left'
font_size: 50
canvas.before:
Color:
rgb: 0.18, .5, .92
Rectangle:
pos: self.pos
size: self.size
Widget:
size_hint: .1, 1
canvas.before:
Color:
rgb: 0.18, .5, .92
Rectangle:
pos: self.pos
size: self.size
BoxLayout:
orientation: 'horizontal'
size_hint: 1, .4
canvas.before:
Color:
rgb: 0.8, 0.8, 0.8
Rectangle:
pos: self.pos
size: self.size
Label:
text: 'Recipes'
font_size: 30
color: 0.18, .5, .92, 1
size_hint: 1, 1
Button:
id: btn
text: 'Select a recipe...'
on_release: dropdown.open(self)
height: '48dp'
size_hint: .5, .3
pos: self.x, .3
DropDown:
id: dropdown
on_parent: self.dismiss()
on_select: btn.text = '{}'.format(args[1])
Button:
text: 'First recipe'
size_hint_y: None
height: '48dp'
on_release: dropdown.select('First Item')
Button:
text: 'Second recipe'
size_hint_y: None
height: '48dp'
on_release: dropdown.select('Second Item')
Button:
text: 'Third recipe'
size_hint_y: None
height: '48dp'
on_release: dropdown.select('Third Item')
Button:
text: '+'
font_size: 30
background_normal: ""
background_color: 0.18, .5, .92, 1
#on_release:
BoxLayout:
orientation: 'vertical'
BoxLayout:
orientation: 'horizontal'
Label:
text: 'Food'
font_size: 30
color: 0.18, .5, .92, 1
Label:
text: 'Cal'
font_size: 30
color: 0.18, .5, .92, 1
BoxLayout:
orientation: 'horizontal'
Label:
text: 'Simple Cheese Omelette'
font_size: 30
color: 0.18, .5, .92, 1
Label:
text: '241'
font_size: 30
color: 0.18, .5, .92, 1
BoxLayout:
orientation: 'horizontal'
Label:
text: 'Burger'
font_size: 30
color: 0.18, .5, .92, 1
Label:
text: '295'
font_size: 30
color: 0.18, .5, .92, 1
BoxLayout:
orientation: 'horizontal'
Label:
text: 'Tomato and caper linguine '
font_size: 30
color: 0.18, .5, .92, 1
Label:
text: '393'
font_size: 30
color: 0.18, .5, .92, 1
BoxLayout:
orientation: 'vertical'
canvas.before:
Color:
rgb: 0.8, 0.8, 0.8
Rectangle:
pos: self.pos
size: self.size
BoxLayout:
orientation: 'horizontal'
Label:
text: 'Total Cal'
font_size: 30
color: 0.18, .5, .92, 1
Label:
text: '929'
font_size: 30
color: 0.18, .5, .92, 1
BoxLayout:
orientation: 'horizontal'
Label:
text: 'You are under calories'
font_size: 30
color: 0.18, .5, .92, 1
Button:
text: 'Clear'
font_size: 75
background_normal: ""
background_color: 0.18, .5, .92, 1
#on_release:
''')
class RecipeApp(App):
def build(self):
return root_widget
if __name__ == "__main__":
RecipeApp().run()
This(https://imgur.com/a/zW2z0) (can't format it for some reason) is what the output of that code looks like. The top bar with the "<" button is how I want it and I've only tried to edit the horizontal row of widgets below it. I cannot position the dropdown menu with the 'select your recipe' label how I would like it. I have tried altering it's y-axis multiple times but it always sinks to the botton of the boxlayout. I even tried giving it a new boxlayout just for the dropdown and tried doing: pos: self.parent.x, self.parent.y * 0.5 assuming it would go halfway up the y-axis of it's parent layout (the boxlayout) but still nothing. I am wondering whether it would be better to just use a floatlayout and manually position all the widgets but I am unsure how this will work well when I compile it into an APK for an Android device. What is the best way to go about positioning my widgets on the screen?
Use pos_hint for this.
If pos_hint: {'top': 1}, the top of the widget will hit the roof of the parent box.
So if your widgets height is 30% of its parent box (size_hint: 0.5, 0.3), and you want it to be centered vertically, you want pos_hint: {'top': 0.5 + 0.3/2}, which means the top of the widget will be half way to the roof + half of the widgets height which is 15% of the parent box.
That takes us 65% to the top :)
size_hint: 0.5, 0.3
pos_hint: { 'top' : 0.65}
If the widgets size_hint is dynamic you can do something like this.
pos_hint: {'top': 0.5 + self.size_hint[1]/2}
And lets take your Select recipe button as an example:
Button:
id: btn
text: 'Select a recipe...'
on_release: dropdown.open(self)
height: '48dp'
size_hint: .5, .3
pos_hint: {'top': 0.5 + self.size_hint[1]/2}

Categories

Resources