Создание хронологии событий на основе отзывов клиентов

Наши клиенты запросили визуальную диаграмму, которая отображает общее «состояние» всей их среды за определенный период времени. Мы обсудили это как команда и решили создать интерактивную временную шкалу. Мы разработали интерактивную временную шкалу, представляющую все события, происходящие в их среде за определенный период времени.

Я начал изучать библиотеки визуализации данных, включая D3.js и viz.js, а также некоторые другие, и показал моей команде несколько примеров, чтобы зажечь идеи о том, что возможно. В конце концов я решил использовать временную шкалу viz.js, потому что она предоставила план, который нам нужно было разработать. Однако я знал, что мне придется значительно настроить сценарий, чтобы он соответствовал нашему дизайну и потребностям пользователей.

Мы прошли несколько итераций - на основе отзывов клиентов я несколько раз менял и модифицировал скрипт Viz.js, чтобы он соответствовал потребностям клиентов и продукта.

Итерация 1

Это первый построенный мною прототип. Используя библиотеку viz.js и некоторые приемы CSS, я смог быстро создать макет интерактивного прототипа, который наш исследователь мог протестировать с нашими клиентами.

Положительные отзывы клиентов: действительно легко понять, поскольку для всех типов событий есть своя линия.

Проблема дизайна, которую необходимо решить. Четыре линии занимали много места по вертикали, и нам нужно было уместить другие элементы. Также нам нужно было найти способ объединить события, произошедшие за короткий промежуток времени при уменьшении масштаба. В этой демонстрации они перекрываются, что скрывает релевантные данные для наших пользователей.

Итерация 2

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

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

Проблема дизайна, которую необходимо решить. В этой версии проблемой оставалось перекрытие событий. Мы подумали о создании более крупного пузыря, когда события перекрываются. Но в клиентской среде в худшем случае могут быть сотни событий. Сохранение относительных размеров пузырей при таком большом разбросе данных было большой проблемой. С другой стороны, использование цвета только для отображения различных событий создало новую озабоченность по поводу доступности.

Итерация 3

Решенные проблемы: мы решили проблему перекрытия событий в этой итерации. Мы обсудили это как команда и пришли к идее «захват приоритетных событий». Таким образом, когда два или более событий объединяются, значок объединенного события является производным от самого серьезного события. Мы также отобразили количество объединенных событий выше, чтобы четко указать, сколько событий было объединено. Мы переработали кнопки управления, чтобы они стали более компактными для экономии места. Мы также добавили 4 разных формы значков поверх цвета, чтобы решить проблему доступности. Мы переместили информацию о дате и времени для временной шкалы под событиями.

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

Изменение библиотеки: я назначил определенный класс на основе типа события, которое без проблем появляется в некластерном режиме. Я изменил метод `links.Timeline.ClusterGenerator.prototype.getClusters`, чтобы добавить настраиваемую логику для определения того, какой класс следует добавить в режиме кластера.

Кластеризованное событие знает, сколько событий у него есть. Затем он перебирает все события, чтобы получить список имен классов (мы используем имя класса, чтобы определить, какой это тип события).

Вот как мы установили приоритетность типов событий, от наиболее серьезных до наименее серьезных:
1. Ошибка
2. Предупреждение
3. Техническое обслуживание
4. Информация / ОК

В зависимости от приоритета кластер покажет этот конкретный стиль. Для этого я использовал следующий код:

var classNames = [];
var clusterEventIds = [];
clusterItems.forEach(function (item) {
    classNames.push(item.className);
});
//custom code: picking highest one for cluster className
//className order by : [undefined, 'yellow', 'green-m', 'green']
var clusterClassName = undefined;
var selectedItemIndex = 0;
if (classNames.indexOf(undefined) > -1) {
    clusterClassName = undefined;
    selectedItemIndex = classNames.indexOf(undefined);
}
else if(classNames.indexOf('yellow') > -1) {
    clusterClassName = 'yellow';
    selectedItemIndex = classNames.indexOf('yellow');
}
else if(classNames.indexOf('green-m') > -1) {
    clusterClassName = 'green-m';
    electedItemIndex = classNames.indexOf('green-m');
}
else if(classNames.indexOf('green') > -1) {
    clusterClassName = 'green';
    selectedItemIndex = classNames.indexOf('green');
}

Я установил className для объекта кластера на основе приведенной выше логики:

//custom code: assigning highest order className to the cluster
cluster.className = clusterClassName;
//Set the className in 
`links.Timeline.ItemBox.prototype.updateDOM`:
if (this.className) {
    links.Timeline.addClassName(divBox, this.className);
    links.Timeline.addClassName(divLine, this.className);
    links.Timeline.addClassName(divDot, this.className);
}

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

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

Итерация 4

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

Одним из требований проекта была возможность выделения объекта / кластера временной шкалы, когда пользователь нажимает на него. Кроме того, необходимо было уведомить внешнюю функцию для выполнения дополнительных задач, таких как прокрутка до конкретных сведений о событии, подключенных к странице, связанной с событием, по которому было выполнено нажатие. Итак, мне нужно было связать каждое событие с объектом / кластером временной шкалы. Сначала я добавил дополнительные объекты в функцию `links.Timeline.Item`, чтобы включить:

this.event_id = (data.event_id) ? data.event_id : null;
this.event_ids = (data.event_ids) ? data.event_ids: [];

Пока мы определяли className кластера событий, мы также собрали идентификаторы событий в `links.Timeline.ClusterGenerator.prototype.getClusters`:

var classNames = [];
var clusterEventIds = [];
clusterItems.forEach(function (item) {
    clusterEventIds.push(item.event_id);
    classNames.push(item.className);
});

Затем я использовал индекс className, чтобы определить приоритетное событие, которое мы собираемся использовать при срабатывании события мыши, и передать event_id и event_ids при создании Item:

var eventId = clusterItems[selectedItemIndex].event_id;
// boxes only
cluster = this.timeline.createItem({
    ‘start’: new Date(avg),
    ‘content’: content,
    ‘group’: group,
    ‘event_id’: eventId,
    ‘event_ids’: clusterEventIds
});

Теперь я знаю, какое событие связано с каким объектом временной шкалы. Мне нужно было добавить события мыши к элементу и добавить правильные функции обратного вызова с помощью eventId. Итак, я обновил функцию `links.Timeline.ItemBox.prototype.updateDOM` следующим кодом:

links.Timeline.addEventListener(divDot, ‘mouseover’, function() {
    if(mouseoverItemEventCallback != undefined) mouseoverItemEventCallback(this.event_id);
}.bind(this));
links.Timeline.addEventListener(divDot, ‘click’, function() {
    if(clickItemEventCallback != undefined) clickItemEventCallback(this.event_id, divDot);
}.bind(this));
links.Timeline.addEventListener(divDot, ‘keypress’, function(event) {
    var keycode = event.which || event.keyCode;
    if(keycode == 13 && clickItemEventCallback != undefined) clickItemEventCallback(this.event_id, divDot);
}.bind(this));
itemToEventMapCallback(this.event_id, divDot, this.event_ids);

Прочие модификации

Доступность: я добавил следующий код, чтобы элементы шкалы времени могли быть доступны с клавиатуры в функции `links.Timeline.ItemBox.prototype.updateDOM`:

divDot.setAttribute(“tabindex”, 0);
divDot.setAttribute(“role”, “button”);

Локаль: viz.js имеет функцию локали. Мне пришлось использовать предопределенные строковые константы вместе с настраиваемыми строками (AM, PM, всплывающая подсказка) и использовать их динамически, чтобы убедиться, что строки отображаются соответствующим образом в зависимости от языкового стандарта браузера.

Хотите узнать больше? Ознакомьтесь с этой статьей в моем личном блоге, чтобы попробовать интерактивную шкалу времени на себе.

Отдельное спасибо Александру Вунщику из команды viz.js за включение моего примера в их витрину.

Райан Арнелл (Ryan Arnell) - инженер пользовательского интерфейса и интерфейса пользователя в IBM, базирующейся в Остине. Приведенная выше статья носит личный характер и не обязательно отражает позицию, стратегию или мнение IBM.