Can JavaScript Teach k‑Nearest Neighbors? Build a Visual kNN Classifier from Scratch
This article walks through the theory of k‑nearest‑neighbors, explains feature selection and normalization, shows how to implement the algorithm with plain JavaScript classes, visualizes results on an HTML canvas, and discusses practical limitations and extensions.
Problem Definition
Given a table of residential units described by rooms (1‑10) and area (250‑1700), predict the class apartment, house or flat for a new, unlabeled unit using the k‑nearest‑neighbors (kNN) algorithm.
Dataset
Rooms,Area,Type
1,350,apartment
2,300,apartment
3,300,apartment
4,250,apartment
4,500,apartment
4,400,apartment
5,450,apartment
7,850,house
7,900,house
7,1200,house
8,1500,house
9,1300,house
8,1240,house
10,1700,house
9,1000,house
1,800,flat
3,900,flat
2,700,flat
1,900,flat
2,1150,flat
1,1000,flat
2,1200,flat
1,1300,flatk‑Nearest‑Neighbor Intuition
Place all points (including the unknown “mystery point”) on a 2‑D plot (rooms on the x‑axis, area on the y‑axis). Choose a small integer k (e.g., 3 for this dataset). The algorithm finds the k closest labeled points and assigns the majority label among them to the mystery point.
Step‑by‑Step Process
Plot every data point and the mystery point.
Compute Euclidean distance from the mystery point to each known point.
Sort distances and select the k smallest.
Count the class labels of those k neighbors; the most frequent label becomes the prediction.
Feature Normalization
Because rooms ranges 1‑10 while area ranges 250‑1700, raw distances would be dominated by the area dimension. Scale each feature to the [0,1] interval:
rooms_norm = (rooms - min_rooms) / (max_rooms - min_rooms) area_norm = (area - min_area) / (max_area - min_area)This equalizes the influence of each dimension in distance calculations.
JavaScript Implementation
Two constructor functions model the data: Node – stores type, area, rooms and later neighbors and distance. NodeList – holds an array of Node objects and the chosen k value.
Core Methods
NodeList.prototype.calculateRanges = function() {
this.areas = {min: 1e6, max: 0};
this.rooms = {min: 1e6, max: 0};
for (var i in this.nodes) {
var n = this.nodes[i];
if (n.rooms < this.rooms.min) this.rooms.min = n.rooms;
if (n.rooms > this.rooms.max) this.rooms.max = n.rooms;
if (n.area < this.areas.min) this.areas.min = n.area;
if (n.area > this.areas.max) this.areas.max = n.area;
}
};
NodeList.prototype.determineUnknown = function() {
this.calculateRanges();
for (var i in this.nodes) {
var node = this.nodes[i];
if (!node.type) { // unknown node
node.neighbors = [];
// clone known nodes as neighbors
for (var j in this.nodes) {
var other = this.nodes[j];
if (!other.type) continue;
node.neighbors.push(new Node(other));
}
node.measureDistances(this.areas, this.rooms);
node.sortByDistance();
console.log(node.guessType(this.k));
}
}
};
Node.prototype.measureDistances = function(area_range_obj, rooms_range_obj) {
var rooms_range = rooms_range_obj.max - rooms_range_obj.min;
var area_range = area_range_obj.max - area_range_obj.min;
for (var i in this.neighbors) {
var neighbor = this.neighbors[i];
var delta_rooms = (neighbor.rooms - this.rooms) / rooms_range;
var delta_area = (neighbor.area - this.area) / area_range;
neighbor.distance = Math.sqrt(delta_rooms*delta_rooms + delta_area*delta_area);
}
};
Node.prototype.sortByDistance = function() {
this.neighbors.sort(function(a,b){return a.distance-b.distance;});
};
Node.prototype.guessType = function(k) {
var types = {};
for (var i=0; i<k; i++) {
var t = this.neighbors[i].type;
types[t] = (types[t]||0) + 1;
}
var guess = {type:false, count:0};
for (var t in types) {
if (types[t] > guess.count) {
guess.type = t;
guess.count = types[t];
}
}
this.guess = guess;
return guess;
};
NodeList.prototype.draw = function(canvas_id) {
var rooms_range = this.rooms.max - this.rooms.min;
var areas_range = this.areas.max - this.areas.min;
var canvas = document.getElementById(canvas_id);
var ctx = canvas.getContext('2d');
var width = 400, height = 400, padding = 40;
ctx.clearRect(0,0,width,height);
for (var i in this.nodes) {
var n = this.nodes[i];
ctx.save();
switch(n.type) {
case 'apartment': ctx.fillStyle='red'; break;
case 'house': ctx.fillStyle='green'; break;
case 'flat': ctx.fillStyle='blue'; break;
default: ctx.fillStyle='#666666';
}
var x = ((n.rooms - this.rooms.min) * (width/rooms_range) * (width-padding)/width) + padding/2;
var y = ((n.area - this.areas.min) * (height/areas_range) * (height-padding)/height) + padding/2;
y = Math.abs(y - height);
ctx.translate(x, y);
ctx.beginPath();
ctx.arc(0,0,5,0,Math.PI*2,true);
ctx.fill();
ctx.closePath();
if (!n.type) { // unknown node – draw radius of influence
var radius = n.neighbors[this.k-1].distance * width * (width-padding)/width;
ctx.strokeStyle = n.guess.type==='apartment'?'red':n.guess.type==='house'?'green':'blue';
ctx.beginPath();
ctx.arc(0,0,radius,0,Math.PI*2,true);
ctx.stroke();
ctx.closePath();
}
ctx.restore();
}
};Demo Execution
The demo creates a NodeList with k=3, loads the static dataset, then every five seconds generates a random mystery point, runs determineUnknown(), and redraws the canvas.
var run = function() {
var nodes = new NodeList(3);
for (var i in data) {
nodes.add(new Node(data[i]));
}
var random_rooms = Math.round(Math.random()*10);
var random_area = Math.round(Math.random()*2000);
nodes.add(new Node({rooms:random_rooms, area:random_area, type:false}));
nodes.determineUnknown();
nodes.draw('canvas');
};
window.onload = function(){
setInterval(run,5000);
run();
};Weaknesses
If the training data densely fills the feature space, kNN cannot discover useful patterns; it works best when data forms clusters or is separable.
Distance computation is O(N²) and becomes prohibitive for thousands of points. A practical mitigation is to pre‑filter points whose feature values lie far outside the mystery point’s range before computing distances.
Potential Applications
Finding the nearest named color to an RGB value (image search).
Matching profiles on a dating site based on multiple attributes.
Retrieving the most similar documents using word‑frequency features.
Predicting purchase intent from e‑commerce browsing behavior.
Reference
Original tutorial: http://burakkanber.com/blog/machine-learning-in-js-k-nearest-neighbor-part-1/
AI Large-Model Wave and Transformation Guide
Focuses on the latest large-model trends, applications, technical architectures, and related information.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
