4

I have the following data:

my %col1 = ( 'foo' => 1,
             'bar' => 1 );

my %col2  = ('foo' => ['cat1','cat1','cat2'],
             'bar' => ['cat3','cat2','cat3'] );

my %col3  = ('foo' => [2.3,1.2,1.0],
             'bar' => [7.4,4.3,2.2]);

What I want to do is to create a HTML table that looks like this but with 4th column where it contains shape with gradiented color. The intensity of the color is given by score in col3. The shape could be just a simple bullet (• HTML: •)

foo cat1,cat1,cat2 2.3,1.2,1    
bar cat3,cat2,cat3 7.4,4.3,2.2

Figure below illustrate what I intend to do: enter image description here

The intensity for each color category (col 3) ranges from 1-10.

What's the best way to go about it?

The key point I'm having problem is not about creating the HTML table, I can do that. But more how to create a HTML based shape with gradient color.

I'm stuck with the following code:

foreach my $nm (keys %col1) {
   my @cats = @{$col2{$nm}};
   my @vals = @{$col3{$nm}};

   print $nm, " ", join(",",@cats), " " ,join(",",@vals), "\n";
}
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
neversaint
  • 60,904
  • 137
  • 310
  • 477

1 Answers1

4

If I understood your question correctly, you don't really want a shape with a color gradient. What you really want is solid-color objects whose colors are determined by two parameters:

  • The category (cat1, cat2 and cat3 in your example)
  • A degree of coloration (the weights in column 3)

Furthermore, the colors in your example seem to follow a model where smaller numbers are nearer to white, larger numbers are nearer to black, and numbers in the middle are saturated colors. In terms of color space, this suggests something like the Hue, Saturation, Lightness color space, as depicted in this picture from Wikipedia:

HSL Color Space depiction

To map this back to your example above, I suspect weights near 0.0 would be near the top of the double-cone, weights near 10.0 would be near the bottom of the double-cone, and the colors represented by your categories have a fixed hue and saturation (notionally near the equator of the double-cone) adjusted by these weights.

The following perl code converts an HSL color to RGB. It was adapted from this StackOverflow answer, which in turn was adapted from the Wikipedia page I linked above. This is only part of the necessary solution:

##  
##  Converts an HSL color value to RGB. Conversion formula
##  adapted from http://en.wikipedia.org/wiki/HSL_color_space.
##  Assumes h, s, and l are contained in the set [0, 1] and
##  returns r, g, and b in the set [0, 255].
##  
##  @param   Number  h       The hue
##  @param   Number  s       The saturation
##  @param   Number  l       The lightness
##  @return  Array           The RGB representation
##  
sub hsl_to_rgb
{
    my ($h, $s, $l) = @_;
    my ($r, $g, $b);

    if ($s == 0)
    {
        $r = $g = $b = $l; ## achromatic
    } else
    {
        sub hue2rgb
        {
            my ($p, $q, $t) = @_;

            while ($t < 0) { $t += 1;                                   }
            while ($t > 1) { $t -= 1;                                   }
            if ($t < 1/6)  { return $p + ($q - $p) * 6 * $t;            }
            if ($t < 1/2)  { return $q;                                 }
            if ($t < 2/3)  { return $p + ($q - $p) * (2/3 - $t) * 6;    }
            return $p;
        }

        my $q = $l < 0.5 ? $l * (1 + $s) : $l + $s - $l * $s;
        my $p = 2 * $l - $q;
        $r = hue2rgb($p, $q, $h + 1/3);
        $g = hue2rgb($p, $q, $h);
        $b = hue2rgb($p, $q, $h - 1/3);
    }

    return [ int($r * 255), int($g * 255), int($b * 255) ];
}

This function takes an HSL color in the form of, say, (0, 1.0, 0.5) for pure red, and returns an RGB triplet of the form [ 255, 0, 0 ]. That gives you one piece of the puzzle.

Based on what I can tell from your problem statement, you have a set of base colors that you can probably express in terms of hue and saturation, sorted by category. Something like this:

my %cat_colors = 
(           #   Hue   Saturation
    cat1 => [ 0.00000, 1.00000 ],   # Red
    cat2 => [ 0.16667, 1.00000 ],   # Yellow
    cat3 => [ 0.33333, 1.00000 ],   # Green
);

Update by OP: This is brilliant. For adding colors in more category, the 'hue' value can be checked in this website: http://www.color-hex.com/ . Note for example that this website state the HSL for red is : 0.00 1.00 0.50.

Note: You'll definitely want to play with these colors. If you have Photoshop or some other tool that lets you see colors in terms of different color spaces, that may help you pick the "base" color for each category to be exactly what you want. For the hsl_to_rgb code above, you'll want to scale the hue and saturation values to be in the range [0, 1].

Now how do we apply this to the weight parameter you gave above? It appears, as I said above, that it maps directly to the lightness parameter in HSL, with a weight of 0.0 mapping to white, and a weight of 10.0 mapping to black. (At least, to a first order, given your sketch, that seems like a good starting point.) In HSL, though, 1.0 is white and 0.0 is black. So to map weight to lightness, you need an expression like this:

$lightness = 1 - ($weight / 10);

You could wrap this in a function:

sub weight_to_light
{
    return 1 - ($_[0] / 10);
}

The final piece in the equation is mapping the RGB returned by hsl_to_rgb into something HTML can work with. That's actually pretty simple. The following short function converts the array of R,G,B values from hsl_to_rgb into an HTML-ready hexadecimal string:

sub rgb_to_html_hex
{
    return sprintf "#%.2X%.2X%.2X", @{ $_[0] };
}

So, putting it all together: If you want to produce a hash named %col4 given the details in %col2 and %col3 that contains the strings you'll need in HTML to color objects according to the coloring model above, you need the two functions above, the %cat_colors mapping, and some code like this:

my %col4;

foreach my $key (keys %col2)
{
    foreach my $i ( 0 .. scalar( @{ $col2{$key} } ) - 1 )
    {
        my $base_color = $cat_colors{ $col2{$key}->[$i] };
        my $rgb_color  = hsl_to_rgb( @$base_color,
                                     weight_to_light( $col3{$key}->[$i] ) );

        my $html_color = rgb_to_html_hex( $rgb_color );

        $col4{$key}->[$i] = $html_color;
    }
}

Once you've generated %col4, you can use the resulting HTML strings to color individual entities. My modern HTML is a bit rusty, but I know that constructs such as <FONT COLOR="#xxxxxx"> worked at one time, and may still yet work. I leave it to you to construct syntactically valid and sufficiently modern HTML from the values computed.

Now, you must admit, I've made some bold assumptions about the problem you're trying to solve and how it needs to be solved. I think, though, I'm in the general ballpark. If you have questions, feel free to leave comments.

And... for your cut-and-paste convenience, here's a quick test program where I copied all the above bits, along with your sample data set into a standalone, testable perl program. Enjoy! The last two lines use YAML::XS to dump out the computed values for %col4 so I could see if they were plausible.

#!/usr/bin/perl -w

use Modern::Perl;

##  
##  Converts an HSL color value to RGB. Conversion formula
##  adapted from http://en.wikipedia.org/wiki/HSL_color_space.
##  Assumes h, s, and l are contained in the set [0, 1] and
##  returns r, g, and b in the set [0, 255].
##  
##  @param   Number  h       The hue
##  @param   Number  s       The saturation
##  @param   Number  l       The lightness
##  @return  Array           The RGB representation
##  
sub hsl_to_rgb
{
    my ($h, $s, $l) = @_;
    my ($r, $g, $b);

    if ($s == 0)
    {
        $r = $g = $b = $l; ## achromatic
    } else
    {
        sub hue2rgb
        {
            my ($p, $q, $t) = @_;

            while ($t < 0) { $t += 1;                                   }
            while ($t > 1) { $t -= 1;                                   }
            if ($t < 1/6)  { return $p + ($q - $p) * 6 * $t;            }
            if ($t < 1/2)  { return $q;                                 }
            if ($t < 2/3)  { return $p + ($q - $p) * (2/3 - $t) * 6;    }
            return $p;
        }

        my $q = $l < 0.5 ? $l * (1 + $s) : $l + $s - $l * $s;
        my $p = 2 * $l - $q;
        $r = hue2rgb($p, $q, $h + 1/3);
        $g = hue2rgb($p, $q, $h);
        $b = hue2rgb($p, $q, $h - 1/3);
    }

    return [ int($r * 255), int($g * 255), int($b * 255) ];
}

sub rgb_to_html_hex
{
    return sprintf "#%.2X%.2X%.2X", @{ $_[0] };
}

my %col1 = ( 'foo' => 1, 'bar' => 1 );

my %col2  = ('foo' => ['cat1','cat1','cat2'],
             'bar' => ['cat3','cat2','cat3'] );

my %col3  = ('foo' => [2.3,1.2,1.0],
             'bar' => [7.4,4.3,2.2]);


my %cat_colors =
(            #   Hue   Saturation
     cat1 => [ 0.00000, 1.00000 ],   # Red
     cat2 => [ 0.16667, 1.00000 ],   # Yellow
     cat3 => [ 0.33333, 1.00000 ],   # Green
);

sub weight_to_light
{
    return 1 - ($_[0] / 10);
}

my %col4;

foreach my $key (keys %col2)
{
    foreach my $i ( 0 .. scalar( @{ $col2{$key} } ) - 1 )
    {
        my $base_color = $cat_colors{ $col2{$key}->[$i] };
        my $rgb_color  = hsl_to_rgb( @$base_color,
                                     weight_to_light( $col3{$key}->[$i] ) );

        my $html_color = rgb_to_html_hex( $rgb_color );

        $col4{$key}->[$i] = $html_color;
    }
}


use YAML::XS;
say Dump( \%col4 );
Community
  • 1
  • 1
Joe Z
  • 17,413
  • 3
  • 28
  • 39
  • 1
    Addendum: If you want the endpoints of the color gradation to stop a bit before black and white at the ends of the weighting, you can change the `weight_to_light` function to limit the range somewhat. For example, `0.2 + 0.06 * $_[0]` will map the weights 0 - 10 to the lightness values 0.2 to 0.8, which may actually give a better effect for your purposes. – Joe Z Nov 18 '13 at 15:04
  • 1
    Addendum 2: I modified the script above slightly to output a crude HTML table. My HTML skills are easily a decade or more out of date, but it still renders properly in a modern web browser. You can find the code and the output here: http://spatula-city.org/~im14u2c/stackoverflow/gradation/ You'll note that I stick mostly to the perl part of the question and not the HTML for a reason. :-) – Joe Z Nov 19 '13 at 05:44
  • 1
    And in that formula I gave in Addendum 1 above, I should have said 0.8 - 0.6 * $_[0]. The file 'output2.html' at the link above shows the alternate colors that formula gives. Aside from the fact that my base colors don't exactly match your mockup above, the overall behavior seems to match your spec. Please let me know if I didn't interpret your question correctly. – Joe Z Nov 19 '13 at 05:49
  • 1
    Erf... I come back to this after 2 days and notice that your weights go 1..10 instead of 0..10. That's easily accounted for by tweaking the function `weight_to_light`. For example, my modified function becomes something like `0.13333 + 0.06667 * $_[0]`. As I said above, if there's any aspect of the above solution that needs reexamination, please comment here and I can address. – Joe Z Nov 21 '13 at 06:18
  • thanks a million this is truly life saving. I edited your post a bit so that it can remind me how to set %cat_colors if I have more categories. Award granted! – neversaint Nov 22 '13 at 03:03
  • 1
    @neversaint: Glad I could help. :-) This was fairly subtle, but at least I recognized what you were trying to do in the mockup scan. I had a couple false starts myself until I realized exactly what you were doing. – Joe Z Nov 22 '13 at 03:23