HMIx 实用指南
为 WinCC Unified 创建饼图 Custom Web Control
饼图可以直观比较多条产线的产量。我们将用 HTML、CSS 和 JavaScript 创建一个环形饼图,再把它加入 TIA Portal,用三个内部变量进行测试。

本指南的操作环境:TIA Portal / WinCC Unified V21.1。请在目标设备上检查示例的导入和运行情况。
什么是 Custom Web Control?
Custom Web Control,简称 CWC,是放在 WinCC Unified 画面中的一个小型 Web 组件。HTML 定义内容,CSS 决定外观,JavaScript 负责响应。当标准 HMI 对象无法提供所需的显示方式或交互时,就可以考虑使用它。
例如生产看板、表格或饼图。控件通过属性接收数据,属性是有名称的输入,如 ValueA。在 TIA Portal 中将属性绑定到 HMI 变量后,CWC 就能接收数值并更新图表。控件本身不会直接连接 PLC。
- 需要自定义交互、多项关联数据或特殊布局时,可以选择 CWC。
- 如果只是让一个符号改变颜色、位置或填充高度,动态 SVG 往往更简单。
- 如果要重复使用一组标准 HMI 对象,可以考虑面板(faceplate)。
在 HMIx,你可以免费下载数百种 HMI 资源。可以从目录中的现成控件开始,也可以向 HMIx AI 生成器描述想法,创建适合自己项目的资源。

开始前需要准备什么
建立一个测试项目,添加 WinCC Unified 设备和空白画面。电脑需要安装带 WinCC Unified Engineering 的 TIA Portal,以及匹配的 Runtime 或仿真组件。传统 Comfort 项目与 Unified 项目是不同的目标。
- 首次测试可使用 Unified PC Runtime,或目标 Unified 设备支持的仿真功能。
- 如需修改示例,准备文本编辑器和 ZIP 压缩工具即可。Visual Studio Code 很方便,但并非必需。
- 无需 PLC:用三个 HMI 内部变量和三个输入/输出域提供测试数据。
浏览器预览只能检查外观。WebCC 通信和变量绑定必须在 Unified 中验证。
创建一个包含三个数值的饼图
这个饼图采用环形结构,中心显示合计,图例显示各个系列。三个输入为 ValueA、ValueB 和 ValueC,初始值分别是 45、35 和 20。它们代表数量,不必相加等于 100;控件会自动计算各自占总数的比例。
准备文件
在根目录创建 manifest.json,以及 assets 和 control 两个文件夹。在 control 中创建 index.html 和 code.js。从下方链接的西门子官方示例获取 webcc.min.js,也放入 control。每个文件各有用途,无需使用 Web 框架。
定义三个输入
在 manifest.json 中将 ValueA、ValueB 和 ValueC 声明为 number,默认值分别为 45、35 和 20。清单还需指定 HMIx Pie Chart 名称、控件标识和入口文件。
绘制图表和图例
在 control/index.html 中加入三个环形扇区、合计以及 A、B、C 图例。在 code.js 中用各项数值除以总数计算比例。如果三个值都为零,就显示空环。示例在 HTML 内使用 SVG,不需要额外的图表库。
连接 Unified
加载 Siemens 提供的 webcc.min.js,不要修改该文件。用相同的三个属性启动 WebCC,连接成功后读取当前值,并在 onPropertyChanged 通知变化时重绘。这样重新打开画面时也能显示当前数据。
打包文件
让 manifest.json、assets 和 control 直接位于 ZIP 根目录,不要再套一层文件夹。ZIP 名称应是用花括号包围的控件 GUID。修改示例后,按相同结构重新压缩;只更改外部 ZIP 文件名不会创建新的控件身份。
manifest.json 和 JavaScript 中的 ValueA、ValueB、ValueC 名称必须一致。本指南下载的是三个输入的原创教学示例;目录中的 Pie Chart 使用 Apache ECharts,包含五个带有 Value 和 Title 的元素。
完整 ZIP 包,包含三个数值输入和 WebCC。已检查代码与结构;请在 WinCC Unified V21.1 中验证导入和连接。
查看示例代码
可选阅读:下载文件包含完整示例。完成本次测试不需要手动输入这些代码。
manifest.json
{
"mver": "1.2.0",
"control": {
"identity": {
"name": "HmixPieChart",
"version": "1.0",
"displayname": "HMIx Pie Chart",
"type": "guid://8D821B43-4EF5-4BF1-A31D-931D283C7C04",
"start": "./control/index.html"
},
"metadata": {
"author": "HMIx",
"description": "Educational read-only three-value pie chart"
},
"contracts": {
"api": {
"methods": {},
"events": {},
"properties": {
"ValueA": { "type": "number", "default": 45 },
"ValueB": { "type": "number", "default": 35 },
"ValueC": { "type": "number", "default": 20 }
}
}
},
"types": {}
}
}
control/index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>HMIx Pie Chart</title>
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 20px;
background: #171e26;
color: #f3f5f6;
font:
14px system-ui,
sans-serif;
}
header {
display: flex;
justify-content: space-between;
color: #bac5ce;
font-size: 11px;
letter-spacing: 0.14em;
}
main {
display: flex;
align-items: center;
justify-content: center;
gap: 24px;
min-height: 240px;
}
svg {
width: 55%;
max-width: 300px;
flex-shrink: 0;
}
ul {
list-style: none;
padding: 0;
min-width: 110px;
}
li {
display: grid;
grid-template-columns: 10px 1fr auto;
align-items: center;
gap: 10px;
padding: 14px 0;
border-bottom: 1px solid #35404a;
}
.dot {
width: 8px;
height: 8px;
border-radius: 50%;
}
small {
display: block;
margin-top: 4px;
color: #bac5ce;
font-variant-numeric: tabular-nums;
}
strong {
font-variant-numeric: tabular-nums;
}
@media (max-width: 360px) {
body {
padding: 12px;
}
main {
gap: 12px;
}
}
</style>
</head>
<body>
<header><span>HMIx</span><span>PIE CHART</span></header>
<main>
<svg viewBox="0 0 220 220" role="img" aria-labelledby="chart-title">
<title id="chart-title">Distribution of A, B and C</title>
<circle
cx="110"
cy="110"
r="76"
fill="none"
stroke="#35404a"
stroke-width="28"
/>
<g transform="rotate(-90 110 110)" fill="none" stroke-width="28">
<circle id="ValueA" cx="110" cy="110" r="76" stroke="#47d2c8" />
<circle id="ValueB" cx="110" cy="110" r="76" stroke="#79a9ed" />
<circle id="ValueC" cx="110" cy="110" r="76" stroke="#f5c16c" />
</g>
<text
id="total"
x="110"
y="115"
text-anchor="middle"
fill="#f3f5f6"
font-size="30"
font-weight="600"
>
100
</text>
<text
x="110"
y="139"
text-anchor="middle"
fill="#bac5ce"
font-size="10"
letter-spacing="2"
>
A + B + C
</text>
</svg>
<ul aria-label="Values and shares">
<li>
<i class="dot" style="background: #47d2c8"></i
><span>A<small id="ValueA-amount">45</small></span
><strong id="ValueA-percent">45%</strong>
</li>
<li>
<i class="dot" style="background: #79a9ed"></i
><span>B<small id="ValueB-amount">35</small></span
><strong id="ValueB-percent">35%</strong>
</li>
<li>
<i class="dot" style="background: #f5c16c"></i
><span>C<small id="ValueC-amount">20</small></span
><strong id="ValueC-percent">20%</strong>
</li>
</ul>
</main>
<script src="webcc.min.js"></script>
<script src="code.js"></script>
</body>
</html>
control/code.js
/* HMIx educational pie chart. Values are amounts, not required percentages. */
var values = { ValueA: 45, ValueB: 35, ValueC: 20 };
var keys = ['ValueA', 'ValueB', 'ValueC'];
var circumference = 2 * Math.PI * 76;
function drawChart() {
// Normalize before adding to avoid overflow for very large finite values.
var maximum = Math.max(values.ValueA, values.ValueB, values.ValueC, 1);
var sum = keys.reduce(function (n, key) {
return n + values[key] / maximum;
}, 0);
var offset = 0;
keys.forEach(function (key) {
var fraction = sum ? values[key] / maximum / sum : 0;
var arc = document.getElementById(key);
var length = fraction * circumference;
arc.setAttribute('stroke-dasharray', length + ' ' + circumference);
arc.setAttribute('stroke-dashoffset', String(-offset));
arc.setAttribute('visibility', fraction ? 'visible' : 'hidden');
document.getElementById(key + '-amount').textContent = String(values[key]);
document.getElementById(key + '-percent').textContent =
Math.round(fraction * 100) + '%';
offset += length;
});
var total = values.ValueA + values.ValueB + values.ValueC;
document.getElementById('total').textContent = isFinite(total)
? String(total)
: '—';
}
function setValue(key, value) {
if (keys.indexOf(key) === -1 || typeof value !== 'number' || !isFinite(value))
return;
values[key] = Math.max(0, value);
}
drawChart();
if (typeof WebCC !== 'undefined') {
WebCC.start(
function (connected) {
if (!connected) return;
keys.forEach(function (key) {
setValue(key, WebCC.Properties[key]);
});
drawChart();
WebCC.onPropertyChanged.subscribe(function (change) {
setValue(change.key, change.value);
drawChart();
});
},
{
methods: {},
events: [],
properties: { ValueA: 45, ValueB: 35, ValueC: 20 },
},
['HMI'],
10000,
);
}
把 ZIP 加入 TIA Portal 项目
找到项目文件夹
打开存放 TIA Portal 项目的文件夹。在 UserFiles 内创建 CustomControls 文件夹;如果已存在,直接使用。
复制 CWC 包
将可导入的 {GUID}.zip 放入 UserFiles\CustomControls。这个 ZIP 应保持压缩状态。不要只复制 index.html,也不要把最外层的下载资源包误当作控件 ZIP。
刷新并放置控件
打开 Unified 画面。在工具 → 自定义控件(Tools → My controls)中刷新,将 HMIx Pie Chart 拖入画面。为图表和图例留出足够空间,例如 480 × 300 像素。
检查接口
选中控件,打开属性 → 属性 → 接口。找到 ValueA、ValueB 和 ValueC,先设置静态值 45、35 和 20。下一步再将它们绑定到变量。

用三个内部变量测试,无需 PLC
创建三个内部变量
在 HMI 变量表中添加 ProductionA、ProductionB 和 ProductionC,使用 Real 等数值类型,不连接 PLC。
逐一绑定输入和变量
在 CWC 接口中选择变量动态化:ValueA → ProductionA、ValueB → ProductionB、ValueC → ProductionC。保持“只读”启用,由输入/输出域修改数据。
添加三个输入/输出域
为每个变量添加一个输入/输出模式的数值域,分别标为 A、B 和 C。本次测试使用 0 到 100 的数量;如果可配置输入上下限,请一并设置。
编译并运行
将此画面设置为起始画面,编译 HMI 并修复报告的错误。启动支持的 HMI 仿真,或将测试项目加载到 Unified PC Runtime 后启动。打开 Runtime 客户端;如有登录要求,使用测试项目中配置的用户。
测试两组数据和全零状态
输入 45、35 和 20,并逐项确认。再把 A 改为 90:其扇区会变大,合计变为 145。最后把三个值都设为零,图表应显示空环。重新打开画面,检查是否显示当前数值。

先看看预期效果
调整 A、B 或 C,观察扇区变化。输入的是数量,图表会自动计算百分比。
满足这些条件,测试才算成功
45 / 35 / 20 的合计为 100。A = 90 时,合计为 145,A 约占 62%。三个值都为零时显示空环。重新打开画面后应显示当前数值。
遇到问题时如何检查
- 找不到控件:检查 UserFiles\CustomControls、ZIP 根目录结构、清单 JSON 语法和 GUID 文件名,然后刷新 My controls。
- 图表显示但不变化:检查 ValueA、ValueB、ValueC 的名称、变量绑定和 WebCC 连接。在本地打开 HTML 只能检查外观,不能验证与 Unified 的连接。
- 在 PC 上正常、在 Unified Comfort Panel 上失败:将依赖文件放进控件包。西门子对面板中的外部链接,以及通过 fetch 或 XMLHttpRequest 获取外部数据有明确限制。
- 修改没有生效:重新生成 ZIP,按照 TIA Portal 的控件更新流程更新,编译并重新加载测试项目。保留一份正常工作的版本。
首次测试成功后,再逐项增加功能。连接实际工艺过程之前,先在目标设备上验证最终控件。
常见问题
必须会 JavaScript 吗?
导入和绑定现成 CWC 不需要编写 JavaScript。若要手动修改行为,了解 HTML、CSS 和 JavaScript 基础会有帮助。HMIx AI 可以准备文件,但仍需在 Unified 中检查和测试结果。
可以在其他项目中复用吗?
把可导入 ZIP 复制到另一个项目的 UserFiles\CustomControls,并配置该项目的变量。一起保存控件包与版本信息,不要假定它与库中版本化的 faceplate 类型具有相同的管理方式。
预览正常就代表可以使用了吗?
预览确认的是外观,而非全部 Runtime 集成。还应检查启动过程、属性变化、目标设备以及实际使用的画面尺寸。