I'd like to create a custom kivy widget that plots data in response to user input or live location data. I need the current dimensions and position of the widget to draw the line, but I can't seem to get those correctly, and I can't trigger a line update either with a button click for example.
Here is a sample code, when button is clicked, the GraphWidget should plot the line.
import numpy as np
from kivy.app import App
from kivy.properties import ObjectProperty, NumericProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
class BoxLayoutMain(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.gw = GraphWidget()
def plot_line(self):
# called when user clicks button
data = np.asarray([[0,0],[100,100],[200,50]]) # test data
self.gw.plot(data, min_x=0, max_x=200, min_y=0, max_y=150)
class GraphWidget(Widget):
trace_points = ObjectProperty((0, 0, 0, 0))
graph_w = NumericProperty(100)
graph_h = NumericProperty(100)
graph_x0 = NumericProperty(0)
graph_y0 = NumericProperty(0)
def on_size(self, *args):
# dimensions are correct here:
self.graph_w = self.width
self.graph_h = self.height
self.graph_x0 = self.pos[0]
self.graph_y0 = self.pos[1]
self.test()
def test(self):
# dimensions are also correct here:
print('W ', self.graph_w)
print('H ', self.graph_h)
print('X ', self.graph_x0)
print('Y ', self.graph_y0)
# a test line is drawn when triggered by on_size method
self.trace_points = [400, 0, 600, 500]
def plot(self,data_xy, min_x, max_x, min_y, max_y):
# called when user clicks button
# dimensions are not correct here:
print('W ', self.graph_w)
print('H ', self.graph_h)
print('X ', self.graph_x0)
print('Y ', self.graph_y0)
# transforming data points to pixels, needs correct widget dimensions/position:
xf = self.graph_w / (max_x - min_x)
yf = self.graph_h / (max_y - min_y)
X = (data_xy[:, 0] - min_x) * xf + self.graph_x0
Y = (data_xy[:, 1] - min_y) * yf + self.graph_y0
nps = np.column_stack((X, Y))
# line is not drawn/updated
self.trace_points = list(nps.flatten().astype(int))
class GraphTestApp(App):
pass
GraphTestApp().run()
The kv file:
BoxLayoutMain:
<BoxLayoutMain>:
orientation: 'horizontal'
Button:
text: 'Click'
on_press: root.plot_line()
GraphWidget:
<GraphWidget>:
canvas:
Color:
rgb: 1,0,0
Line:
width: 2
points: self.trace_points
Thanks!
The problem is that the line:
self.gw = GraphWidget()
in the __init__()
mthod of BoxLayoutMain
is creating a new instance of GraphWidget
, and that instance is not in your GUI. So any reference to self.gw
is not relevant to the GraphWidget
in your GUI. You just need to get a reference to the correct instance of GraphWidget
. One way to do that is by using ObjectProperty
and ids
. You can modify your BoxLayoutMain
to have an ObjectProperty
named gw
:
class BoxLayoutMain(BoxLayout):
gw = ObjectProperty(None)
# def __init__(self, **kwargs):
# super().__init__(**kwargs)
# self.gw = GraphWidget()
And the __init__()
method is no longer needed. Then add the ids
to your kv
:
BoxLayoutMain:
<BoxLayoutMain>:
gw: gw # set the gw ObjectProperty to the widget with id of gw
orientation: 'horizontal'
Button:
text: 'Click'
on_press: root.plot_line()
GraphWidget:
id: gw # id used above to get a reference to this GraphWidget
<GraphWidget>:
canvas:
Color:
rgb: 1,0,0
Line:
width: 2
points: self.trace_points