How can I add borders to whole rows and columns with openpyxl?
I tried:
import openpyxl
from openpyxl.styles import borders
from openpyxl.styles.borders import Border
wb = openpyxl.Workbook()
ws = wb.active
border1 = borders.Side(style = None, color = Color(indexed = 0), border_style = 'thin')
border0 = borders.Side(style = None, color = None, border_style = None)
thin = Border(left = border1, right = border0, bottom = border0, top = border 0)
ws.column['C'].border = thin
I then got the Error:
Worksheet object has no attribute column
Is there a possibility to assign the border to whole row/column or do I need to apply it to the cells one by one?
Here an example how to iterate through the cells to aplly the border to each cell. min_col = 3 and max_col = 3 leads to column 'C' and with max_row you can set till which row you want the border.
import openpyxl
from openpyxl.styles import borders
from openpyxl.styles.borders import Border
wb = openpyxl.load_workbook('border.xlsx')
ws = wb.active
border1 = borders.Side(style = None, color = 'FF000000', border_style = 'thin')
border0 = borders.Side(style = None, color = None, border_style = None)
thin = Border(left = border1, right = border0, bottom = border0, top = border0)
for row in ws.iter_rows(min_row=1, min_col=3, max_row=20, max_col=3):
for cell in row:
cell.border = thin
wb.save('border_new.xlsx')
Related
I'm currently stuck when it comes to color coding cells with specific values containing =="PPI". I'm using openpyxl to save an excel sheet that will state the color code RedFill if these cells in column B contain the value "PPI". I have a feeling I'm missing something specific in my code.
from openpyxl import load_workbook
from openpyxl.styles import Font, Color, Alignment, Border, Side, PatternFill
filename='DB_1.xlsx'
workbook = load_workbook(filename='DB_1.xlsx')
sheet = workbook.active
bold_font = Font(bold=True)
big_blue_text = Font(color="000066CC", size=11)
center_aligned_text = Alignment(horizontal="center")
redFill = PatternFill(start_color='FFFF0000',end_color='FFFF0000',fill_type='solid')
cell_1= sheet["G1"]
cell_1
cell_1.value = " Notes "
cell_1.value
cell_2 = sheet["A1"]
cell_2
cell_2.value = " Primary Key"
cell_2.value
sheet.auto_filter.ref = "A1:G1"
for c in sheet["A1:G1"][0]:
c.font = bold_font
for a in sheet["A1:G1"][0]:
a.font = big_blue_text
for b in sheet["A1:G1"][0]:
b.alignment = center_aligned_text
for d in sheet["B:B"]:
if d == "PPI":
d.PatternFill = RedFill
More specifically:
for d in sheet["B:B"]:
if d == "PPI":
d.PatternFill = RedFill
Below are the samples data that I need to color code that contains "PPI":
enter image description here
There are few changes required...
The d == "PPI" will return false as it is not searching for a
pattern. You need to search for the substring using something like
if "PPI" in d.value
If true, you need to use d.fill = redFill, not d.PatternFill
If not already doing it, do save the file
I was able to make this work updating the code like this. Hope this helps.
for d in sheet["B:B"]:
if "PPI" in d.value:
d.fill = redFill
workbook.save('new.xlsx') ## Or whatever name you want the updated file to be in
My goal is to create an Excel file and change the background color of some cells based on their value using conditional formatting, using openpyxl.
When I open the file created with Excel, I can see that the rule is there, but the rule does not include the formatting to apply (background color set to none). The cells have thus no background color, although the border of the cells respecting the formula are not visible, like when the background is white.
I don't see if I made a mistake, or if there is some trouble with openpyxl.
Here is a MWE:
from openpyxl import Workbook
from openpyxl.styles import PatternFill
from openpyxl.formatting.rule import CellIsRule
wb = Workbook()
ws = wb.active
ws['B2'] = -2
ws['B3'] = -1
ws['B4'] = 0
ws['C2'] = -1
ws['C3'] = 0
ws['C4'] = 1
fill = PatternFill(start_color='538DD5', fill_type='solid')
ws.conditional_formatting.add('B2:C4', CellIsRule(operator='lessThan', formula=[0], fill=fill))
wb.save('mwe.xlsx')
wb.close()
You need to add the parameter end_color like this :
fill = PatternFill(start_color='538DD5',end_color='538DD5',fill_type='solid')
check this link : https://openpyxl.readthedocs.io/en/stable/formatting.html
from openpyxl import Workbook
from openpyxl.styles import PatternFill
from openpyxl.formatting.rule import CellIsRule
wb = Workbook()
ws = wb.active
ws['B2'] = -2
ws['B3'] = -1
ws['B4'] = 0
ws['C2'] = -1
ws['C3'] = 0
ws['C4'] = 1
fill = PatternFill(start_color='538DD5',end_color='538DD5',fill_type='solid')
#print(fill)
ws.conditional_formatting.add('B2:C4', CellIsRule(operator='lessThan', formula=[0], fill=fill))
wb.save('mwe.xlsx')
wb.close()
result :
Following #GiovaniSalazar answer, I did more tests.
The parameters used for the color (start_color, end_color, fgColor, bgColor) do not have the same behaviour with conditional formatting and simple formatting (bug in openpyxl ?).
Here is a comparison of both. The only one working for both formatting is start_color + end_color.
from openpyxl import Workbook
from openpyxl.styles import PatternFill
from openpyxl.formatting.rule import CellIsRule
wb = Workbook()
ws = wb.active
ws['C2'] = -4
ws['C3'] = -3
ws['C4'] = -2
ws['C5'] = -1
ws['D2'] = 4
ws['D3'] = 3
ws['D4'] = 2
ws['D5'] = 1
ws['C1'] = 'Cond. formatting'
ws['F1'] = 'Formatting'
ws['A2'] = 'start+end'
fill = PatternFill(start_color='538DD5', end_color='538DD5', fill_type='solid')
# OK
ws.conditional_formatting.add('C2:D2', CellIsRule(operator='lessThan', formula=[0], fill=fill))
# OK
ws['F2'].fill = fill
ws['A3'] = 'start'
fill = PatternFill(start_color='538DD5', fill_type='solid')
# Problem (white background)
ws.conditional_formatting.add('C3:D3', CellIsRule(operator='lessThan', formula=[0], fill=fill))
# OK
ws['F3'].fill = fill
ws['A4'] = 'fgColor'
fill = PatternFill(fgColor='538DD5', fill_type='solid')
# Problem (white background)
ws.conditional_formatting.add('C4:D4', CellIsRule(operator='lessThan', formula=[0], fill=fill))
# OK
ws['F4'].fill = fill
ws['A5'] = 'bgColor'
fill = PatternFill(bgColor='538DD5', fill_type='solid')
# OK
ws.conditional_formatting.add('C5:D5', CellIsRule(operator='lessThan', formula=[0], fill=fill))
# Problem (black background)
ws['F5'].fill = fill
wb.save('mwe.xlsx')
wb.close()
Output Excel file:
Output Excel file
EDIT: Solved, solution in answer below.
I have a graph created with openpyxl that has two y axes sharing a DateAxis. Although the first selection of data is showing on the graph, the second isn't. There's also a strange gray line on the bottom of the graph that wasn't there before. I think it's just a small error I'm missing somewhere, but I can't see where. Especially considering I have my range of cells defined correctly. What could I be doing wrong?
import openpyxl
from openpyxl import Workbook, chart
from openpyxl.chart import LineChart, Reference, Series
from openpyxl.chart.axis import DateAxis
from datetime import date, datetime, timedelta, time
ws2 = wb['sheet2']
dates = chart.Reference(ws2, min_col=1, min_row=2, max_row=sheet.max_row)
vBat = chart.Reference(ws2, min_col=2, min_row=1, max_col=2, max_row=sheet.max_row)
qBat = chart.Reference(ws2, min_col=3, min_row=1, max_col=3)
c1 = chart.LineChart()
c1.title = "SLA Discharge - 5.5A: V_BAT"
c1.style = 12
c1.x_axis.majorTimeUnit = "days"
c1.x_axis = chart.axis.DateAxis()
c1.x_axis.title = "Time"
c1.x_axis.crosses = "min"
c1.x_axis.majorTickMark = "out"
c1.x_axis.number_format = 'd-HH-MM-SS'
c1.add_data(vBat, titles_from_data=True)
c1.set_categories(dates)
c1.y_axis.title = "Battery Voltage"
c1.y_axis.crossAx = 500
c1.y_axis.majorGridlines = None
c2 = chart.LineChart()
c2.x_axis.axId = 500 # same as c1
c2.add_data(qBat, titles_from_data=True, from_rows=True)
c2.set_categories(dates)
c2.y_axis.axId = 200
c2.y_axis.title = "Qbat Percentage"
c2.y_axis.crossAx = 500
c1.y_axis.crosses = "max"
c1 += c2
s1 = c1.series[0]
s1.graphicalProperties.line.solidFill = "BE4B48"
s1.graphicalProperties.line.width = 25000 # width in EMUs.
s1.smooth = True # Make the line smooth
s2 = c2.series[0]
s2.graphicalProperties.line.solidFill = "48BBBE"
s2.graphicalProperties.line.width = 25000 # width in EMUs.
s2.smooth = True # Make the line smooth
ws2.add_chart(c1, "D5")
Interestingly enough,
vBat = chart.Reference(ws2, min_col=2, min_row=1, max_col=2, max_row=sheet.max_row)
is fine. However, doing the same thing to qBat with:
qBat = chart.Reference(ws2, min_col=3, min_row=1, max_col=3, max_row=sheet.max_row)
"corrupts" the workbook and displays an error message upon opening and doesn't print any chart. Removing max_row=sheet.max_row from both lines produces an incorrect DateAxis where there are only two points and they're both the first two values in the time column.
first, in c2.add_data(qBat, titles_from_data=True, from_rows=True), remove from_rows=True.
Then, change qBat to:
qBat = chart.Reference(ws2, min_col=3, min_row=1, max_col=3, max_row=sheet.max_row)
def columnandcellformate(sheet_name,bold = 0,font_color = '#000000',bg_color = '#ffffff',align = '' ,bottom = 0 ,top = 3,right = 0,left = 0,font_size = 10 ,starcolumn = 0, endrow = 0 ):
global sheet_format
sheet_format=sheet_name.add_format({
'bottom':bottom,
'top' : top,
'bg_color':bg_color,
'font_color' : font_color,
'align':align,
'font_size':font_size,
'bold': bold,
'font_name':'Batang'
})
What is default value of top,bottom,right,left, My function is making cell top,bottom,right and left blank
I think your default background color may have been causing some issues with the cell borders. I've added a few conditions based on whether you want these called by your function or not. These conditions make use of Format Methods such as format.set_bg_color(), format.set_bottom() (see docs for more information on these). They only provide a background color if you change it from the default.
import xlsxwriter
def columnandcellformate(bold = 0, font_color = '#000000', bg_color = 'none', align = '' , bottom = 999, top = 999, right = 999, left = 999, font_size = 10):
global sheet_format
sheet_format=workbook.add_format({
'font_color' : font_color,
'align': align,
'font_size': font_size,
'bold': bold,
'font_name': 'Batang'
})
if bg_color != 'none':
sheet_format.set_bg_color(bg_color)
if bottom != 999:
sheet_format.set_bottom(bottom)
if top != 999:
sheet_format.set_top(top)
if right != 999:
sheet_format.set_right(right)
if left != 999:
sheet_format.set_left(left)
workbook = xlsxwriter.Workbook('test.xlsx')
ws = workbook.add_worksheet('test_1')
columnandcellformate()
ws.write('B1', 'foo', sheet_format)
columnandcellformate(bold = 1, font_color = '#9C0006', bg_color = '#FFC7CE', align = '', bottom = 2, top = 1, right = 1, left = 1, font_size = 10)
ws.write('B3', 'bar', sheet_format)
workbook.close()
Expected Output:
The default values for format properties are almost all 0/False. See the initialization code for a format object.
I've started putting together some code to take Pandas data and put it into a PowerPoint slide. The template I'm using defaults to Medium Style 2 - Accent 1 which would be fine as changing the font and background are fairly easy, but there doesn't appear to be an implemented portion to python-pptx that allows for changing cell borders. Below is my code, open to any solution. (Altering the XML or changing the template default to populate a better style would be good options for me, but haven't found good documentation on how to do either). Medium Style 4 would be ideal for me as it has exactly the borders I'm looking for.
import pandas
import numpy
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor
#Template Location
tmplLoc = 'C:/Desktop/'
#Read in Template
prs = Presentation(tmplLoc+'Template.pptx')
#Import data as Pandas Dataframe - dummy data for now
df = pandas.DataFrame(numpy.random.randn(10,10),columns=list('ABCDEFGHIJ'))
#Determine Table Header
header = list(df.columns.values)
#Determine rows and columns
in_rows = df.shape[0]
in_cols = df.shape[1]
#Insert table from C1 template
slide_layout = prs.slide_layouts[11]
slide = prs.slides.add_slide(slide_layout)
#Set slide title
title_placeholder = slide.shapes.title
title_placeholder.text = "Slide Title"
#Augment placeholder to be a table
placeholder = slide.placeholders[1]
graphic_frame = placeholder.insert_table(rows = in_rows+1, cols = in_cols)
table = graphic_frame.table
#table.apply_style = 'MediumStyle4'
#table.apply_style = 'D7AC3CCA-C797-4891-BE02-D94E43425B78'
#Set column widths
table.columns[0].width = Inches(2.23)
table.columns[1].width = Inches(0.9)
table.columns[2].width = Inches(0.6)
table.columns[3].width = Inches(2)
table.columns[4].width = Inches(0.6)
table.columns[5].width = Inches(0.6)
table.columns[6].width = Inches(0.6)
table.columns[7].width = Inches(0.6)
table.columns[8].width = Inches(0.6)
table.columns[9].width = Inches(0.6)
#total_width = 2.23+0.9+0.6+2+0.6*6
#Insert data into table
for rows in xrange(in_rows+1):
for cols in xrange(in_cols):
#Write column titles
if rows == 0:
table.cell(rows, cols).text = header[cols]
table.cell(rows, cols).text_frame.paragraphs[0].font.size=Pt(14)
table.cell(rows, cols).text_frame.paragraphs[0].font.color.rgb = RGBColor(255, 255, 255)
table.cell(rows, cols).fill.solid()
table.cell(rows, cols).fill.fore_color.rgb=RGBColor(0, 58, 111)
#Write rest of table entries
else:
table.cell(rows, cols).text = str("{0:.2f}".format(df.iloc[rows-1,cols]))
table.cell(rows, cols).text_frame.paragraphs[0].font.size=Pt(10)
table.cell(rows, cols).text_frame.paragraphs[0].font.color.rgb = RGBColor(0, 0, 0)
table.cell(rows, cols).fill.solid()
table.cell(rows, cols).fill.fore_color.rgb=RGBColor(255, 255, 255)
#Write Table to File
prs.save('C:/Desktop/test.pptx')
Maybe not really clean code but allowed me to adjust all borders of all cells in a table:
from pptx.oxml.xmlchemy import OxmlElement
def SubElement(parent, tagname, **kwargs):
element = OxmlElement(tagname)
element.attrib.update(kwargs)
parent.append(element)
return element
def _set_cell_border(cell, border_color="000000", border_width='12700'):
tc = cell._tc
tcPr = tc.get_or_add_tcPr()
for lines in ['a:lnL','a:lnR','a:lnT','a:lnB']:
ln = SubElement(tcPr, lines, w=border_width, cap='flat', cmpd='sng', algn='ctr')
solidFill = SubElement(ln, 'a:solidFill')
srgbClr = SubElement(solidFill, 'a:srgbClr', val=border_color)
prstDash = SubElement(ln, 'a:prstDash', val='solid')
round_ = SubElement(ln, 'a:round')
headEnd = SubElement(ln, 'a:headEnd', type='none', w='med', len='med')
tailEnd = SubElement(ln, 'a:tailEnd', type='none', w='med', len='med')
Based on this post: https://groups.google.com/forum/#!topic/python-pptx/UTkdemIZICw
In case someone else comes across this issue again, some changes should be made to the solution posted by JuuLes87 to avoid that Microsoft Office PowerPoint requires to repair the generated presentation.
After carefully inspecting the xml string of the table generated by pptx, I found that the requirement to repair the presentation seemed to be due to the duplicated nodes of 'a:lnL' or 'a:lnR' or 'a:lnT' or 'a:lnB' in the children elements of 'a:tcPr'. So we only need to remove nodes of ['a:lnL','a:lnR','a:lnT','a:lnB'] before these nodes are inserted as below.
from pptx.oxml.xmlchemy import OxmlElement
def SubElement(parent, tagname, **kwargs):
element = OxmlElement(tagname)
element.attrib.update(kwargs)
parent.append(element)
return element
def _set_cell_border(cell, border_color="000000", border_width='12700'):
tc = cell._tc
tcPr = tc.get_or_add_tcPr()
for lines in ['a:lnL','a:lnR','a:lnT','a:lnB']:
# Every time before a node is inserted, the nodes with the same tag should be removed.
tag = lines.split(":")[-1]
for e in tcPr.getchildren():
if tag in str(e.tag):
tcPr.remove(e)
# end
ln = SubElement(tcPr, lines, w=border_width, cap='flat', cmpd='sng', algn='ctr')
solidFill = SubElement(ln, 'a:solidFill')
srgbClr = SubElement(solidFill, 'a:srgbClr', val=border_color)
prstDash = SubElement(ln, 'a:prstDash', val='solid')
round_ = SubElement(ln, 'a:round')
headEnd = SubElement(ln, 'a:headEnd', type='none', w='med', len='med')
tailEnd = SubElement(ln, 'a:tailEnd', type='none', w='med', len='med')
I had a hard time figuring out why this wasn't working. For anyone else struggling with this, I had to add the following to the end of the function:
return cell
When using, you want to use the function as such:
cell = _set_cell_border(cell)