# Edit in JSFiddle

```<script type="text/python">
from math import sqrt
from random import randint
from browser import document as doc
import math

class KMeans:
def __init__(self, x, y, n_clusters = 1, limit = 10):
self.x = x
self.x_min = min(x)
self.x_max = max(x)
self.y = y
self.y_min = min(y)
self.y_max = max(y)
self.n_clusters = n_clusters
self.limit = limit
self._init_centroids()
self.iterations = 0

def _init_centroids(self):
self.x_centroids = []
self.y_centroids = []
self.colors = []
for i in range(self.n_clusters):
self.x_centroids.append(randint(self.x_min, self.x_max))
self.y_centroids.append(randint(self.y_min, self.y_max))
r = randint(0,255)
g = randint(0,255)
b = randint(0,255)
color = 'rgb({0},{1},{2})'.format(r, g, b)
self.colors.append(color)
self._assign_centroids()

def _assign_centroids(self):
self.c = []
# Maximum possible distance to a centroid
dmax  = sqrt((self.x_min - self.x_max)**2 +
(self.y_min - self.y_max)**2)
for xi, yi in zip(self.x, self.y):
cluster = 0
d0 = dmax
for i in range(self.n_clusters):
d = sqrt((xi - self.x_centroids[i])**2 +
(yi - self.y_centroids[i])**2)
if d < d0:
cluster = i
d0 = d
self.c.append(cluster)

def _recalculate_centroids(self):
self._x_centroids = self.x_centroids[:]
self._y_centroids = self.y_centroids[:]
for n in range(self.n_clusters):
x0 = 0
y0 = 0
cont = 0
for i, c in enumerate(self.c):
if c == n:
cont += 1
x0 += self.x[i]
y0 += self.y[i]
self.x_centroids[n] = x0 / cont
self.y_centroids[n] = y0 / cont
self._assign_centroids()

def _check_stop(self):
for i in range(self.n_clusters):
d = sqrt(
(self._x_centroids[i] - self.x_centroids[i])**2 +
(self._y_centroids[i] - self.y_centroids[i])**2
)
if d > self.limit:
return False
return True

def __iter__(self):
return self

def __next__(self):
self.iterations += 1
self._recalculate_centroids()
stop = self._check_stop()
if stop == True:
raise StopIteration
return self

## Base classes for higher level objects
class Figure:
"""
Base class to create other elements.
"""
def __init__(self, canvasid,
facecolor = "white",
edgecolor = "black",
borderwidth = None):
"""
Parameters
----------
*canvasid*: String
String indicating the canvas id where the image should be
rendered.
*facecolor*: String
String value containing a valid HTML color
*edgecolor*: String
String value containing a valid HTML color
*borderwidth*: Integer
Value indicating the width of the border in pixels.
If not provided it will 0 and the edgecolor will not be
visible
"""

if isinstance(canvasid, str):
self.id = canvasid
else:
raise Exception("The canvasid parameter should be a string")

try:
self.canvas = doc[self.id]
except:
raise Exception("No HTML element with id=%s" %
self.id)

try:
self._W = self.canvas.width
self._H = self.canvas.height
self._ctx = self.canvas.getContext("2d")
except:
raise Exception("You must provide the ID of a <canvas> element")

self.facecolor = facecolor
self.borderwidth = borderwidth
self.edgecolor = edgecolor
self.clf()

def clf(self):
"clear the figure"
self._ctx.save()

# The following line should clear the canvas but I found a
# problem when I use beginPath ¿¿¿???
#self._ctx.clearRect(0, 0, self._W, self._H)
# So I use the following line that is less performant but
# this operation shouldn't be done very often...
self.canvas.width = self.canvas.width

self._ctx.fillStyle = self.facecolor
self._ctx.fillRect(0, 0, self._W, self._H)
self._ctx.fill()
if self.borderwidth:
self._ctx.lineWidth = self.borderwidth
self._ctx.strokeStyle = self.edgecolor
self._ctx.strokeRect(0, 0, self._W, self._H)
self._ctx.stroke()
self._ctx.restore()

class Shape:
"""
Base class to create other elements.
"""
def __init__(self, context, x, y,
facecolor = "black",
edgecolor = "black",
#alpha = 1,
borderwidth = None):
"""
Parameters
----------
*context*: a canvas context
a valid canvas context where the text will be rendered
*x*: int or float
x value for location in pixels
*y*: int or float
y value for location in pixels
*facecolor*: String
String value containing a valid HTML color
*edgecolor*: String
String value containing a valid HTML color
*alpha*: int or float
Value between 0 (transparent) and 1 (opaque) to set the
transparency of the text
*borderwidth*: Integer
Value indicating the width of the border in pixels.
If not provided it will 0 and the edgecolor will not be
visible
"""
self._ctx = context
self.x = x
self.y = y
self.facecolor = facecolor
self.borderwidth = borderwidth
self.edgecolor = edgecolor
#self.alpha = alpha

class Circle(Shape):
def __init__(self, *args, radius = 10, **kwargs):
"""
Parameters
----------
radius of the circle in pixels.
"""
Shape.__init__(self, *args, **kwargs)
self.draw()

def draw(self):
self._ctx.save()
#self._ctx.globalAlpha = self.alpha
self._ctx.beginPath()
self._ctx.fillStyle = self.facecolor
self._ctx.arc(self.x, self.y, self.r, 0, 2 * math.pi)
self._ctx.fill()
if self.borderwidth:
self._ctx.lineWidth = self.borderwidth
self._ctx.strokeStyle = self.edgecolor
self._ctx.arc(self.x, self.y, self.r, 0, 2 * math.pi)
self._ctx.stroke()
self._ctx.closePath()
self._ctx.restore()

class Line(Shape):
def __init__(self, *args, polygon = False, borderwidth = 2, **kwargs):
Shape.__init__(self, *args, **kwargs)
self.borderwidth = borderwidth
self.polygon = polygon
self.draw()

def draw(self):
self._ctx.save()
#self._ctx.globalAlpha = self.alpha
self._ctx.beginPath()
self._ctx.moveTo(self.x[0], self.y[0])
for i in range(len(self.x)):
self._ctx.lineTo(self.x[i], self.y[i])
if self.polygon:
self._ctx.closePath()
if self.facecolor:
self._ctx.fillStyle = self.facecolor
self._ctx.fill()
if self.borderwidth:
self._ctx.lineWidth = self.borderwidth
self._ctx.strokeStyle = self.edgecolor
self._ctx.stroke()
self._ctx.restore()

fig = Figure('cnvs01', borderwidth = 2)

n_points = 50
x = [randint(10, fig._W - 10) for value in range(n_points)]
y = [randint(10, fig._H - 10) for value in range(n_points)]

kmeans = KMeans(x, y, n_clusters = 4, limit = 1)

def plot(obj):
fig._ctx.save()
fig._ctx.fillStyle= "#ffffff"
fig._ctx.globalAlpha = 0.3
fig._ctx.fillRect(2,2,fig._W-4,fig._H-4)
fig._ctx.restore()
x = obj.x
y = obj.y
npoints = len(x)
colors = obj.colors
xc = obj.x_centroids
yc = obj.y_centroids
c = obj.c
for i in range(npoints):
color = colors[c[i]]
Line(fig._ctx, [x[i], xc[c[i]]], [y[i], yc[c[i]]],
facecolor = color, edgecolor = color)
Circle(fig._ctx, x[i], y[i],
facecolor = color, edgecolor = 'black',
borderwidth = 1, radius = 4)
for xci, yci, color in zip(xc, yc, colors):
Circle(fig._ctx, xci, yci,
facecolor = color, edgecolor = 'black',
borderwidth = 1, radius = 8)

def update(ev):
plot(kmeans)
try:
next(kmeans)
except:
#doc['button'].disabled = True
del doc['button']

doc['button'].bind('click', update)
</script>

<div id="main">
<p>
<canvas id="cnvs01" width=500 height=500></canvas>
</p>
<p>
<button id="button" class="btn">Next step</button>
</p>
</div>```