Files
JITToolBox/utils/function.py
2025-06-29 03:04:04 +08:00

241 lines
8.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Copyright (c) 2025 Jeffrey Hsu - JITToolBox
# #
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import io
import os
import re
import shutil
import sys
import tempfile
from typing import Optional
import matplotlib
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QWidget
from matplotlib import pyplot as plt
from qfluentwidgets import InfoBar, InfoBarPosition
def format_ranges(nums):
"""格式化区间,支持特殊标记"""
if not nums:
return ""
def parse_value(val):
"""解析值,返回主数值和次要标记"""
if isinstance(val, int):
return val, ""
match = re.match(r"(\d+)[(](.*?)[)]", str(val))
if match:
return int(match.group(1)), match.group(2)
return int(val), ""
# 排序规则:主数值优先,次要标记其次
sorted_nums = sorted(nums, key=lambda x: parse_value(x))
result = []
start = sorted_nums[0]
end = sorted_nums[0]
def is_continuous(val1, val2):
"""判断两个值是否连续"""
main1, sub1 = parse_value(val1)
main2, sub2 = parse_value(val2)
# 主数值连续,且次要标记为空(特殊标记视为不连续)
return main2 == main1 + 1 and not sub2
for i in range(1, len(sorted_nums)):
if is_continuous(end, sorted_nums[i]):
end = sorted_nums[i]
else:
# 处理一个区间
if parse_value(start)[0] == parse_value(end)[0]: # 单值或特殊标记
result.append(str(start))
elif parse_value(end)[0] - parse_value(start)[0] >= 2: # 连续区间
result.append(f"{parse_value(start)[0]}~{parse_value(end)[0]}")
else: # 非连续的单值
result.extend(map(str, [start, end]))
start = end = sorted_nums[i]
# 添加最后一段区间
if parse_value(start)[0] == parse_value(end)[0]: # 单值或特殊标记
result.append(str(start))
elif parse_value(end)[0] - parse_value(start)[0] >= 2: # 连续区间
result.append(f"{parse_value(start)[0]}~{parse_value(end)[0]}")
else: # 非连续的单值
result.extend(map(str, [start, end]))
return "".join(result)
def support_version_str(lst: list[str]) -> str:
"""支持的版本字符串"""
if len(lst) == 1:
return lst[0]
elif len(lst) == 2:
return f"{lst[0]}{lst[1]}"
else:
return f"{lst[0]}~{lst[-1]}"
def get_rank(score: int | float) -> str:
"""根据分数获取等级"""
if score >= 0.9:
return "优秀"
elif score >= 0.8:
return "良好"
elif score >= 0.7:
return "中等"
elif score >= 0.6:
return "及格"
else:
return "不及格"
def min_score_people_name(data: list, kpi_number: int, start_index: int, end_index: int) -> list[tuple[str]]:
"""获取最低分人名"""
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13
# 20060510XX 覃XX 40.5 45 49 0.738 16 17 0 0.330 24.5 15 14 0.695
remove_indices = [5, 9, 13, 17, 21]
# 更新后的数据
# 0 1 2 3 4 5 6 7 8 9 10
# 20060510XX 覃XX 40.5 45 49 16 17 0 24.5 15 14
index = 2 + kpi_number * 3
updated_data = [[item for j, item in enumerate(row) if j not in remove_indices] for row in data]
result = [(), (), ()]
min_scores = [min(updated_data[start_index:end_index],
key=lambda x: x[index + i] if x[index + i] is not None else 0)[index + i] for i in range(3)]
for row in updated_data[start_index:end_index]:
for i in range(3):
if row[index + i] == min_scores[i]:
result[i] += (row[1],)
return result
def get_class_index_range(lst: list[int], num: int):
"""获取班级的索引范围"""
if num < 0 or num > len(lst):
raise ValueError("OutOfRange")
# 计算前num-1项和加一
first_value = sum(lst[:num])
# 计算前num项和加一
second_value = sum(lst[:num + 1]) if num < len(lst) else "OutOfRange"
return (first_value, second_value)
def check_version(version, compatible_versions):
# 将版本字符串转换为数字列表以便比较
version_nums = [int(v) for v in version.split('.')]
min_version_nums = [int(v) for v in compatible_versions[0].split('.')]
max_version_nums = [int(v) for v in compatible_versions[-1].split('.')]
# 比较版本
if version_nums < min_version_nums:
return False, "不支持低于{}的版本,如需尝试生成请勾选‘关闭兼容性检查’".format(compatible_versions[0])
elif version_nums > max_version_nums:
return True, "版本过高可能会导致兼容性问题"
else:
return True, ""
def gen_picture(data: list[list[str]], kpi_number: int, start_index: int, end_index: int):
# 设置全局字体
matplotlib.rcParams['font.family'] = ['Times New Roman', 'KaiTi']
matplotlib.rcParams['font.size'] = 10
# 将厘米转换为英寸
width_in_inches = 8 * 0.393701
height_in_inches = 6 * 0.393701
# 创建一个图形和三个子图
fig, axs = plt.subplots(1, kpi_number, figsize=(width_in_inches * kpi_number, height_in_inches))
# 遍历每个指定的索引来生成散点图
for ax, index in zip(axs, range(kpi_number)):
# 处理数据
data_indices = [5, 9, 13, 17, 21]
update_data = [[float(item) for j, item in enumerate(row) if j in data_indices] for row in data]
x = [i for i in range(end_index - start_index)]
y = [row[index] for row in update_data[start_index:end_index]]
# 绘制散点图
ax.scatter(x, y, label=f'目标{index + 1}达成值', s=10)
# 添加期望值直线
ax.axhline(y=0.65, color='r', linestyle='-', label='期望值')
# 设置轴标签
ax.set_xlabel('学生学号')
ax.set_ylabel('达成值')
# 设置y轴范围和刻度
ax.set_ylim(0, 1)
ax.set_yticks([i * 0.2 for i in range(6)])
# 设置图例
ax.legend(loc='upper center', bbox_to_anchor=(0.5, -0.35), ncol=2, frameon=False)
# 设置坐标原点为(0, 0)
ax.set_xlim(x.index(x[0]), None)
# 调整子图间距
plt.tight_layout()
# 创建一个内存中的文件对象
buf = io.BytesIO()
# 保存图形
plt.savefig(buf, format='png', dpi=300, bbox_inches='tight')
# 将文件对象转换为字节对象
res = buf.getvalue()
buf.close()
return res
def resource_path(relative_path: str) -> str:
if hasattr(sys, '_MEIPASS'):
base_path = sys._MEIPASS
else:
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
def open_template(file_name: str, widget: QWidget) -> None:
"""将模板文件复制到临时目录并打开"""
file_path = resource_path("template/" + file_name)
if not os.path.exists(file_path):
raise FileNotFoundError(f"Template file '{file_name}' not found.")
# 复制到临时目录
tmp_dir = tempfile.gettempdir()
tmp_file_path = os.path.join(tmp_dir, file_name)
shutil.copy(file_path, tmp_file_path)
# 打开文件
if sys.platform.startswith('win'):
os.startfile(tmp_file_path)
elif sys.platform.startswith('darwin'):
os.system(f'open "{tmp_file_path}"')
else:
os.system(f'xdg-open "{tmp_file_path}"')
InfoBar.info(
title='已打开文件',
content="编辑后请将文件另存为,保存至其他目录",
orient=Qt.Horizontal,
isClosable=True,
position=InfoBarPosition.TOP_RIGHT,
duration=2000,
parent=widget
)
RELEASE_ENV = getattr(sys, 'frozen', False)