Время прочтения: 7 мин.
Графы так или иначе связаны с анализом данных. Они либо визуализируют результат этого анализа, либо являются средством для его проведения.
Само же словосочетание «анализ данных» практически всегда равно python. Но эта статья будет посвящена веб-приложению графовой аналитики, написанному на C# + JavaScript.
Для простоты восприятия структурированной информации можно использовать различные инструменты: таблицы, схемы и тд. Но нагляднее отобразить её в виде связанного, а иногда и направленного графа.
В процессе работы мы столкнулись с проблемой отрисовки графов по различным входным данным, в требованиях было желание универсальности разрабатываемого решения.
Великий Интернет, конечно же знает всё и очень настойчиво советовал использовать python с библиотеками NetworkX, Plolty и подобные. Однако у нас было ограничение в серверном ПО. При чём тут сервер? Просто наше универсальное средство должно было быть доступно каждому сотруднику. Так вот, сервер… Он имеет возможность публикации решений на C# или на python2 с большим ограничением по доступным библиотекам. Выбор был очевиден: C# – простота и адекватность решения.
Сервер, доступность решения, web-версия, frontend… Ощущаете это? О, да… Именно JavaScript идеально вписался в наше «представление о прекрасном». На нём есть отличная библиотека Cytoscape.js для изображения различных вариаций на тему графов.
Перейдём к любимой части повествования: код и с чем его едят.
Для реализации простого интерактивного графа, состоящего из 2 вершин и ребра между ними, нам потребуется 3 файла: index.html – файлик запуска красоты; code.js – рабочая лошадка и style.css – стиль и расположения.
Index.html
<!DOCTYPE html>
<html>
<head>
<link href="style.css" rel="stylesheet" />
<meta name="viewport" charset=utf-8 content="user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, minimal-ui">
<title>First Example</title>
<script src="cytoscape.min.js"></script>
</head>
<body>
<div id="cy"></div>
<script src="code.js"></script>
</body>
</html>
Index.html довольно шаблонный. Основные моменты, на которые стоит обратить внимание, это указание ссылки на файл стилей (<link href="style.css" rel="stylesheet" />
), подключение файла со скриптом (<script src="code.js"></script>
) и подключение библиотеки (<script src="cytoscape.min.js"></script>
).
style.css
body {
font: 14px helvetica neue, helvetica, arial, sans-serif;
}
#cy {
height: 100%;
width: 100%;
position: absolute;
left: 0;
top: 0;
}
В файле со стилями задаются параметры шрифтов, размера и координат поля для размещения построенного графа. В Index-файле можно расположить много дополнительных элементов, стили которых лучше прописать в style.css.
code.js
var cy = window.cy = cytoscape({
container: document.getElementById('cy'),
boxSelectionEnabled: false,
layout: {
name: 'preset',
padding: 5,
},
style: [
{
selector: 'node',
css: {
'content': 'data(id)',
'text-valign': 'center',
'text-halign': 'center'
}
},
{
selector: 'edge',
css: {
'curve-style': 'bezier',
'target-arrow-shape': 'triangle'
}
}
],
elements: {
nodes: [
{ data: { id: 'a' }, position: { x: 10, y: 15 } },
{ data: { id: 'b' }, position: { x: 100, y: 65 } }
],
edges: [
{ data: { id: 'ab', source: 'a', target: 'b' } }
]
},
});
Самым интересным, на мой взгляд, является исполняемый код в js файле. Именно в нём можно задать различные формы стрелок для рёбер ('target-arrow-shape': 'triangle'
), параметры вершин, например, цвет и размер ("height": n_height, "width": n_ width, "color": n_color
).
В зависимости от указанного значения в поле Layout name может быть задана различная форма графа. Например, мы создавали две окружности, одна из которых вложена в другую.
В результате открытия файла Index.html в браузере будет отображено следующее:
К чему был упомянут C#, если всё работает на 3 файлах? Он обеспечивает возможность публикации приложения на сервере. Если создать новый проект по архитектурному паттерну MVC (Model-View-Controller) и перенести код из Index.html в представление, а остальные файлы добавить в скрипты, то изменив пути их подключения, получим самостоятельное приложение с возможностью размещения на сервере.
Как можно заметить, прописывать каждый узел и ребро между ними явно затруднительно и ни о какой универсальности речи быть не может. Тогда добавим в code.js чтение данных из txt файлов и их визуализацию на странице.
code.js
document.getElementById('file-input').addEventListener('change', function (evt) {
elements = [];
let reader = new FileReader();
reader.readAsText(evt.target.files[0], "utf-8");
reader.onerror = function (event) { alert("Фйл не может быть прочитан!"); }
reader.onload = function (event) {
createElement(event.target.result);
window.cy = createGraph();
}
});
function createElement(arr) {
let rows = arr.split("\n");
for (i = 0; i < rows.length; i++) {
if (rows[i] == "") break;
let columns = rows[i].split(";");
let lineName = "line" + i;
elements.push({
"data": {
"id": columns[0],
"degr": "6",
"height": "20",
"width": "20",
"color": "#FFFF00",
"shape": "round",
},
"group": "nodes"
});
elements.push({
"data": {
"id": columns[1],
"degr": "2",
"height": "40",
"width": "40",
"color": "#00CC00",
"shape": "rectangle",
},
"group": "nodes"
});
elements.push({
"data": {
"id": lineName,
"source": (columns[2] >= 0) ? columns[0] : columns[1],
"target": (columns[2] >= 0) ? columns[1] : columns[0],
"width": "5",
"color": "#708090",
"style": "Solid",
"arrow": "triangle",
"opacity": "0.65"
},
"group": "edges"
});
}
}
function createGraph() {
var cy = window.cy = cytoscape({
container: document.getElementById('cy'),
layout: {
name: 'concentric',
concentric: function (node) { return node.data('degr'); },
levelWidth: function () { return 2; }
},
style: [{
selector: 'node',
style: {
'content': 'data(id)',
'background-color': 'data(color)',
'width': 'data(width)',
'height': 'data(height)',
'shape': 'data(shape)'
}
}, {
selector: 'edge',
style: {
'curve-style': 'straight',
'width': 'data(width)',
'opacity': 'data(opacity)',
'target-arrow-shape': 'data(arrow)',
'line-color': 'data(color)',
'target-arrow-color': 'data(color)'
}
}],
elements: elements,
wheelSensitivity: 0.5,
});
return cy;
}
И, соответственно на страницу Index.html или представления необходимо добавить кнопку для выбора файла.
Index.html
<!DOCTYPE html>
<html>
<head>
<link href="style.css" rel="stylesheet" />
<meta name="viewport" charset=utf-8 content="user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, minimal-ui">
<title>First Example</title>
<script src="cytoscape.min.js"></script>
</head>
<body>
<h1>Инструмент для работы с графами</h1><br>
<div class="line">
<button id="graphml" onclick="document.getElementById('file-input').click();"><b>Выбрать файл</b></button>
<input id="file-input" type="file" name="name" style="display: none;" />
</div>
<div id="cy"></div>
<script src="code.js"></script>
</body>
</html>
Добавим дополнительные стили и посмотрим на приведённый ниже рисунок, который является результатом наших модификаций.
style.css
body {
font: 14px helvetica neue, helvetica, arial, sans-serif;
}
#cy {
height: 100%;
width: 100%;
position: absolute;
left: 0;
top: 0;
}
#graphml {
background-color: #2bc528;
color: white;
border: none;
opacity: 0.8;
padding: 7px 10px;
}
.line{
position:absolute;
margin-top: 10px;
margin-bottom: 10px;
z-index: 900;
}
Входной файл для примера:
Pers1; Comp1; 10
Pers1; Comp2; 15
Pers1; Comp3; 5
Pers1; Comp4; 5
Pers2; Comp1; 3
Pers2; Comp5; 5
Pers2; Comp2; 80
Pers2; Comp4; 1
Pers3; Comp5; 25
Pers3; Comp1; 45
Pers3; Comp6; 17
Pers3; Comp3; 17
Pers4; Comp6; 18
Pers4; Comp1; 71
Pers4; Comp5; 71
Pers4; Comp2; 83
Если во входном файле поставить отрицательное значение в третьем столбце, то направление стрелки изменится на противоположное.
Полученный инструмент уже можно использовать как приложение для визуализации графов, готовое к публикации на сервере. Однако, изменяя различные параметры можно получить довольно интересный и полезный инструмент для анализа больших данных.
В контексте поставленной перед нами задачей, приведённый код был модернизирован и результат его работы представлен на следующих рисунках.
На рисунке 3 размер и цвет вершин зависит от суммы взаимодействия вершин друг с другом, а на рисунке 4 с помощью рассматриваемой библиотеки получилось построить граф с областями. Главное условие для него – разные имена вершин во всех квадратах, поэтому были использованы дополнительные индексы от 1 до 3.
Замечательную библиотеку можно скачать с официального сайта http://cytoscape.org, а подробные и интерактивные примеры можно посмотреть по ссылке: http://js.cytoscape.org.
А как вы относитесь к анализу данных не на python?