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.

AI Large-Model Wave and Transformation Guide
AI Large-Model Wave and Transformation Guide
AI Large-Model Wave and Transformation Guide
Can JavaScript Teach k‑Nearest Neighbors? Build a Visual kNN Classifier from Scratch

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,flat

k‑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/

JavaScriptmachine learningVisualizationalgorithm implementationfeature normalizationk-nearest neighbors
AI Large-Model Wave and Transformation Guide
Written by

AI Large-Model Wave and Transformation Guide

Focuses on the latest large-model trends, applications, technical architectures, and related information.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.