Loop to display Rating-Stars

Comments on my website can receive a rating from other members from 1 to 5, and I calculate the “Avg Rating” for each.

Now I need to display the appropriate number of Star icons next to each Comment.

The tricky part is that an Avg Rating could be something like 4.3.

Not sure the best way to handle this, but I am leaning towards just displaying a “1/2 Star” to represent any fractional amount. (No one is going to be able to distinguish 1/4 of a star vs. 1/3 of a star and so on, right?!)

I have a simple FOR loop that will display the “whole Stars”, but I am trying to figure out how to tell PHP this…

When you look at the Avg Rating, take the part that is to the left of the decimal and display that many “whole Stars”, and then if there is any part to the right of the decimal, well just display a single “1/2 Star” icon for that.

Follow me?

Actually, Paul O’Brien - here on SitePoint - taught me how to create fractional Star icons using CSS, but I personally think it is overkill to be showing 1/10 of a Star and so on?! So I thought maybe just showing a 1/2 Star for any fractional amount was good enough.

But whether I show 4 whole Stars and a 1/2 Star to represent an Avg Rating of 4.3, or I go crazy and display 4 whole Stars and a 3/10 Star, what is clearly holding me back is not creating the Star icons themselves, but figuring out the PHP part. :blush: :blush:

I Googled how to break a Decimal up into the Whole part and Fractional part, but all of the solutions seem to fail with decimals less than 1 (e.g. 0.25) and negative decimals (e.g. -1.40).

Now, granted, I won’t have a negative Rating, and since you can only choose 1 to 5, I guess a Comment will never have n Avg Rating less than 1.0 Nonetheless, it would be nice to find a way to separate a decimal that works for all types of decimals in case I need to use that some place else.

Hope all of this makes some sense?! (:

Sincerely,

Debbie

Can’t you just take the intval() of the average rating then subtract it from the original, leaving the decimal portion? Or did I misunderstand the question?


$avint = intval($avrating);
$avdec = abs($avrating - $avint);
if ($avdec > 0) {   // or maybe it should be >=0.5?
  // show portion graphic
  }

I’m not sure why that would matter; the lowest possible average rating is 1.0, unless there were no votes, in which case you would first check to make sure the average does not equal 0 or null first before continuing.

I just wrote the following function and would welcome your feedback…


<?php

function getSplitDecimal($number=NULL){
		/**
		 * Returns components of Decimal number.
		 *
		 * Written on:	2014-08-14
		 * Updated on:	
		 *
		 * @param Double		$number
		 *
		 * @return	array		$splitDecimal {Whole part, Fractional part}
		 */

		if (empty($number)){
			// No Value.
			$wholePart = NULL;
			$fractionalPart = NULL;

		}elseif (!is_numeric($number)){
			// Not Number.
			$wholePart = NULL;
			$fractionalPart = NULL;
			
		}else{
			// Valid Number.
			$wholePart = (int)$number;
			$fractionalPart = $number - $wholePart;
		}
		
		// Set Array.
		$splitDecimal['whole'] = $wholePart;
		$splitDecimal['fraction'] = $fractionalPart;
		
		return $splitDecimal;
}


// Test Data.
//$number = NULL;
//$number = '';
//$number = 'hello';
//$number = 0x1A;
//$number = 10.9;
//$number = 4.3;
//$number = -7.1;
//$number = 0.25;


// Call Function.
$splitDecimal = getSplitDecimal($number);


// Display Results.
echo "<br/>FUNCTION<br/>";
echo "number = $number<br/>";
echo 'splitDecimal[whole] = ' . $splitDecimal['whole'] . "<br/>";
echo 'splitDecimal[fraction] = ' . $splitDecimal['fraction'] . "<br/>";

?>

How does that look?

Sincerely,

Debbie

Seems fine to me. I note the use of casting instead of the intval() function which should not cause a problem and, if the internet is to be believed, should work faster too.

Here is a simplified version of this function…


	function getSplitDecimal($input=NULL){
		/**
		 * Returns components of Decimal number.
		 *
		 * Written on:	2014-08-14
		 * Updated on:	
		 *
		 * @param Double		$input
		 *
		 * @return	array		$splitDecimal {Whole part, Fractional part}
		 */
		
		if (is_numeric($input)){
			// Valid Number.
			$wholePart = (int)$input;
			$fractionalPart = $input - $wholePart;
			
		}else{
			// Not Number.
			$wholePart = NULL;
			$fractionalPart = NULL;			
		}
		
		// Set Array.
		$splitDecimal['whole'] = $wholePart;
		$splitDecimal['fraction'] = $fractionalPart;
		
		return $splitDecimal;
		
	}//End of getSplitDecimal

Based on my testing, it works with these values…


//$number = NULL;
//$number = '';
//$number = 'hello';
//$number = 0x1A;
//$number = 10.9;
$number = 4.3;
//$number = -7.1;
//$number = 0.25;
//$number = 0;
//$number = 0.0;
//$number = "0";
//$number = "0x1A";

$splitDecimal = getSplitDecimal($number);

echo "<br/>FUNCTION<br/>";
echo "number = $number<br/>";
echo 'splitDecimal[whole] = ' . $splitDecimal['whole'] . "<br/>";
echo 'splitDecimal[fraction] = ' . $splitDecimal['fraction'] . "<br/>";

About the only questionable thing is how to handle things like NULLS and STRINGS…

For my purposes, returning nothing if the input is not a number seems okay, but others may disagree.

Sincerely,

Debbie

Honestly, I think all of this can be done much simpler in the templates (view layer) and closer to what Paul O’B originally suggested.

<!-- dummy value for example -->
<?php $avgRating = 4.3 ?>

<!doctype html>

<style>
    .star-rating {
        text-indent: -10000px; /* hide the text (because we're progressively enhancing) */
        height: 16px; /* because this is how tall the star image is */
        background: url("http://www.pmob.co.uk/temp/images/star-matrix.gif") left bottom; /* Paul O'B's star sprite */
    }
</style>

<!--
    avgRating / 5 gets us the rating as a fraction of 1 (that is, 4.3 -> 0.86)
    the 5-star image is 80px wide, therefore the rating fraction times the max width
    gets us the star-width for this rating
-->
<div class="star-rating" style="width: <?php echo htmlspecialchars(80 * ($avgRating / 5)) ?>px">
    Rating: <?php echo htmlspecialchars($avgRating) ?>
</div>

Then if you want the stars to always be in half-star increments, you still use the same setup, and the only difference is some rounding beforehand.

<!-- dummy value for example -->
<?php $avgRating = 4.3 ?>
[COLOR="#FF0000"]<?php $roundedAvgRating = round($avgRating / 0.5) * 0.5 ?>[/COLOR]

<!doctype html>

<style>
    .star-rating {
        text-indent: -10000px; /* hide the text (because we're progressively enhancing) */
        height: 16px; /* because this is how tall the star image is */
        background: url("http://www.pmob.co.uk/temp/images/star-matrix.gif") left bottom; /* Paul O'B's star sprite */
    }
</style>

<!--
    avgRating / 5 gets us the rating as a fraction of 1 (that is, 4.3 -> 0.86)
    the 5-star image is 80px wide, therefore the rating fraction times the max width
    gets us the star-width for this rating
-->
<div class="star-rating" style="width: <?php echo htmlspecialchars(80 * ($[COLOR="#FF0000"]roundedAvgRating [/COLOR]/ 5)) ?>px">
    Rating: <?php echo htmlspecialchars($[COLOR="#FF0000"]roundedAvgRating[/COLOR]) ?>
</div>

I realize this is a little clunky for your liking but a variation of this was used on a site I made. True, html is built in this version, but you can cut that out if needed and just return number. Note: it doesn’t check input types, which I know you had mentioned but I would assume the input type is set before ever getting to the database in the first place.

<?php
function stars($all){
	$whole = floor($all);
	$fraction = $all - $whole;
	
	if($fraction < .25){
		$dec=0;
	}elseif($fraction >= .25 && $fraction < .75){
		$dec=.50;
	}elseif($fraction >= .75){
		$dec=1;
	}
	$r = $whole + $dec;
	
	//As we sometimes round up, we split again 	
	$stars="";
	$newwhole = floor($r);
	$fraction = $r - $newwhole;
	for($s=1;$s<=$newwhole;$s++){
			$stars .= "<span class=\\"star left\\">&nbsp;</span>\\r";	
		}
		if($fraction==.5){
			$stars .= "<span class=\\"halfstar left\\">&nbsp;</span>\\r";	
		}
		return $stars;
}

$Totalrating = 4.36;
$Totalstars = stars($Totalrating);
echo $Totalstars;
?>

Returns

<span class="star left">&nbsp;</span>
<span class="star left">&nbsp;</span>
<span class="star left">&nbsp;</span>
<span class="star left">&nbsp;</span>
<span class="halfstar left">&nbsp;</span>

As clunky as it sounds, I must admit that when I last did something like this I simply created a graphic for each value and named it based on the value passed in - rating1.jpg, rating2.jpg and so on, and then displaying the correct graphic for a value was simple. I was using a graphic similar to an old-style LED meter as found on eighties tape decks, so I wanted to show empty slots as well as full ones, so rating3.jpg would have three filled-in blocks and two empty ones. I also didn’t support decimal places for the rating, which drastically reduced the number of graphics required - I think if I had, I may have tried one of the above methods, although it wasn’t a PHP site.

Reading other threads on this subject, I see Debbie doesn’t want to round up at all, even if the number is 4.999999, so this modified version rounds down.

<html>
<head>
<style type="text/css">
.starcontainer{
 width:100px;
}
.left{
 float:left;
}
.star{
 background:url(../images/ministar.png) top no-repeat;
 height:18px;
 width:18px;
 padding: 0;
 margin:0 1px;
 font-size:1px;
}
.halfstar{
 background:url(../images/halfministar.png) top no-repeat;
 height:18px;
 width:9px;
 padding: 0;
 margin:0 1px;
 font-size:1px;
}
</style>
</head>
<body>
<?php
function rounddown($all){
	$r = floor($all * 100) / 100;
	return $r;
}

$Totalrating = 4.49999999999; 

$RoundedRating = rounddown($Totalrating); 


	
	$stars="";
	$newwhole = floor($RoundedRating);      
	$fraction = $RoundedRating - $newwhole;
	for($s=1;$s<=$newwhole;$s++){
		$stars .= "<span class=\\"star left\\">&nbsp;</span>\\r";	
	}
	if($fraction >= .5){
		$stars .= "<span class=\\"halfstar left\\">&nbsp;</span>\\r";	
	}



echo 'Total Rating: ' . $RoundedRating . '  <div class="starcontainer">' . "\\r" . $stars . '</div>';
?>
</body>
</html>

Returns

Total Rating: 4.99  <div class="starcontainer">
<span class="star left">&nbsp;</span>
<span class="star left">&nbsp;</span>
<span class="star left">&nbsp;</span>
<span class="star left">&nbsp;</span>
<span class="halfstar left">&nbsp;</span>
</div>

Slight modification to display inline.

<html>
<head>
<style type="text/css">
.starwrapper {
 width:220px;
 overflow:hidden;
}
.starcontainer{
 width:100px;
 float:left;
 margin-left:10px;
}
.left{
 float:left;
}
.star{
 background:url(images/ministar.png) top no-repeat;
 height:18px;
 width:18px;
 padding: 0;
 margin:0 1px;
 font-size:1px;
}
.halfstar{
 background:url(images/halfministar.png) top no-repeat;
 height:18px;
 width:9px;
 padding: 0;
 margin:0 1px;
 font-size:1px;
}
</style>
</head>
<body>
<?php
function rounddown($all){
	$r = floor($all * 100) / 100;
	return $r;
}

$Totalrating = 4.99999999999;

$RoundedRating = rounddown($Totalrating);


	
	$stars="";
	$newwhole = floor($RoundedRating);
	$fraction = $RoundedRating - $newwhole;
	for($s=1;$s<=$newwhole;$s++){
		$stars .= "<span class=\\"star left\\">&nbsp;</span>\\r";	
	}
	if($fraction > .5){
		$stars .= "<span class=\\"halfstar left\\">&nbsp;</span>\\r";	
	}



echo '<div class="starwrapper">
	<div class="left">Total Rating: ' . $RoundedRating . '</div>
	<div class="starcontainer">' . "\\r" . $stars . '</div>
</div>';
?>
</body>
</html>