Skip to content

WebAssembly Examples

Complete WebAssembly examples demonstrating fastlowess-wasm for browser-based smoothing.

Batch Smoothing

Process complete datasets in the browser.

<!--
  fastlowess Batch Smoothing Example

  This example demonstrates batch LOWESS smoothing features:
  - Basic smoothing with different parameters
  - Robustness iterations for outlier handling
  - Confidence and prediction intervals
  - Diagnostics and cross-validation

  The batch adapter (smooth function) is the primary interface for
  processing complete datasets that fit in memory.
-->
<!DOCTYPE html>
<html>

<head>
    <title>fastlowess WASM - Batch Smoothing Example</title>
    <style>
        body {
            font-family: monospace;
            max-width: 900px;
            margin: 0 auto;
            padding: 20px;
            background: #1e1e1e;
            color: #d4d4d4;
        }

        h1 {
            color: #569cd6;
        }

        #output {
            white-space: pre-wrap;
            font-family: 'Consolas', 'Monaco', monospace;
        }

        .log-section {
            margin-bottom: 20px;
            border-left: 2px solid #569cd6;
            padding-left: 10px;
        }

        button {
            cursor: pointer;
            padding: 8px 16px;
            font-size: 14px;
            background: #0e639c;
            color: white;
            border: none;
            border-radius: 4px;
        }

        button:hover {
            background: #1177bb;
        }
    </style>
</head>

<body>
    <h1>fastlowess Batch Smoothing Example</h1>
    <p>Running WASM implementation...</p>
    <div id="output">Initializing...</div>

    <script type="module">
        import init, { smooth } from "../../bindings/wasm/pkg-web/fastlowess_wasm.js";

        // Helper to log to screen
        function log(msg) {
            const out = document.getElementById('output');
            out.innerText += msg + "\n";
            console.log(msg);
        }

        function clearLog() {
            document.getElementById('output').innerText = "";
        }

        // 1. Generate Data
        function generateSampleData(nPoints = 1000) {
            const x = new Float64Array(nPoints);
            const y = new Float64Array(nPoints);

            for (let i = 0; i < nPoints; i++) {
                x[i] = (i / (nPoints - 1)) * 50; // range 0 to 50
                // Trend + Seasonality
                const yTrue = 0.5 * x[i] + 5 * Math.sin(x[i] * 0.5);
                // Gaussian noise (approx)
                let u1 = Math.random();
                let u2 = Math.random();
                let z = Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2);
                y[i] = yTrue + z * 1.5;
            }

            // Add significant outliers (10% of data)
            const nOutliers = Math.round(nPoints * 0.1);
            for (let k = 0; k < nOutliers; k++) {
                const idx = Math.floor(Math.random() * nPoints);
                const sign = Math.random() < 0.5 ? -1 : 1;
                y[idx] += (10 + Math.random() * 10) * sign;
            }
            return { x, y };
        }

        async function runDemo() {
            try {
                clearLog();
                log("=== fastlowess Batch Smoothing Example ===\n");

                await init();

                // 1. Generate Data
                const { x, y } = generateSampleData(1000);
                log(`1. Generated ${x.length} data points with outliers.`);

                // 2. Basic Smoothing (Default parameters)
                log("\n2. Running basic smoothing...");
                const startBasic = performance.now();
                const resBasic = smooth(x, y, { iterations: 0, fraction: 0.05 });
                log(`   - Completed in ${(performance.now() - startBasic).toFixed(2)}ms`);

                // 3. Robust Smoothing (IRLS)
                log("\n3. Running robust smoothing (3 iterations)...");
                const resRobust = smooth(x, y, {
                    fraction: 0.05,
                    iterations: 3,
                    robustnessMethod: "bisquare",
                    returnRobustnessWeights: true
                });
                const weights = resRobust.robustnessWeights;
                let outlierCount = 0;
                if (weights) {
                    for (let i = 0; i < weights.length; i++) {
                        if (weights[i] < 0.1) outlierCount++;
                    }
                }
                log(`   - Identified ~${outlierCount} outliers (weight < 0.1)`);

                // 4. Uncertainty Quantification
                log("\n4. Computing confidence and prediction intervals...");
                const resIntervals = smooth(x, y, {
                    fraction: 0.05,
                    confidenceIntervals: 0.95,
                    predictionIntervals: 0.95,
                    returnDiagnostics: true
                });

                if (resIntervals.diagnostics) {
                    const d = resIntervals.diagnostics;
                    log(`   - Fit Statistics:`);
                    log(`     R²:   ${d.rSquared.toFixed(4)}`);
                    log(`     RMSE: ${d.rmse.toFixed(4)}`);
                    log(`     MAE:  ${d.mae.toFixed(4)}`);
                }

                // 5. Cross-Validation for optimal fraction
                log("\n5. Running cross-validation to find optimal fraction...");
                const cvFractions = [0.05, 0.1, 0.2, 0.4];
                const resCV = smooth(x, y, {
                    cvFractions,
                    cvMethod: "kfold",
                    cvK: 5
                });

                log(`   - Optimal fraction selected: ${resCV.fractionUsed}`);
                if (resCV.cvScores) {
                    log(`   - CV Scores: [${Array.from(resCV.cvScores).map(n => n.toFixed(4)).join(', ')}]`);
                }

                // 6. Boundary Policy Comparison
                log("\n6. Demonstrating boundary policy effects on linear data...");
                const xl = new Float64Array(50);
                const yl = new Float64Array(50);
                for (let i = 0; i < 50; i++) {
                    xl[i] = (i / 49) * 10;
                    yl[i] = 2 * xl[i] + 1;
                }

                const rExt = smooth(xl, yl, { fraction: 0.6, boundaryPolicy: "extend" });
                const rRef = smooth(xl, yl, { fraction: 0.6, boundaryPolicy: "reflect" });
                const rZr = smooth(xl, yl, { fraction: 0.6, boundaryPolicy: "zero" });

                log(`   - Extend (Default): first=${rExt.y[0].toFixed(2)}, last=${rExt.y[49].toFixed(2)}`);
                log(`   - Reflect:          first=${rRef.y[0].toFixed(2)}, last=${rRef.y[49].toFixed(2)}`);
                log(`   - Zero:             first=${rZr.y[0].toFixed(2)}, last=${rZr.y[49].toFixed(2)}`);

                log("\n=== Batch Smoothing Example Complete ===");

            } catch (e) {
                log(`\nError: ${e}`);
                console.error(e);
            }
        }

        // Start execution
        runDemo();
    </script>
</body>

</html>

Download batch_smoothing.html


Streaming Smoothing

Process large datasets in memory-efficient chunks in the browser.

<!DOCTYPE html>
<html>

<head>
    <title>fastlowess WASM - Streaming Smoothing</title>
    <style>
        body {
            font-family: sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }

        .controls {
            margin: 20px 0;
            padding: 10px;
            background: #f0f0f0;
            border-radius: 4px;
        }

        #output {
            white-space: pre-wrap;
            background: #fafafa;
            border: 1px solid #ddd;
            padding: 10px;
        }

        button {
            cursor: pointer;
            padding: 5px 10px;
        }
    </style>
</head>

<body>
    <h1>Streaming Smoothing Example</h1>
    <p>Using <code>StreamingLowessWasm</code> to process large data in chunks in the browser.</p>

    <div class="controls">
        <button id="runBtn">Start Streaming Process</button>
    </div>

    <h3>Output Logs:</h3>
    <div id="output">Ready.</div>

    <script type="module">
        import init, { StreamingLowessWasm } from "../../bindings/wasm/pkg-web/fastlowess_wasm.js";

        async function run() {
            try {
                await init();
                log("WASM Initialized.");
                document.getElementById('runBtn').addEventListener('click', startStreaming);
            } catch (e) {
                log("Error: " + e);
            }
        }

        async function startStreaming() {
            try {
                log("\nStarting streaming process...");

                // Initialize Streamer
                const streamer = new StreamingLowessWasm(
                    { fraction: 0.1 },
                    { chunkSize: 1000, overlap: 100 }
                );

                // Simulate processing 10 chunks
                const chunkSize = 1000;
                let totalPoints = 0;

                log("Processing chunks...");

                const start = performance.now();

                for (let i = 0; i < 10; i++) {
                    // Generate random chunk
                    const x = new Float64Array(chunkSize);
                    const y = new Float64Array(chunkSize);
                    for (let j = 0; j < chunkSize; j++) {
                        const globalIdx = i * chunkSize + j;
                        x[j] = globalIdx;
                        y[j] = Math.log(globalIdx + 1) + Math.random();
                    }

                    // Process
                    const res = streamer.processChunk(x, y);
                    if (res && res.y) {
                        totalPoints += res.y.length;
                    }

                    // Small delay to let UI update (simulate async stream)
                    if (i % 2 === 0) await new Promise(r => setTimeout(r, 10));
                }

                const finalRes = streamer.finalize();
                if (finalRes && finalRes.y) totalPoints += finalRes.y.length;

                const end = performance.now();

                log(`Streaming complete.`);
                log(`Total output points: ${totalPoints}`);
                log(`Time taken: ${(end - start).toFixed(2)}ms`);

            } catch (e) {
                log("Error: " + e);
                console.error(e);
            }
        }

        function log(msg) {
            const out = document.getElementById('output');
            out.innerText += msg + "\n";
            console.log(msg);
        }

        run();
    </script>
</body>

</html>

Download streaming_smoothing.html


Online Smoothing

Real-time smoothing with sliding window for browser applications.

<!DOCTYPE html>
<html>

<head>
    <title>fastlowess WASM - Online Smoothing</title>
    <style>
        body {
            font-family: sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }

        .controls {
            margin: 20px 0;
            padding: 10px;
            background: #f0f0f0;
            border-radius: 4px;
        }

        #output {
            white-space: pre-wrap;
            background: #fafafa;
            border: 1px solid #ddd;
            padding: 10px;
            height: 300px;
            overflow-y: scroll;
        }

        button {
            cursor: pointer;
            padding: 5px 10px;
        }

        .metric {
            font-weight: bold;
            color: #0066cc;
        }
    </style>
</head>

<body>
    <h1>Online Smoothing Example</h1>
    <p>Using <code>OnlineLowessWasm</code> for real-time data smoothing.</p>

    <div class="controls">
        <button id="startBtn">Start Simulation</button>
        <button id="stopBtn" disabled>Stop</button>
        <span id="status">Idle</span>
    </div>

    <h3>Live Logs:</h3>
    <div id="output">Waiting to start...</div>

    <script type="module">
        import init, { OnlineLowessWasm } from "../../bindings/wasm/pkg-web/fastlowess_wasm.js";

        let running = false;
        let onlineModel = null;
        let t = 0;

        async function run() {
            try {
                await init();
                log("WASM Initialized.");

                document.getElementById('startBtn').addEventListener('click', startSim);
                document.getElementById('stopBtn').addEventListener('click', stopSim);
            } catch (e) {
                log("Error: " + e);
            }
        }

        function startSim() {
            running = true;
            document.getElementById('startBtn').disabled = true;
            document.getElementById('stopBtn').disabled = false;
            document.getElementById('status').innerText = "Running...";

            // Initialize new model
            onlineModel = new OnlineLowessWasm(
                { fraction: 0.2 },
                { windowCapacity: 50, updateMode: "incremental" }
            );
            t = 0;

            log("\n=== Starting Real-time Simulation ===");
            requestAnimationFrame(step);
        }

        function stopSim() {
            running = false;
            document.getElementById('startBtn').disabled = false;
            document.getElementById('stopBtn').disabled = true;
            document.getElementById('status').innerText = "Stopped";
            log("=== Simulation Stopped ===");
        }

        function step() {
            if (!running) return;

            // Generate one point
            const noise = (Math.random() - 0.5) * 2.0;
            const signal = 10 * Math.sin(t * 0.1);
            const y = signal + noise;

            // Update model
            const smoothed = onlineModel.update(t, y);

            // Log output
            if (smoothed !== undefined && smoothed !== null) {
                log(`t=${t}, raw=${y.toFixed(2)}, smoothed=${smoothed.toFixed(2)}`);
            } else {
                log(`t=${t}, raw=${y.toFixed(2)}, smoothed=(insufficient data)`);
            }

            t++;

            // Slow down loop for visibility
            setTimeout(() => {
                // Auto-stop after 200 points
                if (t >= 200) {
                    stopSim();
                    log("=== Auto-stop limit reached (200 points) ===");
                } else if (running) {
                    requestAnimationFrame(step);
                }
            }, 100);
        }

        function log(msg) {
            const out = document.getElementById('output');
            out.innerText = msg + "\n" + out.innerText;
            if (out.innerText.length > 5000) out.innerText = out.innerText.substring(0, 5000); // Limit buffer
        }

        run();
    </script>
</body>

</html>

Download online_smoothing.html


Installation

NPM

npm install fastlowess-wasm

CDN

<script type="module">
  import init, { smooth } from 'https://unpkg.com/fastlowess-wasm@latest';

  await init();
  // Ready to use
</script>

Quick Start

Browser (ES Modules)

import init, { smooth, smoothStreaming, smoothOnline } from 'fastlowess-wasm';

async function main() {
    // Initialize WASM module
    await init();

    // Generate sample data
    const x = Float64Array.from({ length: 100 }, (_, i) => i * 0.1);
    const y = Float64Array.from(x, xi => Math.sin(xi) + Math.random() * 0.2);

    // Basic smoothing
    const result = smooth(x, y, { fraction: 0.3 });
    console.log('Smoothed values:', result.y);

    // With options
    const resultWithOptions = smooth(x, y, {
        fraction: 0.3,
        iterations: 3,
        confidenceIntervals: 0.95,
        returnDiagnostics: true
    });

    console.log('R²:', resultWithOptions.diagnostics.rSquared);
}

main();

Node.js

const { smooth } = require('fastlowess-wasm');

// Same API as browser
const result = smooth(x, y, { fraction: 0.3 });

Features

The WebAssembly bindings provide:

  • Zero dependencies - Pure WASM, no runtime requirements
  • TypedArray support - Works with Float64Array for efficiency
  • Same API as Node.js - Consistent interface across platforms
  • Small bundle size - Optimized with wasm-opt