用渐变填充条形图的径向条

Posted

技术标签:

【中文标题】用渐变填充条形图的径向条【英文标题】:Fill radial bars of barplot with gradient 【发布时间】:2022-01-19 12:09:47 【问题描述】:

我正在尝试生成显示各种颜色范围的径向(圆形)条形图,以便比较它们。使用径向条形图可以更好地理解色调值的圆形性质并表示超出范围的范围,例如 [340°, 10°]

为了用这张图片进行说明,想法是将左侧条形图的条的黑色替换为对应的彩色圆圈部分(显示在右侧)。

这是我用来生成径向条形图的代码:

import matplotlib.pyplot as plt
from numpy import pi

start_values = [0, 3*pi/4, pi/6]
range_values = [pi/3, 3*pi/10, pi/2]

ax = plt.subplot(projection='polar')

for i in range(len(start_values)):
    ax.barh(i, range_values[i], left=start_values[i], color=(0, 0, 0))

我想应用 Matplotlib 的 hsv 颜色图,但 barh() 的颜色参数不接受。

在互联网上,我只找到了将渐变或 cmaps 应用于带有矩形条的常规条形图的技术(例如:How to fill matplotlib bars with a gradient? 但是,这些技术不适用于圆形条。

【问题讨论】:

【参考方案1】:

感谢皮埃尔,我使用您的技术构建了一个图表,该图表也将饱和度和值表示为半径方向上的梯度。 这是给定示例的返回值(添加了饱和度和值范围)

代码:

def radius_barplot(lower_color, upper_color, ax, index=None, angle_precision=1, full_color=0.9, plot_index=(1, 1, 1)):
    
    ax = plt.subplot(plot_index[0], plot_index[1], plot_index[2], projection="polar")
    
    #print("Building graph")
    if index is None:
        if isinstance(lower_color[0], pd.Series):
            index = lower_color[0].index
            lower_color = [parameter.tolist() for parameter in lower_color]
            upper_color = [parameter.tolist() for parameter in upper_color]
        else:
            index= range(len(lower_color[0]))
            
    pi2 = 2*pi
    hue_only = 1 - full_color

    for i in range(len(index)):

        #print("Frame n°", i+1, sep='')
        min_hue, max_hue = lower_color[0][i] / 179 * 2 * pi, upper_color[0][i] / 179 * 2 * pi
        min_sat, min_val = lower_color[1][i] / 255, lower_color[2][i] / 255
        range_hue, range_sat, range_val = max_hue - min_hue, round(upper_color[1][i] - lower_color[1][i]), round(upper_color[2][i] - lower_color[2][i])
        n_height = min(max(range_sat, range_val), 20)
        pas_height = 1*full_color/n_height
        large_pas_angle, large_pas_height = angle_precision*1.5/180*pi, pas_height*1.25
        pas_sat, pas_val = range_sat / 255 / n_height, range_val / 255 / n_height

        #print("Angle range: [", round(min_hue/pi*180), "; ", round((max_hue)/pi*180), "]", sep='')
        
        # For each degree of the angle
        for angle in np.arange(min_hue, max_hue, angle_precision/180*pi):
            
            # To avoid an overflow on the end of the interval
            if angle + large_pas_angle > max_hue: large_pas_angle = max_hue - angle
                
            # Keep a positive angle for the hue value
            if angle < 0: angle = 2*pi + angle
                        
            # For each line inside the current bar
            if full_color > 0:
                for height in range(n_height):
                    """
                    print("Height: [", round(i + height*pas_height, 2), ", ", round(i + height*pas_height + pas_height, 2),
                          "], angle: ", round(angle/pi*180, 3),
                          "°: color [", round(angle*360), ", ", round((min_sat + height*pas_sat)*100), ", ", round((min_val + height*pas_val)*100), "]", sep='')
                    """
                
                    ax.barh(y=i + height*pas_height,
                            width=large_pas_angle, left=angle, height=large_pas_height, align='edge',
                            color=colorsys.hsv_to_rgb(angle/pi2,
                                                      min_sat + height*pas_sat,
                                                      min_val + height*pas_val)
                    )
            
            
            # To display the reference hue (with full saturation and value)
            ax.barh(y=i + full_color,
                    width=large_pas_angle, left=angle, height=hue_only, align='edge',
                    color=(colorsys.hsv_to_rgb(angle/pi2, 1, 1)))
            # """
        ax.set_rgrids(range(len(index)), labels=index)

radius_barplot(
    ((0, (3*pi/4)/(2*pi)*179, (pi/6)/(2*pi)*179), (0, 200, 50), (0, 50, 200)),  # Minimal values of colors ranges
    (((pi/3)/(2*pi)*179, (21*pi/20)/(2*pi)*179, (2*pi/3)/(2*pi)*179), (255, 250, 100), (255, 100, 250)),  # Maximal values of colors ranges
    axes)

图表:

这种技术的主要问题是它的复杂性:在我的计算机上返回这​​张图需要 1 分 45 秒,而获得我感兴趣的图则需要将近 5 分钟。

因此,如果有人有更好、更优化的方法来获得类似的结果,我仍然会感兴趣。

【讨论】:

【参考方案2】:

不是最优雅的版本,但它可以完成工作

import matplotlib.pyplot as plt
from numpy import pi
import colorsys

start_values = [0, 3*pi/4, pi/6]
range_values = [pi/3, 3*pi/10, pi/2]

ax = plt.subplot(projection='polar')

n = 100

for i in range(len(start_values)):
  ax.barh(i, [range_values[i]/n]*n,
          left=[start_values[i]+j*range_values[i]/n for j in range(n)],
          color=[colorsys.hsv_to_rgb((start_values[i]+(j+0.5)*range_values[i]/n)/(2*pi),1,1) for j in range(n)])

plt.show()

输出:

【讨论】:

以上是关于用渐变填充条形图的径向条的主要内容,如果未能解决你的问题,请参考以下文章

无绘制圆形渐变

如何配置柱形图的颜色?

直方图和条形图有啥区别?

带有组和构面的堆叠 ggplot 条形图的百分比标签

java生成饼状图,条形图,折线图的技术可以动态的显示

如何给SVG填充和描边应用线性渐变