In the last post I mentioned I’d like to look at enlarging the values for one of the rows to create larger colour areas. So far that hasn’t worked out so well. All too often the enlarged areas overwhelm the rest of the image. But, you may as well see what is happening.

Enlarging Coloured Areas

Seemed simple enough. Just multiply one of the two datasets by some value for each set of colours.

Some Code

I decided to start with a small number of multipliers. And none of them too large at least to start. With a slight probability of 1 being chosen more often. Also, in order to ensure images are saved to file properly, I added some global variables.

p_mlt1 = [i / 10 for i in range(10, 17, 1)]
p_mlt1.extend([1.0, 1.0])

# need to keep track of multiplied data to save images correctly
c_mlts, i_data = [], []

Then a function to generate a list of multipliers and apply it to the dataset passed to the function.

def incr_data(a_rows):
  # multiply data rows by random value between 1 and 2 inclusive
  # only going to use a few values, with prob of 1 slightly higher than rest
  m_rows = []
  mlts = []
  for d_rw in a_rows:
    c_mlt = random.choice(p_mlt1)
    mlts.append(c_mlt)
    # print(f"\t\t\tDEBUG {do_plt}: multiplier is {c_mlt}")
    m_rows.append(d_rw * c_mlt)
  return mlts, m_rows

To allow some control I added another input request in the code for plot tye 40.

        elif cnt_inp == 8:
          tu_ml = input(f"\n\tApply random multiples to 2nd data row, y or n; default {u_ml}: ").lower()
          if tu_ml.lower() == 'q':
            cont_inp = False
            continue
          if tu_ml != '':
            if tu_ml not in ['y', 'n']:
              u_ml = random.choice([True, False])
            else:
              u_ml = True if tu_ml == 'y' else False
          print(f"\t\tmultiplier will be applied: {u_ml}")

And finally in the image generating functions, I call `incr_data()`` as appropriate. Here’s one of the modified functions.

def btw_rnd(axt, bc='m', r=False, fix=None, mlt=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
  global c_mlts, i_data

  # for now assume fix is None or a valid index value into r_xs

  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)
  print(f"\tbtw_rnd(bc='{bc}', r={r}, fix={fix}, mlt={mlt}) -> base row: {b_rw}")

  if not t_sv:
    if mlt:
      c_mlts, i_data = incr_data(r_ys, b_rw)
    else:
      c_mlts = [1.0 for _ in range(len(r_ys))]
      i_data = r_ys
    print(f"\t\t\tDEBUG: mlt={mlt}, c_mlts={c_mlts}")

  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)
  if bc == 'rr':
    r_btw = list(r_btw)
    random.shuffle(r_btw)

  for i in r_btw:
    if i == b_rw and c_mlts[i] == 1.0:
      continue
    if fix is not None:
      print(f"\t\tax.fill_between(r_xs[{fix}], r_ys[{i}]*{c_mlts[i]}, r_ys[{b_rw}], alpha={alph})")
      axt.fill_between(r_xs[fix], i_data[i], r_ys[b_rw], alpha=1, color=cycle[c_ndx])
      c_ndx = (c_ndx + n_fs) % c_len
    else:
      print(f"\t\tax.fill_between(r_xs[{i}], r_ys[{i}]*{c_mlts[i]}, r_ys[{b_rw}], alpha={alph})")
      axt.fill_between(r_xs[i], i_data[i], r_ys[b_rw], alpha=1, color=cycle[c_ndx])
      c_ndx = (c_ndx + n_fs) % c_len
  
  return b_rw

Examples

btw_rnd(bc='e', r=False, fix=None, mlt=True) -> base row: 4
gnarly spirograph images generated with matplotlib fill_between(), with variations between which rows the colouring occurs

And, my debug printing shows the specific multipliers:

ax.fill_between(r_xs[0], r_ys[0]*1.3, r_ys[4], alpha=0.62)
ax.fill_between(r_xs[1], r_ys[1]*1.5, r_ys[4], alpha=0.62)
ax.fill_between(r_xs[2], r_ys[2]*1.6, r_ys[4], alpha=0.62)
ax.fill_between(r_xs[3], r_ys[3]*1.5, r_ys[4], alpha=0.62)
ax.fill_between(r_xs[4], r_ys[4]*1.3, r_ys[4], alpha=0.62)
btw_rnd(bc='e', r=False, fix=None, mlt=True) -> base row: 4
gnarly spirograph images generated with matplotlib fill_between(), with variations between which rows the colouring occurs
ax.fill_between(r_xs[0], r_ys[0]*1.2, r_ys[4], alpha=0.63)
ax.fill_between(r_xs[1], r_ys[1]*1.0, r_ys[4], alpha=0.63)
ax.fill_between(r_xs[2], r_ys[2]*1.5, r_ys[4], alpha=0.63)
ax.fill_between(r_xs[3], r_ys[3]*1.3, r_ys[4], alpha=0.63)
ax.fill_between(r_xs[4], r_ys[4]*1.1, r_ys[4], alpha=0.63)

Not entirely without merit, but not really what I was hoping for. I looked at the code but could not come up with a way to determine when/where to apply a multiplier for greatest effect.

Attempt #2

Eventually, I decided to try only applying a multiplier to the earliest plotted elements. Say the first half or so. And, I decided to use slightly larger multipliers.

p_mlt2 = [i / 10 for i in range(12, 21, 2)]

def incr_data(a_rows):
  # multiply data rows by random value between 1.2 and 2 inclusive
  m_rows = []
  mlts = []
  n_rw = len(a_rows)
  r_hlf = n_rw // 2
  for i in range(n_rw):
    if i <= r_hlf:
      c_mlt = random.choice(p_mlt2)
      mlts.append(c_mlt)
      m_rows.append(a_rows[i] * c_mlt)
    else:
      c_mlt = 1.0
      mlts.append(c_mlt)
      m_rows.append(a_rows[i])
  return mlts, m_rows
btw_rnd(bc='e', r=False, fix=None, mlt=True) -> base row: 7
gnarly spirograph images generated with matplotlib fill_between(), with variations between which rows the colouring occurs
ax.fill_between(r_xs[0], r_ys[0]*1.2, r_ys[7], alpha=0.64)
ax.fill_between(r_xs[1], r_ys[1]*1.8, r_ys[7], alpha=0.64)
ax.fill_between(r_xs[2], r_ys[2]*1.6, r_ys[7], alpha=0.64)
ax.fill_between(r_xs[3], r_ys[3]*1.6, r_ys[7], alpha=0.64)
ax.fill_between(r_xs[4], r_ys[4]*1.0, r_ys[7], alpha=0.64)
ax.fill_between(r_xs[5], r_ys[5]*1.0, r_ys[7], alpha=0.64)
ax.fill_between(r_xs[6], r_ys[6]*1.0, r_ys[7], alpha=0.64)

And, with the 2nd plot function.

btw_n_apart(dx=1, ol=True, r=False, fix=None, mlt=True)
gnarly spirograph images generated with matplotlib fill_between(), with variations between which rows the colouring occurs
ax.fill_between(r_xs[1], r_ys[1], r_ys[2]*1.8, alpha=0.65) <- 1 apart
ax.fill_between(r_xs[2], r_ys[2], r_ys[3]*1.8, alpha=0.65) <- 1 apart
ax.fill_between(r_xs[3], r_ys[3], r_ys[4]*1.0, alpha=0.65) <- 1 apart
ax.fill_between(r_xs[4], r_ys[4], r_ys[5]*1.0, alpha=0.65) <- 1 apart

Well, now I am beginning to believe this idea has some real potential in certain cases.

More Colour Patches

For an attempt at a mosiac like image, there just weren’t enough colour patches being generated. So, that’s what I am going to try and change next. I figured the easiest approach was to just plot each set of data in pieces and use a different colour (more or less) for each piece.

Some Code

I started by increasing the number of colours available to my plotting functions. I had been using 32. I have moved that up to 48. Don’t want too few and don’t want too may.

I added another command line input request to plot type 40. And, of course, some additional module variables.

elif cnt_inp == 7:
  tu_sc = input(f"\n\tHow many colour sections per curve; default {u_sc}: ")
  if tu_sc.lower() == 'q':
    cont_inp = False
    continue
  if tu_sc.isnumeric():
    u_sc = int(tu_sc)
  else:
    print(f"\t\tinput for number sections not valid, using currentn default {u_sc}")

Then I modified my plotting functions to behave accordingly. Here’s an example. I plot the last section outside the loop, so that I could use slice indexing of the type [x:]. Seemed the simplest way to deal with a final section of a different size than the rest.

def btw_n_apart_x(axt, dx=2, ol=True, r=False, fix=None, mlt=False, sect=1):
  global c_mlts, i_data

  # between 0,0+dx ; 1,1+dx; etc
  print(f"\tbtw_n_apart_x(x='{dx}', ol={ol}, r={r}, fix={fix}, mlt={mlt}, sect={sect})")
  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(2, n_fs, 2) 
  else:
    if r:
      b_rng = range(n_fs, 0, -1)
    else:
      b_rng = range(1, n_fs)

  if not t_sv:
    if mlt:
      c_mlts, i_data = incr_data(r_xs)
    else:
      c_mlts = [1.0 for _ in range(len(r_xs))]
      i_data = r_xs
    print(f"\t\t\tDEBUG: mlt={mlt}, c_mlts={c_mlts}")

  s_sz = len(r_xs[0]) // sect

  for i in b_rng:
    if i == i+dx and c_mlts[i] == 1.0:
      continue
    if fix is not None:
      print(f"\t\tax.fill_betweenx(r_ys[{fix}], r_xs[{i}], r_xs[{i+dx}]*{c_mlts[i+dx]}, alpha={alph}) <- {dx} apart")
      axt.fill_betweenx(r_ys[fix], r_xs[i], i_data[i+dx], alpha=1, color=cycle[c_ndx])
    else:
      if sect == 1:
        print(f"\t\tax.fill_betweenx(r_ys[{i}], r_xs[{i}], r_xs[{i+dx}]*{c_mlts[i+dx]}, alpha={alph}) <- {dx} apart")
        axt.fill_betweenx(r_ys[i], r_xs[i], i_data[i+dx], alpha=1, color=cycle[c_ndx])
        c_ndx = (c_ndx + n_fs) % c_len
      else:
        for j in range(sect-1):
          s_st = s_sz * j
          s_nd = s_sz * (j + 1)
          print(f"\t\tax.fill_betweenx(r_ys[{i}][{s_st}:{s_nd}], r_xs[{i}][{s_st}:{s_nd}], r_xs[{i+dx}][{s_st}:{s_nd}]*{c_mlts[i+dx]}, alpha={alph}) <- {dx} apart")
          axt.fill_betweenx(r_ys[i][s_st:s_nd], r_xs[i][s_st:s_nd], i_data[i+dx][s_st:s_nd], alpha=1, color=cycle[c_ndx])
          c_ndx = (c_ndx + n_fs) % c_len

        print(f"\t\tax.fill_between(r_ys[{i}][{s_nd}:], r_xs[{i}][{s_nd}:], r_xs[{i+dx}][{s_nd}:]*{c_mlts[i+dx]}, alpha={alph}) <- {dx} apart")
        axt.fill_between(r_ys[i][s_nd:], r_xs[i][s_nd:], i_data[i+dx][s_nd:], alpha=1, color=cycle[c_ndx])

Examples

All of the following examples are based on the following base curve. I used 1024 data points for each data row so that they would be evenly divisible by 16 and 32.

wheels: 4 (Ellipse), k_f: 4, cgv: 1, points: 1024
widths: [1, 0.6503102540243678j, 0.4222906117613383j, 0.29369367562909154j],
heights: [1, 0.7157412210254107, 0.5865546100568727j, 0.5838976346880688],
freqs: [-3, 13, 13, 13]
btw_n_apart(dx=1, ol=True, r=False, fix=None, mlt=False, sect=16)
gnarly spirograph images generated with matplotlib fill_between(), with variations between which rows the colouring occurs

I’m liking this. But, I sure can’t figure out why I am getting those gaps. No matter, they add their own interest to the image.

Let’s do that with the colouring going in the ‘x-axis` direction.

btw_n_apart_x(dx=1, ol=True, r=False, fix=None, mlt=False, sect=16)
gnarly spirograph images generated with matplotlib fill_between(), with variations between which rows the colouring occurs

You’d never know those two were for all practical purposes the same spirograph.

Let’s drop the data row overlapping.

btw_n_apart_x(dx=1, ol=False, r=False, fix=None, mlt=False, sect=16)
gnarly spirograph images generated with matplotlib fill_between(), with variations between which rows the colouring occurs

Well, I think one can tell that the above two are some how related.

Let’s try the other image function type.

btw_rnd(bc='e', r=False, fix=None, mlt=False, sect=16) -> base row: 3
gnarly spirograph images generated with matplotlib fill_between(), with variations between which rows the colouring occurs

That looks a fair bit like the first one. But…

Let’s try using the middle row.

btw_rnd(bc='m', r=False, fix=None, mlt=True, sect=16) -> base row: 2
gnarly spirograph images generated with matplotlib fill_between(), with variations between which rows the colouring occurs

And, reverse the plotting order. To me, a very strange rearrangement of things.

btw_rnd(bc='m', r=True, fix=None, mlt=True, sect=16) -> base row: 2
gnarly spirograph images generated with matplotlib fill_between(), with variations between which rows the colouring occurs

Back to the other plotting function. Using dx=2, no overlap and in reverse order.

btw_n_apart(dx=2, ol=False, r=True, fix=None, mlt=True, sect=16)
gnarly spirograph images generated with matplotlib fill_between(), with variations between which rows the colouring occurs

And, finally, one with 32 sections. Though you’d be hard pressed to tell. Expect the particular colour map may have something to do with that.

btw_rnd(bc='m', r=False, fix=None, mlt=False, sect=32) -> base row: 2
gnarly spirograph images generated with matplotlib fill_between(), with variations between which rows the colouring occurs

Done

A bit of code, a number of images—likely enough for this post.

Did want to see what reducing the symmetry would look like, but think I will save that for another short post. May also consider checking out larger multipliers. Possibly for a greater number of data rows.

Until then, enjoy your play time.