pythongraphcolorsgradienteach

How to create a colour gradient in each graph bar python?


This is the current output of my code at the moment.

I have this the code:

azul das barras

rb=0
gb=80
bb=141
color_bar=rgb_to_hex(rb, gb, bb)

laranja da linha

rl=237
gl=125
bl=49

color_line=rgb_to_hex(rl, gl, bl)

cor branco

rbra=255
gbra=255
bbra=255

color_text_table=rgb_to_hex(rbra,gbra,bbra)

#Tamanho da letra e espessura linha fontsize=18 linewidth=5

#configuração tamanho da imagem plt.rcParams['figure.figsize']=(20,10)

#Alterar cor da frame do gráfico ou outros parametros plt.rc('axes',edgecolor=color_bar, lw=linewidth-2.5)

#Criar Gráfico de Barras

width_bar=0.35
lim_sup=grouped\['OID_MEM_ID'\].max()+(grouped\['OID_MEM_ID'\].max()-grouped\['OID_MEM_ID'\].min())\*2
ax = grouped.plot('Mês Ano','OID_MEM_ID',
color=color_bar,
kind ='bar',
fontsize=fontsize+2,
legend=False,
width=width_bar,
ylim=(0,lim_sup))

#Retirar valores y do gráfico ax.axes.yaxis.set_ticklabels([])

I expect this output

What can I do to improve my solution?


Solution

  • We can use a combination of matplotlib's mcolors, Polygon, and Bbox to put together a gradient bar plot:

    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib.colors as mcolors
    from matplotlib.patches import Polygon
    from matplotlib.transforms import Bbox
    
    y = np.random.random(4)*0.01
    fill_color = [0, 0.25, 0.75]
    box_width = 0.35
    box_floor = 0
    box_alpha = 1.0
    ax = plt.subplot(1, 1, 1)
    rgb = mcolors.colorConverter.to_rgb(fill_color)
    box_color = np.zeros((100, 1, 4), dtype=float)
    box_color[:, :, :3] = rgb
    box_color[:, :, -1] = np.linspace(0, box_alpha, 100)[:, None]
    for _x in range(len(y)):
        im = ax.imshow(box_color, aspect='auto', extent=[_x - box_width + 1, _x + box_width + 1, box_floor, y[_x]], origin='lower')
        xy = np.vstack([[_x - box_width + 1, box_floor], Bbox.from_bounds(_x - box_width + 1, _x + box_width + 1, box_floor, y[_x]), [_x + box_width + 1, box_floor], [_x - box_width + 1, box_floor]])
        clip_path = Polygon(xy, facecolor='none', edgecolor='none', closed=True)
        ax.add_patch(clip_path)
        im.set_clip_path(clip_path)
    ax.plot(np.arange(1, len(y) + 1, 1), y[::-1], ls='-', lw=1.5, color=[1, 0.5, 0])
    ax.set_ylim(0, y.max() + (0.1*y.max()))
    ax.set_xlim(-box_width, len(y) + box_width + 1)
    plt.show()
    

    Outputs:

    enter image description here

    However, it doesn't scale out well beyond the tenths place (0.1) so for data with larger numbers we'd have to use something like rescale_y and also adjust the yticklabels:

    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib.colors as mcolors
    from matplotlib.patches import Polygon
    from matplotlib.transforms import Bbox
    
    _y = np.random.random(4)*1000
    def rescale_y(_y, _exp=0):
        while max(_y) > 0.1:
            _exp += 1
            _y*=0.1
        return _y, _exp
    
    y, _exp = rescale_y(_y)
    fill_color = [0, 0.25, 0.75]
    box_width = 0.35
    box_floor = 0
    box_alpha = 1.0
    ax = plt.subplot(1, 1, 1)
    rgb = mcolors.colorConverter.to_rgb(fill_color)
    box_color = np.zeros((100, 1, 4), dtype=float)
    box_color[:, :, :3] = rgb
    box_color[:, :, -1] = np.linspace(0, box_alpha, 100)[:, None]
    for _x in range(len(y)):
        im = ax.imshow(box_color, aspect='auto', extent=[_x - box_width + 1, _x + box_width + 1, box_floor, y[_x]], origin='lower')
        xy = np.vstack([[_x - box_width + 1, box_floor], Bbox.from_bounds(_x - box_width + 1, _x + box_width + 1, box_floor, y[_x]), [_x + box_width + 1, box_floor], [_x - box_width + 1, box_floor]])
        clip_path = Polygon(xy, facecolor='none', edgecolor='none', closed=True)
        ax.add_patch(clip_path)
        im.set_clip_path(clip_path)
    ax.plot(np.arange(1, len(y) + 1, 1), y[::-1], ls='-', lw=1.5, color=[1, 0.5, 0])
    ax.set_ylim(0, y.max() + (0.1*y.max()))
    if _exp:
        ax.set_yticklabels([str(round(_tick*(10**(_exp)))) for _tick in ax.get_yticks()])
    ax.set_xlim(-box_width, len(y) + box_width + 1)
    plt.show()
    

    Outputs:

    enter image description here

    Otherwise...

    enter image description here

    If we want different fill_colors for each bar in our plot:

    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib.colors as mcolors
    from matplotlib.patches import Polygon
    from matplotlib.transforms import Bbox
    
    _y = np.random.random(4)*1000
    def rescale_y(_y, _exp=0):
        while max(_y) > 0.1:
            _exp += 1
            _y*=0.1
        return _y, _exp
    
    y, _exp = rescale_y(_y)
    fill_colors = [[0, 0.25, 0.75], [1, 0, 0], [0, 1, 0], [1, 1, 0]]
    box_width = 0.35
    box_floor = 0
    box_alpha = 1.0
    ax = plt.subplot(1, 1, 1)
    for _x in range(len(y)):
        rgb = mcolors.colorConverter.to_rgb(fill_colors[_x])
        box_color = np.zeros((100, 1, 4), dtype=float)
        box_color[:, :, :3] = rgb
        box_color[:, :, -1] = np.linspace(0, box_alpha, 100)[:, None]
        im = ax.imshow(box_color, aspect='auto', extent=[_x - box_width + 1, _x + box_width + 1, box_floor, y[_x]], origin='lower')
        xy = np.vstack([[_x - box_width + 1, box_floor], Bbox.from_bounds(_x - box_width + 1, _x + box_width + 1, box_floor, y[_x]), [_x + box_width + 1, box_floor], [_x - box_width + 1, box_floor]])
        clip_path = Polygon(xy, facecolor='none', edgecolor='none', closed=True)
        ax.add_patch(clip_path)
        im.set_clip_path(clip_path)
    ax.plot(np.arange(1, len(y) + 1, 1), y[::-1], ls='-', lw=1.5, color=[1, 0.5, 0])
    ax.set_ylim(0, y.max() + (0.1*y.max()))
    if _exp:
        ax.set_yticklabels([str(round(_tick*(10**(_exp)))) for _tick in ax.get_yticks()])
    ax.set_xlim(-box_width, len(y) + box_width + 1)
    plt.show()
    

    Outputs:

    enter image description here