Create a tag cloud by wrapping array values in h1 through h6 tags based on a mapping array of weights
Asked Answered
N

9

79

I have the following arrays:

$artist = ["the roots", "michael jackson", "billy idol", "more", "and more", "and_YET_MORE"];
$count = [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.

Nord answered 1/8, 2008 at 21:40 Comment(0)
H
43

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>"
}
Harkey answered 31/5, 2010 at 11:53 Comment(1)
Unless I'm mistaken, this algorithm is too skewed toward <h6>. In the sample set: [5, 3, 9, 1, 1, 3], your snippet assigns weight 9 as <h6> (good), but then 5 (the next highest) as <h5> despite 5 being the midpoint between min value 1 and max value 9. Without doing any math, I would assume that weight 5 should be either <h3> or <h4> (a mid-range tag). You might review my conversion of your answer: 3v4l.org/dv3gvWilen
S
34

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.">";
}
Serpasil answered 1/8, 2008 at 23:10 Comment(0)
H
27

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)?

Himalayas answered 3/8, 2008 at 13:1 Comment(0)
V
10

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>
Villegas answered 17/9, 2008 at 21:29 Comment(1)
This seems a good approach to me. If your data is in an array just skip the database part. I'd recommend you store the artist name & count in a single associative array. To make that work with the above code use something like: $tags = array("the roots" => 5,"michael jackson" = 3,"billy idol" => 9,"madonna" => 1); I would agree don't use H tags since it messes up your semantics. Spans would be my choice. Finally, a helper exists in Zend Framework that may just do what you need. See framework.zend.com/manual/en/zend.tag.htmlButyraceous
H
8

@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>";
}
Housemaster answered 1/8, 2008 at 23:58 Comment(1)
The asked question specified that lesser counts should be represented as larger fonts.Wilen
M
3

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

Misrule answered 14/7, 2010 at 18:19 Comment(1)
echo sprintf() should never exist in any code for any reason -- this is what printf() is for.Wilen
S
3

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>";
}
?>
Sydelle answered 28/1, 2017 at 4:39 Comment(0)
O
2

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>.

Oby answered 2/10, 2008 at 21:47 Comment(2)
why did someone give it a -1?Harkey
This question has always been tagged as a PHP question.Wilen
W
0

With a few trivial mathematical manipulations, the distribution of the sizing can be steered toward a balanced output which is representative of the actual data spread. The following will ensure that <h6> is always used and then if there are at least two unique weighting values in $count, then <h1> will be used. Then if there are 3 or more unique weighting values, they will find their relative place in the range between the minimum and maximum values.

Code: (Demo with several test examples)

function balancedTagCloud(array $values, array $priorities): array
{
    $low = min($priorities) - 1;
    $high = max($priorities) - $low;
    return array_map(
        fn($v, $p) => sprintf(
            '<h%1$d>%2$s</h%1$d>',
            ceil(($p - $low) / $high * 6),
            $v
        ),
        $values,
        $priorities
    );
}

echo implode("\n", balancedTagCloud($artist, $count));

Output from the sample input:

<h4>the roots</h4>
<h2>michael jackson</h2>
<h6>billy idol</h6>
<h1>more</h1>
<h1>and more</h1>
<h2>and_YET_MORE</h2>
Wilen answered 23/11, 2023 at 21:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.