Created
October 11, 2021 18:58
-
-
Save leontabak/33addca961dd51c32f55ac59332f6586 to your computer and use it in GitHub Desktop.
Create clusters of points with Kotlin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.awt.Color | |
import java.awt.Graphics | |
import java.awt.Graphics2D | |
import java.awt.geom.AffineTransform | |
import java.awt.geom.Ellipse2D | |
import javax.swing.JFrame | |
import javax.swing.JPanel | |
import kotlin.math.cos | |
import kotlin.math.sin | |
import kotlin.math.ln | |
import kotlin.math.sqrt | |
import kotlin.math.PI | |
import kotlin.random.Random | |
object ClusterConstants { | |
// specify the size of the window | |
// on the computer's screen | |
const val WIDTH = 768 | |
const val HEIGHT = 768 | |
const val TITLE = "Clusters" | |
val BG_COLOR = Color(40, 40, 72) | |
} // ClusterConstants | |
data class Rectangle( | |
// use this class to specify the | |
// bounds on the virtual world | |
// in which the program draws | |
// a picture | |
val xMin: Double, val yMin: Double, | |
val xMax: Double, val yMax: Double | |
) // Rectangle | |
data class Point(val x: Double, val y: Double, var index: Int = -1) | |
fun makeClusterCenterGenerator( | |
rng: Random, | |
bounds: Rectangle | |
): () -> Point { | |
fun clusterCenterGenerator(): Point { | |
val x = bounds.xMin + | |
(bounds.xMax - bounds.xMin) * rng.nextDouble() | |
val y = bounds.yMin + | |
(bounds.yMax - bounds.yMin) * rng.nextDouble() | |
return Point(x, y) | |
} // clusterCenterGenerator() | |
return ::clusterCenterGenerator | |
} // makeClusterCenterGenerator() | |
fun makeClusterPointGenerator( | |
rng: Random, | |
center: Point, | |
meanRadius: Double | |
): () -> Point { | |
fun clusterPointGenerator(): Point { | |
val distance = meanRadius * ln(rng.nextDouble()) | |
val angle = 2.0 * PI * rng.nextDouble() | |
val x = center.x + distance * cos(angle) | |
val y = center.y + distance * sin(angle) | |
return Point(x, y) | |
} // clusterPointGenerator() | |
return ::clusterPointGenerator | |
} // makeClusterPointGenerator() | |
class PictureCanvas( | |
private val bounds: Rectangle, | |
private val bg: Color | |
) : JPanel() { | |
var center = Point( | |
(bounds.xMin + bounds.xMax) / 2, | |
(bounds.yMin + bounds.yMax) / 2 | |
) | |
var points = mutableListOf<Point>() | |
set(value) { | |
field = value | |
repaint() | |
} | |
init { | |
this.background = bg | |
} // init | |
override fun paintComponent(g: Graphics) { | |
super.paintComponent(g) | |
val g2D = g as Graphics2D | |
val w = this.width | |
val h = this.height | |
// AffineTransforms can rotate, scale, | |
// and rotate shapes---use to make shapes | |
// defined in world coordinates fit in | |
// this panel (whose location and dimensions | |
// differ from the world in which another | |
// part of the program defined shapes) | |
val translate = AffineTransform() | |
translate.translate(-bounds.xMin, -bounds.yMin) | |
val sx = w / (bounds.xMax - bounds.xMin) | |
val sy = h / (bounds.yMax - bounds.yMin) | |
val scale = AffineTransform() | |
scale.scale(sx, sy) | |
val transform = AffineTransform() | |
transform.concatenate(scale) | |
transform.concatenate(translate) | |
val radius = minOf( | |
bounds.xMax - bounds.xMin, | |
bounds.yMax - bounds.yMin | |
) / 200 | |
val palette = listOf( | |
Color(106, 224, 160), | |
Color(192, 120, 224), | |
Color(236, 180, 116), | |
Color(120, 136, 248) | |
) | |
g2D.color = Color.BLUE | |
for (p in points) { | |
val dot = Ellipse2D.Double( | |
p.x - radius, p.y - radius, | |
2 * radius, 2 * radius | |
) | |
when (p.index) { | |
0 -> g2D.color = palette[0] | |
1 -> g2D.color = palette[1] | |
2 -> g2D.color = palette[2] | |
3 -> g2D.color = palette[3] | |
else -> g2D.color = Color.RED | |
} | |
g2D.fill(transform.createTransformedShape(dot)) | |
} // for | |
// make a circle with a specified center | |
// and radius---the constructor's arguments | |
// are the coordinates of the upper left corner | |
// of the smallest rectangle that can hold the ellipse | |
// and the width and length of that rectangle | |
val dot = Ellipse2D.Double( | |
center.x - radius, center.y - radius, | |
2 * radius, 2 * radius | |
) | |
g2D.color = Color.RED // or any other color | |
// fill() draws the outline of the shape and its | |
// interior | |
// must transform shape before drawing it | |
g2D.fill(transform.createTransformedShape(dot)) | |
} // paintComponent() | |
/* | |
override fun repaint() { | |
// if there is a need to redraw the picture | |
// after changing colors, shapes, and so on, | |
// call this method (not paintComponent) | |
} // repaint() | |
*/ | |
} // PictureCanvas | |
class PictureFrame( | |
private val w: Int, private val h: Int, | |
private val bg: Color, title: String | |
) : JFrame(title) { | |
val panel = PictureCanvas( | |
Rectangle(-1.0, -1.0, +1.0, +1.0), | |
bg | |
) | |
init { | |
this.setSize(w, h) | |
this.defaultCloseOperation = JFrame.EXIT_ON_CLOSE | |
this.isVisible = true | |
this.contentPane = panel | |
} // init | |
} // PictureFrame | |
fun makeCluster( | |
rng: Random, | |
center: Point, | |
meanRadius: Double | |
): List<Point> { | |
val g = makeClusterPointGenerator(rng, center, meanRadius) | |
val manyPoints = mutableListOf<Point>() | |
for (i in 0 until 1024) | |
manyPoints.add(g()) | |
return manyPoints | |
} // makeCluster() | |
fun distance(p0: Point, p1: Point): Double { | |
val xDiff = p0.x - p1.x | |
val yDiff = p0.y - p1.y | |
return sqrt(xDiff * xDiff + yDiff * yDiff) | |
} // distance() | |
fun near(p0: Point, p1: Point, threshold: Double): Boolean { | |
return distance(p0, p1) < threshold | |
} // near() | |
fun main() { | |
println("Good morning!") | |
val picture = PictureFrame( | |
ClusterConstants.WIDTH, | |
ClusterConstants.HEIGHT, | |
ClusterConstants.BG_COLOR, | |
ClusterConstants.TITLE | |
) | |
val rng = Random(System.nanoTime()) | |
val bounds = Rectangle( | |
-1.0, -1.0, | |
+1.0, +1.0 | |
) | |
val radius = 0.2 | |
val f = makeClusterCenterGenerator(rng, bounds) | |
val clusterCenter = f() | |
val allPoints = mutableListOf<Point>() | |
for (i in 0 until 4) { | |
val center = f() | |
val manyPoints = | |
makeCluster(rng, center, radius). | |
filter { near(center, it, 2 * radius) }. | |
map { Point(it.x, it.y, i) } | |
allPoints.addAll(manyPoints) | |
} // for | |
picture.panel.points = allPoints | |
} // main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment