123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443 |
- // jQuery UI style "widget" for displaying a graph
- ////////////////////////////////////////////////////////////////////////////////
- //
- // graph
- //
- $(function()
- {
- // the widget definition, where "itop" is the namespace,
- // "dashboard" the widget name
- $.widget( "itop.simple_graph",
- {
- // default options
- options:
- {
- xmin: 0,
- xmax: 0,
- ymin: 0,
- ymax: 0,
- align: 'center',
- 'vertical-align': 'middle',
- export_as_pdf_url: '',
- export_as_document_url: '',
- drill_down_url: '',
- },
-
- // the constructor
- _create: function()
- {
- var me = this;
- this.aNodes = [];
- this.aEdges = [];
- this.fZoom = 1.0;
- this.xOffset = 0;
- this.yOffset = 0;
- this.iTextHeight = 12;
-
- this.auto_scale();
- this.oPaper = Raphael(this.element.get(0), this.element.width(), this.element.height());
- this.element
- .addClass('itop-simple-graph')
- .addClass('graph');
-
- this._create_toolkit_menu();
- this._build_context_menus();
- },
-
- // called when created, and later when changing options
- _refresh: function()
- {
- this.draw();
- },
- // events bound via _bind are removed automatically
- // revert other modifications here
- _destroy: function()
- {
- var sId = this.element.attr('id');
- this.element
- .removeClass('itop-simple-graph')
- .removeClass('graph');
-
- $('#tk_graph'+sId).remove();
- $('#graph_'+sId+'_export_as_pdf').remove();
-
- },
- // _setOptions is called with a hash of all options that are changing
- _setOptions: function()
- {
- this._superApply(arguments);
- },
- // _setOption is called for each individual option that is changing
- _setOption: function( key, value )
- {
- this._superApply(arguments);
- },
- draw: function()
- {
- this.oPaper.clear();
- for(var k in this.aNodes)
- {
- this._draw_node(this.aNodes[k]);
- }
- for(var k in this.aEdges)
- {
- this._draw_edge(this.aEdges[k]);
- }
- },
- _draw_node: function(oNode)
- {
- var iWidth = oNode.width;
- var iHeight = 32;
- var xPos = Math.round(oNode.x * this.fZoom + this.xOffset);
- var yPos = Math.round(oNode.y * this.fZoom + this.yOffset);
- oNode.tx = 0;
- oNode.ty = 0;
- switch(oNode.shape)
- {
- case 'disc':
- oNode.aElements.push(this.oPaper.circle(xPos, yPos, iWidth*this.fZoom / 2).attr(oNode.disc_attr));
- var oText = this.oPaper.text(xPos, yPos, oNode.label);
- oText.attr(oNode.text_attr);
- oText.transform('s'+this.fZoom);
- oNode.aElements.push(oText);
- break;
-
- case 'group':
- oNode.aElements.push(this.oPaper.circle(xPos, yPos, iWidth*this.fZoom / 2).attr({fill: '#fff', 'stroke-width':0}));
- oNode.aElements.push(this.oPaper.circle(xPos, yPos, iWidth*this.fZoom / 2).attr(oNode.disc_attr));
- var xIcon = xPos - 18 * this.fZoom;
- var yIcon = yPos - 18 * this.fZoom;
- oNode.aElements.push(this.oPaper.image(oNode.icon_url, xIcon, yIcon, 16*this.fZoom, 16*this.fZoom).attr(oNode.icon_attr));
- oNode.aElements.push(this.oPaper.image(oNode.icon_url, xIcon + 18*this.fZoom, yIcon, 16*this.fZoom, 16*this.fZoom).attr(oNode.icon_attr));
- oNode.aElements.push(this.oPaper.image(oNode.icon_url, xIcon + 9*this.fZoom, yIcon + 18*this.fZoom, 16*this.fZoom, 16*this.fZoom).attr(oNode.icon_attr));
- var oText = this.oPaper.text(xPos, yPos +2, oNode.label);
- oText.attr(oNode.text_attr);
- oText.transform('s'+this.fZoom);
- var oBB = oText.getBBox();
- var dy = iHeight/2*this.fZoom + oBB.height/2;
- oText.remove();
- oText = this.oPaper.text(xPos, yPos +dy +2, oNode.label);
- oText.attr(oNode.text_attr);
- oText.transform('s'+this.fZoom);
- oNode.aElements.push(oText);
- oNode.aElements.push(this.oPaper.rect( xPos - oBB.width/2 -2, yPos - oBB.height/2 + dy, oBB.width +4, oBB.height).attr({fill: '#fff', stroke: '#fff', opacity: 0.9}));
- oText.toFront();
- break;
-
- case 'icon':
- if(Raphael.svg)
- {
- // the colorShift plugin works only in SVG
- oNode.aElements.push(this.oPaper.image(oNode.icon_url, xPos - iWidth * this.fZoom/2, yPos - iHeight * this.fZoom/2, iWidth*this.fZoom, iHeight*this.fZoom).colorShift('#fff', 1));
- }
- oNode.aElements.push(this.oPaper.image(oNode.icon_url, xPos - iWidth * this.fZoom/2, yPos - iHeight * this.fZoom/2, iWidth*this.fZoom, iHeight*this.fZoom).attr(oNode.icon_attr));
- var oText = this.oPaper.text( xPos, yPos, oNode.label);
- oText.attr(oNode.text_attr);
- oText.transform('S'+this.fZoom);
- var oBB = oText.getBBox();
- var dy = iHeight/2*this.fZoom + oBB.height/2;
- oText.remove();
- oText = this.oPaper.text( xPos, yPos + dy, oNode.label);
- oText.attr(oNode.text_attr);
- oText.transform('S'+this.fZoom);
- oNode.aElements.push(oText);
- oNode.aElements.push(this.oPaper.rect( xPos - oBB.width/2 -2, yPos - oBB.height/2 + dy, oBB.width +4, oBB.height).attr({fill: '#fff', stroke: '#fff', opacity: 0.9}).toBack());
- break;
- }
- if (oNode.source)
- {
- oNode.aElements.push(this.oPaper.circle(xPos, yPos, 1.25*iWidth*this.fZoom / 2).attr({stroke: '#c33', 'stroke-width': 3*this.fZoom }).toBack());
- }
- if (oNode.sink)
- {
- oNode.aElements.push(this.oPaper.circle(xPos, yPos, 1.25*iWidth*this.fZoom / 2).attr({stroke: '#33c', 'stroke-width': 3*this.fZoom }).toBack());
- }
-
- var me = this;
- for(k in oNode.aElements)
- {
- var sNodeId = oNode.id;
- $(oNode.aElements[k].node).attr({'data-type': oNode.shape, 'data-id': oNode.id} ).attr('class', 'popupMenuTarget');
- oNode.aElements[k].drag(function(dx, dy, x, y, event) { me._move(sNodeId, dx, dy, x, y, event); }, function(x, y, event) { me._drag_start(sNodeId, x, y, event); }, function (event) { me._drag_end(sNodeId, event); });
- }
- },
- _move: function(sNodeId, dx, dy, x, y, event)
- {
- var origDx = dx / this.fZoom;
- var origDy = dy / this.fZoom;
-
- var oNode = this._find_node(sNodeId);
- oNode.x = oNode.xOrig + origDx;
- oNode.y = oNode.yOrig + origDy;
-
- for(k in oNode.aElements)
- {
- oNode.aElements[k].transform('t'+(oNode.tx + dx)+', '+(oNode.ty + dy));
-
- for(j in this.aEdges)
- {
- var oEdge = this.aEdges[j];
- if ((oEdge.source_node_id == sNodeId) || (oEdge.sink_node_id == sNodeId))
- {
- var sPath = this._get_edge_path(oEdge);
- oEdge.aElements[0].attr({path: sPath});
- }
- }
- }
- },
- _drag_start: function(sNodeId, x, y, event)
- {
- var oNode = this._find_node(sNodeId);
- oNode.xOrig = oNode.x;
- oNode.yOrig = oNode.y;
-
- },
- _drag_end: function(sNodeId, event)
- {
- var oNode = this._find_node(sNodeId);
- oNode.tx += (oNode.x - oNode.xOrig) * this.fZoom;
- oNode.ty += (oNode.y - oNode.yOrig) * this.fZoom;
- oNode.xOrig = oNode.x;
- oNode.yOrig = oNode.y;
- },
- _get_edge_path: function(oEdge)
- {
- var oStart = this._find_node(oEdge.source_node_id);
- var oEnd = this._find_node(oEdge.sink_node_id);
- var iArrowSize = 5;
-
- if ((oStart == null) || (oEnd == null)) return '';
-
- var xStart = Math.round(oStart.x * this.fZoom + this.xOffset);
- var yStart = Math.round(oStart.y * this.fZoom + this.yOffset);
- var xEnd = Math.round(oEnd.x * this.fZoom + this.xOffset);
- var yEnd = Math.round(oEnd.y * this.fZoom + this.yOffset);
- var sPath = Raphael.format('M{0},{1}L{2},{3}', xStart, yStart, xEnd, yEnd);
- var vx = (xEnd - xStart);
- var vy = (yEnd - yStart);
- var l = Math.sqrt(vx*vx+vy*vy);
- vx = vx / l;
- vy = vy / l;
- var ux = -vy;
- var uy = vx;
- var lPos = Math.max(l/2, l - 40*this.fZoom);
- var xArrow = xStart + vx * lPos;
- var yArrow = yStart + vy * lPos;
- sPath += Raphael.format('M{0},{1}l{2},{3}M{4},{5}l{6},{7}', xArrow, yArrow, this.fZoom * iArrowSize *(-vx + ux), this.fZoom * iArrowSize *(-vy + uy), xArrow, yArrow, this.fZoom * iArrowSize *(-vx - ux), this.fZoom * iArrowSize *(-vy - uy));
- return sPath;
- },
- _draw_edge: function(oEdge)
- {
- var fStrokeSize = Math.max(1, 2 * this.fZoom);
- var sPath = this._get_edge_path(oEdge);
- var oAttr = $.extend(oEdge.attr);
- oAttr['stroke-linecap'] = 'round';
- oAttr['stroke-width'] = fStrokeSize;
- oEdge.aElements.push(this.oPaper.path(sPath).attr(oAttr).toBack());
- },
- _find_node: function(sId)
- {
- for(var k in this.aNodes)
- {
- if (this.aNodes[k].id == sId) return this.aNodes[k];
- }
- return null;
- },
- auto_scale: function()
- {
- var fMaxZoom = 1.5;
- var maxHeight = this.element.parent().height();
- // Compute the available height
- var element = this.element;
- this.element.parent().children().each(function() {
- if($(this).is(':visible') && !$(this).hasClass('graph') && ($(this).attr('id') != element.attr('id')))
- {
- maxHeight = maxHeight - $(this).height();
- }
- });
-
- this.element.height(maxHeight - 20);
-
- iMargin = 10;
- xmin = this.options.xmin - iMargin;
- xmax = this.options.xmax + iMargin;
- ymin = this.options.ymin - iMargin;
- ymax = this.options.ymax + iMargin;
- var xScale = this.element.width() / (xmax - xmin);
- var yScale = this.element.height() / (ymax - ymin + this.iTextHeight);
-
- this.fZoom = Math.min(xScale, yScale, fMaxZoom);
- switch(this.options.align)
- {
- case 'left':
- this.xOffset = -xmin * this.fZoom;
- break;
-
- case 'right':
- this.xOffset = (this.element.width() - (xmax - xmin) * this.fZoom);
- break;
-
- case 'center':
- this.xOffset = (this.element.width() - (xmax - xmin) * this.fZoom) / 2;
- break;
- }
- switch(this.options['vertical-align'])
- {
- case 'top':
- this.yOffset = -ymin * this.fZoom;
- break;
-
- case 'bottom':
- this.yOffset = this.element.height() - (ymax + this.iTextHeight) * this.fZoom;
- break;
-
- case 'middle':
- this.yOffset = (this.element.height() - (ymax - ymin + this.iTextHeight) * this.fZoom) / 2;
- break;
- }
-
-
- },
- add_node: function(oNode)
- {
- oNode.aElements = [];
- this.aNodes.push(oNode);
- },
- add_edge: function(oEdge)
- {
- oEdge.aElements = [];
- this.aEdges.push(oEdge);
- },
- show_group: function(sGroupId)
- {
- // Activate the 3rd tab
- this.element.closest('.ui-tabs').tabs("option", "active", 2);
- // Scroll into view the group
- if ($('#'+sGroupId).length > 0)
- {
- $('#'+sGroupId)[0].scrollIntoView();
- }
- },
- _create_toolkit_menu: function()
- {
- var sPopupMenuId = 'tk_graph'+this.element.attr('id');
- var sHtml = '<div class="itop_popup toolkit_menu graph" style="font-size: 12px;" id="'+sPopupMenuId+'"><ul><li><img src="../images/toolkit_menu.png"><ul>';
- if (this.options.export_as_pdf_url != '')
- {
- sHtml += '<li><a href="#" id="'+sPopupMenuId+'_pdf">Export as PDF...</a></li>';
- }
- if (this.options.export_as_document_url != '')
- {
- sHtml += '<li><a href="#" id="'+sPopupMenuId+'_document">Export as document...</a></li>';
- }
- sHtml += '<li><a href="#" id="'+sPopupMenuId+'_reload">Refresh</a></li>';
- sHtml += '</ul></li></ul></div>';
-
- this.element.before(sHtml);
- $('#'+sPopupMenuId).popupmenu();
-
-
- var me = this;
- $('#'+sPopupMenuId+'_pdf').click(function() { me.export_as_pdf(); });
- $('#'+sPopupMenuId+'_document').click(function() { me.export_as_document(); });
- $('#'+sPopupMenuId+'_reload').click(function() { me.reload(); });
-
- },
- _build_context_menus: function()
- {
- var sId = this.element.attr('id');
- var me = this;
-
- $.contextMenu({
- selector: '#'+sId+' .popupMenuTarget',
- build: function(trigger, e) {
- // this callback is executed every time the menu is to be shown
- // its results are destroyed every time the menu is hidden
- // e is the original contextmenu event, containing e.pageX and e.pageY (amongst other data)
- var sType = trigger.attr('data-type');
- var sNodeId = trigger.attr('data-id');
- var oNode = me._find_node(sNodeId);
-
- /*
- var sObjName = trigger.attr('data-class');
- var sIndex = trigger.attr('data-index');
- var originalEvent = e;
- var bHasItems = false;
- */
- var oResult = {callback: null, items: {}};
- switch(sType)
- {
- case 'group':
- var sGroupIndex = oNode.group_index;
- oResult = {
- callback: function(key, options) {
- var me = $('.itop-simple-graph').data('itopSimple_graph'); // need a live value
- me.show_group('relation_group_'+sGroupIndex);
- },
- items: { 'show': {name: 'Show group' } }
- };
- break;
-
- case 'icon':
- var sObjClass = oNode.obj_class;
- var sObjKey = oNode.obj_key;
- oResult = {
- callback: function(key, options) {
- var me = $('.itop-simple-graph').data('itopSimple_graph'); // need a live value
- var sURL = me.options.drill_down_url.replace('%1$s', sObjClass).replace('%2$s', sObjKey);
- window.location.href = sURL;
- },
- items: { 'details': {name: 'Show Details' } }
- };
- break;
-
- default:
- oResult = false; // No context menu
-
- }
- return oResult;
- }
- });
-
- },
- export_as_pdf: function()
- {
- var oPositions = {};
- for(k in this.aNodes)
- {
- oPositions[this.aNodes[k].id] = {x: this.aNodes[k].x, y: this.aNodes[k].y };
- }
- var sHtmlForm = '<div id="PDFExportDlg'+this.element.attr('id')+'"><form id="graph_'+this.element.attr('id')+'_export_as_pdf" target="_blank" action="'+this.options.export_as_pdf_url+'" method="post">';
- sHtmlForm += '<input type="hidden" name="positions" value="">';
- sHtmlForm += '<table>';
- sHtmlForm += '<tr><td>Page format:</td><td><select name="p"><option value="A3">A3</option><option value="A4" selected>A4</option><option value="Letter">Letter</option></select></td></tr>';
- sHtmlForm += '<tr><td>Page orientation:</td><td><select name="o"><option value="L" selected>Landscape</option><option value="P">Portrait</select></td></tr>';
- sHtmlForm += '<table>';
- sHtmlForm += '</form></div>';
-
- $('body').append(sHtmlForm);
- $('#graph_'+this.element.attr('id')+'_export_as_pdf input[name="positions"]').val(JSON.stringify(oPositions));
- var me = this;
- $('#PDFExportDlg'+this.element.attr('id')).dialog({
- modal: true,
- title: 'PDF format options',
- buttons: [
- {text: 'Cancel', click: function() { $(this).dialog('close');} },
- {text: 'Export', click: function() { $('#graph_'+me.element.attr('id')+'_export_as_pdf').submit(); $(this).dialog('close');} },
- ]
- });
- //$('#graph_'+this.element.attr('id')+'_export_as_pdf').submit();
- },
- export_as_document: function()
- {
- alert('Export as document: not yet implemented');
- },
- reload: function()
- {
- alert('Reload: not yet implemented');
- }
- });
- });
|