Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[data grid] Autofit Column Width #1241

Closed
zirkelc opened this issue Mar 16, 2021 · 46 comments · Fixed by #10180
Closed

[data grid] Autofit Column Width #1241

zirkelc opened this issue Mar 16, 2021 · 46 comments · Fixed by #10180
Assignees
Labels
component: data grid This is the name of the generic UI component, not the React module! feature: Rendering layout Related to the data grid Rendering engine new feature New feature or request plan: Pro Impact at least one Pro user waiting for 👍 Waiting for upvotes

Comments

@zirkelc
Copy link

zirkelc commented Mar 16, 2021

Summary 💡

It would be helpful to add fields to the columns definition to handle dynamic content where the actual size is not known beforehand. For example, to have a flag like fitContent and a minWidth/maxWidth field to make columns responsive to the actual value at runtime. Right now, I need to either specify absolute widths or proportional flex values.

Feedback from #5875:

Hopefully the header cells will be considered as well

Examples 🌈

Motivation 🔦

I built an application where users can select data from a database and the structure depends on the actual query that the user defines. For example, the user could run a query with two returning columns where the first is a string and the second a number (e.g. Product name and price). The number column does not need much space and the string column should get all the remaining space.

Use cases

  1. Autofit on double-clicking column header separator.
    This is an established pattern that has been used for years in various data grids, like MS Excel, Google Sheets, and many others.
  2. Autofit programmatically on the first render:
    • autofit a single column
    • autofit all columns
  3. Allow to include/exclude the column header:
    • If included, the column header content width will be considered when calculating the new width
    • If excluded, the column header content width will not be taken into account

Challenges

  1. Which plans should support this feature? Column resizing is a Pro feature, so it would make sense for Autofit Column Width to be a Pro feature as well.
  2. How should it work with dynamic auto height? There's not much sense in using autofit column width in this case, so we might just disable autofit in this case.

Benchmark

@zirkelc zirkelc added the status: waiting for maintainer These issues haven't been looked at yet by a maintainer label Mar 16, 2021
@oliviertassinari oliviertassinari added component: data grid This is the name of the generic UI component, not the React module! and removed status: waiting for maintainer These issues haven't been looked at yet by a maintainer labels Mar 16, 2021
@oliviertassinari
Copy link
Member

oliviertassinari commented Mar 16, 2021

One solution could be to implement an apiRef.current.autoSizeColumn() method and have developers call it once the data grid is loaded.

We likely want to implement a double click -> auto-size UX on the column headers, we could share the same method.

@oliviertassinari oliviertassinari added the new feature New feature or request label Mar 16, 2021
@dtassone
Copy link
Member

That would be a tricky one as the virtualisation doesn't render every cells so it will be tricky to calculate the width for the unrendered cells. It could also increase dynamically when we render content that does not fit the size of a cell.

@oliviertassinari
Copy link
Member

@dtassone I imagine that only using the visible rows for a given column would be enough?

@dtassone
Copy link
Member

Probably 🤔
Worth doing some benchmarking

@oliviertassinari oliviertassinari added the waiting for 👍 Waiting for upvotes label Jul 28, 2021
@iamchathu

This comment was marked as outdated.

@alxndrsn
Copy link

alxndrsn commented Nov 1, 2021

I've implemented with a wrapper component, something like this:

import React, { Component } from 'react';
import _ from 'lodash';

import { DataGrid } from '@mui/x-data-grid';
import CircularProgress from '@material-ui/core/CircularProgress';
import Grid from '@material-ui/core/Grid';

export default class StretchyDataGrid extends Component {
  constructor(props) {
    super(props);

    this.ref = React.createRef();

    const { columns } = props;
    this.state = { mappedColumns:_.clone(columns) };
  }

  autoSizeColumns = () => {
    const domRows = [ ...this.ref.current?.querySelectorAll('.MuiDataGrid-row') ];
    const domReady = (this.props.rows?.length === 0) || domRows.length;

    if(!domReady) {
      setTimeout(this.autoSizeColumns);
      return;
    }

    this.setState(previousState => {
      const mappedColumns = _.clone(previousState.mappedColumns);

      mappedColumns
        .forEach((col, idx) => {
          const maxContentWidth = domRows
              .reduce((previousMax, dR) => Math.max(previousMax, dR.childNodes[idx].scrollWidth), 0);
          if(maxContentWidth < this.ref.current.clientWidth / mappedColumns.length) {
            col.width = maxContentWidth;
            delete col.flex;
          } else {
            delete col.width;
            col.flex = 1;
          }
        });

      return { mappedColumns, resized:true };
    });
  }

  render() {
    const { mappedColumns, resized } = this.state;
    const { columns, ...props } = this.props;

    return (
      <>
        {!resized &&
          <Grid container alignItems="center" justifyContent="center" style={{height:'100%', position:'absolute', opacity:0.8, backgroundColor:'white', textAlign:'center', zIndex:1}}>
            <Grid item xs={12}>
              <CircularProgress/>
            </Grid>
          </Grid>
        }
        <DataGrid
          ref={this.ref}
          onResize={this.autoSizeColumns}
          columns={mappedColumns}
          {...props /* eslint-disable-line react/jsx-props-no-spreading */}
        />
      </>
    );
  }
}

This is extracted from a more complicated implementation, so apologies if there's anything missing/broken.

@troylateral

This comment was marked as resolved.

@oliviertassinari

This comment has been minimized.

@troylateral

This comment has been minimized.

@TamirCode
Copy link

TamirCode commented Aug 28, 2022

my solution for now taken out of my project. Let me know if any of u improve on it because I was in a rush. make sure u have a columns state to use this, as well as gridRef if u want the fitScreen option to work too

	// returns width of the biggest row inside a column
	function maxOfCol(colIndex: number) {

		let invisibleContainer = document.createElement("div")

		invisibleContainer.style.visibility = "hidden"
		invisibleContainer.style.zIndex = "-9999999999"
		invisibleContainer.style.position = "absolute"
		invisibleContainer.style.fontSize = "14px"
		invisibleContainer.style.top = "0"
		invisibleContainer.style.left = "0"

		document.body.append(invisibleContainer)

		let widths: any[] = []

		document.querySelectorAll<HTMLElement>(`[aria-colindex="${colIndex + 1}"]`).forEach(cell => {

			let invisibleCell = document.createElement("div")
			invisibleCell.innerHTML = cell.innerHTML
			invisibleCell.style.width = "max-content"
			invisibleCell.style.maxWidth = "none"
			invisibleCell.style.minWidth = "none"

			invisibleContainer.append(invisibleCell)

			widths.push(Math.ceil(invisibleCell.clientWidth))

		})

		let max = Math.max(...widths)

		if (max !== 0 && max < 50) { max = 50 }

		invisibleContainer.remove()

		return max
	}


	type ResizeType = "condense" | "maxContent" | "fitScreen"

	function resizeColumns(columns: GridColDef[], resizeType: ResizeType): GridColDef[] {
		let cols = [...columns]

		cols.forEach((col: GridColDef, index: number) => {

			if (resizeType === "fitScreen") {

				let gridWidth = gridRef.current.clientWidth
				let idColWidth = 90
				let scrollbar = 20
				let padding = 24 * 2
				const columnWidth = Math.floor((gridWidth - padding - scrollbar - idColWidth) / (cols.length - 1))

				col.width = columnWidth

				if (col.field.toLowerCase() === "id") { col.width = idColWidth }

			} else if (resizeType === "maxContent") {

				let maxColWidth = maxOfCol(index)
				col.width = maxColWidth + 22

			} else {
				col.width = 0
			}

		})

		return cols
	}

	const CustomToolbar: FC = () => {

		const [resizeMenuAnchorEl, setResizeMenuAnchorEl] = useState<null | HTMLElement>(null)
		const isResizeMenuOpen = Boolean(resizeMenuAnchorEl)
		const openResizeMenu = (event: React.MouseEvent<HTMLButtonElement>) => { setResizeMenuAnchorEl(event.currentTarget) }
		const closeResizeMenu = () => { setResizeMenuAnchorEl(null) }

		return (
			<GridToolbarContainer sx={{ justifyContent: "space-between" }}>
				<div style={{ display: "flex", flexWrap: "wrap" }}>
					<GridToolbarColumnsButton sx={{ ml: "10px", fontSize: "13px" }} />
					<GridToolbarFilterButton sx={{ ml: "10px", fontSize: "13px" }} />
					<GridToolbarDensitySelector sx={{ ml: "10px", fontSize: "13px" }} />
					<GridToolbarExport sx={{ ml: "10px", fontSize: "13px" }} />
				</div>
				<div>

					<Tooltip title="Resize columns">
						<Button onClick={openResizeMenu}><ExpandIcon sx={{ transform: "rotate(90deg)" }} /></Button>
					</Tooltip>

					<Menu anchorEl={resizeMenuAnchorEl} open={isResizeMenuOpen} onClose={closeResizeMenu}>
						<MenuItem onClick={() => { closeResizeMenu(); setColumns(resizeColumns(columns, "fitScreen")) }}>Fit Screen</MenuItem>
						<MenuItem onClick={() => { closeResizeMenu(); setColumns(resizeColumns(columns, "maxContent")) }}>Max Content</MenuItem>
						<MenuItem onClick={() => { closeResizeMenu(); setColumns(resizeColumns(columns, "condense")) }}>Condense</MenuItem>
					</Menu>

				</div>
			</GridToolbarContainer>
		)
	}

	return (
		<>
			<div style={{ height: '100%', width: '100%', padding: "24px" }} ref={gridRef} >

				<DataGridPro
                                        // ...
					components={{ Toolbar: CustomToolbar }}
                                        // ...
				/>
				
			</div>

@gavbrennan
Copy link
Contributor

We likely want to implement a double click -> auto-size UX on the column headers, we could share the same method.
Can I ask if this is currently on the roadmap? We have had a request for exactly this (double click on the column divider, like in Excel, to resize) and I would like to be able to give a realistic timeframe.

@MoonSulong
Copy link

We likely want to implement a double click -> auto-size UX on the column headers, we could share the same method.

May I ask if there is any update or plan for this feature?

@flaviendelangle flaviendelangle changed the title [DataGrid] Column sizing fit content [data grid] Column sizing fit content Oct 17, 2022
@e965
Copy link

e965 commented Nov 14, 2022

It is very strange that at least some basic solution to this problem does not exist out of the box (for example, devs could add matching column size to content along with a horizontal table scroll). I'm waiting for a solution.

@TamirCode
Copy link

At least for now to make it easier just have this option available only if virtualization is disabled
I think many of us will happily give that up for this essential table feature

@TamirCode
Copy link

TamirCode commented Nov 22, 2022

temporary solution (simplified)

if u have checkbox column disabled, the colindex should be +1 rather than +2
make sure to disable virtualization to use this

	// returns width of the biggest row inside a column
	function maxOfCol(colIndex) {

		let invisibleContainer = document.createElement("div")
		invisibleContainer.style = "visibility: hidden; z-index: -9999999999; position: absolute; font-size: 14px; top: 0; left: 0;"
		document.body.append(invisibleContainer)

		let widths = []

		document.querySelectorAll(`[aria-colindex="${colIndex + 2}"]`).forEach(cell => {

			let invisibleCell = document.createElement("div")
			invisibleCell.innerHTML = cell.innerHTML
			invisibleCell.style = "width: max-content; max-width: none; min-width: none;"
			invisibleContainer.append(invisibleCell)

			widths.push(Math.ceil(invisibleCell.clientWidth + 1))
		})

		let max = Math.max(...widths)

		if (max !== 0 && max < 30) { max = 30 }

		invisibleContainer.remove()

		return max
	}

// use this function somewhere as a button. table content must be rendered first

	function resizeColumns() {
		let cols = [...columns] // columns is my columns state.

		cols.forEach((col, index) => {
				let maxColWidth = maxOfCol(index)
				col.width = maxColWidth + 20
		})

		setColumns(cols)
	}

@sunilsr1986
Copy link

Hi,
Waiting for this feature release, Could you please update when we would get this ?

@crmacca
Copy link

crmacca commented Dec 7, 2022

This would be a great addittion, instead of just setting a huge overflow area (big blank space which looks cheesy)

@trulyronak

This comment was marked as resolved.

@albandum
Copy link

albandum commented May 11, 2023

👍 needed on our side as well. A bit sad to have most of the text truncated all the time

@Primajin
Copy link

We also currently have a very custom implementation in place: We listen to the loading state via useEffect and then trigger a check on the table ref if the scrollwidth of the contents per column row is larger than the column and then take the larger of the two and set the column to that width. Works fine but is a lot of fiddling around... 😕

@spookycookie27
Copy link

We also currently have a very custom implementation in place: We listen to the loading state via useEffect and then trigger a check on the table ref if the scrollwidth of the contents per column row is larger than the column and then take the larger of the two and set the column to that width. Works fine but is a lot of fiddling around... 😕

could provide an example? sound interesting

@ghaerdi
Copy link

ghaerdi commented May 16, 2023

We need this. it's a bit frustrating to program it yourself

@spookycookie27
Copy link

Yup it is, it seems pretty fundamental too.
I have to use a function to predict the width of the problematic column(s), and pass the desired width(s) into a function which returns the columns array

@Legys
Copy link

Legys commented May 24, 2023

Shockingly realized there was no such option and you have to adjust values manually, praying there won't be a content for a cell that breaks everything.
Also, I've found maxWidth of GridColDef interface. It seems not to work. Whatever you put there, it won't affect on cell during render, as width, minWidth, and maxWidth of the cell is always the same value. A value of width, at least from my observations.
By the fact this thread exists, I don't understand why maxWidth is even there in docs.

@DevPowers
Copy link

Following! This is a critical feature for Mobile where the column widths should be as small as possible while still fitting the data. On web I can just set the fixed width to be a bit bigger than the anticipated size of the field, but on mobile this results in all of my columns being a bit larger than necessary.

@sah
Copy link

sah commented Jul 12, 2023

A very common feature of spreadsheets and grids like this is to size a column to fit content when the user double-clicks the column header divider instead of dragging it. I was surprised that DataGrid doesn't support this! Would be great to have general facilities for sizing columns to fit content.

@mauro-ni
Copy link

We really need this feature.
Is a temporary working solution to adopt while waiting for an official support?

Many thanks.

@andreac92
Copy link

Really need this feature!! 🙏

@kota65535
Copy link

This is really needed.

@wileyjones
Copy link

Adding on here... really need this. This has been open for 2+ years, definitely re-evaluating just building our own tables if a feature like this has been on back-burner for so long despite how much the community wants official support.

@pizerg
Copy link

pizerg commented Aug 2, 2023

We really need this as well, is this actually going to happen in the near future or should we start migrating to other alternatives such as AG-Grid?

@pyrsv
Copy link

pyrsv commented Aug 16, 2023

+++ it`s really needed feature
it is basic feature in other libs, please dont push us to migrate )))

@lifinsky
Copy link

Highly anticipated feature!

@iwex
Copy link

iwex commented Aug 16, 2023

Hello! @joserodolfofreitas, please take a look
We are Pro subscribers.

@pedroeckel
Copy link

We really need this feature!
Any updates?

@mauro-ni
Copy link

Hi, we are Premium subscribers and we really need this feature.
Could you please provide an example with a temporary work around?

Many thanks.

Mauro

@cherniavskii cherniavskii moved this from 🆕 Needs refinement to 🔖 Ready in MUI X Data Grid Aug 29, 2023
@cherniavskii

This comment was marked as outdated.

@romgrk romgrk self-assigned this Aug 29, 2023
@dizzyjaguar
Copy link

Is this in the works?

@cherniavskii
Copy link
Member

@dizzyjaguar We plan to work on it this quarter (Q3)

@cherniavskii cherniavskii changed the title [data grid] Column sizing fit content [data grid] Autofit Column Width Aug 30, 2023
@cherniavskii cherniavskii moved this from 🔖 Ready to 🏗 In progress in MUI X Data Grid Sep 14, 2023
@MBilalShafi MBilalShafi moved this from 🏗 In progress to ✅ Done in MUI X Data Grid Sep 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: data grid This is the name of the generic UI component, not the React module! feature: Rendering layout Related to the data grid Rendering engine new feature New feature or request plan: Pro Impact at least one Pro user waiting for 👍 Waiting for upvotes
Projects
Status: Done
Development

Successfully merging a pull request may close this issue.