Skip to content

Commit

Permalink
Merge pull request #404 from nengo/html-nodes
Browse files Browse the repository at this point in the history
HTMLView component for custom visualizations
  • Loading branch information
tcstewar committed Aug 5, 2015
2 parents 76b830c + 2c35ff0 commit bf7ab6e
Show file tree
Hide file tree
Showing 8 changed files with 291 additions and 0 deletions.
1 change: 1 addition & 0 deletions nengo_gui/components/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
45 changes: 45 additions & 0 deletions nengo_gui/components/htmlview.py
Original file line number Diff line number Diff line change
@@ -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]]
3 changes: 3 additions & 0 deletions nengo_gui/components/netgraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
84 changes: 84 additions & 0 deletions nengo_gui/examples/basics/html.py
Original file line number Diff line number Diff line change
@@ -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_ = '<h1>Ready...</h1>'
return 0
elif t < 2.0:
timer_function._nengo_html_ = '<h1>Set...</h1>'
return 0
else:
timer_function._nengo_html_ = '<h1>Go!</h1>'
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_ = '<h2>small</h2>'
elif -0.5 < x < 0.5:
amount_function._nengo_html_ = '<h2>medium</h2>'
else:
amount_function._nengo_html_ = '<h2>large</h2>'
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_ = '''
<svg width="100%" height="100%" viewbox="0 0 100 100">
<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" style="stroke:black"/>
<line x1="{x2}" y1="{y2}" x2="{x3}" y2="{y3}" style="stroke:black"/>
</svg>
'''.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)

65 changes: 65 additions & 0 deletions nengo_gui/examples/basics/html.py.cfg
Original file line number Diff line number Diff line change
@@ -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)
88 changes: 88 additions & 0 deletions nengo_gui/static/htmlview.js
Original file line number Diff line number Diff line change
@@ -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();
};
4 changes: 4 additions & 0 deletions nengo_gui/static/netgraph_item.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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',
Expand Down
1 change: 1 addition & 0 deletions nengo_gui/templates/page.html
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ <h4 class="modal-title"></h4>
<script src="static/sim_control.js"></script>
<script src="static/netgraph.js"></script>
<script src="static/pointer.js"></script>
<script src="static/htmlview.js"></script>
<script src="static/netgraph_item.js"></script>
<script src="static/netgraph_conn.js"></script>
<script src="static/top_toolbar.js"></script>
Expand Down

0 comments on commit bf7ab6e

Please sign in to comment.