You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

239 lines
9.6 KiB

# -*- 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()