13

Steiner Chains are a set of N circles where each circle is tangent to 2 other non-intersecting circles as well as the the previous and next circles of the chain, as seen in the below images:

Order 3 Order 5 Order 7

In this challenge, you will write a program/function that draws Steiner chains recursively, that is, circles of a given chain will be the base circles of another iteration of chains:

enter image description here

Challenge

Write a program/function that accepts image dimensions and a list of integers denoting the level of circles in each successive iteration of chains, and output an image with the recursive Steiner chains drawn to it.

Input

Your program/function will accept 2 arguments:

  • s - width and height of image
  • ls - list of positive integers denoting the number of circles present in each successive iteration of chains, ordered from the top-most chain to the bottom-most chain

Output

Your program/function will output an image of dimension s x s displaying the recusive Steiner chain.

  • The top level base circle will be as large as the image with a diameter of s, centered inside the image
  • To make things easy, the 2 base circles of a Steiner chain will be concentric, that is, the centerpoints of the 2 baseline circles will be the same
  • Given an outer radius, R, and the number of circles in a chain, N, the formula for the inner radius R' is R' = (R-R*sin(pi/N))/(sin(pi/N)+1)
  • Circles of the chain as well as the inner base circle will be the outer base circles of the next iteration of chains
  • While recursing through the chain circles, the order of the next chain should correspond to the next value in ls
  • While recursing through the inner circle of a chain, the order should be the same as its parents order (example [5,2]):
  • Order 5.2
  • All chains should end recursion at a depth of the length of ls
  • The rotation of the chains doesn't matter:
  • Rotation 1 Rotation 2
  • However, the rotations of recursive chains relative to their parents centerpoint should be the same:
  • Order 5.2 Invalid Order 5.2
  • All circles should be drawn with an outline or solid fill
  • Color choice is left to the implementation, save for loopholes (for example, filling everything with the same color)

Example Runs

In the following examples, color is determined by (depth of the recursion)^4.

You can find source here.

chain(600,[5,4,3])

5.4.3

chain(600,[11,1,1,1,1,1,1])

11.1.1.1.1.1.1

chain(600,[5,6,7,8,9])

5.6.7.8.9

CC BY-SA 3.0
1

2 Answers 2

4

Javascript ES6, 379 bytes

This solution was used to generate the example runs in the question.

f=(s,ls)=>{with(V=document.createElement`canvas`)with(getContext`2d`)with(Math)return(width=height=s,translate(s/=2,s),(S=(o,d=0,n=ls[d],i=(o-o*sin(PI/n))/(sin(PI/n)+1),r=0)=>{fillStyle=`rgba(0,0,0,${pow(d/ls.length,4)})`;beginPath(),arc(0,0,o,-PI,PI),fill();if(d++<ls.length){S(i,d,n);for(;r<n;++r){save();translate(0,(o+i)/2);S((o-i)/2,d);restore();rotate((2*PI)/n);}}})(s),V)}

Ungolfed:

f=(s,ls)=>{                                        // define function that accepts image dimensions and a list of orders
 with(V=document.createElement`canvas`)            // create canvas to draw on, bring its functions into current scope chain
 with(getContext`2d`)                              // bring graphics functions into current scope chain
 with(Math)return(                                 // bring Math functions into current scope chain
  width=height=s,                                  // set width and height of image
  translate(s/=2,s),                               // center the transform on image
   (S=(o,d=0,                                      // define recursive function that accepts outer radius, depth, and optionally order
       n=ls[d],                                    // default chain order to corresponding order in input list
       i=(o-o*sin(PI/n))/(sin(PI/n)+1),            // calculate inner base circle radius
       r=0)=>{                                     // initialize for loop var
    fillStyle=`rgba(0,0,0,${pow(d/ls.length,4)})`; // fill based on depth
    beginPath(),arc(0,0,o,-PI,PI),fill();          // draw circle
    if(d++<ls.length){                             // if within recursion limit
     S(i,d,n);                                     //   recurse on inner circle
     for(;r<n;++r){                                //   loop through all circles of the chain
      save();                                      //   save transform
      translate(0,(o+i)/2);                        //   translate origin to middle of the 2 base circles
      S((o-i)/2,d);                                //   recurse on chain circle
      restore();                                   //   restore transform
      rotate((2*PI)/n);                            //   rotate transform to next circle in chain
   }}})(s),                                        // begin the recursion
 V)}                                               // return the canvas

Note: f returns a canvas.

Example run (assumes there's a <body> to append to):

document.body.appendChild(f(600,[13,7,11,5,3]))

Should dump the following image to the page:

Output

CC BY-SA 3.0
0

@Dendrobium

I was trying to port @Dendrobium's Javascript answer in Python.

But the resolution of my fractal image is terrible.

from PIL import Image, ImageDraw
import numpy as np
import matplotlib.pyplot as plt

def draw_fractal(s, ls):
    # Create a new image with a white background
    image = Image.new('RGBA', (s, s), color = (0, 0, 0, 0))
    draw = ImageDraw.Draw(image)
    
    # Begin the recursion
    draw_fractal_recursive(draw, s/2, s/2, s/2, 0, ls, s)
    
    # Display the image
    plt.imshow(np.array(image))
    plt.show()

def draw_fractal_recursive(draw, x, y, o, depth, ls, s):
    if depth >= len(ls):
        return

    # Chain order to corresponding order in input list
    n = ls[depth]
    
    # Calculate inner base circle radius
    i = (o-o*np.sin(np.pi/n))/(np.sin(np.pi/n)+1)
    
    # Fill based on depth
    rgba_color = (0, 0, 0, int(255 * np.power(depth/len(ls), 4)))

    # Draw circle
    draw.ellipse((x-o, y-o, x+o, y+o), fill=rgba_color)

    # Recurse on inner circle
    draw_fractal_recursive(draw, x, y, i, depth+1, ls, s)

    # Loop through all circles of the chain
    for r in range(n):
        # Translate origin to middle of the 2 base circles
        new_x = x + (o+i)/2 * np.cos(2 * np.pi * r / n)
        new_y = y + (o+i)/2 * np.sin(2 * np.pi * r / n)
        
        # Recurse on chain circle
        draw_fractal_recursive(draw, new_x, new_y, (o-i)/2, depth+1, ls, s)

# Call the function
draw_fractal(600,[13,7,11,5,3])

enter image description here

CC BY-SA 4.0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.