Пример дашборда с использованием разнообразных функций visApi() для обработки и взаимодействия с Drill Down на виджетах.
Версия платформы
2.22
Статус
в доработке
Сложность
выше среднего
Используемые библиотеки
Вступление
В данном примере демонстрируются следующие фичи:
Drill Down для кастомного виджета (2 и более измерений в строках запроса).
Использование динамических данных во всплывающих подсказках (tooltip) при Drill Down.
Динамическое изменение подписи в заголовке виджета в зависимости от уровня Drill Down.
Отображение текущего уровня Drill Down в другом виджете.
На картинке можно увидеть виджет Treemap, на котором выполнен Drill Down до 2 уровня. При этом заголовок виджета и текст всплывающей подсказки отображают значение элемента измерения внутрь которого был сделан Drill Down. Виджет “Текущее измерение” также отображает, что сейчас мы находимся на уровне атрибута измерения “Subcategory” (“Подкатегория”) .
Данные
В Викубе есть группа показателей “Фактические продажи - рубли“ (из общедоступных демо данных) с несколькими показателями (для treemap с цветовой осью потребуется 2 показателя) и с несколькими атрибутами измерений (или измерениями). Для работы Drill Down нужно как минимум два измерения или атрибута.
В виджете treemap в DataTab на вкладке Привязка данных настроен 1 запрос.
В столбцах два показателя: “Стоимость продаж” (используется как основной показатель, отвечает за размер сегментов на виджете) и “Прибыль” (используется как дополнительный показатель, на основании которого рассчитывается в коде значение цветового показателя).
В строках три атрибута измерения (не в режиме группировки) “Категория”, “Подкатегория”, “Наименование продукта” для возможности постепенной детализации по атрибутам от самого малочисленного к самому многочисленному по элементам. В настройках установлен флаг “Использовать Drill Down”.
В виджете “Текущее измерение” данные не установлены. В коде данный виджет получает структуру данных с виджета treemap и отображает её, используя идентификаторы атрибутов измерений.
Treemap, затем “Текущее измерение” (в версии 2.24 и выше можно в виджете “Текущее измерение” использовать visApi().onWidgetLoadedListener() для задания правильного порядка выполнения кода)
Просмотр части данных на первом уровне Drill Down в виде таблицы:
Отображается только пересечение показателей с первым измерением, так как включен Drill Down.
Код
Порядок создания виджетов во избежание ошибок при выполнении кода: Treemap, затем “Текущее измерение” (в версии 2.24 и выше можно в виджете “Текущее измерение” использовать visApi().onWidgetLoadedListener() для задания правильного порядка выполнения кода).
Код виджета Treemap.
В коде виджета описаны настройки, с которыми отрисовывается диаграмма, как преобразуются данные в формат подходящий для виджета, как создаются и обрабатываются подписки на те или иные события.
/** Описание порядка дриллдауна, далее по тексту будут помечены соответствующие блоки.
* 1) Делаем drilldown с помощью visApi().drillDown.
* 2) В подписке visApi().onDrillDownListener получаем данные следующего уровня после drilldown.
* 3) Строим новый уровень виджета с помощью полученных новых данных.
* 4) Делаем drilldup с помощью visApi().drillup или через навигационную панель, которую можно вызвать с помощью visApi().showControlPanel.
*/
//сохранаяем ИД виджета для дальнейшего ипользования в коде
var widgetGuid = w.general.renderTo;
//Инициализируем создание графика с переданными параметрами
var chart = Highcharts.chart({
chart: {
type: 'treemap', //задаём тип виджета
renderTo: widgetGuid,
events: {
drillup: function() { // Вызывается, когда нажали нативную кнопку "Назад" от Highcharts
/*4)*/ visApi().drillUp(widgetGuid); // Обязательно нужно вызывать эту функцию, когда делается drillUp не через навигационную панель.
}
}
},
xAxis: { // настраиваем ось Х
type: "category"
},
colorAxis: { // настраиваем цветовую шкалу
minColor: '#FFFFFF',
maxColor: Highcharts.getOptions().colors[0]
},
plotOptions: {
series: {
point: {
events: { // Вызываем функцию Дриллдауна при одинарном клике на сектор диаграммы, передавая значение, в которое нужно сделать Дриллдаун
click: function () {
/*1)*/ visApi().drillDown(widgetGuid, [this.name]);
}
}
}
}
},
series: buildSeries(w.data), // запрашиваем данные для серий в фунции buildSeries()
tooltip: { //настраиваем всплывающаю подсказку
enabled: true,
useHTML: true,
formatter: function() {// создаём функцию, которая возвращает строку в всплывающей подсказке
let str = '';
if (visApi().getDrillLevel(w.general.renderTo)>0) { // Проверяем значение текущего уровня дриллдана и если оно больше 0 (то есть не стартовый уровень)
str = visApi().getDrillPath(w.general.renderTo).map(item => item[0]).join(' ,')+'<br>'; // то добавляем в строку туллтипа путь по которому мы уже сделали дриллдаун
}
return str +`<h3>${this.key}</h3><br>
<b> Доля прибыли: ${this.point.colorValue}</b><br>
<b>${this.series.name}:</b> ${this.point.value}<br>
<b>${this.series.userOptions.custom.secondName}:</b> ${this.point.custom.secondValue}`;
}
}
});
/**
* Собирает серии для гистограммы
* Функция buildSeries() формирует данные для вставки в виджет
* массив входящих данны преобразует в структуру, которая воспринимает библиотекой highcharts
* для каждого виджета могут быть особенности в структуре данных серии, уточнять надо в документации библиотеки Highcharts
*/
function buildSeries(data) {
return data.cols.filter((item,index) => index <1).map(function (col, colIndex) {
var points = data.rows.map(function (row, rowIndex) {
return {
value: data.values[colIndex][rowIndex], //задаём основное значение показателя в точке
custom:{secondValue: data.values[colIndex+1][rowIndex]}, //задаём второстепенное значение
colorValue: Math.round(data.values[colIndex+1][rowIndex]/data.values[colIndex][rowIndex]*1000)/10, //задаём значение цвета, в данном примере как отношение двух показателей
name: row[0],
};
});
return {
name: col.join(' - '),
custom:{secondName: data.cols[1].join(' - ')},
layoutAlgorithm: 'squarified',
data: points
};
});
}
/**
* Получает точку на гистограмме по названию, для добавления к сериям дриллдана
*/
function getPointByName(pointName) {
var selectedPoint = null;
chart.series.forEach(function (serie) {
selectedPoint = serie.data.find(function (point) {
return point.name == point.name;
});
});
return selectedPoint;
}
/**
* Подписывается на DrillDown, чтобы отловить новые данные после Drill Down и отрисовать их
*/
visApi().onDrillDownListener({widgetGuid: widgetGuid, guid: "1111"}, function(event) {
/*2)*/ var selectedPointName = event.selectedValues[0];
drillDownOnChart(selectedPointName, event.widgetDataContainer.dataFrame);
});
/**
* Делает drilldown на гистограмме
*/
/*3)*/function drillDownOnChart(pointName, dataFrame) {
var point = getPointByName(pointName);
var newSeries = buildSeries(dataFrame); // преобразуем новые данные в формат Highcharts
newSeries.forEach(function(serie) {
chart.addSingleSeriesAsDrilldown(point, serie); // добавляем новые серии для дриллдана на график
});
point.doDrilldown(); // https://github.com/highcharts/highcharts/issues/8324#issuecomment-388766611
chart.applyDrilldown(); // применяем дриллдаун на графике, то есть визуально проваливаемся в точку
chart.drillUpButton.element.style.display = "block"; // можно отобразить кнопку нативную кнопку "Назад" вместо visApi().showControlPanel(widgetGuid);
}
// Добавляем подписку на дриллдаун для изменения заголовка виджета, в заголовок передаётся значение пути дриллдана
visApi().onDrillDownListener({widgetGuid: w.general.renderTo, guid: w.general.renderTo+"Header"}, function (info) {
$('#widget-header-' + w.general.renderTo + ' a').text(info.hierarchyPath.map(item => item[0]).join(' => '))
});
// Добавляем подписку на дриллап для изменения заголовка виджета, в заголовок передаётся значение пути дриллдана, если выходим на 1 уровень, то вместо пути отображается название
visApi().onDrillUpListener({widgetGuid: w.general.renderTo, guid: w.general.renderTo+"Header"}, function (info) {
$('#widget-header-' + w.general.renderTo + ' a').text(info.hierarchyPath.length===0?"Treemap" : info.hierarchyPath.map(item => item[0]).join(' => '))
});
Код виджета “Текущее измерение”
В коде виджета описан способ получения данных с другого виджета, как работает визуализация текущего уровня и подписка на события Drill Down/Drill Up
//Сохраняем ИД виджета на котором требуется отслеживать уровень дриллдауна
let idWidget = 'e34485e3a07a448f8b1d2296bee1abba';
// visApi().onWidgetLoadedListener({widgetGuid:idWidget, guid:idWidget+"_loaded"}, function () { //ожидание загрузки виджета treemap, доступно с версии 2.24
//Получаем структуру данного виджета, и выбираем из всего настройки OLAP запроса. Сохраняем в переменную
let levelsWidget = visApi().getWidgetByGuid(idWidget).
widgetState.dataSettings.olapSettings.olapGroups[0].attributeGroupsInRows.map(item => item.attributes[0].id);
//формируем строку текста для виджета, в которой будет html таблица, в строках размещены измерения из OLAP запроса
w.general.text = `<table id = "table-${w.general.renderTo}">` + levelsWidget.map(item => `<tr><td>${item}</td></tr>`).join('') + '</table>'
//отрисовываем виджет
TextRender({
text: w.general,
style: w.style
});
//сохраняем в переменную DOM-элемент таблицы содержащийся в текстовом виджете
let table = document.getElementById('table-'+w.general.renderTo);
//создаём функцию для установки цвета фона для ячеек таблицы
function setBackgroundColor() {
//убираем цвета фона для всех ячеек таблицы
for (let i=0; i<table.rows.length; i++) {
table.rows[i].style.backgroundColor = '';
}
//задаём цвет фона для ячейки с индексом полученным в visApi().getDrillLevel() (которая возвращает текущий уровень дриллдауна)
table.rows[visApi().getDrillLevel(idWidget)].style.backgroundColor = 'green'
}
//подписываемся на событие дриллдауна на требуемом виджете и при событии вызываем ранее созданную функцию установки фона
visApi().onDrillDownListener({widgetGuid: idWidget, guid: "setBackgroundColorDrilldown"}, function(event) {
setBackgroundColor()
});
//подписываемся на событие дриллапа на требуемом виджете и при событии вызываем ранее созданную функцию установки фона
visApi().onDrillUpListener({widgetGuid: idWidget, guid: "setBackgroundColorUpdown"}, function(event) {
setBackgroundColor()
});
//Выполняем функцию установки фона при инициализации виджета, для отображения стартового уровня.
setBackgroundColor();
// }); //ожидание загрузки виджета treemap, доступно с версии 2.24
Живой пример
В живом примере при наведении на сектор виджета Treemap, появляется вплывающая подсказка, отображающая наименование сегмента и показатели для него. Кликнув на сегмент, будет выполнен Drill Down внутрь выбранной области, данные перестроятся по следующему измерению с учётом значения элемента, на котором был выполнен Drill Down. После этого изменится текст заголовка виджета и текст всплывающей подсказки. На виджете “Текущее измерение” выделение перейдёт на выбранный уровень Drill Down. Находясь в Drill Down виджет отображает кнопку “Назад”, кликнув по которой, можно перейти на один уровень вверх по иерархии измерений на виджете.
Виджет “Текущее измерение” не взаимодействует с пользователем, только отображает текущее измерение (не его элемент) виджета Treemap. Клик по нему не выполняет никаких действий.
Примечания.
В данном примере для выполнения Drill Up используются элементы управления (кнопка “Назад”) из библиотеки Highcharts взамен стандартных элементов управления из платформы Visiology (круглая иконка со стрелочкой вверх). Это необходимо для правильного выполнения функции Drill Up при таком формате данных. Для использования стандартных элементов управления требуется другой подход к обработке данных при Drill Down и Drill Up.
Здесь реализован только один из множества кейсов с Drill Down. Идеи и предложения по добавлению других примеров можно направлять на support@visiology.su. На данный момент в планах есть “выбор уровня Drill-Down в виджете фильтр с обратной синхронизацией”, а также синхронный Drill-Down на нескольких виджетах.
На полноценную реализацию синхронного Drill-Down на нескольких виджетах также создан реквест #45989. Возможно данная фича будет добавлена в стоковый функционал платформы, в таком случае необходимость кастомного примера отпадет.