I'm new at Python, trying to fill a canvas with random pixels. Could someone tell me why it's doing horizontal stripes?
import tkinter
from random import randint
from binascii import hexlify
class App:
def __init__(self, t):
x=200
y=200
xy=x*y
b=b'#000000 '
s=bytearray(b*xy)
c = tkinter.Canvas(t, width=x, height=y);
self.i = tkinter.PhotoImage(width=x,height=y)
for k in range (0,8*xy,8):
s[k+1:k+7]=hexlify(bytes([randint(0,255) for i in range(3)]))
print (s[:100])
pixels=s.decode("ascii")
self.i.put(pixels,(0,0,x,y))
print (len(s),xy*8)
c.create_image(0, 0, image = self.i, anchor=tkinter.NW)
c.pack()
t = tkinter.Tk()
a = App(t)
t.mainloop()
Which gives e.g.:
I would suggest you do something a bit simpler, e.g.:
class App:
def __init__(self, t, w=200, h=200):
self.image = tkinter.PhotoImage(width=w, height=h) # create empty image
for x in range(w): # iterate over width
for y in range(h): # and height
rgb = [randint(0, 255) for _ in range(3)] # generate one pixel
self.image.put("#{:02x}{:02x}{:02x}".format(*rgb), (y, x)) # add pixel
c = tkinter.Canvas(t, width=w, height=h);
c.create_image(0, 0, image=self.image, anchor=tkinter.NW)
c.pack()
This is much easier to understand, and gives me:
which I suspect is what you were hoping for.
To reduce the number of image.puts, note that the format for data is (for a 2x2 black image):
'{#000000 #000000} {#000000 #000000}'
You could therefore use:
self.image = tkinter.PhotoImage(width=w, height=h)
lines = []
for _ in range(h):
line = []
for _ in range(w):
rgb = [randint(0, 255) for _ in range(3)]
line.append("#{:02x}{:02x}{:02x}".format(*rgb))
lines.append('{{{}}}'.format(' '.join(line)))
self.image.put(' '.join(lines))
which only has one image.put (see e.g. Why is Photoimage put slow?) and gives a similar-looking image. Your image was stripy because it was interpreting each pixel colour as a line colour, as you hadn't included the '{' and '}' for each line.
Related
I have edited a code here:
import moviepy.editor as mp
import numpy as np
import tempfile
import matplotlib.pyplot as plt
from PIL import Image, ImageDraw, ImageFont, ImageOps
from textwrap import wrap
# Constants-
WIDTH = 1800
HEIGHT = 1000
FONT_SIZE_USER_INFO = int(WIDTH * 0.05)
FONT_SIZE_TEXT = int(WIDTH * 0.044)
FONT_USER_INFO = ImageFont.truetype(
"arial.ttf", FONT_SIZE_USER_INFO, encoding="utf-8")
FONT_TEXT = ImageFont.truetype("arial.ttf", FONT_SIZE_TEXT, encoding="utf-8")
COLOR_BG = 'white'
COLOR_NAME = 'black'
COLOR_TAG = (64, 64, 64)
COLOR_TEXT = 'black'
COORD_PHOTO = (round(WIDTH/6), round(HEIGHT/6))
COORD_NAME = (round(WIDTH/2.9), round(HEIGHT/4.1))
COORD_TAG = (round(WIDTH/2.9), round(HEIGHT/3))
COORD_TEXT = (round(WIDTH/6), round(HEIGHT/2))
LINE_MARGIN = 15
user_name = "José Fernando Costa"
user_tag = "#soulsinporto"
user_pic = 'avortr.png'
text = "Go out there and do some fun shit, not because it makes money, but because it is fun for you!"
video_name = "tw1.mp4"
PHOTO_SIZE = int(WIDTH * 0.15)
def create_frame(t):
# Break the text string into smaller strings, each having a maximum of 37 characters (a.k.a. create the lines of text for the image)
text_string_lines = wrap(text[:int(t*len(text))], 37)
x = COORD_TEXT[0]
y = COORD_TEXT[1]
temp_img = Image.new('RGB', (0, 0))
temp_img_draw_interf = ImageDraw.Draw(temp_img)
line_height = [
temp_img_draw_interf.textsize(text_string_lines[i], font=FONT_TEXT)[1]
for i in range(len(text_string_lines))
]
img = Image.new('RGB', (WIDTH, HEIGHT), color=COLOR_BG)
draw_interf = ImageDraw.Draw(img)
photo = Image.open(user_pic)
photo_width, photo_height = photo.size
photo = photo.resize((PHOTO_SIZE, PHOTO_SIZE), Image.ANTIALIAS)
img.paste(photo, COORD_PHOTO)
draw_interf.text(COORD_NAME, user_name,
font=FONT_USER_INFO, fill=COLOR_NAME)
draw_interf.text(COORD_TAG, user_tag, font=FONT_USER_INFO, fill=COLOR_TAG)
for i in range(len(text_string_lines)):
draw_interf.text(
(x, y), text_string_lines[i], font=FONT_TEXT, fill=COLOR_TEXT)
y += line_height[i] + LINE_MARGIN
img_np = np.array(img)
return img_np
This bottom part converts them into an mp4.
animation = mp.VideoClip(create_frame, duration=len(text)/60).set_fps(60)
animation.write_videofile(video_name)
which is a revised code of 'quotespy', and it produces a result that is working.
However, I wish to edit the code such that the only parameter I would have to define is the text itself, and that all other variables and current constants are relative and based on the text, and the length + height of the text.
I've tried switching the code so that it draws a temp image but I cannot seem to return the text width and height as a global variable to define the final image WIDTH and HEIGHT. I figured that the best way is to draw the image first, to define the line_height and the line_margin? But it doesn't seem to work.
What I am expecting is:
Draw temp image of 'text', where, depending on length of text (with line breaks etc), it produces 2 variables, line-width and line-height.
Based on this, the coord_photo, coord_username and coord_usertag will be applied to be positioned above the text/ textbox.
after which, the final image's WIDTH and HEIGHT will be defined as such:
WIDTH = (line-width + 100px left, 100px right)
HEIGHT = (line-height + 200px top, 200px bottom)
I'm not sure how to properly edit the code to structure it so that it does not feel like a looping function call.
Hope that someone can help me with my code! Much appreciated.
from keras.models import load_model
from tkinter import *
import tkinter as tk
import appscript
#import win32gui
from PIL import ImageGrab, Image
import numpy as np
model = load_model('mnist.h5')
def predict_digit(img):
#resize image to 28x28 pixels
img = img.resize((28,28))
#convert rgb to grayscale
img = img.convert('L')
img = np.array(img)
#reshaping to support our model input and normalizing
img = img.reshape(1,28,28,1)
img = img/255.0
#predicting the class
res = model.predict([img])[0]
return np.argmax(res), max(res)
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.x = self.y = 0
# Creating elements
self.canvas = tk.Canvas(self, width=300, height=300, bg = "white", cursor="cross")
self.label = tk.Label(self, text="Draw..", font=("Helvetica", 48))
self.classify_btn = tk.Button(self, text = "Recognise", command = self.classify_handwriting)
self.button_clear = tk.Button(self, text = "Clear", command = self.clear_all)
# Grid structure
self.canvas.grid(row=0, column=0, pady=2, sticky=W, )
self.label.grid(row=0, column=1,pady=2, padx=2)
self.classify_btn.grid(row=1, column=1, pady=2, padx=2)
self.button_clear.grid(row=1, column=0, pady=2)
#self.canvas.bind("<Motion>", self.start_pos)
self.canvas.bind("<B1-Motion>", self.draw_lines)
def clear_all(self):
self.canvas.delete("all")
def classify_handwriting(self):
HWND = self.canvas.winfo_id() # get the handle of the canvas
rect=self.canvas.coords(HWND)
#rect = win32gui.GetWindowRect(HWND) # get the coordinate of the canvas
a,b,c,d = rect
rect=(a+4,b+4,c-4,d-4)
im = ImageGrab.grab(rect)
digit, acc = predict_digit(im)
self.label.configure(text= str(digit)+', '+ str(int(acc*100))+'%')
def draw_lines(self, event):
self.x = event.x
self.y = event.y
r=8
HWND=self.canvas.create_oval(self.x-r, self.y-r, self.x + r, self.y + r, fill='black')
app = App()
mainloop()
Can anyone help me especially with this part :
HWND = self.canvas.winfo_id() # get the handle of the canvas
rect=self.canvas.coords(HWND)
#rect = win32gui.GetWindowRect(HWND) # get the coordinate of the canvas
a,b,c,d = rect
rect=(a+4,b+4,c-4,d-4)
im = ImageGrab.grab(rect)
This is the error that appears :
Exception in Tkinter callback
Traceback (most recent call last):
File "/Users/xxx/opt/anaconda3/lib/python3.8/tkinter/__init__.py", line 1883, in __call__
return self.func(*args)
File "<ipython-input-4-28fb2cc31a85>", line 52, in classify_handwriting
a,b,c,d = rect
ValueError: not enough values to unpack (expected 4, got 0)
I am trying to create an ocr program to identify handwriting by letting the user draw a number and return the current value with high accuracy .
But the problem faced was with the canvas and how to get the coordinates without using the win32gui module (Since im a mac user) so I'm looking for a solution or an alternative for win32gui method GetWindowRect
self.canvas.coords(HWND) is going to return the coordinates of an object on the canvas with the id or tag of whatever is in HWND. It does not return the coordinates of the canvas itself.
Since HWND doesn't represent an item on the canvas, self.canvas.coords(HWND) returns an empty list. That is why you get the error not enough values to unpack - your code requires four values but the list has zero.
If you are wanting the coordinates of the canvas on the physical screen there are other methods you can use. See the following code:
x, y = (self.canvas.winfo_rootx(), self.canvas.winfo_rooty())
width, height = (self.canvas.winfo_width(), self.canvas.winfo_height())
a, b, c, d = (x, y, x+width, y+height)
Correct! It works fine on MACOS. comment the "import win32gui" line. Modify "classify_handwriting" function as below:
def classify_handwriting(self):
x, y = (self.canvas.winfo_rootx(), self.canvas.winfo_rooty())
width, height = (self.canvas.winfo_width(),
self.canvas.winfo_height())
a, b, c, d = (x, y, x+width, y+height)
im = ImageGrab.grab(bbox=(a,b,c,d))
Hope it works in your local setting!
I want to have some transition to show the images in the canvas, I'm using Tkinter and I'm looping through the images to show them on the canvas but I need to have some transition while switching among the images.
I'm using canvasName.create_image method for showing the images. Need a way to show them smoothly.
Here is my code:
def Multi_view_rotate():
window.geometry(str(scr_w)+"x"+str(scr_h)+"+0+0")
z_out = 20
global timeSleep
timeSleepVal = int(timeSleep.get())
global footerPath
footerPath = footerPath.get()
#geting director from entry boxes
global portDirEntry
portDirEntry = portDirEntry.get()
global colorEntry
bgcolor = colorEntry.get()
allPaths = getPaths(portDirEntry)
#directory = r"C:\Users\DotNet\Desktop\Ragazinana Data reduced\diashow\4 Random\Landschaft"
#Get paths
pathsPrt = allPaths[0]
pathsLand = allPaths[1]
#read the image
#call the function to get the picture object with new size
global numOfImagesPort
global numOfImagesLand
#footer path
#footerPath = "C:/Users/DotNet/Desktop/Ragazinana Data reduced/diashow/ragaziana_s.jpg"
#Footer will take 8% of the screen width
per_w_footer = cal_per_num(8, scr_w)
# Footer Image operations
canvasFoot = Canvas(window,width=per_w_footer, height=scr_h, bg=bgcolor, highlightthickness=1, highlightbackground=bgcolor)
canvasFoot.grid(row=0, column=0)
#footerImg = get_img_fit_size(footerPath, scr_h, per_w_footer, True)
footerImg1 = Image.open(footerPath)
footerImg2 = footerImg1.transpose(Image.ROTATE_270)
footerImg3 = footerImg2.resize((int(per_w_footer),int(scr_h)), Image.ANTIALIAS)
footerImg = ImageTk.PhotoImage(footerImg3)
footer = canvasFoot.create_image(per_w_footer/2,scr_h/2,anchor=CENTER, image=footerImg)
while(numOfImagesPort<=len(pathsPrt)-1 or numOfImagesLand<=len(pathsLand)-1 ):
pathPort = pathsPrt[numOfImagesPort]
#increase the index to get the next file in the next loop
numOfImagesPort=numOfImagesPort+1
#if the next photo is out of bound then assign it to the first index
if(numOfImagesPort >= len(pathsPrt)):# if total is 5 pic, 1st loop 0 > 6 /reset the loop
numOfImagesPort=0
# each image will take as following in percentage
per_w_imgs_portriate = cal_per_num(42, scr_w)
per_w_imgs_landscape= cal_per_num(50, scr_w)
#Create the canvases
canvasPort = Canvas(window,width=per_w_imgs_portriate, height=scr_h, bg=bgcolor, highlightthickness=10, highlightbackground=bgcolor)
#gird plays the canvas without it the canvas will not work
canvasPort.grid(row=0, column=1)
#in order to make the picture fit in the rotated state in the half of the screen
# we make the get_img_fit_size adjust it to us to that size by providing
# screen hight as a width and half of the screen with as a height
imgPort = get_img_fit_size(pathPort, scr_h, per_w_imgs_landscape, True)
portImgCanvas = canvasPort.create_image(int(scr_w/4.3),int(scr_h/2),anchor=CENTER, image=imgPort)**
window.update()
time.sleep(timeSleepVal/2)
# Landscape image
pathLand = pathsLand[numOfImagesLand]
numOfImagesLand = numOfImagesLand+1
if(numOfImagesLand >= len(pathsLand)):
numOfImagesLand=0
canvasLand = Canvas(window,width=per_w_imgs_landscape, height=scr_h, bg=bgcolor, highlightthickness=10, highlightbackground=bgcolor)
canvasLand.grid(row=0, column=2)
imgLand = get_img_fit_size(pathLand, scr_h, per_w_imgs_portriate, True)
landImgCanvas = canvasLand.create_image(int(scr_w/4.5),int(scr_h/2),anchor=CENTER, image=imgLand)
window.update()
time.sleep(timeSleepVal/2)
window.mainloop()
I don't think there is something like this built into Tkinter.PhotoImage, but you could manually create a "fade" transition by randomly selecting pixels and setting them to the color values of the next image:
import tkinter, random
root = tkinter.Tk()
c = tkinter.Canvas(root, width=800, height=400)
c.pack()
img_a = tkinter.PhotoImage(file="a.gif")
img_b = tkinter.PhotoImage(file="b.gif")
i = c.create_image(0, 0, image=img_a, anchor="nw")
pixels = [(x, y) for x in range(img_a.width()) for y in range(img_a.height())]
random.shuffle(pixels)
def fade(n=1000):
global pixels, i
for _ in range(min(n, len(pixels))):
x, y = pixels.pop()
col = "#%02x%02x%02x" % img_b.get(x,y)
img_a.put(col, (x, y))
c.delete(i)
i = c.create_image(0, 0, image=img_a, anchor="nw")
if pixels:
c.after(1, fade)
fade()
root.mainloop()
This is slow, though. The after with 1 ms is only to keep the UI from freezing (don't use while with time.sleep in Tkinter!). For a smoother transition, instead of replacing pixel values you might gradually shift all pixels towards the values in the next image, but that will be even slower since you'd change all pixels in each step.
Instead of pure tkinter, we can try it wit PIL and numpy, but it is not noticeably faster, and least not the way I did it:
import numpy as np
from PIL import Image, ImageTk
from itertools import islice
...
arr_a = np.array(Image.open("a.gif").convert("RGB"))
arr_b = np.array(Image.open("b.gif").convert("RGB"))
img = ImageTk.PhotoImage(Image.fromarray(arr_a, mode="RGB"))
i = c.create_image(0, 0, image=img, anchor="nw")
h, w, _ = arr_a.shape
pixels = [(x, y) for x in range(w) for y in range(h)]
random.shuffle(pixels)
def fade(k=0, n=1000):
global i, img
X, Y = zip(*islice(pixels, k, k+n))
arr_a[Y,X] = arr_b[Y,X]
c.delete(i)
img = ImageTk.PhotoImage(Image.fromarray(arr_a, mode="RGB"))
i = c.create_image(0, 0, image=img, anchor="nw")
if k + n < w * h:
c.after(1, fade, k+n, n)
fade()
root.mainloop()
However, this also allows us to replace entire lines at once. The effect is not quite as nice, but it is much faster (also note changed n and if condition).
...
h, w, _ = arr_a.shape
lines = list(range(h))
random.shuffle(lines)
def fade(k=0, n=10):
global i, img
Y = lines[k:k+n]
arr_a[Y] = arr_b[Y]
...
if k + n < h:
c.after(1, fade, k+n, n)
...
This can also easily be transformed to a vertical or horizontal slide transition by simply not shuffling the lines (for columns, use arr_a[:,X] = arr_b[:,X]).
I have created a simple Tkinter application with a canvas widget like this:
from tkinter import *
root = Tk()
root.geometry("500x500-500+500")
canvas = Canvas(root, width = 400, height = 400, bg = "white")
canvas.pack()
canvas.create_line(0, 0, 200, 100, width = 20, fill = "black")
root.mainloop()
My question is, how can I get the color of the canvas in a specific position? Say for instance I clicked somewhere on the line, how can I get back the color "black" from that?
In other words, if I wanted a function like this,
def getColor(cnvs, event = None):
x = event.x
y = event.y
# somehow gets the color of cnvs at position (x, y) and stores it as color
return color
how would I go about doing that?
You can take a screen shot of the canvas using Pillow.ImageGrab module and get the required pixel color from the snapshot image:
from PIL import ImageGrab
def get_color(cnvs, event):
x, y = cnvs.winfo_rootx()+event.x, cnvs.winfo_rooty()+event.y
# x, y = cnvs.winfo_pointerx(), cnvs.winfo_pointery()
image = ImageGrab.grab((x, y, x+1, y+1)) # 1 pixel image
return image.getpixel((0, 0))
Note that the color returned is in (R, G, B) format.
I have a sensor that needs to be calibrated. The error depends on the orientation of the sensor and can be estimated and shown to the user. I would like to do this visually using tkinter for python 3.x.
The ideal result would be something like this with the black bar live updating depending on the live error:
How could I do this best in tkinter? I looked at the Scale and Progressbar widgets but they did not have the needed functionality.
I was thinking about showing the colorbar as an image and overlaying the black indicator bar and constantly updating the position of this black bar. Would this be possible?
I shall split up the answer in two parts. The first part solves the issue of live updating the data, by using two threads as suggested by #Martineau. The communication between the threads is done by a simple lock and a global variable.
The second part creates the calibration bar widget using the gradient calculation algorithm defined by #Martineau.
PART 1:
This example code shows a small window with one number. The number is generated in one thread and the GUI is shown by another thread.
import threading
import time
import copy
import tkinter as tk
import random
class ThreadCreateData(threading.Thread):
def __init__(self, name):
threading.Thread.__init__(self)
self.name = name
def run(self):
#Declaring data global allows to access it between threads
global data
# create data for the first time
data_original = self.create_data()
while True: # Go in the permanent loop
print('Data creator tries to get lock')
lock.acquire()
print('Data creator has it!')
data = copy.deepcopy(data_original)
print('Data creator is releasing it')
lock.release()
print('Data creator is creating data...')
data_original = self.create_data()
def create_data(self):
'''A function that returns a string representation of a number changing between one and ten.'''
a = random.randrange(1, 10)
time.sleep(1) #Simulating calculation time
return str(a)
class ThreadShowData(threading.Thread):
def __init__(self, name):
threading.Thread.__init__(self)
self.name = name
def run(self):
# Declaring data global allows to access it between threads
global data
root = tk.Tk()
root.geometry("200x150")
# creation of an instance
app = Window(root, lock)
# mainloop
root.mainloop()
# Here, we are creating our class, Window, and inheriting from the Frame
# class. Frame is a class from the tkinter module. (see Lib/tkinter/__init__)
class Window(tk.Frame):
# Define settings upon initialization. Here you can specify
def __init__(self, master=None,lock=None):
# parameters that you want to send through the Frame class.
tk.Frame.__init__(self, master)
# reference to the master widget, which is the tk window
self.master = master
#Execute function update_gui after 1ms
self.master.after(1, self.update_gui(lock))
def update_gui(self, lock):
global data
print('updating')
print('GUI trying to get lock')
lock.acquire()
print('GUI got the lock')
new_data = copy.deepcopy(data)
print('GUI releasing lock')
lock.release()
data_label = tk.Label(self.master, text=new_data)
data_label.grid(row=1, column=0)
print('GUI wating to update')
self.master.after(2000, lambda: self.update_gui(lock)) #run update_gui every 2 seconds
if __name__ == '__main__':
# creating the lock
lock = threading.Lock()
#Initializing data
data = None
#creating threads
a = ThreadCreateData("Data_creating_thread")
b = ThreadShowData("Data_showing_thread")
#starting threads
b.start()
a.start()
PART 2: Below the code for a simple calibration bar widget is shown. The bar only contains 5 ticks you can adapt the code to add more if wanted. Pay attention to the needed input formats. To test the widget a random value is generated and shown on the widget every 0.5s.
import tkinter as tk
from PIL import ImageTk, Image
import sys
EPSILON = sys.float_info.epsilon # Smallest possible difference.
###Functions to create the color bar (credits to Martineau)
def convert_to_rgb(minval, maxval, val, colors):
for index, color in enumerate(colors):
if color == 'YELLOW':
colors[index] = (255, 255, 0)
elif color == 'RED':
colors[index] = (255, 0, 0)
elif color == 'GREEN':
colors[index] = (0, 255, 0)
# "colors" is a series of RGB colors delineating a series of
# adjacent linear color gradients between each pair.
# Determine where the given value falls proportionality within
# the range from minval->maxval and scale that fractional value
# by the total number in the "colors" pallette.
i_f = float(val - minval) / float(maxval - minval) * (len(colors) - 1)
# Determine the lower index of the pair of color indices this
# value corresponds and its fractional distance between the lower
# and the upper colors.
i, f = int(i_f // 1), i_f % 1 # Split into whole & fractional parts.
# Does it fall exactly on one of the color points?
if f < EPSILON:
return colors[i]
else: # Otherwise return a color within the range between them.
(r1, g1, b1), (r2, g2, b2) = colors[i], colors[i + 1]
return int(r1 + f * (r2 - r1)), int(g1 + f * (g2 - g1)), int(b1 + f * (b2 - b1))
def create_gradient_img(size, colors):
''''Creates a gradient image based on size (1x2 tuple) and colors (1x3 tuple with strings as entries,
possible entries are GREEN RED and YELLOW)'''
img = Image.new('RGB', (size[0],size[1]), "black") # Create a new image
pixels = img.load() # Create the pixel map
for i in range(img.size[0]): # For every pixel:
for j in range(img.size[1]):
pixels[i,j] = convert_to_rgb(minval=0,maxval=size[0],val=i,colors=colors) # Set the colour accordingly
return img
### The widget
class CalibrationBar(tk.Frame):
""""The calibration bar widget. Takes as arguments the parent, the start value of the calibration bar, the
limits in the form of a 1x5 list these will form the ticks on the bar and the boolean two sided. In case it
is two sided the gradient will be double."""
def __init__(self, parent, limits, name, value=0, two_sided=False):
tk.Frame.__init__(self, parent)
#Assign attributes
self.value = value
self.limits = limits
self.two_sided = two_sided
self.name=name
#Test that the limits are 5 digits
assert len(limits)== 5 , 'There are 5 ticks so you should give me 5 values!'
#Create a canvas in which we are going to put the drawings
self.canvas_width = 400
self.canvas_height = 100
self.canvas = tk.Canvas(self,
width=self.canvas_width,
height=self.canvas_height)
#Create the color bar
self.bar_offset = int(0.05 * self.canvas_width)
self.bar_width = int(self.canvas_width*0.9)
self.bar_height = int(self.canvas_height*0.8)
if two_sided:
self.color_bar = ImageTk.PhotoImage(create_gradient_img([self.bar_width,self.bar_height],['RED','GREEN','RED']))
else:
self.color_bar = ImageTk.PhotoImage(create_gradient_img([self.bar_width,self.bar_height], ['GREEN', 'YELLOW', 'RED']))
#Put the colorbar on the canvas
self.canvas.create_image(self.bar_offset, 0, image=self.color_bar, anchor = tk.NW)
#Indicator line
self.indicator_line = self.create_indicator_line()
#Tick lines & values
for i in range(0,5):
print(str(limits[i]))
if i==4:
print('was dees')
self.canvas.create_line(self.bar_offset + int(self.bar_width - 2), int(self.canvas_height * 0.7),
self.bar_offset + int(self.bar_width - 2), int(self.canvas_height * 0.9), fill="#000000", width=3)
self.canvas.create_text(self.bar_offset + int(self.bar_width - 2), int(self.canvas_height * 0.9), text=str(limits[i]), anchor=tk.N)
else:
self.canvas.create_line(self.bar_offset + int(i * self.bar_width / 4), int(self.canvas_height * 0.7), self.bar_offset + int(i * self.bar_width / 4), int(self.canvas_height * 0.9), fill="#000000", width=3)
self.canvas.create_text(self.bar_offset + int(i * self.bar_width / 4), int(self.canvas_height * 0.9), text=str(limits[i]), anchor=tk.N)
#Text
self.label = tk.Label(text=self.name+': '+str(self.value),font=14)
#Positioning
self.canvas.grid(row=0,column=0,sticky=tk.N)
self.label.grid(row=1,column=0,sticky=tk.N)
def create_indicator_line(self):
""""Creates the indicator line"""
diff = self.value-self.limits[0]
ratio = diff/(self.limits[-1]-self.limits[0])
if diff<0:
ratio=0
elif ratio>1:
ratio=1
xpos = int(self.bar_offset+ratio*self.bar_width)
return self.canvas.create_line(xpos, 0, xpos, 0.9 * self.canvas_height, fill="#000000", width=3)
def update_value(self,value):
self.value = value
self.label.config(text = self.name+': '+str(self.value))
self.canvas.delete(self.indicator_line)
self.indicator_line = self.create_indicator_line()
###Creation of window to place the widget
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.geometry('400x400')
self.calibration_bar = CalibrationBar(self, value= -5, limits=[-10, -5, 0, 5, 10], name='Inclination angle', two_sided=True)
self.calibration_bar.grid(column=0, row=4)
self.after(500,self.update_data)
def update_data(self):
""""Randomly assing values to the widget and update the widget."""
import random
a = random.randrange(-15, 15)
self.calibration_bar.update_value(a)
self.after(500, self.update_data)
###Calling our window
if __name__ == "__main__":
app=App()
app.mainloop()
This is how it looks like:
To get a live updating calibration bar you should just combine part one and two in your application.