How to freeze first column using Openpyxl - python

I am using the following code to freeze the first column of my excel sheer generated using openpyxl. But this is not working and showing me an error.
dim_holder = DimensionHolder(worksheet=worksheet)
for col in range(worksheet.min_column, worksheet.max_column + 1):
dim_holder[get_column_letter(col)] = ColumnDimension(
worksheet, min=col, max=col, width=20)
# worksheet.column_dimensions.group(2, 10)
dim_holder['A'] = ColumnDimension(
worksheet, min=1, max=1, width=35)
worksheet.column_dimensions = dim_holder
worksheet.freeze_panes = 'A'
How can I freeze the entire first column, so that it will be sticky when I do horizontal scrolling ?

As not all code is present, but you have mentioned that you are using openpyxl, I am going to assume that. To freeze panes, you need to give the cell. That will freeze the panes above and to the left of the same. So, when you give A, it will not help, need to change it to B1 instead. A simple example is given below.
wb=openpyxl.load_workbook('YourFile.xlsx')
ws=wb['YourSheet']
c = ws['B1'] ## Freeze everything to left of B (that is A) and no columns to feeze
ws.freeze_panes = c
wb.save('YourFile.xlsx')

Related

How do I update a value in a cell without coordinates?

I find the required cell through the row number and column name (Column Search is necessary because columns can change their place in the table) and try to update its value using gspread. The code works without error, but for some reason it updates a completely different cell (A1)
import gspread
sa = gspread.service_account('path to service_account.json')
sh = sa.open('name of sheet')
wks = sh.worksheet('Sheet 1')
all = wks.get_all_records() #Get a list of dictionaries from the sheet
tab = 'Column number 3' #We will search and change the value from this column
cell_to_update = (all[0].get(tab)) #If we print() this, we get value of cell C2
wks.update(cell_to_update,'any text')
I do not know why, but it updates cell A1, although it should update cell C2
Thanks to a tip from #BRemmelzwaal, I found the answer to my question:
all = wks.get_all_records() #Get a list of dictionaries from the sheet
tab = 'Column number 3' #We will search and change the value from this column
value_to_update = (all[0].get(tab)) #If we print() this, we get value of cell C2
cell_to_update = wks.find(str(value_to_update))
wks.update_cell(cell_to_update.row, cell_to_update.col, 'any text')

Conditional_formatting to find duplicate values using openpyxl

I am trying to have a class highlight any duplicate values in column A using openpyxl.
Currently column A has the following values:
A
A
B
C
A
C
A
The end result would have all of the A's and C's cells colored in red. My code below doesn't throw any errors, but the file when opened after being ran through this doesn't have the any coloring in the duplicated cells.
import openpyxl
from openpyxl import formatting, styles
from openpyxl.formatting import Rule
class Duplicates():
def __init__(self, wb2):
self.wb2 = wb2
ws2=self.wb2.active
self.red_fill = styles.PatternFill(start_color ='ffc7ce', end_color = 'ffc7ce', fill_type = 'solid')
dxf= styles.differential.DifferentialStyle(fill=self.red_fill)
rule = Rule(type='duplicateValues',dxf=dxf,stopIfTrue = None)
ws2.conditional_formatting.add('$A:$A',rule)
self.wb2.save('testing.xlsx')
Duplicates(wb2)
Any help would be much appreciated.
Try this code, it work for me
red_text = Font(color="9C0006")
red_fill = PatternFill(bgColor="FFC7CE")
dxf = DifferentialStyle(font=red_text, fill=red_fill)
rule = Rule(type="duplicateValues", text="highlight", dxf=dxf)
wsRes.conditional_formatting.add('B1:B10000', rule)

Switching data source for bokeh serve hbar plot not working: Blank plot

The Problem
I wanted to create an interactive hbar plot, where you can switch between 3 different data sources, using a select widget, a python callback and a local bokeh serve. The plot with the default source renders fine, but when I switch to a different source, the y labels stay the same and the plot turns blank. Changing back to the original value on the select widget does not show the plot I started out with and stays blank.
When I hard-code the inital source to a different one in the code, it renders just fine until I switch it by using the widget again, so the data itself seems to work fine individually.
Am I missing something? I read through many threads, docs and tutorials but can't find anything wrong with my code.
Here is what I have done so far:
I read a .csv and create 3 seperate dataframes and then convert then to columndatasources. Every source has 10 data entries with the columns "species", "ci_lower" and "ci_upper".
Here is an example of one source (all three are built exactly the same way, with different taxon classes):
df = pd.read_csv(os.path.join(os.path.dirname(__file__), "AZA_MLE_Jul2018_utf8.csv",), encoding='utf-8')
m_df = df[df["taxon_class"]=="Mammalia"]
m_df = m_df.sort_values(by="mle", ascending=False)
m_df = m_df.reset_index(drop=True)
m_df = m_df.head(10)
m_df = m_df.sort_values(by="species", ascending=False)
m_df = m_df.reset_index(drop=True)
m_source = bp.ColumnDataSource(m_df)
I saved all 3 sources in a dict:
sources_dict={
"Mammalia": m_source,
"Aves": a_source,
"Reptilia": r_source
}
... and then created my variable called "source" that should change interactively with the "Mammalia" source as default:
source = sources_dict["Mammalia"]
Next I created a figure and added a hbar plot with the source variable as follows:
plot = bp.figure(x_range=(0, np.amax(source.data["ci_upper"])+5), y_range=source.data["species"])
plot.hbar(y="species", right="ci_lower", left="ci_upper", height=0.5, fill_color="#b3de69", source=source)
Then I added the select widget with a python callback:
def select_handler(attr, old, new):
source.data["species"]=sources_dict[new].data["species"]
source.data["ci_lower"]=sources_dict[new].data["ci_lower"]
source.data["ci_upper"]=sources_dict[new].data["ci_upper"]
select = Select(title="Taxonomic Class:", value="Mammalia", options=list(sources_dict.keys()))
select.on_change("value", select_handler)
curdoc().add_root(bk.layouts.row(plot, select))
I tried this:
My suspicion was that the error lies within the callback function, so I tried many different variants, all with the same bad result. I will list some of them here:
I tried using a python native dictionary:
new_data= {
'species': sources_dict[new].data["species"],
'ci_lower': sources_dict[new].data["ci_lower"],
'ci_upper': sources_dict[new].data["ci_upper"]
}
source.data=new_data
I tried assigning the whole data source, not just swapping the data
source=sources_dict[new]
I also tried using dict()
source.data = dict(species=sources_dict[new].data["species"], ci_lower=sources_dict[new].data["ci_lower"], ci_upper=sources_dict[new].data["ci_upper"])
Screenshots
Here is a screenshot of the initial plot, when I run the py file with bokeh serve --show file.py
And here one after changing the selected value:
Would greatly appreaciate any hints that could help me figure this out
Answering your question in the comment, changing data does not change the ranges because y_range=some_thing is just a convenience over creating a proper range class that's done behind the curtain.
Here's how you can do it manually. Notice that I don't touch x_range at all - by default it's DataRange1d that computes its start/end values automatically.
from bokeh.io import curdoc
from bokeh.layouts import column
from bokeh.models import Select, ColumnDataSource
from bokeh.plotting import figure
d1 = dict(x=[0, 1], y=['a', 'b'])
d2 = dict(x=[8, 9], y=['x', 'y'])
ds = ColumnDataSource(d1)
def get_factors(data):
return sorted(set(data['y']))
p = figure(y_range=get_factors(d1))
p.circle(x='x', y='y', source=ds)
s = Select(options=['1', '2'], value='1')
def update(attr, old, new):
if new == '1':
ds.data = d1
else:
ds.data = d2
p.y_range.factors = get_factors(ds.data)
s.on_change('value', update)
curdoc().add_root(column(p, s))

How to use `ListCtrl` on wxpython

How can I append row and it's corresponding data into ListCtrl.
I've just finished how to use TreeCtrl(Relatively easier than ListCtrl), it shows me a clear usage of matching single GUI object and data. But ListCtrl dose not.
How can I append or insert single row with it's corresponding data.
How can I access row and it's data
How can I manipulated them (Editing data/row, Deleting data/row)
Can you explain summary of them? Thank you.
I know my question is so simple and I can get about this from doc somewhat.
I read docs, but still I got no clue
I know that wxPython docs are retarded and gives no much help, here is some quick tips below,
i added explanations in comments:
# create new list control
listctrl = wx.dataview.DataViewListCtrl( my_panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.dataview.DV_SINGLE )
# setup listctrl columns
listctrl.AppendTextColumn('first name', width=220) # normal text column
listctrl.AppendBitmapColumn('my images', 0, width=35) # you can add images in this col
listctrl.AppendProgressColumn('Progress', align=wx.ALIGN_CENTER) # a progress bar
listctrl.SetRowHeight(30) # define all rows height
# add data, note myList is a list or tuple contains the exact type of data for each columns and same length as col numbers
listctrl.AppendItem(myList)
# to modify an entry "a single cell located at row x col"
listctrl.SetValue(myNewValue, row, column)
this is what works for me:
import wx
il_icons = wx.ImageList(16, 16, mask=True, initialCount=2)
il_icons.Add(wx.Bitmap('icon01.png'))
il_icons.Add(wx.Bitmap('icon02.png'))
lc_list = wx.ListCtrl(self, wx.ID_ANY, style=wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_EDIT_LABELS | wx.LC_VRULES, name='lc_list')
lc_list.AssignImageList(il_icons, which=wx.IMAGE_LIST_SMALL)
lc_list.AppendColumn('col01', format=wx.LIST_FORMAT_LEFT, width=64)
lc_list.AppendColumn('col02', format=wx.LIST_FORMAT_RIGHT, width=64)
lc_list.Append(('item01',100))
lc_list.Append(('item02',200))
lc_list.SetItemColumnImage(0,0,0)
lc_list.SetItemColumnImage(1,0,1)
lc_list.Bind(wx.EVT_LIST_ITEM_SELECTED, OnItemSelected)
lc_list.Show(True)

Apply borders to all cells in a range with openpyxl

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!
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')
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.
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')
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')
#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'))))
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
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'))
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
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

Categories

Resources