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>
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¶
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
Float64Arrayfor efficiency - Same API as Node.js - Consistent interface across platforms
- Small bundle size - Optimized with
wasm-opt