zoukankan      html  css  js  c++  java
  • Python数据可视化:一张很漂亮的商业图

    前言

    本文的文字及图片来源于网络,仅供学习、交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理

    以下文章来源于Lin王发林,作者:WangFalin


     

    Python数据分析:零基础入门教学(讲解+实战)

    https://www.bilibili.com/video/BV18f4y1i7q9/

    前言

    上个月的时候看到一张很漂亮的商业图,很喜欢,然后就忘了。刚好前两天看到一篇文章来临摹此图,于是学习了一下其思路和代码,然后拿来实践了一下,效果还可以,特此纪念,以后应该还有用得上的地方。那商业图我就不放了,然后把我的图放在这里↓

     

    数据准备

    可以看到,图中主要有4列数据组成,分别是公司logo、公司名称、所属工具和市值增长值。于是先准备数据,就是我常用的软件工具列举了一下,共20个,然后数值是使用率吧,Type是用来标记颜色的,最后一列分类是次软件的主要作用,&符号连接两个或多个主要用途。如下↓

    import matplotlib.pyplot as plt
    import pandas as pd
    import os
    os.chdir(r'E:PythonSeabornOthers')
    mydata = pd.read_excel('不规则条形图数据.xlsx')

     

    设置中文字体正常显示

    mycolor = {
        'Green': '#8ABD25',
        'Pink': '#F57FEF',
        'Yellow': '#EBE639',
        'Red': '#EB3939',
        'Orange': '#EBAF39',
        'Blue': '#39A4EB',
        'Black': '#4D6E83',
        'Gray': '#A3A4A5',
    }

    自定义颜色,后面直接根据Type类型进行调用就行了

    logosize = 0.037 #软件图标大小
    right_height = 20 #右边矩形填充的高度,建议和数据行数一样多
    ratio = 0.05 #这个系数会影响右边矩形整体的偏移情况,建议值是(1/行数)
    ratio2 = 0.8 #这个系数影响右边矩形上面的下移程度
    ratio3 = 0.005 #分类图标的水平位置
    ratio4 = 0.01 #数影响右边矩形下面的上移程度
    ratio5 = 0.01 #影响软件文字的上下水平

    一些影响的参数,因为涉及多处,所以提出来统一修改了,还有一些参数需要里面改。

    def create_fill_area(row):
        # 初始化包围填充区域的上下线条y坐标
        line1, line2 = [1 - ratio*row, 1 - ratio*row], [1- ratio*(row+1), 1- ratio*(row+1)]
        # 追加阴影段y坐标
        line1.append(ratio4 + (right_height - row) * (ratio2 - ratio4) / right_height)
        line2.append(ratio4 + (right_height - row - 1) * (ratio2 - ratio4) / right_height)
        # 追加最后一段平行段y坐标
        line1.append(ratio4 + (right_height - row) * (ratio2 - ratio4) / right_height)
        line2.append(ratio4 + (right_height - row - 1) * (ratio2 - ratio4) / right_height)
        return line1, line2

    为了创建出不同条带,配合matplotlib中的fill_between。为了处理好左侧与右侧的竖直方向等分区域,我们可以在对原数据每一行循环的过程中,自定义下列函数来计算区域范围↓

    fig, ax = plt.subplots(figsize=(4.8, 6))
    ax.set_xlim(0, 1.11)
    ax.set_ylim(0, 1)
    for row in range(mydata.shape[0]):
        # 定义区域填充对应的x坐标
        x = [0, 0.15, 0.215, 0.6+mydata.at[row, 'Values'] / 1000]
        # 生成区域填充对应的y坐标
        line1, line2 = create_fill_area(row)
        # 对指定区域进行填充
        ax.fill_between(x,
                        line1,
                        line2,
                        color=mycolor[mydata.at[row, 'Type']],
                        edgecolor='none')
        
        # 从logo文件夹下读取对应logo图片
        try:
            logo = plt.imread(f'logo/{mydata.at[row, "Tools"]}.png')
        except FileNotFoundError:
            logo = plt.imread(f'logo/{mydata.at[row, "Tools"]}.jpg')
        
        # 插入软件logo
        ax_logo = ax.inset_axes((0.05, 1 - ratio*(row+1)+0.005, 0.08, logosize))
        ax_logo.imshow(logo)
        
        ax_logo.axis('off')
        ax_logo.set_facecolor(mycolor[mydata.at[row, 'Type']])
        
        # 处理单个及多个功能情况下的绘制
        for idx, Category in enumerate(mydata.at[row, 'Category'].split('&')[::-1]):
            
            # 读取对应功能图片
            flag = plt.imread(f'flag/{Category}.png')
            # 插入功能子图
            ax_flag = ax.inset_axes((0.545-idx*0.06, 0.013+(right_height - row - 1)*((ratio2 - ratio3) / right_height), 0.1, 0.025))
            ax_flag.imshow(flag)
            ax_flag.axis('off')
            ax_flag.set_facecolor(mycolor[mydata.at[row, 'Type']])
        # 绘制排名
        ax.text(0.025, (1 - ratio*row + 1 - ratio*(row+1)) / 2, str(row+1), 
                ha='center', va='center',
                fontsize=9, color='black')
        # 绘制软件名称
        ax.text(0.215+ratio5, 0.5 * (ratio5 + (right_height - row - 1) * (ratio2 - ratio5) / right_height + ratio5 + (right_height - row) * (ratio2 - ratio5) / right_height), 
                mydata.at[row, 'Tools'], 
                ha='left', va='center',
                fontsize=9, color='#FFFFFF', 
                weight='bold')
        # 处理第一名文字在填充区域内部,其余文字在填充区域外的情况
        if mydata.at[row, 'Tools'] == 'Exce1l':
            ax.text(1, 0.5 * (ratio3 + (right_height - row) * (ratio2 - ratio3) / right_height 
                              + ratio3 + (right_height - row - 1) * (ratio2 - ratio3) / right_height)-0.0025,
                    ''+str(mydata.at[row, 'Values']/4)+'%',
                    color='white',
                    fontsize=10,
                    ha='right',
                    va='center',
                    weight='bold')
        else:
            # 配合归一化对字体进行大小映射
            ax.text(0.6+mydata.at[row, 'Values'] / 1000 + ratio3, 
                    0.5 * (ratio3 + (right_height - row) * (ratio2 - ratio3) / right_height + ratio3 + (right_height - row - 1) * (ratio2 - ratio3) / right_height)-0.0025,
                    ''+str(int(mydata.at[row, 'Values']/4))+'%',
                    color=mycolor[mydata.at[row, 'Type']],
                    fontsize=7+((mydata.at[row, 'Values'] - mydata['Values'].min()) 
                                / (mydata['Values'].max() - mydata['Values'].min())) * 5,
                    ha='left',
                    va='center',
                    weight='bold')
    # 对指定区域进行带透明度的黑色蒙版,以达到阴影效果
    ax.fill_between([0.15, 0.215],
                    [0, ratio4],
                    [1, ratio2],
                    color='black',
                    alpha=0.2, # 设置透明度
                    edgecolor='none')
    # 补充其余文字标注
    ax.text(0.215+ratio5, 0.805, '软件名称', 
            color='#565555', fontsize=6,
            ha='left')
    ax.text(0.67, 0.805, '软件类型', 
            color='#565555', fontsize=6,
            ha='center')
    #补充上方数值刻度
    ax.text(0.6, 0.825, '0', 
            color='#a9a8a8', fontsize=8,
            ha='center')    
    for i in range(1, 5):
        print(i)
        ax.text(0.6+0.1*i, 0.825, f'{i*25}%', 
                color='#a9a8a8', fontsize=9,
                ha='center')   
        ax.vlines(0.6+0.1*i, 0.01, 0.82, 
                  color='#dcdcdb', linewidth=0.2)
    ax.set_xticks([])
    ax.set_yticks([])
    ax.spines['left'].set_color('none')
    ax.spines['right'].set_color('none')
    ax.spines['top'].set_color('none')
    ax.spines['bottom'].set_color('none')
    # 补充下排图例
    ax_bar1 = ax.inset_axes((0.215, 0.88, 0.57, 0.02), transform=ax.transAxes)
    ax_bar1.set_xlim(-0.45, 3.6)
    ax_bar1.bar(range(4), height=1, width=0.8, 
                color=['#8ABD25', '#F57FEF', '#EBE639', '#EB3939'])
    ax_bar1.set_xticks(range(4))
    ax_bar1.set_xticklabels(['绿色系', '紫色系', '黄色系', '红色系'],
                            fontsize=7, color='#4f4e4e', weight='bold')
    ax_bar1.set_yticks([])
    ax_bar1.spines['left'].set_color('none')
    ax_bar1.spines['right'].set_color('none')
    ax_bar1.spines['top'].set_color('none')
    ax_bar1.spines['bottom'].set_color('none')
    ax_bar1.tick_params(color='none', pad=-2)
    ax_bar1.set_facecolor('#f8f8f8')
    
    
    # 补充上排图例
    ax_bar2 = ax.inset_axes((0.215, 0.98, 0.57, 0.02), transform=ax.transAxes)
    ax_bar2.set_xlim(-0.45, 3.6)
    ax_bar2.bar(range(4), height=1, width=0.8, 
                color=['#EBAF39', '#39A4EB', '#4D6E83', '#A3A4A5'])
    
    
    ax_bar2.set_xticks(range(4))
    ax_bar2.set_xticklabels(['橙色系', '蓝色系', '黑色系', '灰色系'],
                            fontsize=7, color='#4f4e4e', weight='bold')
    ax_bar2.set_yticks([])
    ax_bar2.spines['left'].set_color('none')
    ax_bar2.spines['right'].set_color('none')
    ax_bar2.spines['top'].set_color('none')
    ax_bar2.spines['bottom'].set_color('none')
    ax_bar2.tick_params(color='none', pad=-2)
    ax_bar2.set_facecolor('#f8f8f8')
    ax.set_facecolor('#f8f8f8')
    fig.set_facecolor('#f8f8f8')
    
    
    fig.savefig('输出结果.png', dpi=800, bbox_inches='tight')

    下面是绘图代码,都有注释说明,读一遍应该都能读懂,只是一些巧妙计算的逻辑需要理一下。这里没有绘制标题,可以借助PS添加一个完美的标题。

    fig, ax = plt.subplots(figsize=(4.8, 6))
    ax.set_xlim(0, 1.11)
    ax.set_ylim(0, 1)
    for row in range(mydata.shape[0]):
        # 定义区域填充对应的x坐标
        x = [0, 0.15, 0.215, 0.6+mydata.at[row, 'Values'] / 1000]
        # 生成区域填充对应的y坐标
        line1, line2 = create_fill_area(row)
        # 对指定区域进行填充
        ax.fill_between(x,
                        line1,
                        line2,
                        color=mycolor[mydata.at[row, 'Type']],
                        edgecolor='none')
        
        # 从logo文件夹下读取对应logo图片
        try:
            logo = plt.imread(f'logo/{mydata.at[row, "Tools"]}.png')
        except FileNotFoundError:
            logo = plt.imread(f'logo/{mydata.at[row, "Tools"]}.jpg')
        
        # 插入软件logo
        ax_logo = ax.inset_axes((0.05, 1 - ratio*(row+1)+0.005, 0.08, logosize))
        ax_logo.imshow(logo)
        
        ax_logo.axis('off')
        ax_logo.set_facecolor(mycolor[mydata.at[row, 'Type']])
        
        # 处理单个及多个功能情况下的绘制
        for idx, Category in enumerate(mydata.at[row, 'Category'].split('&')[::-1]):
            
            # 读取对应功能图片
            flag = plt.imread(f'flag/{Category}.png')
            # 插入功能子图
            ax_flag = ax.inset_axes((0.545-idx*0.06, 0.013+(right_height - row - 1)*((ratio2 - ratio3) / right_height), 0.1, 0.025))
            ax_flag.imshow(flag)
            ax_flag.axis('off')
            ax_flag.set_facecolor(mycolor[mydata.at[row, 'Type']])
        # 绘制排名
        ax.text(0.025, (1 - ratio*row + 1 - ratio*(row+1)) / 2, str(row+1), 
                ha='center', va='center',
                fontsize=9, color='black')
        # 绘制软件名称
        ax.text(0.215+ratio5, 0.5 * (ratio5 + (right_height - row - 1) * (ratio2 - ratio5) / right_height + ratio5 + (right_height - row) * (ratio2 - ratio5) / right_height), 
                mydata.at[row, 'Tools'], 
                ha='left', va='center',
                fontsize=9, color='#FFFFFF', 
                weight='bold')
        # 处理第一名文字在填充区域内部,其余文字在填充区域外的情况
        if mydata.at[row, 'Tools'] == 'Exce1l':
            ax.text(1, 0.5 * (ratio3 + (right_height - row) * (ratio2 - ratio3) / right_height 
                              + ratio3 + (right_height - row - 1) * (ratio2 - ratio3) / right_height)-0.0025,
                    ''+str(mydata.at[row, 'Values']/4)+'%',
                    color='white',
                    fontsize=10,
                    ha='right',
                    va='center',
                    weight='bold')
        else:
            # 配合归一化对字体进行大小映射
            ax.text(0.6+mydata.at[row, 'Values'] / 1000 + ratio3, 
                    0.5 * (ratio3 + (right_height - row) * (ratio2 - ratio3) / right_height + ratio3 + (right_height - row - 1) * (ratio2 - ratio3) / right_height)-0.0025,
                    ''+str(int(mydata.at[row, 'Values']/4))+'%',
                    color=mycolor[mydata.at[row, 'Type']],
                    fontsize=7+((mydata.at[row, 'Values'] - mydata['Values'].min()) 
                                / (mydata['Values'].max() - mydata['Values'].min())) * 5,
                    ha='left',
                    va='center',
                    weight='bold')
    # 对指定区域进行带透明度的黑色蒙版,以达到阴影效果
    ax.fill_between([0.15, 0.215],
                    [0, ratio4],
                    [1, ratio2],
                    color='black',
                    alpha=0.2, # 设置透明度
                    edgecolor='none')
    # 补充其余文字标注
    ax.text(0.215+ratio5, 0.805, '软件名称', 
            color='#565555', fontsize=6,
            ha='left')
    ax.text(0.67, 0.805, '软件类型', 
            color='#565555', fontsize=6,
            ha='center')
    #补充上方数值刻度
    ax.text(0.6, 0.825, '0', 
            color='#a9a8a8', fontsize=8,
            ha='center')    
    for i in range(1, 5):
        print(i)
        ax.text(0.6+0.1*i, 0.825, f'{i*25}%', 
                color='#a9a8a8', fontsize=9,
                ha='center')   
        ax.vlines(0.6+0.1*i, 0.01, 0.82, 
                  color='#dcdcdb', linewidth=0.2)
    ax.set_xticks([])
    ax.set_yticks([])
    ax.spines['left'].set_color('none')
    ax.spines['right'].set_color('none')
    ax.spines['top'].set_color('none')
    ax.spines['bottom'].set_color('none')
    # 补充下排图例
    ax_bar1 = ax.inset_axes((0.215, 0.88, 0.57, 0.02), transform=ax.transAxes)
    ax_bar1.set_xlim(-0.45, 3.6)
    ax_bar1.bar(range(4), height=1, width=0.8, 
                color=['#8ABD25', '#F57FEF', '#EBE639', '#EB3939'])
    ax_bar1.set_xticks(range(4))
    ax_bar1.set_xticklabels(['绿色系', '紫色系', '黄色系', '红色系'],
                            fontsize=7, color='#4f4e4e', weight='bold')
    ax_bar1.set_yticks([])
    ax_bar1.spines['left'].set_color('none')
    ax_bar1.spines['right'].set_color('none')
    ax_bar1.spines['top'].set_color('none')
    ax_bar1.spines['bottom'].set_color('none')
    ax_bar1.tick_params(color='none', pad=-2)
    ax_bar1.set_facecolor('#f8f8f8')
    
    
    # 补充上排图例
    ax_bar2 = ax.inset_axes((0.215, 0.98, 0.57, 0.02), transform=ax.transAxes)
    ax_bar2.set_xlim(-0.45, 3.6)
    ax_bar2.bar(range(4), height=1, width=0.8, 
                color=['#EBAF39', '#39A4EB', '#4D6E83', '#A3A4A5'])
    
    
    ax_bar2.set_xticks(range(4))
    ax_bar2.set_xticklabels(['橙色系', '蓝色系', '黑色系', '灰色系'],
                            fontsize=7, color='#4f4e4e', weight='bold')
    ax_bar2.set_yticks([])
    ax_bar2.spines['left'].set_color('none')
    ax_bar2.spines['right'].set_color('none')
    ax_bar2.spines['top'].set_color('none')
    ax_bar2.spines['bottom'].set_color('none')
    ax_bar2.tick_params(color='none', pad=-2)
    ax_bar2.set_facecolor('#f8f8f8')
    ax.set_facecolor('#f8f8f8')
    fig.set_facecolor('#f8f8f8')
    
    
    fig.savefig('输出结果.png', dpi=800, bbox_inches='tight')

     

    这个颜色有点艳丽,可以调整一下颜色就行了,然后用PS加个标题,底部再加点看不懂的小文字显得高端,然后基本上就大功告成了。

  • 相关阅读:
    MySQL存储过程和函数
    MySQL数据类型
    MySQL—基础(SQL语句)
    如何将一串字符串按照某个特定的字符分割后倒叙输出,如:www.baidu.com输出为com.baidu.www
    JAVA WEB数据中文编码问题
    如何用一条SQL语句从登录日志表中查询统计出每个人登录的次数
    JAVA WEB tomcat启动关闭问题
    thinkphp知识点
    smarty模板内容
    smarty基础
  • 原文地址:https://www.cnblogs.com/hhh188764/p/14190477.html
Copyright © 2011-2022 走看看