Displaying UL data in 3 uneven columns - php

I have a MySQL query that I am saving to a variable to output on the page. I have the data in an unordered list, but I am trying to split the data into three columns using 3 unordered lists floated left. Here is my current code:
$sqlCommand =
"SELECT list_specialty.id, list_specialty.specialty FROM list_specialty
ORDER BY list_specialty.specialty ASC";
$query = mysqli_query($myConnection,$sqlCommand) or die (mysqli_error($myConnection));
$specialtyDisplay = '';
$num = mysqli_num_rows($query);
$split = intval($num/3); //number of items in every column
$i = 0;
while ($row = mysqli_fetch_array($query)) {
$specialtyid = $row["id"];
$specialtyname = $row["specialty"];
$specialtyDisplay .=
'<li>' . $specialtyname . '</li>';
$i++;
if ($i == $split) {
$specialtyDisplay .= '</ul> <ul>';
$i = 0;
}
}
This code works GREAT as long as my data is exactly divisible by 3 (equal columns).
However, when my total number of rows is not divisible by 3, the above code won't work.
I need a solution that will balance the columns, so I can have either 3 equal columns, one extra element in the first column, or one extra element in the first and second column.
What do I need to change in my code to account for that?

This will work:
set $split to the length of the longest column
rows have 0, 1 or 2 columns that are longer by one element. -> $longcols = $num % 3
substract 1 from $split when column number $longcols is printed (unless all columns have equal length)
Implemented in the code:
$num = mysqli_num_rows($query);
$split = ceil($num/3); // 1.
$i = 0;
$col = 1; //for counting columns
$longcols = $num % 3; // 2.
while ($row = mysqli_fetch_array($query)) {
$specialtyid = $row["id"];
$specialtyname = $row["specialty"];
$specialtyDisplay .=
'<li>' . $specialtyname . '</li>';
$i++;
if ($i == $split) {
$specialtyDisplay .= '</ul> <ul>';
if( $longcols != 0 && $longcols == $col ) { $split--; } // 3.
$i = 0;
$col++; //increase columns count
}
}
The lines with comments are those that I have changed/inserted.

If you are going to support better browsers, you can use CSS 3 Multicolumns. It doesn't require you to split. Just CSS does the magic.
div#multicolumn {
-moz-column-count: 3;
-moz-column-gap: 20px;
-webkit-column-count: 3;
-webkit-column-gap: 20px;
column-count: 3;
column-gap: 20px;
}
Screenshot:
CSS3 MultiColumn http://css.flepstudio.org/images/css3/css3-multi-column.jpg

Related

Php foreach split list into columns (preferably equally)

I have an ordered list which is 19 entries long (but could change and be more or less). I'm listing it on a drop down menu but because of its length the column is dropping below the fold of the page.
I'd like to create a separate column (ul or div) to either divide the list into 2 or 3 equally, or have set list sizes e.g. max 7 per list.
Any ideas? Current code:
<div id="colour" class="dropmenudiv">
<?php
$sql = "select * from rug_colours where id <> 0 and active = 1 order by name";
$rs = $database->query($sql);
$index = 0;
foreach($rs as $v) {
echo "<a href=\"//$base_url/?action=search&colour=".$v['id']."\" >".$v['name']."</a>";
}
?>
Try something along the lines of:
<div id="colour" class="dropmenudiv">
<?php
$sql = "select * from rug_colours where id <> 0 and active = 1 order by name";
$rs = $database->query($sql);
$column_height = 7;
echo "<div class='column'>";
foreach($rs as $idx => $v) {
echo "<a href=\"//$base_url/?action=search&colour=".$v['id']."\" >".$v['name']."</a>";
if($idx % $column_height) echo "</div><div class='column'>";
}
echo "</div>";
?>
and for equal split you might try this:
$max_column_height = 7;
$no_of_cols = ceil(count($rs) / $max_column_height);
$column_height = floor($count($rs) / $no_of_cols);
You should use index variable to divide it into 2 or 3 div.
Following is example to make it in three parts:
$index = 0;
foreach($rs as $v) {
if($index > 7){
$index = 0; // reset to zero. You can also seperate it by any tag div or ul if you want
}
echo "<a href=\"//$base_url/?action=search&colour=".$v['id']."\" >".$v['name']."</a>";
$index++;
}
For an evenly spread distribution, first divide the number of elements by 7 (or whichever maximum rows you want to allow), rounding upwards. This gives the number of columns. Then divide the number of elements by the number of columns, rounding upwards: this gives you the actual number of rows you need.
I like array_chunk for this purpose:
$maxRowCount = 7;
$colCount = ceil(count($rs) / $maxRowCount);
$chunkSize = ceil(count($rs) / $colCount);
foreach(array_chunk($rs, $chunkSize) as $column) {
echo "<div class='column'>\n";
foreach($column as $v) {
echo "<a href=\"//$base_url/?action=search&colour={$v['id']}\" >{$v['name']}</a>";
}
echo "</div>\n";
}
You can create array of columns based on current index in foreach() loop like
$abc = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
$cols = [];
$perCol = 7;
foreach($abc as $index => $val) {
$colInd = $index / $perCol;
$cols[$colInd][] = $val;
}
print_r($cols);
This will split data in $abc into 3 coluns by 7 items per column.

Even distribution of PHP arrays across columns

So, I want to distribute evenly lists across 3 columns. The lists cannot be broken up or reordered.
At the moment, I have 5 lists each containing respectively 4, 4, 6, 3 and 3 items.
My initial approach was:
$lists = [4,4,6,3,3];
$columns = 3;
$total_links = 20;
$items_per_column = ceil($total_links/$columns);
$current_column = 1;
$counter = 0;
$lists_by_column = [];
foreach ($lists as $total_items) {
$counter += $total_items;
$lists_by_column[$current_column][] = $total_items;
if ($counter > $current_column*$links_per_column) {
$current_column++;
}
}
Results in:
[
[4],
[4,6],
[3,3]
]
But, I want it to look like this:
[
[4,4],
[6],
[3,3]
]
I want to always have the least possible variation in length between the columns.
Other examples of expected results:
[6,4,4,6] => [[6], [4,4], [6]]
[4,4,4,4,6] => [[4,4], [4,4], [6]]
[10,4,4,3,5] => [[10], [4,4], [3,5]]
[2,2,4,6,4,3,3,3] => [[2,2,4], [6,4], [3,3,3]]
Roughly what you need to do is loop over the number of columns within your foreach(). That will distribute them for you.
$numrows = ceil(count($lists) / $columns);
$thisrow = 1;
foreach ($lists as $total_items) {
if($thisrow < $numrows){
for($i = 1; $i <= $columns; $i++){
$lists_by_column[$i][] = $total_items;
}
}else{
//this is the last row
//find out how many columns need to fit.
//1 column is easy, it goes in the first column
//2 columns is when you'll need to skip the middle one
//3 columns is easy because it's full
}
$thisrow++;
}
This will be an even distribution, from left to right. But you actually want a modified even distribution that will look symmetrical to the eye. So within the foreach loop, you'll need to keep track of 1.) if you're on the last row of three, and 2.) if there are 2 remainders, to have it skip col2 and push to col3 instead. You'll need to set that up to be able to play around with it,...but you're just a couple of logic gates away from the land of milk and honey.
So, I ended up using this code:
$lists = [4,4,6,3,3];
$columns = 3;
$total_links = 20;
$items_per_column = ceil($total_links/$columns);
$current_column = 1;
$lists_by_column = [];
for ($i = 0; $i < count($lists); $i++) {
$total = $lists[$i];
$lists_by_column[$current_column][] = $lists[$i];
//Loop until reaching the end of the column
while ($total < $items_per_column && $i+1 < count($lists)) {
if ($total + $lists[$i+1] > $items_per_column) {
break;
}
$i++;
$total += $lists[$i];
$lists_by_column[$current_column][] = $lists[$i];
}
//When exiting the loop the last time we need another break
if (!isset($lists[$i+1])) {break;}
//If the last item goes onto the next column
if (abs($total - $items_per_column) < abs($total + $lists[$i+1] - $items_per_column)) {
$current_column++;
//If the last item goes onto the current column
} else if ($total + $lists[$i+1] > $items_per_column) {
$i++;
$lists_by_column[$current_column][] = $lists[$i];
$current_column++;
}
}

PHP loop to sort table

I'm querying a database for names that are numbered 1-26 alphabetically. I have the following code, but since HTML is structured tr then td, the table appears alphabetically by row as opposed to by column. How can I make it appear in order by column?
$query = mysql_query("SELECT name FROM people WHERE main=1 ORDER BY id");
$i = 0;
while($result = mysql_fetch_array($query)) {
$name = $result['name'];
if ($i % 5 == 0) echo "<tr>\n";
echo "<td width=\"150\">";
echo "".$name."<br />";
echo "</td>\n";
$i++;
if ($i % 5 == 0) echo "</tr>\n";
};
alpha beta charlie
delta echo foxtrot
vs.
alpha charlie echo
beta delta foxtrot
Also, I'm open to reworking the code if there's a more efficient way.
You could just access the output array in strides. Compute how many rows you need as the number of results divided by 5, and use the row count as the stride.
$ncols = 5;
$nrows = $nresults / $ncols + ($nresults % $ncols == 0 ? 0 : 1);
for ($i = 0; $i < $nrows; $i++)
{
// start row
for ($j = 0; $k < $ncols; $j++)
{
// print $results[$nrows * $j + $i]
}
// end row
}
You'll have to transfer your query results into an array $results first. Since you'll have to know the total number of results, this is sort of mandatory, though I'd be curious if anyone has a solution that can work while fetching the results.
Update: See Justin's answer for a cool solution that grows the output while fetching the query results line by line. Since it's currently being worked on, here's a summary (credits to Justin):
$nresults = mysql_num_rows($query);
$ncols = 5;
$nrows = (int) ceil($nresults / $ncols);
$i = 0; $cols = array_fill(0, $nrows, "");
while ($result = mysql_fetch_array($query))
$cols[$i++ % $nrows] .= "<td>$result['name']</td>";
echo "<tr>" . implode("</tr><tr>", $cols) . "</tr>";
Edit:
After the discussion in the comments between myself, Kerrek SB and the OP bswinnerton, the following code seems to be the most effective:
$columns = 3;
$rowcount = mysql_num_rows($query);
$rows = ceil($rowcount / $columns);
$rowdata = array_fill(0, $rows, "");
$ctr = 0;
while ($result = mysql_fetch_array($query))
$rowdata[$ctr++ % $rows] .= '<td>'.$result['name'].'</td>';
echo '<tr>'.implode('</tr><tr>',$rowdata).'</tr>';
This will create three columns, filled vertically (my original answer would create three rows). It also properly initializes the array (preventing PHP warnings), yields a correct row count for result counts that aren't divisible by the column count, and incorporates Kerrek's clever "calc-row-in-the-subscript" trick.
Original Post:
You could use arrays and implode() This way, you only have to make one pass through your results:
$row = 0;
$rows = 3;
$rowdata = array();
while($result = mysql_fetch_array($query))
{
if ($row >= $rows) $row = 0;
$rowdata[$row++] .= '<td>'.$result['name'].'</td>';
}
echo '<tr>'.implode('</tr><tr>',$rowdata).'</tr>';

PHP - Tricky... array into columns, but in a specific order

<?php
$combinedArray = array("apple","banana","watermelon","lemon","orange","mango");
$num_cols = 3;
$i = 0;
foreach ($combinedArray as $r ){
/*** use modulo to check if the row should end ***/
echo $i++%$num_cols==0 ? '<div style="clear:both;"></div>' : '';
/*** output the array item ***/
?>
<div style="float:left; width:33%;">
<?php
echo $r;
?>
</div>
<?php
}
?>
<div style="clear:both;"></div>
The above code will print out the array like this:
apple --- banana --- watermelon
lemon --- orange --- mango
However, I need it like this:
apple --- watermelon --- orange
banana --- lemon --- mango
Do you know how to convert this? Basically, each value in the array needs to be placed underneath the one above, but it must be based on this same structure of 3 columns, and also an equal amount of fruits per column/row (unless there was like 7 fruits there would be 3 in one column and 2 in the other columns.
Sorry I know it's confusing lol
Thanks everyone for your help... I realized a better way to do it though. Simple put, I have 3 columns floating next to eachother. And in each column, I add a list of the items into it and stop when I hit the max items per row.
working code:
<div style="float:left; width:33%;">
<?php
$combinedArraySizeOf = sizeof($combinedArray);
$num_cols = 3;
$iPerRow = $combinedArraySizeOf / $num_cols;
for ($i=0; $i!=$combinedArraySizeOf; $i++){
if ($i % $iPerRow == 0){
echo '</div><div style="float:left; width:33%;">';
}
echo $combinedArray[$i]."<br />";
}
?>
</div>
<div style='clear:both;'></div>
Don't forget to clear both at the end if necessary :P
Why aren't you doing exactly what you want to do? I mean show them in columns, instead of rows?
$combinedArray = array("apple","banana","watermelon","lemon","orange","mango");
$num_cols = 3;
$rowCount = ceil(count($combinedArray)/$num_cols);
$i = 1; // in order the modulus to work correctly
?>
<div style="float: left; width:33%"> <!-- this is the first column -->
foreach ($combinedArray as $r ){
?>
<div> <!-- just a div containing $r -->
<?php
echo $r;
?>
</div>
<?php
// this is where the magic happens
// check if we have enough rows and start another column
if ($i % $rowCount == 0) {
?>
</div> <!-- close the previous column and start a new one -->
<div style="float: left; width:33%"> <!-- this is the new column -->
<?php
}
$i++;
}
?>
</div> <!-- closing the last open column -->
<div style="clear:both;"></div>
This should do just the job you wish. Marvin's answer is better if you want to use only tables without divs.
$combinedArray = array("apple","banana","watermelon","lemon","orange","mango");
$step = 2;
$i = 0;
$new_array = array();
foreach ($combinedArray as $r ){
$remainder = ($i % $step);
$new_array[$remainder][] = $r;
$i++;
}
foreach($new_array as $array)
{
echo implode(' --- ', $array)."<br>";
}
Would this work?
$combinedArray = array("apple","banana","watermelon","lemon","orange","mango");
$num_cols = 3;
$rows = ceil(count($combinedArray)/$num_cols);
for($i = 0; $i < $rows; $i++){
for($j = 0; $j < $num_cols; $j++){
echo $combinedArray[(($i+$j) * $rows)-$i];
}
echo "<br />";
}
This would also need to check that the value existed for cases where the number of items wasn't precisely divisible by the number of columns, you could do that with the following change:
$combinedArray = array("apple","banana","watermelon","lemon","orange","mango");
$num_cols = 3;
$rows = ceil(count($combinedArray)/$num_cols);
for($i = 0; $i < $rows; $i++){
for($j = 0; $j < $num_cols; $j++){
$cell = (($i+$j) * $rows)-$i;
if($cell > count($combinedArray)) break;
echo $combinedArray[$cell];
}
echo "<br />";
}
If this order is what you ideally want, but it's not critical that it works in all browsers, perhaps you should look at coloumn layout (still very experimental css3 draft). If you use dispay inline block for the element in each coloumn you'll have the current order as a fallback.
You could also use a table for the layout and use a for loop something like this (pseudo php code, it's been a while since I've coded any php):
maxHeight = Math.ceil(array.length / 3) // meaning length=6 will give 3,
// length=7 will give 4
$x = -1; // horizontal index
for(i = 0; i < array.length(); i++){
$y = i % maxHeight; // vertical index
if($y == 0){
$x++;
}
addToTable($x,$y, array[i]);
}

What's the best way to generate a tag cloud from an array using h1 through h6 for sizing?

I have the following arrays:
$artist = array("the roots", "michael jackson", "billy idol", "more", "and more", "and_YET_MORE");
$count = array(5, 3, 9, 1, 1, 3);
I want to generate a tag cloud that will have artists with a higher number in $count enclosed in h6 tags and the lowest enclosed h1 tags.
You will want to add a logarithmic function to it too. (taken from tagadelic, my Drupal module to create tag clouds http://drupal.org/project/tagadelic):
db_query('SELECT COUNT(*) AS count, id, name FROM ... ORDER BY count DESC');
$steps = 6;
$tags = array();
$min = 1e9;
$max = -1e9;
while ($tag = db_fetch_object($result)) {
$tag->number_of_posts = $tag->count; #sets the amount of items a certain tag has attached to it
$tag->count = log($tag->count);
$min = min($min, $tag->count);
$max = max($max, $tag->count);
$tags[$tag->tid] = $tag;
}
// Note: we need to ensure the range is slightly too large to make sure even
// the largest element is rounded down.
$range = max(.01, $max - $min) * 1.0001;
foreach ($tags as $key => $value) {
$tags[$key]->weight = 1 + floor($steps * ($value->count - $min) / $range);
}
Then in your view or template:
foreach ($tags as $tag) {
$output .= "<h$tag->weight>$tag->name</h$tag->weight>"
}
Off the top of my head...
$artist = array("the roots","michael jackson","billy idol","more","and more","and_YET_MORE");
$count = array(5,3,9,1,1,3);
$highest = max($count);
for (int $x = 0; $x < count($artist); $x++)
{
$normalized = $count[$x] / $highest;
$heading = ceil($normalized * 6); // 6 heading types
echo "<h".$heading.">".$artist[$x]."</h".$heading.">";
}
Perhaps this is a little academic and off topic but hX tags are probably not the best choice for a tag cloud for reasons of document structure and all that sort of thing.
Maybe spans or an ol with appropriate class attributes (plus some CSS)?
Have used this snippet for a while, credit is prism-perfect.net. Doesn't use H tags though
<div id="tags">
<div class="title">Popular Searches</div>
<?php
// Snippet taken from [prism-perfect.net]
include "/path/to/public_html/search/settings/database.php";
include "/path/to/public_html/search/settings/conf.php";
$query = "SELECT query AS tag, COUNT(*) AS quantity
FROM sphider_query_log
WHERE results > 0
GROUP BY query
ORDER BY query ASC
LIMIT 10";
$result = mysql_query($query) or die(mysql_error());
while ($row = mysql_fetch_array($result)) {
$tags[$row['tag']] = $row['quantity'];
}
// change these font sizes if you will
$max_size = 30; // max font size in %
$min_size = 11; // min font size in %
// get the largest and smallest array values
$max_qty = max(array_values($tags));
$min_qty = min(array_values($tags));
// find the range of values
$spread = $max_qty - $min_qty;
if (0 == $spread) { // we don't want to divide by zero
$spread = 1;
}
// determine the font-size increment
// this is the increase per tag quantity (times used)
$step = ($max_size - $min_size)/($spread);
// loop through our tag array
foreach ($tags as $key => $value) {
// calculate CSS font-size
// find the $value in excess of $min_qty
// multiply by the font-size increment ($size)
// and add the $min_size set above
$size = $min_size + (($value - $min_qty) * $step);
// uncomment if you want sizes in whole %:
// $size = ceil($size);
// you'll need to put the link destination in place of the /search/search.php...
// (assuming your tag links to some sort of details page)
echo '<a href="/search/search.php?query='.$key.'&search=1" style="font-size: '.$size.'px"';
// perhaps adjust this title attribute for the things that are tagged
echo ' title="'.$value.' things tagged with '.$key.'"';
echo '>'.$key.'</a> ';
// notice the space at the end of the link
}
?>
</div>
#Ryan
That's correct but it actually makes the tags with the least number, larger. This code has been tested:
$artist = array("the roots","michael jackson","billy idol","more","and more","and_YET_MORE");
$count = array(5,3,9,1,1,3);
$highest = max($count);
for ($x = 0; $x < count($artist); $x++) {
$normalized = ($highest - $count[$x]+1) / $highest;
$heading = ceil($normalized * 6); // 6 heading types
echo "<h$heading>{$artist[$x]}</h$heading>";
}
This method is for SQL/PostgreSQL fanatics. It does the entire job in the database, and it prints text with "slugified" link. It uses Doctrine ORM just for the sql call, I'm not using objects.
Suppose we have 10 sizes:
public function getAllForTagCloud($fontSizes = 10)
{
$sql = sprintf("SELECT count(tag) as tagcount,tag,slug,
floor((count(*) * %d )/(select max(t) from
(select count(tag) as t from magazine_tag group by tag) t)::numeric(6,2))
as ranking
from magazine_tag mt group by tag,slug", $fontSizes);
$q = Doctrine_Manager::getInstance()->getCurrentConnection();
return $q->execute($sql);
}
then you print them with some CSS class, from .tagranking10 (the best) to .tagranking1 (the worst):
<?php foreach ($allTags as $tag): ?>
<span class="<?php echo 'tagrank'.$tag['ranking'] ?>">
<?php echo sprintf('<a rel="tag" href="/search/by/tag/%s">%s</a>',
$tag['slug'], $tag['tag']
); ?>
</span>
<?php endforeach; ?>
and this is the CSS:
/* put your size of choice */
.tagrank1{font-size: 0.3em;}
.tagrank2{font-size: 0.4em;}
.tagrank3{font-size: 0.5em;}
/* go on till tagrank10 */
This method displays all tags. If you have a lot of them, you probably don't want your tag cloud to become a tag storm. In that case you would append an HAVING TO clause to your SQL query:
-- minimum tag count is 8 --
HAVING count(tag) > 7
That's all
I know it's a very old post, still I'm posting my view as it may help someone in future.
Here is the tagcloud I used in my website:
http://www.vbausefulcodes.in/
<?php
$input= array("vba","macros","excel","outlook","powerpoint","access","database","interview questions","sendkeys","word","excel projects","visual basic projects","excel vba","macro","excel visual basic","tutorial","programming","learn macros","vba examples");
$rand_tags = array_rand($input, 5);
for ($x = 0; $x <= 4; $x++) {
$size = rand ( 1 , 4 );
echo "<font size='$size'>" . $input[$rand_tags[$x]] . " " . "</font>";
}
echo "<br>";
$rand_tags = array_rand($input, 7);
for ($x = 0; $x <= 6; $x++) {
$size = rand ( 1 , 4 );
echo "<font size='$size'>" . $input[$rand_tags[$x]] . " " . "</font>";
}
echo "<br>";
$rand_tags = array_rand($input, 5);
for ($x = 0; $x <= 4; $x++) {
$size = rand ( 1 , 4 );
echo "<font size='$size'>" . $input[$rand_tags[$x]] . " " . "</font>";
}
?>
As a helper in Rails:
def tag_cloud (strings, counts)
max = counts.max
strings.map { |a| "<span style='font-size:#{((counts[strings.index(a)] * 4.0)/max).ceil}em'>#{a}</span> " }
end
Call this from the view:
<%= tag_cloud($artists, $counts) %>
This outputs <span style='font-size:_em'> elements in an array that will be converted to a string in the view to ultimately render like so:
<span style='font-size:3em'>the roots</span>
<span style='font-size:2em'>michael jackson</span>
<span style='font-size:4em'>billy idol</span>
<span style='font-size:1em'>more</span>
<span style='font-size:1em'>and more</span>
<span style='font-size:2em'>and_YET_MORE</span>
It would be better to have a class attribute and reference the classes in a style sheet as mentioned by Brendan above. Much better than using h1-h6 semantically and there's less style baggage with a <span>.

Categories