-
Notifications
You must be signed in to change notification settings - Fork 105
/
Copy pathknn.html
241 lines (217 loc) · 8.64 KB
/
knn.html
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>K-Nearest Neighbors Demo</title>
<style>
canvas {
border: 1px solid black;
display: block;
margin: 20px auto;
}
.controls {
text-align: center;
margin: 20px;
}
footer {
text-align: center;
margin-top: 20px;
font-size: 14px;
}
.instructions {
text-align: center;
margin: 20px auto;
font-size: 16px;
width: 80%;
}
</style>
</head>
<body>
<h1 style="text-align: center;">K-Nearest Neighbors Demo</h1>
<div class="instructions">
<p>Instructions:</p>
<ul>
<li>Select the type of point you want to place (Red or Blue) using the dropdown menu.</li>
<li>Click anywhere on the canvas to place the selected point.</li>
<li>Click on an existing point to select it. The `K` nearest neighbors of the selected point will be highlighted with lines.</li>
<li>Adjust the number of neighbors (K) using the input box to see how the neighbors and decision boundary change dynamically.</li>
<li>Select a distance metric from the dropdown to observe its effect on the decision boundaries.</li>
<li>Use the "Clear Points" button to reset the canvas and start over.</li>
</ul>
</div>
<canvas id="knnCanvas" width="600" height="400"></canvas>
<div class="controls">
<label for="point-class">Point Type:</label>
<select id="point-class">
<option value="red">Red</option>
<option value="blue">Blue</option>
</select>
<label for="k-value">Num Neighbors (K):</label>
<input type="number" id="k-value" min="1" value="3">
<label for="distance-metric">Distance Metric:</label>
<select id="distance-metric">
<option value="euclidean">Euclidean</option>
<option value="manhattan">Manhattan</option>
<option value="chebyshev">Chebyshev</option>
<option value="cosine">Cosine</option>
</select>
<button onclick="clearPoints()">Clear Points</button>
</div>
<footer>
(c) Fayyaz Minhas
</footer>
<script>
const canvas = document.getElementById('knnCanvas');
const ctx = canvas.getContext('2d');
const kInput = document.getElementById('k-value');
const metricInput = document.getElementById('distance-metric');
let points = []; // Stores data points
let k = 3; // Default number of neighbors
let selectedPoint = null; // Keeps track of the currently selected point
// Function to clear points
function clearPoints() {
points = [];
selectedPoint = null;
updateKMax();
drawCanvas();
}
// Function to calculate distance between two points
function calculateDistance(point1, point2, metric = 'euclidean') {
const dx = point1.x - point2.x;
const dy = point1.y - point2.y;
switch (metric) {
case 'euclidean':
return Math.sqrt(dx * dx + dy * dy);
case 'manhattan':
return Math.abs(dx) + Math.abs(dy);
case 'chebyshev':
return Math.max(Math.abs(dx), Math.abs(dy));
case 'cosine':
const dotProduct = point1.x * point2.x + point1.y * point2.y;
const magnitude1 = Math.sqrt(point1.x ** 2 + point1.y ** 2);
const magnitude2 = Math.sqrt(point2.x ** 2 + point2.y ** 2);
return 1 - (dotProduct / (magnitude1 * magnitude2));
default:
return Math.sqrt(dx * dx + dy * dy); // Default to Euclidean
}
}
// Function to classify a point using KNN
function classifyPoint(x, y) {
const distances = points.map(point => {
return {
...point,
distance: calculateDistance({ x, y }, point, metricInput.value)
};
});
distances.sort((a, b) => a.distance - b.distance);
const nearestNeighbors = distances.slice(0, k);
const classCounts = { red: 0, blue: 0 };
nearestNeighbors.forEach(neighbor => {
classCounts[neighbor.class]++;
});
return classCounts.red > classCounts.blue ? 'red' : 'blue';
}
// Function to draw the canvas
function drawCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let x = 0; x < canvas.width; x += 5) {
for (let y = 0; y < canvas.height; y += 5) {
const color = classifyPoint(x, y);
ctx.fillStyle = color === 'red' ? 'rgba(255, 0, 0, 0.1)' : 'rgba(0, 0, 255, 0.1)';
ctx.fillRect(x, y, 5, 5);
}
}
if (selectedPoint) {
highlightKNearestNeighbors(selectedPoint);
}
points.forEach(point => {
if (point.class === 'red') {
drawDiamond(point.x, point.y, 'red');
} else {
ctx.fillStyle = 'blue';
ctx.beginPath();
ctx.arc(point.x, point.y, 5, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
}
});
}
// Function to draw a diamond shape
function drawDiamond(x, y, color) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(x, y - 5);
ctx.lineTo(x + 5, y);
ctx.lineTo(x, y + 5);
ctx.lineTo(x - 5, y);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
// Function to highlight K nearest neighbors of a point
function highlightKNearestNeighbors(point) {
const distances = points.map(p => {
return {
...p,
distance: calculateDistance(point, p, metricInput.value)
};
});
distances.sort((a, b) => a.distance - b.distance);
const nearestNeighbors = distances.slice(1, k + 1);
ctx.strokeStyle = 'black';
ctx.lineWidth = 1;
nearestNeighbors.forEach(neighbor => {
ctx.beginPath();
ctx.moveTo(point.x, point.y);
ctx.lineTo(neighbor.x, neighbor.y);
ctx.stroke();
});
}
// Function to update the maximum value of K
function updateKMax() {
const maxK = Math.max(points.length - 1, 1);
kInput.max = maxK;
if (k > maxK) {
k = maxK;
kInput.value = maxK;
}
}
// Function to check if a click is near an existing point
function findPointAt(x, y) {
return points.find(point => {
const dx = point.x - x;
const dy = point.y - y;
return Math.sqrt(dx * dx + dy * dy) <= 5;
});
}
// Add event listener to canvas for adding or selecting points
canvas.addEventListener('click', (event) => {
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
const clickedPoint = findPointAt(x, y);
if (clickedPoint) {
selectedPoint = clickedPoint;
} else {
const pointClass = document.getElementById('point-class').value;
const newPoint = { x, y, class: pointClass };
points.push(newPoint);
selectedPoint = null;
}
updateKMax();
drawCanvas();
});
// Add event listener to update K value
kInput.addEventListener('input', (event) => {
k = parseInt(event.target.value, 10);
drawCanvas();
});
// Re-draw when distance metric changes
metricInput.addEventListener('change', () => {
drawCanvas();
});
drawCanvas();
</script>
</body>
</html>