Создаем граф при помощи jQuery + jsPlumb

Пример графа Задача: найти способ визуализации объектов (элементов HTML-верстки) в виде графа.

Перерыв на просторах интернета достаточно большое количество готовых инструментов для рисования векторной графики при помощи Javascript, я нашел оптимальное, для меня решение. Это использование jQuery вместе с плагином jsPlumb. Этот плагин использует возможности тега canvas для рисования графики. Все современные браузеры его уже поддерживают (хотя и не в полном объеме), за исключением, как вы уже догадались, Internet Exlorer (включая 8-ю версию). Благо команда Google своевременно позаботилась о портировании большинства возможностей canvas через поддержку VML (которую только и имеет IE). В версии гугла нету поддержки градиентов. Для придания красоты и лучшего визуального восприятия, я воспользовался библиотекой Сергея Чикуенка rocon для скругления уголков HTML-блоков.

Плагин jsPlumb имеет всего лишь 2 вида конечных точек (Endpoint) при соединении блоков. Это квадрат и круг. Но для для графа важно отражать направление связей между объектами, для чего идеально бы подходили стрелки (треугольники). Поэтому я немного модифицировал плагин и добавил новый метод для рисования треугольника (модифицированный плагин jquery.jsPlumb-1.0.4.mod.js), желающие его могут найти и изменить под свои нужды (например сделать просто стрелки вместо залитого треугольника). При запуске плагина jsPlumb, он получает идентификатор (id) HTML-элемента и используя встроенные или переданные настройки, создает 3 элемента canvas. Два из них это элементы Endpoint, а третий — соединительная линия. Для создания нашего графа мы будем скрывать начальную точку чтобы она не мешала восприятию направления в связях объектов. Чтобы создать древовидную структуру таких объектов, я сделал специальную плавающую верстку, которая имеет специальные родительские контейнеры, которые могут содержать как еще одну "ветку" графов, так и непосредственно сами конечные элементы графов. Далее, вся эта конструкции парсится при помощи jQuery, плагином jsPlumb создаются связи между объектами. Но что делать, если один объект может иметь сколько угодно "входов" и "выходов"? Для этого каждый конечный элемент графа имеет дополнительный HTML-аттрибут "also-connection-with" который содержит список дополнительных элементов, с которыми надо построить связь. Эти элементы также парсятся и по аналогии строятся связи к ними.

Вот что получилось в итоге:

<html>
	<head>
		<title>jsPlumb demo</title>
		<script type="text/javascript" src="http://explorercanvas.googlecode.com/svn/trunk/excanvas.js"></script>
		<script type="text/javascript" src="http://rocon.googlecode.com/svn/trunk/js/rounded-corners-min.js"></script>
		<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
		<script type="text/javascript" src="jquery.jsPlumb-1.0.4.mod.js"></script>
        <script type="text/javascript">
			$(function()
			{
				// выставляем параметры по умолчанию
				jsPlumb.setDraggableByDefault( false );
				jsPlumb.DEFAULT_PAINT_STYLE = { strokeStyle: 'gray', gradient:{ stops:[[0,'#225588'], [1,'#558822']] }, lineWidth: 1 };
				jsPlumb.DEFAULT_ANCHORS = [ jsPlumb.Anchors.RightMiddle, jsPlumb.Anchors.LeftMiddle ];
				jsPlumb.DEFAULT_ENDPOINT_STYLES = [{ fillStyle:'#225588' }, { fillStyle:'#558822' }]; 
				jsPlumb.DEFAULT_ENDPOINT = new jsPlumb.Endpoints.Triangle({width:15, height:15});
				jsPlumb.DEFAULT_CONNECTOR = new jsPlumb.Connectors.Bezier();

				var jsPlumbAnchorsRef = {
					top		: jsPlumb.Anchors.TopCenter,
					right	: jsPlumb.Anchors.RightMiddle,
					bottom	: jsPlumb.Anchors.BottomCenter,
					left	: jsPlumb.Anchors.LeftMiddle
				}
				
				// парсим иерархию объектов в верстке и строим связи
				$('#plumbs div.window + div.tasks-box').each(function()
				{
					var level = $(this).prev();
					$(this).find('> div.window').each(function(){ level.plumb({ target: this.id }) });
				});
				
				// парсим дополнительные связи между объектами
				$('#plumbs div.window').each(function()
				{
					var self = $(this),
						connections = self.attr('also-connection-with');
					
					if( connections != undefined )
					{
						$(connections.split(/\s*;\s*/)).each(function()
						{
							var connectWith = this.split(/\s*:\s*/);
							var id = connectWith[0];
							var anchor = connectWith[1].split('-');
							
							self.plumb({ target: id, anchors: [jsPlumbAnchorsRef[anchor[0]], jsPlumbAnchorsRef[anchor[1]]] });
						});
					}
				});
				
				// скрываем стартовые точки в каждой связе
				$('canvas._jsPlumb_endpoint').filter(':even').hide();
				
		   });
		</script>

		<style type="text/css">	
			._jsPlumb_connector { z-index:100; }
			.rocon { z-index:10; }
		
			.tasks-box { float:left; }
			.tasks-box h4 { margin:0; padding-bottom:.5em; }
			.tasks-box .window { 
				position:relative; 
				border:1px solid #225588;
				background:white;
				padding:1em; 
				margin:1.5em 3.5em;
				float:left; 
				clear:left;
				z-index:10;
			}
			.tasks-box a { color:#039; text-decoration:none; border-bottom:2px solid #039; }
			.tasks-box a:hover { border-bottom:2px dotted gray; color:gray; }
		</style>
        
	</head>
	<body>
    
    	<!-- sample tasks tree -->
        <div id="plumbs" class="tasks-box">
            <div class="window rc15" id="window1" also-connection-with="window6:bottom-top">
                <h4>Task name 1</h4>
                <em>Description</em>
            </div>
            <div class="window rc15" id="window2">
                <h4>Task name 2</h4>
                <em>Description</em>
            </div>
            <div class="tasks-box">
                    <div class="window rc15" id="window4">
                        <h4>Task name 4</h4>
                        <em>Description</em>
                    </div>
                    <div class="window rc15" id="window5">
                        <h4>Task name 5</h4>
                        <em>Description dsfa dsfdsaf das fdsaf dsf dasf sdfdsf</em>
                    </div>
                    <div class="window rc15" id="window6">
                        <h4>Task name 6</h4>
                        <em>Description</em>
                    </div>
                    <div class="tasks-box">
                            <div class="window rc15" id="window8" also-connection-with="window4:right-right; window5:right-right; window6:bottom-bottom">
                                <h4>Task name 8</h4>
                                <em>Description dsf adsf adsfdasf df</em>
                            </div>
                    </div>
            </div>
            <div class="window rc15" id="window3">Text block 3</div>
            <div class="window rc15" id="window7"><a href="http://morrisonpitt.com/jsPlumb/doc/usage.html" title="Documentation">jsPlumb docs</a></div>
        </div>
        <br clear="all" />
        <!-- /sample tasks tree -->
    
	</body>
</html>
Смотреть пример | Скачать в архиве
16.06.2010 / javascript, jquery, canvas
Понравилась статья?
Подпишись на рассылку через RSS или следуй за нами в Twitter!
Похожие статьи:
Оценка статьи: проголосовало - 1, средняя оценка - 4
Комментарии к статье (2):
[ 12.08.2010 - 17:52 ] - Timmy
Интересно получилось.
Я использовал dracula (http://dracula.ameisenbar.de/) , но в итоге оказалось, что граф нельзя изменять после загрузки страницы. А может, я просто плохо знаю javascript :)
Ну на сайте (дракулы) есть кнопка "Redraw", значит можно менять :) Плохо что там не показаны направления связей элементов графа..
[ 17.08.2010 - 11:34 ] - Timmy
Направления связей указывать можно (я это использовал), а вот насчет смены графа... мне показалось, что там можно менять именно расположение (layout), но не сам граф. Опять же, я с javascript знаком весьма поверхностно :)
Добавить комментарий
АНТИСПАМ: Выберите улыбающийся смайл: yep! nope! nope!
Сделать заказ
Файл>>