A large sortable table with rotated column headers

Below an example of a large sortable table with rotated column headers. I also created a jsFiddle you can watch here. By rotating the headers you can add many more columns to the viewport while preserving readability. If you wish to implement something like this yourself there are a few things to consider:

  • The table-layout needs to be fixed. This means that all content that extends the available with will be hidden from view.
  • You need to calculate the total available width for the table. Then calculate the available with per column. In the CSS code alter following line and enter the available width per column on the dots: :root {–columnWidth: …px;}
  • I have used jquery to sort the table.

Below a real example with some extra features.

Name
dec 2019
jan 2020
feb 2020
mar 2020
apr 2020
may 2020
jun 2020
Washington 430101528304152148852
London 73194023968763676438
Berlin 299943727511153642384
Paris 565517320130777269102

Copy example

<style>

:root {
  --columnWidth: 72px;
--columnHeight: 100px;
  --orderIconPos: calc(var(--columnWidth) - 20px);
}


#jqSpreadSheet { table-layout: fixed;border-collapse: collapse;width:calc(var(--columnWidth)*13) }
#jqSpreadSheet * {font:14px trebuchet ms;line-height:24px;}

#jqSpreadSheet thead {z-index:100}
#jqSpreadSheet th {background-color:#ffffff;position:relative;text-align:center;vertical-align:bottom;border-bottom:3px solid rgba(5,141,199,1)}
#jqSpreadSheet th {cursor:pointer;width:var(--columnWidth);padding-left:5px;text-align:left;font-weight:bold;color:black;}
#jqSpreadSheet th[sort='true'] sorticon{ z-index:999999;display:block;position:absolute;left:0px;right:0px;height:2px;}
#jqSpreadSheet th[sort='true'] sorticon:before{content: ''; width: 0;position:relative;top:8px;margin-left:var(--orderIconPos);height: 0;border-left: 10px solid transparent;border-right: 10px solid transparent;border-top: 10px solid rgba(5,141,199,1)}

#jqSpreadSheet th.rotate {height: 90px;white-space: nowrap;vertical-align:bottom;}
#jqSpreadSheet th.rotate small {font-size:12px;}
#jqSpreadSheet th.rotate > div:nth-child(1) {transform-origin: bottom right;transform: rotate(45deg);position:relative;text-align:right;position:relative;width: var(--columnWidth);height:var(--columnHeight)}
#jqSpreadSheet th.rotate > div:nth-child(1) > span {display:block;font-size:15px;font-weight:900 !important;width:150px;overflow:hidden;position:absolute;bottom:-32px;right:2px;border-top:1px solid rgba(5,141,199,1);padding-right:35px;line-height:32px !important;}

#jqSpreadSheet tbody {z-index:99}
#jqSpreadSheet tr.jqSpreadSheetBodyContentRow:hover td{background-color:rgba(5,141,199,0.3);color:black} 
#jqSpreadSheet tr.jqSpreadSheetBodyContentRow:hover td:nth-child(1) {background-color:rgba(5,141,199,1);color:white;}
#jqSpreadSheet tr.jqSpreadSheetFooterContentRow td {background-color:rgba(5,141,199,1);color:white;}

#jqSpreadSheet td {width: var(--columnWidth);position:relative;background-color:white;overflow:hidden;text-align:right;padding: 8px 5px;border-right: 1px solid #ddd;border-bottom: 1px solid #ddd;white-space: nowrap;z-index:97;}
#jqSpreadSheet td:nth-child(1) {background-color:#ffffff;text-align:left;font-weight:900 !important;padding-left:5px;}
</style>
<table id="jqSpreadSheet"  cellpadding="0" cellspacing="0" style="z-index:3;" align="center">
	<thead>
		<tr>
			<th sort="true" class="rotate"><div><span class="rotate">Name</span></div><sorticon/></th>
			<th sort="false" class="rotate"><div><span class="rotate">dec <small>2019</small></span></div><sorticon/></th>
			<th sort="false" class="rotate"><div><span class="rotate">jan <small>2020</small></span></div><sorticon/></th>
			<th sort="false" class="rotate"><div><span class="rotate">feb <small>2020</small></span></div><sorticon/></th>
			<th sort="false" class="rotate"><div><span class="rotate">mar <small>2020</small></span></div><sorticon/></th>
			<th sort="false" class="rotate"><div><span class="rotate">apr <small>2020</small></span></div><sorticon/></th>
			<th sort="false" class="rotate"><div><span class="rotate">may <small>2020</small></span></div><sorticon/></th>
			<th sort="false" class="rotate"><div><span class="rotate">jun <small>2020</small></span></div><sorticon/></th>
			<th sort="false" class="rotate"><div><span class="rotate">jul <small>2020</small></span></div><sorticon/></th>
			<th sort="false" class="rotate"><div><span class="rotate">aug <small>2020</small></span></div><sorticon/></th>
			<th sort="false" class="rotate"><div><span class="rotate">sept <small>2020</small></span></div><sorticon/></th>
			<th sort="false" class="rotate"><div><span class="rotate">okt <small>2020</small></span></div><sorticon/></th>
			<th sort="false" class="rotate"><div><span class="rotate">nov <small>2020</small></span></div><sorticon/></th>
		</tr>
	</thead>
	<tbody>
		<tr class="jqSpreadSheetBodyContentRow" data-index="0">
			<td>Washington</td> 
			<td>430</td>
			<td>101</td>
			<td>528</td>
			<td>304</td>
			<td>152</td>
			<td>148</td>
			<td>852</td>
			<td>912</td>
			<td>923</td>
			<td>976</td>
			<td>598</td>
			<td>126</td>
		</tr>
		<tr class="jqSpreadSheetBodyContentRow" data-index="1">
			<td>London</td> 
			<td>73</td>
			<td>194</td>
			<td>023</td>
			<td>968</td>
			<td>763</td>
			<td>676</td>
			<td>438</td>
			<td>422</td>
			<td>150</td>
			<td>941</td>
			<td>314</td>
			<td>213</td>
		</tr>
		<tr class="jqSpreadSheetBodyContentRow" data-index="2">
			<td>Berlin</td> 
			<td>299</td>
			<td>943</td>
			<td>727</td>
			<td>511</td>
			<td>153</td>
			<td>642</td>
			<td>384</td>
			<td>334</td>
			<td>390</td>
			<td>415</td>
			<td>927</td>
			<td>533</td>
		</tr>
		<tr class="jqSpreadSheetBodyContentRow" data-index="3">
			<td>Paris</td> 
			<td>565</td>
			<td>517</td>
			<td>320</td>
			<td>130</td>
			<td>777</td>
			<td>269</td>
			<td>102</td>
			<td>515</td>
			<td>440</td>
			<td>229</td>
			<td>627</td>
			<td>931</td>
		</tr>
	</tbody>
</table>

<script
  src="https://code.jquery.com/jquery-2.2.4.min.js"
  integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44="
  crossorigin="anonymous"></script>
<script>
var jqSpreadSheetObject = {};
jqSpreadSheetObject.enabled = false;
jqSpreadSheetObject.query = [];
$(document).ready(function(){
    (function(){
        $('.jqSpreadSheetFooterContentRow  td').each(function(e,i){
            $(this).attr('data-value',parseInt(($(this).html().length==0 ? 0 : $(this).html())));
            $(this).attr('data-percentage','100');
        });
        $('.jqSpreadSheetBodyContentRow  td').each(function(e,i){
            $(this).attr('data-value',($(this).html().length==0 ? 0 : $(this).html()));
            $(this).attr('data-percentage',($(this).attr('data-value')/$(".jqSpreadSheetFooterContentRow td").eq($(this).index()).attr('data-value')*100).toFixed(2));
            if($(this).attr('data-percentage') == 'NaN') {$(this).attr('data-percentage',0)};
 
            if (jqSpreadSheetObject.query.length-1<$(this).parent().index()) {jqSpreadSheetObject.query.push({row:$(this).parent().index(),columns:[]})}
            jqSpreadSheetObject.query[$(this).parent().index()]["columns"].push(isNaN(parseInt($(this).attr('data-value'))) ? $(this).attr('data-value') : parseInt($(this).attr('data-value')));
        });
 
        jqSpreadSheetObject.enabled = true;
    })();
 
    $(document).on("click","#jqSpreadSheet th",function(){
        /* Remove sort icon */
        $("#jqSpreadSheet thead th[sort='true']").first().attr("sort","false");
 
        /* Set sort icon */
        $(this).attr("sort","true");
        let tbody = $('#jqSpreadSheet tbody');
 
        /* Sort array */
        let columnIndex = $(this).index();
        jqSpreadSheetObject.query.sort(function(a,b){
            if(columnIndex!= 0) return (a.columns[columnIndex] < b.columns[columnIndex] ? -1 : 1)
            else {return (b.columns[columnIndex]).localeCompare(a.columns[columnIndex])}
        });
 
        /* Sort table */
        jqSpreadSheetObject.query.forEach(function(e,i){
            let $row = $(tbody).find("tr[data-index="+e.row+"]");
            $(tbody).prepend($row);
        });
    });
 
    $(document).on("click",".togglejqSpreadSheet",function(){
        let that = this;
        $('.togglejqSpreadSheet').each(function(e,i){$(this).removeClass('ff_cb fc_b')});
        $(this).addClass('ff_cb fc_b')
 
        $('.jqSpreadSheetBodyContentRow td:not(:first-child),.jqSpreadSheetFooterContentRow td:not(:first-child)').each(function(e,i){
            $(this).html($(this).attr('data-' + $(that).attr("data-toggle")) + (($(that).attr("data-toggle")=='percentage') ? '%' : ''));
        });
 
    });
    $(document).on("click","#jqSpreadSheet thead th:not(:first-child)",function(){
 
    });
});
</script>

Leave a Reply

Your email address will not be published. Required fields are marked *