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