This series just does not want to quit. Well, in truth, I don’t want to quit playing with spirographs! Whether or not they are in truth any longer all spirographs.

Well, let’s move on with that idea of creating somesort of a “mosiac” image variation. As mentioned previously, I have so far been way to lazy to try and figure out a way to identify all the fragments in a spirograph plot. Then figuring out how to colour them might prove to be an issue as well. Guthrie’s problem notwithstanding.

But as mentioned I came across matplotlib’s matplotlib.pyplot.fill_between. Consequently, this is likely going to be a reasonably short post.

I decided I would start by plotting between the first row and all the other rows. Another plot type, 27, for the purposes of this post. In this case we are colouring between the y values for any give x. We can, I believe, also plot colours between the x values. Perhaps something to also have a look at. Though don’t know if that will work with this dataset.

    elif do_plt == 27:
      c_ndx = 0
      # was having trouble with IndexError: list index out of range
      # for some reason I have lc_frq = 32 for this type of plot
      c_len = len(cycle)
      # between 0 and all the others
      n_fs = len(r_xs) - 1
      for i in range(n_fs, 0, -1):
        print(f"DEBUG {do_plt}: ax.fill_between(r_xs[{i}], r_ys[{i}], r_ys[0], alpha=alph)")
        # ax.fill_between(r_xs[i], r_ys[i], r_ys[i+1], alpha=alph, color=cycle[c_ndx])
        ax.fill_between(r_xs[i], r_ys[i], r_ys[0], alpha=alph, color=cycle[c_ndx])
        # c_ndx += 1
        # not enough colour variation, use a bigger jump
        c_ndx = (c_ndx + n_fs) % c_len

Short and sweet. An example or two. Difference between these two is values for k-fold, wheel frequencies, wheel sizes, etc.

wheels: 6 (['t', 't', 'r', 't', 'c', 'c']), alpha = 0.87
gnarly spirograph images plotted with colors between different data rows
wheels: 6 (['t', 't', 'r', 't', 'c', 'c']), alpha = 0.74
gnarly spirograph images plotted with colors between different data rows

Wasn’t sure about having transparency (another one of those night time thoughts). So plotted one without any transparency.

Wheels: 11 rhombus, alpha = 1.0
gnarly spirograph images plotted with colors between different data rows

A touch stark. Though the colour map may have something to do with that situation. Have decided, at this time and for plots of this type, to limit the alpha value to a random value between 0.6 and 0.86 inclusive. Added some code to look after that.

Wheels: 5 ellipses, alpha = 0.86
gnarly spirograph images plotted with colors between different data rows

Colour Against the Last Row

Well, what if we add colour between each row and the last row rather than the first row. Or, between adjacent rows, or… Looks, like it is time to write a function or three.

Functions and Refactoring

And, that’s exactly what I started doing. Very quickly had half a dozen or more. Then I did a wee refactor and cut that down by half. Then after a night’s rest and bit of nighttime thinking and a bit of double checking and debugging, got that down to two.

def btw_rnd(axt, bc='m', r=False):
  # between bc (m = middle, s = start, e = end, r = random) and all the others
  # thought about changing r to a number and removing the random selection in
  # the function, but for now leaving as is
  if bc == 'm':
    b_rw = n_whl // 2
  elif bc == 's':
    b_rw = 0
  elif bc == 'e':
    b_rw = n_whl - 1
  else:
    b_rw = random.randint(1, n_whl - 2)

  c_ndx = 0
  c_len = len(cycle)
  c_jmp = c_len // 5
  if c_jmp % 2 == 0:
    c_jmp += 1

  n_fs = len(r_xs)
  if r:
    n_fs -= 1
  if r:
    r_btw = range(n_fs, -1, -1)
  else:
    r_btw = range(0, n_fs)

  for i in r_btw:
    if i == b_rw:
      continue
    axt.fill_between(r_xs[i], r_ys[i], r_ys[b_rw], alpha=alph, color=cycle[c_ndx])
    c_ndx = (c_ndx + n_fs) % c_len
  
  return b_rw


def btw_x_apart(axt, dx=2, ol=True, r=False):
  # between 0,0+dx ; 1,1+dx; etc
  c_ndx = 0
  c_len = len(cycle)

  if r:
    n_fs = len(r_xs) - dx - 1
  else:
    n_fs = len(r_xs) - dx
  if dx == 1 and not ol:
    if r:
      b_rng = range(n_fs, -1, -2)
    else:
      b_rng = range(0, n_fs, 2) 
  else:
    if r:
      b_rng = range(n_fs, -1, -1)
    else:
      b_rng = range(0, n_fs)

  for i in b_rng:
    if i == i+dx:
      continue
    axt.fill_between(r_xs[i], r_ys[i], r_ys[i+dx], alpha=alph, color=cycle[c_ndx])
    c_ndx = (c_ndx + n_fs) % c_len

To make sure the two final functions in fact could replace all the earlier functions I created some new plot types to plot the development function output against the appropriate production function with the appropriate parameters. I won’t show you all those, suffice it to say that as near as my eyes could tell (well, and my debug prints to the terminal window) the outputs were eventually identical for all cases.

I then reworked the new plot types to show the output for a variety of cases (4 sub plots for each). I will provide a few examples.

Examples

In the following plot, the top two show the result of colouring between the first row and all the others. The last two show what happens when we colour between the last row and all the others. The second image in each case reverses the order in which things are added to the image.

Plot type 32, alpha: 0.95
gnarly spirograph images generated with matplotlib fill_between(), with variations between which rows the colouring occurs

The following colour between adjacent rows, with or without overlap. I.E. overlap generates 1-2, 2-3, etc.; no overlap generates 1-2, 3-4, etc. So, the number of wheels significantly affects the result in the latter case. Again, the second in each case reverses the order in which the colours are added to image.

Plot type 33, alpha: 0.95
gnarly spirograph images generated with matplotlib fill_between(), with variations between which rows the colouring occurs

Sorry about the repetition in this next one. Forgot to refactor my code so that the second two images would have a different dx value.

Here the selected rows are some distance, dx, apart. So the colouring is done between 1-1+x, 2-2+x, etc. Again the second in each pair reverses plotting order.

Plot type 34, alpha: 0.95
gnarly spirograph images generated with matplotlib fill_between(), with variations between which rows the colouring occurs

In this bunch we select some row, other than the first or last, to use with all the other rows. ’m’ means the middle (more or less, for odd numbers of wheels it will be the integer value below the result of dividing by two). ‘r’ tells the function to select a random row. In this case I did refactor the code to make sure a different value was selected. Why not for the above image — slow-witted I guess.

r=False: plot front to back. r=True: plot back to front. Not sure front and back are correct in this context; but, I’m sure it gets the intended meaning across.

Plot type 35, alpha: 0.95
gnarly spirograph images generated with matplotlib fill_between(), with variations between which rows the colouring occurs

Refactor Plot 27

I now had a whole lot of options. I certainly didn’t want to create a new plot type for each one. That could go on forever. So, for the short term I decided to have one of my current variations generated in sequence by plot type 27. To do so, I take the modulus of the variable lp_cnt against the current number of options. Something like which = lp_cnt % nbr_opts. lp_cnt is incremented every iteration of the spiro_fini.py command line interface (in the terminal window). So, if I continuously execute plot 27 with the same curve data, I will for nbr_opts iterations get a different variation. At which point it will start going through them again. But, colours and other variables would likely change.

The real problem is now I have a lenghty if-elif... block. Will leave it that way for now. But, should likely figure out some way to code a single block using functions and random or fixed parameters each time through. A serious lack of user control, but I am still in development mode.

Done

I think I will leave that refactoring for another day. Seems to me that this post is plenty long enough. Though perhaps short on really meaningful content.

And, I have just started Harvard’s CS50 Introduction to AI. So, once again, lots of time constraints in my life.

Until next time, enjoy your time coding.

Resources