Skip to content

Commit b87ceab

Browse files
author
Steven Silvester
authored
Merge pull request #221 from ibdafna/auto_resize
Add auto-resize function for column widths
2 parents 479cbab + 15da4f5 commit b87ceab

File tree

1 file changed

+181
-0
lines changed

1 file changed

+181
-0
lines changed

packages/datagrid/src/datagrid.ts

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ import {
6464
JSONExt
6565
} from '@lumino/coreutils';
6666

67+
import {
68+
TextRenderer
69+
} from './textrenderer';
70+
6771
/**
6872
* A widget which implements a high-performance tabular data grid.
6973
*
@@ -1477,6 +1481,75 @@ class DataGrid extends Widget {
14771481
this.repaintOverlay();
14781482
}
14791483

1484+
/**
1485+
* Auto sizes column widths based on their text content.
1486+
* @param area
1487+
*/
1488+
fitColumnNames(area: DataGrid.ColumnFitType = 'all', numCols?: number): void {
1489+
// Attempt resizing only if a data model is present.
1490+
if (this.dataModel) {
1491+
// Tracking remaining columns to be resized if numCols arg passed.
1492+
let colsRemaining = (numCols === undefined) || (numCols < 0)
1493+
? undefined
1494+
: numCols;
1495+
1496+
if (area === 'row-header' || area === 'all') {
1497+
// Respecting any column resize cap, if one has been passed.
1498+
if (colsRemaining !== undefined) {
1499+
const rowColumnCount = this.dataModel.columnCount('row-header');
1500+
/*
1501+
If we have more row-header columns than columns available
1502+
for resize, resize only remaining columns as per allowance
1503+
and set remaining resize allowance number to 0.
1504+
*/
1505+
if (colsRemaining - rowColumnCount < 0) {
1506+
this._fitRowColumnHeaders(this.dataModel, colsRemaining)
1507+
colsRemaining = 0;
1508+
} else {
1509+
/*
1510+
Otherwise the entire row-header column count can be resized.
1511+
Resize all row-header columns and subtract from remaining
1512+
column resize allowance.
1513+
*/
1514+
this._fitRowColumnHeaders(this.dataModel, rowColumnCount);
1515+
colsRemaining = colsRemaining - rowColumnCount;
1516+
}
1517+
} else {
1518+
// No column resize cap passed - resizing all columns.
1519+
this._fitRowColumnHeaders(this.dataModel);
1520+
}
1521+
1522+
}
1523+
1524+
if (area === 'body' || area === 'all') {
1525+
// Respecting any column resize cap, if one has been passed.
1526+
if (colsRemaining !== undefined) {
1527+
const bodyColumnCount = this.dataModel.columnCount('body');
1528+
/*
1529+
If we have more body columns than columns available
1530+
for resize, resize only remaining columns as per allowance
1531+
and set remaining resize allowance number to 0.
1532+
*/
1533+
if (colsRemaining - bodyColumnCount < 0) {
1534+
this._fitBodyColumnHeaders(this.dataModel, colsRemaining);
1535+
colsRemaining = 0;
1536+
} else {
1537+
/*
1538+
Otherwise the entire body column count can be resized.
1539+
Resize based on the smallest number between remaining
1540+
resize allowance and body column count.
1541+
*/
1542+
this._fitBodyColumnHeaders(this.dataModel,
1543+
Math.min(colsRemaining, bodyColumnCount));
1544+
}
1545+
} else {
1546+
// No column resize cap passed - resizing all columns.
1547+
this._fitBodyColumnHeaders(this.dataModel);
1548+
}
1549+
}
1550+
}
1551+
}
1552+
14801553
/**
14811554
* Map a client position to local viewport coordinates.
14821555
*
@@ -3718,6 +3791,108 @@ class DataGrid extends Widget {
37183791
this.drawCornerHeaderRegion(rx, ry, rw, rh);
37193792
}
37203793

3794+
/**
3795+
* Resizes body column headers so their text fits
3796+
* without clipping or wrapping.
3797+
* @param dataModel
3798+
*/
3799+
private _fitBodyColumnHeaders(dataModel: DataModel, numCols?: number): void {
3800+
// Get the body column count
3801+
const bodyColumnCount = numCols === undefined
3802+
? dataModel.columnCount('body')
3803+
: numCols;
3804+
3805+
for (let i = 0; i < bodyColumnCount; i++) {
3806+
/*
3807+
if we're working with nested column headers,
3808+
retrieve the nested levels and iterate on them.
3809+
*/
3810+
const numRows = dataModel.rowCount('column-header');
3811+
3812+
/*
3813+
Calculate the maximum text width vertically, across
3814+
all nested rows under a given column number.
3815+
*/
3816+
let maxWidth = 0;
3817+
for (let j = 0; j < numRows; j++) {
3818+
const cellValue = dataModel.data('column-header', j, i);
3819+
3820+
// Basic CellConfig object to get the renderer for that cell
3821+
let config = {
3822+
x: 0, y: 0, width: 0, height: 0,
3823+
region: 'column-header' as DataModel.CellRegion, row: 0, column: i,
3824+
value: (null as any), metadata: DataModel.emptyMetadata
3825+
};
3826+
3827+
// Get the renderer for the given cell
3828+
const renderer = this.cellRenderers.get(config) as TextRenderer;
3829+
3830+
// Use the canvas context to measure the cell's text width
3831+
const gc = this.canvasGC;
3832+
gc.font = CellRenderer.resolveOption(renderer.font, config);
3833+
const textWidth = gc.measureText(cellValue).width;
3834+
3835+
// Update the maximum width for that column.
3836+
maxWidth = Math.max(maxWidth, textWidth);
3837+
}
3838+
3839+
/*
3840+
Send a resize message with new width for the given column.
3841+
Using a padding of 15 pixels to leave some room.
3842+
*/
3843+
this.resizeColumn('body', i, maxWidth + 15);
3844+
}
3845+
}
3846+
3847+
/**
3848+
* Resizes row header columns so their text fits
3849+
* without clipping or wrapping.
3850+
* @param dataModel
3851+
*/
3852+
private _fitRowColumnHeaders(dataModel: DataModel, numCols?: number): void {
3853+
/*
3854+
if we're working with nested row headers,
3855+
retrieve the nested levels and iterate on them.
3856+
*/
3857+
const rowColumnCount = numCols === undefined
3858+
? dataModel.columnCount('row-header')
3859+
: numCols;
3860+
3861+
for (let i = 0; i < rowColumnCount; i++) {
3862+
const numCols = dataModel.rowCount('column-header');
3863+
/*
3864+
Calculate the maximum text width vertically, across
3865+
all nested columns under a given row index.
3866+
*/
3867+
let maxWidth = 0;
3868+
for (let j = 0; j < numCols; j++) {
3869+
const cellValue = dataModel.data('corner-header', j, i);
3870+
3871+
// Basic CellConfig object to get the renderer for that cell.
3872+
let config = {
3873+
x: 0, y: 0, width: 0, height: 0,
3874+
region: 'column-header' as DataModel.CellRegion, row: 0, column: i,
3875+
value: (null as any), metadata: DataModel.emptyMetadata
3876+
};
3877+
3878+
// Get the renderer for the given cell.
3879+
const renderer = this.cellRenderers.get(config) as TextRenderer;
3880+
3881+
// Use the canvas context to measure the cell's text width
3882+
const gc = this.canvasGC;
3883+
gc.font = CellRenderer.resolveOption(renderer.font, config);
3884+
const textWidth = gc.measureText(cellValue).width;
3885+
maxWidth = Math.max(maxWidth, textWidth);
3886+
}
3887+
3888+
/*
3889+
Send a resize message with new width for the given column.
3890+
Using a padding of 15 pixels to leave some room.
3891+
*/
3892+
this.resizeColumn('row-header', i, maxWidth + 15);
3893+
}
3894+
}
3895+
37213896
/**
37223897
* Paint the overlay content for the entire grid.
37233898
*
@@ -5693,6 +5868,12 @@ namespace DataGrid {
56935868
export
56945869
type HeaderVisibility = 'all' | 'row' | 'column' | 'none';
56955870

5871+
/**
5872+
* A type alias for the supported column auto resize modes.
5873+
*/
5874+
export
5875+
type ColumnFitType = 'all' | 'row-header' | 'body';
5876+
56965877
/**
56975878
* A type alias for the arguments to a copy format function.
56985879
*/

0 commit comments

Comments
 (0)