Reputation: 8558
I have a script that takes a pandas dataframe and chops it up into several hundred chunks and saves each chunk as a separate excel file. Each chunk will have the same number of columns but the number of rows varies. I've figured out how to apply all the other necessary formatting to these files with openpyxl, but I haven't yet determined the fastest way to apply borders. Also, I think I'm just not applying borders correctly, because the code below (which I suspect shouldn't need to loop over each cell individually) doesn't apply any borders.
from openpyxl.style import Border
wb = load_workbook(filename = _fname)
ws = wb.worksheets[0]
for _row in ws.range('A1:L'+str(ws.get_highest_row() ) ):
for _cell in _row:
_cell.style.borders.left.border_style = Border.BORDER_THIN
_cell.style.borders.right.border_style = Border.BORDER_THIN
_cell.style.borders.top.border_style = Border.BORDER_THIN
_cell.style.borders.bottom.border_style = Border.BORDER_THIN
wb.save(_fname)
So this code works, but it doesn't apply the border I expect (the default border in excel) and it takes a lot more steps than I'd prefer. My expectation is that I should be able to do something like this:
from openpyxl.style import Border
wb = load_workbook(filename = _fname)
ws = wb.worksheets[0]
_range = ws.some_range_func('A1:L'+str(ws.get_highest_row() ) ):
_range.style.borders.all_borders = Borders.BORDER_THIN
Does this functionality exist? If not, can someone please be so kind as to at least explain how to apply the default border style and not this slightly thicker border? None of Border.BORDER_THICK, Border.BORDER_MEDIUM, Border.BORDER_THIN, or Border.BORDER_HAIR seem correct.
Thanks!
Upvotes: 17
Views: 61466
Reputation: 340
from copy import copy
def set_border(ws, cell_range, style='thin'):
rows = ws[cell_range]
for row in rows:
temp_row = copy(row[0].border)
row[0].border = Border(left=Side(style=style), right=temp_row.right, top=temp_row.top, bottom=temp_row.bottom)
temp_row = copy(row[-1].border)
row[-1].border = Border(right=Side(style=style), left=temp_row.left, top=temp_row.top, bottom=temp_row.bottom)
for c in rows[0]:
temp_row = copy(c.border)
c.border = Border(top=Side(style=style), left=temp_row.left, bottom=temp_row.bottom, right=temp_row.right)
for c in rows[-1]:
temp_row = copy(c.border)
c.border = Border(bottom=Side(style=style), left=temp_row.left, top=temp_row.top, right=temp_row.right)
This keeps the existing borders of the side and you can also style your border
Upvotes: 3
Reputation: 9521
May be this is handy:
from openpyxl.reader.excel import load_workbook
from openpyxl.style import Border
def set_border(ws, cell_range):
rows = was[cell_range]
for row in rows:
row[0].style.borders.left.border_style = Border.BORDER_THIN
row[-1].style.borders.right.border_style = Border.BORDER_THIN
for c in rows[0]:
c.style.borders.top.border_style = Border.BORDER_THIN
for c in rows[-1]:
c.style.borders.bottom.border_style = Border.BORDER_THIN
#usage example:
ws = load_workbook('example.xlsx').get_active_sheet()
set_broder(ws, "C3:H10")
It performs reasonably fast.
Upvotes: 9
Reputation: 572
In more pythonic way for openpyxl==3.0.5:
from openpyxl.styles import Border, Side
def set_border(ws, cell_range):
thin = Side(border_style="thin", color="000000")
for row in ws[cell_range]:
for cell in row:
cell.border = Border(top=thin, left=thin, right=thin, bottom=thin)
set_border(worksheet, 'A5:C10')
Upvotes: 22
Reputation: 247
There is a slight modification to answer from @Karimov
Below is how your code should be
from openpyxl.styles import Border, Side, Font, Alignment
def __format_ws__(self, ws, cell_range):
border = Border(left=Side(border_style='thin', color='000000'),
right=Side(border_style='thin', color='000000'),
top=Side(border_style='thin', color='000000'),
bottom=Side(border_style='thin', color='000000'))
rows = ws[cell_range]
for row in rows:
for cell in row:
cell.border = border
A much faster way that uses list comprehension is below:
def __format_ws__(self, ws, cell_range):
#applying border and alignment
font = Font(size=9)
align=Alignment(horizontal='left', vertical='center')
border = Border(left=Side(border_style='thin', color='000000'),
right=Side(border_style='thin', color='000000'),
top=Side(border_style='thin', color='000000'),
bottom=Side(border_style='thin', color='000000'))
rows = [rows for rows in ws[cell_range]]
flattened = [item for sublist in rows for item in sublist]
[(setattr(cell,'border',border), setattr(cell,'font',font), setattr(cell,'alignment',align)) for cell in flattened]
The way you use it is:
self.__format_ws__(ws=writer.book.worksheets[0], cell_range='A1:G10')
Upvotes: 6
Reputation: 616
Had the same issue but couldn't find anything that fixes this problem for 2019 because of depreciation. I have something that works below.. could be better but works for all intends and purposes.
def set_border(ws, cell_range):
rows = ws[cell_range]
for row in rows:
if row == rows[0][0] or row == rows[0][-1] or row == rows[-1][0] or row == rows[-1][-1]:
pass
else:
row[0].border = Border(left=Side(style='thin'))
row[-1].border = Border(right=Side(style='thin'))
for c in rows[0]:
c.border = Border(top=Side(style='thin'))
for c in rows[-1]:
c.border = Border(bottom=Side(style='thin'))
rows[0][0].border = Border(left=Side(style='thin'), top=Side(style='thin'))
rows[0][-1].border = Border(right=Side(style='thin'), top=Side(style='thin'))
rows[-1][0].border = Border(left=Side(style='thin'), bottom=Side(style='thin'))
rows[-1][-1].border = Border(right=Side(style='thin'), bottom=Side(style='thin'))
Upvotes: 1
Reputation: 140
Decision that works on openpyxl 2.3.5
from openpyxl.styles import Border, Side
def set_border(ws, cell_range):
border = Border(left=Side(border_style='thin', color='000000'),
right=Side(border_style='thin', color='000000'),
top=Side(border_style='thin', color='000000'),
bottom=Side(border_style='thin', color='000000'))
rows = ws.iter_rows(cell_range)
for row in rows:
for cell in row:
cell.border = border
set_border(worksheet, 'A5:C10')
Upvotes: 5
Reputation: 2012
seems there is no built-in for this task, and we have to make some steps ourselves, like:
#need make conversion from alphabet to number due to range function
def A2N(s,e):
return range(ord(s), ord(e)+1)
#B1 is the border you defined
#Assume you trying border A1-Q1 ... A3-Q3
X = A2N('A','Q')
#print X
your_desired_sheet_range_rows = range(1,4)
#need two loop to go through cells
for row in your_desired_sheet_rows:
for col in X:
ca = chr(col)
sheet[ca+str(row)].border=B1
Upvotes: -1
Reputation: 211
@user698585 your approach seems nice but it doesn't work anymore as the present version of the openpyxl change the implementation. So this should be updated into e.g.
ws.cell(row=1, column=1).style.border.top.border_style = borders.BORDER_MEDIUM
but it results with an error that changing the style is not allowed. As a workaround I just defined a dedicated styles, but they are just a duplication of the present styles plus border definition - not so good solution as works only if you know what style has the cell under the change.
border_style = Style(font=Font(name='Console', size=10, bold=False,
color=Color(openpyxl.styles.colors.BLACK)),
fill=PatternFill(patternType='solid', fgColor=Color(rgb='00C5D9F1')),
border=Border(bottom=Side(border_style='medium', color=Color(rgb='FF000000'))))
Upvotes: 2
Reputation: 7491
if you need styling (borders...) for pandas excel dataframe my fork just got merged into master https://github.com/pydata/pandas/pull/2370#issuecomment-10898427
as for you borders problems. setting all borders at once does not seam to work in openpyxl.
In [34]: c.style.borders.all_borders.border_style = openpyxl.style.Border.BORDER_THIN
In [36]: c.style
'Calibri':11:False:False:False:False:'none':False:'FF000000':'none':0:'FFFFFFFF':'FF000000':'none':'FF000000':'none':'FF000000':'none':'FF000000':'none':'FF000000':'none':'FF000000':0:'thin':'FF000000':'none':'FF000000':'none':'FF000000':'none':'FF000000':'none':'FF000000':'general':'bottom':0:False:False:0:'General':0:'inherit':'inherit'
setting individually works ('thin':'FF000000')
In [37]: c.style.borders.top.border_style = openpyxl.style.Border.BORDER_THIN
In [38]: c.style
Out[38]: 'Calibri':11:False:False:False:False:'none':False:'FF000000':'none':0:'FFFFFFFF':'FF000000':'none':'FF000000':'none':'FF000000':'thin':'FF000000':'none':'FF000000':'none':'FF000000':0:'thin':'FF000000':'none':'FF000000':'none':'FF000000':'none':'FF000000':'none':'FF000000':'general':'bottom':0:False:False:0:'General':0:'inherit':'inherit'
maybe a bug in openpyxl. but no big deal just wrap setting bottom , top, left, right in function
Upvotes: 1