Skip to content

Instantly share code, notes, and snippets.

@leontabak
Created October 11, 2021 18:58
Show Gist options
  • Save leontabak/33addca961dd51c32f55ac59332f6586 to your computer and use it in GitHub Desktop.
Save leontabak/33addca961dd51c32f55ac59332f6586 to your computer and use it in GitHub Desktop.
Create clusters of points with Kotlin
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