# 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="/files/LnVCMT8WhBTvurKcI4N7" 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)

***


---

# Agent Instructions: 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:

```
GET https://docs.aic-eec.com/interfacing-with-infineon-psoc-tm-edge/hmi-development/sensor-to-hmi-display/ux-ui-workshops/chart-timeseries.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
