Matplotlib自定义ListedColormap实现烈度值与颜色固定映射
问题根因
当前映射逻辑失效的核心原因是 仅创建了离散色表,但没有固定烈度值和色标区间的一一绑定关系,matplotlib会默认根据传入GeoDataFrame的 Intensity 字段实际取值范围动态计算归一化边界:
- 多值场景下,实际取值的上下限刚好覆盖预设烈度分级区间,所以映射结果碰巧符合预期
- 单值场景下,Intensity 字段的最小值和最大值完全相等,动态计算的归一化区间发生退化,导致所有值直接映射到色表的起始位(也就是定义的第一个颜色
#f3f3f3)
修复实现方案
要实现固定的单个烈度值对应唯一专属颜色的映射规则,不需要依赖matplotlib的动态归一化逻辑,需要把烈度值和色标的绑定关系写死,分三步调整代码:
- 明确固定所有的烈度取值集合,不把分级逻辑藏在颜色数组里
# 预设的9级地震烈度固定取值,顺序与自定义颜色一一对应 intensity_levels = [1, 2, 3, 4, 5, 5.5, 6, 6.5, 7] # 每个烈度值对应的专属自定义颜色 color_map = [ '#f3f3f3', '#1aff1a', '#ffff00', '#ff8900', '#ff5500', '#b64100', '#993300', '#aa3c00', '#800080' ] cm = matplotlib.colors.ListedColormap(color_map)
- 构造固定的色标映射边界,替代matplotlib的动态范围计算
用BoundaryNorm实现硬绑定,让每个烈度值独占一个颜色的映射区间,不受传入数据的取值范围影响:
# 构造固定边界:在相邻烈度值之间取中点,确保每个烈度值落在独立区间内 bounds = [ intensity_levels[0] - 0.5, # 区间下界,覆盖所有可能的最小烈度 1.5, # 分隔1和2的中点 2.5, # 分隔2和3的中点 3.5, # 分隔3和4的中点 4.5, # 分隔4和5的中点 5.25, # 分隔5和5.5的中点 5.75, # 分隔5.5和6的中点 6.25, # 分隔6和6.5的中点 6.75, # 分隔6.5和7的中点 intensity_levels[-1] + 0.5 # 区间上界,覆盖所有可能的最大烈度 ] # 生成固定规则的归一化器,完全锁定映射逻辑 norm = matplotlib.colors.BoundaryNorm(bounds, ncolors=cm.N)
- 绘图时传入固定归一化规则,覆盖默认的动态计算逻辑
draw_area.plot( "Intensity", ax=axes, cmap=cm, norm=norm, # 传入固定归一化规则,替代默认的动态范围计算 legend=True )
可选优化:匹配自定义图例展示
默认BoundaryNorm生成的图例是按照数值区间展示的,如果需要直接显示对应烈度值作为标签,可以在绘图后手动调整图例刻度:
# 获取自动生成的图例对象 leg = axes.get_legend() # 把图例的刻度位置替换为真实烈度值 leg.set_ticks(intensity_levels) # 把图例标签替换为对应烈度的字符串 leg.set_ticklabels([str(x) for x in intensity_levels])
效果验证
调整完成后:
- 单值场景下(比如全区域Intensity为2),会按照固定规则映射到对应的专属颜色
#1aff1a,不会再出现统一映射到色表首色的问题 - 多值场景下,每个烈度值都会严格匹配预先配置的自定义颜色,不会因为数据范围变化发生色标偏移
内容的提问来源于stack exchange,提问作者Lambertchen
相关产品推荐
相关产品推荐

