# Chart Dashboard

## Lab 11: Real Chart Dashboard

### Part 2 - Sensor Visualization

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

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

* **Multi-Chart Visualization**: ในระบบ monitoring จริง ข้อมูล sensor เดียวกันแสดงได้หลายรูปแบบ ต้องเลือก chart type ที่เหมาะกับลักษณะข้อมูล
* **Active Tab Optimization**: เมื่อมี chart หลายตัว การอัพเดทเฉพาะ chart ที่มองเห็นช่วยลด CPU load อย่างมาก (ลดได้ 75%)
* **Custom Draw Events**: เทคนิค faded area chart ใช้ draw callback ซึ่งเป็นความสามารถขั้นสูงของ LVGL ที่ใช้ในระบบ production จริง
* **Tilt Analysis**: รวม Complementary Filter (Accel + Gyro fusion) เพื่อคำนวณ Roll/Pitch angle ที่แม่นยำ
* **Professional Dashboard**: รูปแบบ multi-chart dashboard ที่ใช้ใน SCADA, Process Control, และ Industrial IoT

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

1. **Bar Chart (Real Data)**: เปรียบเทียบ Accel X/Y/Z แบบ side-by-side ด้วย `LV_CHART_TYPE_BAR` + colored series
2. **Area Chart (Faded)**: แสดง Tilt Magnitude พร้อม gradient fill ด้วย custom draw callback (`LV_EVENT_DRAW_TASK_ADDED`)
3. **Scatter Chart (2D Mapping)**: แสดง Roll vs Pitch เป็น "Tilt Ball" ด้วย `LV_CHART_TYPE_SCATTER` + `lv_chart_set_next_value2()`
4. **Line Chart (Multi-Series)**: แสดง Gyroscope Roll/Pitch/Yaw เป็น time-series
5. **TabView LEFT Layout**: จัดวาง 4 charts ใน tabs ด้านซ้าย (vertical tab bar 80px)
6. **Active Tab Optimization**: อัพเดทเฉพาะ tab ที่มองเห็นด้วย `lv_tabview_get_tab_active()`
7. **Tilt Integration**: ใช้ `aic_tilt_init()` + `aic_tilt_update_from_imu()` สำหรับ Area และ Scatter tabs

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

1. เรียก `aic_sensors_init()` + `aic_tilt_init(NULL)` ก่อนสร้าง UI
2. สร้าง TabView แบบ LEFT tab bar (80px) พร้อม 4 tabs: Bar, Area, Scatter, Line
3. แต่ละ tab สร้าง chart ด้วยฟังก์ชันแยก (`ex11_create_xxx_tab()`)
4. Timer callback (100ms) อ่าน IMU + Gyro + Tilt แล้วอัพเดทเฉพาะ active tab
5. Tab Area ใช้ custom draw callback สำหรับ faded gradient effect

<figure><img src="/files/rjo6DkPtclX2RFvE3yrL" alt=""><figcaption></figcaption></figure>

***

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

#### 2.1 Dashboard Architecture

```
┌─────────────────────────────────────────────────────────────┐
│              CHART DASHBOARD ARCHITECTURE                   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ┌──────────────────────────────────────────────────────┐  │
│   │                 SENSOR LAYER                         │  │
│   │                                                      │  │
│   │   ┌───────────────┐    ┌───────────────┐             │  │
│   │   │  Accelerometer│    │   Gyroscope   │             │  │
│   │   │   ax, ay, az  │    │   gx, gy, gz  │             │  │
│   │   └───────┬───────┘    └───────┬───────┘             │  │
│   │           │                    │                     │  │
│   │           └────────┬───────────┘                     │  │
│   │                    │                                 │  │
│   │           ┌────────▼────────┐                        │  │
│   │           │  Tilt Analysis  │                        │  │
│   │           │   Roll, Pitch   │                        │  │
│   │           └────────┬────────┘                        │  │
│   └────────────────────┼─────────────────────────────────┘  │
│                        │                                    │
│   ┌────────────────────▼─────────────────────────────────┐  │
│   │              TIMER CALLBACK                          │  │
│   │                                                      │  │
│   │   1. Read all sensors                                │  │
│   │   2. Get active_tab = lv_tabview_get_tab_active()    │  │
│   │   3. switch(active_tab) - Update ONLY that chart     │  │
│   │                                                      │  │
│   └──────────────────────────────────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

#### 2.2 Tab Layout (Side Tabs)

```
┌─────────────────────────────────────────────────────────────┐
│  ┌────────┐                                                 │
│  │  Bar   │  ╔═══════════════════════════════════════════╗  │
│  │ Chart  │  ║         Tab Content (400x320)             ║  │
│  ├────────┤  ║                                           ║  │
│  │  Area  │  ║   Tab 0: Bar Chart (Accel X/Y/Z)          ║  │
│  │ Chart  │  ║      [====X====]  X: +0.5                 ║  │
│  ├────────┤  ║      [====Y====]  Y: -0.3                 ║  │
│  │Scatter │  ║      [====Z====]  Z: +9.8                 ║  │
│  │ Chart  │  ║                                           ║  │
│  ├────────┤  ║   Tab 1: Area Chart (Tilt Magnitude)      ║  │
│  │  Line  │  ║      ╱╲    ╱╲                             ║  │
│  │ Chart  │  ║     ╱  ╲  ╱  ╲╱╲                          ║  │
│  └────────┘  ║    ╱    ╲╱      ╲  (faded fill)           ║  │
│              ║                                           ║  │
│   Tab Bar    ║   Tab 2: Scatter (Roll vs Pitch)          ║  │
│    80px      ║           •  •                            ║  │
│              ║        •   •   •  (motion pattern)        ║  │
│              ║                                           ║  │
│              ║   Tab 3: Line Chart (Gyroscope)           ║  │
│              ║      ─── Roll                             ║  │
│              ║      ─── Pitch                            ║  │
│              ║      ─── Yaw                              ║  │
│              ╚═══════════════════════════════════════════╝  │
└─────────────────────────────────────────────────────────────┘
```

#### 2.3 Chart Data Sources

```
┌─────────────────────────────────────────────────────────────┐
│                    DATA SOURCES PER TAB                     │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   Tab 0: Bar Chart                                          │
│     Source: aic_imu_read_accel(&ax, &ay, &az)               │
│     Mapping: -5..+5 m/s² → 0..100 (sensitive)               │
│                                                             │
│   Tab 1: Area Chart                                         │
│     Source: aic_tilt_get_roll(), aic_tilt_get_pitch()       │
│     Mapping: magnitude = sqrt(roll² + pitch²) degrees       │
│     Range: 0..90 degrees                                    │
│                                                             │
│   Tab 2: Scatter Chart                                      │
│     Source: Roll, Pitch from Tilt Analysis                  │
│     X-axis: Roll (-90..+90 → 0..200)                        │
│     Y-axis: Pitch (-90..+90 → 0..200)                       │
│     Center = 100,100 (level position)                       │
│                                                             │
│   Tab 3: Line Chart                                         │
│     Source: aic_imu_read_gyro(&gx, &gy, &gz)                │
│     Mapping: -2..+2 rad/s → -200..+200                      │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

***

### 3. ฟังก์ชันสำคัญ

#### 3.1 Sensor & Tilt APIs

<table><thead><tr><th width="315.70098876953125">Function</th><th width="87.8572998046875">Return</th><th>Description</th></tr></thead><tbody><tr><td><code>aic_sensors_init()</code></td><td>void</td><td>Initialize sensor subsystem (เรียกครั้งเดียว)</td></tr><tr><td><code>aic_tilt_init(NULL)</code></td><td>void</td><td>Initialize Complementary Filter สำหรับ Tilt</td></tr><tr><td><code>aic_imu_read_accel(&#x26;ax, &#x26;ay, &#x26;az)</code></td><td>bool</td><td>อ่าน accelerometer (m/s2)</td></tr><tr><td><code>aic_imu_read_gyro(&#x26;gx, &#x26;gy, &#x26;gz)</code></td><td>bool</td><td>อ่าน gyroscope (rad/s)</td></tr><tr><td><code>aic_tilt_update_from_imu()</code></td><td>bool</td><td>อัพเดท tilt estimation (accel + gyro fusion)</td></tr><tr><td><code>aic_tilt_get_roll()</code></td><td>float</td><td>อ่าน roll angle (degrees)</td></tr><tr><td><code>aic_tilt_get_pitch()</code></td><td>float</td><td>อ่าน pitch angle (degrees)</td></tr></tbody></table>

#### 3.2 Chart Type APIs

<table><thead><tr><th width="130.30963134765625">Chart Type</th><th width="250.8011474609375">Create</th><th>Update</th><th>Description</th></tr></thead><tbody><tr><td>BAR</td><td><code>lv_chart_set_type(chart, LV_CHART_TYPE_BAR)</code></td><td><code>lv_chart_set_value_by_id(chart, ser, id, val)</code></td><td>แท่งเปรียบเทียบค่า</td></tr><tr><td>LINE</td><td><code>lv_chart_set_type(chart, LV_CHART_TYPE_LINE)</code></td><td><code>lv_chart_set_next_value(chart, ser, val)</code></td><td>เส้น time-series</td></tr><tr><td>SCATTER</td><td><code>lv_chart_set_type(chart, LV_CHART_TYPE_SCATTER)</code></td><td><code>lv_chart_set_next_value2(chart, ser, x, y)</code></td><td>จุด X-Y correlation</td></tr></tbody></table>

#### 3.3 TabView APIs

<table><thead><tr><th width="440.62640380859375">Function</th><th>Description</th></tr></thead><tbody><tr><td><code>lv_tabview_create(parent)</code></td><td>สร้าง TabView</td></tr><tr><td><code>lv_tabview_set_tab_bar_position(tv, LV_DIR_LEFT)</code></td><td>Tab bar ด้านซ้าย</td></tr><tr><td><code>lv_tabview_set_tab_bar_size(tv, 80)</code></td><td>ความกว้าง tab bar 80px</td></tr><tr><td><code>lv_tabview_add_tab(tv, "name")</code></td><td>เพิ่ม tab ใหม่</td></tr><tr><td><code>lv_tabview_get_tab_active(tv)</code></td><td>อ่าน active tab index (0, 1, 2, 3)</td></tr><tr><td><code>lv_tabview_get_tab_bar(tv)</code></td><td>อ่าน tab button bar object</td></tr><tr><td><code>lv_tabview_get_content(tv)</code></td><td>อ่าน content container</td></tr></tbody></table>

#### 3.4 Draw Callback APIs (สำหรับ Faded Area)

<table><thead><tr><th width="361.688232421875">Function</th><th>Description</th></tr></thead><tbody><tr><td><code>lv_obj_add_event_cb(obj, cb, LV_EVENT_DRAW_TASK_ADDED, NULL)</code></td><td>Register draw callback</td></tr><tr><td><code>lv_obj_add_flag(obj, LV_OBJ_FLAG_SEND_DRAW_TASK_EVENTS)</code></td><td>Enable draw task events</td></tr><tr><td><code>lv_event_get_draw_task(e)</code></td><td>อ่าน draw task จาก event</td></tr><tr><td><code>lv_draw_task_get_draw_dsc(task)</code></td><td>อ่าน draw descriptor</td></tr><tr><td><code>lv_draw_task_get_type(task)</code></td><td>อ่าน draw type (LINE, RECT, etc.)</td></tr><tr><td><code>lv_draw_triangle(layer, &#x26;dsc)</code></td><td>วาดสามเหลี่ยม (gradient fill)</td></tr><tr><td><code>lv_draw_rect(layer, &#x26;dsc, &#x26;area)</code></td><td>วาดสี่เหลี่ยม (fade to transparent)</td></tr></tbody></table>

#### 3.5 Include Files

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

#ifdef CYBSP_ENABLED
#include "aic-eec/aic-eec.h"
#include "aic-eec/sensors.h"
#include "aic-eec/tilt.h"
#include "../../shared/imu_shared.h"
#endif
```

***

### 4. โค้ดตัวอย่าง

#### 4.1 Global Variables & Defines

```c
#define EX11_CHART_POINTS    50
#define EX11_SCATTER_POINTS  30

/* Tab 1: Bar Chart - current X, Y, Z values with colors */
static lv_obj_t * ex11_bar_chart;
static lv_chart_series_t * ex11_bar_ser_x;  /* Red for X */
static lv_chart_series_t * ex11_bar_ser_y;  /* Green for Y */
static lv_chart_series_t * ex11_bar_ser_z;  /* Blue for Z */
static lv_obj_t * ex11_bar_labels[3];

/* Tab 2: Area Chart - tilt magnitude over time */
static lv_obj_t * ex11_area_chart;
static lv_chart_series_t * ex11_area_ser;
static lv_obj_t * ex11_area_label;

/* Tab 3: Scatter Chart - Roll vs Pitch (Tilt Ball) */
static lv_obj_t * ex11_scatter_chart;
static lv_chart_series_t * ex11_scatter_ser;
static lv_obj_t * ex11_scatter_label;

/* Tab 4: Line Chart - Gyroscope time series */
static lv_obj_t * ex11_line_chart;
static lv_chart_series_t * ex11_line_ser[3];
static lv_obj_t * ex11_line_labels[3];

/* Timer and TabView reference */
static lv_timer_t * ex11_chart_timer;
static lv_obj_t * ex11_tabview;
```

#### 4.2 Simulation Helpers (PC Simulator)

```c
#ifndef CYBSP_ENABLED
static bool simulate_imu_accel(float *ax, float *ay, float *az)
{
    static float t = 0.0f;
    t += 0.05f;
    *ax = 2.0f * sinf(t);
    *ay = 1.5f * cosf(t * 0.7f);
    *az = 9.81f + 0.5f * sinf(t * 2.0f);
    return true;
}

static bool simulate_imu_gyro(float *gx, float *gy, float *gz)
{
    static float t = 0.0f;
    t += 0.03f;
    *gx = 0.8f * sinf(t * 1.2f);
    *gy = 0.5f * cosf(t * 0.8f);
    *gz = 0.3f * sinf(t * 1.5f);
    return true;
}

static float sim_roll = 0.0f, sim_pitch = 0.0f;

static bool simulate_tilt_update(void)
{
    static float t = 0.0f;
    t += 0.05f;
    sim_roll = 30.0f * sinf(t * 0.5f);
    sim_pitch = 20.0f * cosf(t * 0.3f);
    return true;
}
#endif
```

#### 4.3 Timer Callback - Active Tab Optimization

นี่คือหัวใจสำคัญของ Lab นี้ -- timer callback ที่อ่านข้อมูลจากทุก sensor แต่อัพเดทเฉพาะ chart ของ tab ที่กำลังแสดงผลอยู่:

```c
static void ex11_chart_timer_cb(lv_timer_t * timer)
{
    (void)timer;

    /* ===== Read ALL sensor data ===== */
    float ax, ay, az;
    float gx, gy, gz;

#ifdef CYBSP_ENABLED
    if(!aic_imu_read_accel(&ax, &ay, &az)) {
        ax = 0; ay = 0; az = 9.81f;
    }
    if(!aic_imu_read_gyro(&gx, &gy, &gz)) {
        gx = 0; gy = 0; gz = 0;
    }
    aic_tilt_update_from_imu();
    float roll = aic_tilt_get_roll();
    float pitch = aic_tilt_get_pitch();
#else
    simulate_imu_accel(&ax, &ay, &az);
    simulate_imu_gyro(&gx, &gy, &gz);
    simulate_tilt_update();
    float roll = sim_roll;
    float pitch = sim_pitch;
#endif

    /* ===== Get active tab index ===== */
    /* 0=Bar, 1=Area, 2=Scatter, 3=Line */
    uint32_t active_tab = lv_tabview_get_tab_active(ex11_tabview);

    /* ===== Update ONLY the active tab ===== */
    switch(active_tab) {

        case 0: /* Tab 1: Bar Chart - Accel X/Y/Z */
        {
            /* Scale: -5 to +5 m/s2 -> 0 to 100 */
            int32_t bar_x = (int32_t)((ax + 5.0f) * 10.0f);
            int32_t bar_y = (int32_t)((ay + 5.0f) * 10.0f);
            int32_t bar_z = (int32_t)((az + 5.0f) * 10.0f);

            /* Clamp to range */
            if(bar_x < 0) bar_x = 0; if(bar_x > 100) bar_x = 100;
            if(bar_y < 0) bar_y = 0; if(bar_y > 100) bar_y = 100;
            if(bar_z < 0) bar_z = 0; if(bar_z > 100) bar_z = 100;

            /* Update each colored bar */
            lv_chart_set_value_by_id(ex11_bar_chart,
                ex11_bar_ser_x, 0, bar_x);
            lv_chart_set_value_by_id(ex11_bar_chart,
                ex11_bar_ser_y, 0, bar_y);
            lv_chart_set_value_by_id(ex11_bar_chart,
                ex11_bar_ser_z, 0, bar_z);
            lv_chart_refresh(ex11_bar_chart);

            lv_label_set_text_fmt(ex11_bar_labels[0],
                "X: %.1f", (double)ax);
            lv_label_set_text_fmt(ex11_bar_labels[1],
                "Y: %.1f", (double)ay);
            lv_label_set_text_fmt(ex11_bar_labels[2],
                "Z: %.1f", (double)az);
            break;
        }

        case 1: /* Tab 2: Area Chart - Tilt Magnitude */
        {
            /* Tilt magnitude: sqrt(roll^2 + pitch^2) */
            float tilt_mag = sqrtf(roll * roll + pitch * pitch);
            int32_t tilt_scaled = (int32_t)(tilt_mag);
            if(tilt_scaled > 90) tilt_scaled = 90;

            lv_chart_set_next_value(ex11_area_chart,
                ex11_area_ser, tilt_scaled);
            lv_label_set_text_fmt(ex11_area_label,
                "Tilt: %.1f deg (R:%.1f P:%.1f)",
                (double)tilt_mag, (double)roll, (double)pitch);
            break;
        }

        case 2: /* Tab 3: Scatter - Roll vs Pitch */
        {
            /* Map: -90..+90 -> 0..200 (center=100) */
            int32_t scatter_x = (int32_t)(
                (roll + 90.0f) * (200.0f / 180.0f));
            int32_t scatter_y = (int32_t)(
                (pitch + 90.0f) * (200.0f / 180.0f));

            if(scatter_x < 0) scatter_x = 0;
            if(scatter_x > 200) scatter_x = 200;
            if(scatter_y < 0) scatter_y = 0;
            if(scatter_y > 200) scatter_y = 200;

            lv_chart_set_next_value2(ex11_scatter_chart,
                ex11_scatter_ser, scatter_x, scatter_y);
            lv_label_set_text_fmt(ex11_scatter_label,
                "Roll: %.1f  Pitch: %.1f",
                (double)roll, (double)pitch);
            break;
        }

        case 3: /* Tab 4: Line Chart - Gyroscope */
        {
            /* Scale by 100 (Ex7 pattern) */
            int32_t gx_scaled = (int32_t)(gx * 100);
            int32_t gy_scaled = (int32_t)(gy * 100);
            int32_t gz_scaled = (int32_t)(gz * 100);

            lv_chart_set_next_value(ex11_line_chart,
                ex11_line_ser[0], gx_scaled);
            lv_chart_set_next_value(ex11_line_chart,
                ex11_line_ser[1], gy_scaled);
            lv_chart_set_next_value(ex11_line_chart,
                ex11_line_ser[2], gz_scaled);

            lv_label_set_text_fmt(ex11_line_labels[0],
                "Gx: %.2f", (double)gx);
            lv_label_set_text_fmt(ex11_line_labels[1],
                "Gy: %.2f", (double)gy);
            lv_label_set_text_fmt(ex11_line_labels[2],
                "Gz: %.2f", (double)gz);
            break;
        }
    }
}
```

#### 4.4 Tab 1: Bar Chart - Real Accel X/Y/Z

```c
static void ex11_create_bar_chart_tab(lv_obj_t * parent)
{
    /* Title */
    lv_obj_t * title = lv_label_create(parent);
    lv_label_set_text(title, "Bar: Real Accel X/Y/Z");
    lv_obj_set_style_text_font(title, LV_FONT_DEFAULT, 0);
    lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 5);

    /* Create Bar Chart - 3 colored bars for X, Y, Z */
    ex11_bar_chart = lv_chart_create(parent);
    lv_obj_set_size(ex11_bar_chart, 390, 280);
    lv_obj_align(ex11_bar_chart, LV_ALIGN_CENTER, 0, 0);
    lv_chart_set_type(ex11_bar_chart, LV_CHART_TYPE_BAR);
    lv_chart_set_point_count(ex11_bar_chart, 1);
    lv_chart_set_range(ex11_bar_chart,
        LV_CHART_AXIS_PRIMARY_Y, 0, 100);

    /* Add 3 colored series: X=Red, Y=Green, Z=Blue */
    ex11_bar_ser_x = lv_chart_add_series(ex11_bar_chart,
        lv_palette_main(LV_PALETTE_RED),
        LV_CHART_AXIS_PRIMARY_Y);
    ex11_bar_ser_y = lv_chart_add_series(ex11_bar_chart,
        lv_palette_main(LV_PALETTE_GREEN),
        LV_CHART_AXIS_PRIMARY_Y);
    ex11_bar_ser_z = lv_chart_add_series(ex11_bar_chart,
        lv_palette_main(LV_PALETTE_BLUE),
        LV_CHART_AXIS_PRIMARY_Y);

    /* Set initial values */
    lv_chart_set_value_by_id(ex11_bar_chart,
        ex11_bar_ser_x, 0, 50);
    lv_chart_set_value_by_id(ex11_bar_chart,
        ex11_bar_ser_y, 0, 50);
    lv_chart_set_value_by_id(ex11_bar_chart,
        ex11_bar_ser_z, 0, 98);

    /* Style: spacing between colored bars */
    lv_obj_set_style_pad_column(ex11_bar_chart, 30, 0);

    /* Value labels below chart */
    const char *axis_names[] = {"X", "Y", "Z"};
    lv_color_t colors[] = {
        lv_palette_main(LV_PALETTE_RED),
        lv_palette_main(LV_PALETTE_GREEN),
        lv_palette_main(LV_PALETTE_BLUE)
    };

    for(int i = 0; i < 3; i++) {
        ex11_bar_labels[i] = lv_label_create(parent);
        lv_label_set_text_fmt(ex11_bar_labels[i],
            "%s: 0.00", axis_names[i]);
        lv_obj_set_style_text_color(
            ex11_bar_labels[i], colors[i], 0);
        lv_obj_align(ex11_bar_labels[i],
            LV_ALIGN_BOTTOM_LEFT, 30 + i * 120, -5);
    }
}
```

#### 4.5 Faded Area Draw Callback

นี่คือ draw callback ที่สร้าง gradient fill ใต้เส้น chart ซึ่งอ้างอิงจาก `lv_example_chart_5` ของ LVGL:

```c
static void ex11_area_draw_cb(lv_event_t * e)
{
    lv_draw_task_t * draw_task = lv_event_get_draw_task(e);
    lv_draw_dsc_base_t * base_dsc =
        lv_draw_task_get_draw_dsc(draw_task);

    /* Only process line items (chart line segments) */
    if(base_dsc->part != LV_PART_ITEMS) return;
    if(lv_draw_task_get_type(draw_task) !=
        LV_DRAW_TASK_TYPE_LINE) return;

    lv_obj_t * obj = lv_event_get_target(e);
    lv_area_t coords;
    lv_obj_get_coords(obj, &coords);

    /* Get series color for gradient */
    const lv_chart_series_t * ser =
        lv_chart_get_series_next(obj, NULL);
    lv_color_t ser_color =
        lv_chart_get_series_color(obj, ser);

    lv_draw_line_dsc_t * draw_line_dsc =
        lv_draw_task_get_draw_dsc(draw_task);

    /* ===== TRIANGLE: Fill between line and lowest point ===== */
    lv_draw_triangle_dsc_t tri_dsc;
    lv_draw_triangle_dsc_init(&tri_dsc);
    tri_dsc.p[0].x = draw_line_dsc->p1.x;
    tri_dsc.p[0].y = draw_line_dsc->p1.y;
    tri_dsc.p[1].x = draw_line_dsc->p2.x;
    tri_dsc.p[1].y = draw_line_dsc->p2.y;
    tri_dsc.p[2].x = draw_line_dsc->p1.y < draw_line_dsc->p2.y
        ? draw_line_dsc->p1.x : draw_line_dsc->p2.x;
    tri_dsc.p[2].y = LV_MAX(draw_line_dsc->p1.y,
                             draw_line_dsc->p2.y);
    tri_dsc.bg_grad.dir = LV_GRAD_DIR_VER;

    /* Calculate opacity based on Y position */
    int32_t full_h = lv_obj_get_height(obj);
    int32_t fract_upper = (int32_t)(
        LV_MIN(draw_line_dsc->p1.y, draw_line_dsc->p2.y)
        - coords.y1) * 255 / full_h;
    int32_t fract_lower = (int32_t)(
        LV_MAX(draw_line_dsc->p1.y, draw_line_dsc->p2.y)
        - coords.y1) * 255 / full_h;

    tri_dsc.bg_grad.stops[0].color = ser_color;
    tri_dsc.bg_grad.stops[0].opa = 255 - fract_upper;
    tri_dsc.bg_grad.stops[0].frac = 0;
    tri_dsc.bg_grad.stops[1].color = ser_color;
    tri_dsc.bg_grad.stops[1].opa = 255 - fract_lower;
    tri_dsc.bg_grad.stops[1].frac = 255;

    lv_draw_triangle(base_dsc->layer, &tri_dsc);

    /* ===== RECTANGLE: Fade from lowest point to bottom ===== */
    lv_draw_rect_dsc_t rect_dsc;
    lv_draw_rect_dsc_init(&rect_dsc);
    rect_dsc.bg_grad.dir = LV_GRAD_DIR_VER;
    rect_dsc.bg_grad.stops[0].color = ser_color;
    rect_dsc.bg_grad.stops[0].frac = 0;
    rect_dsc.bg_grad.stops[0].opa = 255 - fract_lower;
    rect_dsc.bg_grad.stops[1].color = ser_color;
    rect_dsc.bg_grad.stops[1].frac = 255;
    rect_dsc.bg_grad.stops[1].opa = 0;

    lv_area_t rect_area;
    rect_area.x1 = (int32_t)draw_line_dsc->p1.x;
    rect_area.x2 = (int32_t)draw_line_dsc->p2.x - 1;
    rect_area.y1 = (int32_t)LV_MAX(draw_line_dsc->p1.y,
                                     draw_line_dsc->p2.y) - 1;
    rect_area.y2 = (int32_t)coords.y2;
    lv_draw_rect(base_dsc->layer, &rect_dsc, &rect_area);
}
```

#### 4.6 Tab 2: Area Chart - Tilt Magnitude

```c
static void ex11_create_area_chart_tab(lv_obj_t * parent)
{
    /* Title */
    lv_obj_t * title = lv_label_create(parent);
    lv_label_set_text(title, "Area: Tilt Magnitude (deg)");
    lv_obj_set_style_text_font(title, LV_FONT_DEFAULT, 0);
    lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 5);

    /* Create Area Chart - tilt angle over time */
    ex11_area_chart = lv_chart_create(parent);
    lv_obj_set_size(ex11_area_chart, 390, 285);
    lv_obj_align(ex11_area_chart, LV_ALIGN_CENTER, 0, 0);
    lv_obj_set_style_pad_all(ex11_area_chart, 0, 0);
    lv_chart_set_type(ex11_area_chart, LV_CHART_TYPE_LINE);
    lv_chart_set_point_count(ex11_area_chart,
        EX11_CHART_POINTS);
    lv_chart_set_range(ex11_area_chart,
        LV_CHART_AXIS_PRIMARY_Y, 0, 90);

    /* Add horizontal reference lines */
    lv_chart_set_div_line_count(ex11_area_chart, 5, 8);

    /* CRITICAL: Enable draw task events for faded area */
    lv_obj_add_event_cb(ex11_area_chart,
        ex11_area_draw_cb,
        LV_EVENT_DRAW_TASK_ADDED, NULL);
    lv_obj_add_flag(ex11_area_chart,
        LV_OBJ_FLAG_SEND_DRAW_TASK_EVENTS);

    /* Add series */
    ex11_area_ser = lv_chart_add_series(ex11_area_chart,
        lv_palette_main(LV_PALETTE_RED),
        LV_CHART_AXIS_PRIMARY_Y);

    /* Show points and line */
    lv_obj_set_style_size(ex11_area_chart, 4, 4,
        LV_PART_INDICATOR);
    lv_obj_set_style_line_width(ex11_area_chart, 3,
        LV_PART_ITEMS);

    /* Value label */
    ex11_area_label = lv_label_create(parent);
    lv_label_set_text(ex11_area_label, "Tilt: 0.0 deg");
    lv_obj_set_style_text_color(ex11_area_label,
        lv_palette_main(LV_PALETTE_RED), 0);
    lv_obj_align(ex11_area_label,
        LV_ALIGN_BOTTOM_MID, 0, -5);
}
```

#### 4.7 Tab 3: Scatter Chart - Roll vs Pitch (Tilt Ball)

```c
static void ex11_create_scatter_chart_tab(lv_obj_t * parent)
{
    /* Title */
    lv_obj_t * title = lv_label_create(parent);
    lv_label_set_text(title,
        "Scatter: Roll vs Pitch (Tilt Ball)");
    lv_obj_set_style_text_font(title, LV_FONT_DEFAULT, 0);
    lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 5);

    /* Create Scatter Chart - square-ish for scatter */
    ex11_scatter_chart = lv_chart_create(parent);
    lv_obj_set_size(ex11_scatter_chart, 290, 280);
    lv_obj_align(ex11_scatter_chart,
        LV_ALIGN_CENTER, 0, 0);
    lv_chart_set_type(ex11_scatter_chart,
        LV_CHART_TYPE_SCATTER);
    lv_chart_set_point_count(ex11_scatter_chart,
        EX11_SCATTER_POINTS);

    /* X and Y ranges */
    lv_chart_set_range(ex11_scatter_chart,
        LV_CHART_AXIS_PRIMARY_X, 0, 200);
    lv_chart_set_range(ex11_scatter_chart,
        LV_CHART_AXIS_PRIMARY_Y, 0, 200);

    /* Add scatter series */
    ex11_scatter_ser = lv_chart_add_series(
        ex11_scatter_chart,
        lv_palette_main(LV_PALETTE_BLUE),
        LV_CHART_AXIS_PRIMARY_Y);

    /* Style: larger points, semi-transparent */
    lv_obj_set_style_size(ex11_scatter_chart,
        10, 10, LV_PART_INDICATOR);
    lv_obj_set_style_bg_opa(ex11_scatter_chart,
        LV_OPA_70, LV_PART_INDICATOR);

    /* Grid lines */
    lv_chart_set_div_line_count(ex11_scatter_chart, 5, 5);

    /* Value label */
    ex11_scatter_label = lv_label_create(parent);
    lv_label_set_text(ex11_scatter_label,
        "Roll: 0.0  Pitch: 0.0");
    lv_obj_set_style_text_color(ex11_scatter_label,
        lv_palette_main(LV_PALETTE_BLUE), 0);
    lv_obj_align(ex11_scatter_label,
        LV_ALIGN_BOTTOM_MID, 0, -5);
}
```

#### 4.8 Tab 4: Line Chart - Gyroscope R/P/Y

```c
static void ex11_create_line_chart_tab(lv_obj_t * parent)
{
    /* Title */
    lv_obj_t * title = lv_label_create(parent);
    lv_label_set_text(title, "Line: Real Gyro R/P/Y");
    lv_obj_set_style_text_font(title, LV_FONT_DEFAULT, 0);
    lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 5);

    /* Create Line Chart - Ex7's stable pattern */
    ex11_line_chart = lv_chart_create(parent);
    lv_obj_set_size(ex11_line_chart, 390, 275);
    lv_obj_align(ex11_line_chart,
        LV_ALIGN_CENTER, 0, -5);
    lv_chart_set_type(ex11_line_chart,
        LV_CHART_TYPE_LINE);
    lv_chart_set_point_count(ex11_line_chart,
        EX11_CHART_POINTS);

    /* Range: -1500 to 1500 (same as Ex7) */
    lv_chart_set_range(ex11_line_chart,
        LV_CHART_AXIS_PRIMARY_Y, -1500, 1500);

    /* Hide points, line width 2px */
    lv_obj_set_style_size(ex11_line_chart, 0, 0,
        LV_PART_INDICATOR);
    lv_obj_set_style_line_width(ex11_line_chart, 2,
        LV_PART_ITEMS);

    /* Grid lines */
    lv_chart_set_div_line_count(ex11_line_chart, 5, 5);

    /* Add 3 series: Roll(Red), Pitch(Green), Yaw(Blue) */
    lv_color_t colors[] = {
        lv_palette_main(LV_PALETTE_RED),
        lv_palette_main(LV_PALETTE_GREEN),
        lv_palette_main(LV_PALETTE_BLUE)
    };
    const char *names[] = {"Roll", "Pitch", "Yaw"};

    for(int i = 0; i < 3; i++) {
        ex11_line_ser[i] = lv_chart_add_series(
            ex11_line_chart, colors[i],
            LV_CHART_AXIS_PRIMARY_Y);

        ex11_line_labels[i] = lv_label_create(parent);
        lv_label_set_text_fmt(ex11_line_labels[i],
            "%s: 0.00", names[i]);
        lv_obj_set_style_text_color(
            ex11_line_labels[i], colors[i], 0);
        lv_obj_align(ex11_line_labels[i],
            LV_ALIGN_BOTTOM_LEFT, 20 + i * 125, -5);
    }
}
```

#### 4.9 Main Function - Assembling the Dashboard

```c
void part2_ex11_real_chart_dashboard(void)
{
    /* ===== HARDWARE INITIALIZATION ===== */
#ifdef CYBSP_ENABLED
    aic_sensors_init();
    aic_tilt_init(NULL);  /* Complementary Filter */
#endif

    /* ===== Create TabView (LEFT tab bar) ===== */
    ex11_tabview = lv_tabview_create(lv_screen_active());
    lv_obj_t * tabview = ex11_tabview;
    lv_tabview_set_tab_bar_position(tabview, LV_DIR_LEFT);
    lv_tabview_set_tab_bar_size(tabview, 80);

    /* Style the TabView background */
    lv_obj_set_style_bg_color(tabview,
        lv_palette_lighten(LV_PALETTE_BLUE, 2), 0);

    /* Style tab buttons */
    lv_obj_t * tab_btns = lv_tabview_get_tab_bar(tabview);
    lv_obj_set_style_bg_color(tab_btns,
        lv_palette_darken(LV_PALETTE_GREY, 3), 0);
    lv_obj_set_style_text_color(tab_btns,
        lv_palette_lighten(LV_PALETTE_GREY, 5), 0);
    lv_obj_set_style_border_side(tab_btns,
        LV_BORDER_SIDE_RIGHT,
        LV_PART_ITEMS | LV_STATE_CHECKED);

    /* ===== Create 4 Tabs ===== */
    lv_obj_t * tab_bar = lv_tabview_add_tab(tabview, "Bar");
    lv_obj_t * tab_area = lv_tabview_add_tab(tabview, "Area");
    lv_obj_t * tab_scatter = lv_tabview_add_tab(tabview,
        "Scatter");
    lv_obj_t * tab_line = lv_tabview_add_tab(tabview, "Line");

    /* Style tab content backgrounds */
    lv_obj_t * tabs[] = {tab_bar, tab_area,
                         tab_scatter, tab_line};
    for(int i = 0; i < 4; i++) {
        lv_obj_set_style_bg_color(tabs[i],
            lv_color_hex(0xF5F5F5), 0);
        lv_obj_set_style_bg_opa(tabs[i],
            LV_OPA_COVER, 0);
    }

    /* ===== Populate each tab ===== */
    ex11_create_bar_chart_tab(tab_bar);
    ex11_create_area_chart_tab(tab_area);
    ex11_create_scatter_chart_tab(tab_scatter);
    ex11_create_line_chart_tab(tab_line);

    /* CRITICAL: Disable scrolling on content */
    lv_obj_remove_flag(
        lv_tabview_get_content(tabview),
        LV_OBJ_FLAG_SCROLLABLE);

    /* ===== Timer: 100ms update ===== */
    ex11_chart_timer = lv_timer_create(
        ex11_chart_timer_cb, 100, NULL);

    /* ===== Footer ===== */
    aic_create_footer(lv_screen_active());

    printf("[Part2] Ex11: Real Chart Dashboard started\r\n");
}
```

***

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

#### 5.1 Active Tab Optimization - ทำไมต้องอัพเดทเฉพาะ Tab ที่มองเห็น

```c
/* ===== Problem: Update ทุก chart ทุกรอบ ===== */
/* 4 charts x 100ms = CPU load สูง, frame rate ต่ำ */
/* chart ที่ซ่อนอยู่ก็ต้อง redraw ทั้งที่ไม่มีคนเห็น */

/* ===== Solution: Update เฉพาะ active tab ===== */
uint32_t active = lv_tabview_get_tab_active(tabview);

switch(active) {
    case 0: update_bar_chart();     break;
    case 1: update_area_chart();    break;
    case 2: update_scatter_chart(); break;
    case 3: update_line_chart();    break;
}

/* ===== ข้อดี ===== */
/* 1. CPU load ลดลง 75% (update 1 chart แทน 4) */
/* 2. Frame rate สูงขึ้น */
/* 3. Sensor data ยังอ่านทุกรอบ (ไม่พลาดข้อมูล) */

/* ===== ข้อเสีย ===== */
/* 1. Tab ที่ซ่อนจะไม่มีข้อมูลใหม่ */
/* 2. เมื่อสลับ tab, chart จะแสดงข้อมูลเก่า */
/* 3. Area/Line chart จะขาดช่วง (gap) */

/* ===== Trade-off: เมื่อไหร่ใช้ pattern นี้ ===== */
/* - 4+ charts              -> ใช้ Active Tab Optimization */
/* - 2-3 charts (เบา ๆ)      -> อัพเดททุก chart ก็ได้ */
/* - Real-time critical     -> ต้องอัพเดททุกตัว */
```

#### 5.2 Area Chart - Custom Draw Callback อธิบาย

```c
/* ===== ทำไม Area Chart ไม่ใช่ built-in feature? ===== */
/* LVGL v9 ไม่มี chart type "AREA" โดยตรง */
/* ต้องสร้างเองโดยวาด shape ใต้เส้น line chart */

/* ===== ขั้นตอนการทำงาน ===== */

/* 1. Register callback + enable flag */
lv_obj_add_event_cb(chart, draw_cb,
    LV_EVENT_DRAW_TASK_ADDED, NULL);
lv_obj_add_flag(chart,
    LV_OBJ_FLAG_SEND_DRAW_TASK_EVENTS);

/* 2. ใน callback: ตรวจว่าเป็น line segment */
if(base_dsc->part == LV_PART_ITEMS &&
   lv_draw_task_get_type(task) == LV_DRAW_TASK_TYPE_LINE)
{
    /* 3. ดึง P1, P2 ของ line segment */
    /* P1 = จุดเริ่มต้น, P2 = จุดสิ้นสุด */

    /* 4. วาด TRIANGLE ระหว่าง P1, P2, จุดต่ำสุด */
    /* Triangle มี vertical gradient: */
    /*   - ด้านบน: opacity สูง (เห็นชัด) */
    /*   - ด้านล่าง: opacity ต่ำ (จาง) */

    /* 5. วาด RECTANGLE จากจุดต่ำสุดถึงก้น chart */
    /* Rectangle fade จาก opacity ต่ำ → โปร่งใส */
}

/* ===== ผลลัพธ์ ===== */
/* เส้น chart มี gradient fill ใต้เส้น */
/* ค่าสูง = สีเข้ม, ค่าต่ำ = สีจาง */
/* สวยงามและอ่านง่าย */
```

#### 5.3 Scatter Chart - 2D Mapping Pattern

```c
/* ===== Scatter Chart ต่างจาก Line Chart อย่างไร? ===== */

/* Line Chart: set_next_value(chart, ser, y_value) */
/* - X-axis = auto (time index: 0, 1, 2, ...) */
/* - Y-axis = value ที่กำหนด */
/* - เหมาะกับ time-series */

/* Scatter Chart: set_next_value2(chart, ser, x_val, y_val) */
/* - X-axis = value ที่กำหนด (ไม่ใช่ time index) */
/* - Y-axis = value ที่กำหนด */
/* - เหมาะกับ X-Y correlation / 2D mapping */

/* ===== Roll vs Pitch Mapping ===== */
/* Roll:  -90 to +90 degrees -> 0 to 200 (center = 100) */
/* Pitch: -90 to +90 degrees -> 0 to 200 (center = 100) */

int32_t scatter_x = (int32_t)((roll + 90.0f) * (200.0f / 180.0f));
int32_t scatter_y = (int32_t)((pitch + 90.0f) * (200.0f / 180.0f));

/* เมื่อบอร์ดวางราบ: scatter_x = 100, scatter_y = 100 (center) */
/* เอียงซ้าย: scatter_x < 100 */
/* เอียงขวา: scatter_x > 100 */
/* เอียงหน้า: scatter_y < 100 */
/* เอียงหลัง: scatter_y > 100 */

/* ===== ต้องตั้ง range ทั้ง X และ Y ===== */
lv_chart_set_range(chart, LV_CHART_AXIS_PRIMARY_X, 0, 200);
lv_chart_set_range(chart, LV_CHART_AXIS_PRIMARY_Y, 0, 200);

/* ===== Point Count สำหรับ Scatter ===== */
/* 30 points = trail effect (เห็นทิศทางการเคลื่อนที่) */
/* เมื่อเกิน 30 จุด, จุดเก่าสุดจะหายไป */
lv_chart_set_point_count(chart, 30);
```

#### 5.4 Data Scaling Patterns

```c
/* ===== Pattern 1: Linear Mapping ===== */
/* Map value from range [in_min, in_max] to [out_min, out_max] */
/* Formula: out = (val - in_min) / (in_max - in_min) * (out_max - out_min) + out_min */

/* Accel (-5..+5) -> Bar (0..100) */
int32_t bar = (int32_t)((ax + 5.0f) * 10.0f);
/* When ax=-5: bar=0, ax=0: bar=50, ax=+5: bar=100 */

/* ===== Pattern 2: Scale Factor ===== */
/* Multiply by constant to convert float to int */
/* Gyro (rad/s, small values) -> Chart int */
int32_t gx_scaled = (int32_t)(gx * 100);
/* gx=0.12 -> 12, gx=-1.50 -> -150 */

/* ===== Pattern 3: Magnitude Calculation ===== */
/* Combine multiple axes into single value */
float tilt_mag = sqrtf(roll * roll + pitch * pitch);
/* Range: 0 (flat) to ~127 (max tilt) */
/* Clamp to 0-90 for chart */

/* ===== Pattern 4: Center Mapping ===== */
/* Map bipolar range to positive range with center */
/* Roll (-90..+90) -> Scatter (0..200, center=100) */
int32_t x = (int32_t)((roll + 90.0f) * (200.0f / 180.0f));

/* ===== Always Clamp! ===== */
if(val < min) val = min;
if(val > max) val = max;
```

#### 5.5 Bar Chart: set\_value\_by\_id vs set\_next\_value

```c
/* ===== set_next_value (สำหรับ Line/Area) ===== */
/* เพิ่มจุดใหม่ทางขวา, จุดเก่าเลื่อนซ้าย */
/* เหมาะกับ time-series ที่ข้อมูลเข้ามาเรื่อย ๆ */
lv_chart_set_next_value(chart, series, value);

/* ===== set_value_by_id (สำหรับ Bar) ===== */
/* อัพเดทค่าที่ตำแหน่ง (id) ที่กำหนด */
/* เหมาะกับ Bar chart ที่แต่ละแท่งแทน category */
lv_chart_set_value_by_id(chart, series, 0, value);
/* id=0 = แท่งแรก (และแท่งเดียว เพราะ point_count=1) */

/* ===== สำคัญ: lv_chart_refresh() ===== */
/* หลัง set_value_by_id ต้องเรียก refresh */
/* เพราะ set_value_by_id ไม่ trigger redraw อัตโนมัติ */
lv_chart_set_value_by_id(chart, ser_x, 0, bar_x);
lv_chart_set_value_by_id(chart, ser_y, 0, bar_y);
lv_chart_set_value_by_id(chart, ser_z, 0, bar_z);
lv_chart_refresh(chart);  /* MUST call after set_value_by_id */

/* set_next_value ไม่ต้อง refresh (ทำอัตโนมัติ) */
```

#### 5.6 Tilt Analysis: Complementary Filter

```c
/* ===== ทำไมไม่ใช้ Accelerometer อย่างเดียว? ===== */
/* Accel: วัดทิศทางแรงโน้มถ่วง -> คำนวณมุมได้ */
/* แต่: noisy มาก + ถ้ามี vibration จะผิดพลาด */

/* ===== ทำไมไม่ใช้ Gyroscope อย่างเดียว? ===== */
/* Gyro: วัดความเร็วเชิงมุม -> integrate ได้มุม */
/* แต่: drift ตลอดเวลา (มุมเพิ่มขึ้นเรื่อย ๆ) */

/* ===== Complementary Filter: Best of Both ===== */
/* angle = alpha * (angle + gyro*dt) + (1-alpha) * accel_angle */
/* alpha = 0.98 (trust gyro 98% for short-term) */
/* 1-alpha = 0.02 (trust accel 2% for long-term) */

/* ===== ใน Lab นี้: ===== */
/* 1. aic_tilt_init(NULL)           -> Initialize filter */
/* 2. aic_tilt_update_from_imu()    -> Fusion step (ทุก 100ms) */
/* 3. aic_tilt_get_roll()           -> Filtered roll angle */
/* 4. aic_tilt_get_pitch()          -> Filtered pitch angle */

/* ===== ใช้ใน Tab ไหน? ===== */
/* Tab 1 (Area): magnitude = sqrt(roll^2 + pitch^2) */
/* Tab 2 (Scatter): x = roll, y = pitch */
```

***

### 6. แบบฝึกหัด

#### Exercise 1: FFT Frequency Display Tab

**โจทย์:** เพิ่ม Tab ที่ 5 "FFT" แสดง Frequency Spectrum ของ accelerometer Z-axis:

**Requirements:**

* เก็บข้อมูล Z-axis 64 samples ล่าสุด (circular buffer)
* คำนวณ Simple DFT (Discrete Fourier Transform) ขนาด 64 จุด
* แสดงเป็น Bar chart: แกน X = frequency bin (0-32), แกน Y = magnitude
* อัพเดทผลลัพธ์ทุก 64 samples ใหม่ (ไม่ใช่ทุก sample)
* แสดง dominant frequency (ความถี่ที่มี magnitude สูงสุด)

**Layout:**

```bash
FFT Tab:
  "FFT: Z-axis Frequency Spectrum"
  ┌──────────────────────────────┐
  │  BAR CHART (32 bars)         │
  │  ████                        │
  │  ████ ██                     │
  │  ████ ██ ██ ██               │
  │  0Hz  5Hz 10Hz 15Hz          │
  └──────────────────────────────┘
  "Dominant: 3.5 Hz | Power: 42.3"
```

**การประยุกต์ใช้งานจริง:**

* Vibration Analysis: ตรวจจับ resonance frequency ของเครื่องจักร
* Predictive Maintenance: ตรวจสอบ bearing wear จาก vibration spectrum
* Structural Health Monitoring: ตรวจสอบ natural frequency ของสะพาน/อาคาร

**Hint:**

* DFT bin k: `mag[k] = sqrt(real[k]^2 + imag[k]^2)` where `real[k] = sum(x[n] * cos(2*PI*k*n/N))` and `imag[k] = sum(x[n] * sin(2*PI*k*n/N))`
* Frequency resolution: `f_res = sample_rate / N = 10Hz / 64 = 0.156 Hz/bin`
* ใช้ `lv_chart_set_point_count(chart, 32)` (แสดงเฉพาะครึ่งแรกของ DFT)
* `lv_chart_set_value_by_id()` สำหรับอัพเดทแต่ละ bar

***

#### Exercise 2: Recording and Playback

**โจทย์:** เพิ่มปุ่ม Record/Playback สำหรับบันทึกข้อมูล sensor แล้วเล่นซ้ำ:

**Requirements:**

* ปุ่ม "REC" สีแดง: เริ่มบันทึกข้อมูล (accel + gyro + tilt) สูงสุด 500 samples
* ปุ่ม "STOP" สีเทา: หยุดบันทึก
* ปุ่ม "PLAY" สีเขียว: เล่นข้อมูลที่บันทึกไว้กลับใน chart
* แสดง recording status: "Recording... 123/500" หรือ "Playing... 45/123"
* เมื่อ playback, timer อ่านจาก buffer แทน sensor จริง

**Layout:**

```bash
เพิ่มบริเวณล่างของแต่ละ tab:
  [REC] [STOP] [PLAY]  "Idle | 0 samples"
```

**การประยุกต์ใช้งานจริง:**

* Flight Data Recorder: บันทึกข้อมูลการบินสำหรับวิเคราะห์ภายหลัง
* Sports Analytics: บันทึก motion ของนักกีฬาแล้วเล่นซ้ำเพื่อวิเคราะห์ท่าทาง
* Quality Control: บันทึก vibration pattern สำหรับเปรียบเทียบกับ reference

**Hint:**

* ใช้ struct สำหรับ sample: `typedef struct { float ax, ay, az, gx, gy, gz, roll, pitch; } sensor_sample_t;`
* Static array: `static sensor_sample_t recording[500];`
* State machine: `IDLE -> RECORDING -> STOPPED -> PLAYING -> IDLE`
* Playback timer: ใช้ timer เดิม แต่เปลี่ยน data source

***

### 7. References

* [LVGL Chart](https://docs.lvgl.io/9.2/widgets/chart.html)
* [lv\_example\_chart\_5](https://github.com/lvgl/lvgl/blob/release/v9.2/examples/widgets/chart/lv_example_chart_5.c) (Area effect)
* [LVGL TabView](https://docs.lvgl.io/9.2/widgets/tabview.html)

***


---

# 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/hardware-interfacing-workshops/chart-dashboard.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.
