Headless Anomaly Detection, Custom UI Live Demo
DatastripesAnomaly is the fastest way to ship anomaly detection, but every piece of it is built on public APIs you can use directly when you want your own UI:
useAnomalyDetection, a React hook (@datastripes/anomaly/react) and a Vue composable (@datastripes/anomaly/vue) that run the engine and hand you reactiveresult,isLoading,error, andalgorithmstate, plus anexecute()function.executeAnomalyDetection(), the plain async function from the root@datastripes/anomalyentry, with no UI framework involved at all.
Live demo: a custom widget on useAnomalyDetection
Everything below (the algorithm picker, the metric tiles, the anomalies table) is ordinary page markup. The only Datastripes code is the useAnomalyDetection call from @datastripes/anomaly/vue.
Changing the algorithm re-runs detection automatically: algorithm is a reactive ref returned by the composable, and the composable re-executes when it changes. Recalculate calls execute() explicitly.
Source (Vue)
vue
<script setup>
import { computed } from 'vue';
import { useAnomalyDetection } from '@datastripes/anomaly/vue';
const { result, isLoading, algorithm, execute } = useAnomalyDetection({
data: saasData,
algorithm: 'ensemble',
columnMapping: { timeKey: 'date', targetKey: 'annual_recurring_revenue' }
});
const anomalies = computed(() => (result.value?.points ?? []).filter((p) => p.isAnomaly));
</script>
<template>
<select v-model="algorithm">
<option value="ensemble">Ensemble (majority vote)</option>
<option value="zscore">Z-score (global)</option>
<option value="iqr">IQR</option>
</select>
<button @click="execute()">Recalculate</button>
<p v-if="isLoading && !result">Scanning for anomalies…</p>
<ul v-else-if="result">
<li>Anomaly rate: {{ result.metrics.anomalyRate.toFixed(2) }}%</li>
<li v-for="p in anomalies" :key="p.index">{{ p.dateStr }}: {{ Math.round(p.value) }} ({{ p.severity }})</li>
</ul>
</template>Source (React)
The React hook has the same shape, with setter functions instead of refs:
tsx
import { useAnomalyDetection } from '@datastripes/anomaly/react';
export function ArrAnomalyWidget({ data }) {
const { result, isLoading, algorithm, setAlgorithm, execute } = useAnomalyDetection({
data,
algorithm: 'ensemble',
columnMapping: { timeKey: 'date', targetKey: 'annual_recurring_revenue' }
});
if (isLoading && !result) return <p>Scanning for anomalies…</p>;
if (!result) return null;
return (
<div>
<select value={algorithm} onChange={(e) => setAlgorithm(e.target.value as any)}>
<option value="ensemble">Ensemble (majority vote)</option>
<option value="zscore">Z-score (global)</option>
<option value="iqr">IQR</option>
</select>
<button onClick={() => execute()}>Recalculate</button>
<p>Anomaly rate: {result.metrics.anomalyRate.toFixed(2)}%</p>
</div>
);
}No UI at all: executeAnomalyDetection()
When you don't need any component state, for example to feed your own chart library or to run detection inside a Web Worker, call the engine directly from the root entry:
ts
import { executeAnomalyDetection } from '@datastripes/anomaly';
const result = await executeAnomalyDetection({
data: myRecords, // records, a CSV string, or a JSON array
algorithm: 'ensemble'
});
const anomalies = result.points.filter((p) => p.isAnomaly);
console.log('Anomalies:', result.metrics.anomalyCount);
console.log('Execution time:', result.metrics.executionTimeMs, 'ms');The Scatter detection demo is built the same way, but calls executeScatterAnomalyDetection() for bivariate (x, y) data instead. See Types for the full AnomalyOutput shape.