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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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