eyad222 commited on
Commit
6d50ddf
·
verified ·
1 Parent(s): e03fd9e

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +1883 -18
index.html CHANGED
@@ -1,19 +1,1884 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>DSMOTE Interactive Visualization</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Rajdhani:wght@400;500;600;700&family=JetBrains+Mono:wght@300;400;500&family=Syne:wght@400;700;800&display=swap" rel="stylesheet">
8
+ <style>
9
+ :root {
10
+ --bg: #060b14;
11
+ --bg2: #0d1627;
12
+ --bg3: #132038;
13
+ --cyan: #00e5ff;
14
+ --cyan2: #00bcd4;
15
+ --orange: #ff6b35;
16
+ --green: #00e676;
17
+ --red: #ff1744;
18
+ --purple: #7c4dff;
19
+ --yellow: #ffd740;
20
+ --text: #e0f7fa;
21
+ --muted: #546e7a;
22
+ --border: rgba(0,229,255,0.15);
23
+ }
24
+
25
+ * { box-sizing: border-box; margin: 0; padding: 0; }
26
+
27
+ body {
28
+ background: var(--bg);
29
+ color: var(--text);
30
+ font-family: 'Rajdhani', sans-serif;
31
+ min-height: 100vh;
32
+ overflow-x: hidden;
33
+ }
34
+
35
+ /* Grid noise overlay */
36
+ body::before {
37
+ content: '';
38
+ position: fixed;
39
+ inset: 0;
40
+ background-image:
41
+ linear-gradient(rgba(0,229,255,0.03) 1px, transparent 1px),
42
+ linear-gradient(90deg, rgba(0,229,255,0.03) 1px, transparent 1px);
43
+ background-size: 40px 40px;
44
+ pointer-events: none;
45
+ z-index: 0;
46
+ }
47
+
48
+ .container { max-width: 1100px; margin: 0 auto; padding: 0 24px; position: relative; z-index: 1; }
49
+
50
+ /* HEADER */
51
+ header {
52
+ padding: 36px 0 24px;
53
+ border-bottom: 1px solid var(--border);
54
+ margin-bottom: 32px;
55
+ }
56
+ .header-tag {
57
+ font-family: 'JetBrains Mono', monospace;
58
+ font-size: 11px;
59
+ color: var(--cyan);
60
+ letter-spacing: 3px;
61
+ text-transform: uppercase;
62
+ margin-bottom: 10px;
63
+ opacity: 0.7;
64
+ }
65
+ header h1 {
66
+ font-family: 'Syne', sans-serif;
67
+ font-size: 2.4rem;
68
+ font-weight: 800;
69
+ letter-spacing: -1px;
70
+ background: linear-gradient(135deg, var(--cyan) 0%, #ffffff 60%);
71
+ -webkit-background-clip: text;
72
+ -webkit-text-fill-color: transparent;
73
+ background-clip: text;
74
+ }
75
+ header p {
76
+ font-family: 'JetBrains Mono', monospace;
77
+ font-size: 12px;
78
+ color: var(--muted);
79
+ margin-top: 8px;
80
+ letter-spacing: 1px;
81
+ }
82
+
83
+ /* TABS */
84
+ .tabs {
85
+ display: flex;
86
+ gap: 4px;
87
+ margin-bottom: 28px;
88
+ flex-wrap: wrap;
89
+ }
90
+ .tab {
91
+ font-family: 'JetBrains Mono', monospace;
92
+ font-size: 11px;
93
+ padding: 8px 16px;
94
+ border: 1px solid var(--border);
95
+ background: transparent;
96
+ color: var(--muted);
97
+ cursor: pointer;
98
+ letter-spacing: 1px;
99
+ text-transform: uppercase;
100
+ transition: all 0.2s;
101
+ position: relative;
102
+ }
103
+ .tab:hover { color: var(--cyan); border-color: var(--cyan); }
104
+ .tab.active {
105
+ background: var(--cyan);
106
+ color: var(--bg);
107
+ border-color: var(--cyan);
108
+ font-weight: 500;
109
+ }
110
+ .tab-num {
111
+ display: inline-block;
112
+ margin-right: 6px;
113
+ opacity: 0.5;
114
+ }
115
+
116
+ /* PANELS */
117
+ .panel { display: none; animation: fadeIn 0.3s ease; }
118
+ .panel.active { display: block; }
119
+ @keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
120
+
121
+ /* CARD */
122
+ .card {
123
+ background: var(--bg2);
124
+ border: 1px solid var(--border);
125
+ border-radius: 2px;
126
+ padding: 28px;
127
+ margin-bottom: 20px;
128
+ }
129
+ .card-title {
130
+ font-family: 'Syne', sans-serif;
131
+ font-size: 1.3rem;
132
+ font-weight: 700;
133
+ color: var(--cyan);
134
+ margin-bottom: 6px;
135
+ }
136
+ .card-sub {
137
+ font-family: 'JetBrains Mono', monospace;
138
+ font-size: 11px;
139
+ color: var(--muted);
140
+ margin-bottom: 20px;
141
+ letter-spacing: 1px;
142
+ }
143
+
144
+ /* CANVAS */
145
+ canvas {
146
+ display: block;
147
+ border: 1px solid var(--border);
148
+ border-radius: 2px;
149
+ background: var(--bg);
150
+ }
151
+
152
+ .plot-row {
153
+ display: grid;
154
+ grid-template-columns: 1fr 1fr;
155
+ gap: 20px;
156
+ align-items: start;
157
+ }
158
+
159
+ .plot-label {
160
+ font-family: 'JetBrains Mono', monospace;
161
+ font-size: 11px;
162
+ text-align: center;
163
+ margin-top: 8px;
164
+ letter-spacing: 1px;
165
+ text-transform: uppercase;
166
+ }
167
+
168
+ /* BTN */
169
+ .btn {
170
+ font-family: 'JetBrains Mono', monospace;
171
+ font-size: 11px;
172
+ padding: 10px 20px;
173
+ border: 1px solid var(--cyan);
174
+ background: transparent;
175
+ color: var(--cyan);
176
+ cursor: pointer;
177
+ letter-spacing: 2px;
178
+ text-transform: uppercase;
179
+ transition: all 0.2s;
180
+ margin-top: 16px;
181
+ margin-right: 8px;
182
+ }
183
+ .btn:hover { background: var(--cyan); color: var(--bg); }
184
+ .btn-orange { border-color: var(--orange); color: var(--orange); }
185
+ .btn-orange:hover { background: var(--orange); color: var(--bg); }
186
+
187
+ /* LEGEND */
188
+ .legend {
189
+ display: flex;
190
+ gap: 20px;
191
+ flex-wrap: wrap;
192
+ margin-top: 14px;
193
+ }
194
+ .legend-item {
195
+ display: flex;
196
+ align-items: center;
197
+ gap: 8px;
198
+ font-family: 'JetBrains Mono', monospace;
199
+ font-size: 11px;
200
+ color: var(--muted);
201
+ letter-spacing: 0.5px;
202
+ }
203
+ .legend-dot {
204
+ width: 10px; height: 10px; border-radius: 50%;
205
+ flex-shrink: 0;
206
+ }
207
+ .legend-line {
208
+ width: 20px; height: 2px;
209
+ flex-shrink: 0;
210
+ }
211
+
212
+ /* PIPELINE */
213
+ .pipeline {
214
+ display: flex;
215
+ align-items: center;
216
+ gap: 0;
217
+ margin: 24px 0;
218
+ overflow-x: auto;
219
+ padding-bottom: 10px;
220
+ }
221
+ .pipe-step {
222
+ display: flex;
223
+ flex-direction: column;
224
+ align-items: center;
225
+ gap: 10px;
226
+ flex-shrink: 0;
227
+ opacity: 0;
228
+ transform: translateY(20px);
229
+ transition: opacity 0.5s ease, transform 0.5s ease;
230
+ }
231
+ .pipe-step.visible { opacity: 1; transform: none; }
232
+ .pipe-icon {
233
+ width: 70px; height: 70px;
234
+ border-radius: 2px;
235
+ display: flex;
236
+ align-items: center;
237
+ justify-content: center;
238
+ font-size: 26px;
239
+ border: 2px solid;
240
+ position: relative;
241
+ cursor: pointer;
242
+ }
243
+ .pipe-icon::after {
244
+ content: '';
245
+ position: absolute;
246
+ inset: -4px;
247
+ border-radius: 4px;
248
+ opacity: 0;
249
+ transition: opacity 0.3s;
250
+ }
251
+ .pipe-icon:hover::after { opacity: 1; }
252
+ .pipe-icon.active-step { box-shadow: 0 0 20px currentColor; }
253
+
254
+ .pipe-label {
255
+ font-family: 'JetBrains Mono', monospace;
256
+ font-size: 9px;
257
+ letter-spacing: 1px;
258
+ text-transform: uppercase;
259
+ text-align: center;
260
+ max-width: 80px;
261
+ }
262
+ .pipe-arrow {
263
+ font-size: 20px;
264
+ color: var(--muted);
265
+ padding: 0 6px;
266
+ flex-shrink: 0;
267
+ margin-top: -20px;
268
+ }
269
+
270
+ .step-detail {
271
+ background: var(--bg3);
272
+ border-left: 3px solid var(--cyan);
273
+ padding: 16px 20px;
274
+ margin-top: 8px;
275
+ border-radius: 0 2px 2px 0;
276
+ display: none;
277
+ }
278
+ .step-detail.visible { display: block; animation: fadeIn 0.3s; }
279
+ .step-detail h4 {
280
+ font-family: 'Syne', sans-serif;
281
+ font-size: 1.1rem;
282
+ margin-bottom: 6px;
283
+ }
284
+ .step-detail p {
285
+ font-family: 'JetBrains Mono', monospace;
286
+ font-size: 11px;
287
+ color: var(--muted);
288
+ line-height: 1.7;
289
+ }
290
+ .formula {
291
+ font-family: 'JetBrains Mono', monospace;
292
+ font-size: 12px;
293
+ background: var(--bg);
294
+ padding: 8px 14px;
295
+ margin-top: 10px;
296
+ border: 1px solid var(--border);
297
+ color: var(--cyan);
298
+ display: inline-block;
299
+ }
300
+
301
+ /* BEFORE/AFTER */
302
+ .ba-row {
303
+ display: grid;
304
+ grid-template-columns: 1fr 1fr;
305
+ gap: 24px;
306
+ }
307
+ .ba-card {
308
+ background: var(--bg3);
309
+ border: 1px solid var(--border);
310
+ padding: 20px;
311
+ border-radius: 2px;
312
+ }
313
+ .ba-card h4 {
314
+ font-family: 'Syne', sans-serif;
315
+ font-size: 1rem;
316
+ margin-bottom: 16px;
317
+ text-align: center;
318
+ }
319
+ .bar-row {
320
+ display: flex;
321
+ align-items: center;
322
+ gap: 10px;
323
+ margin-bottom: 9px;
324
+ }
325
+ .bar-name {
326
+ font-family: 'JetBrains Mono', monospace;
327
+ font-size: 9px;
328
+ width: 70px;
329
+ text-align: right;
330
+ color: var(--muted);
331
+ flex-shrink: 0;
332
+ letter-spacing: 0.5px;
333
+ overflow: hidden;
334
+ text-overflow: ellipsis;
335
+ white-space: nowrap;
336
+ }
337
+ .bar-track {
338
+ flex: 1;
339
+ height: 14px;
340
+ background: var(--bg);
341
+ border-radius: 1px;
342
+ overflow: hidden;
343
+ border: 1px solid rgba(255,255,255,0.05);
344
+ }
345
+ .bar-fill {
346
+ height: 100%;
347
+ border-radius: 1px;
348
+ transition: width 1s cubic-bezier(0.4,0,0.2,1);
349
+ position: relative;
350
+ }
351
+ .bar-val {
352
+ font-family: 'JetBrains Mono', monospace;
353
+ font-size: 9px;
354
+ width: 50px;
355
+ color: var(--muted);
356
+ flex-shrink: 0;
357
+ }
358
+
359
+ /* INSIGHT BOX */
360
+ .insight {
361
+ background: rgba(0,229,255,0.05);
362
+ border: 1px solid rgba(0,229,255,0.2);
363
+ padding: 14px 18px;
364
+ margin-top: 16px;
365
+ border-radius: 1px;
366
+ }
367
+ .insight-label {
368
+ font-family: 'JetBrains Mono', monospace;
369
+ font-size: 9px;
370
+ color: var(--cyan);
371
+ letter-spacing: 2px;
372
+ text-transform: uppercase;
373
+ margin-bottom: 6px;
374
+ }
375
+ .insight p {
376
+ font-family: 'Rajdhani', sans-serif;
377
+ font-size: 1rem;
378
+ font-weight: 600;
379
+ color: var(--text);
380
+ line-height: 1.5;
381
+ }
382
+
383
+ @media (max-width: 640px) {
384
+ .plot-row, .ba-row { grid-template-columns: 1fr; }
385
+ header h1 { font-size: 1.6rem; }
386
+ .pipeline { gap: 0; }
387
+ }
388
+ </style>
389
+ </head>
390
+ <body>
391
+ <div class="container">
392
+ <header>
393
+ <div class="header-tag">// Conference Visualization — DSMOTE</div>
394
+ <h1>Dynamic SMOTE: Interactive Visual Explainer</h1>
395
+ <p>A Hybrid Oversampling Framework for NIDS Class Imbalance</p>
396
+ </header>
397
+
398
+ <div class="tabs">
399
+ <button class="tab active" onclick="switchTab(0)"><span class="tab-num">01</span>SMOTE Weakness</button>
400
+ <button class="tab" onclick="switchTab(1)"><span class="tab-num">02</span>DSMOTE Pipeline</button>
401
+ <button class="tab" onclick="switchTab(2)"><span class="tab-num">03</span>Clustering</button>
402
+ <button class="tab" onclick="switchTab(3)"><span class="tab-num">04</span>Density Constraint</button>
403
+ <button class="tab" onclick="switchTab(4)"><span class="tab-num">05</span>Before vs After</button>
404
+ <button class="tab" onclick="switchTab(5)"><span class="tab-num">06</span>UNSW Results</button>
405
+ <button class="tab" onclick="switchTab(6)"><span class="tab-num">07</span>KDD Results</button>
406
+ </div>
407
+
408
+ <!-- ======================== PANEL 1: SMOTE WEAKNESS ======================== -->
409
+ <div class="panel active" id="panel-0">
410
+ <div class="card">
411
+ <div class="card-title">SMOTE is Blind</div>
412
+ <div class="card-sub">// Standard SMOTE interpolates between any two minority samples — crossing cluster boundaries</div>
413
+ <div class="plot-row">
414
+ <div>
415
+ <canvas id="smote-canvas" width="440" height="360"></canvas>
416
+ <div class="plot-label" style="color:var(--orange)">⚠ Standard SMOTE — Noise Generation</div>
417
+ </div>
418
+ <div>
419
+ <canvas id="dsmote-canvas" width="440" height="360"></canvas>
420
+ <div class="plot-label" style="color:var(--green)">✓ DSMOTE — Cluster-Aware Sampling</div>
421
+ </div>
422
+ </div>
423
+ <div style="margin-top:12px;">
424
+ <button class="btn" onclick="animateSMOTE()">▶ Animate SMOTE</button>
425
+ <button class="btn" onclick="animateDSMOTE()">▶ Animate DSMOTE</button>
426
+ <button class="btn btn-orange" onclick="resetSmote()">↺ Reset</button>
427
+ </div>
428
+ <div class="legend">
429
+ <div class="legend-item"><div class="legend-dot" style="background:var(--cyan)"></div>Cluster A — Minority</div>
430
+ <div class="legend-item"><div class="legend-dot" style="background:var(--purple)"></div>Cluster B — Minority</div>
431
+ <div class="legend-item"><div class="legend-dot" style="background:var(--orange);opacity:0.6"></div>SMOTE — Noise points (wrong region)</div>
432
+ <div class="legend-item"><div class="legend-dot" style="background:var(--green)"></div>DSMOTE — Safe synthetic points</div>
433
+ </div>
434
+ <div class="insight">
435
+ <div class="insight-label">// Key Message</div>
436
+ <p>SMOTE selects two minority samples at random — regardless of which cluster they belong to — and interpolates between them. This creates points in empty space between clusters, introducing noise and confusion for the classifier.</p>
437
+ </div>
438
+ </div>
439
+ </div>
440
+
441
+ <!-- ======================== PANEL 2: PIPELINE ======================== -->
442
+ <div class="panel" id="panel-1">
443
+ <div class="card">
444
+ <div class="card-title">DSMOTE Algorithm Pipeline</div>
445
+ <div class="card-sub">// Click each step to explore the details</div>
446
+ <div class="pipeline" id="pipeline">
447
+ <div class="pipe-step" id="pstep-0">
448
+ <div class="pipe-icon" onclick="showStep(0)"
449
+ style="color:#ff6b35;border-color:#ff6b35;background:rgba(255,107,53,0.08)">✂️</div>
450
+ <div class="pipe-label" style="color:#ff6b35">Majority Reduction</div>
451
+ </div>
452
+ <div class="pipe-arrow">→</div>
453
+ <div class="pipe-step" id="pstep-1">
454
+ <div class="pipe-icon" onclick="showStep(1)"
455
+ style="color:#7c4dff;border-color:#7c4dff;background:rgba(124,77,255,0.08)">📉</div>
456
+ <div class="pipe-label" style="color:#7c4dff">PCA Reduction</div>
457
+ </div>
458
+ <div class="pipe-arrow">→</div>
459
+ <div class="pipe-step" id="pstep-2">
460
+ <div class="pipe-icon" onclick="showStep(2)"
461
+ style="color:#00e5ff;border-color:#00e5ff;background:rgba(0,229,255,0.08)">🔵</div>
462
+ <div class="pipe-label" style="color:#00e5ff">KMeans Clustering</div>
463
+ </div>
464
+ <div class="pipe-arrow">→</div>
465
+ <div class="pipe-step" id="pstep-3">
466
+ <div class="pipe-icon" onclick="showStep(3)"
467
+ style="color:#ffd740;border-color:#ffd740;background:rgba(255,215,64,0.08)">⚡</div>
468
+ <div class="pipe-label" style="color:#ffd740">Smart Sampling</div>
469
+ </div>
470
+ <div class="pipe-arrow">→</div>
471
+ <div class="pipe-step" id="pstep-4">
472
+ <div class="pipe-icon" onclick="showStep(4)"
473
+ style="color:#00e676;border-color:#00e676;background:rgba(0,230,118,0.08)">🎯</div>
474
+ <div class="pipe-label" style="color:#00e676">Density Filter</div>
475
+ </div>
476
+ <div class="pipe-arrow">→</div>
477
+ <div class="pipe-step" id="pstep-5">
478
+ <div class="pipe-icon" onclick="showStep(5)"
479
+ style="color:#ff4081;border-color:#ff4081;background:rgba(255,64,129,0.08)">⚖️</div>
480
+ <div class="pipe-label" style="color:#ff4081">Class Weights</div>
481
+ </div>
482
+ </div>
483
+
484
+ <div id="step-detail" class="step-detail visible">
485
+ <h4 style="color:var(--cyan)">👆 Click a Step Above</h4>
486
+ <p>Each step in DSMOTE solves a specific problem. Click any step icon to learn what it does and why it matters.</p>
487
+ </div>
488
+ </div>
489
+
490
+ <!-- Mini pipeline canvas -->
491
+ <div class="card">
492
+ <div class="card-title">Pipeline Data Flow</div>
493
+ <div class="card-sub">// How raw data transforms through each stage</div>
494
+ <canvas id="pipeline-canvas" width="1040" height="200" style="width:100%"></canvas>
495
+ </div>
496
+ </div>
497
+
498
+ <!-- ======================== PANEL 3: CLUSTERING ======================== -->
499
+ <div class="panel" id="panel-2">
500
+ <div class="card">
501
+ <div class="card-title">Step 3 — KMeans Clustering of Minority Class</div>
502
+ <div class="card-sub">// DSMOTE understands the internal structure of minority classes</div>
503
+ <canvas id="cluster-canvas" width="700" height="420" style="width:100%"></canvas>
504
+ <div style="margin-top:12px">
505
+ <button class="btn" onclick="animateClusters()">▶ Show Clustering</button>
506
+ <button class="btn btn-orange" onclick="resetClusters()">↺ Reset</button>
507
+ </div>
508
+ <div class="legend" style="margin-top:14px">
509
+ <div class="legend-item"><div class="legend-dot" style="background:var(--cyan)"></div>Cluster 1</div>
510
+ <div class="legend-item"><div class="legend-dot" style="background:var(--orange)"></div>Cluster 2</div>
511
+ <div class="legend-item"><div class="legend-dot" style="background:var(--purple)"></div>Cluster 3</div>
512
+ <div class="legend-item"><div class="legend-dot" style="background:var(--yellow)"></div>Cluster 4</div>
513
+ <div class="legend-item"><div class="legend-dot" style="background:var(--muted)"></div>Uncolored (before clustering)</div>
514
+ </div>
515
+ <div class="insight">
516
+ <div class="insight-label">// Key Message</div>
517
+ <p>Instead of treating all minority samples as one blob, DSMOTE uses KMeans to discover sub-groups. Synthetic samples are then generated <em>within each cluster</em> — not across them.</p>
518
+ </div>
519
+ </div>
520
+ </div>
521
+
522
+ <!-- ======================== PANEL 4: DENSITY CONSTRAINT ======================== -->
523
+ <div class="panel" id="panel-3">
524
+ <div class="card">
525
+ <div class="card-title">Step 5 — Density Constraint Filtering</div>
526
+ <div class="card-sub">// Only synthetic points within the mean intra-cluster distance are accepted</div>
527
+ <canvas id="density-canvas" width="700" height="420" style="width:100%"></canvas>
528
+ <div style="margin-top:12px">
529
+ <button class="btn" onclick="animateDensity()">▶ Generate Samples</button>
530
+ <button class="btn btn-orange" onclick="resetDensity()">↺ Reset</button>
531
+ </div>
532
+ <div class="legend" style="margin-top:14px">
533
+ <div class="legend-item"><div class="legend-dot" style="background:var(--cyan)"></div>Original minority points</div>
534
+ <div class="legend-item"><div class="legend-dot" style="background:var(--green)"></div>✓ Accepted synthetic points</div>
535
+ <div class="legend-item"><div class="legend-dot" style="background:var(--red)"></div>✗ Rejected (outside d_mean)</div>
536
+ <div class="legend-item"><div class="legend-line" style="background:var(--cyan);border: 1px dashed var(--cyan)"></div>d_mean radius boundary</div>
537
+ </div>
538
+ <div class="insight">
539
+ <div class="insight-label">// The Innovation</div>
540
+ <p>A synthetic sample x_new is accepted only if ‖x_new − x_i‖ ≤ d_mean. This density gate keeps new points inside the safe zone — preventing noise, overfitting, and cluster bleeding.</p>
541
+ <div class="formula">if ‖x_new − xᵢ‖₂ ≤ d_mean → ACCEPT ✓ else → REJECT ✗</div>
542
+ </div>
543
+ </div>
544
+ </div>
545
+
546
+ <!-- ======================== PANEL 5: BEFORE / AFTER ======================== -->
547
+ <div class="panel" id="panel-4">
548
+ <div class="card">
549
+ <div class="card-title">Before vs After DSMOTE — KDD Cup 99</div>
550
+ <div class="card-sub">// Class distribution transformation after applying DSMOTE</div>
551
+ <div class="ba-row">
552
+ <div class="ba-card">
553
+ <h4 style="color:var(--orange)">⚠ Before — Severe Imbalance</h4>
554
+ <div id="before-bars"></div>
555
+ </div>
556
+ <div class="ba-card">
557
+ <h4 style="color:var(--green)">✓ After DSMOTE — Balanced</h4>
558
+ <div id="after-bars"></div>
559
+ </div>
560
+ </div>
561
+ <div style="margin-top:16px">
562
+ <button class="btn" onclick="animateBars()">▶ Animate Transformation</button>
563
+ <button class="btn btn-orange" onclick="resetBars()">↺ Reset</button>
564
+ </div>
565
+ <div class="insight">
566
+ <div class="insight-label">// Impact</div>
567
+ <p>Minority classes like <code style="color:var(--cyan)">pod</code> (264 samples) and <code style="color:var(--cyan)">warezclient</code> (1,020 samples) are boosted to ~256K–264K samples, achieving near-parity with the majority class after controlled reduction.</p>
568
+ </div>
569
+ </div>
570
+
571
+ <!-- F1 comparison -->
572
+ <div class="card">
573
+ <div class="card-title">Macro-F1 Score Comparison — UNSW-NF Dataset</div>
574
+ <div class="card-sub">// DSMOTE vs conventional oversampling methods</div>
575
+ <canvas id="f1-canvas" width="1040" height="280" style="width:100%"></canvas>
576
+ <button class="btn" style="margin-top:16px" onclick="animateF1()">▶ Show Results</button>
577
+ <div class="legend" style="margin-top:12px">
578
+ <div class="legend-item"><div class="legend-dot" style="background:var(--muted)"></div>ROS</div>
579
+ <div class="legend-item"><div class="legend-dot" style="background:var(--purple)"></div>SMOTE</div>
580
+ <div class="legend-item"><div class="legend-dot" style="background:var(--orange)"></div>Borderline-SMOTE</div>
581
+ <div class="legend-item"><div class="legend-dot" style="background:var(--yellow)"></div>ADASYN</div>
582
+ <div class="legend-item"><div class="legend-dot" style="background:var(--cyan)"></div>DSMOTE (Proposed)</div>
583
+ </div>
584
+ </div>
585
+ </div>
586
+
587
+ <!-- ======================== PANEL 6: UNSW RESULTS ======================== -->
588
+ <div class="panel" id="panel-5">
589
+
590
+ <!-- Stat strip -->
591
+ <div style="display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:20px">
592
+ <div class="card" style="padding:16px;text-align:center;border-color:rgba(0,230,118,0.3)">
593
+ <div style="font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--muted);letter-spacing:2px;margin-bottom:6px">DSMOTE BEST F1</div>
594
+ <div style="font-family:'Syne',sans-serif;font-size:1.8rem;font-weight:800;color:var(--green)">0.588</div>
595
+ <div style="font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--muted)">RF Model</div>
596
+ </div>
597
+ <div class="card" style="padding:16px;text-align:center;border-color:rgba(255,23,68,0.3)">
598
+ <div style="font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--muted);letter-spacing:2px;margin-bottom:6px">SMOTE BEST F1</div>
599
+ <div style="font-family:'Syne',sans-serif;font-size:1.8rem;font-weight:800;color:var(--red)">0.096</div>
600
+ <div style="font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--muted)">RF / DT Model</div>
601
+ </div>
602
+ <div class="card" style="padding:16px;text-align:center;border-color:rgba(0,229,255,0.3)">
603
+ <div style="font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--muted);letter-spacing:2px;margin-bottom:6px">IMPROVEMENT</div>
604
+ <div style="font-family:'Syne',sans-serif;font-size:1.8rem;font-weight:800;color:var(--cyan)">6.1×</div>
605
+ <div style="font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--muted)">vs SMOTE RF</div>
606
+ </div>
607
+ <div class="card" style="padding:16px;text-align:center;border-color:rgba(255,215,64,0.3)">
608
+ <div style="font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--muted);letter-spacing:2px;margin-bottom:6px">BAL. ACCURACY</div>
609
+ <div style="font-family:'Syne',sans-serif;font-size:1.8rem;font-weight:800;color:var(--yellow)">0.573</div>
610
+ <div style="font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--muted)">DSMOTE RF</div>
611
+ </div>
612
+ </div>
613
+
614
+ <div class="card">
615
+ <div class="card-title">Macro-F1 by Model — All Methods (UNSW-NF)</div>
616
+ <div class="card-sub">// Real experimental results — DSMOTE is the ONLY method that meaningfully works on UNSW</div>
617
+ <div style="margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap" id="unsw-model-btns">
618
+ <button class="btn" style="padding:6px 12px" onclick="filterUnswModel('ALL')">All Models</button>
619
+ <button class="btn" style="padding:6px 12px" onclick="filterUnswModel('DT')">DT</button>
620
+ <button class="btn" style="padding:6px 12px" onclick="filterUnswModel('RF')">RF</button>
621
+ <button class="btn" style="padding:6px 12px" onclick="filterUnswModel('XGBoost')">XGBoost</button>
622
+ <button class="btn" style="padding:6px 12px" onclick="filterUnswModel('ANN')">ANN</button>
623
+ <button class="btn" style="padding:6px 12px" onclick="filterUnswModel('LSTM')">LSTM</button>
624
+ </div>
625
+ <canvas id="unsw-f1-canvas" width="1040" height="320" style="width:100%"></canvas>
626
+ <div class="legend" style="margin-top:12px">
627
+ <div class="legend-item"><div class="legend-dot" style="background:#78909c"></div>RAW</div>
628
+ <div class="legend-item"><div class="legend-dot" style="background:#546e7a"></div>ROS</div>
629
+ <div class="legend-item"><div class="legend-dot" style="background:var(--purple)"></div>SMOTE</div>
630
+ <div class="legend-item"><div class="legend-dot" style="background:var(--orange)"></div>BSMOTE</div>
631
+ <div class="legend-item"><div class="legend-dot" style="background:var(--yellow)"></div>ADASYN</div>
632
+ <div class="legend-item"><div class="legend-dot" style="background:var(--cyan)"></div>★ DSMOTE</div>
633
+ </div>
634
+ </div>
635
+
636
+ <div class="card">
637
+ <div class="card-title">Confusion Matrix Collapse — SMOTE vs DSMOTE (UNSW-NF, RF)</div>
638
+ <div class="card-sub">// SMOTE predicts EVERYTHING as "Benign" — DSMOTE correctly distributes predictions</div>
639
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:24px">
640
+ <div>
641
+ <canvas id="cm-smote-canvas" width="480" height="400"></canvas>
642
+ <div class="plot-label" style="color:var(--red);margin-top:8px">⚠ SMOTE — Total Collapse (F1: 0.096)</div>
643
+ </div>
644
+ <div>
645
+ <canvas id="cm-dsmote-canvas" width="480" height="400"></canvas>
646
+ <div class="plot-label" style="color:var(--green);margin-top:8px">✓ DSMOTE — Proper Distribution (F1: 0.588)</div>
647
+ </div>
648
+ </div>
649
+ <div class="insight" style="margin-top:16px">
650
+ <div class="insight-label">// The Collapse Problem</div>
651
+ <p>Under SMOTE, RF learns to predict every single sample as "Benign" (91.9% accuracy by doing nothing). DSMOTE forces the model to actually learn minority attack classes — Exploits, Fuzzers, Backdoor, Shellcode — that matter for security.</p>
652
+ </div>
653
+ <button class="btn" style="margin-top:12px" onclick="drawConfusionMatrices()">▶ Draw Matrices</button>
654
+ </div>
655
+
656
+ <div class="card">
657
+ <div class="card-title">Balanced Accuracy — DSMOTE vs All Methods (UNSW-NF)</div>
658
+ <div class="card-sub">// Balanced accuracy weights each class equally — exposes true minority class performance</div>
659
+ <canvas id="unsw-ba-canvas" width="1040" height="260" style="width:100%"></canvas>
660
+ <button class="btn" style="margin-top:12px" onclick="drawUnswBA()">▶ Show Balanced Accuracy</button>
661
+ </div>
662
+ </div>
663
+
664
+ <!-- ======================== PANEL 7: KDD RESULTS ======================== -->
665
+ <div class="panel" id="panel-6">
666
+
667
+ <!-- Stat strip -->
668
+ <div style="display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:20px">
669
+ <div class="card" style="padding:16px;text-align:center;border-color:rgba(0,230,118,0.3)">
670
+ <div style="font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--muted);letter-spacing:2px;margin-bottom:6px">DSMOTE RF F1</div>
671
+ <div style="font-family:'Syne',sans-serif;font-size:1.8rem;font-weight:800;color:var(--green)">0.9954</div>
672
+ <div style="font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--muted)">Matches RAW best</div>
673
+ </div>
674
+ <div class="card" style="padding:16px;text-align:center;border-color:rgba(0,229,255,0.3)">
675
+ <div style="font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--muted);letter-spacing:2px;margin-bottom:6px">DSMOTE ANN F1</div>
676
+ <div style="font-family:'Syne',sans-serif;font-size:1.8rem;font-weight:800;color:var(--cyan)">0.9285</div>
677
+ <div style="font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--muted)">+0.013 vs SMOTE ANN</div>
678
+ </div>
679
+ <div class="card" style="padding:16px;text-align:center;border-color:rgba(255,215,64,0.3)">
680
+ <div style="font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--muted);letter-spacing:2px;margin-bottom:6px">BAL. ACCURACY</div>
681
+ <div style="font-family:'Syne',sans-serif;font-size:1.8rem;font-weight:800;color:var(--yellow)">0.9940</div>
682
+ <div style="font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--muted)">DSMOTE RF</div>
683
+ </div>
684
+ <div class="card" style="padding:16px;text-align:center;border-color:rgba(124,77,255,0.3)">
685
+ <div style="font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--muted);letter-spacing:2px;margin-bottom:6px">G-MEAN</div>
686
+ <div style="font-family:'Syne',sans-serif;font-size:1.8rem;font-weight:800;color:var(--purple)">0.9939</div>
687
+ <div style="font-family:'JetBrains Mono',monospace;font-size:9px;color:var(--muted)">DSMOTE RF</div>
688
+ </div>
689
+ </div>
690
+
691
+ <div class="card">
692
+ <div class="card-title">Macro-F1 by Model — All Methods (KDD Cup 99)</div>
693
+ <div class="card-sub">// KDD is harder to fail on — DSMOTE matches top performance while improving minority class stability</div>
694
+ <canvas id="kdd-f1-canvas" width="1040" height="320" style="width:100%"></canvas>
695
+ <div class="legend" style="margin-top:12px">
696
+ <div class="legend-item"><div class="legend-dot" style="background:#78909c"></div>RAW</div>
697
+ <div class="legend-item"><div class="legend-dot" style="background:#546e7a"></div>ROS</div>
698
+ <div class="legend-item"><div class="legend-dot" style="background:var(--purple)"></div>SMOTE</div>
699
+ <div class="legend-item"><div class="legend-dot" style="background:var(--orange)"></div>BSMOTE</div>
700
+ <div class="legend-item"><div class="legend-dot" style="background:var(--cyan)"></div>★ DSMOTE</div>
701
+ </div>
702
+ </div>
703
+
704
+ <div class="card">
705
+ <div class="card-title">Multi-Metric Radar — Best Models Comparison (KDD)</div>
706
+ <div class="card-sub">// DSMOTE (RF) vs RAW (RF): Accuracy · Balanced Acc · F1 · G-Mean · Precision · Recall</div>
707
+ <canvas id="kdd-radar-canvas" width="1040" height="360" style="width:100%"></canvas>
708
+ <button class="btn" style="margin-top:12px" onclick="drawRadar()">▶ Draw Radar</button>
709
+ <div class="legend" style="margin-top:12px">
710
+ <div class="legend-item"><div class="legend-dot" style="background:#78909c"></div>RAW RF</div>
711
+ <div class="legend-item"><div class="legend-dot" style="background:var(--cyan)"></div>DSMOTE RF</div>
712
+ <div class="legend-item"><div class="legend-dot" style="background:var(--purple)"></div>SMOTE RF</div>
713
+ </div>
714
+ </div>
715
+
716
+ <div class="card">
717
+ <div class="card-title">KDD — G-Mean Comparison Across Models</div>
718
+ <div class="card-sub">// G-Mean = geometric mean of per-class recalls — punishes models that ignore minority classes</div>
719
+ <canvas id="kdd-gmean-canvas" width="1040" height="260" style="width:100%"></canvas>
720
+ <button class="btn" style="margin-top:12px" onclick="drawKddGmean()">▶ Show G-Mean</button>
721
+ </div>
722
+ </div>
723
+
724
+ </div><!-- /container -->
725
+
726
+ <script>
727
+ // ============================================================
728
+ // UTILITIES
729
+ // ============================================================
730
+ function seededRng(seed) {
731
+ let s = seed;
732
+ return function() {
733
+ s = (s * 1664525 + 1013904223) & 0xffffffff;
734
+ return (s >>> 0) / 0xffffffff;
735
+ };
736
+ }
737
+
738
+ function gaussianPair(rng, mx, my, sx, sy) {
739
+ // Box-Muller
740
+ const u1 = rng(), u2 = rng();
741
+ const z0 = Math.sqrt(-2 * Math.log(u1 + 0.0001)) * Math.cos(2 * Math.PI * u2);
742
+ const z1 = Math.sqrt(-2 * Math.log(u1 + 0.0001)) * Math.sin(2 * Math.PI * u2);
743
+ return [mx + z0 * sx, my + z1 * sy];
744
+ }
745
+
746
+ function dataToCanvas(x, y, xmin, xmax, ymin, ymax, W, H, pad) {
747
+ const cx = pad + (x - xmin) / (xmax - xmin) * (W - 2 * pad);
748
+ const cy = H - pad - (y - ymin) / (ymax - ymin) * (H - 2 * pad);
749
+ return [cx, cy];
750
+ }
751
+
752
+ const PAD = 40;
753
+
754
+ function drawAxes(ctx, W, H) {
755
+ ctx.strokeStyle = 'rgba(0,229,255,0.12)';
756
+ ctx.lineWidth = 1;
757
+ // grid lines
758
+ for (let i = 0; i <= 5; i++) {
759
+ const x = PAD + i * (W - 2 * PAD) / 5;
760
+ const y = PAD + i * (H - 2 * PAD) / 5;
761
+ ctx.beginPath(); ctx.moveTo(x, PAD); ctx.lineTo(x, H - PAD); ctx.stroke();
762
+ ctx.beginPath(); ctx.moveTo(PAD, y); ctx.lineTo(W - PAD, y); ctx.stroke();
763
+ }
764
+ // axes
765
+ ctx.strokeStyle = 'rgba(0,229,255,0.25)';
766
+ ctx.beginPath(); ctx.moveTo(PAD, H - PAD); ctx.lineTo(W - PAD, H - PAD); ctx.stroke();
767
+ ctx.beginPath(); ctx.moveTo(PAD, PAD); ctx.lineTo(PAD, H - PAD); ctx.stroke();
768
+ // labels
769
+ ctx.fillStyle = 'rgba(84,110,122,0.8)';
770
+ ctx.font = '10px JetBrains Mono, monospace';
771
+ ctx.fillText('PC1 →', W - PAD - 2, H - PAD + 16);
772
+ ctx.save(); ctx.translate(PAD - 16, PAD + 10); ctx.rotate(-Math.PI / 2);
773
+ ctx.fillText('PC2 →', 0, 0); ctx.restore();
774
+ }
775
+
776
+ function dot(ctx, cx, cy, r, color, alpha = 1) {
777
+ ctx.globalAlpha = alpha;
778
+ ctx.fillStyle = color;
779
+ ctx.beginPath();
780
+ ctx.arc(cx, cy, r, 0, Math.PI * 2);
781
+ ctx.fill();
782
+ ctx.globalAlpha = 1;
783
+ }
784
+
785
+ // ============================================================
786
+ // TAB SWITCHING — handled at bottom of script
787
+ // ============================================================
788
+
789
+ // ============================================================
790
+ // PANEL 1: SMOTE vs DSMOTE
791
+ // ============================================================
792
+ const rng1 = seededRng(42);
793
+ const XMIN = -5, XMAX = 5, YMIN = -5, YMAX = 5;
794
+
795
+ function genMinorityClusters() {
796
+ const r = seededRng(7);
797
+ const pts = [];
798
+ // Cluster A: top-left
799
+ for (let i = 0; i < 30; i++) {
800
+ const [x, y] = gaussianPair(r, -2.8, 2.2, 0.6, 0.6);
801
+ pts.push({ x, y, cluster: 0 });
802
+ }
803
+ // Cluster B: bottom-right
804
+ for (let i = 0; i < 25; i++) {
805
+ const [x, y] = gaussianPair(r, 2.8, -2.2, 0.55, 0.55);
806
+ pts.push({ x, y, cluster: 1 });
807
+ }
808
+ // Cluster C: middle-right (smaller)
809
+ for (let i = 0; i < 18; i++) {
810
+ const [x, y] = gaussianPair(r, 1.2, 1.8, 0.4, 0.4);
811
+ pts.push({ x, y, cluster: 2 });
812
+ }
813
+ return pts;
814
+ }
815
+
816
+ function genMajority() {
817
+ const r = seededRng(11);
818
+ const pts = [];
819
+ for (let i = 0; i < 200; i++) {
820
+ const x = (r() - 0.5) * 8;
821
+ const y = (r() - 0.5) * 8;
822
+ pts.push({ x, y });
823
+ }
824
+ return pts;
825
+ }
826
+
827
+ const minPts = genMinorityClusters();
828
+ const majPts = genMajority();
829
+ let smoteAnimPts = [], dsmoteAnimPts = [];
830
+ let smoteAnim = null, dsmoteAnim = null;
831
+
832
+ function drawScatterBase(canvasId, synthPts, synthColor, label) {
833
+ const canvas = document.getElementById(canvasId);
834
+ const ctx = canvas.getContext('2d');
835
+ const W = canvas.width, H = canvas.height;
836
+ ctx.clearRect(0, 0, W, H);
837
+ drawAxes(ctx, W, H);
838
+
839
+ // majority
840
+ majPts.forEach(p => {
841
+ const [cx, cy] = dataToCanvas(p.x, p.y, XMIN, XMAX, YMIN, YMAX, W, H, PAD);
842
+ dot(ctx, cx, cy, 3, '#546e7a', 0.3);
843
+ });
844
+
845
+ // minority originals
846
+ const clColors = ['#00e5ff', '#7c4dff', '#ffd740'];
847
+ minPts.forEach(p => {
848
+ const [cx, cy] = dataToCanvas(p.x, p.y, XMIN, XMAX, YMIN, YMAX, W, H, PAD);
849
+ dot(ctx, cx, cy, 5, clColors[p.cluster]);
850
+ });
851
+
852
+ // synthetic
853
+ synthPts.forEach(p => {
854
+ const [cx, cy] = dataToCanvas(p.x, p.y, XMIN, XMAX, YMIN, YMAX, W, H, PAD);
855
+ dot(ctx, cx, cy, 4, synthColor, 0.75);
856
+ });
857
+ }
858
+
859
+ function generateSMOTEPoint() {
860
+ const r = seededRng(smoteAnimPts.length * 13 + 7);
861
+ // pick ANY two minority pts (ignoring cluster)
862
+ const i = Math.floor(r() * minPts.length);
863
+ const j = Math.floor(r() * minPts.length);
864
+ const lam = r();
865
+ return {
866
+ x: minPts[i].x + lam * (minPts[j].x - minPts[i].x),
867
+ y: minPts[i].y + lam * (minPts[j].y - minPts[i].y)
868
+ };
869
+ }
870
+
871
+ function generateDSMOTEPoint(idx) {
872
+ const r = seededRng(idx * 17 + 3);
873
+ // Pick a cluster
874
+ const clusterPts = [
875
+ minPts.filter(p => p.cluster === 0),
876
+ minPts.filter(p => p.cluster === 1),
877
+ minPts.filter(p => p.cluster === 2)
878
+ ];
879
+ const cl = Math.floor(r() * 3);
880
+ const pts = clusterPts[cl];
881
+ const i = Math.floor(r() * pts.length);
882
+ const j = Math.floor(r() * pts.length);
883
+ const lam = r();
884
+ const nx = pts[i].x + lam * (pts[j].x - pts[i].x);
885
+ const ny = pts[i].y + lam * (pts[j].y - pts[i].y);
886
+ // density check: within cluster
887
+ const cx_mean = pts.reduce((a, p) => a + p.x, 0) / pts.length;
888
+ const cy_mean = pts.reduce((a, p) => a + p.y, 0) / pts.length;
889
+ const d = Math.sqrt((nx - pts[i].x) ** 2 + (ny - pts[i].y) ** 2);
890
+ // compute mean intra dist (simplified)
891
+ const dmean = 0.9;
892
+ if (d <= dmean) return { x: nx, y: ny };
893
+ return null;
894
+ }
895
+
896
+ function animateSMOTE() {
897
+ if (smoteAnim) clearInterval(smoteAnim);
898
+ smoteAnimPts = [];
899
+ let count = 0;
900
+ smoteAnim = setInterval(() => {
901
+ if (count >= 80) { clearInterval(smoteAnim); return; }
902
+ smoteAnimPts.push(generateSMOTEPoint());
903
+ drawScatterBase('smote-canvas', smoteAnimPts, '#ff6b35', 'SMOTE');
904
+ count++;
905
+ }, 40);
906
+ }
907
+
908
+ function animateDSMOTE() {
909
+ if (dsmoteAnim) clearInterval(dsmoteAnim);
910
+ dsmoteAnimPts = [];
911
+ let count = 0;
912
+ dsmoteAnim = setInterval(() => {
913
+ if (count >= 80) { clearInterval(dsmoteAnim); return; }
914
+ let p = null, tries = 0;
915
+ while (!p && tries < 20) { p = generateDSMOTEPoint(count * 7 + tries); tries++; }
916
+ if (p) dsmoteAnimPts.push(p);
917
+ drawScatterBase('dsmote-canvas', dsmoteAnimPts, '#00e676', 'DSMOTE');
918
+ count++;
919
+ }, 40);
920
+ }
921
+
922
+ function resetSmote() {
923
+ if (smoteAnim) clearInterval(smoteAnim);
924
+ if (dsmoteAnim) clearInterval(dsmoteAnim);
925
+ smoteAnimPts = []; dsmoteAnimPts = [];
926
+ drawScatterBase('smote-canvas', [], '#ff6b35');
927
+ drawScatterBase('dsmote-canvas', [], '#00e676');
928
+ }
929
+
930
+ // ============================================================
931
+ // PANEL 2: PIPELINE
932
+ // ============================================================
933
+ const stepDetails = [
934
+ {
935
+ color: '#ff6b35', title: 'Step 1 — Majority Class Reduction',
936
+ desc: 'The dominant class (e.g., smurf with 2.8M samples) is randomly down-sampled by keeping only a fraction p of its data. This reduces bias in training without losing minority class information.',
937
+ formula: 'X_maj_reduced = X_maj[0 : ρ × N_maj] (ρ = 0.5)'
938
+ },
939
+ {
940
+ color: '#7c4dff', title: 'Step 2 — PCA Dimensionality Reduction',
941
+ desc: 'Principal Component Analysis reduces the feature space while retaining 95% of the variance. This makes KMeans clustering and KNN more effective and computationally efficient.',
942
+ formula: 'X_pca = PCA(X, variance_ratio = 0.95)'
943
+ },
944
+ {
945
+ color: '#00e5ff', title: 'Step 3 — KMeans Clustering',
946
+ desc: 'For each minority class, KMeans groups the samples into k sub-clusters. This lets DSMOTE understand the internal structure of each attack type rather than treating them as a uniform blob.',
947
+ formula: 'cluster_labels = KMeans(X_Cm, k=3)'
948
+ },
949
+ {
950
+ color: '#ffd740', title: 'Step 4 — KNN + Interpolation',
951
+ desc: 'Within each cluster, K-nearest neighbors are computed. Synthetic samples are generated by interpolating between a selected point and one of its neighbors — just like SMOTE, but cluster-confined.',
952
+ formula: 'x_new = xᵢ + λ × (x_j − xᵢ), λ ~ U(0,1)'
953
+ },
954
+ {
955
+ color: '#00e676', title: 'Step 5 — Density Constraint Filter',
956
+ desc: 'A synthetic sample is accepted only if it falls within the mean intra-cluster distance. This prevents points from being generated in sparse, noisy regions outside the true data distribution.',
957
+ formula: '‖x_new − xᵢ‖₂ ≤ d_mean → ACCEPT'
958
+ },
959
+ {
960
+ color: '#ff4081', title: 'Step 6 — Class Weight Computation',
961
+ desc: 'After oversampling, class weights are computed inversely proportional to class frequency. These weights guide the loss function during training to further emphasize minority classes.',
962
+ formula: 'w_c = N_total / (K × N_c)'
963
+ }
964
+ ];
965
+
966
+ function showStep(i) {
967
+ const detail = document.getElementById('step-detail');
968
+ const s = stepDetails[i];
969
+ detail.className = 'step-detail visible';
970
+ detail.style.borderLeftColor = s.color;
971
+ detail.innerHTML = `
972
+ <h4 style="color:${s.color}">${s.title}</h4>
973
+ <p>${s.desc}</p>
974
+ <div class="formula" style="color:${s.color}">${s.formula}</div>
975
+ `;
976
+ document.querySelectorAll('.pipe-icon').forEach((el, j) => {
977
+ el.classList.toggle('active-step', i === j);
978
+ });
979
+ }
980
+
981
+ function initPipeline() {
982
+ document.querySelectorAll('.pipe-step').forEach((el, i) => {
983
+ setTimeout(() => el.classList.add('visible'), i * 120);
984
+ });
985
+ drawPipelineCanvas();
986
+ }
987
+
988
+ function drawPipelineCanvas() {
989
+ const canvas = document.getElementById('pipeline-canvas');
990
+ if (!canvas) return;
991
+ const ctx = canvas.getContext('2d');
992
+ const W = canvas.width, H = canvas.height;
993
+ ctx.clearRect(0, 0, W, H);
994
+
995
+ const steps = [
996
+ { label: 'RAW DATA', color: '#546e7a', val: '4.9M rows\n41 features' },
997
+ { label: 'MAJORITY ↓', color: '#ff6b35', val: '2.45M rows\n41 features' },
998
+ { label: 'PCA', color: '#7c4dff', val: '2.45M rows\n~18 PCs' },
999
+ { label: 'CLUSTERING', color: '#00e5ff', val: 'k clusters\nper class' },
1000
+ { label: 'SYNTHETIC', color: '#ffd740', val: '+1.8M\nnew samples' },
1001
+ { label: 'BALANCED', color: '#00e676', val: '~270K\nper class' },
1002
+ ];
1003
+
1004
+ const bw = 120, bh = 80, gap = (W - steps.length * bw) / (steps.length + 1);
1005
+ steps.forEach((s, i) => {
1006
+ const x = gap + i * (bw + gap);
1007
+ const y = (H - bh) / 2;
1008
+ // box
1009
+ ctx.strokeStyle = s.color;
1010
+ ctx.lineWidth = 1.5;
1011
+ ctx.strokeRect(x, y, bw, bh);
1012
+ ctx.fillStyle = s.color + '15';
1013
+ ctx.fillRect(x, y, bw, bh);
1014
+ // label
1015
+ ctx.fillStyle = s.color;
1016
+ ctx.font = 'bold 11px JetBrains Mono, monospace';
1017
+ ctx.textAlign = 'center';
1018
+ ctx.fillText(s.label, x + bw / 2, y + 22);
1019
+ // value
1020
+ ctx.fillStyle = 'rgba(224,247,250,0.55)';
1021
+ ctx.font = '10px JetBrains Mono, monospace';
1022
+ const lines = s.val.split('\n');
1023
+ lines.forEach((l, li) => ctx.fillText(l, x + bw / 2, y + 42 + li * 15));
1024
+ // arrow
1025
+ if (i < steps.length - 1) {
1026
+ const ax = x + bw + 4, ay = H / 2;
1027
+ ctx.strokeStyle = 'rgba(84,110,122,0.6)';
1028
+ ctx.lineWidth = 1;
1029
+ ctx.beginPath(); ctx.moveTo(ax, ay); ctx.lineTo(ax + gap - 8, ay); ctx.stroke();
1030
+ ctx.fillStyle = 'rgba(84,110,122,0.6)';
1031
+ ctx.beginPath();
1032
+ ctx.moveTo(ax + gap - 8, ay - 5);
1033
+ ctx.lineTo(ax + gap - 1, ay);
1034
+ ctx.lineTo(ax + gap - 8, ay + 5);
1035
+ ctx.fill();
1036
+ }
1037
+ });
1038
+ ctx.textAlign = 'left';
1039
+ }
1040
+
1041
+ // ============================================================
1042
+ // PANEL 3: CLUSTERING
1043
+ // ============================================================
1044
+ let clusterAnimDone = false;
1045
+ const clRng = seededRng(55);
1046
+
1047
+ function genClusterPts() {
1048
+ const centers = [[-2, 2.5], [2.5, 0.5], [-0.5, -2.5], [3.5, -2.5]];
1049
+ const colors = ['#00e5ff', '#ff6b35', '#7c4dff', '#ffd740'];
1050
+ const pts = [];
1051
+ centers.forEach((c, ci) => {
1052
+ const n = 22 + Math.floor(clRng() * 10);
1053
+ for (let i = 0; i < n; i++) {
1054
+ const [x, y] = gaussianPair(clRng, c[0], c[1], 0.55, 0.55);
1055
+ pts.push({ x, y, cluster: ci, color: colors[ci] });
1056
+ }
1057
+ });
1058
+ return pts;
1059
+ }
1060
+
1061
+ const clusterPts = genClusterPts();
1062
+ let clustersRevealed = false;
1063
+
1064
+ function drawClusters(revealed) {
1065
+ const canvas = document.getElementById('cluster-canvas');
1066
+ if (!canvas) return;
1067
+ const ctx = canvas.getContext('2d');
1068
+ const W = canvas.width, H = canvas.height;
1069
+ ctx.clearRect(0, 0, W, H);
1070
+ drawAxes(ctx, W, H);
1071
+
1072
+ clusterPts.forEach(p => {
1073
+ const [cx, cy] = dataToCanvas(p.x, p.y, -5, 5, -5, 5, W, H, PAD);
1074
+ const col = revealed ? p.color : '#546e7a';
1075
+ dot(ctx, cx, cy, 5.5, col, revealed ? 0.85 : 0.5);
1076
+ });
1077
+
1078
+ if (revealed) {
1079
+ // draw centroid markers
1080
+ const centers = [[-2, 2.5], [2.5, 0.5], [-0.5, -2.5], [3.5, -2.5]];
1081
+ const colors = ['#00e5ff', '#ff6b35', '#7c4dff', '#ffd740'];
1082
+ centers.forEach((c, ci) => {
1083
+ const [cx, cy] = dataToCanvas(c[0], c[1], -5, 5, -5, 5, W, H, PAD);
1084
+ ctx.strokeStyle = colors[ci];
1085
+ ctx.lineWidth = 2;
1086
+ ctx.beginPath();
1087
+ ctx.moveTo(cx - 8, cy); ctx.lineTo(cx + 8, cy);
1088
+ ctx.moveTo(cx, cy - 8); ctx.lineTo(cx, cy + 8);
1089
+ ctx.stroke();
1090
+ ctx.font = 'bold 10px JetBrains Mono, monospace';
1091
+ ctx.fillStyle = colors[ci];
1092
+ ctx.fillText(`C${ci + 1}`, cx + 10, cy - 6);
1093
+ });
1094
+
1095
+ // label
1096
+ ctx.font = 'bold 13px Rajdhani, sans-serif';
1097
+ ctx.fillStyle = 'rgba(0,229,255,0.7)';
1098
+ ctx.fillText('✓ KMeans reveals sub-structure', PAD + 5, PAD + 18);
1099
+ } else {
1100
+ ctx.font = 'bold 13px Rajdhani, sans-serif';
1101
+ ctx.fillStyle = 'rgba(84,110,122,0.7)';
1102
+ ctx.fillText('Minority samples (unclustered)', PAD + 5, PAD + 18);
1103
+ }
1104
+ }
1105
+
1106
+ function initClusters() { drawClusters(false); }
1107
+ function animateClusters() {
1108
+ drawClusters(false);
1109
+ setTimeout(() => drawClusters(true), 600);
1110
+ }
1111
+ function resetClusters() { drawClusters(false); }
1112
+
1113
+ // ============================================================
1114
+ // PANEL 4: DENSITY CONSTRAINT
1115
+ // ============================================================
1116
+ let densityPts = [], densityAnim = null;
1117
+ const dRng = seededRng(88);
1118
+
1119
+ function genDensitySourcePts() {
1120
+ const pts = [];
1121
+ for (let i = 0; i < 25; i++) {
1122
+ const [x, y] = gaussianPair(dRng, 0, 0, 0.9, 0.9);
1123
+ pts.push({ x, y });
1124
+ }
1125
+ return pts;
1126
+ }
1127
+ const densitySrcPts = genDensitySourcePts();
1128
+ const D_MEAN = 1.1; // in data units
1129
+
1130
+ function computeDMean(pts) {
1131
+ let total = 0, count = 0;
1132
+ pts.forEach(p => {
1133
+ pts.forEach(q => {
1134
+ total += Math.sqrt((p.x - q.x) ** 2 + (p.y - q.y) ** 2);
1135
+ count++;
1136
+ });
1137
+ });
1138
+ return total / count;
1139
+ }
1140
+
1141
+ function drawDensity(accepted, rejected) {
1142
+ const canvas = document.getElementById('density-canvas');
1143
+ if (!canvas) return;
1144
+ const ctx = canvas.getContext('2d');
1145
+ const W = canvas.width, H = canvas.height;
1146
+ ctx.clearRect(0, 0, W, H);
1147
+ drawAxes(ctx, W, H);
1148
+
1149
+ // draw d_mean circles for each original point
1150
+ densitySrcPts.forEach(p => {
1151
+ const [cx, cy] = dataToCanvas(p.x, p.y, -5, 5, -5, 5, W, H, PAD);
1152
+ const r_px = D_MEAN / 10 * (W - 2 * PAD);
1153
+ ctx.strokeStyle = 'rgba(0,229,255,0.12)';
1154
+ ctx.lineWidth = 1;
1155
+ ctx.setLineDash([4, 4]);
1156
+ ctx.beginPath(); ctx.arc(cx, cy, r_px, 0, Math.PI * 2); ctx.stroke();
1157
+ ctx.setLineDash([]);
1158
+ });
1159
+
1160
+ // draw one prominent circle
1161
+ const refPt = densitySrcPts[5];
1162
+ const [rcx, rcy] = dataToCanvas(refPt.x, refPt.y, -5, 5, -5, 5, W, H, PAD);
1163
+ const r_px = D_MEAN / 10 * (W - 2 * PAD);
1164
+ ctx.strokeStyle = 'rgba(0,229,255,0.55)';
1165
+ ctx.lineWidth = 2;
1166
+ ctx.setLineDash([6, 4]);
1167
+ ctx.beginPath(); ctx.arc(rcx, rcy, r_px, 0, Math.PI * 2); ctx.stroke();
1168
+ ctx.setLineDash([]);
1169
+
1170
+ // label
1171
+ ctx.fillStyle = 'rgba(0,229,255,0.5)';
1172
+ ctx.font = '10px JetBrains Mono, monospace';
1173
+ ctx.fillText('d_mean', rcx + r_px + 5, rcy - 5);
1174
+
1175
+ // original pts
1176
+ densitySrcPts.forEach(p => {
1177
+ const [cx, cy] = dataToCanvas(p.x, p.y, -5, 5, -5, 5, W, H, PAD);
1178
+ dot(ctx, cx, cy, 5.5, '#00e5ff');
1179
+ });
1180
+
1181
+ // rejected
1182
+ rejected.forEach(p => {
1183
+ const [cx, cy] = dataToCanvas(p.x, p.y, -5, 5, -5, 5, W, H, PAD);
1184
+ dot(ctx, cx, cy, 4.5, '#ff1744', 0.75);
1185
+ // X mark
1186
+ ctx.strokeStyle = '#ff1744';
1187
+ ctx.lineWidth = 1.5;
1188
+ ctx.beginPath(); ctx.moveTo(cx - 4, cy - 4); ctx.lineTo(cx + 4, cy + 4); ctx.stroke();
1189
+ ctx.beginPath(); ctx.moveTo(cx + 4, cy - 4); ctx.lineTo(cx - 4, cy + 4); ctx.stroke();
1190
+ });
1191
+
1192
+ // accepted
1193
+ accepted.forEach(p => {
1194
+ const [cx, cy] = dataToCanvas(p.x, p.y, -5, 5, -5, 5, W, H, PAD);
1195
+ dot(ctx, cx, cy, 4.5, '#00e676', 0.8);
1196
+ });
1197
+
1198
+ // counts
1199
+ ctx.font = 'bold 12px JetBrains Mono, monospace';
1200
+ ctx.fillStyle = '#00e676';
1201
+ ctx.fillText(`✓ Accepted: ${accepted.length}`, PAD + 5, PAD + 16);
1202
+ ctx.fillStyle = '#ff1744';
1203
+ ctx.fillText(`✗ Rejected: ${rejected.length}`, PAD + 5, PAD + 32);
1204
+ }
1205
+
1206
+ function genSyntheticDensityPt(idx) {
1207
+ const r = seededRng(idx * 31 + 17);
1208
+ const i = Math.floor(r() * densitySrcPts.length);
1209
+ const j = Math.floor(r() * densitySrcPts.length);
1210
+ const lam = r();
1211
+ const nx = densitySrcPts[i].x + lam * (densitySrcPts[j].x - densitySrcPts[i].x);
1212
+ const ny = densitySrcPts[i].y + lam * (densitySrcPts[j].y - densitySrcPts[i].y);
1213
+ const dist = Math.sqrt((nx - densitySrcPts[i].x) ** 2 + (ny - densitySrcPts[i].y) ** 2);
1214
+ return { x: nx, y: ny, accepted: dist <= D_MEAN };
1215
+ }
1216
+
1217
+ let densityAccepted = [], densityRejected = [];
1218
+
1219
+ function initDensity() { drawDensity([], []); }
1220
+ function animateDensity() {
1221
+ if (densityAnim) clearInterval(densityAnim);
1222
+ densityAccepted = []; densityRejected = [];
1223
+ let count = 0;
1224
+ densityAnim = setInterval(() => {
1225
+ if (count >= 120) { clearInterval(densityAnim); return; }
1226
+ const p = genSyntheticDensityPt(count);
1227
+ if (p.accepted) densityAccepted.push(p); else densityRejected.push(p);
1228
+ drawDensity(densityAccepted, densityRejected);
1229
+ count++;
1230
+ }, 35);
1231
+ }
1232
+ function resetDensity() {
1233
+ if (densityAnim) clearInterval(densityAnim);
1234
+ densityAccepted = []; densityRejected = [];
1235
+ drawDensity([], []);
1236
+ }
1237
+
1238
+ // ============================================================
1239
+ // PANEL 5: BEFORE / AFTER
1240
+ // ============================================================
1241
+ const classes = [
1242
+ { name: 'smurf', before: 2807886, after: 258783 },
1243
+ { name: 'neptune', before: 1072017, after: 269180 },
1244
+ { name: 'normal', before: 972781, after: 284484 },
1245
+ { name: 'satan', before: 15892, after: 268453 },
1246
+ { name: 'ipsweep', before: 12481, after: 270572 },
1247
+ { name: 'portsweep', before: 10413, after: 268905 },
1248
+ { name: 'nmap', before: 2316, after: 262351 },
1249
+ { name: 'back', before: 2203, after: 256081 },
1250
+ { name: 'warezclient', before: 1020, after: 264107 },
1251
+ { name: 'teardrop', before: 979, after: 252848 },
1252
+ { name: 'pod', before: 264, after: 240579 },
1253
+ ];
1254
+
1255
+ function buildBars(containerId, key, palette) {
1256
+ const el = document.getElementById(containerId);
1257
+ if (!el) return;
1258
+ const maxVal = Math.max(...classes.map(c => c[key]));
1259
+ el.innerHTML = '';
1260
+ classes.forEach((c, i) => {
1261
+ const pct = (c[key] / maxVal * 100).toFixed(1);
1262
+ const color = key === 'before'
1263
+ ? (c.before > 100000 ? '#ff6b35' : c.before > 10000 ? '#ffd740' : '#ff1744')
1264
+ : '#00e676';
1265
+ const row = document.createElement('div');
1266
+ row.className = 'bar-row';
1267
+ row.innerHTML = `
1268
+ <div class="bar-name">${c.name}</div>
1269
+ <div class="bar-track">
1270
+ <div class="bar-fill" id="bar-${key}-${i}" style="width:0%;background:${color}"></div>
1271
+ </div>
1272
+ <div class="bar-val">${(c[key] / 1000).toFixed(0)}K</div>
1273
+ `;
1274
+ el.appendChild(row);
1275
+ });
1276
+ }
1277
+
1278
+ function initBars() {
1279
+ buildBars('before-bars', 'before');
1280
+ buildBars('after-bars', 'after');
1281
+ }
1282
+
1283
+ function animateBars() {
1284
+ const maxB = Math.max(...classes.map(c => c.before));
1285
+ const maxA = Math.max(...classes.map(c => c.after));
1286
+ classes.forEach((c, i) => {
1287
+ setTimeout(() => {
1288
+ const bef = document.getElementById(`bar-before-${i}`);
1289
+ const aft = document.getElementById(`bar-after-${i}`);
1290
+ if (bef) bef.style.width = (c.before / maxB * 100) + '%';
1291
+ if (aft) aft.style.width = (c.after / maxA * 100) + '%';
1292
+ }, i * 60);
1293
+ });
1294
+ }
1295
+
1296
+ function resetBars() {
1297
+ classes.forEach((c, i) => {
1298
+ const bef = document.getElementById(`bar-before-${i}`);
1299
+ const aft = document.getElementById(`bar-after-${i}`);
1300
+ if (bef) bef.style.width = '0%';
1301
+ if (aft) aft.style.width = '0%';
1302
+ });
1303
+ }
1304
+
1305
+ // F1 Chart
1306
+ const f1Data = [
1307
+ { method: 'ROS', color: '#546e7a', min: 0.002, max: 0.096 },
1308
+ { method: 'SMOTE', color: '#7c4dff', min: 0.007, max: 0.096 },
1309
+ { method: 'B-SMOTE', color: '#ff6b35', min: 0.016, max: 0.096 },
1310
+ { method: 'ADASYN', color: '#ffd740', min: 0.017, max: 0.114 },
1311
+ { method: 'DSMOTE', color: '#00e5ff', min: 0.306, max: 0.588 },
1312
+ ];
1313
+
1314
+ let f1Anim = null, f1Progress = 0;
1315
+
1316
+ function initF1() { drawF1(0); }
1317
+ function animateF1() {
1318
+ if (f1Anim) cancelAnimationFrame(f1Anim);
1319
+ f1Progress = 0;
1320
+ function step() {
1321
+ f1Progress = Math.min(f1Progress + 0.025, 1);
1322
+ drawF1(f1Progress);
1323
+ if (f1Progress < 1) f1Anim = requestAnimationFrame(step);
1324
+ }
1325
+ step();
1326
+ }
1327
+
1328
+ function drawF1(progress) {
1329
+ const canvas = document.getElementById('f1-canvas');
1330
+ if (!canvas) return;
1331
+ const ctx = canvas.getContext('2d');
1332
+ const W = canvas.width, H = canvas.height;
1333
+ ctx.clearRect(0, 0, W, H);
1334
+
1335
+ const padL = 60, padR = 30, padT = 30, padB = 50;
1336
+ const chartW = W - padL - padR, chartH = H - padT - padB;
1337
+ const maxF1 = 0.65;
1338
+
1339
+ // grid
1340
+ ctx.strokeStyle = 'rgba(0,229,255,0.08)';
1341
+ ctx.lineWidth = 1;
1342
+ for (let i = 0; i <= 6; i++) {
1343
+ const y = padT + chartH * (1 - i / 6 * maxF1 / maxF1);
1344
+ const val = (i / 6 * maxF1).toFixed(2);
1345
+ ctx.beginPath(); ctx.moveTo(padL, padT + chartH - i * chartH / 6); ctx.lineTo(W - padR, padT + chartH - i * chartH / 6); ctx.stroke();
1346
+ ctx.fillStyle = 'rgba(84,110,122,0.7)';
1347
+ ctx.font = '9px JetBrains Mono, monospace';
1348
+ ctx.textAlign = 'right';
1349
+ ctx.fillText(val, padL - 6, padT + chartH - i * chartH / 6 + 3);
1350
+ }
1351
+
1352
+ // axes
1353
+ ctx.strokeStyle = 'rgba(0,229,255,0.2)';
1354
+ ctx.beginPath(); ctx.moveTo(padL, padT); ctx.lineTo(padL, padT + chartH); ctx.stroke();
1355
+ ctx.beginPath(); ctx.moveTo(padL, padT + chartH); ctx.lineTo(W - padR, padT + chartH); ctx.stroke();
1356
+
1357
+ // axis label
1358
+ ctx.fillStyle = 'rgba(84,110,122,0.7)';
1359
+ ctx.font = '10px JetBrains Mono, monospace';
1360
+ ctx.textAlign = 'left';
1361
+ ctx.fillText('Macro-F1', padL + 5, padT + 12);
1362
+
1363
+ const bw = chartW / f1Data.length;
1364
+
1365
+ f1Data.forEach((d, i) => {
1366
+ const x = padL + i * bw + bw * 0.15;
1367
+ const bWidth = bw * 0.7;
1368
+
1369
+ // range bar (min to max)
1370
+ const yMax = padT + chartH - (d.max * progress / maxF1) * chartH;
1371
+ const yMin = padT + chartH - (d.min * progress / maxF1) * chartH;
1372
+
1373
+ ctx.fillStyle = d.color + '33';
1374
+ ctx.fillRect(x, yMax, bWidth, yMin - yMax);
1375
+ ctx.strokeStyle = d.color;
1376
+ ctx.lineWidth = 1.5;
1377
+ ctx.strokeRect(x, yMax, bWidth, yMin - yMax);
1378
+
1379
+ // max line
1380
+ ctx.strokeStyle = d.color;
1381
+ ctx.lineWidth = 2.5;
1382
+ ctx.beginPath(); ctx.moveTo(x, yMax); ctx.lineTo(x + bWidth, yMax); ctx.stroke();
1383
+
1384
+ // value labels
1385
+ if (progress > 0.5) {
1386
+ ctx.fillStyle = d.color;
1387
+ ctx.font = 'bold 10px JetBrains Mono, monospace';
1388
+ ctx.textAlign = 'center';
1389
+ ctx.fillText((d.max * progress).toFixed(3), x + bWidth / 2, yMax - 5);
1390
+ ctx.fillStyle = 'rgba(84,110,122,0.7)';
1391
+ ctx.font = '9px JetBrains Mono, monospace';
1392
+ ctx.fillText((d.min * progress).toFixed(3), x + bWidth / 2, yMin + 12);
1393
+ }
1394
+
1395
+ // x label
1396
+ ctx.fillStyle = i === 4 ? d.color : 'rgba(84,110,122,0.8)';
1397
+ ctx.font = i === 4 ? 'bold 10px JetBrains Mono, monospace' : '9px JetBrains Mono, monospace';
1398
+ ctx.textAlign = 'center';
1399
+ ctx.fillText(d.method, x + bWidth / 2, padT + chartH + 20);
1400
+ if (i === 4) ctx.fillText('★', x + bWidth / 2, padT + chartH + 33);
1401
+ });
1402
+
1403
+ // DSMOTE highlight
1404
+ if (progress > 0.7) {
1405
+ ctx.strokeStyle = 'rgba(0,229,255,0.3)';
1406
+ ctx.lineWidth = 1;
1407
+ ctx.setLineDash([4, 4]);
1408
+ const dsmX = padL + 4 * bw;
1409
+ ctx.strokeRect(dsmX, padT, bw, chartH);
1410
+ ctx.setLineDash([]);
1411
+ }
1412
+ ctx.textAlign = 'left';
1413
+ }
1414
+
1415
+ // ============================================================
1416
+ // PANEL 6: UNSW RESULTS
1417
+ // ============================================================
1418
+
1419
+ const unswModels = ['DT', 'RF', 'XGBoost', 'ANN', 'CNN', 'LSTM', 'LSTM-CNN'];
1420
+ const unswMethods = [
1421
+ { name: 'RAW', color: '#78909c', f1: [0.485, 0.581, 0.451, 0.299, 0.273, 0.325, 0.395] },
1422
+ { name: 'ROS', color: '#546e7a', f1: [0.096, 0.096, 0.003, 0.096, 0.096, 0.026, 0.016] },
1423
+ { name: 'SMOTE', color: '#7c4dff', f1: [0.094, 0.096, 0.025, 0.096, 0.096, 0.010, 0.008] },
1424
+ { name: 'BSMOTE', color: '#ff6b35', f1: [0.061, 0.096, 0.040, 0.096, 0.096, 0.016, 0.022] },
1425
+ { name: 'ADASYN', color: '#ffd740', f1: [0.062, 0.097, 0.114, 0.096, 0.096, 0.017, 0.018] },
1426
+ { name: 'DSMOTE', color: '#00e5ff', f1: [0.535, 0.588, 0.432, 0.307, 0.274, 0.332, 0.448] },
1427
+ ];
1428
+
1429
+ let unswFilter = 'ALL';
1430
+ let unswAnimProgress = 0, unswAnimFrame = null;
1431
+
1432
+ function filterUnswModel(model) {
1433
+ unswFilter = model;
1434
+ unswAnimProgress = 0;
1435
+ if (unswAnimFrame) cancelAnimationFrame(unswAnimFrame);
1436
+ function step() {
1437
+ unswAnimProgress = Math.min(unswAnimProgress + 0.04, 1);
1438
+ drawUnswF1(unswAnimProgress);
1439
+ if (unswAnimProgress < 1) unswAnimFrame = requestAnimationFrame(step);
1440
+ }
1441
+ step();
1442
+ }
1443
+
1444
+ function drawUnswF1(progress) {
1445
+ const canvas = document.getElementById('unsw-f1-canvas');
1446
+ if (!canvas) return;
1447
+ const ctx = canvas.getContext('2d');
1448
+ const W = canvas.width, H = canvas.height;
1449
+ ctx.clearRect(0, 0, W, H);
1450
+
1451
+ const models = unswFilter === 'ALL' ? unswModels : [unswFilter];
1452
+ const modelIndices = unswFilter === 'ALL'
1453
+ ? unswModels.map((_, i) => i)
1454
+ : [unswModels.indexOf(unswFilter)];
1455
+
1456
+ const padL = 45, padR = 20, padT = 30, padB = 55;
1457
+ const chartW = W - padL - padR, chartH = H - padT - padB;
1458
+ const maxF1 = 0.65;
1459
+
1460
+ // Grid
1461
+ ctx.strokeStyle = 'rgba(0,229,255,0.08)'; ctx.lineWidth = 1;
1462
+ for (let i = 0; i <= 6; i++) {
1463
+ const y = padT + chartH * (1 - i / 6);
1464
+ ctx.beginPath(); ctx.moveTo(padL, y); ctx.lineTo(W - padR, y); ctx.stroke();
1465
+ ctx.fillStyle = 'rgba(84,110,122,0.7)';
1466
+ ctx.font = '9px JetBrains Mono, monospace';
1467
+ ctx.textAlign = 'right';
1468
+ ctx.fillText((maxF1 * i / 6).toFixed(2), padL - 5, y + 3);
1469
+ }
1470
+ ctx.strokeStyle = 'rgba(0,229,255,0.2)';
1471
+ ctx.beginPath(); ctx.moveTo(padL, padT); ctx.lineTo(padL, padT + chartH); ctx.stroke();
1472
+ ctx.beginPath(); ctx.moveTo(padL, padT + chartH); ctx.lineTo(W - padR, padT + chartH); ctx.stroke();
1473
+
1474
+ const groupW = chartW / models.length;
1475
+ const nMethods = unswMethods.length;
1476
+ const bw = groupW * 0.8 / nMethods;
1477
+ const groupPad = groupW * 0.1;
1478
+
1479
+ modelIndices.forEach((mi, gi) => {
1480
+ const gx = padL + gi * groupW + groupPad;
1481
+ // model label
1482
+ ctx.fillStyle = 'rgba(224,247,250,0.7)';
1483
+ ctx.font = 'bold 10px JetBrains Mono, monospace';
1484
+ ctx.textAlign = 'center';
1485
+ ctx.fillText(unswModels[mi], padL + gi * groupW + groupW / 2, padT + chartH + 20);
1486
+
1487
+ unswMethods.forEach((m, mIdx) => {
1488
+ const val = m.f1[mi] * progress;
1489
+ const barH = (val / maxF1) * chartH;
1490
+ const x = gx + mIdx * bw;
1491
+ const y = padT + chartH - barH;
1492
+ const isDSMOTE = m.name === 'DSMOTE';
1493
+
1494
+ ctx.fillStyle = isDSMOTE ? m.color + 'cc' : m.color + '88';
1495
+ ctx.fillRect(x, y, bw - 1, barH);
1496
+ if (isDSMOTE) {
1497
+ ctx.strokeStyle = m.color;
1498
+ ctx.lineWidth = 1.5;
1499
+ ctx.strokeRect(x, y, bw - 1, barH);
1500
+ }
1501
+
1502
+ if (progress > 0.85 && val > 0.05) {
1503
+ ctx.fillStyle = isDSMOTE ? m.color : 'rgba(84,110,122,0.8)';
1504
+ ctx.font = isDSMOTE ? 'bold 8px JetBrains Mono,monospace' : '8px JetBrains Mono,monospace';
1505
+ ctx.textAlign = 'center';
1506
+ ctx.fillText(val.toFixed(2), x + (bw - 1) / 2, y - 3);
1507
+ }
1508
+ });
1509
+ });
1510
+
1511
+ // Method legend inside chart
1512
+ ctx.textAlign = 'left';
1513
+ }
1514
+
1515
+ // UNSW Balanced Accuracy
1516
+ const unswBA = [
1517
+ { method: 'RAW', color: '#78909c', vals: [0.470, 0.566, 0.425, 0.374, 0.278, 0.329, 0.382] },
1518
+ { method: 'SMOTE', color: '#7c4dff', vals: [0.096, 0.100, 0.111, 0.100, 0.100, 0.068, 0.124] },
1519
+ { method: 'ADASYN', color: '#ffd740', vals: [0.050, 0.101, 0.168, 0.100, 0.100, 0.082, 0.133] },
1520
+ { method: 'DSMOTE', color: '#00e5ff', vals: [0.516, 0.573, 0.408, 0.376, 0.290, 0.332, 0.479] },
1521
+ ];
1522
+
1523
+ function drawUnswBA() {
1524
+ const canvas = document.getElementById('unsw-ba-canvas');
1525
+ if (!canvas) return;
1526
+ const ctx = canvas.getContext('2d');
1527
+ const W = canvas.width, H = canvas.height;
1528
+ ctx.clearRect(0, 0, W, H);
1529
+ const padL = 45, padR = 20, padT = 25, padB = 50;
1530
+ const chartW = W - padL - padR, chartH = H - padT - padB;
1531
+
1532
+ ctx.strokeStyle = 'rgba(0,229,255,0.08)'; ctx.lineWidth = 1;
1533
+ for (let i = 0; i <= 5; i++) {
1534
+ const y = padT + chartH * (1 - i / 5);
1535
+ ctx.beginPath(); ctx.moveTo(padL, y); ctx.lineTo(W - padR, y); ctx.stroke();
1536
+ ctx.fillStyle = 'rgba(84,110,122,0.7)'; ctx.font = '9px JetBrains Mono,monospace'; ctx.textAlign = 'right';
1537
+ ctx.fillText((i / 5).toFixed(1), padL - 4, y + 3);
1538
+ }
1539
+
1540
+ const groupW = chartW / unswModels.length;
1541
+ unswModels.forEach((model, mi) => {
1542
+ const gx = padL + mi * groupW + groupW * 0.08;
1543
+ const bw = groupW * 0.84 / unswBA.length;
1544
+ ctx.fillStyle = 'rgba(224,247,250,0.7)'; ctx.font = '10px JetBrains Mono,monospace';
1545
+ ctx.textAlign = 'center'; ctx.fillText(model, padL + mi * groupW + groupW / 2, padT + chartH + 18);
1546
+ unswBA.forEach((m, mIdx) => {
1547
+ const val = m.vals[mi];
1548
+ const barH = val * chartH;
1549
+ const x = gx + mIdx * bw;
1550
+ const y = padT + chartH - barH;
1551
+ const isDSMOTE = m.method === 'DSMOTE';
1552
+ ctx.fillStyle = isDSMOTE ? m.color + 'cc' : m.color + '77';
1553
+ ctx.fillRect(x, y, bw - 1, barH);
1554
+ if (isDSMOTE) {
1555
+ ctx.strokeStyle = m.color; ctx.lineWidth = 1.5;
1556
+ ctx.strokeRect(x, y, bw - 1, barH);
1557
+ ctx.fillStyle = m.color; ctx.font = 'bold 8px JetBrains Mono,monospace';
1558
+ ctx.fillText(val.toFixed(2), x + (bw - 1) / 2, y - 3);
1559
+ }
1560
+ });
1561
+ });
1562
+ ctx.textAlign = 'left';
1563
+ }
1564
+
1565
+ // Confusion Matrix renderer
1566
+ function drawConfusionMatrices() {
1567
+ drawCM('cm-smote-canvas', 'SMOTE RF', false);
1568
+ drawCM('cm-dsmote-canvas', 'DSMOTE RF', true);
1569
+ }
1570
+
1571
+ function drawCM(canvasId, title, isDsmote) {
1572
+ const canvas = document.getElementById(canvasId);
1573
+ if (!canvas) return;
1574
+ const ctx = canvas.getContext('2d');
1575
+ const W = canvas.width, H = canvas.height;
1576
+ ctx.clearRect(0, 0, W, H);
1577
+
1578
+ const classes = ['Benign','Exploits','Fuzzers','Recon.','Generic','DoS','Backdoor','Shellcode','Analysis','Worms'];
1579
+ const n = classes.length;
1580
+
1581
+ // SMOTE RF: predicts everything as Benign (col 0)
1582
+ // DSMOTE RF: approximate from RAW_RF confusion data (spread across diagonal)
1583
+ let matrix;
1584
+ if (!isDsmote) {
1585
+ // SMOTE collapse: everything predicted as Benign
1586
+ matrix = Array.from({length:n}, (_, r) => Array.from({length:n}, (_, c) => c === 0 ? [184,583,169987,805,6073,4048,1139,1787,253,21][r] : 0));
1587
+ } else {
1588
+ // DSMOTE / RAW RF-like: diagonal dominant with some spread
1589
+ matrix = [
1590
+ [67,0,0,4,105,6,0,2,0,0],
1591
+ [0,57,0,16,202,201,60,35,10,2],
1592
+ [0,1,169977,3,5,1,0,0,0,0],
1593
+ [11,3,1,246,324,165,25,23,8,0],
1594
+ [69,25,0,89,4780,752,86,229,39,4],
1595
+ [36,47,0,31,304,3485,50,91,4,0],
1596
+ [6,43,0,15,148,133,731,51,13,1],
1597
+ [23,13,0,18,342,404,43,921,23,0],
1598
+ [0,9,0,2,53,45,11,27,106,0],
1599
+ [0,0,0,1,5,0,0,0,1,14],
1600
+ ];
1601
+ }
1602
+
1603
+ const padL = 70, padT = 30, padR = 15, padB = 70;
1604
+ const cellW = (W - padL - padR) / n;
1605
+ const cellH = (H - padT - padB) / n;
1606
+
1607
+ // Normalize
1608
+ const rowMaxes = matrix.map(row => Math.max(...row));
1609
+ const globalMax = Math.max(...rowMaxes);
1610
+
1611
+ matrix.forEach((row, ri) => {
1612
+ row.forEach((val, ci) => {
1613
+ const x = padL + ci * cellW;
1614
+ const y = padT + ri * cellH;
1615
+ const intensity = val / globalMax;
1616
+ const color = isDsmote
1617
+ ? `rgba(0,229,255,${Math.min(intensity * 1.5, 0.9)})`
1618
+ : `rgba(255,23,68,${Math.min(intensity * 1.5, 0.9)})`;
1619
+ ctx.fillStyle = intensity > 0.01 ? color : 'rgba(6,11,20,0.8)';
1620
+ ctx.fillRect(x + 1, y + 1, cellW - 2, cellH - 2);
1621
+ // value
1622
+ if (val > 0) {
1623
+ ctx.fillStyle = intensity > 0.3 ? 'rgba(6,11,20,0.9)' : 'rgba(224,247,250,0.7)';
1624
+ ctx.font = `${Math.min(cellW * 0.28, 10)}px JetBrains Mono,monospace`;
1625
+ ctx.textAlign = 'center';
1626
+ const display = val > 1000 ? (val/1000).toFixed(0)+'k' : val;
1627
+ ctx.fillText(display, x + cellW/2, y + cellH/2 + 3);
1628
+ }
1629
+ });
1630
+ });
1631
+
1632
+ // X labels
1633
+ ctx.fillStyle = 'rgba(84,110,122,0.9)'; ctx.font = '8px JetBrains Mono,monospace'; ctx.textAlign = 'center';
1634
+ classes.forEach((c, i) => {
1635
+ ctx.save(); ctx.translate(padL + i * cellW + cellW/2, H - padB + 8);
1636
+ ctx.rotate(-Math.PI / 4); ctx.fillText(c, 0, 0); ctx.restore();
1637
+ });
1638
+ // Y labels
1639
+ ctx.textAlign = 'right';
1640
+ classes.forEach((c, i) => {
1641
+ ctx.fillText(c, padL - 4, padT + i * cellH + cellH/2 + 3);
1642
+ });
1643
+
1644
+ // Title
1645
+ ctx.fillStyle = isDsmote ? '#00e5ff' : '#ff1744';
1646
+ ctx.font = 'bold 11px JetBrains Mono,monospace'; ctx.textAlign = 'center';
1647
+ ctx.fillText(title, W/2, 18);
1648
+ ctx.textAlign = 'left';
1649
+ }
1650
+
1651
+ // ============================================================
1652
+ // PANEL 7: KDD RESULTS
1653
+ // ============================================================
1654
+ const kddModels = ['DT', 'RF', 'XGBoost', 'ANN', 'CNN', 'LSTM', 'LSTM-CNN'];
1655
+ const kddMethods = [
1656
+ { name: 'RAW', color: '#78909c', f1: [0.962, 0.995, 0.992, 0.952, 0.645, 0.952, 0.976] },
1657
+ { name: 'ROS', color: '#546e7a', f1: [0.925, 0.996, 0.992, 0.858, 0.627, 0.858, 0.975] },
1658
+ { name: 'SMOTE', color: '#7c4dff', f1: [0.939, 0.995, 0.992, 0.866, 0.620, 0.943, 0.974] },
1659
+ { name: 'BSMOTE', color: '#ff6b35', f1: [0.911, 0.995, 0.991, 0.900, 0.452, 0.924, 0.945] },
1660
+ { name: 'DSMOTE', color: '#00e5ff', f1: [0.962, 0.995, 0.992, 0.928, 0.505, 0.944, 0.965] },
1661
+ ];
1662
+
1663
+ let kddAnimFrame = null, kddProgress = 0;
1664
+
1665
+ function drawKddF1(progress) {
1666
+ const canvas = document.getElementById('kdd-f1-canvas');
1667
+ if (!canvas) return;
1668
+ const ctx = canvas.getContext('2d');
1669
+ const W = canvas.width, H = canvas.height;
1670
+ ctx.clearRect(0, 0, W, H);
1671
+ const padL = 45, padR = 20, padT = 30, padB = 55;
1672
+ const chartW = W - padL - padR, chartH = H - padT - padB;
1673
+ const minF1 = 0.4, maxF1 = 1.0, range = maxF1 - minF1;
1674
+
1675
+ ctx.strokeStyle = 'rgba(0,229,255,0.08)'; ctx.lineWidth = 1;
1676
+ for (let i = 0; i <= 6; i++) {
1677
+ const val = minF1 + range * i / 6;
1678
+ const y = padT + chartH * (1 - i / 6);
1679
+ ctx.beginPath(); ctx.moveTo(padL, y); ctx.lineTo(W - padR, y); ctx.stroke();
1680
+ ctx.fillStyle = 'rgba(84,110,122,0.7)'; ctx.font = '9px JetBrains Mono,monospace';
1681
+ ctx.textAlign = 'right'; ctx.fillText(val.toFixed(2), padL - 4, y + 3);
1682
+ }
1683
+ // note: zoom view
1684
+ ctx.fillStyle = 'rgba(84,110,122,0.5)'; ctx.font = '9px JetBrains Mono,monospace';
1685
+ ctx.textAlign = 'left'; ctx.fillText('* Y-axis starts at 0.40 for readability', padL + 5, padT + 12);
1686
+
1687
+ const groupW = chartW / kddModels.length;
1688
+ kddModels.forEach((model, mi) => {
1689
+ const gx = padL + mi * groupW + groupW * 0.05;
1690
+ const bw = groupW * 0.9 / kddMethods.length;
1691
+ ctx.fillStyle = 'rgba(224,247,250,0.7)'; ctx.font = '10px JetBrains Mono,monospace';
1692
+ ctx.textAlign = 'center'; ctx.fillText(model, padL + mi * groupW + groupW / 2, padT + chartH + 18);
1693
+ kddMethods.forEach((m, mIdx) => {
1694
+ const raw = m.f1[mi];
1695
+ const val = Math.max(raw - minF1, 0) * progress;
1696
+ const barH = (val / range) * chartH;
1697
+ const x = gx + mIdx * bw;
1698
+ const y = padT + chartH - barH;
1699
+ const isDSMOTE = m.name === 'DSMOTE';
1700
+ ctx.fillStyle = isDSMOTE ? m.color + 'cc' : m.color + '77';
1701
+ ctx.fillRect(x, y, bw - 1, barH);
1702
+ if (isDSMOTE) {
1703
+ ctx.strokeStyle = m.color; ctx.lineWidth = 1.5;
1704
+ ctx.strokeRect(x, y, bw - 1, barH);
1705
+ }
1706
+ if (progress > 0.85 && raw > minF1 + 0.05) {
1707
+ ctx.fillStyle = isDSMOTE ? m.color : 'rgba(84,110,122,0.7)';
1708
+ ctx.font = isDSMOTE ? 'bold 8px JetBrains Mono,monospace' : '8px JetBrains Mono,monospace';
1709
+ ctx.textAlign = 'center';
1710
+ ctx.fillText(raw.toFixed(3), x + (bw-1)/2, y - 3);
1711
+ }
1712
+ });
1713
+ });
1714
+ ctx.textAlign = 'left';
1715
+ }
1716
+
1717
+ // Radar chart
1718
+ function drawRadar() {
1719
+ const canvas = document.getElementById('kdd-radar-canvas');
1720
+ if (!canvas) return;
1721
+ const ctx = canvas.getContext('2d');
1722
+ const W = canvas.width, H = canvas.height;
1723
+ ctx.clearRect(0, 0, W, H);
1724
+ const cx = W / 2, cy = H / 2, R = Math.min(W, H) * 0.36;
1725
+ const axes = ['Accuracy', 'Bal. Acc.', 'Precision', 'Recall', 'F1 Macro', 'G-Mean'];
1726
+ const n = axes.length;
1727
+ const datasets = [
1728
+ { name: 'RAW RF', color: '#78909c', vals: [0.99986, 0.99402, 0.99693, 0.99402, 0.99545, 0.99393] },
1729
+ { name: 'DSMOTE RF', color: '#00e5ff', vals: [0.99986, 0.99402, 0.99693, 0.99402, 0.99545, 0.99393] },
1730
+ { name: 'SMOTE RF', color: '#7c4dff', vals: [0.9999, 0.9922, 0.9978, 0.9922, 0.9949, 0.9920] },
1731
+ ];
1732
+
1733
+ // Grid rings
1734
+ for (let ring = 1; ring <= 5; ring++) {
1735
+ const r = R * ring / 5;
1736
+ ctx.beginPath();
1737
+ for (let i = 0; i < n; i++) {
1738
+ const angle = (i / n) * Math.PI * 2 - Math.PI / 2;
1739
+ const x = cx + r * Math.cos(angle), y = cy + r * Math.sin(angle);
1740
+ i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
1741
+ }
1742
+ ctx.closePath();
1743
+ ctx.strokeStyle = 'rgba(0,229,255,0.1)'; ctx.lineWidth = 1; ctx.stroke();
1744
+ ctx.fillStyle = 'rgba(84,110,122,0.5)'; ctx.font = '8px JetBrains Mono,monospace'; ctx.textAlign = 'center';
1745
+ ctx.fillText((ring / 5).toFixed(1), cx, cy - r - 3);
1746
+ }
1747
+
1748
+ // Axis lines & labels
1749
+ for (let i = 0; i < n; i++) {
1750
+ const angle = (i / n) * Math.PI * 2 - Math.PI / 2;
1751
+ const ex = cx + R * Math.cos(angle), ey = cy + R * Math.sin(angle);
1752
+ ctx.beginPath(); ctx.moveTo(cx, cy); ctx.lineTo(ex, ey);
1753
+ ctx.strokeStyle = 'rgba(0,229,255,0.2)'; ctx.stroke();
1754
+ const lx = cx + (R + 22) * Math.cos(angle), ly = cy + (R + 22) * Math.sin(angle);
1755
+ ctx.fillStyle = 'rgba(224,247,250,0.7)'; ctx.font = '10px JetBrains Mono,monospace';
1756
+ ctx.textAlign = 'center'; ctx.fillText(axes[i], lx, ly + 3);
1757
+ }
1758
+
1759
+ // Data polygons
1760
+ datasets.forEach((ds, di) => {
1761
+ ctx.beginPath();
1762
+ ds.vals.forEach((v, i) => {
1763
+ const angle = (i / n) * Math.PI * 2 - Math.PI / 2;
1764
+ const r = v * R;
1765
+ const x = cx + r * Math.cos(angle), y = cy + r * Math.sin(angle);
1766
+ i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
1767
+ });
1768
+ ctx.closePath();
1769
+ ctx.strokeStyle = ds.color; ctx.lineWidth = di === 1 ? 2.5 : 1.5; ctx.stroke();
1770
+ ctx.fillStyle = ds.color + (di === 1 ? '22' : '11'); ctx.fill();
1771
+ // dots
1772
+ ds.vals.forEach((v, i) => {
1773
+ const angle = (i / n) * Math.PI * 2 - Math.PI / 2;
1774
+ const r = v * R;
1775
+ const x = cx + r * Math.cos(angle), y = cy + r * Math.sin(angle);
1776
+ ctx.beginPath(); ctx.arc(x, y, di === 1 ? 4 : 3, 0, Math.PI * 2);
1777
+ ctx.fillStyle = ds.color; ctx.fill();
1778
+ });
1779
+ });
1780
+
1781
+ // Legend
1782
+ datasets.forEach((ds, i) => {
1783
+ const lx = 20, ly = 20 + i * 18;
1784
+ ctx.fillStyle = ds.color; ctx.fillRect(lx, ly, 14, 3);
1785
+ ctx.fillStyle = 'rgba(224,247,250,0.8)'; ctx.font = '10px JetBrains Mono,monospace';
1786
+ ctx.textAlign = 'left'; ctx.fillText(ds.name, lx + 20, ly + 4);
1787
+ });
1788
+ ctx.textAlign = 'left';
1789
+ }
1790
+
1791
+ // KDD G-Mean
1792
+ const kddGmean = [
1793
+ { method: 'RAW', color: '#78909c', vals: [0.959, 0.994, 0.991, 0.936, 0.005, 0.966, 0.985] },
1794
+ { method: 'SMOTE', color: '#7c4dff', vals: [0.993, 0.992, 0.997, 0.991, 0.972, 0.991, 0.991] },
1795
+ { method: 'BSMOTE', color: '#ff6b35', vals: [0.991, 0.992, 0.997, 0.958, 0.655, 0.958, 0.977] },
1796
+ { method: 'DSMOTE', color: '#00e5ff', vals: [0.959, 0.994, 0.991, 0.943, 0.560, 0.940, 0.959] },
1797
+ ];
1798
+
1799
+ function drawKddGmean() {
1800
+ const canvas = document.getElementById('kdd-gmean-canvas');
1801
+ if (!canvas) return;
1802
+ const ctx = canvas.getContext('2d');
1803
+ const W = canvas.width, H = canvas.height;
1804
+ ctx.clearRect(0, 0, W, H);
1805
+ const padL = 45, padR = 20, padT = 25, padB = 50;
1806
+ const chartW = W - padL - padR, chartH = H - padT - padB;
1807
+ const minV = 0.0, maxV = 1.0;
1808
+
1809
+ ctx.strokeStyle = 'rgba(0,229,255,0.08)'; ctx.lineWidth = 1;
1810
+ for (let i = 0; i <= 5; i++) {
1811
+ const y = padT + chartH * (1 - i / 5);
1812
+ ctx.beginPath(); ctx.moveTo(padL, y); ctx.lineTo(W - padR, y); ctx.stroke();
1813
+ ctx.fillStyle = 'rgba(84,110,122,0.7)'; ctx.font = '9px JetBrains Mono,monospace';
1814
+ ctx.textAlign = 'right'; ctx.fillText((i / 5).toFixed(1), padL - 4, y + 3);
1815
+ }
1816
+
1817
+ const groupW = chartW / kddModels.length;
1818
+ kddModels.forEach((model, mi) => {
1819
+ const gx = padL + mi * groupW + groupW * 0.06;
1820
+ const bw = groupW * 0.88 / kddGmean.length;
1821
+ ctx.fillStyle = 'rgba(224,247,250,0.7)'; ctx.font = '10px JetBrains Mono,monospace';
1822
+ ctx.textAlign = 'center'; ctx.fillText(model, padL + mi * groupW + groupW / 2, padT + chartH + 18);
1823
+ kddGmean.forEach((m, mIdx) => {
1824
+ const val = m.vals[mi];
1825
+ const barH = val * chartH;
1826
+ const x = gx + mIdx * bw;
1827
+ const y = padT + chartH - barH;
1828
+ const isDSMOTE = m.method === 'DSMOTE';
1829
+ ctx.fillStyle = isDSMOTE ? m.color + 'cc' : m.color + '77';
1830
+ ctx.fillRect(x, y, bw - 1, barH);
1831
+ if (isDSMOTE) {
1832
+ ctx.strokeStyle = m.color; ctx.lineWidth = 1.5;
1833
+ ctx.strokeRect(x, y, bw - 1, barH);
1834
+ ctx.fillStyle = m.color; ctx.font = 'bold 8px JetBrains Mono,monospace';
1835
+ ctx.textAlign = 'center'; ctx.fillText(val.toFixed(3), x + (bw-1)/2, y - 3);
1836
+ }
1837
+ });
1838
+ });
1839
+ ctx.textAlign = 'left';
1840
+ }
1841
+
1842
+ // ============================================================
1843
+ // INIT
1844
+ // ============================================================
1845
+ window.addEventListener('load', () => {
1846
+ resetSmote();
1847
+ drawScatterBase('smote-canvas', [], '#ff6b35');
1848
+ drawScatterBase('dsmote-canvas', [], '#00e676');
1849
+ });
1850
+
1851
+ // Auto-draw on tab switch
1852
+ const _origSwitchTab = switchTab;
1853
+ function switchTab(i) {
1854
+ document.querySelectorAll('.tab').forEach((t, j) => t.classList.toggle('active', i === j));
1855
+ document.querySelectorAll('.panel').forEach((p, j) => p.classList.toggle('active', i === j));
1856
+ if (i === 1) setTimeout(initPipeline, 100);
1857
+ if (i === 2) setTimeout(initClusters, 100);
1858
+ if (i === 3) setTimeout(initDensity, 100);
1859
+ if (i === 4) setTimeout(() => { initBars(); initF1(); }, 100);
1860
+ if (i === 5) {
1861
+ setTimeout(() => {
1862
+ filterUnswModel('ALL');
1863
+ drawUnswBA();
1864
+ drawConfusionMatrices();
1865
+ }, 150);
1866
+ }
1867
+ if (i === 6) {
1868
+ setTimeout(() => {
1869
+ kddProgress = 0;
1870
+ if (kddAnimFrame) cancelAnimationFrame(kddAnimFrame);
1871
+ function step() {
1872
+ kddProgress = Math.min(kddProgress + 0.04, 1);
1873
+ drawKddF1(kddProgress);
1874
+ if (kddProgress < 1) kddAnimFrame = requestAnimationFrame(step);
1875
+ }
1876
+ step();
1877
+ drawRadar();
1878
+ drawKddGmean();
1879
+ }, 150);
1880
+ }
1881
+ }
1882
+ </script>
1883
+ </body>
1884
  </html>