Created
December 15, 2012 07:06
-
-
Save marsbomber/4291868 to your computer and use it in GitHub Desktop.
Make up 100% CSS width from doing percentage calculation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
I'll illustrate the problem with an example. | |
I query the database for some aggregated results. Say there are 4 | |
aggregated counters I'm interested in and wanting to display them | |
in a 100% width div. Each of the counters should contribute to a | |
percentage of the full width. | |
For instance, my db query returns me somthing like this | |
| counter_1 | counter_2 | counter_3 | counter 4| | |
| 1 | 1 | 1 | 1 | | |
I need to construct some HTML markup similar to this | |
<div class="bar"> | |
<span class="first" width="25%">Counter 1 - 25%</span> | |
<span class="middle" width="25%">Counter 2 - 25%</span> | |
<span class="middle" width="25%">Counter 3 - 25%</span> | |
<span class="last" width="25%">Counter 4 - 25%</span> | |
</div> | |
The above markup is easy enough to generate, some simple calculation | |
will give us | |
total_count = 4 | |
counter_1% = 1/4 = 25% | |
counter_2% = 1/4 = 25% | |
counter_3% = 1/4 = 25% | |
counter_4% = 1/4 = 25% | |
exactly what we are after. | |
Now, consider the following | |
| counter_1 | counter_2 | counter_3 | counter 4| | |
| 1 | 1 | 1 | 0 | | |
total_count = 3 | |
counter_1% = 1/3 = 33.3333333333% | |
counter_2% = 1/3 = 33.3333333333% | |
counter_3% = 1/3 = 33.3333333333% | |
counter_4% = 0/3 = 0% | |
We can see the fraction number is 100% precise (of course ...) but | |
unfortunately, we cannot use things like 1/3 in CSS (as far as I know). | |
So some kind of rounding/flooring/ceiling (doesn't matter which one | |
you pick, it'll become problematic) needs to be done ... | |
Let's use rounding here (solution 1) | |
total_count = 3 | |
counter_1% = 1/3 = 33% | |
counter_2% = 1/3 = 33% | |
counter_3% = 1/3 = 33% | |
counter_4% = 0/3 = 0% | |
This will produce | |
<div class="bar"> | |
<span class="first" width="33%">Counter 1 - 33%</span> | |
<span class="middle" width="33%">Counter 2 - 33%</span> | |
<span class="middle" width="33%">Counter 3 - 33%</span> | |
<span class="last" width="0%">Counter 4 - 0%</span> | |
</div> | |
It's somewhat acceptable, if the client doesn't mind the fact that | |
he/she is missing 1% on the stats ... | |
If we use a calculation like "Round each one until the last, | |
then subtract the partial sum from the total to get that one", | |
which is the exact strategy I used to start with ... It actually makes it worse. | |
total_count = 3 | |
counter_1% = 1/3 = 33% (round) | |
counter_2% = 1/3 = 33% (round) | |
counter_3% = 1/3 = 33% (round) | |
counter_4% = 0/3 = 100% - 33% * 3 (remainder) = 1% | |
You can see, this just becomes simply wrong... | |
So ... At the end, I use solution 1, but rounded with 2 more 2 decimals | |
total_count = 3 | |
counter_1% = 1/3 = 33.33% | |
counter_2% = 1/3 = 33.33% | |
counter_3% = 1/3 = 33.33% | |
counter_4% = 0/3 = 0% | |
<div class="bar"> | |
<span class="first" width="33.33%">Counter 1 - 33.33%</span> | |
<span class="middle" width="33.33%">Counter 2 - 33.33%</span> | |
<span class="middle" width="33.33%">Counter 3 - 33.33%</span> | |
<span class="last" width="0%">Counter 4 - 0%</span> | |
</div> | |
Still not a full 100%, but I hope it's more acceptable... |
... so to refine your "at the end" solution, you decide on 33.33% for the first one, that leaves 66.67% Divide that in half, you get 33.34%, leaving 33.33% over. The point is, round each one, subtract it from the total, then divide the remainder. This effectively is how Bresenhams algorithm work for drawing pixels to approximate a vector. It's well-established as the best available approximation.
I see what you mean. Thanks man!
I quickly hacked out something. It should do what you suggested :)
stats = [1, 1, 1, 0]
def percentagerize(collection, result=[], remainder=1)
return result if collection.count == 0
denominator = collection.inject(:+)
dup_collection = collection.dup
numerator = dup_collection.shift
if denominator == 0
percent = 0.0
else
percent = (remainder * (numerator / denominator.to_f)).round(4)
end
remainder -= percent
result << percent
percentagerize(dup_collection, result, remainder)
end
puts percentagerize(stats)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Take the first one, round(100_(1/3)) or 33%, leaving 67%. You have 1/2 left, that's round(67_(1/2)), which rounds upwards to 34%. The last one must be 67-34 = 33. This sort of progressive rounding then dividing the remainder is the best you can do without going to fractional percentages (and even then the same principle applies, but rounding to the nearest 0.1 or 0.01.