Sierpinsky Triangle
Mix.install([
{:kino_vega_lite, "~> 0.1.13"},
{:vega_lite, "~> 0.1.11"},
{:kino, "~> 0.15.3"}
])
Section
alias VegaLite, as: Vl
alias Kino.VegaLite, as: KVl
import Kino.Shorts
defmodule Sierpinsky do
# The Sierpinsky triangle is a type of fractal. The traditional method of
# constructing it involves taking an equilateral triangle and repeatedly
# removing the central portion, carrying on this process indefinitely with
# each of the smaller triangles that form.
# The approach we're using here is a bit different.
# We begin by plotting a random point within the
# triangle, then calculate the midpoint between this point and one of the
# vertices (chosen at random). After marking this midpoint, we simply
# carry on with the process, using our newly plotted point as the starting
# position and once again finding the midpoint between it and a randomly
# selected vertex.
@dim 150
# Initialize the chart
def init_chart do
Vl.new(width: @dim * 2, height: @dim * 2)
|> Vl.mark(:point, size: 1)
|> Vl.encode_field(:x, "x", type: :quantitative)
|> Vl.encode_field(:y, "y", type: :quantitative)
|> KVl.new()
end
def init_vertexes() do
vertex_y = :math.sqrt(@dim * @dim - @dim * @dim / 4)
[%{x: 0, y: 0, iter: 0}, %{x: @dim, y: 0, iter: 0}, %{x: @dim / 2, y: vertex_y}]
end
def refresh(chart, vertexes) do
KVl.clear(chart)
KVl.push_many(chart, vertexes)
end
# Calculate the midpoint between two points
def midpoint(point1, point2) do
%{x: (point1[:x] + point2[:x])/2, y: (point1[:y] + point2[:y])/2}
end
# draw the next point in the fractal
def next_point(vertexes, [point | list]) do
# chose a midpoint between the previous point and a randomly selected vertex
next_point = midpoint(Enum.at(vertexes, Enum.random(0..2)), point)
[next_point, point | list]
end
def draw(chart, vertexes, iterations) do
for _ <- 0..trunc(iterations/100) do
points = Enum.reduce(1..100, [%{x: 0, y: 0, iter: 0}],
fn _i, point_list ->
next_point(vertexes, point_list)
end)
KVl.push_many(chart, points)
end
end
end
# initialize the frame
vertexes = Sierpinsky.init_vertexes()
chart = Sierpinsky.init_chart()
# initialize the buttons
input = Kino.Input.range("Iterations", min: 100, max: 60000, step: 100)
start = Kino.Control.button("Start")
clear = Kino.Control.button("Clear")
chart_layout = Kino.Layout.grid([chart], columns: 1)
button_layout1 = Kino.Layout.grid([input], columns: 1)
button_layout2 = Kino.Layout.grid([start, clear], columns: 2)
button_layout = Kino.Layout.grid([button_layout1, button_layout2], columns: 1, boxed: true)
layout = Kino.Layout.grid([chart_layout, button_layout], columns: 2)
stream = Kino.Control.tagged_stream([iterations: input, start: start, clear: clear])
# render the frame and listen for events
frame = Kino.Frame.new() |> Kino.render()
Kino.Frame.render(frame, layout)
Kino.listen(stream, %{iterations: 100}, fn
{:iterations, event}, state ->
{:cont, %{state | iterations: trunc(event.value)}}
{:start, _}, state ->
Sierpinsky.draw(chart, vertexes, state.iterations)
{:cont, state}
{:clear, _}, state ->
Sierpinsky.refresh(chart, vertexes)
{:cont, state}
end)