Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Sierpinsky Triangle

Sierpinsky/sierpinsky.livemd

Sierpinsky Triangle

Mix.install([
  {:kino_vega_lite, "~> 0.1.13"},
  {:vega_lite, "~> 0.1.11"}
])

Section

alias VegaLite, as: Vl
alias Kino.VegaLite, as: KVl
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 200
  
  # Initialize the chart
  def init do
    chart =
      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.render() 
    vertex_y = :math.sqrt(@dim * @dim - @dim * @dim / 4)
    vertexes = [%{x: 0, y: 0}, %{x: @dim, y: 0}, %{x: @dim / 2, y: vertex_y}]
    {chart, vertexes}
  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

  # first draw, add the vertexes to the graph
  def first_draw(chart, vertexes) do
    KVl.push_many(chart, vertexes)
  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}], 
               fn _i, point_list -> 
                 next_point(vertexes, point_list) 
               end)
      KVl.push_many(chart, points)
    end
  end
end
# initialize the frame
{chart, vertexes} = Sierpinsky.init()
Sierpinsky.first_draw(chart, vertexes)

# create the buttons
frame = Kino.Frame.new() |> Kino.render()
input = Kino.Input.range("Iterations", min: 100, max: 60000)
start = Kino.Control.button("Start") 
clear = Kino.Control.button("Clear") 

layout = Kino.Layout.grid([input, start, clear], columns: 3) 
Kino.Frame.render(frame, layout)

stream = Kino.Control.tagged_stream([iterations: input, start: start, clear: clear]) 

Kino.listen(stream, %{iterations: 100}, fn
  {:iterations, event}, state -> 
    {:cont, %{state | iterations: trunc(event.value)}} 
  {:start, _}, state ->
    IO.puts("Iterations: #{state.iterations}")
   Sierpinsky.draw(chart, vertexes, state.iterations)
    {:cont, state}
  {:clear, _}, state -> 
    Sierpinsky.refresh(chart, vertexes)
    {:cont, state}
  end)