Foliotek Developer Blog

Computing Color-Based Representation of Performance

Initial Work

I have been working on trying to visually represent numerical data of how a particular user/student has performed, and I thought I would share some of my thoughts. There are many different scales in our system with which a student may be scored. For instance, a metric may have 5 levels of scoring:

  • Does not meet expectations: 1 point
  • Approaches expectations: 2 points
  • Meets expectations: 3 points
  • Exceeds expectations: 4 points
  • Exemplary: 5 points

The maximum amount of points is 5 and the minimum is 1. One way to represent this is to show a miniature rubric of the levels with the student’s level selected. This just a simple table with a td for each level. Mousing over the td shows the level’s name. This table is specified at a 100 px width.

Adding Color

However, just having a colored indicator for what was selected seemed a little bland, so I began trying to envision how an appropriate color could be added. Red and Green are commonly used to associate bad to good, so I decided to try to compute appropriate greens and reds to represent the student’s score. The all green (#0F0) and all red (#F00) seem a little too much, so I thought I would set the range between what I will refer to as “strong green” and “strong red” (#0A0 and #A00).

*For anyone not familiar with this representation, it is the RGB/Red Green Blue value in hexadecimal with an integer range of 0 to 255 for each. “A” = 160 an “F” = 255. *

The Math

So, how do you represent the different levels by color? Essentially, the amount of green will start at zero and increase to “A” (160) and the amount of red will start at “A” (160) and decrease to zero. The blue will remain at zero no matter what.

In the case above, I initially came up with this computation:

Green = ( (Points Earned) / (Max Points Possible) ) * 160
= ( 4 / 5 ) * 160
= 80% green
= 128 (80 in hex)

Red = 160 – Green
= 32 (20 in hex)

Computed Color = #280 (20% of max red, 80% of max green, no blue)

The Mathematical Error

However, this does not work completely right, specifically due to the fact that the scale does not begin at zero. Thus, while the highest score will compute to the maximum green, the lowest score will not compute to no green, but to 20% of the maximum amount of green.

To fix this, I realized I needed to essentially make the minimum score equal to zero. This can be easily done by subtracting the minimum points possible from BOTH the Points Earned and the Max Points Possible.

Green = ( (Points Earned) – (Min Points Possible) ) / ( (Max Points Possible) – (Min Points Possible) ) * 160
= ( (4 – 1) / (5 – 1) ) * 160
= ( 3 / 4 ) * 160
= 75% green
= 120 (78 in hex)

Red = 160 – Green
= 40 (28 in hex)

Computed Color = #280 (20% of max red, 80% of max green, no blue)

Thus, there are five possibilities: 0% Green, 25% Green, 50% Green, 75% Green and 100% Green. The inclusive zero is the key here.

The Colorful Grid

This allows for a more colorful grid. I have provided one grid with a single score selected and then one with each of them, just to show the different colors:

Expanding to Aggregate Scores

We also needed a way to represent aggregate data that would not have specific set of defined levels. For instance, if a user had 20 scores from varying metrics, how could this be represented?

What I did was again computed the total number of points and the total earned points. Then I again adjusted for the minimum number of points possible in cases where the metrics do not start at zero.

I enlarged the width of the table for this to stretch across the entire screen. I’ll make it 500 px for this example. This table also has just three cells, with the first and third being essentially spacers and the second cell representing the score. I realized I only needed to compute the width of the first cell. The second cell will be a fixed width of 30 px, and then the third cell will take up the remaining space, if any.

The math for computing the color will still be the same. The key thing is to compute the size of the first column. If the table width is 500 px and the width of the score cell is 30 px, then there is only a total range of 0 to 470 pixels where that cell can be placed on the range. When it is placed at 470 pixels, that is the maximum it can be, as it will fill the remainder of the space on the table.

The Math for Placement

So, let’s assume that the total number of points a student received was 215 out of a total possible of 250. The minimum points earned will be 15. Adjusting the scale, this would indicate 200 out of 235 points earned. We can easily see, then, that our math should give us a width of the first cell of 400 pixels, since 235 is half of 470. (Yeah, I made it easy. I know. But this way we can validate the math.)

Placement = ( (Total Points Earned) – (Min Points Possible) ) / ( (Max Points Possible) – (Min Points Possible) ) * 470
= ( 215 – 15 ) / ( 250 – 15 ) * 470
= 200 / 235 * 470
= 400

Green = ( (Points Earned) – (Min Points Possible) ) / ( (Max Points Possible) – (Min Points Possible) ) * 160
= ( (215 – 15) / (250 – 15) ) * 160
= ( 200 / 235 ) * 160
= 85% green
= 136 (88 in hex)

Red = 160 – Green
= 24 (18 in hex)

Thus, the table looks like this:

Conclusion

So, I thought that was pretty interesting, but it gives a fairly clear visual representation of a student’s performance.


Convert HTML to BBCode in C#

Here is a code snippet that will convert HTML into BBCode using C#.

I know you are not suppose to use regular expressions to manipulate HTML and that I should have used agility pack, but this was the quickest solution to write.
[sourcecode language='csharp']

public string FormatHtmlIntoBBCode(string desc)
{
desc = Regex.Replace (desc, @”
“, “[br]“, RegexOptions.IgnoreCase);
desc = Regex.Replace (desc, @”

]>”, “[ulist]“, RegexOptions.IgnoreCase);
desc = Regex.Replace (desc, @”“, “[/ulist]“, RegexOptions.IgnoreCase);
desc = Regex.Replace (desc, @” ]
>”, “[olist]“, RegexOptions.IgnoreCase);
desc = Regex.Replace (desc, @”“, “[/olist]“, RegexOptions.IgnoreCase);
desc = Regex.Replace (desc, @”6. “, “[]“, RegexOptions.IgnoreCase);
desc = Regex.Replace (desc, @”“, “”, RegexOptions.IgnoreCase);
desc = Regex.Replace (desc, @”“, “[b]“, RegexOptions.IgnoreCase);
desc = Regex.Replace (desc, @”>”, “[/b]“, RegexOptions.IgnoreCase);
desc = Regex.Replace (desc, @”
“, “[strong]“, RegexOptions.IgnoreCase);
desc = Regex.Replace (desc, @”“, “[/strong]“, RegexOptions.IgnoreCase);
desc = Regex.Replace (desc, @”“, “[u]“, RegexOptions.IgnoreCase);
desc = Regex.Replace (desc, @”“, “[/u]“, RegexOptions.IgnoreCase);
desc = Regex.Replace (desc, @”
“, “[i]“, RegexOptions.IgnoreCase);
desc = Regex.Replace (desc, @”“, “[/i]“, RegexOptions.IgnoreCase);
desc = Regex.Replace (desc, @”*“, “[em]“, RegexOptions.IgnoreCase);
desc = Regex.Replace (desc, @”“, “[/em]“, RegexOptions.IgnoreCase);
desc = Regex.Replace (desc, @”“, “[sup]“, RegexOptions.IgnoreCase);
desc = Regex.Replace (desc, @”“, “[/sup]“, RegexOptions.IgnoreCase);
desc = Regex.Replace (desc, @”“, “[sub]“, RegexOptions.IgnoreCase);
desc = Regex.Replace (desc, @”“, “[/sub]“, RegexOptions.IgnoreCase);
desc = Regex.Replace (desc, @”- - - - - -

]*>”, “[hr]“, RegexOptions.IgnoreCase);
desc = Regex.Replace (desc, @”“, “[strike]“, RegexOptions.IgnoreCase);
desc = Regex.Replace (desc, @”“, “[/strike]“, RegexOptions.IgnoreCase);
desc = Regex.Replace (desc, @”

“, “[h1]“, RegexOptions.IgnoreCase);

desc = Regex.Replace (desc, @”“, “[/h1]“, RegexOptions.IgnoreCase);
desc = Regex.Replace (desc, @”

“, “[h2]“, RegexOptions.IgnoreCase);

desc = Regex.Replace (desc, @”“, “[/h2]“, RegexOptions.IgnoreCase);
desc = Regex.Replace (desc, @”

“, “[h3]“, RegexOptions.IgnoreCase);

desc = Regex.Replace (desc, @”“, “[/h3]“, RegexOptions.IgnoreCase);
desc = Regex.Replace (desc, @” desc = Regex.Replace (desc, @"“, “[/url]“, RegexOptions.IgnoreCase);
desc = Regex.Replace (desc, @”‘>”, “‘]”, RegexOptions.IgnoreCase);

//match on image tags
var match = Regex.Matches(desc, @”!“, RegexOptions.IgnoreCase);
if(match.Count > 0)
desc = Regex.Replace (desc, match[0].ToString(), “[img]“+ match[0].Groups[1].Value + “[/img]“, RegexOptions.IgnoreCase);

return desc;
}
[/sourcecode]

There you have it. ?A simple piece of code that will convert a simple html block into BBcode.

**


The non-breaking space "?"

This post may sound elementary to those of you who went through college after the web development was in full swing.? Admittedly, that was just slightly before my time.? So, what was a small epiphany for me may be common knowledge to you, but please pardon my ignorance and delight at what I learned.

Sometimes you just need a little more space somewhere, and fortunately, sometime long ago when I was just starting to work on the web, someone introduced me to the beloved “ ”.? Since that time, I have used it occasionally whenever it was too tedious to add space another way.

However, I recently needed to have a cell in a header row define the space for the column.? I don’t recall the exact circumstance now, but for whatever reason, defining no wrap wasn’t available.

Enter the often-used but seldom understood  .? I needed the text in this column to not break at the spaces.? I was working with another person, when it occurred to me that the non-breaking space is probably a non-breaking space.

I replaced the spaces with   and the problem was solved!


jQuery Custom Selector for selecting elements by exact text :textEquals

I needed a way to find labels based on the text that they contained.? I thought about using :contains() for this, but in this particular case the text items I was searching on were names and I could have similar names that :contains() could incorrectly match on.? For instance, if I was searching for “Banks, Tim” and there was an item with the text “Banks, Timothy” I would get both items returned.? This is not the behavior I was looking for.

I decided to write a little custom selector to match on exact text.? Here is the code for the custom selector:

$.expr[':'].textEquals = function(a, i, m) {  
    return $(a).text().match("^" + m[3] + "$");  
}; 

What is happening here is I am using a regular expression to test if the start and end of the element’s text matches the string passed in.? Now I could search for the name “Banks, Tim” on a label element like this:

$("label:textEquals('Banks, Tim')");  

Make Table Rows Sortable Using jQuery UI Sortable

So you want to make table rows sortable using jQuery UI? Luckily, the Sortable interaction does most of the work for you.

But there’s a catch: one problem that I ran into when implementing this (with UI version 1.7) was the cell widths of the row would collapse once I started dragging it.

Suppose you have a table of data, like this one:

<table class="grid" id="sort" title="Kurt Vonnegut novels">  
<thead>  
<tr><th>Year</th><th>Title</th><th>Grade</th></tr>  
</thead>  
<tbody>  
<tr><td>1969</td><td>Slaughterhouse-Five</td><td>A+</td></tr>  
<tr><td>1952</td><td>Player Piano</td><td>B</td></tr>  
<tr><td>1963</td><td>Cat’s Cradle</td><td>A+</td></tr>  
<tr><td>1973</td><td>Breakfast of Champions</td><td>C</td></tr>  
<tr><td>1965</td><td>God Bless You, Mr. Rosewater</td><td>A</td></tr>  
</tbody>  
</table>  

Your first attempt to make it sortable might look like this:

 $(“#sort tbody”).sortable().disableSelection();  

And it actually works, but there is a bit of a problem. The cell widths seem to be collapsing once you start dragging a row (notice how close the “C” cell is to the “Breakfast of Champions” cell). It looks like this:

Sortable row collapsed widths

The problem has to do with the helper object. The helper object is basically the DOM element that follows the cursor during the drag event. When it is created by default, the cells collapse to the size of the content inside of them.

You can specify a function that returns a jQuery object to create a custom helper object. By creating a function that will keep the cell widths consistent, this problem can be fixed.

**Update:** I have posted a [jsFiddle demo of table sorting with jQuery UI](http://jsfiddle.net/bgrins/tzYbU/) to show how the fix works. There is another fix, [proposed on StackOverflow](http://stackoverflow.com/a/1372954/76137) that includes cloning the original row (to not set the width permanently). This is also included in the jsFiddle.
// Return a helper with preserved width of cells  
var fixHelper = function(e, ui) {  
  ui.children().each(function() {  
    $(this).width($(this).width());  
  });  
  return ui;  
};
$(“#sort tbody”).sortable({  
 helper: fixHelper  
 }).disableSelection();  

Now it works as expected:
Sortable row fixed