我想在Python的xlsxwriter中模拟Excel自动调整功能。根据此网址,它不是直接支持的: http://xlsxwriter.readthedocs.io/worksheet.html
但是,循环遍历工作表上的每个单元格并确定列的最大大小应该非常简单,只需使用worksheet.set_column(row,col,width)来设置宽度。
阻止我写这篇文章的并发症是:
答案 0 :(得分:20)
作为一般规则,您希望列的宽度略大于列中最长字符串的大小。 xlsxwriter列的1个单位大约等于一个字符的宽度。因此,您可以通过将每列设置为该列中的最大字符数来模拟自动调整。
例如,在使用pandas dataframes和xlsxwriter时,我倾向于使用下面的代码。
它首先找到索引的最大宽度,它始终是pandas的左列,用于excel渲染数据帧。然后,它返回从左到右移动的每个剩余列的所有值的最大值和列名称。
对于您正在使用的任何数据而言,调整此代码并不太难。
def get_col_widths(dataframe):
# First we find the maximum length of the index column
idx_max = max([len(str(s)) for s in dataframe.index.values] + [len(str(dataframe.index.name))])
# Then, we concatenate this to the max of the lengths of column name and its values for each column, left to right
return [idx_max] + [max([len(str(s)) for s in dataframe[col].values] + [len(col)]) for col in dataframe.columns]
for i, width in enumerate(get_col_widths(dataframe)):
worksheet.set_column(i, i, width)
答案 1 :(得分:4)
我同意Cole Diamond的观点。我需要做一些非常相似的事情,它对我来说很好。其中self.columns是我的列列表
def set_column_width(self):
length_list = [len(x) for x in self.columns]
for i, width in enumerate(length_list):
self.worksheet.set_column(i, i, width)
答案 2 :(得分:3)
我最近遇到了同样的问题,这就是我提出的问题:
r = 0
c = 0
for x in list:
worksheet.set_column('{0}:{0}'.format(chr(c + ord('A'))), len(str(x)) + 2)
worksheet.write(r, c, x)
c += 1
在我的示例中,r
将是您输出的行号,c
将是您输出的列号(均为0索引),x
将是来自list
的值,您想要在单元格中。
'{0}:{0}'.format(chr(c + ord('A')))
部分获取提供的列号并将其转换为xlsxwriter接受的列字母,因此如果c = 0
set_column
看到'A:A'
,则{{1}然后它会看到c = 1
,依此类推。
'B:B'
段确定您尝试输出的字符串的长度,然后向其添加2以确保excel单元格足够宽,因为字符串的长度与宽度不完全相关细胞。您可能想玩,而不是根据您的数据添加len(str(x)) + 2
或更多。
xlsxwriter接受的单位有点难以解释。当您处于Excel中并将鼠标悬停在可以更改列宽的位置时,您将看到2
。在这个例子中,它接受的单位是Width: 8.43 (64 pixels)
,我认为是厘米?但是excel甚至没有提供一个单元,至少没有明确说明。
注意:我只在包含1行数据的excel文件上尝试过此答案。如果您有多行,则需要确定哪一行具有“最长”信息并仅将其应用于该行。但是,如果每列不管行数大致相同,那么这应该适合你。
祝你好运,我希望这会有所帮助!
答案 3 :(得分:2)
我在Github site of xlsxwriter上找到了另一种模拟Autofit的解决方法。我修改它以返回水平文本(列宽)或90°旋转文本(行高)的近似大小:
from PIL import ImageFont
def get_cell_size(value, font_name, font_size, dimension="width"):
""" value: cell content
font_name: The name of the font in the target cell
font_size: The size of the font in the target cell """
font = ImageFont.truetype(font_name, size=font_size)
(size, h) = font.getsize(str(value))
if dimension == "height":
return size * 0.92 # fit value experimentally determined
return size * 0.13 # fit value experimentally determined
这不涉及可能影响文本大小的粗体文本或其他格式元素。否则它的效果非常好。
要查找自动调整列的宽度:
def get_col_width(data, font_name, font_size, min_width=1):
""" Assume 'data' to be an iterable (rows) of iterables (columns / cells)
Also, every cell is assumed to have the same font and font size.
Returns a list with the autofit-width per column """
colwidth = [min_width for col in data[0]]
for x, row in enumerate(data):
for y, value in enumerate(row):
colwidth[y] = max(colwidth[y], get_cell_size(value, font_name, font_size))
return colwidth
答案 4 :(得分:2)
该URL没有指定set_column的第三个参数的单位。
列宽以字体Calibri,大小11(这是Excel标准)中'0'字符宽度的倍数给出。
我找不到一种方法来测量要插入单元格中的项目的宽度。
为了掌握字符串的确切宽度,可以使用tkinter
的功能来测量以像素为单位的字符串长度,具体取决于字体/大小/粗细/等。如果您定义字体,例如
reference_font = tkinter.font.Font(family='Calibri', size=11)
之后,您可以使用其measure
方法来确定像素的字符串宽度,例如
reference_font.measure('This is a string.')
为了对Excel表中的单元格执行此操作,需要考虑其格式(它包含有关所用字体的所有信息)。这意味着,如果您使用worksheet.write(row, col, cell_string, format)
向表中写了一些东西,则可以得到如下使用的字体:
used_font = tkinter.font.Font(family = format.font_name,
size = format.font_size,
weight = ('bold' if format.bold else 'normal'),
slant = ('italic' if format.italic else 'roman'),
underline = format.underline,
overstrike = format.font_strikeout)
然后将像元宽度确定为
cell_width = used_font.measure(cell_string+' ')/reference_font.measure('0')
将空格添加到字符串以提供一定的空白。这样,结果实际上非常接近Excel的自动拟合结果,因此我认为Excel就是这样做的。
要使tkinter
魔术起作用,必须打开tkinter.Tk()
实例(一个窗口),因此返回所需单元格宽度的函数的完整代码应如下所示:
import tkinter
import tkinter.font
def get_cell_width(cell_string, format = None):
root = tkinter.Tk()
reference_font = tkinter.font.Font(family='Calibri', size=11)
if format:
used_font = tkinter.font.Font(family = format.font_name,
size = format.font_size,
weight = ('bold' if format.bold else 'normal'),
slant = ('italic' if format.italic else 'roman'),
underline = format.underline,
overstrike = format.font_strikeout)
else:
used_font = reference_font
cell_width = used_font.measure(cell_string+' ')/reference_font.measure('0')
root.update_idletasks()
root.destroy()
return cell_width
当然,如果要经常执行root
处理和参考字体创建功能,则当然可以。另外,为工作簿使用查找表格式->字体可能会更快,这样就不必每次都定义使用的字体。
最后,可以处理单元格字符串中的换行符:
pixelwidths = (used_font.measure(part) for part in cell_string.split('\n'))
cell_width = (max(pixelwidths) + used_font.measure(' '))/reference_font.measure('0')
此外,如果您使用的是Excel过滤器功能,则下拉箭头符号还需要另外18个像素(在Excel中为100%缩放)。而且可能会有合并的单元格跨越多列...有很大的改进空间!
xlsxwriter似乎没有读回特定单元格的方法。这意味着我在编写单元格时需要跟踪每个单元格的宽度。如果我可以遍历所有单元格,那将更好,这样可以编写通用例程。
如果您不希望在自己的数据结构中保持跟踪,则至少可以采取三种方法:
(A)注册一个写处理程序来完成这项工作:
您可以为所有标准类型注册写处理程序。在处理程序函数中,您只需传递写命令,还可以执行簿记wrt。列宽。这样,您只需要最后读取并设置最佳列宽(在关闭workbook
之前)。
# add worksheet attribute to store column widths
worksheet.colWidths = [0]*number_of_used_columns
# register write handler
for stdtype in [str, int, float, bool, datetime, timedelta]:
worksheet.add_write_handler(stdtype, colWidthTracker)
def colWidthTracker(sheet, row, col, value, format):
# update column width
sheet.colWidths[col] = max(sheet.colWidths[col], get_cell_width(value, format))
# forward write command
if isinstance(value, str):
if value == '':
sheet.write_blank(row, col, value, format)
else:
sheet.write_string(row, col, value, format)
elif isinstance(value, int) or isinstance(value, float):
sheet.write_number(row, col, value, format)
elif isinstance(value, bool):
sheet.write_boolean(row, col, value, format)
elif isinstance(value, datetime) or isinstance(value, timedelta):
sheet.write_datetime(row, col, value, format)
else:
raise TypeError('colWidthTracker cannot handle this type.')
# and in the end...
for col in columns_to_be_autofitted:
worksheet.set_column(col, col, worksheet.colWidths[col])
(B)使用karolyi's answer above浏览XlsxWriter内部变量中存储的数据。但是,这是discouraged by the module's author,因为它可能会在将来的版本中中断。
(C)遵循recommendation of jmcnamara:继承并覆盖默认工作表类,并添加一些自动拟合代码,例如以下示例:xlsxwriter.readthedocs.io/example_inheritance2.html
答案 5 :(得分:1)
这是支持行和列的MultiIndex的代码版本-不太漂亮,但对我有用。它扩展到@ cole-diamond答案:
def _xls_make_columns_wide_enough(dataframe, worksheet, padding=1.1, index=True):
def get_col_widths(dataframe, padding, index):
max_width_idx = []
if index and isinstance(dataframe.index, pd.MultiIndex):
# Index name lengths
max_width_idx = [len(v) for v in dataframe.index.names]
# Index value lengths
for column, content in enumerate(dataframe.index.levels):
max_width_idx[column] = max(max_width_idx[column],
max([len(str(v)) for v in content.values]))
elif index:
max_width_idx = [
max([len(str(s))
for s in dataframe.index.values] + [len(str(dataframe.index.name))])
]
if isinstance(dataframe.columns, pd.MultiIndex):
# Take care of columns - headers first.
max_width_column = [0] * len(dataframe.columns.get_level_values(0))
for level in range(len(dataframe.columns.levels)):
values = dataframe.columns.get_level_values(level).values
max_width_column = [
max(v1, len(str(v2))) for v1, v2 in zip(max_width_column, values)
]
# Now content.
for idx, col in enumerate(dataframe.columns):
max_width_column[idx] = max(max_width_column[idx],
max([len(str(v)) for v in dataframe[col].values]))
else:
max_width_column = [
max([len(str(s)) for s in dataframe[col].values] + [len(col)])
for col in dataframe.columns
]
return [round(v * padding) for v in max_width_idx + max_width_column]
for i, width in enumerate(get_col_widths(dataframe, padding, index)):
worksheet.set_column(i, i, width)
答案 6 :(得分:0)
我的版本将遍历一个工作表并自动设置字段长度:
from typing import Optional
from xlsxwriter.worksheet import (
Worksheet, cell_number_tuple, cell_string_tuple)
def get_column_width(worksheet: Worksheet, column: int) -> Optional[int]:
"""Get the max column width in a `Worksheet` column."""
strings = getattr(worksheet, '_ts_all_strings', None)
if strings is None:
strings = worksheet._ts_all_strings = sorted(
worksheet.str_table.string_table,
key=worksheet.str_table.string_table.__getitem__)
lengths = set()
for row_id, colums_dict in worksheet.table.items(): # type: int, dict
data = colums_dict.get(column)
if not data:
continue
if type(data) is cell_string_tuple:
iter_length = len(strings[data.string])
if not iter_length:
continue
lengths.add(iter_length)
continue
if type(data) is cell_number_tuple:
iter_length = len(str(data.number))
if not iter_length:
continue
lengths.add(iter_length)
if not lengths:
return None
return max(lengths)
def set_column_autowidth(worksheet: Worksheet, column: int):
"""
Set the width automatically on a column in the `Worksheet`.
!!! Make sure you run this function AFTER having all cells filled in
the worksheet!
"""
maxwidth = get_column_width(worksheet=worksheet, column=column)
if maxwidth is None:
return
worksheet.set_column(first_col=column, last_col=column, width=maxwidth)
只需在列中调用set_column_autowidth
。
答案 7 :(得分:0)
Cole Diamond's answer很棒。我刚刚更新了子例程来处理多索引行和列。
def get_col_widths(dataframe):
# First we find the maximum length of the index columns
idx_max = [max([len(str(s)) for s in dataframe.index.get_level_values(idx)] + [len(str(idx))]) for idx in dataframe.index.names]
# Then, we concatenate this to the max of the lengths of column name and its values for each column, left to right
return idx_max + [max([len(str(s)) for s in dataframe[col].values] + \
[len(str(x)) for x in col] if dataframe.columns.nlevels > 1 else [len(str(col))]) for col in dataframe.columns]