Update a line segment in python kivy after a fixed time - python

I am trying to create a line and update is after a fixed interval of time (say 5 seconds). I wrote the code below but it does not update the line. Can anyone help me figure out who to do it?
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.graphics import Color, Ellipse, Line
import time
from kivy.clock import Clock
class MyWidget(Widget):
def my_callback(dt,ds):
Line(points=[100, 100, 200, 100, 100, 200], width=10)
pass
def __init__(self, **kwargs):
super(MyWidget, self).__init__(**kwargs)
with self.canvas:
self.line = Line(points=[100, 100, 200, 100, 100, 200], width=1)
self.line.width = 2
Clock.schedule_once(self.my_callback, 5)
pass
# add your instruction for main canvas here
with self.canvas.before:
pass
# you can use this to add instructions rendered before
with self.canvas.after:
pass
# you can use this to add instructions rendered after
class LineExtendedApp(App):
def build(self):
root = GridLayout(cols=2, padding=50, spacing=50)
root.add_widget(MyWidget())
return root
if __name__ == '__main__':
LineExtendedApp().run()

my_callback is not actually called within the with statement but rather 5s later when that is long gone, and even it if was it wouldn't do what you want - it would draw a new Line, not modify the existing one.
Instead you can change my_callback to do:
self.line.points = [100, 100, 200, 100, 100, 200]
self.line.width = 10
This will modify the existing line as you want. You can also take the clock scheduling out of the with statement but its position doesn't actually matter as long as it stays in the __init__

Related

Centering Tesselator Shape in Kivy

Well I am using an 'experimental' feature in kivy called Tesselator (https://kivy.org/doc/stable/api-kivy.graphics.tesselator.html). Currently my app draws a wee shape in the bottom left. It doesn't use a .kv file.
I would like to know the best way to make the shape remain centred on the screen as you change the window's size? I have seen how to do this with the basic shapes, but I can't figure out how to do this with a tessellated shape.
Any suggestions and advice is much appreciated! The code is below.
import kivy
from kivy.app import App
from kivy.app import App
from kivy.graphics import Mesh
from kivy.graphics.tesselator import Tesselator
from kivy.uix.floatlayout import FloatLayout
class MapBackground(FloatLayout):
def __init__(self, **kwargs):
super(MapBackground, self).__init__(**kwargs)
self.shapes = [
[100, 100, 300, 100, 300, 300, 100, 300],
[150, 150, 250, 150, 250, 250, 150, 250]
]
self.draw()
# draws the map shape
def draw(self):
# make the tesselator object
tess = Tesselator()
# add all the shapes
for shape in self.shapes:
if len(shape) >= 3:
tess.add_contour(shape)
# call the tesselate method to compute the points
if not tess.tesselate(): # returns false if doesn't work
print('tesselator did\'t work:(')
return
# clear canvas
self.canvas.clear()
# draw shapes
for vertices, indices in tess.meshes:
self.canvas.add(Mesh(
vertices=vertices,
indices=indices,
mode='triangle_fan'
))
class TimmApp(App):
def build(self):
self.title = 'The Interactive Museum Map'
return MapBackground()
if __name__ == '__main__':
TimmApp().run()
I did what I wanted by making the widget the shape was drawn in to be inside a float layout. Then (using the kv file seen below) I binded a method called draw() to the event on_size. This meant that the shape was redrawn whenever the window changed shape. I won't show the code for draw() because it's a lot.
#:kivy 1.11.1
FloatLayout:
MapBackground:
on_size: self.draw()

How To Update Canvas Every Second?

I am trying to create a clock with 3 arcs, one for hours, minutes, and seconds. The app currently draws each of the arcs, with different colors and radii as expected. The arcs also fill according to the time at application launch. The red arc represents hours, the green arc represents minutes, and the blue arc represents seconds. From here, I am trying to update the canvas so that the arcs change according to the time (once every second). I would also like a label that displays the time in the center of the clock. Can anyone help me with this problem? I am relatively new to Kivy so I'm not really sure what I should be doing.
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Color, Line
from kivy.uix.floatlayout import FloatLayout
from kivy.clock import Clock
from datetime import datetime
# update canvas every second
# display time in center of clock
class drawClock(FloatLayout):
def __init__(self, **kwargs):
super(drawClock, self).__init__(**kwargs)
self.bind(pos=self.updateClock)
self.bind(size=self.updateClock)
def updateClock(self, *kwargs):
with self.canvas:
now = datetime.now()
hour = now.hour
minute = now.minute
second = now.second
hourAngle = (hour%12) * 30
minuteAngle = minute * 6
secondAngle = second * 6
Line(circle = (self.center_x, self.center_y, 80, 0, hourAngle), width = 2, color = Color(1,0,0))
Line(circle = (self.center_x, self.center_y, 65, 0, minuteAngle), width = 2, color = Color(0,1,0))
Line(circle = (self.center_x, self.center_y, 50, 0, secondAngle), width = 2, color = Color(0,0,1))
class mainApp(App):
def build(self):
return drawClock()
if __name__ == '__main__':
mainApp().run()
You have to use the Clock.schedule_interval() to update the painting, but instead of creating new Lines you must reuse them so you save resources:
from kivy.app import App
from kivy.graphics import Color, Line
from kivy.uix.floatlayout import FloatLayout
from kivy.clock import Clock
from datetime import datetime
class drawClock(FloatLayout):
def __init__(self, **kwargs):
super(drawClock, self).__init__(**kwargs)
self.bind(pos=self.updateClock)
self.bind(size=self.updateClock)
Clock.schedule_interval(self.updateClock, 1)
self.create_canvas()
def create_canvas(self):
with self.canvas:
self.hour_line = Line(width = 2, color = Color(1,0,0))
self.minute_line = Line(width = 2, color = Color(0,1,0))
self.second_line = Line(width = 2, color = Color(0,0,1))
def updateClock(self, *kwargs):
now = datetime.now()
hour = now.hour
minute = now.minute
second = now.second
hourAngle = (hour%12) * 30
minuteAngle = minute * 6
secondAngle = second * 6
self.hour_line.circle = self.center_x, self.center_y, 80, 0, hourAngle
self.minute_line.circle = self.center_x, self.center_y, 65, 0, minuteAngle
self.second_line.circle = self.center_x, self.center_y, 50, 0, secondAngle
class mainApp(App):
def build(self):
return drawClock()
if __name__ == '__main__':
mainApp().run()
Use the update() method. This is a crude way, but it will give you what you want. The update method basically updates the window.
A more efficient way to do it is use the after() method.
The after basically tells what to do after, so figure it out.
window.after(delay_ms, callback=None, *args)
Those are the paramters for after().
For example, this would be to wait one second then carry out func up
window.after(1000, up)

Python Kivy Plots with MeshLinePlot confusion

I am trying to implement real time plots for measurements to a Kivy application, but I do not understand the inner workings of the kivy garden library.
My goal that I want to achieve: I want to have multiple plots inside a ScrollView such that I can add multiple real time plots programmatically from a dictionary and scroll through them if they occupy more space than one screen height. The main problem I have is that the Graph does not behave like an ordinary Widget but rather behaves like a canvas. I already tried to implement this with matplotlib as backend_kivyagg, but I failed to have fixed size for every subplot that I created.
There are multiple things that I do not understand why they happen like they happen.
from math import sin, cos
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.scrollview import ScrollView
from kivy.uix.widget import Widget
from kivy.garden.graph import Graph, MeshLinePlot
class Plot(Widget):
def __init__(self):
super(Plot, self).__init__()
self.graph = Graph(xlabel="x", ylabel="y", x_ticks_minor=5, x_ticks_major=25, y_ticks_major=1,
y_grid_label=True, x_grid_label=True, x_grid=True, y_grid=True,
xmin=-0, xmax=100, ymin=-1, ymax=1, draw_border=False)
# graph.size = (1200, 400)
# self.graph.pos = self.center
self.plot = MeshLinePlot(color=[1, 1, 1, 1])
self.plot.points = [(x, sin(x / 10.)) for x in range(0, 101)]
self.plot2 = MeshLinePlot(color=[1, 0, 0, 1])
self.plot2.points = [(x, cos(x / 10.)) for x in range(0, 101)]
self.add_widget(self.graph)
self.graph.add_plot(self.plot)
self.graph.add_plot(self.plot2)
class GraphLayoutApp(App):
def build(self):
scroll_view = ScrollView()
grid_layout = GridLayout(cols=1, row_force_default=True, padding=20, spacing=20)
graph = Plot()
graph2 = Plot()
label = Label(text="Hello World!")
label2 = Label(text="Hello World!")
grid_layout.add_widget(label)
grid_layout.add_widget(graph)
grid_layout.add_widget(label2)
grid_layout.add_widget(graph2)
scroll_view.add_widget(grid_layout)
return scroll_view
if __name__ == '__main__':
GraphLayoutApp().run()
Questions:
Inside the class GraphLayoutApp I create two objects graph and graph2 but if I add them to the GridLayout() only one does appear? How
can I add multiple graphs?
Also Inside the build method of the class GraphLayoutApp I create two lables. I wanted to first display the first label then the graph
and then the second label label2. But it seems to me that the graph
is always displayed in the lower left corner. I guess this has to do
with the canvas on which it is drawn, but I cannot solve it.
Here is a modified version with some things fixed:
The issue here is that you made your Plot inherit from Widget, which really the most basic widget in the framework, and as it's not a layout, doesn't manage anything in the children you add to it, so the Graph widget you added to it was left at the default size/position (respectively [100, 100] and [0, 0]), and so they stacked on each others, i used RelativeLayout, which sets children by default to its own size and position, so the Graph follows the widget. Another solution is to simply make Plot inherit from Graph, since it's the only child, and to use "self.add_plot" instead of "self.graph.add_plot", and to override the Graph parameters, the best solution is probably to create a KV rule for the Plot class. But the first solution was the minimal change from your code.
The general principle you missed is that in kivy, widgets are by default not constrained to any position/size, unless their parents are layouts that specifically manage that (like GridLayout did for your labels).
I also made the GridLayout automatically size itself to the minimum_size (determined by the hardcoded size i put in all widgets), so you actually have something to scroll on.
This code is also very python-driven, in kivy, you usually want to do more things using KV, rather than using add_widget for static things.
from math import sin, cos
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.scrollview import ScrollView
from kivy.uix.widget import Widget
from kivy.uix.relativelayout import RelativeLayout
from kivy_garden.graph import Graph, MeshLinePlot
class Plot(RelativeLayout):
def __init__(self, **kwargs):
super(Plot, self).__init__(**kwargs)
self.graph = Graph(xlabel="x", ylabel="y", x_ticks_minor=5, x_ticks_major=25, y_ticks_major=1,
y_grid_label=True, x_grid_label=True, x_grid=True, y_grid=True,
xmin=-0, xmax=100, ymin=-1, ymax=1, draw_border=False)
# graph.size = (1200, 400)
# self.graph.pos = self.center
self.plot = MeshLinePlot(color=[1, 1, 1, 1])
self.plot.points = [(x, sin(x / 10.)) for x in range(0, 101)]
self.plot2 = MeshLinePlot(color=[1, 0, 0, 1])
self.plot2.points = [(x, cos(x / 10.)) for x in range(0, 101)]
self.add_widget(self.graph)
self.graph.add_plot(self.plot)
self.graph.add_plot(self.plot2)
class GraphLayoutApp(App):
def build(self):
scroll_view = ScrollView()
grid_layout = GridLayout(cols=1, padding=20, spacing=20, size_hint_y=None)
grid_layout.bind(minimum_size=grid_layout.setter('size'))
graph = Plot(size_hint_y=None, height=500)
graph2 = Plot(size_hint_y=None, height=500)
label = Label(text="Hello World!", size_hint_y=None)
label2 = Label(text="Hello World!", size_hint_y=None)
grid_layout.add_widget(label)
grid_layout.add_widget(graph)
grid_layout.add_widget(label2)
grid_layout.add_widget(graph2)
scroll_view.add_widget(grid_layout)
# return grid_layout
return scroll_view
if __name__ == '__main__':
GraphLayoutApp().run()

Large number of Unicode characters not rendering correctly in a kivy TextInput

Question: How can I get all Unicode characters to render correctly in a TextInput using Kivy?
More details below
I'm generating random Unicode characters in a range between 0x0200 and 0x9990 which is massive the issue is that a large portion of the characters will not render correctly in a TextInput to be more specific less than half will work.
Whatever doesn't render ends up looking like a small rectangle with an x through it, yet when I copy and paste it into another display source it works fine. I've run the code through idle and it displays fine as well the issue seems to be with kivy, any suggestions as to why this is happening?
import random
import kivy
from kivy.uix.textinput import TextInput
from kivy.core.window import Window
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.app import App
kivy.require('1.9.1')
class testclass(object):
def example(self, event):
k_len = list()
complete = ''
for i in range(32):
k_len.append(random.randint(0x0200, 0x9990))
for i in k_len:
if i != 32:
complete += chr(i)
result.text = complete
t = testclass()
Root = Widget(size = (600, 200))
buttonOne = Button(text = 'click me', pos = (1,170), size = (120,30))
buttonOne.bind(on_press = t.example)
result = TextInput(hint_text = 'Output: ', size = (600, 50), pos = (0, 0), multiline = (True))
Root.add_widget(buttonOne)
Root.add_widget(result)
class testappApp(App):
def build(self):
return Root
Window.size = (600, 200)
if __name__ == '__main__':
testappApp().run()
This code will only work if you have kivy setup, you can tweak it to work in idle but as I stated the code works as intended it's just not displaying correctly within kivy :)
Your font does not seem to support these characters - switch to another one with support for that range (see https://en.wikipedia.org/wiki/Unicode_block for more info on what needs to be there)

Animate canvas Line on Kivy

I have an array with x and y positions. I want to show these points linking a line successively for each point, then, creating an animation. It is just like a path tracking whose trail is the line. I'm using python-kivy to try to display it.
I couldn't find any help on google.
There's a button that triggers this animation.
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics.vertex_instructions import Line
from kivy.graphics.context_instructions import Color
from time import sleep
x_moves = [250, 250.4305, 249.8804, 246.0923, 239.7496, 233.8188, 225.7797, 215.8385, 205.8413, 196.6497, 189.7026, 181.2445, 174.9816, 171.9882, 166.1171, 161.6505, 159.9929, 161.1338, 164.853, 168.2874, 170.768, 178.6918, 184.5233, 190.0262, 195.607, 202.0255, 210.5954, 216.1031, 219.6285, 224.9134, 230.2314, 237.7017, 243.7408, 250.5839, 256.2949]
y_moves = [250, 240.0093, 230.0244, 220.7697, 213.0386, 204.9872, 199.0396, 197.9567, 197.7209, 201.6598, 208.8527, 214.1875, 221.9834, 231.5249, 239.62, 248.567, 258.4287, 268.3634, 277.6461, 287.0379, 296.7253, 302.8256, 310.9492, 319.299, 327.5969, 335.2652, 340.4185, 348.7651, 358.1231, 366.6125, 375.0812, 381.7291, 389.6996, 396.9915, 405.2003]
class my_App(App):
def build(self):
self.widget = Widget()
self.widget.on_touch_down = self.touch
with self.widget.canvas:
Color(0, 1, 0, 1) #just initial config
Line(points = [0,0,500,0,500,500,0,500], close = True) #just initial config
return self.widget
def touch(self, touch): #executes the animation
pts = []
for i in range(len(x_moves)):
pts.append(x_moves[i])
pts.append(y_moves[i])
self.widget.canvas.add(Line(points = pts))
sleep(0.1)
if __name__ == '__main__':
obj = my_App()
obj.run()
This is my code that doesn't work. But that's the idea.
You're blocking the UI from updating because your function doesn't return and you use sleep(). For obvious reasons, Kivy can't do anything while your code is running. If you want to wait before running some more code, you can use Kivy's Clock for this. Then Kivy will be able to update the screen.
But you should probably just look at Kivy's Animation, which is built to do this much better.

Categories

Resources