diff --git a/nengo_gui/components/netgraph.py b/nengo_gui/components/netgraph.py index efebcbf2..416d2edc 100644 --- a/nengo_gui/components/netgraph.py +++ b/nengo_gui/components/netgraph.py @@ -543,6 +543,10 @@ def get_extra_info(self, obj): info['sp_targets'] = ( nengo_gui.components.spa_plot.SpaPlot.applicable_targets(obj)) + + # the line number information is injected via nengo_gui.exec_env + if hasattr(obj,"_line_number"): + info['line_number'] = obj._line_number return info def send_pan_and_zoom(self, client): diff --git a/nengo_gui/exec_env.py b/nengo_gui/exec_env.py index eb3a34ae..27cc65a4 100644 --- a/nengo_gui/exec_env.py +++ b/nengo_gui/exec_env.py @@ -6,6 +6,8 @@ import sys from nengo.utils.compat import StringIO +import nengo + # list of Simulators to check for known_modules = ['nengo', 'nengo_ocl', 'nengo_distilled', @@ -45,6 +47,22 @@ def __init__(self, *args, **kwargs): return DummySimulator +# create a wrapper class for NengoObjects that records the line number +# they were created on +def make_line_number_class(cls): + class LineNumberClass(cls): + _original_class = cls + def __init__(self, *args, **kwargs): + for fn, ln, func, line in reversed(traceback.extract_stack()): + if fn == compiled_filename: + self._line_number = ln + break + else: + self._line_number = None + super(LineNumberClass, self).__init__(*args, **kwargs) + return LineNumberClass + + # thread local storage for storing whether we are executing a script flag = threading.local() @@ -110,6 +128,12 @@ def __enter__(self): self.simulators[mod] = mod.Simulator mod.Simulator = make_dummy(mod.Simulator) + # install wrapper classes to keep track of creation line numbers + for name in ['Node', 'Network', 'Connection', 'Ensemble']: + setattr(nengo, name, + make_line_number_class(getattr(nengo, name))) + + def __exit__(self, exc_type, exc_value, traceback): for mod, cls in self.simulators.items(): mod.Simulator = cls @@ -121,3 +145,8 @@ def __exit__(self, exc_type, exc_value, traceback): if self.added_directory is not None: sys.path.remove(self.added_directory) self.added_directory = None + + # remove line number wrapper classes + for name in ['Node', 'Network', 'Connection', 'Ensemble']: + setattr(nengo, name, + getattr(nengo, name)._original_class) diff --git a/nengo_gui/static/ace.js b/nengo_gui/static/ace.js index 07ca9b8a..fdffd361 100644 --- a/nengo_gui/static/ace.js +++ b/nengo_gui/static/ace.js @@ -32,7 +32,8 @@ Nengo.Ace = function (uid, args) { var code_div = document.createElement('div') code_div.id = 'editor' $('#rightpane').append(code_div); - this.editor = ace.edit('editor') + this.editor = ace.edit('editor'); + this.editor.$blockScrolling = Infinity; this.editor.getSession().setMode("ace/mode/python"); this.editor.gotoLine(1); this.marker = null; diff --git a/nengo_gui/static/components/netgraph.css b/nengo_gui/static/components/netgraph.css index 583b64b0..fe25c395 100644 --- a/nengo_gui/static/components/netgraph.css +++ b/nengo_gui/static/components/netgraph.css @@ -17,7 +17,6 @@ g.net rect{ .netgraph g text { text-anchor: middle; dominant-baseline: text-before-edge; - cursor: default !important; } .netgraph g text:active { @@ -63,3 +62,18 @@ rect.view{ dominant-baseline: text-before-edge; } +.node:hover > rect{ + filter: url(#drop-shadow); +} + +.ens:hover > g{ + filter: url(#drop-shadow); +} + +.net:hover > rect{ + filter: url(#drop-shadow); +} + +.conn:hover{ + filter: url(#drop-shadow); +} diff --git a/nengo_gui/static/components/netgraph.js b/nengo_gui/static/components/netgraph.js index 8b50075c..8d5649d8 100644 --- a/nengo_gui/static/components/netgraph.js +++ b/nengo_gui/static/components/netgraph.js @@ -16,6 +16,12 @@ Nengo.NetGraph = function(parent, args) { this.offsetX = 0; // global x,y pan offset this.offsetY = 0; + // override the random position chosen by the server for a new item. + // This is used by the drag-and-drop system to ensure that the newly + // created items appear at the position they were dropped at. + this.override_positions = {}; + this.override_sizes = {} + var scale = 1.0; Object.defineProperty(this, 'scale', { // global scaling factor @@ -127,7 +133,7 @@ Nengo.NetGraph = function(parent, args) { this.width = $(this.svg).width(); this.height = $(this.svg).height(); - + this.tool_height = $(toolbar.toolbar).height(); /** three separate layers, so that expanded networks are at the back, @@ -157,6 +163,9 @@ Nengo.NetGraph = function(parent, args) { defs.appendChild(s); this.svg.insertBefore(defs, this.svg.firstChild); + Nengo.create_filter([1,1,1],2,"drop-shadow"); + Nengo.create_filter([2,0,0],1,"red-drop-shadow"); + Nengo.create_filter([0,2,0],1,"green-drop-shadow"); /** connect to server */ this.ws = Nengo.create_websocket(args.uid); @@ -303,8 +312,10 @@ Nengo.NetGraph = function(parent, args) { if (self.menu.visible_any()) { self.menu.hide_any(); } else { - self.menu.show(event.clientX, event.clientY, - self.generate_menu()); + if(Nengo.vpl.edit_mode == false){ + self.menu.show(event.clientX, event.clientY, + self.generate_menu()); + } } }); @@ -348,9 +359,9 @@ Nengo.NetGraph.prototype.on_message = function(event) { item.y = data.pos[1]; item.width = data.size[0]; item.height = data.size[1]; - + item.redraw(); - + this.scaleMiniMap(); } else if (data.type === 'config') { @@ -373,7 +384,8 @@ Nengo.NetGraph.prototype.on_message = function(event) { if (item === undefined) { item = this.svg_conns[data.uid]; } - + delete this.override_positions[data.uid]; + delete this.override_sizes[data.uid]; item.remove(); } else if (data.type === 'reconnect') { @@ -447,6 +459,12 @@ Nengo.NetGraph.prototype.create_object = function(info) { var item_mini = new Nengo.NetGraphItem(this, info, true); this.minimap_objects[info.uid] = item_mini; + if(this.override_positions.hasOwnProperty(info.uid)){ + info.pos[0] = this.override_positions[info.uid][0]; + info.pos[1] = this.override_positions[info.uid][1]; + info.size[0] = this.override_sizes[info.uid][0]; + info.size[1] = this.override_sizes[info.uid][1]; + } var item = new Nengo.NetGraphItem(this, info, false, item_mini); this.svg_objects[info.uid] = item; @@ -461,7 +479,7 @@ Nengo.NetGraph.prototype.create_object = function(info) { Nengo.NetGraph.prototype.create_connection = function(info) { var conn_mini = new Nengo.NetGraphConnection(this, info, true); this.minimap_conns[info.uid] = conn_mini; - + var conn = new Nengo.NetGraphConnection(this, info, false, conn_mini); this.svg_conns[info.uid] = conn; }; @@ -480,7 +498,7 @@ Nengo.NetGraph.prototype.on_resize = function(event) { var new_width = item.get_screen_width() / this.scale; var new_height = item.get_screen_height() / this.scale; item.width = new_width/(2*width); - item.height = new_height/(2*height); + item.height = new_height/(2*height); } } } @@ -583,7 +601,7 @@ Nengo.NetGraph.prototype.create_minimap = function () { this.mm_width = $(this.minimap).width(); this.mm_height = $(this.minimap).height(); - + // default display minimap this.mm_display = true; this.toggleMiniMap(); @@ -665,7 +683,7 @@ Nengo.NetGraph.prototype.scaleMiniMap = function () { * main viewport and scale the viewbox to reflect that. */ Nengo.NetGraph.prototype.scaleMiniMapViewBox = function () { if (!this.mm_display) { return; } - + var mm_w = this.mm_width var mm_h = this.mm_height diff --git a/nengo_gui/static/components/netgraph_conn.js b/nengo_gui/static/components/netgraph_conn.js index b137102c..92937e0c 100644 --- a/nengo_gui/static/components/netgraph_conn.js +++ b/nengo_gui/static/components/netgraph_conn.js @@ -57,6 +57,19 @@ Nengo.NetGraphConnection = function(ng, info, minimap, mini_conn) { /** create the line and its arrowhead marker */ this.g = ng.createSVGElement('g'); + this.g.classList.add('conn'); + this.menu = new Nengo.Menu(this.ng.parent); + var self = this; + $(this.g).bind('contextmenu', function(event) { + event.preventDefault(); + event.stopPropagation(); + if (self.menu.visible_any()) { + self.menu.hide_any(); + } else { + self.menu.show(event.clientX, event.clientY, + self.generate_menu()); + } + }); this.create_line(); @@ -190,7 +203,7 @@ Nengo.NetGraphConnection.prototype.find_post = function() { Nengo.NetGraphConnection.prototype.set_pres = function(pres) { this.pres = pres; this.set_pre(this.find_pre()); - + if (!this.minimap) { this.mini_conn.set_pres(pres); } @@ -198,7 +211,7 @@ Nengo.NetGraphConnection.prototype.set_pres = function(pres) { Nengo.NetGraphConnection.prototype.set_posts = function(posts) { this.posts = posts; this.set_post(this.find_post()); - + if (!this.minimap) { this.mini_conn.set_posts(posts); } @@ -236,7 +249,7 @@ Nengo.NetGraphConnection.prototype.remove = function() { this.removed = true; delete this.ng.svg_conns[this.uid]; - + if (!this.minimap) { this.mini_conn.remove(); } @@ -341,7 +354,7 @@ Nengo.NetGraphConnection.prototype.redraw = function() { this.marker.setAttribute('transform', 'translate(' + mx + ',' + my + ') rotate('+ angle +')'); } - + if (!this.minimap && this.ng.mm_display) { this.mini_conn.redraw(); } @@ -372,3 +385,12 @@ Nengo.NetGraphConnection.prototype.intersect_length = function(theta, alpha, wid return [x,y]; } + +Nengo.NetGraphConnection.prototype.generate_menu = function(){ + var self = this; + var items = []; + items.push(['Delete Connection',function(){ + Nengo.vpl.delete_connection(self.pre.uid,self.post.uid); + }]); + return items; +} diff --git a/nengo_gui/static/components/netgraph_item.js b/nengo_gui/static/components/netgraph_item.js index 63a239cd..169b16a5 100644 --- a/nengo_gui/static/components/netgraph_item.js +++ b/nengo_gui/static/components/netgraph_item.js @@ -12,7 +12,7 @@ */ Nengo.NetGraphItem = function(ng, info, minimap, mini_item) { var self = this; - + this.ng = ng; this.type = info.type; this.uid = info.uid; @@ -22,6 +22,9 @@ Nengo.NetGraphItem = function(ng, info, minimap, mini_item) { this.fixed_width = null; this.fixed_height = null; this.dimensions = info.dimensions; + if(info.type == 'ens'){ + this.n_neurons = info.n_neurons; + } this.minimap = minimap; this.html_node = info.html; if (minimap == false) { @@ -32,7 +35,7 @@ Nengo.NetGraphItem = function(ng, info, minimap, mini_item) { this.g_networks = ng.g_networks_mini; this.g_items = ng.g_items_mini; } - + var width = info.size[0]; Object.defineProperty(this, 'width', { get: function() { @@ -40,7 +43,7 @@ Nengo.NetGraphItem = function(ng, info, minimap, mini_item) { }, set: function(val) { width = val; - + if (!this.minimap) { this.mini_item.width = val } @@ -53,7 +56,7 @@ Nengo.NetGraphItem = function(ng, info, minimap, mini_item) { }, set: function(val) { height = val; - + if (!this.minimap) { this.mini_item.height = val } @@ -66,7 +69,7 @@ Nengo.NetGraphItem = function(ng, info, minimap, mini_item) { }, set: function(val) { x = val; - + if (!this.minimap) { this.mini_item.x = val } @@ -79,13 +82,13 @@ Nengo.NetGraphItem = function(ng, info, minimap, mini_item) { }, set: function(val) { y = val; - + if (!this.minimap) { this.mini_item.y = val } } }); - + /** if this is a network, the children list is the set of NetGraphItems * and NetGraphConnections that are inside this network */ this.children = []; @@ -119,6 +122,7 @@ Nengo.NetGraphItem = function(ng, info, minimap, mini_item) { var g = this.ng.createSVGElement('g'); this.g = g; this.g_items.appendChild(g); + g.setAttribute('data-id',this.uid); g.classList.add(this.type); this.area = this.ng.createSVGElement('rect'); @@ -164,7 +168,7 @@ Nengo.NetGraphItem = function(ng, info, minimap, mini_item) { g.appendChild(this.area); this.redraw(); - + interact.margin(10); if (!this.minimap) { @@ -197,6 +201,7 @@ Nengo.NetGraphItem = function(ng, info, minimap, mini_item) { onend: function(event) { var item = self.ng.svg_objects[uid]; item.constrain_position(); + self.ng.override_positions[uid] = [item.x,item.y]; self.ng.notify({act:"pos", uid:uid, x:item.x, y:item.y}); item.redraw(); @@ -283,7 +288,7 @@ Nengo.NetGraphItem = function(ng, info, minimap, mini_item) { } item.redraw(); - + if (self.depth === 1) { self.ng.scaleMiniMap(); } @@ -292,6 +297,7 @@ Nengo.NetGraphItem = function(ng, info, minimap, mini_item) { var item = self.ng.svg_objects[uid]; item.constrain_position(); item.redraw(); + self.ng.override_sizes[uid] = [item.width,item.height]; self.ng.notify({act:"pos_size", uid:uid, x:item.x, y:item.y, width:item.width, height:item.height}); @@ -410,6 +416,7 @@ Nengo.NetGraphItem.prototype.generate_menu = function () { function() {self.create_graph('SpaSimilarity', self.sp_targets[0]);}]); } // TODO: Enable input and output value plots for basal ganglia network + items.push(['Delete Component',function(){Nengo.vpl.delete_component(self.uid)}]); items.push(['Details ...', function() {self.create_modal();}]); return items; }; @@ -581,7 +588,7 @@ Nengo.NetGraphItem.prototype.remove = function() { if (this.depth == 1) { this.ng.scaleMiniMap(); } - + if (!this.minimap) { this.mini_item.remove(); } @@ -616,10 +623,10 @@ Nengo.NetGraphItem.prototype.constrain_position = function() { if (this.parent !== null) { this.width = Math.min(0.5, this.width); this.height = Math.min(0.5, this.height); - + this.x = Math.min(this.x, 1.0-this.width); this.x = Math.max(this.x, this.width); - + this.y = Math.min(this.y, 1.0-this.height); this.y = Math.max(this.y, this.height); } @@ -732,7 +739,7 @@ Nengo.NetGraphItem.prototype.redraw_size = function() { Nengo.NetGraphItem.prototype.get_screen_width = function() { if (this.minimap && !this.ng.mm_display) { return 1; } - + if (this.fixed_width !== null) { return this.fixed_width; } @@ -754,7 +761,7 @@ Nengo.NetGraphItem.prototype.get_screen_width = function() { Nengo.NetGraphItem.prototype.get_screen_height = function() { if (this.minimap && !this.ng.mm_display) { return 1; } - + if (this.fixed_height !== null) { return this.fixed_height; } @@ -782,9 +789,9 @@ Nengo.NetGraphItem.prototype.redraw = function() { this.redraw_children(); this.redraw_child_connections(); this.redraw_connections(); - - if (!this.minimap && this.ng.mm_display) { - this.mini_item.redraw() + + if (!this.minimap && this.ng.mm_display) { + this.mini_item.redraw() } } @@ -793,7 +800,7 @@ Nengo.NetGraphItem.prototype.redraw = function() { Nengo.NetGraphItem.prototype.get_screen_location = function() { // FIXME this should probably use this.ng.get_scaled_width and this.ng.get_scaled_height if (this.minimap && !this.ng.mm_display) { return [1, 1]; } - + if (this.minimap == false) { var w = this.ng.width * this.ng.scale; var h = this.ng.height * this.ng.scale; diff --git a/nengo_gui/static/drag_and_drop.css b/nengo_gui/static/drag_and_drop.css new file mode 100644 index 00000000..77f53a2f --- /dev/null +++ b/nengo_gui/static/drag_and_drop.css @@ -0,0 +1,117 @@ +.drag_drop_menu{ + height:100%; + background: #ddd; + color:inherit; + overflow: hidden; +} + +.off{ + cursor: pointer; + background: inherit; + fill: #DDD; + -webkit-transition-duration: 0.4s; /* Safari */ + transition-duration: 0.2s; +} + +.off:hover{ + transform: scale(1.2); + fill: #DDD; + color: #000; +} + +.on{ + transition-duration: 0.2s; + cursor: pointer; + background-color: #CCC; + fill: #DDD; + color: #000; + box-shadow: inset 0 0 5px #000; +} + +#mode_list{ + position: relative; + top: 0; + left: 0; + width: 100%; + color: inherit; + z-index: 99999; + padding: 2px; +} + +.mode{ + position: relative; + margin: 2px; + float: left; + width: 80px; + height: 80px; + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ +} +.mode > div{ + text-align: center; + position: relative; + margin: 0 auto; + width: 100%; + height: 100%; +} + +.mode > div >svg{ + width: 100%; +} + +.mode > div > p{ + position: absolute; + bottom: -13%; + margin-left: auto; + margin-right: auto; + left: 0; + right: 0; + display: none; + font-size: 12px; +} + +.mode > div > span{ + position: absolute; + margin-left: auto; + margin-right: auto; + left: 0; + right: 0; + top: 25%; + font-size: 2em; +} + +.mode-group { + overflow: hidden; +} + +.mode-group > p { + border-bottom: 1px solid black; +} + +.node_effect_good > rect{ + filter: url(#green-drop-shadow) !important; +} + +.node_effect_bad > rect{ + filter: url(#red-drop-shadow) !important; +} + +.ens_effect_good > g{ + filter: url(#green-drop-shadow) !important; +} + +.node.red_hover:hover rect { + filter: url(#red-drop-shadow) !important; +} + +.ens.red_hover:hover > g{ + filter: url(#red-drop-shadow) !important; +} + +.net.red_hover:hover > rect{ + filter: url(#red-drop-shadow) !important; +} + +.conn.red_hover:hover { + filter: url(#red-drop-shadow); +} diff --git a/nengo_gui/static/drag_and_drop.js b/nengo_gui/static/drag_and_drop.js new file mode 100644 index 00000000..271feb01 --- /dev/null +++ b/nengo_gui/static/drag_and_drop.js @@ -0,0 +1,645 @@ +Nengo.VPL = function(){ + var self = this; + self.edit_mode = false; + + $("#mode_list").height($(document).height()*0.85); + $(document).keyup(function(e) { + if (e.keyCode === 27) self.unselect_component("Pointer"); // esc + }); + + //Setting Click handlers for toggle buttons + $("#Delete").on('click',function(){ + self.component_toggle('Delete'); + }) + $("#Pointer").on('click',function(){ + self.unselect_component('Pointer'); + }) + $("#Node").on('click',function(){ + self.component_toggle('Node'); + }); + + $("#Ensemble").on('click',function(){ + self.component_toggle('Ensemble'); + }); + + $("#Network").on('click',function(){ + self.component_toggle('Network'); + }); + + $("#Connection").on('click',function(){ + self.component_toggle('Connection'); + }); +} + +/** Activates type mode for creating components */ +Nengo.VPL.prototype.component_toggle = function(type) { + var ng = Nengo.netgraph; + var self = this; + + /** Activates and deactivates modes, accordingly */ + if($("#"+type).attr('class') == 'mode off'){ + self.unselect_component(); + $("#"+type).attr('class','mode on'); + } else{ + self.unselect_component("Pointer"); + return; + } + + /** Activates component or connection mode */ + if($("#"+type).attr('class') == 'mode on'){ + var cur_obj = ""; + var obj_class = ""; + var obj_name = ""; + var element = document.getElementById('main'); + /** Adds green border and deactivates click and drag events. */ + element.style['box-shadow'] = "inset 0 0 30px #5da3ff"; + self.toggle_item_drag(false); + if(type != 'Connection' && type != "Delete"){ + $('#netgraph').css('cursor','crosshair'); + self.create_component(type); + } else if(type == 'Connection'){ + $('#netgraph').css('cursor','cell'); + self.create_connection(); + } else if(type == 'Delete'){ + $('.node').attr('class','node red_hover'); + $('.ens').attr('class','ens red_hover'); + $('.net').attr('class','net red_hover'); + $('.conn').attr('class','conn red_hover'); + $('#netgraph').css('cursor','pointer'); + self.delete_mode(); + } + } +} + +/** Creates the interaction, click events, and visuals for drawing and creation + of components. type - one of ['Node','Ensemble'.'Network']*/ +Nengo.VPL.prototype.create_component = function(type){ + var ng = Nengo.netgraph; + var self = this; + var cur_obj, obj_class,obj_name; + var cursor_uid = "cursor."+type; + var name_convert = {"Node":"node","Ensemble":"ens","Network":"net"} + var element = document.getElementById('main'); + + $(document).mousedown(function(event) { + /** Cancels/deactivates click event if it is a right click. */ + if(event.button == 2){ + self.unselect_component("Pointer"); + return; + } + + cur_obj = $($(event.target).parent()[0]); + obj_class = cur_obj.attr('class'); + obj_name = cur_obj.attr('data-id'); + + /** Checks if user click is within the netgraph. */ + if (event.pageX - $("#main").offset().left < 0 || + event.pageX - $("#main").offset().left > $("#main").width() - 80 || + event.pageY - $("#main").offset().top < 0 || + event.pageY - $("#main").offset().top > $("#main").height() - 80) { + return; + } + var cursor_start = self.compute_position(event); + var cursor_width, cursor_height, cursor_end, final_pos, final_size; + var mousemoved = false; + + if(obj_class.indexOf('net') != -1){ + event.parent_network = obj_name; + } else{ + event.parent_network = null; + } + + /** Creates temporary cursor component. */ + ng.create_object({'pos':[-1000,-1000],'size': + [0,0],'uid':cursor_uid, + 'type':name_convert[type],'parent':null,'label':""}); + var cursor_item = ng.svg_objects[cursor_uid]; + cursor_item.shape.style['fill-opacity'] = 0.4; + cursor_item.x = cursor_start[0]; + cursor_item.y = cursor_start[1]; + + /** Activates if the event is a click and drag. */ + $(document).mousemove(function(evt) { + + mousemoved = true; + + cursor_end = self.compute_position(evt); + cursor_width = Math.abs(cursor_end[0] - cursor_start[0]); + cursor_height = Math.abs(cursor_end[1] - cursor_start[1]); + cursor_item.x = cursor_start[0] + (cursor_end[0] - cursor_start[0])/2; + cursor_item.y = cursor_start[1] + (cursor_end[1] - cursor_start[1])/2 + cursor_item.width = cursor_width/2; + cursor_item.height = cursor_height/2; + cursor_item.constrain_position(); + cursor_item.redraw(); + final_pos = [cursor_item.x,cursor_item.y]; + final_size = [cursor_item.width,cursor_item.height]; + return false; + }); + + /** On drag end */ + $(document).one('mouseup', function() { + ng.on_message({"data":JSON.stringify({"type":"remove","uid":cursor_uid})}); + + if(mousemoved == true){ + /** Checks if component is larger than minimum size. */ + if(cursor_width > 0.08 && cursor_height > 0.08){ + /** Converts all properties from the cursor component + * to the new component. */ + var obj_prop = {}; + obj_prop.pos = self.compute_position({"parent_network":event.parent_network, + "component_pos":final_pos}); + obj_prop.size = self.compute_size(type,{"parent_network":event.parent_network, + "component_size":final_size}); + obj_prop.parent_network = event.parent_network; + + var comp_name = self.add_component(type,obj_prop); + console.log(comp_name); + /** Checks every 100ms until the component shows up in + * the netgraph and notifies the server. */ + var checkExist = setInterval(function() { + if (ng.svg_objects[comp_name] != null) { + var new_comp = ng.svg_objects[comp_name]; + self.toggle_item_drag(false); + new_comp.constrain_position(); + ng.redraw(); + ng.override_positions[comp_name] = [new_comp.x,new_comp.y]; + ng.override_sizes[comp_name] = [new_comp.width,new_comp.height]; + ng.notify({act:"pos_size", uid:comp_name, x:new_comp.x, + y:new_comp.y, width: new_comp.width, + height: new_comp.height}); + + + clearInterval(checkExist); + } + }, 100); + } + $(document).unbind('mousemove mouseup'); + } + }); + }); +} +/** Creates the interaction, click events, and visuals for drawing and creation + of connections.*/ +Nengo.VPL.prototype.create_connection = function(){ + var self = this; + var cur_obj = ""; + var obj_class = ""; + var obj_name = ""; + var ng = Nengo.netgraph; + var mousemove = false; + /** Creates dashed line to signify potential connection. */ + self.delete_dashed_line(); + self.create_dash_line(); + + var objects = {}; + objects.uids = []; + objects.classes = []; + + self.show_connectable(true,true); + /** On main click and not drag. */ + $(document).mousedown(function(event) { + if(event.button == 2){ + self.unselect_component("Pointer"); + return; + } + cur_obj = $($(event.target).parent()[0]); + if(cur_obj.attr('class') != null){ + obj_class = cur_obj.attr('class').split(" ")[0]; + } + obj_name = cur_obj.attr('data-id'); + var rel_pos = []; + var obj_pos; + var over_obj_pos; + + /** Adds first part of potential connection if + * it is an ensemble or node. */ + if(obj_class != null && (obj_class == 'ens' || + obj_class == 'node')){ + objects.uids.push(obj_name); + objects.classes.push(obj_class); + } + if(objects.uids.length == 0){ + return; + } else{ + self.show_connectable(true,false); + } + $(document).mousemove(function(evt) { + mousemove = true; + /** After the first component is selected. */ + rel_pos = []; + obj_pos = []; + over_obj_pos = []; + var over_obj_svg; + var over_obj_name; + var over_obj_class; + + /** Updates cursor connection line's + * position. */ + var page_offset = $("#main").offset(); + obj_pos = ng.svg_objects[objects.uids[0]].get_screen_location(); + over_obj_svg = $($(evt.target).parent()[0]); + over_obj_name = over_obj_svg.attr('data-id'); + if(over_obj_svg.attr('class') != null){ + over_obj_class = over_obj_svg.attr('class').split(" ")[0]; + } + + $(".temp_line").attr('x1',obj_pos[0]); + $(".temp_line").attr('y1',obj_pos[1]); + if(over_obj_class != null && (over_obj_class == 'ens'|| + over_obj_class == 'node')){ + rel_pos = []; + over_obj_pos = ng.svg_objects[over_obj_name].get_screen_location(); + + rel_pos[0] = over_obj_pos[0]-0.07*(over_obj_pos[0]-obj_pos[0]); + rel_pos[1] = over_obj_pos[1]-0.07*(over_obj_pos[1]-obj_pos[1]); + + $(".temp_line").attr('x2',(rel_pos[0])); + $(".temp_line").attr('y2',(rel_pos[1])); + + }else{ + rel_pos[0] = evt.pageX-page_offset.left; + rel_pos[1] = evt.pageY-page_offset.top; + $(".temp_line").attr('x2',rel_pos[0]); + $(".temp_line").attr('y2',rel_pos[1]); + } + }); + $(document).one('mouseup', function(evt) { + /** Completes Connection, adds appropriate + * code to editor and resets line. */ + cur_obj = $($(evt.target).parent()[0]); + if(cur_obj.attr('class') != null){ + obj_class = cur_obj.attr('class').split(" ")[0]; + } + obj_name = cur_obj.attr('data-id'); + + /** Adds first part of potential connection if + * it is an ensemble or node. */ + if(rel_pos != false){ + if(obj_class != null && (obj_class == 'ens' || + obj_class == 'node')){ + objects.uids.push(obj_name); + objects.classes.push(obj_class); + } + if(objects.uids.length == 2){ + self.add_connection(objects.uids[0],objects.uids[1]); + } + } + objects = {}; + objects.uids = []; + objects.classes = []; + self.delete_dashed_line(); + self.create_dash_line(); + self.show_connectable(true,true); + $(document).unbind('mousemove mouseup'); + }); + }); +} + +/** Creates the interaction, click events, and visuals for drawing and creation + of deleting objects.*/ +Nengo.VPL.prototype.delete_mode = function(){ + var self = this; + var cur_obj = ""; + var obj_class = ""; + var obj_name = ""; + $(document).mousemove(function(event) { + $('.node').attr('class','node red_hover'); + $('.ens').attr('class','ens red_hover'); + $('.net').attr('class','net red_hover'); + $('.conn').attr('class','conn red_hover'); + cur_obj = $($(event.target).parent()[0]); + if(cur_obj.attr('class') != null){ + obj_class = cur_obj.attr('class').split(" ")[0]; + } + obj_name = cur_obj.attr('data-id'); + if(obj_name != null){ + $("#netgraph").css('cursor','pointer'); + }else{ + $("#netgraph").css('cursor',''); + } + $(document).click(function(evt){ + if(event.button == 2){ + self.unselect_component("Pointer"); + return; + } + if(obj_name != null){ + self.delete_component(obj_name); + } + }); + }); +} + +/** Deactivates the various styling and event handlers activated by the +* component toggle. */ +Nengo.VPL.prototype.unselect_component = function(default_mode){ + var ng = Nengo.netgraph; + this.toggle_item_drag(true); + $(".mode").attr('class','mode off'); + $('#netgraph').css('cursor',''); + if(default_mode == "Pointer"){ + $("#"+default_mode).attr('class','mode on'); + } + this.show_connectable(false,false); + this.delete_dashed_line(); + $(document).unbind('mousedown mousemove mouseup click'); + var element = document.getElementById('main'); + element.style['box-shadow'] = ""; +} + +/** Creates and inserts the appropriate code for the type of connection */ +Nengo.VPL.prototype.add_connection = function(obj1,obj2){ + var ng = Nengo.netgraph; + var item1 = ng.svg_objects[obj1]; + var item2 = ng.svg_objects[obj2]; + var dim_out = item1.dimensions; + var dim_in = 0; + + if(item2.type == "node" && item2.passthrough == null){ + dim_in = 0; + } else{ + dim_in = item2.dimensions; + } + + var component_code = "nengo.Connection("+obj1+", "+obj2+")"; + if (dim_out > dim_in){ + component_code = "nengo.Connection("+obj1+"[:"+dim_in+"], "+obj2+")"; + } else if (dim_out < dim_in) { + component_code = "nengo.Connection("+obj1+", "+obj2+"[:"+dim_out+"])"; + } + + if(dim_in != 0){ + var tab = " "; + var editor = ace.edit('editor'); + var last_line = editor.session.getLength() + 1; + + editor.session.insert({"row":editor.session.getLength()+1,"column":0} + ,"\n"); + editor.session.insert({"row":editor.session.getLength()+1,"column":0} + ,tab + component_code); + } +} + +/** Creates and inserts the appropriate code for the type of connection */ +Nengo.VPL.prototype.add_component = function(type, info) { + var tab = " "; + var ng = Nengo.netgraph; + var editor = ace.edit('editor'); + var last_line = editor.session.getLength() + 1; + var obj_names = ng.svg_objects; + var size = [] + var component_code = ""; + var component_name = ""; + var code_str = ""; + var name_switch = {"Ensemble":"ensemble","Node":"node","Network":"network"}; + var parent_net = ng.svg_objects[info.parent_network]; + + if(info.parent_network == null || info.parent_network == "cursor.Network"){ + component_name = this.open_name(name_switch[type]); + } else{ + component_name = this.open_name(info.parent_network+"."+name_switch[type]); + } + + if (type == "Ensemble") { + code_str = " = nengo.Ensemble(n_neurons=50, dimensions=1)"; + } else if (type == "Node") { + code_str = " = nengo.Node([0])"; + } else if (type == "Network") { + code_str = " = nengo.Network()"; + } + component_code = component_name + code_str; + ng.override_sizes[component_name] = info.size; + ng.override_positions[component_name] = info.pos; + + if(info.parent_network == null || info.parent_network == "cursor.Network"){ + editor.session.insert({"row":last_line+1,"column":0},"\n"); + editor.session.insert({"row":last_line+1,"column":0},tab + component_code); + } else{ + var selection = editor.find("with "+info.parent_network+":"); + if(selection != null){ + editor.replace("with "+info.parent_network+":\n"+tab+tab+ + component_code); + } else{ + editor.session.insert({"row":editor.session.getLength()+1,"column":0} + ,"\n"); + editor.session.insert({"row":last_line+1,"column":0}, + tab+"with "+info.parent_network+":\n"+tab+tab+ + component_code+"\n"+tab+tab+"pass"); + } + } + return component_name; +} + +/** Computes the position of a component based on the click event. */ +Nengo.VPL.prototype.compute_position = function(event) { + var ng = Nengo.netgraph; + var pos = []; + //Handles mouse position Transform + var w = ng.get_scaled_width(); + var offsetX = ng.offsetX * w; + var h = ng.get_scaled_height(); + var offsetY = ng.offsetY * h; + var page_offset = $("#main").offset(); + var parent, ratio_x, ratio_y; + var parent_pos = [1,1]; + if(event.component_pos == "" || event.component_pos == null){ + pos[0] = ((event.pageX - page_offset.left) - offsetX) / w; + pos[1] = ((event.pageY - page_offset.top) - offsetY) / h; + } else{ + pos = event.component_pos; + } + if(event.parent_network != "" && event.parent_network != null && + event.parent_network != "cursor.Network"){ + + var parent = ng.svg_objects[event.parent_network]; + var parent_size = this.compute_network_size(parent); + parent.expand(); + parent_pos[0] = (parent.get_screen_location()[0] - offsetX)/w; + parent_pos[1] = (parent.get_screen_location()[1] - offsetY)/h; + pos[0] = ((pos[0] - parent_pos[0])/parent_size[0] + 1)/2; + pos[1] = ((pos[1] - parent_pos[1])/parent_size[1] + 1)/2; + } + return pos; +} + +/** Computes the size of a component based on the click event. */ +Nengo.VPL.prototype.compute_size = function(type,event){ + var ng = Nengo.netgraph; + var size = []; + var parent_net = ng.svg_objects[event.parent_network]; + var component_size = event.component_size; + var parent_size = []; + size = [component_size[0],component_size[1]]; + if(event.parent_network != null && event.parent_network != "cursor.Network"){ + size= [size[0]/2,size[1]/2]; + parent_size = this.compute_network_size(parent_net); + size = [size[0]/parent_size[0],size[1]/parent_size[1]]; + } + return size; +} + +/** Computes the size of a network in the global coordinate system. */ +Nengo.VPL.prototype.compute_network_size = function (parent_net){ + var parent_size = [1,1]; + if (parent_net.parent != null) { + parent_size[0] = parent_net.width*2; + parent_size[1] = parent_net.height*2; + parent_net = parent_net.parent; + while(parent_net.parent != null){ + parent_size[0] *= parent_net.width*2; + parent_size[1] *= parent_net.height*2; + parent_net = parent_net.parent; + } + } + parent_size = [parent_net.width*parent_size[0], + parent_net.height*parent_size[1]]; + return parent_size; +} + +/** Finds the next available uid for a type of component */ +Nengo.VPL.prototype.open_name = function(name) { + var num = 1; + var editor = ace.edit('editor'); + while (editor.find(name + num + " =") != null) { + num ++; + } + return name + num; +} + +/** Adds css to show which connections are valid and which aren't */ +Nengo.VPL.prototype.show_connectable = function(toggle,allowed_connect){ + var node_style; + if(toggle == false){ + $(".node").attr("class","node"); + $(".ens").attr("class","ens"); + $(".net").attr("class","net"); + $(".conn").attr("class","conn"); + $(".node").unbind("mouseover mouseout"); + $(".ens").unbind("mouseover mouseout"); + return; + } + else{ + if(allowed_connect == true){ + node_style = 'node node_effect_good'; + } else{ + node_style = 'node node_effect_bad'; + } + $(".node").mouseover(function(){ + $(this).attr('class',node_style); + }).mouseout(function(){ + $(this).attr('class','node'); + }) + $(".ens").mouseover(function(){ + $(this).attr('class','ens ens_effect_good'); + }).mouseout(function(){ + $(this).attr('class','ens'); + }); + } +} + +Nengo.VPL.prototype.create_dash_line = function(){ + var svg = d3.select('#netgraph'); + var temp_g = svg.append('svg:g').attr("class","temp_group"); + var line = temp_g.append('svg:line') + .attr('stroke','#000000') + .attr('stroke-linecap','line') + .attr('stroke-dasharray',"20, 10") + .attr('stroke-width','10') + .attr('class','temp_line') + .attr('marker-end', 'url(#marker_arrow)'); + var marker = temp_g.append('svg:marker') + .attr('id', 'marker_arrow') + .attr('markerHeight', 6) + .attr('markerWidth', 6) + .attr('markerUnits', 'strokeWidth') + .attr('orient', 'auto') + .attr('refX', 0) + .attr('refY', 0) + .attr('viewBox', '-5 -5 10 10') + .append('svg:path') + .attr('d', 'M 0,0 m -5,-5 L 5,0 L -5,5 Z') + .attr('fill', '#000000') + +} + +Nengo.VPL.prototype.delete_dashed_line = function(){ + if($(".temp_group") != null){ + $(".temp_group").remove(); + } +} + +/** Activates/deactivates drag/resize events. */ +Nengo.VPL.prototype.toggle_item_drag = function(toggle){ + var self = this; + if(toggle){ + setTimeout(function(){ + self.edit_mode = false; + },100); + } else{ + setTimeout(function(){ + self.edit_mode = true; + },100); + } + interact($("#netgraph")[0]).draggable({ + enabled: toggle // explicitly disable dragging + }); + for(var item in Nengo.netgraph.svg_objects){ + interact(Nengo.netgraph.svg_objects[item].g).draggable({ + enabled: toggle // explicitly disable dragging + }); + interact(Nengo.netgraph.svg_objects[item].area).resizable({ + enabled: toggle // explicitly disable dragging + }); + } +} + +/** Deletes code that is used to create a nengo component */ +Nengo.VPL.prototype.delete_component = function(uid){ + var editor = ace.edit("editor"); + var re = new RegExp("(?:[^\.])"+uid+"\\s*="); + var component = Nengo.netgraph.svg_objects[uid]; + var start_with,end_with,net_children; + var self = this; + + this.delete_connections(uid); + var code_line = editor.find(re); + + while(code_line != null){ + editor.removeLines(code_line.start.row); + code_line = editor.find(re); + } + + if(component.type == "net"){ + var net_children = component.children; + for(var x = 0; x < net_children.length; x++){ + this.delete_component(net_children[x].uid); + } + + start_with = editor.find("with "+uid); + if(start_with != null){ + editor.removeLines(start_with.start.row); + end_with = editor.find("pass"); + editor.removeLines(end_with.start.row); + } + } +} + +/** Deletes all connections to a specific component */ +Nengo.VPL.prototype.delete_connections = function(uid){ + this.delete_connection(uid,""); +} + +/** Deletes a specific connection between two components */ +Nengo.VPL.prototype.delete_connection = function(uid1,uid2){ + var editor = ace.edit("editor"); + var re = new RegExp("nengo\.Connection\s*.*(?:[^\.])"+uid1+".*"+uid2); + var code_line = editor.find(re); + while(code_line){ + editor.removeLines(code_line.start.row); + code_line = editor.find(re); + } +} + +$(document).ready(function(){ + Nengo.vpl = new Nengo.VPL(); +}); diff --git a/nengo_gui/static/lib/css/bootstrap-slider.min.css b/nengo_gui/static/lib/css/bootstrap-slider.min.css new file mode 100644 index 00000000..1fecc069 --- /dev/null +++ b/nengo_gui/static/lib/css/bootstrap-slider.min.css @@ -0,0 +1,41 @@ +/*! ======================================================= + VERSION 9.4.1 +========================================================= */ +/*! ========================================================= + * bootstrap-slider.js + * + * Maintainers: + * Kyle Kemp + * - Twitter: @seiyria + * - Github: seiyria + * Rohit Kalkur + * - Twitter: @Rovolutionary + * - Github: rovolution + * + * ========================================================= + * + * bootstrap-slider is released under the MIT License + * Copyright (c) 2016 Kyle Kemp, Rohit Kalkur, and contributors + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * ========================================================= */.slider{display:inline-block;vertical-align:middle;position:relative}.slider.slider-horizontal{width:210px;height:20px}.slider.slider-horizontal .slider-track{height:10px;width:100%;margin-top:-5px;top:50%;left:0}.slider.slider-horizontal .slider-selection,.slider.slider-horizontal .slider-track-low,.slider.slider-horizontal .slider-track-high{height:100%;top:0;bottom:0}.slider.slider-horizontal .slider-tick,.slider.slider-horizontal .slider-handle{margin-left:-10px}.slider.slider-horizontal .slider-tick.triangle,.slider.slider-horizontal .slider-handle.triangle{position:relative;top:50%;transform:translateY(-50%);border-width:0 10px 10px 10px;width:0;height:0;border-bottom-color:#0480be;margin-top:0}.slider.slider-horizontal .slider-tick-container{white-space:nowrap;position:absolute;top:0;left:0;width:100%}.slider.slider-horizontal .slider-tick-label-container{white-space:nowrap;margin-top:20px}.slider.slider-horizontal .slider-tick-label-container .slider-tick-label{padding-top:4px;display:inline-block;text-align:center}.slider.slider-vertical{height:210px;width:20px}.slider.slider-vertical .slider-track{width:10px;height:100%;left:25%;top:0}.slider.slider-vertical .slider-selection{width:100%;left:0;top:0;bottom:0}.slider.slider-vertical .slider-track-low,.slider.slider-vertical .slider-track-high{width:100%;left:0;right:0}.slider.slider-vertical .slider-tick,.slider.slider-vertical .slider-handle{margin-top:-10px}.slider.slider-vertical .slider-tick.triangle,.slider.slider-vertical .slider-handle.triangle{border-width:10px 0 10px 10px;width:1px;height:1px;border-left-color:#0480be;margin-left:0}.slider.slider-vertical .slider-tick-label-container{white-space:nowrap}.slider.slider-vertical .slider-tick-label-container .slider-tick-label{padding-left:4px}.slider.slider-disabled .slider-handle{background-image:-webkit-linear-gradient(top,#dfdfdf 0,#bebebe 100%);background-image:-o-linear-gradient(top,#dfdfdf 0,#bebebe 100%);background-image:linear-gradient(to bottom,#dfdfdf 0,#bebebe 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdfdfdf',endColorstr='#ffbebebe',GradientType=0)}.slider.slider-disabled .slider-track{background-image:-webkit-linear-gradient(top,#e5e5e5 0,#e9e9e9 100%);background-image:-o-linear-gradient(top,#e5e5e5 0,#e9e9e9 100%);background-image:linear-gradient(to bottom,#e5e5e5 0,#e9e9e9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe5e5e5',endColorstr='#ffe9e9e9',GradientType=0);cursor:not-allowed}.slider input{display:none}.slider .tooltip.top{margin-top:-36px}.slider .tooltip-inner{white-space:nowrap;max-width:none}.slider .hide{display:none}.slider-track{position:absolute;cursor:pointer;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#f9f9f9 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#f9f9f9 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#f9f9f9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);border-radius:4px}.slider-selection{position:absolute;background-image:-webkit-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#f9f9f9 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border-radius:4px}.slider-selection.tick-slider-selection{background-image:-webkit-linear-gradient(top,#89cdef 0,#81bfde 100%);background-image:-o-linear-gradient(top,#89cdef 0,#81bfde 100%);background-image:linear-gradient(to bottom,#89cdef 0,#81bfde 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff89cdef',endColorstr='#ff81bfde',GradientType=0)}.slider-track-low,.slider-track-high{position:absolute;background:transparent;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border-radius:4px}.slider-handle{position:absolute;top:0;width:20px;height:20px;background-color:#337ab7;background-image:-webkit-linear-gradient(top,#149bdf 0,#0480be 100%);background-image:-o-linear-gradient(top,#149bdf 0,#0480be 100%);background-image:linear-gradient(to bottom,#149bdf 0,#0480be 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);filter:none;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);border:0 solid transparent}.slider-handle.round{border-radius:50%}.slider-handle.triangle{background:transparent none}.slider-handle.custom{background:transparent none}.slider-handle.custom::before{line-height:20px;font-size:20px;content:'\2605';color:#726204}.slider-tick{position:absolute;width:20px;height:20px;background-image:-webkit-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#f9f9f9 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;filter:none;opacity:.8;border:0 solid transparent}.slider-tick.round{border-radius:50%}.slider-tick.triangle{background:transparent none}.slider-tick.custom{background:transparent none}.slider-tick.custom::before{line-height:20px;font-size:20px;content:'\2605';color:#726204}.slider-tick.in-selection{background-image:-webkit-linear-gradient(top,#89cdef 0,#81bfde 100%);background-image:-o-linear-gradient(top,#89cdef 0,#81bfde 100%);background-image:linear-gradient(to bottom,#89cdef 0,#81bfde 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff89cdef',endColorstr='#ff81bfde',GradientType=0);opacity:1} \ No newline at end of file diff --git a/nengo_gui/static/lib/js/bootstrap-slider.min.js b/nengo_gui/static/lib/js/bootstrap-slider.min.js new file mode 100644 index 00000000..b926993a --- /dev/null +++ b/nengo_gui/static/lib/js/bootstrap-slider.min.js @@ -0,0 +1,5 @@ +/*! ======================================================= + VERSION 9.4.1 +========================================================= */ +"use strict";var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol?"symbol":typeof a},windowIsDefined="object"===("undefined"==typeof window?"undefined":_typeof(window));!function(a){if("function"==typeof define&&define.amd)define(["jquery"],a);else if("object"===("undefined"==typeof module?"undefined":_typeof(module))&&module.exports){var b;try{b=require("jquery")}catch(c){b=null}module.exports=a(b)}else window&&(window.Slider=a(window.jQuery))}(function(a){var b="slider",c="bootstrapSlider";windowIsDefined&&!window.console&&(window.console={}),windowIsDefined&&!window.console.log&&(window.console.log=function(){}),windowIsDefined&&!window.console.warn&&(window.console.warn=function(){});var d;return function(a){function b(){}function c(a){function c(b){b.prototype.option||(b.prototype.option=function(b){a.isPlainObject(b)&&(this.options=a.extend(!0,this.options,b))})}function e(b,c){a.fn[b]=function(e){if("string"==typeof e){for(var g=d.call(arguments,1),h=0,i=this.length;i>h;h++){var j=this[h],k=a.data(j,b);if(k)if(a.isFunction(k[e])&&"_"!==e.charAt(0)){var l=k[e].apply(k,g);if(void 0!==l&&l!==k)return l}else f("no such method '"+e+"' for "+b+" instance");else f("cannot call methods on "+b+" prior to initialization; attempted to call '"+e+"'")}return this}var m=this.map(function(){var d=a.data(this,b);return d?(d.option(e),d._init()):(d=new c(this,e),a.data(this,b,d)),a(this)});return!m||m.length>1?m:m[0]}}if(a){var f="undefined"==typeof console?b:function(a){console.error(a)};return a.bridget=function(a,b){c(b),e(a,b)},a.bridget}}var d=Array.prototype.slice;c(a)}(a),function(a){function e(b,c){function d(a,b){var c="data-slider-"+b.replace(/_/g,"-"),d=a.getAttribute(c);try{return JSON.parse(d)}catch(e){return d}}this._state={value:null,enabled:null,offset:null,size:null,percentage:null,inDrag:!1,over:!1},this.ticksCallbackMap={},this.handleCallbackMap={},"string"==typeof b?this.element=document.querySelector(b):b instanceof HTMLElement&&(this.element=b),c=c?c:{};for(var e=Object.keys(this.defaultOptions),f=0;f0)for(var s=0;s0){for(this.ticksContainer=document.createElement("div"),this.ticksContainer.className="slider-tick-container",f=0;f0)for(this.tickLabelContainer=document.createElement("div"),this.tickLabelContainer.className="slider-tick-label-container",f=0;f0&&(this.options.max=Math.max.apply(Math,this.options.ticks),this.options.min=Math.min.apply(Math,this.options.ticks)),Array.isArray(this.options.value)?(this.options.range=!0,this._state.value=this.options.value):this.options.range?this._state.value=[this.options.value,this.options.max]:this._state.value=this.options.value,this.trackLow=k||this.trackLow,this.trackSelection=j||this.trackSelection,this.trackHigh=l||this.trackHigh,"none"===this.options.selection&&(this._addClass(this.trackLow,"hide"),this._addClass(this.trackSelection,"hide"),this._addClass(this.trackHigh,"hide")),this.handle1=m||this.handle1,this.handle2=n||this.handle2,p===!0)for(this._removeClass(this.handle1,"round triangle"),this._removeClass(this.handle2,"round triangle hide"),f=0;f0){for(var d,e,f,g=0,h=1;hthis.options.max?this.options.max:k},toPercentage:function(a){if(this.options.max===this.options.min)return 0;if(this.options.ticks_positions.length>0){for(var b,c,d,e=0,f=0;f0?this.options.ticks[f-1]:0,d=f>0?this.options.ticks_positions[f-1]:0,c=this.options.ticks[f],e=this.options.ticks_positions[f];break}if(f>0){var g=(a-b)/(c-b);return d+g*(e-d)}}return 100*(a-this.options.min)/(this.options.max-this.options.min)}},logarithmic:{toValue:function(a){var b=0===this.options.min?0:Math.log(this.options.min),c=Math.log(this.options.max),d=Math.exp(b+(c-b)*a/100);return d=this.options.min+Math.round((d-this.options.min)/this.options.step)*this.options.step,dthis.options.max?this.options.max:d},toPercentage:function(a){if(this.options.max===this.options.min)return 0;var b=Math.log(this.options.max),c=0===this.options.min?0:Math.log(this.options.min),d=0===a?0:Math.log(a);return 100*(d-c)/(b-c)}}};d=function(a,b){return e.call(this,a,b),this},d.prototype={_init:function(){},constructor:d,defaultOptions:{id:"",min:0,max:10,step:1,precision:0,orientation:"horizontal",value:5,range:!1,selection:"before",tooltip:"show",tooltip_split:!1,handle:"round",reversed:!1,enabled:!0,formatter:function(a){return Array.isArray(a)?a[0]+" : "+a[1]:a},natural_arrow_keys:!1,ticks:[],ticks_positions:[],ticks_labels:[],ticks_snap_bounds:0,ticks_tooltip:!1,scale:"linear",focus:!1,tooltip_position:null,labelledby:null,rangeHighlights:[]},getElement:function(){return this.sliderElem},getValue:function(){return this.options.range?this._state.value:this._state.value[0]},setValue:function(a,b,c){a||(a=0);var d=this.getValue();this._state.value=this._validateInputValue(a);var e=this._applyPrecision.bind(this);this.options.range?(this._state.value[0]=e(this._state.value[0]),this._state.value[1]=e(this._state.value[1]),this._state.value[0]=Math.max(this.options.min,Math.min(this.options.max,this._state.value[0])),this._state.value[1]=Math.max(this.options.min,Math.min(this.options.max,this._state.value[1]))):(this._state.value=e(this._state.value),this._state.value=[Math.max(this.options.min,Math.min(this.options.max,this._state.value))],this._addClass(this.handle2,"hide"),"after"===this.options.selection?this._state.value[1]=this.options.max:this._state.value[1]=this.options.min),this.options.max>this.options.min?this._state.percentage=[this._toPercentage(this._state.value[0]),this._toPercentage(this._state.value[1]),100*this.options.step/(this.options.max-this.options.min)]:this._state.percentage=[0,0,100],this._layout();var f=this.options.range?this._state.value:this._state.value[0];return this._setDataVal(f),b===!0&&this._trigger("slide",f),d!==f&&c===!0&&this._trigger("change",{oldValue:d,newValue:f}),this},destroy:function(){this._removeSliderEventHandlers(),this.sliderElem.parentNode.removeChild(this.sliderElem),this.element.style.display="",this._cleanUpEventCallbacksMap(),this.element.removeAttribute("data"),a&&(this._unbindJQueryEventHandlers(),this.$element.removeData("slider"))},disable:function(){return this._state.enabled=!1,this.handle1.removeAttribute("tabindex"),this.handle2.removeAttribute("tabindex"),this._addClass(this.sliderElem,"slider-disabled"),this._trigger("slideDisabled"),this},enable:function(){return this._state.enabled=!0,this.handle1.setAttribute("tabindex",0),this.handle2.setAttribute("tabindex",0),this._removeClass(this.sliderElem,"slider-disabled"),this._trigger("slideEnabled"),this},toggle:function(){return this._state.enabled?this.disable():this.enable(),this},isEnabled:function(){return this._state.enabled},on:function(a,b){return this._bindNonQueryEventHandler(a,b),this},off:function(b,c){a?(this.$element.off(b,c),this.$sliderElem.off(b,c)):this._unbindNonQueryEventHandler(b,c)},getAttribute:function(a){return a?this.options[a]:this.options},setAttribute:function(a,b){return this.options[a]=b,this},refresh:function(){return this._removeSliderEventHandlers(),e.call(this,this.element,this.options),a&&a.data(this.element,"slider",this),this},relayout:function(){return this._resize(),this._layout(),this},_removeSliderEventHandlers:function(){if(this.handle1.removeEventListener("keydown",this.handle1Keydown,!1),this.handle2.removeEventListener("keydown",this.handle2Keydown,!1),this.options.ticks_tooltip){for(var a=this.ticksContainer.getElementsByClassName("slider-tick"),b=0;b=0?c:this.attributes["aria-valuenow"].value,e=parseInt(d,10);b.value[0]=e,b.percentage[0]=a.options.ticks_positions[e],a._setToolTipOnMouseOver(b),a._showTooltip()};return b.addEventListener("mouseenter",d,!1),d},addMouseLeave:function(a,b){var c=function(){a._hideTooltip()};return b.addEventListener("mouseleave",c,!1),c}}},_layout:function(){var a;if(a=this.options.reversed?[100-this._state.percentage[0],this.options.range?100-this._state.percentage[1]:this._state.percentage[1]]:[this._state.percentage[0],this._state.percentage[1]],this.handle1.style[this.stylePos]=a[0]+"%",this.handle1.setAttribute("aria-valuenow",this._state.value[0]),this.handle2.style[this.stylePos]=a[1]+"%",this.handle2.setAttribute("aria-valuenow",this._state.value[1]),this.rangeHighlightElements.length>0&&Array.isArray(this.options.rangeHighlights)&&this.options.rangeHighlights.length>0)for(var b=0;b0){var g="vertical"===this.options.orientation?"height":"width",h="vertical"===this.options.orientation?"marginTop":"marginLeft",i=this._state.size/(this.options.ticks.length-1);if(this.tickLabelContainer){var j=0;if(0===this.options.ticks_positions.length)"vertical"!==this.options.orientation&&(this.tickLabelContainer.style[h]=-i/2+"px"),j=this.tickLabelContainer.offsetHeight;else for(k=0;kj&&(j=this.tickLabelContainer.childNodes[k].offsetHeight);"horizontal"===this.options.orientation&&(this.sliderElem.style.marginBottom=j+"px")}for(var k=0;k=a[0]&&l<=a[1]&&this._addClass(this.ticks[k],"in-selection"):"after"===this.options.selection&&l>=a[0]?this._addClass(this.ticks[k],"in-selection"):"before"===this.options.selection&&l<=a[0]&&this._addClass(this.ticks[k],"in-selection"),this.tickLabels[k]&&(this.tickLabels[k].style[g]=i+"px","vertical"!==this.options.orientation&&void 0!==this.options.ticks_positions[k]?(this.tickLabels[k].style.position="absolute",this.tickLabels[k].style[this.stylePos]=l+"%",this.tickLabels[k].style[h]=-i/2+"px"):"vertical"===this.options.orientation&&(this.tickLabels[k].style.marginLeft=this.sliderElem.offsetWidth+"px",this.tickLabelContainer.style.marginTop=this.sliderElem.offsetWidth/2*-1+"px"))}}var m;if(this.options.range){m=this.options.formatter(this._state.value),this._setText(this.tooltipInner,m),this.tooltip.style[this.stylePos]=(a[1]+a[0])/2+"%","vertical"===this.options.orientation?this._css(this.tooltip,"margin-top",-this.tooltip.offsetHeight/2+"px"):this._css(this.tooltip,"margin-left",-this.tooltip.offsetWidth/2+"px"),"vertical"===this.options.orientation?this._css(this.tooltip,"margin-top",-this.tooltip.offsetHeight/2+"px"):this._css(this.tooltip,"margin-left",-this.tooltip.offsetWidth/2+"px");var n=this.options.formatter(this._state.value[0]);this._setText(this.tooltipInner_min,n);var o=this.options.formatter(this._state.value[1]);this._setText(this.tooltipInner_max,o),this.tooltip_min.style[this.stylePos]=a[0]+"%","vertical"===this.options.orientation?this._css(this.tooltip_min,"margin-top",-this.tooltip_min.offsetHeight/2+"px"):this._css(this.tooltip_min,"margin-left",-this.tooltip_min.offsetWidth/2+"px"),this.tooltip_max.style[this.stylePos]=a[1]+"%","vertical"===this.options.orientation?this._css(this.tooltip_max,"margin-top",-this.tooltip_max.offsetHeight/2+"px"):this._css(this.tooltip_max,"margin-left",-this.tooltip_max.offsetWidth/2+"px")}else m=this.options.formatter(this._state.value[0]),this._setText(this.tooltipInner,m),this.tooltip.style[this.stylePos]=a[0]+"%","vertical"===this.options.orientation?this._css(this.tooltip,"margin-top",-this.tooltip.offsetHeight/2+"px"):this._css(this.tooltip,"margin-left",-this.tooltip.offsetWidth/2+"px");if("vertical"===this.options.orientation)this.trackLow.style.top="0",this.trackLow.style.height=Math.min(a[0],a[1])+"%",this.trackSelection.style.top=Math.min(a[0],a[1])+"%",this.trackSelection.style.height=Math.abs(a[0]-a[1])+"%",this.trackHigh.style.bottom="0",this.trackHigh.style.height=100-Math.min(a[0],a[1])-Math.abs(a[0]-a[1])+"%";else{this.trackLow.style.left="0",this.trackLow.style.width=Math.min(a[0],a[1])+"%",this.trackSelection.style.left=Math.min(a[0],a[1])+"%",this.trackSelection.style.width=Math.abs(a[0]-a[1])+"%",this.trackHigh.style.right="0",this.trackHigh.style.width=100-Math.min(a[0],a[1])-Math.abs(a[0]-a[1])+"%";var p=this.tooltip_min.getBoundingClientRect(),q=this.tooltip_max.getBoundingClientRect();"bottom"===this.options.tooltip_position?p.right>q.left?(this._removeClass(this.tooltip_max,"bottom"),this._addClass(this.tooltip_max,"top"),this.tooltip_max.style.top="",this.tooltip_max.style.bottom="22px"):(this._removeClass(this.tooltip_max,"top"),this._addClass(this.tooltip_max,"bottom"),this.tooltip_max.style.top=this.tooltip_min.style.top,this.tooltip_max.style.bottom=""):p.right>q.left?(this._removeClass(this.tooltip_max,"top"),this._addClass(this.tooltip_max,"bottom"),this.tooltip_max.style.top="18px"):(this._removeClass(this.tooltip_max,"bottom"),this._addClass(this.tooltip_max,"top"),this.tooltip_max.style.top=this.tooltip_min.style.top)}},_createHighlightRange:function(a,b){return this._isHighlightRange(a,b)?a>b?{start:b,size:a-b}:{start:a,size:b-a}:null},_isHighlightRange:function(a,b){return a>=0&&100>=a&&b>=0&&100>=b?!0:!1},_resize:function(a){this._state.offset=this._offset(this.sliderElem),this._state.size=this.sliderElem[this.sizePos],this._layout()},_removeProperty:function(a,b){a.style.removeProperty?a.style.removeProperty(b):a.style.removeAttribute(b)},_mousedown:function(a){if(!this._state.enabled)return!1;this._state.offset=this._offset(this.sliderElem),this._state.size=this.sliderElem[this.sizePos];var b=this._getPercentage(a);if(this.options.range){var c=Math.abs(this._state.percentage[0]-b),d=Math.abs(this._state.percentage[1]-b);this._state.dragged=d>c?0:1,this._adjustPercentageForRangeSliders(b)}else this._state.dragged=0;this._state.percentage[this._state.dragged]=b,this._layout(),this.touchCapable&&(document.removeEventListener("touchmove",this.mousemove,!1),document.removeEventListener("touchend",this.mouseup,!1)),this.mousemove&&document.removeEventListener("mousemove",this.mousemove,!1),this.mouseup&&document.removeEventListener("mouseup",this.mouseup,!1),this.mousemove=this._mousemove.bind(this),this.mouseup=this._mouseup.bind(this),this.touchCapable&&(document.addEventListener("touchmove",this.mousemove,!1),document.addEventListener("touchend",this.mouseup,!1)),document.addEventListener("mousemove",this.mousemove,!1),document.addEventListener("mouseup",this.mouseup,!1),this._state.inDrag=!0;var e=this._calculateValue();return this._trigger("slideStart",e),this._setDataVal(e),this.setValue(e,!1,!0),this._pauseEvent(a),this.options.focus&&this._triggerFocusOnHandle(this._state.dragged),!0},_touchstart:function(a){if(void 0===a.changedTouches)return void this._mousedown(a);var b=a.changedTouches[0];this.touchX=b.pageX,this.touchY=b.pageY},_triggerFocusOnHandle:function(a){0===a&&this.handle1.focus(),1===a&&this.handle2.focus()},_keydown:function(a,b){if(!this._state.enabled)return!1;var c;switch(b.keyCode){case 37:case 40:c=-1;break;case 39:case 38:c=1}if(c){if(this.options.natural_arrow_keys){var d="vertical"===this.options.orientation&&!this.options.reversed,e="horizontal"===this.options.orientation&&this.options.reversed;(d||e)&&(c=-c)}var f=this._state.value[a]+c*this.options.step;return this.options.range&&(f=[a?this._state.value[0]:f,a?f:this._state.value[1]]),this._trigger("slideStart",f),this._setDataVal(f),this.setValue(f,!0,!0),this._setDataVal(f),this._trigger("slideStop",f),this._layout(),this._pauseEvent(b),!1}},_pauseEvent:function(a){a.stopPropagation&&a.stopPropagation(),a.preventDefault&&a.preventDefault(),a.cancelBubble=!0,a.returnValue=!1},_mousemove:function(a){if(!this._state.enabled)return!1;var b=this._getPercentage(a);this._adjustPercentageForRangeSliders(b),this._state.percentage[this._state.dragged]=b,this._layout();var c=this._calculateValue(!0);return this.setValue(c,!0,!0),!1},_touchmove:function(a){if(void 0!==a.changedTouches){var b=a.changedTouches[0],c=b.pageX-this.touchX,d=b.pageY-this.touchY;this._state.inDrag||("vertical"===this.options.orientation&&5>=c&&c>=-5&&(d>=15||-15>=d)?this._mousedown(a):5>=d&&d>=-5&&(c>=15||-15>=c)&&this._mousedown(a))}},_adjustPercentageForRangeSliders:function(a){if(this.options.range){var b=this._getNumDigitsAfterDecimalPlace(a);b=b?b-1:0;var c=this._applyToFixedAndParseFloat(a,b);0===this._state.dragged&&this._applyToFixedAndParseFloat(this._state.percentage[1],b)c&&(this._state.percentage[1]=this._state.percentage[0],this._state.dragged=0)}},_mouseup:function(){if(!this._state.enabled)return!1;this.touchCapable&&(document.removeEventListener("touchmove",this.mousemove,!1),document.removeEventListener("touchend",this.mouseup,!1)),document.removeEventListener("mousemove",this.mousemove,!1),document.removeEventListener("mouseup",this.mouseup,!1),this._state.inDrag=!1,this._state.over===!1&&this._hideTooltip();var a=this._calculateValue(!0);return this._layout(),this._setDataVal(a),this._trigger("slideStop",a),!1},_calculateValue:function(a){var b;if(this.options.range?(b=[this.options.min,this.options.max],0!==this._state.percentage[0]&&(b[0]=this._toValue(this._state.percentage[0]),b[0]=this._applyPrecision(b[0])),100!==this._state.percentage[1]&&(b[1]=this._toValue(this._state.percentage[1]),b[1]=this._applyPrecision(b[1]))):(b=this._toValue(this._state.percentage[0]),b=parseFloat(b),b=this._applyPrecision(b)),a){for(var c=[b,1/0],d=0;d self.minWidth || event.deltaRect.right > 0){ + self.width += event.deltaRect.right; + + $(".sidenav-container").width(self.width); + + $(self.tabs[self.current_tab]).width(self.width); + } + Nengo.netgraph.on_resize(); + }); + // file_browser $('#Open_file_button')[0].addEventListener('click', function() { if (!$(this).hasClass('deactivated') && file_browser_open == false) { @@ -46,19 +63,15 @@ Nengo.SideMenu = function() { var accord_num = document.getElementsByClassName('accordion-container') .length + 1; - for (var x = 1; x < accord_num; x++) { - $('#accordion-container' + String(x)) - .find('.accordion-toggle') - .click(function() { + $('.accordion-container').find('.accordion-toggle').click(function() { - // Expand or collapse this panel - $(this).next().slideToggle(); + // Expand or collapse this panel + $(this).next().slideToggle(); - // Hide the other panels - $(".accordion-content").not($(this).next()).slideUp(); + // Hide the other panels + $(".accordion-content").not($(this).next()).slideUp(); - }); - } + }); $(window).on('resize', function() {self.on_resize();}); self.on_resize(); @@ -89,12 +102,13 @@ Nengo.SideMenu.prototype.toggle_side_nav = function() { if (self.menu_open === false) { trans_val = "0"; self.menu_open = true; + element.style.display = "flex"; } else { - trans_val = String(-self.menu_width); + trans_val = String(-self.width); self.menu_open = false; + element.style.display = "none"; } - - element.style.transform = "translate(" + trans_val + "px)"; + Nengo.netgraph.on_resize(); }; Nengo.SideMenu.prototype.show_side_nav = function() { @@ -114,6 +128,9 @@ Nengo.SideMenu.prototype.hide_side_nav = function() { Nengo.SideMenu.prototype.menu_tab_click = function(it, pos_num, close_if_selected) { var self = this; var trans_func = function() { + + self.current_tab = pos_num; + if ($(it).hasClass('deactivated')) { return; } @@ -123,7 +140,14 @@ Nengo.SideMenu.prototype.menu_tab_click = function(it, pos_num, close_if_selecte } else { self.show_side_nav(); var element = document.getElementById("Menu_content"); - var trans_val = String(-self.menu_width * pos_num); + $(self.tabs[self.current_tab]).width(self.width); + $(".sidenav-container").width(self.width); + + var trans_width = 0; + for(var x = 0; x < pos_num; x++){ + trans_width += $(self.tabs[x]).width(); + } + var trans_val = String(-trans_width); element.style.transform = "translate(" + trans_val + "px)"; self.focus_reset(); $(it).addClass("selected"); @@ -141,6 +165,7 @@ Nengo.SideMenu.prototype.focus_reset = function() { /** This lets you browse the files available on the server */ Nengo.SideMenu.prototype.file_browser = function() { + var self = this; sim.ws.send('browse'); fb = $('#filebrowser'); @@ -152,7 +177,7 @@ Nengo.SideMenu.prototype.file_browser = function() { window.location.assign('/?filename=' + file); } ); - + $("#filebrowser").height(self.height-5); }; /** Export the layout to the SVG in Downloads folder **/ diff --git a/nengo_gui/static/vpl_config.css b/nengo_gui/static/vpl_config.css new file mode 100644 index 00000000..6788b5ae --- /dev/null +++ b/nengo_gui/static/vpl_config.css @@ -0,0 +1,32 @@ +#graph_container{ + position: relative; + width: 500px; + height: 270px; + margin-left: auto; + margin-right: auto; + left: 0px; +} + +#ensModalForm input.form-control { + height: 25px !important; + width: 40px !important; + padding: 2px; + margin-right: 15px; +} + +#ensModalForm .controls { + margin-bottom: 8px; +} + + +.slider-selection { + background: #5da3ff; +} + +.slider-track-high { + background: #EEE; +} + +.slider-handle { + background: #A6A6A6; +} diff --git a/nengo_gui/static/vpl_config.js b/nengo_gui/static/vpl_config.js new file mode 100644 index 00000000..92f164d0 --- /dev/null +++ b/nengo_gui/static/vpl_config.js @@ -0,0 +1,257 @@ +Nengo.VPLConfig = function(){ + this.$param_form = $('
'+ + '
'+ + '
'+ + '
'+ + '
'+ +'
'); + this.$model_form = $('
'+ + '
'+ + ' '+ + ''+ + '
'+ + '
'+ + '
'+ + '
') + this.neuron_params = {} + this.neuron_params['LIF'] = {tau_rc: {min:0,max:1,default:0.02,step:0.01}, + tau_ref: {min:0,max:0.01,default:0.002,step:0.001}, + min_voltage: {min:0,max:10,default:0,step:1}}; + this.graph_container = $('
'); + this.graph_w = 500; + this.radius = 1; + this.sliders = {}; +} + +Nengo.VPLConfig.prototype.ensemble_modal = function(){ + var self = this; + + Nengo.modal.clear_body(); + Nengo.modal.show() + Nengo.modal.title('Edit Ensemble');; + var tabs = Nengo.modal.tabbed_body([{id: 'params', title: 'Parameters'}, + {id: 'model', title: 'Neuron Model'}]); + this.graph_container.prependTo(".tab-content"); + self.$param_form.appendTo(tabs.params); + self.$model_form.appendTo(tabs.model); + + $("#ens_model").on("change",function(){ + var optionSelected = $("option:selected", this); + var value = this.value; + + for(var item in self.neuron_params[value]){ + console.log(item); + self.create_slider("#model_controls",item,item,{ + min: self.neuron_params[value][item]['min'], + max: self.neuron_params[value][item]['max'], + tooltip: "hide", + value: self.neuron_params[value][item]['default'], + step: self.neuron_params[value][item]['step'], + }); + } + }); + self.redraw_graph("#graph_container",[1,2,3],[[1,2,3],[4,5,6]],'radius','frequency') + self.create_slider("#param_controls","ens_dimension","Dimension",{ + min: 1, + max: 10, + tooltip: "hide", + value: 1, + step: 1, + id: "ens_dimensionC", + }); + self.create_slider("#param_controls","ex1","Radius",{ + min: 0.1, + max: 2, + tooltip: "hide", + value: 1, + step: 0.1, + id: "ex1C", + }); + self.create_slider("#param_controls","neuron_num","Neuron #",{ + min: 1, + max: 200, + tooltip: "hide", + value: 50, + step: 1, + id: "neuron_numC", + }); + + +} + +Nengo.VPLConfig.prototype.create_slider = function(parent_selector,new_id,label,options){ + var self = this; + var control_group = $('
') + .appendTo($(parent_selector)); + var label_tag = $('') + .appendTo($(control_group)); + var slide_val = $('') + .appendTo($(control_group)); + var slider_input = $('') + .appendTo($(control_group)); + + + self.sliders[new_id] = new Slider('#'+new_id,options); + slide_val.val(self.sliders[new_id].getValue()); + self.sliders[new_id].on("slide",function(){ + $(slide_val).val(self.sliders[new_id].getValue()); + }); + $(slide_val).on("change",function(){ + var max_val = self.sliders[new_id].getAttribute("max"); + var value = parseFloat($(this).val(),10); + if (value > max_val){ + self.sliders[new_id].setAttribute("max",value); + } + self.sliders[new_id].setValue(value); + }) + // var x_axis = []; + // for(var x = 1; x <= 3; x++){ + // x_axis.push((x/3)*self.radius); + // } + // self.redraw_graph("#graph_container",x_axis,[[1,2,3],[4,5,6]],'radius','frequency') +} + + +Nengo.VPLConfig.prototype.redraw_graph = function(selector, x, ys, x_label, y_label){ + var self = this; + $("#graph_container > svg").remove(); + self.graph_container.prependTo(".tab-content"); + self.multiline_plot(selector, x, ys, x_label, y_label); +} + +Nengo.VPLConfig.prototype.multiline_plot = function(selector, x, ys, x_label, y_label) { + + var margin = {left: 30, top: 0, right: 0, bottom: 30}; + var w = 500 - margin.left - margin.right; + var h = 220 - margin.bottom - margin.top; + var graph_w = w + margin.left + margin.right; + var graph_h = h + margin.bottom + margin.top; + var text_offset = 15; + + var scale_x = d3.scale.linear() + .domain([ x[0], x[x.length - 1] ]) + .range([margin.left, w - margin.right]); + var scale_y = d3.scale.linear() + .domain([d3.min(ys, function(y){ return d3.min(y); }) - 0.01, + d3.max(ys, function(y){ return d3.max(y); }) + 0.01]) + .range([h+margin.top, margin.top]); + + // Add an SVG element with the desired dimensions and margin. + var svg = d3.select(selector).append("svg") + .attr("viewBox","0 0 "+graph_w+" "+graph_h) + .attr("width","100%") + .attr("height","100%"); + + // create the axes + var xAxis = d3.svg.axis() + .scale(scale_x) + .orient("bottom") + .ticks(9); + svg.append("g") + .attr("class", "axis axis_x unselectable") + .attr("transform", "translate(0," + (h+margin.top) + ")") + .call(xAxis); + + var yAxisLeft = d3.svg.axis() + .scale(scale_y) + .ticks(5) + .orient("left"); + var yAxisRight = d3.svg.axis() + .scale(scale_x) + .scale(scale_y) + .ticks(5) + .orient("right"); + svg.append("g") + .attr("class", "axis axis_y unselectable") + .attr("transform", "translate(" + margin.left + ",0)") + .call(yAxisLeft); + svg.append("g") + .attr("class", "axis axis_y unselectable") + .attr("transform", "translate(" + w + ",0)") + .call(yAxisRight); + + // label the axes + if (x_label !== "") { + svg.append("text") + .attr("class", "x label") + .attr("text-anchor", "middle") + .attr("x", graph_w / 2) + .attr("y", text_offset + graph_h - margin.bottom / 2) + .text(x_label); + } + + if (y_label !== "") { + svg.append("text") + .attr("class", "y label") + .attr("text-anchor", "middle") + .attr("x", -graph_h/2) + .attr("y", -text_offset + margin.left / 2) + .attr("dy", ".75em") + .attr("transform", "rotate(-90)") + .text(y_label); + } + + // add the lines + var colors = Nengo.make_colors(ys.length); + + var line = d3.svg.line() + .x(function(d, i) { return scale_x(x[i]); }) + .y(function(d) { return scale_y(d); }) + + svg.append("g") + .selectAll("path") + .data(ys) + .enter() + .append("path") + .attr("d", line) + .attr("class", "line") + .style("stroke", function(d, i) { return colors[i]; }); +} + +Nengo.VPLConfig.prototype.ensemble_config = function(uid){ + var vpl = Nengo.vpl; + var editor = ace.edit("editor"); + var re = new RegExp("(?:[^\.])"+uid+"\\s*="); + var code_line = editor.find(re); + var component = Nengo.netgraph.svg_objects[uid]; + Nengo.modal.component_config(uid); + Nengo.modal.show(); + Nengo.modal.footer('ok_cancel', + function(e) { + var n_number = $("#config-neuron-number").val(); + var dim_number = $("#config-dimension").val(); + var Range = require("ace/range").Range; + var tab = " "; + var tabs; + Nengo.netgraph.svg_objects[uid].n_neurons = n_number; + Nengo.netgraph.svg_objects[uid].dimensions = dim_number; + + if(component.parent == null){tabs = tab;} + else{tabs = tab+tab;} + + editor.session.replace(new Range(code_line.start.row, 0, code_line.start.row, Number.MAX_VALUE), + tabs+uid+" = nengo.Ensemble(n_neurons="+n_number+",dimensions="+dim_number+")"); + vpl.delete_connections(uid); + var conn_in = component.conn_in; + var conn_out = component.conn_out; + for(var x = 0; x < conn_in.length; x++){ + vpl.add_connection(conn_in[x].pre.uid,conn_in[x].post.uid); + } + for(var x = 0; x < conn_out.length; x++){ + vpl.add_connection(conn_out[x].pre.uid,conn_out[x].post.uid); + } + $('#OK').attr('data-dismiss', 'modal'); + }, + function () { + $('#cancel-button').attr('data-dismiss', 'modal'); + } + ); +} + +$(document).ready(function(){ + Nengo.vpl_config = new Nengo.VPLConfig(); +}); diff --git a/nengo_gui/templates/page.html b/nengo_gui/templates/page.html index d9f053f2..f56f392a 100644 --- a/nengo_gui/templates/page.html +++ b/nengo_gui/templates/page.html @@ -17,13 +17,60 @@ + + + +
+ +
+
@@ -31,15 +78,94 @@ -
+
-
-
+
+
+
+

Controls

+
+
+ + +

Move

+
+
+ +
+
+ + +

Remove

+
+
+
+
+

Components

+
+
+ + + + + + + + + + +

Ensemble

+
+
+ +
+
+ + + + + +

Node

+
+
+ +
+
+ + + + + + + + + + +

Network

+
+
+ +
+
+ + + + + + +

Connection

+
+
+
+
+
+
+
-

Configure Settings

+

Settings

@@ -53,13 +179,13 @@
-

Download Simulation Data

+

Simulation Data

-

Export layout as SVG

+

Layout as SVG

@@ -72,52 +198,10 @@

-
-
- -
-
@@ -174,6 +258,7 @@ + @@ -204,6 +289,8 @@ + +