J. Katzer
J. Katzer

Reputation: 107

Kivy garden graph: axes are drawn but plot is missing

Concerning the following code, which is a subset of my calculation game app, I have two questions:

1) Why is dummy_series not plotted, although the axes (with ticks and labels are drawn)

2) How do I get the quit button to properly exit the app (does it have to remove all widgets from the root widget? Is there an oposite to AppObject.run(), that stops the app? - SOLVED: App.get_running_app().stop()

For the first question, the relevant part of the code is found in the StatisticScreen and the PlotScreen classes. The first one creates the dummy_series, initializes the creation of the graph widget and changes the screen to the PlotScreen. In the PlotScreen class, there is showPlot methods, which is basically copied from the github README.

So far I tried to change the overall background color to white. Both by "canvas before" a white rectangle, and by really changing the background color of the window. Both had no effect (exept the axes and labels were hidden, because they are also white). Then I tried to color the graph differently each time I create it (taken from the same github repo, there is a TestApp in if __name__ == '__main__':). But there is still no graph.

For the second question please consider the changeScreen-method of CalculationRoot. If it is called with quit as the argument, currently it just empties the screen_list and returns False. The idea was to call the callback of the "Back"-Button (key=27,1000). Since closing the App with the "Back"-Button actually works, given the screen_list is empty, I thought I could use this existing process. Also scheduling the keyHandler-method of the app object CalculationApp does not have the desired effect of closing the app.

# Python build-in Modules
import os
import operator                 # better handling of +, #, *, etc.
import webbrowser               # access homepages via the about section
import random                   # create random math questions
import datetime                 # for the timer
import itertools                # eg for cycling colors
from functools import partial   # schedule callback functions that take arguments different from 'dt'

# Kivy
from kivy.lang.builder import Builder
from kivy.app import App
from kivy.core.window import Window
from kivy.utils import platform
from kivy.uix.screenmanager import Screen
from kivy.properties import ObjectProperty, NumericProperty, StringProperty
from kivy.storage.dictstore import DictStore
from kivy.utils import get_color_from_hex as rgb

from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.scrollview import ScrollView

from kivy.clock import Clock
from kivy.uix.label import Label
from kivy.logger import Logger

from kivy.garden.graph import Graph, MeshLinePlot, SmoothLinePlot

# Non-standard python
import numpy as np







###############################################################################
# Constants
###############################################################################

BACKGROUND_COLOR = [0,0,0,0]
TEXT_COLOR = [1,1,1,1]

kv_string = """

#:import Factory kivy.factory.Factory

#:set color_button (0.784, 0.4, 0.216, 1)  # brown
#:set color_button_pressed (0.659, 0.3, 0.431, 1)  # darker brown
#:set color_background_down '(0.4, 0.4, 0.4, 1)'  # purple

<WrappedLabel@Label>:
    size_hint_y: None
    height: self.texture_size[1] + self.texture_size[1]/2
    markup: True

<GridLabel@Label>:
    font_size: min(root.width, root.height) * .3

<StatisticsSpinner@Spinner>:
    background_color: color_button if self.state == 'normal' else color_button_pressed  
    background_down: color_background_down
    option_cls: Factory.get("SpinnerLabel")

<SpinnerLabel@SpinnerOption>:
    background_color: color_button if self.state == 'down' else color_button_pressed
    background_down: color_background_down

<CalculationRoot>:
    orientation: 'vertical'
    cg_screen_manager: cg_screen_manager
    statistic_screen: statistic_screen
    plot_screen: plot_screen

    ScreenManager:
        id: cg_screen_manager
        StartScreen:
            name: 'StartScreen'
        StatisticScreen:
            id: statistic_screen
            name: 'StatisticScreen'
        PlotScreen:
            id: plot_screen
            name: 'PlotScreen'

<StartScreen@Screen>:
    BoxLayout:
        orientation: 'vertical'
        padding: root.width * .02, root.height * .02
        spacing: min(root.width, root.height) * .02

        WrappedLabel:
            text: '[b] Calculation Game [/b]'
            font_size: min(root.width, root.height) * .1

        Button:
            text: 'Statistic'
            on_release: app.root.changeScreen(self.text.lower())

        Button:
            text: 'Quit'           

<StatisticScreen@Screen>:
    stats_operation_spinner: stats_operation_spinner
    stats_difficulty_spinner: stats_difficulty_spinner
    stats_num_questions_button: stats_num_questions_button
    BoxLayout:
        orientation: 'vertical'
        padding: root.width * .02, root.height * .02
        spacing: min(root.width, root.height) * .02

        WrappedLabel:
            text: '[b] Statistics [/b]'
            halign: 'center'
            font_size: min(root.width, root.height) * .1

        GridLayout:
            size_hint: .9,.4
            cols: 2
            pos_hint: {'center_x': .5}
            GridLabel:
                text: 'Operation Type'
            StatisticsSpinner:
                id: stats_operation_spinner
                text: '+'
                values: ['+', '-', '*', ':', '%']
            GridLabel:
                text: 'Difficulty'
            StatisticsSpinner:
                id: stats_difficulty_spinner
                text: '1'
                values: ['1','2','3','4']
            GridLabel:
                text: 'Number of Questions'
            Button:
                id: stats_num_questions_button
                text: '8'
                on_release: app.root.statistic_screen.switchNumQuestions(self, self.text)
                # on_release: self.text = '16' if self.text == '8' else self.text = '8'
                background_color: color_button if self.state == 'normal' else color_button_pressed
        Button:
            size_hint: 1, .2
            text: 'Plot'
            on_release: app.root.statistic_screen.showPlot()
            font_size: min(root.width, root.height) * .1


<PlotScreen@Screen>:

"""



###############################################################################
# Widgets
###############################################################################



class StatisticScreen(Screen):
    """ Selection screen, where you can fix the parameters for 
        a pot of your statistics. 
    """
    def __init__(self, *args, **kwargs):
        super(StatisticScreen, self).__init__(*args, **kwargs)       


    def showPlot(self):
        """ 'onPlotButtonPress'
            callback for the 'plot'-Button on the bottom of StatisticScreen 
        """

        dummy_series = np.random.randint(1, 10, (12,))        
        App.get_running_app().root.ids.plot_screen.createKivyPlot(dummy_series)
        App.get_running_app().root.changeScreen('plot')


    def switchNumQuestions(self, instance, text):
        """ 'onNumQuestionsButtonPress'
            callback for the 'choose number of questions'-button. 
        """
        if int(text) == 16:
            instance.text = '8'
        else:
            instance.text = '16'



class PlotScreen(Screen):
    def __init__(self, *args, **kwargs):        
        super(PlotScreen, self).__init__(*args, **kwargs)  
        self.series = None     
        self.graph_figure = None        # error if uncommented... why? (referred to former name self.canvas)
        self.colors = itertools.cycle([rgb('7dac9f'), rgb('dc7062'), rgb('66a8d4'), rgb('e5b060')])


    def createKivyPlot(self, series=np.array(range(12))):
        graph_theme = {
                'label_options': {
                    'color': rgb('444444'),  # color of tick labels and titles
                    'bold': True},
                'background_color': rgb('f8f8f2'),  # back ground color of canvas
                'tick_color': rgb('808080'),  # ticks and grid
                'border_color': rgb('808080')}  # border drawn around each graph

        self.graph_figure = Graph(xlabel='Last 12 games', ylabel='Average Response Time', x_ticks_minor=1,
        x_ticks_major=5, y_ticks_major=1,
        y_grid_label=True, x_grid_label=True, padding=10,
        x_grid=True, y_grid=True, xmin=0, xmax=len(series), ymin=0, ymax=int(1.5*max(series)), **graph_theme)
        plot = SmoothLinePlot(mode='line_strip', color=next(self.colors))
        plot.points = [(x, series[x]) for x in range(0, len(series))]
        self.graph_figure.add_plot(plot)

        self.add_widget(self.graph_figure)

    def destroy(self): 

        self.remove_widget(self.graph_figure)
        self.graph_figure = None


###############################################################################
# Root Widget
###############################################################################

class CalculationRoot(BoxLayout):
    """ Root of all widgets
    """
    calculation_screen = ObjectProperty(None)
    def __init__(self, *args, **kwargs):
        super(CalculationRoot, self).__init__(*args, **kwargs)
        self.screen_list = [] # previously visited screens


    def changeScreen(self, next_screen):
        if self.screen_list == [] or self.ids.cg_screen_manager.current != self.screen_list[-1]:
            self.screen_list.append(self.ids.cg_screen_manager.current)

        if next_screen == 'start':
            self.ids.cg_screen_manager.current = 'StartScreen'
        elif next_screen == 'statistic':
            self.ids.cg_screen_manager.current = 'StatisticScreen'          
        elif next_screen == 'plot':
            self.ids.cg_screen_manager.current = 'PlotScreen'       
        elif next_screen == 'quit':
            self.screen_list == []
            #self.onBackBtnPress()    # not working
            #Clock.schedule_once(partial(App.get_running_app().keyHandler(27)), 0)    # not working
            return False

    def onBackBtnPress(self):
        if self.screen_list:
            self.ids.cg_screen_manager.current = self.screen_list.pop()
            return True
        return False



###############################################################################
# App Object
###############################################################################

class CalculationApp(App):
    """ App object
    """
    def __init__(self, *args, **kwargs):
        super(CalculationApp, self).__init__(*args, **kwargs)
        self.use_kivy_settings = False


    def keyHandler(self, *args):
        key = args[1]
        print(key)

        if key in (1000, 27):
            return self.root.onBackBtnPress()

    def post_build_init(self, ev):
        if platform == 'android':
            pass

        win = self._app_window
        win.bind(on_keyboard = self.keyHandler)

    def build(self):
        Builder.load_string(kv_string)
        self.bind(on_start=self.post_build_init)
        return CalculationRoot()


if __name__ in ('__main__', '__android__'):
    CalculationApp().run()

Upvotes: 0

Views: 888

Answers (1)

John Anderson
John Anderson

Reputation: 39137

Apparently, there has been a problem with using kivy.garden.graph inside a ScreenManager for some time. According to this issue report, it has been fixed in kivy version v1.10.1.dev0. However, I think you can get around it by adding _with_stencilbuffer=False to your call to Graph().

And to stop the app, you can modify your kv_string in the StartScreen section to include :

    Button:
        text: 'Quit'
        on_release: app.stop() 

Upvotes: 1

Related Questions