Methodology: How to display data from a dictionary-object, so that it is updated?

884 views
Skip to first unread message

Der Achim

unread,
Jul 30, 2015, 4:52:50 AM7/30/15
to Kivy users support
Hi,

I have a huge data object that stores weather data.

For displaying that data, I use a GridLayout where the memebers Labls, which are set to the weather data. Excerpt:
            Label:
                text: str(root.forecast.hourly[0]["temp"])
            Label:
                text: str(root.forecast.hourly[1]["temp"])

This seemed a pretty neat approach to me. Of course, it does not update the display, if the underlaying data in the forecast-object is changing.
As far as I understand, it is not 'bound'.

What would be a good way to implement this? Use a huge number of NumericProperty to store that data? But then, I like data that belongs togeather to stay together (in the forecast-structure).

Any hints are highly welcome. I think I still have big difficulties understanding how Pyhton+Kivy work...

Thanks a lot!

Achim

Der Achim

unread,
Jul 31, 2015, 1:29:02 PM7/31/15
to Kivy users support, uj69...@arcor.de
Here is a stripped down version of the code in question (it's runnable):

#!/usr/bin/env python
# -*- coding: latin-1 -*-
#

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.clock import Clock

kv_string
= '''
<DisplayDailyTemperatureForecast>:
    GridLayout:
        rows: 1
        size: min(root.size[0], "500 dp"), "100 dp"
        spacing: ["5 dp", 0]

        Label:
            text: "Temperatur"
            size_hint_x: None
            width: "100 dp"

        Label:
            text: str(root.forecast.hourly[0]["temp"])
        Label:
            text: str(root.forecast.hourly[1]["temp"])
        Label:
            text: str(root.forecast.hourly[2]["temp"])
        Label:
            text: str(root.forecast.hourly[3]["temp"])
        Label:
            text: str(root.forecast.hourly[4]["temp"])
        Label:
            text: str(root.forecast.hourly[5]["temp"])
'''

Builder.load_string(kv_string)

class TemperatureForecast(object):
   
"""Retrieve data from weather forecast service and store it internally.
   
    Data format:
    .hourly
        [0 .. ]
            "
hour"    (time of forecast)
            "
temp"    (temperature)
            "
pop"    (probabilitiy of precipitation)
    """

   
   
def __init__(self, **kwargs):        
       
"""Create instance variables. """
       
self.hourly = []
       
self.target_hours = [6, 9, 12, 15, 18, 21]


   
def initData(self):
       
# define time points where we want information. if the lists are still empty, initialize them to a list of silly numbers:
       
while len(self.hourly) < len(self.target_hours):
           
self.hourly.append(None)
           
self.hourly[len(self.hourly) - 1] = {
               
"hour": self.target_hours[len(self.hourly) - 1],
               
"temp": -99,
               
"pop": -98 }


class DisplayDailyTemperatureForecast(Widget):
   
""" Retrieve forecast and display it in a gridlayout """
    forecast
= TemperatureForecast()
    forecast
.initData()
    forecast
.hourly[0]["temp"] = 1968    # works
   
   
def updateForecast(self, dt):
       
print "old value is: ", self.forecast.hourly[0]["temp"]
       
self.forecast.hourly[0]["temp"] = 2015            # does NOT change displayed number...
       
print "new value is: ", self.forecast.hourly[0]["temp"]


class DisplayApp(App):
   
def build(self):
       
self.theWeather = DisplayDailyTemperatureForecast()
       
Clock.schedule_once(self.theWeather.updateForecast, 4)
       
return self.theWeather

if __name__ == '__main__':
   
DisplayApp().run()



The TemperatureForecast class generates a class variable 'hourly', which is an array of dicts.This is assigned to 'forecast', a variable of the display class DisplayDailyTemperatureForecast.
The latter uses a GridView to display the variable 'forecast'. It accesses its members directly using a syntax like: root.forecast.hourly[0]["temp"]
In the kv this looks like:

        Label:
            text: str(root.forecast.hourly[0]["temp"])

To me, this is a nice approach with little overhead. Of course, if something changes in the underlaying data class, those changes have to be made in the display class as well.

But now, have a look at the updateForecast() methode of the display class. It manipulates the data of the 'forecast' variable. But this is not reflected in the GridViews's display! Why, isn't it bound??

So, what to do to get the display updated?
And, as a more general question, is this a good design? What do you think?

Thanks for your opinion!
Achim

Karla Muguerza

unread,
Jul 31, 2015, 10:05:23 PM7/31/15
to Kivy users support, uj69...@arcor.de
Hi!

I think I would use a different approach, but considering your code I would do the following:

1. KV. Bind the text label to a StringProperty in your main. 

Label:
text: root.text_label

class DisplayDailyTemperatureForecast(Widget):
text_label = StringProperty("temp")


2. In your main updateForecast. update the text label value. This will automatically update your label.

def updateForecast(self, dt):
self.text_label = str(self.forecast.hourly[0]["temp"])


Message has been deleted
Message has been deleted

Der Achim

unread,
Aug 1, 2015, 9:00:48 AM8/1/15
to Kivy users support, uj69...@arcor.de, over....@gmail.com
Duplicating all data from a data structure into kivy's XxxProperty is a possibility, albeit not a very nice one.
This creates a lot of redundend data.

The complete structure in question looks like this:
forecast
   
.hourly
       
[0 .. ]

           
"hour"    (time of forecast)
           
"temp"    (temperature)
           
"pop"    (probabilitiy of precipitation)

   
.daily
       
[0 .. ]
           
"day"    (day of forecast)
           
"temp_low"
           
"temp_high"
           
"pop"     (probabilitiy of precipitation)
           
"conditions"    (human readable description)


So to kivy-fy this, I would need to copy each data member into a kivy property:

forecast_hourly_0_hour = NumericProperty(forecast.hourly[0]["hour"])
forecast_hourly_1_hour = NumericProperty(forecast.hourly[1]["hour"])
forecast_hourly_2_hour = NumericProperty(forecast.hourly[2]["hour"])
forecast_hourly_3_hour = NumericProperty(forecast.hourly[3]["hour"])
forecast_hourly_4_hour = NumericProperty(forecast.hourly[4]["hour"])
forecast_hourly_5_hour = NumericProperty(forecast.hourly[5]["hour"])

forecast_hourly_0_temp = NumericProperty(forecast.hourly[0]["temp"])
forecast_hourly_1_temp = NumericProperty(forecast.hourly[1]["temp"])
forecast_hourly_2_temp = NumericProperty(forecast.hourly[2]["temp"])
forecast_hourly_3_temp = NumericProperty(forecast.hourly[3]["temp"])
forecast_hourly_4_temp = NumericProperty(forecast.hourly[4]["temp"])
forecast_hourly_5_temp = NumericProperty(forecast.hourly[5]["temp"])

forecast_hourly_0_pop = NumericProperty(forecast.hourly[0]["pop"])
forecast_hourly_1_pop = NumericProperty(forecast.hourly[1]["pop"])
forecast_hourly_2_pop = NumericProperty(forecast.hourly[2]["pop"])
forecast_hourly_3_pop = NumericProperty(forecast.hourly[3]["pop"])
forecast_hourly_4_pop = NumericProperty(forecast.hourly[4]["pop"])
forecast_hourly_5_pop = NumericProperty(forecast.hourly[5]["pop"])


In my opinion, this destroys one of kivy's biggest advantages, the direct binding of a grafical element to its underlying data: the real data is in the structure, the displayed data is in the kivy properties.

JSON is a widly used data format in the web and Python's support of it is incredible. But then you get a big data structure. And how to get this data structure properly displayed without bulldozing it completely into single NumericProperties?


ZenCODE

unread,
Aug 1, 2015, 9:01:00 AM8/1/15
to Kivy users support
Another option is to use ids in the kv file.

#!/usr/bin/env python
# -*- coding: latin-1 -*-
#

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.clock import Clock

kv_string
=
'''

<DisplayDailyTemperatureForecast>:
    GridLayout:
        rows: 1
        size: min(root.size[0], "500 dp"), "100 dp"
        spacing: ["5 dp", 0]

        Label:
            text: "Temperatur"
            size_hint_x: None
            width: "100 dp"

        Label:
            id: temp0

            text: str(root.forecast.hourly[0]["temp"])
        Label:
            text: str(root.forecast.hourly[1]["temp"])
        Label:
            text: str(root.forecast.hourly[2]["temp"])
        Label:
            text: str(root.forecast.hourly[3]["temp"])
        Label:
            text: str(root.forecast.hourly[4]["temp"])
        Label:

            text: str(root.forecast.hourly[5]["temp"])
'''

Builder.load_string(kv_string)

class TemperatureForecast(object):
   
"""Retrieve data from weather forecast service and store it internally.
   
    Data format:
    .hourly
        [0 .. ]
            "
hour"    (time of forecast)
            "
temp"    (temperature)
            "
pop"    (probabilitiy of precipitation)
    """

   
   
def __init__(self, **kwargs):        
       
"""Create instance variables. """

       
self.hourly = []
       
self.target_hours = [6, 9, 12, 15, 18, 21]


   
def initData(self):
       
# define time points where we want information. if the lists are still empty, initialize them to a list of silly numbers:
       
while len(self.hourly) < len(self.target_hours):
           
self.hourly.append(None)
           
self.hourly[len(self.hourly) - 1] = {
               
"hour": self.target_hours[len(self.hourly) - 1],
               
"temp": -99,
               
"pop": -98 }


class DisplayDailyTemperatureForecast(Widget):
   
""" Retrieve forecast and display it in a gridlayout """
    forecast
= TemperatureForecast()
    forecast
.initData()
    forecast
.hourly[0]["temp"] = 1968    # works
   
   
def updateForecast(self, dt):
       
print "old value is: ", self.forecast.hourly[0]["temp"]
       
self.forecast.hourly[0]["temp"] = 2015            # does NOT change displayed number...

       
self.ids['temp0'].text = str(2015)

Der Achim

unread,
Aug 1, 2015, 9:14:32 AM8/1/15
to Kivy users support
ZenCode, you are right, but then this is a hack: The displayed data is not updated because the unterlying data changed but because I inserted a special command to override the displayed data...
Maybe I should only work with ids:
(in the kv:)

Label:
    id
: label_1
Label:
    id
: label_2
...

(and in the code:)

self.ids['label_0'].text = str(root.forecast.hourly[0]["temp"])
self.ids['label_1].text = str(root.forecast.hourly[1]["temp"])
...


Of course, I have to do this assignment again every time something in the data structure changes.
This should have been kivy's task!
Thanks for your hint.
Achim

Der Achim

unread,
Aug 1, 2015, 9:31:57 AM8/1/15
to Kivy users support
ZenCode's approach works, but is this really the desired way in kivy? (Maybe I am just spoiled by model-view-controller thinking, but this has served me good so far...)
A runnable example is in the attached file. Each time, something changes in the forecast-structure, updateDisplay() hast to be called which writes the data into the Label's ids.

   
def updateDisplay(self):
       
self.ids['label_forecast_hourly_0_temp'].text = str(self.forecast.hourly[0]["temp"])
       
self.ids['label_forecast_hourly_1_temp'].text = str(self.forecast.hourly[1]["temp"])
       
self.ids['label_forecast_hourly_2_temp'].text = str(self.forecast.hourly[2]["temp"])
       
self.ids['label_forecast_hourly_3_temp'].text = str(self.forecast.hourly[3]["temp"])
       
self.ids['label_forecast_hourly_4_temp'].text = str(self.forecast.hourly[4]["temp"])
       
self.ids['label_forecast_hourly_5_temp'].text = str(self.forecast.hourly[5]["temp"])
       
self.ids['label_forecast_hourly_0_pop'].text = str(self.forecast.hourly[0]["pop"])
       
self.ids['label_forecast_hourly_1_pop'].text = str(self.forecast.hourly[1]["pop"])
       
self.ids['label_forecast_hourly_2_pop'].text = str(self.forecast.hourly[2]["pop"])
       
self.ids['label_forecast_hourly_3_pop'].text = str(self.forecast.hourly[3]["pop"])
       
self.ids['label_forecast_hourly_4_pop'].text = str(self.forecast.hourly[4]["pop"])
       
self.ids['label_forecast_hourly_5_pop'].text = str(self.forecast.hourly[5]["pop"])


Achim
main.py

ZenCODE

unread,
Aug 1, 2015, 9:55:33 AM8/1/15
to Kivy users support
Ooh, that not so pretty or efficient. :-) The idea is that the index and name can map to the id. So, instead of updating all of them, say item 3 of the temp changed, then you know the widgets is 'label_forecast_hourly_3_temp', ie.

   self.ids['label_forecast_hourly_{0}_temp'.format(index)].text = self.forecast.hourly[index]["temp"]

So, the name of the dictionary item changed gives you the name of the widget. It's a one-to-one mapping. Does that make sense? If not sure how you dictionary is changed....

Cheers

ZenCODE

unread,
Aug 1, 2015, 9:58:50 AM8/1/15
to Kivy users support
If you really must update all, prefer something like:

    for x in range(0, 6):
       
self.ids['label_forecast_hourly_{0}_temp'.format(x)].text = str(self.forecast.hourly[x]["temp"])
       
self.ids['label_forecast_hourly_{0}_pop'.format(x)].text = str(self.forecast.hourly[x]["pop"])



Der Achim

unread,
Aug 2, 2015, 1:06:32 PM8/2/15
to Kivy users support
Since I do not know, which data members have changed, I have to update everything. Thanks for your hint about the .format(), that makes this horrible piece of code at least visually less appalling...

    def updateDisplay(self):
       
for i in range(0, len(self.forecast.hourly)):
           
self.ids['label_forecast_hourly_{}_temp'.format(i)].text = str(self.forecast.hourly[i]["temp"])
           
self.ids['label_forecast_hourly_{}_pop'.format(i)].text = str(self.forecast.hourly[i]["pop"])


But then again, someone has some good thoughts about model-view-controller in kivy???

Achim
Reply all
Reply to author
Forward
0 new messages