ASP.NET MVC: История одного проекта "Облако тегов" (часть 11)
Сайтостроение | создано: 25.06.2012 | опубликовано: 25.06.2012 | обновлено: 13.01.2024 | просмотров: 11673 | всего комментариев: 1
Облако меток очень удобный и распространный вариант навигации по сайту. Тема этой статьи - "Облако меток" (или облако тегов, если хотите).
Содержание
ASP.NET MVC: История одного проекта "Готовимся к старту" (часть 1)
ASP.NET MVC: История одного проекта "Всё ради данных" (часть 2)
ASP.NET MVC: История одного проекта "Шаблоны и внешний вид" (часть 3)
ASP.NET MVC: История одного проекта "Еще немного классов" (часть 4)
ASP.NET MVC: История одного проекта "UI - всё для пользователя" (часть 5)
ASP.NET MVC: История одного проекта "UI - Добавление экспоната" (часть 6)
ASP.NET MVC: История одного проекта "UI - Редактирование экспоната" (часть 7)
ASP.NET MVC: История одного проекта "Обработка ошибок" (часть 8)
ASP.NET MVC: История одного проекта "Фильтрация" (часть 9)
ASP.NET MVC: История одного проекта "Поиск" (часть 10)
ASP.NET MVC: История одного проекта "Облако тегов" (часть 11)
ASP.NET MVC: История одного проекта "Главная страница" (часть 12)
Тема дня
Облако тегов очень удобный и распространный вариант навигации по сайту. Тема этой части - "Облако меток" (или облако тегов, если хотите, или вообще, облако ключевых слов).
Предмет разговора
Экспонаты в музее юмора помечаются метками, а значит эти метки можно представить пользователю в виде облака. Облако буду делать с разбиением на группы (кластеры), чтобы оно наглядно отображало частоту использования меток, отображая часто используемы большим размеров, а малоспользуемые, соответственно, меньшим.
Кластерный анализ
Итак, приступим. У меня на сайте в музее очень большое количество меток. Для того чтобы сформировать "правильное" облако меток, воспользуюсь методом кластерного анализа. Этот алгоритм достаточно прост, и для достижения успешного результата надо:
- Задать количиство групп (кластеров)
- Определить центр масс для каждой группы. Это обычное число, по которому выполняется разбиение. В моем случае это частота использования тега. Минимальное количество я задал для первой группы, а максимальное количество, соответственно, для последней 10-ой группы.
- Определить расстояние от центра массы до тега для каждой метки. Это простое арифметическое вычитание из количества использования метки числа центра масс .
- Сгруппировать метки в в группы (кластеры). Требуется найти какой центр масс ближе числу использований метки. В эту группу и добавляем метку.
- Пересчитываем значения центров масс. На этом этапе надо определить среднее значение частоты использования всех тегов, которые вошли в группу (кластер) в пункте 4.
- Повторяем пункты с 3-го по 5-ый, пока не закончатся метки.
Я определю в своем облаке 10 групп (кластеров). У меня кластеры (группы) будут отображаться разными CSS стилями. Таким образом, у меня будет для каждой из групп задан свой стиль в CSS. Определения стилей показывать не буду, пусть каждый раскрасит метки в зависимости от "частоты" самостоятельно.
Класс TagCloud
Для начала создаю класс, который будет использоваться при формировании облака меток:
/// <summary>
/// Класс тега для облака.
/// </summary>
public class TagCloud {
#region конструкторы
public TagCloud() { }
public TagCloud(string name, string css, int total) {
this.Name = name;
this.CssClass = css;
this.Total = total;
}
public TagCloud(int id, string name, string css, int total) {
this.Id = id;
this.Name = name;
this.CssClass = css;
this.Total = total;
}
#endregion
#region свойства
/// <summary>
/// Идентификатор
/// </summary>
public int Id { get; set; }
/// <summary>
/// Наименование
/// </summary>
public string Name { get; set; }
/// <summary>
/// Стиль Css
/// </summary>
public string CssClass { get; set; }
/// <summary>
/// Всего изпользован
/// </summary>
public int Total { get; set; }
#endregion
}
Формирование облака
Надеюсь всё понятно, со свойствами и с конструкторами класса TagCloud. Теперь надо сделать расширение (extension), которое будет метки (tag) превращать в метку для облака (TagCloud):
internal static IEnumerable<TagCloud> TagsToCloudItems(this IEnumerable<Tag> source) {
if (source == null) throw new ArgumentNullException("source");
return source.Select(x => new TagCloud(x.Id, x.Name, "tag", x.Exhibits.Count));
}
Следующим этапом требуется создать расширение (extension), которое разобъёт сформированные метки для облака (TagCloud) по группам (в кластеры). Приведу этот код целиком:
/// <summary>
/// Создает облако тегов
/// </summary>
/// <param name="tagsCloud">Массив меток</param>
/// <param name="clusterCount">Количество кластеров при генерации</param>
internal static IEnumerable<TagCloud> CreatorCloud(this IEnumerable<TagCloud> tagsCloud, int ClusterCount) {
int totalCount = tagsCloud.Count();
tagsCloud = tagsCloud.OrderBy(ff => ff.Total).ToArray();
List<List<TagCloud>> clusters = new List<List<TagCloud>>();
if (totalCount > 0) {
int min = tagsCloud.Min(c => c.Total);
int max = tagsCloud.Max(c => c.Total) + min;
int completeRange = max - min;
double groupRange = (double)completeRange / (double)(ClusterCount);
List<TagCloud> cluster = new List<TagCloud>();
double currentRange = min + groupRange;
for (int i = 0; i < totalCount; i++) {
while (tagsCloud.ToArray()[i].Total > currentRange) {
clusters.Add(cluster);
cluster = new List<TagCloud>();
currentRange += groupRange;
}
cluster.Add(tagsCloud.ToArray()[i]);
}
clusters.Add(cluster);
}
TagCloud tc;
List<TagCloud> result = new List<TagCloud>();
for (int i = 0; i < clusters.Count; i++) {
foreach (TagCloud item in clusters[i]) {
tc = new TagCloud(item.Id, item.Name, "tag" + i.ToString(), item.Total);
result.Add(tc);
}
}
return result.OrderBy(x => x.Name).AsEnumerable();
}
Можно было бы сделать всё в одном методе: и превращение меток в облачные метки, и сразу же разбросать их по группам. Но я специально разделил на два метода. Первый будет использоваться в WCF-сервисе для Silverlight. Далее в контролере Museum создаем новый ActionResult:
[OutputCache(Duration = 2880)]
public ActionResult Cloud() {
var model = tagRepository
.AllIncluding(x => x.Exhibits)
.TagsToCloudItems()
.CreateCloud(10);
return View(model);
}
Просто последовательно вызываю два расширения на коллекцию меток (Tag), и затем отдаю их в представление (View). Обратите внимание на то, что я пометил данный метод аттрибутом OutputCache, который на 48 часов будет кэшировать данные выдаваемые в результате выполнения этого метода. Это сделано в силу того, что частота обновления облака не велика.
UI облака
Если есть метод Cloud значить должно быть и представление (View). В этом представлении подключаю дополнительный CSS для отображения меток по группам кластера, о котором я говорил выше. А отрисовку меток делает UserControl (выделено жирным):
@section header{
<link href="@Url.Content("/content/tags.css")" rel="stylesheet" type="text/css" />
}
@model IEnumerable<Calabonga.Mvc.Humor.Models.TagCloud>
@{
ViewBag.Title = "Облоко тегов";
<strong> Layout = "~/Views/Shared/_LayoutMain.cshtml";
</strong>}
<h2>
Облако меток музейных экспонатов</h2>
<p><strong>@Html.Partial("Controls/TagCloudControl")</strong></p>
И сам контрол для отрисовки меток:
@model IEnumerable<Calabonga.Mvc.Humor.Models.TagCloud>
@if (Model != null && Model.Count() > 0) {
foreach (Calabonga.Mvc.Humor.Models.TagCloud item in Model) {
<span style="margin: 2px; padding: 2px; line-height: 2em; background-color: #f5f5f5;">
@Html.ActionLink(
string.Concat(item.Name, " (", @item.Total, ")"),
"index",
"museum",
new { t = item.Name },
new { @class = item.CssClass })
</span>
}
}
Запускаем... Хм... Ссылки-то на облако меток нет... Облако меток у меня есть только в музее юмора, а на ленте быть не должно. Значить достаточно разместить ссылку на некоторых страницах музея юмора (но не ленты экспонатов, чтобы не вводить в заблуждение посетителей). Я разместил на странице отображения всех экспонатов музея и на детальном просмотре. Метки кликабельны, а значит получилось то, что и требовалось.
Заключение
Осталось сказать: "спасибо за внимание".
Комментарии к статье (1)
Думаю "нынче" теги должны быть с какими то более магическими штуками :)