Vanilla 1.1.9 is a product of Lussumo. More Information: Documentation, Community Support.
I am building a tag cloud at the moment, using PHP and a MySQL database. Inside the database I have got a table called 'pages', which has a column called 'tags'. Tags are in this case separated with line breaks and can contain letters, numbers, hyphens, underscores and periods.
Here is what I have got, so far:
<?
function cloud() {
global $root, $db;
// $root contains the root dir of the site, for example 'http://cssbeauty.com/'
// $db contains an array of details of the database (username, password, prefix, etc)
db();
$query = mysql_query('SELECT tags FROM '.$db['prefix'].'pages WHERE tags!=""');
$rows = mysql_numrows($query);
mysql_close();
if ($rows > 0) {
while($items = mysql_fetch_array($query)) {
$tagList = explode("\n", $items[0]);
while(list($key, $value) = each($tagList)){
if ($value) {
$tags[$value] += 1;
}
}
}
$minval = min(array_values($tags));
if ($minval > 2) {
$minval = 2;
}
$maxval = max(array_values($tags));
if ($maxval < 2) {
$maxval = 2;
}
echo '<ul>';
foreach ($tags as $tag => $count) {
$size = ($count - $minval) / ($maxval - $minval) * 100;
if ($size > 80) {
$level = 9;
} else if ($size > 70) {
$level = 8;
} else if ($size > 60) {
$level = 7;
} else if ($size > 50) {
$level = 6;
} else if ($size > 40) {
$level = 5;
} else if ($size > 30) {
$level = 4;
} else if ($size > 20) {
$level = 3;
} else if ($size > 15) {
$level = 2;
} else {
$level = 1;
}
echo '<li><a rel="tag" href="'.$root.'tag/'.$tag.'/?phpMyAdmin=4594f30712f4fabaff6997416810f3f2" class="level'.$level.'">'.$tag.'</a></li>';
}
echo '</ul>';
}
}
?>
What is left to do:
Anyone who can help me with these two? Comments/Adjustments on current code are also welcome :-)
natcasesort ($tags)
cloud($limit){
...
$offset = "0";
if ($limit == 0) $limit = 18446744073709551615;
$query = mysql_query('SELECT tags FROM '.$db['prefix'].'pages WHERE tags!=""' LIMIT $offset, $limit );
...
}
L3ggy: Looks like a nice script, but it does not sort tags alphabetically and does not have the ability to set a maximum amount of tags :-(
neXus: Heheheh, if only it were that easy :-D I experimented a bit with those a couple of days ago. natcasesort only sorts the selected rows of the database, appearantly, not the tags individually. The same goes for the limit in the SQL query. (It limits the amount of rows that get selected, not the amount of tags.)
I found a working solution for the alphabetical order, though:
echo '<ul>';
foreach ($tags as $tag => $count) {
echo '<li><a rel="tag" href="'.$root.'tag/'.$tag.'/?phpMyAdmin=4594f30712f4fabaff6997416810f3f2" class="level'.$level.'">'.$tag.'</a></li>';
}
echo '</ul>';
had to be replaced with
foreach ($tags as $tag => $count) {
$cloud[] = '<li><a rel="tag" href="'.$root.'tag/'.$tag.'/?phpMyAdmin=4594f30712f4fabaff6997416810f3f2" class="level'.$level.'">'.$tag.'</a></li>';
}
natcasesort($cloud);
for(reset($cloud); strcmp($key = key($cloud),""); next($cloud)) {
$shown .= $cloud[$key];
}
$shown = '<ul>
'.$shown.'
</ul>';
A lot of code, for such a simple thing, I guess... but as long as I have not found a better solution it will have to do.
Still looking for a way to output the 20 most popular tags... :-)
Well, you first problem is that your database is not set up very well for tagging. If you database were configured better, this would be a simple script. Here's the problem:
From what I can tell, you have a table called pages
that has a column called tags
. This means that if you have 237 pages tagged with sports
, you'll have 237 rows in your database containing the sports
tag. This is not good. In addition, your tags field can store multiple tags. This is going to make anything you do harder, because it's going to be damn near impossible (if not impossible) to sort the tags in the database.
You understood in your model that there's a relationship between tags and pages, but the relationship was modeled poorly. The relationship between tags and pages is a many-to-many relationship, and the best way to model a many-to-many relationship is with a join table. This may mean more back end work, because you'll have to parse the tags the user gives you before tagging the page in the database, but it's worth it.
A better way to organize your database is to have three tables. Your pages
table could remain essentially changed, except you'll remove the tags
column. You'll then create a new tags
table. The only thing the table needs to store is an ID and the tag itself. Then you'll have a third table that models the many-to-many relationship between tags and pages. Call it pages_tags
for the example. This table would have two columns: page_id
and tag_id
.
Now the database is set up properly. If you have 237 pages tagged with sports
, your tags
table will have one sports
record, and your pages_tags
table will have 237 records (each relating the sports tag to a page).
Your query then becomes much easier:
SELECT pages_tags.tag_id, COUNT(pages_tags.page_id) AS 'uses', tags.tag_name
FROM pages_tags
INNER JOIN tags ON pages_tags.tag_id = tags.id
GROUP BY pages_tags.tag_id
ORDER BY COUNT(pages_tags.page_id)
LIMIT 20;That query will return the 20 most popular tags and the number of times each tag is used (along with the tag id). At this point, the list is sorted in order of popularity. Let's assume you have 10 different font sizes you're going to use in your tag cloud represented by CSS class names level_1
(smallest) to level_10
(largest). Before you do anything to your data, it's in an array like the following:
Array (
[0] => Array(
[tag_id] => ###,
[uses] => ###,
[tag_name] => ###
),
[1] => Array(
[tag_id] => ###,
[uses] => ###,
[tag_name] => ###
),
...,
[19] => Array(
[tag_id] => ###,
[uses] => ###,
[tag_name] => ###
)
)You should then loop through the array and add a css_class
index to each position in the array. Then your array looks like the following:
Array (
[0] => Array(
[tag_id] => ###,
[uses] => ###,
[tag_name] => ###,
[css_class] => '$$$$$'
),
[1] => Array(
[tag_id] => ###,
[uses] => ###,
[tag_name] => ###,
[css_class] => '$$$$$'
),
...,
[19] => Array(
[tag_id] => ###,
[uses] => ###,
[tag_name] => ###,
[css_class] => '$$$$$'
)
)You can then use PHP's usort
function to sort the array by tag_name
. At that point, you can loop through the array and print a list item for each index:
foreach ($tags as $tag) {
$this_tag = (object)$tag;
echo("<li><a class=\"{$tag->css_class}\" href=\"/tags/{$tag->tag_name}\">{$tag->tag_name}</a></li>");
}Hopefully this helps. I know you didn't ask about your database structure, but I think it's kind of at the root of your problem.
Wow, many thanks for taking the time to write all that, Varland. I think you're totally right, although I had to stare at that query for a couple of minutes, heheh. (I'm not that experienced with queries as you probably already noticed...)
Eiriks:
In the last code sample I included, you can see that one of the things I've done is the following:
$this_tag = (object)$tag;I do this because I prefer PHP's object syntax to its array syntax (I'd rather be able to say $this_tag->tag_name than $this_tag['tag_name']). In either case, you can refer to each property of the array in the way I just showed.
Am I missing something? What are you not understanding, because I'd love to help. Are you talking about sorting the array? Hopefully what I've said here is helpful. If not, post again and let me know what you'd like.
SELECT *, count(name) AS TagSum FROM photo_has_tag, tag_has_name WHERE photo_has_tag.tid = tag_has_name.tid AND tag_has_name.tactive = 1 GROUP BY name ORDER BY Rand() DESC;
while ($myTagsRow = mysql_fetch_array($myTagsQuery)) {
$mytag_cloud[$myTagsRow[name]] = $myTagsRow['TagSum'];
}
$cloud_max = max($mintag_cloud);
$cloud_min = min($mintag_cloud);
$cloud_dist = ($cloud_max - $cloud_min)/3;
foreach($mytag_cloud as $tagname=>$tag){
if ($tag == $cloud_min){
print "<_intentional_a href=\"file.php?tag=".$tid."\" style=\"font-size:0.5em; text-decoration:none;\">".htmlentities($tagname)." < /_intentional_a>\r\n ";
}
elseif ($tag == $cloud_max){
print "<_intentional_a href=\"file.php?tag=".$tid."\" style=\"font-size:5em; text-decoration:none;\">".htmlentities($tagname). " < / _intentional_a>\r\n ";
}
elseif ($tag > ($cloud_max+($cloud_dist*2))) {
print "<_intentional_a href=\"file.php?tag=".$tid."\" style=\"font-size:3em; text-decoration:none;\">".htmlentities($tagname). " < / _intentional_a> \r\n";
}
elseif ($tag > ($cloud_min+$cloud_dist)) {
print "<_intentional_a href=\"file.php?tag=".$tid."\" style=\"font-size:1.6em; text-decoration:none;\">".htmlentities($tagname)." < / _intentional_a> \r\n";
}
else {
print "<_intentional_a href=\"file.php?tag=".$tid."\" style=\"font-size:0.9em; text-decoration:none;\">".htmlentities($tagname)." < / _intentional_a> \r\n";
}
}
Eiriks…
First, let me comment on your database structure… Consider revising it and following a standard naming practice (if you're following on now, it sucks). I like the Rails way of naming database tables and columns. The basics are this:
You may or may not like this particular convention, but it's always worked well for me. If you followed this convention, your database would have the following three tables:
See the similarity to the structure I listed above? The query is the same, except you want to add the condition that the tag's is_active field should be true. So, adjust the query accordingly:
SELECT pages_tags.tag_id, COUNT(pages_tags.photo_id) AS 'uses', tags.tag_name
FROM pages_tags
INNER JOIN tags ON pages_tags.tag_id = tags.id
GROUP BY pages_tags.tag_id
HAVING tags.is_active IS TRUE
ORDER BY COUNT(pages_tags.page_id)
LIMIT 20;From that point, the process is exactly like the post above. Please let me know if I'm getting at what you were looking for.
$sample = array('one'=>"test1",'two'=>"test2",'three'=>"test3");
$object = (object)$sample;
echo $object->one;Yes.
1 to 15 of 15