diff --git a/README.md b/README.md index 5df40de95..e7b8ad825 100644 --- a/README.md +++ b/README.md @@ -45,8 +45,8 @@ Thanks to JetBrains for an [Open Source License](https://www.jetbrains.com/buy/o ## Team - * [Ilkin Safarli](https://github.com/kinimesi), [Hasan Balci](https://github.com/hasanbalci), [Leonard Dervishi](https://github.com/leonarddrv), [Istemi Bahceci](https://github.com/istemi-bahceci), and [Ugur Dogrusoz](https://github.com/ugurdogrusoz) of [i-Vis at Bilkent University](http://www.cs.bilkent.edu.tr/~ivis), [Metin Can Siper](https://github.com/metincansiper), [Ozgun Babur](https://github.com/ozgunbabur), and [Emek Demir](https://github.com/emekdemir) of the Demir Lab at [OHSU](http://www.ohsu.edu/), and [Ludovic Roy](https://github.com/royludo) and [Alexander Mazein](https://github.com/amazein) of [EISBM](http://eisbm.org) + * [Ilkin Safarli](https://github.com/kinimesi), [Hasan Balci](https://github.com/hasanbalci), [Leonard Dervishi](https://github.com/leonarddrv), [Ahmet Candiroglu](https://github.com/ahmetcandiroglu), [Kaan Sancak](https://github.com/kaansancak), and [Ugur Dogrusoz](https://github.com/ugurdogrusoz) of [i-Vis at Bilkent University](http://www.cs.bilkent.edu.tr/~ivis), [Metin Can Siper](https://github.com/metincansiper), [Ozgun Babur](https://github.com/ozgunbabur), and [Emek Demir](https://github.com/emekdemir) of the Demir Lab at [OHSU](http://www.ohsu.edu/), and [Alexander Mazein](https://github.com/amazein) of [EISBM](http://eisbm.org) #### Alumni - * Alper Karacelik, Selim Firat Yilmaz + * [Ludovic Roy](https://github.com/royludo), [Istemi Bahceci](https://github.com/istemi-bahceci), Alper Karacelik, Selim Firat Yilmaz diff --git a/app/css/chise.css b/app/css/chise.css index 6162555e2..b98d2601d 100644 --- a/app/css/chise.css +++ b/app/css/chise.css @@ -5,6 +5,11 @@ body { padding: 0px; } +.drag-and-drop-file{ + border: 2px dashed #0B9BCD !important; + background-color: #EEEEEE; +} + .sbgn-nav-menu .open > .dropdown-menu { animation-name: slidenavAnimation; animation-duration:.3s; @@ -185,8 +190,8 @@ body { } .network-panel { - height: 680px; - width: 800px; + height: 100%; + width: 100%; } #network-tabs-list-container { @@ -434,6 +439,14 @@ input::-webkit-outer-spin-button, input::-webkit-inner-spin-button { margin: 0; } +#template-modal-content { + position: fixed; + width: 600px; + top: 50%; + left: 50%; + margin-left: -300px; +} + .sbgn-input-small { width: 50px !important; } @@ -723,6 +736,8 @@ hr.inspector-divider { border-style: solid !important; border-width: 1px !important; border-radius: calc !important; + font-size: 12px; + align: center; } #sbgn-toolbar img { @@ -737,6 +752,14 @@ img#template-reaction-add-button:hover { cursor: pointer; } +img#template-reversible-input-add-button:hover { + cursor: pointer; +} + +img#template-reversible-output-add-button:hover { + cursor: pointer; +} + .pointer-button:hover { cursor: pointer; } diff --git a/app/img/toolbar/marquee.svg b/app/img/toolbar/marquee.svg new file mode 100644 index 000000000..5f9fd673c --- /dev/null +++ b/app/img/toolbar/marquee.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/js/app-cy.js b/app/js/app-cy.js index 5e37e14c4..5f004d478 100644 --- a/app/js/app-cy.js +++ b/app/js/app-cy.js @@ -36,14 +36,14 @@ module.exports = function (chiseInstance) { ur.action("changeMenu", appUndoActions.changeMenu, appUndoActions.changeMenu); ur.action("refreshColorSchemeMenu", appUndoActions.refreshColorSchemeMenu, appUndoActions.refreshColorSchemeMenu); } - + function cytoscapeExtensionsAndContextMenu() { cy.expandCollapse(getExpandCollapseOptions(cy)); var contextMenus = cy.contextMenus({ menuItemClasses: ['custom-menu-item'], }); - + cy.autopanOnDrag(); cy.edgeBendEditing({ @@ -125,6 +125,14 @@ module.exports = function (chiseInstance) { }, coreAsWell: true // Whether core instance have this item on cxttap }, + { + id: 'ctx-menu-zoom-to-selected', + content: 'Zoom to Selected', + onClickFunction: function() { + $("#zoom-to-selected").trigger('click'); + }, + coreAsWell: true + }, { id: 'ctx-menu-expand', // ID of menu item content: 'Expand', // Title of menu item @@ -199,6 +207,53 @@ module.exports = function (chiseInstance) { $("#highlight-processes-of-selected").trigger('click'); } }, + { + id: 'ctx-menu-animate-edge', + content: 'Navigate to Other End', + selector: 'edge', + onClickFunction: function (event) { + var cyTarget = event.target || event.cyTarget; + appUtilities.navigateToOtherEnd(cyTarget, event.renderedPosition, event.position); + } + }, + { + id: 'ctx-menu-convert-into-reversible', + content: 'Convert into Reversible Reaction', + selector: 'node[class="process"]', + onClickFunction: function (event) { + var cyTarget = event.target || event.cyTarget; + var consumptionEdges = cyTarget._private.edges.filter(edge => edge._private.data.class === "consumption"); + + if (consumptionEdges.length > 0) { + var ur = cy.undoRedo(); + ur.do("convertIntoReversibleReaction", {processId: cyTarget.id(), collection: consumptionEdges, mapType: "Unknown"}); + } + var currentArrowScale = Number($('#arrow-scale').val()); + cyTarget.connectedEdges().style('arrow-scale', currentArrowScale); + } + }, + { + id: 'ctx-menu-relocate-info-boxes', + content: 'Relocate Information Boxes', + selector: 'node[class^="macromolecule"],[class^="complex"],[class^="simple chemical"],[class^="nucleic acid feature"],[class^="compartment"]', + onClickFunction: function (event){ + var cyTarget = event.target || event.cyTarget; + appUtilities.relocateInfoBoxes(cyTarget); + } + }, + { + id: 'ctx-menu-fit-content-into-node', + content: 'Resize Node to Content', + selector: 'node[class^="macromolecule"],[class^="complex"],[class^="simple chemical"],[class^="nucleic acid feature"],' + + '[class^="unspecified entity"], [class^="perturbing agent"],[class^="phenotype"],[class^="tag"],[class^="compartment"],[class^="submap"],[class^="BA"]', + onClickFunction: function (event) { + var cyTarget = event.target || event.cyTarget; + //Collection holds the element and is used to generalize resizeNodeToContent function (which is used from Edit-> Menu) + var collection = cy.collection(); + collection = collection.add(cyTarget); + appUtilities.resizeNodesToContent(collection); + } + } ]); cy.clipboard({ @@ -287,7 +342,7 @@ module.exports = function (chiseInstance) { return parseFloat(ele.data('border-width')) + 2; }, 'border-color': highlightColor - }, + }, unhighlighted: {// styles for when nodes are unhighlighted. 'opacity': function (ele) { // We return the same opacity because to override the unhibhlighted ele opacity in view-utilities @@ -303,7 +358,7 @@ module.exports = function (chiseInstance) { 'line-color': highlightColor, 'source-arrow-color': highlightColor, 'target-arrow-color': highlightColor - }, + }, unhighlighted: {// styles for when edges are unhighlighted. 'opacity': function (ele) { // We return the same opacity because to override the unhibhlighted ele opacity in view-utilities @@ -328,9 +383,9 @@ module.exports = function (chiseInstance) { }); return nodesToSelect; }, - neighborSelectTime: 500 //ms + neighborSelectTime: 500 //ms }); - + cy.nodeResize({ padding: 2, // spacing between node and grapples/rectangle undoable: appUtilities.undoable, // and if cy.undoRedo exists @@ -343,22 +398,22 @@ module.exports = function (chiseInstance) { boundingRectangleLineColor: "darkgray", boundingRectangleLineWidth: 1.5, zIndex: 999, - getCompoundMinWidth: function(node) { - return node.data('minWidth') || 0; + getCompoundMinWidth: function(node) { + return node.data('minWidth') || 0; }, - getCompoundMinHeight: function(node) { - return node.data('minHeight') || 0; + getCompoundMinHeight: function(node) { + return node.data('minHeight') || 0; }, getCompoundMinWidthBiasRight: function(node) { - return node.data('minWidthBiasRight') || 0; + return node.data('minWidthBiasRight') || 0; }, - getCompoundMinWidthBiasLeft: function(node) { - return node.data('minWidthBiasLeft') || 0; + getCompoundMinWidthBiasLeft: function(node) { + return node.data('minWidthBiasLeft') || 0; }, getCompoundMinHeightBiasTop: function(node) { return node.data('minHeightBiasTop') || 0; }, - getCompoundMinHeightBiasBottom: function(node) { + getCompoundMinHeightBiasBottom: function(node) { return node.data('minHeightBiasBottom') || 0; }, setWidth: function(node, width) { @@ -371,23 +426,23 @@ module.exports = function (chiseInstance) { bbox.h = height; node.data('bbox', bbox); }, - setCompoundMinWidth: function(node, minWidth) { - node.data('minWidth', minWidth); + setCompoundMinWidth: function(node, minWidth) { + node.data('minWidth', minWidth); }, - setCompoundMinHeight: function(node, minHeight) { - node.data('minHeight', minHeight); + setCompoundMinHeight: function(node, minHeight) { + node.data('minHeight', minHeight); }, setCompoundMinWidthBiasLeft: function(node, minWidthBiasLeft) { - node.data('minWidthBiasLeft', minWidthBiasLeft); + node.data('minWidthBiasLeft', minWidthBiasLeft); }, setCompoundMinWidthBiasRight: function(node, minHeightBiasRight) { - node.data('minWidthBiasRight', minHeightBiasRight); + node.data('minWidthBiasRight', minHeightBiasRight); }, - setCompoundMinHeightBiasTop: function(node, minHeightBiasTop) { - node.data('minHeightBiasTop', minHeightBiasTop); + setCompoundMinHeightBiasTop: function(node, minHeightBiasTop) { + node.data('minHeightBiasTop', minHeightBiasTop); }, setCompoundMinHeightBiasBottom: function(node, minHeightBiasBottom) { - node.data('minHeightBiasBottom', minHeightBiasBottom); + node.data('minHeightBiasBottom', minHeightBiasBottom); }, minWidth: function (node) { var data = node.data("resizeMinWidth"); @@ -399,6 +454,10 @@ module.exports = function (chiseInstance) { }, // a function returns min height of node isFixedAspectRatioResizeMode: function (node) { + //Initially checks if Aspect ratio in Object properties is checked + if (appUtilities.nodeResizeUseAspectRatio) + return true; + //Otherwise it checks 'processes', 'and', 'or' etc. which have fixedAspectRatio as default var sbgnclass = node.data("class"); return chiseInstance.elementUtilities.mustBeSquare(sbgnclass); }, // with only 4 active grapples (at corners) @@ -421,7 +480,7 @@ module.exports = function (chiseInstance) { w: "w-resize" } }); - + //For adding edges interactively cy.edgehandles({ // fired when edgehandles is done and entities are added @@ -434,7 +493,7 @@ module.exports = function (chiseInstance) { // We need to remove interactively added entities because we should add the edge with the chise api addedEntities.remove(); - + /* * If in add edge mode create an edge */ @@ -459,7 +518,7 @@ module.exports = function (chiseInstance) { var currentArrowScale = Number($('#arrow-scale').val()); addedEdge.style('arrow-scale', currentArrowScale); } - + // If not in sustain mode set selection mode if (!modeProperties.sustainMode) { modeHandler.setSelectionMode(); @@ -523,15 +582,43 @@ module.exports = function (chiseInstance) { }; cy.panzoom(panProps); + + //Overwrite the default background-opacity (transparency) of simple nodes from chise + + var sbgnclasses = ['macromolecule', 'simple chemical', 'unspecified entity', + 'nucleic acid feature', 'perturbing agent', 'source and sink', 'phenotype', + 'process', 'omitted process', 'uncertain process', 'association', + 'dissociation', 'tag', 'and', 'or', 'not', 'delay','BA plain', + 'BA unspecified entity', 'BA simple chemical', 'BA macromolecule', + 'BA nucleic acid feature', 'BA perturbing agent', 'BA complex']; + + for (i=0; i (containerPos.left + containerWidth)){ - left -= (left + nodeLabelTextbox.width()) - (containerPos.left + containerWidth) + 5; - } - + left -= (left + nodeLabelTextbox.width()) - (containerPos.left + containerWidth) + 5; + } + left = left.toString() + 'px'; - + // Adjust top of the textbox var top = containerPos.top + this.renderedPosition().y; top -= nodeLabelTextbox.height() / 2; @@ -928,15 +1030,15 @@ module.exports = function (chiseInstance) { var nodeType = node.data('class'); if (nodeType == "compartment" || nodeType.startsWith("complex") || nodeType == "submap") top += (node.outerHeight() / 2 * cy.zoom() ); - + // If textbox goes beyond the borders of container, "+5" is for better seperation if(top < containerPos.top){ - top = containerPos.top + 5; + top = containerPos.top + 5; } if((top + nodeLabelTextbox.height()) > (containerPos.top + containerHeight)){ - top -= (top + nodeLabelTextbox.height()) - (containerPos.top + containerHeight) + 5; + top -= (top + nodeLabelTextbox.height()) - (containerPos.top + containerHeight) + 5; } - + top = top.toString() + 'px'; nodeLabelTextbox.css('left', left); @@ -956,7 +1058,7 @@ module.exports = function (chiseInstance) { var handleInspectorThrottled = _.throttle(function() { inspectorUtilities.handleSBGNInspector(); }, 200); - + // When we select/unselect many elements in one operation these 'select' / 'unselect' events called may times // and unfortunetaly the inspector is refreshed many times. This seriously decreases the performance. To handle this // problem we call the method used to refresh the inspector in a throttled way and decrease the number of calls. @@ -968,12 +1070,12 @@ module.exports = function (chiseInstance) { $('#inspector-palette-tab a').blur(); $('#inspector-map-tab a').blur(); } - //Remove grapples while node-label-textbox is visible + //Remove grapples while node-label-textbox is visible if($("#node-label-textbox").is(":visible")){ cy.nodeResize('get').removeGrapples(); } }); - + cy.on('unselect', function() { if($("#node-label-textbox").is(":visible")){ cy.nodes().unselect(); @@ -981,7 +1083,7 @@ module.exports = function (chiseInstance) { $("#node-label-textbox").blur(); handleInspectorThrottled(); }); - + /* * Set/unset the first selected node on select/unselect node events to align w.r.t that node when needed */ @@ -989,14 +1091,14 @@ module.exports = function (chiseInstance) { if (!appUtilities.firstSelectedNode) { appUtilities.firstSelectedNode = this; } - - // Unselect nodeToUnselect and then unset it here + + // Unselect nodeToUnselect and then unset it here if (nodeToUnselect && nodeToUnselect.id() === this.id()) { nodeToUnselect.unselect(); nodeToUnselect = undefined; } }); - + cy.on('unselect', 'node', function() { if (appUtilities.firstSelectedNode && appUtilities.firstSelectedNode.id() === this.id()) { appUtilities.firstSelectedNode = undefined; @@ -1021,6 +1123,13 @@ module.exports = function (chiseInstance) { { appUtilities.getChiseInstance(cy).endSpinner('layout-spinner'); } + cy.nodes().forEach(function(ele){ + // skip nodes without any auxiliary units + if(!ele.data('statesandinfos') || ele.data('statesandinfos').length == 0) { + return; + } + chiseInstance.classes.AuxUnitLayout.fitUnits(ele); + }); }); // if the position of compound changes by repositioning its children @@ -1040,11 +1149,30 @@ module.exports = function (chiseInstance) { cy.trigger('noderesize.resizedrag', ['unknown', parent]); } }); + + // update background image style when data changes + cy.on('data', 'node', function(event) { + var node = event.target; + + if(!node || !node.isNode()) + return; + + var keys = ['background-image', 'background-fit', 'background-image-opacity', + 'background-position-x', 'background-position-y', 'background-height', 'background-width']; + + var opt = {}; + keys.forEach(function(key){ + opt[key] = node.data(key); + }); + + node.style(opt); + }); } function updateInfoBox(node) { for(var location in node.data('auxunitlayouts')) { chiseInstance.classes.AuxUnitLayout.update(node.data('auxunitlayouts')[location], cy); } + chiseInstance.classes.AuxUnitLayout.fitUnits(node); //Fit infoBoxes } }; diff --git a/app/js/app-menu.js b/app/js/app-menu.js index 114f4d705..f690c5bc8 100644 --- a/app/js/app-menu.js +++ b/app/js/app-menu.js @@ -11,8 +11,8 @@ var _ = require('underscore'); module.exports = function() { var dynamicResize = appUtilities.dynamicResize.bind(appUtilities); - var layoutPropertiesView, generalPropertiesView, pathsBetweenQueryView, pathsByURIQueryView, promptSaveView, promptConfirmationView, - promptMapTypeView, promptInvalidFileView, promptInvalidURIWarning, reactionTemplateView, gridPropertiesView, fontPropertiesView, fileSaveView; + var layoutPropertiesView, generalPropertiesView, neighborhoodQueryView, pathsBetweenQueryView, pathsFromToQueryView, commonStreamQueryView, pathsByURIQueryView, promptSaveView, promptConfirmationView, + promptMapTypeView, promptInvalidFileView, promptFileConversionErrorView, promptInvalidURIWarning, reactionTemplateView, gridPropertiesView, fontPropertiesView, fileSaveView; function validateSBGNML(xml) { $.ajax({ @@ -32,7 +32,55 @@ module.exports = function() { } }); } - + + function cd2sbgnml(xml) { + + $.ajax({ + type: 'post', + url: "http://web.newteditor.org:8080/cd2sbgnml", + data: xml, + success: function (data) { + var chiseInstance = appUtilities.getActiveChiseInstance(); + var cy = appUtilities.getActiveCy(); + chiseInstance.endSpinner("load-spinner"); + if (cy.elements().length !== 0) { + promptConfirmationView.render(function() { + chiseInstance.loadSBGNMLText(data); + }); + } + else { + chiseInstance.loadSBGNMLText(data); + } + }, + error: function (XMLHttpRequest) { + var chiseInstance = appUtilities.getActiveChiseInstance(); + chiseInstance.endSpinner("load-spinner"); + promptFileConversionErrorView.render(); + if (XMLHttpRequest.status === 0) { + document.getElementById("file-conversion-error-message").innerText = "Conversion service is not available!"; + } + } + }) + } + + function sbgnml2cd(xml) { + + $.ajax({ + type: 'post', + url: "http://web.newteditor.org:8080/sbgnml2cd", + data: xml, + success: function (data) { + fileSaveView.render("celldesigner", null, data); + }, + error: function (XMLHttpRequest) { + promptFileConversionErrorView.render(); + if (XMLHttpRequest.status === 0) { + document.getElementById("file-conversion-error-message").innerText = "Conversion service is not available!"; + } + } + }) + } + function loadSample(filename) { // use the active chise instance @@ -47,7 +95,7 @@ module.exports = function() { $(window).on('resize', _.debounce(dynamicResize, 100)); - // appUtilities.dynamicResize(); + dynamicResize(); layoutPropertiesView = appUtilities.layoutPropertiesView = new BackboneViews.LayoutPropertiesView({el: '#layout-properties-table'}); colorSchemeInspectorView = appUtilities.colorSchemeInspectorView = new BackboneViews.ColorSchemeInspectorView({el: '#color-scheme-template-container'}); @@ -55,19 +103,24 @@ module.exports = function() { mapTabGeneralPanel = appUtilities.mapTabGeneralPanel = new BackboneViews.MapTabGeneralPanel({el: '#map-tab-general-container'}); mapTabLabelPanel = appUtilities.mapTabLabelPanel = new BackboneViews.MapTabLabelPanel({el: '#map-tab-label-container'}); mapTabRearrangementPanel = appUtilities.mapTabRearrangementPanel = new BackboneViews.MapTabRearrangementPanel({el: '#map-tab-rearrangement-container'}); + neighborhoodQueryView = appUtilities.neighborhoodQueryView = new BackboneViews.NeighborhoodQueryView({el: '#query-neighborhood-table'}); pathsBetweenQueryView = appUtilities.pathsBetweenQueryView = new BackboneViews.PathsBetweenQueryView({el: '#query-pathsbetween-table'}); + pathsFromToQueryView = appUtilities.pathsFromToQueryView = new BackboneViews.PathsFromToQueryView({el: '#query-pathsfromto-table'}); + commonStreamQueryView = appUtilities.commonStreamQueryView = new BackboneViews.CommonStreamQueryView({el: '#query-commonstream-table'}); pathsByURIQueryView = appUtilities.pathsByURIQueryView = new BackboneViews.PathsByURIQueryView({el: '#query-pathsbyURI-table'}); //promptSaveView = appUtilities.promptSaveView = new BackboneViews.PromptSaveView({el: '#prompt-save-table'}); // see PromptSaveView in backbone-views.js fileSaveView = appUtilities.fileSaveView = new BackboneViews.FileSaveView({el: '#file-save-table'}); promptConfirmationView = appUtilities.promptConfirmationView = new BackboneViews.PromptConfirmationView({el: '#prompt-confirmation-table'}); promptMapTypeView = appUtilities.promptMapTypeView = new BackboneViews.PromptMapTypeView({el: '#prompt-mapType-table'}); promptInvalidFileView = appUtilities.promptInvalidFileView = new BackboneViews.PromptInvalidFileView({el: '#prompt-invalidFile-table'}); + promptFileConversionErrorView = appUtilities.promptFileConversionErrorView = new BackboneViews.PromptFileConversionErrorView({el: '#prompt-fileConversionError-table'}); reactionTemplateView = appUtilities.reactionTemplateView = new BackboneViews.ReactionTemplateView({el: '#reaction-template-table'}); gridPropertiesView = appUtilities.gridPropertiesView = new BackboneViews.GridPropertiesView({el: '#grid-properties-table'}); fontPropertiesView = appUtilities.fontPropertiesView = new BackboneViews.FontPropertiesView({el: '#font-properties-table'}); promptInvalidURIView = appUtilities.promptInvalidURIView = new BackboneViews.PromptInvalidURIView({el: '#prompt-invalidURI-table'}); promptInvalidURIWarning = appUtilities.promptInvalidURIWarning = new BackboneViews.PromptInvalidURIWarning({el: '#prompt-invalidURI-table'}); promptInvalidURLWarning = appUtilities.promptInvalidURLWarning = new BackboneViews.PromptInvalidURLWarning({el: '#prompt-invalidURL-table'}); + promptInvalidImageWarning = appUtilities.promptInvalidImageWarning = new BackboneViews.PromptInvalidImageWarning({el: '#prompt-invalidImage-table'}); toolbarButtonsAndMenu(); keyboardShortcuts(); @@ -80,10 +133,9 @@ module.exports = function() { // set the current file name for cy appUtilities.setScratch(cy, 'currentFileName', filename); - //clean and reset things cy.elements().unselect(); - + appUtilities.disableInfoBoxRelocation(); // if the event is triggered for the active instance do the followings if ( isActiveInstance ) { @@ -229,7 +281,7 @@ module.exports = function() { $("#file-input").trigger('click'); }); - $("#file-input").change(function () { + $("#file-input").change(function (e, fileObject) { // use the active chise instance var chiseInstance = appUtilities.getActiveChiseInstance(); @@ -237,11 +289,11 @@ module.exports = function() { // use cy instance assocated with chise instance var cy = appUtilities.getActiveCy(); - if ($(this).val() != "") { - var file = this.files[0]; + if ($(this).val() != "" || fileObject) { + var file = this.files[0] || fileObject; var loadCallbackSBGNMLValidity = function (text) { validateSBGNML(text); - } + } var loadCallbackInvalidityWarning = function () { promptInvalidFileView.render(); } @@ -255,9 +307,28 @@ module.exports = function() { } }); + $('#import-celldesigner-file').click(function (){ + $("#celldesigner-file-input").trigger('click'); + }); + + $('#celldesigner-file-input').change(function (e, fileObject) { + + if ($(this).val() != "" || fileObject) { + var file = this.files[0] || fileObject; + appUtilities.setFileContent(file.name); + var reader = new FileReader(); + reader.onload = function(event) { + chiseInstance = appUtilities.getActiveChiseInstance(); + chiseInstance.startSpinner("load-spinner"); + cd2sbgnml(event.target.result); + }; + reader.readAsText(file); + } + }); + $("#import-simple-af-file").click(function () { $("#simple-af-file-input").trigger('click'); - }); + }); $("#simple-af-file-input").change(function () { var chiseInstance = appUtilities.getActiveChiseInstance(); @@ -268,7 +339,7 @@ module.exports = function() { var loadCallbackInvalidityWarning = function () { promptInvalidFileView.render(); } - + if ($(this).val() != "") { var file = this.files[0]; @@ -314,12 +385,31 @@ module.exports = function() { // get and set properties from file var properties = chiseInstance.getMapProperties(); + // init map properties + var mapProperties = ( properties && properties.mapProperties ) ? properties.mapProperties : {}; - // some operations are to be performed if properties.mapProperties exists - var mapPropertiesExist = ( properties && properties.mapProperties ); + var urlParams = appUtilities.getScratch(cy, 'urlParams'); + + if (urlParams) { + // clear urlParams from scratch + appUtilities.setScratch(cy, 'urlParams', undefined); + + // filter map properties from the url parameters + var mapPropsFromUrl = appUtilities.filterMapProperties(urlParams); + + // merge the map properties coming from url into + // the map properties read from file + for ( var prop in mapPropsFromUrl ) { + mapProperties[prop] = mapPropsFromUrl[prop]; + } + } + + // some operations are to be performed if there is any map property + // that comes from URL or read from file + var mapPropertiesExist = ( !$.isEmptyObject( mapProperties ) ); if (mapPropertiesExist) { - appUtilities.setMapProperties(properties.mapProperties); + appUtilities.setMapProperties(mapProperties); } // some operations are to be done if the event is triggered for the active instance @@ -370,9 +460,9 @@ module.exports = function() { e.preventDefault(); $("#about_modal").modal('show'); }); - + $(".title").click(function(e){ - e.stopPropagation(); + e.stopPropagation(); }); var selectorToSampleFileName = { @@ -402,7 +492,7 @@ module.exports = function() { promptConfirmationView.render(function(){loadSample(selectorToSampleFileName[selector])}); } else { - loadSample(selectorToSampleFileName[selector]); + loadSample(selectorToSampleFileName[selector]); } }); })(selector); @@ -434,7 +524,7 @@ module.exports = function() { cy.elements().unselect(); cy.edges().select(); }); - + $("#hide-selected, #hide-selected-icon").click(function(e) { // use active cy instance @@ -488,6 +578,20 @@ module.exports = function() { $('#inspector-palette-tab a').tab('show'); }); + $("#resize-nodes-to-content").click(function (e) { + + // use active chise instance + var chiseInstance = appUtilities.getActiveChiseInstance(); + + // use cy instance associated with chise instance + var cy = chiseInstance.getCy(); + + //Remove processes and other nodes which cannot be resized according to content + var toBeResized = cy.nodes().difference('node[class*="process"],[class*="association"],[class*="dissociation"],[class="source and sink"],[class="and"],[class="or"],[class="not"],[class="delay"]'); + appUtilities.resizeNodesToContent(toBeResized); + + }); + $("#highlight-neighbors-of-selected").click(function (e) { // use active chise instance @@ -517,7 +621,7 @@ module.exports = function() { $("#highlight-search-menu-item").click(function (e) { $("#search-by-label-text-box").focus(); }); - + $("#highlight-selected, #highlight-selected-icon").click(function (e) { // use active chise instance @@ -571,8 +675,20 @@ module.exports = function() { } }); + $("#query-neighborhood").click(function (e) { + neighborhoodQueryView.render(); + }); + $("#query-pathsbetween, #query-pathsbetween-icon").click(function (e) { - pathsBetweenQueryView.render(); + pathsBetweenQueryView.render(); + }); + + $("#query-pathsfromto").click(function (e) { + pathsFromToQueryView.render(); + }); + + $("#query-commonstream").click(function (e) { + commonStreamQueryView.render(); }); $("#query-pathsbyURI").click(function (e) { @@ -724,6 +840,15 @@ module.exports = function() { chiseInstance.expandAll(); }); + $("#zoom-to-selected").click( function(e){ + // use the active chise instance + var cy = appUtilities.getActiveCy(); + + var viewUtilities = cy.viewUtilities('get'); + + viewUtilities.zoomToSelected(cy.$(':selected')); + }); + $("#perform-layout, #perform-layout-icon").click(function (e) { // use active chise instance @@ -802,7 +927,21 @@ module.exports = function() { $("#save-as-sbgnml, #save-icon").click(function (evt) { //var filename = document.getElementById('file-name').innerHTML; //chise.saveAsSbgnml(filename); - fileSaveView.render(); + fileSaveView.render("sbgnml", "0.2"); + }); + + $("#export-as-sbgnml3-file").click(function (evt) { + fileSaveView.render("sbgnml", "0.3"); + }); + + $("#export-as-celldesigner-file").click(function (evt) { + var chiseInstance = appUtilities.getActiveChiseInstance(); + var sbgnml = chiseInstance.createSbgnml(); + sbgnml2cd(sbgnml); + }); + + $("#export-as-sbgnml-plain-file").click(function (evt) { + fileSaveView.render("sbgnml", "plain"); }); $("#add-complex-for-selected").click(function (e) { @@ -850,7 +989,9 @@ module.exports = function() { // use cy instance associated with chise instance var cy = chiseInstance.getCy(); - chiseInstance.cloneElements(cy.nodes(':selected')); + //When the menu option is clicked paste at mouse location is false + var pasteAtMouseLoc = false; + chiseInstance.cloneElements(cy.nodes(':selected'), pasteAtMouseLoc); }); /* @@ -977,6 +1118,10 @@ module.exports = function() { modeHandler.setSelectionMode(); }); + $('#marquee-zoom-mode-icon').click(function(e){ + modeHandler.setMarqueeZoomMode(); + }); + $('#add-node-mode-icon').click(function (e) { modeHandler.setAddNodeMode(); @@ -1051,6 +1196,7 @@ module.exports = function() { var target = $(e.target).attr("href"); // activated tab console.log(target); appUtilities.setActiveNetwork(target); + inspectorUtilities.handleSBGNInspector(); }); } }; diff --git a/app/js/app-mode-handler.js b/app/js/app-mode-handler.js index 15286bffb..4fa8c0ec8 100644 --- a/app/js/app-mode-handler.js +++ b/app/js/app-mode-handler.js @@ -51,6 +51,11 @@ var modeHandler = { modeProperties.sustainMode = false; } + if(modeProperties.mode == "marquee-zoom-mode") { + var viewUtilities = cy.viewUtilities('get'); + viewUtilities.disableMarqueeZoom(); + } + if (modeProperties.mode != "add-node-mode") { cy.elements().unselect(); modeProperties.mode = "add-node-mode"; @@ -58,6 +63,7 @@ var modeHandler = { $('#select-mode-icon').parent().removeClass('selected-mode'); $('#add-edge-mode-icon').parent().removeClass('selected-mode'); $('#add-node-mode-icon').parent().addClass('selected-mode'); + $('#marquee-zoom-mode-icon').parent().removeClass('selected-mode'); $('.node-palette img').removeClass('inactive-palette-element'); $('.edge-palette img').addClass('inactive-palette-element'); @@ -87,7 +93,6 @@ var modeHandler = { // if the edgeType will remain same, add edge mode is already enabled and sustain mode is not set before, then set the sustain mode // so that users will be able to add the current edge type in a sustainable way. setAddEdgeMode: function (edgeType, language, _cy) { - // if _cy param is not set use the active cy instance var cy = _cy || appUtilities.getActiveCy(); @@ -107,6 +112,11 @@ var modeHandler = { modeProperties.sustainMode = false; } + if(modeProperties.mode == "marquee-zoom-mode") { + var viewUtilities = cy.viewUtilities('get'); + viewUtilities.disableMarqueeZoom(); + } + if (modeProperties.mode != "add-edge-mode") { cy.elements().unselect(); modeProperties.mode = "add-edge-mode"; @@ -114,6 +124,7 @@ var modeHandler = { $('#select-mode-icon').parent().removeClass('selected-mode'); $('#add-edge-mode-icon').parent().addClass('selected-mode'); $('#add-node-mode-icon').parent().removeClass('selected-mode'); + $('#marquee-zoom-mode-icon').parent().removeClass('selected-mode'); $('.node-palette img').addClass('inactive-palette-element'); $('.edge-palette img').removeClass('inactive-palette-element'); @@ -147,10 +158,16 @@ var modeHandler = { // access mode properties of the cy var modeProperties = appUtilities.getScratch(cy, 'modeProperties'); + if(modeProperties.mode == "marquee-zoom-mode") { + var viewUtilities = cy.viewUtilities('get') + viewUtilities.disableMarqueeZoom(); + } + if (modeProperties.mode != "selection-mode") { $('#select-mode-icon').parent().addClass('selected-mode'); $('#add-edge-mode-icon').parent().removeClass('selected-mode'); $('#add-node-mode-icon').parent().removeClass('selected-mode'); + $('#marquee-zoom-mode-icon').parent().removeClass('selected-mode'); $('.node-palette img').addClass('inactive-palette-element'); $('.edge-palette img').addClass('inactive-palette-element'); @@ -163,13 +180,51 @@ var modeHandler = { cy.autoungrabify(false); cy.autounselectify(false); } - $('.selected-mode-sustainable').removeClass('selected-mode-sustainable'); modeProperties.sustainMode = false; // reset mode properties of cy appUtilities.setScratch(cy, 'modeProperties', modeProperties); }, + // Set marquee zoom mode, disables sustainable mode. + setMarqueeZoomMode: function(_cy){ + // if _cy param is not set use the active cy instance + var cy = _cy || appUtilities.getActiveCy(); + + // access mode properties of the cy + var modeProperties = appUtilities.getScratch(cy, 'modeProperties'); + + if(modeProperties.mode != "marquee-zoom-mode"){ + $('#select-mode-icon').parent().removeClass('selected-mode'); + $('#add-edge-mode-icon').parent().removeClass('selected-mode'); + $('#add-node-mode-icon').parent().removeClass('selected-mode'); + $('#marquee-zoom-mode-icon').parent().addClass('selected-mode'); + $('.node-palette img').addClass('inactive-palette-element'); + $('.edge-palette img').addClass('inactive-palette-element'); + + modeProperties.mode = "marquee-zoom-mode"; + + var viewUtilities = cy.viewUtilities('get'); + + var setSelectionAfterAnimation = function(){ + modeHandler.setSelectionMode(cy); + viewUtilities.disableMarqueeZoom(); + } + + viewUtilities.enableMarqueeZoom(setSelectionAfterAnimation); + + cy.autoungrabify(false); + + $('.selected-mode-sustainable').removeClass('selected-mode-sustainable'); + modeProperties.sustainMode = false; + } + else{ + modeHandler.setSelectionMode(cy); + } + + // reset mode properties of cy + appUtilities.setScratch(cy, 'modeProperties', modeProperties); + }, autoEnableMenuItems: function (enable) { if (enable) { $("#expand-selected").parent("li").removeClass("disabled"); diff --git a/app/js/app-utilities.js b/app/js/app-utilities.js index 978051f9c..a9aa199ba 100644 --- a/app/js/app-utilities.js +++ b/app/js/app-utilities.js @@ -104,6 +104,7 @@ appUtilities.adjustUIComponents = function (_cy) { $('#add-node-mode-icon').parent().removeClass('selected-mode'); $('#add-edge-mode-icon').parent().removeClass('selected-mode-sustainable'); $('#add-node-mode-icon').parent().removeClass('selected-mode-sustainable'); + $('#marquee-zoom-mode-icon').parent().removeClass('selected-mode'); $('.node-palette img').addClass('inactive-palette-element'); $('.edge-palette img').addClass('inactive-palette-element'); $('.selected-mode-sustainable').removeClass('selected-mode-sustainable'); @@ -146,6 +147,11 @@ appUtilities.adjustUIComponents = function (_cy) { $('.edge-palette .selected-mode').addClass('selected-mode-sustainable'); } + } + else if( mode === 'marquee-zoom-mode'){ + + $('#marquee-zoom-mode-icon').parent().addClass('selected-mode'); + } // adjust status of grid guide related icons in toolbar @@ -361,7 +367,7 @@ appUtilities.createNewNetwork = function () { var currentGeneralProperties = appUtilities.getScratch(newInst.getCy(), 'currentGeneralProperties'); return currentGeneralProperties.dynamicLabelSize; }, - // Whether to infer nesting on load + // Whether to infer nesting on load inferNestingOnLoad: function () { var currentGeneralProperties = appUtilities.getScratch(newInst.getCy(), 'currentGeneralProperties'); return currentGeneralProperties.inferNestingOnLoad; @@ -397,6 +403,15 @@ appUtilities.createNewNetwork = function () { } }); + //set border-width of selected nodes to a fixed value + newInst.getCy().style() + .selector('node:selected') + .css({ + 'border-width': function(ele){ + return Math.max(ele.data("border-width"), 3); + } + }); + // set scracth pad of the related cy instance with these properties appUtilities.setScratch(newInst.getCy(), 'currentLayoutProperties', currentLayoutProperties); appUtilities.setScratch(newInst.getCy(), 'currentGridProperties', currentGridProperties); @@ -427,9 +442,6 @@ appUtilities.createNewNetwork = function () { // physically open the new tab appUtilities.chooseNetworkTab(appUtilities.nextNetworkId); - // resize html components according to the window size - appUtilities.dynamicResize(); - // activate palette tab if (!$('#inspector-palette-tab').hasClass('active')) { $('#inspector-palette-tab a').tab('show'); @@ -497,6 +509,46 @@ appUtilities.removePhysicalNetworkComponents = function (networkKey) { $(tabSelector).remove(); }; +appUtilities.dropHandler = function (ev) { + + // Prevent default behavior (Prevent file from being opened) + ev.preventDefault(); + + if (ev.originalEvent.dataTransfer.items) { + // Use DataTransferItemList interface to access the file(s) + for (var i = 0; i < ev.originalEvent.dataTransfer.items.length; i++) { + // If dropped items aren't files, reject them + if (ev.originalEvent.dataTransfer.items[i].kind === 'file') { + var file = ev.originalEvent.dataTransfer.items[i].getAsFile(); + $("#file-input").trigger("change", [file]); + } + } + } else { + // Use DataTransfer interface to access the file(s) + for (var i = 0; i < ev.originalEvent.dataTransfer.files.length; i++) { + $("#file-input").trigger("change", [file]); + } + } + + // Pass event to removeDragData for cleanup + removeDragData(ev); +}; +appUtilities.dragOverHandler = function (ev) { + // Prevent default behavior (Prevent file from being opened) + ev.preventDefault(); +}; + +function removeDragData(ev) { + + if (ev.originalEvent.dataTransfer.items) { + // Use DataTransferItemList interface to remove the drag data + ev.originalEvent.dataTransfer.items.clear(); + } else { + // Use DataTransfer interface to remove the drag data + ev.originalEvent.dataTransfer.clearData(); + } +} + appUtilities.createPhysicalNetworkComponents = function (panelId, tabId, tabDesc) { // the component that includes the tab panels @@ -507,6 +559,18 @@ appUtilities.createPhysicalNetworkComponents = function (panelId, tabId, tabDesc // create new panel inside the panels parent panelsParent.append(newPanelStr); + $("#" + panelId).on("drop", function(event){ + appUtilities.dropHandler(event); + $("#network-panels-container").removeClass("drag-and-drop-file"); + }); + $("#" + panelId).on("dragover", function(event){ + appUtilities.dragOverHandler(event); + $("#network-panels-container").addClass("drag-and-drop-file"); + }); + $("#" + panelId).on("dragleave", function(event){ + $("#network-panels-container").removeClass("drag-and-drop-file"); + }); + // the container that lists the network tabs var tabsList = $('#network-tabs-list'); @@ -659,7 +723,7 @@ appUtilities.defaultGeneralProperties = { allowCompoundNodeResize: false, mapColorScheme: 'black_white', defaultInfoboxHeight: 12, - defaultInfoboxWidth: 30, + defaultInfoboxWidth: 8, mapType: function() {return appUtilities.getActiveChiseInstance().getMapType() || "Unknown"}, mapName: "", mapDescription: "" @@ -738,10 +802,11 @@ appUtilities.getExpandCollapseOptions = function (_cy) { var cy = _cy || self.getActiveCy(); if ( !self.getScratch(cy, 'currentGeneralProperties').recalculateLayoutOnComplexityManagement ) { + cy.trigger('fit-units-after-expandcollapse'); return; } - self.triggerIncrementalLayout(cy); + cy.trigger('fit-units-after-expandcollapse'); }, expandCollapseCueSize: 12, expandCollapseCuePosition: function (node) { @@ -783,7 +848,7 @@ appUtilities.dynamicResize = function () { //This is the margin on left and right of the main content when the page is //displayed var mainContentMargin = 10; - $("#network-panels-container, .network-panel").width(windowWidth * 0.8 - mainContentMargin); + $("#network-panels-container").width(windowWidth * 0.8 - mainContentMargin); $("#sbgn-inspector").width(windowWidth * 0.2 - mainContentMargin); var w = $("#sbgn-inspector-and-canvas").width(); $(".nav-menu").width(w); @@ -795,9 +860,12 @@ appUtilities.dynamicResize = function () { if (windowHeight > canvasHeight) { - $("#network-panels-container, .network-panel").height(windowHeight * 0.85); + $("#network-panels-container").height(windowHeight * 0.85); $("#sbgn-inspector").height(windowHeight * 0.85); } + + // trigger an event to notify that newt components are dynamically resized + $(document).trigger('newtAfterDynamicResize'); }; /* appUtilities.nodeQtipFunction = function (node) { @@ -1711,8 +1779,9 @@ appUtilities.getAllStyles = function (_cy) { var collapsedChildrenEdges = collapsedChildren.filter("edge"); var edges = cy.edges().union(collapsedChildrenEdges); - // first get all used colors, then deal with them and keep reference to them + // first get all used colors and background images, then deal with them and keep reference to them var colorUsed = appUtilities.getColorsFromElements(nodes, edges); + var imagesUsed = appUtilities.getImagesFromElements(nodes); var nodePropertiesToXml = { 'background-color': 'fill', @@ -1722,7 +1791,14 @@ appUtilities.getAllStyles = function (_cy) { 'font-size': 'fontSize', 'font-weight': 'fontWeight', 'font-style': 'fontStyle', - 'font-family': 'fontFamily' + 'font-family': 'fontFamily', + 'background-image': 'backgroundImage', + 'background-fit': 'backgroundFit', + 'background-position-x': 'backgroundPosX', + 'background-position-y': 'backgroundPosY', + 'background-height': 'backgroundHeight', + 'background-width': 'backgroundWidth', + 'background-image-opacity': 'backgroundImageOpacity', }; var edgePropertiesToXml = { 'line-color': 'stroke', @@ -1733,7 +1809,12 @@ appUtilities.getAllStyles = function (_cy) { var hash = ""; for(var cssProp in properties){ if (element.data(cssProp)) { - hash += element.data(cssProp).toString(); + if(cssProp === 'background-image'){ + var imgs = appUtilities.elementValidImages(element); + hash += appUtilities.elementValidImageIDs(imgs, imagesUsed); + } + else + hash += element.data(cssProp).toString(); } else { hash += ""; @@ -1752,6 +1833,11 @@ appUtilities.getAllStyles = function (_cy) { var colorID = colorUsed[validColor]; props[properties[cssProp]] = colorID; } + //if it is background image property, replace it with corresponding id + else if(cssProp == 'background-image'){ + var imgs = appUtilities.elementValidImages(element); + props[properties[cssProp]] = appUtilities.elementValidImageIDs(imgs, imagesUsed); + } else{ props[properties[cssProp]] = element.data(cssProp); } @@ -1803,6 +1889,7 @@ appUtilities.getAllStyles = function (_cy) { return { colors: colorUsed, + images: imagesUsed, background: containerBgColor, styles: styles }; @@ -1840,6 +1927,28 @@ appUtilities.elementValidColor = function (ele, colorProperty) { } }; +appUtilities.elementValidImages = function (ele) { + if (ele.isNode() && ele.data('background-image')) { + return ele.data('background-image').split(" "); + } + else { // element don't have that property + return undefined; + } +}; + +appUtilities.elementValidImageIDs = function (imgs, imagesUsed) { + if(imgs && imagesUsed && imgs.length > 0){ + var ids = []; + imgs.forEach(function(img){ + ids.push(imagesUsed[img]); + }); + return ids.join(" "); + } + else{ + return undefined; + } +} + /* returns: { xmlValid: id @@ -1873,6 +1982,25 @@ appUtilities.getColorsFromElements = function (nodes, edges) { return colorHash; } +appUtilities.getImagesFromElements = function (nodes) { + var imageHash = {}; + var imageID = 0; + for(var i=0; i 0; i=i-2){ + new_edge_pts.push(edge_pts[i-1], edge_pts[i]); + } + edge_pts = new_edge_pts; + } + + var starting_point = 0; + var minimum; + for(var i = 0; i < edge_pts.length-3; i=i+2){ + var a_b = Math.pow((mouse_normal.x-edge_pts[i]), 2) + Math.pow((mouse_normal.y-edge_pts[i+1]), 2); + a_b = Math.sqrt(a_b); + + var b_c = Math.pow((mouse_normal.x-edge_pts[i+2]), 2) + Math.pow((mouse_normal.y-edge_pts[i+3]), 2); + b_c = Math.sqrt(b_c); + + var a_c = Math.pow((edge_pts[i+2]-edge_pts[i]), 2) + Math.pow((edge_pts[i+3]-edge_pts[i+1]), 2); + a_c = Math.sqrt(a_c); + + var difference = Math.abs(a_c - a_b - b_c); + + if(minimum === undefined || minimum > difference){ + minimum = difference; + starting_point = i+2; + } + } + + var start_node = source_to_target ? source_node : target_node; + var s_normal = start_node.position(); + var s_rendered = start_node.renderedPosition(); + var zoom_level = cy.zoom(); + var finished = (edge_pts.length-starting_point-1)/2; + + // Animate for each bend point + for(var i = starting_point; i < edge_pts.length-1; i=i+2){ + // Convert normal position into rendered position + var rend_x = (edge_pts[i] - s_normal.x) * zoom_level + s_rendered.x; + var rend_y = (edge_pts[i+1] - s_normal.y) * zoom_level + s_rendered.y; + + cy.animate({ + duration: 1400, + panBy: {x: (mouse_rend.x-rend_x), y: (mouse_rend.y-rend_y)}, + easing: 'ease', + complete: function(){ + finished--; + if(finished <= 0) + (source_to_target ? target_node : source_node).select(); + } + }); + } + + + +} + +//Info-box drag handlers +appUtilities.relocationDragHandler; +appUtilities.RelocationHandler; + +var relocatedNode; + +//Enables info-box relocation if a node is selected +appUtilities.relocateInfoBoxes = function(node){ + var cy = this.getActiveCy(); + this.disableInfoBoxRelocation(); + //Abort if node has no info-boxes or selected ele is not a node + if (node.data("auxunitlayouts") === undefined || node.data("statesandinfos").length === 0 || !node.isNode()) { + this.disableInfoBoxRelocation(); + return; + } + cy.nodes(":selected").unselect(); + relocatedNode = node; + this.enableInfoBoxRelocation(node); +} + +//Checks whether a info-box is selected in a given mouse position +appUtilities.checkMouseContainsInfoBox = function(unit, mouse_down_x, mouse_down_y){ + var box = unit.bbox; + var instance = this.getActiveSbgnvizInstance(); + var cy = this.getActiveCy(); + var coords = instance.classes.AuxiliaryUnit.getAbsoluteCoord(unit, cy); + var x_loc = coords.x; + var y_loc = coords.y; + var width = box.w; + var height = box.h; + return ((mouse_down_x >= x_loc - width / 2) && (mouse_down_x <= x_loc + width / 2)) + && ( (mouse_down_y >= y_loc - height / 2) && (mouse_down_y <= y_loc + height / 2)); +} + +//Enables info-box appUtilities.RelocationHandler +appUtilities.enableInfoBoxRelocation = function(node){ + var cy = this.getActiveCy(); + //Disable box movements + var oldColor = node.data("border-color"); + node.data("border-color", "#d67614"); + var selectedBox; + var anchorSide; + cy.on('mousedown', appUtilities.RelocationHandler = function(event){ + //Check whether event contained by infobox of a node + //Lock the node so that it won't change position when + //Info boxes are dragged + cy.autounselectify(true); + cy.autolock(true); + var top = node.data('auxunitlayouts').top; + var bottom = node.data('auxunitlayouts').bottom; + var right = node.data('auxunitlayouts').right; + var left = node.data('auxunitlayouts').left; + + //Get mouse positions + var mouse_down_x = event.position.x; + var mouse_down_y = event.position.y; + var instance = appUtilities.getActiveSbgnvizInstance(); + var oldAnchorSide; //Hold old anchor side to modify units + //Check top units + if (top !== undefined && selectedBox === undefined) { + var units = top.units; + for (var i = units.length-1; i >= 0 ; i--) { + if (appUtilities.checkMouseContainsInfoBox(units[i], mouse_down_x, mouse_down_y)) { + selectedBox = units[i]; + oldAnchorSide = selectedBox.anchorSide; + break; + } + } + } + //Check right units + if (right !== undefined && selectedBox === undefined) { + var units = right.units; + for (var i = units.length-1; i >= 0 ; i--) { + if (appUtilities.checkMouseContainsInfoBox(units[i], mouse_down_x, mouse_down_y)) { + selectedBox = units[i]; + oldAnchorSide = selectedBox.anchorSide; + break; + } + } + } + //Check bottom units + if (bottom !== undefined && selectedBox === undefined) { + var units = bottom.units; + for (var i = units.length-1; i >= 0 ; i--) { + if (appUtilities.checkMouseContainsInfoBox(units[i], mouse_down_x, mouse_down_y)) { + selectedBox = units[i]; + oldAnchorSide = selectedBox.anchorSide; + break; + } + } + } + //Check left units + if (left !== undefined && selectedBox === undefined) { + var units = left.units; + for (var i = units.length-1; i >= 0 ; i--) { + if (appUtilities.checkMouseContainsInfoBox(units[i], mouse_down_x, mouse_down_y)) { + selectedBox = units[i]; + oldAnchorSide = selectedBox.anchorSide; + break; + } + } + } + + //If no info-box found abort + if (selectedBox === undefined) { + appUtilities.disableInfoBoxRelocation(); + node.data("border-color", oldColor); + relocatedNode = undefined; + return; + } + //Else If a info-box contained by event move info-box + var instance = appUtilities.getActiveSbgnvizInstance(); + var position = node.position(); + selectedBox.dashed = true; + var last_mouse_x = mouse_down_x; + var last_mouse_y = mouse_down_y; + if ((node.data("class") === "compartment" || node.data("class") === "complex") + && node._private.children !== undefined && node._private.children.length !== 0) { + var parentWidth = node._private.autoWidth; + var parentHeight = node._private.autoHeight; + var padding = node._private.autoPadding; + } + else { + var parentWidth = node.data("bbox").w; + var parentHeight = node.data("bbox").h; + var padding = 0; + } + var parentX1 = position.x - parentWidth/2 - padding; + var parentX2 = position.x + parentWidth/2 + padding; + var parentY1 = position.y - parentHeight/2 - padding; + var parentY2 = position.y + parentHeight/2 + padding; + cy.on("mousemove", appUtilities.relocationDragHandler = function(event){ + if (selectedBox === undefined) { //If selected box is undefined somehow abort + appUtilities.disableInfoBoxRelocation(); + node.data("border-color", oldColor); + relocatedNode = undefined; + return; + } + //Clear visual cues during relocation + cy.expandCollapse('get').clearVisualCue(node); + var drag_x = event.position.x; + var drag_y = event.position.y; + var anchorSide = selectedBox.anchorSide; + var gap, shift_x, shift_y, box_new_x, box_new_y; + + //If anchor side is top or bottom only move in x direction + if (anchorSide === "top" || anchorSide === "bottom") { + if (anchorSide === "top") { + gap = instance.classes.AuxUnitLayout.getCurrentTopGap(); + } + else { + gap = instance.classes.AuxUnitLayout.getCurrentBottomGap(); + } + + shift_x = drag_x - last_mouse_x; + box_new_x = selectedBox.bbox.x + shift_x; //Calculate new box position + //Get absolute position + var absoluteCoords = instance.classes.AuxiliaryUnit.convertToAbsoluteCoord(selectedBox, box_new_x, selectedBox.bbox.y, cy); + var newRelativeCoords; + if (absoluteCoords.x - selectedBox.bbox.w/2 < (parentX1) + gap) { //Box cannot go futher than parentBox + margin on left side + newRelativeCoords = instance.classes.AuxiliaryUnit.convertToRelativeCoord(selectedBox,((parentX1) + gap + selectedBox.bbox.w/2), + absoluteCoords.y, cy); + selectedBox.bbox.x = newRelativeCoords.x; + } + else if (absoluteCoords.x + selectedBox.bbox.w/2 > (parentX2) - gap) { //Box cannot go futher than parentBox - margin on right side + newRelativeCoords = instance.classes.AuxiliaryUnit.convertToRelativeCoord(selectedBox, ((parentX2) - gap - selectedBox.bbox.w/2), + absoluteCoords.y, cy); + selectedBox.bbox.x = newRelativeCoords.x; + } + else { //Else it is already relative + selectedBox.bbox.x = box_new_x; + } + //If box is at margin points allow it to change anchor side + //If it on left it can pass left anchor side + absoluteCoords = instance.classes.AuxiliaryUnit.convertToAbsoluteCoord(selectedBox, selectedBox.bbox.x, selectedBox.bbox.y, cy); //Get current absolute coords + if (absoluteCoords.x === (parentX1) + gap + selectedBox.bbox.w/2) { //If it is on the left margin allow it to change anchor sides + //If it is in the top and mouse moves bottom it can go left anchor + if (last_mouse_y < drag_y && anchorSide === "top") { + selectedBox.anchorSide = "left"; //Set new anchor side + newRelativeCoords = instance.classes.AuxiliaryUnit.convertToRelativeCoord(selectedBox, (parentX1), + (parentY1 + instance.classes.AuxUnitLayout.getCurrentLeftGap()), cy); + selectedBox.bbox.x = newRelativeCoords.x; + selectedBox.bbox.y = newRelativeCoords.y; + } + else if (last_mouse_y > drag_y && anchorSide === "bottom") { //If it is in the bottom and mouse moves up it can go left anchor side + selectedBox.anchorSide = "left"; //Set new anchor side + newRelativeCoords = instance.classes.AuxiliaryUnit.convertToRelativeCoord(selectedBox, (parentX1), + (parentY2 - instance.classes.AuxUnitLayout.getCurrentLeftGap()), cy); + selectedBox.bbox.x = newRelativeCoords.x; + selectedBox.bbox.y = newRelativeCoords.y; + } + } + //If it on right it can pass right anchor side, * 2 for relaxation + else if (absoluteCoords.x === (parentX2) - gap - selectedBox.bbox.w/2) { + if (last_mouse_y < drag_y && anchorSide === "top") { + selectedBox.anchorSide = "right"; //Set new anchor side + newRelativeCoords = instance.classes.AuxiliaryUnit.convertToRelativeCoord(selectedBox, (parentX2), + (parentY1 + instance.classes.AuxUnitLayout.getCurrentRightGap()), cy); + selectedBox.bbox.x = newRelativeCoords.x; + selectedBox.bbox.y = newRelativeCoords.y; + } + else if (last_mouse_y > drag_y && anchorSide === "bottom") { //If it is in the bottom and mouse moves up it can go left anchor side + selectedBox.anchorSide = "right"; //Set new anchor side + newRelativeCoords = instance.classes.AuxiliaryUnit.convertToRelativeCoord(selectedBox, (parentX2), + (parentY2 - instance.classes.AuxUnitLayout.getCurrentRightGap()), cy); + selectedBox.bbox.x = newRelativeCoords.x; + selectedBox.bbox.y = newRelativeCoords.y; + } + } + } + else { + //If anchor side left or right only move in y direction + if (anchorSide === "right") { + gap = instance.classes.AuxUnitLayout.getCurrentRightGap(); + } + else { + gap = instance.classes.AuxUnitLayout.getCurrentLeftGap(); + } + + shift_y = drag_y - last_mouse_y; + box_new_y = selectedBox.bbox.y + shift_y; //Calculate new box position + + //Get absolute position + var absoluteCoords = instance.classes.AuxiliaryUnit.convertToAbsoluteCoord(selectedBox, selectedBox.bbox.x, box_new_y, cy); + var newRelativeCoords; + if (absoluteCoords.y - selectedBox.bbox.h/2 < (parentY1) + gap) { //Box cannot go futher than parentBox + margin on left side + newRelativeCoords = instance.classes.AuxiliaryUnit.convertToRelativeCoord(selectedBox, absoluteCoords.x, + (parentY1) + gap + selectedBox.bbox.h/2, cy); + selectedBox.bbox.y = newRelativeCoords.y; + } + else if (absoluteCoords.y + selectedBox.bbox.h/2 > (parentY2) - gap) { //Box cannot go futher than parentBox - margin on right side + newRelativeCoords = instance.classes.AuxiliaryUnit.convertToRelativeCoord(selectedBox, absoluteCoords.x, + (parentY2) - gap - selectedBox.bbox.h/2, cy); + selectedBox.bbox.y = newRelativeCoords.y; + } + else { //Else it is already relative + selectedBox.bbox.y = box_new_y; + } + + absoluteCoords = instance.classes.AuxiliaryUnit.convertToAbsoluteCoord(selectedBox, selectedBox.bbox.x, selectedBox.bbox.y, cy); + //Set anchor side changes + if (absoluteCoords.y === (parentY1) + gap + selectedBox.bbox.h / 2) { //If it is on the top margin allow it to change anchor sides + //If it is in the top and mouse moves bottom it can go left anchor + if (last_mouse_x < drag_x && anchorSide === "left") { + selectedBox.anchorSide = "top"; //Set new anchor side + newRelativeCoords = instance.classes.AuxiliaryUnit.convertToRelativeCoord(selectedBox, (parentX1) + + instance.classes.AuxUnitLayout.getCurrentTopGap(), (parentY1), cy); + selectedBox.bbox.x = newRelativeCoords.x; + selectedBox.bbox.y = newRelativeCoords.y; + } + else if (last_mouse_x > drag_x && anchorSide === "right") { //If it is in the right and mouse moves up it can go top anchor side + selectedBox.anchorSide = "top"; //Set new anchor side + newRelativeCoords = instance.classes.AuxiliaryUnit.convertToRelativeCoord(selectedBox, (parentX2) + - instance.classes.AuxUnitLayout.getCurrentTopGap(), (parentY1), cy); + selectedBox.bbox.x = newRelativeCoords.x; + selectedBox.bbox.y = newRelativeCoords.y; + } + } + //If it on right it can pass right anchor side + else if (absoluteCoords.y === (parentY2) - gap - selectedBox.bbox.h/2) { + if (last_mouse_x < drag_x && anchorSide === "left") { + selectedBox.anchorSide = "bottom"; //Set new anchor side + newRelativeCoords = instance.classes.AuxiliaryUnit.convertToRelativeCoord(selectedBox, (parentX1) + + instance.classes.AuxUnitLayout.getCurrentBottomGap(), (parentY2), cy); + selectedBox.bbox.x = newRelativeCoords.x; + selectedBox.bbox.y = newRelativeCoords.y; + } + else if (last_mouse_x > drag_x && anchorSide === "right") { //If it is in the bottom and mouse moves up it can go left anchor side + selectedBox.anchorSide = "bottom"; //Set new anchor side + newRelativeCoords = instance.classes.AuxiliaryUnit.convertToRelativeCoord(selectedBox, (parentX2) + - instance.classes.AuxUnitLayout.getCurrentBottomGap(), (parentY2), cy); + selectedBox.bbox.x = newRelativeCoords.x; + selectedBox.bbox.y = newRelativeCoords.y; + } + } + + } + + last_mouse_x = drag_x; + last_mouse_y = drag_y; + cy.style().update(); + }); + + cy.on("mouseup", function(event){ + appUtilities.disableInfoBoxRelocationDrag(); + if (selectedBox !== undefined && oldAnchorSide !== undefined) { + selectedBox.dashed = false; + instance.classes.AuxUnitLayout.modifyUnits(node, selectedBox, oldAnchorSide, cy); //Modify aux unit layouts + selectedBox = undefined; + anchorSide = undefined; + oldAnchorSide = undefined; + cy.autolock(false); + } + }); + + }); } +//Disables info-box relocation +appUtilities.disableInfoBoxRelocation = function(color){ + var cy = this.getActiveCy(); + if (appUtilities.RelocationHandler !== undefined) { + //Remove listerners + cy.off('mousedown', appUtilities.RelocationHandler); + appUtilities.disableInfoBoxRelocationDrag(); + if (relocatedNode !== undefined) { + relocatedNode.data("border-color", color); + relocatedNode = undefined; + } + relocatedNode = undefined; + appUtilities.RelocationHandler = undefined; + cy.autolock(false); //Make the nodes moveable again + cy.autounselectify(false); //Make the nodes selectable + } + +}; + +//Disables info-box dragging +appUtilities.disableInfoBoxRelocationDrag = function(){ + if (appUtilities.relocationDragHandler !== undefined) { + var cy = this.getActiveCy(); + //Remove listerners + cy.off('mousemove', appUtilities.relocationDragHandler); + appUtilities.relocationDragHandler = undefined; + } +}; + +appUtilities.resizeNodesToContent = function(collection){ + + var chiseInstance = appUtilities.getActiveChiseInstance(); + var cy = appUtilities.getActiveCy(); + + var actions = []; + collection.forEach(function( node ){ + var bbox = node.data('bbox'); + bbox.w = calculateWidth(node); + bbox.h = calculateHeight(node); + + chiseInstance.classes.AuxUnitLayout.fitUnits(node); + chiseInstance.resizeNodesToContent(node, bbox, actions); + }); + + cy.undoRedo().do("batch", actions); + cy.style().update(); + cy.nodeResize('get').refreshGrapples(); + + function calculateWidth(cyTarget) { + // Label width calculation + var labelText = cyTarget._private.style.label.value; + var labelFontSize = cyTarget._private.style['font-size'].value * 80/100; + var labelWidth = labelText.length * labelFontSize * 80/100; + var labelWidth = (labelWidth === 0) ? 15 : labelWidth; + + // Separation of info boxes based on their locations + var statesandinfos = cyTarget._private.data.statesandinfos; + var bottomInfoBoxes = statesandinfos.filter(box => box.anchorSide === "bottom"); + var topInfoBoxes = statesandinfos.filter(box => box.anchorSide === "top"); + var leftInfoBoxes = statesandinfos.filter(box => box.anchorSide === "left"); + var rightInfoBoxes = statesandinfos.filter(box => box.anchorSide === "right"); + + var horizontalMargin = 10; + + var bottomWidth = horizontalMargin; + var topWidth = horizontalMargin; + var middleWidth = 0; + var leftWidth = 0; + var rightWidth = 0; + + bottomInfoBoxes.forEach(function (infoBox) { + bottomWidth += infoBox.bbox.w; + }); + + topInfoBoxes.forEach(function (infoBox) { + topWidth += infoBox.bbox.w; + }); + + leftInfoBoxes.forEach(function (infoBox) { + leftWidth = (leftWidth > infoBox.bbox.w/2) ? leftWidth : infoBox.bbox.w/2; + }); + + rightInfoBoxes.forEach(function (infoBox) { + rightWidth = (rightWidth > infoBox.bbox.w/2) ? rightWidth : infoBox.bbox.w/2; + }); + + middleWidth = labelWidth + leftWidth + rightWidth + 3*horizontalMargin; + var width = Math.max(bottomWidth, topWidth, labelWidth); + return width + 2*horizontalMargin; + } + + function calculateHeight(cyTarget) { + var defaultHeight = 30; + var infoBoxHeight = 0; + var margin = 10; + + var statesandinfos = cyTarget._private.data.statesandinfos; + var leftInfoBoxes = statesandinfos.filter(box => box.anchorSide === "left"); + var rightInfoBoxes = statesandinfos.filter(box => box.anchorSide === "right"); + + var leftHeight = 2*margin; + var rightHeight = 2*margin; + leftInfoBoxes.forEach(function (infoBox) { + leftHeight += infoBox.bbox.h; + }); + + rightInfoBoxes.forEach(function (infoBox) { + rightHeight += infoBox.bbox.h; + }); + + var height = Math.max(leftHeight, rightHeight, defaultHeight); + return height; + } +}; + module.exports = appUtilities; diff --git a/app/js/backbone-views.js b/app/js/backbone-views.js index f90a6098a..330708876 100644 --- a/app/js/backbone-views.js +++ b/app/js/backbone-views.js @@ -2,6 +2,7 @@ var jquery = $ = require('jquery'); var _ = require('underscore'); var Backbone = require('backbone'); var chroma = require('chroma-js'); +var FileSaver = require('filesaverjs'); var appUtilities = require('./app-utilities'); var setFileContent = appUtilities.setFileContent.bind(appUtilities); @@ -179,6 +180,61 @@ var BioGeneView = Backbone.View.extend({ } }); +/** + * Backbone view for the Chemical information. + */ +var ChemicalView = Backbone.View.extend({ + render: function () { + // pass variables in using Underscore.js template + var variables = { + chemicalDescription: this.model.description[0], + chebiName: this.model.label, + chebiID: this.model.obo_id.substring(6, this.model.obo_id.length) //Gets only the nr from ChEBI:15422 format + }; + + // compile the template using underscore + var template = _.template($("#chemical-template").html()); + template = template(variables); + + // load the compiled HTML into the Backbone "el" + this.$el.html(template); + + // format after loading + this.format(this.model); + + return this; + }, + format: function () + { + // hide rows with undefined data + if (this.model.label == undefined) + this.$el.find(".chebi-name").hide(); + + if (this.model.description[0] == undefined) + this.$el.find(".chemical-description").hide(); + + if (this.model.obo_id == undefined) + this.$el.find(".chebi-id").hide(); + + var expanderOpts = {slicePoint: 150, + expandPrefix: ' ', + expandText: ' (...)', + userCollapseText: ' (show less)', + moreClass: 'expander-read-more', + lessClass: 'expander-read-less', + detailClass: 'expander-details', + // do not use default effects + // (see https://github.com/kswedberg/jquery-expander/issues/46) + expandEffect: 'fadeIn', + collapseEffect: 'fadeOut'}; + + $(".chemical-description .expandable").expander(expanderOpts); + + expanderOpts.slicePoint = 2; // show comma and the space + expanderOpts.widow = 0; // hide everything else in any case + } +}); + /** * SBGN Layout view for the Sample Application. */ @@ -422,7 +478,7 @@ var MapTabGeneralPanel = GeneralPropertiesParentView.extend({ self.params.inferNestingOnLoad = {id: "infer-nesting-on-load", type: "checkbox", property: "currentGeneralProperties.inferNestingOnLoad"}; - + self.params.enablePorts = {id: "enable-ports", type: "checkbox", property: "currentGeneralProperties.enablePorts", update: self.applyUpdate}; @@ -497,7 +553,7 @@ var MapTabGeneralPanel = GeneralPropertiesParentView.extend({ cy.undoRedo().do("changeMenu", self.params.allowCompoundNodeResize); $('#allow-compound-node-resize').blur(); }); - + $(document).on("change", "#infer-nesting-on-load", function (evt) { // use active cy instance @@ -797,10 +853,26 @@ var MapTabRearrangementPanel = GeneralPropertiesParentView.extend({ } });*/ + +String.prototype.replaceAll = function(search, replace) +{ + //if replace is not sent, return original string otherwise it will + //replace search string with 'undefined'. + if (replace === undefined) { + return this.toString(); + } + + return this.replace(new RegExp('[' + search + ']', 'g'), replace); +}; + +//Global variable used to check which PathwayCommon dialog was open recently +//Clicking Ok in Error dialog will redirect to opening of that certain dialog again +var PCdialog = ""; + /** - * Paths Between Query view for the Sample Application. + * Neighborhood Query view for the Sample Application. */ -var PathsBetweenQueryView = Backbone.View.extend({ +var NeighborhoodQueryView = Backbone.View.extend({ defaultQueryParameters: { geneSymbols: "", lengthLimit: 1 @@ -809,7 +881,7 @@ var PathsBetweenQueryView = Backbone.View.extend({ initialize: function () { var self = this; self.copyProperties(); - self.template = _.template($("#query-pathsbetween-template").html()); + self.template = _.template($("#query-neighborhood-template").html()); self.template = self.template(self.currentQueryParameters); }, copyProperties: function () { @@ -818,13 +890,14 @@ var PathsBetweenQueryView = Backbone.View.extend({ render: function () { var self = this; - self.template = _.template($("#query-pathsbetween-template").html()); + self.template = _.template($("#query-neighborhood-template").html()); self.template = self.template(self.currentQueryParameters); $(self.el).html(self.template); $(self.el).modal('show'); + PCdialog = "Neighborhood"; - $(document).off("click", "#save-query-pathsbetween").on("click", "#save-query-pathsbetween", function (evt) { + $(document).off("click", "#save-query-neighborhood").on("click", "#save-query-neighborhood", function (evt) { // use active chise instance var chiseInstance = appUtilities.getActiveChiseInstance(); @@ -832,12 +905,12 @@ var PathsBetweenQueryView = Backbone.View.extend({ // use the associated cy instance var cy = chiseInstance.getCy(); - self.currentQueryParameters.geneSymbols = document.getElementById("query-pathsbetween-gene-symbols").value; - self.currentQueryParameters.lengthLimit = Number(document.getElementById("query-pathsbetween-length-limit").value); + self.currentQueryParameters.geneSymbols = document.getElementById("query-neighborhood-gene-symbols").value; + self.currentQueryParameters.lengthLimit = Number(document.getElementById("query-neighborhood-length-limit").value); var geneSymbols = self.currentQueryParameters.geneSymbols.trim(); if (geneSymbols.length === 0) { - document.getElementById("query-pathsbetween-gene-symbols").focus(); + document.getElementById("query-neighborhood-gene-symbols").focus(); return; } // geneSymbols is cleaned up from undesired characters such as #,$,! etc. and spaces put before and after the string @@ -847,16 +920,16 @@ var PathsBetweenQueryView = Backbone.View.extend({ new PromptInvalidQueryView({el: '#prompt-invalidQuery-table'}).render(); return; } - if (self.currentQueryParameters.lengthLimit > 3) { + if (self.currentQueryParameters.lengthLimit > 2) { $(self.el).modal('toggle'); new PromptInvalidLengthLimitView({el: '#prompt-invalidLengthLimit-table'}).render(); - document.getElementById("query-pathsbetween-length-limit").focus(); + document.getElementById("query-neighborhood-length-limit").focus(); return; } - var queryURL = "http://www.pathwaycommons.org/pc2/graph?format=SBGN&kind=PATHSBETWEEN&limit=" + var queryURL = "http://www.pathwaycommons.org/pc2/graph?format=SBGN&kind=NEIGHBORHOOD&limit=" + self.currentQueryParameters.lengthLimit; - var geneSymbolsArray = geneSymbols.replace("\n", " ").replace("\t", " ").split(" "); + var geneSymbolsArray = geneSymbols.replaceAll("\n", " ").replaceAll("\t", " ").split(" "); var filename = ""; var sources = ""; @@ -874,14 +947,14 @@ var PathsBetweenQueryView = Backbone.View.extend({ filename = filename + '_' + currentGeneSymbol; } } - filename = filename + '_PATHSBETWEEN.sbgnml'; + filename = filename + '_NEIGHBORHOOD.sbgnml'; - chiseInstance.startSpinner('paths-between-spinner'); + chiseInstance.startSpinner('neighborhood-spinner'); queryURL = queryURL + sources; var currentGeneralProperties = appUtilities.getScratch(cy, 'currentGeneralProperties'); var currentInferNestingOnLoad = currentGeneralProperties.inferNestingOnLoad; - + $.ajax({ url: queryURL, type: 'GET', @@ -889,7 +962,7 @@ var PathsBetweenQueryView = Backbone.View.extend({ if (data == null) { new PromptInvalidQueryView({el: '#prompt-invalidQuery-table'}).render(); - chiseInstance.endSpinner('paths-between-spinner'); + chiseInstance.endSpinner('neighborhood-spinner'); } else { @@ -897,7 +970,7 @@ var PathsBetweenQueryView = Backbone.View.extend({ currentGeneralProperties.inferNestingOnLoad = false; chiseInstance.updateGraph(chiseInstance.convertSbgnmlToJson(data), undefined, true); currentGeneralProperties.inferNestingOnLoad = currentInferNestingOnLoad; - chiseInstance.endSpinner('paths-between-spinner'); + chiseInstance.endSpinner('neighborhood-spinner'); $(document).trigger('sbgnvizLoadFileEnd', [ filename, cy ]); } } @@ -905,7 +978,7 @@ var PathsBetweenQueryView = Backbone.View.extend({ $(self.el).modal('toggle'); }); - $(document).off("click", "#cancel-query-pathsbetween").on("click", "#cancel-query-pathsbetween", function (evt) { + $(document).off("click", "#cancel-query-neighborhood").on("click", "#cancel-query-neighborhood", function (evt) { $(self.el).modal('toggle'); }); @@ -913,6 +986,389 @@ var PathsBetweenQueryView = Backbone.View.extend({ } }); +/** + * Paths Between Query view for the Sample Application. + */ +var PathsBetweenQueryView = Backbone.View.extend({ + defaultQueryParameters: { + geneSymbols: "", + lengthLimit: 1 + }, + currentQueryParameters: null, + initialize: function () { + var self = this; + self.copyProperties(); + self.template = _.template($("#query-pathsbetween-template").html()); + self.template = self.template(self.currentQueryParameters); + }, + copyProperties: function () { + this.currentQueryParameters = _.clone(this.defaultQueryParameters); + }, + render: function () { + + var self = this; + self.template = _.template($("#query-pathsbetween-template").html()); + self.template = self.template(self.currentQueryParameters); + $(self.el).html(self.template); + + $(self.el).modal('show'); + PCdialog = "PathsBetween"; + + $(document).off("click", "#save-query-pathsbetween").on("click", "#save-query-pathsbetween", function (evt) { + + // use active chise instance + var chiseInstance = appUtilities.getActiveChiseInstance(); + + // use the associated cy instance + var cy = chiseInstance.getCy(); + + self.currentQueryParameters.geneSymbols = document.getElementById("query-pathsbetween-gene-symbols").value; + self.currentQueryParameters.lengthLimit = Number(document.getElementById("query-pathsbetween-length-limit").value); + + var geneSymbols = self.currentQueryParameters.geneSymbols.trim(); + if (geneSymbols.length === 0) { + document.getElementById("query-pathsbetween-gene-symbols").focus(); + return; + } + // geneSymbols is cleaned up from undesired characters such as #,$,! etc. and spaces put before and after the string + geneSymbols = geneSymbols.replace(/[^a-zA-Z0-9\n\t ]/g, "").trim(); + if (geneSymbols.length === 0) { + $(self.el).modal('toggle'); + new PromptInvalidQueryView({el: '#prompt-invalidQuery-table'}).render(); + return; + } + if (self.currentQueryParameters.lengthLimit > 3) { + $(self.el).modal('toggle'); + new PromptInvalidLengthLimitView({el: '#prompt-invalidLengthLimit-table'}).render(); + document.getElementById("query-pathsbetween-length-limit").focus(); + return; + } + + var queryURL = "http://www.pathwaycommons.org/pc2/graph?format=SBGN&kind=PATHSBETWEEN&limit=" + + self.currentQueryParameters.lengthLimit; + var geneSymbolsArray = geneSymbols.replaceAll("\n", " ").replaceAll("\t", " ").split(" "); + + var filename = ""; + var sources = ""; + for (var i = 0; i < geneSymbolsArray.length; i++) { + var currentGeneSymbol = geneSymbolsArray[i]; + if (currentGeneSymbol.length == 0 || currentGeneSymbol == ' ' + || currentGeneSymbol == '\n' || currentGeneSymbol == '\t') { + continue; + } + sources = sources + "&source=" + currentGeneSymbol; + + if (filename == '') { + filename = currentGeneSymbol; + } else { + filename = filename + '_' + currentGeneSymbol; + } + } + filename = filename + '_PATHSBETWEEN.sbgnml'; + + chiseInstance.startSpinner('paths-between-spinner'); + queryURL = queryURL + sources; + + var currentGeneralProperties = appUtilities.getScratch(cy, 'currentGeneralProperties'); + var currentInferNestingOnLoad = currentGeneralProperties.inferNestingOnLoad; + + $.ajax({ + url: queryURL, + type: 'GET', + success: function (data) { + if (data == null) + { + new PromptInvalidQueryView({el: '#prompt-invalidQuery-table'}).render(); + chiseInstance.endSpinner('paths-between-spinner'); + } + else + { + $(document).trigger('sbgnvizLoadFile', [ filename, cy ]); + currentGeneralProperties.inferNestingOnLoad = false; + chiseInstance.updateGraph(chiseInstance.convertSbgnmlToJson(data), undefined, true); + currentGeneralProperties.inferNestingOnLoad = currentInferNestingOnLoad; + chiseInstance.endSpinner('paths-between-spinner'); + $(document).trigger('sbgnvizLoadFileEnd', [ filename, cy ]); + } + } + }); + $(self.el).modal('toggle'); + }); + + $(document).off("click", "#cancel-query-pathsbetween").on("click", "#cancel-query-pathsbetween", function (evt) { + $(self.el).modal('toggle'); + }); + + return this; + } +}); + +/** + * Paths From To Query view for the Sample Application. + */ +var PathsFromToQueryView = Backbone.View.extend({ + defaultQueryParameters: { + sourceSymbols: "", + targetSymbols: "", + lengthLimit: 1 + }, + currentQueryParameters: null, + initialize: function () { + var self = this; + self.copyProperties(); + self.template = _.template($("#query-pathsfromto-template").html()); + self.template = self.template(self.currentQueryParameters); + }, + copyProperties: function () { + this.currentQueryParameters = _.clone(this.defaultQueryParameters); + }, + render: function () { + + var self = this; + self.template = _.template($("#query-pathsfromto-template").html()); + self.template = self.template(self.currentQueryParameters); + $(self.el).html(self.template); + + $(self.el).modal('show'); + PCdialog = "PathsFromTo"; + + $(document).off("click", "#save-query-pathsfromto").on("click", "#save-query-pathsfromto", function (evt) { + + // use active chise instance + var chiseInstance = appUtilities.getActiveChiseInstance(); + + // use the associated cy instance + var cy = chiseInstance.getCy(); + + self.currentQueryParameters.sourceSymbols = document.getElementById("query-pathsfromto-source-symbols").value; + self.currentQueryParameters.targetSymbols = document.getElementById("query-pathsfromto-target-symbols").value; + self.currentQueryParameters.lengthLimit = Number(document.getElementById("query-pathsfromto-length-limit").value); + + var sourceSymbols = self.currentQueryParameters.sourceSymbols.trim(); + if (sourceSymbols.length === 0) { + document.getElementById("query-pathsfromto-source-symbols").focus(); + return; + } + // sourceSymbols is cleaned up from undesired characters such as #,$,! etc. and spaces put before and after the string + sourceSymbols = sourceSymbols.replace(/[^a-zA-Z0-9\n\t ]/g, "").trim(); + if (sourceSymbols.length === 0) { + $(self.el).modal('toggle'); + new PromptInvalidQueryView({el: '#prompt-invalidQuery-table'}).render(); + return; + } + + var targetSymbols = self.currentQueryParameters.targetSymbols.trim(); + if (targetSymbols.length === 0) { + document.getElementById("query-pathsfromto-target-symbols").focus(); + return; + } + // targetSymbols is cleaned up from undesired characters such as #,$,! etc. and spaces put before and after the string + targetSymbols = targetSymbols.replace(/[^a-zA-Z0-9\n\t ]/g, "").trim(); + if (targetSymbols.length === 0) { + $(self.el).modal('toggle'); + new PromptInvalidQueryView({el: '#prompt-invalidQuery-table'}).render(); + return; + } + + if (self.currentQueryParameters.lengthLimit > 3) { + $(self.el).modal('toggle'); + new PromptInvalidLengthLimitView({el: '#prompt-invalidLengthLimit-table'}).render(); + document.getElementById("query-pathsfromto-length-limit").focus(); + return; + } + + var queryURL = "http://www.pathwaycommons.org/pc2/graph?format=SBGN&kind=PATHSFROMTO&limit=" + + self.currentQueryParameters.lengthLimit; + var sourceSymbolsArray = sourceSymbols.replaceAll("\n", " ").replaceAll("\t", " ").split(" "); + var targetSymbolsArray = targetSymbols.replaceAll("\n", " ").replaceAll("\t", " ").split(" "); + + var filename = ""; + var sources = ""; + var targets = ""; + for (var i = 0; i < sourceSymbolsArray.length; i++) { + var currentGeneSymbol = sourceSymbolsArray[i]; + if (currentGeneSymbol.length == 0 || currentGeneSymbol == ' ' + || currentGeneSymbol == '\n' || currentGeneSymbol == '\t') { + continue; + } + sources = sources + "&source=" + currentGeneSymbol; + + if (filename == '') { + filename = currentGeneSymbol; + } else { + filename = filename + '_' + currentGeneSymbol; + } + } + for (var i = 0; i < targetSymbolsArray.length; i++) { + var currentGeneSymbol = targetSymbolsArray[i]; + if (currentGeneSymbol.length == 0 || currentGeneSymbol == ' ' + || currentGeneSymbol == '\n' || currentGeneSymbol == '\t') { + continue; + } + targets = targets + "&target=" + currentGeneSymbol; + + if (filename == '') { + filename = currentGeneSymbol; + } else { + filename = filename + '_' + currentGeneSymbol; + } + } + filename = filename + '_PATHSFROMTO.sbgnml'; + + chiseInstance.startSpinner('paths-fromto-spinner'); + queryURL = queryURL + sources + targets; + + var currentGeneralProperties = appUtilities.getScratch(cy, 'currentGeneralProperties'); + var currentInferNestingOnLoad = currentGeneralProperties.inferNestingOnLoad; + + $.ajax({ + url: queryURL, + type: 'GET', + success: function (data) { + if (data == null) + { + new PromptInvalidQueryView({el: '#prompt-invalidQuery-table'}).render(); + chiseInstance.endSpinner('paths-fromto-spinner'); + } + else + { + $(document).trigger('sbgnvizLoadFile', [ filename, cy ]); + currentGeneralProperties.inferNestingOnLoad = false; + chiseInstance.updateGraph(chiseInstance.convertSbgnmlToJson(data), undefined, true); + currentGeneralProperties.inferNestingOnLoad = currentInferNestingOnLoad; + chiseInstance.endSpinner('paths-fromto-spinner'); + $(document).trigger('sbgnvizLoadFileEnd', [ filename, cy ]); + } + } + }); + $(self.el).modal('toggle'); + }); + + $(document).off("click", "#cancel-query-pathsfromto").on("click", "#cancel-query-pathsfromto", function (evt) { + $(self.el).modal('toggle'); + }); + + return this; + } +}); + +/** + * Common Stream Query view for the Sample Application. + */ +var CommonStreamQueryView = Backbone.View.extend({ + defaultQueryParameters: { + geneSymbols: "", + lengthLimit: 1 + }, + currentQueryParameters: null, + initialize: function () { + var self = this; + self.copyProperties(); + self.template = _.template($("#query-commonstream-template").html()); + self.template = self.template(self.currentQueryParameters); + }, + copyProperties: function () { + this.currentQueryParameters = _.clone(this.defaultQueryParameters); + }, + render: function () { + + var self = this; + self.template = _.template($("#query-commonstream-template").html()); + self.template = self.template(self.currentQueryParameters); + $(self.el).html(self.template); + + $(self.el).modal('show'); + PCdialog = "CommonStream"; + + $(document).off("click", "#save-query-commonstream").on("click", "#save-query-commonstream", function (evt) { + + // use active chise instance + var chiseInstance = appUtilities.getActiveChiseInstance(); + + // use the associated cy instance + var cy = chiseInstance.getCy(); + + self.currentQueryParameters.geneSymbols = document.getElementById("query-commonstream-gene-symbols").value; + self.currentQueryParameters.lengthLimit = Number(document.getElementById("query-commonstream-length-limit").value); + + var geneSymbols = self.currentQueryParameters.geneSymbols.trim(); + if (geneSymbols.length === 0) { + document.getElementById("query-commonstream-gene-symbols").focus(); + return; + } + // geneSymbols is cleaned up from undesired characters such as #,$,! etc. and spaces put before and after the string + geneSymbols = geneSymbols.replace(/[^a-zA-Z0-9\n\t ]/g, "").trim(); + if (geneSymbols.length === 0) { + $(self.el).modal('toggle'); + new PromptInvalidQueryView({el: '#prompt-invalidQuery-table'}).render(); + return; + } + if (self.currentQueryParameters.lengthLimit > 3) { + $(self.el).modal('toggle'); + new PromptInvalidLengthLimitView({el: '#prompt-invalidLengthLimit-table'}).render(); + document.getElementById("query-commonstream-length-limit").focus(); + return; + } + + var queryURL = "http://beta.pathwaycommons.org/pc2/graph?format=SBGN&kind=COMMONSTREAM&limit=" + + self.currentQueryParameters.lengthLimit; + var geneSymbolsArray = geneSymbols.replaceAll("\n", " ").replaceAll("\t", " ").split(" "); + + var filename = ""; + var sources = ""; + for (var i = 0; i < geneSymbolsArray.length; i++) { + var currentGeneSymbol = geneSymbolsArray[i]; + if (currentGeneSymbol.length == 0 || currentGeneSymbol == ' ' + || currentGeneSymbol == '\n' || currentGeneSymbol == '\t') { + continue; + } + sources = sources + "&source=" + currentGeneSymbol; + + if (filename == '') { + filename = currentGeneSymbol; + } else { + filename = filename + '_' + currentGeneSymbol; + } + } + filename = filename + '_COMMONSTREAM.sbgnml'; + + chiseInstance.startSpinner('common-stream-spinner'); + queryURL = queryURL + sources; + + var currentGeneralProperties = appUtilities.getScratch(cy, 'currentGeneralProperties'); + var currentInferNestingOnLoad = currentGeneralProperties.inferNestingOnLoad; + + $.ajax({ + url: queryURL, + type: 'GET', + success: function (data) { + if (data == null) + { + new PromptInvalidQueryView({el: '#prompt-invalidQuery-table'}).render(); + chiseInstance.endSpinner('common-stream-spinner'); + } + else + { + $(document).trigger('sbgnvizLoadFile', [ filename, cy ]); + currentGeneralProperties.inferNestingOnLoad = false; + chiseInstance.updateGraph(chiseInstance.convertSbgnmlToJson(data), undefined, true); + currentGeneralProperties.inferNestingOnLoad = currentInferNestingOnLoad; + chiseInstance.endSpinner('common-stream-spinner'); + $(document).trigger('sbgnvizLoadFileEnd', [ filename, cy ]); + } + } + }); + $(self.el).modal('toggle'); + }); + + $(document).off("click", "#cancel-query-commonstream").on("click", "#cancel-query-commonstream", function (evt) { + $(self.el).modal('toggle'); + }); + + return this; + } +}); + /** * Paths By URI Query view for the Sample Application. */ @@ -1018,7 +1474,7 @@ var PathsByURIQueryView = Backbone.View.extend({ So this PromptSaveView isn't used for now, replaced by PromptConfirmationView. */ var PromptSaveView = Backbone.View.extend({ - + initialize: function () { var self = this; self.template = _.template($("#prompt-save-template").html()); @@ -1035,12 +1491,12 @@ var PromptSaveView = Backbone.View.extend({ afterFunction(); $(self.el).modal('toggle'); }); - + $(document).off("click", "#prompt-save-reject").on("click", "#prompt-save-reject", function (evt) { afterFunction(); $(self.el).modal('toggle'); }); - + $(document).off("click", "#prompt-save-cancel").on("click", "#prompt-save-cancel", function (evt) { $(self.el).modal('toggle'); }); @@ -1057,14 +1513,27 @@ var FileSaveView = Backbone.View.extend({ var self = this; self.template = _.template($("#file-save-template").html()); }, - render: function () { + /* + possibility to use different export format here in the future + fileformat: sbgnml + version: for sbgnml: 0.2, 0.3 + */ + render: function (fileformat, version, text) { var self = this; self.template = _.template($("#file-save-template").html()); $(self.el).html(self.template); $(self.el).modal('show'); + $("#file-save-table").keyup(function(e){ + if (e.which == 13 && $(self.el).data('bs.modal').isShown && !$("#file-save-accept").is(":focus") && !$("#file-save-cancel").is(":focus")){ + $("#file-save-accept").click(); + } + }); + var filename = document.getElementById('file-name').innerHTML; + if (fileformat === "celldesigner") + filename = filename.substring(0, filename.lastIndexOf('.')).concat(".xml"); $("#file-save-filename").val(filename); $(document).off("click", "#file-save-accept").on("click", "#file-save-accept", function (evt) { @@ -1080,10 +1549,29 @@ var FileSaveView = Backbone.View.extend({ filename = $("#file-save-filename").val(); appUtilities.setFileContent(filename); - var renderInfo = appUtilities.getAllStyles(); - var properties = jquery.extend(true, {}, currentGeneralProperties); - delete properties.mapType; // already stored in sbgn file, no need to store in extension as property - chiseInstance.saveAsSbgnml(filename, renderInfo, properties); + + if(fileformat === "sbgnml") { + var renderInfo = appUtilities.getAllStyles(); + var properties = jquery.extend(true, {}, currentGeneralProperties); + delete properties.mapType; // already stored in sbgn file, no need to store in extension as property + // Exclude extensions if the version is plain + if (version === "plain") { + chiseInstance.saveAsSbgnml(filename, version); + } + else { + chiseInstance.saveAsSbgnml(filename, version, renderInfo, properties); + } + } + else if(fileformat === "celldesigner") { + var blob = new Blob([text], { + type: "text/plain;charset=utf-8;", + }); + FileSaver.saveAs(blob, filename); + } + else { // invalid file format provided + console.error("FileSaveView received unsupported file format: "+fileformat); + } + $(self.el).modal('toggle'); }); @@ -1162,7 +1650,14 @@ var PromptInvalidQueryView = Backbone.View.extend({ $(document).off("click", "#prompt-invalidQuery-confirm").on("click", "#prompt-invalidQuery-confirm", function (evt) { $(self.el).modal('toggle'); - appUtilities.pathsBetweenQueryView.render(); + if (PCdialog == "Neighborhood") + appUtilities.neighborhoodQueryView.render(); + else if (PCdialog == "PathsBetween") + appUtilities.pathsBetweenQueryView.render(); + else if (PCdialog == "PathsFromTo") + appUtilities.pathsFromToQueryView.render(); + else if (PCdialog == "CommonStream") + appUtilities.commonStreamQueryView.render(); }); return this; @@ -1179,11 +1674,22 @@ var PromptInvalidLengthLimitView = Backbone.View.extend({ self.template = _.template($("#prompt-invalidLengthLimit-template").html()); $(self.el).html(self.template); + if (PCdialog == "Neighborhood") + document.getElementById("length-limit-constant").innerHTML = "Length limit can be at most 2."; + else + document.getElementById("length-limit-constant").innerHTML = "Length limit can be at most 3."; $(self.el).modal('show'); $(document).off("click", "#prompt-invalidLengthLimit-confirm").on("click", "#prompt-invalidLengthLimit-confirm", function (evt) { $(self.el).modal('toggle'); - appUtilities.pathsBetweenQueryView.render(); + if (PCdialog == "Neighborhood") + appUtilities.neighborhoodQueryView.render(); + else if (PCdialog == "PathsBetween") + appUtilities.pathsBetweenQueryView.render(); + else if (PCdialog == "PathsFromTo") + appUtilities.pathsFromToQueryView.render(); + else if (PCdialog == "CommonStream") + appUtilities.commonStreamQueryView.render(); }); return this; @@ -1242,15 +1748,35 @@ var PromptInvalidFileView = Backbone.View.extend({ $(self.el).html(self.template); $(self.el).modal('show'); - + $(document).off("click", "#prompt-invalidFile-confirm").on("click", "#prompt-invalidFile-confirm", function (evt) { $(self.el).modal('toggle'); }); - + return this; } }); +var PromptFileConversionErrorView = Backbone.View.extend({ + initialize: function () { + var self = this; + self.template = _.template($("#prompt-fileConversionError-template").html()); + }, + render: function() { + var self = this; + self.template = _.template($("#prompt-fileConversionError-template").html()); + + $(self.el).html(self.template); + $(self.el).modal('show'); + + $(document).off("click", "#prompt-fileConversionError-confirm").on("click", "#prompt-fileConversionError-confirm", function (evt) { + $(self.el).modal('toggle'); + }); + + return this; + } +}); + var PromptInvalidURLWarning = Backbone.View.extend({ initialize: function () { var self = this; @@ -1262,65 +1788,200 @@ var PromptInvalidURLWarning = Backbone.View.extend({ $(self.el).html(self.template); $(self.el).modal('show'); - + $(document).off("click", "#prompt-invalidURL-confirm").on("click", "#prompt-invalidURL-confirm", function (evt) { $(self.el).modal('toggle'); }); - + + return this; + } +}); + +var PromptInvalidImageWarning = Backbone.View.extend({ + initialize: function () { + var self = this; + self.template = _.template($("#prompt-invalidImage-template").html()); + }, + render: function (msg) { + var self = this; + var tmp = $("#prompt-invalidImage-template").html(); + var spanText = ''; + var s = tmp.indexOf(spanText) + var e = tmp.indexOf(''); + tmp = tmp.substring(0, s + spanText.length) + msg + tmp.substring(e); + self.template = _.template(tmp); + + $(self.el).html(self.template); + $(self.el).modal('show'); + + $(document).off("click", "#prompt-invalidImage-confirm").on("click", "#prompt-invalidImage-confirm", function (evt) { + $(self.el).modal('toggle'); + }); + return this; } }); var ReactionTemplateView = Backbone.View.extend({ - addMacromolecule: function (i) { + addMacromolecule: function (type, i) { var html = "" + "" - + ""; - - $('#template-reaction-dissociated-table :input.template-reaction-textbox').last().closest('tr').after(html); + + ""; + if( type == "reaction"){ + html += "" + + "" + + ""; + html += ""; + $('#template-reversible-input-table :input.template-reaction-textbox').last().closest('tr').after(html); + } + else{ + html += ""; - + var optionsStr = ""; - + for ( var i = 0; i < fontFamilies.length; i++ ) { var fontFamily = fontFamilies[i]; var optionId = self.getOptionIdByFontFamily(fontFamily); - var optionStr = "