Exploring the aesthetics of table design

Alexander Bub

As you remember, in one of our latest articles we dealt with the problem of fitting the columns width according to the columns content.
 
img1
 
At this point we already know that all columns which do not fit the page’s width during report rendering would make rendering engine create a number of pages to the right in order to store the columns that exceed the page’s width. This looks good, but not in case when the table has a couple of parameters and a hundred of columns for year values. Of course we could try switching the position of columns and parameters in the table so the overall view would be quite acceptable, but these changes most probably will not meet the customer’s requirements. One more thing in favor of the upcoming modification is that the table divided into several partitions would be perceived better by the user, unlike viewing the enormous table with tons of data making the whole perception confused.
 
The main concept
 
On this step we already have a data source modified for convenient output, as well as calculated columns width values. As far as we see, this part of template responsible for table output should be placed inside a DataBand that would iterate through the pre-modified data source containing the table partitions data. A CrossBand in its turn would be used to output the columns defined in a current partition. In general, all we have to do is to write a script that would create partitions based on the widths of the columns.
 
A sample to the rescue
 
To begin with, we should consider the data type that would be used for storing the information about the table partitions. To tell the truth, it would be quite enough to get the index of the first column and the total count of columns in a partition. Let’s describe the structure in CommonScript as follows:
 

  1. class TableBlock  
  2. {  
  3.     public int StartIndex { getset; }  
  4.     public int Count {get;set;}  
  5. }  

 
A method used for partitioning the table can also be added into the CommonScript. For convenience’ sake, a line containing table names would be duplicated in each partition. Let’s add the partitions list as a data source named ‘linesData’ :
 
  1. void SplitDataToLines()  
  2. {     
  3.     System.Collections.Generic.List<TableBlock> lines = new System.Collections.Generic.List<TableBlock>();    
  4.     DataObjects.Add(“linesData”, lines);  
  5.       
  6.     if(columnWidths.Count == 0)  
  7.     {  
  8.         return;  
  9.     }  
  10.       
  11.     int index = 1;  
  12.     double currentBlockWidth = firstColumnWidth + columnWidths[0];  
  13.     double pageWidth = page1.Size.Width - page1.Margins.Size.Width;  
  14.     TableBlock currentBlock = new TableBlock();  
  15.     currentBlock.StartIndex = 0;  
  16.     lines.Add(currentBlock);  
  17.       
  18.     while(index < columnWidths.Count)  
  19.     {  
  20.         if(currentBlockWidth + columnWidths[index] > pageWidth)  
  21.         {  
  22.             currentBlock.Count = index - currentBlock.StartIndex;  
  23.             currentBlockWidth = firstColumnWidth;  
  24.             currentBlock = new TableBlock();  
  25.             currentBlock.StartIndex = index;  
  26.             lines.Add(currentBlock);  
  27.         }  
  28.         currentBlockWidth += columnWidths[index];  
  29.         index++;  
  30.     }  
  31.     currentBlock.Count = index - currentBlock.StartIndex;  
  32. }  

 
At this point, we defined a data source in CommonScript and would use a CalculateColumnWidth method for our calculations.
 
  1. CrossBandSample.TableData data;  

 
With the help of GenerateScript the data source is being retrieved and partitioned:
 
  1. try  
  2. {     
  3.   // Getting datasource  
  4.   data = DataObjects["data"as CrossBandSample.TableData;  
  5.   CalculateColumnWidths();  
  6.   SplitDataToLines();  
  7. }  
  8. catch(Exception exc)  
  9. {  
  10.   System.Windows.Forms.MessageBox.Show(exc.ToString());  
  11. }  

 
So far so good. Now, we have all the necessary stuff to output the report as we expect it, but some of the template modification is still required. To start with, let’s add an upper level DataBand with ‘linesData’ data source, and then place the rest of the content inside it.
 
img2
 
At the end of the DataBand we should place a Detail control that would set the spacing between the table partitions.
 
Now we are able to use the CrossBand data sources directly, that’s why we can deselect the DataSource property and use InstanceCount. Let’s add the following code to Header’s GenerateScript:
 
  1. crossBandColumn.InstanceCount = (int) dataBand2["Count"];  
  2. crossBandValue.InstanceCount = (int) dataBand2["Count"];  

 
At this point we have to fix the code used for setting the columns width, since LineNumber does not match the overall table column numbers any more, and is only valid for columns inside a separate table partition.
 
In GenerateScript crossBandColumn the following code should be added:
 
  1. crossBandColumn.Size = new Vector(columnWidths[(int)dataBand2["StartIndex"] + crossBandColumn.LineNumber], crossBandColumn.Size.Height);  
  2. textBoxColumn.Size = crossBandColumn.Size;  

 
In GenerateScript crossBandValue the code should look as follows:
 
  1. crossBandValue.Size = new Vector(columnWidths[(int)dataBand2["StartIndex"] + crossBandValue.LineNumber], crossBandValue.Size.Height);  
  2. textBoxValue.Size = crossBandValue.Size;  

 
After this, we fix the code for getting the column names and the values:
 
  1. data.ColumnNames[(int)dataBand2["StartIndex"] + crossBandColumn.LineNumber - 1]  

 
  1. data.Rows[dataBand1.LineNumber - 1].Values[(int)dataBand2["StartIndex"] - 1 + crossBandValue.LineNumber]  

 
The result is really worth it.
 
img3
 
A sample used in this article can be downloaded by the following link http://perpetuumsoft.com/Support/downloads/samples/CrossBandSampleContinueOnPage.zip

June 4th, 2014

Leave a Comment