Pers.narod.ru. PHP. Пишем "облако тегов" на PHP

Как вывести на сайте теги или ключевые слова разными размерами шрифта, чтобы чем популярнее тег, тем больше был размер слова, но не до бесконечности, а в каких-то заданных пределах?

Уф, трудней всего было перевести вопрос на приемлемый русский :)

Действительно, "облака тегов" есть на большинстве современных сайтов, имеют они самый разный вид, от красивых крутящихся "флешек" до обычного "поля слов", как в этом блоге. Вся фишка в том, чтобы более популярные слова выводились большим размером шрифта, но процесс этот не мог зайти слишком далеко и был заранее ограничен какими-то заданными программистом рамками :) Нам вполне хватит нескольких строчек кода из исходника этого блога, которые мы ещё упростим.

Понадобятся всего 3 настройки:

define ("TAGSLEVEL","10"); //Сколько разных размеров текста допускаем
define ("BASEFONT","8");   //Размер для вывода текста "нулевого уровня" (в пунктах)
define ("PTSTEP","1");	  //Каждый следующий уровень на PTSTEP пунктов больше

То есть, мы различаем не более 10 разных размеров шрифта, текст никогда не будет меньше 8 и больше BASEFONT+PTSTEP*TAGSLEVEL = 18 пунктов.

Предположим, что интересующая нас информация хранится в таблице tags со следующей структурой:

CREATE TABLE tags (
 name        varchar(32), #Название тега, ключевое слово
 rating      int          #Рейтинг, мера популярности или значимости тега
);

Разумеется, в таблице могут быть другие поля, но они для нас сейчас несущественны.

Сначала узнаем, каков максимальный из существующих "рейтинг" тега:

$data = mysql_fetch_array(mysql_query("select max(rating) from tags"));
$max=$data[0];

Теперь выберем все теги и в типовом цикле обработки сформируем облако. Размер каждого тега-слова будет задаваться конструкцией вида <span style="font-size: 10pt;">слово</span>, то есть, обычными средствами HTML. Получится примерно такой код:

$sql='select * from tags';
$result = mysql_query($sql);
if ($result and mysql_num_rows($result)) {
 while ($tag = mysql_fetch_assoc($result)) {
  $size = BASEFONT;
  if ($max > 0)  $size += PTSTEP * round ($tag['rating'] * TAGSLEVEL / $max);
  print '<span style="font-size: '.$size.'pt;">'.htmlspecialchars($tag['name']).'</a></span> ';
 }
}

Всё дело, конечно, в формуле для вычисления размера текста $size по рейтингу тега $tag['rating']. Пояснить её легко. В общем виде значение x, принадлежащее исходному интервалу [a,b], можно линейно преобразовать в значение y, принадлежащее выходному интервалу [c,d], по формуле:

Если каждый очередной рейтинг принадлежит интервалу [0,$max] (исходный интервал), а нам надо "перегнать" его в размер текста, принадлежащий интервалу [BASEFONT,BASEFONT+TAGSLEVEL*PTSTEP] (выходной интервал), то, подставив значения в формулу, получаем

BASEFONT + ($tag['rating']-0)*(BASEFONT+TAGSLEVEL*PTSTEP-BASEFONT)/($max-0)

или, после сокращения и округления (ведь все размеры шрифта в пунктах - целые числа), видим то, что написано в коде.

Часть 2: Логарифмируем облако тегов :)

Всё написанное выше про несложное формирование облака тегов вполне верно. Однако уже не раз, в том числе и в блоге, я обращал внимание на то, что "линейное" изменение размера тега в зависимости от его частоты встречаемости (рейтинга) не очень хорошо отражает суть дела.

Так, самый распространённый на момент написания сообщения тег встречается в моём блоге 77 раз, после пересчёта размера шрифта в диапазон [8,18] пунктов он отобразится максимальным допустимым размером 18 пунктов. Второй по частоте тег имеет 73 вхождения и получит размер в 17 пунктов, что вполне неплохо. Но уже тег, имеющий всего-навсего 4 вхождения отобразится шрифтом в 9 пунктов, а 27 вхождений, то есть чуть больше трети от максимума, дадут размер целых 12 пунктов, то есть, практически "средний" из всех возможных размеров.

Просто некоторое количество тегов обычно намного популярнее большинства других, а линейный расчёт размера шрифта приводит к тому, что небольшое количество важных тегов визуально теряется на фоне множества мало- и среднераспространённых.

Интуитивно ясно, что шкала зависимости размера шрифта от рейтинга слова должна быть скорее логарифмической, чем линейной, ведь именно логарифмическая шкала исключительно удобна для отображения больших диапазонов значений величин. Кроме того, для многих органов чувств величина ощущения пропорциональна логарифму воздействия. Так что давайте попробуем вывести "эвристическое" логарифмирование для размера тегов.

Предположим, что мы рассчитали линейный размер шрифта $size и по-прежнему имеем максимальную из частот встречаемости в переменной $max, частоту встречаемости текущего слова (тега) - в элементе массива $tag['rating'], а отображение "логарифмических тегов" зависит от установки настройки "движка" с именем LOGTAGS в ненулевое значение. Эта настройка может храниться, например, в файле конфигурации и быть по умолчанию выключенной:

define ("LOGTAGS","0");

Для пересчёта размеров шрифта по логарифмической шкале вместо линейной напишем следующий фрагмент программы:

if (LOGTAGS) { //Если включена настройка логарифмирования тегов
 $rating=0;
 if ($tag['rating']>0) $rating=log($tag['rating']);
 $size=max(BASEFONT,round($size*$rating/log($max)));
}

Натуральный логарифм на PHP пишется именно log, впрочем, применение десятичного log10 мало что бы изменило.

Этот код нужно выполнять в ветви алгоритма if ($max > 0) после вычисления обычного линейного размера $size оператором $size+=....

Недостаток приведённого кода - если шаг изменения размеров тега PTSTEP не равен 1, то допустимые размеры шрифта могут не соблюдаться из-за дополнительного пересчёта, например, разрешены только 10 и 12 пунктов, а получится 11. Так что для соблюдения размеров, заданных в PTSTEP, код придётся дополнить следующим (перед закрывающей фигурной скобкой):

$ost = ($size-BASEFONT)%PTSTEP;
if ($ost<>0) $size-=$ost;

Окончательно имеем вот что:

if ($max > 0) {
 $size += PTSTEP * round ($tag['rating'] * TAGSLEVEL / $max);
 if (LOGTAGS) {
  $rating=0;
  if ($tag['rating']>0) $rating=log($tag['rating']);
  $size=max(BASEFONT,round($size*$rating/log($max)));
  $ost = ($size-BASEFONT)%PTSTEP;
  if ($ost<>0) $size-=$ost;
 }
}

Такое облако понравилось мне гораздо больше на сайтах, где я его поставил. Хотя, надо полагать, логарифмическая шкала становится лучше линейной при достаточно большом количестве тегов и заметном разбросе значений их "рейтинга".

Понять, что именно изменилось, поможет картинка:

Линейное и логарифмическое масштабирование размеров слов в зависимости от частоты встречаемости

На ней видно, что менее популярные слова теперь "проваливаются" по размерам шрифта.

А вот документ Excel, в котором я обсчитал это изменение, прежде чем добавить его в код:

 Расчёт линейного и логарифмического размера шрифта в Excel (30 Кб)

Рейтинг@Mail.ru

вверх гостевая; E-mail
Hosted by uCoz