# -*- encoding: utf-8 -*- import math import queue import time import tkinter as tk from tkinter import * from tkinter import scrolledtext import tkinter.messagebox import tkinter.simpledialog from data import * BLUE = "#0080FF" BLACK = "#000000" RED = "#FFAAAA" YELLOW = "#FFFACD" LINE = '#c8d2c8' GREY = '#070b19' GREEN = '#5ba585' NODE = '#33a8cd' ZERO = 'gold' nodes = 6#设置初始节点数量 d = Data(1, nodes) root = tk.Tk() #设置主界面 root.title("旅行商问题") # 设置标题 root.geometry(f"{d.canvas_len + 370}x{d.canvas_len + 100}") # 设置大小 root.resizable(0,0) # 设置不能调整显示边框 frame = tk.Frame(root, padx=20, pady=20, width=d.canvas_len, height=d.canvas_len + 50) frame.grid() # 绘制显示框 frame_top = tk.Frame(frame, width=d.canvas_len, height=50) frame_top.grid(row=0, column=0) # 绘制信息输出栏 cv = canvas = tk.Canvas(frame, bg='white', bd=2, relief="sunken", width=d.canvas_len, height=d.canvas_len) canvas.grid(row=1, column=0)#放置绘图Canvas画布 frame_right = tk.Frame(root, bg=RED, width=300, height=d.canvas_len + 50)# 右边栏 frame_right.grid(row=0, column=1) # 右边栏 def dir2(x, y): # 计算两点间距离 return (x[0] - y[0]) * (x[0] - y[0]) + (x[1] - y[1]) * (x[1] - y[1]) main_menu = tk.Menu(root) root.config(menu=main_menu) def node_edge(n1, n2, no=1): # 根据点的编号找边的编号 if n1 > n2:#为了防止充分编号,让n1小于n2 n1, n2 = n2, n1 for e in d.Edges:#遍历所有路径,找到符合要求的返回 if e[1] == n1 and e[2] == n2 and e[8] == no: return e[0] return -1#若找不到,则返回-1 class Graph: def draw(self): cv.delete('all')#清空画布 dis, tim = self.edges_show(cv, d.Edges)#绘制边 self.lab_show(frame_top, len(d.Nodes), tim, dis)#显示边信息 self.nodes_show(cv)#绘制点 cv.update()#更新画布 def lab_show(self, frame: tk.Frame, nodes: int, time: int, distance: int): labs = ["节点数目", "时间", "距离(成本)"] x0 = d.canvas_len // 3 for i in range(len(labs)): lab1 = tk.Label(frame, text=labs[i], fg=BLACK, font=('微软雅黑', 12)) if i == 0: lab2 = tk.Label(frame, text=f"{nodes}", bg=BLUE, fg=BLACK, font=('微软雅黑', 12)) elif i == 1: lab2 = tk.Label(frame, text=f"{time}", bg=BLUE, fg=BLACK, font=('微软雅黑', 12)) else: lab2 = tk.Label(frame, text=f"{distance}", bg=BLUE, fg=BLACK, font=('微软雅黑', 12)) lab1.place(x=10 + x0 * i, y=10, width=80, height=30) lab2.place(x=10 + x0 * i + 80, y=10, width=80, height=30) lab1 = tk.Label(frame, text='分钟', fg=BLACK, font=('微软雅黑', 12)) lab1.place(x=170 + x0, y=10, width=50, height=30) lab1 = tk.Label(frame, text='千米', fg=BLACK, font=('微软雅黑', 12)) lab1.place(x=170 + x0 * 2, y=10, width=50, height=30) def nodes_show(self, cv: tk.Canvas): """ 显示节点 :param cv: tkcanvas对象 :return: """ COL = NODE# 初始化节点颜色 for i in d.Nodes:# 遍历每一个节点 x, y = i[3] #记录该节点坐标 if i[2] == 1 or i[2] == 2:# 如果节点被标记,则将节点标为红色 COL = RED elif i[0] == 0:# 如果节点为起始节点,则将节点标记为特殊颜色 COL = ZERO elif i[2] == 5:# 如果节点已经被访问,则将节点标记(用户路径功能) COL = 'gold' else:#否则则使用初始颜色 COL = NODE if i[4] == 1:# 如果是必经节点,则加黑色边框 w = 1 else:# 如果是非必经节点,则没有边框 w = 0 # 根据坐标,圆的颜色与是否有边框绘制圆圈来表示节点 cv.create_oval(x - 20, y - 20, x + 20, y + 20, fill=COL, width=w,)# outline='#ee28a3') if d.source == 1:# 根据节点信息将节点的标签显示在节点上 cv.create_text(x, y, text=f'{i[1]}', fill=GREY, font=('华文楷体', 24)) else: cv.create_text(x, y, text=f'{i[1]}', fill=GREY, font=('华文楷体', 18)) cv.update() def edges_show(self, cv: tk.Canvas, edges: d.Edges): """ 显示链接 :param cv: tkcanvas对象 :return: """ distance = 0 # 初始化被标记的线的总路径 time1 = 0 # 初始化被标记的线的总时间 for edge in edges: node_1 = d.node_co(edge[1]) # 链接线编号对应的坐标1 node_2 = d.node_co(edge[2]) # 链接线编号对应的坐标2 if edge[8] == 2 and edge[3] == 1: # 若该链接为两点间第二条连线 self.more_edge_show(2, edge) elif edge[8] == 3 and edge[3] == 1: # 若为两点间第3条连线 self.more_edge_show(3, edge) if edge[8] == 1 and edge[3] == 1:# 若为两点间第1条连线且该连接存在 self.edge_info(cv, f'{edge[5]} {edge[6]} {edge[7]}', node_1, node_2) point = [node_1, node_2] if edge[4] == 1 and edge[8] == 1 and edge[3] == 1:# 如果路径被标记,则用绿色绘制 cv.create_line(point, fill=GREEN, width=4) distance += edge[6]# 更新总路径 time1 += edge[7]# 更新总时间 elif edge[8] == 1 and edge[3] == 1:# 如果是节点之间的第一条线,则用正常颜色标记 cv.create_line(point, fill=LINE, width=2) if d.Nodes[edge[1]][2] + d.Nodes[edge[2]][2] == 3:# 若该线被用户选中,则用蓝色虚线标记 cv.create_line(point, fill=BLUE, width=5, dash=(4, 14)) return (distance, time1)# 返回标记路径的总时间与路程 def more_edge_show(self, no, E): # print(n1,n2) n1 = E[1] n2 = E[2] node_1 = d.node_co(n1) node_2 = d.node_co(n2) (n1x, n1y) = node_1 (n2x, n2y) = node_2 n0x = (n1x + n2x) / 2 n0y = (n1y + n2y) / 2 (Ox, Oy) = d.center x = 30 # 新路径距原路径距离 if no == 2: if not Ox - n0x == 0: k = (Oy - n0y) / (Ox - n0x) n3x = n0x + x * math.cos(math.atan(k)) n3y = n0y + x * math.sin(math.atan(k)) else: n3x = n0x n3y = n0y + x elif no == 3: if not Ox - n0x == 0: k = (Oy - n0y) / (Ox - n0x) n3x = n0x - x * math.cos(math.atan(k)) n3y = n0y - x * math.sin(math.atan(k)) else: n3x = n0x n3y = n0y - x self.moreedge_info(cv, f'{E[5]} {E[6]} {E[7]}', node_1, node_2, (n3x, n3y), no) cv.create_line((n1x, n1y), (n3x, n3y), fill=LINE, width=2) cv.create_line((n2x, n2y), (n3x, n3y), fill=LINE, width=2) def node_edge(self, a, b): # 根据点的编号找边的编号 if a > b: x = a a = b b = x return int(a * (2 * len(d.Nodes) - a - 3) / 2 + b - 1) def moreedge_info(self, cv: tk.Canvas, info: str, n1: tuple, n2: tuple, n3: tuple, no): if d.edgeinfo == 0: return if no == 2: flag = 1 else: flag = -1 x = n3[0] y = n3[1] if (n2[1] - n1[1]) != 0: # 斜率为非0度时的显示 reat = math.atan((n2[0] - n1[0]) / (n2[1] - n1[1])) # 根据斜率计算弧度 ang = round(math.degrees(reat)) + 90 # 根据弧度转角度 if ang > 89: # 斜度大于90度调整坐标 cv.create_text(x + 15 * flag, y, text=f'{info}', fill="black", font=('微软雅黑', 12), angle=ang) # cv.create_text(x, y, text=f'{i[1]}', fill="red", font=('微软雅黑', 24)) else: cv.create_text(x, y + 15 * flag, text=f'{info}', fill="black", font=('微软雅黑', 12), angle=ang) else: ang = round(math.degrees(0)) # 根据弧度转角度 cv.create_text(x - 30 * flag, y + 10 * flag, text=f'{info}', fill="black", font=('微软雅黑', 12), angle=ang) def edge_info(self, cv: tk.Canvas, info: str, n1: tuple, n2: tuple): ''' 取连线中点坐标,显示info的信息 :param info: 连线信息 :param center: 画布中心点 :param n1: 连线端点1 :param n2: 连线端点2 :return: None ''' if d.edgeinfo == 0: #若设置为不显示则直接返回 return x = (n1[0] - n2[0]) // 2 + n2[0]#计算中点x坐标 y = (n1[1] - n2[1]) // 2 + n2[1]#计算中点y坐标 if (n2[1] - n1[1]) != 0: # 斜率为非0度时的显示 reat = math.atan((n2[0] - n1[0]) / (n2[1] - n1[1])) # 根据斜率计算弧度 ang = round(math.degrees(reat)) + 90 # 根据弧度转角度 if ang > 89: # 斜度大于90度调整坐标 text_x, text_y = x + 15, y else:# 斜度大于90度调整坐标 text_x, text_y = x, y + 15 else:# 斜率为0度时的显示 ang = round(math.degrees(0)) # 根据弧度转角度 text_x, text_y = x - 30, y + 10 cv.create_text(text_x, text_y, text=f'{info}', fill="black", font=('微软雅黑', 12), angle=ang)#根据信息显示文字 G = Graph() if __name__ == '__main__': # G = Graph() G.draw() mainloop()