Легко и просто: графики и диаграммы на веб-страницах. Часть 3
Согласно своему определению, диаграмма (от греч. diagramma — изображение, рисунок, чертеж) - это графическое изображение, наглядно показывающее соотношение каких-либо величин. Прошлые две статьи серии были посвящены классическим видам диаграмм, т.е. линейным и столбчатым. Сегодня же я начну знакомство с парой javascript библиотек, служащих не для показа соотношения каких-либо величин, а для отображения их структуры, связей и взаимодействия.
Из приведенного выше описания первое, что приходит на ум, – это изображения всевозможных “сетей”, деревьев и графов. Несложно придумать возможные сферы их применения, начиная от изображения генеалогических деревьев, производственных циклов или отношений в коллективе. Ох, как мне сразу вспомнились милые студенческие годы, когда мы на занятиях по психологии рисовали всевозможные схемы разбиения коллектива на подгруппы и взаимоотношения в виде стрелочек, как между этими подгруппами, так и внутри них. В общем, придумать еще несколько возможных сфер применения оставлю вам, а сам же сосредоточусь на техническом описании того, какие javascript-библиотеки умеют рисовать “сети”, графы и деревья, а также то, как ими пользоваться. В плане рассказ о двух достаточно популярных библиотеках: JIT или JavaScript Information Visualization Toolkit, а также MooWheel.
MooWheel (домашний сайт проекта >сайт foundation: "1.4.1990"}, children : [
{id: 'managers', name: 'Managers', children: [
{id: "jim", name: "Jim Tapkin", children: []},
{id: "pet", name: "Pet Maslov", children: []},
]},
{id: 'security', name: 'Security', children: [
{id: "ivan", name: "Ivan Dolvich", children: []},
{id: "igor", name: "Igor Dolvich", children: []},
]}
]};
Как видите, данные для HyperTree имеют древовидную структуру: на верхнем уровне (корне дерева) находится элемент “organization”, в который входят два отдела: “managers” и “security”. Отделы, в свою очередь, заполнены конкретными сотрудниками. Каждый элемент дерева обязан иметь как минимум идентификатор “id” и название “name”. Также если элемент не является конечным (не является “листом”), то у него может быть произвольное количество дочерних элементов; и все они находятся внутри массива “children”. В примере корневой элемент “organization” также содержит свойство “data” с какими-то непонятными характеристиками “url” и “foundation” (дата основания). Дело в том, что JIT построен по принципам MVC (Model View Controller), т.е. отделяет информацию, привязанную к узлу “data”, от ее внешнего вида и поведения. Фактически мы можем, используя информацию, хранящуюся в “data”, реализовать свой уникальный внешний вид диаграммы: настроить то, как выглядит и ведет себя каждый узел по отдельности. Следующий этап – это вызов конструктора HyperTree, перед которым нужно подготовить объект Canvas, т.е. “холст”, на котором мы вскоре будем рисовать.
var canvas = new Canvas('htree', { injectInto: 'placeholder', width: 640, height: 480});
Здесь все очевидно: свойство “injectTo” указывает, внутрь какого html-элемента будет помещен “холст” для рисования и его линейные размеры.
window.ht = new Hypertree(canvas, {
Node: { type: "circle", dim: 9, color: "#ff0000" },
Edge: { lineWidth: 2, color: "#00ff00" },
duration: 1500,
transition: Trans.Quart.easeInOut,
onCreateLabel: funOnCreateLabel ,
onPlaceLabel: funOnPlaceLabel
});
// теперь выполняем загрузку данных и отрисовку дерева
ht.loadJSON(data);
ht.refresh();
Первый параметр конструктора класса HyperTree сложностей не вызывает, а вот второй, отвечающий за внешний вид рисунка, содержит много нового и неочевидного. Прежде всего, мы настраиваем внешний вид узлов дерева “Node”. В примере я решил, что узлы дерева будут отображены в форме кружка “circle” (также есть варианты “none”, “square”, “rectangle”, “circle”, “triangle”, “star”). Параметр “dim” задает размер узла дерева, а “color”, очевидно, его цвет. Для настройки внешнего вида ребер, соединяющих узлы дерева, используем свойство “Edge”. Так, я решил сделать линии толщиной в 2 пикселя и зеленого цвета. Параметры “duration” и “transition” управляют анимацией HyperTree. Дело в том, что способности человека к восприятию больших объемов информации ограничены, и желательно, чтобы одновременно в поле внимания попали только те узлы дерева, которые сгруппированы вокруг центрального (вначале это корень дерева). А остальные узлы дерева будут спрятаны где-то “вдали”. Но если пользователь выполняет клик по какому-то узлу дерева, расположенному “на периферии”, то этот узел становится центральным. Т.е. он перемещается в центр рисунка, и рядом с ним будут расположены и видны только те узлы, с которыми он непосредственно соединен. Итак, параметр “transition” управляет тем, какая анимация будет проиграна при смене движении узлов с периферии в центр, а то, сколько это займет времени, задается опцией “duration”. Параметры “onCreateLabel” и “funOnPlaceLabel” ссылаются на функции, которые должен определить пользователь и внутри которых нужно детально указать, как должны выглядеть узлы дерева. К примеру, моя функция funOnCreateLabel, определяет узел как обычный фрагмент текста, помещенный внутрь тега “i” (а ведь можно было назначить каждому узлу и индивидуальную картинку). Что касается привязанного к узлу дерева обработчика события “onclick”, то он вызывает тот самый описанный выше метод центрирования изображения дерева на элементе, по которому был выполнен клик.
function funOnCreateLabel(domElement, node) {
domElement.innerHTML = '<<>>' + node.name + '<<>>';
domElement.onclick = function () { window.ht.onClick(node.id); };
}
Очевидно, что в ходе перемещения узлов необходимо изменять не только их координаты (забота JIT), но и параметры внешнего вида: те же размеры шрифта и цвет. Так, функция funOnPlaceLabel в зависимости от того, на какой “глубине” находится узел, задает различным его размер шрифта. Если узел является центральным, т.е. по нему был выполнен клик, то его глубина равна нулю, а надпись для узла будет выведена жирным шрифтом размером в 16px. Узлы с “глубиной” 1 – это те узлы, с которыми центральный узел соединен напрямую, и для них размер шрифта будет поменьше – 14px. Узлы же с глубиной 2 и более нужно вообще спрятать:
function funOnPlaceLabel(domElement, node) {
var style = domElement.style;
style.display = '';
style.cursor = 'pointer';
if (node._depth == 0) {
style.fontSize = "16px";
style.fontWeight = 'bold';
style.color = "#ff0000";
}
else if (node._depth == 1) {
style.fontSize = "14px";
style.fontWeight = 'bold';
style.color = "#aa0000";
} else {
style.display = 'none';
}
var left = parseInt(style.left);
var w = domElement.offsetWidth;
style.left = (left - w / 2) + 'px';
}
То, что у меня получилось, показано на рис. 3.Однако статическая картинка не дает в полной мере возможность оценить результат и “поиграть” с построенным деревом, так что я рекомендую обратить ваше внимание на примеры HyperTree, идущие в поставке с JIT.
black-zorro@tut.by black-zorro.com
Из приведенного выше описания первое, что приходит на ум, – это изображения всевозможных “сетей”, деревьев и графов. Несложно придумать возможные сферы их применения, начиная от изображения генеалогических деревьев, производственных циклов или отношений в коллективе. Ох, как мне сразу вспомнились милые студенческие годы, когда мы на занятиях по психологии рисовали всевозможные схемы разбиения коллектива на подгруппы и взаимоотношения в виде стрелочек, как между этими подгруппами, так и внутри них. В общем, придумать еще несколько возможных сфер применения оставлю вам, а сам же сосредоточусь на техническом описании того, какие javascript-библиотеки умеют рисовать “сети”, графы и деревья, а также то, как ими пользоваться. В плане рассказ о двух достаточно популярных библиотеках: JIT или JavaScript Information Visualization Toolkit, а также MooWheel.
MooWheel (домашний сайт проекта >сайт foundation: "1.4.1990"}, children : [
{id: 'managers', name: 'Managers', children: [
{id: "jim", name: "Jim Tapkin", children: []},
{id: "pet", name: "Pet Maslov", children: []},
]},
{id: 'security', name: 'Security', children: [
{id: "ivan", name: "Ivan Dolvich", children: []},
{id: "igor", name: "Igor Dolvich", children: []},
]}
]};
Как видите, данные для HyperTree имеют древовидную структуру: на верхнем уровне (корне дерева) находится элемент “organization”, в который входят два отдела: “managers” и “security”. Отделы, в свою очередь, заполнены конкретными сотрудниками. Каждый элемент дерева обязан иметь как минимум идентификатор “id” и название “name”. Также если элемент не является конечным (не является “листом”), то у него может быть произвольное количество дочерних элементов; и все они находятся внутри массива “children”. В примере корневой элемент “organization” также содержит свойство “data” с какими-то непонятными характеристиками “url” и “foundation” (дата основания). Дело в том, что JIT построен по принципам MVC (Model View Controller), т.е. отделяет информацию, привязанную к узлу “data”, от ее внешнего вида и поведения. Фактически мы можем, используя информацию, хранящуюся в “data”, реализовать свой уникальный внешний вид диаграммы: настроить то, как выглядит и ведет себя каждый узел по отдельности. Следующий этап – это вызов конструктора HyperTree, перед которым нужно подготовить объект Canvas, т.е. “холст”, на котором мы вскоре будем рисовать.
var canvas = new Canvas('htree', { injectInto: 'placeholder', width: 640, height: 480});
Здесь все очевидно: свойство “injectTo” указывает, внутрь какого html-элемента будет помещен “холст” для рисования и его линейные размеры.
window.ht = new Hypertree(canvas, {
Node: { type: "circle", dim: 9, color: "#ff0000" },
Edge: { lineWidth: 2, color: "#00ff00" },
duration: 1500,
transition: Trans.Quart.easeInOut,
onCreateLabel: funOnCreateLabel ,
onPlaceLabel: funOnPlaceLabel
});
// теперь выполняем загрузку данных и отрисовку дерева
ht.loadJSON(data);
ht.refresh();
Первый параметр конструктора класса HyperTree сложностей не вызывает, а вот второй, отвечающий за внешний вид рисунка, содержит много нового и неочевидного. Прежде всего, мы настраиваем внешний вид узлов дерева “Node”. В примере я решил, что узлы дерева будут отображены в форме кружка “circle” (также есть варианты “none”, “square”, “rectangle”, “circle”, “triangle”, “star”). Параметр “dim” задает размер узла дерева, а “color”, очевидно, его цвет. Для настройки внешнего вида ребер, соединяющих узлы дерева, используем свойство “Edge”. Так, я решил сделать линии толщиной в 2 пикселя и зеленого цвета. Параметры “duration” и “transition” управляют анимацией HyperTree. Дело в том, что способности человека к восприятию больших объемов информации ограничены, и желательно, чтобы одновременно в поле внимания попали только те узлы дерева, которые сгруппированы вокруг центрального (вначале это корень дерева). А остальные узлы дерева будут спрятаны где-то “вдали”. Но если пользователь выполняет клик по какому-то узлу дерева, расположенному “на периферии”, то этот узел становится центральным. Т.е. он перемещается в центр рисунка, и рядом с ним будут расположены и видны только те узлы, с которыми он непосредственно соединен. Итак, параметр “transition” управляет тем, какая анимация будет проиграна при смене движении узлов с периферии в центр, а то, сколько это займет времени, задается опцией “duration”. Параметры “onCreateLabel” и “funOnPlaceLabel” ссылаются на функции, которые должен определить пользователь и внутри которых нужно детально указать, как должны выглядеть узлы дерева. К примеру, моя функция funOnCreateLabel, определяет узел как обычный фрагмент текста, помещенный внутрь тега “i” (а ведь можно было назначить каждому узлу и индивидуальную картинку). Что касается привязанного к узлу дерева обработчика события “onclick”, то он вызывает тот самый описанный выше метод центрирования изображения дерева на элементе, по которому был выполнен клик.
function funOnCreateLabel(domElement, node) {
domElement.innerHTML = '<<>>' + node.name + '<<>>';
domElement.onclick = function () { window.ht.onClick(node.id); };
}
Очевидно, что в ходе перемещения узлов необходимо изменять не только их координаты (забота JIT), но и параметры внешнего вида: те же размеры шрифта и цвет. Так, функция funOnPlaceLabel в зависимости от того, на какой “глубине” находится узел, задает различным его размер шрифта. Если узел является центральным, т.е. по нему был выполнен клик, то его глубина равна нулю, а надпись для узла будет выведена жирным шрифтом размером в 16px. Узлы с “глубиной” 1 – это те узлы, с которыми центральный узел соединен напрямую, и для них размер шрифта будет поменьше – 14px. Узлы же с глубиной 2 и более нужно вообще спрятать:
function funOnPlaceLabel(domElement, node) {
var style = domElement.style;
style.display = '';
style.cursor = 'pointer';
if (node._depth == 0) {
style.fontSize = "16px";
style.fontWeight = 'bold';
style.color = "#ff0000";
}
else if (node._depth == 1) {
style.fontSize = "14px";
style.fontWeight = 'bold';
style.color = "#aa0000";
} else {
style.display = 'none';
}
var left = parseInt(style.left);
var w = domElement.offsetWidth;
style.left = (left - w / 2) + 'px';
}
То, что у меня получилось, показано на рис. 3.Однако статическая картинка не дает в полной мере возможность оценить результат и “поиграть” с построенным деревом, так что я рекомендую обратить ваше внимание на примеры HyperTree, идущие в поставке с JIT.
black-zorro@tut.by black-zorro.com
Компьютерная газета. Статья была опубликована в номере 30 за 2009 год в рубрике программирование