Качественная визуализация играет одну из наиболее важных ролей в аналитике данных. Графики, диаграммы, инфографика и другие визуальные средства позволяют быстро и легко замечать и интерпретировать связи и взаимоотношения, а также выявлять проблемные ситуации и определять потенциальные возможности для роста и развития бизнеса, которые не привлекли бы внимания в виде необработанных данных.
Однако часто бывает, что штатных средств визуализации в системе недостаточно для закрытия некоторых потребностей бизнеса. В таких случаях прибегают к созданию своих, так называемых, кастомных виджетов.
С версии 1.16 в Analytic Workspace есть возможность самостоятельно создавать собственный виджет с использованием HTML, CSS и JS, благодаря встроенной интеграции с Echarts, где доступно 400 видов визуализации.
Расскажем, как визуализировать связи между источниками/моделями/виджетами и дашбордами в виде одного понятного графа, то есть построим граф связей аналитических объектов, который в дальнейшем также можно использовать при разработке новых объектов, а также понимать влияние изменений существующих.
Однако часто бывает, что штатных средств визуализации в системе недостаточно для закрытия некоторых потребностей бизнеса. В таких случаях прибегают к созданию своих, так называемых, кастомных виджетов.
С версии 1.16 в Analytic Workspace есть возможность самостоятельно создавать собственный виджет с использованием HTML, CSS и JS, благодаря встроенной интеграции с Echarts, где доступно 400 видов визуализации.
Расскажем, как визуализировать связи между источниками/моделями/виджетами и дашбордами в виде одного понятного графа, то есть построим граф связей аналитических объектов, который в дальнейшем также можно использовать при разработке новых объектов, а также понимать влияние изменений существующих.
Создание модели данных
Шаг 1. Для начала необходимо подключиться к своей же внутренней БД PostgreSQL, где хранятся метаданные, которые и будут использоваться для создания модели. Тонкости подключения к БД опустим, сейчас не об этом, перейдем сразу к делу.
Итоговая модель должна выглядеть так, как на картинке выше.
Также для удобства прикрепляем картинку с таблицами и их связями. Отметим, везде используется left-join.
Шаг 2. После построения итоговой модели необходимо найти, переименовать и сделать справочными следующие 4 поля:
Шаг 3. Далее загружаем данные и переходим к созданию виджета.
Создание виджета
Шаг 1. Определяем структуру данных виджета, используя те самые 4 поля, и располагаем их в строках. В результате получаем что-то вроде «плоского дерева».
Шаг 2. Выбираем вид виджета HTML и заполняем следующие вкладки.
Шаг 3. В HTML прописываем подключение библиотеки и задание div-а с виджетом.
Пример HTML:
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.0/dist/echarts.min.js"></script>
<div id="force-graph"></div>
В дальнейшем нам еще понадобится: id="force-graph"
Именно по этому идентификатору привязываем CSS стили, в рассматриваемом нами случае они достаточно простые.
Именно по этому идентификатору привязываем CSS стили, в рассматриваемом нами случае они достаточно простые.
Пример CSS:
#force-graph
{
width: 100%;
height: 100%;
}
Шаг 4. Вся магия происходит во вкладке JS.
- Формируем узлы графа;
- Формируем связи между узлами;
- Задаем разные опции:
- Категории узлов (группировка) для расцветки;
- Размеры узлов;
- Расстояние между узлами и т. д.
Разработка и отладка
// Инициализация виджета
var forceGraph = echarts.init(document.getElementById('force-graph'));
// при обновлении данных перерисовываем виджет
function render() {
const categories = [
{
"name": "Панель"
},
{
"name": "Виджет"
},
{
"name": "Модель"
},
{
"name": "Источник"
}
]
var catIndex = categories.map(function (a) {
return a.name;
})
/*
* получаем названия колонок
[0] name - Название дашборда
[1] widget_name - Название виджета
[2] model_name - Название модели
[3] data_source_name - Название источника
*/
var columns = window.WIDGET.columns
.map(column => column.field);
var data = [];
var links = []
window.DATA.data.forEach(item => {
// Нам нужно получить список всех узлов графа, для этого
// бежим по всем строкам в табличном представлении
// раскидываем данные строки в объект data с проверкой на уникальность
// прописываем префикс, определяющий к какой категории относится объект
// это также даст уникальность объектам
// Панели
if (data.indexOf("Панель|" + item[columns[0]]?.value) === -1) {
data.push("Панель|" + item[columns[0]]?.value)
}
// Виджеты
if (data.indexOf("Виджет|" + item[columns[1]]?.value) === -1) {
data.push("Виджет|" + item[columns[1]]?.value)
}
// Модели
if (data.indexOf("Модель|" + item[columns[2]]?.value) === -1) {
data.push("Модель|" + item[columns[2]]?.value)
}
// Источники
if (data.indexOf("Источник|" + item[columns[3]]?.value) === -1) {
data.push("Источник|" + item[columns[3]]?.value)
}
// Нам нужно получить связи между узлами графа
// Попутно обозначив из какого и в какой узел эта связь
// Так как есть модели и источники с одним наименованием, это приводит к ошибке
// Связь панели [0] и виджета [1]
links.push({
source: "Панель|" + item[columns[0]]?.value || '',
target: "Виджет|" + item[columns[1]]?.value || '',
})
// Связь виджета [1] и модели [2]
links.push({
source: "Виджет|" + item[columns[1]]?.value || '',
target: "Модель|" + item[columns[2]]?.value || '',
})
// Связь модели [2] и источника [3]
// Так как есть модели и источники с одним наименованием, это приводит к ошибке
links.push({
source: "Модель|" + item[columns[2]]?.value || '',
target: "Источник|" + item[columns[3]]?.value || '',
})
})
var nodesVolume = {}
// проставляем "значение" это количество ссылок на объект
// от этого будет зависить величина (диаметр) узла
// за каждую ссылку на этот узел добавляем значение
links.forEach(link => {
if (isNaN(nodesVolume[link.source])) {
nodesVolume[link.source] = 20 // начинаем с 20
}
else {
nodesVolume[link.source] += 1
}
})
debugger
// Формируем узлы для графа
var nodes = []
data.forEach(item => {
var category = item.split('|')[0] // отделяем категорию
var itemName = item.split('|')[1] // отделяем наименование
var categoryIndex = catIndex.indexOf(category) // порядковый номер категории требуется в настройках виджета
var ssize = nodesVolume[item] / 2 // размер текста
if (category == "Источник") {
ssize = 10
}
nodes.push({
name: itemName,
value: nodesVolume[item] - 20,
category: categoryIndex,
id: item,
label: { show: categoryIndex == 2 }, // сразу показываем текст узлов только для моделей, для остальных при наведении
symbolSize: ssize
})
})
// определяем настройки для графа
// что значит каждая настройка можно почитать в документации https://echarts.apache.org/en/option.html#series-graph
var option = {
tooltip: {},
legend: [
{
data: catIndex
}
],
series: [
{
type: 'graph',
layout: 'force',
animation: false,
roam: true,
symbol: 'roundRect',
stateAnimation: {
duration: 0,
easing: 'cubicIn'
},
label: {
position: 'right',
formatter: '{b} ({c})',
show: false
},
draggable: true,
data: nodes,
categories: categories,
force: {
edgeLength: 50,
repulsion: 50,
gravity: 0.1,
initLayout: null,
friction: 0.1,
layoutAnimation: true
},
edges: links
}
]
}
// для удобства логируем все, что передали
console.log(option)
// Подставляем новые данные и обновляем виджет
forceGraph.setOption(option)
}
Поскольку разработка клиентская, для отладки кода необходимо открыть DevTools-ы браузера, в коде в нужном месте прописать debugger.
Отладка для JS-кода привычная.
Отладка для JS-кода привычная.
Лог в консоли выглядит следующим образом. (см. картинку ниже)
Для запуска тестового скрипта жмем «Выполнить», а когда все готово «Опубликовать».
Таким образом, в результате проделанной работы мы визуализировали связи между источниками/моделями/виджетами и дашбордами в виде одного понятного графа.