0

I'm making a plot to compare band structure calculations from two different methods. This means plotting multiple lines for each set of data. I want to have a set of widgets that controls each set of data separately. The code below works if I only plot one set of data, but I can't get the widgets to work properly for two sets of data.

#!/usr/bin/env python3

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, TextBox

#cols = ['blue', 'red', 'green', 'purple']
cols = ['#3f54bf','#c14142','#59bf3f','#b83fbf']

finam = ['wan_band.dat','wan_band.pwx.dat']
#finam = ['wan_band.dat'] # this works

lbot = len(finam)*0.09 + 0.06
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=lbot)
ax.margins(x=0) # lines go to the edge of the horizontal axes

def setlines(lines, txbx1, txbx2):
    ''' turn lines on/off based on text box values '''
    try:
        mn = int(txbx1) - 1
        mx = int(txbx2) - 1
        for ib in range(len(lines)): 
            if (ib<mn) or (ib>mx):
                lines[ib].set_visible(False)
            else :
                lines[ib].set_visible(True)

        plt.draw()
    except ValueError as err:
        print('Invalid range')
#end def setlines(cnt, lines, txbx1, txbx2):

def alphalines(lines, valin):
    ''' set lines' opacity '''
    maxval = int('ff',16)
    maxval = hex(int(valin*maxval))[2:]
    for ib in range(bcnt):
        lines[ib].set_color(cols[cnt]+maxval)
    plt.draw()
#end def alphalines(lines, valtxt):

lines  = [0]*len(finam) # 2d list to hold Line2Ds
txbox1 = [0]*len(finam) # list of Lo Band TextBoxes
txbox2 = [0]*len(finam) # lsit of Hi Band TextBoxes
alslid = [0]*len(finam) # list of Line Opacity Sliders

for cnt, fnam in enumerate(finam):

    ptcnt = 0 # point count
    fid   = open(fnam, 'r')
    fiit  = iter(fid)
    for line in fiit:
        if line.strip() == '' :
            break
        ptcnt += 1
    fid.close()

    bandat_raw = np.loadtxt(fnam)
    bcnt = int(np.round((bandat_raw.shape[0] / (ptcnt))))

    print(ptcnt)
    print(bcnt)

    # get views of the raw data that are easier to work with
    kbandat = bandat_raw[:ptcnt,0]                       # k point length along path
    ebandat = bandat_raw.reshape((bcnt,ptcnt,2))[:,:,1]  # band energy @ k-points

    lines[cnt] = [0]*bcnt # point this list element to another list
    for ib in range(bcnt):
        #l, = plt.plot(kbandat, ebandat[ib], c=cols[cnt],lw=1.0)
        l, = ax.plot(kbandat, ebandat[ib], c=cols[cnt],lw=1.0)
        lines[cnt][ib] = l

    y0 = 0.03 + 0.07*cnt
    bxht = 0.035
    axbox1 = plt.axes([0.03, y0, 0.08, bxht]) # x0, y0, width, height
    axbox2 = plt.axes([0.13, y0, 0.08, bxht])
    txbox1[cnt] = TextBox(axbox1, '', initial=str(1))
    txbox2[cnt] = TextBox(axbox2, '', initial=str(bcnt))
    txbox1[cnt].on_submit( lambda x: setlines(lines[cnt], x, txbox2[cnt].text) ) 
    txbox2[cnt].on_submit( lambda x: setlines(lines[cnt], txbox1[cnt].text, x) ) 

    axalpha = plt.axes([0.25, y0, 0.65, bxht])
    alslid[cnt] = Slider(axalpha, '', 0.1, 1.0, valinit=1.0)
    salpha = alslid[cnt]
    alslid[cnt].on_changed( lambda x: alphalines(lines[cnt], x) )

#end for cnt, fnam in enumerate(finam):

plt.text(0.01, 1.2, 'Lo Band', transform=axbox1.transAxes)
plt.text(0.01, 1.2, 'Hi Band', transform=axbox2.transAxes)
plt.text(0.01, 1.2, 'Line Opacity', transform=axalpha.transAxes)
plt.show()

All the widgets only control the last data set plotted instead of the individual data sets I tried to associate with each widget. Here is a sample output: enter image description here

Here the bottom slider should be changing the blue lines' opacity, but instead it changes the red lines' opacity. Originally the variables txbox1, txbox2, and alslid were not lists. I changed them to lists though to ensure they weren't garbage collected but it didn't change anything.

Here is the test data set1 and set2 I've been using. They should be saved as files 'wan_band.dat' and 'wan_band.pwx.dat' as per the hard coded list finam in the code.

mTesseracted
  • 290
  • 1
  • 10
  • If requested I can remove the TextBoxes to simplify it and just use the sliders to present the problem, which will shorten the code by ~24 lines. – mTesseracted Jul 17 '19 at 05:38

1 Answers1

0

I figured it out, using a lambda to partially execute some functions with an iterator value meant they were always being evaluated with the last value of the iterator. Switching to functools.partial fixed the issue.

mTesseracted
  • 290
  • 1
  • 10