From 1d875721afaeb14aa1759eec91f29cb73bfc4d11 Mon Sep 17 00:00:00 2001 From: Sean Aubin Date: Thu, 1 Oct 2015 18:40:19 -0400 Subject: [PATCH 01/25] Added basic labels to value plot --- nengo_gui/components/value.py | 4 +- nengo_gui/config.py | 3 +- nengo_gui/static/components/netgraph_item.js | 1 + nengo_gui/static/components/value.js | 43 ++++++++++++++++++++ nengo_gui/static/nengo.js | 1 - 5 files changed, 48 insertions(+), 4 deletions(-) diff --git a/nengo_gui/components/value.py b/nengo_gui/components/value.py index 827a2758..7efacc39 100644 --- a/nengo_gui/components/value.py +++ b/nengo_gui/components/value.py @@ -10,8 +10,8 @@ class Value(Component): """The server-side system for a Value plot.""" # the parameters to be stored in the .cfg file - config_defaults = dict(max_value=1, - min_value=-1, + config_defaults = dict(max_value=1, min_value=-1, + show_legend=False, legend_labels=[], **Component.config_defaults) def __init__(self, obj): diff --git a/nengo_gui/config.py b/nengo_gui/config.py index f4ffea65..6cc2fd20 100644 --- a/nengo_gui/config.py +++ b/nengo_gui/config.py @@ -47,7 +47,8 @@ def dumps(self, uids): lines.append('%s = %s' % (uid, obj.code_python(uids))) for k in obj.config_defaults.keys(): v = getattr(self[obj], k) - if isinstance(v, bool): + if(isinstance(v, bool) or isinstance(v, list) + or isinstance(v, dict)): val = '%s' % v else: val = '%g' % v diff --git a/nengo_gui/static/components/netgraph_item.js b/nengo_gui/static/components/netgraph_item.js index 17109829..a1cdba83 100644 --- a/nengo_gui/static/components/netgraph_item.js +++ b/nengo_gui/static/components/netgraph_item.js @@ -362,6 +362,7 @@ Nengo.NetGraphItem.prototype.generate_menu = function () { items.push(['Semantic pointer plot', function() {self.create_graph('SpaSimilarity', self.sp_targets[0]);}]) } + // TODO: Enable input and output value plots for basal ganglia network items.push(['Details ...', function() {self.create_modal();}]); return items; }; diff --git a/nengo_gui/static/components/value.js b/nengo_gui/static/components/value.js index 97073b0a..79badec0 100644 --- a/nengo_gui/static/components/value.js +++ b/nengo_gui/static/components/value.js @@ -114,6 +114,27 @@ Nengo.Value = function(parent, sim, args) { this.on_resize(this.get_screen_width(), this.get_screen_height()); this.axes2d.axis_y.tickValues([args.min_value, args.max_value]); this.axes2d.fit_ticks(this); + + this.colors = Nengo.make_colors(6); + this.color_func = function(d, i) {return self.colors[i % 6]}; + this.legend = document.createElement('div'); + this.legend.classList.add('legend'); + this.div.appendChild(this.legend); + + this.legend_labels = args.legend_labels || []; + if(this.legend_labels.length !== this.n_lines){ + // fill up an array with temporary labels + for(i=0; i Date: Sat, 3 Oct 2015 20:51:40 -0400 Subject: [PATCH 02/25] added modal to edit labels --- nengo_gui/static/components/value.js | 41 ++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/nengo_gui/static/components/value.js b/nengo_gui/static/components/value.js index 79badec0..c10f3268 100644 --- a/nengo_gui/static/components/value.js +++ b/nengo_gui/static/components/value.js @@ -258,6 +258,9 @@ Nengo.Value.prototype.generate_menu = function() { items.push(['Show legend', function() {self.set_show_legend(true);}]); } + // TODO: give the legend it's own context menu + items.push(['Set legend labels', function () {self.set_legend_labels();}]) + // add the parent's menu items to this return $.merge(items, Nengo.Component.prototype.generate_menu.call(this)); }; @@ -277,6 +280,44 @@ Nengo.Value.prototype.set_show_legend = function(value){ } } +Nengo.Value.prototype.set_legend_labels = function() { + var self = this; + + Nengo.modal.title('Enter comma seperated legend label values'); + Nengo.modal.single_input_body('Legend label', 'New value'); + Nengo.modal.footer('ok_cancel', function(e) { + var label_csv = $('#singleInput').val(); + var modal = $('#myModalForm').data('bs.validator'); + + // No validation to do. + // Blank string mean do nothing + // Long strings okay + // Excissive entries get ignored + // Missing entries get replaced by default value + // Empty entries assumed to be indication to skip modification + // TODO: Allow escaping of commas + if ((label_csv !== null) && (label_csv !== '')) { + labels = label_csv.split(','); + + for(i=0; i Date: Sun, 4 Oct 2015 15:39:13 -0400 Subject: [PATCH 03/25] Create plots for the input and the output of the SPA Basal Ganglia --- nengo_gui/components/__init__.py | 1 + nengo_gui/components/bg_plot.py | 59 +++++++++++++++++++ nengo_gui/components/component.py | 7 ++- nengo_gui/components/netgraph.py | 10 ++++ nengo_gui/components/value.py | 13 ++-- nengo_gui/static/components/netgraph_item.js | 24 +++++++- nengo_gui/static/components/spa_similarity.js | 6 +- nengo_gui/static/components/value.js | 6 +- nengo_gui/static/nengo.js | 18 +++++- 9 files changed, 127 insertions(+), 17 deletions(-) create mode 100644 nengo_gui/components/bg_plot.py diff --git a/nengo_gui/components/__init__.py b/nengo_gui/components/__init__.py index a31ed078..779c7054 100644 --- a/nengo_gui/components/__init__.py +++ b/nengo_gui/components/__init__.py @@ -11,6 +11,7 @@ from .spa_similarity import SpaSimilarity from .htmlview import HTMLView from .spike_grid import SpikeGrid +from .bg_plot import BGPlot # Old versions of the .cfg files used Templates which had slightly different # names than the Components currently use. This code allows us to diff --git a/nengo_gui/components/bg_plot.py b/nengo_gui/components/bg_plot.py new file mode 100644 index 00000000..7f5b870b --- /dev/null +++ b/nengo_gui/components/bg_plot.py @@ -0,0 +1,59 @@ +import struct + +import nengo +import numpy as np +import ipdb + +from nengo_gui.components.component import Component +from nengo_gui.components.value import Value + + +class BGPlot(Value): + """The server-side system for the SPA Basal Ganglia plot.""" + + # the parameters to be stored in the .cfg file + config_defaults = dict(max_value=1, min_value=-1, + show_legend=True, legend_labels=[], + **Component.config_defaults) + + def __init__(self, obj, **kwargs): + args = kwargs["args"] + super(BGPlot, self).__init__(obj, args["n_lines"]) + + # default legends to show + self.def_legend_labels = args["legend_labels"] + + # the item to connect to + self.probe_target = args["probe_target"] + + self.label = "bg " + self.probe_target + + # the binary data format to sent in. In this case, it is a list of + # floats, with the first float being the time stamp and the rest + # being the vector values, one per dimension. + self.struct = struct.Struct('<%df' % (1 + self.n_lines)) + + def attach(self, page, config, uid): + super(Value, self).attach(page, config, uid) + + def add_nengo_objects(self, page): + # create a Node and a Connection so the Node will be given the + # data we want to show while the model is running. + with page.model: + self.node = nengo.Node(self.gather_data, + size_in=self.n_lines) + if self.probe_target == "input": + self.conn = nengo.Connection(self.obj.input, self.node, synapse=0.01) + else: + self.conn = nengo.Connection(self.obj.output, self.node, synapse=0.01) + + def javascript(self): + # generate the javascript that will create the client-side object + info = dict(uid=id(self), label=self.label, + n_lines=self.n_lines, synapse=0) + + if getattr(self.config, "legend_labels") == []: + json = self.javascript_config(info, override={"legend_labels":self.def_legend_labels}) + else: + json = self.javascript_config(info) + return 'new Nengo.Value(main, sim, %s);' % json \ No newline at end of file diff --git a/nengo_gui/components/component.py b/nengo_gui/components/component.py index 63b42d7c..b3b0720c 100644 --- a/nengo_gui/components/component.py +++ b/nengo_gui/components/component.py @@ -82,13 +82,16 @@ def remove_nengo_objects(self, page): """ pass - def javascript_config(self, cfg): + def javascript_config(self, cfg, override = {}): """Convert the nengo.Config information into javascript. This is needed so we can send that config information to the client. """ for attr in self.config._clsparams.params: - cfg[attr] = getattr(self.config, attr) + if attr not in override: + cfg[attr] = getattr(self.config, attr) + else: + cfg[attr] = override[attr] return json.dumps(cfg) def code_python(self, uids): diff --git a/nengo_gui/components/netgraph.py b/nengo_gui/components/netgraph.py index 81eab98e..09bf8fb3 100644 --- a/nengo_gui/components/netgraph.py +++ b/nengo_gui/components/netgraph.py @@ -5,6 +5,7 @@ import threading import nengo +from nengo import spa import json from nengo_gui.components.component import Component @@ -469,6 +470,15 @@ def get_extra_info(self, obj): elif isinstance(obj, nengo.Ensemble): info['dimensions'] = int(obj.size_out) info['n_neurons'] = int(obj.n_neurons) + # TODO: Add the same functionality for the BasalGanglia non-spa net + elif isinstance(obj, spa.BasalGanglia): + info['bg_inputs'] = obj.input.size_in + info['input_labels'] = [] + for ac in obj.actions.actions: + if ac.name == None: + info['input_labels'].append(ac.condition.expression.__str__()) + else: + info['input_labels'].append(ac.name) info['sp_targets'] = ( nengo_gui.components.pointer.Pointer.applicable_targets(obj)) return info diff --git a/nengo_gui/components/value.py b/nengo_gui/components/value.py index 7efacc39..a9bed495 100644 --- a/nengo_gui/components/value.py +++ b/nengo_gui/components/value.py @@ -14,7 +14,7 @@ class Value(Component): show_legend=False, legend_labels=[], **Component.config_defaults) - def __init__(self, obj): + def __init__(self, obj, n_lines=0): super(Value, self).__init__() # the object whose decoded value should be displayed self.obj = obj @@ -23,7 +23,11 @@ def __init__(self, obj): self.data = [] # the number of data values to send - self.n_lines = int(obj.size_out) + self.n_lines = 0 + if n_lines == 0: + self.n_lines = int(obj.size_out) + else: + self.n_lines = n_lines # the binary data format to sent in. In this case, it is a list of # floats, with the first float being the time stamp and the rest @@ -40,7 +44,7 @@ def add_nengo_objects(self, page): # data we want to show while the model is running. with page.model: self.node = nengo.Node(self.gather_data, - size_in=self.obj.size_out) + size_in=self.n_lines) self.conn = nengo.Connection(self.obj, self.node, synapse=0.01) def remove_nengo_objects(self, page): @@ -68,7 +72,8 @@ def update_client(self, client): def javascript(self): # generate the javascript that will create the client-side object info = dict(uid=id(self), label=self.label, - n_lines=self.n_lines, synapse=0) + n_lines=self.n_lines, synapse=0) + json = self.javascript_config(info) return 'new Nengo.Value(main, sim, %s);' % json diff --git a/nengo_gui/static/components/netgraph_item.js b/nengo_gui/static/components/netgraph_item.js index a1cdba83..e4440f81 100644 --- a/nengo_gui/static/components/netgraph_item.js +++ b/nengo_gui/static/components/netgraph_item.js @@ -18,7 +18,6 @@ Nengo.NetGraphItem = function(ng, info, minimap, mini_item) { this.size = info.size; this.type = info.type; this.uid = info.uid; - this.sp_targets = info.sp_targets; this.passthrough = info.passthrough; this.fixed_width = null; this.fixed_height = null; @@ -34,6 +33,12 @@ Nengo.NetGraphItem = function(ng, info, minimap, mini_item) { this.g_items = ng.g_items_mini; } + // SPA network specific parameter + this.sp_targets = info.sp_targets; + // Basal Ganglia network specific parameters + this.bg_inputs = info.bg_inputs; + this.input_labels = info.input_labels; + /** if this is a network, the children list is the set of NetGraphItems * and NetGraphConnections that are inside this network */ this.children = []; @@ -362,7 +367,22 @@ Nengo.NetGraphItem.prototype.generate_menu = function () { items.push(['Semantic pointer plot', function() {self.create_graph('SpaSimilarity', self.sp_targets[0]);}]) } - // TODO: Enable input and output value plots for basal ganglia network + if (this.bg_inputs) { + items.push(['Input Plot', + function () { + self.create_graph('BGPlot', + {"n_lines":self.bg_inputs, "legend_labels":self.input_labels, "probe_target":"input"} + ); + } + ]); + items.push(['Output Plot', + function () { + self.create_graph('BGPlot', + {"n_lines":self.bg_inputs, "legend_labels":self.input_labels, "probe_target":"output"} + ); + } + ]); + } items.push(['Details ...', function() {self.create_modal();}]); return items; }; diff --git a/nengo_gui/static/components/spa_similarity.js b/nengo_gui/static/components/spa_similarity.js index 36633d5f..22ded5b5 100644 --- a/nengo_gui/static/components/spa_similarity.js +++ b/nengo_gui/static/components/spa_similarity.js @@ -27,7 +27,7 @@ Nengo.SpaSimilarity = function(parent, sim, args) { this.legend = document.createElement('div'); this.legend.classList.add('legend', 'unselectable'); this.div.appendChild(this.legend); - this.legend_svg = Nengo.draw_legend(this.legend, args.pointer_labels, this.color_func); + this.legend_svg = Nengo.draw_legend(this.legend, args.pointer_labels, this.color_func, this.uid); }; Nengo.SpaSimilarity.prototype = Object.create(Nengo.Value.prototype); @@ -42,7 +42,7 @@ Nengo.SpaSimilarity.prototype.reset_legend_and_data = function(new_labels){ while(this.legend.lastChild){ this.legend.removeChild(this.legend.lastChild); } - this.legend_svg = d3.select(this.legend).append("svg"); + this.legend_svg = d3.select(this.legend).append("svg").attr("id", "#id"+this.uid); // redraw all the legends if they exist this.pointer_labels = []; @@ -99,7 +99,7 @@ Nengo.SpaSimilarity.prototype.update_legend = function(new_labels){ }); // expand the width of the svg of the longest string - var label_list = $(".legend-label").toArray(); + var label_list = $("#id"+this.uid+" .legend-label").toArray(); var longest_label = label_list.sort( function (a, b) { return b.getBBox().width - a.getBBox().width; } )[0]; diff --git a/nengo_gui/static/components/value.js b/nengo_gui/static/components/value.js index c10f3268..33c43ed7 100644 --- a/nengo_gui/static/components/value.js +++ b/nengo_gui/static/components/value.js @@ -133,7 +133,7 @@ Nengo.Value = function(parent, sim, args) { this.show_legend = args.show_legend || false; if(this.show_legend === true){ - Nengo.draw_legend(this.legend, this.legend_labels, this.color_func); + Nengo.draw_legend(this.legend, this.legend_labels, this.color_func, this.uid); } }; @@ -271,7 +271,7 @@ Nengo.Value.prototype.set_show_legend = function(value){ this.save_layout(); } if (this.show_legend === true){ - Nengo.draw_legend(this.legend, this.legend_labels, this.color_func); + Nengo.draw_legend(this.legend, this.legend_labels, this.color_func, this.uid); } else { // delete the legend's children while(this.legend.lastChild){ @@ -309,7 +309,7 @@ Nengo.Value.prototype.set_legend_labels = function() { while(self.legend.lastChild){ self.legend.removeChild(self.legend.lastChild); } - Nengo.draw_legend(self.legend, self.legend_labels, self.color_func); + Nengo.draw_legend(self.legend, self.legend_labels, self.color_func, this.uid); } $('#OK').attr('data-dismiss', 'modal'); }); diff --git a/nengo_gui/static/nengo.js b/nengo_gui/static/nengo.js index aabd7e06..c5ff540b 100644 --- a/nengo_gui/static/nengo.js +++ b/nengo_gui/static/nengo.js @@ -72,12 +72,13 @@ Nengo.next_zindex = function() { } /* draw a legend */ -Nengo.draw_legend = function(parent, labels, color_func){ +Nengo.draw_legend = function(parent, labels, color_func, uid){ // "20" is around the size of the font legend_svg = d3.select(parent) .append("svg") .attr("width", 150) - .attr("height", 20*labels.length); + .attr("height", 20*labels.length) + .attr("id", "id"+uid); legend_svg.selectAll('rect') .data(labels) @@ -88,15 +89,26 @@ Nengo.draw_legend = function(parent, labels, color_func){ .attr("width", 10) .attr("height", 10) .style("fill", color_func); - + legend_svg.selectAll('text') .data(labels) .enter() .append("text") .attr("x", 15) .attr("y", function(d, i){ return i * 20 + 9;}) + .attr("class", "legend-label") .html(function(d, i) { return labels[i]; }); + + // expand the width of the svg to the length of the longest string + var label_list = $("#id"+uid+" .legend-label").toArray(); + var longest_label = label_list.sort( + function (a, b) { return b.getBBox().width - a.getBBox().width; } + )[0]; + // "30" is for the box colours which is around two characters wide + var svg_right_edge = longest_label.getBBox().width + 30; + legend_svg.attr("width", svg_right_edge); + return legend_svg; } From f328e9bab2e1d51f8a40dca61883d76698df5f7d Mon Sep 17 00:00:00 2001 From: Sean Aubin Date: Tue, 13 Oct 2015 22:32:16 -0400 Subject: [PATCH 04/25] Added ability to choose different color palette --- nengo_gui/static/color.js | 45 ++++++++++++++ nengo_gui/static/components/spa_similarity.js | 3 - nengo_gui/static/components/value.js | 62 ++++++++++++++++--- nengo_gui/static/modal.js | 3 +- nengo_gui/static/nengo.js | 2 +- nengo_gui/templates/page.html | 1 + 6 files changed, 104 insertions(+), 12 deletions(-) create mode 100644 nengo_gui/static/color.js diff --git a/nengo_gui/static/color.js b/nengo_gui/static/color.js new file mode 100644 index 00000000..026638d5 --- /dev/null +++ b/nengo_gui/static/color.js @@ -0,0 +1,45 @@ +/** + * Generate a color sequence of a given length. + */ + +/** + * Generate a color sequence of a given length. + * + * Colors are defined using a color blind-friendly palette. + */ +Nengo.make_colors = function(N) { + // Color blind palette with blue, green, red, magenta, yellow, cyan + var palette = ["#1c73b3", "#039f74", "#d65e00", "#cd79a7", "#f0e542", "#56b4ea"]; + var c = []; + + for (var i = 0; i < N; i++) { + c.push(palette[i % palette.length]); + } + return c; +} + +/** + * Color blind-friendly palette. + */ +Nengo.default_colors = function() { + // Color blind palette with blue, green, red, magenta, yellow, cyan + var palette = ["#1c73b3", "#039f74", "#d65e00", "#cd79a7", "#f0e542", "#56b4ea"]; + return function(i){ return palette[i%palette.length] }; +} + +/** + * Color palette use by Google for graphics, trends, etc... + */ +Nengo.google_colors = function() { + var palette = ["#3366cc", "#dc3912", "#ff9900", "#109618", "#990099", "#0099c6", "#dd4477", "#66aa00", "#b82e2e", "#316395", "#994499", "#22aa99", "#aaaa11", "#6633cc", "#e67300", "#8b0707", "#651067", "#329262", "#5574a6", "#3b3eac"]; + return function(i){ return palette[i%palette.length] }; +} + +/** list of valid color choices */ +Nengo.color_choices = [ + ["Nengo Color-Blind Friendly (6 colors)", {"func":Nengo.default_colors(), "mod":6}], + ["Google (20 colors)", {"func":Nengo.google_colors(), "mod":20}], + ["D3.js A (20 colors)", {"func":d3.scale.category20(), "mod":20}], + ["D3.js B (20 colors)", {"func":d3.scale.category20b(), "mod":20}], + ["D3.js C (20 colors)", {"func":d3.scale.category20c(), "mod":20}] +]; diff --git a/nengo_gui/static/components/spa_similarity.js b/nengo_gui/static/components/spa_similarity.js index 22ded5b5..482c7a56 100644 --- a/nengo_gui/static/components/spa_similarity.js +++ b/nengo_gui/static/components/spa_similarity.js @@ -17,9 +17,6 @@ Nengo.SpaSimilarity = function(parent, sim, args) { var self = this; - this.colors = Nengo.make_colors(6); - this.color_func = function(d, i) {return self.colors[i % 6]}; - this.line.defined(function(d) { return !isNaN(d)}); // create the legend from label args diff --git a/nengo_gui/static/components/value.js b/nengo_gui/static/components/value.js index 33c43ed7..75c8d3ca 100644 --- a/nengo_gui/static/components/value.js +++ b/nengo_gui/static/components/value.js @@ -46,11 +46,14 @@ Nengo.Value = function(parent, sim, args) { this.path = this.axes2d.svg.append("g").selectAll('path') .data(this.data_store.data); - this.colors = Nengo.make_colors(this.n_lines); + // create the color function + // TODO: save this in the config + this.color_func = Nengo.default_colors(); + this.path.enter() .append('path') .attr('class', 'line') - .style('stroke', function(d, i) {return self.colors[i];}); + .style('stroke', function(d, i){return self.color_func(i)}); // Flag for whether or not update code should be changing the crosshair // Both zooming and the simulator time changing cause an update, but the crosshair @@ -115,8 +118,6 @@ Nengo.Value = function(parent, sim, args) { this.axes2d.axis_y.tickValues([args.min_value, args.max_value]); this.axes2d.fit_ticks(this); - this.colors = Nengo.make_colors(6); - this.color_func = function(d, i) {return self.colors[i % 6]}; this.legend = document.createElement('div'); this.legend.classList.add('legend'); this.div.appendChild(this.legend); @@ -265,6 +266,49 @@ Nengo.Value.prototype.generate_menu = function() { return $.merge(items, Nengo.Component.prototype.generate_menu.call(this)); }; +Nengo.Value.prototype.set_color_func = function() { + var self = this; + + Nengo.modal.clear_body(); + // TODO: Let the user define their own palette + Nengo.modal.title('Choose a palette'); + + // Create a radio button form with the available palettes + var body = Nengo.modal.$body; + var radio_html = "
"; + radio_html += ""+Nengo.color_choices[0][0]+"
" + for (i = 1; i < Nengo.color_choices.length; i++) { + radio_html += ""+Nengo.color_choices[i][0]+"
" + } + radio_html += "
"; + body.append(radio_html); + + // TODO: Make this thing easier to select + var selected_palette = 0; + Nengo.modal.footer('ok_cancel', function(e) { + selected_palette = $("#palette input:radio[name='pal']:checked").val(); + self.color_func = Nengo.color_choices[selected_palette][1]["func"]; + + self.path.style('stroke', function(d, i){return self.color_func(i)}); + if(self.show_legend === true){ + self.clear_legend(); + Nengo.draw_legend(self.legend, self.legend_labels, self.color_func, self.uid); + } + + $('#OK').attr('data-dismiss', 'modal'); + }); + + // allow enter keypress + $("#palette").keypress(function(event) { + if (event.which == 13) { + event.preventDefault(); + $('#OK').click(); + } + }); + + Nengo.modal.show(); +} + Nengo.Value.prototype.set_show_legend = function(value){ if (this.show_legend !== value) { this.show_legend = value; @@ -274,9 +318,13 @@ Nengo.Value.prototype.set_show_legend = function(value){ Nengo.draw_legend(this.legend, this.legend_labels, this.color_func, this.uid); } else { // delete the legend's children - while(this.legend.lastChild){ - this.legend.removeChild(this.legend.lastChild); - } + this.clear_legend(); + } +} + +Nengo.Value.prototype.clear_legend = function() { + while(this.legend.lastChild){ + this.legend.removeChild(this.legend.lastChild); } } diff --git a/nengo_gui/static/modal.js b/nengo_gui/static/modal.js index a2ac7b6c..0c8c3ab7 100644 --- a/nengo_gui/static/modal.js +++ b/nengo_gui/static/modal.js @@ -230,7 +230,8 @@ Nengo.Modal.prototype.main_config = function(options) { event.preventDefault(); $('#OK').click(); } - });} + }); +} /** * Sets up the body for standard input forms diff --git a/nengo_gui/static/nengo.js b/nengo_gui/static/nengo.js index c5ff540b..4e634013 100644 --- a/nengo_gui/static/nengo.js +++ b/nengo_gui/static/nengo.js @@ -88,7 +88,7 @@ Nengo.draw_legend = function(parent, labels, color_func, uid){ .attr("y", function(d, i){ return i * 20;}) .attr("width", 10) .attr("height", 10) - .style("fill", color_func); + .style("fill", function(d, i) {return color_func(i)}); legend_svg.selectAll('text') .data(labels) diff --git a/nengo_gui/templates/page.html b/nengo_gui/templates/page.html index b4d572bc..e2b1707a 100644 --- a/nengo_gui/templates/page.html +++ b/nengo_gui/templates/page.html @@ -140,5 +140,6 @@ + From 4921aa95dc3ca770bec5f933afc022d0e62799fb Mon Sep 17 00:00:00 2001 From: Sean Aubin Date: Tue, 13 Oct 2015 21:58:28 -0400 Subject: [PATCH 05/25] added color palette to config --- nengo_gui/components/value.py | 2 +- nengo_gui/static/components/value.js | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/nengo_gui/components/value.py b/nengo_gui/components/value.py index a9bed495..313f0361 100644 --- a/nengo_gui/components/value.py +++ b/nengo_gui/components/value.py @@ -11,7 +11,7 @@ class Value(Component): # the parameters to be stored in the .cfg file config_defaults = dict(max_value=1, min_value=-1, - show_legend=False, legend_labels=[], + show_legend=False, legend_labels=[], palette_index=0, **Component.config_defaults) def __init__(self, obj, n_lines=0): diff --git a/nengo_gui/static/components/value.js b/nengo_gui/static/components/value.js index 75c8d3ca..5b68e1e4 100644 --- a/nengo_gui/static/components/value.js +++ b/nengo_gui/static/components/value.js @@ -47,8 +47,8 @@ Nengo.Value = function(parent, sim, args) { .data(this.data_store.data); // create the color function - // TODO: save this in the config - this.color_func = Nengo.default_colors(); + this.palette_index = args.palette_index || 0; + this.color_func = Nengo.color_choices[this.palette_index][1]["func"]; this.path.enter() .append('path') @@ -283,22 +283,21 @@ Nengo.Value.prototype.set_color_func = function() { radio_html += ""; body.append(radio_html); - // TODO: Make this thing easier to select - var selected_palette = 0; + // TODO: Make this thing easier to select for the user Nengo.modal.footer('ok_cancel', function(e) { - selected_palette = $("#palette input:radio[name='pal']:checked").val(); - self.color_func = Nengo.color_choices[selected_palette][1]["func"]; + self.palette_index = Number($("#palette input:radio[name='pal']:checked").val()); + self.color_func = Nengo.color_choices[self.palette_index][1]["func"]; self.path.style('stroke', function(d, i){return self.color_func(i)}); if(self.show_legend === true){ self.clear_legend(); Nengo.draw_legend(self.legend, self.legend_labels, self.color_func, self.uid); } - + self.save_layout(); $('#OK').attr('data-dismiss', 'modal'); }); - // allow enter keypress + // allow "Enter" keypress $("#palette").keypress(function(event) { if (event.which == 13) { event.preventDefault(); @@ -358,6 +357,7 @@ Nengo.Value.prototype.set_legend_labels = function() { self.legend.removeChild(self.legend.lastChild); } Nengo.draw_legend(self.legend, self.legend_labels, self.color_func, this.uid); + self.save_layout(); } $('#OK').attr('data-dismiss', 'modal'); }); @@ -372,6 +372,7 @@ Nengo.Value.prototype.layout_info = function () { info.legend_labels = this.legend_labels; info.min_value = this.axes2d.scale_y.domain()[0]; info.max_value = this.axes2d.scale_y.domain()[1]; + info.palette_index = this.palette_index; return info; } From 91c57da103a03528fad96a5a897913728fe6b969 Mon Sep 17 00:00:00 2001 From: Sean Aubin Date: Tue, 13 Oct 2015 22:23:50 -0400 Subject: [PATCH 06/25] fixed legend setting behaviour --- nengo_gui/static/components/value.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nengo_gui/static/components/value.js b/nengo_gui/static/components/value.js index 5b68e1e4..7c0d4c43 100644 --- a/nengo_gui/static/components/value.js +++ b/nengo_gui/static/components/value.js @@ -346,9 +346,11 @@ Nengo.Value.prototype.set_legend_labels = function() { if ((label_csv !== null) && (label_csv !== '')) { labels = label_csv.split(','); - for(i=0; i Date: Fri, 16 Oct 2015 11:45:43 -0400 Subject: [PATCH 07/25] expanded palette switching to spa_similarity and bg_plot --- nengo_gui/components/bg_plot.py | 7 +- nengo_gui/components/spa_similarity.py | 8 +- nengo_gui/static/components/spa_similarity.js | 74 ++++++++++++++++--- nengo_gui/static/components/value.js | 2 + 4 files changed, 70 insertions(+), 21 deletions(-) diff --git a/nengo_gui/components/bg_plot.py b/nengo_gui/components/bg_plot.py index 7f5b870b..93ca69f6 100644 --- a/nengo_gui/components/bg_plot.py +++ b/nengo_gui/components/bg_plot.py @@ -4,7 +4,6 @@ import numpy as np import ipdb -from nengo_gui.components.component import Component from nengo_gui.components.value import Value @@ -12,9 +11,9 @@ class BGPlot(Value): """The server-side system for the SPA Basal Ganglia plot.""" # the parameters to be stored in the .cfg file - config_defaults = dict(max_value=1, min_value=-1, - show_legend=True, legend_labels=[], - **Component.config_defaults) + config_defaults = Value.config_defaults + config_defaults["palette_index"] = 1 + config_defaults["show_legend"] = True def __init__(self, obj, **kwargs): args = kwargs["args"] diff --git a/nengo_gui/components/spa_similarity.py b/nengo_gui/components/spa_similarity.py index 4a746b17..4ad19d79 100644 --- a/nengo_gui/components/spa_similarity.py +++ b/nengo_gui/components/spa_similarity.py @@ -7,9 +7,8 @@ class SpaSimilarity(SpaPlot): - config_defaults = dict(max_value=1, - min_value=-1, - show_pairs=False, + config_defaults = dict(max_value=1.5, min_value=-1.5, + palette_index=1, show_pairs=False, **Component.config_defaults) def __init__(self, obj, **kwargs): @@ -80,8 +79,7 @@ def update_legend(self, vocab): def javascript(self): """Generate the javascript that will create the client-side object""" info = dict(uid=id(self), label=self.label, n_lines=len(self.labels), - synapse=0, min_value=-1.5, max_value=1.5, - pointer_labels=self.labels) + synapse=0, pointer_labels=self.labels) json = self.javascript_config(info) return 'new Nengo.SpaSimilarity(main, sim, %s);' % json diff --git a/nengo_gui/static/components/spa_similarity.js b/nengo_gui/static/components/spa_similarity.js index 482c7a56..c9ed2e7e 100644 --- a/nengo_gui/static/components/spa_similarity.js +++ b/nengo_gui/static/components/spa_similarity.js @@ -20,11 +20,11 @@ Nengo.SpaSimilarity = function(parent, sim, args) { this.line.defined(function(d) { return !isNaN(d)}); // create the legend from label args - this.pointer_labels = args.pointer_labels; + this.legend_labels = args.pointer_labels; this.legend = document.createElement('div'); this.legend.classList.add('legend', 'unselectable'); this.div.appendChild(this.legend); - this.legend_svg = Nengo.draw_legend(this.legend, args.pointer_labels, this.color_func, this.uid); + this.legend_svg = Nengo.draw_legend(this.legend, this.legend_labels, this.color_func, this.uid); }; Nengo.SpaSimilarity.prototype = Object.create(Nengo.Value.prototype); @@ -42,7 +42,7 @@ Nengo.SpaSimilarity.prototype.reset_legend_and_data = function(new_labels){ this.legend_svg = d3.select(this.legend).append("svg").attr("id", "#id"+this.uid); // redraw all the legends if they exist - this.pointer_labels = []; + this.legend_labels = []; if(new_labels[0] != ""){ this.update_legend(new_labels); } @@ -68,16 +68,16 @@ Nengo.SpaSimilarity.prototype.data_msg = function(push_data){ Nengo.SpaSimilarity.prototype.update_legend = function(new_labels){ var self = this; - this.pointer_labels = this.pointer_labels.concat(new_labels); + this.legend_labels = this.legend_labels.concat(new_labels); // expand the height of the svg, where "20" is around the height of the font - this.legend_svg.attr("height", 20 * this.pointer_labels.length); + this.legend_svg.attr("height", 20 * this.legend_labels.length); // Data join - var recs = this.legend_svg.selectAll("rect").data(this.pointer_labels); - var legend_labels = this.legend_svg.selectAll(".legend-label").data(this.pointer_labels); - var val_texts = this.legend_svg.selectAll(".val").data(this.pointer_labels); + var recs = this.legend_svg.selectAll("rect").data(this.legend_labels); + var legend_labels = this.legend_svg.selectAll(".legend-label").data(this.legend_labels); + var val_texts = this.legend_svg.selectAll(".val").data(this.legend_labels); // enter to append remaining lines recs.enter() .append("rect") @@ -85,14 +85,14 @@ Nengo.SpaSimilarity.prototype.update_legend = function(new_labels){ .attr("y", function(d, i){ return i * 20;}) .attr("width", 10) .attr("height", 10) - .style("fill", this.color_func); + .style("fill", function(d, i) {return self.color_func(i)}); legend_labels.enter().append("text") .attr("x", 15) .attr("y", function(d, i){ return i * 20 + 9;}) .attr("class", "legend-label") .html(function(d, i) { - return self.pointer_labels[i]; + return self.legend_labels[i]; }); // expand the width of the svg of the longest string @@ -149,7 +149,7 @@ Nengo.SpaSimilarity.prototype.update = function() { this.path.enter() .append('path') .attr('class', 'line') - .style('stroke', this.color_func) + .style('stroke', function(d, i) {return self.color_func(i)}) .attr('d', self.line); // remove any lines that aren't needed anymore this.path.exit().remove(); @@ -163,7 +163,7 @@ Nengo.SpaSimilarity.prototype.update = function() { } // update the text in the legend - var texts = this.legend_svg.selectAll(".val").data(this.pointer_labels); + var texts = this.legend_svg.selectAll(".val").data(this.legend_labels); texts.html(function(d, i) { var sign = ''; @@ -187,10 +187,60 @@ Nengo.SpaSimilarity.prototype.generate_menu = function() { items.push(['Show pairs', function() {self.set_show_pairs(true);}]); } + items.push(['Change color palette', function() {self.set_color_func();}]) + // add the parent's menu items to this return $.merge(items, Nengo.Component.prototype.generate_menu.call(this)); }; +Nengo.SpaSimilarity.prototype.set_color_func = function() { + var self = this; + + Nengo.modal.clear_body(); + // TODO: Let the user define their own palette + Nengo.modal.title('Choose a palette'); + + // Create a radio button form with the available palettes + var body = Nengo.modal.$body; + var radio_html = "
"; + radio_html += ""+Nengo.color_choices[0][0]+"
" + for (i = 1; i < Nengo.color_choices.length; i++) { + radio_html += ""+Nengo.color_choices[i][0]+"
" + } + radio_html += "
"; + body.append(radio_html); + + // TODO: Make this thing easier to select for the user + Nengo.modal.footer('ok_cancel', function(e) { + self.palette_index = Number($("#palette input:radio[name='pal']:checked").val()); + self.color_func = Nengo.color_choices[self.palette_index][1]["func"]; + + self.path.style('stroke', function(d, i){return self.color_func(i)}); + self.legend_svg.selectAll("rect").remove(); + var recs = self.legend_svg.selectAll("rect").data(self.legend_labels); + recs.enter() + .append("rect") + .attr("x", 0) + .attr("y", function(d, i){ return i * 20;}) + .attr("width", 10) + .attr("height", 10) + .style("fill", function(d, i) {return self.color_func(i)}); + self.save_layout(); + $('#OK').attr('data-dismiss', 'modal'); + }); + + // allow "Enter" keypress + $("#palette").keypress(function(event) { + if (event.which == 13) { + event.preventDefault(); + $('#OK').click(); + } + }); + + Nengo.modal.show(); +} + + Nengo.SpaSimilarity.prototype.set_show_pairs = function(value) { if (this.show_pairs !== value) { this.show_pairs = value; diff --git a/nengo_gui/static/components/value.js b/nengo_gui/static/components/value.js index 7c0d4c43..6a5fb718 100644 --- a/nengo_gui/static/components/value.js +++ b/nengo_gui/static/components/value.js @@ -262,6 +262,8 @@ Nengo.Value.prototype.generate_menu = function() { // TODO: give the legend it's own context menu items.push(['Set legend labels', function () {self.set_legend_labels();}]) + items.push(['Change color palette', function () {self.set_color_func();}]) + // add the parent's menu items to this return $.merge(items, Nengo.Component.prototype.generate_menu.call(this)); }; From 74aa0f50a87863e9234d9b68ad69c9337a017050 Mon Sep 17 00:00:00 2001 From: Sean Aubin Date: Fri, 16 Oct 2015 11:46:41 -0400 Subject: [PATCH 08/25] removed left-over ipdb --- nengo_gui/components/bg_plot.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nengo_gui/components/bg_plot.py b/nengo_gui/components/bg_plot.py index 93ca69f6..50774f28 100644 --- a/nengo_gui/components/bg_plot.py +++ b/nengo_gui/components/bg_plot.py @@ -2,7 +2,6 @@ import nengo import numpy as np -import ipdb from nengo_gui.components.value import Value From ded744a04181ba9309a59b53cc272e4c87fc0e6e Mon Sep 17 00:00:00 2001 From: Sean Aubin Date: Fri, 16 Oct 2015 12:39:40 -0400 Subject: [PATCH 09/25] Fixed id and empty labels error for spasimilarity --- nengo_gui/static/components/spa_similarity.js | 2 +- nengo_gui/static/nengo.js | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/nengo_gui/static/components/spa_similarity.js b/nengo_gui/static/components/spa_similarity.js index c9ed2e7e..ecdc5c1e 100644 --- a/nengo_gui/static/components/spa_similarity.js +++ b/nengo_gui/static/components/spa_similarity.js @@ -39,7 +39,7 @@ Nengo.SpaSimilarity.prototype.reset_legend_and_data = function(new_labels){ while(this.legend.lastChild){ this.legend.removeChild(this.legend.lastChild); } - this.legend_svg = d3.select(this.legend).append("svg").attr("id", "#id"+this.uid); + this.legend_svg = d3.select(this.legend).append("svg").attr("id", "id"+this.uid); // redraw all the legends if they exist this.legend_labels = []; diff --git a/nengo_gui/static/nengo.js b/nengo_gui/static/nengo.js index 4e634013..bcb1dd1e 100644 --- a/nengo_gui/static/nengo.js +++ b/nengo_gui/static/nengo.js @@ -80,6 +80,10 @@ Nengo.draw_legend = function(parent, labels, color_func, uid){ .attr("height", 20*labels.length) .attr("id", "id"+uid); + if(labels.length == 0){ + return legend_svg; + } + legend_svg.selectAll('rect') .data(labels) .enter() From ed4f79e438c4acfd2be20d993e23d44271b09263 Mon Sep 17 00:00:00 2001 From: Sean Aubin Date: Mon, 19 Oct 2015 21:20:27 -0400 Subject: [PATCH 10/25] Removed redundant variable from bg_plot --- nengo_gui/components/bg_plot.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/nengo_gui/components/bg_plot.py b/nengo_gui/components/bg_plot.py index 50774f28..2df4fa69 100644 --- a/nengo_gui/components/bg_plot.py +++ b/nengo_gui/components/bg_plot.py @@ -26,11 +26,6 @@ def __init__(self, obj, **kwargs): self.label = "bg " + self.probe_target - # the binary data format to sent in. In this case, it is a list of - # floats, with the first float being the time stamp and the rest - # being the vector values, one per dimension. - self.struct = struct.Struct('<%df' % (1 + self.n_lines)) - def attach(self, page, config, uid): super(Value, self).attach(page, config, uid) From a91e6dc5a9aefc488526c55d11ab4b93ae1df02c Mon Sep 17 00:00:00 2001 From: Sean Aubin Date: Thu, 1 Oct 2015 18:40:19 -0400 Subject: [PATCH 11/25] Added basic labels to value plot --- nengo_gui/components/value.py | 4 +- nengo_gui/config.py | 3 +- nengo_gui/static/components/netgraph_item.js | 1 + nengo_gui/static/components/value.js | 43 ++++++++++++++++++++ nengo_gui/static/nengo.js | 1 - 5 files changed, 48 insertions(+), 4 deletions(-) diff --git a/nengo_gui/components/value.py b/nengo_gui/components/value.py index 827a2758..7efacc39 100644 --- a/nengo_gui/components/value.py +++ b/nengo_gui/components/value.py @@ -10,8 +10,8 @@ class Value(Component): """The server-side system for a Value plot.""" # the parameters to be stored in the .cfg file - config_defaults = dict(max_value=1, - min_value=-1, + config_defaults = dict(max_value=1, min_value=-1, + show_legend=False, legend_labels=[], **Component.config_defaults) def __init__(self, obj): diff --git a/nengo_gui/config.py b/nengo_gui/config.py index f4ffea65..6cc2fd20 100644 --- a/nengo_gui/config.py +++ b/nengo_gui/config.py @@ -47,7 +47,8 @@ def dumps(self, uids): lines.append('%s = %s' % (uid, obj.code_python(uids))) for k in obj.config_defaults.keys(): v = getattr(self[obj], k) - if isinstance(v, bool): + if(isinstance(v, bool) or isinstance(v, list) + or isinstance(v, dict)): val = '%s' % v else: val = '%g' % v diff --git a/nengo_gui/static/components/netgraph_item.js b/nengo_gui/static/components/netgraph_item.js index 17109829..a1cdba83 100644 --- a/nengo_gui/static/components/netgraph_item.js +++ b/nengo_gui/static/components/netgraph_item.js @@ -362,6 +362,7 @@ Nengo.NetGraphItem.prototype.generate_menu = function () { items.push(['Semantic pointer plot', function() {self.create_graph('SpaSimilarity', self.sp_targets[0]);}]) } + // TODO: Enable input and output value plots for basal ganglia network items.push(['Details ...', function() {self.create_modal();}]); return items; }; diff --git a/nengo_gui/static/components/value.js b/nengo_gui/static/components/value.js index 97073b0a..79badec0 100644 --- a/nengo_gui/static/components/value.js +++ b/nengo_gui/static/components/value.js @@ -114,6 +114,27 @@ Nengo.Value = function(parent, sim, args) { this.on_resize(this.get_screen_width(), this.get_screen_height()); this.axes2d.axis_y.tickValues([args.min_value, args.max_value]); this.axes2d.fit_ticks(this); + + this.colors = Nengo.make_colors(6); + this.color_func = function(d, i) {return self.colors[i % 6]}; + this.legend = document.createElement('div'); + this.legend.classList.add('legend'); + this.div.appendChild(this.legend); + + this.legend_labels = args.legend_labels || []; + if(this.legend_labels.length !== this.n_lines){ + // fill up an array with temporary labels + for(i=0; i Date: Sat, 3 Oct 2015 20:51:40 -0400 Subject: [PATCH 12/25] added modal to edit labels --- nengo_gui/static/components/value.js | 41 ++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/nengo_gui/static/components/value.js b/nengo_gui/static/components/value.js index 79badec0..c10f3268 100644 --- a/nengo_gui/static/components/value.js +++ b/nengo_gui/static/components/value.js @@ -258,6 +258,9 @@ Nengo.Value.prototype.generate_menu = function() { items.push(['Show legend', function() {self.set_show_legend(true);}]); } + // TODO: give the legend it's own context menu + items.push(['Set legend labels', function () {self.set_legend_labels();}]) + // add the parent's menu items to this return $.merge(items, Nengo.Component.prototype.generate_menu.call(this)); }; @@ -277,6 +280,44 @@ Nengo.Value.prototype.set_show_legend = function(value){ } } +Nengo.Value.prototype.set_legend_labels = function() { + var self = this; + + Nengo.modal.title('Enter comma seperated legend label values'); + Nengo.modal.single_input_body('Legend label', 'New value'); + Nengo.modal.footer('ok_cancel', function(e) { + var label_csv = $('#singleInput').val(); + var modal = $('#myModalForm').data('bs.validator'); + + // No validation to do. + // Blank string mean do nothing + // Long strings okay + // Excissive entries get ignored + // Missing entries get replaced by default value + // Empty entries assumed to be indication to skip modification + // TODO: Allow escaping of commas + if ((label_csv !== null) && (label_csv !== '')) { + labels = label_csv.split(','); + + for(i=0; i Date: Sun, 4 Oct 2015 15:39:13 -0400 Subject: [PATCH 13/25] Create plots for the input and the output of the SPA Basal Ganglia --- nengo_gui/components/__init__.py | 1 + nengo_gui/components/bg_plot.py | 59 +++++++++++++++++++ nengo_gui/components/component.py | 7 ++- nengo_gui/components/netgraph.py | 10 ++++ nengo_gui/components/value.py | 13 ++-- nengo_gui/static/components/netgraph_item.js | 24 +++++++- nengo_gui/static/components/spa_similarity.js | 6 +- nengo_gui/static/components/value.js | 6 +- nengo_gui/static/nengo.js | 18 +++++- 9 files changed, 127 insertions(+), 17 deletions(-) create mode 100644 nengo_gui/components/bg_plot.py diff --git a/nengo_gui/components/__init__.py b/nengo_gui/components/__init__.py index a31ed078..779c7054 100644 --- a/nengo_gui/components/__init__.py +++ b/nengo_gui/components/__init__.py @@ -11,6 +11,7 @@ from .spa_similarity import SpaSimilarity from .htmlview import HTMLView from .spike_grid import SpikeGrid +from .bg_plot import BGPlot # Old versions of the .cfg files used Templates which had slightly different # names than the Components currently use. This code allows us to diff --git a/nengo_gui/components/bg_plot.py b/nengo_gui/components/bg_plot.py new file mode 100644 index 00000000..7f5b870b --- /dev/null +++ b/nengo_gui/components/bg_plot.py @@ -0,0 +1,59 @@ +import struct + +import nengo +import numpy as np +import ipdb + +from nengo_gui.components.component import Component +from nengo_gui.components.value import Value + + +class BGPlot(Value): + """The server-side system for the SPA Basal Ganglia plot.""" + + # the parameters to be stored in the .cfg file + config_defaults = dict(max_value=1, min_value=-1, + show_legend=True, legend_labels=[], + **Component.config_defaults) + + def __init__(self, obj, **kwargs): + args = kwargs["args"] + super(BGPlot, self).__init__(obj, args["n_lines"]) + + # default legends to show + self.def_legend_labels = args["legend_labels"] + + # the item to connect to + self.probe_target = args["probe_target"] + + self.label = "bg " + self.probe_target + + # the binary data format to sent in. In this case, it is a list of + # floats, with the first float being the time stamp and the rest + # being the vector values, one per dimension. + self.struct = struct.Struct('<%df' % (1 + self.n_lines)) + + def attach(self, page, config, uid): + super(Value, self).attach(page, config, uid) + + def add_nengo_objects(self, page): + # create a Node and a Connection so the Node will be given the + # data we want to show while the model is running. + with page.model: + self.node = nengo.Node(self.gather_data, + size_in=self.n_lines) + if self.probe_target == "input": + self.conn = nengo.Connection(self.obj.input, self.node, synapse=0.01) + else: + self.conn = nengo.Connection(self.obj.output, self.node, synapse=0.01) + + def javascript(self): + # generate the javascript that will create the client-side object + info = dict(uid=id(self), label=self.label, + n_lines=self.n_lines, synapse=0) + + if getattr(self.config, "legend_labels") == []: + json = self.javascript_config(info, override={"legend_labels":self.def_legend_labels}) + else: + json = self.javascript_config(info) + return 'new Nengo.Value(main, sim, %s);' % json \ No newline at end of file diff --git a/nengo_gui/components/component.py b/nengo_gui/components/component.py index 63b42d7c..b3b0720c 100644 --- a/nengo_gui/components/component.py +++ b/nengo_gui/components/component.py @@ -82,13 +82,16 @@ def remove_nengo_objects(self, page): """ pass - def javascript_config(self, cfg): + def javascript_config(self, cfg, override = {}): """Convert the nengo.Config information into javascript. This is needed so we can send that config information to the client. """ for attr in self.config._clsparams.params: - cfg[attr] = getattr(self.config, attr) + if attr not in override: + cfg[attr] = getattr(self.config, attr) + else: + cfg[attr] = override[attr] return json.dumps(cfg) def code_python(self, uids): diff --git a/nengo_gui/components/netgraph.py b/nengo_gui/components/netgraph.py index 81eab98e..09bf8fb3 100644 --- a/nengo_gui/components/netgraph.py +++ b/nengo_gui/components/netgraph.py @@ -5,6 +5,7 @@ import threading import nengo +from nengo import spa import json from nengo_gui.components.component import Component @@ -469,6 +470,15 @@ def get_extra_info(self, obj): elif isinstance(obj, nengo.Ensemble): info['dimensions'] = int(obj.size_out) info['n_neurons'] = int(obj.n_neurons) + # TODO: Add the same functionality for the BasalGanglia non-spa net + elif isinstance(obj, spa.BasalGanglia): + info['bg_inputs'] = obj.input.size_in + info['input_labels'] = [] + for ac in obj.actions.actions: + if ac.name == None: + info['input_labels'].append(ac.condition.expression.__str__()) + else: + info['input_labels'].append(ac.name) info['sp_targets'] = ( nengo_gui.components.pointer.Pointer.applicable_targets(obj)) return info diff --git a/nengo_gui/components/value.py b/nengo_gui/components/value.py index 7efacc39..a9bed495 100644 --- a/nengo_gui/components/value.py +++ b/nengo_gui/components/value.py @@ -14,7 +14,7 @@ class Value(Component): show_legend=False, legend_labels=[], **Component.config_defaults) - def __init__(self, obj): + def __init__(self, obj, n_lines=0): super(Value, self).__init__() # the object whose decoded value should be displayed self.obj = obj @@ -23,7 +23,11 @@ def __init__(self, obj): self.data = [] # the number of data values to send - self.n_lines = int(obj.size_out) + self.n_lines = 0 + if n_lines == 0: + self.n_lines = int(obj.size_out) + else: + self.n_lines = n_lines # the binary data format to sent in. In this case, it is a list of # floats, with the first float being the time stamp and the rest @@ -40,7 +44,7 @@ def add_nengo_objects(self, page): # data we want to show while the model is running. with page.model: self.node = nengo.Node(self.gather_data, - size_in=self.obj.size_out) + size_in=self.n_lines) self.conn = nengo.Connection(self.obj, self.node, synapse=0.01) def remove_nengo_objects(self, page): @@ -68,7 +72,8 @@ def update_client(self, client): def javascript(self): # generate the javascript that will create the client-side object info = dict(uid=id(self), label=self.label, - n_lines=self.n_lines, synapse=0) + n_lines=self.n_lines, synapse=0) + json = self.javascript_config(info) return 'new Nengo.Value(main, sim, %s);' % json diff --git a/nengo_gui/static/components/netgraph_item.js b/nengo_gui/static/components/netgraph_item.js index a1cdba83..e4440f81 100644 --- a/nengo_gui/static/components/netgraph_item.js +++ b/nengo_gui/static/components/netgraph_item.js @@ -18,7 +18,6 @@ Nengo.NetGraphItem = function(ng, info, minimap, mini_item) { this.size = info.size; this.type = info.type; this.uid = info.uid; - this.sp_targets = info.sp_targets; this.passthrough = info.passthrough; this.fixed_width = null; this.fixed_height = null; @@ -34,6 +33,12 @@ Nengo.NetGraphItem = function(ng, info, minimap, mini_item) { this.g_items = ng.g_items_mini; } + // SPA network specific parameter + this.sp_targets = info.sp_targets; + // Basal Ganglia network specific parameters + this.bg_inputs = info.bg_inputs; + this.input_labels = info.input_labels; + /** if this is a network, the children list is the set of NetGraphItems * and NetGraphConnections that are inside this network */ this.children = []; @@ -362,7 +367,22 @@ Nengo.NetGraphItem.prototype.generate_menu = function () { items.push(['Semantic pointer plot', function() {self.create_graph('SpaSimilarity', self.sp_targets[0]);}]) } - // TODO: Enable input and output value plots for basal ganglia network + if (this.bg_inputs) { + items.push(['Input Plot', + function () { + self.create_graph('BGPlot', + {"n_lines":self.bg_inputs, "legend_labels":self.input_labels, "probe_target":"input"} + ); + } + ]); + items.push(['Output Plot', + function () { + self.create_graph('BGPlot', + {"n_lines":self.bg_inputs, "legend_labels":self.input_labels, "probe_target":"output"} + ); + } + ]); + } items.push(['Details ...', function() {self.create_modal();}]); return items; }; diff --git a/nengo_gui/static/components/spa_similarity.js b/nengo_gui/static/components/spa_similarity.js index 36633d5f..22ded5b5 100644 --- a/nengo_gui/static/components/spa_similarity.js +++ b/nengo_gui/static/components/spa_similarity.js @@ -27,7 +27,7 @@ Nengo.SpaSimilarity = function(parent, sim, args) { this.legend = document.createElement('div'); this.legend.classList.add('legend', 'unselectable'); this.div.appendChild(this.legend); - this.legend_svg = Nengo.draw_legend(this.legend, args.pointer_labels, this.color_func); + this.legend_svg = Nengo.draw_legend(this.legend, args.pointer_labels, this.color_func, this.uid); }; Nengo.SpaSimilarity.prototype = Object.create(Nengo.Value.prototype); @@ -42,7 +42,7 @@ Nengo.SpaSimilarity.prototype.reset_legend_and_data = function(new_labels){ while(this.legend.lastChild){ this.legend.removeChild(this.legend.lastChild); } - this.legend_svg = d3.select(this.legend).append("svg"); + this.legend_svg = d3.select(this.legend).append("svg").attr("id", "#id"+this.uid); // redraw all the legends if they exist this.pointer_labels = []; @@ -99,7 +99,7 @@ Nengo.SpaSimilarity.prototype.update_legend = function(new_labels){ }); // expand the width of the svg of the longest string - var label_list = $(".legend-label").toArray(); + var label_list = $("#id"+this.uid+" .legend-label").toArray(); var longest_label = label_list.sort( function (a, b) { return b.getBBox().width - a.getBBox().width; } )[0]; diff --git a/nengo_gui/static/components/value.js b/nengo_gui/static/components/value.js index c10f3268..33c43ed7 100644 --- a/nengo_gui/static/components/value.js +++ b/nengo_gui/static/components/value.js @@ -133,7 +133,7 @@ Nengo.Value = function(parent, sim, args) { this.show_legend = args.show_legend || false; if(this.show_legend === true){ - Nengo.draw_legend(this.legend, this.legend_labels, this.color_func); + Nengo.draw_legend(this.legend, this.legend_labels, this.color_func, this.uid); } }; @@ -271,7 +271,7 @@ Nengo.Value.prototype.set_show_legend = function(value){ this.save_layout(); } if (this.show_legend === true){ - Nengo.draw_legend(this.legend, this.legend_labels, this.color_func); + Nengo.draw_legend(this.legend, this.legend_labels, this.color_func, this.uid); } else { // delete the legend's children while(this.legend.lastChild){ @@ -309,7 +309,7 @@ Nengo.Value.prototype.set_legend_labels = function() { while(self.legend.lastChild){ self.legend.removeChild(self.legend.lastChild); } - Nengo.draw_legend(self.legend, self.legend_labels, self.color_func); + Nengo.draw_legend(self.legend, self.legend_labels, self.color_func, this.uid); } $('#OK').attr('data-dismiss', 'modal'); }); diff --git a/nengo_gui/static/nengo.js b/nengo_gui/static/nengo.js index aabd7e06..c5ff540b 100644 --- a/nengo_gui/static/nengo.js +++ b/nengo_gui/static/nengo.js @@ -72,12 +72,13 @@ Nengo.next_zindex = function() { } /* draw a legend */ -Nengo.draw_legend = function(parent, labels, color_func){ +Nengo.draw_legend = function(parent, labels, color_func, uid){ // "20" is around the size of the font legend_svg = d3.select(parent) .append("svg") .attr("width", 150) - .attr("height", 20*labels.length); + .attr("height", 20*labels.length) + .attr("id", "id"+uid); legend_svg.selectAll('rect') .data(labels) @@ -88,15 +89,26 @@ Nengo.draw_legend = function(parent, labels, color_func){ .attr("width", 10) .attr("height", 10) .style("fill", color_func); - + legend_svg.selectAll('text') .data(labels) .enter() .append("text") .attr("x", 15) .attr("y", function(d, i){ return i * 20 + 9;}) + .attr("class", "legend-label") .html(function(d, i) { return labels[i]; }); + + // expand the width of the svg to the length of the longest string + var label_list = $("#id"+uid+" .legend-label").toArray(); + var longest_label = label_list.sort( + function (a, b) { return b.getBBox().width - a.getBBox().width; } + )[0]; + // "30" is for the box colours which is around two characters wide + var svg_right_edge = longest_label.getBBox().width + 30; + legend_svg.attr("width", svg_right_edge); + return legend_svg; } From f64d72662d2c79bba36303eec48af1afdc0aebac Mon Sep 17 00:00:00 2001 From: Sean Aubin Date: Tue, 13 Oct 2015 22:32:16 -0400 Subject: [PATCH 14/25] Added ability to choose different color palette --- nengo_gui/static/color.js | 45 ++++++++++++++ nengo_gui/static/components/spa_similarity.js | 3 - nengo_gui/static/components/value.js | 62 ++++++++++++++++--- nengo_gui/static/modal.js | 3 +- nengo_gui/static/nengo.js | 2 +- nengo_gui/templates/page.html | 1 + 6 files changed, 104 insertions(+), 12 deletions(-) create mode 100644 nengo_gui/static/color.js diff --git a/nengo_gui/static/color.js b/nengo_gui/static/color.js new file mode 100644 index 00000000..026638d5 --- /dev/null +++ b/nengo_gui/static/color.js @@ -0,0 +1,45 @@ +/** + * Generate a color sequence of a given length. + */ + +/** + * Generate a color sequence of a given length. + * + * Colors are defined using a color blind-friendly palette. + */ +Nengo.make_colors = function(N) { + // Color blind palette with blue, green, red, magenta, yellow, cyan + var palette = ["#1c73b3", "#039f74", "#d65e00", "#cd79a7", "#f0e542", "#56b4ea"]; + var c = []; + + for (var i = 0; i < N; i++) { + c.push(palette[i % palette.length]); + } + return c; +} + +/** + * Color blind-friendly palette. + */ +Nengo.default_colors = function() { + // Color blind palette with blue, green, red, magenta, yellow, cyan + var palette = ["#1c73b3", "#039f74", "#d65e00", "#cd79a7", "#f0e542", "#56b4ea"]; + return function(i){ return palette[i%palette.length] }; +} + +/** + * Color palette use by Google for graphics, trends, etc... + */ +Nengo.google_colors = function() { + var palette = ["#3366cc", "#dc3912", "#ff9900", "#109618", "#990099", "#0099c6", "#dd4477", "#66aa00", "#b82e2e", "#316395", "#994499", "#22aa99", "#aaaa11", "#6633cc", "#e67300", "#8b0707", "#651067", "#329262", "#5574a6", "#3b3eac"]; + return function(i){ return palette[i%palette.length] }; +} + +/** list of valid color choices */ +Nengo.color_choices = [ + ["Nengo Color-Blind Friendly (6 colors)", {"func":Nengo.default_colors(), "mod":6}], + ["Google (20 colors)", {"func":Nengo.google_colors(), "mod":20}], + ["D3.js A (20 colors)", {"func":d3.scale.category20(), "mod":20}], + ["D3.js B (20 colors)", {"func":d3.scale.category20b(), "mod":20}], + ["D3.js C (20 colors)", {"func":d3.scale.category20c(), "mod":20}] +]; diff --git a/nengo_gui/static/components/spa_similarity.js b/nengo_gui/static/components/spa_similarity.js index 22ded5b5..482c7a56 100644 --- a/nengo_gui/static/components/spa_similarity.js +++ b/nengo_gui/static/components/spa_similarity.js @@ -17,9 +17,6 @@ Nengo.SpaSimilarity = function(parent, sim, args) { var self = this; - this.colors = Nengo.make_colors(6); - this.color_func = function(d, i) {return self.colors[i % 6]}; - this.line.defined(function(d) { return !isNaN(d)}); // create the legend from label args diff --git a/nengo_gui/static/components/value.js b/nengo_gui/static/components/value.js index 33c43ed7..75c8d3ca 100644 --- a/nengo_gui/static/components/value.js +++ b/nengo_gui/static/components/value.js @@ -46,11 +46,14 @@ Nengo.Value = function(parent, sim, args) { this.path = this.axes2d.svg.append("g").selectAll('path') .data(this.data_store.data); - this.colors = Nengo.make_colors(this.n_lines); + // create the color function + // TODO: save this in the config + this.color_func = Nengo.default_colors(); + this.path.enter() .append('path') .attr('class', 'line') - .style('stroke', function(d, i) {return self.colors[i];}); + .style('stroke', function(d, i){return self.color_func(i)}); // Flag for whether or not update code should be changing the crosshair // Both zooming and the simulator time changing cause an update, but the crosshair @@ -115,8 +118,6 @@ Nengo.Value = function(parent, sim, args) { this.axes2d.axis_y.tickValues([args.min_value, args.max_value]); this.axes2d.fit_ticks(this); - this.colors = Nengo.make_colors(6); - this.color_func = function(d, i) {return self.colors[i % 6]}; this.legend = document.createElement('div'); this.legend.classList.add('legend'); this.div.appendChild(this.legend); @@ -265,6 +266,49 @@ Nengo.Value.prototype.generate_menu = function() { return $.merge(items, Nengo.Component.prototype.generate_menu.call(this)); }; +Nengo.Value.prototype.set_color_func = function() { + var self = this; + + Nengo.modal.clear_body(); + // TODO: Let the user define their own palette + Nengo.modal.title('Choose a palette'); + + // Create a radio button form with the available palettes + var body = Nengo.modal.$body; + var radio_html = "
"; + radio_html += ""+Nengo.color_choices[0][0]+"
" + for (i = 1; i < Nengo.color_choices.length; i++) { + radio_html += ""+Nengo.color_choices[i][0]+"
" + } + radio_html += "
"; + body.append(radio_html); + + // TODO: Make this thing easier to select + var selected_palette = 0; + Nengo.modal.footer('ok_cancel', function(e) { + selected_palette = $("#palette input:radio[name='pal']:checked").val(); + self.color_func = Nengo.color_choices[selected_palette][1]["func"]; + + self.path.style('stroke', function(d, i){return self.color_func(i)}); + if(self.show_legend === true){ + self.clear_legend(); + Nengo.draw_legend(self.legend, self.legend_labels, self.color_func, self.uid); + } + + $('#OK').attr('data-dismiss', 'modal'); + }); + + // allow enter keypress + $("#palette").keypress(function(event) { + if (event.which == 13) { + event.preventDefault(); + $('#OK').click(); + } + }); + + Nengo.modal.show(); +} + Nengo.Value.prototype.set_show_legend = function(value){ if (this.show_legend !== value) { this.show_legend = value; @@ -274,9 +318,13 @@ Nengo.Value.prototype.set_show_legend = function(value){ Nengo.draw_legend(this.legend, this.legend_labels, this.color_func, this.uid); } else { // delete the legend's children - while(this.legend.lastChild){ - this.legend.removeChild(this.legend.lastChild); - } + this.clear_legend(); + } +} + +Nengo.Value.prototype.clear_legend = function() { + while(this.legend.lastChild){ + this.legend.removeChild(this.legend.lastChild); } } diff --git a/nengo_gui/static/modal.js b/nengo_gui/static/modal.js index a2ac7b6c..0c8c3ab7 100644 --- a/nengo_gui/static/modal.js +++ b/nengo_gui/static/modal.js @@ -230,7 +230,8 @@ Nengo.Modal.prototype.main_config = function(options) { event.preventDefault(); $('#OK').click(); } - });} + }); +} /** * Sets up the body for standard input forms diff --git a/nengo_gui/static/nengo.js b/nengo_gui/static/nengo.js index c5ff540b..4e634013 100644 --- a/nengo_gui/static/nengo.js +++ b/nengo_gui/static/nengo.js @@ -88,7 +88,7 @@ Nengo.draw_legend = function(parent, labels, color_func, uid){ .attr("y", function(d, i){ return i * 20;}) .attr("width", 10) .attr("height", 10) - .style("fill", color_func); + .style("fill", function(d, i) {return color_func(i)}); legend_svg.selectAll('text') .data(labels) diff --git a/nengo_gui/templates/page.html b/nengo_gui/templates/page.html index b4d572bc..e2b1707a 100644 --- a/nengo_gui/templates/page.html +++ b/nengo_gui/templates/page.html @@ -140,5 +140,6 @@ + From 8c06f90b8a16bef5598e21e5f0c6ca9074877a5d Mon Sep 17 00:00:00 2001 From: Sean Aubin Date: Tue, 13 Oct 2015 21:58:28 -0400 Subject: [PATCH 15/25] added color palette to config --- nengo_gui/components/value.py | 2 +- nengo_gui/static/components/value.js | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/nengo_gui/components/value.py b/nengo_gui/components/value.py index a9bed495..313f0361 100644 --- a/nengo_gui/components/value.py +++ b/nengo_gui/components/value.py @@ -11,7 +11,7 @@ class Value(Component): # the parameters to be stored in the .cfg file config_defaults = dict(max_value=1, min_value=-1, - show_legend=False, legend_labels=[], + show_legend=False, legend_labels=[], palette_index=0, **Component.config_defaults) def __init__(self, obj, n_lines=0): diff --git a/nengo_gui/static/components/value.js b/nengo_gui/static/components/value.js index 75c8d3ca..5b68e1e4 100644 --- a/nengo_gui/static/components/value.js +++ b/nengo_gui/static/components/value.js @@ -47,8 +47,8 @@ Nengo.Value = function(parent, sim, args) { .data(this.data_store.data); // create the color function - // TODO: save this in the config - this.color_func = Nengo.default_colors(); + this.palette_index = args.palette_index || 0; + this.color_func = Nengo.color_choices[this.palette_index][1]["func"]; this.path.enter() .append('path') @@ -283,22 +283,21 @@ Nengo.Value.prototype.set_color_func = function() { radio_html += ""; body.append(radio_html); - // TODO: Make this thing easier to select - var selected_palette = 0; + // TODO: Make this thing easier to select for the user Nengo.modal.footer('ok_cancel', function(e) { - selected_palette = $("#palette input:radio[name='pal']:checked").val(); - self.color_func = Nengo.color_choices[selected_palette][1]["func"]; + self.palette_index = Number($("#palette input:radio[name='pal']:checked").val()); + self.color_func = Nengo.color_choices[self.palette_index][1]["func"]; self.path.style('stroke', function(d, i){return self.color_func(i)}); if(self.show_legend === true){ self.clear_legend(); Nengo.draw_legend(self.legend, self.legend_labels, self.color_func, self.uid); } - + self.save_layout(); $('#OK').attr('data-dismiss', 'modal'); }); - // allow enter keypress + // allow "Enter" keypress $("#palette").keypress(function(event) { if (event.which == 13) { event.preventDefault(); @@ -358,6 +357,7 @@ Nengo.Value.prototype.set_legend_labels = function() { self.legend.removeChild(self.legend.lastChild); } Nengo.draw_legend(self.legend, self.legend_labels, self.color_func, this.uid); + self.save_layout(); } $('#OK').attr('data-dismiss', 'modal'); }); @@ -372,6 +372,7 @@ Nengo.Value.prototype.layout_info = function () { info.legend_labels = this.legend_labels; info.min_value = this.axes2d.scale_y.domain()[0]; info.max_value = this.axes2d.scale_y.domain()[1]; + info.palette_index = this.palette_index; return info; } From a7b597887b6e73dc040f5a45dca9ec9510f9b1cb Mon Sep 17 00:00:00 2001 From: Sean Aubin Date: Tue, 13 Oct 2015 22:23:50 -0400 Subject: [PATCH 16/25] fixed legend setting behaviour --- nengo_gui/static/components/value.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nengo_gui/static/components/value.js b/nengo_gui/static/components/value.js index 5b68e1e4..7c0d4c43 100644 --- a/nengo_gui/static/components/value.js +++ b/nengo_gui/static/components/value.js @@ -346,9 +346,11 @@ Nengo.Value.prototype.set_legend_labels = function() { if ((label_csv !== null) && (label_csv !== '')) { labels = label_csv.split(','); - for(i=0; i Date: Fri, 16 Oct 2015 11:45:43 -0400 Subject: [PATCH 17/25] expanded palette switching to spa_similarity and bg_plot --- nengo_gui/components/bg_plot.py | 7 +- nengo_gui/components/spa_similarity.py | 8 +- nengo_gui/static/components/spa_similarity.js | 74 ++++++++++++++++--- nengo_gui/static/components/value.js | 2 + 4 files changed, 70 insertions(+), 21 deletions(-) diff --git a/nengo_gui/components/bg_plot.py b/nengo_gui/components/bg_plot.py index 7f5b870b..93ca69f6 100644 --- a/nengo_gui/components/bg_plot.py +++ b/nengo_gui/components/bg_plot.py @@ -4,7 +4,6 @@ import numpy as np import ipdb -from nengo_gui.components.component import Component from nengo_gui.components.value import Value @@ -12,9 +11,9 @@ class BGPlot(Value): """The server-side system for the SPA Basal Ganglia plot.""" # the parameters to be stored in the .cfg file - config_defaults = dict(max_value=1, min_value=-1, - show_legend=True, legend_labels=[], - **Component.config_defaults) + config_defaults = Value.config_defaults + config_defaults["palette_index"] = 1 + config_defaults["show_legend"] = True def __init__(self, obj, **kwargs): args = kwargs["args"] diff --git a/nengo_gui/components/spa_similarity.py b/nengo_gui/components/spa_similarity.py index 4a746b17..4ad19d79 100644 --- a/nengo_gui/components/spa_similarity.py +++ b/nengo_gui/components/spa_similarity.py @@ -7,9 +7,8 @@ class SpaSimilarity(SpaPlot): - config_defaults = dict(max_value=1, - min_value=-1, - show_pairs=False, + config_defaults = dict(max_value=1.5, min_value=-1.5, + palette_index=1, show_pairs=False, **Component.config_defaults) def __init__(self, obj, **kwargs): @@ -80,8 +79,7 @@ def update_legend(self, vocab): def javascript(self): """Generate the javascript that will create the client-side object""" info = dict(uid=id(self), label=self.label, n_lines=len(self.labels), - synapse=0, min_value=-1.5, max_value=1.5, - pointer_labels=self.labels) + synapse=0, pointer_labels=self.labels) json = self.javascript_config(info) return 'new Nengo.SpaSimilarity(main, sim, %s);' % json diff --git a/nengo_gui/static/components/spa_similarity.js b/nengo_gui/static/components/spa_similarity.js index 482c7a56..c9ed2e7e 100644 --- a/nengo_gui/static/components/spa_similarity.js +++ b/nengo_gui/static/components/spa_similarity.js @@ -20,11 +20,11 @@ Nengo.SpaSimilarity = function(parent, sim, args) { this.line.defined(function(d) { return !isNaN(d)}); // create the legend from label args - this.pointer_labels = args.pointer_labels; + this.legend_labels = args.pointer_labels; this.legend = document.createElement('div'); this.legend.classList.add('legend', 'unselectable'); this.div.appendChild(this.legend); - this.legend_svg = Nengo.draw_legend(this.legend, args.pointer_labels, this.color_func, this.uid); + this.legend_svg = Nengo.draw_legend(this.legend, this.legend_labels, this.color_func, this.uid); }; Nengo.SpaSimilarity.prototype = Object.create(Nengo.Value.prototype); @@ -42,7 +42,7 @@ Nengo.SpaSimilarity.prototype.reset_legend_and_data = function(new_labels){ this.legend_svg = d3.select(this.legend).append("svg").attr("id", "#id"+this.uid); // redraw all the legends if they exist - this.pointer_labels = []; + this.legend_labels = []; if(new_labels[0] != ""){ this.update_legend(new_labels); } @@ -68,16 +68,16 @@ Nengo.SpaSimilarity.prototype.data_msg = function(push_data){ Nengo.SpaSimilarity.prototype.update_legend = function(new_labels){ var self = this; - this.pointer_labels = this.pointer_labels.concat(new_labels); + this.legend_labels = this.legend_labels.concat(new_labels); // expand the height of the svg, where "20" is around the height of the font - this.legend_svg.attr("height", 20 * this.pointer_labels.length); + this.legend_svg.attr("height", 20 * this.legend_labels.length); // Data join - var recs = this.legend_svg.selectAll("rect").data(this.pointer_labels); - var legend_labels = this.legend_svg.selectAll(".legend-label").data(this.pointer_labels); - var val_texts = this.legend_svg.selectAll(".val").data(this.pointer_labels); + var recs = this.legend_svg.selectAll("rect").data(this.legend_labels); + var legend_labels = this.legend_svg.selectAll(".legend-label").data(this.legend_labels); + var val_texts = this.legend_svg.selectAll(".val").data(this.legend_labels); // enter to append remaining lines recs.enter() .append("rect") @@ -85,14 +85,14 @@ Nengo.SpaSimilarity.prototype.update_legend = function(new_labels){ .attr("y", function(d, i){ return i * 20;}) .attr("width", 10) .attr("height", 10) - .style("fill", this.color_func); + .style("fill", function(d, i) {return self.color_func(i)}); legend_labels.enter().append("text") .attr("x", 15) .attr("y", function(d, i){ return i * 20 + 9;}) .attr("class", "legend-label") .html(function(d, i) { - return self.pointer_labels[i]; + return self.legend_labels[i]; }); // expand the width of the svg of the longest string @@ -149,7 +149,7 @@ Nengo.SpaSimilarity.prototype.update = function() { this.path.enter() .append('path') .attr('class', 'line') - .style('stroke', this.color_func) + .style('stroke', function(d, i) {return self.color_func(i)}) .attr('d', self.line); // remove any lines that aren't needed anymore this.path.exit().remove(); @@ -163,7 +163,7 @@ Nengo.SpaSimilarity.prototype.update = function() { } // update the text in the legend - var texts = this.legend_svg.selectAll(".val").data(this.pointer_labels); + var texts = this.legend_svg.selectAll(".val").data(this.legend_labels); texts.html(function(d, i) { var sign = ''; @@ -187,10 +187,60 @@ Nengo.SpaSimilarity.prototype.generate_menu = function() { items.push(['Show pairs', function() {self.set_show_pairs(true);}]); } + items.push(['Change color palette', function() {self.set_color_func();}]) + // add the parent's menu items to this return $.merge(items, Nengo.Component.prototype.generate_menu.call(this)); }; +Nengo.SpaSimilarity.prototype.set_color_func = function() { + var self = this; + + Nengo.modal.clear_body(); + // TODO: Let the user define their own palette + Nengo.modal.title('Choose a palette'); + + // Create a radio button form with the available palettes + var body = Nengo.modal.$body; + var radio_html = "
"; + radio_html += ""+Nengo.color_choices[0][0]+"
" + for (i = 1; i < Nengo.color_choices.length; i++) { + radio_html += ""+Nengo.color_choices[i][0]+"
" + } + radio_html += "
"; + body.append(radio_html); + + // TODO: Make this thing easier to select for the user + Nengo.modal.footer('ok_cancel', function(e) { + self.palette_index = Number($("#palette input:radio[name='pal']:checked").val()); + self.color_func = Nengo.color_choices[self.palette_index][1]["func"]; + + self.path.style('stroke', function(d, i){return self.color_func(i)}); + self.legend_svg.selectAll("rect").remove(); + var recs = self.legend_svg.selectAll("rect").data(self.legend_labels); + recs.enter() + .append("rect") + .attr("x", 0) + .attr("y", function(d, i){ return i * 20;}) + .attr("width", 10) + .attr("height", 10) + .style("fill", function(d, i) {return self.color_func(i)}); + self.save_layout(); + $('#OK').attr('data-dismiss', 'modal'); + }); + + // allow "Enter" keypress + $("#palette").keypress(function(event) { + if (event.which == 13) { + event.preventDefault(); + $('#OK').click(); + } + }); + + Nengo.modal.show(); +} + + Nengo.SpaSimilarity.prototype.set_show_pairs = function(value) { if (this.show_pairs !== value) { this.show_pairs = value; diff --git a/nengo_gui/static/components/value.js b/nengo_gui/static/components/value.js index 7c0d4c43..6a5fb718 100644 --- a/nengo_gui/static/components/value.js +++ b/nengo_gui/static/components/value.js @@ -262,6 +262,8 @@ Nengo.Value.prototype.generate_menu = function() { // TODO: give the legend it's own context menu items.push(['Set legend labels', function () {self.set_legend_labels();}]) + items.push(['Change color palette', function () {self.set_color_func();}]) + // add the parent's menu items to this return $.merge(items, Nengo.Component.prototype.generate_menu.call(this)); }; From 0e7cef09278ac927ea996677d38ca0916f26a6b2 Mon Sep 17 00:00:00 2001 From: Sean Aubin Date: Fri, 16 Oct 2015 11:46:41 -0400 Subject: [PATCH 18/25] removed left-over ipdb --- nengo_gui/components/bg_plot.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nengo_gui/components/bg_plot.py b/nengo_gui/components/bg_plot.py index 93ca69f6..50774f28 100644 --- a/nengo_gui/components/bg_plot.py +++ b/nengo_gui/components/bg_plot.py @@ -2,7 +2,6 @@ import nengo import numpy as np -import ipdb from nengo_gui.components.value import Value From 30b4e0232e005ddd16b36545457494b23889af2a Mon Sep 17 00:00:00 2001 From: Sean Aubin Date: Fri, 16 Oct 2015 12:39:40 -0400 Subject: [PATCH 19/25] Fixed id and empty labels error for spasimilarity --- nengo_gui/static/components/spa_similarity.js | 2 +- nengo_gui/static/nengo.js | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/nengo_gui/static/components/spa_similarity.js b/nengo_gui/static/components/spa_similarity.js index c9ed2e7e..ecdc5c1e 100644 --- a/nengo_gui/static/components/spa_similarity.js +++ b/nengo_gui/static/components/spa_similarity.js @@ -39,7 +39,7 @@ Nengo.SpaSimilarity.prototype.reset_legend_and_data = function(new_labels){ while(this.legend.lastChild){ this.legend.removeChild(this.legend.lastChild); } - this.legend_svg = d3.select(this.legend).append("svg").attr("id", "#id"+this.uid); + this.legend_svg = d3.select(this.legend).append("svg").attr("id", "id"+this.uid); // redraw all the legends if they exist this.legend_labels = []; diff --git a/nengo_gui/static/nengo.js b/nengo_gui/static/nengo.js index 4e634013..bcb1dd1e 100644 --- a/nengo_gui/static/nengo.js +++ b/nengo_gui/static/nengo.js @@ -80,6 +80,10 @@ Nengo.draw_legend = function(parent, labels, color_func, uid){ .attr("height", 20*labels.length) .attr("id", "id"+uid); + if(labels.length == 0){ + return legend_svg; + } + legend_svg.selectAll('rect') .data(labels) .enter() From 91e0a31b783091b88da764e9f211c12d91b40c3c Mon Sep 17 00:00:00 2001 From: Sean Aubin Date: Mon, 19 Oct 2015 21:20:27 -0400 Subject: [PATCH 20/25] Removed redundant variable from bg_plot --- nengo_gui/components/bg_plot.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/nengo_gui/components/bg_plot.py b/nengo_gui/components/bg_plot.py index 50774f28..2df4fa69 100644 --- a/nengo_gui/components/bg_plot.py +++ b/nengo_gui/components/bg_plot.py @@ -26,11 +26,6 @@ def __init__(self, obj, **kwargs): self.label = "bg " + self.probe_target - # the binary data format to sent in. In this case, it is a list of - # floats, with the first float being the time stamp and the rest - # being the vector values, one per dimension. - self.struct = struct.Struct('<%df' % (1 + self.n_lines)) - def attach(self, page, config, uid): super(Value, self).attach(page, config, uid) From a913d866cbacdefa60e6dacb42b20eb8edca6301 Mon Sep 17 00:00:00 2001 From: Sean Aubin Date: Wed, 28 Oct 2015 15:25:48 -0400 Subject: [PATCH 21/25] fixed formatting stuff --- nengo_gui/static/components/value.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/nengo_gui/static/components/value.js b/nengo_gui/static/components/value.js index 6a5fb718..1414af22 100644 --- a/nengo_gui/static/components/value.js +++ b/nengo_gui/static/components/value.js @@ -123,9 +123,9 @@ Nengo.Value = function(parent, sim, args) { this.div.appendChild(this.legend); this.legend_labels = args.legend_labels || []; - if(this.legend_labels.length !== this.n_lines){ + if (this.legend_labels.length !== this.n_lines) { // fill up an array with temporary labels - for(i=0; i Date: Thu, 29 Oct 2015 20:32:28 -0400 Subject: [PATCH 22/25] fix bg plot disapearing problem --- nengo_gui/components/bg_plot.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/nengo_gui/components/bg_plot.py b/nengo_gui/components/bg_plot.py index 2df4fa69..26f69ce2 100644 --- a/nengo_gui/components/bg_plot.py +++ b/nengo_gui/components/bg_plot.py @@ -47,6 +47,14 @@ def javascript(self): if getattr(self.config, "legend_labels") == []: json = self.javascript_config(info, override={"legend_labels":self.def_legend_labels}) + self.config.legend_labels = self.def_legend_labels else: json = self.javascript_config(info) - return 'new Nengo.Value(main, sim, %s);' % json \ No newline at end of file + return 'new Nengo.Value(main, sim, %s);' % json + + def code_python_args(self, uids): + return [ + uids[self.obj], + ' args=dict(n_lines=%s, legend_labels=%s, probe_target="%s")' + % (self.n_lines, self.config.legend_labels, self.probe_target,) + ] From 89092b0eff3e3bf215a16fef57ac2a7b0af6387e Mon Sep 17 00:00:00 2001 From: Sean Aubin Date: Mon, 16 Nov 2015 17:13:32 -0500 Subject: [PATCH 23/25] delete useless fil --- nengo_gui/components/tests/test_component.py | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 nengo_gui/components/tests/test_component.py diff --git a/nengo_gui/components/tests/test_component.py b/nengo_gui/components/tests/test_component.py deleted file mode 100644 index 9ebc5631..00000000 --- a/nengo_gui/components/tests/test_component.py +++ /dev/null @@ -1,8 +0,0 @@ -from nengo_gui.components.component import Component - -def test_javascript_config(): - # test that the config is applying defaults properly - info = dict(uid=123, label="pants", - n_lines=4, synapse=0) - co = Component() - json = co.javascript_config(info) \ No newline at end of file From b319286ba392f13b4f49f2ad92e8d0e6f8a49673 Mon Sep 17 00:00:00 2001 From: Sean Aubin Date: Mon, 16 Nov 2015 22:18:41 -0500 Subject: [PATCH 24/25] fixup --- nengo_gui/components/component.py | 2 +- nengo_gui/components/value.py | 6 +++--- nengo_gui/static/modal.js | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/nengo_gui/components/component.py b/nengo_gui/components/component.py index 088443d7..7d02552f 100644 --- a/nengo_gui/components/component.py +++ b/nengo_gui/components/component.py @@ -84,7 +84,7 @@ def remove_nengo_objects(self, page): """ pass - def javascript_config(self, cfg, override = {}): + def javascript_config(self, cfg): """Convert the nengo.Config information into javascript. This is needed so we can send that config information to the client. diff --git a/nengo_gui/components/value.py b/nengo_gui/components/value.py index 184978d7..2016ed28 100644 --- a/nengo_gui/components/value.py +++ b/nengo_gui/components/value.py @@ -11,10 +11,10 @@ class Value(Component): # the parameters to be stored in the .cfg file config_defaults = dict(max_value=1, min_value=-1, - show_legend=False, legend_labels=[], palette_index=0, - **Component.config_defaults) + show_legend=False, legend_labels=[], palette_index=0, + **Component.config_defaults) - def __init__(self, obj, n_lines=0): + def __init__(self, obj): super(Value, self).__init__() # the object whose decoded value should be displayed self.obj = obj diff --git a/nengo_gui/static/modal.js b/nengo_gui/static/modal.js index a0ccd3b6..b1526a23 100644 --- a/nengo_gui/static/modal.js +++ b/nengo_gui/static/modal.js @@ -230,8 +230,7 @@ Nengo.Modal.prototype.main_config = function(options) { event.preventDefault(); $('#OK').click(); } - }); -} + });} /** * Sets up the body for standard input forms From 59272f3a8a11d5925aee91dfaafd7dfe9cbca836 Mon Sep 17 00:00:00 2001 From: Sean Aubin Date: Mon, 16 Nov 2015 22:30:09 -0500 Subject: [PATCH 25/25] fixup config --- nengo_gui/config.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nengo_gui/config.py b/nengo_gui/config.py index 6cc2fd20..f4ffea65 100644 --- a/nengo_gui/config.py +++ b/nengo_gui/config.py @@ -47,8 +47,7 @@ def dumps(self, uids): lines.append('%s = %s' % (uid, obj.code_python(uids))) for k in obj.config_defaults.keys(): v = getattr(self[obj], k) - if(isinstance(v, bool) or isinstance(v, list) - or isinstance(v, dict)): + if isinstance(v, bool): val = '%s' % v else: val = '%g' % v