mirror of
https://github.com/nwchemgit/nwchem.git
synced 2026-07-28 14:15:30 -04:00
500 lines
18 KiB
Python
Executable file
500 lines
18 KiB
Python
Executable file
'''
|
|
Stuff for driving MS office applications from Python using COM
|
|
|
|
Currently just Excel but Word will come soon.
|
|
'''
|
|
|
|
from win32com.client import Dispatch
|
|
from types import *
|
|
from string import uppercase
|
|
|
|
class Excel:
|
|
'''
|
|
Wrapper for MS Excel derived from that in Python Programing on Win32
|
|
'''
|
|
def __init__(self,filename=None):
|
|
'''
|
|
Open a new Excel spreadsheet optionally associated with a file
|
|
'''
|
|
self.xlApp = Dispatch("Excel.Application")
|
|
if filename:
|
|
self.filename = filename
|
|
self.xlBook = self.xlApp.Workbooks.Open(filename)
|
|
else:
|
|
self.xlBook = self.xlApp.Workbooks.Add()
|
|
self.filename = ''
|
|
self.open = 1
|
|
|
|
def save(self, newfilename=None):
|
|
'''
|
|
Save the workbook either to the default file, another file,
|
|
or let Excel query the user where to save it.
|
|
'''
|
|
if newfilename:
|
|
self.filename = newfilename
|
|
self.xlBook.SaveAs(newfilename)
|
|
else:
|
|
self.xlBook.Save()
|
|
|
|
def close(self):
|
|
self.xlBook.Close(SaveChanges=0)
|
|
del self.xlApp
|
|
self.open = 0
|
|
|
|
def getCell(self, row, col, sheet=1):
|
|
'''
|
|
Returns the value in cell (row,col) or None if it is blank.
|
|
'''
|
|
xlSheet = self.xlBook.Worksheets(sheet)
|
|
return xlSheet.Cells(row,col).Value
|
|
|
|
def getCellFormula(self, row, col, sheet=1):
|
|
'''
|
|
Returns the formula in cell (row,col) or the value if
|
|
there is no formula. If there is no value nor formula,
|
|
None is returned.
|
|
'''
|
|
xlSheet = self.xlBook.Worksheets(sheet)
|
|
result = xlSheet.Cells(row,col).Formula
|
|
if result == '': # A blank field seems to return a blank string
|
|
result = None
|
|
return result
|
|
|
|
def setCell(self, value, row, col, sheet=1):
|
|
'''
|
|
Sets the value in cell (row,col).
|
|
'''
|
|
xlSheet = self.xlBook.Worksheets(sheet)
|
|
xlSheet.Cells(row,col).Value = value
|
|
|
|
def getRange(self, row1, col1, row2=None, col2=None, sheet=1):
|
|
'''
|
|
Returns the data in the given range as a 2d array (i.e., as
|
|
a tuple of tuples). If the bottom corner is not specified
|
|
or is incompletely specified, assume a dimension of 1.
|
|
'''
|
|
if not row2:
|
|
row2 = row1
|
|
if not col2:
|
|
col2 = col1
|
|
xlSheet = self.xlBook.Worksheets(sheet)
|
|
cell1 = xlSheet.Cells(row1,col1)
|
|
cell2 = xlSheet.Cells(row2,col2)
|
|
return xlSheet.Range(cell1,cell2).Value
|
|
|
|
def matrixDimensions(self, data):
|
|
'''
|
|
Determine the dimemension of the matrix data which can be a
|
|
scalar, vector, or 2-D matrix. Allows for string data, or for
|
|
matrices in which the first row or column are strings labels
|
|
for series ... so look at the last row to determine the length
|
|
of a row (= number of columns). If the data is a vector then
|
|
it is taken as a row-vector in order to be consistent with how
|
|
the default extension happens when assigning a simple list or
|
|
vector into a rectangular range in Excel.
|
|
'''
|
|
last = None
|
|
n = m = 1
|
|
try:
|
|
n = len(data)
|
|
last = data[-1]
|
|
except TypeError:
|
|
n = m = 1 # We have a scalar
|
|
|
|
if last:
|
|
if type(last) == StringType:
|
|
m = n # Row-vector of strings
|
|
n = 1
|
|
else:
|
|
try:
|
|
m = len(last)
|
|
except TypeError:
|
|
m = n # Row-vector of scalars
|
|
n = 1
|
|
return (n,m)
|
|
|
|
def setRange(self, data, row1=1, col1=1, row2=None, col2=None, sheet=1):
|
|
'''
|
|
Set the range of cells to the given data.
|
|
|
|
If both corners of the range are specified, the corresponding
|
|
piece of data is copied into the range. If data is too small,
|
|
then it is mystically extended to fill the range. E.g., you
|
|
can fill a range from a scalar or a vector. Vectors are treated
|
|
as row-vectors when filling a rectangular region.
|
|
|
|
Optionally, you specify only the top-left corner of range in
|
|
row1, cell1 and specify row2<=0 - the other coordinate is figured
|
|
out from the dimension of the data. This can always be overriden by
|
|
specifying the full range coordinates.
|
|
|
|
If no coordinates are given, the data is put into the top left
|
|
of the spreadsheet.
|
|
|
|
Returns the range that was set.
|
|
'''
|
|
(n,m) = self.matrixDimensions(data)
|
|
if not row2:
|
|
row2 = row1 + n - 1
|
|
if not col2:
|
|
col2 = col1 + m - 1
|
|
xlSheet = self.xlBook.Worksheets(sheet)
|
|
cell1 = xlSheet.Cells(row1,col1)
|
|
cell2 = xlSheet.Cells(row2,col2)
|
|
xlSheet.Range(cell1,cell2).Value = data
|
|
return (row1, col1, row2, col2)
|
|
|
|
def getContiguousRange(self, row1, col1, sheet=1):
|
|
'''
|
|
Returns data in the range which forms a continguous
|
|
block with top-left corner in cell (row1,col1).
|
|
|
|
Starting from the specified cell, scan down/across
|
|
the first column/row and identify the range bordered
|
|
by blank cells. Blanks within the region will be
|
|
set to None.
|
|
'''
|
|
xlSheet = self.xlBook.Worksheets(sheet)
|
|
row2 = row1
|
|
while xlSheet.Cells(row2+1,col1).Value not in [None,'']:
|
|
row2 = row2 + 1
|
|
|
|
col2 = col1
|
|
while xlSheet.Cells(row1,col2+1).Value not in [None,'']:
|
|
col2 = col2 + 1
|
|
return self.getRange(row1, col1, row2, col2, sheet=sheet)
|
|
|
|
def selectRange(self, row1, col1, row2=None, col2=None, sheet=1):
|
|
'''
|
|
Select the range of cells on the specified sheet. It also
|
|
has to select that sheet as the active worksheet.
|
|
'''
|
|
if not row2:
|
|
row2 = row1
|
|
if not col2:
|
|
col2 = col1
|
|
xlSheet = self.xlBook.Worksheets(sheet)
|
|
xlSheet.Select()
|
|
cell1 = xlSheet.Cells(row1,col1)
|
|
cell2 = xlSheet.Cells(row2,col2)
|
|
xlSheet.Range(cell1,cell2).Select()
|
|
|
|
def chartRange(self, row1, col1, row2, col2, sheet=1,
|
|
**keys):
|
|
'''
|
|
Chart the data in the specified range. Additional options
|
|
are processed by chartSelectedRange.
|
|
'''
|
|
self.selectRange(row1, col1, row2, col2, sheet=sheet)
|
|
keys['sheet'] = sheet
|
|
apply(self.chartSelectedRange, (), keys)
|
|
|
|
def chartSelectedRange(self,
|
|
title=None, xlabel=None, ylabel=None,
|
|
plotby='columns',
|
|
charttype='xy',
|
|
sheet=1,
|
|
xmin=None, xmax=None,
|
|
ymin=None, ymax=None,
|
|
xlog=0, ylog=0):
|
|
'''
|
|
The interface to Excel charts. Just a few of the capabilities
|
|
are exposed here.
|
|
|
|
[The first of a set of options is the default]
|
|
|
|
plotby = 'columns' ... data series run down columns
|
|
. = 'rows' ... across rows
|
|
|
|
charttype = 'xy' ... XY scatter plot with lines and points.
|
|
. First series is X. Others are y1, y2, etc.
|
|
. = 'surface' ... Surfce plot of a scalar function of
|
|
. two variables. Data should be a grid of the function.
|
|
. = 'contour' or 'colorcontour' ... Contour plot of a scalar
|
|
. function of two variables. Data should be a grid of
|
|
. values.
|
|
xmin and xmax = min/max values of the x or category axis
|
|
. It defaults to autoscale by Excel. This only applies to
|
|
. XY plots (since the surfce/contor plots do not use
|
|
. values for the category axes ... they use string labels)
|
|
ymin and ymax = min/max values of the y or value axis
|
|
. It defaults to auto by Excel. Applies to all charts.
|
|
xlog = 0 ... use a linear for x or category axis.
|
|
. = 1 ... use a log (values must be positive)
|
|
. This only applies to XY plots.
|
|
ylog = 0 ... use a linear for the value or Y axis
|
|
. = 1 ... use a log .
|
|
. Applies to all charts
|
|
|
|
If the first element of each data series is a string, it is
|
|
used to label the series. If this string is representable as
|
|
a numerical value you must precede it with a single quote to
|
|
force Excel to treat it as a string. Note that you must use
|
|
strings. If you use numbers it will be interpreted as data
|
|
and incorporated into the plot. For the 2-D plots (xy,
|
|
surface, contour) you can border the actual data on left and
|
|
on the top with strings to label axes.
|
|
'''
|
|
charttypes = {'xy':74, 'surface':83, 'colorcontour':85, 'contour':56}
|
|
try:
|
|
charttype = charttypes[charttype]
|
|
except KeyError:
|
|
print 'Excel.chartSelectedRange: Unkown charttype', charttype, \
|
|
' defaulting to XY'
|
|
charttype = charttypes['xy']
|
|
|
|
# Make the chart and set how the data will be interpreted
|
|
# Taking a reference to the active chart does not seemt to work???
|
|
self.xlApp.Charts.Add()
|
|
self.xlApp.ActiveChart.ChartType = charttype
|
|
xlRows=1
|
|
xlColumns=2
|
|
if plotby == 'rows':
|
|
self.xlApp.ActiveChart.PlotBy = xlRows
|
|
elif plotby == 'columns':
|
|
self.xlApp.ActiveChart.PlotBy = xlColumns
|
|
else:
|
|
print 'Excel.chartSelectedRange: Unknown plotby', charttype, \
|
|
' defaulting to columns'
|
|
self.xlApp.ActiveChart.PlotBy = xlColumns
|
|
|
|
# Set the title and axis labels
|
|
if title:
|
|
self.xlApp.ActiveChart.HasTitle = 1
|
|
self.xlApp.ActiveChart.ChartTitle.Characters.Text = title
|
|
xlCategory=1
|
|
xlValue=2
|
|
#xlSeries=3
|
|
xlPrimary=1
|
|
#xlSecondary=2
|
|
if xlabel:
|
|
self.xlApp.ActiveChart.Axes(xlCategory,xlPrimary).HasTitle = 1
|
|
self.xlApp.ActiveChart.Axes(xlCategory,xlPrimary).AxisTitle.Characters.Text = xlabel
|
|
if ylabel:
|
|
self.xlApp.ActiveChart.Axes(xlValue,xlPrimary).HasTitle = 1
|
|
self.xlApp.ActiveChart.Axes(xlValue,xlPrimary).AxisTitle.Characters.Text = ylabel
|
|
|
|
# Set the axis scale and log options
|
|
xlLinear = 0xffffefdc
|
|
xlLogarithmic=0xffffefdb
|
|
if ymin != None:
|
|
self.xlApp.ActiveChart.Axes(xlValue).MinimumScale = ymin
|
|
if ymax != None:
|
|
self.xlApp.ActiveChart.Axes(xlValue).MaximumScale = ymax
|
|
if ylog:
|
|
self.xlApp.ActiveChart.Axes(xlValue).ScaleType = xlLogarithmic
|
|
if charttype == charttypes['xy']:
|
|
if xmin != None:
|
|
self.xlApp.ActiveChart.Axes(xlCategory).MinimumScale = xmin
|
|
if xmax != None:
|
|
self.xlApp.ActiveChart.Axes(xlCategory).MaximumScale = xmax
|
|
if xlog:
|
|
self.xlApp.ActiveChart.Axes(xlCategory).ScaleType = xlLogarithmic
|
|
# A legend is kinda useful
|
|
self.xlApp.ActiveChart.HasLegend = 1
|
|
|
|
def chartData(self, data, row1=1, col1=1, sheet=1, **keys):
|
|
'''
|
|
Simplest interface for creating a chart. Data is a matrix
|
|
of data. Paste it into a sheet and plot it. All arguments
|
|
except the data can be defaulted. Optional arguments are passed
|
|
to the actual charting function.
|
|
'''
|
|
(n,m) = self.matrixDimensions(data)
|
|
row2 = row1 + n - 1
|
|
col2 = col1 + m - 1
|
|
self.setRange(data, row1, col1, row2, col2, sheet=sheet)
|
|
keys['sheet'] = sheet
|
|
apply(self.chartRange, (row1, col1, row2, col2), keys)
|
|
|
|
def a1(self, row, col, absrow=0, abscol=0):
|
|
'''
|
|
Return a string that may be used to adress the cell in
|
|
a formula. The row and/or column adress may be made absolute
|
|
by setting absrow/col to true values.
|
|
|
|
Internally we are adressing cells in the spreadsheet using
|
|
integers (row,col), which is what Excel calls R1C1 style
|
|
references. But, unless the user has turned-on R1C1 style
|
|
adressing (unlikely!) this will not work in formulae
|
|
so we must translate to the usual adressing style, called A1,
|
|
which uses letters for the columns and numbers for the rows,
|
|
writing the column index first.
|
|
|
|
E.g., A1 = R1C1 = (1,1), and B3 = R3C2 = (3,2).
|
|
|
|
Absolute adresses are preceded with a $ symbol.
|
|
'''
|
|
ar = ac = ''
|
|
if absrow: ar = '$'
|
|
if abscol: ac = '$'
|
|
if col < 1 or col > 256:
|
|
raise RangeError, 'column index must be in [1,256]'
|
|
(c1,c2) = divmod(col-1,26)
|
|
if c1:
|
|
c = uppercase[c1] + uppercase[c2]
|
|
else:
|
|
c = uppercase[c2]
|
|
r = str(row)
|
|
return ac + c + ar + r
|
|
|
|
def visible(self):
|
|
'''
|
|
Make the spreadsheet visible.
|
|
'''
|
|
self.xlApp.Visible = 1
|
|
|
|
def invisible(self):
|
|
'''
|
|
Make the spreadsheet invisible.
|
|
'''
|
|
self.xlApp.Visible = 0
|
|
|
|
def isvisible(self):
|
|
'''
|
|
Returns true if the spreadsheet is visible.
|
|
'''
|
|
return self.xlApp.Visible
|
|
|
|
def __del__(self):
|
|
'''
|
|
Destructor ... may be uncessary but it cannot hurt.
|
|
'''
|
|
if self.open:
|
|
self.close()
|
|
|
|
if __name__ == "__main__":
|
|
from math import *
|
|
import time
|
|
# Make a worksheet and test set/getCell
|
|
xls = Excel()
|
|
print ' Setting cell(2,2) to "Hi"'
|
|
xls.setCell("Hi", 2, 2)
|
|
print xls.getCell(2,2)
|
|
print ' Setting cell(1,2) to "(1,2)"'
|
|
xls.setCell("(1,2)", 1, 2)
|
|
print ' Setting cell(2,1) to "(1,2)"'
|
|
xls.setCell("(2,1)", 2, 1)
|
|
xls.visible()
|
|
|
|
# Test setting a range to a scalar and getting contiguous range
|
|
print ' Setting 9,1,12,2 to 0'
|
|
xls.setRange(0,9,1,12,2)
|
|
print ' Getting same contiguous range back ... expecting matrix(4,2)=0'
|
|
value = xls.getContiguousRange(9,1)
|
|
print value
|
|
|
|
# Test setting/getting a range from/to a matrix
|
|
n = 3
|
|
m = 5
|
|
x = [0]*n
|
|
for i in range(n):
|
|
x[i] = [0]*m
|
|
for j in range(m):
|
|
x[i][j] = i + j
|
|
print ' Setting range (3:,4:) to '
|
|
print x
|
|
xls.setRange(x,3,4) # Auto determination of the bottom corner
|
|
print ' Got back from same range ',3,3,3+n-1,4+m-1
|
|
y = xls.getRange(3,4,3+n-1,4+m-1)
|
|
print y
|
|
|
|
# Add names for the series that will eventually become the chart
|
|
names = []
|
|
for i in range(m):
|
|
names.append("y%d" % i)
|
|
xls.setRange(names,2,4)
|
|
|
|
# Test selecting a range
|
|
print ' Selecting range ', 3,3,3+n-1,4+m-1
|
|
xls.selectRange(3,4,3+n-1,4+m-1)
|
|
|
|
# Test general matrix
|
|
xls.setRange([[1,2],[5,6],["hi","bye"]],1,10,3,11)
|
|
|
|
# Test making an x-y plot (changes the range selection)
|
|
xls.chartRange(2,4,3+n-1,4+m-1,
|
|
title='THIS IS THE TITLE',
|
|
xlabel='XXXXX',
|
|
ylabel='YYYYY')
|
|
|
|
# Test making an x-y plot just from the data ... use a
|
|
# second sheet and the simple chart interface
|
|
print ' Creating chart of sin(x) and cos(x) using second sheet'
|
|
n = 20
|
|
m = 3
|
|
h = 2*pi/(n-1)
|
|
data = range(n+1)
|
|
data[0] = ['x', 'sin', 'cos']
|
|
for i in range(n):
|
|
x = i*h
|
|
data[i+1] = (x,sin(x),cos(x))
|
|
xls.chartData(data,sheet=2)
|
|
|
|
# Try using a formula to add up the absolute values of the data
|
|
# Use absolute values for the rows but not the columns to test
|
|
# reuse of the formula.
|
|
formula = '=sum('+xls.a1(2,2,absrow=1)+':'+xls.a1(21,2,absrow=1)+')'
|
|
print ' The formula is ', formula
|
|
xls.setCell('Total',23,1,sheet=2)
|
|
xls.setCell(formula,23,2,sheet=2)
|
|
xls.setCell(formula,23,3,sheet=2)
|
|
# Getting the cell contents back will get the value not the formula
|
|
print ' The formula from the sheet is ', xls.getCellFormula(23,2,sheet=2)
|
|
print ' The value of the formula (sum of sin) is ', \
|
|
xls.getCell(23,2,sheet=2)
|
|
print ' The formula from where there is only the value "Total" is', \
|
|
xls.getCellFormula(23,1,sheet=2)
|
|
print ' The formula from where there is nothing ',\
|
|
xls.getCellFormula(23,4,sheet=2)
|
|
print ' The value from where there is nothing ',\
|
|
xls.getCell(23,4,sheet=2)
|
|
|
|
# Make a surface plot by creating a 2-D grid bordered on the
|
|
# left and top with strings to indicate the values. Note the
|
|
# use of a single quote before the value in the labels in
|
|
# order to force Excel to treat them as strings.
|
|
print ' Create surface chart of exp(-0.1*r*r)*cos(1.3*r)'
|
|
n = 10
|
|
h = 2*pi/(n-1)
|
|
data = range(n+1)
|
|
data[0] = range(n+1)
|
|
data[0][0] = ' '
|
|
for i in range(n):
|
|
x = i*h-pi
|
|
data[i+1] = range(n+1)
|
|
data[0][i+1] = data[i+1][0] = ("'%5.2f" % x)
|
|
for j in range(n):
|
|
y = j*h-pi
|
|
r = sqrt(x*x+y*y)
|
|
data[i+1][j+1] = exp(-0.1*r*r)*cos(1.3*r)
|
|
# Specify (row1,col1) to avoid overwriting the previous data
|
|
# Also, specify axis ranges to make the animation smoother.
|
|
xls.chartData(data,1,5,sheet=2,charttype='surface',
|
|
ymin=-1,ymax=1)
|
|
|
|
# Animate the chart by periodically updating the data range.
|
|
nloop = 60
|
|
for loop in range(1,nloop+1):
|
|
phase = loop*2*pi/(nloop-1)
|
|
for i in range(n):
|
|
x = i*h-pi
|
|
for j in range(n):
|
|
y = j*h-pi
|
|
r = sqrt(x*x+y*y)
|
|
data[i+1][j+1] = exp(-0.1*r*r)*cos(1.3*r+phase)
|
|
time.sleep(0.5)
|
|
xls.setRange(data,1,5,sheet=2)
|
|
|
|
# Finally make a chart with all options set
|
|
print ' Creating chart of sin(x) and cos(x) using second sheet'
|
|
n = 81
|
|
data = range(n+1)
|
|
data[0] = ['Age', 'Wisdom']
|
|
for i in range(n):
|
|
data[i+1] = [i+1, 1.0 + 100.0*exp(-((i-40)**2)/400.0)]
|
|
xls.chartData(data,1,1,sheet=3,plotby='columns',charttype='xy',
|
|
xmin=1,xmax=80,ymin=1,ymax=100,ylog=1,
|
|
title='Wisdom vs. Age', xlabel='Age/years',
|
|
ylabel='Wisdom')
|
|
|