2024-05-18 20:56:23 +08:00
|
|
|
|
from io import BytesIO
|
|
|
|
|
|
from pathlib import Path
|
2024-12-10 19:49:11 +08:00
|
|
|
|
import random
|
2025-06-16 09:11:41 +08:00
|
|
|
|
import sys
|
2024-05-18 20:56:23 +08:00
|
|
|
|
|
2025-02-04 02:15:21 +08:00
|
|
|
|
from pydantic import BaseModel, Field
|
2024-05-18 20:56:23 +08:00
|
|
|
|
|
|
|
|
|
|
from ._build_image import BuildImage
|
|
|
|
|
|
|
2025-06-16 09:11:41 +08:00
|
|
|
|
if sys.version_info >= (3, 11):
|
|
|
|
|
|
from enum import StrEnum
|
|
|
|
|
|
else:
|
|
|
|
|
|
from strenum import StrEnum
|
|
|
|
|
|
|
2024-05-18 20:56:23 +08:00
|
|
|
|
|
|
|
|
|
|
class MatType(StrEnum):
|
|
|
|
|
|
LINE = "LINE"
|
|
|
|
|
|
"""折线图"""
|
|
|
|
|
|
BAR = "BAR"
|
|
|
|
|
|
"""柱状图"""
|
|
|
|
|
|
BARH = "BARH"
|
|
|
|
|
|
"""横向柱状图"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BuildMatData(BaseModel):
|
|
|
|
|
|
mat_type: MatType
|
|
|
|
|
|
"""类型"""
|
2025-02-04 02:15:21 +08:00
|
|
|
|
data: list[int | float] = Field(default_factory=list)
|
2024-05-18 20:56:23 +08:00
|
|
|
|
"""数据"""
|
|
|
|
|
|
x_name: str | None = None
|
|
|
|
|
|
"""X轴坐标名称"""
|
|
|
|
|
|
y_name: str | None = None
|
|
|
|
|
|
"""Y轴坐标名称"""
|
2025-02-04 02:15:21 +08:00
|
|
|
|
x_index: list[str] = Field(default_factory=list)
|
2024-05-18 20:56:23 +08:00
|
|
|
|
"""显示轴坐标值"""
|
2025-02-04 02:15:21 +08:00
|
|
|
|
y_index: list[int | float] = Field(default_factory=list)
|
2024-05-18 20:56:23 +08:00
|
|
|
|
"""数据轴坐标值"""
|
2024-05-27 16:09:24 +08:00
|
|
|
|
space: tuple[int, int] = (20, 20)
|
2024-05-18 20:56:23 +08:00
|
|
|
|
"""坐标值间隔(X, Y)"""
|
|
|
|
|
|
rotate: tuple[int, int] = (0, 0)
|
|
|
|
|
|
"""坐标值旋转(X, Y)"""
|
|
|
|
|
|
title: str | None = None
|
|
|
|
|
|
"""标题"""
|
|
|
|
|
|
font: str = "msyh.ttf"
|
|
|
|
|
|
"""字体"""
|
|
|
|
|
|
font_size: int = 15
|
|
|
|
|
|
"""字体大小"""
|
|
|
|
|
|
display_num: bool = True
|
|
|
|
|
|
"""是否在点与柱状图顶部显示数值"""
|
|
|
|
|
|
is_grid: bool = False
|
|
|
|
|
|
"""是否添加栅格"""
|
|
|
|
|
|
background_color: tuple[int, int, int] | str = (255, 255, 255)
|
|
|
|
|
|
"""背景颜色"""
|
|
|
|
|
|
background: Path | bytes | None = None
|
|
|
|
|
|
"""背景图片"""
|
2025-02-04 02:15:21 +08:00
|
|
|
|
bar_color: list[str] = Field(default_factory=lambda: ["*"])
|
2024-05-18 20:56:23 +08:00
|
|
|
|
"""柱状图柱子颜色, 多个时随机, 使用 * 时七色随机"""
|
|
|
|
|
|
padding: tuple[int, int] = (50, 50)
|
|
|
|
|
|
"""图表上下左右边距"""
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-02-04 04:18:54 +08:00
|
|
|
|
class BuildMat:
|
2024-05-18 20:56:23 +08:00
|
|
|
|
"""
|
|
|
|
|
|
针对 折线图/柱状图,基于 BuildImage 编写的 非常难用的 自定义画图工具
|
|
|
|
|
|
目前仅支持 正整数
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
class InitGraph(BaseModel):
|
|
|
|
|
|
mark_image: BuildImage
|
|
|
|
|
|
"""BuildImage"""
|
|
|
|
|
|
x_height: int
|
2024-05-27 16:09:24 +08:00
|
|
|
|
"""横坐标开始高度"""
|
|
|
|
|
|
y_width: int
|
|
|
|
|
|
"""纵坐标开始宽度"""
|
2024-05-18 20:56:23 +08:00
|
|
|
|
x_point: list[int]
|
|
|
|
|
|
"""横坐标坐标"""
|
2024-05-27 16:09:24 +08:00
|
|
|
|
y_point: list[int]
|
|
|
|
|
|
"""纵坐标坐标"""
|
2024-05-18 20:56:23 +08:00
|
|
|
|
graph_height: int
|
|
|
|
|
|
"""坐标轴高度"""
|
|
|
|
|
|
|
|
|
|
|
|
class Config:
|
|
|
|
|
|
arbitrary_types_allowed = True
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, mat_type: MatType) -> None:
|
|
|
|
|
|
self.line_length = 760
|
|
|
|
|
|
self._x_padding = 0
|
|
|
|
|
|
self._y_padding = 0
|
|
|
|
|
|
self.build_data = BuildMatData(mat_type=mat_type)
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def x_name(self) -> str | None:
|
|
|
|
|
|
return self.build_data.x_name
|
|
|
|
|
|
|
|
|
|
|
|
@x_name.setter
|
|
|
|
|
|
def x_name(self, data: str) -> str | None:
|
|
|
|
|
|
self.build_data.x_name = data
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def y_name(self) -> str | None:
|
|
|
|
|
|
return self.build_data.y_name
|
|
|
|
|
|
|
|
|
|
|
|
@y_name.setter
|
|
|
|
|
|
def y_name(self, data: str) -> str | None:
|
|
|
|
|
|
self.build_data.y_name = data
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def data(self) -> list[int | float]:
|
|
|
|
|
|
return self.build_data.data
|
|
|
|
|
|
|
|
|
|
|
|
@data.setter
|
|
|
|
|
|
def data(self, data: list[int | float]):
|
|
|
|
|
|
self._check_value(data, self.build_data.y_index)
|
|
|
|
|
|
self.build_data.data = data
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def x_index(self) -> list[str]:
|
|
|
|
|
|
return self.build_data.x_index
|
|
|
|
|
|
|
|
|
|
|
|
@x_index.setter
|
|
|
|
|
|
def x_index(self, data: list[str]):
|
|
|
|
|
|
self.build_data.x_index = data
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def y_index(self) -> list[int | float]:
|
|
|
|
|
|
return self.build_data.y_index
|
|
|
|
|
|
|
|
|
|
|
|
@y_index.setter
|
|
|
|
|
|
def y_index(self, data: list[int | float]):
|
|
|
|
|
|
# self._check_value(self.build_data.data, data)
|
|
|
|
|
|
data.sort()
|
|
|
|
|
|
self.build_data.y_index = data
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def space(self) -> tuple[int, int]:
|
|
|
|
|
|
return self.build_data.space
|
|
|
|
|
|
|
|
|
|
|
|
@space.setter
|
|
|
|
|
|
def space(self, data: tuple[int, int]):
|
|
|
|
|
|
self.build_data.space = data
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def rotate(self) -> tuple[int, int]:
|
|
|
|
|
|
return self.build_data.rotate
|
|
|
|
|
|
|
|
|
|
|
|
@rotate.setter
|
|
|
|
|
|
def rotate(self, data: tuple[int, int]):
|
|
|
|
|
|
self.build_data.rotate = data
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def title(self) -> str | None:
|
|
|
|
|
|
return self.build_data.title
|
|
|
|
|
|
|
|
|
|
|
|
@title.setter
|
|
|
|
|
|
def title(self, data: str):
|
|
|
|
|
|
self.build_data.title = data
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def font(self) -> str:
|
|
|
|
|
|
return self.build_data.font
|
|
|
|
|
|
|
|
|
|
|
|
@font.setter
|
|
|
|
|
|
def font(self, data: str):
|
|
|
|
|
|
self.build_data.font = data
|
|
|
|
|
|
|
|
|
|
|
|
# @property
|
|
|
|
|
|
# def font_size(self) -> int:
|
|
|
|
|
|
# return self.build_data.font_size
|
|
|
|
|
|
|
|
|
|
|
|
# @font_size.setter
|
|
|
|
|
|
# def font_size(self, data: int):
|
|
|
|
|
|
# self.build_data.font_size = data
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def display_num(self) -> bool:
|
|
|
|
|
|
return self.build_data.display_num
|
|
|
|
|
|
|
|
|
|
|
|
@display_num.setter
|
|
|
|
|
|
def display_num(self, data: bool):
|
|
|
|
|
|
self.build_data.display_num = data
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def is_grid(self) -> bool:
|
|
|
|
|
|
return self.build_data.is_grid
|
|
|
|
|
|
|
|
|
|
|
|
@is_grid.setter
|
|
|
|
|
|
def is_grid(self, data: bool):
|
|
|
|
|
|
self.build_data.is_grid = data
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def background_color(self) -> tuple[int, int, int] | str:
|
|
|
|
|
|
return self.build_data.background_color
|
|
|
|
|
|
|
|
|
|
|
|
@background_color.setter
|
|
|
|
|
|
def background_color(self, data: tuple[int, int, int] | str):
|
|
|
|
|
|
self.build_data.background_color = data
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def background(self) -> Path | bytes | None:
|
|
|
|
|
|
return self.build_data.background
|
|
|
|
|
|
|
|
|
|
|
|
@background.setter
|
|
|
|
|
|
def background(self, data: Path | bytes):
|
|
|
|
|
|
self.build_data.background = data
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def bar_color(self) -> list[str]:
|
|
|
|
|
|
return self.build_data.bar_color
|
|
|
|
|
|
|
|
|
|
|
|
@bar_color.setter
|
|
|
|
|
|
def bar_color(self, data: list[str]):
|
|
|
|
|
|
self.build_data.bar_color = data
|
|
|
|
|
|
|
|
|
|
|
|
def _check_value(
|
|
|
|
|
|
self,
|
|
|
|
|
|
y: list[int | float],
|
|
|
|
|
|
y_index: list[int | float] | None = None,
|
|
|
|
|
|
x_index: list[int | float] | None = None,
|
|
|
|
|
|
):
|
|
|
|
|
|
"""检查值合法性
|
|
|
|
|
|
|
|
|
|
|
|
参数:
|
|
|
|
|
|
y: 坐标值
|
|
|
|
|
|
y_index: y轴坐标值
|
|
|
|
|
|
x_index: x轴坐标值
|
|
|
|
|
|
"""
|
|
|
|
|
|
if y_index:
|
|
|
|
|
|
_value = x_index if self.build_data.mat_type == "barh" else y_index
|
|
|
|
|
|
if not isinstance(y[0], str):
|
|
|
|
|
|
__y = [float(t_y) for t_y in y]
|
|
|
|
|
|
_y_index = [float(t_y) for t_y in y_index]
|
|
|
|
|
|
if max(__y) > max(_y_index):
|
|
|
|
|
|
raise ValueError("坐标点的值必须小于y轴坐标的最大值...")
|
|
|
|
|
|
i = -9999999999
|
|
|
|
|
|
for _y in _y_index:
|
|
|
|
|
|
if _y > i:
|
|
|
|
|
|
i = _y
|
|
|
|
|
|
else:
|
|
|
|
|
|
raise ValueError("y轴坐标值必须有序...")
|
|
|
|
|
|
|
2024-05-27 16:09:24 +08:00
|
|
|
|
async def build(self) -> BuildImage:
|
2024-05-18 20:56:23 +08:00
|
|
|
|
"""构造图片"""
|
2024-05-27 16:09:24 +08:00
|
|
|
|
A = BuildImage(1, 1)
|
2024-05-18 20:56:23 +08:00
|
|
|
|
bar_color = self.build_data.bar_color
|
|
|
|
|
|
if "*" in bar_color:
|
|
|
|
|
|
bar_color = [
|
|
|
|
|
|
"#FF0000",
|
|
|
|
|
|
"#FF7F00",
|
|
|
|
|
|
"#FFFF00",
|
|
|
|
|
|
"#00FF00",
|
|
|
|
|
|
"#00FFFF",
|
|
|
|
|
|
"#0000FF",
|
|
|
|
|
|
"#8B00FF",
|
|
|
|
|
|
]
|
|
|
|
|
|
init_graph = await self._init_graph()
|
|
|
|
|
|
mark_image = None
|
|
|
|
|
|
if self.build_data.mat_type == MatType.LINE:
|
|
|
|
|
|
mark_image = await self._build_line_graph(init_graph, bar_color)
|
|
|
|
|
|
if self.build_data.mat_type == MatType.BAR:
|
2024-05-27 16:09:24 +08:00
|
|
|
|
mark_image = await self._build_bar_graph(init_graph, bar_color)
|
2024-05-18 20:56:23 +08:00
|
|
|
|
if self.build_data.mat_type == MatType.BARH:
|
2024-05-27 16:09:24 +08:00
|
|
|
|
mark_image = await self._build_barh_graph(init_graph, bar_color)
|
2024-05-18 20:56:23 +08:00
|
|
|
|
if mark_image:
|
|
|
|
|
|
padding_width, padding_height = self.build_data.padding
|
2024-05-27 16:09:24 +08:00
|
|
|
|
width = mark_image.width + padding_width
|
2024-05-18 20:56:23 +08:00
|
|
|
|
height = mark_image.height + padding_height * 2
|
|
|
|
|
|
if self.build_data.background:
|
|
|
|
|
|
if isinstance(self.build_data.background, bytes):
|
|
|
|
|
|
A = BuildImage(
|
|
|
|
|
|
width, height, background=BytesIO(self.build_data.background)
|
|
|
|
|
|
)
|
|
|
|
|
|
elif isinstance(self.build_data.background, Path):
|
|
|
|
|
|
A = BuildImage(width, height, background=self.build_data.background)
|
|
|
|
|
|
else:
|
|
|
|
|
|
A = BuildImage(width, height, self.build_data.background_color)
|
|
|
|
|
|
if A:
|
2024-05-27 16:09:24 +08:00
|
|
|
|
await A.paste(mark_image, (10, padding_height))
|
2024-05-18 20:56:23 +08:00
|
|
|
|
if self.build_data.title:
|
|
|
|
|
|
font = BuildImage.load_font(
|
|
|
|
|
|
self.build_data.font, self.build_data.font_size + 7
|
|
|
|
|
|
)
|
|
|
|
|
|
title_width, title_height = BuildImage.get_text_size(
|
|
|
|
|
|
self.build_data.title, font
|
|
|
|
|
|
)
|
|
|
|
|
|
pos = (
|
|
|
|
|
|
int(A.width / 2 - title_width / 2),
|
|
|
|
|
|
int(padding_height / 2 - title_height / 2),
|
|
|
|
|
|
)
|
|
|
|
|
|
await A.text(pos, self.build_data.title)
|
|
|
|
|
|
if self.build_data.x_name:
|
|
|
|
|
|
font = BuildImage.load_font(
|
|
|
|
|
|
self.build_data.font, self.build_data.font_size + 4
|
|
|
|
|
|
)
|
|
|
|
|
|
title_width, title_height = BuildImage.get_text_size(
|
2024-12-10 19:49:11 +08:00
|
|
|
|
self.build_data.x_name,
|
|
|
|
|
|
font, # type: ignore
|
2024-05-18 20:56:23 +08:00
|
|
|
|
)
|
|
|
|
|
|
pos = (
|
|
|
|
|
|
A.width - title_width - 20,
|
|
|
|
|
|
A.height - int(padding_height / 2 + title_height),
|
|
|
|
|
|
)
|
|
|
|
|
|
await A.text(pos, self.build_data.x_name)
|
|
|
|
|
|
return A
|
|
|
|
|
|
|
|
|
|
|
|
async def _init_graph(self) -> InitGraph:
|
|
|
|
|
|
"""构造初始化图表
|
|
|
|
|
|
|
|
|
|
|
|
返回:
|
|
|
|
|
|
InitGraph: InitGraph
|
|
|
|
|
|
"""
|
|
|
|
|
|
padding_width = 0
|
|
|
|
|
|
padding_height = 0
|
|
|
|
|
|
font = BuildImage.load_font(self.build_data.font, self.build_data.font_size)
|
2024-05-27 16:09:24 +08:00
|
|
|
|
x_width_list = []
|
|
|
|
|
|
y_height_list = []
|
2024-05-18 20:56:23 +08:00
|
|
|
|
for x in self.build_data.x_index:
|
|
|
|
|
|
text_size = BuildImage.get_text_size(x, font)
|
|
|
|
|
|
if text_size[1] > padding_height:
|
|
|
|
|
|
padding_height = text_size[1]
|
2024-05-27 16:09:24 +08:00
|
|
|
|
x_width_list.append(text_size)
|
2024-05-18 20:56:23 +08:00
|
|
|
|
if not self.build_data.y_index:
|
|
|
|
|
|
"""没有指定y_index时,使用data自动生成"""
|
|
|
|
|
|
max_num = max(self.build_data.data)
|
2024-05-27 16:09:24 +08:00
|
|
|
|
if max_num < 5:
|
|
|
|
|
|
max_num = 5
|
2024-05-18 20:56:23 +08:00
|
|
|
|
s = int(max_num / 5)
|
|
|
|
|
|
_y_index = [max_num]
|
|
|
|
|
|
for _n in range(4):
|
|
|
|
|
|
max_num -= s
|
|
|
|
|
|
_y_index.append(max_num)
|
|
|
|
|
|
_y_index.sort()
|
2024-05-27 16:09:24 +08:00
|
|
|
|
# if len(_y_index) > 1:
|
|
|
|
|
|
# if _y_index[0] == _y_index[-1]:
|
|
|
|
|
|
# _tmp = ["_" for _ in range(len(_y_index) - 1)]
|
|
|
|
|
|
# _tmp.append(str(_y_index[0]))
|
|
|
|
|
|
# _y_index = _tmp
|
|
|
|
|
|
self.build_data.y_index = _y_index # type: ignore
|
2024-05-18 20:56:23 +08:00
|
|
|
|
for item in self.build_data.y_index:
|
|
|
|
|
|
text_size = BuildImage.get_text_size(str(item), font)
|
|
|
|
|
|
if text_size[0] > padding_width:
|
|
|
|
|
|
padding_width = text_size[0]
|
2024-05-27 16:09:24 +08:00
|
|
|
|
y_height_list.append(text_size)
|
|
|
|
|
|
if self.build_data.mat_type == MatType.BARH:
|
|
|
|
|
|
_tmp = x_width_list
|
|
|
|
|
|
x_width_list = y_height_list
|
|
|
|
|
|
y_height_list = _tmp
|
|
|
|
|
|
old_space = self.build_data.space
|
|
|
|
|
|
width = padding_width * 2 + self.build_data.space[0] * 2 + 20
|
2024-05-18 20:56:23 +08:00
|
|
|
|
height = (
|
2024-05-27 16:09:24 +08:00
|
|
|
|
sum([h[1] + self.build_data.space[1] for h in y_height_list])
|
2024-05-18 20:56:23 +08:00
|
|
|
|
+ self.build_data.space[1] * 2
|
|
|
|
|
|
+ 30
|
|
|
|
|
|
)
|
2024-05-27 16:09:24 +08:00
|
|
|
|
_x_index = self.build_data.x_index
|
|
|
|
|
|
_y_index = self.build_data.y_index
|
|
|
|
|
|
_barh_max_text_width = 0
|
2024-05-18 20:56:23 +08:00
|
|
|
|
if self.build_data.mat_type == MatType.BARH:
|
2024-05-27 16:09:24 +08:00
|
|
|
|
"""XY轴下标互换"""
|
|
|
|
|
|
_tmp = _y_index
|
|
|
|
|
|
_y_index = _x_index
|
|
|
|
|
|
_x_index = _tmp
|
|
|
|
|
|
"""额外增加字体宽度"""
|
|
|
|
|
|
for s in self.build_data.x_index:
|
|
|
|
|
|
s_w, s_h = BuildImage.get_text_size(s, font)
|
|
|
|
|
|
if s_w > _barh_max_text_width:
|
|
|
|
|
|
_barh_max_text_width = s_w
|
|
|
|
|
|
width += _barh_max_text_width
|
|
|
|
|
|
width += self.build_data.space[0] * 2 - old_space[0] * 2
|
|
|
|
|
|
"""X轴重新等均分配"""
|
|
|
|
|
|
x_length = width - padding_width * 2 - _barh_max_text_width
|
|
|
|
|
|
x_space = int((x_length - 20) / (len(_x_index) + 1))
|
|
|
|
|
|
if x_space < 50:
|
|
|
|
|
|
"""加大间距更加美观"""
|
|
|
|
|
|
x_space = 50
|
|
|
|
|
|
self.build_data.space = (x_space, self.build_data.space[1])
|
|
|
|
|
|
width += self.build_data.space[0] * (len(_x_index) - 1)
|
|
|
|
|
|
else:
|
|
|
|
|
|
"""非横向柱状图时加字体宽度"""
|
|
|
|
|
|
width += sum([w[0] + self.build_data.space[0] for w in x_width_list])
|
|
|
|
|
|
|
2024-05-18 20:56:23 +08:00
|
|
|
|
A = BuildImage(
|
2024-05-27 16:09:24 +08:00
|
|
|
|
width + 5,
|
2024-05-18 20:56:23 +08:00
|
|
|
|
(height + 10),
|
2024-05-27 16:09:24 +08:00
|
|
|
|
# color=(255, 255, 255),
|
2024-05-18 20:56:23 +08:00
|
|
|
|
color=(255, 255, 255, 0),
|
|
|
|
|
|
)
|
|
|
|
|
|
padding_height += 5
|
2024-05-27 16:09:24 +08:00
|
|
|
|
"""高"""
|
2024-05-18 20:56:23 +08:00
|
|
|
|
await A.line(
|
|
|
|
|
|
(
|
2024-05-27 16:09:24 +08:00
|
|
|
|
padding_width + 5 + _barh_max_text_width,
|
2024-05-18 20:56:23 +08:00
|
|
|
|
padding_height,
|
2024-05-27 16:09:24 +08:00
|
|
|
|
padding_width + 5 + _barh_max_text_width,
|
2024-05-18 20:56:23 +08:00
|
|
|
|
height - padding_height,
|
|
|
|
|
|
),
|
|
|
|
|
|
width=2,
|
|
|
|
|
|
)
|
2024-05-27 16:09:24 +08:00
|
|
|
|
"""长"""
|
2024-05-18 20:56:23 +08:00
|
|
|
|
await A.line(
|
|
|
|
|
|
(
|
2024-05-27 16:09:24 +08:00
|
|
|
|
padding_width + 5 + _barh_max_text_width,
|
2024-05-18 20:56:23 +08:00
|
|
|
|
height - padding_height,
|
|
|
|
|
|
width - padding_width + 5,
|
|
|
|
|
|
height - padding_height,
|
|
|
|
|
|
),
|
|
|
|
|
|
width=2,
|
|
|
|
|
|
)
|
2024-05-27 16:09:24 +08:00
|
|
|
|
x_cur_width = (
|
|
|
|
|
|
padding_width + _barh_max_text_width + self.build_data.space[0] + 5
|
|
|
|
|
|
)
|
|
|
|
|
|
if self.build_data.mat_type != MatType.BARH:
|
|
|
|
|
|
"""添加字体宽度"""
|
|
|
|
|
|
x_cur_width += x_width_list[0][0]
|
|
|
|
|
|
x_cur_height = height - y_height_list[0][1] - 5
|
|
|
|
|
|
# await A.point((x_cur_width, x_cur_height), (0, 0, 0))
|
2024-05-18 20:56:23 +08:00
|
|
|
|
x_point = []
|
|
|
|
|
|
for i, _x in enumerate(_x_index):
|
|
|
|
|
|
"""X轴数值"""
|
2024-05-27 16:09:24 +08:00
|
|
|
|
grid_height = x_cur_height
|
2024-05-18 20:56:23 +08:00
|
|
|
|
if self.build_data.is_grid:
|
|
|
|
|
|
grid_height = padding_height
|
2024-05-27 16:09:24 +08:00
|
|
|
|
await A.line(
|
|
|
|
|
|
(
|
|
|
|
|
|
x_cur_width,
|
|
|
|
|
|
x_cur_height - 1,
|
|
|
|
|
|
x_cur_width,
|
|
|
|
|
|
grid_height - 5,
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
x_point.append(x_cur_width - 1)
|
|
|
|
|
|
mid_point = x_cur_width - int(x_width_list[i][0] / 2)
|
|
|
|
|
|
await A.text((mid_point, x_cur_height), str(_x), font=font)
|
|
|
|
|
|
x_cur_width += self.build_data.space[0]
|
|
|
|
|
|
if self.build_data.mat_type != MatType.BARH:
|
|
|
|
|
|
"""添加字体宽度"""
|
|
|
|
|
|
x_cur_width += x_width_list[i][0]
|
|
|
|
|
|
y_cur_width = padding_width + _barh_max_text_width
|
|
|
|
|
|
y_cur_height = height - self.build_data.padding[1] - 9
|
|
|
|
|
|
start_height = y_cur_height
|
|
|
|
|
|
# await A.point((y_cur_width, y_cur_height), (0, 0, 0))
|
|
|
|
|
|
y_point = []
|
2024-05-18 20:56:23 +08:00
|
|
|
|
for i, _y in enumerate(_y_index):
|
|
|
|
|
|
"""Y轴数值"""
|
2024-05-27 16:09:24 +08:00
|
|
|
|
grid_width = y_cur_width
|
2024-05-18 20:56:23 +08:00
|
|
|
|
if self.build_data.is_grid:
|
|
|
|
|
|
grid_width = width - padding_width + 5
|
2024-05-27 16:09:24 +08:00
|
|
|
|
y_point.append(y_cur_height)
|
|
|
|
|
|
await A.line((y_cur_width + 5, y_cur_height, grid_width + 11, y_cur_height))
|
2024-05-18 20:56:23 +08:00
|
|
|
|
text_width = BuildImage.get_text_size(str(_y), font)[0]
|
|
|
|
|
|
await A.text(
|
2024-05-27 16:09:24 +08:00
|
|
|
|
(
|
|
|
|
|
|
y_cur_width - text_width,
|
|
|
|
|
|
y_cur_height - int(y_height_list[i][1] / 2) - 3,
|
|
|
|
|
|
),
|
2024-05-18 20:56:23 +08:00
|
|
|
|
str(_y),
|
|
|
|
|
|
font=font,
|
|
|
|
|
|
)
|
2024-05-27 16:09:24 +08:00
|
|
|
|
y_cur_height -= y_height_list[i][1] + self.build_data.space[1]
|
|
|
|
|
|
graph_height = 0
|
|
|
|
|
|
if self.build_data.mat_type == MatType.BARH:
|
|
|
|
|
|
graph_height = (
|
|
|
|
|
|
x_cur_width
|
|
|
|
|
|
- self.build_data.space[0]
|
|
|
|
|
|
- _barh_max_text_width
|
|
|
|
|
|
- padding_width
|
|
|
|
|
|
- 5
|
|
|
|
|
|
)
|
|
|
|
|
|
else:
|
|
|
|
|
|
graph_height = start_height - y_cur_height + 7
|
2024-05-18 20:56:23 +08:00
|
|
|
|
return self.InitGraph(
|
|
|
|
|
|
mark_image=A,
|
2024-05-27 16:09:24 +08:00
|
|
|
|
x_height=height - y_height_list[0][1] - 5,
|
|
|
|
|
|
y_width=padding_width + 5 + _barh_max_text_width,
|
2024-05-18 20:56:23 +08:00
|
|
|
|
graph_height=graph_height,
|
|
|
|
|
|
x_point=x_point,
|
2024-05-27 16:09:24 +08:00
|
|
|
|
y_point=y_point,
|
2024-05-18 20:56:23 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
async def _build_line_graph(
|
|
|
|
|
|
self, init_graph: InitGraph, bar_color: list[str]
|
|
|
|
|
|
) -> BuildImage:
|
|
|
|
|
|
"""构建折线图
|
|
|
|
|
|
|
|
|
|
|
|
参数:
|
|
|
|
|
|
init_graph: InitGraph
|
|
|
|
|
|
bar_color: 颜色列表
|
|
|
|
|
|
|
|
|
|
|
|
返回:
|
|
|
|
|
|
BuildImage: 折线图
|
|
|
|
|
|
"""
|
|
|
|
|
|
font = BuildImage.load_font(self.build_data.font, self.build_data.font_size)
|
|
|
|
|
|
mark_image = init_graph.mark_image
|
|
|
|
|
|
x_height = init_graph.x_height
|
|
|
|
|
|
graph_height = init_graph.graph_height
|
|
|
|
|
|
random_color = random.choice(bar_color)
|
|
|
|
|
|
_black_point = BuildImage(11, 11, color=random_color)
|
|
|
|
|
|
await _black_point.circle()
|
|
|
|
|
|
max_num = max(self.y_index)
|
|
|
|
|
|
point_list = []
|
|
|
|
|
|
for x_p, y in zip(init_graph.x_point, self.build_data.data):
|
|
|
|
|
|
"""折线图标点"""
|
2024-05-27 16:09:24 +08:00
|
|
|
|
y_height = int(y / max_num * graph_height)
|
|
|
|
|
|
await mark_image.paste(_black_point, (x_p - 3, x_height - y_height))
|
|
|
|
|
|
point_list.append((x_p + 1, x_height - y_height + 1))
|
2024-05-18 20:56:23 +08:00
|
|
|
|
for i in range(len(point_list) - 1):
|
|
|
|
|
|
"""画线"""
|
|
|
|
|
|
a_x, a_y = point_list[i]
|
|
|
|
|
|
b_x, b_y = point_list[i + 1]
|
|
|
|
|
|
await mark_image.line((a_x, a_y, b_x, b_y), random_color)
|
|
|
|
|
|
if self.build_data.display_num:
|
|
|
|
|
|
"""显示数值"""
|
|
|
|
|
|
value = self.build_data.data[i]
|
|
|
|
|
|
text_size = BuildImage.get_text_size(str(value), font)
|
|
|
|
|
|
await mark_image.text(
|
|
|
|
|
|
(a_x - int(text_size[0] / 2), a_y - text_size[1] - 5),
|
|
|
|
|
|
str(value),
|
|
|
|
|
|
font=font,
|
|
|
|
|
|
)
|
|
|
|
|
|
"""最后一个数值显示"""
|
|
|
|
|
|
value = self.build_data.data[-1]
|
|
|
|
|
|
text_size = BuildImage.get_text_size(str(value), font)
|
|
|
|
|
|
await mark_image.text(
|
|
|
|
|
|
(
|
|
|
|
|
|
point_list[-1][0] - int(text_size[0] / 2),
|
|
|
|
|
|
point_list[-1][1] - text_size[1] - 5,
|
|
|
|
|
|
),
|
|
|
|
|
|
str(value),
|
|
|
|
|
|
font=font,
|
|
|
|
|
|
)
|
|
|
|
|
|
return mark_image
|
|
|
|
|
|
|
2024-05-27 16:09:24 +08:00
|
|
|
|
async def _build_bar_graph(self, init_graph: InitGraph, bar_color: list[str]):
|
|
|
|
|
|
"""构建折线图
|
2024-05-18 20:56:23 +08:00
|
|
|
|
|
2024-05-27 16:09:24 +08:00
|
|
|
|
参数:
|
|
|
|
|
|
init_graph: InitGraph
|
|
|
|
|
|
bar_color: 颜色列表
|
|
|
|
|
|
|
|
|
|
|
|
返回:
|
|
|
|
|
|
BuildImage: 折线图
|
|
|
|
|
|
"""
|
2024-05-18 20:56:23 +08:00
|
|
|
|
pass
|
2024-05-27 16:09:24 +08:00
|
|
|
|
|
|
|
|
|
|
async def _build_barh_graph(self, init_graph: InitGraph, bar_color: list[str]):
|
|
|
|
|
|
"""构建折线图
|
|
|
|
|
|
|
|
|
|
|
|
参数:
|
|
|
|
|
|
init_graph: InitGraph
|
|
|
|
|
|
bar_color: 颜色列表
|
|
|
|
|
|
|
|
|
|
|
|
返回:
|
|
|
|
|
|
BuildImage: 横向柱状图
|
|
|
|
|
|
"""
|
|
|
|
|
|
font = BuildImage.load_font(self.build_data.font, self.build_data.font_size)
|
|
|
|
|
|
mark_image = init_graph.mark_image
|
|
|
|
|
|
y_width = init_graph.y_width
|
|
|
|
|
|
graph_height = init_graph.graph_height
|
|
|
|
|
|
random_color = random.choice(bar_color)
|
|
|
|
|
|
max_num = max(self.y_index)
|
|
|
|
|
|
for y_p, y in zip(init_graph.y_point, self.build_data.data):
|
2024-06-10 21:10:04 +08:00
|
|
|
|
bar_width = int(y / max_num * graph_height) or 1
|
2024-05-27 16:09:24 +08:00
|
|
|
|
bar = BuildImage(bar_width, 18, random_color)
|
|
|
|
|
|
await mark_image.paste(bar, (y_width + 1, y_p - 9))
|
|
|
|
|
|
if self.build_data.display_num:
|
|
|
|
|
|
"""显示数值"""
|
|
|
|
|
|
await mark_image.text(
|
|
|
|
|
|
(y_width + bar_width + 5, y_p - 12), str(y), font=font
|
|
|
|
|
|
)
|
|
|
|
|
|
return mark_image
|