File size: 3,984 Bytes
79fb5cc
 
 
ef886da
 
79fb5cc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ef886da
79fb5cc
ef886da
79fb5cc
 
 
ef886da
79fb5cc
 
 
ef886da
79fb5cc
 
 
 
 
 
 
 
 
 
 
 
ef886da
 
79fb5cc
ef886da
79fb5cc
 
ef886da
 
 
 
 
 
 
79fb5cc
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import { useState } from 'react';
import axios from 'axios';
import { Braces } from 'lucide-react';
import { motion } from 'framer-motion';
import { PageHeader, ResultBox, ErrorBox, SubmitButton, UploadZone, SectionLabel } from '../components/UI';

export default function DbscanLab() {
    const [file, setFile] = useState(null);
    const [eps, setEps] = useState(0.5);
    const [minSamples, setMinSamples] = useState(5);
    const [result, setResult] = useState(null);
    const [error, setError] = useState('');
    const [loading, setLoading] = useState(false);

    const handleSubmit = async (e) => {
        e.preventDefault();
        if (!file) return setError('Please upload a file');
        setLoading(true); setError(''); setResult(null);
        const fd = new FormData();
        fd.append('file', file);
        fd.append('eps', eps);
        fd.append('min_samples', minSamples);
        try {
            const res = await axios.post('/api/dbscan', fd);
            setResult(res.data);
        } catch (err) {
            setError(err.response?.data?.error || 'Request failed');
        } finally { setLoading(false); }
    };

    return (
        <div className="max-w-3xl mx-auto">
            <PageHeader icon={Braces} title="DBSCAN Lab" subtitle="Density-based spatial clustering to identify complex patterns and outliers." />

            <form onSubmit={handleSubmit} className="glass-card p-6 sm:p-8 space-y-6">
                <UploadZone accept=".csv,.xlsx" onChange={(e) => setFile(e.target.files[0])} label="Upload Data Structure" sublabel=".CSV or .XLSX datasets" />
                <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
                    <div>
                        <label className="block text-sm font-semibold text-slate-300 mb-2.5">Epsilon (eps)</label>
                        <input type="number" value={eps} onChange={(e) => setEps(e.target.value)} step="0.01" min="0.01" className="quantum-input" />
                    </div>
                    <div>
                        <label className="block text-sm font-semibold text-slate-300 mb-2.5">Min Samples</label>
                        <input type="number" value={minSamples} onChange={(e) => setMinSamples(e.target.value)} min="1" className="quantum-input" />
                    </div>
                </div>
                <SubmitButton loading={loading}>
                    <Braces size={18} /> Run DBSCAN
                </SubmitButton>
            </form>

            <ErrorBox message={error} />

            {result && (
                <ResultBox>
                    <SectionLabel>DBSCAN Visualization</SectionLabel>
                    <motion.div initial={{ opacity: 0, scale: 0.95 }} animate={{ opacity: 1, scale: 1 }} className="bg-white p-4 rounded-2xl">
                        <img src={`data:image/png;base64,${result.plot}`} alt="DBSCAN Plot" className="w-full rounded-xl" />
                    </motion.div>
                    {result.cluster_info && (
                        <div className="mt-5 grid grid-cols-2 sm:grid-cols-3 gap-3">
                            {Object.entries(result.cluster_info).map(([k, v], i) => (
                                <motion.div key={k} initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: i * 0.1 }}
                                    className="stat-glow p-4 rounded-2xl bg-white/[0.02] border border-white/[0.05]">
                                    <span className="text-[10px] font-bold text-purple-400 uppercase tracking-widest">{k === '-1' ? 'Noise Points' : `Cluster ${k}`}</span>
                                    <p className="text-xl font-black mt-1">{v}</p>
                                    <span className="text-[11px] text-slate-500">Entities</span>
                                </motion.div>
                            ))}
                        </div>
                    )}
                </ResultBox>
            )}
        </div>
    );
}