abhera
abhera

Reputation: 125

How to draw an arc on a tkinter canvas?

I'm studying automata theory and I was requested to program the graph (tree) of an automaton that looks more or less like:

this

So far I got this (I'm using tkinter and canvas to draw):

from tkinter import Tk, Canvas, mainloop

def circle(canvas, x, y, r, width):
   id = canvas.create_oval (x-r, y-r, x+r, y+r, width = width)
   return id

def line (canvas, x1, y1, x2, y2, width):
    canvas.create_line (x1, y1, x2, y2, width = width)

def text (canvas, x, y, text):
    canvas.create_text (x, y, text = text, font = ("bold", 20))

w = Canvas(Tk (), width=1000, height=600, bg = "white")

circle (w , 150, 300, 70, 3)
circle (w , 150, 300, 50, 3)
circle (w , 370, 300, 70, 3)
circle (w , 640, 300, 70, 3)
circle (w , 910, 300, 70, 3)

line (w, 10, 300, 80, 300, 3)
circle (w, 73, 300, 5, 6)
line (w, 220, 300, 300, 300, 3)
circle (w, 293, 300, 5, 6)
line (w, 440, 300, 570, 300, 3)
circle (w, 567, 300, 5, 6)
line (w, 710, 300, 840, 300, 3)
circle (w, 837, 300, 5, 6)

text (w, 150, 300, "q0")
text (w, 370, 300, "q1")
text (w, 640, 300, "q2")
text (w, 910, 300, "q3")

w.pack()
mainloop()

Which displays this:

screenshot of image created by current code

I don't need the arrows, because I will use dots instead. The problem is that I need to draw a line from circle q3 to circle q0, and from circle q0 to circle q0, too (a "bucle"). I tried the canvas.create_arc() method, but I can't get the handle of it. Is there an alternative? Any ideas on how to draw the "bucle"?

Upvotes: 8

Views: 12872

Answers (2)

KokoEfraim
KokoEfraim

Reputation: 327

You can use tkinter canvas to draw your 'bucket' using Canvas line of arranged points (should be encircled). Here my example of drawing a returning angle with a 'bucket' shape. I use python 3.9

import tkinter as tk

def P(x,y):
    """
    For convenience only.
    Transform point in cartesian (x,y) to Canvas (X,Y)
    As both system has difference y direction:
    Cartesian y-axis from bottom-left - up 
    Canvas Y-axis from top-left - down 
    """
    X = M + (x/xmax) * (W-2*M)
    Y = M + (1-(y/ymax)) * (H-2*M)
    return (X,Y)

def draw(window):
    """"
    Draw the lines
    """
    c = tk.Canvas(window, width=W, height=H)
    c.grid()
    
    # tuple of points to shape a 'bucket'
    points = P(60,0), P(90,50), P(50,100), P(10,50), P(40,0)
    
    fracture = c.create_line(points, arrow='last', fill='yellow')
    smooth = c.create_line(points, arrow='last', smooth=1)
   
"""
xmin is minimum value along cartesian x-axis
xmax is maximum value along cartesian x-axis
ymin is minimum value along cartesian y-axis
ymax is maximum value along cartesian y-axis
W is canvas width, in pixel
H is canvas height, in pixel
M is minimum margin inside canvas to ensure objects like arrow fully shown.

"""
M = 4  
W = 310
H = 210
xmin = 0
xmax = 100
ymin = 0
ymax = 100    

window = tk.Tk()
draw(window)
window.mainloop()

If you run the above code you will see a yellow fracture lines which is the original point-line and a black smooth line of the same line with option smooth set to 1 and option arrow set to 'last'.

Here the result

enter image description here

Upvotes: 1

martineau
martineau

Reputation: 123473

Here's some utility functions that provide an alternative way draw arcs on a tkinter.Canvas. Instead of the usual specification of two-points, (x0, y0) and (x1, y1) to define an enclosing rectangle, the arc functions accept a starting and stopping angle in the open range of [0..360) degrees.

It also illustrates how to have arrowheads drawn at the ends of straight lines (but not arcs) since you asked about that, too.

from tkinter import Canvas, mainloop, Tk

def circle(canvas, x, y, r, width):
    return canvas.create_oval(x+r, y+r, x-r, y-r, width=width)

def circular_arc(canvas, x, y, r, t0, t1, width):
    return canvas.create_arc(x-r, y-r, x+r, y+r, start=t0, extent=t1-t0,
                             style='arc', width=width)

def ellipse(canvas, x, y, r1, r2, width):
    return canvas.create_oval(x+r1, y+r2, x-r1, y-r2, width=width)

def elliptical_arc(canvas, x, y, r1, r2, t0, t1, width):
    return canvas.create_arc(x-r1, y-r2, x+r1, y+r2, start=t0, extent=t1-t0,
                             style='arc', width=width)

def line(canvas, x1, y1, x2, y2, width, start_arrow=0, end_arrow=0):
    arrow_opts = start_arrow << 1 | end_arrow
    arrows = {0b10: 'first', 0b01: 'last', 0b11: 'both'}.get(arrow_opts, None)
    return canvas.create_line(x1, y1, x2, y2, width=width, arrow=arrows)

def text(canvas, x, y, text):
    return canvas.create_text(x, y, text=text, font=('bold', 20))


w = Canvas(Tk(), width=1000, height=600, bg='white')

circle(w, 150, 300, 70, 3)  # q0 outer edge
circle(w, 150, 300, 50, 3)  # q0 inner edge
circle(w, 370, 300, 70, 3)  # q1
circle(w, 640, 300, 70, 3)  # q2
circle(w, 910, 300, 70, 3)  # q3

# Draw arc from circle q3 to q0.
midx, midy = (150+910) / 2, 300
r1, r2 = 910-midx, 70+70
elliptical_arc(w, midx, midy, r1, r2, 30, 180-30, 3)

line(w,  10, 300,  80, 300, 3, end_arrow=1)
line(w, 220, 300, 300, 300, 3, end_arrow=1)
line(w, 440, 300, 570, 300, 3, end_arrow=1)
line(w, 710, 300, 840, 300, 3, end_arrow=1)

text(w, 150, 300, 'q0')
text(w, 370, 300, 'q1')
text(w, 640, 300, 'q2')
text(w, 910, 300, 'q3')

w.pack()
mainloop()

This is what it draws:

screenshot showing output

It doesn't draw a "buckle" like you want partly because drawing "a line from circle q3 to circle q0, and from circle q0 to circle q0" isn't like the illustration at the beginning of your question which is one drawn between two circles (if I understand correctly what you meant by the term).

However, it does provide another way for you to draw arcs on a canvas.

Upvotes: 7

Related Questions