feat(export): 多格式后台导出、主题与警告框渲染 #43
@@ -226,9 +226,14 @@ def _sample_segments(
|
|||||||
ymin: float,
|
ymin: float,
|
||||||
ymax: float,
|
ymax: float,
|
||||||
) -> list[list[tuple[float, float]]]:
|
) -> list[list[tuple[float, float]]]:
|
||||||
"""采样并映射为像素点段,再裁剪到绘图矩形;非有限点处断段,避免画穿渐近线。"""
|
"""采样并映射为像素点段,再裁剪到绘图矩形。
|
||||||
|
|
||||||
|
两处断段:非有限点处(画穿渐近线);相邻有限采样点横跨可见范围上下两侧时
|
||||||
|
(渐近点恰好落在两个采样点之间,否则会被裁剪成贯穿绘图区的伪竖线)。
|
||||||
|
"""
|
||||||
segments: list[list[tuple[float, float]]] = []
|
segments: list[list[tuple[float, float]]] = []
|
||||||
points: list[tuple[float, float]] = []
|
points: list[tuple[float, float]] = []
|
||||||
|
prev_y: float | None = None
|
||||||
for i in range(_SAMPLES + 1):
|
for i in range(_SAMPLES + 1):
|
||||||
x = xmin + (xmax - xmin) * i / _SAMPLES
|
x = xmin + (xmax - xmin) * i / _SAMPLES
|
||||||
try:
|
try:
|
||||||
@@ -239,6 +244,7 @@ def _sample_segments(
|
|||||||
if points:
|
if points:
|
||||||
segments.append(points)
|
segments.append(points)
|
||||||
points = []
|
points = []
|
||||||
|
prev_y = None
|
||||||
continue
|
continue
|
||||||
px = _sx(x, xmin, xmax)
|
px = _sx(x, xmin, xmax)
|
||||||
py = _sy(y, ymin, ymax)
|
py = _sy(y, ymin, ymax)
|
||||||
@@ -247,8 +253,18 @@ def _sample_segments(
|
|||||||
if points:
|
if points:
|
||||||
segments.append(points)
|
segments.append(points)
|
||||||
points = []
|
points = []
|
||||||
|
prev_y = None
|
||||||
continue
|
continue
|
||||||
|
# 渐近线检测:相邻有限采样点分居可见范围上下两侧(一个 < ymin、一个 > ymax),
|
||||||
|
# 说明两者之间夹着竖直渐近线,断段避免被 Liang-Barsky 裁剪成贯穿绘图区的伪竖线
|
||||||
|
if prev_y is not None and (
|
||||||
|
(prev_y < ymin and y > ymax) or (prev_y > ymax and y < ymin)
|
||||||
|
):
|
||||||
|
if points:
|
||||||
|
segments.append(points)
|
||||||
|
points = []
|
||||||
points.append((px, py))
|
points.append((px, py))
|
||||||
|
prev_y = y
|
||||||
if points:
|
if points:
|
||||||
segments.append(points)
|
segments.append(points)
|
||||||
|
|
||||||
|
|||||||
@@ -425,3 +425,19 @@ def test_render_reportlab_ylabel_within_drawing_bounds() -> None:
|
|||||||
x0, y0, x1, y1 = ylabel_groups[0].getBounds()
|
x0, y0, x1, y1 = ylabel_groups[0].getBounds()
|
||||||
assert 0 <= x0 <= x1 <= 640
|
assert 0 <= x0 <= x1 <= 640
|
||||||
assert 0 <= y0 <= y1 <= 480
|
assert 0 <= y0 <= y1 <= 480
|
||||||
|
|
||||||
|
|
||||||
|
def test_compute_geometry_breaks_at_asymptote() -> None:
|
||||||
|
# P2:渐近点落在两个采样点之间时,两侧采样仍有限,若不断段会被 Liang-Barsky
|
||||||
|
# 裁剪成贯穿绘图区的伪竖线;这里断言不存在跨越上下边界的伪连接线段。
|
||||||
|
from app.plot.render import _PLOT_Y0, _PLOT_Y1, compute_geometry
|
||||||
|
|
||||||
|
plot = parse_source("domain: -1, 1\nrange: -10, 10\ny = 1/(x-0.013)").plot
|
||||||
|
geo = compute_geometry(plot)
|
||||||
|
assert any(geo.polylines) # 渐近线两侧的曲线分支仍在绘图区内可见
|
||||||
|
full_height = _PLOT_Y1 - _PLOT_Y0
|
||||||
|
for segments in geo.polylines:
|
||||||
|
for seg in segments:
|
||||||
|
# 相邻点垂直跨度若接近整个绘图区高度,即为渐近线伪连接
|
||||||
|
for (_, py0), (_, py1) in zip(seg, seg[1:]):
|
||||||
|
assert abs(py1 - py0) < full_height * 0.5
|
||||||
|
|||||||
Reference in New Issue
Block a user