KNN Visualizer Using React JS
React JS has become a very powerful tool due its prime focus on individual components. Working with DOM, in general, is very hard but React eases it immensely.
In this tutorial, we will make a KNN visualizer using create-react-app. I assume, you know basics of Modern JavaScript like arrow functions, Object & Arrays functions and Promises. By the end of this tutorial, you will create something like this:
Let’s get started!
1: Create your React app
Go to your bash, and create a new react project using the following commands:
npx create-react-app knn-visualizer
cd knn-visualizer
npm start
This creates the react app, with all the necessary functionality you need, already built into the app with the name knn-visualizer. After the execution of last command, you should see your react app getting launched which might look something like this:
2: Make App a class component
knn-visualizer
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
└── src
├── App.css
├── App.js
├── App.test.js
├── index.css
├── index.js
├── logo.svg
└── serviceWorker.js
The directory structure of your application might look like something above. The files you will be mostly working on are the src folder and public folder. React, by default, creates App.js which is a functional component. We would need a class component, so replace the App.js by the following code:
import React from 'react';
import logo from './logo.svg';
import './App.css';class App extends React.Component {
render(){
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>Edit <code>src/App.js</code> and save to reload.</p>
<a className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer">
Learn React
</a>
</header>
</div>);
}
}export default App;
Your output should not change after changing the content of App.js.
Great, so we’ve converted our functional component to class component! Onto next!
3: Installing react-bootstrap: React bootstrap offers a whole lot of React components through their npm package. You can download the packages using npm, or yarn depending on what you prefer.
npm install react-bootstrap bootstrap
This will still not include bootstrap styles to your components, so don’t forget to add bootstrap CDN to your public/index.html file.
<link
rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk"
crossorigin="anonymous"/>
4: Add a reactstrap Navbar to your render() function. Here we have used the readily available components available from react-bootstrap. Play with some more components, and do not forget the import statement at the top.
//do not forget this import statement
import { Nav, Navbar, NavDropdown } from 'react-bootstrap'<Navbar bg="light" expand="lg">
<Navbar.Brand href="/">KNN Visualizer</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="mr-auto">
<Nav.Link>Start Training </Nav.Link>
<Nav.Link>Stop Training </Nav.Link>
<Nav.Link>Predicting </Nav.Link>
<NavDropdown title="Choose Color" id="basic-nav-dropdown"
<NavDropdown.Item>Red</NavDropdown.Item>
<NavDropdown.Item>Blue</NavDropdown.Item>
<NavDropdown.Item>Green</NavDropdown.Item>
</NavDropdown>
</Nav>
</Navbar.Collapse>
</Navbar>
5: Add constructor and handlers to update the state through the Nav components. We use arrow functions for this purpose. These functions are anonymous and change the way this binds to the functions.
constructor(props) {
super(props)
this.state = {
color: null,
isTraining: false,
isPredicting: false,
input: [],
k: 5,
n: 3,
colorCodes: ['red', 'blue', 'green'],
modalShow: false
}
}//in render function<Nav.Link onClick={() => {this.setState({isTraining: true})}}>
Start Training
</Nav.Link>
<Nav.Link onClick={() => {this.setState({isTraining: false})}}>
Stop Training
</Nav.Link>
<Nav.Link onClick={() => {this.setState({isPredicting: true})}}>
Predicting
</Nav.Link>
setState() function helps in updating the state of the component. We have used arrow functions in implementation as it immensely helps to avoid bugs by avoiding use of this within callbacks.
6: Add Canvas to your App Component. In this step, we will add canvas element to our App component, and implement draw function which will be triggered upon onClick event of JavaScript. You can add canvas to another component and export the component to your App.js file for more modular code but I have implemented itin the same file to avoid passing props to the child component.
draw = (event) => {
var canvas = document.getElementById("canvas")
var context = canvas.getContext("2d");
var pos = this.getMousePos(canvas, event)
var posx = pos.x
var posy = pos.y
if (this.state.isTraining && this.state.color!== null) {
context.fillStyle = this.state.color
context.beginPath()
context.arc(posx, posy, 9, 0, 2*Math.PI)
this.setState({input: [...this.state.input, {X: posx, Y: posy, label: this.state.colorCodes.indexOf(this.state.color)}]})
context.fill()
} else if (this.state.isPredicting) {
//predicting - here we will call our knn function
}
}getMousePos(canvas, event) {
var rect = canvas.getBoundingClientRect()
return {
x: event.clientX - rect.left,
y: event.clientY - rect.top
}
}//in render method add<canvas id="canvas"
ref="canvas"
width={window.innerWidth}
height={window.innerHeight}
onClick={e => {let nativeEvent = e.nativeEvent; this.draw(nativeEvent);}}/>
- In the above code, we get the element canvas by its id, following which we check whether the user is training or predicting using our state.
- If the user is training, then we fill the circle (which represents our sample points) in the color chosen by the user.
- The label point is added to the input which is an object of three key value pairs {x, y, label} where x & y represent the position of the circle and label defines the class of the point (in this case, it represents the color)
7: Implementing the KNN helper functions. KNN algorithm calculates the similarity between the points by calculating Euclidean distance between the query point and the training points, and choosing the k nearest neighbors. We then output the class of the query point by finding out the class which is most frequent among the nearest neighbors.
7.1 Calculating the euclidean distance: Here we use the euclidean formula to calculate the distance between two two-dimensional points
euclideanDistance (P1, P2) {
return Math.sqrt(Math.pow(P1.X - P2.X, 2) + Math.pow(P1.Y - P2.Y, 2))
}
7.2 Getting the nearest neighbors: We loop through the input points, ie. the training points and call the euclideanDistance function which was implemented above. After that, we sort the inputs in ascending order with the distance key as the similarity between two points is inversely proportional to distance between them
getNeighbors(posx, posy) {
var distances = []
for (let input of this.state.input) {
var elem = {neighbor: input, distance: this.euclideanDistance(input, {X: posx, Y: posy})}
distances = [...distances, elem]
distances.sort(function(a, b){return a.distance - b.distance});
}
return distances
}
7.3 The primary KNN function which takes the input as the query point. First get all the neighbors with the distances in ascending order. Then we get the nearest K neighbors through the JavaScript function slice. After that, we calculate the frequency of classes occurring in the nearest neighbors, and return the output class which has maximum frequency
knn (posx, posy) {
var neighbors = this.getNeighbors(posx, posy)
var outputs = []
for(let i in this.state.colorCodes) {
var output = {color: this.state.colorCodes[i], n: 0}
outputs = [...outputs, output]
}
var nearestNeighbors = neighbors.slice(0, this.state.k)
for (let i in nearestNeighbors) {
outputs[nearestNeighbors[i].neighbor.label].n += 1
}
outputs.sort(function(first, second) {
return second.n - first.n;
})
return outputs[0].color
}
8: Using the KNN helper functions in our draw function: Now, we are at the last step of our KNN visualizer! First, get the color or the label of the query point using the knn function, and update color in the this.state. Then, draw the circle using the arc function. We have made the draw function asynchronous to make sure that the output color is obtained first in the promise as it returns, and only then the circle is drawn.
draw = async (event) => {
var canvas = document.getElementById("canvas")
var context = canvas.getContext("2d");
var pos = this.getMousePos(canvas, event)
var posx = pos.x
var posy = pos.y
if (this.state.isTraining && this.state.color !== null) {
context.fillStyle = this.state.color
context.beginPath()
context.arc(posx, posy, 9, 0, 2*Math.PI)
this.setState({input: [...this.state.input, {X: posx, Y: posy, label: this.state.colorCodes.indexOf(this.state.color)}]})
context.fill()
}
else if (this.state.isPredicting) {
var color = await this.knn(posx, posy)
context.fillStyle = color
context.beginPath()
context.arc(posx, posy, 9, 0, 2*Math.PI)
context.fill()
this.setState({outputColor: null})
}
}
Now that we have created a very basic KNN visualizer using React JS, try customizing it by taking k and n parameters as input by the user by generating the colors for the classes dynamically. Also try to play with the cool components provided by Reactstrap!
For inspiration, check out what I have made. And the code for this project can be found here.