Python Matplotlib绘制柏拉图以及在ax.table上绘制矩形、直线、椭圆

个人示例

Bar

垂直柱状图

创建一个柱状图,并且在每个柱子的上方显示百分比:

image-20240820221102259

代码实现:

import matplotlib.pyplot as plt  
# 示例数据  
categories = ['A', 'B', 'C', 'D']  
values = [0.23, 0.45, 0.56, 0.78]  # 这些值将被转换为百分数  
# 创建一个figure和axes  
fig, ax = plt.subplots()  
# 绘制柱状图  
bars = ax.bar(categories, values)  
# 为每个柱子添加标签,将值转换为百分数  
ax.bar_label(bars, labels=[f' {val*100:.2f}%' for val in values])  
# 设置标题和标签  
ax.set_title('Bar Chart with Percentage Labels')  
ax.set_xlabel('Categories')  
ax.set_ylabel('Values (%)')  
# 显示图表  
plt.show()

水平柱状图

创建一个水平柱状图,并且在柱子的底部显示百分比:

image-20240820220506499
import matplotlib.pyplot as plt  
import numpy as np  

# 示例数据  
categories = ['A', 'B', 'C', 'D']  
values = np.random.rand(4)  # 生成0到100之间的随机小数  
# 创建一个figure和axes  
fig, ax = plt.subplots()  
# 绘制水平柱状图  
bars = ax.barh(categories, values, color='skyblue')  
ax.bar_label(bars, labels=[f' {val*100:.2f}%' for val in values])  
# 设置标题和标签  
ax.set_title('Horizontal Bar Chart with Percent Labels')  
ax.set_xlabel('Percentage')  
# 显示图表  
plt.show()

柏拉图

红色的为累加百分比,柱状图为每个类型的值。

image-20240820222021852

代码:

import matplotlib.pyplot as plt  
import numpy as np  
  
# 示例数据:分类和对应的值  
categories = ['Category A', 'Category B', 'Category C', 'Category D', 'Category E']  
values = [30, 20, 15, 10, 25]  
# 计算累积和  
cumulative_values = np.cumsum(values)
# 计算累积百分比  
cumulative_percentages = cumulative_values / cumulative_values[-1] * 100
percentages = []  
# 遍历列表,计算并打印每个元素的百分比  
for number in values:  
    percentages.append((number / sum(values)))
# 创建图形和坐标轴  
fig, ax1 = plt.subplots()  
# 绘制柱状图  
color = 'tab:blue'  
ax1.set_xlabel('Categories')  
ax1.set_ylabel('Values', color=color)  
bars = ax1.bar(categories, values, color=color)  
ax1.bar_label(bars, labels=[f' {val*100:.2f}%' for val in percentages])  
ax1.tick_params(axis='y', labelcolor=color)  
# 绘制累积百分比的折线图  
ax2 = ax1.twinx()  # 创建共享x轴的第二个y轴  
color = 'tab:red'  
ax2.set_ylabel('Cumulative Percentage (%)', color=color)  
ax2.plot(categories, cumulative_percentages, color=color, marker='D', ms=7, linestyle='-')  
ax2.tick_params(axis='y', labelcolor=color)
# 设置图表标题  
fig.suptitle('Pareto Chart Example')  
# 显示图形  
plt.show()

改进:在柏拉图下方显示一个table

image-20240908151122019
import matplotlib.pyplot as plt  
import numpy as np  

# 示例数据:分类和对应的值  
categories = ['Category A', 'Category B', 'Category C', 'Category D', 'Category E']  
values = [30, 20, 15, 10, 25]  
cumulative_values = np.cumsum(values)   # 计算累积和
cumulative_percentages = cumulative_values / cumulative_values[-1] * 100    # 计算累积百分比
percentages = []
for number in values:   # 遍历列表,计算并打印每个元素的百分比
    percentages.append((number / sum(values)))
# 创建图形和坐标轴  
fig, ax1 = plt.subplots()  
# 绘制柱状图  
color = 'tab:blue'
ax1.set_ylabel('Values', color=color)  
bars = ax1.bar(categories, values, color=color)  
ax1.bar_label(bars, labels=[f' {val*100:.2f}%' for val in percentages])  
ax1.tick_params(axis='y', labelcolor=color)  
# 绘制累积百分比的折线图  
ax2 = ax1.twinx()  # 创建共享x轴的第二个y轴  
color = 'tab:red'  
ax2.set_ylabel('Cumulative Percentage (%)', color=color)  
ax2.plot(categories, cumulative_percentages, color=color, marker='D', ms=7, linestyle='-')  
ax2.tick_params(axis='y', labelcolor=color)
# 添加table
cell_text = [categories, values, percentages, cumulative_percentages]
columns = ["Type", "Values", "Percentages", "Cumulative"]
ax1.table(cellText=cell_text, rowLabels=columns, loc='bottom', cellLoc='center')
plt.xticks([])
plt.subplots_adjust(left=0.2, bottom=0.1)
fig.tight_layout() 
# 设置图表标题  
fig.suptitle('Pareto Chart Example')
plt.tight_layout()
# 显示图形  
plt.show()

轴坐标

在 Matplotlib 中,轴坐标(也称为归一化坐标)是一种特殊的坐标系统,它用于相对于当前轴(Axes)的位置和大小来定位图形元素。轴坐标的范围是从 0 到 1,其中 (0, 0) 表示轴的左下角,(1, 1) 表示轴的右上角。这种坐标系统不依赖于数据的实际值或图形的物理尺寸,而是基于轴的可视化区域。

使用轴坐标可以方便地放置图形元素,如标题、图例、注释或自定义的图形(如矩形、圆形等),而无需担心数据范围或图形窗口大小的变化。这是因为轴坐标是相对于轴的可视化区域进行解释的,而不是相对于数据或图形窗口的像素尺寸。

在 Matplotlib 中,你可以通过传递 transform=ax.transAxes 参数给图形元素(如 RectangleText 等)来指定使用轴坐标。ax 是你的 Axes 对象,而 transAxes 是该轴对象的一个属性,表示轴坐标变换。

在table上绘制图形和线条

ax.table创建的时候,使用的是轴坐标系统,但是ax.table的get_window_extent()方法返回的是像素坐标,而不是轴坐标:

bbox = the_table.get_window_extent()
x0, y0 = bbox.x0, bbox.y0  				# 获取左下角的坐标  
width, height = bbox.width, bbox.height # 获取宽度和高度

下面讲解在ax.table上绘制各种图形时的坐标系统:

  • Rectangle:绘制矩形,使用的是轴坐标系统

    # :                +------------------+
    # :                |                  |
    # :              height               |
    # :                |                  |
    # :               (xy)---- width -----+
    class matplotlib.patches.Rectangle(xy, width, height, *, angle=0.0, rotation_point='xy', **kwargs)
    
  • axhline:绘制水平直线,使用的是轴坐标系统

    '''
    y: 水平线的数据坐标中的y位置, 使用的是像素坐标系
    xmin: 应介于0和1之间,0表示绘图的最左侧,1表示绘图的最右侧, 使用的是轴坐标系。
    xmax: 应介于0和1之间,0表示绘图的最左侧,1表示绘图的最右侧, 使用的是轴坐标系。
    '''
    Axes.axhline(y=0, xmin=0, xmax=1, **kwargs)
    
  • axvline:绘制垂直线,使用的是轴坐标系统

    '''
    x: 垂直线的数据坐标中的x位置, 使用的是像素坐标系
    ymin: 应介于0和1之间,0是绘图的底部,1是绘图的顶部, 使用的是轴坐标系。
    ymax: 应介于0和1之间,0是绘图的底部,1是绘图的顶部, 使用的是轴坐标系。
    '''
    Axes.axvline(x=0, ymin=0, ymax=1, **kwargs)
    
  • Ellipse:绘制椭圆,使用的是像素坐标系统

    # xy: 椭圆中心的 xy 坐标
    # width: 长轴的总长度
    # height: 短轴的总长度
    class matplotlib.patches.Ellipse(xy, width, height, *, angle=0, **kwargs)
    

以下是一个举例,创建一个table,然后在上面绘制水平直线、垂直直线、矩形、椭圆形:

image-20240908165124140

实现代码:

import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
from matplotlib.patches import Ellipse
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas

cell_width_px = 0
cell_height_px = 0
width_px = 0
height_px = 0
# 对于像素坐标系统,需要计算每个Cell的宽度和高度
def calculate_cell_size(fig, num_rows, num_cols):
    global width_px
    global height_px
    global cell_width_px
    global cell_height_px
    # 绘制椭圆图形(这一步是必需的,以便FigureCanvas可以捕获渲染后的图形)
    fig.canvas.draw()
    # 获取FigureCanvas对象(这里我们假设使用的是Agg后端,但你可以根据你的后端进行调整)  
    canvas = FigureCanvas(fig)
    # 获取图形的宽度和高度(以像素为单位)  
    width_px, height_px = canvas.get_width_height()
    # 估算每个单元格的像素宽度和高度, 减去边距、标签等
    cell_width_px = width_px / num_cols - 16    # 16只是人为估计出来的误差
    cell_height_px = height_px / num_rows + 19  # 19只是人为估计出来的误差
    print(f"Estimated width in pixels: {width_px}")
    print(f"Estimated height in pixels: {height_px}")
    print(f"Estimated cell width in pixels: {cell_width_px}")
    print(f"Estimated cell height in pixels: {cell_height_px}")


# x0, y0, x1, y1代表的是cell的位置
# :      +------------------+
# :      |                  |
# :    height               |
# :      |                  |
# :     (xy)---- width -----+
def draw_rectangle(ax, rows, columns, xy, width, heights):
    # 由于创建表时已经设置了轴坐标范围为(0, 0)->(1, 1), 因此计算单个cell的宽和高就很方便
    cell_width = 1 / columns
    cell_height = 1 / rows
    # 由于表格中央被我们当作坐标原点, 但是极坐标的坐标原点默认为左下角, 因此需要添加一个偏移
    start_x = (xy[1] + columns / 2 )* cell_width
    start_y = (xy[0] + rows / 2) * cell_height
    rect_width = width * cell_width
    rect_height = heights * cell_height
    print (start_x, start_y, rect_width, rect_height)
    rect = Rectangle((start_x, start_y), rect_width, rect_height, linewidth=1.5,
                    edgecolor='blue', facecolor='none', transform=ax.transAxes)
    ax.add_patch(rect)


# 绘制椭圆形
# xy: 椭圆原点(x, y)
# width: 水平轴直径
# height: 垂直轴执行
def draw_ellipse(ax, xy, width, height):
    ellipse_width = cell_width_px * width       # 水平轴
    ellipse_height = cell_height_px * height    # 垂直轴
    x1 = xy[0] * cell_width_px
    y1 = xy[1] * cell_height_px
    ellipse = Ellipse((x1, y1), ellipse_width, ellipse_height, edgecolor='red', facecolor='none')
    ax.add_patch(ellipse)


# 在轴上添加一个水平线: matplotlib.pyplot.axhline(y=0, xmin=0, xmax=1, **kwargs)
# '''
#  :       +--------------------+
#  :       |                    |
# (x0, y0) +----------- y1 -----+
#  :       |                    |
#  :       +--------------------+
# '''
def draw_axhline(ax, columns, x0, y0, y1):
    cell_width = 1 / columns                # 创建表时已经设置了轴坐标范围为(0, 0)->(1, 1)
    y = x0 * cell_height_px                 # 使用的是数据(像素)坐标系
    x_min = (y0 + columns / 2) * cell_width # 使用的是轴坐标系
    x_max = (y1 + columns / 2) * cell_width # 使用的是轴坐标系
    print (y, x_min, x_max)
    ax.axhline(y, x_min, x_max, linewidth=1.5, color='red')


# 在轴上添加一条垂直线: Axes.axvline(x=0, ymin=0, ymax=1, **kwargs)
# '''
#   :         y1-------------------+
#   :         |                    |
#   :         |--------------------+
#   :         |                    |
#   :(x0, y0) +--------------------+
# '''
def draw_axvline(ax, rows, y0, x0, x1):
    cell_height = 1 / rows                  # 创建表时已经设置了轴坐标范围为(0, 0)->(1, 1)
    x = y0 * cell_width_px                  # 使用的是数据(像素)坐标系
    y_min = (x0 + rows / 2) * cell_height   # 使用的是轴坐标系
    y_max = (x1 + rows / 2) * cell_height   # 使用的是轴坐标系
    print (x, y_min, y_max)
    ax.axvline(x, y_min, y_max, linewidth=2, color='cyan')


if __name__ == '__main__':
    rows = 8
    columns = 10
    cell_text = [[1 for x in range(columns)] for y in range(rows)]
    fig, ax = plt.subplots()
    the_table = ax.table(
        cellText=cell_text, 
        loc='center', cellLoc='center',
        bbox=[0, 0, 1, 1],  # 为了方便, 将轴坐标范围设置为[0, 0]->[1, 1]
        edges = 'closed',
    )
    calculate_cell_size(fig, rows, columns)
    # 将坐标原点设置到table的中央
    ax.set_xlim(-height_px/2, height_px/2)  # 设置坐标轴原点
    ax.set_ylim(-width_px/2, width_px/2)    # 设置坐标轴原点
    # 绘图
    draw_rectangle(ax, rows, columns, (1, 1), 2, 2)
    draw_ellipse(ax, (1, 1), 6, 6)
    draw_axhline(ax, columns, -1, 1, 3)
    draw_axvline(ax, rows, -1, 1, 3)

    plt.show()

实现效果:

image-20240908210705243
posted @ 2024-09-08 21:11  zhengcixi  阅读(151)  评论(0编辑  收藏  举报
回到顶部