用Matplotlib绘制柱状图(仿照极客湾的Mate60 Pro评测跑分图)

极客湾的Mate60 Pro评测跑分图

大家好!前段时间刷新闻看到一张Mate60 Pro的一张测评图,感觉挺有这种风格的数据展示方式挺有科技感。然后有个想法,想用Matplotlib去复现一下。这两天闲了下来,写个blog,打算从入门、进阶到高级三个阶段绘制这个柱状图。

注意,如果你用的是版本比较低的matplotlib和python,直接拿我的代码使用的话,有些是可能会报错的哦!

#我的matplotlib的版本
import matplotlib
print(matplotlib.__version__)
3.6.3

1、基础绘制

仔细观察这个图,图中内容比较简单,主要是两组数据即多核与单核的跑分。这两组数据比较好的表现方式就是柱状图(当然,横向和纵向的表达方式都是可以的),例图中呈现的是横向柱状图。

所以,咱们从最简单barh函数开始吧。

废话不多说,直接上代码

import numpy as np
import matplotlib.pyplot as plt

plt.rcParams['font.family'] = 'Microsoft Yahei'
plt.rcParams['axes.unicode_minus'] = False

Multicore = np.array([4019,3770,5150,3830,3765,3395,3820])
Singlecore = np.array([1005,1019,1508,1255,1127,903,907])
name = ['麒麟9000S\n华为Mate 60Pro','麒麟9000\n华为Mate 40Pro','骁龙8 Gen2\n小米13','骁龙8 Gen1\nMoto edge X30',
       '骁龙888\n魅族18','骁龙865\n一加8 Pro','天玑8100\n红米K50']
#多核的数据
Multicore = np.array([4019,3770,5150,3830,3765,3395,3820])
#单核的数据
Singlecore = np.array([1005,1019,1508,1255,1127,903,907])
x = np.arange(len(Multicore))

fig,ax = plt.subplots(figsize=(12,5))
height = 0.25
ax.barh(x-0.7*height,Multicore ,height=height,label='多核')
ax.barh(x+0.7*height,Singlecore,height=height,label='单核')
ax.set_yticks(x,labels=name,rotation=0)
ax.legend()
ax.invert_yaxis()
plt.show()

显示结果

基础版本

基础版本比较好理解,就是两个barh就可以画出来,唯一需要注意的就是柱子的位置和height大小。

2、进阶版本

有了基础版本,就有了复现例图的基础框架了。
进阶版本就是在此基础上,美化图片中的元素,在细节上尽可能做到一致。

2.1 颜色的设置

2.1.1、背景颜色的设置

图中的背景色是黑色,因此

fig,ax = plt.subplots(figsize=(12,5),facecolor='k') #图片的背景设置为黑色
ax.set_facecolor('k') #axes的背景颜色设置为黑色

与背景无关的元素就不能有黑色的存在,否则就混在一起,看不出来。

2.1.2、字体颜色的设置

(1)基础版本中bar的颜色是默认色,这里我们需要对其进行修改。

p1 = ax.barh(x-0.2,Multicore ,height=width,color='#5A8FFF',label='多核')
p2 = ax.barh(x+0.2,Singlecore,height=width,color='#CEFD89',label='单核')

(2)同时,左侧的y轴的刻度标签字体,根据处理器品牌的不同也是不同的颜色。因此也需要进行处理。

#这里首先要重新定义一下名称列表,将芯片与对应型号分开
#目的是不再用yticks和yticklabels,而是用ax.text去实现。
name1 = ['麒麟9000S','华为Mate 60Pro','麒麟9000','华为Mate 40Pro','骁龙8 Gen2','小米13','骁龙8 Gen1','Moto edge X30',
       '骁龙888','魅族18','骁龙865','一加8 Pro','天玑8100','红米K50']
for i in range(len(Multicore)):
    ax.text(-750,i-1.5*height,s=name1[0::2][i],va='top',ha='left',fontsize=12,color=colors[i],transform = ax.transData)
    ax.text(-750,i+0.5*height,s=name1[1::2][i],va='top',ha='left',fontsize=9,color='w',transform = ax.transData)

2.2 bar数值显示

这个直接用bar_label函数就可以啦

for p in [p1,p2]:
    ax.bar_label(p,color='w',padding=10)

上完整代码

import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.family'] = 'Microsoft Yahei'

Multicore = np.array([4019,3770,5150,3830,3765,3395,3820])
Singlecore = np.array([1005,1019,1508,1255,1127,903,907])
name1 = ['麒麟9000S','华为Mate 60Pro','麒麟9000','华为Mate 40Pro','骁龙8 Gen2','小米13','骁龙8 Gen1','Moto edge X30',
       '骁龙888','魅族18','骁龙865','一加8 Pro','天玑8100','红米K50']
x = np.arange(len(Multicore))

height = 0.25

colors=['r','r','lightskyblue','lightskyblue','lightskyblue','lightskyblue','orange']
fig,ax = plt.subplots(figsize=(12,5),facecolor='k')
ax.set_facecolor('k')

p1 = ax.barh(x-0.2,Multicore ,height=height,color='#5A8FFF',label='多核')
p2 = ax.barh(x+0.2,Singlecore,height=height,color='#CEFD89',label='单核')

for p in [p1,p2]:
    ax.bar_label(p,color='w',padding=10)

ax.legend(frameon= False,labelcolor='w',loc=(0.9,0.98),ncol=2)
ax.invert_yaxis()
ax.set_xticks([])
ax.set_yticks([])
ax.set_xlim(-800,5400)
for i in range(len(Multicore)):
    ax.text(-750,i-1.5*height,s=name1[0::2][i],va='top',ha='left',fontsize=12,color=colors[i],transform = ax.transData)
    ax.text(-750,i+0.5*height,s=name1[1::2][i],va='top',ha='left',fontsize=9,color='w',transform = ax.transData)
plt.show()

显示结果

进阶版图片怎么样,是不是有点点像了捏?

3、高级版本

虽然实现了进阶版本,但是也是大面上实现了这样一个样式。仍然有些元素或者说细节没有实现。
抱着追求完美的态度,总结一下进阶版本仍然存在的问题:


1、bar的形状是带圆角的长方形,这个难以用bar函数实现
2、图形的上方有个炫酷的标题和一个logo


如何解决上述问题呢?

很遗憾,基础代码进阶代码的思路在这里可能用不了。为了实现这个功能,需要重新转换思路。

3.1 带圆角的bar

其实bar对象是class matplotlib.patches.Rectangle 的一个实例。那么圆角矩阵就对应class matplotlib.patches.FancyBboxPatch

for i in range(len(Multicore)):
    mcp.append(mpatches.FancyBboxPatch((0,i-1.5*height),Multicore[i],height,ec="none",color='#5A8FFF',
                            boxstyle=mpatches.BoxStyle("Round4", pad=0.03)))
    scp.append(mpatches.FancyBboxPatch((0,i+0.5*height),Singlecore[i],height,ec="none",color='#CEFD89',
                            boxstyle=mpatches.BoxStyle("Round4", pad=0.03)))
    ax.text(-0.4,i-1.5*height,s=name1[0::2][i],va='top',ha='left',fontsize=12,color=colors[i],transform = ax.transData)
    ax.text(-0.4,i+0.5*height,s=name1[1::2][i],va='top',ha='left',fontsize=9,color='w',transform = ax.transData)
for mp,sp in zip(mcp,scp):
    ax.add_artist(mp)

其中 MulticoreSinglecore 的数据都除以3000。原因就是FancyBboxPatch长宽比例差距过大的时候round几乎看不出来。

Multicore = np.array([4019,3770,5150,3830,3765,3395,3820])/3000
Singlecore = np.array([1005,1019,1508,1255,1127,903,907])/3000

3.2 标题和Logo

3.2.1 标题

废话不多说,直接上代码
>||<这个多边形的形状是我一点点找坐标调出来的,太费劲了 >||<

verts = [
   [-0.58,-2.3],
   [-0.32,-2.4],
   [-0.3, -2.2], 
   [1.,   -2.2],  
   [0.98, -1.3], 
   [0.95, -1.1],
   [-0.3, -1.1], 
   [-0.3, -1.1],
   [-0.32,-0.9],
   [-0.52,-0.9],
   [-0.55,-1.1],
   [-0.52,-0.9],
]
# 坐标对应的路径代码
codes = [
    Path.MOVETO,
    Path.LINETO,
    Path.LINETO,
    Path.LINETO,
    Path.LINETO,
    Path.LINETO,
    Path.LINETO,
    Path.LINETO,
    Path.LINETO,
    Path.LINETO,
    Path.LINETO,
    Path.CLOSEPOLY,
]
path1 = Path(verts, codes)
patch1 = mpatches.PathPatch(path1, facecolor='lightgrey', zorder=0,lw=2,alpha=0.7)
ax.add_patch(patch1)

3.2.2 Logo

由于Logo这个我没有现成的图片,不知道会不会涉及版权啥的,我就用一个自己电脑里的logo图标替代一下。

myfig = mpimg.imread(r'E:/Desktop/Logo.png')
axin = ax.inset_axes([0,0.85,0.12,0.12])
axin.imshow(myfig)
axin.set_axis_off()

我的logo
Logo

最终完整代码

上完整代码

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib.path import Path
import matplotlib.image as mpimg
plt.rcParams['font.family'] = 'Microsoft Yahei'

height= 0.2
mcp = []
scp = []
colors=['r','r','lightskyblue','lightskyblue','lightskyblue','lightskyblue','orange']
name1 = ['麒麟9000S','华为Mate 60Pro','麒麟9000','华为Mate 40Pro','骁龙8 Gen2','小米13','骁龙8 Gen1','Moto edge X30',
       '骁龙888','魅族18','骁龙865','一加8 Pro','天玑8100','红米K50']
Multicore = np.array([4019,3770,5150,3830,3765,3395,3820])/3000
Singlecore = np.array([1005,1019,1508,1255,1127,903,907])/3000
x = np.arange(len(Multicore))


fig,ax = plt.subplots(figsize=(12,6),facecolor='k',dpi=600)

for i in range(len(Multicore)):
    mcp.append(mpatches.FancyBboxPatch((0,i-1.5*height),Multicore[i],height,ec="none",color='#5A8FFF',
                            boxstyle=mpatches.BoxStyle("Round4", pad=0.03)))
    scp.append(mpatches.FancyBboxPatch((0,i+0.5*height),Singlecore[i],height,ec="none",color='#CEFD89',
                            boxstyle=mpatches.BoxStyle("Round4", pad=0.03)))
    ax.text(-0.4,i-1.5*height,s=name1[0::2][i],va='top',ha='left',fontsize=12,color=colors[i],transform = ax.transData)
    ax.text(-0.4,i+0.5*height,s=name1[1::2][i],va='top',ha='left',fontsize=9,color='w',transform = ax.transData)
for mp,sp in zip(mcp,scp):
    ax.add_artist(mp)
    ax.add_artist(sp)
p1 = ax.barh(x-0.2,Multicore ,height=height,color='none')
p2 = ax.barh(x+0.2,Singlecore,height=height,color='none')
for p in [p1,p2]:
    ax.bar_label(p,labels=[int(np.round(i)) for i in p.datavalues*2000],color='w',padding=10)
ax.set_facecolor('k')

ax.set_xlim(-0.6,2.)
ax.set_ylim(-2.5,6.5)
ax.set_xticks([])
ax.set_yticks([])
ax.invert_yaxis()

myfig = mpimg.imread(r'E:/Desktop/Logo.png')
axin = ax.inset_axes([0,0.85,0.12,0.12])
axin.imshow(myfig)
axin.set_axis_off()
verts = [
   [-0.58,-2.3],
   [-0.32,-2.4],
   [-0.3, -2.2], 
   [1.,  -2.2],  
   [0.98, -1.3], 
   [0.95, -1.1],
   [-0.3, -1.1], 
   [-0.3, -1.1],
	 [-0.32,-0.9],
	 [-0.52,-0.9],
	 [-0.55,-1.1],
	 [-0.52,-0.9],
]

# 坐标对应的路径代码
codes = [
    Path.MOVETO,
    Path.LINETO,
    Path.LINETO,
    Path.LINETO,
    Path.LINETO,
    Path.LINETO,
    Path.LINETO,
    Path.LINETO,
    Path.LINETO,
    Path.LINETO,
    Path.LINETO,
    Path.CLOSEPOLY,
]

path1 = Path(verts, codes)
patch1 = mpatches.PathPatch(path1, facecolor='lightgrey', zorder=0,lw=2,alpha=0.7)
ax.add_patch(patch1)
ax.text(-0.2, -1.6,'Geekbench 5 CPU 测试',fontstyle='italic',color='w',va='center',ha='left',fontsize=20,fontweight='bold')
ax.legend(handles=[mp,sp],labels=['多核','单核'],frameon=False,labelcolor='w',ncol=2,loc=(0.78,0.84),fontsize=12,markerfirst=False)

plt.show()

附图

高级版图片

已经十分相似了。哈哈哈哈!

不过仍然有两个小问题可以再进行优化:

1、legend中的矩形没有做到圆角。
2、bar的颜色没有改成渐变色。


欢迎小伙伴对代码进行优化和讨论!
也欢迎小伙伴向我进行咨询!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值