Kivy - Circle Drawing
In this chapter, we shall dynamically draw a circle on a Kivy application window. The idea is to set the value of width, height, and the center point of circle with four Kivy slider widgets and refresh the size and the position on a box layout canvas.
The "kivy.graphics" module includes the Ellipse class. A circle is actually an ellipse with equal width and height.
Syntax
The syntax to draw an ellipse on the canvas of any widget is −
with widget.canvas:
Color(1, 1, 1)
Ellipse(
pos=(w, h), size=(cx, cy),
angle_start=0, angle_end=360
)
Sliders are to be used to select the values of "w", "h", "cx" and "cy". The intended appearance of our application window is as follows −
The main layout uses a vertical box layout. It includes two horizontal box, each housing two sliders and two labels. In the first, width and height selectors are placed; and in the second, X and Y coordinates of the ellipse can be selected. The top vertical box then adds a FloatLayout, whose canvas is the target for drawing.
The following "kv" file assembles the above widget structure.
Example
BoxLayout:
orientation: 'vertical'
BoxLayout:
size_hint_y: None
height: sp(50)
BoxLayout:
orientation: 'horizontal'
Slider:
id: wd
min: 100
max: 300
value: 200
Label:
text: 'Width: {}'.format(int(wd.value))
Slider:
id: ht
min: 100
max: 300
value: 200
Label:
text: 'Height: {}'.format(int(ht.value))
BoxLayout:
size_hint_y: None
height: sp(50)
BoxLayout:
orientation: 'horizontal'
Slider:
id: cx
min: 10
max: 600
value: 360
Label:
text: 'cx: {}'.format(int(cx.value))
Slider:
id: cy
min: 10
max: 300
value: 50
Label:
text: 'cy: {}'.format(int(cy.value))
FloatLayout:
canvas:
Color:
rgb: 1, 1, 1
Ellipse:
pos: cx.value, cy.value
size: wd.value, ht.value
angle_start: 0
angle_end: 360
All you need to do is load this "kv" file in our Kivy App code. Call the load_file() method of "kivy.lang.Builder" class inside the build() method.
from kivy.app import App
from kivy.lang import Builder
from kivy.core.window import Window
Window.size = (720,400)
class CircleApp(App):
def build(self):
return Builder.load_file('circledemo.kv')
CircleApp().run()
That's it. Run the program, and you will get the circle drawn at the starting position. Slide the controls for different values and see the circle changing its position and size.
If you wish to use pure Python code to compose the application window appearance, you can do so by manually placing the required widgets inside the build() method as follows −
def build(self):
# main layout
lo = BoxLayout(orientation='vertical', size=Window.size)
# for width and height sliders
sliders_wh = BoxLayout(size_hint_y=None, height=50)
slb1 = BoxLayout(orientation='horizontal')
self.sl1 = Slider(min=100, max=300, value=200)
l1 = Label(text='Width: {}'.format(int(self.sl1.value)))
slb1.add_widget(self.sl1)
slb1.add_widget(l1)
self.sl2 = Slider(min=100, max=300, value=200)
l2 = Label(text='Height: {}'.format(int(self.sl2.value)))
slb1.add_widget(self.sl2)
slb1.add_widget(l2)
sliders_wh.add_widget(slb1)
# for cx and cy sliders
sliders_xy = BoxLayout(size_hint_y=None, height=50)
slb2 = BoxLayout(orientation='horizontal')
self.sl3 = Slider(min=10, max=600, value=360)
l3 = Label(text='cx: {}'.format(int(self.sl3.value)))
slb2.add_widget(self.sl3)
slb2.add_widget(l3)
self.sl4 = Slider(min=10, max=300, value=50)
l4 = Label(text='cy: {}'.format(int(self.sl4.value)))
slb2.add_widget(self.sl4)
slb2.add_widget(l4)
sliders_xy.add_widget(slb2)
lo.add_widget(sliders_wh)
lo.add_widget(sliders_xy)
self.flo = FloatLayout() # circle canvas
lo.add_widget(self.flo)
# redraw cicle
self.ev = Clock.schedule_interval(self.callback, .3)
return lo
However, to keep on refreshing the shape and position of circle on the canvas, we need to schedule a periodic event, clear the canvas to wipe out the existing circle and redraw it with instantaneous values of width, height, cx and cy.
The following callback method has to be added in the App class −
def callback(self, dt):
self.flo.canvas.clear()
with self.flo.canvas:
Color(1, 1, 1)
Ellipse(
pos=(self.sl3.value, self.sl4.value),
size=(self.sl1.value, self.sl2.value),
angle_start=0, angle_end=360
)
You can now run the code. Exactly the same result will be obtained as with the "kv" file version.