diff --git a/nengo_gui/components/__init__.py b/nengo_gui/components/__init__.py index a46052b6..67167e7d 100644 --- a/nengo_gui/components/__init__.py +++ b/nengo_gui/components/__init__.py @@ -8,6 +8,7 @@ from .pointer import Pointer from .netgraph import NetGraph from .ace_editor import AceEditor +from .htmlview import HTMLView # 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/htmlview.py b/nengo_gui/components/htmlview.py new file mode 100644 index 00000000..6172678a --- /dev/null +++ b/nengo_gui/components/htmlview.py @@ -0,0 +1,45 @@ +import struct +import collections + +import nengo +import numpy as np + +from nengo_gui.components.component import Component + + +class HTMLView(Component): + def __init__(self, obj): + super(HTMLView, self).__init__() + self.obj = obj + self.obj_output = obj.output + self.data = collections.deque() + + def attach(self, page, config, uid): + super(HTMLView, self).attach(page, config, uid) + self.label = page.get_label(self.obj) + + def add_nengo_objects(self, page): + with page.model: + self.obj.output = self.gather_data + + def remove_nengo_objects(self, page): + self.obj.output = self.obj_output + + def gather_data(self, t, *x): + value = self.obj_output(t, *x) + data = '%g %s' % (t, self.obj_output._nengo_html_) + self.data.append(data) + return value + + def update_client(self, client): + while len(self.data) > 0: + item = self.data.popleft() + client.write(item) + + def javascript(self): + info = dict(uid=id(self), label=self.label) + json = self.javascript_config(info) + return 'new Nengo.HTMLView(main, sim, %s);' % json + + def code_python_args(self, uids): + return [uids[self.obj]] diff --git a/nengo_gui/components/netgraph.py b/nengo_gui/components/netgraph.py index 7b6e99ec..e5df5283 100644 --- a/nengo_gui/components/netgraph.py +++ b/nengo_gui/components/netgraph.py @@ -433,6 +433,9 @@ def create_object(self, client, obj, type, parent): info['passthrough'] = True if type == 'ens' or type == 'node': info['dimensions'] = int(obj.size_out) + if type == 'node': + if callable(obj.output) and hasattr(obj.output, '_nengo_html_'): + info['html'] = True info['sp_targets'] = ( nengo_gui.components.pointer.Pointer.applicable_targets(obj)) diff --git a/nengo_gui/examples/basics/html.py b/nengo_gui/examples/basics/html.py new file mode 100644 index 00000000..29054fc3 --- /dev/null +++ b/nengo_gui/examples/basics/html.py @@ -0,0 +1,84 @@ +# Creating Custom Displays + +# This demonstrates using the special _nengo_html_ variable to create +# custom visualizations. In general, this lets you take values from the model +# and use them to make HTML that displays something useful for your situation. +# +# The basic technique is that if you define a function for a Node, in addition +# to returning a value, you can also set the _nengo_html_ parameter on that +# function. You can set it to any HTML you want, and that HTML will be +# displayed in a custom HTML graph (available by right-clicking on the Node). + +# The first example is a timer function that says "Ready..." for the +# first second of the simulation, then says "Set..." for the next second, +# and then says "Go!". This shows how you can change the HTML based on the +# time of the simulation. +# +# The second example shows that the custom HTML can also be based on the values +# represented by neurons. Here, we make a Node that reads in a value that is +# treated as an amount. If the value is above 0.5, the HTML says "large", +# if the value is below -0.5, it says "small", and otherwise it says "medium". +# +# Finally, the third example uses SVG and trigonometry to create a simple +# visualization of a two-joint arm. The two values represented by the +# Ensemble are the two joint angles (the shoulder and the elbow). From these +# values, the position of the elbow and hand are computed, and lines are +# drawn to show the arm. + +import nengo +import numpy as np + +model = nengo.Network() +with model: + + # Example 1: a timer + def timer_function(t): + if t < 1.0: + timer_function._nengo_html_ = '

Ready...

' + return 0 + elif t < 2.0: + timer_function._nengo_html_ = '

Set...

' + return 0 + else: + timer_function._nengo_html_ = '

Go!

' + return 1 + timer = nengo.Node(timer_function) + + + # Example 2: displaying a value + def amount_function(t, x): + if x < -0.5: + amount_function._nengo_html_ = '

small

' + elif -0.5 < x < 0.5: + amount_function._nengo_html_ = '

medium

' + else: + amount_function._nengo_html_ = '

large

' + stim_amount = nengo.Node(0) + amount = nengo.Ensemble(n_neurons=100, dimensions=1) + display_amount = nengo.Node(amount_function, size_in=1) + nengo.Connection(stim_amount, amount) + nengo.Connection(amount, display_amount) + + + # Example 3: a two-joint arm + def arm_function(t, angles): + len0 = 50 + len1 = 30 + x1 = 50 + y1 = 100 + x2 = x1 + len0 * np.sin(angles[0]) + y2 = y1 - len0 * np.cos(angles[0]) + x3 = x2 + len1 * np.sin(angles[0] + angles[1]) + y3 = y2 - len1 * np.cos(angles[0] + angles[1]) + arm_function._nengo_html_ = ''' + + + + + '''.format(**locals()) + stim_angles = nengo.Node([0.3, 0.3]) + angles = nengo.Ensemble(n_neurons=200, dimensions=2) + arm = nengo.Node(arm_function, size_in=2) + nengo.Connection(stim_angles, angles) + nengo.Connection(angles, arm) + \ No newline at end of file diff --git a/nengo_gui/examples/basics/html.py.cfg b/nengo_gui/examples/basics/html.py.cfg new file mode 100644 index 00000000..cfa54bd9 --- /dev/null +++ b/nengo_gui/examples/basics/html.py.cfg @@ -0,0 +1,65 @@ +_viz_0 = nengo_gui.components.HTMLView(arm) +_viz_config[_viz_0].label_visible = True +_viz_config[_viz_0].width = 0.145178 +_viz_config[_viz_0].height = 0.151002 +_viz_config[_viz_0].y = 0.768603 +_viz_config[_viz_0].x = 0.876166 +_viz_1 = nengo_gui.components.Slider(stim_angles) +_viz_config[_viz_1].label_visible = True +_viz_config[_viz_1].width = 0.124673 +_viz_config[_viz_1].x = 0.233144 +_viz_config[_viz_1].y = 0.790798 +_viz_config[_viz_1].max_value = 1 +_viz_config[_viz_1].min_value = -1 +_viz_config[_viz_1].height = 0.145663 +_viz_2 = nengo_gui.components.HTMLView(timer) +_viz_config[_viz_2].label_visible = True +_viz_config[_viz_2].width = 0.197581 +_viz_config[_viz_2].height = 0.0953106 +_viz_config[_viz_2].y = 0.0978446 +_viz_config[_viz_2].x = 0.833259 +_viz_4 = nengo_gui.components.Value(timer) +_viz_config[_viz_4].label_visible = True +_viz_config[_viz_4].width = 0.145816 +_viz_config[_viz_4].x = 0.497152 +_viz_config[_viz_4].y = 0.0915505 +_viz_config[_viz_4].max_value = 1 +_viz_config[_viz_4].min_value = -1 +_viz_config[_viz_4].height = 0.101605 +_viz_5 = nengo_gui.components.Slider(stim_amount) +_viz_config[_viz_5].label_visible = False +_viz_config[_viz_5].width = 0.0998842 +_viz_config[_viz_5].x = 0.212906 +_viz_config[_viz_5].y = 0.379993 +_viz_config[_viz_5].max_value = 1 +_viz_config[_viz_5].min_value = -1 +_viz_config[_viz_5].height = 0.143865 +_viz_6 = nengo_gui.components.HTMLView(display_amount) +_viz_config[_viz_6].label_visible = True +_viz_config[_viz_6].width = 0.127589 +_viz_config[_viz_6].height = 0.1061 +_viz_config[_viz_6].y = 0.415959 +_viz_config[_viz_6].x = 0.856185 +_viz_ace_editor = nengo_gui.components.AceEditor() +_viz_net_graph = nengo_gui.components.NetGraph() +_viz_sim_control = nengo_gui.components.SimControl() +_viz_config[_viz_sim_control].kept_time = 4 +_viz_config[_viz_sim_control].shown_time = 0.5 +_viz_config[amount].pos=(0.5338306914210958, 0.4086156823527614) +_viz_config[amount].size=(0.09749681440124454, 0.10359036530132232) +_viz_config[angles].pos=(0.5519433694044356, 0.7832269165834734) +_viz_config[angles].size=(0.07919650508520541, 0.09767091585553361) +_viz_config[arm].pos=(0.8780716431486573, 0.7769328184385826) +_viz_config[arm].size=(0.158196444012342, 0.14761158744564548) +_viz_config[display_amount].pos=(0.8561991906510176, 0.41041399610844403) +_viz_config[display_amount].size=(0.1392403185143874, 0.11883856735471768) +_viz_config[model].pos=(-0.04680408247364714, 0.01466241789112259) +_viz_config[model].size=(1.0055632718705836, 1.0055632718705836) +_viz_config[model].expanded=True +_viz_config[model].has_layout=True +_viz_config[stim_amount].pos=(0.21292035569101672, 0.4032207410857123) +_viz_config[stim_amount].size=(0.10132806751847824, 0.11883856735471773) +_viz_config[stim_angles].pos=(0.23084097794972894, 0.786109085846491) +_viz_config[stim_angles].size=(0.1255178668096988, 0.13956290262502596) +_viz_config[timer].pos=(0.20136940789451646, 0.10488798199188551) +_viz_config[timer].size=(0.10038894308056127, 0.08287229224105784) \ No newline at end of file diff --git a/nengo_gui/static/htmlview.js b/nengo_gui/static/htmlview.js new file mode 100644 index 00000000..b6ee3c61 --- /dev/null +++ b/nengo_gui/static/htmlview.js @@ -0,0 +1,88 @@ +/** + * Decoded pointer display + * @constructor + * + * @param {dict} args - A set of constructor arguments (see Nengo.Component) + * @param {Nengo.SimControl} args.sim - the simulation controller + */ + +Nengo.HTMLView = function(parent, sim, args) { + Nengo.Component.call(this, parent, args); + var self = this; + + this.sim = sim; + + this.pdiv = document.createElement('div'); + this.pdiv.style.width = '100%'; + this.pdiv.style.height = '100%'; + Nengo.set_transform(this.pdiv, 0, 0); + this.pdiv.style.position = 'fixed'; + this.pdiv.classList.add('htmlview'); + this.div.appendChild(this.pdiv); + + /** for storing the accumulated data */ + this.data_store = new Nengo.DataStore(1, this.sim, 0); + + /** call schedule_update whenever the time is adjusted in the SimControl */ + this.sim.div.addEventListener('adjust_time', + function(e) {self.schedule_update();}, false); + + this.on_resize(this.get_screen_width(), this.get_screen_height()); + + + +}; +Nengo.HTMLView.prototype = Object.create(Nengo.Component.prototype); +Nengo.HTMLView.prototype.constructor = Nengo.Pointer; + + +/** + * Receive new line data from the server + */ +Nengo.HTMLView.prototype.on_message = function(event) { + var data = event.data.split(" ", 1); + var time = parseFloat(data[0]); + + var msg = event.data.substring(data[0].length + 1); + + this.data_store.push([time, msg]); + this.schedule_update(); +} + +/** + * Redraw the lines and axis due to changed data + */ +Nengo.HTMLView.prototype.update = function() { + /** let the data store clear out old values */ + this.data_store.update(); + + var data = this.data_store.get_last_data()[0]; + + if (data === undefined) { + data = ''; + } + + this.pdiv.innerHTML = data; + +}; + +/** + * Adjust the graph layout due to changed size + */ +Nengo.HTMLView.prototype.on_resize = function(width, height) { + if (width < this.minWidth) { + width = this.minWidth; + } + if (height < this.minHeight) { + height = this.minHeight; + }; + + this.width = width; + this.height = height; + //this.div.style.width = width; + //this.div.style.height = height; + + this.label.style.width = width; + + this.update(); +}; diff --git a/nengo_gui/static/netgraph_item.js b/nengo_gui/static/netgraph_item.js index 73110686..971a6d88 100644 --- a/nengo_gui/static/netgraph_item.js +++ b/nengo_gui/static/netgraph_item.js @@ -24,6 +24,7 @@ Nengo.NetGraphItem = function(ng, info, minimap, mini_item) { this.fixed_height = null; this.dimensions = info.dimensions; this.minimap = minimap; + this.html_node = info.html; if (minimap == false) { this.g_networks = ng.g_networks; this.g_items = ng.g_items; @@ -350,6 +351,9 @@ Nengo.NetGraphItem.prototype.generate_menu = function () { if (this.dimensions > 1) { items.push(['XY-value', function() {self.create_graph('XYValue');}]) } + if (this.html_node) { + items.push(['HTML', function() {self.create_graph('HTMLView');}]) + } } if (this.sp_targets.length > 0) { items.push(['Semantic pointer', diff --git a/nengo_gui/templates/page.html b/nengo_gui/templates/page.html index 3f101d11..33ad8930 100644 --- a/nengo_gui/templates/page.html +++ b/nengo_gui/templates/page.html @@ -125,6 +125,7 @@ +