You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何Matplotlib绘制的函数切线线段长度不一致?

问题描述

尝试绘制衰减正弦函数图像,并在特定点添加代表梯度的切线线段,但视觉上线段长度各不相同。计算时打印的线段长度为1,但理论上期望线段长度为2(因为向两侧延伸)。

初始绘制代码:

import numpy as np
import matplotlib.pyplot as plt

# create a decaying sinewave, 8 cycles with decaying amplitude
t = np.linspace(0, 8, 1000)
decaying_sine = np.sin(t * np.pi) * np.exp(-t * np.pi / 4)
plt.plot(t, decaying_sine)

切线绘制函数及调用:

def plot_graph_tangent_at_time(graph, t, t_index, label='slope'):
    # Get the slope of graph at that location
    slope = np.gradient(graph, t)[t_index]
    tangent = np.arctan(1/slope)
    line_length = 1
    line_w = np.sin(tangent) * line_length
    line_h = np.cos(tangent) * line_length

    # Add a line to the graph
    plt.scatter(t[t_index], graph[t_index], color='C1', linewidth=2)
    plt.plot([t[t_index] - line_w, t[t_index] + line_w], [graph[t_index] - line_h, graph[t_index] + line_h], 'C1--', label=label)

# 调用绘制切线
plot_graph_tangent_at_time(decaying_sine, t, 100)
plot_graph_tangent_at_time(decaying_sine, t, 165)
plot_graph_tangent_at_time(decaying_sine, t, 500)
plot_graph_tangent_at_time(decaying_sine, t, 700)

plt.legend()
plt.show()

添加的长度验证代码:

# recalculate the length of the line
line_length = np.sqrt(line_w**2 + line_h**2)
print(line_length)

打印结果为1,但期望线段总长度为2,且视觉上各线段长短不一。


问题分析与解决

1. 视觉长度不一致的根本原因

Matplotlib默认采用非等比例坐标轴,x轴和y轴上的单位长度对应的屏幕像素数不同。你计算的是数学上的欧氏长度,但屏幕显示时,x、y方向的缩放比例不同,导致同样数学长度的线段,视觉上像素长度差异明显。

2. 线段长度计算的逻辑偏差

你打印的line_length = 1是单侧线段的长度:代码中line_w和line_h是从中心点向单方向(左/下)延伸的分量,整个切线是向两侧各延伸一段,所以总数学长度应为2 * np.sqrt(line_w² + line_h²) = 2,你只计算了单侧长度,因此打印结果为1,这是计算逻辑的误解。

3. 修正方案

方案A:强制坐标轴等比例(简单直接)

在plt.show()前添加代码,让x、y轴单位长度的像素占比一致:

plt.gca().set_aspect('equal', adjustable='box')

这样所有数学长度相同的切线,视觉上就会一样长,但可能会拉伸原函数图像,破坏其原本的比例展示。

方案B:根据坐标轴缩放调整线段分量(推荐,不影响原图比例)

修改切线函数,根据当前坐标轴的像素缩放比例计算线段增量,确保所有切线的视觉长度一致:

def plot_graph_tangent_at_time(graph, t, t_index, label='slope', visual_pixel_length=25):
    ax = plt.gca()
    slope = np.gradient(graph, t)[t_index]
    
    # 获取坐标轴的像素-单位转换比例
    x_range = ax.get_xlim()[1] - ax.get_xlim()[0]
    y_range = ax.get_ylim()[1] - ax.get_ylim()[0]
    x_pix_per_unit = ax.bbox.width / x_range
    y_pix_per_unit = ax.bbox.height / y_range
    
    # 计算x、y方向的单侧增量,确保视觉长度为设定的像素值
    dx = visual_pixel_length / np.sqrt(x_pix_per_unit**2 + (slope * y_pix_per_unit)**2)
    dy = slope * dx
    
    plt.scatter(t[t_index], graph[t_index], color='C1', linewidth=2)
    plt.plot([t[t_index]-dx, t[t_index]+dx], 
             [graph[t_index]-dy, graph[t_index]+dy], 
             'C1--', label=label)

其中visual_pixel_length是你想要的切线视觉长度(单位:像素),调整这个值可以统一所有切线的显示长短,且不会改变原函数图像的比例。

方案C:修正数学长度计算逻辑

如果只需要保证切线的数学长度一致(不管视觉),可以简化切线分量的计算,避免用arctan(1/slope)的绕弯方式:

def plot_graph_tangent_at_time(graph, t, t_index, label='slope', half_line_length=1):
    slope = np.gradient(graph, t)[t_index]
    # 计算单侧的x、y增量,确保单侧数学长度为half_line_length
    dx = half_line_length / np.sqrt(1 + slope**2)
    dy = slope * dx
    
    plt.scatter(t[t_index], graph[t_index], color='C1', linewidth=2)
    plt.plot([t[t_index]-dx, t[t_index]+dx], 
             [graph[t_index]-dy, graph[t_index]+dy], 
             'C1--', label=label)

这里half_line_length是单侧切线的数学长度,整个切线的总数学长度为2 * half_line_length,若要视觉一致,需搭配方案A的等比例坐标轴设置。


内容的提问来源于stack exchange,提问作者Danny Staple

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 23:31:36