1<?php 2/* $Id: data_table.php 1000 2011-08-06 01:19:27Z lbayuk $ 3phplot / contrib / data_table.php: Draw a table of data values 4 5 Copyright (c) 2011, lbayuk -at- users.sourceforge.net 6 All rights reserved. 7 8 Redistribution and use in source and binary forms, with or without 9 modification, are permitted provided that the following conditions are met: 10 11 * Redistributions of source code must retain the above copyright 12 notice, this list of conditions and the following disclaimer. 13 * Redistributions in binary form must reproduce the above copyright 14 notice, this list of conditions and the following disclaimer in the 15 documentation and/or other materials provided with the distribution. 16 17 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 POSSIBILITY OF SUCH DAMAGE. 28 29This function draws a data table. That is, using PHP GD functions, it draws 30a table containing elements from a 2-dimensional array of text and number data. 31 32This is meant to be used as a PHPlot callback, but does not use PHPlot. 33It takes 2 arguments: a PHP GD image resource, and an array of settings. It 34draws the table into the image and returns TRUE. On error, it uses 35trigger_error(); if an error handler returns, it returns FALSE. 36 37The following keys are required in the settings array: 38 39 'data' => A PHPlot-style data array. Array[0..n-1] of rows, each 40 row entry is an array with values (strings or numbers). The 41 'headers' entry (see below) determines which columns are used in 42 the table. Other settings control how the values are formatted and 43 drawn in the table. 44 Note: Multi-line string values (with newlines) do not work. 45 46 'headers' => Array of headers for the data array. 47 There must be an entry for each data array column. The entry is the 48 header label for that column. NULL means do not include this data array 49 column in the table. 50 Header labels are centered within the header row of the table. 51 Multi-line entries (with newlines) are not supported and do not work. 52 53The following keys are optional in the settings array: 54 55 'position' => Array (x, y) giving the GD coordinates for the upper left 56 corner of the table. The default is (0,0), which is the upper left 57 corner of the image. 58 59 'height' => Height in pixels of the table. 60 If not given or empty or 0, the table height is calculated as the minimum 61 value, taking into account the number of rows, font height, and 62 cellpadding value. 63 64 'width' => Width in pixels of the table 65 If not given or empty or 0, the table width is calculated to just fit 66 the widest value in each column (after formatting). 67 If width is not provided, column_widths is ignored. 68 69 'column_widths' => Array of relative column width weights. 70 For example: array(4,2,1,1) means the 1st column is 2 times 71 as wide as the 2nd, and 4 times as wide as the 3rd and 4th. 72 If missing, all columns get equal width. Ignored if the overall 73 table width is not given. 74 75 'column_formats' => Array of printf formats for each column. 76 An empty string means no formatting for that column - the value is 77 drawn into the table using PHP's defaults. 78 If missing, there is no formatting for any column. 79 80 'column_alignments' => Array of L C or R for horizontal alignment, meaning 81 left align, center, or right align. 82 An empty string means automatic: align right if numeric, else left. 83 Default if missing is automatic for all columns. 84 85 'font' => GD font number 1-5. 1 is smallest, 5 is largest. Default is 2. 86 87 'color' => Array with R, G, B color specification. Default is (0,0,0)=black. 88 White would be (255,255,255). This color is used for grid lines 89 and text in the table. 90 91 'cellpadding' => Line to text gap in pixels. Default is 4. 92 93Note that the arrays 'column_widths', 'column_formats', and 'column_alignments' 94have one entry for each column in the table which will be drawn. This is 95different from 'headers', which has one column for each column in the 96data array. These sizes will be the same only if 'headers' has no NULL 97entries - that is, no data array columns are being skipped. 98 99*/ 100function draw_data_table($img, $settings) 101{ 102 // Apply defaults, then extract all settings as variables named 'o_*': 103 extract(array_merge(array( 104 'color' => array(0, 0, 0), // Default black 105 'font' => 2, // Default GD font 106 'position' => array(0, 0), // Default to upper left corner 107 'width' => 0, // Default auto width calculation 108 'height' => 0, // Default auto height calculation 109 'cellpadding' => 4, // Default line-to-text spacing 110 ), $settings), EXTR_PREFIX_ALL, 'o'); 111 list($x, $y) = $o_position; // Expand to separate variables 112 113 // Check for mandatory settings: 114 if (!isset($o_data, $o_headers)) { 115 trigger_error("draw_data_table error: 'headers' and 'data' are required"); 116 return FALSE; // In case error handler returns 117 } 118 119 // Font and color setup: 120 $char_width = imagefontwidth($o_font); 121 $char_height = imagefontheight($o_font); 122 $color = imagecolorresolve($img, $o_color[0], $o_color[1], $o_color[2]); 123 $pad2 = 2 * $o_cellpadding; // Pad all 4 sides of cells 124 125 // Calculate the number of rows and columns in the table: 126 $n_rows = count($o_data) + 1; // Add 1 for header row 127 // Count non-skipped columns: 128 $n_cols = 0; 129 foreach ($o_headers as $h) if (!is_null($h)) $n_cols++; 130 // Number of columns in the data array and in $o_headers: 131 $n_data_cols = count($o_headers); 132 133 // Default column weights so all columns have equal width. 134 if (empty($o_column_widths)) 135 $o_column_widths = array_fill(0, $n_cols, 1); 136 137 // Default column formats to no formatting: 138 if (empty($o_column_formats)) 139 $o_column_formats = array_fill(0, $n_cols, ''); 140 141 // Default column alignments to auto align: 142 if (empty($o_column_alignments)) 143 $o_column_alignments = array_fill(0, $n_cols, ''); 144 145 // Make sure there are the right number of entries. 146 if (count($o_column_widths) != $n_cols 147 || count($o_column_formats) != $n_cols 148 || count($o_column_alignments) != $n_cols) { 149 trigger_error("draw_data_table error: Mismatch in size of column spec arrays"); 150 return FALSE; // In case error handler returns 151 } 152 153 // If the table height is not supplied, calculate the space needed. 154 if (empty($o_height)) 155 $o_height = $n_rows * ($char_height + $pad2); 156 // Then calculate the height of each row. 157 $row_height = $o_height / $n_rows; 158 159 // If the table width is not supplied, calculate the space needed based 160 // on the widest value for each column (including header). The column width 161 // factors are ignored, since each column will be as wide as needed. 162 if (empty($o_width)) { 163 $o_width = 0; 164 $col = 0; // Index to unskipped columns 165 for ($i = 0; $i < $n_data_cols; $i++) { // Index to all columns 166 if (is_null($o_headers[$i])) continue; // Skip column 167 // Find the longest string in this column, post-formatting. 168 $len = strlen($o_headers[$i]); // Start with the header 169 for ($row = 1; $row < $n_rows; $row++) { 170 if (($cell = $o_data[$row - 1][$i]) !== '') { // Non-empty 171 // Apply cell format if specified: 172 if (($fmt = $o_column_formats[$col]) != '') 173 $cell = sprintf($fmt, $cell); 174 if (($this_len = strlen($cell)) > $len) 175 $len = $this_len; 176 } 177 } 178 // Assign column width, and accumulate the total: 179 $o_width += $col_width[$col++] = $len * $char_width + $pad2; 180 } 181 } else { // Table width, and optionally column width factors, are supplied. 182 // Calculate the width of each column. 183 $col_width_scale = $o_width / array_sum($o_column_widths); 184 for ($col = 0; $col < $n_cols; $col++) 185 $col_width[$col] = $o_column_widths[$col] * $col_width_scale; 186 } 187 188 // Calculate the column start positions within the table: 189 $col_start[0] = 0; 190 for ($i = 1; $i < $n_cols; $i++) 191 $col_start[$i] = $col_start[$i-1] + $col_width[$i-1]; 192 193 // Draw the table grid (without outer border) 194 $x2 = $x + $o_width - 1; 195 for ($row = 1; $row < $n_rows; $row++) { 196 $y0 = $y + $row_height * $row; // Avoid accumulating errors. 197 imageline($img, $x, $y0, $x2, $y0, $color); 198 } 199 $y2 = $y + $o_height - 1; 200 for ($col = 1; $col < $n_cols; $col++) { 201 $x0 = $x + $col_start[$col]; 202 imageline($img, $x0, $y, $x0, $y2, $color); 203 } 204 205 // Draw the header row, then the data rows 206 for ($row = 0; $row < $n_rows; $row++) { 207 208 // Vertically center the cell contents within the cell: 209 $y0 = $y + $row_height * ($row + 0.5) - $char_height / 2; 210 211 if ($row == 0) $cells = $o_headers; // Header row 212 else $cells = $o_data[$row - 1]; // -1 accounts for header row. 213 214 $col = 0; // Index to unskipped columns 215 for ($i = 0; $i < $n_data_cols; $i++) { // Index to all columns 216 217 if (is_null($o_headers[$i])) continue; // NULL header => skip column 218 219 if (($cell = $cells[$i]) !== '') { // Empty cell? 220 if ($row == 0) { 221 $alg = 'C'; // Header row forces center alignment 222 } else { 223 // Apply cell format if specified: 224 if (($fmt = $o_column_formats[$col]) != '') 225 $cell = sprintf($fmt, $cell); 226 // Get cell alignment: 227 $alg = $o_column_alignments[$col]; 228 } 229 230 // Calculate upper left position for this cell's text: 231 if (empty($alg)) // Default alignment: numbers right, else left. 232 $alg = is_numeric($cell) ? 'R' : 'L'; 233 $x0 = $x + $col_start[$col]; 234 if ($alg == 'R') { 235 $x0 += $col_width[$col] - strlen($cell) * $char_width - $o_cellpadding; 236 } elseif ($alg == 'C') { 237 $x0 += ($col_width[$col] - strlen($cell) * $char_width) / 2; 238 } else { // Default, assume L 239 $x0 += $o_cellpadding; 240 } 241 imagestring($img, $o_font, $x0, $y0, $cell, $color); 242 } 243 $col++; 244 } 245 } 246 return TRUE; 247} 248