> For the complete documentation index, see [llms.txt](https://docs.aic-eec.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.aic-eec.com/interfacing-with-infineon-psoc-tm-edge/hmi-development/sensor-to-hmi-display/ux-ui-workshops/chart-timeseries.md).

# Chart Timeseries

## Lab 3: Chart Widget (Time-Series Data)

***

### 1. โครงสร้างภาพรวมของ Lab

#### Why? - ทำไมต้องเรียนรู้เรื่องนี้

* **Trend Analysis**: Chart แสดงแนวโน้มข้อมูลตามเวลา ทำให้เห็นรูปแบบที่ตัวเลขอย่างเดียวมองไม่เห็น
* **Multi-Sensor**: เปรียบเทียบข้อมูลจากหลาย sensor พร้อมกันบนกราฟเดียว
* **Real-time Monitoring**: ดู sensor data แบบ live scrolling เหมือน oscilloscope
* **Industry Standard**: Line chart เป็น visualization ที่ใช้มากที่สุดใน SCADA, DCS, และ IoT Dashboard

#### What? - จะได้เรียนรู้อะไร

1. **Chart Widget**: สร้าง line chart ด้วย `lv_chart_create()`
2. **Data Series**: เพิ่ม series หลายเส้นด้วย `lv_chart_add_series()`
3. **Auto-Scrolling**: ข้อมูลเลื่อนอัตโนมัติ (ring buffer) ด้วย `lv_chart_set_next_value()`
4. **Point Count**: กำหนดจำนวนจุดแสดงผล (50-100 จุดที่แนะนำ)
5. **Chart Styling**: ปรับ line width, ซ่อน data points, grid lines

#### How? - จะทำอย่างไร

1. สร้าง Chart widget กำหนด type เป็น `LV_CHART_TYPE_LINE`
2. เพิ่ม 3 series สำหรับ Accelerometer X, Y, Z
3. ใช้ timer อัพเดทข้อมูลทุก 100ms
4. จำลอง sine wave + noise เป็น accelerometer data
5. แสดง labels บอกค่าปัจจุบันของแต่ละ axis

<figure><img src="https://1856353139-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MClo3nC-1US0rbK8Qau%2Fuploads%2F9Nw7gR2yHVoF1EoyyQDv%2Fweek4_ex3_chart_timeseries.gif?alt=media&amp;token=4fb3bc6f-f4d8-4c25-b46a-2518fa51f345" alt=""><figcaption></figcaption></figure>

***

### 2. หลักการทำงานและ Flowchart

#### 2.1 Chart Data Flow (Ring Buffer)

```
┌─────────────────────────────────────────────────────────────┐
│                  CHART DATA FLOW                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   lv_chart_set_next_value() ทำงานแบบ Ring Buffer:           │
│                                                             │
│   Point Count = 8 (ตัวอย่าง)                                  │
│                                                             │
│   เวลา t=0:  [10][20][30][40][50][60][70][80]               │
│                                                             │
│   เวลา t=1:  [20][30][40][50][60][70][80][NEW]  ← เพิ่มขวา    │
│               └─── เลื่อนทั้งหมดไปซ้าย ───┘                      │
│                                                             │
│   เวลา t=2:  [30][40][50][60][70][80][85][NEW]              │
│                                                             │
│   *** ไม่ต้องจัดการ buffer เอง! LVGL จัดการให้ ***               │
│                                                             │
│   กราฟจะ scroll อัตโนมัติ เหมือน oscilloscope                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

#### 2.2 Multiple Series Display

```
┌─────────────────────────────────────────────────────────────┐
│                 MULTI-SERIES CHART                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   Y-axis                                                    │
│   1000 ┤                                                    │
│    800 ┤  ─── X (Red)                                       │
│    600 ┤  ─── Y (Green)           ╱╲                        │
│    400 ┤  ─── Z (Blue)          ╱    ╲      ╱╲              │
│    200 ┤                  ╱╲  ╱        ╲  ╱    ╲            │
│      0 ┤────────────────╱──╲╱──────────╲╱──────╲──► Time    │
│   -200 ┤             ╱╲                                     │
│   -400 ┤           ╱    ╲                                   │
│   -600 ┤         ╱        ╲                                 │
│   -800 ┤                                                    │
│  -1000 ┤                                                    │
│         ├───┬───┬───┬───┬───┬───┬───┬───┬───┬───┤           │
│         0   10  20  30  40  50  60  70  80  90 100          │
│                    Point Index (time)                       │
│                                                             │
│   แต่ละ series มี buffer แยก, อัพเดทพร้อมกัน                     │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

#### 2.3 Sensor Data Scaling

```
┌─────────────────────────────────────────────────────────────┐
│               ACCELEROMETER DATA SCALING                    │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   Accelerometer Output:                                     │
│   - Range: -2g to +2g                                       │
│   - Resolution: float (m/s^2)                               │
│   - Example: ax = 1.23 m/s^2                                │
│                                                             │
│   LVGL Chart ใช้ int (lv_chart_set_next_value):              │
│   - ต้องแปลง float → int                                     │
│   - คูณ 100 เพื่อรักษาทศนิยม 2 ตำแหน่ง                            │
│                                                             │
│   ax_raw = 1.23 m/s^2                                       │
│         │                                                   │
│         ▼                                                   │
│   ax_int = (int)(1.23 * 100) = 123                          │
│         │                                                   │
│         ▼                                                   │
│   lv_chart_set_next_value(chart, ser_x, 123)                │
│         │                                                   │
│         ▼                                                   │
│   Chart Y-axis range: -2000 to +2000                        │
│   (= -20.00 to +20.00 m/s^2)                                │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

***

### 3. ฟังก์ชันสำคัญ (API Reference)

#### 3.1 Chart Creation & Type

<table><thead><tr><th width="368.63067626953125">Function</th><th width="245.00146484375">Parameters</th><th>Description</th></tr></thead><tbody><tr><td><code>lv_chart_create(parent)</code></td><td>parent: lv_obj_t*</td><td>สร้าง chart widget</td></tr><tr><td><code>lv_chart_set_type(chart, type)</code></td><td>type: lv_chart_type_t</td><td>LINE, BAR, SCATTER</td></tr><tr><td><code>lv_chart_set_point_count(chart, cnt)</code></td><td>cnt: uint32_t</td><td>จำนวนจุดที่แสดง (แนะนำ 50-100)</td></tr><tr><td><code>lv_chart_set_range(chart, axis, min, max)</code></td><td>axis: LV_CHART_AXIS_PRIMARY_Y</td><td>กำหนดช่วง Y-axis</td></tr></tbody></table>

#### 3.2 Data Series

<table><thead><tr><th width="364.52911376953125">Function</th><th>Parameters</th><th>Description</th></tr></thead><tbody><tr><td><code>lv_chart_add_series(chart, color, axis)</code></td><td>color, axis</td><td>เพิ่ม data series ใหม่</td></tr><tr><td><code>lv_chart_set_next_value(chart, ser, val)</code></td><td>ser, val: int32_t</td><td>เพิ่มจุดใหม่ (auto-scroll)</td></tr><tr><td><code>lv_chart_set_all_value(chart, ser, val)</code></td><td>ser, val: int32_t</td><td>ตั้งทุกจุดเป็นค่าเดียวกัน</td></tr><tr><td><code>lv_chart_refresh(chart)</code></td><td>-</td><td>บังคับ redraw chart</td></tr></tbody></table>

#### 3.3 Chart Styling

| Function                                                  | Description                          |
| --------------------------------------------------------- | ------------------------------------ |
| `lv_obj_set_style_line_width(chart, px, LV_PART_ITEMS)`   | ความหนาเส้น                          |
| `lv_obj_set_style_size(chart, 0, 0, LV_PART_INDICATOR)`   | ซ่อน data point dots                 |
| `lv_chart_set_div_line_count(chart, h, v)`                | จำนวนเส้น grid (h=แนวนอน, v=แนวตั้ง) |
| `lv_obj_set_style_line_color(chart, color, LV_PART_MAIN)` | สีเส้น grid                          |
| `lv_obj_set_style_line_opa(chart, opa, LV_PART_MAIN)`     | ความโปร่งใสของ grid                  |

#### 3.4 Chart Axis

| Function                                                                                       | Description      |
| ---------------------------------------------------------------------------------------------- | ---------------- |
| `lv_chart_set_range(chart, LV_CHART_AXIS_PRIMARY_Y, min, max)`                                 | กำหนดช่วง Y ซ้าย |
| `lv_chart_set_range(chart, LV_CHART_AXIS_SECONDARY_Y, min, max)`                               | กำหนดช่วง Y ขวา  |
| `lv_chart_set_axis_tick(chart, axis, major_len, minor_len, major_cnt, minor_cnt, draw_labels)` | ตั้ง tick marks  |

***

### 4. โค้ดตัวอย่าง (Step-by-Step Code)

#### 4.1 Step 1: Global Variables

```c
#include "lvgl.h"
#include <math.h>

/* ---- Chart Handles ---- */
static lv_obj_t * accel_chart;          /* Chart widget */
static lv_chart_series_t * ser_x;       /* Series X (Red) */
static lv_chart_series_t * ser_y;       /* Series Y (Green) */
static lv_chart_series_t * ser_z;       /* Series Z (Blue) */

/* ---- Labels ---- */
static lv_obj_t * x_label;
static lv_obj_t * y_label;
static lv_obj_t * z_label;

/* ---- Timer ---- */
static lv_timer_t * chart_timer;

/* ---- Constants ---- */
#define CHART_POINT_COUNT   80    /* จำนวนจุดที่แสดง */
#define SCALE_FACTOR        100   /* x100 สำหรับแปลง float->int */
#define Y_RANGE             1500  /* +/- 15.00 m/s^2 */
```

#### 4.2 Step 2: Simulated Accelerometer Data

```c
/*
 * simulate_accel() - จำลองข้อมูล accelerometer 3 แกน
 *
 * X: sine wave (การเคลื่อนที่ซ้าย-ขวา)
 * Y: cosine wave (การเคลื่อนที่หน้า-หลัง)
 * Z: ค่าคงที่ ~981 (gravity) + noise
 *
 * Output: int32_t ที่คูณ 100 แล้ว (เช่น 981 = 9.81 m/s^2)
 */
static void simulate_accel(int32_t * ax, int32_t * ay, int32_t * az)
{
    static uint32_t tick = 0;
    tick++;

    float t = (float)tick * 0.08f;

    /* X-axis: sine wave, amplitude 5 m/s^2 */
    *ax = (int32_t)(500.0f * sinf(t));
    *ax += (lv_rand(0, 40) - 20);  /* noise +/- 0.20 */

    /* Y-axis: cosine wave, amplitude 3 m/s^2 */
    *ay = (int32_t)(300.0f * cosf(t * 0.7f));
    *ay += (lv_rand(0, 30) - 15);

    /* Z-axis: gravity (9.81 m/s^2 = 981) + vibration */
    *az = 981 + (int32_t)(50.0f * sinf(t * 3.0f));
    *az += (lv_rand(0, 20) - 10);
}
```

#### 4.3 Step 3: Timer Callback

```c
/*
 * chart_timer_cb() - อัพเดท chart ทุก 100ms
 */
static void chart_timer_cb(lv_timer_t * timer)
{
    (void)timer;

    int32_t ax, ay, az;
    simulate_accel(&ax, &ay, &az);

    /* เพิ่มจุดใหม่ให้แต่ละ series (auto-scroll) */
    lv_chart_set_next_value(accel_chart, ser_x, ax);
    lv_chart_set_next_value(accel_chart, ser_y, ay);
    lv_chart_set_next_value(accel_chart, ser_z, az);

    /* อัพเดท labels แสดงค่าปัจจุบัน */
    lv_label_set_text_fmt(x_label, "X: %d.%02d",
                          (int)(ax / 100), abs((int)(ax % 100)));
    lv_label_set_text_fmt(y_label, "Y: %d.%02d",
                          (int)(ay / 100), abs((int)(ay % 100)));
    lv_label_set_text_fmt(z_label, "Z: %d.%02d",
                          (int)(az / 100), abs((int)(az % 100)));
}
```

#### 4.4 Step 4: Main Function - สร้าง Chart UI

```c
void part2_ex3_chart_timeseries(void)
{
    /* ---- Background ---- */
    lv_obj_set_style_bg_color(lv_screen_active(),
                               lv_color_hex(0x0a0a1a), 0);

    /* ---- Title ---- */
    lv_obj_t * title = lv_label_create(lv_screen_active());
    lv_label_set_text(title, "Accelerometer Monitor");
    lv_obj_set_style_text_color(title, lv_color_hex(0x00d4ff), 0);
    lv_obj_set_style_text_font(title, &lv_font_montserrat_24, 0);
    lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 5);

    /* ---- Chart ---- */
    accel_chart = lv_chart_create(lv_screen_active());
    lv_obj_set_size(accel_chart, 280, 140);
    lv_obj_align(accel_chart, LV_ALIGN_CENTER, 0, -10);

    /* ตั้งค่า Chart */
    lv_chart_set_type(accel_chart, LV_CHART_TYPE_LINE);
    lv_chart_set_point_count(accel_chart, CHART_POINT_COUNT);
    lv_chart_set_range(accel_chart, LV_CHART_AXIS_PRIMARY_Y,
                       -Y_RANGE, Y_RANGE);

    /* Grid lines */
    lv_chart_set_div_line_count(accel_chart, 5, 8);
    lv_obj_set_style_line_color(accel_chart,
                                 lv_color_hex(0x333355), LV_PART_MAIN);
    lv_obj_set_style_line_opa(accel_chart, LV_OPA_50, LV_PART_MAIN);

    /* Chart background */
    lv_obj_set_style_bg_color(accel_chart,
                               lv_color_hex(0x111122), 0);
    lv_obj_set_style_border_color(accel_chart,
                                   lv_color_hex(0x333355), 0);

    /* Line width (ความหนาเส้นกราฟ) */
    lv_obj_set_style_line_width(accel_chart, 2, LV_PART_ITEMS);

    /* ซ่อน data point dots */
    lv_obj_set_style_size(accel_chart, 0, 0, LV_PART_INDICATOR);

    /* ---- เพิ่ม 3 Series ---- */
    ser_x = lv_chart_add_series(accel_chart,
                lv_palette_main(LV_PALETTE_RED),
                LV_CHART_AXIS_PRIMARY_Y);

    ser_y = lv_chart_add_series(accel_chart,
                lv_palette_main(LV_PALETTE_GREEN),
                LV_CHART_AXIS_PRIMARY_Y);

    ser_z = lv_chart_add_series(accel_chart,
                lv_palette_main(LV_PALETTE_BLUE),
                LV_CHART_AXIS_PRIMARY_Y);

    /* เริ่มต้นค่า 0 ทุกจุด */
    lv_chart_set_all_value(accel_chart, ser_x, 0);
    lv_chart_set_all_value(accel_chart, ser_y, 0);
    lv_chart_set_all_value(accel_chart, ser_z, 981);

    /* ---- Legend Labels ---- */
    x_label = lv_label_create(lv_screen_active());
    lv_label_set_text(x_label, "X: 0.00");
    lv_obj_set_style_text_color(x_label,
                                 lv_palette_main(LV_PALETTE_RED), 0);
    lv_obj_align(x_label, LV_ALIGN_BOTTOM_LEFT, 15, -10);

    y_label = lv_label_create(lv_screen_active());
    lv_label_set_text(y_label, "Y: 0.00");
    lv_obj_set_style_text_color(y_label,
                                 lv_palette_main(LV_PALETTE_GREEN), 0);
    lv_obj_align(y_label, LV_ALIGN_BOTTOM_MID, 0, -10);

    z_label = lv_label_create(lv_screen_active());
    lv_label_set_text(z_label, "Z: 9.81");
    lv_obj_set_style_text_color(z_label,
                                 lv_palette_main(LV_PALETTE_BLUE), 0);
    lv_obj_align(z_label, LV_ALIGN_BOTTOM_RIGHT, -15, -10);

    /* ---- Timer: อัพเดททุก 100ms ---- */
    chart_timer = lv_timer_create(chart_timer_cb, 100, NULL);
}
```

#### 4.5 UI Layout

```
┌──────────────────────────────────────┐
│       Accelerometer Monitor          │
│                                      │
│  ┌──────────────────────────────┐    │
│  │  1500 ┤     ╱╲               │    │
│  │  1000 ┤───╱──╲──────────Z    │    │
│  │   500 ┤ ╱      ╲     ╱╲      │    │
│  │     0 ┤╱────────╲──╱──╲──X   │    │
│  │  -500 ┤          ╲╱    ╲Y    │    │
│  │ -1000 ┤                      │    │
│  │ -1500 ┤                      │    │
│  └──────────────────────────────┘    │
│                                      │
│  X: 3.42    Y: -1.87    Z: 9.81      │
└──────────────────────────────────────┘
```

***

### 5. องค์ความรู้และเทคนิค

#### 5.1 Point Count Guidelines

| Application    | Points  | Update Rate | Notes                 |
| -------------- | ------- | ----------- | --------------------- |
| Temperature    | 50-100  | 1-5 sec     | ช้า, ดูแนวโน้ม        |
| Accelerometer  | 80-150  | 50-100ms    | กลาง, ดู vibration    |
| Audio Waveform | 200-500 | 10-20ms     | เร็ว, ดูรูปคลื่น      |
| Power Meter    | 60-120  | 1 sec       | กลาง, ดู load pattern |

> **สำคัญ**: point\_count ยิ่งมาก ยิ่งใช้ RAM มาก แต่ละ series ใช้ `point_count * sizeof(lv_coord_t)` bytes

#### 5.2 Float to Int Conversion Pattern

```c
/* Pattern มาตรฐานสำหรับ embedded chart */

/* 1. กำหนด scale factor */
#define SCALE  100  /* ทศนิยม 2 ตำแหน่ง */

/* 2. แปลง float → int ก่อนเก็บ */
float sensor_value = 3.14f;
int32_t chart_value = (int32_t)(sensor_value * SCALE);  /* 314 */

/* 3. ส่งค่า int ให้ chart */
lv_chart_set_next_value(chart, series, chart_value);

/* 4. แสดงผลกลับเป็น float ที่ label */
lv_label_set_text_fmt(label, "%d.%02d",
                      chart_value / SCALE,
                      abs(chart_value % SCALE));

/* หมายเหตุ: abs() จำเป็นสำหรับค่าลบ
 * -314 / 100 = -3, -314 % 100 = -14
 * ต้องแสดง "-3.14" ไม่ใช่ "-3.-14" */
```

#### 5.3 ซ่อน Data Points (Clean Lines)

```c
/* Default: มี dot ที่ทุกจุด → ดูรก */
/* ซ่อน dot ให้เหลือแค่เส้น */
lv_obj_set_style_size(chart, 0, 0, LV_PART_INDICATOR);

/* ถ้าต้องการแสดงเฉพาะจุดสุดท้าย: ทำไม่ได้โดยตรง
 * ต้อง custom draw event (ระดับ advanced) */
```

#### 5.4 Y-Range Auto vs Fixed

```c
/* Fixed range (แนะนำ สำหรับ known sensor range) */
lv_chart_set_range(chart, LV_CHART_AXIS_PRIMARY_Y, -2000, 2000);
/* ดี: stable display, ไม่กระตุก
 * เสีย: ถ้าค่าเปลี่ยนน้อยจะเห็นเป็นเส้นตรง */

/* Auto-range (ต้องทำเอง ไม่มี built-in) */
static int32_t y_min = INT32_MAX, y_max = INT32_MIN;
void auto_range_update(int32_t value) {
    if (value < y_min) y_min = value;
    if (value > y_max) y_max = value;
    int32_t margin = (y_max - y_min) / 10;  /* 10% margin */
    lv_chart_set_range(chart, LV_CHART_AXIS_PRIMARY_Y,
                       y_min - margin, y_max + margin);
}
```

#### 5.5 Pause/Resume Chart

```c
/* Pattern: Pause/Resume ด้วย timer control */
static bool is_paused = false;

void toggle_pause(void) {
    is_paused = !is_paused;
    if (is_paused) {
        lv_timer_pause(chart_timer);
    } else {
        lv_timer_resume(chart_timer);
    }
}
```

#### 5.6 Performance Optimization

```c
/* 1. ลด point count (ลด memory + render time) */
lv_chart_set_point_count(chart, 60);  /* ไม่เกิน 150 */

/* 2. ลด update rate */
lv_timer_create(cb, 200, NULL);  /* 5 FPS แทน 10 FPS */

/* 3. ใช้ LV_CHART_UPDATE_MODE_SHIFT (default, ดีอยู่แล้ว) */
lv_chart_set_update_mode(chart, LV_CHART_UPDATE_MODE_SHIFT);

/* 4. ซ่อน data points (ลด render ถ้ามี 100+ จุด) */
lv_obj_set_style_size(chart, 0, 0, LV_PART_INDICATOR);
```

***

### 6. แบบฝึกหัด (Exercises)

#### Exercise 1: Dual-Axis Environmental Chart (Temperature + Humidity)

สร้าง Chart แสดง Temperature และ Humidity พร้อม Legend:

**Requirements:**

* Chart แบบ LINE แสดง 2 series
* **Temperature** (Primary Y-axis): range 0-50 degC, สีแดง
* **Humidity** (Secondary Y-axis): range 0-100%, สีฟ้า
* Point count: 60 จุด (แสดง 1 นาทีที่ 1 จุด/วินาที)
* **Legend box** แสดงสีและชื่อ series
* Labels แสดงค่าปัจจุบันของ Temp และ Humidity
* Timer จำลองค่าที่เปลี่ยนแปลง (ทุก 1 วินาที)

```
┌──────────────────────────────────────┐
│     Environment Monitor              │
│                                      │
│  50degC ┌───────────────────┐ 100%   │
│         │  ╱╲    Temp ───   │        │
│         │ ╱  ╲  Humid ---   │        │
│         │╱    ╲─────╲       │        │
│         │      ╲     ╲──    │        │
│  0degC  └───────────────────┘ 0%     │
│                                      │
│  ■ Temp: 28 degC  ■ Humid: 65%       │
└──────────────────────────────────────┘
```

**Hints:**

* ใช้ `LV_CHART_AXIS_PRIMARY_Y` สำหรับ Temp, `LV_CHART_AXIS_SECONDARY_Y` สำหรับ Humidity
* สร้าง legend ด้วย container + label + color rectangle

***

#### Exercise 2: Chart with Auto-Scaling Y-Axis and Grid Lines

สร้าง Chart ที่ Y-axis ปรับ range อัตโนมัติตามข้อมูล:

**Requirements:**

* Chart แสดง 1 series (Vibration data)
* **Auto-scaling**: Y-axis ปรับ min/max ตามข้อมูลจริง + 10% margin
* Grid lines: 5 แนวนอน, 10 แนวตั้ง
* **Y-axis labels** แสดง min/max value ปัจจุบัน
* Point count: 100 จุด
* Timer จำลอง vibration data (sine + random amplitude)
* เพิ่ม **Pause/Resume** button

```
┌──────────────────────────────────────┐
│    Vibration Analyzer                │
│                                      │
│  +523 ┌──────────────────────┐       │
│       │  ╱╲  ╱╲  ╱╲          │       │
│     0 │─╱──╲╱──╲╱──╲─────────│       │
│       │              ╲╱╲  ╱╲ │       │
│  -489 └───────────────────╲╱─┘       │
│                                      │
│  Current: 234   [PAUSE]              │
└──────────────────────────────────────┘
```

**Hints:**

* ติดตาม y\_min, y\_max ใน timer callback
* ใช้ `lv_chart_set_range()` ทุกรอบ update
* Reset y\_min/y\_max ทุก N samples เพื่อให้ range ลดลงได้

***

### 7. References

* [LVGL Chart](https://docs.lvgl.io/9.2/widgets/chart.html)
* [lv\_example\_chart\_1.c](https://github.com/lvgl/lvgl/blob/release/v9.2/examples/widgets/chart/lv_example_chart_1.c)

***
