ThingsBoard 地图组件聚类标记颜色函数(Clustering Marker Function):参数、示例与底层实现解析

原创2026-10-03 12:28:211,411 阅读
文章标签:物联网后端数据可视化消息队列

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)开关控制,该开关在以下两处设置组件中均可配置:

当开关打开后,你可以在随附的代码编辑器中输入一个 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。这里印证了文档中的两个关键点:

  1. childCount 来自聚类对象的 getChildCount(),即真实的子标记总数;
  2. 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 启用并配置函数

  1. 打开地图小部件(Map Widget)的编辑界面,进入标记聚类设置(Marker Clustering Settings);
  2. 打开使用地图标记聚类(Use map markers clustering)开关,并可根据需要配置 zoomOnClick(点击聚类是否缩放)、maxZoom(最大缩放层级)、maxClusterRadius(聚类最大半径)、zoomAnimation(缩放动画)、chunkedLoad(分块加载)等选项;
  3. 打开使用聚类标记颜色函数(Use cluster marker color function)开关;
  4. 在函数编辑框中粘贴第二节或第三节的代码,保存小部件。

5.2 调试要点

  • 数据键声明:函数中访问的遥测/属性键(如 markerData.temperature)必须在数据源配置中声明,否则该字段为 undefined;
  • 返回值检查:所有分支都要返回一个 tinycolor 可解析的合法颜色字符串,或显式返回空值以触发默认分级样式;
  • 遍历全部标记:data 是数组,判断聚类告警状态时要遍历(如 for...of),不要只检查 data[0];
  • 颜色格式:推荐使用标准颜色名或十六进制,避免依赖个别浏览器才支持的扩展色名。

六、相关仓库文件导航

登录后查看全文
thingsboard