> For the complete documentation index, see [llms.txt](https://docs.icraft.design/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.icraft.design/advanced-tips/digital-twin-creation-workflow.md).

# Digital Twin Creation Workflow

This tutorial uses the **iCraft server cluster status monitoring** example to explain step-by-step how to build a real-time monitoring digital twin from scratch — including **data planning, 3D element mapping, UI prompt integration, front-end data pushing, interaction, and deployment best practices**.

***

## Detailed Steps & Implementation Recommendations

#### Define Monitoring Metrics & Thresholds

* **Common metrics:** CPU usage, memory utilization, service uptime, network throughput, etc.
* **Threshold Strategy (Example):**

  * **Normal (Green)**
  * **Warning (Yellow):** Metric > 80%
  * **Critical (Red):** Metric > 90%

  \
  Use distinct colors and icons in the UI/animation for instant anomaly identification. This case uses a similar threshold strategy to switch node state colors.

***

#### Prepare 3D Scene & Element Naming

* In the iCraft Editor, place each server or service node as an independent object in the scene to build a complete interactive 3D environment.
* **Naming Convention:** \
  Assign predictable element names (e.g., `SVC-DTS-01`, `SVC-DB-02`).\
  The front-end script will locate elements precisely via `player.getElementsByName(name)`.
* For overall cluster status visualization, prepare different materials or animations for each state (e.g., flashing, breathing highlight, pipeline flow effects).

***

#### Design Status Cards & Tips

* **Tip**：For quick single-point inspection; keep design concise (service name, status icon, key values).
* **StatusCard**：Persistent or click-triggered data card, supporting frosted glass / card UI styles.

***

#### **Integrate iCraftPlayer**

Use `@icraft/player-react` to import and render the player in your component:

```
<ICraftPlayer src="/path/to/scene.iplayer" ref={playerRef} onReady={onReady} onClick={onClick} addons={["ZoomBar"]}/>
```

`onReady`：After the scene is ready, use `player.getElementsByName(...)` to find default active elements and initialize tooltips.\
`onClick`：When the user clicks a scene element, clear old tooltips, then create/inject a new StatusCard container based on the current mode and display content.

* Properly manage `element.tip` (reset, `updateInnerHTML`, `updateVisible`).
* Clear old fixed containers on click to avoid DOM memory leaks.

***

#### Data Flow & Update Mechanism (Simulated or Real Push)

* **Update Frequency**: \
  Refresh `activeData` every **100ms** to simulate near-real-time monitoring (in production, frequency depends on data source and performance). Use `setInterval(..., 100)` to configure the interval.
* **Data Acquisition**: \
  Connect via WebSocket, Server-Sent Events, or timed REST API polling. Map latest data to corresponding elements and update the StatusCard DOM content.
* **Concurrency & Throttling**: \
  For large node volumes, use **throttling**, batch updates, or update only visible/focused nodes to reduce rendering overhead.

***

#### Interaction Modes: Floating / Fixed

* **Floating Tooltip**: \
  Follows the mouse or element position for quick inspection; suitable for exploratory monitoring.
* **Fixed StatusCard**: \
  Toggled via a switch. Creates a fixed DOM container attached to the player (e.g., top-right corner) for persistent display of critical node information.

***

#### State Visualization Strategy

* Combine **colors (green/yellow/red)**, icons, and simple animations (breathing / flashing) for fast anomaly detection in the global view.
* When data exceeds thresholds:
  * Change material color
  * Trigger element “alert animation”
  * Highlight alarm items on the StatusCard
* For critical alerts, add sound or global pop-up reminders (use sparingly to avoid noise and false alarms).

***

#### Testing, Performance & Deployment

* **Local Testing**: \
  Validate data updates, interactions, and visuals using a local data simulator first.
* **Performance Monitoring**: \
  Track FPS, memory usage, and DOM node count. Mass real-time updates may cause bottlenecks; reduce refresh rate or switch to **event-driven updates** (only on data changes) if needed.
* **Security**: \
  For real data streams, ensure authentication and TLS encryption on WebSocket/HTTP channels to prevent sensitive data leakage on the client.
* **Deployment**: \
  Package the final scene as `.iplayer` or embed it into a web page for integration into monitoring dashboards or control consoles.

***

## Demo

![](/files/k9M2mStWgJvf58Yd2096)

***

## Sample Code Structure

**index.tsx：**&#x52;eact entry component. Manages iCraft Player initialization, user interaction, tooltip/fixed card display, and timed data update logic.

{% code lineNumbers="true" expandable="true" %}

```
import { MouseEventParams, Element3D, ICraftPlayer, ICraftPlayerInstance } from "@icraft/player-react";
import { useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { getData } from "./utils";
import { StatusCardProps } from "./interface";
import StatusCard from "./StatusCard";
import styles from "./index.module.css";
 
export default () => {
  const playerRef = useRef<ICraftPlayerInstance>(null);
  const [tipContainer, setTipContainer] = useState<HTMLDivElement | null>(null);
  const [activeService, setActiveService] = useState<string | null>("DTS");
  const [activeData, setActiveData] = useState<StatusCardProps | null>(null);
  const [fixed, setFixed] = useState<boolean>(false);
  const elementRef = useRef<Element3D | null>(null);
 
  const onReady = () => {
    const player = playerRef.current;
    if (!activeService) {
      return;
    }
    const element = player?.getElementsByName(activeService)?.[0];
    if (!element) return;
    elementRef.current = element;
    if (element?.tip) {
      const container = document.createElement("div");
      element.tip.updateInnerHTML(container);
      element.tip.updateVisible(true);
      setTipContainer(container);
    }
  };
 
  const onClick = (params: MouseEventParams) => {
    const { instance: element } = params;
    const player = playerRef.current;
    // clear old tip
    elementRef.current?.tip?.reset();
 
    // clear old fixed container
    if (fixed && tipContainer) {
      tipContainer.remove();
    }
 
    elementRef.current = element;
    if (!element) {
      setActiveService(null);
      setTipContainer(null);
      return;
    }
    const activeName = element.options?.name;
    setActiveService(activeName || null);
 
    if (fixed) {
      const container = document.createElement("div");
      container.style.position = "absolute";
      container.style.top = "10px";
      container.style.right = "10px";
      container.style.zIndex = "100";
      player?.getDom().appendChild(container);
      setTipContainer(container);
    } else {
      if (element?.tip) {
        const container = document.createElement("div");
        element.tip.updateInnerHTML(container);
        element.tip.updateVisible(true);
        setTipContainer(container);
      }
    }
  };
 
  useEffect(() => {
    if (!activeService) return;
    const interval = setInterval(() => {
      setActiveData(getData(activeService));
    }, 100);
    return () => clearInterval(interval);
  }, [activeService]);
 
  return (
    <div className={styles.container}>
      <ICraftPlayer
        src='/templates/ControlPlatform.iplayer'
        ref={playerRef}
        onReady={onReady}
        onClick={onClick}
        addons={["ZoomBar"]}
      />
      <div className={styles.checkbox}>
        <label className={styles.switch}>
          <input
            type='checkbox'
            checked={fixed}
            onChange={(e) => setFixed(e.target.checked)}
          />
          <span className={styles.slider}></span>
        </label>
        <span>fixed</span>
      </div>
      {tipContainer &&
        activeData &&
        createPortal(
          <StatusCard
            serviceName={activeData.serviceName}
            status={activeData.status}
            metrics={activeData.metrics}
          />,
          tipContainer
        )}
    </div>
  );
};
```

{% endcode %}

***

**index.module.css：**&#x50;age-level layout styles for `index.tsx`.

{% code lineNumbers="true" expandable="true" %}

```
.container{
  width: 100%;
  height: 100%;
  position: relative;
  overflow: hidden;
}
 
.checkbox {
  position: absolute;
  top: 10px;
  left: 10px;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 8px;
}
 
.switch {
  position: relative;
  display: inline-block;
  width: 40px;
  height: 20px;
}
 
.switch input {
  opacity: 0;
  width: 0;
  height: 0;
}
 
.slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #ccc;
  transition: .4s;
  border-radius: 20px;
}
 
.slider:before {
  position: absolute;
  content: "";
  height: 16px;
  width: 16px;
  left: 2px;
  bottom: 2px;
  background-color: white;
  transition: .4s;
  border-radius: 50%;
}
 
input:checked + .slider {
  background-color: #2196F3;
}
 
input:checked + .slider:before {
  transform: translateX(20px);
}
```

{% endcode %}

***

**StatusCard.tsx：**&#x52;eact component for rendering service node status cards and state UI.

{% code lineNumbers="true" expandable="true" %}

```
import React from "react";
import styles from "./StatusCard.module.css";
import { StatusCardProps } from "./interface";
import { getProgressBarClassName, getStatusDisplay } from "./utils";
 
const StatusCard: React.FC<StatusCardProps> = ({ serviceName, status, metrics }) => {
  const statusDisplay = getStatusDisplay(status);
 
  return (
    <div className={styles.statusCard}>
      <div className={styles.content}>
        <div className={styles.titleRow}>
          <div className={styles.titleLeft}>
            <span className={styles.title}>{serviceName}</span>
          </div>
          <div className={styles.titleRight}>
            <span className={styles.checkmark} style={{ color: statusDisplay.color }}>
              {status === "running" ? "✓" : "×"}
            </span>
            <span className={styles.status} style={{ color: statusDisplay.color }}>
              {statusDisplay.text}
            </span>
          </div>
        </div>
 
        <div className={styles.metrics}>
          <div className={styles.metricRow}>
            <div className={styles.metricLabel}>
              <span>🔄 CPU Usage</span>
              <span>{metrics.cpu}%</span>
            </div>
            <div className={styles.progressWrapper}>
              <div 
                className={getProgressBarClassName(metrics.cpu)}
                style={{ width: `${metrics.cpu}%` }}
              />
            </div>
 
          </div>
 
          <div className={styles.metricRow}>
            <div className={styles.metricLabel}>
              <span>💾 Memory Usage</span>
              <span>{metrics.memory}%</span>
            </div>
            <div className={styles.progressWrapper}>
              <div 
                className={getProgressBarClassName(metrics.memory)}
                style={{ width: `${metrics.memory}%` }}
              />
            </div>
            
          </div>
 
          <div className={styles.uptimeRow}>
            <span>⏱️ Running Time</span>
            <div className={styles.uptimeValue}>
              {metrics.uptime.days}d {metrics.uptime.hours}h {metrics.uptime.minutes}m
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};
 
export default StatusCard;
```

{% endcode %}

***

**StatusCard.module.css：**&#x53;tyle definitions for `StatusCard.tsx`.

{% code lineNumbers="true" expandable="true" %}

```
.statusCard {
  width: 260px;
  padding: 12px;
  position: relative;
  border-radius: 12px;
  z-index: 1;
}
 
.statusCard::before {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(255, 255, 255, 0.8);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: inherit;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
  z-index: -1;
}
 
.content {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 16px;
  z-index: 2;
}
 
.titleRow {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
}
 
.titleLeft {
  display: flex;
  align-items: center;
}
 
.titleRight {
  display: flex;
  align-items: center;
  gap: 4px;
  font-weight: bold;
}
 
.title {
  font-size: 14px;
  color: #374151;
  font-weight: bold;
}
 
.checkmark {
  color: #22c55e;
  margin-right: 2px;
}
 
.status {
  font-size: 14px;
  font-weight: bold;
}
 
.lastUpdate {
  font-size: 13px;
  color: #6B7280;
}
 
.metrics {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
 
.metricRow {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 13px;
  color: #4B5563;
}
 
.metricLabel {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
 
.progressWrapper {
  width: 100%;
  height: 4px;
  background: rgba(0, 0, 0, 0.05);
  border-radius: 2px;
  overflow: hidden;
}
 
.progressBar {
  height: 100%;
  background: #22c55e;
  border-radius: 2px;
  transition: all 0.3s ease;
}
 
.progressBar.warning {
  background: #f59e0b;
}
 
.progressBar.danger {
  background: #ef4444;
}
 
.footer {
  font-size: 13px;
  color: #6B7280;
}
 
.uptimeRow {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  gap: 4px;
  padding-top: 4px;
  font-size: 13px;
  color: #4B5563;
}
 
.uptimeValue {
  color: #6B7280;
} 
```

{% endcode %}

***

**interface.ts：**&#x54;ype interface module defining component data structures and type constraints.

{% code lineNumbers="true" expandable="true" %}

```
export type StatusType = "running" | "stopped" | "error";
 
export interface StatusCardProps {
  // Service basic info
  serviceName: string;
  status: StatusType;
  // System metrics
  metrics: {
    cpu: number;
    memory: number;
    uptime: {
      days: number;
      hours: number;
      minutes: number;
    };
  };
}
```

{% endcode %}

***

**utils.ts：**&#x55;tility and simulated data source module for fetching/generating real-time data.

{% code lineNumbers="true" expandable="true" %}

```
import { StatusCardProps } from "./interface";
import styles from "./StatusCard.module.css";
 
export const getFluctuatedValue = (currentValue: number, maxFluctuation: number) => {
  const fluctuation = (Math.random() * 2 - 1) * maxFluctuation;
  const newValue = currentValue + fluctuation;
  return Number(Math.min(Math.max(newValue, 0), 100).toFixed(02));
};
 
export const getRandomMetrics = () => {
  return {
    cpu: getFluctuatedValue(40, 2),
    memory: getFluctuatedValue(50, 1),
    uptime: { days: 1, hours: 2, minutes: 10 },
  };
};
 
export const getData = (serviceName: string) => {
  return {
    serviceName,
    status: "running",
    metrics: getRandomMetrics()
  } as StatusCardProps;
};
 
export const getProgressBarClassName = (value: number) => {
  return `${styles.progressBar}${
    value >= 90 ? ` ${styles.danger}` : 
    value >= 80 ? ` ${styles.warning}` : 
    ''
  }`;
};
 
const statusMap = {
  running: { text: "Running", color: "#22c55e" },
  stopped: { text: "Stopped", color: "#6B7280" },
  error: { text: "Error", color: "#EF4444" },
};
 
export const getStatusDisplay = (status: StatusCardProps["status"]) => {
  return statusMap[status];
};
```

{% endcode %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.icraft.design/advanced-tips/digital-twin-creation-workflow.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
