这个方法的核心依然是基于matplotlib.legend._get_legend_handles_labels
函数。然后将得到的handlers, labels进行重排,使得即使再调用Legend
类的绘制方法对图例进行列排列,最终的效果也是图例的行显示,如[1、2、3、4、5、6]
,当指定ncols=2
,Legend
类的绘制方法得到的图例如下:
1 4
2 5
3 6
如果要实现J借助Legend
类的绘制方法得到如下图例:
1 2
3 4
5 6
可以可以发现,将[1、2、3、4、5、6]
重排为[1,3,5,4,5,6]
即可。这里算法就是先按照行切分的方式将原始的handlers变为(-1,ncols)
的2d列表(简单的表示矩阵),然后进行列扫描就行。但是这样要先遍历获取得2d列表的子列表的index=0的元素,然后是index=1,2,…, ncols-1处的元素。由于,这样不利于编程,所以就将2D进行转置,然后进行行扫描,即(列扫描)
# -*- coding: utf-8 -*-
'''
@Time : 2024/1/5 20:25 \n
@Author : 月司 \n
@Email : 815603884@qq.com \n
@File : legend的横向显示.py \n
@version : 1.0 \n
@Software: PyCharm \n
@Desc :
'''
from typing import List, Any
import matplotlib.pyplot as plt
# 创建一个新的图例对象
from matplotlib.legend import Legend
import matplotlib.axes as axes
#这个函数可以获得axis对象的已经绘制的artist中的所有的hander和labels,这个可以只给它一个axis参数。注意这个参数需要是列表类的。
from matplotlib.legend import _get_legend_handles_labels as get_legend_handles_labels
# 自定义图例对象
class CustomLegend(Legend):
def __init__(self, ax:axes.Axes,
handlers: List[Any]=None,
labels: List[str]=None,
horizontal:bool=False,
loc:str=None, ncols:int=1, **kwargs):
if (handlers is None) != (labels is None): # Check if only one of handlers or labels is provided
raise ValueError("Both 'handlers' and 'labels' must be specified if one is provided.")
if (handlers is None) and (labels is None): # get default handlers and labels from ax
# 获取当前轴的句柄和标签
handlers, labels = get_legend_handles_labels(axs=[ax]) # note: the param axs is list object
if horizontal:
handles, labels = map(lambda lst: self.transform_list_to_2d(lst, ncols), [handlers, labels])
super().__init__(parent=ax, handles=handlers, labels=labels, loc=loc, ncols=ncols, **kwargs)
def transform_list_to_2d(self,lst, ncol):
"""
Transform a list into a 2D list and merge elements by indices.
:param lst: The original list.
:param ncol: Number of columns,i.e., number of elements per sublist in the 2D list.
:return: origin_2D new list containing merged elements from the 2D list.
"""
# 将原始列表转换为二维列表,安按照想要的横向序列切分handlers
origin_2D = [lst[i:i + ncol] for i in range(0, len(lst), ncol)]
nrow = len(origin_2D)
# 简单的先给子列表填充None,这样就是一个矩阵了
for sub_list in origin_2D:
# 其实一般都是最后一行空缺
append_nums = ncol - len(sub_list)
for i in range(append_nums):
sub_list.append(None)
reset_list = []
# 这里就是完成了形式上转置,实际上没有转置后的列表(矩阵),然后根据转置前后的列和行互换,直接找到对应的转置前的元素
reset_ncol = nrow
rest_nrow = ncol
for row in range(rest_nrow):
for col in range(reset_ncol):
# 对于原始的数据就是列扫描
_item = origin_2D[col][row]
if _item:
reset_list.append(_item)
return reset_list
if __name__ == '__main__':
# 示例使用这个函数
fig, ax = plt.subplots()
handlers = [ax.scatter(range(10), [i * x for x in range(10)], label=f'Line {i}') for i in range(6)]
labels=[f'label {i}' for i in range(7)]
# 创建一个自定义图例
#leg=CustomLegend(ax, loc='upper left', ncols=2,horizontal=True)
leg=ax.legend(ncols=2)
ax.add_artist(leg)
# 显示图表
plt.show()
实际上这个直接进行列扫描也是可以的。
得到的效果如下:
从上面可以看到横向的图例的布局和竖向图例的布局是一模一样的,但是图例的排序数据变了。另外就是ncols始终控制着图例的列数。这个跟方法1不一样之处,是方法1始终是最后一行或者一列有空缺,而这里明显布局更佳均衡。但是方法1可以更加灵活,比如添加title。
当ncols=2
的例子: