Skip to content

chart.tc

Google Line Chart — 1000 data points stored in array

Source on GitHub

// Google Line Chart — 1000 data points stored in array
// Demonstrates: heap arrays, WebPage, WebCall, webSend, webFlush, random()
// WebPage draws the chart once, EverySecond shifts new data in
// WebCall shows a sensor-style status row (refreshed periodically)

#define N 1000

int data[N];       // ring buffer of sensor values (heap-allocated, >255)
int pos = 0;       // write position (ring buffer head)
int counter = 0;

// Simple integer sine approximation (x in 0..399 period, returns -100..100)
int isin(int x) {
    int p = x % 400;
    if (p < 0) p = p + 400;
    if (p < 100) return p;
    if (p < 200) return 200 - p;
    if (p < 300) return -(p - 200);
    return -(400 - p);
}

void EverySecond() {
    // Add a new data point each second (sine + noise)
    int s = isin(counter);
    int noise = random(-15, 15);
    data[pos] = s + noise;
    pos++;
    if (pos >= N) pos = 0;
    counter++;
}

void WebPage() {
    // Called once when page is drawn (FUNC_WEB_ADD_MAIN_BUTTON)
    // Perfect for charts, custom HTML, scripts that should load once
    char buf[128];
    int i;

    // ── Chart container and Google Charts loader ──
    webSend("<div id=\"tc_chart\" style=\"width:100%;height:400px;\"></div>");
    webSend("<script src=\"https://www.gstatic.com/charts/loader.js\"></script>");
    webSend("<script>");
    webSend("google.charts.load('current',{packages:['corechart']});");
    webSend("google.charts.setOnLoadCallback(function(){");
    webSend("var d=new google.visualization.DataTable();");
    webSend("d.addColumn('number','X');");
    webSend("d.addColumn('number','Value');");
    webSend("d.addColumn('number','Avg');");
    webSend("d.addRows([");
    webFlush();

    // ── Output all 1000 data points from ring buffer ──
    int avg = 0;
    i = 0;
    while (i < N) {
        // Read from ring buffer: oldest first
        int idx = pos + i;
        if (idx >= N) idx = idx - N;
        int val = data[idx];

        // Running average (smoothing filter)
        avg = (avg * 9 + val * 10) / 10;
        int smooth = avg / 10;

        // Format: [x, value, avg],
        sprintf(buf, "[%d,%d,%d]", i, val, smooth);
        if (i < N - 1) {
            strcat(buf, ",");
        }
        webSend(buf);

        // Flush every 100 rows to keep buffer manageable
        if (i % 100 == 99) {
            webFlush();
        }
        i++;
    }

    // ── Chart options and render ──
    webSend("]);");
    webSend("var o={title:'TinyC Live Data (1000 pts)',");
    webSend("curveType:'none',legend:{position:'bottom'},");
    webSend("hAxis:{title:'Sample'},vAxis:{title:'Value'},");
    webSend("chartArea:{width:'80%',height:'70%'},");
    webSend("series:{0:{lineWidth:1,color:'#89b4fa'},");
    webSend("1:{lineWidth:2,color:'#f38ba8'}}};");
    webSend("var c=new google.visualization.LineChart(");
    webSend("document.getElementById('tc_chart'));");
    webSend("c.draw(d,o);});");
    webSend("</script>");
    webFlush();
}

void WebCall() {
    // Called periodically for sensor display (FUNC_WEB_SENSOR)
    // Shows a status row that auto-refreshes
    char buf[64];
    sprintf(buf, "{s}TinyC Chart{m}%d samples{e}", counter);
    webSend(buf);
}

void JsonCall() {
    char buf[64];
    sprintf(buf, ",\"TinyC\":{\"Samples\":%d}", counter);
    responseAppend(buf);
}

int main() {
    // Fill array with initial data
    int i = 0;
    while (i < N) {
        data[i] = isin(i) + random(-15, 15);
        i++;
    }
    pos = 0;
    counter = 0;
    printStr("Chart demo active\n");
    printStr("Open Tasmota web page to see the chart\n");
    return 0;
}