Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Algorithm for calculating variable column widths for set table width

Tags:

algorithm

math

I need to figure out an algorithm that will calculate the optimized size of the column widths given the following:

  • the width of the table is fixed to the size of the page
  • the data inside the columns will be variable thus the width of the columns are variable
  • the width must be optimized to know when to wrap a column and when not to

So given the following data:

'From' => '03/06/2014',
'To' => '03/06/2014',
'First Name' => 'John Doe',
'Status' => 'approved',
'Type' => 'PTO',
'Amount' => '8 Hours',
'Notes' => 'Oops! Who knew I would need one more day. This should be all I need over the next week to support my trip.'

How can I calculate the optimal column widths so that the 'notes' column does not squeeze the other widths down to a less than acceptable width?

Sample of Problem

UPDATE: I currently know the width of the page & the width of the font so I can calculate the max width requirements for each column. It should fill the available space on the page. However, I would prefer the columns not wrap unless necessary. Like this:

enter image description here

like image 818
Chuck Burgess Avatar asked Mar 05 '14 19:03

Chuck Burgess


1 Answers

An easy solution is to assign attributes to your colums; for example your Notes columns could be flexible. Then you could calculate the maximum width of each column over all rows, set that width for all non-flexible columns and then distribute the remaining space evenly (or possibly weighted by their max width) to the flexible columns.

But you could also try to find out the attributes with some simple conditions:

  • a column can be word-wrapped if any of its entries has spaces in it. In your example, the dates and possibly the status and type entries cannot be wrapped. The name shouldn't be wrapped under normal circumstances but could be wrapped, if the name is long or if more than one name is given. The notes column should be wrapped.
  • a column should be flexible if its maximum width exceeds, say, the width a cell would have if all sizes were evenly distributed.

Then go about as described above: Calculate all non-flexible columns width. Check if there is enough space; if not, make the wrappable columns flexible, too. Then calculate the width of the flexible cells, weighted by their maximum widths.

A possible pseudocode algorithm is below. It makes liberal use of various heuristics, so you should probably take it with a grain of salt. You can adjust these conditions according to your use case, but it will be difficult to cater for all possible cases.

function layout(table[], width, gutter, col[])

    var maxw[col.length]        # max. text width over all rows
    var maxl[col.length]        # max. width of longest word
    var flex[col.length]        # is column flexible?
    var wrap[col.length]        # can column be wrapped?
    var colw[col.length]        # final width of columns

    foreach row in table:
        for i = 0 to col.length:
            cell = row[i]
            maxw[i] = max(maxw[i], textwidth(cell))
            if cell.find(" "):
                maxl[i] = max(maxl[i], wordwidth(cell))

    var left = width - (col.length - 1) * gutter
    var avg = left / col.length
    var nflex = 0

    # determine whether columns should be flexible and assign
    # width of non-flexible cells

    for i = 0 to col.length:
        flex[i] = (maxw[i] > 2 * avg)       # ???
        if flex[i]:
            nflex++
        else:
            colw[i] = maxw[i]
            left -= colw[i]

    # if there is not enough space, make columns that could
    # be word-wrapped flexible, too

    if left < nflex * avg:
        for i = 0 to col.length:
            if !flex[i] and wrap[i]:
                left += width[i]
                colw[i] = 0
                flex[i] = true
                nflex += 1

    # Calculate weights for flexible columns. The max width
    # is capped at the page width to treat columns that have to 
    # be wrapped more or less equal

    var tot = 0
    for i = 0 to col.length:
        if flex[i]:
            maxw[i] = min(maxw[i], width)      # ???
            tot += maxw[i]

    # Now assign the actual width for flexible columns. Make
    # sure that it is at least as long as the longest word length

    for i = 0 to col.length:
        if flex[i]:
            colw[i] = left * maxw[i] / tot
            colw[i] = max(colw[i], maxl[i])
            left -= colw[i]

    return colw
like image 53
M Oehm Avatar answered Sep 29 '22 11:09

M Oehm