def nice_levels(data, approx_nlev=10, max_nlev=28): """Compute a vector of "levels" at "nice" increments. Returns a 1-D array of "levels" (e.g., contour levels) calculated to give an aesthetically pleasing and human-readable interval, if possible. If not, returns levels for approx_nlev levels between the maximum and minimum of data. In any event, the function will return no more than max_nlev levels. Keyword Input Parameter: * data: Array of values to calculate levels for. Can be of any size and shape. Keyword Input Parameter: * approx_nlev: Integer referring to approximately how many levels to return. This is the way of adjusting how "coarse" or "fine" to make the vector of levels. * max_nlev: The maximum number of levels the function will permit to be returned. The interval of levels will be adjusted to keep the number of levels returned under this value. If approx_nlev is chosen to be greater than or equal to max_nlev, an exception is raised. Output: * This function returns a 1-D array of contour levels. Function is adaptation of parts of IDL routine contour_plot.pro by Johnny Lin. This is why the capitalization conventions of Python are not strictly followed in this function. Examples: >>> z = N.array([-24.5, 50.3, 183.1, 20.]) >>> out = nice_levels(z) >>> ['%g' % out[i] for i in range(len(out))] ['-30', '0', '30', '60', '90', '120', '150', '180', '210'] >>> z = N.array([-24.5, 50.3, 183.1, 20.]) >>> out = nice_levels(z, approx_nlev=5) >>> ['%g' % out[i] for i in range(len(out))] ['-50', '0', '50', '100', '150', '200'] >>> z = N.array([-24.5, 50.3, 183.1, 20.]) >>> out = nice_levels(z, approx_nlev=10) >>> ['%g' % out[i] for i in range(len(out))] ['-30', '0', '30', '60', '90', '120', '150', '180', '210'] """ #- Default settings and error check: if approx_nlev >= max_nlev: raise ValueError, 'max_nlev is too small' MAX_zd_ok = N.max(data) MIN_zd_ok = N.min(data) nlevels = N.min([approx_nlev, max_nlev]) tmpcmax = MAX_zd_ok tmpcmin = MIN_zd_ok tmpcint = N.abs((tmpcmax - tmpcmin) / float(nlevels)) #- See if the cint can be "even". If not, return alternative # contour levels vector: #+ Guess a possible cint. Look for an "even" value that is # closest to that: guesscint = N.abs((MAX_zd_ok - MIN_zd_ok) / float(nlevels)) if (guesscint > 1e-10) and (guesscint < 1e+10): possiblecint = [ 1e-10, 1e-9, 1e-8, 1e-7, 1e-6, 1e-5, 0.0001, 0.0002, 0.0005, 0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1., 2., 5., 10., 20., 30., 45., 50., 100., 200., 500., 1000., 2000., 5000., 10000., 20000., 50000., 1e+5, 1e+6, 1e+7, 1e+8, 1e+9, 1e+10 ] diffcint = N.abs(possiblecint - guesscint) tempcint = N.compress(diffcint == N.min(diffcint), possiblecint)[0] tcidx = N.compress(where_close(possiblecint, tempcint), N.arange(N.size(possiblecint)))[0] #+ Look around at the "even" values nearby the possible option # for cint. Calculate how many contours each of those cints # would give. Dictionary ncon_count is the number of # contours for a given test cint. test_tcidxs are the indices # in possiblecint to examine in detail; these index values # will be the keys in ncon_count. if tcidx == 0: tcidx = 1 if tcidx == N.size(possiblecint) - 1: tcidx = N.size(possiblecint) - 2 ncon_count = {} test_tcidxs = [tcidx - 1, tcidx, tcidx + 1] for i in test_tcidxs: itcval = possiblecint[i] positivecon = N.arange(max_nlev + 2, dtype=float) * itcval negativecon = (N.arange(max_nlev + 2, dtype=float) + 1.0) * itcval * (-1.0) if (MAX_zd_ok + itcval >= 0) and (MIN_zd_ok - itcval >= 0): ncon_count[i] = N.sum( \ N.logical_and(positivecon <= MAX_zd_ok + itcval, positivecon >= MIN_zd_ok - itcval) ) elif (MAX_zd_ok + itcval < 0) and (MIN_zd_ok - itcval < 0): ncon_count[i] = N.sum( \ N.logical_and(negativecon <= MAX_zd_ok + itcval, negativecon >= MIN_zd_ok - itcval) ) else: ncon_count[i] = N.sum(positivecon <= MAX_zd_ok + itcval) \ + N.sum(negativecon >= MIN_zd_ok - itcval) #+ Select the cint that has the fewest levels if it has at # least nlevels-1. Otherwise, try to find the next cint with # the fewest levels that is below max_nlev. tempcint is what # you get (changed, if warranted) leaving this section: min_ncon_count = N.min(ncon_count.values()) current_best_count = max_nlev for i in test_tcidxs: if (ncon_count[i] == min_ncon_count) and \ (ncon_count[i] >= nlevels-1): tempcint = possiblecint[i] current_best_count = ncon_count[i] break elif (ncon_count[i] == min_ncon_count) and \ (ncon_count[i] < nlevels-1): continue elif ncon_count[i] > max_nlev: continue else: if N.abs(ncon_count[i]-nlevels) < \ N.abs(current_best_count-nlevels): tempcint = possiblecint[i] current_best_count = ncon_count[i] continue #+ Create levels for case with neg. and pos. contours. There # is the case of all pos., all neg., and mixed pos. and neg. # contours: positivecon = N.arange(max_nlev + 2, dtype=float) * tempcint negativecon = (N.arange(max_nlev + 2, dtype=float) + 1.0) * tempcint * (-1.0) if (MAX_zd_ok + tempcint >= 0) and (MIN_zd_ok - tempcint >= 0): tmpclevels = N.compress( \ N.logical_and(positivecon <= MAX_zd_ok + tempcint, positivecon >= MIN_zd_ok - tempcint), positivecon ) elif (MAX_zd_ok + tempcint < 0) and (MIN_zd_ok - tempcint < 0): tmpclevels = N.compress( \ N.logical_and(negativecon <= MAX_zd_ok + tempcint, negativecon >= MIN_zd_ok - tempcint), negativecon ) else: uppercon = N.compress(positivecon <= MAX_zd_ok + tempcint, positivecon) lowercon = N.compress(negativecon >= MIN_zd_ok - tempcint, negativecon) tmpclevels = N.concatenate([lowercon, uppercon]) #+ Sort clevels, reset number of levels, maximum, minimum, # and interval of contour based on the automatic setting: tmpclevels = N.sort(tmpclevels) if (N.size(tmpclevels) <= max_nlev) and (N.size(tmpclevels) > 0): nlevels = N.size(tmpclevels) tmpcmax = tmpclevels[-1] tmpcmin = tmpclevels[0] tmpcint = tempcint else: pass #- Return output: return N.arange(tmpcmin, tmpcmax + tmpcint, tmpcint)
def nice_levels(data, approx_nlev=10, max_nlev=28): """Compute a vector of "levels" at "nice" increments. Returns a 1-D array of "levels" (e.g., contour levels) calculated to give an aesthetically pleasing and human-readable interval, if possible. If not, returns levels for approx_nlev levels between the maximum and minimum of data. In any event, the function will return no more than max_nlev levels. Keyword Input Parameter: * data: Array of values to calculate levels for. Can be of any size and shape. Keyword Input Parameter: * approx_nlev: Integer referring to approximately how many levels to return. This is the way of adjusting how "coarse" or "fine" to make the vector of levels. * max_nlev: The maximum number of levels the function will permit to be returned. The interval of levels will be adjusted to keep the number of levels returned under this value. If approx_nlev is chosen to be greater than or equal to max_nlev, an exception is raised. Output: * This function returns a 1-D array of contour levels. Function is adaptation of parts of IDL routine contour_plot.pro by Johnny Lin. This is why the capitalization conventions of Python are not strictly followed in this function. Examples: >>> z = N.array([-24.5, 50.3, 183.1, 20.]) >>> out = nice_levels(z) >>> ['%g' % out[i] for i in range(len(out))] ['-30', '0', '30', '60', '90', '120', '150', '180', '210'] >>> z = N.array([-24.5, 50.3, 183.1, 20.]) >>> out = nice_levels(z, approx_nlev=5) >>> ['%g' % out[i] for i in range(len(out))] ['-50', '0', '50', '100', '150', '200'] >>> z = N.array([-24.5, 50.3, 183.1, 20.]) >>> out = nice_levels(z, approx_nlev=10) >>> ['%g' % out[i] for i in range(len(out))] ['-30', '0', '30', '60', '90', '120', '150', '180', '210'] """ #- Default settings and error check: if approx_nlev >= max_nlev: raise ValueError, 'max_nlev is too small' MAX_zd_ok = N.max(data) MIN_zd_ok = N.min(data) nlevels = N.min([approx_nlev, max_nlev]) tmpcmax = MAX_zd_ok tmpcmin = MIN_zd_ok tmpcint = N.abs( (tmpcmax-tmpcmin)/float(nlevels) ) #- See if the cint can be "even". If not, return alternative # contour levels vector: #+ Guess a possible cint. Look for an "even" value that is # closest to that: guesscint = N.abs( (MAX_zd_ok-MIN_zd_ok)/float(nlevels) ) if (guesscint > 1e-10) and (guesscint < 1e+10): possiblecint = [1e-10, 1e-9, 1e-8, 1e-7, 1e-6, 1e-5, 0.0001, 0.0002, 0.0005, 0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1., 2., 5., 10., 20., 30., 45., 50., 100., 200., 500., 1000., 2000., 5000., 10000., 20000., 50000., 1e+5, 1e+6, 1e+7, 1e+8, 1e+9, 1e+10] diffcint = N.abs(possiblecint-guesscint) tempcint = N.compress( diffcint == N.min(diffcint), possiblecint )[0] tcidx = N.compress( where_close( possiblecint, tempcint ), N.arange(N.size(possiblecint)) )[0] #+ Look around at the "even" values nearby the possible option # for cint. Calculate how many contours each of those cints # would give. Dictionary ncon_count is the number of # contours for a given test cint. test_tcidxs are the indices # in possiblecint to examine in detail; these index values # will be the keys in ncon_count. if tcidx == 0: tcidx = 1 if tcidx == N.size(possiblecint)-1: tcidx = N.size(possiblecint)-2 ncon_count = {} test_tcidxs = [tcidx-1, tcidx, tcidx+1] for i in test_tcidxs: itcval = possiblecint[i] positivecon = N.arange(max_nlev+2, dtype=float)*itcval negativecon = (N.arange(max_nlev+2, dtype=float)+1.0)*itcval*(-1.0) if (MAX_zd_ok + itcval >= 0) and (MIN_zd_ok - itcval >= 0): ncon_count[i] = N.sum( \ N.logical_and(positivecon <= MAX_zd_ok + itcval, positivecon >= MIN_zd_ok - itcval) ) elif (MAX_zd_ok + itcval < 0) and (MIN_zd_ok - itcval < 0): ncon_count[i] = N.sum( \ N.logical_and(negativecon <= MAX_zd_ok + itcval, negativecon >= MIN_zd_ok - itcval) ) else: ncon_count[i] = N.sum(positivecon <= MAX_zd_ok + itcval) \ + N.sum(negativecon >= MIN_zd_ok - itcval) #+ Select the cint that has the fewest levels if it has at # least nlevels-1. Otherwise, try to find the next cint with # the fewest levels that is below max_nlev. tempcint is what # you get (changed, if warranted) leaving this section: min_ncon_count = N.min(ncon_count.values()) current_best_count = max_nlev for i in test_tcidxs: if (ncon_count[i] == min_ncon_count) and \ (ncon_count[i] >= nlevels-1): tempcint = possiblecint[i] current_best_count = ncon_count[i] break elif (ncon_count[i] == min_ncon_count) and \ (ncon_count[i] < nlevels-1): continue elif ncon_count[i] > max_nlev: continue else: if N.abs(ncon_count[i]-nlevels) < \ N.abs(current_best_count-nlevels): tempcint = possiblecint[i] current_best_count = ncon_count[i] continue #+ Create levels for case with neg. and pos. contours. There # is the case of all pos., all neg., and mixed pos. and neg. # contours: positivecon = N.arange(max_nlev+2, dtype=float)*tempcint negativecon = (N.arange(max_nlev+2, dtype=float)+1.0)*tempcint*(-1.0) if (MAX_zd_ok + tempcint >= 0) and (MIN_zd_ok - tempcint >= 0): tmpclevels = N.compress( \ N.logical_and(positivecon <= MAX_zd_ok + tempcint, positivecon >= MIN_zd_ok - tempcint), positivecon ) elif (MAX_zd_ok + tempcint < 0) and (MIN_zd_ok - tempcint < 0): tmpclevels = N.compress( \ N.logical_and(negativecon <= MAX_zd_ok + tempcint, negativecon >= MIN_zd_ok - tempcint), negativecon ) else: uppercon = N.compress( positivecon <= MAX_zd_ok + tempcint, positivecon ) lowercon = N.compress( negativecon >= MIN_zd_ok - tempcint, negativecon ) tmpclevels = N.concatenate([lowercon, uppercon]) #+ Sort clevels, reset number of levels, maximum, minimum, # and interval of contour based on the automatic setting: tmpclevels = N.sort(tmpclevels) if (N.size(tmpclevels) <= max_nlev ) and (N.size(tmpclevels) > 0): nlevels = N.size(tmpclevels) tmpcmax = tmpclevels[-1] tmpcmin = tmpclevels[0] tmpcint = tempcint else: pass #- Return output: return N.arange(tmpcmin, tmpcmax+tmpcint, tmpcint)