Skip to content

Commit 5323f99

Browse files
committed
Init
1 parent ac60845 commit 5323f99

File tree

7 files changed

+262
-0
lines changed

7 files changed

+262
-0
lines changed

.gitignore

Whitespace-only changes.

README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
![java processing3](https://img.shields.io/badge/java-processing3-orange)
2+
![algorithm k-means](https://img.shields.io/badge/algorithm-k--means-blueviolet)
3+
![licence MIT](https://img.shields.io/badge/license-MIT-green)
4+
5+
# K-Means
6+
7+
This is is a graphical fun project visualizing the effect of the [k-means](https://en.wikipedia.org/wiki/K-means_clustering) clustering algorithm which is used in data science to analyse many data points into groups.
8+
9+
## Example collage
10+
11+
In the example below you see the colums with 100x100, 200x200, 400x400 and 800x800 datapoints. The rows are the groupsize of 1, 2, 4, 8, 16, 50, 100, 200 and 2000. The images were all rendered in 800x800.
12+
13+
![collage](examples/kmeans_collage.png)
14+
15+
## Example rendering
16+
17+
groupSize 64 | groupSize 8
18+
- | -
19+
100x100 datapoints | 100x100 datapoints
20+
![render_example_1](examples/render_1.gif) | ![render_example_1](examples/render_2.gif)
21+
22+
In this program you can configure some options to influence the graphics produced and to be auto-saved.
23+
24+
## Configuration
25+
26+
The code has at the beginning a configuration section. The options are described below and summarized in the code itself. Default values are included.
27+
28+
- **renderSize**
29+
To specify the render size set the resolution in the setup() method. For HD with 1280 x 720 call the size() method like below. *Unfortunately this can not be a variable like the others due to [Processing](https://processing.org/) limitation.*
30+
```size(1280, 720);```
31+
32+
- **groupSize**
33+
How many groups to you want to have? Minium is 1, maximum is unlimed, but it makes no sense to take a very high number. These groups will be generated randomly.
34+
35+
- **dataPointSize**
36+
How many points to you want? The more you have, the clearer the result will be. Think of it like a screen resolution and better give as a number like x * y (200 * 200). These data points will be generated randomly.
37+
38+
- **paddingSize**
39+
This will keep a padding of pixels of painting the groups and data points.
40+
41+
- **blurStrength**
42+
After every pass a blur filter is applied. Think of it as an antialiasing value. 2 would affect a 2x2 blurring. Can be 0 if you do not like it.
43+
44+
- **finalBlurStrength**
45+
Same as blurStrength, but will be applied at the finish.
46+
47+
- **rotateColors**
48+
The color palette is generated for each time. To get a more interesting image, you can enable rotating the color palette. This will run every pass, if enabled.
49+
50+
- **saveImage**
51+
The images will be stored into the data directory, when this option is enabled. Filename format is `<timestamp>_gs<groupsize>_dp<datapoints>.png`. That makes it flexible to reuse configuration of the options you like.
52+
53+
- **loopSize**
54+
Because of the random factors, every picture will be individual. The points, groups and colors are randomly generated for each run. To make it easy creating lots of pictures and pick the most liked, you can specifiy a number of loops how many pictures should be created. When saveImage is activated, all pictures will be stored in the data directory.

data/.gitkeep

Whitespace-only changes.

examples/kmeans_collage.png

11.9 MB
Loading

examples/render_1.gif

2.07 MB
Loading

examples/render_2.gif

2.74 MB
Loading

kmeans.pde

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
import java.util.Date; //<>//
2+
3+
// ==== Begin Config ====
4+
5+
// How many groups to create
6+
final int groupSize = 64;
7+
8+
// How many data points to create
9+
final int dataPointSize = 100 * 100;
10+
11+
// Padding to border
12+
final int paddingSize = 20;
13+
14+
// Blur strength
15+
final int blurStrength = 2;
16+
final int finalBlurStrength = 1;
17+
18+
// Rotate color palette after each pass
19+
final boolean rotateColors = true;
20+
21+
// Save image
22+
final boolean saveImage = true;
23+
24+
// How many loops / images to create
25+
final int loopSize = 10;
26+
27+
// ==== End Config ====
28+
29+
PVector[] groups = new PVector[groupSize];
30+
PVector[] means = new PVector[groupSize];
31+
int[] clusters = new int[groupSize];
32+
color[] colors = new color[groupSize];
33+
PVector[] dataPoints = new PVector[dataPointSize];
34+
int dataRow = 0;
35+
int meansChanged = groupSize;
36+
int pass = 1;
37+
int loopsToDo = loopSize;
38+
long timerStart;
39+
40+
void setup() {
41+
42+
// Picture size to render
43+
size(800, 800);
44+
45+
reset();
46+
47+
}
48+
49+
void reset() {
50+
51+
timerStart = new Date().getTime();
52+
clear();
53+
54+
float n1 = random(1) / groupSize;
55+
float n2 = random(1) / groupSize;
56+
float n3 = random(1) / groupSize;
57+
58+
for(int i = 0; i < groupSize; i++) {
59+
groups[i] = new PVector(getRandomWidth(), getRandomHeight());
60+
colors[i] = color(noise(i * n1) * 255, noise(i * n2) * 255, noise(i * n3) * 255);
61+
means[i] = new PVector(0, 0);
62+
clusters[i] = 0;
63+
}
64+
65+
for(int i = 0; i < dataPointSize; i++) {
66+
dataPoints[i] = new PVector(getRandomWidth(), getRandomHeight());
67+
}
68+
69+
meansChanged = groupSize;
70+
pass = 1;
71+
72+
drawDataPoints();
73+
74+
}
75+
76+
float getRandomWidth() {
77+
78+
return abs(random(width - paddingSize - paddingSize) + paddingSize);
79+
80+
}
81+
82+
float getRandomHeight() {
83+
84+
return abs(random(height - paddingSize - paddingSize) + paddingSize);
85+
86+
}
87+
88+
void drawGroups() {
89+
90+
noStroke();
91+
92+
for(int i = 0; i < groupSize; i++) {
93+
fill(colors[i]);
94+
circle(groups[i].x, groups[i].y, 4);
95+
}
96+
97+
}
98+
99+
void drawDataPoints() {
100+
101+
noStroke();
102+
103+
fill(0, 0, 255);
104+
for(int i = 0; i < dataPointSize; i++) {
105+
circle(dataPoints[i].x, dataPoints[i].y, 2);
106+
}
107+
108+
}
109+
110+
int calculateMeans() {
111+
112+
int changes = 0; //<>//
113+
114+
for(int i = 0; i < groupSize; i++) {
115+
PVector mean = new PVector((int) (means[i].x / clusters[i]), (int) (means[i].y / clusters[i]));
116+
means[i] = new PVector(0, 0);
117+
clusters[i] = 0;
118+
if(mean.x == (int) groups[i].x && mean.y == (int) groups[i].y) break;
119+
groups[i] = mean;
120+
changes++;
121+
}
122+
123+
return changes;
124+
125+
}
126+
127+
void rotateColors() {
128+
129+
if(!rotateColors) return;
130+
131+
color first = colors[0];
132+
for(int i = 0, s = colors.length - 1; i < s; i++) {
133+
colors[i] = colors[i + 1];
134+
}
135+
136+
colors[colors.length - 1] = first;
137+
138+
}
139+
140+
void drawConnections() {
141+
142+
for(int steps = 0, stepMax = (int) sqrt(width * height); steps < stepMax; steps++) {
143+
144+
if(dataRow >= dataPointSize) {
145+
long timerEnd = new Date().getTime();
146+
long timeDiff = timerEnd - timerStart;
147+
timerStart = timerEnd;
148+
println(String.format("Loop %d/%d; Pass %d; Means %d; Took %.02fs",
149+
loopSize - loopsToDo + 1, loopSize, pass++, (meansChanged = calculateMeans()), (float) timeDiff / 1000
150+
));
151+
dataRow = 0;
152+
rotateColors();
153+
if(meansChanged > 0 && blurStrength > 0) filter(BLUR, 2);
154+
155+
return;
156+
}
157+
158+
int smallestIndex = 0;
159+
float shortest = max(width, height);
160+
161+
for(int j = 0; j < groupSize; j++) {
162+
float distance = dist(dataPoints[dataRow].x, dataPoints[dataRow].y, groups[j].x, groups[j].y);
163+
if(distance < shortest) {
164+
shortest = distance;
165+
smallestIndex = j;
166+
}
167+
}
168+
169+
clusters[smallestIndex]++;
170+
means[smallestIndex] = new PVector(means[smallestIndex].x + dataPoints[dataRow].x, means[smallestIndex].y + dataPoints[dataRow].y);
171+
stroke(colors[smallestIndex]);
172+
line(dataPoints[dataRow].x, dataPoints[dataRow].y, groups[smallestIndex].x, groups[smallestIndex].y);
173+
174+
dataRow++;
175+
}
176+
177+
}
178+
179+
void draw() {
180+
181+
drawGroups();
182+
drawConnections();
183+
184+
if(meansChanged == 0) {
185+
if(finalBlurStrength > 0) filter(BLUR, 1);
186+
187+
if(saveImage) {
188+
Date date = new Date();
189+
save(String.format("data/%d_gs%d_dp%d.png",
190+
date.getTime(), groupSize, dataPointSize
191+
));
192+
}
193+
194+
loopsToDo--;
195+
println(String.format("Done; Loop %d/%d",
196+
loopSize - loopsToDo, loopSize
197+
));
198+
199+
if(loopsToDo <= 0) {
200+
noLoop();
201+
return;
202+
}
203+
204+
reset();
205+
206+
}
207+
208+
}

0 commit comments

Comments
 (0)