exceljs icon indicating copy to clipboard operation
exceljs copied to clipboard

Auto Column Width

Open iwanjunaid opened this issue 9 years ago • 23 comments

Is there a way to set column width based on cell's content length ?

thank you

iwanjunaid avatar Mar 14 '16 02:03 iwanjunaid

+1 Would be very helpful!

Toub avatar Jun 19 '16 19:06 Toub

+1

CassianoNeves avatar Jul 18 '16 20:07 CassianoNeves

+1

richardaum avatar Aug 02 '16 14:08 richardaum

+1

hmtuan196 avatar Sep 26 '16 02:09 hmtuan196

+1

StephenErstad-zz avatar Oct 11 '16 19:10 StephenErstad-zz

+1

rhernandez-itemsoft avatar Nov 09 '16 05:11 rhernandez-itemsoft

look #36

mmattosr avatar Nov 11 '16 18:11 mmattosr

+1

lnmput avatar Jan 17 '18 06:01 lnmput

Hopfully this because something built into ExcelJS down the road, but in the meantime this is how I solved the issue:

const wb = new Excel.Workbook()
const ws = wb.addWorksheet('TestSheet')

ws.columns = [
  { header: 'Column1', key: 'col1'},
  { header: 'ColumnTwo', key: 'col2'},
  { header: 'Col3', key: 'col3'},
  { header: 'TheGreatColFour', key: 'col4'},
  { header: 'JustCol5', key: 'col5'},
]

ws.columns.forEach(column => {
  column.width = column.header.length < 12 ? 12 : column.header.length
})

This allows you to have a minimum width (12) or base the column width off the number of characters in the header string. Sadly, unless you're using a mono typeface this isn't going to be exactly accurate, but it'll get you a lot closer.

If you need to base it off of contents instead of header length, you can always loop through the rows to find the longest string length and base the width off of that.

CrescentCraftorium avatar Jan 17 '18 16:01 CrescentCraftorium

there is an array with values ​​if I add the row all well, all values ​​are exported to excel, but I can not build the column ((( who can do??

PashaFrontEnd avatar Apr 14 '18 06:04 PashaFrontEnd

I've had some luck with the still rough but maybe more accurate approach below in TypeScript:

/** Determined this value based upon experimentation */
export const PIXELS_PER_EXCEL_WIDTH_UNIT = 7.5

export function autoSize(sheet: excel.Worksheet, fromRow: number) {
	const canvas = document.createElement('canvas')
	const ctx = canvas.getContext('2d')
	if (!ctx) {
		return
	}

	const maxColumnLengths: Array<number> = []
	sheet.eachRow((row, rowNum) => {
		if (rowNum < fromRow) {
			return
		}

		row.eachCell((cell, num) => {
			if (typeof cell.value === 'string') {
				if (maxColumnLengths[num] === undefined) {
					maxColumnLengths[num] = 0
				}

				const fontSize = cell.font && cell.font.size ? cell.font.size : 11
				ctx.font = `${fontSize}pt Arial`
				const metrics = ctx.measureText(cell.value)
				const cellWidth = metrics.width
				
				maxColumnLengths[num] = Math.max(maxColumnLengths[num], cellWidth)
			}
		})
	})

	for (let i = 1; i <= sheet.columnCount; i++) {
		const col = sheet.getColumn(i)
		const width = maxColumnLengths[i]
		if (width) {
			col.width = width / PIXELS_PER_EXCEL_WIDTH_UNIT + 1
		}
	}
}

karlvr avatar Mar 04 '19 05:03 karlvr

This worked well for me :

`// Auto width column const workbook = new Excel.Workbook(); const sheet1 = workbook.addWorksheet('sheet1');

  for (let i = 0; i < sheet1.columns.length; i += 1) { 
    let dataMax = 0;
    const column = sheet1.columns[i];
    for (let j = 1; j < column.values.length; j += 1) {
      const columnLength = column.values[j].length;
      if (columnLength > dataMax) {
        dataMax = columnLength;
      }
    }
    column.width = dataMax < 10 ? 10 : dataMax;
  }`

ameliarahman avatar Nov 12 '19 07:11 ameliarahman

+1

mllrdev-miguelsobong avatar Nov 26 '19 11:11 mllrdev-miguelsobong

+1

cerinoligutom avatar Nov 26 '19 12:11 cerinoligutom

This worked well for me :

`// Auto width column const workbook = new Excel.Workbook(); const sheet1 = workbook.addWorksheet('sheet1');

  for (let i = 0; i < sheet1.columns.length; i += 1) { 
    let dataMax = 0;
    const column = sheet1.columns[i];
    for (let j = 1; j < column.values.length; j += 1) {
      const columnLength = column.values[j].length;
      if (columnLength > dataMax) {
        dataMax = columnLength;
      }
    }
    column.width = dataMax < 10 ? 10 : dataMax;
  }`

Very nice explanation, but when I tried this has a performance issue, my worksheet has lots of rows /columns running into thousands. Any help on optimising my response time.

boyepanthera avatar Feb 14 '20 08:02 boyepanthera

This way of approching @ameliarahman solution gave me better performance. hope it helps.

Of course this will only work if you only have strings and cells with data in all cells, otherwise you need to add the correspondent validations, like checking if cell.value is not null or if cell.value is a number, and act accordingly.

this.worksheet.columns.forEach(function(column){
	var dataMax = 0;
	column.eachCell({ includeEmpty: true }, function(cell){
		var columnLength = cell.value.length;	
		if (columnLength > dataMax) {
			dataMax = columnLength;
		 }
       })
       column.width = dataMax < 10 ? 10 : dataMax;
});

davidconvista avatar Jul 07 '20 10:07 davidconvista

A bit different solution and in TypeScript:

private _autosizeColumnCells({columns}: Worksheet) {
    let dataMax: number[];
    let max: number;
    columns.forEach((column: Column) => {
        dataMax = [];
        column.eachCell({includeEmpty: false}, (cell: Cell) => {
            dataMax.push(cell.value?.toString().length || 0);
        });
        max = Math.max(...dataMax);
        column.width = max < 10 ? 10 : max;
    });
}`

weihsth avatar Oct 24 '20 10:10 weihsth

worksheet.columns.forEach(function (column) {
  var dataMax = 0;
  column.eachCell({ includeEmpty: true }, function (cell) {
    dataMax = cell.value?cell.value.toString().length:0;
  })
  column.width = dataMax < 10 ? 10 : dataMax;
});

pattersongarth avatar Oct 25 '20 06:10 pattersongarth

worksheet.columns.forEach(function (column) {
  var dataMax = 0;
  column.eachCell({ includeEmpty: true }, function (cell) {
    dataMax = cell.value?cell.value.toString().length:0;
  })
  column.width = dataMax < 10 ? 10 : dataMax;
});

although the cells can be expanded normally, this calculation method seems to be inaccurate. do you know the default unit of width? cm or wch?

hellobq avatar Dec 29 '20 03:12 hellobq

A little bit diff way to make it reusable for all worksheets.

`class Exporter{ widthConfig(data) { data.forEach(function(column){ var dataMax = 0; column.eachCell({ includeEmpty: true }, function(cell){ var columnLength = cell.value.length; if (columnLength > dataMax) { dataMax = columnLength; } }) column.width = dataMax <= 10 ? 25 : dataMax; console.log('column.width: ', column.width) });

}

export(){ this.widthConfig(worksheet.columns) } }`

mvermadev avatar Feb 28 '21 05:02 mvermadev

Feel free to use my solution

/**
 * Autofit columns by width
 *
 * @param worksheet {ExcelJS.Worksheet}
 * @param minimalWidth
 */
const autoWidth = (worksheet, minimalWidth = 10) => {
    worksheet.columns.forEach((column) => {
        let maxColumnLength = 0;
        column.eachCell({ includeEmpty: true }, (cell) => {
            maxColumnLength = Math.max(
                maxColumnLength,
                minimalWidth,
                cell.value ? cell.value.toString().length : 0
            );
        });
        column.width = maxColumnLength + 2;
    });
};

MeisQuietude avatar Mar 18 '21 12:03 MeisQuietude

Inspired by @MeisQuietude solution for autoHeight

const autoHeight = (worksheet) => {
    const lineHeight = 12 // height per line is roughly 12
    worksheet.eachRow((row) => {
        let maxLine = 1
        row.eachCell((cell) => {
            maxLine = Math.max(cell.value.split('\n').length - 1, maxLine)
        })
        row.height = lineHeight * maxLine
    })
}

Caveat: Performance will be slow due to the iteration of between rows and cells!

ttcy avatar Aug 28 '21 14:08 ttcy

I Use this logic for resizing the height & should also work with merged cells. public static autoCellHeight(workSheet : Worksheet , rowNumberMin ? : number , rowNumberMax ? : number) { let columnWidths = workSheet.columns.map((column) => column.width); if(rowNumberMin && rowNumberMax) { for(let i = (rowNumberMin || 1); i <= (rowNumberMax || workSheet.lastRow.number); i++) { let multiplier = 1; let mergedCells = 0; let mergedCellWidth = 0; let mergedContentWidth = 0; workSheet.getRow(i).eachCell({ includeEmpty: false },(cell, colNumber)=> { if(cell.isMerged) { // @ts-ignore if(cell._mergeCount) { // @ts-ignore mergedCells = cell._mergeCount + 1; mergedContentWidth = cell.value?.toString().length || 0; } mergedCellWidth = mergedCellWidth + columnWidths[colNumber - 1]; mergedCells = mergedCells - 1; if(mergedCells === 0) { multiplier = Math.max(Math.ceil(mergedContentWidth / mergedCellWidth) , multiplier); mergedCells= 0; mergedCellWidth= 0; mergedContentWidth = 0; } } else { multiplier = Math.max((cell.value ? Math.ceil((cell.value.toString().length + 1) / columnWidths[colNumber - 1]) : 1), multiplier); } }); workSheet.getRow(i).height = (workSheet.getRow(i).height || 15.75) * multiplier; } } } if anyone can give me what scaler can be used for different font sizes for arial that would be really helpful

aditya9970 avatar Sep 08 '22 16:09 aditya9970

Here is the answer I was looking for, hope it would be helpful for someone. range.format.autofitColumns(); https://learn.microsoft.com/en-us/javascript/api/excel/excel.rangeformat?view=excel-js-preview#excel-excel-rangeformat-autofitcolumns-member(1)

For height you can see https://learn.microsoft.com/en-us/javascript/api/excel/excel.rangeformat?view=excel-js-preview#excel-excel-rangeformat-autofitrows-member(1)

anton-advasoftware avatar Sep 22 '22 21:09 anton-advasoftware

Here is the answer I was looking for, hope it would be helpful for someone. range.format.autofitColumns(); https://learn.microsoft.com/en-us/javascript/api/excel/excel.rangeformat?view=excel-js-preview#excel-excel-rangeformat-autofitcolumns-member(1)

For height you can see https://learn.microsoft.com/en-us/javascript/api/excel/excel.rangeformat?view=excel-js-preview#excel-excel-rangeformat-autofitrows-member(1)

Unfortunately, that is the Office Add-ins JavaScript API, which will only work inside an instance of Excel. Autofitting columns is an internal API to Excel program itself, and people have been trying to figure out how to gracefully implement it within the Office Open XML spec for years ( what everyone besides Microsoft has to grapple with to create XLSX files ). The best solution to do this (still kindof a hack) has been around for a long time, but It only works when the spreadsheet is opened by excel, and only sometimes... you have to set these attributes on the columns of a worksheet : {BestFit : true, CustomWidth : true} There are caveats to this since this is kindof a backwards way of achieving autofit. See: how to use "bestfit" attribute to autofit column width - MSDN also ECMA-376 - Office Open XML - 1st Edition

rnwelsh avatar Sep 28 '22 22:09 rnwelsh

@MeisQuietude I'm using Angular, so I adapted your solution to use it in service like a method:

import * as ExcelJS from 'exceljs';

@Injectable()
export class ExportService {
// code
    private setHeadersWidth(worksheet: ExcelJS.Worksheet): void {
        const minColumnLength: number = 10;
        worksheet.eachColumnKey((column: ExcelJS.Column): void => {
            let columnLength = 0;
            column.eachCell((cell: ExcelJS.Cell): void => {
                columnLength = Math.max(columnLength, minColumnLength, cell?.value ? cell.value?.toString().length : 0);
            });
            column.width = columnLength + 2;
        });
    }
// code
}

RomanVavryn avatar Jan 17 '23 13:01 RomanVavryn

When there's a numFmt involved, especially for Date values, the solutions given above can produce very inaccurate results. Below is my solution that uses some of the concepts previously suggested, as well as the SSF library to format cell values with numFmt before getting the length.

import { format } from 'ssf';

// Convert Date object to Microsoft serial date aka ms date aka OA date
const dateToSerial = (date: Date): number => {
  const timezoneOffset = date.getTimezoneOffset() / (60 * 24);
  const msDate = date.getTime() / 86400000 + (25569 - timezoneOffset);
  return msDate;
};

const autoFitColumn = (column: ExcelJS.Column) => {
  const numFmt = column.numFmt;
  let maxLength = 6;
  column.eachCell({ includeEmpty: true }, (cell: ExcelJS.Cell) => {
    let columnLength: number;
    if (numFmt && cell.value != undefined) {
      switch (cell.type) {
        case ExcelJS.ValueType.Date:
          const serialDate = dateToSerial(cell.value as Date);
          const formattedDate = format(numFmt, serialDate);
          columnLength = formattedDate.length;
          break;
        case ExcelJS.ValueType.Number:
          const formattedNumber = format(numFmt, cell.value as Number);
          columnLength = formattedNumber.length;
          break;
        default:
          const formatted = format(numFmt, cell.value);
          columnLength = formatted.length;
          break;
      }
    } else {
      columnLength = cell.text.length;
    }
    maxLength = Math.max(maxLength, columnLength);
  });
  column.width = maxLength + 2;
};

eberridge avatar Feb 12 '23 07:02 eberridge

When there's a numFmt involved, especially for Date values, the solutions given above can produce very inaccurate results. Below is my solution that uses some of the concepts previously suggested, as well as the SSF library to format cell values with numFmt before getting the length.

import { format } from 'ssf';

// Convert Date object to Microsoft serial date aka ms date aka OA date
const dateToSerial = (date: Date): number => {
  const timezoneOffset = date.getTimezoneOffset() / (60 * 24);
  const msDate = date.getTime() / 86400000 + (25569 - timezoneOffset);
  return msDate;
};

const autoFitColumn = (column: ExcelJS.Column) => {
  const numFmt = column.numFmt;
  let maxLength = 6;
  column.eachCell({ includeEmpty: true }, (cell: ExcelJS.Cell) => {
    let columnLength: number;
    if (numFmt && cell.value != undefined) {
      switch (cell.type) {
        case ExcelJS.ValueType.Date:
          const serialDate = dateToSerial(cell.value as Date);
          const formattedDate = format(numFmt, serialDate);
          columnLength = formattedDate.length;
          break;
        case ExcelJS.ValueType.Number:
          const formattedNumber = format(numFmt, cell.value as Number);
          columnLength = formattedNumber.length;
          break;
        default:
          const formatted = format(numFmt, cell.value);
          columnLength = formatted.length;
          break;
      }
    } else {
      columnLength = cell.text.length;
    }
    maxLength = Math.max(maxLength, columnLength);
  });
  column.width = maxLength + 2;
};

This works really great! Only a suggestion, add cases for rich text and formula, format on those type of values throws error: This is my addition (i didn't find a solution of how to get value of formula after calculation):

        case ValueType.RichText:
          const richTextValue = (cell.value as CellRichTextValue).richText.map(prop('text')).join('');
          columnLength = richTextValue.length;
          break;
        case ValueType.Formula:
          break;
      

vladPopovschii avatar Mar 01 '23 08:03 vladPopovschii

+1

Geriano avatar Jul 15 '23 13:07 Geriano