web-dev-qa-db-fra.com

Simuler une colonne d'ajustement automatique dans xlsxwriter

Je voudrais simuler la fonction d'ajustement automatique d'Excel dans xlsxwriter de Python. Selon cette URL, il n'est pas directement pris en charge: http://xlsxwriter.readthedocs.io/worksheet.html

Cependant, il devrait être assez simple de parcourir chaque cellule de la feuille, de déterminer la taille maximale de la colonne et d’utiliser simplement worksheet.set_column (row, col, width) pour définir la largeur.

Les complications qui m'empêchent d'écrire ceci sont:

  1. Cette URL ne spécifie pas quelles sont les unités pour le troisième argument de set_column.
  2. Je n'arrive pas à trouver un moyen de mesurer la largeur de l'élément que je veux insérer dans la cellule.
  3. xlsxwriter ne semble pas avoir de méthode pour relire une cellule particulière. Cela signifie que je dois garder une trace de la largeur de chaque cellule pendant que j'écris la cellule. Ce serait mieux si je pouvais simplement parcourir toutes les cellules, ainsi une routine générique pourrait être écrite.
19
Michael Potter

En règle générale, vous voulez que la largeur des colonnes soit un peu plus grande que la taille de la chaîne la plus longue de la colonne. Le avec une unité des colonnes xlsxwriter est à peu près égal à la largeur d’un caractère. Vous pouvez donc simuler un ajustement automatique en définissant chaque colonne sur le nombre maximal de caractères de cette colonne. 

Par exemple, j’ai tendance à utiliser le code ci-dessous lorsque je travaille avec des bases de données pandas et xlsxwriter.

Il trouve d’abord la largeur maximale de l’index, qui est toujours la colonne de gauche d’un cadre de données rendu pandas à Excel. Ensuite, il renvoie le maximum de toutes les valeurs et le nom de la colonne pour chacune des colonnes restantes en se déplaçant de gauche à droite. 

Il ne devrait pas être trop difficile d’adapter ce code aux données que vous utilisez.

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)
17
Cole Diamond

Je suis d'accord avec Cole Diamond. J'avais besoin de faire quelque chose de très similaire, cela a bien fonctionné pour moi. où self.columns est ma liste de colonnes

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)
4
dfresh22

Il existe une autre solution de contournement de simulation automatique que j'ai trouvée sur le site Github de xlsxwriter . Je l'ai modifié pour retourner la taille approximative du texte horizontal (largeur de colonne) ou du texte pivoté à 90 ° (hauteur de la ligne):

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

Cela ne concerne pas le texte en gras ou d'autres éléments de format pouvant affecter la taille du texte. Sinon, ça marche plutôt bien.

Pour trouver la largeur de vos colonnes pour l'ajustement automatique:

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    
2
ascripter

J'ai récemment rencontré le même problème et voici ce que j'ai proposé:

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

Dans mon exemple, r correspond au numéro de la ligne vers laquelle vous écrivez, c correspond au numéro de la colonne sur laquelle vous indiquez (les deux indexés), et x correspond à la valeur de list que vous souhaitez insérer dans la cellule.

la pièce '{0}:{0}'.format(chr(c + ord('A'))) prend le numéro de colonne fourni et la convertit en lettre de colonne acceptée par xlsxwriter. Ainsi, si c = 0set_column verrait 'A:A', si c = 1, il verrait aussi 'B:B', etc.

la pièce len(str(x)) + 2 détermine la longueur de la chaîne que vous essayez de générer, puis ajoute 2 unités pour garantir que la cellule Excel est suffisamment large, car la longueur de la chaîne ne correspond pas exactement à la largeur de la cellule. Vous voudrez peut-être jouer avec plutôt que vous ajoutez 2 ou éventuellement plus en fonction de vos données.

Les unités acceptées par xlsxwriter sont un peu plus difficiles à expliquer. Lorsque vous êtes dans Excel et que vous survolez la position où vous pouvez modifier la largeur de la colonne, vous verrez Width: 8.43 (64 pixels). Dans cet exemple, l’unité qu’elle accepte est le 8.43, que j’estime être le centimètre? Mais Excel ne fournit même pas une unité, du moins pas explicitement.

Remarque: Je n'ai essayé cette réponse que sur des fichiers Excel contenant une ligne de données. Si vous avez plusieurs lignes, vous aurez besoin d'un moyen de déterminer quelle ligne contiendra l'information la plus longue et de l'appliquer uniquement à cette ligne. Mais si chaque colonne aura à peu près la même taille quelle que soit la ligne, cela devrait fonctionner correctement pour vous.

Bonne chance et j'espère que cela aide!

2
RC_Crusher07

Voici une version du code qui prend en charge MultiIndex pour les lignes et les colonnes - ce n’est pas joli mais fonctionne pour moi. Il développe la réponse @ 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)
1
Sebastian

La réponse de Cole Diamond est géniale. Je viens de mettre à jour le sous-programme pour gérer les lignes et les colonnes multi-index.

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]
0
Soumitra

Ma version qui va parcourir la feuille de calcul et définir automatiquement la longueur des champs:

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)

il suffit d'appeler set_column_autowidth avec la colonne.

0
karolyi