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, { Lowess } 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 = new Lowess({ iterations: 0, fraction: 0.05 }).fit(x, y);
                log(`   - Completed in ${(performance.now() - startBasic).toFixed(2)}ms`);

                // 3. Robust Smoothing (IRLS)
                log("\n3. Running robust smoothing (3 iterations)...");
                const resRobust = new Lowess({
                    fraction: 0.05,
                    iterations: 3,
                    robustness_method: "bisquare",
                    return_robustness_weights: true
                }).fit(x, y);
                const weights = resRobust.robustness_weights;
                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 = new Lowess({
                    fraction: 0.05,
                    confidence_intervals: 0.95,
                    prediction_intervals: 0.95,
                    return_diagnostics: true
                }).fit(x, y);

                if (resIntervals.diagnostics) {
                    const d = resIntervals.diagnostics;
                    log(`   - Fit Statistics:`);
                    log(`     R²:   ${d.r_squared.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 = new Lowess({
                    cv_fractions: cvFractions,
                    cv_method: "kfold",
                    cv_k: 5
                }).fit(x, y);

                log(`   - Optimal fraction selected: ${resCV.fraction_used}`);
                if (resCV.cv_scores) {
                    log(`   - CV Scores: [${Array.from(resCV.cv_scores).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 = new Lowess({ fraction: 0.6, boundary_policy: "extend" }).fit(xl, yl);
                const rRef = new Lowess({ fraction: 0.6, boundary_policy: "reflect" }).fit(xl, yl);
                const rZr = new Lowess({ fraction: 0.6, boundary_policy: "zero" }).fit(xl, yl);

                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)}`);

                // Custom Weights
                log("\nCustom Weights Examples:");

                const cwX = new Float64Array(10).map((_, i) => i);
                const cwY = new Float64Array(10).map((_, i) => i * 2);
                cwY[5] = 100.0;
                const cwNoW = new Lowess({ fraction: 0.5, iterations: 0 }).fit(cwX, cwY);
                const cwWeights = new Float64Array(10).fill(1.0); cwWeights[5] = 0.0;
                const cwZeroW = new Lowess({ fraction: 0.5, iterations: 0 }).fit(cwX, cwY, cwWeights);
                const errNoW = Math.abs(cwNoW.y[5] - 10.0);
                const errZeroW = Math.abs(cwZeroW.y[5] - 10.0);
                log(` - Zero weight at outlier: error ${errNoW.toFixed(2)} -> ${errZeroW.toFixed(2)}`);

                const spX = new Float64Array(15).map((_, i) => i);
                const spY = new Float64Array(15).fill(0.0); spY[7] = 10.0;
                const spEq = new Lowess({ fraction: 0.6, iterations: 0 }).fit(spX, spY);
                const spW = new Float64Array(15).fill(1.0); spW[7] = 100.0;
                const spHi = new Lowess({ fraction: 0.6, iterations: 0 }).fit(spX, spY, spW);
                log(` - High weight at spike: fit ${spEq.y[7].toFixed(4)} -> ${spHi.y[7].toFixed(4)}`);

                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, { StreamingLowess } 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 StreamingLowess(
                    { fraction: 0.1 },
                    { chunk_size: 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.process_chunk(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, { OnlineLowess } 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 OnlineLowess(
                { fraction: 0.2 },
                { window_capacity: 50, update_mode: "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 result = onlineModel.add_point(t, y);

            // Log output
            if (result !== undefined && result !== null) {
                log(`t=${t}, raw=${y.toFixed(2)}, smoothed=${result.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, { Lowess } from 'https://unpkg.com/fastlowess-wasm@latest';

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

Quick Start

Node.js

const { Lowess } = require('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) + ((xi * 7 % 1) - 0.5) * 0.2);

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

    // With options
    const modelWithOptions = new Lowess({
        fraction: 0.3,
        iterations: 3,
        confidence_intervals: 0.95,
        return_diagnostics: true
    });
    const resultWithOptions = modelWithOptions.fit(x, y);

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

main();

Browser (ES Modules)

import init, { Lowess } from 'fastlowess-wasm';

await init();

const x = Float64Array.from({ length: 100 }, (_, i) => i * 0.1);
const y = Float64Array.from(x, xi => Math.sin(xi) + ((xi * 7 % 1) - 0.5) * 0.2);

const model = new Lowess({ fraction: 0.3 });
const result = model.fit(x, y);
console.log('Smoothed values:', result.y);

Node.js (CommonJS)

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

const n = 100;
const x = Float64Array.from({ length: n }, (_, i) => i * 2 * Math.PI / (n - 1));
const y = Float64Array.from(x, (xi, i) => Math.sin(xi) + (((i * 7 + 3) % 17) / 17 - 0.5) * 0.6);

const model = new Lowess({ fraction: 0.3 });
const result = model.fit(x, y);

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