ThingsBoard 地图组件聚类标记颜色函数(Clustering Marker Function):参数、示例与底层实现解析
ThingsBoard 地图组件聚类标记颜色函数(Clustering Marker Function):参数、示例与底层实现解析
导读
本文围绕 ThingsBoard 地图组件中的**聚类标记颜色函数(Clustering marker function)**展开,该函数允许开发者通过一段 JavaScript 代码,根据聚类内的标记数据(如温度遥测值)或聚类的子标记数量(childCount),动态计算聚类标记的显示颜色,从而实现"绿色代表正常、红色代表告警"之类的可视化效果。读完本文,你将掌握该函数的完整签名、参数结构、返回值规则,以及它在 ThingsBoard 地图插件底层的真实调用链与默认兜底逻辑,可以直接在自己的地图组件中编写并调试聚类配色函数。
一、函数是什么:地图标记聚类的动态配色入口
在地图组件(Map Widget)中,当标记数量较多时,ThingsBoard 会启用**标记聚类(Marker Clustering)**能力:彼此邻近的多个标记被合并显示为一个"聚类标记"(cluster marker),避免地图视觉过载。聚类标记的默认样式由聚类包含的子标记数量决定——数量少时显示小尺寸标记,数量多时显示大尺寸标记。
聚类标记颜色函数(clustering_color_fn)就是用来按业务规则动态决定聚类标记颜色的扩展点。它由设置面板中的"使用聚类标记颜色函数"(Use cluster marker color function)开关控制,该开关在以下两处设置组件中均可配置:
- 传统地图(map-legacy)设置:marker-clustering-settings.component.html,通过
helpId="widget/lib/map-legacy/clustering_color_fn"直接引用本文档; - 通用地图数据图层设置:marker-clustering-settings.component.html,对应新版地图数据图层的聚类配置。
当开关打开后,你可以在随附的代码编辑器中输入一个 JavaScript 函数,该函数会在每次绘制聚类标记时被调用,其返回值作为聚类标记的颜色。
二、函数签名与参数详解
函数的签名固定为:
function (data, childCount): string
即接收两个参数,返回一个字符串类型的颜色值。
2.1 参数 data:FormattedData[]
data 是该聚类所包含的全部子标记(marker)组成的数组,每个元素是一个 FormattedData 对象。它代表实体的基本属性(例如 entityId、entityName),并提供对实体其他属性(attributes)与遥测数据(timeseries)的访问——前提是这些数据键已在小部件的数据源(datasource)配置中声明。
从当前仓库源码看,FormattedData 接口定义于 widget.models.ts,其核心字段如下:
| 字段 | 类型 | 含义 |
|---|---|---|
entityId |
string |
实体唯一标识 |
entityName |
string |
实体名称 |
deviceName |
string |
设备名称 |
entityType |
EntityType |
实体类型(如 DEVICE、ASSET) |
entityLabel |
string |
实体标签 |
entityDescription |
string |
实体描述 |
aliasName |
string |
实体别名(来自别名定义) |
dsIndex |
number |
数据源在数组中的索引 |
dsName |
string |
数据源名称 |
deviceType |
string |
设备类型 |
$datasource |
Datasource |
对应的数据源对象 |
[key: string]: any |
any |
数据源配置中声明的其他属性/遥测键 |
最后一行 [key: string]: any 是关键:只要你在数据源配置中声明了某个键(例如 temperature),在函数中就可以通过 markerData.temperature 直接读取其值。
2.2 参数 childCount:number
childCount 是该聚类中包含的子标记总数。它不依赖你的数据键,无论聚类由多少个标记聚合而成,该值都会准确反映实际数量,适合用来实现"按规模分级着色"。
2.3 返回值:颜色字符串
函数应返回一个字符串形式的颜色值,用于呈现聚类标记。支持的取值与 CSS 颜色一致,例如:
- 颜色名称:
'red'、'green'、'yellow' - 十六进制:
'#FF0000' rgb()/rgba()表达式
兜底规则:如果函数没有返回任何数据(例如返回了 undefined 或空值),则使用设置字段 Color(颜色) 中配置的颜色值。
三、官方示例:按遥测值与按规模分级着色
以下两个示例均出自本文档,可直接复制到地图组件的函数编辑器中({:copy-code} 为 ThingsBoard 的代码块复制标记)。
示例一:根据温度遥测值计算颜色
let customColor;
for (let markerData of data) {
if (markerData.temperature > 40) {
customColor = 'red'
}
}
return customColor ? customColor : 'green';
逻辑说明:遍历聚类内全部标记,若任一标记的温度遥测值超过 40,则将聚类标记染为红色;否则返回绿色。需要注意的是,该循环遍历的是聚类内所有标记,因此只要有一个标记超阈值,整个聚类就会显示红色——这符合"聚类中任一设备告警则整个聚类标红"的运维直觉。
示例二:根据 childCount 计算颜色
if (childCount < 10) {
return 'green';
} else if (childCount < 100) {
return 'yellow';
} else {
return 'red';
}
逻辑说明:按聚类包含的标记数量分三档着色——少于 10 个为绿色,10 至 99 个为黄色,100 个及以上为红色。这个模式适用于"按区域设备密度预警"的场景。
扩展:同时结合遥测值与数量的综合配色
你可以自由组合上述两种策略,例如先按告警阈值判断,再按规模兜底:
let alert = false;
for (let markerData of data) {
if (markerData.temperature > 40 || markerData.humidity > 80) {
alert = true;
}
}
if (alert) {
return 'red';
} else if (childCount > 100) {
return 'orange';
} else {
return 'green';
}
四、底层实现原理解析(源码级)
从源码结构看,聚类颜色函数的实际执行链路位于 markers-data-layer.ts(新版地图数据图层;传统 map-legacy 地图亦复用同一套聚类机制与帮助文档入口)。
4.1 函数的编译与校验
在数据图层初始化阶段,组件会将设置中配置的函数字符串编译为可执行函数,并显式声明其形参为 data 与 childCount:
parseTbFunction<ClusterMarkerColorFunction>(
this.getCtx().http,
this.settings.markerClustering.clusterMarkerColorFunction,
['data', 'childCount']
).pipe(...)
对应源码位置:markers-data-layer.ts。parseTbFunction 的第二个参数指定了函数签名,编译结果缓存在 this.clusterMarkerColorFunction 中,供每次绘制聚类标记时调用。
4.2 聚类标记的绘制与参数注入
当开启"使用聚类标记颜色函数"(useClusterMarkerColorFunction)后,组件会为聚类图层配置 iconCreateFunction,核心逻辑如下:
iconCreateFunction = (cluster) => {
const childCount = cluster.getChildCount();
const data = cluster.getAllChildMarkers()
.map(clusterMarker => clusterMarker.options.tbMarkerData);
const markerColor = this.clusterMarkerColorFunction ?
safeExecuteTbFunction(this.clusterMarkerColorFunction, [data, childCount]) : null;
...
}
对应源码位置:markers-data-layer.ts。这里印证了文档中的两个关键点:
childCount来自聚类对象的getChildCount(),即真实的子标记总数;data通过遍历聚类内全部子标记、读取每个标记的tbMarkerData得到,即FormattedData[]数组。
4.3 颜色校验与默认兜底
返回值不会直接使用,而是先经过校验:
if (isDefinedAndNotNull(markerColor) && tinycolor(markerColor).isValid()) {
// 使用函数返回的颜色绘制聚类标记图标
const parsedColor = tinycolor(markerColor);
const alpha = parsedColor.getAlpha();
return L.divIcon({
html: `<div style="background-color: ${parsedColor.setAlpha(alpha * 0.4).toRgbString()};" ...>
<div style="background-color: ${parsedColor.setAlpha(alpha * 0.9).toRgbString()};">
<span>${childCount}</span>
</div>`,
...
});
}
对应源码位置:markers-data-layer.ts。从中可以提炼三个实现事实:
- 颜色必须合法:函数返回值需通过
tinycolor的颜色有效性校验(支持颜色名、十六进制、rgb/rgba等格式)。若返回非法值(如任意文本),则回退到默认样式; - 透明度分层:合法的颜色会被拆分为两层——外层以 40% 透明度填充、内层以 90% 透明度填充,形成圆角聚类的层次感;
- 默认分级样式:当函数未启用或返回无效颜色时,组件按
childCount套用内置 CSS 分级:小于 10 为small、10 至 99 为medium、100 及以上为large,即"小于 10 绿色、小于 100 黄色、其余红色"的默认视觉(源码中的默认分级逻辑见 markers-data-layer.ts)。
4.4 与文档"Color 设置兜底"的关系
文档指出"未返回数据时使用 Color 设置字段的颜色"。结合上面的源码可以看到,这一兜底实际表现为两级:函数未启用或返回空/非法值时,首先走的是内置的 small/medium/large 分级样式;而"Color(颜色)"设置字段在传统地图配置中用于指定标记本身的默认颜色。因此编写函数时,请务必保证所有分支都有返回值(官方示例以 customColor ? customColor : 'green' 的兜底写法收尾),以获得完全可控的配色效果。
五、使用步骤与调试建议
5.1 启用并配置函数
- 打开地图小部件(Map Widget)的编辑界面,进入标记聚类设置(Marker Clustering Settings);
- 打开使用地图标记聚类(Use map markers clustering)开关,并可根据需要配置
zoomOnClick(点击聚类是否缩放)、maxZoom(最大缩放层级)、maxClusterRadius(聚类最大半径)、zoomAnimation(缩放动画)、chunkedLoad(分块加载)等选项; - 打开使用聚类标记颜色函数(Use cluster marker color function)开关;
- 在函数编辑框中粘贴第二节或第三节的代码,保存小部件。
5.2 调试要点
- 数据键声明:函数中访问的遥测/属性键(如
markerData.temperature)必须在数据源配置中声明,否则该字段为undefined; - 返回值检查:所有分支都要返回一个
tinycolor可解析的合法颜色字符串,或显式返回空值以触发默认分级样式; - 遍历全部标记:
data是数组,判断聚类告警状态时要遍历(如for...of),不要只检查data[0]; - 颜色格式:推荐使用标准颜色名或十六进制,避免依赖个别浏览器才支持的扩展色名。
六、相关仓库文件导航
- 本文档(帮助原文):clustering_color_fn.md
- 传统地图聚类设置组件(引用本文档帮助):marker-clustering-settings.component.html
- 通用地图聚类设置组件:marker-clustering-settings.component.html
FormattedData接口定义:widget.models.ts- 聚类标记绘制与函数执行源码:markers-data-layer.ts