HMIx 实用指南

为 WinCC Unified 创建饼图 Custom Web Control

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

HMIx 团队阅读约 10 分钟核对日期
工业 HMI 上显示三个系列的环形饼图,数值分别为 45、35 和 20 的示意图
为 HMIx 用 AI 制作的示意图。可下载示例使用相同的三个数值,包含环形图和图例。

本指南的操作环境:TIA Portal / WinCC Unified V21.1。请在目标设备上检查示例的导入和运行情况。

01

什么是 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 生成器描述想法,创建适合自己项目的资源。

HMIx 的 Pie Chart,包含五个扇区、图例和系列提示信息
HMIx 已发布的 Pie Chart – Dashboard Widget:包含五个元素和可配置标题的环形图,可从产品页下载。下面我们将创建一个三个输入的版本,便于理解其工作方式。 在 HMIx 查看此资源

西门子 · Custom Web Control 编程

02

开始前需要准备什么

建立一个测试项目,添加 WinCC Unified 设备和空白画面。电脑需要安装带 WinCC Unified Engineering 的 TIA Portal,以及匹配的 Runtime 或仿真组件。传统 Comfort 项目与 Unified 项目是不同的目标。

  • 首次测试可使用 Unified PC Runtime,或目标 Unified 设备支持的仿真功能。
  • 如需修改示例,准备文本编辑器和 ZIP 压缩工具即可。Visual Studio Code 很方便,但并非必需。
  • 无需 PLC:用三个 HMI 内部变量和三个输入/输出域提供测试数据。

浏览器预览只能检查外观。WebCC 通信和变量绑定必须在 Unified 中验证。

03

创建一个包含三个数值的饼图

这个饼图采用环形结构,中心显示合计,图例显示各个系列。三个输入为 ValueA、ValueB 和 ValueC,初始值分别是 45、35 和 20。它们代表数量,不必相加等于 100;控件会自动计算各自占总数的比例。

  1. 准备文件

    在根目录创建 manifest.json,以及 assets 和 control 两个文件夹。在 control 中创建 index.html 和 code.js。从下方链接的西门子官方示例获取 webcc.min.js,也放入 control。每个文件各有用途,无需使用 Web 框架。

  2. 定义三个输入

    在 manifest.json 中将 ValueA、ValueB 和 ValueC 声明为 number,默认值分别为 45、35 和 20。清单还需指定 HMIx Pie Chart 名称、控件标识和入口文件。

  3. 绘制图表和图例

    在 control/index.html 中加入三个环形扇区、合计以及 A、B、C 图例。在 code.js 中用各项数值除以总数计算比例。如果三个值都为零,就显示空环。示例在 HTML 内使用 SVG,不需要额外的图表库。

  4. 连接 Unified

    加载 Siemens 提供的 webcc.min.js,不要修改该文件。用相同的三个属性启动 WebCC,连接成功后读取当前值,并在 onPropertyChanged 通知变化时重绘。这样重新打开画面时也能显示当前数据。

  5. 打包文件

    让 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,
  );
}

西门子 · 清单文件结构西门子 · WebCC 与 Runtime 接口西门子 · 示例文件与 WebCC 库

04

把 ZIP 加入 TIA Portal 项目

  1. 找到项目文件夹

    打开存放 TIA Portal 项目的文件夹。在 UserFiles 内创建 CustomControls 文件夹;如果已存在,直接使用。

  2. 复制 CWC 包

    将可导入的 {GUID}.zip 放入 UserFiles\CustomControls。这个 ZIP 应保持压缩状态。不要只复制 index.html,也不要把最外层的下载资源包误当作控件 ZIP。

  3. 刷新并放置控件

    打开 Unified 画面。在工具 → 自定义控件(Tools → My controls)中刷新,将 HMIx Pie Chart 拖入画面。为图表和图例留出足够空间,例如 480 × 300 像素。

  4. 检查接口

    选中控件,打开属性 → 属性 → 接口。找到 ValueA、ValueB 和 ValueC,先设置静态值 45、35 和 20。下一步再将它们绑定到变量。

西门子真实截图:My controls、Unified 画面中的仪表,以及 Interface 属性区域
Siemens 工程截图:自定义控件 → 画面 → 接口。图中使用 GaugeMeter;本指南应选择 HMIx Pie Chart 及其 ValueA、ValueB、ValueC 属性。 来源 · © Siemens AG · 许可证与署名

西门子 · 安装和使用 CWC

05

用三个内部变量测试,无需 PLC

  1. 创建三个内部变量

    在 HMI 变量表中添加 ProductionA、ProductionB 和 ProductionC,使用 Real 等数值类型,不连接 PLC。

  2. 逐一绑定输入和变量

    在 CWC 接口中选择变量动态化:ValueA → ProductionA、ValueB → ProductionB、ValueC → ProductionC。保持“只读”启用,由输入/输出域修改数据。

  3. 添加三个输入/输出域

    为每个变量添加一个输入/输出模式的数值域,分别标为 A、B 和 C。本次测试使用 0 到 100 的数量;如果可配置输入上下限,请一并设置。

  4. 编译并运行

    将此画面设置为起始画面,编译 HMI 并修复报告的错误。启动支持的 HMI 仿真,或将测试项目加载到 Unified PC Runtime 后启动。打开 Runtime 客户端;如有登录要求,使用测试项目中配置的用户。

  5. 测试两组数据和全零状态

    输入 45、35 和 20,并逐项确认。再把 A 改为 90:其扇区会变大,合计变为 145。最后把三个值都设为零,图表应显示空环。重新打开画面,检查是否显示当前数值。

西门子截图:CWC 属性连接到变量,并启用了 Read-only
Siemens 示例中的属性绑定。将 GaugeValue / Motor_Speed 换为 ValueA / ProductionA,对 B 和 C 重复操作,并保持“只读”启用。 来源 · © Siemens AG · 许可证与署名

先看看预期效果

调整 A、B 或 C,观察扇区变化。输入的是数量,图表会自动计算百分比。

100合计
45 / 45%
35 / 35%
20 / 20%
这是本指南的交互演示,并非 WinCC Runtime 会话。

满足这些条件,测试才算成功

45 / 35 / 20 的合计为 100。A = 90 时,合计为 145,A 约占 62%。三个值都为零时显示空环。重新打开画面后应显示当前数值。

西门子 · 安装和使用 CWC西门子 · 工程示例与原始截图

06

遇到问题时如何检查

  • 找不到控件:检查 UserFiles\CustomControls、ZIP 根目录结构、清单 JSON 语法和 GUID 文件名,然后刷新 My controls。
  • 图表显示但不变化:检查 ValueA、ValueB、ValueC 的名称、变量绑定和 WebCC 连接。在本地打开 HTML 只能检查外观,不能验证与 Unified 的连接。
  • 在 PC 上正常、在 Unified Comfort Panel 上失败:将依赖文件放进控件包。西门子对面板中的外部链接,以及通过 fetch 或 XMLHttpRequest 获取外部数据有明确限制。
  • 修改没有生效:重新生成 ZIP,按照 TIA Portal 的控件更新流程更新,编译并重新加载测试项目。保留一份正常工作的版本。

首次测试成功后,再逐项增加功能。连接实际工艺过程之前,先在目标设备上验证最终控件。

西门子 · Unified Comfort Panel 的 CWC 限制

常见问题

必须会 JavaScript 吗?

导入和绑定现成 CWC 不需要编写 JavaScript。若要手动修改行为,了解 HTML、CSS 和 JavaScript 基础会有帮助。HMIx AI 可以准备文件,但仍需在 Unified 中检查和测试结果。

可以在其他项目中复用吗?

把可导入 ZIP 复制到另一个项目的 UserFiles\CustomControls,并配置该项目的变量。一起保存控件包与版本信息,不要假定它与库中版本化的 faceplate 类型具有相同的管理方式。

预览正常就代表可以使用了吗?

预览确认的是外观,而非全部 Runtime 集成。还应检查启动过程、属性变化、目标设备以及实际使用的画面尺寸。

资料来源与延伸阅读