Python Forum
Help with flowchart maker program
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Help with flowchart maker program
#1
Hello! I am currently a pretty fresh coder in Python and general programming and have been trying to create a Flowchart Maker. The only import I have needed to use is Tkinter for its GUI. Everything is running smoothly, but I am having troubles figuring out a way to make the arrows. I do not really know how to make an efficient version of lines that are able to bend and do not collide with the objects, since my only idea is making a spaghetti of 40 different if statements and such. The code that I need help making is at line 300. Once again, I have only had around 2 months of experience, so I apologize for the likely not refined code and would love to help explain any questions or concerns. As an added bonus, if anyone has any better ideas on how to refine the code or make data storage better, I would love to hear it! For general information for replication I am running Python through Visual Code Studio with Homebrew's 3.11.4 version. I should probably put this through Github too, but I really do not have any clue as to how to work Github, where I would put it, etc. Anyway, thank you guys for any help, and I'll try to check in and make sure I can answer any questions!

from tkinter import *

#info variable setup:
#   index 0 - object
#   index 1-4: scaling dots
#   index 5-8: Anchor dots
#   index 9-end: arrow connections


#width x height of all objects
DIAMOND_SIZE = [300,200]

CIRCLE_SIZE = [300,200]

RECTANGLE_SIZE = [300,200]

PARALLELOGRAM_SIZE = [300,200]
PARALLELOGRAM_ANGLE = 45

ARROW_SIZE = 300
ARROW_WIDTH = 30

DOT_SIZE = 10

COLOR = "#666666"
BASE_COLOR = "#663300"

last_clicked = None
show_anchors = False

first_anchor = None
first_coords = None
first_anchor_coords = None
first_anchor_orient = None
first_object_hitbox = None
first_object_info = None

second_anchor = None
second_coords = None
second_anchor_coords = None
second_anchor_orient = None
second_object_hitbox = None
second_object_info = None

#instating the window
window = Tk()
window.title("Flowchart Maker")
#window.attributes("-fullscreen",True)
#instating the canvas that fills the whole window even when expanded
canvas = Canvas(window,bg="#000000")
canvas.pack(fill="both", expand=True)

def anchor_mode():
    global show_anchors
    if show_anchors:
        show_anchors = False
        canvas.itemconfig("anchor",state="hidden")
        anchor_button.config(text="Arrow Mode: OFF")
    else:
        show_anchors = True
        canvas.itemconfig("anchor",state="normal")
        anchor_button.config(text="Arrow Mode: ON")

anchor_button = Button(canvas,highlightbackground="#CCCCAA",text="Arrow Mode: OFF",font=("Noteworthy",25),command=anchor_mode,fg=BASE_COLOR,padx=5,pady=5,relief=GROOVE,bd=4)
anchor_button.pack(side=BOTTOM)


#CREATION OF CLASSES

#click function: this basically just finds the place that the user clicked inside the object
#without this the program wouldn't be able to find the distance between point A and B

#drag function: this finds where the cursor is now and subtracts the distance between where the cursor is to where it was
#Then the program uses canvas.move() to move the object however many units and then sets the last click position to where the cursor is now for future calls

#__init__ function: each time an object is created, it gets the width x height constants and is created at a specific location
#it looks like a lot of math, but in reality it is the size of every object above itself + 20 for spacing between each object

class Icon:
    
    def click(self, event):
        #coordinates of the place we click the label at (Point A)
        self.click_x = event.x
        self.click_y = event.y
        print("Click:",self.click_x,self.click_y)
        
        global last_clicked
        
        try:
            canvas.itemconfig(self.info[4], state="normal")  
            canvas.itemconfig(last_clicked.info[1], state="hidden")
            canvas.itemconfig(last_clicked.info[2], state="hidden")
            canvas.itemconfig(last_clicked.info[3], state="hidden")
            canvas.itemconfig(last_clicked.info[4], state="hidden")
        except AttributeError:
            pass
        finally:
            last_clicked = self
            
            canvas.itemconfig(self.info[1], state="normal")
            canvas.itemconfig(self.info[2], state="normal")
            canvas.itemconfig(self.info[3], state="normal")
    
    def drag(self, event, obj_type):
        #coordinates of the place we are dragging the widget to (Point B)
        pointer_x=event.x
        pointer_y=event.y
        print("Pointer:",pointer_x,pointer_y)
        
        #distance between Point A and Point B for x and y
        x = pointer_x - self.click_x
        y = pointer_y - self.click_y
        
        canvas.move(self.info[0], x, y)
        canvas.move(self.info[1], x, y)
        canvas.move(self.info[2], x, y)
        canvas.move(self.info[3], x, y)
        canvas.move(self.info[4], x, y)
        
        canvas.move(self.info[5], x, y)
        canvas.move(self.info[6], x, y)
        canvas.move(self.info[7], x, y)
        canvas.move(self.info[8], x, y)
        
        self.click_x = pointer_x
        self.click_y = pointer_y
        
        self.coords = canvas.coords(self.info[0])
        
        if obj_type == "circle":
            self.hitbox = [self.coords[0],self.coords[1],
                            self.coords[2],self.coords[3]]
        elif obj_type == "rectangle":
            self.hitbox = [self.coords[0], self.coords[1],
                            self.coords[4], self.coords[5]]
        elif obj_type == "diamond":
            self.hitbox = [self.coords[2], self.coords[1],
                            self.coords[6], self.coords[5]]
        elif obj_type == "parallelogram":
            self.hitbox = [self.coords[2], self.coords[1],
                            self.coords[6], self.coords[5]]
        
        print("Hitbox:",self.hitbox)
    
    def scale(self, event, obj_type, dot):
        coords = canvas.coords(self.info[0])
        if obj_type == "circle":
            if dot == 1:
                canvas.coords(self.info[0], [event.x,event.y,coords[2],coords[3]])
            elif dot == 2:
                canvas.coords(self.info[0], [event.x,coords[1],coords[2],event.y])
            elif dot == 3:
                canvas.coords(self.info[0], [coords[0],coords[1],event.x,event.y])
            elif dot == 4:
                canvas.coords(self.info[0], [coords[0],event.y,event.x,coords[3]])
            
            coords = canvas.coords(self.info[0])
            
            canvas.coords(self.info[1], [coords[0]-DOT_SIZE, coords[1]-DOT_SIZE, coords[0]+DOT_SIZE, coords[1]+DOT_SIZE])    
            canvas.coords(self.info[2], [coords[0]-DOT_SIZE, coords[3]-DOT_SIZE, coords[0]+DOT_SIZE, coords[3]+DOT_SIZE])
            canvas.coords(self.info[3], [coords[2]-DOT_SIZE, coords[3]-DOT_SIZE, coords[2]+DOT_SIZE, coords[3]+DOT_SIZE])
            canvas.coords(self.info[4], [coords[2]-DOT_SIZE, coords[1]-DOT_SIZE, coords[2]+DOT_SIZE, coords[1]+DOT_SIZE])
            
            canvas.coords(self.info[5], [coords[0] + ((coords[2]-coords[0])/2) -DOT_SIZE, coords[1]-DOT_SIZE, coords[0] + ((coords[2]-coords[0])/2) +DOT_SIZE, coords[1]+DOT_SIZE])    
            canvas.coords(self.info[6], [coords[0] -DOT_SIZE, coords[1] + ((coords[3]-coords[1])/2) -DOT_SIZE, coords[0] +DOT_SIZE, coords[1] + ((coords[3]-coords[1])/2) + DOT_SIZE])
            canvas.coords(self.info[7], [coords[0] + ((coords[2]-coords[0])/2) -DOT_SIZE, coords[3] -DOT_SIZE, coords[0] + ((coords[2]-coords[0])/2) +DOT_SIZE, coords[3] + DOT_SIZE])
            canvas.coords(self.info[8], [coords[2] -DOT_SIZE, coords[1] + ((coords[3]-coords[1])/2) -DOT_SIZE, coords[2] +DOT_SIZE, coords[1] + ((coords[3]-coords[1])/2) + DOT_SIZE])
        
        elif obj_type == "rectangle": 
            if dot == 1:
                canvas.coords(self.info[0], [event.x, event.y, event.x, coords[3], coords[4], coords[5], coords[6], event.y])
            elif dot == 2:
                canvas.coords(self.info[0], [event.x, coords[1], event.x, event.y, coords[4], event.y, coords[6], coords[7]])
            elif dot == 3:
                canvas.coords(self.info[0], [coords[0], coords[1], coords[2], event.y, event.x, event.y, event.x, coords[7]])
            elif dot == 4:
                canvas.coords(self.info[0], [coords[0], event.y, coords[2], coords[3], event.x, coords[5], event.x, event.y])
            
            coords = canvas.coords(self.info[0])
            
            canvas.coords(self.info[1], [coords[0]-DOT_SIZE, coords[1]-DOT_SIZE, coords[0]+DOT_SIZE, coords[1]+DOT_SIZE])    
            canvas.coords(self.info[2], [coords[2]-DOT_SIZE, coords[3]-DOT_SIZE, coords[2]+DOT_SIZE, coords[3]+DOT_SIZE])
            canvas.coords(self.info[3], [coords[4]-DOT_SIZE, coords[5]-DOT_SIZE, coords[4]+DOT_SIZE, coords[5]+DOT_SIZE])
            canvas.coords(self.info[4], [coords[6]-DOT_SIZE, coords[7]-DOT_SIZE, coords[6]+DOT_SIZE, coords[7]+DOT_SIZE])
            
            canvas.coords(self.info[5], [coords[0] + ((coords[6]-coords[0])/2) -DOT_SIZE, coords[1]-DOT_SIZE, coords[0] + ((coords[6]-coords[0])/2) +DOT_SIZE, coords[1]+DOT_SIZE])    
            canvas.coords(self.info[6], [coords[2] + ((coords[0]-coords[2])/2) -DOT_SIZE, coords[1] + ((coords[3]-coords[1])/2) -DOT_SIZE, coords[2] + ((coords[0]-coords[2])/2) +DOT_SIZE, coords[1] + ((coords[3]-coords[1])/2) + DOT_SIZE])
            canvas.coords(self.info[7], [coords[2] + ((coords[4]-coords[2])/2) -DOT_SIZE, coords[3] -DOT_SIZE, coords[2] + ((coords[4]-coords[2])/2) +DOT_SIZE, coords[3] + DOT_SIZE])
            canvas.coords(self.info[8], [coords[4] + ((coords[6]-coords[4])/2) -DOT_SIZE, coords[7] + ((coords[5]-coords[7])/2) -DOT_SIZE, coords[4] + ((coords[6]-coords[4])/2) +DOT_SIZE, coords[7] + ((coords[5]-coords[7])/2) + DOT_SIZE])
            
        elif obj_type == "diamond":
            if dot == 1:
                canvas.coords(self.info[0], [event.x + ((coords[6]-coords[2])/2), event.y, 
                                            event.x, event.y + ((coords[5]-coords[1])/2),
                                            event.x + ((coords[6]-coords[2])/2), coords[5],
                                            coords[6], event.y + ((coords[5]-coords[1])/2)])
            elif dot == 2:
                canvas.coords(self.info[0], [event.x + ((coords[6]-coords[2])/2), coords[1],
                                            event.x, event.y - ((coords[5]-coords[1])/2),
                                            event.x + ((coords[6]-coords[2])/2), event.y,
                                            coords[6], event.y - ((coords[5]-coords[1])/2)])
            elif dot == 3:
                canvas.coords(self.info[0], [event.x - ((coords[6]-coords[2])/2), coords[1],
                                            coords[2], event.y - ((coords[5]-coords[1])/2),
                                            event.x - ((coords[6]-coords[2])/2), event.y,
                                            event.x, event.y - ((coords[5]-coords[1])/2)])
            elif dot == 4:
                canvas.coords(self.info[0], [event.x - ((coords[6]-coords[2])/2), event.y,
                                            coords[2], event.y + ((coords[5] - coords[1])/2),
                                            event.x - ((coords[6]-coords[2])/2), coords[5],
                                            event.x, event.y + ((coords[5] - coords[1])/2)])
            
            coords = canvas.coords(self.info[0])
            
            canvas.coords(self.info[1], [coords[2]-DOT_SIZE, coords[1]-DOT_SIZE, coords[2]+DOT_SIZE, coords[1]+DOT_SIZE])    
            canvas.coords(self.info[2], [coords[2]-DOT_SIZE, coords[5]-DOT_SIZE, coords[2]+DOT_SIZE, coords[5]+DOT_SIZE])
            canvas.coords(self.info[3], [coords[6]-DOT_SIZE, coords[5]-DOT_SIZE, coords[6]+DOT_SIZE, coords[5]+DOT_SIZE])
            canvas.coords(self.info[4], [coords[6]-DOT_SIZE, coords[1]-DOT_SIZE, coords[6]+DOT_SIZE, coords[1]+DOT_SIZE])
            
            canvas.coords(self.info[5], [coords[0]-DOT_SIZE, coords[1]-DOT_SIZE, coords[0]+DOT_SIZE, coords[1]+DOT_SIZE])    
            canvas.coords(self.info[6], [coords[2]-DOT_SIZE, coords[3]-DOT_SIZE, coords[2]+DOT_SIZE, coords[3]+DOT_SIZE])
            canvas.coords(self.info[7], [coords[4]-DOT_SIZE, coords[5]-DOT_SIZE, coords[4]+DOT_SIZE, coords[5]+DOT_SIZE])
            canvas.coords(self.info[8], [coords[6]-DOT_SIZE, coords[7]-DOT_SIZE, coords[6]+DOT_SIZE, coords[7]+DOT_SIZE])
        
        elif obj_type == "parallelogram":
            if dot == 1:
                canvas.coords(self.info[0], [event.x + PARALLELOGRAM_ANGLE, event.y, 
                                            event.x, coords[3],
                                            coords[4], coords[5],
                                            coords[6], event.y])
            elif dot == 2:
                canvas.coords(self.info[0], [event.x + 45, coords[1], 
                                            event.x, event.y,
                                            coords[4], event.y,
                                            coords[6], coords[7]])
            elif dot == 3:
                canvas.coords(self.info[0], [coords[0], coords[1], 
                                            coords[2], event.y,
                                            event.x - PARALLELOGRAM_ANGLE, event.y,
                                            event.x, coords[7]])
            elif dot == 4:
                canvas.coords(self.info[0], [coords[0], event.y, 
                                            coords[2], coords[3],
                                            event.x - PARALLELOGRAM_ANGLE, coords[5],
                                            event.x, event.y])
            
            coords = canvas.coords(self.info[0])
            
            canvas.coords(self.info[1], [coords[2]-DOT_SIZE, coords[1]-DOT_SIZE, coords[2]+DOT_SIZE, coords[1]+DOT_SIZE])    
            canvas.coords(self.info[2], [coords[2]-DOT_SIZE, coords[3]-DOT_SIZE, coords[2]+DOT_SIZE, coords[3]+DOT_SIZE])
            canvas.coords(self.info[3], [coords[6]-DOT_SIZE, coords[5]-DOT_SIZE, coords[6]+DOT_SIZE, coords[5]+DOT_SIZE])
            canvas.coords(self.info[4], [coords[6]-DOT_SIZE, coords[7]-DOT_SIZE, coords[6]+DOT_SIZE, coords[7]+DOT_SIZE])
            
            canvas.coords(self.info[5], [coords[0] + ((coords[6]-coords[0])/2) -DOT_SIZE, coords[1]-DOT_SIZE, coords[0] + ((coords[6]-coords[0])/2) +DOT_SIZE, coords[1]+DOT_SIZE])    
            canvas.coords(self.info[6], [coords[2] + ((coords[0]-coords[2])/2) -DOT_SIZE, coords[1] + ((coords[3]-coords[1])/2) -DOT_SIZE, coords[2] + ((coords[0]-coords[2])/2) +DOT_SIZE, coords[1] + ((coords[3]-coords[1])/2) + DOT_SIZE])
            canvas.coords(self.info[7], [coords[2] + ((coords[4]-coords[2])/2) -DOT_SIZE, coords[3] -DOT_SIZE, coords[2] + ((coords[4]-coords[2])/2) +DOT_SIZE, coords[3] + DOT_SIZE])
            canvas.coords(self.info[8], [coords[4] + ((coords[6]-coords[4])/2) -DOT_SIZE, coords[7] + ((coords[5]-coords[7])/2) -DOT_SIZE, coords[4] + ((coords[6]-coords[4])/2) +DOT_SIZE, coords[7] + ((coords[5]-coords[7])/2) + DOT_SIZE])
            
    def connect(self, event, anchor):
        def check_for_orient(info, anchor):
            if info[5] == anchor:
                return "top"
            elif info[6] == anchor:
                return "left"
            elif info[7] == anchor:
                return "bottom"
            elif info[8] == anchor:
                return "right"
            else:
                return None

        global first_anchor, second_anchor, first_coords, second_coords, first_anchor_coords, second_anchor_coords, first_anchor_orient, second_anchor_orient, first_object_hitbox, second_object_hitbox, first_object_info, second_object_info
        if first_anchor == None:
            
            first_anchor = anchor
            first_object_info = self.info
            first_anchor_orient = check_for_orient(first_object_info, anchor)
            
            first_anchor_coords = canvas.coords(first_anchor)
            first_anchor_coords = [first_anchor_coords[0]+DOT_SIZE, first_anchor_coords[1]+DOT_SIZE]
            print("first anchor clicked: ",first_anchor_coords)
            
            first_object_hitbox = self.hitbox
            
        elif second_anchor != first_anchor:
            
            second_anchor = anchor
            second_object_info = self.info
            second_anchor_orient = check_for_orient(self.info, anchor)
            
            second_anchor_coords = canvas.coords(second_anchor)
            second_anchor_coords = [second_anchor_coords[0]+DOT_SIZE, second_anchor_coords[1]+DOT_SIZE]
            print("second anchor clicked: ",second_anchor_coords)
            
            second_object_hitbox = self.hitbox
            
            line_coords = []
            line_coords.append(first_anchor_coords)
            
            #Code goes here! Thank you for any help!
            
            line_coords.append(second_anchor_coords)
            

            first_anchor = None
            second_anchor = None
            
                
            line = Arrow("#AA66AA", line_coords)
            
            line.info.append(first_object_info[0])
            line.info.append(second_object_info[0])
            
            first_object_info.append(line)
            second_object_info.append(line)
            
            

    
           

class Circle(Icon):
    def __init__(self, color):
        self.info =[]
        
        self.info.append(canvas.create_oval(10,10,10+CIRCLE_SIZE[0],10+CIRCLE_SIZE[1], fill=color,width=4,outline="black"))
        self.coords = canvas.coords(self.info[0])
        print(self.coords)
        
        self.info.append(canvas.create_oval(((self.coords[0])-DOT_SIZE,self.coords[1]-DOT_SIZE),
                                              ((self.coords[0])+DOT_SIZE,self.coords[1]+DOT_SIZE),
                                              fill="white", outline="black", width=3, state="hidden", tag="dot"))
        self.info.append(canvas.create_oval((self.coords[0]-DOT_SIZE,(self.coords[3])-DOT_SIZE),
                                              (self.coords[0]+DOT_SIZE,(self.coords[3])+DOT_SIZE),
                                              fill="white", outline="black", width=3, state="hidden", tag="dot"))
        self.info.append(canvas.create_oval((self.coords[2]-DOT_SIZE,(self.coords[3])-DOT_SIZE),
                                              (self.coords[2]+DOT_SIZE,(self.coords[3])+DOT_SIZE),
                                              fill="white", outline="black", width=3, state="hidden", tag="dot"))
        self.info.append(canvas.create_oval(((self.coords[2])-DOT_SIZE,self.coords[1]-DOT_SIZE),
                                              ((self.coords[2])+DOT_SIZE,self.coords[1]+DOT_SIZE),
                                              fill="white", outline="black", width=3, state="hidden", tag="dot"))
        
        self.info.append(canvas.create_oval((self.coords[0] + ((self.coords[2]-self.coords[0])/2) -DOT_SIZE, self.coords[1]-DOT_SIZE),
                                              (self.coords[0] + ((self.coords[2]-self.coords[0])/2) +DOT_SIZE, self.coords[1]+DOT_SIZE),
                                              fill="#999900", outline="black", width=3, state="hidden", tag="anchor"))
        self.info.append(canvas.create_oval((self.coords[0] -DOT_SIZE, self.coords[1] + ((self.coords[3]-self.coords[1])/2) -DOT_SIZE),
                                              (self.coords[0] +DOT_SIZE, self.coords[1] + ((self.coords[3]-self.coords[1])/2) + DOT_SIZE),
                                              fill="#999900", outline="black", width=3, state="hidden", tag="anchor"))
        self.info.append(canvas.create_oval((self.coords[0] + ((self.coords[2]-self.coords[0])/2) -DOT_SIZE, self.coords[3] -DOT_SIZE),
                                              (self.coords[0] + ((self.coords[2]-self.coords[0])/2) +DOT_SIZE, self.coords[3] +DOT_SIZE),
                                              fill="#999900", outline="black", width=3, state="hidden", tag="anchor"))
        self.info.append(canvas.create_oval((self.coords[2] -DOT_SIZE, self.coords[1] + ((self.coords[3]-self.coords[1])/2) -DOT_SIZE),
                                              (self.coords[2] +DOT_SIZE, self.coords[1] + ((self.coords[3]-self.coords[1])/2) + DOT_SIZE),
                                              fill="#999900", outline="black", width=3, state="hidden", tag="anchor"))
        
        self.hitbox = [self.coords[0],self.coords[1],
                       self.coords[2],self.coords[3]]
        
        canvas.tag_bind(self.info[0],"<Button-1>",self.click)
        canvas.tag_bind(self.info[0],"<B1-Motion>",lambda event: self.drag(event, "circle"))
        
        canvas.tag_bind(self.info[1], "<B1-Motion>", lambda event: self.scale(event, "circle", 1))
        canvas.tag_bind(self.info[2], "<B1-Motion>", lambda event: self.scale(event, "circle", 2))
        canvas.tag_bind(self.info[3], "<B1-Motion>", lambda event: self.scale(event, "circle", 3))
        canvas.tag_bind(self.info[4], "<B1-Motion>", lambda event: self.scale(event, "circle", 4))
        
        canvas.tag_bind(self.info[5], "<Button-1>", lambda event: self.connect(event, self.info[5]))
        canvas.tag_bind(self.info[6], "<Button-1>", lambda event: self.connect(event, self.info[6]))
        canvas.tag_bind(self.info[7], "<Button-1>", lambda event: self.connect(event, self.info[7]))
        canvas.tag_bind(self.info[8], "<Button-1>", lambda event: self.connect(event, self.info[8]))  

class Rectangle(Icon):
    def __init__(self, color):
        self.info =[]
        
        self.info.append(canvas.create_polygon(10, CIRCLE_SIZE[1]+20,
                                            10, CIRCLE_SIZE[1]+20+RECTANGLE_SIZE[1],
                                            10+RECTANGLE_SIZE[0], CIRCLE_SIZE[1]+20+RECTANGLE_SIZE[1],
                                            10+RECTANGLE_SIZE[0], CIRCLE_SIZE[1]+20,
                                            fill=color,width=4,outline="black"))
        self.coords = canvas.coords(self.info[0])
        
        self.info.append(canvas.create_oval((self.coords[0]-DOT_SIZE,self.coords[1]-DOT_SIZE),
                                              (self.coords[0]+DOT_SIZE,self.coords[1]+DOT_SIZE),
                                              fill="white",outline="black",width=3,state="hidden"))
        self.info.append(canvas.create_oval((self.coords[2]-DOT_SIZE,self.coords[3]-DOT_SIZE),
                                              (self.coords[2]+DOT_SIZE,self.coords[3]+DOT_SIZE),
                                              fill="white",outline="black",width=3,state="hidden"))
        self.info.append(canvas.create_oval((self.coords[4]-DOT_SIZE,self.coords[5]-DOT_SIZE),
                                              (self.coords[4]+DOT_SIZE,self.coords[5]+DOT_SIZE),
                                              fill="white",outline="black",width=3,state="hidden"))
        self.info.append(canvas.create_oval((self.coords[6]-DOT_SIZE,self.coords[7]-DOT_SIZE),
                                              (self.coords[6]+DOT_SIZE,self.coords[7]+DOT_SIZE),
                                              fill="white",outline="black",width=3,state="hidden"))
        
        self.info.append(canvas.create_oval((self.coords[0] + ((self.coords[6]-self.coords[0])/2) -DOT_SIZE, self.coords[1]-DOT_SIZE),
                                              (self.coords[0] + ((self.coords[6]-self.coords[0])/2) +DOT_SIZE, self.coords[1]+DOT_SIZE),
                                              fill="#999900", outline="black", width=3, state="hidden", tag="anchor"))
        self.info.append(canvas.create_oval((self.coords[2] + ((self.coords[0]-self.coords[2])/2) -DOT_SIZE, self.coords[1] + ((self.coords[3]-self.coords[1])/2) -DOT_SIZE),
                                              (self.coords[2] + ((self.coords[0]-self.coords[2])/2) +DOT_SIZE, self.coords[1] + ((self.coords[3]-self.coords[1])/2) + DOT_SIZE),
                                              fill="#999900", outline="black", width=3, state="hidden", tag="anchor"))
        self.info.append(canvas.create_oval((self.coords[2] + ((self.coords[4]-self.coords[2])/2) -DOT_SIZE, self.coords[3] -DOT_SIZE),
                                              (self.coords[2] + ((self.coords[4]-self.coords[2])/2) +DOT_SIZE, self.coords[3] + DOT_SIZE),
                                              fill="#999900", outline="black", width=3, state="hidden", tag="anchor"))
        self.info.append(canvas.create_oval((self.coords[4] + ((self.coords[6]-self.coords[4])/2) -DOT_SIZE, self.coords[7] + ((self.coords[5]-self.coords[7])/2) -DOT_SIZE),
                                              (self.coords[4] + ((self.coords[6]-self.coords[4])/2) +DOT_SIZE, self.coords[7] + ((self.coords[5]-self.coords[7])/2) + DOT_SIZE),
                                              fill="#999900", outline="black", width=3, state="hidden", tag="anchor"))
        
        self.hitbox = [self.coords[0], self.coords[1],
                       self.coords[4], self.coords[5]]
        
        canvas.tag_bind(self.info[0],"<Button-1>",self.click)
        canvas.tag_bind(self.info[0],"<B1-Motion>",lambda event: self.drag(event, "rectangle"))
        
        canvas.tag_bind(self.info[1], "<B1-Motion>", lambda event: self.scale(event, "rectangle", 1))
        canvas.tag_bind(self.info[2], "<B1-Motion>", lambda event: self.scale(event, "rectangle", 2))
        canvas.tag_bind(self.info[3], "<B1-Motion>", lambda event: self.scale(event, "rectangle", 3))
        canvas.tag_bind(self.info[4], "<B1-Motion>", lambda event: self.scale(event, "rectangle", 4))
        
        canvas.tag_bind(self.info[5], "<Button-1>", lambda event: self.connect(event, self.info[5]))
        canvas.tag_bind(self.info[6], "<Button-1>", lambda event: self.connect(event, self.info[6]))
        canvas.tag_bind(self.info[7], "<Button-1>", lambda event: self.connect(event, self.info[7]))
        canvas.tag_bind(self.info[8], "<Button-1>", lambda event: self.connect(event, self.info[8]))  

class Diamond(Icon):
    def __init__(self, color):
        self.info =[]
        
        self.info.append(canvas.create_polygon((10+ (DIAMOND_SIZE[0] / 2), (RECTANGLE_SIZE[1]+20+CIRCLE_SIZE[1]+20)),
                                             (10, (RECTANGLE_SIZE[1]+20+CIRCLE_SIZE[1]+20) + (DIAMOND_SIZE[1] / 2)),
                                             (10+ (DIAMOND_SIZE[0] / 2), (RECTANGLE_SIZE[1]+20+CIRCLE_SIZE[1]+20)  + DIAMOND_SIZE[1]),
                                             (10+ DIAMOND_SIZE[0], (RECTANGLE_SIZE[1]+20+CIRCLE_SIZE[1]+20) + (DIAMOND_SIZE[1] / 2)),
                                             fill=color,width=4,outline="black"))
        self.coords = canvas.coords(self.info[0])
        
        self.info.append(canvas.create_oval((self.coords[2]-DOT_SIZE,self.coords[1]-DOT_SIZE),
                                              (self.coords[2]+DOT_SIZE,self.coords[1]+DOT_SIZE),
                                              fill="white", outline="black", width=3, state="hidden"))
        self.info.append(canvas.create_oval((self.coords[2]-DOT_SIZE,self.coords[5]-DOT_SIZE),
                                              (self.coords[2]+DOT_SIZE,self.coords[5]+DOT_SIZE),
                                              fill="white", outline="black", width=3, state="hidden"))
        self.info.append(canvas.create_oval((self.coords[6]-DOT_SIZE,self.coords[5]-DOT_SIZE),
                                              (self.coords[6]+DOT_SIZE,self.coords[5]+DOT_SIZE),
                                              fill="white", outline="black", width=3, state="hidden"))
        self.info.append(canvas.create_oval((self.coords[6]-DOT_SIZE,self.coords[1]-DOT_SIZE),
                                              (self.coords[6]+DOT_SIZE,self.coords[1]+DOT_SIZE),
                                              fill="white", outline="black", width=3, state="hidden"))
        
        self.info.append(canvas.create_oval((self.coords[0] -DOT_SIZE, self.coords[1]-DOT_SIZE),
                                              (self.coords[0] +DOT_SIZE, self.coords[1]+DOT_SIZE),
                                              fill="#999900", outline="black", width=3, state="hidden", tag="anchor"))
        self.info.append(canvas.create_oval((self.coords[2] -DOT_SIZE, self.coords[3] -DOT_SIZE),
                                              (self.coords[2] +DOT_SIZE, self.coords[3] +DOT_SIZE),
                                              fill="#999900", outline="black", width=3, state="hidden", tag="anchor"))
        self.info.append(canvas.create_oval((self.coords[4] -DOT_SIZE, self.coords[5] -DOT_SIZE),
                                              (self.coords[4] +DOT_SIZE, self.coords[5] +DOT_SIZE),
                                              fill="#999900", outline="black", width=3, state="hidden", tag="anchor"))
        self.info.append(canvas.create_oval((self.coords[6] -DOT_SIZE, self.coords[7] -DOT_SIZE),
                                              (self.coords[6] +DOT_SIZE, self.coords[7] +DOT_SIZE),
                                              fill="#999900", outline="black", width=3, state="hidden", tag="anchor"))
        
        self.hitbox = [self.coords[2], self.coords[1],
                       self.coords[6], self.coords[5]]
        
        canvas.tag_bind(self.info[0],"<Button-1>",self.click)
        canvas.tag_bind(self.info[0],"<B1-Motion>",lambda event: self.drag(event, "diamond"))
        
        canvas.tag_bind(self.info[1], "<B1-Motion>", lambda event: self.scale(event, "diamond", 1))
        canvas.tag_bind(self.info[2], "<B1-Motion>", lambda event: self.scale(event, "diamond", 2))
        canvas.tag_bind(self.info[3], "<B1-Motion>", lambda event: self.scale(event, "diamond", 3))
        canvas.tag_bind(self.info[4], "<B1-Motion>", lambda event: self.scale(event, "diamond", 4))
        
        canvas.tag_bind(self.info[5], "<Button-1>", lambda event: self.connect(event, self.info[5]))
        canvas.tag_bind(self.info[6], "<Button-1>", lambda event: self.connect(event, self.info[6]))
        canvas.tag_bind(self.info[7], "<Button-1>", lambda event: self.connect(event, self.info[7]))
        canvas.tag_bind(self.info[8], "<Button-1>", lambda event: self.connect(event, self.info[8]))  

class Parallelogram(Icon):
    def __init__(self, color):
        self.info =[]
        
        self.info.append(canvas.create_polygon((10+PARALLELOGRAM_ANGLE, (RECTANGLE_SIZE[1]+20+CIRCLE_SIZE[1]+20+DIAMOND_SIZE[1]+20)),
                                             (10, (RECTANGLE_SIZE[1]+20+CIRCLE_SIZE[1]+20+DIAMOND_SIZE[1]+20)+PARALLELOGRAM_SIZE[1]),
                                             (10+PARALLELOGRAM_SIZE[0]-PARALLELOGRAM_ANGLE, (RECTANGLE_SIZE[1]+20+CIRCLE_SIZE[1]+20+DIAMOND_SIZE[1]+20)+PARALLELOGRAM_SIZE[1]),
                                             (10+PARALLELOGRAM_SIZE[0], (RECTANGLE_SIZE[1]+20+CIRCLE_SIZE[1]+20+DIAMOND_SIZE[1]+20)),
                                             fill=color,width=4,outline="black"))
        self.coords = canvas.coords(self.info[0])
        
        self.info.append(canvas.create_oval((self.coords[2]-DOT_SIZE,self.coords[1]-DOT_SIZE),
                                              (self.coords[2]+DOT_SIZE,self.coords[1]+DOT_SIZE),
                                              fill="white", outline="black", width=3, state="hidden"))
        self.info.append(canvas.create_oval((self.coords[2]-DOT_SIZE,self.coords[3]-DOT_SIZE),
                                              (self.coords[2]+DOT_SIZE,self.coords[3]+DOT_SIZE),
                                              fill="white", outline="black", width=3, state="hidden"))
        self.info.append(canvas.create_oval((self.coords[6]-DOT_SIZE,self.coords[5]-DOT_SIZE),
                                              (self.coords[6]+DOT_SIZE,self.coords[5]+DOT_SIZE),
                                              fill="white", outline="black", width=3, state="hidden"))
        self.info.append(canvas.create_oval((self.coords[6]-DOT_SIZE,self.coords[7]-DOT_SIZE),
                                              (self.coords[6]+DOT_SIZE,self.coords[7]+DOT_SIZE),
                                              fill="white", outline="black", width=3, state="hidden"))
        
        self.info.append(canvas.create_oval((self.coords[0] + ((self.coords[6]-self.coords[0])/2) -DOT_SIZE, self.coords[1]-DOT_SIZE),
                                              (self.coords[0] + ((self.coords[6]-self.coords[0])/2) +DOT_SIZE, self.coords[1]+DOT_SIZE),
                                              fill="#999900", outline="black", width=3, state="hidden", tag="anchor"))
        self.info.append(canvas.create_oval((self.coords[2] + ((self.coords[0]-self.coords[2])/2) -DOT_SIZE, self.coords[1] + ((self.coords[3]-self.coords[1])/2) -DOT_SIZE),
                                              (self.coords[2] + ((self.coords[0]-self.coords[2])/2) +DOT_SIZE, self.coords[1] + ((self.coords[3]-self.coords[1])/2) + DOT_SIZE),
                                              fill="#999900", outline="black", width=3, state="hidden", tag="anchor"))
        self.info.append(canvas.create_oval((self.coords[2] + ((self.coords[4]-self.coords[2])/2) -DOT_SIZE, self.coords[3] -DOT_SIZE),
                                              (self.coords[2] + ((self.coords[4]-self.coords[2])/2) +DOT_SIZE, self.coords[3] + DOT_SIZE),
                                              fill="#999900", outline="black", width=3, state="hidden", tag="anchor"))
        self.info.append(canvas.create_oval((self.coords[4] + ((self.coords[6]-self.coords[4])/2) -DOT_SIZE, self.coords[7] + ((self.coords[5]-self.coords[7])/2) -DOT_SIZE),
                                              (self.coords[4] + ((self.coords[6]-self.coords[4])/2) +DOT_SIZE, self.coords[7] + ((self.coords[5]-self.coords[7])/2) + DOT_SIZE),
                                              fill="#999900", outline="black", width=3, state="hidden", tag="anchor"))
        
        self.hitbox = [self.coords[2], self.coords[1],
                       self.coords[6], self.coords[5]]
        
        canvas.tag_bind(self.info[0],"<Button-1>",self.click)
        canvas.tag_bind(self.info[0],"<B1-Motion>",lambda event: self.drag(event, "parallelogram"))
        
        canvas.tag_bind(self.info[1], "<B1-Motion>", lambda event: self.scale(event, "parallelogram", 1))
        canvas.tag_bind(self.info[2], "<B1-Motion>", lambda event: self.scale(event, "parallelogram", 2))
        canvas.tag_bind(self.info[3], "<B1-Motion>", lambda event: self.scale(event, "parallelogram", 3))
        canvas.tag_bind(self.info[4], "<B1-Motion>", lambda event: self.scale(event, "parallelogram", 4))
        
        canvas.tag_bind(self.info[5], "<Button-1>", lambda event: self.connect(event, self.info[5]))
        canvas.tag_bind(self.info[6], "<Button-1>", lambda event: self.connect(event, self.info[6]))
        canvas.tag_bind(self.info[7], "<Button-1>", lambda event: self.connect(event, self.info[7]))
        canvas.tag_bind(self.info[8], "<Button-1>", lambda event: self.connect(event, self.info[8]))  

class Arrow(Icon):

    
    def __init__(self, color, coordinates):
        self.info =[]
        
        self.info.append(canvas.create_line((coordinates),
                                             fill=color, width=2, arrow="last", arrowshape=(30,30,9)))
        self.coords = canvas.coords(self.info[0])
        print(self.coords)

#For each object, there is a base object that creates a new canvas item when it is clicked
#The math for where it is placed is the same as the __init__ variables above
#when it is clicked, is creates another circle with the color from the constant at the top

base_oval = canvas.create_oval(10,10,10+CIRCLE_SIZE[0],
                               10+CIRCLE_SIZE[1],
                               fill=BASE_COLOR, width=4, outline="black")
canvas.tag_bind(base_oval,"<Button-1>",lambda self: Circle(COLOR))

base_rectangle = canvas.create_rectangle(10,(CIRCLE_SIZE[1]+20),
                                        10+RECTANGLE_SIZE[0],(CIRCLE_SIZE[1]+20)+RECTANGLE_SIZE[1],
                                        fill=BASE_COLOR, width=4, outline="black")
canvas.tag_bind(base_rectangle,"<Button-1>",lambda self: Rectangle(COLOR))

base_diamond = canvas.create_polygon((10+ (DIAMOND_SIZE[0] / 2), (RECTANGLE_SIZE[1]+20+CIRCLE_SIZE[1]+20)),
                                             (10, (RECTANGLE_SIZE[1]+20+CIRCLE_SIZE[1]+20) + (DIAMOND_SIZE[1] / 2)),
                                             (10+ (DIAMOND_SIZE[0] / 2), (RECTANGLE_SIZE[1]+20+CIRCLE_SIZE[1]+20)  + DIAMOND_SIZE[1]),
                                             (10+ DIAMOND_SIZE[0], (RECTANGLE_SIZE[1]+20+CIRCLE_SIZE[1]+20) + (DIAMOND_SIZE[1] / 2)),
                                             fill=BASE_COLOR, width=4, outline="black")
canvas.tag_bind(base_diamond,"<Button-1>",lambda self: Diamond(COLOR))

base_parallelogram = canvas.create_polygon((10+PARALLELOGRAM_ANGLE, (RECTANGLE_SIZE[1]+20+CIRCLE_SIZE[1]+20+DIAMOND_SIZE[1]+20)),
                                             (10, (RECTANGLE_SIZE[1]+20+CIRCLE_SIZE[1]+20+DIAMOND_SIZE[1]+20)+PARALLELOGRAM_SIZE[1]),
                                             (10+PARALLELOGRAM_SIZE[0]-PARALLELOGRAM_ANGLE, (RECTANGLE_SIZE[1]+20+CIRCLE_SIZE[1]+20+DIAMOND_SIZE[1]+20)+PARALLELOGRAM_SIZE[1]),
                                             (10+PARALLELOGRAM_SIZE[0], (RECTANGLE_SIZE[1]+20+CIRCLE_SIZE[1]+20+DIAMOND_SIZE[1]+20)),
                                             fill=BASE_COLOR, width=4, outline="black")
canvas.tag_bind(base_parallelogram,"<Button-1>",lambda self: Parallelogram(COLOR))


window.mainloop()
Reply
#2
There are several videos on this subject, google "arrows for tlinter".
Reply
#3
I had fun playing with your code. I wrote a version that uses more class inheritance. It isn't a full implementation, but you might find it interesting.
from abc import abstractmethod
import tkinter as tk
import types


class Shape:
    """Base class for shapes."""
    def __init__(self, canvas, center, size, outline="black", fill=''):
        """
        canvas  - Canvas on which I am drawn.
        center  - (x, y) coordinates of my center.
        size    - (width, height) of my bounding box
        outline - Color of my outline.  Use '' for no outline.
        fill    - Color used to fill shape.  Use '' for no fill.
        """
        self.id = None
        self.canvas = canvas
        self._center = center
        self._size = size
        self.outline = outline
        self.fill = fill
        self.create()
        self.canvas.tag_bind(self.id, "<B1-Motion>", self.drag)
        self.canvas.tag_bind(self.id, "<Button-1>", self.click)
        self.drag_offset = (0, 0)
  
    @abstractmethod
    def create(self):
        """Create canvas object."""

    @property
    def coords(self):
        """Return coords for shape.
        This coords returns Upper left and lower right coordinates.  It works for ovals and rectangles.
        Other shapes may have different coordinates.
        """
        x, y = self._center
        w, h = self._size
        return (x-w/2, y-h/2, x+w/2, y+h/2)

    @property
    def center(self):
        """Return (x, y) coordinates of center."""
        return self._center

    @center.setter
    def center(self, xy):
        """Set (x, y) coordinates of center.  Moves shape."""
        self._center = xy
        self.canvas.coords(self.id, self.coords)

    @property
    def size(self):
        """Return (width, height)"""
        return self._size

    @size.setter
    def size(self, wh):
        """Set (width, height).  Resizes shape."""
        self._size = wh
        self.canvas.coords(self.id, self.coords)

    @property
    def x(self):
        """Return center x coordinate."""
        return self._center[0]

    @property
    def y(self):
        """Return center y coordinate."""
        return self._center[1]

    def hide(self):
        """Hide canvas object."""
        self.canvas.itemconfigure(self.id, state='hidden')

    def show(self):
        """Show canvas object."""
        self.canvas.itemconfigure(self.id, state='normal')

    def drag(self, event):
        """Mouse drag coallback.  Move shape to follow mouse."""
        self.center = (event.x + self.drag_offset[0], event.y + self.drag_offset[1])

    def click(self, event):
        """Mouse click callback."""
        self.drag_offset = (self.x - event.x, self.y - event.y)


class Handle(Shape):
    """Grab handle for resizing an Icon."""
    def __init__(self, parent, center):
        self.parent = parent
        super().__init__(
            canvas=parent.canvas,
            center=center,
            size=(10, 10),
            outline='',
            fill='red')

    def create(self):
        self.id = self.canvas.create_oval(self.coords, outline=self.outline, fill=self.fill)
    
    def drag(self, event):
        """Reshape parent using my new position."""
        self._center = (event.x, event.y)
        self.parent.reshape()


class Icon(Shape):
    """A shape with reshaping handles."""
    def __init__(self, canvas, center, size=(200, 200), outline="black", fill='white'):
        super().__init__(canvas, center, size, outline, fill)
        self.handles = [Handle(self, (0, 0)) for _ in range(4)]
        self.update_handles()
        self.is_factory = False

    def update_handles(self):
        """Update locations of the resize handles."""
        x, y = self._center
        w, h = self._size
        self.handles[0].center = (x, y-h/2)
        self.handles[1].center = (x+w/2, y)
        self.handles[2].center = (x, y+h/2)
        self.handles[3].center = (x-w/2, y)

    def reshape(self):
        """Resape the object based on location of resize handles."""
        x1 = self.handles[3].x
        y1 = self.handles[0].y
        x2 = self.handles[1].x
        y2 = self.handles[2].y
        self._center = ((x1 + x2) / 2, (y1 + y2) / 2)
        self._size = (max(0, x2 - x1), max(0, y2 - y1))
        self.canvas.coords(self.id, self.coords)
        self.update_handles()

    def factory(self):
        """Turn icon into an icon factory that makes duplicates of self."""
        self.is_factory = True
        for handle in self.handles:
            handle.hide()

    def click(self, event):
        """Mouse click callback."""
        if self.is_factory:
            self.__class__(self.canvas, self.center, self.size, self.outline, self.fill)
        else:
            super().click(event)

    def drag(self, event):
        """Mouse drag callback."""
        if not self.is_factory:
            super().drag(event)
            self.update_handles()


class Oval(Icon):
    """An oval shaped Icon."""
    def create(self):
        self.id = self.canvas.create_oval(self.coords, outline=self.outline, fill=self.fill)


class Rectangle(Icon):
    """A rectangular Icon."""
    def create(self):
        self.id = self.canvas.create_rectangle(self.coords, outline=self.outline, fill=self.fill)


class Diamond(Icon):
    """A diamond shaped icon."""
    def create(self):
        self.id = self.canvas.create_polygon(self.coords, outline=self.outline, fill=self.fill)

    @property
    def coords(self):
        """Return diamond polygon points."""
        x, y = self.center
        w, h = self.size
        return (x, y-h/2, x+w/2, y, x, y+h/2, x-w/2, y)


root = tk.Tk()
canvas = tk.Canvas(root, width=600, height=600)
canvas.pack()

Oval(canvas, center=(50,100), size=(50, 50), fill="green").factory()
Rectangle(canvas, center=(50,200), size=(50, 50), fill="blue").factory()
Diamond(canvas, center=(50,300), size=(50, 50), fill="yellow").factory()

root.mainloop()
New shapes can be added with very little code. Standard shapes only require a create() method. Other shapes might require a new coords method, maybe even an update_handles method. For example, this adds a parallelogram Icon:
class Parallelogram(Diamond):
    """A parallelogram shaped Icon."""
    @property
    def coords(self):
        """Return parallelogram polygon points."""
        x, y = self.center
        w, h = self.size
        x1 = x - (w + h)/2
        x2 = x1 + w + h
        y1 = y - h / 2
        y2 = y1 + h
        return (x2, y1, x2-h, y2, x1, y2, x1+h, y1)
            
    def update_handles(self):
        """Update handle coordinates."""
        coords = [iter(self.coords)] * 2
        points = list(zip(*coords))
        a = points[-1]
        for handle, b in zip(self.handles, points):
            handle.center = ((a[0] + b[0]) / 2, (a[1] + b[1]) / 2)
            a = b
A special update_handles() was required because the resize handles are not centered on the sides of the bounding box.

This adds a parallelogram factory.
Parallelogram(canvas, center=(50,400), size=(50, 30), fill="pink").factory()
bluebouncyball likes this post
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Label Maker FPDF, Reportlab jamesaarr 1 2,672 Aug-09-2021, 11:57 PM
Last Post: Pedroski55
  Sentence maker help bidoofis 2 2,503 Feb-08-2019, 03:59 AM
Last Post: bidoofis
  Help with image maker program SteampunkMaverick12 2 3,174 Apr-23-2018, 07:00 PM
Last Post: SteampunkMaverick12

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020