From b13b81542e0377861bea25ea9d2b699819a33cb0 Mon Sep 17 00:00:00 2001 From: Trevor Bekolay Date: Mon, 22 Aug 2016 17:08:30 -0400 Subject: [PATCH] WIP: style fixes --- .gitignore | 3 + nengo_gui/static/components/2d_axes.ts | 52 +- nengo_gui/static/components/component.ts | 210 ++--- nengo_gui/static/components/htmlview.ts | 45 +- nengo_gui/static/components/image.ts | 76 +- nengo_gui/static/components/netgraph.ts | 448 ++++++----- nengo_gui/static/components/netgraph_conn.ts | 195 ++--- nengo_gui/static/components/netgraph_item.ts | 637 ++++++++-------- nengo_gui/static/components/pointer.ts | 142 ++-- nengo_gui/static/components/raster.ts | 118 +-- nengo_gui/static/components/slider.ts | 211 ++--- nengo_gui/static/components/slidercontrol.ts | 136 ++-- nengo_gui/static/components/spa_similarity.ts | 81 +- nengo_gui/static/components/time_axes.ts | 36 +- nengo_gui/static/components/value.ts | 305 ++++---- nengo_gui/static/components/xy_axes.ts | 7 +- nengo_gui/static/components/xyvalue.ts | 174 +++-- nengo_gui/static/config.ts | 18 +- nengo_gui/static/data_to_csv.ts | 34 +- nengo_gui/static/datastore.ts | 166 ++-- nengo_gui/static/dist/nengo.js | 36 +- nengo_gui/static/editor.ts | 153 ++-- nengo_gui/static/hotkeys.ts | 61 +- nengo_gui/static/jquery.ts | 16 - nengo_gui/static/menu.ts | 78 +- nengo_gui/static/modal.ts | 721 +++++++++--------- nengo_gui/static/nengo.js | 54 -- nengo_gui/static/nengo.ts | 50 +- nengo_gui/static/side_menu.ts | 84 +- nengo_gui/static/sim_control.ts | 237 +++--- nengo_gui/static/test/datastore_test.js | 199 ----- nengo_gui/static/tests/datastore.test.ts | 252 ++++++ nengo_gui/static/tooltips.ts | 157 ++-- nengo_gui/static/top_toolbar.ts | 118 +-- nengo_gui/static/utils.ts | 46 +- nengo_gui/static/viewport.ts | 48 +- nengo_gui/static/webpack.d.ts | 5 - nengo_gui/templates/page.html | 2 +- package.json | 9 +- tsconfig.json | 3 +- tslint.json | 8 + typings.json | 11 +- webpack.config.js | 12 +- 43 files changed, 2905 insertions(+), 2549 deletions(-) delete mode 100644 nengo_gui/static/jquery.ts delete mode 100644 nengo_gui/static/nengo.js delete mode 100644 nengo_gui/static/test/datastore_test.js create mode 100644 nengo_gui/static/tests/datastore.test.ts delete mode 100644 nengo_gui/static/webpack.d.ts create mode 100644 tslint.json diff --git a/.gitignore b/.gitignore index 9758f20a..b6770ff6 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,6 @@ out/ # TypeScript typings/ + +# JS files associated with tests +**/tests/*.js diff --git a/nengo_gui/static/components/2d_axes.ts b/nengo_gui/static/components/2d_axes.ts index 960d325b..8dfe239e 100644 --- a/nengo_gui/static/components/2d_axes.ts +++ b/nengo_gui/static/components/2d_axes.ts @@ -11,18 +11,35 @@ */ import * as d3 from "d3"; +import * as $ from "jquery"; export default class Axes2D { + ax_bottom; + ax_left; + ax_right; + ax_top; + axis_x; + axis_x_g; + axis_y; + axis_y_g; + height; + max_y_width; + min_height; + min_width; + scale_x; + scale_y; + svg; + tick_padding; + tick_size; + width; constructor(parent, args) { - var self = this; - this.max_y_width = 100; // Draw the plot as an SVG - this.svg = d3.select(parent).append('svg') - .attr('width', '100%') - .attr('height', '100%'); + this.svg = d3.select(parent).append("svg") + .attr("width", "100%") + .attr("height", "100%"); // Scales for mapping x and y values to pixels this.scale_x = d3.scale.linear(); @@ -54,7 +71,7 @@ export default class Axes2D { }; set_axes_geometry(width, height) { - var scale = parseFloat($('#main').css('font-size')); + const scale = parseFloat($("#main").css("font-size")); this.width = width; this.height = height; this.ax_left = this.max_y_width; @@ -70,12 +87,12 @@ export default class Axes2D { * Adjust the graph layout due to changed size */ on_resize(width, height) { - if (width < this.minWidth) { - width = this.minWidth; + if (width < this.min_width) { + width = this.min_width; + } + if (height < this.min_height) { + height = this.min_height; } - if (height < this.minHeight) { - height = this.minHeight; - }; this.set_axes_geometry(width, height); this.scale_x.range([this.ax_left, this.ax_right]); @@ -96,20 +113,19 @@ export default class Axes2D { }; fit_ticks(parent) { - var self = this; + const self = this; setTimeout(function() { - var ticks = $(parent.div).find('.tick'); - var max_w = 0; - for (var i = 0; i < ticks.length; i++) { - var w = ticks[i].getBBox().width; + const ticks = $(parent.div).find(".tick"); + let max_w = 0; + for (let i = 0; i < ticks.length; i++) { + const w = ( ticks[i]).getBBox().width; if (w > max_w) { max_w = w; } } self.max_y_width = max_w; - self.set_axes_geometry(); + self.set_axes_geometry(parent.width, parent.height); // TODO: parent? self.on_resize(parent.width, parent.height); }, 1); }; - } diff --git a/nengo_gui/static/components/component.ts b/nengo_gui/static/components/component.ts index d486afc5..29b0d86d 100644 --- a/nengo_gui/static/components/component.ts +++ b/nengo_gui/static/components/component.ts @@ -19,6 +19,7 @@ */ import * as interact from "interact.js"; +import * as $ from "jquery"; import * as menu from "../menu"; import * as utils from "../utils"; @@ -26,20 +27,37 @@ import * as utils from "../utils"; export var all_components = []; export function save_all_components() { - for (var index in all_components) { - all_components[index].save_layout(); + for (let i = 0; i < all_components.length; i++) { + all_components[i].save_layout(); } }; export class Component { + div; + h; + height; + label; + label_visible; + menu; + min_height; + min_width; + parent; + pending_update; + uid; + viewport; + w; + width; + ws; + x; + y; constructor(parent, viewport, args) { - var self = this; + const self = this; this.viewport = viewport; // Create the div for the component and position it - this.div = document.createElement('div'); + this.div = document.createElement("div"); // Prevent interact from messing up cursor interact(this.div).styleCursor(true); @@ -52,25 +70,25 @@ export class Component { this.redraw_size(); this.redraw_pos(); - this.div.style.position = 'absolute'; - this.div.classList.add('graph'); + this.div.style.position = "absolute"; + this.div.classList.add("graph"); parent.appendChild(this.div); this.parent = parent; - this.label = document.createElement('div'); - this.label.classList.add('label', 'unselectable'); - this.label.innerHTML = args.label.replace('<', '<').replace('>', '>'); - this.label.style.position = 'fixed'; + this.label = document.createElement("div"); + this.label.classList.add("label", "unselectable"); + this.label.innerHTML = args.label.replace("<", "<").replace(">", ">"); + this.label.style.position = "fixed"; this.label.style.width = args.width; - this.label.style.height = '2em'; + this.label.style.height = "2em"; this.label_visible = true; this.div.appendChild(this.label); if (args.label_visible === false) { - this.hide_label(); + this.hide_label(null); } - self.minWidth = 2; - self.minHeight = 2; + self.min_width = 2; + self.min_height = 2; // Move element to be drawn on top when clicked on @@ -84,59 +102,58 @@ export class Component { interact(this.div) .draggable({ inertia: true, - onstart: function() { - menu.hide_any(); + onend: function(event) { + self.save_layout(); }, onmove: function(event) { - var target = event.target; - self.x = self.x + event.dx / - (self.viewport.w * self.viewport.scale); + (self.viewport.width * self.viewport.scale); self.y = self.y + event.dy / - (self.viewport.h * self.viewport.scale); - + (self.viewport.height * self.viewport.scale); self.redraw_pos(); - }, - onend: function(event) { - self.save_layout(); - } + onstart: function() { + menu.hide_any(); + }, }); // Allow element to be resized interact(this.div) .resizable({ - edges: {left: true, top: true, right: true, bottom: true} + edges: { bottom: true, left: true, right: true, top: true }, }) - .on('resizestart', function(event) { + .on("resizestart", function(event) { menu.hide_any(); }) - .on('resizemove', function(event) { - var target = event.target; - var newWidth = event.rect.width; - var newHeight = event.rect.height; - var dx = event.deltaRect.left ; - var dy = event.deltaRect.top ; - var dz = event.deltaRect.right; - var da = event.deltaRect.bottom; - - self.x += (dx + dz) / 2 / (viewport.w * viewport.scale); - self.y += (dy + da) / 2 / (viewport.h * viewport.scale); - - self.w = newWidth / (viewport.w * viewport.scale) / 2; - self.h = newHeight / (viewport.h * viewport.scale) / 2; + .on("resizemove", function(event) { + const newWidth = event.rect.width; + const newHeight = event.rect.height; + const dx = event.deltaRect.left; + const dy = event.deltaRect.top; + const dz = event.deltaRect.right; + const da = event.deltaRect.bottom; + + self.x += (dx + dz) / 2 / + (self.viewport.width * self.viewport.scale); + self.y += (dy + da) / 2 / + (self.viewport.height * self.viewport.scale); + + self.w = newWidth / + (self.viewport.width * self.viewport.scale) / 2; + self.h = newHeight / + (self.viewport.height * self.viewport.scale) / 2; self.on_resize(newWidth, newHeight); self.redraw_size(); self.redraw_pos(); }) - .on('resizeend', function(event) { + .on("resizeend", function(event) { self.save_layout(); }); // Open a WebSocket to the server this.uid = args.uid; - if (this.uid != undefined) { + if (this.uid !== undefined) { this.ws = utils.create_websocket(this.uid); this.ws.onmessage = function(event) { self.on_message(event); @@ -148,8 +165,8 @@ export class Component { this.menu = new menu.Menu(self.parent); interact(this.div) - .on('hold', function(event) { // Change to 'tap' for right click - if (event.button == 0) { + .on("hold", function(event) { // Change to 'tap' for right click + if (event.button === 0) { if (self.menu.visible_any()) { menu.hide_any(); } else { @@ -159,14 +176,14 @@ export class Component { event.stopPropagation(); } }) - .on('tap', function(event) { // Get rid of menus when clicking off - if (event.button == 0) { + .on("tap", function(event) { // Get rid of menus when clicking off + if (event.button === 0) { if (self.menu.visible_any()) { menu.hide_any(); } } }); - $(this.div).bind('contextmenu', function(event) { + $(this.div).bind("contextmenu", function(event) { event.preventDefault(); event.stopPropagation(); if (self.menu.visible_any()) { @@ -182,98 +199,105 @@ export class Component { /** * Method to be called when Component is resized. */ - on_resize(width, height) {}; + on_resize(width, height) { + // Subclasses should implement this. + }; /** * Method to be called when Component received a WebSocket message. */ - on_message(event) {}; + on_message(event) { + // Subclasses should implement this. + }; generate_menu() { - var self = this; - var items = []; + const self = this; + const items = []; if (this.label_visible) { - items.push(['Hide label', function() { - self.hide_label(); + items.push(["Hide label", function() { + self.hide_label(null); self.save_layout(); }]); } else { - items.push(['Show label', function() { - self.show_label(); + items.push(["Show label", function() { + self.show_label(null); self.save_layout(); }]); } - items.push(['Remove', function() {self.remove();}]); + items.push(["Remove", function() { + self.remove(undefined, undefined); + }]); return items; }; remove(undo_flag, notify_server) { - undo_flag = typeof undo_flag !== 'undefined' ? undo_flag : false; - notify_server = typeof notify_server !== 'undefined' ? notify_server : true; + undo_flag = typeof undo_flag !== "undefined" ? undo_flag : false; + notify_server = typeof notify_server !== "undefined" ? notify_server : true; if (notify_server) { if (undo_flag === true) { - this.ws.send('remove_undo'); + this.ws.send("remove_undo"); } else { - this.ws.send('remove'); + this.ws.send("remove"); } } this.parent.removeChild(this.div); - var index = all_components.indexOf(this); + const index = all_components.indexOf(this); all_components.splice(index, 1); }; /** * Schedule update() to be called in the near future. - + * * If update() is already scheduled, then do nothing. This is meant to limit * how fast update() is called in the case that we are changing the data faster * than whatever processing is needed in update(). */ schedule_update(event) { - if (this.pending_update == false) { + if (this.pending_update === false) { this.pending_update = true; - var self = this; - window.setTimeout( - function() { - self.pending_update = false; - self.update(); - }, 10); + const self = this; + window.setTimeout(function() { + self.pending_update = false; + self.update(null); + }, 10); } }; /** * Do any visual updating that is needed due to changes in the underlying data. */ - update(event) {}; + update(event) { + // Subclasses should implement this. + }; hide_label(event) { if (this.label_visible) { - this.label.style.display = 'none'; + this.label.style.display = "none"; this.label_visible = false; } }; show_label(event) { if (!this.label_visible) { - this.label.style.display = 'inline'; + this.label.style.display = "inline"; this.label_visible = true; } }; layout_info() { - var info = {}; - info.x = this.x; - info.y = this.y; - info.width = this.w; - info.height = this.h; - info.label_visible = this.label_visible; - return info; + return { + "height": this.h, + "label_visible": this.label_visible, + "width": this.w, + "x": this.x, + "y": this.y, + }; }; save_layout() { - var info = this.layout_info(); - this.ws.send('config:' + JSON.stringify(info)); + const info = this.layout_info(); + this.ws.send("config:" + JSON.stringify(info)); }; update_layout(config) { @@ -287,33 +311,33 @@ export class Component { this.on_resize(this.get_screen_width(), this.get_screen_height()); if (config.label_visible === true) { - this.show_label(); + this.show_label(null); } else { - this.hide_label(); + this.hide_label(null); } }; redraw_size() { - this.width = this.viewport.w * this.w * this.viewport.scale * 2; - this.height = this.viewport.h * this.h * this.viewport.scale * 2; + const vpscale = this.viewport.scale * 2; + this.width = this.viewport.width * this.w * vpscale; + this.height = this.viewport.height * this.h * vpscale; this.div.style.width = this.width; this.div.style.height = this.height; }; redraw_pos() { - var x = (this.x + this.viewport.x - this.w) * - this.viewport.w * this.viewport.scale; - var y = (this.y + this.viewport.y - this.h) * - this.viewport.h * this.viewport.scale; + const x = (this.x + this.viewport.x - this.w) * + this.viewport.width * this.viewport.scale; + const y = (this.y + this.viewport.y - this.h) * + this.viewport.height * this.viewport.scale; utils.set_transform(this.div, x, y); }; get_screen_width() { - return this.viewport.w * this.w * this.viewport.scale * 2; + return this.viewport.width * this.w * this.viewport.scale * 2; }; get_screen_height() { - return this.viewport.h * this.h * this.viewport.scale * 2; + return this.viewport.height * this.h * this.viewport.scale * 2; }; - } diff --git a/nengo_gui/static/components/htmlview.ts b/nengo_gui/static/components/htmlview.ts index 383ccd85..52483163 100644 --- a/nengo_gui/static/components/htmlview.ts +++ b/nengo_gui/static/components/htmlview.ts @@ -8,32 +8,36 @@ * @param {dict} args - A set of constructor arguments (see Component) */ -import { Component } from "./component"; import { DataStore } from "../datastore"; import * as utils from "../utils"; +import { Component } from "./component"; export default class HTMLView extends Component { + data_store; + pdiv; + sim; constructor(parent, viewport, sim, args) { super(parent, viewport, args); - var self = this; + const self = this; this.sim = sim; - this.pdiv = document.createElement('div'); - this.pdiv.style.width = '100%'; - this.pdiv.style.height = '100%'; + this.pdiv = document.createElement("div"); + this.pdiv.style.width = "100%"; + this.pdiv.style.height = "100%"; utils.set_transform(this.pdiv, 0, 0); - this.pdiv.style.position = 'fixed'; - this.pdiv.classList.add('htmlview'); + this.pdiv.style.position = "fixed"; + this.pdiv.classList.add("htmlview"); this.div.appendChild(this.pdiv); // For storing the accumulated data. this.data_store = new 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.sim.div.addEventListener("adjust_time", function(e) { + self.schedule_update(null); + }, false); this.on_resize(this.get_screen_width(), this.get_screen_height()); }; @@ -42,13 +46,13 @@ export default class HTMLView extends Component { * Receive new line data from the server */ on_message(event) { - var data = event.data.split(" ", 1); - var time = parseFloat(data[0]); + const data = event.data.split(" ", 1); + const time = parseFloat(data[0]); - var msg = event.data.substring(data[0].length + 1); + const msg = event.data.substring(data[0].length + 1); this.data_store.push([time, msg]); - this.schedule_update(); + this.schedule_update(null); }; /** @@ -58,10 +62,10 @@ export default class HTMLView extends Component { // Let the data store clear out old values this.data_store.update(); - var data = this.data_store.get_last_data()[0]; + let data = this.data_store.get_last_data()[0]; if (data === undefined) { - data = ''; + data = ""; } this.pdiv.innerHTML = data; @@ -72,12 +76,12 @@ export default class HTMLView extends Component { * Adjust the graph layout due to changed size */ on_resize(width, height) { - if (width < this.minWidth) { - width = this.minWidth; + if (width < this.min_width) { + width = this.min_width; + } + if (height < this.min_height) { + height = this.min_height; } - if (height < this.minHeight) { - height = this.minHeight; - }; this.width = width; this.height = height; @@ -85,5 +89,4 @@ export default class HTMLView extends Component { this.update(); }; - } diff --git a/nengo_gui/static/components/image.ts b/nengo_gui/static/components/image.ts index 09009d28..0f5f2663 100644 --- a/nengo_gui/static/components/image.ts +++ b/nengo_gui/static/components/image.ts @@ -11,15 +11,25 @@ */ import * as d3 from "d3"; -import { Component } from "./component"; + import { DataStore } from "../datastore"; +import { Component } from "./component"; export default class Image extends Component { + canvas; + data_store; + display_time; + image; + n_pixels; + pixels_x; + pixels_y; + sim; + svg; constructor(parent, viewport, sim, args) { super(parent, viewport, args); - var self = this; + const self = this; self.sim = sim; self.display_time = args.display_time; self.pixels_x = args.pixels_x; @@ -30,16 +40,17 @@ export default class Image extends Component { self.data_store = new DataStore(self.n_pixels, self.sim, 0); // Draw the plot as an SVG - self.svg = d3.select(self.div).append('svg') - .attr('width', '100%') - .attr('height', '100%') - .attr('style', [ - 'padding-top:', '2em', + self.svg = d3.select(self.div).append("svg") + .attr("width", "100%") + .attr("height", "100%") + .attr("style", [ + "padding-top:", "2em", ].join("")); // Call schedule_update whenever the time is adjusted in the SimControl - self.sim.div.addEventListener('adjust_time', - function(e) {self.schedule_update();}, false); + self.sim.div.addEventListener("adjust_time", function(e) { + self.schedule_update(null); + }, false); // Create the image self.image = self.svg.append("image") @@ -50,10 +61,10 @@ export default class Image extends Component { .attr("style", [ "image-rendering: -webkit-optimize-contrast;", "image-rendering: -moz-crisp-edges;", - "image-rendering: pixelated;" + "image-rendering: pixelated;", ].join("")); - self.canvas = document.createElement('CANVAS'); + self.canvas = document.createElement("CANVAS"); self.canvas.width = self.pixels_x; self.canvas.height = self.pixels_y; @@ -65,38 +76,38 @@ export default class Image extends Component { * Receive new line data from the server */ on_message(event) { - var data = new Uint8Array(event.data); - var msg_size = this.n_pixels + 4; + let data = new Uint8Array(event.data); + const msg_size = this.n_pixels + 4; - for (var i = 0; i < data.length; i += msg_size) { - var time_data = new Float32Array(event.data.slice(i, i + 4)); + for (let i = 0; i < data.length; i += msg_size) { + const time_data = new Float32Array(event.data.slice(i, i + 4)); data = Array.prototype.slice.call(data, i + 3, i + msg_size); data[0] = time_data[0]; this.data_store.push(data); } - this.schedule_update(); + this.schedule_update(event); }; /** * Redraw the lines and axis due to changed data */ update() { - var self = this; + const self = this; // Let the data store clear out old values self.data_store.update(); - var data = self.data_store.get_last_data(); - var ctx = self.canvas.getContext("2d"); - var imgData = ctx.getImageData(0, 0, self.pixels_x, self.pixels_y); - for (var i = 0; i < self.n_pixels; i++) { - imgData.data[4*i + 0] = data[i]; - imgData.data[4*i + 1] = data[i]; - imgData.data[4*i + 2] = data[i]; - imgData.data[4*i + 3] = 255; + const data = self.data_store.get_last_data(); + const ctx = self.canvas.getContext("2d"); + const imgData = ctx.getImageData(0, 0, self.pixels_x, self.pixels_y); + for (let i = 0; i < self.n_pixels; i++) { + imgData.data[4 * i + 0] = data[i]; + imgData.data[4 * i + 1] = data[i]; + imgData.data[4 * i + 2] = data[i]; + imgData.data[4 * i + 3] = 255; } ctx.putImageData(imgData, 0, 0); - var dataURL = self.canvas.toDataURL("image/png"); + const dataURL = self.canvas.toDataURL("image/png"); self.image.attr("xlink:href", dataURL); }; @@ -105,13 +116,13 @@ export default class Image extends Component { * Adjust the graph layout due to changed size */ on_resize(width, height) { - var self = this; - if (width < self.minWidth) { - width = self.minWidth; + const self = this; + if (width < self.min_width) { + width = self.min_width; + } + if (height < self.min_height) { + height = self.min_height; } - if (height < self.minHeight) { - height = self.minHeight; - }; self.svg .attr("width", width) @@ -126,5 +137,4 @@ export default class Image extends Component { self.div.style.width = width; self.div.style.height = height; }; - } diff --git a/nengo_gui/static/components/netgraph.ts b/nengo_gui/static/components/netgraph.ts index e2520cfe..81e1c3ac 100644 --- a/nengo_gui/static/components/netgraph.ts +++ b/nengo_gui/static/components/netgraph.ts @@ -11,30 +11,71 @@ */ import * as interact from "interact.js"; +import * as $ from "jquery"; -import "./netgraph.css"; -import * as comp from "./component"; import * as menu from "../menu"; -import NetGraphConnection from "./netgraph_conn"; -import NetGraphItem from "./netgraph_item"; import * as utils from "../utils"; import Viewport from "../viewport"; +import * as comp from "./component"; +import "./netgraph.css"; +import NetGraphConnection from "./netgraph_conn"; +import NetGraphItem from "./netgraph_item"; + +declare var require: any; export default class NetGraph { + aspect_resize; + collapsed_conns; + config; + font_size; + g_conns; + g_conns_mini; + g_items; + g_items_mini; + g_networks; + g_networks_mini; + height; + in_zoom_delay; + menu; + minimap; + minimap_conns; + minimap_div; + minimap_objects; + mm_display; + mm_height; + mm_max_x; + mm_min_x; + mm_max_y; + mm_min_y; + mm_scale; + mm_width; + offsetX; + offsetY; + parent; + scale; + svg; + svg_objects; + svg_conns; + tool_height; + view; + viewport; + width; + ws; + zoom_fonts; constructor(parent, config, args) { - var self = this; + const self = this; this.config = config; this.viewport = new Viewport(this); - if (args.uid[0] === '<') { - console.log("invalid uid for NetGraph: " + args.uid); + if (args.uid[0] === "<") { + console.warn("invalid uid for NetGraph: " + args.uid); } this.offsetX = 0; // Global x,y pan offset this.offsetY = 0; - var scale = 1.0; - Object.defineProperty(this, 'scale', { + let scale = 1.0; + Object.defineProperty(this, "scale", { // Global scaling factor get: function() { return scale; @@ -49,10 +90,10 @@ export default class NetGraph { self.viewport.scale = scale; self.viewport.redraw_all(); - } + }, }); - Object.defineProperty(this, 'zoom_fonts', { + Object.defineProperty(this, "zoom_fonts", { // Scale fonts when zooming get: function() { return self.config.zoom_fonts; @@ -63,10 +104,10 @@ export default class NetGraph { } self.config.zoom_fonts = val; this.update_fonts(); - } + }, }); - Object.defineProperty(this, 'aspect_resize', { + Object.defineProperty(this, "aspect_resize", { // Preserve aspect ratios on window resize get: function() { return self.config.aspect_resize; @@ -77,10 +118,10 @@ export default class NetGraph { } self.config.aspect_resize = val; - } + }, }); - Object.defineProperty(this, 'font_size', { + Object.defineProperty(this, "font_size", { get: function() { return self.config.font_size; }, @@ -90,11 +131,11 @@ export default class NetGraph { } self.config.font_size = val; this.update_fonts(); - } + }, }); // Do networks have transparent backgrounds? - Object.defineProperty(this, 'transparent_nets', { + Object.defineProperty(this, "transparent_nets", { get: function() { return self.config.transparent_nets; }, @@ -103,15 +144,16 @@ export default class NetGraph { return; } self.config.transparent_nets = val; - for (var key in this.svg_objects) { - var ngi = this.svg_objects[key]; - ngi.compute_fill(); - if (ngi.type === 'net' && ngi.expanded) { - ngi.shape.style["fill-opacity"] = val ? 0.0 : 1.0; + for (let key in this.svg_objects) { + if (this.svg_objects.hasOwnProperty(key)) { + const ngi = this.svg_objects[key]; + ngi.compute_fill(); + if (ngi.type === "net" && ngi.expanded) { + ngi.shape.style["fill-opacity"] = val ? 0.0 : 1.0; + } } } - - } + }, }); this.svg_objects = {}; // Dict of all NetGraphItems, by uid @@ -137,12 +179,12 @@ export default class NetGraph { this.collapsed_conns = {}; // Create the master SVG element - this.svg = this.createSVGElement('svg'); - this.svg.classList.add('netgraph'); - this.svg.style.width = '100%'; - this.svg.id = 'netgraph'; - this.svg.style.height = '100%'; - this.svg.style.position = 'absolute'; + this.svg = this.createSVGElement("svg"); + this.svg.classList.add("netgraph"); + this.svg.style.width = "100%"; + this.svg.id = "netgraph"; + this.svg.style.height = "100%"; + this.svg.style.position = "absolute"; interact(this.svg).styleCursor(false); @@ -152,29 +194,29 @@ export default class NetGraph { this.width = $(this.svg).width(); this.height = $(this.svg).height(); - this.tool_height = $(toolbar.toolbar).height(); + this.tool_height = $("#toolbar_object").height(); // Three separate layers, so that expanded networks are at the back, // then connection lines, and then other items (nodes, ensembles, and // collapsed networks) are drawn on top. - this.g_networks = this.createSVGElement('g'); + this.g_networks = this.createSVGElement("g"); this.svg.appendChild(this.g_networks); - this.g_conns = this.createSVGElement('g'); + this.g_conns = this.createSVGElement("g"); this.svg.appendChild(this.g_conns); - this.g_items = this.createSVGElement('g'); + this.g_items = this.createSVGElement("g"); this.svg.appendChild(this.g_items); // Reading netgraph.css file as text and embedding it within def tags; // this is needed for saving the SVG plot to disk. // Load contents of the CSS file as string - var css = require('!!css-loader!./netgraph.css').toString(); + const css = require("!!css-loader!./netgraph.css").toString(); // Embed CSS code into SVG tag - var s = document.createElement('style'); - s.setAttribute('type', 'text/css'); + const s = document.createElement("style"); + s.setAttribute("type", "text/css"); s.innerHTML = ""; - var defs = document.createElement('defs'); + const defs = document.createElement("defs"); defs.appendChild(s); this.svg.insertBefore(defs, this.svg.firstChild); @@ -187,47 +229,50 @@ export default class NetGraph { // Respond to resize events this.svg.addEventListener("resize", function() { - self.on_resize(); + self.on_resize(null); }); window.addEventListener("resize", function() { - self.on_resize(); + self.on_resize(null); }); // Dragging the background pans the full area by changing offsetX,Y - var self = this; - // Define cursor behaviour for background interact(this.svg) - .on('mousedown', function() { - var cursor = document.documentElement.getAttribute('style'); + .on("mousedown", function() { + const cursor = document.documentElement.getAttribute("style"); if (cursor !== null) { if (cursor.match(/resize/) == null) { // Don't change resize cursor document.documentElement.setAttribute( - 'style', 'cursor:move;'); + "style", "cursor:move;"); } } }) - .on('mouseup', function() { - document.documentElement.setAttribute('style', 'cursor:default;'); + .on("mouseup", function() { + document.documentElement.setAttribute("style", "cursor:default;"); }); interact(this.svg) .draggable({ - onstart: function() { - menu.hide_any(); + onend: function(event) { + // Let the server know what happened + self.notify({act: "pan", x: self.offsetX, y: self.offsetY}); }, onmove: function(event) { self.offsetX += event.dx / self.get_scaled_width(); self.offsetY += event.dy / self.get_scaled_height(); - for (var key in self.svg_objects) { - self.svg_objects[key].redraw_position(); - if (self.mm_display) { - self.minimap_objects[key].redraw_position(); + for (let key in self.svg_objects) { + if (self.svg_objects.hasOwnProperty(key)) { + self.svg_objects[key].redraw_position(); + if (self.mm_display) { + self.minimap_objects[key].redraw_position(); + } } } - for (var key in self.svg_conns) { - self.svg_conns[key].redraw(); + for (let key in self.svg_conns) { + if (self.svg_conns.hasOwnProperty(key)) { + self.svg_conns[key].redraw(); + } } self.viewport.x = self.offsetX; @@ -237,57 +282,58 @@ export default class NetGraph { self.scaleMiniMapViewBox(); }, - onend: function(event) { - // Let the server know what happened - self.notify({act: "pan", x: self.offsetX, y: self.offsetY}); - }}); + onstart: function() { + menu.hide_any(); + }, + }); // Scrollwheel on background zooms the full area by changing scale. // Note that offsetX,Y are also changed to zoom into a particular // point in the space - interact(document.getElementById('main')) - .on('click', function(event) { - $('.ace_text-input').blur(); + interact(document.getElementById("main")) + .on("click", function(event) { + $(".ace_text-input").blur(); }) - .on('wheel', function(event) { + .on("wheel", function(event) { event.preventDefault(); menu.hide_any(); - var x = (event.clientX) / self.width; - var y = (event.clientY - self.tool_height) / self.height; + const x = (event.clientX) / self.width; + const y = (event.clientY - self.tool_height) / self.height; + let delta; if (event.deltaMode === 1) { // DOM_DELTA_LINE - if (event.deltaY != 0) { - var delta = Math.log(1. + Math.abs(event.deltaY)) * 60; + if (event.deltaY !== 0) { + delta = Math.log(1. + Math.abs(event.deltaY)) * 60; if (event.deltaY < 0) { delta *= -1; } } else { - var delta = 0; + delta = 0; } } else if (event.deltaMode === 2) { // DOM_DELTA_PAGE // No idea what device would generate scrolling by a page - var delta = 0; + delta = 0; } else { // DOM_DELTA_PIXEL - var delta = event.deltaY; + delta = event.deltaY; } - var scale = 1. + Math.abs(delta) / 600.; + let z_scale = 1. + Math.abs(delta) / 600.; if (delta > 0) { - scale = 1. / scale; + z_scale = 1. / z_scale; } comp.save_all_components(); - var xx = x / self.scale - self.offsetX; - var yy = y / self.scale - self.offsetY; - self.offsetX = (self.offsetX + xx) / scale - xx; - self.offsetY = (self.offsetY + yy) / scale - yy; + const xx = x / self.scale - self.offsetX; + const yy = y / self.scale - self.offsetY; + self.offsetX = (self.offsetX + xx) / z_scale - xx; + self.offsetY = (self.offsetY + yy) / z_scale - yy; - self.scale = scale * self.scale; + self.scale = z_scale * self.scale; self.viewport.x = self.offsetX; self.viewport.y = self.offsetY; self.viewport.redraw_all(); @@ -298,7 +344,10 @@ export default class NetGraph { // Let the server know what happened self.notify({ - act: "zoom", scale: self.scale, x: self.offsetX, y: self.offsetY + act: "zoom", + scale: self.scale, + x: self.offsetX, + y: self.offsetY, }); }); @@ -306,8 +355,8 @@ export default class NetGraph { // Determine when to pull up the menu interact(this.svg) - .on('hold', function(event) { // Change to 'tap' for right click - if (event.button == 0) { + .on("hold", function(event) { // Change to "tap" for right click + if (event.button === 0) { if (self.menu.visible_any()) { menu.hide_any(); } else { @@ -317,15 +366,15 @@ export default class NetGraph { event.stopPropagation(); } }) - .on('tap', function(event) { // Get rid of menus when clicking off - if (event.button == 0) { + .on("tap", function(event) { // Get rid of menus when clicking off + if (event.button === 0) { if (self.menu.visible_any()) { menu.hide_any(); } } }); - $(this.svg).bind('contextmenu', function(event) { + $(this.svg).bind("contextmenu", function(event) { event.preventDefault(); if (self.menu.visible_any()) { menu.hide_any(); @@ -339,39 +388,39 @@ export default class NetGraph { }; generate_menu() { - var self = this; - var items = []; - items.push(['Auto-layout', function() { + const self = this; + return [["Auto-layout", function() { self.notify({act: "feedforward_layout", uid: null}); - }]); - return items; + }]]; }; /** * Event handler for received WebSocket messages */ on_message(event) { - var data = JSON.parse(event.data); - if (data.type === 'net') { + const data = JSON.parse(event.data); + let item; + + if (data.type === "net") { this.create_object(data); - } else if (data.type === 'ens') { + } else if (data.type === "ens") { this.create_object(data); - } else if (data.type === 'node') { + } else if (data.type === "node") { this.create_object(data); - } else if (data.type === 'conn') { + } else if (data.type === "conn") { this.create_connection(data); - } else if (data.type === 'pan') { + } else if (data.type === "pan") { this.set_offset(data.pan[0], data.pan[1]); - } else if (data.type === 'zoom') { + } else if (data.type === "zoom") { this.scale = data.zoom; - } else if (data.type === 'expand') { - var item = this.svg_objects[data.uid]; + } else if (data.type === "expand") { + item = this.svg_objects[data.uid]; item.expand(true, true); - } else if (data.type === 'collapse') { - var item = this.svg_objects[data.uid]; + } else if (data.type === "collapse") { + item = this.svg_objects[data.uid]; item.collapse(true, true); - } else if (data.type === 'pos_size') { - var item = this.svg_objects[data.uid]; + } else if (data.type === "pos_size") { + item = this.svg_objects[data.uid]; item.x = data.pos[0]; item.y = data.pos[1]; item.width = data.size[0]; @@ -381,47 +430,46 @@ export default class NetGraph { this.scaleMiniMap(); - } else if (data.type === 'config') { + } else if (data.type === "config") { // Anything about the config of a component has changed - var uid = data.uid; - for (var i = 0; i < comp.all_components.length; i++) { + const uid = data.uid; + for (let i = 0; i < comp.all_components.length; i++) { if (comp.all_components[i].uid === uid) { comp.all_components[i].update_layout(data.config); break; } } - } else if (data.type === 'js') { - eval(data.code); - } else if (data.type === 'rename') { - var item = this.svg_objects[data.uid]; + } else if (data.type === "js") { + eval(data.code); // tslint:disable-line + } else if (data.type === "rename") { + item = this.svg_objects[data.uid]; item.set_label(data.name); - } else if (data.type === 'remove') { - var item = this.svg_objects[data.uid]; + } else if (data.type === "remove") { + item = this.svg_objects[data.uid]; if (item === undefined) { item = this.svg_conns[data.uid]; } item.remove(); - } else if (data.type === 'reconnect') { - var conn = this.svg_conns[data.uid]; + } else if (data.type === "reconnect") { + const conn = this.svg_conns[data.uid]; conn.set_pres(data.pres); conn.set_posts(data.posts); conn.set_recurrent(data.pres[0] === data.posts[0]); conn.redraw(); - } else if (data.type === 'delete_graph') { - var uid = data.uid; - for (var i = 0; i < comp.all_components.length; i++) { + } else if (data.type === "delete_graph") { + const uid = data.uid; + for (let i = 0; i < comp.all_components.length; i++) { if (comp.all_components[i].uid === uid) { comp.all_components[i].remove(true, data.notify_server); break; } } } else { - console.log('invalid message'); - console.log(data); + console.warn("invalid message:" + data); } }; @@ -447,9 +495,10 @@ export default class NetGraph { update_fonts() { if (this.zoom_fonts) { - $('#main').css('font-size', 3 * this.scale * this.font_size/100 + 'em'); + $("#main").css("font-size", + 3 * this.scale * this.font_size / 100 + "em"); } else { - $('#main').css('font-size', this.font_size/100 + 'em'); + $("#main").css("font-size", this.font_size / 100 + "em"); } }; @@ -457,11 +506,15 @@ export default class NetGraph { * Redraw all elements */ redraw() { - for (var key in this.svg_objects) { - this.svg_objects[key].redraw(); + for (let key in this.svg_objects) { + if (this.svg_objects.hasOwnProperty(key)) { + this.svg_objects[key].redraw(); + } } - for (var key in this.svg_conns) { - this.svg_conns[key].redraw(); + for (let key in this.svg_conns) { + if (this.svg_conns.hasOwnProperty(key)) { + this.svg_conns[key].redraw(); + } } }; @@ -479,10 +532,10 @@ export default class NetGraph { * notified */ create_object(info) { - var item_mini = new NetGraphItem(this, info, true); + const item_mini = new NetGraphItem(this, info, true, null); this.minimap_objects[info.uid] = item_mini; - var item = new NetGraphItem(this, info, false, item_mini); + const item = new NetGraphItem(this, info, false, item_mini); this.svg_objects[info.uid] = item; this.detect_collapsed_conns(item.uid); @@ -495,10 +548,10 @@ export default class NetGraph { * Create a new NetGraphConnection. */ create_connection(info) { - var conn_mini = new NetGraphConnection(this, info, true); + const conn_mini = new NetGraphConnection(this, info, true, null); this.minimap_conns[info.uid] = conn_mini; - var conn = new NetGraphConnection(this, info, false, conn_mini); + const conn = new NetGraphConnection(this, info, false, conn_mini); this.svg_conns[info.uid] = conn; }; @@ -506,17 +559,19 @@ export default class NetGraph { * Handler for resizing the full SVG. */ on_resize(event) { - var width = $(this.svg).width(); - var height = $(this.svg).height(); + const width = $(this.svg).width(); + const height = $(this.svg).height(); if (this.aspect_resize) { - for (var key in this.svg_objects) { - var item = this.svg_objects[key]; - if (item.depth == 1) { - var new_width = item.get_screen_width() / this.scale; - var new_height = item.get_screen_height() / this.scale; - item.width = new_width/(2*width); - item.height = new_height/(2*height); + for (let key in this.svg_objects) { + if (this.svg_objects.hasOwnProperty(key)) { + const item = this.svg_objects[key]; + if (item.depth === 1) { + const new_width = item.get_screen_width() / this.scale; + const new_height = item.get_screen_height() / this.scale; + item.width = new_width / (2 * width); + item.height = new_height / (2 * height); + } } } } @@ -547,7 +602,7 @@ export default class NetGraph { * Expand or collapse a network. */ toggle_network(uid) { - var item = this.svg_objects[uid]; + const item = this.svg_objects[uid]; if (item.expanded) { item.collapse(true); } else { @@ -566,7 +621,7 @@ export default class NetGraph { if (this.collapsed_conns[target] === undefined) { this.collapsed_conns[target] = [conn]; } else { - var index = this.collapsed_conns[target].indexOf(conn); + const index = this.collapsed_conns[target].indexOf(conn); if (index === -1) { this.collapsed_conns[target].push(conn); } @@ -583,11 +638,11 @@ export default class NetGraph { * NetGraphConnections are waiting for it, and notifies them. */ detect_collapsed_conns(uid) { - var conns = this.collapsed_conns[uid]; + const conns = this.collapsed_conns[uid]; if (conns !== undefined) { delete this.collapsed_conns[uid]; - for (var i in conns) { - var conn = conns[i]; + for (let i = 0; i < conns.length; i++) { + const conn = conns[i]; // Make sure the NetGraphConnection hasn't been removed since // it started listening. if (!conn.removed) { @@ -603,25 +658,23 @@ export default class NetGraph { * Create a minimap. */ create_minimap() { - var self = this; - - this.minimap_div = document.createElement('div'); - this.minimap_div.className = 'minimap'; + this.minimap_div = document.createElement("div"); + this.minimap_div.className = "minimap"; this.parent.appendChild(this.minimap_div); - this.minimap = this.createSVGElement('svg'); - this.minimap.classList.add('minimap'); - this.minimap.id = 'minimap'; + this.minimap = this.createSVGElement("svg"); + this.minimap.classList.add("minimap"); + this.minimap.id = "minimap"; this.minimap_div.appendChild(this.minimap); // Box to show current view - this.view = this.createSVGElement('rect'); - this.view.classList.add('view'); + this.view = this.createSVGElement("rect"); + this.view.classList.add("view"); this.minimap.appendChild(this.view); - this.g_networks_mini = this.createSVGElement('g'); - this.g_conns_mini = this.createSVGElement('g'); - this.g_items_mini = this.createSVGElement('g'); + this.g_networks_mini = this.createSVGElement("g"); + this.g_conns_mini = this.createSVGElement("g"); + this.g_items_mini = this.createSVGElement("g"); // Order these are appended is important for layering this.minimap.appendChild(this.g_networks_mini); this.minimap.appendChild(this.g_conns_mini); @@ -636,12 +689,12 @@ export default class NetGraph { }; toggleMiniMap() { - if (this.mm_display == true) { - $('.minimap')[0].style.visibility = 'hidden'; + if (this.mm_display === true) { + $(".minimap")[0].style.visibility = "hidden"; this.g_conns_mini.style.opacity = 0; this.mm_display = false; } else { - $('.minimap')[0].style.visibility = 'visible'; + $(".minimap")[0].style.visibility = "visible"; this.g_conns_mini.style.opacity = 1; this.mm_display = true ; this.scaleMiniMap(); @@ -656,7 +709,7 @@ export default class NetGraph { return; } - var keys = Object.keys(this.svg_objects); + const keys = Object.keys(this.svg_objects); if (keys.length === 0) { return; } @@ -665,35 +718,37 @@ export default class NetGraph { // and only compare against those, or check against all items // in the lists when they move. Might be important for larger // networks. - var first_item = true; - for (var key in this.svg_objects) { - var item = this.svg_objects[key]; - // Ignore anything inside a subnetwork - if (item.depth > 1) { - continue; - } + let first_item = true; + for (let key in this.svg_objects) { + if (this.svg_objects.hasOwnProperty(key)) { + const item = this.svg_objects[key]; + // Ignore anything inside a subnetwork + if (item.depth > 1) { + continue; + } - var minmax_xy = item.getMinMaxXY(); - if (first_item == true) { - this.mm_min_x = minmax_xy[0]; - this.mm_max_x = minmax_xy[1]; - this.mm_min_y = minmax_xy[2]; - this.mm_max_y = minmax_xy[3]; - first_item = false; - continue; - } + const minmax_xy = item.getMinMaxXY(); + if (first_item === true) { + this.mm_min_x = minmax_xy[0]; + this.mm_max_x = minmax_xy[1]; + this.mm_min_y = minmax_xy[2]; + this.mm_max_y = minmax_xy[3]; + first_item = false; + continue; + } - if (this.mm_min_x > minmax_xy[0]) { - this.mm_min_x = minmax_xy[0]; - } - if (this.mm_max_x < minmax_xy[1]) { - this.mm_max_x = minmax_xy[1]; - } - if (this.mm_min_y > minmax_xy[2]) { - this.mm_min_y = minmax_xy[2]; - } - if (this.mm_max_y < minmax_xy[3]) { - this.mm_max_y = minmax_xy[3]; + if (this.mm_min_x > minmax_xy[0]) { + this.mm_min_x = minmax_xy[0]; + } + if (this.mm_max_x < minmax_xy[1]) { + this.mm_max_x = minmax_xy[1]; + } + if (this.mm_min_y > minmax_xy[2]) { + this.mm_min_y = minmax_xy[2]; + } + if (this.mm_max_y < minmax_xy[3]) { + this.mm_max_y = minmax_xy[3]; + } } } @@ -724,24 +779,23 @@ export default class NetGraph { return; } - var mm_w = this.mm_width; - var mm_h = this.mm_height; + const mm_w = this.mm_width; + const mm_h = this.mm_height; - var w = mm_w * this.mm_scale; - var h = mm_h * this.mm_scale; + const w = mm_w * this.mm_scale; + const h = mm_h * this.mm_scale; - var disp_w = (this.mm_max_x - this.mm_min_x) * w; - var disp_h = (this.mm_max_y - this.mm_min_y) * h; + const disp_w = (this.mm_max_x - this.mm_min_x) * w; + const disp_h = (this.mm_max_y - this.mm_min_y) * h; - var view_offsetX = -(this.mm_min_x + this.offsetX) * + const view_offsetX = -(this.mm_min_x + this.offsetX) * w + (mm_w - disp_w) / 2.; - var view_offsetY = -(this.mm_min_y + this.offsetY) * + const view_offsetY = -(this.mm_min_y + this.offsetY) * h + (mm_h - disp_h) / 2.; - this.view.setAttributeNS(null, 'x', view_offsetX); - this.view.setAttributeNS(null, 'y', view_offsetY); - this.view.setAttribute('width', w / this.scale); - this.view.setAttribute('height', h / this.scale); + this.view.setAttributeNS(null, "x", view_offsetX); + this.view.setAttributeNS(null, "y", view_offsetY); + this.view.setAttribute("width", w / this.scale); + this.view.setAttribute("height", h / this.scale); }; - } diff --git a/nengo_gui/static/components/netgraph_conn.ts b/nengo_gui/static/components/netgraph_conn.ts index 9b78a0aa..9e6a5e91 100644 --- a/nengo_gui/static/components/netgraph_conn.ts +++ b/nengo_gui/static/components/netgraph_conn.ts @@ -11,6 +11,23 @@ */ export default class NetGraphConnection { + g; + g_conns; + line; + marker; + mini_conn; + minimap; + ng; + objects; + parent; + post; + posts; + pre; + pres; + recurrent; + recurrent_ellipse; + removed; + uid; constructor(ng, info, minimap, mini_conn) { this.ng = ng; @@ -60,7 +77,7 @@ export default class NetGraphConnection { } // Create the line and its arrowhead marker - this.g = ng.createSVGElement('g'); + this.g = ng.createSVGElement("g"); this.create_line(); @@ -80,32 +97,32 @@ export default class NetGraphConnection { create_line() { if (this.recurrent) { - this.recurrent_ellipse = this.ng.createSVGElement('path'); + this.recurrent_ellipse = this.ng.createSVGElement("path"); this.recurrent_ellipse.setAttribute( - 'd', + "d", "M6.451,28.748C2.448,26.041,0,22.413,0,18.425C0, " + "10.051,10.801,3.262,24.125,3.262 " + "S48.25,10.051,48.25,18.425c0," + "6.453-6.412,11.964-15.45,14.153"); - this.recurrent_ellipse.setAttribute('class', 'recur'); + this.recurrent_ellipse.setAttribute("class", "recur"); this.g.appendChild(this.recurrent_ellipse); - this.marker = this.ng.createSVGElement('path'); + this.marker = this.ng.createSVGElement("path"); this.g.appendChild(this.marker); - if (this.minimap == false) { - this.marker.setAttribute('d', "M 6.5 0 L 0 5.0 L 7.5 8.0 z"); + if (this.minimap === false) { + this.marker.setAttribute("d", "M 6.5 0 L 0 5.0 L 7.5 8.0 z"); } else { - this.marker.setAttribute('d', "M 4 0 L 0 2 L 4 4 z"); + this.marker.setAttribute("d", "M 4 0 L 0 2 L 4 4 z"); } } else { - this.line = this.ng.createSVGElement('line'); + this.line = this.ng.createSVGElement("line"); this.g.appendChild(this.line); - this.marker = this.ng.createSVGElement('path'); - if (this.minimap == false) { - this.marker.setAttribute('d', "M 10 0 L -5 -5 L -5 5 z"); + this.marker = this.ng.createSVGElement("path"); + if (this.minimap === false) { + this.marker.setAttribute("d", "M 10 0 L -5 -5 L -5 5 z"); } else { - this.marker.setAttribute('d', "M 3 0 L -2.5 -2.5 L -2.5 2.5 z"); + this.marker.setAttribute("d", "M 3 0 L -2.5 -2.5 L -2.5 2.5 z"); } this.g.appendChild(this.marker); } @@ -131,9 +148,9 @@ export default class NetGraphConnection { set_pre(pre) { if (this.pre !== null) { // If we're currently connected, disconnect - var index = this.pre.conn_out.indexOf(this); + const index = this.pre.conn_out.indexOf(this); if (index === -1) { - console.log('error removing in set_pre'); + console.warn("error removing in set_pre"); } this.pre.conn_out.splice(index, 1); } @@ -150,9 +167,9 @@ export default class NetGraphConnection { set_post(post) { if (this.post !== null) { // If we're currently connected, disconnect - var index = this.post.conn_in.indexOf(this); + const index = this.post.conn_in.indexOf(this); if (index === -1) { - console.log('error removing in set_pre'); + console.warn("error removing in set_pre"); } this.post.conn_in.splice(index, 1); } @@ -167,8 +184,8 @@ export default class NetGraphConnection { * Determine the best available item to connect from. */ find_pre() { - for (var i in this.pres) { - var pre = this.objects[this.pres[i]]; + for (let i = 0; i < this.pres.length; i++) { + const pre = this.objects[this.pres[i]]; if (pre !== undefined) { return pre; } else { @@ -183,8 +200,8 @@ export default class NetGraphConnection { * Determine the best available item to connect to. */ find_post() { - for (var i in this.posts) { - var post = this.objects[this.posts[i]]; + for (let i = 0; i < this.posts.length; i++) { + const post = this.objects[this.posts[i]]; if (post !== undefined) { return post; } else { @@ -218,25 +235,25 @@ export default class NetGraphConnection { */ remove() { if (!this.minimap && this.parent !== null) { - var index = this.parent.child_connections.indexOf(this); + const index = this.parent.child_connections.indexOf(this); if (index === -1) { - console.log('error removing in remove'); + console.warn("error removing in remove"); } this.parent.child_connections.splice(index, 1); } if (this.pre != null) { - var index = this.pre.conn_out.indexOf(this); + const index = this.pre.conn_out.indexOf(this); if (index === -1) { - console.log('error removing from conn_out'); + console.warn("error removing from conn_out"); } this.pre.conn_out.splice(index, 1); } if (this.post != null) { - var index = this.post.conn_in.indexOf(this); + const index = this.post.conn_in.indexOf(this); if (index === -1) { - console.log('error removing from conn_in'); + console.warn("error removing from conn_in"); } this.post.conn_in.splice(index, 1); } @@ -257,91 +274,91 @@ export default class NetGraphConnection { redraw() { if (this.pre === null || this.post === null) { if (this.line !== undefined) { - this.line.setAttribute('visibility', 'hidden'); + this.line.setAttribute("visibility", "hidden"); } - this.marker.setAttribute('visibility', 'hidden'); + this.marker.setAttribute("visibility", "hidden"); return; } else { if (this.line !== undefined) { - this.line.setAttribute('visibility', 'visible'); + this.line.setAttribute("visibility", "visible"); } - this.marker.setAttribute('visibility', 'visible'); + this.marker.setAttribute("visibility", "visible"); } - var pre_pos = this.pre.get_screen_location(); + const pre_pos = this.pre.get_screen_location(); if (this.recurrent) { - var item = this.objects[this.pres[0]]; + const item = this.objects[this.pres[0]]; if (item === undefined) { - this.marker.setAttribute('visibility', 'hidden'); - this.recurrent_ellipse.setAttribute('visibility', 'hidden'); + this.marker.setAttribute("visibility", "hidden"); + this.recurrent_ellipse.setAttribute("visibility", "hidden"); } else { - this.marker.setAttribute('visibility', 'visible'); - this.recurrent_ellipse.setAttribute('visibility', 'visible'); - var width = item.get_displayed_size()[0]; - var height = item.get_displayed_size()[1]; + this.marker.setAttribute("visibility", "visible"); + this.recurrent_ellipse.setAttribute("visibility", "visible"); + const height = item.get_displayed_size()[1]; - var scale = item.shape.getAttribute('transform'); - var scale_value = parseFloat(scale.split(/[()]+/)[1]); + const scale = item.shape.getAttribute("transform"); + const scale_value = parseFloat(scale.split(/[()]+/)[1]); - if (this.minimap == false) { + if (this.minimap === false) { this.recurrent_ellipse.setAttribute( - 'style', 'stroke-width:' + 2/scale_value + ';'); + "style", "stroke-width:" + 2 / scale_value + ";"); } else { this.recurrent_ellipse.setAttribute( - 'style', 'stroke-width:' + 1/scale_value + ';'); + "style", "stroke-width:" + 1 / scale_value + ";"); } - var ex = pre_pos[0] - scale_value*17.5; - var ey = pre_pos[1] - height - scale_value*36; + const ex = pre_pos[0] - scale_value * 17.5; + const ey = pre_pos[1] - height - scale_value * 36; this.recurrent_ellipse.setAttribute( - 'transform', 'translate(' + ex + ',' + ey + ')' + scale); + "transform", "translate(" + ex + "," + ey + ")" + scale); - var mx = pre_pos[0] - 1; - if (this.minimap == false) { - var my = pre_pos[1] - height - scale_value*32.15 - 5; + const mx = pre_pos[0] - 1; + let my; + if (this.minimap === false) { + my = pre_pos[1] - height - scale_value * 32.15 - 5; } else { - var my = pre_pos[1] - height - scale_value*32 - 2; + my = pre_pos[1] - height - scale_value * 32 - 2; } this.marker.setAttribute( - 'transform', 'translate(' + mx + ',' + my + ')'); + "transform", "translate(" + mx + "," + my + ")"); } } else { - var post_pos = this.post.get_screen_location(); - this.line.setAttribute('x1', pre_pos[0]); - this.line.setAttribute('y1', pre_pos[1]); - this.line.setAttribute('x2', post_pos[0]); - this.line.setAttribute('y2', post_pos[1]); + const post_pos = this.post.get_screen_location(); + this.line.setAttribute("x1", pre_pos[0]); + this.line.setAttribute("y1", pre_pos[1]); + this.line.setAttribute("x2", post_pos[0]); + this.line.setAttribute("y2", post_pos[1]); // Angle between objects - var angle = Math.atan2( + let angle = Math.atan2( post_pos[1] - pre_pos[1], post_pos[0] - pre_pos[0]); - var w1 = this.pre.get_screen_width(); - var h1 = this.pre.get_screen_height(); - var w2 = this.post.get_screen_width(); - var h2 = this.post.get_screen_height(); + const w1 = this.pre.get_screen_width(); + const h1 = this.pre.get_screen_height(); + const w2 = this.post.get_screen_width(); + const h2 = this.post.get_screen_height(); - var a1 = Math.atan2(h1, w1); - var a2 = Math.atan2(h2, w2); + const a1 = Math.atan2(h1, w1); + const a2 = Math.atan2(h2, w2); - var pre_length = this.intersect_length(angle, a1, w1, h1); - var post_to_pre_angle = angle - Math.PI; + const pre_length = this.intersect_length(angle, a1, w1, h1); + let post_to_pre_angle = angle - Math.PI; if (post_to_pre_angle < -Math.PI) { - post_to_pre_angle += 2*Math.PI; + post_to_pre_angle += 2 * Math.PI; } - var post_length = this.intersect_length(post_to_pre_angle, a2, w2, h2); + const post_length = this.intersect_length(post_to_pre_angle, a2, w2, h2); - var mx = (pre_pos[0] + pre_length[0]) * 0.4 + + let mx = (pre_pos[0] + pre_length[0]) * 0.4 + (post_pos[0] + post_length[0]) * 0.6; - var my = (pre_pos[1] + pre_length[1]) * 0.4 + + let my = (pre_pos[1] + pre_length[1]) * 0.4 + (post_pos[1] + post_length[1]) * 0.6; // Check to make sure the marker doesn't go past either endpoint - var vec1 = [post_pos[0] - pre_pos[0], post_pos[1] - pre_pos[1]]; - var vec2 = [mx - pre_pos[0], my - pre_pos[1]]; - var dot_prod = (vec1[0]*vec2[0] + vec1[1]*vec2[1]) / - (vec1[0]*vec1[0] + vec1[1]*vec1[1]); + const vec1 = [post_pos[0] - pre_pos[0], post_pos[1] - pre_pos[1]]; + const vec2 = [mx - pre_pos[0], my - pre_pos[1]]; + const dot_prod = (vec1[0] * vec2[0] + vec1[1] * vec2[1]) / + (vec1[0] * vec1[0] + vec1[1] * vec1[1]); if (dot_prod < 0) { mx = pre_pos[0]; @@ -352,8 +369,8 @@ export default class NetGraphConnection { } angle = 180 / Math.PI * angle; this.marker.setAttribute( - 'transform', 'translate(' + mx + ',' + my + ')' + - ' rotate(' + angle + ')'); + "transform", "translate(" + mx + "," + my + ")" + + " rotate(" + angle + ")"); } if (!this.minimap && this.ng.mm_display) { @@ -367,33 +384,31 @@ export default class NetGraphConnection { * @param {number} theta - the angle of the line * @param {number} alpha - the angle between zero and the top right corner * of the object - **/ + */ intersect_length( theta, alpha, width, height) { - var quad = 0; - var beta = 2 * (Math.PI/2 - alpha); // Angle between top corners - var h2 = (height/2) * (height/2); - var w2 = (width/2) * (width/2); + const beta = 2 * (Math.PI / 2 - alpha); // Angle between top corners + let x; + let y; if (theta >= -alpha && theta < alpha) { // 1st quadrant - var x = width/2; - var y = width/2 * Math.tan(theta); + x = width / 2; + y = width / 2 * Math.tan(theta); } else if (theta >= alpha && theta < alpha + beta) { // 2nd quadrant - var x = (height/2) / Math.tan(theta); - var y = height/2; + x = (height / 2) / Math.tan(theta); + y = height / 2; } else if (theta >= alpha + beta || theta < -(alpha + beta)) { // 3rd quadrant - var x = -width/2; - var y = -width/2 * Math.tan(theta); + x = -width / 2; + y = -width / 2 * Math.tan(theta); } else { // 4th quadrant - var x = -(height/2) / Math.tan(theta); - var y = -height/2; + x = -(height / 2) / Math.tan(theta); + y = -height / 2; } return [x, y]; }; - } diff --git a/nengo_gui/static/components/netgraph_item.ts b/nengo_gui/static/components/netgraph_item.ts index a3fecd5f..3f7f1e55 100644 --- a/nengo_gui/static/components/netgraph_item.ts +++ b/nengo_gui/static/components/netgraph_item.ts @@ -6,18 +6,55 @@ * @param {dict} info - A dictionary of settings for the item, including: * @param {float[]} info.pos - x,y position * @param {float[]} info.size - half width, half height of item - * @param {string} info.type - one of ['net', 'ens', 'node'] + * @param {string} info.type - one of ["net", "ens", "node"] * @param {string} info.uid - unique identifier - * @param {string|null} info.parent - a NetGraphItem with .type=='net' + * @param {string|null} info.parent - a NetGraphItem with .type=="net" */ import * as interact from "interact.js"; +import * as $ from "jquery"; + import * as menu from "../menu"; export default class NetGraphItem { + area; + aspect; + child_connections; + children; + conn_in; + conn_out; + default_output; + depth; + dimensions; + expanded; + fixed_height; + fixed_width; + g; + g_items; + g_networks; + height; + html_node; + label; + label_below; + menu; + min_height; + min_width; + mini_item; + minimap; + ng; + parent; + passthrough; + shape; + size; + sp_targets; + type; + uid; + width; + x; + y; constructor(ng, info, minimap, mini_item) { - var self = this; + const self = this; this.ng = ng; this.type = info.type; @@ -30,7 +67,7 @@ export default class NetGraphItem { this.dimensions = info.dimensions; this.minimap = minimap; this.html_node = info.html; - if (minimap == false) { + if (minimap === false) { this.g_networks = this.ng.g_networks; this.g_items = this.ng.g_items; this.mini_item = mini_item; @@ -39,8 +76,8 @@ export default class NetGraphItem { this.g_items = this.ng.g_items_mini; } - var width = info.size[0]; - Object.defineProperty(this, 'width', { + let width = info.size[0]; + Object.defineProperty(this, "width", { get: function() { return width; }, @@ -50,10 +87,10 @@ export default class NetGraphItem { if (!this.minimap) { this.mini_item.width = val; } - } + }, }); - var height = info.size[1]; - Object.defineProperty(this, 'height', { + let height = info.size[1]; + Object.defineProperty(this, "height", { get: function() { return height; }, @@ -63,10 +100,10 @@ export default class NetGraphItem { if (!this.minimap) { this.mini_item.height = val; } - } + }, }); - var x = info.pos[0]; - Object.defineProperty(this, 'x', { + let x = info.pos[0]; + Object.defineProperty(this, "x", { get: function() { return x; }, @@ -76,10 +113,10 @@ export default class NetGraphItem { if (!this.minimap) { this.mini_item.x = val; } - } + }, }); - var y = info.pos[1]; - Object.defineProperty(this, 'y', { + let y = info.pos[1]; + Object.defineProperty(this, "y", { get: function() { return y; }, @@ -89,7 +126,7 @@ export default class NetGraphItem { if (!this.minimap) { this.mini_item.y = val; } - } + }, }); // If this is a network, the children list is the set of NetGraphItems @@ -102,8 +139,8 @@ export default class NetGraphItem { this.conn_in = []; // Minimum and maximum drawn size, in pixels - this.minWidth = 5; - this.minHeight = 5; + this.min_width = 5; + this.min_height = 5; this.aspect = null; this.expanded = false; @@ -122,73 +159,75 @@ export default class NetGraphItem { } // Create the SVG group to hold this item - var g = this.ng.createSVGElement('g'); + const g = this.ng.createSVGElement("g"); this.g = g; this.g_items.appendChild(g); g.classList.add(this.type); - this.area = this.ng.createSVGElement('rect'); - this.area.style.fill = 'transparent'; + this.area = this.ng.createSVGElement("rect"); + this.area.style.fill = "transparent"; this.menu = new menu.Menu(this.ng.parent); // Different types use different SVG elements for display - if (info.type === 'node') { + if (info.type === "node") { if (this.passthrough) { - this.shape = this.ng.createSVGElement('ellipse'); - if (this.minimap == false) { + this.shape = this.ng.createSVGElement("ellipse"); + if (this.minimap === false) { this.fixed_width = 10; this.fixed_height = 10; } else { this.fixed_width = 3; this.fixed_height = 3; } - this.g.classList.add('passthrough'); + this.g.classList.add("passthrough"); } else { - this.shape = this.ng.createSVGElement('rect'); + this.shape = this.ng.createSVGElement("rect"); } - } else if (info.type === 'net') { - this.shape = this.ng.createSVGElement('rect'); - } else if (info.type === 'ens') { + } else if (info.type === "net") { + this.shape = this.ng.createSVGElement("rect"); + } else if (info.type === "ens") { this.aspect = 1.; this.shape = this.ensemble_svg(); } else { - console.log("Unknown NetGraphItem type"); - console.log(info.type); + console.warn("Unknown NetGraphItem type:" + info.type); } this.compute_fill(); - if (this.minimap == false) { - var label = this.ng.createSVGElement('text'); + if (this.minimap === false) { + const label = this.ng.createSVGElement("text"); this.label = label; label.innerHTML = info.label; g.appendChild(label); - }; + } g.appendChild(this.shape); g.appendChild(this.area); this.redraw(); - interact.margin(10); - if (!this.minimap) { // Dragging an item to change its position - var uid = this.uid; + const uid = this.uid; interact(g).draggable({ - onstart: function() { - menu.hide_any(); - self.move_to_front(); + onend: function(event) { + const item = self.ng.svg_objects[uid]; + item.constrain_position(); + self.ng.notify({ + act: "pos", uid: uid, x: item.x, y: item.y, + }); + + item.redraw(); }, onmove: function(event) { - var w = self.ng.get_scaled_width(); - var h = self.ng.get_scaled_height(); - var item = self.ng.svg_objects[uid]; - var parent = item.parent; + const item = self.ng.svg_objects[uid]; + let w = self.ng.get_scaled_width(); + let h = self.ng.get_scaled_height(); + let parent = item.parent; while (parent !== null) { - w = w * parent.width * 2; - h = h * parent.height * 2; + w *= parent.width * 2; + h *= parent.height * 2; parent = parent.parent; } item.x += event.dx / w; @@ -199,34 +238,30 @@ export default class NetGraphItem { self.ng.scaleMiniMap(); } }, - onend: function(event) { - var item = self.ng.svg_objects[uid]; - item.constrain_position(); - self.ng.notify({ - act: "pos", uid: uid, x: item.x, y: item.y - }); - - item.redraw(); - } + onstart: function() { + menu.hide_any(); + self.move_to_front(); + }, }); if (!this.passthrough) { // Dragging the edge of item to change its size - var tmp = this.shape; - if (info.type === 'ens') { - tmp = $(this.shape.getElementsByClassName('mainCircle'))[0]; + let tmp = this.shape; + if (info.type === "ens") { + tmp = $(this.shape.getElementsByClassName("mainCircle"))[0]; } interact(this.area).resizable({ - edges: {left: true, right: true, bottom: true, top: true}, - invert: this.type == 'ens' ? 'reposition' : 'none' - }).on('resizestart', function(event) { + edges: {bottom: true, left: true, right: true, top: true}, + invert: this.type === "ens" ? "reposition" : "none", + margin: 10, + }).on("resizestart", function(event) { menu.hide_any(); - }).on('resizemove', function(event) { - var item = self.ng.svg_objects[uid]; - var pos = item.get_screen_location(); - var h_scale = self.ng.get_scaled_width(); - var v_scale = self.ng.get_scaled_height(); - var parent = item.parent; + }).on("resizemove", function(event) { + const item = self.ng.svg_objects[uid]; + const pos = item.get_screen_location(); + let h_scale = self.ng.get_scaled_width(); + let v_scale = self.ng.get_scaled_height(); + let parent = item.parent; while (parent !== null) { h_scale = h_scale * parent.width * 2; v_scale = v_scale * parent.height * 2; @@ -236,13 +271,13 @@ export default class NetGraphItem { if (self.aspect !== null) { self.constrain_aspect(); - var vertical_resize = + const vertical_resize = event.edges.bottom || event.edges.top; - var horizontal_resize = + const horizontal_resize = event.edges.left || event.edges.right; - var w = pos[0] - event.clientX + self.ng.offsetX; - var h = pos[1] - event.clientY + self.ng.offsetY; + let w = pos[0] - event.clientX + self.ng.offsetX; + let h = pos[1] - event.clientY + self.ng.offsetY; if (event.edges.right) { w *= -1; @@ -257,13 +292,13 @@ export default class NetGraphItem { h = 1; } - var screen_w = item.width * h_scale; - var screen_h = item.height * v_scale; + const screen_w = item.width * h_scale; + const screen_h = item.height * v_scale; if (horizontal_resize && vertical_resize) { - var p = (screen_w * w + screen_h * h) / Math.sqrt( + const p = (screen_w * w + screen_h * h) / Math.sqrt( screen_w * screen_w + screen_h * screen_h); - var norm = Math.sqrt(self.aspect * self.aspect + 1); + const norm = Math.sqrt(self.aspect * self.aspect + 1); h = p / (self.aspect / norm); w = p * (self.aspect / norm); } else if (horizontal_resize) { @@ -272,16 +307,13 @@ export default class NetGraphItem { w = h * self.aspect; } - var scaled_w = w / h_scale; - var scaled_h = h / v_scale; - - item.width = scaled_w; - item.height = scaled_h; + item.width = w / h_scale; + item.height = h / v_scale; } else { - var dw = event.deltaRect.width / h_scale / 2; - var dh = event.deltaRect.height / v_scale / 2; - var offset_x = dw + event.deltaRect.left / h_scale; - var offset_y = dh + event.deltaRect.top / v_scale; + const dw = event.deltaRect.width / h_scale / 2; + const dh = event.deltaRect.height / v_scale / 2; + const offset_x = dw + event.deltaRect.left / h_scale; + const offset_y = dh + event.deltaRect.top / v_scale; item.width += dw; item.height += dh; @@ -294,21 +326,26 @@ export default class NetGraphItem { if (self.depth === 1) { self.ng.scaleMiniMap(); } - }).on('resizeend', function(event) { - var item = self.ng.svg_objects[uid]; + }).on("resizeend", function(event) { + const item = self.ng.svg_objects[uid]; item.constrain_position(); item.redraw(); - self.ng.notify({act: "pos_size", uid: uid, - x: item.x, y: item.y, - width: item.width, height: item.height}); + self.ng.notify({ + act: "pos_size", + height: item.height, + uid: uid, + width: item.width, + x: item.x, + y: item.y, + }); }); } // Determine when to pull up the menu interact(this.g) - .on('hold', function(event) { - // Change to 'tap' for right click - if (event.button == 0) { + .on("hold", function(event) { + // Change to "tap" for right click + if (event.button === 0) { if (self.menu.visible_any()) { menu.hide_any(); } else { @@ -319,20 +356,20 @@ export default class NetGraphItem { event.stopPropagation(); } }) - .on('tap', function(event) { + .on("tap", function(event) { // Get rid of menus when clicking off - if (event.button == 0) { + if (event.button === 0) { if (self.menu.visible_any()) { menu.hide_any(); } } }) - .on('doubletap', function(event) { + .on("doubletap", function(event) { // Get rid of menus when clicking off - if (event.button == 0) { + if (event.button === 0) { if (self.menu.visible_any()) { menu.hide_any(); - } else if (self.type === 'net') { + } else if (self.type === "net") { if (self.expanded) { self.collapse(true); } else { @@ -341,7 +378,7 @@ export default class NetGraphItem { } } }); - $(this.g).bind('contextmenu', function(event) { + $(this.g).bind("contextmenu", function(event) { event.preventDefault(); event.stopPropagation(); if (self.menu.visible_any()) { @@ -352,14 +389,14 @@ export default class NetGraphItem { } }); - if (info.type === 'net') { + if (info.type === "net") { // If a network is flagged to expand on creation, then expand it if (info.expanded) { // Report to server but do not add to the undo stack this.expand(true, true); } } - }; + } }; set_label(label) { @@ -369,126 +406,128 @@ export default class NetGraphItem { move_to_front() { this.g.parentNode.appendChild(this.g); - for (var item in this.children) { - this.children[item].move_to_front(); + for (let item in this.children) { + if (this.children.hasOwnProperty(item)) { + this.children[item].move_to_front(); + } } }; generate_menu() { - var self = this; - var items = []; - if (this.type === 'net') { + const self = this; + const items = []; + if (this.type === "net") { if (this.expanded) { - items.push(['Collapse network', function() { + items.push(["Collapse network", function() { self.collapse(true); }]); - items.push(['Auto-layout', function() { + items.push(["Auto-layout", function() { self.request_feedforward_layout(); }]); } else { - items.push(['Expand network', function() { + items.push(["Expand network", function() { self.expand(); }]); } - if (this.default_output && this.sp_targets.length == 0) { - items.push(['Output Value', function() { - self.create_graph('Value'); + if (this.default_output && this.sp_targets.length === 0) { + items.push(["Output Value", function() { + self.create_graph("Value"); }]); } } - if (this.type == 'ens') { - items.push(['Value', function() { - self.create_graph('Value'); + if (this.type === "ens") { + items.push(["Value", function() { + self.create_graph("Value"); }]); if (this.dimensions > 1) { - items.push(['XY-value', function() { - self.create_graph('XYValue'); + items.push(["XY-value", function() { + self.create_graph("XYValue"); }]); } - items.push(['Spikes', function() { - self.create_graph('Raster'); + items.push(["Spikes", function() { + self.create_graph("Raster"); }]); - items.push(['Voltages', function() { - self.create_graph('Voltage'); + items.push(["Voltages", function() { + self.create_graph("Voltage"); }]); - items.push(['Firing pattern', function() { - self.create_graph('SpikeGrid'); + items.push(["Firing pattern", function() { + self.create_graph("SpikeGrid"); }]); } - if (this.type == 'node') { - items.push(['Slider', function() { - self.create_graph('Slider'); + if (this.type === "node") { + items.push(["Slider", function() { + self.create_graph("Slider"); }]); if (this.dimensions > 0) { - items.push(['Value', function() { - self.create_graph('Value'); + items.push(["Value", function() { + self.create_graph("Value"); }]); } if (this.dimensions > 1) { - items.push(['XY-value', function() { - self.create_graph('XYValue'); + items.push(["XY-value", function() { + self.create_graph("XYValue"); }]); } if (this.html_node) { - items.push(['HTML', function() { - self.create_graph('HTMLView'); + items.push(["HTML", function() { + self.create_graph("HTMLView"); }]); } } if (this.sp_targets.length > 0) { - items.push(['Semantic pointer cloud', function() { - self.create_graph('Pointer', self.sp_targets[0]); + items.push(["Semantic pointer cloud", function() { + self.create_graph("Pointer", self.sp_targets[0]); }]); - items.push(['Semantic pointer plot', function() { - self.create_graph('SpaSimilarity', self.sp_targets[0]); + 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() { + items.push(["Details ...", function() { self.create_modal(); }]); return items; }; - create_graph(type, args) { - var info = {}; - info.act = 'create_graph'; - info.type = type; - var w = this.get_nested_width(); - var h = this.get_nested_height(); - - var pos = this.get_screen_location(); - - info.x = pos[0] / (this.ng.viewport.w * this.ng.viewport.scale) - - this.ng.viewport.x + w; - info.y = pos[1] / (this.ng.viewport.h * this.ng.viewport.scale) - - this.ng.viewport.y + h; + create_graph(type, args=null) { // tslint:disable-line + const w = this.get_nested_width(); + const h = this.get_nested_height(); + const pos = this.get_screen_location(); + + let info: any = { + "act": "create_graph", + "height": 100 / (this.ng.viewport.height * this.ng.viewport.scale), + "type": type, + "uid": this.uid, + "width": 100 / (this.ng.viewport.width * this.ng.viewport.scale), + "x": pos[0] / (this.ng.viewport.width * this.ng.viewport.scale) - + this.ng.viewport.x + w, + "y": pos[1] / (this.ng.viewport.height * this.ng.viewport.scale) - + this.ng.viewport.y + h, + }; - info.width = 100 / (this.ng.viewport.w * this.ng.viewport.scale); - info.height = 100 / (this.ng.viewport.h * this.ng.viewport.scale); + if (args !== null) { + info.args = args; + } - if (info.type == 'Slider') { + if (info.type === "Slider") { info.width /= 2; } - info.uid = this.uid; - if (typeof(args) != 'undefined') { - info.args = args; - } this.ng.notify(info); }; create_modal() { - var info = {}; - info.act = 'create_modal'; - info.uid = this.uid; - info.conn_in_uids = this.conn_in.map(function(c) { - return c.uid; + this.ng.notify({ + "act": "create_modal", + "conn_in_uids": this.conn_in.map(function(c) { + return c.uid; + }), + "conn_out_uids": this.conn_out.map(function(c) { + return c.uid; + }), + "uid": this.uid, }); - info.conn_out_uids = this.conn_out.map(function(c) { - return c.uid; - }); - this.ng.notify(info); }; request_feedforward_layout() { @@ -498,12 +537,12 @@ export default class NetGraphItem { /** * Expand a collapsed network. */ - expand(rts, auto) { + expand(rts=true, auto=false) { // tslint:disable-line // Default to true if no parameter is specified - rts = typeof rts !== 'undefined' ? rts : true; - auto = typeof auto !== 'undefined' ? auto : false; + rts = typeof rts !== "undefined" ? rts : true; + auto = typeof auto !== "undefined" ? auto : false; - this.g.classList.add('expanded'); + this.g.classList.add("expanded"); if (!this.expanded) { this.expanded = true; @@ -516,8 +555,7 @@ export default class NetGraphItem { this.mini_item.expand(rts, auto); } } else { - console.log("expanded a network that was already expanded"); - console.log(this); + console.warn("expanded a network that was already expanded: " + this); } if (rts) { @@ -532,20 +570,19 @@ export default class NetGraphItem { set_label_below(flag) { if (flag && !this.label_below) { - var screen_h = this.get_screen_height(); + const screen_h = this.get_screen_height(); this.label.setAttribute( - 'transform', 'translate(0, ' + (screen_h / 2) + ')'); + "transform", "translate(0, " + (screen_h / 2) + ")"); } else if (!flag && this.label_below) { - this.label.setAttribute('transform', ''); + this.label.setAttribute("transform", ""); } }; /** * Collapse an expanded network. */ - collapse(report_to_server, auto) { - auto = typeof auto !== 'undefined' ? auto : false; - this.g.classList.remove('expanded'); + collapse(report_to_server, auto=false) { // tslint:disable-line + this.g.classList.remove("expanded"); // Remove child NetGraphItems and NetGraphConnections while (this.child_connections.length > 0) { @@ -566,8 +603,7 @@ export default class NetGraphItem { this.mini_item.collapse(report_to_server, auto); } } else { - console.log("collapsed a network that was already collapsed"); - console.log(this); + console.warn("collapsed a network that was already collapsed: " + this); } if (report_to_server) { @@ -584,14 +620,14 @@ export default class NetGraphItem { * Determine the fill color based on the depth. */ compute_fill() { - var depth = this.ng.transparent_nets ? 1 : this.depth; + const depth = this.ng.transparent_nets ? 1 : this.depth; if (!this.passthrough) { - var fill = Math.round(255 * Math.pow(0.8, depth)); - this.shape.style.fill = 'rgb(' + fill + ',' + fill + ',' + fill + ')'; - var stroke = Math.round(255 * Math.pow(0.8, depth + 2)); + const fill = Math.round(255 * Math.pow(0.8, depth)); + this.shape.style.fill = "rgb(" + fill + "," + fill + "," + fill + ")"; + const stroke = Math.round(255 * Math.pow(0.8, depth + 2)); this.shape.style.stroke = - 'rgb(' + stroke + ',' + stroke + ',' + stroke + ')'; + "rgb(" + stroke + "," + stroke + "," + stroke + ")"; } }; @@ -607,29 +643,29 @@ export default class NetGraphItem { // Remove the item from the parent's children list if (!this.minimap && this.parent !== null) { - var index = this.parent.children.indexOf(this); + const index = this.parent.children.indexOf(this); this.parent.children.splice(index, 1); } delete this.ng.svg_objects[this.uid]; // Update any connections into or out of this item - var conn_in = this.conn_in.slice(); - for (var i in conn_in) { - var conn = conn_in[i]; + const conn_in = this.conn_in.slice(); + for (let i = 0; i < conn_in.length; i++) { + const conn = conn_in[i]; conn.set_post(conn.find_post()); conn.redraw(); } - var conn_out = this.conn_out.slice(); - for (var i in conn_out) { - var conn = conn_out[i]; + const conn_out = this.conn_out.slice(); + for (let i = 0; i < conn_out; i++) { + const conn = conn_out[i]; conn.set_pre(conn.find_pre()); conn.redraw(); } // Remove from the SVG this.g_items.removeChild(this.g); - if (this.depth == 1) { + if (this.depth === 1) { this.ng.scaleMiniMap(); } @@ -644,10 +680,10 @@ export default class NetGraphItem { get_displayed_size() { if (this.aspect !== null) { - var h_scale = this.ng.get_scaled_width(); - var v_scale = this.ng.get_scaled_height(); - var w = this.get_nested_width() * h_scale; - var h = this.get_nested_height() * v_scale; + const h_scale = this.ng.get_scaled_width(); + const v_scale = this.ng.get_scaled_height(); + let w = this.get_nested_width() * h_scale; + let h = this.get_nested_height() * v_scale; if (h * this.aspect < w) { w = h * this.aspect; @@ -677,38 +713,34 @@ export default class NetGraphItem { }; redraw_position() { - var screen = this.get_screen_location(); + const screen = this.get_screen_location(); // Update my position - this.g.setAttribute('transform', 'translate(' + screen[0] + ', ' + - screen[1] + ')'); + this.g.setAttribute("transform", "translate(" + screen[0] + ", " + + screen[1] + ")"); }; redraw_children() { // Update any children's positions - for (var i in this.children) { - var item = this.children[i]; - item.redraw(); + for (let i = 0; i < this.children.length; i++) { + this.children[i].redraw(); } }; redraw_child_connections() { // Update any children's positions - for (var i in this.child_connections) { - var item = this.child_connections[i]; - item.redraw(); + for (let i = 0; i < this.child_connections.length; i++) { + this.child_connections[i].redraw(); } }; redraw_connections() { // Update any connections into and out of this - for (var i in this.conn_in) { - var item = this.conn_in[i]; - item.redraw(); + for (let i = 0; i < this.conn_in.length; i++) { + this.conn_in[i].redraw(); } - for (var i in this.conn_out) { - var item = this.conn_out[i]; - item.redraw(); + for (let i = 0; i < this.conn_out.length; i++) { + this.conn_out[i].redraw(); } }; @@ -716,8 +748,8 @@ export default class NetGraphItem { * Return the width of the item, taking into account parent widths. */ get_nested_width() { - var w = this.width; - var parent = this.parent; + let w = this.width; + let parent = this.parent; while (parent !== null) { w *= parent.width * 2; parent = parent.parent; @@ -729,8 +761,8 @@ export default class NetGraphItem { * Return the height of the item, taking into account parent heights. */ get_nested_height() { - var h = this.height; - var parent = this.parent; + let h = this.height; + let parent = this.parent; while (parent !== null) { h *= parent.height * 2; parent = parent.parent; @@ -739,8 +771,8 @@ export default class NetGraphItem { }; redraw_size() { - var screen_w = this.get_screen_width(); - var screen_h = this.get_screen_height(); + let screen_w = this.get_screen_width(); + let screen_h = this.get_screen_height(); if (this.aspect !== null) { if (screen_h * this.aspect < screen_w) { @@ -751,40 +783,40 @@ export default class NetGraphItem { } // The circle pattern isn't perfectly square, so make its area smaller - var area_w = this.type === 'ens' ? screen_w * 0.97 : screen_w; - var area_h = screen_h; - this.area.setAttribute('transform', - 'translate(-' + (area_w / 2) + ', -' + (area_h / 2) + ')'); - this.area.setAttribute('width', area_w); - this.area.setAttribute('height', area_h); - - if (this.type === 'ens') { - var scale = Math.sqrt(screen_h * screen_h + screen_w * screen_w) / + const area_w = this.type === "ens" ? screen_w * 0.97 : screen_w; + const area_h = screen_h; + this.area.setAttribute("transform", + "translate(-" + (area_w / 2) + ", -" + (area_h / 2) + ")"); + this.area.setAttribute("width", area_w); + this.area.setAttribute("height", area_h); + + if (this.type === "ens") { + const scale = Math.sqrt(screen_h * screen_h + screen_w * screen_w) / Math.sqrt(2); - var r = 17.8; // TODO: Don't hardcode the size of the ensemble - this.shape.setAttribute('transform', 'scale(' + scale / 2 / r + ')'); - this.shape.style.setProperty('stroke-width', 20/scale); + const r = 17.8; // TODO: Don't hardcode the size of the ensemble + this.shape.setAttribute("transform", "scale(" + scale / 2 / r + ")"); + this.shape.style.setProperty("stroke-width", 20 / scale); } else if (this.passthrough) { - this.shape.setAttribute('rx', screen_w / 2); - this.shape.setAttribute('ry', screen_h / 2); + this.shape.setAttribute("rx", screen_w / 2); + this.shape.setAttribute("ry", screen_h / 2); } else { this.shape.setAttribute( - 'transform', - 'translate(-' + (screen_w / 2) + ', -' + (screen_h / 2) + ')'); - this.shape.setAttribute('width', screen_w); - this.shape.setAttribute('height', screen_h); - if (this.type === 'node') { - var radius = Math.min(screen_w, screen_h); + "transform", + "translate(-" + (screen_w / 2) + ", -" + (screen_h / 2) + ")"); + this.shape.setAttribute("width", screen_w); + this.shape.setAttribute("height", screen_h); + if (this.type === "node") { + const radius = Math.min(screen_w, screen_h); // TODO: Don't hardcode .1 as the corner radius scale - this.shape.setAttribute('rx', radius*.1); - this.shape.setAttribute('ry', radius*.1); + this.shape.setAttribute("rx", radius * .1); + this.shape.setAttribute("ry", radius * .1); } } if (!this.minimap) { this.label.setAttribute( - 'transform', 'translate(0, ' + (screen_h / 2) + ')'); - }; + "transform", "translate(0, " + (screen_h / 2) + ")"); + } }; get_screen_width() { @@ -796,16 +828,18 @@ export default class NetGraphItem { return this.fixed_width; } + let w; + let screen_w; if (!this.minimap) { - var w = this.ng.width; - var screen_w = this.get_nested_width() * w * this.ng.scale; + w = this.ng.width; + screen_w = this.get_nested_width() * w * this.ng.scale; } else { - var w = this.ng.mm_width; - var screen_w = this.get_nested_width() * w * this.ng.mm_scale; - }; + w = this.ng.mm_width; + screen_w = this.get_nested_width() * w * this.ng.mm_scale; + } - if (screen_w < this.minWidth) { - screen_w = this.minWidth; + if (screen_w < this.min_width) { + screen_w = this.min_width; } return screen_w * 2; @@ -820,16 +854,18 @@ export default class NetGraphItem { return this.fixed_height; } - if (this.minimap == false) { - var h = this.ng.height; - var screen_h = this.get_nested_height() * h * this.ng.scale; + let h; + let screen_h; + if (this.minimap === false) { + h = this.ng.height; + screen_h = this.get_nested_height() * h * this.ng.scale; } else { - var h = this.ng.mm_height; - var screen_h = this.get_nested_height() * h * this.ng.mm_scale; - }; + h = this.ng.mm_height; + screen_h = this.get_nested_height() * h * this.ng.mm_scale; + } - if (screen_h < this.minHeight) { - screen_h = this.minHeight; + if (screen_h < this.min_height) { + screen_h = this.min_height; } return screen_h * 2; @@ -860,29 +896,33 @@ export default class NetGraphItem { return [1, 1]; } - if (this.minimap == false) { - var w = this.ng.width * this.ng.scale; - var h = this.ng.height * this.ng.scale; + let w; + let h; + let offsetX; + let offsetY; + if (this.minimap === false) { + w = this.ng.width * this.ng.scale; + h = this.ng.height * this.ng.scale; - var offsetX = this.ng.offsetX * w; - var offsetY = this.ng.offsetY * h; + offsetX = this.ng.offsetX * w; + offsetY = this.ng.offsetY * h; } else { - var mm_w = this.ng.mm_width; - var mm_h = this.ng.mm_height; + const mm_w = this.ng.mm_width; + const mm_h = this.ng.mm_height; - var w = mm_w * this.ng.mm_scale; - var h = mm_h * this.ng.mm_scale; + w = mm_w * this.ng.mm_scale; + h = mm_h * this.ng.mm_scale; - var disp_w = (this.ng.mm_max_x - this.ng.mm_min_x) * w; - var disp_h = (this.ng.mm_max_y - this.ng.mm_min_y) * h; + const disp_w = (this.ng.mm_max_x - this.ng.mm_min_x) * w; + const disp_h = (this.ng.mm_max_y - this.ng.mm_min_y) * h; - var offsetX = -this.ng.mm_min_x * w + (mm_w - disp_w) / 2.; - var offsetY = -this.ng.mm_min_y * h + (mm_h - disp_h) / 2.; - }; + offsetX = -this.ng.mm_min_x * w + (mm_w - disp_w) / 2.; + offsetY = -this.ng.mm_min_y * h + (mm_h - disp_h) / 2.; + } - var dx = 0; - var dy = 0; - var parent = this.parent; + let dx = 0; + let dy = 0; + let parent = this.parent; while (parent !== null) { dx *= parent.width * 2; dy *= parent.height * 2; @@ -894,8 +934,8 @@ export default class NetGraphItem { dx *= w; dy *= h; - var ww = w; - var hh = h; + let ww = w; + let hh = h; if (this.parent !== null) { ww *= this.parent.get_nested_width() * 2; hh *= this.parent.get_nested_height() * 2; @@ -909,35 +949,35 @@ export default class NetGraphItem { * Function for drawing ensemble svg. */ ensemble_svg() { - var shape = this.ng.createSVGElement('g'); - shape.setAttribute('class', 'ensemble'); + const shape = this.ng.createSVGElement("g"); + shape.setAttribute("class", "ensemble"); - var dx = -1.25; - var dy = 0.25; + const dx = -1.25; + const dy = 0.25; - var circle = this.ng.createSVGElement('circle'); + let circle = this.ng.createSVGElement("circle"); this.setAttributes( - circle, {'cx': -11.157 + dx, 'cy': -7.481 + dy, 'r': '4.843'}); + circle, {"cx": -11.157 + dx, "cy": -7.481 + dy, "r": "4.843"}); shape.appendChild(circle); - var circle = this.ng.createSVGElement('circle'); + circle = this.ng.createSVGElement("circle"); this.setAttributes( - circle, {'cx': 0.186 + dx, 'cy': -0.127 + dy, 'r': '4.843'}); + circle, {"cx": 0.186 + dx, "cy": -0.127 + dy, "r": "4.843"}); shape.appendChild(circle); - var circle = this.ng.createSVGElement('circle'); + circle = this.ng.createSVGElement("circle"); this.setAttributes( - circle, {'cx': 5.012 + dx, 'cy': 12.56 + dy, 'r': '4.843'}); + circle, {"cx": 5.012 + dx, "cy": 12.56 + dy, "r": "4.843"}); shape.appendChild(circle); - var circle = this.ng.createSVGElement('circle'); + circle = this.ng.createSVGElement("circle"); this.setAttributes( - circle, {'cx': 13.704 + dx, 'cy': -0.771 + dy, 'r': '4.843'}); + circle, {"cx": 13.704 + dx, "cy": -0.771 + dy, "r": "4.843"}); shape.appendChild(circle); - var circle = this.ng.createSVGElement('circle'); + circle = this.ng.createSVGElement("circle"); this.setAttributes( - circle, {'cx': -10.353 + dx, 'cy': 8.413 + dy, 'r': '4.843'}); + circle, {"cx": -10.353 + dx, "cy": 8.413 + dy, "r": "4.843"}); shape.appendChild(circle); - var circle = this.ng.createSVGElement('circle'); + circle = this.ng.createSVGElement("circle"); this.setAttributes( - circle, {'cx': 3.894 + dx, 'cy': -13.158 + dy, 'r': '4.843'}); + circle, {"cx": 3.894 + dx, "cy": -13.158 + dy, "r": "4.843"}); shape.appendChild(circle); return shape; @@ -947,17 +987,18 @@ export default class NetGraphItem { * Helper function for setting attributes. */ setAttributes(el, attrs) { - for (var key in attrs) { - el.setAttribute(key, attrs[key]); + for (let key in attrs) { + if (attrs.hasOwnProperty(key)) { + el.setAttribute(key, attrs[key]); + } } }; getMinMaxXY() { - var min_x = this.x - this.width; - var max_x = this.x + this.width; - var min_y = this.y - this.height; - var max_y = this.y + this.height; + const min_x = this.x - this.width; + const max_x = this.x + this.width; + const min_y = this.y - this.height; + const max_y = this.y + this.height; return [min_x, max_x, min_y, max_y]; }; - } diff --git a/nengo_gui/static/components/pointer.ts b/nengo_gui/static/components/pointer.ts index c19876d7..a535962c 100644 --- a/nengo_gui/static/components/pointer.ts +++ b/nengo_gui/static/components/pointer.ts @@ -11,26 +11,35 @@ * netgraph.js {.on_message} function. */ -import "./pointer.css"; -import { Component } from "./component"; +import * as $ from "jquery"; + import { DataStore } from "../datastore"; import * as utils from "../utils"; +import { Component } from "./component"; +import "./pointer.css"; export default class Pointer extends Component { + data_store; + fixed_value; + mouse_down_time; + pdiv; + pointer_status; + show_pairs; + sim; constructor(parent, viewport, sim, args) { super(parent, viewport, args); - var self = this; + const self = this; this.sim = sim; this.pointer_status = false; - this.pdiv = document.createElement('div'); + this.pdiv = document.createElement("div"); this.pdiv.style.width = args.width; this.pdiv.style.height = args.height; utils.set_transform(this.pdiv, 0, 25); - this.pdiv.style.position = 'fixed'; - this.pdiv.classList.add('pointer'); + this.pdiv.style.position = "fixed"; + this.pdiv.classList.add("pointer"); this.div.appendChild(this.pdiv); this.show_pairs = args.show_pairs; @@ -39,27 +48,27 @@ export default class Pointer extends Component { this.data_store = new 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(); + this.sim.div.addEventListener("adjust_time", function(e) { + self.schedule_update(null); }, false); // Call reset whenever the simulation is reset - this.sim.div.addEventListener('sim_reset', function(e) { - self.reset(); + this.sim.div.addEventListener("sim_reset", function(e) { + self.reset(null); }, false); this.on_resize(this.get_screen_width(), this.get_screen_height()); - this.fixed_value = ''; + this.fixed_value = ""; this.div.addEventListener("mouseup", function(event) { - // For some reason 'tap' doesn't seem to work here while the + // For some reason "tap" doesn't seem to work here while the // simulation is running, so I'm doing the timing myself - var now = new Date().getTime() / 1000; + const now = new Date().getTime() / 1000; if (now - self.mouse_down_time > 0.1) { return; } - if (event.button == 0) { + if (event.button === 0) { if (self.menu.visible) { self.menu.hide(); } else { @@ -75,17 +84,17 @@ export default class Pointer extends Component { }; generate_menu() { - var self = this; - var items = []; - items.push(['Set value...', function() { + const self = this; + const items = []; + items.push(["Set value...", function() { self.set_value(); }]); if (this.show_pairs) { - items.push(['Hide pairs', function() { + items.push(["Hide pairs", function() { self.set_show_pairs(false); }]); } else { - items.push(['Show pairs', function() { + items.push(["Show pairs", function() { self.set_show_pairs(true); }]); } @@ -103,42 +112,45 @@ export default class Pointer extends Component { }; set_value() { - var self = this; - self.sim.modal.title('Enter a Semantic Pointer value...'); - self.sim.modal.single_input_body('Pointer', 'New value'); - self.sim.modal.footer('ok_cancel', function(e) { - var value = $('#singleInput').val(); - var modal = $('#myModalForm').data('bs.validator'); + const self = this; + self.sim.modal.title("Enter a Semantic Pointer value..."); + self.sim.modal.single_input_body("Pointer", "New value"); + self.sim.modal.footer("ok_cancel", function(e) { + let value = $("#singleInput").val(); + const modal = $("#myModalForm").data("bs.validator"); modal.validate(); if (modal.hasErrors() || modal.isIncomplete()) { return; } - if ((value === null) || (value === '')) { - value = ':empty:'; + if ((value === null) || (value === "")) { + value = ":empty:"; } self.fixed_value = value; self.ws.send(value); - $('#OK').attr('data-dismiss', 'modal'); + $("#OK").attr("data-dismiss", "modal"); }); - var $form = $('#myModalForm').validator({ + $("#myModalForm").validator({ custom: { my_validator: function($item) { - var ptr = $item.val(); + let ptr = $item.val(); if (ptr === null) { - ptr = ''; + ptr = ""; } - self.ws.send(':check only:' + ptr); + self.ws.send(":check only:" + ptr); return self.pointer_status; - } - } + }, + }, }); - $('#singleInput').attr('data-error', 'Invalid semantic ' + - 'pointer expression. Semantic pointers themselves must start with ' + - 'a capital letter. Expressions can include mathematical operators ' + - 'such as +, * (circular convolution), and ~ (pseudo-inverse). ' + - 'E.g., (A+~(B*C)*2)*0.5 would be a valid semantic pointer expression.'); + $("#singleInput").attr( + "data-error", + "Invalid semantic pointer expression. Semantic pointers " + + "themselves must start with a capital letter. Expressions " + + "can include mathematical operators such as +, * (circular " + + "convolution), and ~ (pseudo-inverse). E.g., " + + "(A+~(B*C)*2)*0.5 would be a valid semantic pointer " + + "expression."); self.sim.modal.show(); }; @@ -147,21 +159,21 @@ export default class Pointer extends Component { * Receive new line data from the server. */ on_message(event) { - var data = event.data.split(" "); + const data = event.data.split(" "); - if (data[0].substring(0, 11) == "bad_pointer") { + if (data[0].substring(0, 11) === "bad_pointer") { this.pointer_status = false; return; - } else if (data[0].substring(0, 12) == "good_pointer") { + } else if (data[0].substring(0, 12) === "good_pointer") { this.pointer_status = true; return; } - var time = parseFloat(data[0]); + const time = parseFloat(data[0]); - var items = data[1].split(";"); + const items = data[1].split(";"); this.data_store.push([time, items]); - this.schedule_update(); + this.schedule_update(null); }; /** @@ -171,7 +183,7 @@ export default class Pointer extends Component { // Let the data store clear out old values this.data_store.update(); - var data = this.data_store.get_last_data()[0]; + const data = this.data_store.get_last_data()[0]; while (this.pdiv.firstChild) { this.pdiv.removeChild(this.pdiv.firstChild); @@ -183,18 +195,17 @@ export default class Pointer extends Component { return; } - var total_size = 0; - - var items = []; + let total_size = 0; + const items = []; // Display the text in proportion to similarity - for (var i = 0; i < data.length; i++) { - var size = parseFloat(data[i].substring(0, 4)); - var span = document.createElement('span'); + for (let i = 0; i < data.length; i++) { + const size = parseFloat(data[i].substring(0, 4)); + const span = document.createElement("span"); span.innerHTML = data[i].substring(4); this.pdiv.appendChild(span); total_size += size; - var c = Math.floor(255 - 255 * size); + let c = Math.floor(255 - 255 * size); // TODO: Use clip if (c < 0) { c = 0; @@ -202,15 +213,15 @@ export default class Pointer extends Component { if (c > 255) { c = 255; } - span.style.color = 'rgb(' + c + ',' + c + ',' + c + ')'; + span.style.color = "rgb(" + c + "," + c + "," + c + ")"; items.push(span); } - var scale = this.height / total_size * 0.6; + const scale = this.height / total_size * 0.6; - for (var i = 0; i < data.length; i++) { - var size = parseFloat(data[i].substring(0, 4)); - items[i].style.fontSize = '' + (size * scale) + 'px'; + for (let i = 0; i < data.length; i++) { + const size = parseFloat(data[i].substring(0, 4)); + items[i].style.fontSize = "" + (size * scale) + "px"; } }; @@ -218,12 +229,12 @@ export default class Pointer extends Component { * Adjust the graph layout due to changed size. */ on_resize(width, height) { - if (width < this.minWidth) { - width = this.minWidth; + if (width < this.min_width) { + width = this.min_width; + } + if (height < this.min_height) { + height = this.min_height; } - if (height < this.minHeight) { - height = this.minHeight; - }; this.width = width; this.height = height; @@ -236,7 +247,7 @@ export default class Pointer extends Component { }; layout_info() { - var info = Component.prototype.layout_info.call(this); + const info = Component.prototype.layout_info.call(this); info.show_pairs = this.show_pairs; return info; }; @@ -248,7 +259,6 @@ export default class Pointer extends Component { reset(event) { this.data_store.reset(); - this.schedule_update(); + this.schedule_update(event); }; - } diff --git a/nengo_gui/static/components/raster.ts b/nengo_gui/static/components/raster.ts index a3db1507..46806060 100644 --- a/nengo_gui/static/components/raster.ts +++ b/nengo_gui/static/components/raster.ts @@ -13,18 +13,24 @@ */ import * as d3 from "d3"; +import * as $ from "jquery"; -import "./raster.css"; -import { Component } from "./component"; import { DataStore } from "../datastore"; -import TimeAxes from "./time_axes"; import * as utils from "../utils"; +import { Component } from "./component"; +import "./raster.css"; +import TimeAxes from "./time_axes"; export default class Raster extends Component { + axes2d; + data_store; + n_neurons; + path; + sim; constructor(parent, viewport, sim, args) { super(parent, viewport, args); - var self = this; + const self = this; this.n_neurons = args.n_neurons || 1; this.sim = sim; @@ -35,17 +41,17 @@ export default class Raster extends Component { this.axes2d.scale_y.domain([0, args.n_neurons]); // Call schedule_update whenever the time is adjusted in the SimControl - this.sim.div.addEventListener('adjust_time', function(e) { - self.schedule_update(); + this.sim.div.addEventListener("adjust_time", function(e) { + self.schedule_update(null); }, false); // Call reset whenever the simulation is reset - this.sim.div.addEventListener('sim_reset', function(e) { - self.reset(); + this.sim.div.addEventListener("sim_reset", function(e) { + self.reset(null); }, false); // Create the lines on the plots - var line = d3.svg.line() + d3.svg.line() .x(function(d, i) { return self.axes2d.scale_x(this.data_store.times[i]); }) @@ -54,12 +60,12 @@ export default class Raster extends Component { }); this.path = this.axes2d.svg.append("g") - .selectAll('path') + .selectAll("path") .data(this.data_store.data); - this.path.enter().append('path') - .attr('class', 'line') - .style('stroke', utils.make_colors(1)); + this.path.enter().append("path") + .attr("class", "line") + .style("stroke", utils.make_colors(1)); this.update(); this.on_resize(this.get_screen_width(), this.get_screen_height()); @@ -71,17 +77,17 @@ export default class Raster extends Component { * Receive new line data from the server. */ on_message(event) { - var time = new Float32Array(event.data, 0, 1); - var data = new Int16Array(event.data, 4); + const time = new Float32Array(event.data, 0, 1); + const data = new Int16Array(event.data, 4); this.data_store.push([time[0], data]); - this.schedule_update(); + this.schedule_update(event); }; set_n_neurons(n_neurons) { this.n_neurons = n_neurons; this.axes2d.scale_y.domain([0, n_neurons]); this.axes2d.axis_y.tickValues([0, n_neurons]); - this.ws.send('n_neurons:' + n_neurons); + this.ws.send("n_neurons:" + n_neurons); }; /** @@ -92,24 +98,24 @@ export default class Raster extends Component { this.data_store.update(); // Determine visible range from the SimControl - var t1 = this.sim.time_slider.first_shown_time; - var t2 = t1 + this.sim.time_slider.shown_time; + const t1 = this.sim.time_slider.first_shown_time; + const t2 = t1 + this.sim.time_slider.shown_time; this.axes2d.set_time_range(t1, t2); // Update the lines - var shown_data = this.data_store.get_shown_data(); + const shown_data = this.data_store.get_shown_data(); - var path = []; - for (var i = 0; i < shown_data[0].length; i++) { - var t = this.axes2d.scale_x( + const path = []; + for (let i = 0; i < shown_data[0].length; i++) { + const t = this.axes2d.scale_x( this.data_store.times[ this.data_store.first_shown_index + i]); - for (var j = 0; j < shown_data[0][i].length; j++) { - var y1 = this.axes2d.scale_y(shown_data[0][i][j]); - var y2 = this.axes2d.scale_y(shown_data[0][i][j] + 1); - path.push('M ' + t + ' ' + y1 + 'V' + y2); + for (let j = 0; j < shown_data[0][i].length; j++) { + const y1 = this.axes2d.scale_y(shown_data[0][i][j]); + const y2 = this.axes2d.scale_y(shown_data[0][i][j] + 1); + path.push("M " + t + " " + y1 + "V" + y2); } } this.path.attr("d", path.join("")); @@ -119,12 +125,12 @@ export default class Raster extends Component { * Adjust the graph layout due to changed size. */ on_resize(width, height) { - if (width < this.minWidth) { - width = this.minWidth; + if (width < this.min_width) { + width = this.min_width; + } + if (height < this.min_height) { + height = this.min_height; } - if (height < this.minHeight) { - height = this.minHeight; - }; this.axes2d.on_resize(width, height); @@ -140,60 +146,60 @@ export default class Raster extends Component { reset(event) { this.data_store.reset(); - this.schedule_update(); + this.schedule_update(event); }; generate_menu() { - var self = this; - var items = []; - items.push(['Set # neurons...', function() {self.set_neuron_count();}]); + const self = this; + const items = [["Set # neurons...", function() { + self.set_neuron_count(); + }]]; return $.merge(items, Component.prototype.generate_menu.call(this)); }; set_neuron_count() { - var count = this.n_neurons; - var self = this; - self.sim.modal.title('Set number of neurons...'); - self.sim.modal.single_input_body(count, 'Number of neurons'); - self.sim.modal.footer('ok_cancel', function(e) { - var new_count = $('#singleInput').val(); - var modal = $('#myModalForm').data('bs.validator'); + const self = this; + const count = this.n_neurons; + self.sim.modal.title("Set number of neurons..."); + self.sim.modal.single_input_body(count, "Number of neurons"); + self.sim.modal.footer("ok_cancel", function(e) { + let new_count = $("#singleInput").val(); + const modal = $("#myModalForm").data("bs.validator"); modal.validate(); if (modal.hasErrors() || modal.isIncomplete()) { return; } if (new_count !== null) { - new_count = parseInt(new_count); + new_count = parseInt(new_count, 10); self.set_n_neurons(new_count); self.axes2d.fit_ticks(self); } - $('#OK').attr('data-dismiss', 'modal'); + $("#OK").attr("data-dismiss", "modal"); }); - var $form = $('#myModalForm').validator({ + $("#myModalForm").validator({ custom: { my_validator: function($item) { - var num = $item.val(); - var valid = false; + let num = $item.val(); + let valid = false; if ($.isNumeric(num)) { num = Number(num); - if (num >= 0 && Number.isInteger(num)) { + // TODO: make into utils.isInteger + if (num >= 0 && num === parseInt(num, 10)) { valid = true; } } return valid; - } + }, }, }); - $('#singleInput').attr('data-error', 'Input should be a positive integer'); + $("#singleInput").attr("data-error", "Input should be a positive integer"); self.sim.modal.show(); - $('#OK').on('click', function() { - var w = $(self.div).width(); - var h = $(self.div).height(); - self.on_resize(w, h); + $("#OK").on("click", function() { + const div = $(self.div); + self.on_resize(div.width(), div.height()); }); }; - } diff --git a/nengo_gui/static/components/slider.ts b/nengo_gui/static/components/slider.ts index e7367309..decebd3a 100644 --- a/nengo_gui/static/components/slider.ts +++ b/nengo_gui/static/components/slider.ts @@ -12,17 +12,32 @@ * netgraph.js {.on_message} function. */ -import "./slider.css"; -import { Component } from "./component"; +import * as $ from "jquery"; + import { DataStore } from "../datastore"; import * as menu from "../menu"; +import { Component } from "./component"; +import "./slider.css"; import SliderControl from "./slidercontrol"; export default class Slider extends Component { + ax_top; + border_size; + data_store; + filling_slider_value; + group; + immediate_notify; + n_sliders; + notify_msgs; + reset_value; + sim; + slider_height; + sliders; + start_value; constructor(parent, viewport, sim, args) { super(parent, viewport, args); - var self = this; + const self = this; this.sim = sim; // Check if user is filling in a number into a slider @@ -38,13 +53,13 @@ export default class Slider extends Component { this.set_axes_geometry(this.width, this.height); - this.minHeight = 40; + this.min_height = 40; - this.group = document.createElement('div'); + this.group = document.createElement("div"); this.group.style.height = this.slider_height; this.group.style.marginTop = this.ax_top; - this.group.style.whiteSpace = 'nowrap'; - this.group.position = 'relative'; + this.group.style.whiteSpace = "nowrap"; + this.group.position = "relative"; this.div.appendChild(this.group); // Make the sliders @@ -54,22 +69,22 @@ export default class Slider extends Component { this.start_value = args.start_value; this.sliders = []; - for (var i = 0; i < args.n_sliders; i++) { + for (let i = 0; i < args.n_sliders; i++) { // Creating a SliderControl object for every slider handle required - var slider = new SliderControl(args.min_value, args.max_value); - slider.container.style.width = (100 / args.n_sliders) + '%'; + const slider = new SliderControl(args.min_value, args.max_value); + slider.container.style.width = (100 / args.n_sliders) + "%"; slider.display_value(args.start_value[i]); slider.index = i; slider.fixed = false; - slider.on('change', function(event) { + slider.on("change", function(event) { event.target.fixed = true; self.send_value(event.target.index, event.value); - }).on('changestart', function(event) { + }).on("changestart", function(event) { menu.hide_any(); - for (var i in this.sliders) { - if (this.sliders[i] !== event.target) { - this.sliders[i].deactivate_type_mode(); + for (let i = 0; i < self.sliders.length; i++) { + if (self.sliders[i] !== event.target) { + self.sliders[i].deactivate_type_mode(); } } }); @@ -79,12 +94,12 @@ export default class Slider extends Component { } // Call schedule_update whenever the time is adjusted in the SimControl - this.sim.div.addEventListener('adjust_time', function(e) { - self.schedule_update(); + this.sim.div.addEventListener("adjust_time", function(e) { + self.schedule_update(e); }, false); - this.sim.div.addEventListener('sim_reset', function(e) { - self.on_sim_reset(); + this.sim.div.addEventListener("sim_reset", function(e) { + self.on_sim_reset(e); }, false); this.on_resize(this.get_screen_width(), this.get_screen_height()); @@ -93,28 +108,28 @@ export default class Slider extends Component { set_axes_geometry(width, height) { this.width = width; this.height = height; - var scale = parseFloat($('#main').css('font-size')); + const scale = parseFloat($("#main").css("font-size")); this.border_size = 1; this.ax_top = 1.75 * scale; this.slider_height = this.height - this.ax_top; }; send_value(slider_index, value) { - console.assert(typeof slider_index == 'number'); - console.assert(typeof value == 'number'); + console.assert(typeof slider_index === "number"); + console.assert(typeof value === "number"); if (this.immediate_notify) { - this.ws.send(slider_index + ',' + value); + this.ws.send(slider_index + "," + value); } else { - this.notify(slider_index + ',' + value); + this.notify(slider_index + "," + value); } this.sim.time_slider.jump_to_end(); }; on_sim_reset(event) { // Release slider position and reset it - for (var i = 0; i < this.sliders.length; i++) { - this.notify('' + i + ',reset'); + for (let i = 0; i < this.sliders.length; i++) { + this.notify("" + i + ",reset"); this.sliders[i].display_value(this.start_value[i]); this.sliders[i].fixed = false; } @@ -124,12 +139,12 @@ export default class Slider extends Component { * Receive new line data from the server. */ on_message(event) { - var data = new Float32Array(event.data); + const data = new Float32Array(event.data); if (this.data_store === null) { this.data_store = new DataStore(this.sliders.length, this.sim, 0); } this.reset_value = []; - for (var i = 0; i < this.sliders.length; i++) { + for (let i = 0; i < this.sliders.length; i++) { this.reset_value.push(data[i + 1]); if (this.sliders[i].fixed) { @@ -138,30 +153,29 @@ export default class Slider extends Component { } this.data_store.push(data); - this.schedule_update(); + this.schedule_update(event); }; /** * Update visual display based when component is resized. */ on_resize(width, height) { - console.assert(typeof width == 'number'); - console.assert(typeof height == 'number'); + console.assert(typeof width === "number"); + console.assert(typeof height === "number"); - if (width < this.minWidth) { - width = this.minWidth; + if (width < this.min_width) { + width = this.min_width; + } + if (height < this.min_height) { + height = this.min_height; } - if (height < this.minHeight) { - height = this.minHeight; - }; this.set_axes_geometry(width, height); this.group.style.height = height - this.ax_top - 2 * this.border_size; this.group.style.marginTop = this.ax_top; - var N = this.sliders.length; - for (var i in this.sliders) { + for (let i = 0; i < this.sliders.length; i++) { this.sliders[i].on_resize(); } @@ -171,17 +185,18 @@ export default class Slider extends Component { }; generate_menu() { - var self = this; - var items = []; - items.push(['Set range...', function() { - self.set_range(); - }]); - items.push(['Set value...', function() { - self.user_value(); - }]); - items.push(['Reset value', function() { - self.user_reset_value(); - }]); + const self = this; + const items = [ + ["Set range...", function() { + self.set_range(); + }], + ["Set value...", function() { + self.user_value(); + }], + ["Reset value", function() { + self.user_reset_value(); + }], + ]; // Add the parent's menu items to this // TODO: is this really the best way to call the parent's generate_menu()? @@ -192,13 +207,13 @@ export default class Slider extends Component { * Report an event back to the server. */ notify(info) { + const self = this; this.notify_msgs.push(info); // Only send one message at a time // TODO: find a better way to figure out when it's safe to send // another message, rather than just waiting 1ms.... - if (this.notify_msgs.length == 1) { - var self = this; + if (this.notify_msgs.length === 1) { window.setTimeout(function() { self.send_notify_msg(); }, 50); @@ -211,10 +226,10 @@ export default class Slider extends Component { * Also schedule the next message to be sent, if any. */ send_notify_msg() { - var msg = this.notify_msgs[0]; + const self = this; + const msg = this.notify_msgs[0]; this.ws.send(msg); if (this.notify_msgs.length > 1) { - var self = this; window.setTimeout(function() { self.send_notify_msg(); }, 50); @@ -227,9 +242,9 @@ export default class Slider extends Component { if (this.data_store !== null) { this.data_store.update(); - var data = this.data_store.get_last_data(); + const data = this.data_store.get_last_data(); - for (var i = 0; i < this.sliders.length; i++) { + for (let i = 0; i < this.sliders.length; i++) { if (!this.data_store.is_at_end() || !this.sliders[i].fixed) { this.sliders[i].display_value(data[i]); } @@ -238,21 +253,21 @@ export default class Slider extends Component { }; user_value() { - var self = this; + const self = this; // First build the prompt string - var prompt_string = ''; - for (var i = 0; i < this.sliders.length; i++) { + let prompt_string = ""; + for (let i = 0; i < this.sliders.length; i++) { prompt_string = prompt_string + this.sliders[i].value.toFixed(2); - if (i != this.sliders.length - 1) { + if (i !== this.sliders.length - 1) { prompt_string = prompt_string + ", "; } } - self.sim.modal.title('Set slider value(s)...'); - self.sim.modal.single_input_body(prompt_string, 'New value(s)'); - self.sim.modal.footer('ok_cancel', function(e) { - var new_value = $('#singleInput').val(); - var modal = $('#myModalForm').data('bs.validator'); + self.sim.modal.title("Set slider value(s)..."); + self.sim.modal.single_input_body(prompt_string, "New value(s)"); + self.sim.modal.footer("ok_cancel", function(e) { + let new_value = $("#singleInput").val(); + const modal = $("#myModalForm").data("bs.validator"); modal.validate(); if (modal.hasErrors() || modal.isIncomplete()) { @@ -260,42 +275,42 @@ export default class Slider extends Component { } self.immediate_notify = false; if (new_value !== null) { - new_value = new_value.split(','); + new_value = new_value.split(","); // Update the sliders one at a time - for (var i = 0; i < self.sliders.length; i++) { + for (let i = 0; i < self.sliders.length; i++) { self.sliders[i].fixed = true; self.sliders[i].set_value(parseFloat(new_value[i])); } } self.immediate_notify = true; - $('#OK').attr('data-dismiss', 'modal'); + $("#OK").attr("data-dismiss", "modal"); }); - var $form = $('#myModalForm').validator({ + $("#myModalForm").validator({ custom: { my_validator: function($item) { - var nums = $item.val().split(','); - if (nums.length != self.sliders.length) { + const nums = $item.val().split(","); + if (nums.length !== self.sliders.length) { return false; } - for (var i = 0; i < nums.length; i++) { + for (let i = 0; i < nums.length; i++) { if (!$.isNumeric(nums[i])) { return false; } } return true; - } + }, }, }); - $('#singleInput').attr('data-error', 'Input should be one ' + - 'comma-separated numerical value for each slider.'); + $("#singleInput").attr("data-error", "Input should be one " + + "comma-separated numerical value for each slider."); self.sim.modal.show(); }; user_reset_value() { - for (var i = 0; i < this.sliders.length; i++) { - this.notify('' + i + ',reset'); + for (let i = 0; i < this.sliders.length; i++) { + this.notify("" + i + ",reset"); this.sliders[i].set_value(this.reset_value[i]); this.sliders[i].fixed = false; @@ -303,53 +318,52 @@ export default class Slider extends Component { }; set_range() { - var range = this.sliders[0].scale.domain(); - var self = this; - self.sim.modal.title('Set slider range...'); - self.sim.modal.single_input_body([range[1], range[0]], 'New range'); - self.sim.modal.footer('ok_cancel', function(e) { - var new_range = $('#singleInput').val(); - var modal = $('#myModalForm').data('bs.validator'); + const range = this.sliders[0].scale.domain(); + const self = this; + self.sim.modal.title("Set slider range..."); + self.sim.modal.single_input_body([range[1], range[0]], "New range"); + self.sim.modal.footer("ok_cancel", function(e) { + let new_range = $("#singleInput").val(); + const modal = $("#myModalForm").data("bs.validator"); modal.validate(); if (modal.hasErrors() || modal.isIncomplete()) { return; } if (new_range !== null) { - new_range = new_range.split(','); - var min = parseFloat(new_range[0]); - var max = parseFloat(new_range[1]); - for (var i in self.sliders) { + new_range = new_range.split(","); + const min = parseFloat(new_range[0]); + const max = parseFloat(new_range[1]); + for (let i = 0; i < self.sliders.length; i++) { self.sliders[i].set_range(min, max); } self.save_layout(); } - $('#OK').attr('data-dismiss', 'modal'); + $("#OK").attr("data-dismiss", "modal"); }); - var $form = $('#myModalForm').validator({ + $("#myModalForm").validator({ custom: { my_validator: function($item) { - var nums = $item.val().split(','); - var valid = false; + const nums = $item.val().split(","); + let valid = false; if ($.isNumeric(nums[0]) && $.isNumeric(nums[1])) { if (Number(nums[0]) < Number(nums[1])) { // Two numbers, 1st less than 2nd valid = true; } } - return (nums.length == 2 && valid); - } + return (nums.length === 2 && valid); + }, }, }); - $('#singleInput').attr('data-error', 'Input should be in the ' + - 'form ",".'); + $("#singleInput").attr("data-error", "Input should be in the " + + "form ','."); self.sim.modal.show(); }; layout_info() { - var info = Component.prototype.layout_info.call(this); - info.width = info.width; + const info = Component.prototype.layout_info.call(this); info.min_value = this.sliders[0].scale.domain()[1]; info.max_value = this.sliders[0].scale.domain()[0]; return info; @@ -357,10 +371,9 @@ export default class Slider extends Component { update_layout(config) { // FIXME: this has to be backwards to work. Something fishy must be going on - for (var i in this.sliders) { + for (let i = 0; i < this.sliders.length; i++) { this.sliders[i].set_range(config.min_value, config.max_value); } Component.prototype.update_layout.call(this, config); }; - } diff --git a/nengo_gui/static/components/slidercontrol.ts b/nengo_gui/static/components/slidercontrol.ts index d43ee93b..db414446 100644 --- a/nengo_gui/static/components/slidercontrol.ts +++ b/nengo_gui/static/components/slidercontrol.ts @@ -12,13 +12,27 @@ import * as d3 from "d3"; import * as interact from "interact.js"; +import * as $ from "jquery"; import * as utils from "../utils"; export default class SliderControl { + _drag_y; + border_width; + container; + fixed; + guideline; + handle; + index; + listeners; + max; + min; + scale; + type_mode; + value; constructor(min, max) { - var self = this; + const self = this; this.min = min; this.max = max; @@ -32,61 +46,60 @@ export default class SliderControl { this.scale.domain([max, min]); // TODO: move CSS to CSS file - this.container = document.createElement('div'); - this.container.style.display = 'inline-block'; - this.container.style.position = 'relative'; - this.container.style.height = '100%'; - this.container.style.padding = '0.75em 0'; - - this.guideline = document.createElement('div'); - this.guideline.classList.add('guideline'); - this.guideline.style.width = '0.5em'; - this.guideline.style.height = '100%'; - this.guideline.style.margin = 'auto'; - $(this.guideline).on('mousedown', function(event) { + this.container = document.createElement("div"); + this.container.style.display = "inline-block"; + this.container.style.position = "relative"; + this.container.style.height = "100%"; + this.container.style.padding = "0.75em 0"; + + this.guideline = document.createElement("div"); + this.guideline.classList.add("guideline"); + this.guideline.style.width = "0.5em"; + this.guideline.style.height = "100%"; + this.guideline.style.margin = "auto"; + $(this.guideline).on("mousedown", function(event) { self.set_value(self.value); }); this.container.appendChild(this.guideline); - this.handle = document.createElement('div'); - this.handle.classList.add('btn'); - this.handle.classList.add('btn-default'); - this.handle.innerHTML = 'n/a'; - this.handle.style.position = 'absolute'; - this.handle.style.height = '1.5em'; - this.handle.style.marginTop = '0.75em'; - this.handle.style.width = '95%'; - this.handle.style.fontSize = 'inherit'; - this.handle.style.padding = '0.1em 0'; - this.handle.style.borderWidth = this.border_width + 'px'; - this.handle.style.borderColor = '#666'; - this.handle.style.left = '2.5%'; - this.handle.style.transform = 'translate(0, -50%)'; + this.handle = document.createElement("div"); + this.handle.classList.add("btn"); + this.handle.classList.add("btn-default"); + this.handle.innerHTML = "n/a"; + this.handle.style.position = "absolute"; + this.handle.style.height = "1.5em"; + this.handle.style.marginTop = "0.75em"; + this.handle.style.width = "95%"; + this.handle.style.fontSize = "inherit"; + this.handle.style.padding = "0.1em 0"; + this.handle.style.borderWidth = this.border_width + "px"; + this.handle.style.borderColor = "#666"; + this.handle.style.left = "2.5%"; + this.handle.style.transform = "translate(0, -50%)"; this.update_handle_pos(0); this.container.appendChild(this.handle); interact(this.handle).draggable({ - onstart: function() { - self.dispatch('changestart', {'target': this}); - self.deactivate_type_mode(); - self._drag_y = self.get_handle_pos(); + onend: function(event) { + self.dispatch("changeend", {"target": this}); }, onmove: function(event) { - var target = event.target; self._drag_y += event.dy; self.scale.range([0, self.guideline.clientHeight]); self.set_value(self.scale.invert(self._drag_y)); }, - onend: function(event) { - self.dispatch('changeend', {'target': this}); - } + onstart: function() { + self.dispatch("changestart", {"target": this}); + self.deactivate_type_mode(null); + self._drag_y = self.get_handle_pos(); + }, }); - interact(this.handle).on('tap', function(event) { + interact(this.handle).on("tap", function(event) { self.activate_type_mode(); event.stopPropagation(); - }).on('keydown', function(event) { + }).on("keydown", function(event) { self.handle_keypress(event); }); @@ -127,9 +140,8 @@ export default class SliderControl { }; set_value(value) { - var old_value = this.value; this.display_value(value); - this.dispatch('change', {'target': this, 'value': this.value}); + this.dispatch("change", {"target": this, "value": this.value}); }; activate_type_mode() { @@ -137,29 +149,29 @@ export default class SliderControl { return; } - var self = this; + const self = this; - this.dispatch('changestart', {'target': this}); + this.dispatch("changestart", {"target": this}); this.type_mode = true; this.handle.innerHTML = - ''; - var elem = this.handle.querySelector('#value_in_field'); + ""; + const elem = this.handle.querySelector("#value_in_field"); elem.value = this.format_value(this.value); elem.focus(); elem.select(); - elem.style.width = '100%'; - elem.style.textAlign = 'center'; - elem.style.backgroundColor = 'transparent'; - $(elem).on('input', function(event) { + elem.style.width = "100%"; + elem.style.textAlign = "center"; + elem.style.backgroundColor = "transparent"; + $(elem).on("input", function(event) { if (utils.is_num(elem.value)) { - self.handle.style.backgroundColor = ''; + self.handle.style.backgroundColor = ""; } else { - self.handle.style.backgroundColor = 'salmon'; + self.handle.style.backgroundColor = "salmon"; } - }).on('blur', function(event) { - self.deactivate_type_mode(); + }).on("blur", function(event) { + self.deactivate_type_mode(null); }); }; @@ -168,12 +180,12 @@ export default class SliderControl { return; } - this.dispatch('changeend', {'target': this}); + this.dispatch("changeend", {"target": this}); this.type_mode = false; - $(this.handle).off('keydown'); - this.handle.style.backgroundColor = ''; + $(this.handle).off("keydown"); + this.handle.style.backgroundColor = ""; this.handle.innerHTML = this.format_value(this.value); }; @@ -182,18 +194,18 @@ export default class SliderControl { return; } - var enter_keycode = 13; - var esc_keycode = 27; - var key = event.which; + const enter_keycode = 13; + const esc_keycode = 27; + const key = event.which; - if (key == enter_keycode) { - var input = this.handle.querySelector('#value_in_field').value; + if (key === enter_keycode) { + const input = this.handle.querySelector("#value_in_field").value; if (utils.is_num(input)) { - this.deactivate_type_mode(); + this.deactivate_type_mode(null); this.set_value(parseFloat(input)); } - } else if (key == esc_keycode) { - this.deactivate_type_mode(); + } else if (key === esc_keycode) { + this.deactivate_type_mode(null); } }; diff --git a/nengo_gui/static/components/spa_similarity.ts b/nengo_gui/static/components/spa_similarity.ts index 94463cb4..4433aead 100644 --- a/nengo_gui/static/components/spa_similarity.ts +++ b/nengo_gui/static/components/spa_similarity.ts @@ -9,14 +9,18 @@ */ import * as d3 from "d3"; +import * as $ from "jquery"; -import "./spa_similarity.css"; -import { Component } from "./component"; import { GrowableDataStore } from "../datastore"; import * as utils from "../utils"; +import { Component } from "./component"; +import "./spa_similarity.css"; import Value from "./value"; export default class SpaSimilarity extends Value { + legend_svg; + synapse; + show_pairs; constructor(parent, viewport, sim, args) { super(parent, viewport, sim, args); @@ -26,7 +30,7 @@ export default class SpaSimilarity extends Value { new GrowableDataStore(this.n_lines, this.sim, this.synapse); this.show_pairs = false; - var self = this; + const self = this; this.colors = utils.make_colors(6); this.color_func = function(d, i) { @@ -39,8 +43,8 @@ export default class SpaSimilarity extends Value { // Create the legend from label args this.legend_labels = args.pointer_labels; - this.legend = document.createElement('div'); - this.legend.classList.add('legend', 'unselectable'); + this.legend = document.createElement("div"); + this.legend.classList.add("legend", "unselectable"); this.div.appendChild(this.legend); this.legend_svg = utils.draw_legend( this.legend, args.pointer_labels, this.color_func, this.uid); @@ -61,7 +65,7 @@ export default class SpaSimilarity extends Value { // Redraw all the legends if they exist this.legend_labels = []; - if (new_labels[0] != "") { + if (new_labels[0] !== "") { this.update_legend(new_labels); } @@ -69,7 +73,7 @@ export default class SpaSimilarity extends Value { }; data_msg(push_data) { - var data_dims = push_data.length - 1; + const data_dims = push_data.length - 1; // TODO: Move this check inside datastore? if (data_dims > this.data_store.dims) { @@ -78,22 +82,22 @@ export default class SpaSimilarity extends Value { } this.data_store.push(push_data); - this.schedule_update(); + this.schedule_update(null); }; update_legend(new_labels) { - var self = this; + const self = this; 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.legend_labels.length); // Data join - var recs = this.legend_svg.selectAll("rect") + const recs = this.legend_svg.selectAll("rect") .data(this.legend_labels); - var legend_labels = this.legend_svg.selectAll(".legend-label") + const legend_labels = this.legend_svg.selectAll(".legend-label") .data(this.legend_labels); - var val_texts = this.legend_svg.selectAll(".val").data(this.legend_labels); + const val_texts = this.legend_svg.selectAll(".val").data(this.legend_labels); // Enter to append remaining lines recs.enter() .append("rect") @@ -116,12 +120,12 @@ export default class SpaSimilarity extends Value { }); // Expand the width of the svg of the longest string - var label_list = $("#legend" + this.uid + " .legend-label").toArray(); - var longest_label = Math.max.apply(Math, label_list.map(function(o) { + const label_list = $("#legend" + this.uid + " .legend-label").toArray(); + const longest_label = Math.max.apply(Math, label_list.map(function(o) { return o.getBBox().width; })); // 50 is for the similarity measure that is around three characters wide - var svg_right_edge = longest_label + 50; + const svg_right_edge = longest_label + 50; this.legend_svg.attr("width", svg_right_edge); val_texts.attr("x", svg_right_edge) @@ -147,8 +151,8 @@ export default class SpaSimilarity extends Value { * This calls the method associated to handling the type of message. */ on_message(event) { - var data = JSON.parse(event.data); - var func_name = data.shift(); + const data = JSON.parse(event.data); + const func_name = data.shift(); this[func_name](data); }; @@ -160,40 +164,40 @@ export default class SpaSimilarity extends Value { this.data_store.update(); // Determine visible range from the SimControl - var t1 = this.sim.time_slider.first_shown_time; - var t2 = t1 + this.sim.time_slider.shown_time; + const t1 = this.sim.time_slider.first_shown_time; + const t2 = t1 + this.sim.time_slider.shown_time; this.axes2d.set_time_range(t1, t2); // Update the lines - var self = this; - var shown_data = this.data_store.get_shown_data(); + const self = this; + const shown_data = this.data_store.get_shown_data(); // Data join this.path = this.axes2d.svg.selectAll(".line").data(shown_data); // Update - this.path.attr('d', self.line); + this.path.attr("d", self.line); // Enter to append remaining lines this.path.enter() - .append('path') - .attr('class', 'line') - .style('stroke', this.color_func) - .attr('d', self.line); + .append("path") + .attr("class", "line") + .style("stroke", this.color_func) + .attr("d", self.line); // Remove any lines that aren't needed anymore this.path.exit().remove(); // Update the legend text if (this.legend_svg && shown_data[0].length !== 0) { // Get the most recent similarity - var latest_simi = []; - for (var i = 0; i < shown_data.length; i++) { + const latest_simi = []; + for (let i = 0; i < shown_data.length; i++) { latest_simi.push(shown_data[i][shown_data[i].length - 1]); } // Update the text in the legend - var texts = this.legend_svg.selectAll(".val").data(this.legend_labels); + const texts = this.legend_svg.selectAll(".val").data(this.legend_labels); texts.html(function(d, i) { - var sign = ''; + let sign = ""; if (latest_simi[i] < 0) { sign = "−"; } @@ -203,18 +207,19 @@ export default class SpaSimilarity extends Value { }; generate_menu() { - var self = this; - var items = []; - items.push(['Set range...', function() { - self.set_range(); - }]); + const self = this; + const items = [ + ["Set range...", function() { + self.set_range(); + }], + ]; if (this.show_pairs) { - items.push(['Hide pairs', function() { + items.push(["Hide pairs", function() { self.set_show_pairs(false); }]); } else { - items.push(['Show pairs', function() { + items.push(["Show pairs", function() { self.set_show_pairs(true); }]); } @@ -232,7 +237,7 @@ export default class SpaSimilarity extends Value { }; layout_info() { - var info = Component.prototype.layout_info.call(this); + const info = Component.prototype.layout_info.call(this); info.show_pairs = this.show_pairs; info.min_value = this.axes2d.scale_y.domain()[0]; info.max_value = this.axes2d.scale_y.domain()[1]; diff --git a/nengo_gui/static/components/time_axes.ts b/nengo_gui/static/components/time_axes.ts index 7028708a..46c7e736 100644 --- a/nengo_gui/static/components/time_axes.ts +++ b/nengo_gui/static/components/time_axes.ts @@ -9,27 +9,31 @@ * @param {dict} args - A set of constructor arguments (see Axes2D) */ +import * as $ from "jquery"; + import Axes2D from "./2d_axes"; export default class TimeAxes extends Axes2D { + axis_time_end; + axis_time_start; + display_time; constructor(parent, args) { super(parent, args); - var self = this; this.display_time = args.display_time; this.axis_x.ticks(0); this.axis_time_end = this.svg.append("text") .text("Time: NULL") - .attr('class', 'graph_text unselectable')[0][0]; + .attr("class", "graph_text unselectable")[0][0]; this.axis_time_start = this.svg.append("text") .text("Time: NULL") - .attr('class', 'graph_text unselectable')[0][0]; + .attr("class", "graph_text unselectable")[0][0]; - if (this.display_time == false) { - this.axis_time_start.style.display = 'none'; - this.axis_time_end.style.display = 'none'; + if (this.display_time === false) { + this.axis_time_start.style.display = "none"; + this.axis_time_end.style.display = "none"; } }; @@ -43,20 +47,20 @@ export default class TimeAxes extends Axes2D { on_resize(width, height) { Axes2D.prototype.on_resize.call(this, width, height); - var scale = parseFloat($('#main').css('font-size')); - var suppression_width = 6 * scale; - var text_offset = 1.2 * scale; + const scale = parseFloat($("#main").css("font-size")); + const suppression_width = 6 * scale; + const text_offset = 1.2 * scale; - if (width < suppression_width || this.display_time == false) { - this.axis_time_start.style.display = 'none'; + if (width < suppression_width || this.display_time === false) { + this.axis_time_start.style.display = "none"; } else { - this.axis_time_start.style.display = 'block'; + this.axis_time_start.style.display = "block"; } - this.axis_time_start.setAttribute('y', this.ax_bottom + text_offset); - this.axis_time_start.setAttribute('x', this.ax_left - text_offset); - this.axis_time_end.setAttribute('y', this.ax_bottom + text_offset); - this.axis_time_end.setAttribute('x', this.ax_right - text_offset); + this.axis_time_start.setAttribute("y", this.ax_bottom + text_offset); + this.axis_time_start.setAttribute("x", this.ax_left - text_offset); + this.axis_time_end.setAttribute("y", this.ax_bottom + text_offset); + this.axis_time_end.setAttribute("x", this.ax_right - text_offset); }; } diff --git a/nengo_gui/static/components/value.ts b/nengo_gui/static/components/value.ts index 1d015d3c..65e40f10 100644 --- a/nengo_gui/static/components/value.ts +++ b/nengo_gui/static/components/value.ts @@ -15,18 +15,35 @@ */ import * as d3 from "d3"; +import * as $ from "jquery"; -import "./value.css"; -import { Component } from "./component"; import { DataStore } from "../datastore"; -import TimeAxes from "./time_axes"; import * as utils from "../utils"; +import { Component } from "./component"; +import TimeAxes from "./time_axes"; +import "./value.css"; export default class Value extends Component { + axes2d; + color_func; + colors; + crosshair_g; + crosshair_mouse; + crosshair_updates; + data_store; + display_time; + legend; + legend_labels; + line; + n_lines; + path; + show_legend; + sim; + synapse; constructor(parent, viewport, sim, args) { super(parent, viewport, args); - var self = this; + const self = this; this.n_lines = args.n_lines || 1; this.sim = sim; this.display_time = args.display_time; @@ -38,13 +55,13 @@ export default class Value extends Component { this.axes2d = new TimeAxes(this.div, args); // Call schedule_update whenever the time is adjusted in the SimControl - this.sim.div.addEventListener('adjust_time', function(e) { - self.schedule_update(); + this.sim.div.addEventListener("adjust_time", function(e) { + self.schedule_update(e); }, false); // Call reset whenever the simulation is reset - this.sim.div.addEventListener('sim_reset', function(e) { - self.reset(); + this.sim.div.addEventListener("sim_reset", function(e) { + self.reset(e); }, false); // Create the lines on the plots @@ -56,14 +73,14 @@ export default class Value extends Component { return self.axes2d.scale_y(d); }); this.path = this.axes2d.svg.append("g") - .selectAll('path') + .selectAll("path") .data(this.data_store.data); this.colors = utils.make_colors(this.n_lines); this.path.enter() - .append('path') - .attr('class', 'line') - .style('stroke', function(d, i) { + .append("path") + .attr("class", "line") + .style("stroke", function(d, i) { return self.colors[i]; }); @@ -75,52 +92,52 @@ export default class Value extends Component { // Keep track of mouse position TODO: fix this to be not required this.crosshair_mouse = [0, 0]; - this.crosshair_g = this.axes2d.svg.append('g') - .attr('class', 'crosshair'); + this.crosshair_g = this.axes2d.svg.append("g") + .attr("class", "crosshair"); // TODO: put the crosshair properties in CSS - this.crosshair_g.append('line') - .attr('id', 'crosshairX') - .attr('stroke', 'black') - .attr('stroke-width', '0.5px'); + this.crosshair_g.append("line") + .attr("id", "crosshairX") + .attr("stroke", "black") + .attr("stroke-width", "0.5px"); - this.crosshair_g.append('line') - .attr('id', 'crosshairY') - .attr('stroke', 'black') - .attr('stroke-width', '0.5px'); + this.crosshair_g.append("line") + .attr("id", "crosshairY") + .attr("stroke", "black") + .attr("stroke-width", "0.5px"); // TODO: have the fonts and colour set appropriately - this.crosshair_g.append('text') - .attr('id', 'crosshairXtext') - .style('text-anchor', 'middle') - .attr('class', 'graph_text'); + this.crosshair_g.append("text") + .attr("id", "crosshairXtext") + .style("text-anchor", "middle") + .attr("class", "graph_text"); - this.crosshair_g.append('text') - .attr('id', 'crosshairYtext') - .style('text-anchor', 'end') - .attr('class', 'graph_text'); + this.crosshair_g.append("text") + .attr("id", "crosshairYtext") + .style("text-anchor", "end") + .attr("class", "graph_text"); this.axes2d.svg - .on('mouseover', function() { - var mouse = d3.mouse(this); + .on("mouseover", function() { + const mouse = d3.mouse(this); self.crosshair_updates = true; - self.crosshair_g.style('display', null); - self.cross_hair_mouse = [mouse[0], mouse[1]]; - }).on('mouseout', function() { - var mouse = d3.mouse(this); + self.crosshair_g.style("display", null); + self.crosshair_mouse = [mouse[0], mouse[1]]; + }).on("mouseout", function() { + const mouse = d3.mouse(this); self.crosshair_updates = false; - self.crosshair_g.style('display', 'none'); - self.cross_hair_mouse = [mouse[0], mouse[1]]; - }).on('mousemove', function() { - var mouse = d3.mouse(this); + self.crosshair_g.style("display", "none"); + self.crosshair_mouse = [mouse[0], mouse[1]]; + }).on("mousemove", function() { + const mouse = d3.mouse(this); self.crosshair_updates = true; - self.cross_hair_mouse = [mouse[0], mouse[1]]; + self.crosshair_mouse = [mouse[0], mouse[1]]; self.update_crosshair(mouse); - }).on('mousewheel', function() { + }).on("mousewheel", function() { // Hide the crosshair when zooming, // until a better option comes along self.crosshair_updates = false; - self.crosshair_g.style('display', 'none'); + self.crosshair_g.style("display", "none"); }); this.update(); @@ -132,14 +149,14 @@ export default class Value extends Component { this.color_func = function(d, i) { return self.colors[i % 6]; }; - this.legend = document.createElement('div'); - this.legend.classList.add('legend'); + 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 the array with temporary labels - for (var i = this.legend_labels.length; i < this.n_lines; i++) { + for (let i = this.legend_labels.length; i < this.n_lines; i++) { this.legend_labels[i] = "label_" + i; } } @@ -148,49 +165,49 @@ export default class Value extends Component { if (this.show_legend === true) { utils.draw_legend(this.legend, this.legend_labels.slice(0, self.n_lines), - this.color_func); + this.color_func, + this.uid); } }; update_crosshair(mouse) { - var self = this; - var x = mouse[0]; - var y = mouse[1]; + const self = this; + const {x, y} = mouse; // TODO: I don't like having ifs here. // Make a smaller rectangle for mouseovers if (x > this.axes2d.ax_left && x < this.axes2d.ax_right && y > this.axes2d.ax_top && y < this.axes2d.ax_bottom) { - this.crosshair_g.style('display', null); + this.crosshair_g.style("display", null); - this.crosshair_g.select('#crosshairX') - .attr('x1', x) - .attr('y1', this.axes2d.ax_top) - .attr('x2', x) - .attr('y2', this.axes2d.ax_bottom); + this.crosshair_g.select("#crosshairX") + .attr("x1", x) + .attr("y1", this.axes2d.ax_top) + .attr("x2", x) + .attr("y2", this.axes2d.ax_bottom); - this.crosshair_g.select('#crosshairY') - .attr('x1', this.axes2d.ax_left) - .attr('y1', y) - .attr('x2', this.axes2d.ax_right) - .attr('y2', y); + this.crosshair_g.select("#crosshairY") + .attr("x1", this.axes2d.ax_left) + .attr("y1", y) + .attr("x2", this.axes2d.ax_right) + .attr("y2", y); // TODO: don't use magic numbers - this.crosshair_g.select('#crosshairXtext') - .attr('x', x - 2) - .attr('y', this.axes2d.ax_bottom + 17) + this.crosshair_g.select("#crosshairXtext") + .attr("x", x - 2) + .attr("y", this.axes2d.ax_bottom + 17) .text(function() { return Math.round(self.axes2d.scale_x.invert(x) * 100) / 100; }); - this.crosshair_g.select('#crosshairYtext') - .attr('x', this.axes2d.ax_left - 3) - .attr('y', y + 3) + this.crosshair_g.select("#crosshairYtext") + .attr("x", this.axes2d.ax_left - 3) + .attr("y", y + 3) .text(function() { return Math.round(self.axes2d.scale_y.invert(y) * 100) / 100; }); } else { - this.crosshair_g.style('display', 'none'); + this.crosshair_g.style("display", "none"); } }; @@ -198,9 +215,9 @@ export default class Value extends Component { * Receive new line data from the server. */ on_message(event) { - var data = new Float32Array(event.data); + let data = new Float32Array(event.data); data = Array.prototype.slice.call(data); - var size = this.n_lines + 1; + const size = this.n_lines + 1; // Since multiple data packets can be sent with a single event, // make sure to process all the packets. while (data.length >= size) { @@ -208,9 +225,9 @@ export default class Value extends Component { data = data.slice(size); } if (data.length > 0) { - console.log('extra data: ' + data.length); + console.warn("extra data: " + data.length); } - this.schedule_update(); + this.schedule_update(event); }; /** @@ -221,21 +238,21 @@ export default class Value extends Component { this.data_store.update(); // Determine visible range from the SimControl - var t1 = this.sim.time_slider.first_shown_time; - var t2 = t1 + this.sim.time_slider.shown_time; + const t1 = this.sim.time_slider.first_shown_time; + const t2 = t1 + this.sim.time_slider.shown_time; this.axes2d.set_time_range(t1, t2); // Update the lines - var self = this; - var shown_data = this.data_store.get_shown_data(); + const self = this; + const shown_data = this.data_store.get_shown_data(); this.path.data(shown_data) - .attr('d', self.line); + .attr("d", self.line); // Update the crosshair text if the mouse is on top if (this.crosshair_updates) { - this.update_crosshair(this.cross_hair_mouse); + this.update_crosshair(this.crosshair_mouse); } }; @@ -243,12 +260,12 @@ export default class Value extends Component { * Adjust the graph layout due to changed size. */ on_resize(width, height) { - if (width < this.minWidth) { - width = this.minWidth; + if (width < this.min_width) { + width = this.min_width; + } + if (height < this.min_height) { + height = this.min_height; } - if (height < this.minHeight) { - height = this.minHeight; - }; this.axes2d.on_resize(width, height); @@ -263,27 +280,28 @@ export default class Value extends Component { }; generate_menu() { - var self = this; - var items = []; - items.push(['Set range...', function() { - self.set_range(); - }]); - items.push(['Set synapse...', function() { - self.set_synapse_dialog(); - }]); + const self = this; + const items = [ + ["Set range...", function() { + self.set_range(); + }], + ["Set synapse...", function() { + self.set_synapse_dialog(); + }], + ]; if (this.show_legend) { - items.push(['Hide legend', function() { + items.push(["Hide legend", function() { self.set_show_legend(false); }]); } else { - items.push(['Show legend', 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() { + items.push(["Set legend labels", function() { self.set_legend_labels(); }]); @@ -299,7 +317,8 @@ export default class Value extends Component { if (this.show_legend === true) { utils.draw_legend(this.legend, this.legend_labels.slice(0, this.n_lines), - this.color_func); + this.color_func, + this.uid); } else { // Delete the legend's children while (this.legend.lastChild) { @@ -310,23 +329,23 @@ export default class Value extends Component { }; set_legend_labels() { - var self = this; + const self = this; - self.sim.modal.title('Enter comma seperated legend label values'); - self.sim.modal.single_input_body('Legend label', 'New value'); - self.sim.modal.footer('ok_cancel', function(e) { - var label_csv = $('#singleInput').val(); - var modal = $('#myModalForm').data('bs.validator'); + self.sim.modal.title("Enter comma seperated legend label values"); + self.sim.modal.single_input_body("Legend label", "New value"); + self.sim.modal.footer("ok_cancel", function(e) { + const label_csv = $("#singleInput").val(); + $("#myModalForm").data("bs.validator"); // No validation to do. // Empty entries assumed to be indication to skip modification. // Long strings okay. // Excissive entries get ignored. // TODO: Allow escaping of commas - if ((label_csv !== null) && (label_csv !== '')) { - var labels = label_csv.split(','); + if ((label_csv !== null) && (label_csv !== "")) { + const labels = label_csv.split(","); - for (var i = 0; i < self.n_lines; i++) { + for (let i = 0; i < self.n_lines; i++) { if (labels[i] !== "" && labels[i] !== undefined) { self.legend_labels[i] = labels[i]; } @@ -337,17 +356,20 @@ export default class Value extends Component { self.legend.removeChild(self.legend.lastChild); } - utils.draw_legend(self.legend, self.legend_labels, self.color_func); + utils.draw_legend(self.legend, + self.legend_labels, + self.color_func, + self.uid); self.save_layout(); } - $('#OK').attr('data-dismiss', 'modal'); + $("#OK").attr("data-dismiss", "modal"); }); self.sim.modal.show(); }; layout_info() { - var info = Component.prototype.layout_info.call(this); + const info = Component.prototype.layout_info.call(this); info.show_legend = this.show_legend; info.legend_labels = this.legend_labels; info.min_value = this.axes2d.scale_y.domain()[0]; @@ -361,50 +383,49 @@ export default class Value extends Component { }; set_range() { - var range = this.axes2d.scale_y.domain(); - var self = this; - self.sim.modal.title('Set graph range...'); - self.sim.modal.single_input_body(range, 'New range'); - self.sim.modal.footer('ok_cancel', function(e) { - var new_range = $('#singleInput').val(); - var modal = $('#myModalForm').data('bs.validator'); + const range = this.axes2d.scale_y.domain(); + const self = this; + self.sim.modal.title("Set graph range..."); + self.sim.modal.single_input_body(range, "New range"); + self.sim.modal.footer("ok_cancel", function(e) { + let new_range = $("#singleInput").val(); + const modal = $("#myModalForm").data("bs.validator"); modal.validate(); if (modal.hasErrors() || modal.isIncomplete()) { return; } if (new_range !== null) { - new_range = new_range.split(','); - var min = parseFloat(new_range[0]); - var max = parseFloat(new_range[1]); + new_range = new_range.split(","); + const min = parseFloat(new_range[0]); + const max = parseFloat(new_range[1]); self.update_range(min, max); self.save_layout(); self.axes2d.axis_y.tickValues([min, max]); self.axes2d.fit_ticks(self); } - $('#OK').attr('data-dismiss', 'modal'); + $("#OK").attr("data-dismiss", "modal"); }); - var $form = $('#myModalForm').validator({ + $("#myModalForm").validator({ custom: { my_validator: function($item) { - var nums = $item.val().split(','); - var valid = false; + const nums = $item.val().split(","); + let valid = false; if ($.isNumeric(nums[0]) && $.isNumeric(nums[1])) { if (Number(nums[0]) < Number(nums[1])) { valid = true; // Two numbers, 1st less than 2nd } } - return (nums.length == 2 && valid); - } + return (nums.length === 2 && valid); + }, }, }); - $('#singleInput').attr('data-error', 'Input should be in the ' + - 'form ",".'); + $("#singleInput").attr("data-error", "Input should be in the " + + "form ','."); self.sim.modal.show(); - $('#OK').on('click', function() { - var w = $(self.div).width(); - var h = $(self.div).height(); - self.on_resize(w, h); + $("#OK").on("click", function() { + const div = $(self.div); + self.on_resize(div.width(), div.height()); }); }; @@ -415,17 +436,17 @@ export default class Value extends Component { reset(event) { this.data_store.reset(); - this.schedule_update(); + this.schedule_update(event); }; set_synapse_dialog() { - var self = this; - self.sim.modal.title('Set synaptic filter...'); + const self = this; + self.sim.modal.title("Set synaptic filter..."); self.sim.modal.single_input_body(this.synapse, - 'Filter time constant (in seconds)'); - self.sim.modal.footer('ok_cancel', function(e) { - var new_synapse = $('#singleInput').val(); - var modal = $('#myModalForm').data('bs.validator'); + "Filter time constant (in seconds)"); + self.sim.modal.footer("ok_cancel", function(e) { + let new_synapse = $("#singleInput").val(); + const modal = $("#myModalForm").data("bs.validator"); modal.validate(); if (modal.hasErrors() || modal.isIncomplete()) { return; @@ -436,14 +457,14 @@ export default class Value extends Component { return; } self.synapse = new_synapse; - self.ws.send('synapse:' + self.synapse); + self.ws.send("synapse:" + self.synapse); } - $('#OK').attr('data-dismiss', 'modal'); + $("#OK").attr("data-dismiss", "modal"); }); - var $form = $('#myModalForm').validator({ + $("#myModalForm").validator({ custom: { my_validator: function($item) { - var num = $item.val(); + let num = $item.val(); if ($.isNumeric(num)) { num = Number(num); if (num >= 0) { @@ -451,10 +472,10 @@ export default class Value extends Component { } } return false; - } + }, }, }); - $('#singleInput').attr('data-error', 'should be a non-negative number'); + $("#singleInput").attr("data-error", "should be a non-negative number"); self.sim.modal.show(); }; diff --git a/nengo_gui/static/components/xy_axes.ts b/nengo_gui/static/components/xy_axes.ts index d97396d9..374b306b 100644 --- a/nengo_gui/static/components/xy_axes.ts +++ b/nengo_gui/static/components/xy_axes.ts @@ -13,6 +13,8 @@ import Axes2D from "./2d_axes"; export default class XYAxes extends Axes2D { + max_val; + min_val; constructor(parent, args) { super(parent, args); @@ -31,9 +33,9 @@ export default class XYAxes extends Axes2D { on_resize(width, height) { Axes2D.prototype.on_resize.call(this, width, height); - var x_offset = this.ax_bottom - this.min_val / + const x_offset = this.ax_bottom - this.min_val / (this.max_val - this.min_val) * (this.ax_top - this.ax_bottom); - var y_offset = this.ax_left - this.min_val / + const y_offset = this.ax_left - this.min_val / (this.max_val - this.min_val) * (this.ax_right - this.ax_left); this.axis_x_g.attr("transform", "translate(0," + x_offset + ")"); @@ -41,5 +43,4 @@ export default class XYAxes extends Axes2D { this.axis_y_g.attr("transform", "translate(" + y_offset + ", 0)"); this.axis_y_g.call(this.axis_y); }; - } diff --git a/nengo_gui/static/components/xyvalue.ts b/nengo_gui/static/components/xyvalue.ts index d7a461b8..040c0516 100644 --- a/nengo_gui/static/components/xyvalue.ts +++ b/nengo_gui/static/components/xyvalue.ts @@ -16,18 +16,29 @@ */ import * as d3 from "d3"; +import * as $ from "jquery"; -import "./xyvalue.css"; -import { Component } from "./component"; import { DataStore } from "../datastore"; import * as utils from "../utils"; +import { Component } from "./component"; import XYAxes from "./xy_axes"; +import "./xyvalue.css"; export default class XYValue extends Component { + axes2d; + data_store; + index_x; + index_y; + invalid_dims; + n_lines; + path; + recent_circle; + sim; + warning_text; constructor(parent, viewport, sim, args) { super(parent, viewport, args); - var self = this; + const self = this; this.n_lines = args.n_lines || 1; this.sim = sim; @@ -42,36 +53,36 @@ export default class XYValue extends Component { this.index_y = args.index_y; // Call schedule_update whenever the time is adjusted in the SimControl - this.sim.div.addEventListener('adjust_time', function(e) { - self.schedule_update(); + this.sim.div.addEventListener("adjust_time", function(e) { + self.schedule_update(e); }, false); // Call reset whenever the simulation is reset - this.sim.div.addEventListener('sim_reset', function(e) { - self.reset(); + this.sim.div.addEventListener("sim_reset", function(e) { + self.reset(e); }, false); // Create the lines on the plots - var line = d3.svg.line() + d3.svg.line() .x(function(d, i) { return self.axes2d.scale_x(self.data_store.data[this.index_x][i]); }).y(function(d) { - return self.axe2d.scale_y(d); + return self.axes2d.scale_y(d); }); this.path = this.axes2d.svg.append("g") - .selectAll('path') + .selectAll("path") .data([this.data_store.data[this.index_y]]); - this.path.enter().append('path') - .attr('class', 'line') - .style('stroke', utils.make_colors(1)); + this.path.enter().append("path") + .attr("class", "line") + .style("stroke", utils.make_colors(1)); // Create a circle to track the most recent data this.recent_circle = this.axes2d.svg.append("circle") .attr("r", this.get_circle_radius()) - .attr('cx', this.axes2d.scale_x(0)) - .attr('cy', this.axes2d.scale_y(0)) + .attr("cx", this.axes2d.scale_x(0)) + .attr("cy", this.axes2d.scale_y(0)) .style("fill", utils.make_colors(1)[0]) - .style('fill-opacity', 0); + .style("fill-opacity", 0); this.invalid_dims = false; @@ -83,45 +94,45 @@ export default class XYValue extends Component { * Receive new line data from the server. */ on_message(event) { - var data = new Float32Array(event.data); + const data = new Float32Array(event.data); this.data_store.push(data); - this.schedule_update(); + this.schedule_update(event); }; /** * Redraw the lines and axis due to changed data. */ update() { - var self = this; + const self = this; // Let the data store clear out old values this.data_store.update(); // Update the lines if there is data with valid dimensions - var good_idx = self.index_x < self.n_lines && self.index_y < self.n_lines; + const good_idx = self.index_x < self.n_lines && self.index_y < self.n_lines; if (good_idx) { - var shown_data = this.data_store.get_shown_data(); + const shown_data = this.data_store.get_shown_data(); // Update the lines - var line = d3.svg.line() + const line = d3.svg.line() .x(function(d, i) { return self.axes2d.scale_x(shown_data[self.index_x][i]); }).y(function(d) { return self.axes2d.scale_y(d); }); this.path.data([shown_data[this.index_y]]) - .attr('d', line); + .attr("d", line); - var last_index = shown_data[self.index_x].length - 1; + const last_index = shown_data[self.index_x].length - 1; if (last_index >= 0) { // Update the circle if there is valid data this.recent_circle - .attr('cx', self.axes2d.scale_x( + .attr("cx", self.axes2d.scale_x( shown_data[self.index_x][last_index])) - .attr('cy', self.axes2d.scale_y( + .attr("cy", self.axes2d.scale_y( shown_data[self.index_y][last_index])) - .style('fill-opacity', 0.5); + .style("fill-opacity", 0.5); } // If switching from invalids dimensions to valid dimensions, remove @@ -131,11 +142,11 @@ export default class XYValue extends Component { this.invalid_dims = false; } - } else if (this.invalid_dims == false) { + } else if (this.invalid_dims === false) { this.invalid_dims = true; // Create the HTML text element - this.warning_text = document.createElement('div'); + this.warning_text = document.createElement("div"); this.div.appendChild(this.warning_text); this.warning_text.className = "warning-text"; this.warning_text.innerHTML = "Change
Dimension
Indices"; @@ -163,21 +174,22 @@ export default class XYValue extends Component { }; generate_menu() { - var self = this; - var items = []; - items.push(['Set range...', function() { - self.set_range(); - }]); - items.push(['Set X, Y indices...', function() { - self.set_indices(); - }]); + const self = this; + const items = [ + ["Set range...", function() { + self.set_range(); + }], + ["Set X, Y indices...", function() { + self.set_indices(); + }], + ]; // Add the parent's menu items to this return $.merge(items, Component.prototype.generate_menu.call(this)); }; layout_info() { - var info = Component.prototype.layout_info.call(this); + const info = Component.prototype.layout_info.call(this); info.min_value = this.axes2d.scale_y.domain()[0]; info.max_value = this.axes2d.scale_y.domain()[1]; info.index_x = this.index_x; @@ -192,49 +204,49 @@ export default class XYValue extends Component { }; set_range() { - var range = this.axes2d.scale_y.domain(); - var self = this; - self.sim.modal.title('Set graph range...'); - self.sim.modal.single_input_body(range, 'New range'); - self.sim.modal.footer('ok_cancel', function(e) { - var new_range = $('#singleInput').val(); - var modal = $('#myModalForm').data('bs.validator'); + const range = this.axes2d.scale_y.domain(); + const self = this; + self.sim.modal.title("Set graph range..."); + self.sim.modal.single_input_body(range, "New range"); + self.sim.modal.footer("ok_cancel", function(e) { + let new_range = $("#singleInput").val(); + const modal = $("#myModalForm").data("bs.validator"); modal.validate(); if (modal.hasErrors() || modal.isIncomplete()) { return; } if (new_range !== null) { - new_range = new_range.split(','); - var min = parseFloat(new_range[0]); - var max = parseFloat(new_range[1]); + new_range = new_range.split(","); + const min = parseFloat(new_range[0]); + const max = parseFloat(new_range[1]); self.update_range(min, max); self.update(); self.save_layout(); } - $('#OK').attr('data-dismiss', 'modal'); + $("#OK").attr("data-dismiss", "modal"); }); - var $form = $('#myModalForm').validator({ + $("#myModalForm").validator({ custom: { my_validator: function($item) { - var nums = $item.val().split(','); - var valid = false; + const nums = $item.val().split(","); + let valid = false; if ($.isNumeric(nums[0]) && $.isNumeric(nums[1])) { // Two numbers, 1st less than 2nd. // The axes must intersect at 0. - var ordered = Number(nums[0]) < Number(nums[1]); - var zeroed = Number(nums[0]) * Number(nums[1]) <= 0; + const ordered = Number(nums[0]) < Number(nums[1]); + const zeroed = Number(nums[0]) * Number(nums[1]) <= 0; if (ordered && zeroed) { valid = true; } } - return (nums.length == 2 && valid); - } - } + return (nums.length === 2 && valid); + }, + }, }); - $('#singleInput').attr('data-error', 'Input should be in the form ' + - '"," and the axes must cross at zero.'); + $("#singleInput").attr("data-error", "Input should be in the form " + + "',' and the axes must cross at zero."); self.sim.modal.show(); }; @@ -251,45 +263,45 @@ export default class XYValue extends Component { }; set_indices() { - var self = this; - self.sim.modal.title('Set X and Y indices...'); + const self = this; + self.sim.modal.title("Set X and Y indices..."); self.sim.modal.single_input_body( - [this.index_x, this.index_y], 'New indices'); - self.sim.modal.footer('ok_cancel', function(e) { - var new_indices = $('#singleInput').val(); - var modal = $('#myModalForm').data('bs.validator'); + [this.index_x, this.index_y], "New indices"); + self.sim.modal.footer("ok_cancel", function(e) { + let new_indices = $("#singleInput").val(); + const modal = $("#myModalForm").data("bs.validator"); modal.validate(); if (modal.hasErrors() || modal.isIncomplete()) { return; } if (new_indices !== null) { - new_indices = new_indices.split(','); - self.update_indices(parseInt(new_indices[0]), - parseInt(new_indices[1])); + new_indices = new_indices.split(","); + self.update_indices(parseInt(new_indices[0], 10), + parseInt(new_indices[1], 10)); self.save_layout(); } - $('#OK').attr('data-dismiss', 'modal'); + $("#OK").attr("data-dismiss", "modal"); }); - var $form = $('#myModalForm').validator({ + $("#myModalForm").validator({ custom: { my_validator: function($item) { - var nums = $item.val().split(','); - return ((parseInt(Number(nums[0])) == nums[0]) && - (parseInt(Number(nums[1])) == nums[1]) && - (nums.length == 2) && + const nums = $item.val().split(",").map(Number); + return ((parseInt(nums[0], 10) === nums[0]) && + (parseInt(nums[1], 10) === nums[1]) && + (nums.length === 2) && (Number(nums[1]) < self.n_lines && Number(nums[1]) >= 0) && (Number(nums[0]) < self.n_lines && Number(nums[0]) >= 0)); - } - } + }, + }, }); - $('#singleInput').attr( - 'data-error', 'Input should be two positive ' + - 'integers in the form ",". ' + - 'Dimensions are zero indexed.'); + $("#singleInput").attr( + "data-error", "Input should be two positive " + + "integers in the form ','. " + + "Dimensions are zero indexed."); self.sim.modal.show(); }; @@ -302,7 +314,7 @@ export default class XYValue extends Component { reset(event) { this.data_store.reset(); - this.schedule_update(); + this.schedule_update(event); }; } diff --git a/nengo_gui/static/config.ts b/nengo_gui/static/config.ts index 369f698a..16dbf4c4 100644 --- a/nengo_gui/static/config.ts +++ b/nengo_gui/static/config.ts @@ -1,16 +1,16 @@ export default class Config { - constructor() { - var self = this; + const self = this; - var define_option = function(key, default_val) { - var type = typeof(default_val); + const define_option = function(key, default_val) { + const typ = typeof(default_val); Object.defineProperty(self, key, { + enumerable: true, get: function() { - var val = localStorage.getItem("ng." + key) || default_val; - if (type === "boolean") { + const val = localStorage.getItem("ng." + key) || default_val; + if (typ === "boolean") { return val === "true" || val === true; - } else if (type === "number") { + } else if (typ === "number") { return Number(val); } else { return val; @@ -19,7 +19,6 @@ export default class Config { set: function(val) { return localStorage.setItem("ng." + key, val); }, - enumerable: true }); }; @@ -39,11 +38,10 @@ export default class Config { }; restore_defaults() { - for (var option in this) { + for (let option in this) { if (this.hasOwnProperty(option)) { localStorage.removeItem("ng." + option); } } }; - } diff --git a/nengo_gui/static/data_to_csv.ts b/nengo_gui/static/data_to_csv.ts index 8524d8a6..ece137cc 100644 --- a/nengo_gui/static/data_to_csv.ts +++ b/nengo_gui/static/data_to_csv.ts @@ -12,50 +12,47 @@ import XYValue from "./components/xyvalue"; export default function data_to_csv(data_set) { - var values = []; - var dim_values = []; - var times = []; - var csv = []; - var csv_string = ""; + const values = []; + const csv = []; - var data_set = data_set.filter(function(data) { + data_set = data_set.filter(function(data) { return data.constructor === Value || data.constructor === XYValue; }); // Extracts all the values from the data_set variable - for (var x = 0; x < data_set.length; x++) { + for (let x = 0; x < data_set.length; x++) { values.push([]); - for (var y = 0; y < data_set[x].data_store.data.length; y++) { + for (let y = 0; y < data_set[x].data_store.data.length; y++) { values[x].push(data_set[x].data_store.data[y]); } } // Grabs all the time steps - times = data_set[0].data_store.times; + const times = data_set[0].data_store.times; // Headers for the csv file csv.push(["Graph Name"]); csv.push(["Times"]); // Adds ensemble name and appropirate number of spaces to the header - for (var x = 0; x < data_set.length; x++) { + for (let x = 0; x < data_set.length; x++) { csv[0].push(data_set[x].label.innerHTML); - for (var z = 0; z < values[x].length - 1; z++) { + for (let z = 0; z < values[x].length - 1; z++) { csv[0].push([]); } } - for (var x = 0; x < values.length; x++) { - for (var y = 0; y < values[x].length; y++) { + for (let x = 0; x < values.length; x++) { + for (let y = 0; y < values[x].length; y++) { csv[1].push("Dimension" + (y + 1)); } } // Puts the data at each time step into a row in the csv - for (var x = 0; x < times.length; x++) { - var temp_arr = [times[x]]; - for (var y = 0; y < values.length; y++) { - for (var z = 0; z < values[y].length; z++) { + for (let x = 0; x < times.length; x++) { + let temp_arr = [times[x]]; + for (let y = 0; y < values.length; y++) { + for (let z = 0; z < values[y].length; z++) { temp_arr.push(values[y][z][x]); } } @@ -66,6 +63,5 @@ export default function data_to_csv(data_set) { csv.forEach(function(elem, index) { csv[index] = elem.join(","); }); - csv_string = csv.join("\n"); - return csv_string; + return csv.join("\n"); }; diff --git a/nengo_gui/static/datastore.ts b/nengo_gui/static/datastore.ts index 09ee6565..e92ca2d8 100644 --- a/nengo_gui/static/datastore.ts +++ b/nengo_gui/static/datastore.ts @@ -9,13 +9,18 @@ */ export class DataStore { + data; + first_shown_index; + sim; + synapse; + times; constructor(dims, sim, synapse) { this.synapse = synapse; // TODO: get from SimControl this.sim = sim; this.times = []; this.data = []; - for (var i = 0; i < dims; i++) { + for (let i = 0; i < dims; i++) { this.data.push([]); } }; @@ -26,30 +31,31 @@ export class DataStore { * @param {array} row - dims+1 data points, with time as the first one */ push(row) { + const dims = this.data.length; + // If you get data out of order, wipe out the later data if (row[0] < this.times[this.times.length - 1]) { - var index = 0; + let index = 0; while (this.times[index] < row[0]) { index += 1; } - var dims = this.data.length; this.times.splice(index, this.times.length); - for (var i = 0; i < this.data.length; i++) { + for (let i = 0; i < dims; i++) { this.data[i].splice(index, this.data[i].length); } } // Compute lowpass filter (value = value*decay + new_value*(1-decay) - var decay = 0.0; - if ((this.times.length != 0) && (this.synapse > 0)) { - var dt = row[0] - this.times[this.times.length - 1]; + let decay = 0.0; + if ((this.times.length !== 0) && (this.synapse > 0)) { + const dt = row[0] - this.times[this.times.length - 1]; decay = Math.exp(-dt / this.synapse); } // Put filtered values into data array - for (var i = 0; i < this.data.length; i++) { - if (decay == 0.0) { + for (let i = 0; i < dims; i++) { + if (decay === 0.0) { this.data[i].push(row[i + 1]); } else { this.data[i].push(row[i + 1] * (1 - decay) + @@ -67,10 +73,9 @@ export class DataStore { * nothing to display on a reset event. */ reset() { - var index = 0; - this.times.splice(index, this.times.length); - for (var i = 0; i < this.data.length; i++) { - this.data[i].splice(index, this.data[i].length); + this.times.splice(0, this.times.length); + for (let i = 0; i < this.data.length; i++) { + this.data[i].splice(0, this.data[i].length); } }; @@ -84,9 +89,9 @@ export class DataStore { update() { // Figure out how many extra values we have (values whose time stamp is // outside the range to keep) - var extra = 0; + let extra = 0; // How much has the most recent time exceeded how much is allowed to be kept - var limit = this.sim.time_slider.last_time - + const limit = this.sim.time_slider.last_time - this.sim.time_slider.kept_time; while (this.times[extra] < limit) { extra += 1; @@ -95,7 +100,7 @@ export class DataStore { // Remove the extra data if (extra > 0) { this.times = this.times.slice(extra); - for (var i = 0; i < this.data.length; i++) { + for (let i = 0; i < this.data.length; i++) { this.data[i] = this.data[i].slice(extra); } } @@ -106,47 +111,47 @@ export class DataStore { */ get_shown_data() { // Determine time range - var t1 = this.sim.time_slider.first_shown_time; - var t2 = t1 + this.sim.time_slider.shown_time; + const t1 = this.sim.time_slider.first_shown_time; + const t2 = t1 + this.sim.time_slider.shown_time; // Find the corresponding index values - var index = 0; + let index = 0; while (this.times[index] < t1) { index += 1; } - var last_index = index; + let last_index = index; while (this.times[last_index] < t2 && last_index < this.times.length) { last_index += 1; } this.first_shown_index = index; // Return the visible slice of the data - var shown = []; - for (var i = 0; i < this.data.length; i++) { + const shown = []; + for (let i = 0; i < this.data.length; i++) { shown.push(this.data[i].slice(index, last_index)); } return shown; }; is_at_end() { - var ts = this.sim.time_slider; + const ts = this.sim.time_slider; return (ts.last_time < ts.first_shown_time + ts.shown_time + 1e-9); }; get_last_data() { // Determine time range - var t1 = this.sim.time_slider.first_shown_time; - var t2 = t1 + this.sim.time_slider.shown_time; + const t1 = this.sim.time_slider.first_shown_time; + const t2 = t1 + this.sim.time_slider.shown_time; // Find the corresponding index values - var last_index = 0; + let last_index = 0; while (this.times[last_index] < t2 && last_index < this.times.length - 1) { last_index += 1; } // Return the visible slice of the data - var shown = []; - for (var i = 0; i < this.data.length; i++) { + const shown = []; + for (let i = 0; i < this.data.length; i++) { shown.push(this.data[i][last_index]); } return shown; @@ -164,36 +169,35 @@ export class DataStore { * @param {float} synapse - the filter to apply to the data */ export class GrowableDataStore extends DataStore { + _dims; constructor (dims, sim, synapse) { super(dims, sim, synapse); this._dims = dims; - - Object.defineProperty(this, "dims", { - get: function() { - return this._dims; - }, - set: function(dim_val) { - // Throw a bunch of errors if bad things happen. - // Assuming you can only grow dims and not shrink them... - if (this._dims < dim_val) { - for (var i = 0; i < dim_val - this._dims; i++) { - this.data.push([]); - } - } else if (this._dims > dim_val) { - throw "can't decrease size of datastore"; - } - this._dims = dim_val; + } + + get dims() { + return this._dims; + } + + set dims(dim_val) { + // Throw a bunch of errors if bad things happen. + // Assuming you can only grow dims and not shrink them... + if (this._dims < dim_val) { + for (let i = 0; i < dim_val - this._dims; i++) { + this.data.push([]); } - }); - }; + } else if (this._dims > dim_val) { + throw "can't decrease size of datastore"; + } + this._dims = dim_val; + } get_offset() { - var offset = []; - offset.push(0); + const offset = [0]; - for (var i = 1; i < this._dims; i++) { + for (let i = 1; i < this._dims; i++) { if (this.data[i] === undefined) { offset.push(this.data[0].length); } else { @@ -211,17 +215,17 @@ export class GrowableDataStore extends DataStore { */ push(row) { // Get the offsets - var offset = this.get_offset(); + const offset = this.get_offset(); // If you get data out of order, wipe out the later data if (row[0] < this.times[this.times.length - 1]) { - var index = 0; + let index = 0; while (this.times[index] < row[0]) { index += 1; } this.times.splice(index, this.times.length); - for (var i = 0; i < this._dims; i++) { + for (let i = 0; i < this._dims; i++) { if (index - offset[i] >= 0) { this.data[i].splice(index - offset[i], this.data[i].length); } @@ -229,15 +233,15 @@ export class GrowableDataStore extends DataStore { } // Compute lowpass filter (value = value*decay + new_value*(1-decay) - var decay = 0.0; - if ((this.times.length != 0) && (this.synapse > 0)) { - var dt = row[0] - this.times[this.times.length - 1]; + let decay = 0.0; + if ((this.times.length !== 0) && (this.synapse > 0)) { + let dt = row[0] - this.times[this.times.length - 1]; decay = Math.exp(-dt / this.synapse); } // Put filtered values into data array - for (var i = 0; i < this._dims; i++) { - if (decay == 0.0 || this.data[i].length === 0) { + for (let i = 0; i < this._dims; i++) { + if (decay === 0.0 || this.data[i].length === 0) { this.data[i].push(row[i + 1]); } else { this.data[i].push(row[i + 1] * (1 - decay) + @@ -248,15 +252,6 @@ export class GrowableDataStore extends DataStore { this.times.push(row[0]); }; - /** - * Reset dimensions before resetting the datastore. - */ - reset() { - console.log("resetting growable"); - this._dims = 1; - DataStore.call(this, this._dims, this.sim, this.synapse); - }; - /** * Update the data storage. * @@ -267,9 +262,9 @@ export class GrowableDataStore extends DataStore { update() { // Figure out how many extra values we have (values whose time stamp is // outside the range to keep) - var offset = this.get_offset(); - var extra = 0; - var limit = this.sim.time_slider.last_time - + const offset = this.get_offset(); + let extra = 0; + const limit = this.sim.time_slider.last_time - this.sim.time_slider.kept_time; while (this.times[extra] < limit) { extra += 1; @@ -278,7 +273,7 @@ export class GrowableDataStore extends DataStore { // Remove the extra data if (extra > 0) { this.times = this.times.slice(extra); - for (var i = 0; i < this.data.length; i++) { + for (let i = 0; i < this.data.length; i++) { if (extra - offset[i] >= 0) { this.data[i] = this.data[i].slice(extra - offset[i]); } @@ -290,28 +285,28 @@ export class GrowableDataStore extends DataStore { * Return just the data that is to be shown. */ get_shown_data() { - var offset = this.get_offset(); + const offset = this.get_offset(); // Determine time range - var t1 = this.sim.time_slider.first_shown_time; - var t2 = t1 + this.sim.time_slider.shown_time; + const t1 = this.sim.time_slider.first_shown_time; + const t2 = t1 + this.sim.time_slider.shown_time; // Find the corresponding index values - var index = 0; + let index = 0; while (this.times[index] < t1) { index += 1; } // Logically, you should start the search for the - var last_index = index; + let last_index = index; while (this.times[last_index] < t2 && last_index < this.times.length) { last_index += 1; } this.first_shown_index = index; // Return the visible slice of the data - var shown = []; - var nan_number = 0; - var slice_start = 0; - for (var i = 0; i < this._dims; i++) { + const shown = []; + for (let i = 0; i < this._dims; i++) { + let nan_number; + let slice_start; if (last_index > offset[i] && offset[i] !== 0) { @@ -341,25 +336,24 @@ export class GrowableDataStore extends DataStore { }; get_last_data() { - var offset = this.get_offset(); + const offset = this.get_offset(); // Determine time range - var t1 = this.sim.time_slider.first_shown_time; - var t2 = t1 + this.sim.time_slider.shown_time; + const t1 = this.sim.time_slider.first_shown_time; + const t2 = t1 + this.sim.time_slider.shown_time; // Find the corresponding index values - var last_index = 0; + let last_index = 0; while (this.times[last_index] < t2 && last_index < this.times.length - 1) { last_index += 1; } // Return the visible slice of the data - var shown = []; - for (var i = 0; i < this._dims; i++) { + const shown = []; + for (let i = 0; i < this._dims; i++) { if (last_index - offset[i] >= 0) { shown.push(this.data[i][last_index - offset[i]]); } } return shown; }; - } diff --git a/nengo_gui/static/dist/nengo.js b/nengo_gui/static/dist/nengo.js index c7c6f11b..dd50ea49 100644 --- a/nengo_gui/static/dist/nengo.js +++ b/nengo_gui/static/dist/nengo.js @@ -1,4 +1,4 @@ -var Nengo=function(t){function e(n){if(i[n])return i[n].exports;var o=i[n]={exports:{},id:n,loaded:!1};return t[n].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var i={};return e.m=t,e.c=i,e.p="/static/dist/",e(0)}([function(t,e,i){t.exports=i(108)},function(t,e,i){var n,o;/*! +var Nengo=function(t){function e(n){if(i[n])return i[n].exports;var o=i[n]={exports:{},id:n,loaded:!1};return t[n].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var i={};return e.m=t,e.c=i,e.p="/static/dist/",e(0)}([function(t,e,i){t.exports=i(106)},function(t,e,i){var n,o;/*! * jQuery JavaScript Library v2.1.3 * http://jquery.com/ * @@ -23,17 +23,17 @@ var Nengo=function(t){function e(n){if(i[n])return i[n].exports;var o=i[n]={expo */ function(t){function e(t,e,i,n){var o,r,s,a,l,h,u,p,f,g;if((e?e.ownerDocument||e:P)!==B&&T(e),e=e||B,i=i||[],a=e.nodeType,"string"!=typeof t||!t||1!==a&&9!==a&&11!==a)return i;if(!n&&$){if(11!==a&&(o=bt.exec(t)))if(s=o[1]){if(9===a){if(r=e.getElementById(s),!r||!r.parentNode)return i;if(r.id===s)return i.push(r),i}else if(e.ownerDocument&&(r=e.ownerDocument.getElementById(s))&&O(e,r)&&r.id===s)return i.push(r),i}else{if(o[2])return J.apply(i,e.getElementsByTagName(t)),i;if((s=o[3])&&x.getElementsByClassName)return J.apply(i,e.getElementsByClassName(s)),i}if(x.qsa&&(!R||!R.test(t))){if(p=u=N,f=e,g=1!==a&&t,1===a&&"object"!==e.nodeName.toLowerCase()){for(h=E(t),(u=e.getAttribute("id"))?p=u.replace(wt,"\\$&"):e.setAttribute("id",p),p="[id='"+p+"'] ",l=h.length;l--;)h[l]=p+d(h[l]);f=yt.test(t)&&c(e.parentNode)||e,g=h.join(",")}if(g)try{return J.apply(i,f.querySelectorAll(g)),i}catch(m){}finally{u||e.removeAttribute("id")}}}return F(t.replace(lt,"$1"),e,i,n)}function i(){function t(i,n){return e.push(i+" ")>_.cacheLength&&delete t[e.shift()],t[i+" "]=n}var e=[];return t}function n(t){return t[N]=!0,t}function o(t){var e=B.createElement("div");try{return!!t(e)}catch(i){return!1}finally{e.parentNode&&e.parentNode.removeChild(e),e=null}}function r(t,e){for(var i=t.split("|"),n=t.length;n--;)_.attrHandle[i[n]]=e}function s(t,e){var i=e&&t,n=i&&1===t.nodeType&&1===e.nodeType&&(~e.sourceIndex||K)-(~t.sourceIndex||K);if(n)return n;if(i)for(;i=i.nextSibling;)if(i===e)return-1;return t?1:-1}function a(t){return function(e){var i=e.nodeName.toLowerCase();return"input"===i&&e.type===t}}function l(t){return function(e){var i=e.nodeName.toLowerCase();return("input"===i||"button"===i)&&e.type===t}}function h(t){return n(function(e){return e=+e,n(function(i,n){for(var o,r=t([],i.length,e),s=r.length;s--;)i[o=r[s]]&&(i[o]=!(n[o]=i[o]))})})}function c(t){return t&&"undefined"!=typeof t.getElementsByTagName&&t}function u(){}function d(t){for(var e=0,i=t.length,n="";e1?function(e,i,n){for(var o=t.length;o--;)if(!t[o](e,i,n))return!1;return!0}:t[0]}function g(t,i,n){for(var o=0,r=i.length;o-1&&(n[h]=!(s[h]=u))}}else y=m(y===s?y.splice(f,y.length):y),r?r(null,s,y,l):J.apply(s,y)})}function b(t){for(var e,i,n,o=t.length,r=_.relative[t[0].type],s=r||_.relative[" "],a=r?1:0,l=p(function(t){return t===e},s,!0),h=p(function(t){return tt(e,t)>-1},s,!0),c=[function(t,i,n){var o=!r&&(n||i!==D)||((e=i).nodeType?l(t,i,n):h(t,i,n));return e=null,o}];a1&&f(c),a>1&&d(t.slice(0,a-1).concat({value:" "===t[a-2].type?"*":""})).replace(lt,"$1"),i,a0,r=t.length>0,s=function(n,s,a,l,h){var c,u,d,p=0,f="0",g=n&&[],v=[],b=D,y=n||r&&_.find.TAG("*",h),w=W+=null==b?1:Math.random()||.1,x=y.length;for(h&&(D=s!==B&&s);f!==x&&null!=(c=y[f]);f++){if(r&&c){for(u=0;d=t[u++];)if(d(c,s,a)){l.push(c);break}h&&(W=w)}o&&((c=!d&&c)&&p--,n&&g.push(c))}if(p+=f,o&&f!==p){for(u=0;d=i[u++];)d(g,v,s,a);if(n){if(p>0)for(;f--;)g[f]||v[f]||(v[f]=Q.call(l));v=m(v)}J.apply(l,v),h&&!n&&v.length>0&&p+i.length>1&&e.uniqueSort(l)}return h&&(W=w,D=b),g};return o?n(s):s}var w,x,_,A,C,E,k,F,D,S,M,T,B,L,$,R,I,z,O,N="sizzle"+1*new Date,P=t.document,W=0,H=0,U=i(),j=i(),V=i(),q=function(t,e){return t===e&&(M=!0),0},K=1<<31,Y={}.hasOwnProperty,G=[],Q=G.pop,X=G.push,J=G.push,Z=G.slice,tt=function(t,e){for(var i=0,n=t.length;i+~]|"+it+")"+it+"*"),ut=new RegExp("="+it+"*([^\\]'\"]*?)"+it+"*\\]","g"),dt=new RegExp(st),pt=new RegExp("^"+ot+"$"),ft={ID:new RegExp("^#("+nt+")"),CLASS:new RegExp("^\\.("+nt+")"),TAG:new RegExp("^("+nt.replace("w","w*")+")"),ATTR:new RegExp("^"+rt),PSEUDO:new RegExp("^"+st),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+it+"*(even|odd|(([+-]|)(\\d*)n|)"+it+"*(?:([+-]|)"+it+"*(\\d+)|))"+it+"*\\)|)","i"),bool:new RegExp("^(?:"+et+")$","i"),needsContext:new RegExp("^"+it+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+it+"*((?:-\\d)?\\d*)"+it+"*\\)|)(?=[^-]|$)","i")},gt=/^(?:input|select|textarea|button)$/i,mt=/^h\d$/i,vt=/^[^{]+\{\s*\[native \w/,bt=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,yt=/[+~]/,wt=/'|\\/g,xt=new RegExp("\\\\([\\da-f]{1,6}"+it+"?|("+it+")|.)","ig"),_t=function(t,e,i){var n="0x"+e-65536;return n!==n||i?e:n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320)},At=function(){T()};try{J.apply(G=Z.call(P.childNodes),P.childNodes),G[P.childNodes.length].nodeType}catch(Ct){J={apply:G.length?function(t,e){X.apply(t,Z.call(e))}:function(t,e){for(var i=t.length,n=0;t[i++]=e[n++];);t.length=i-1}}}x=e.support={},C=e.isXML=function(t){var e=t&&(t.ownerDocument||t).documentElement;return!!e&&"HTML"!==e.nodeName},T=e.setDocument=function(t){var e,i,n=t?t.ownerDocument||t:P;return n!==B&&9===n.nodeType&&n.documentElement?(B=n,L=n.documentElement,i=n.defaultView,i&&i!==i.top&&(i.addEventListener?i.addEventListener("unload",At,!1):i.attachEvent&&i.attachEvent("onunload",At)),$=!C(n),x.attributes=o(function(t){return t.className="i",!t.getAttribute("className")}),x.getElementsByTagName=o(function(t){return t.appendChild(n.createComment("")),!t.getElementsByTagName("*").length}),x.getElementsByClassName=vt.test(n.getElementsByClassName),x.getById=o(function(t){return L.appendChild(t).id=N,!n.getElementsByName||!n.getElementsByName(N).length}),x.getById?(_.find.ID=function(t,e){if("undefined"!=typeof e.getElementById&&$){var i=e.getElementById(t);return i&&i.parentNode?[i]:[]}},_.filter.ID=function(t){var e=t.replace(xt,_t);return function(t){return t.getAttribute("id")===e}}):(delete _.find.ID,_.filter.ID=function(t){var e=t.replace(xt,_t);return function(t){var i="undefined"!=typeof t.getAttributeNode&&t.getAttributeNode("id");return i&&i.value===e}}),_.find.TAG=x.getElementsByTagName?function(t,e){return"undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t):x.qsa?e.querySelectorAll(t):void 0}:function(t,e){var i,n=[],o=0,r=e.getElementsByTagName(t);if("*"===t){for(;i=r[o++];)1===i.nodeType&&n.push(i);return n}return r},_.find.CLASS=x.getElementsByClassName&&function(t,e){if($)return e.getElementsByClassName(t)},I=[],R=[],(x.qsa=vt.test(n.querySelectorAll))&&(o(function(t){L.appendChild(t).innerHTML="",t.querySelectorAll("[msallowcapture^='']").length&&R.push("[*^$]="+it+"*(?:''|\"\")"),t.querySelectorAll("[selected]").length||R.push("\\["+it+"*(?:value|"+et+")"),t.querySelectorAll("[id~="+N+"-]").length||R.push("~="),t.querySelectorAll(":checked").length||R.push(":checked"),t.querySelectorAll("a#"+N+"+*").length||R.push(".#.+[+~]")}),o(function(t){var e=n.createElement("input");e.setAttribute("type","hidden"),t.appendChild(e).setAttribute("name","D"),t.querySelectorAll("[name=d]").length&&R.push("name"+it+"*[*^$|!~]?="),t.querySelectorAll(":enabled").length||R.push(":enabled",":disabled"),t.querySelectorAll("*,:x"),R.push(",.*:")})),(x.matchesSelector=vt.test(z=L.matches||L.webkitMatchesSelector||L.mozMatchesSelector||L.oMatchesSelector||L.msMatchesSelector))&&o(function(t){x.disconnectedMatch=z.call(t,"div"),z.call(t,"[s!='']:x"),I.push("!=",st)}),R=R.length&&new RegExp(R.join("|")),I=I.length&&new RegExp(I.join("|")),e=vt.test(L.compareDocumentPosition),O=e||vt.test(L.contains)?function(t,e){var i=9===t.nodeType?t.documentElement:t,n=e&&e.parentNode;return t===n||!(!n||1!==n.nodeType||!(i.contains?i.contains(n):t.compareDocumentPosition&&16&t.compareDocumentPosition(n)))}:function(t,e){if(e)for(;e=e.parentNode;)if(e===t)return!0;return!1},q=e?function(t,e){if(t===e)return M=!0,0;var i=!t.compareDocumentPosition-!e.compareDocumentPosition;return i?i:(i=(t.ownerDocument||t)===(e.ownerDocument||e)?t.compareDocumentPosition(e):1,1&i||!x.sortDetached&&e.compareDocumentPosition(t)===i?t===n||t.ownerDocument===P&&O(P,t)?-1:e===n||e.ownerDocument===P&&O(P,e)?1:S?tt(S,t)-tt(S,e):0:4&i?-1:1)}:function(t,e){if(t===e)return M=!0,0;var i,o=0,r=t.parentNode,a=e.parentNode,l=[t],h=[e];if(!r||!a)return t===n?-1:e===n?1:r?-1:a?1:S?tt(S,t)-tt(S,e):0;if(r===a)return s(t,e);for(i=t;i=i.parentNode;)l.unshift(i);for(i=e;i=i.parentNode;)h.unshift(i);for(;l[o]===h[o];)o++;return o?s(l[o],h[o]):l[o]===P?-1:h[o]===P?1:0},n):B},e.matches=function(t,i){return e(t,null,null,i)},e.matchesSelector=function(t,i){if((t.ownerDocument||t)!==B&&T(t),i=i.replace(ut,"='$1']"),x.matchesSelector&&$&&(!I||!I.test(i))&&(!R||!R.test(i)))try{var n=z.call(t,i);if(n||x.disconnectedMatch||t.document&&11!==t.document.nodeType)return n}catch(o){}return e(i,B,null,[t]).length>0},e.contains=function(t,e){return(t.ownerDocument||t)!==B&&T(t),O(t,e)},e.attr=function(t,e){(t.ownerDocument||t)!==B&&T(t);var i=_.attrHandle[e.toLowerCase()],n=i&&Y.call(_.attrHandle,e.toLowerCase())?i(t,e,!$):void 0;return void 0!==n?n:x.attributes||!$?t.getAttribute(e):(n=t.getAttributeNode(e))&&n.specified?n.value:null},e.error=function(t){throw new Error("Syntax error, unrecognized expression: "+t)},e.uniqueSort=function(t){var e,i=[],n=0,o=0;if(M=!x.detectDuplicates,S=!x.sortStable&&t.slice(0),t.sort(q),M){for(;e=t[o++];)e===t[o]&&(n=i.push(o));for(;n--;)t.splice(i[n],1)}return S=null,t},A=e.getText=function(t){var e,i="",n=0,o=t.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof t.textContent)return t.textContent;for(t=t.firstChild;t;t=t.nextSibling)i+=A(t)}else if(3===o||4===o)return t.nodeValue}else for(;e=t[n++];)i+=A(e);return i},_=e.selectors={cacheLength:50,createPseudo:n,match:ft,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(t){return t[1]=t[1].replace(xt,_t),t[3]=(t[3]||t[4]||t[5]||"").replace(xt,_t),"~="===t[2]&&(t[3]=" "+t[3]+" "),t.slice(0,4)},CHILD:function(t){return t[1]=t[1].toLowerCase(),"nth"===t[1].slice(0,3)?(t[3]||e.error(t[0]),t[4]=+(t[4]?t[5]+(t[6]||1):2*("even"===t[3]||"odd"===t[3])),t[5]=+(t[7]+t[8]||"odd"===t[3])):t[3]&&e.error(t[0]),t},PSEUDO:function(t){var e,i=!t[6]&&t[2];return ft.CHILD.test(t[0])?null:(t[3]?t[2]=t[4]||t[5]||"":i&&dt.test(i)&&(e=E(i,!0))&&(e=i.indexOf(")",i.length-e)-i.length)&&(t[0]=t[0].slice(0,e),t[2]=i.slice(0,e)),t.slice(0,3))}},filter:{TAG:function(t){var e=t.replace(xt,_t).toLowerCase();return"*"===t?function(){return!0}:function(t){return t.nodeName&&t.nodeName.toLowerCase()===e}},CLASS:function(t){var e=U[t+" "];return e||(e=new RegExp("(^|"+it+")"+t+"("+it+"|$)"))&&U(t,function(t){return e.test("string"==typeof t.className&&t.className||"undefined"!=typeof t.getAttribute&&t.getAttribute("class")||"")})},ATTR:function(t,i,n){return function(o){var r=e.attr(o,t);return null==r?"!="===i:!i||(r+="","="===i?r===n:"!="===i?r!==n:"^="===i?n&&0===r.indexOf(n):"*="===i?n&&r.indexOf(n)>-1:"$="===i?n&&r.slice(-n.length)===n:"~="===i?(" "+r.replace(at," ")+" ").indexOf(n)>-1:"|="===i&&(r===n||r.slice(0,n.length+1)===n+"-"))}},CHILD:function(t,e,i,n,o){var r="nth"!==t.slice(0,3),s="last"!==t.slice(-4),a="of-type"===e;return 1===n&&0===o?function(t){return!!t.parentNode}:function(e,i,l){var h,c,u,d,p,f,g=r!==s?"nextSibling":"previousSibling",m=e.parentNode,v=a&&e.nodeName.toLowerCase(),b=!l&&!a;if(m){if(r){for(;g;){for(u=e;u=u[g];)if(a?u.nodeName.toLowerCase()===v:1===u.nodeType)return!1;f=g="only"===t&&!f&&"nextSibling"}return!0}if(f=[s?m.firstChild:m.lastChild],s&&b){for(c=m[N]||(m[N]={}),h=c[t]||[],p=h[0]===W&&h[1],d=h[0]===W&&h[2],u=p&&m.childNodes[p];u=++p&&u&&u[g]||(d=p=0)||f.pop();)if(1===u.nodeType&&++d&&u===e){c[t]=[W,p,d];break}}else if(b&&(h=(e[N]||(e[N]={}))[t])&&h[0]===W)d=h[1];else for(;(u=++p&&u&&u[g]||(d=p=0)||f.pop())&&((a?u.nodeName.toLowerCase()!==v:1!==u.nodeType)||!++d||(b&&((u[N]||(u[N]={}))[t]=[W,d]),u!==e)););return d-=o,d===n||d%n===0&&d/n>=0}}},PSEUDO:function(t,i){var o,r=_.pseudos[t]||_.setFilters[t.toLowerCase()]||e.error("unsupported pseudo: "+t);return r[N]?r(i):r.length>1?(o=[t,t,"",i],_.setFilters.hasOwnProperty(t.toLowerCase())?n(function(t,e){for(var n,o=r(t,i),s=o.length;s--;)n=tt(t,o[s]),t[n]=!(e[n]=o[s])}):function(t){return r(t,0,o)}):r}},pseudos:{not:n(function(t){var e=[],i=[],o=k(t.replace(lt,"$1"));return o[N]?n(function(t,e,i,n){for(var r,s=o(t,null,n,[]),a=t.length;a--;)(r=s[a])&&(t[a]=!(e[a]=r))}):function(t,n,r){return e[0]=t,o(e,null,r,i),e[0]=null,!i.pop()}}),has:n(function(t){return function(i){return e(t,i).length>0}}),contains:n(function(t){return t=t.replace(xt,_t),function(e){return(e.textContent||e.innerText||A(e)).indexOf(t)>-1}}),lang:n(function(t){return pt.test(t||"")||e.error("unsupported lang: "+t),t=t.replace(xt,_t).toLowerCase(),function(e){var i;do if(i=$?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return i=i.toLowerCase(),i===t||0===i.indexOf(t+"-");while((e=e.parentNode)&&1===e.nodeType);return!1}}),target:function(e){var i=t.location&&t.location.hash;return i&&i.slice(1)===e.id},root:function(t){return t===L},focus:function(t){return t===B.activeElement&&(!B.hasFocus||B.hasFocus())&&!!(t.type||t.href||~t.tabIndex)},enabled:function(t){return t.disabled===!1},disabled:function(t){return t.disabled===!0},checked:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&!!t.checked||"option"===e&&!!t.selected},selected:function(t){return t.parentNode&&t.parentNode.selectedIndex,t.selected===!0},empty:function(t){for(t=t.firstChild;t;t=t.nextSibling)if(t.nodeType<6)return!1;return!0},parent:function(t){return!_.pseudos.empty(t)},header:function(t){return mt.test(t.nodeName)},input:function(t){return gt.test(t.nodeName)},button:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&"button"===t.type||"button"===e},text:function(t){var e;return"input"===t.nodeName.toLowerCase()&&"text"===t.type&&(null==(e=t.getAttribute("type"))||"text"===e.toLowerCase())},first:h(function(){return[0]}),last:h(function(t,e){return[e-1]}),eq:h(function(t,e,i){return[i<0?i+e:i]}),even:h(function(t,e){for(var i=0;i=0;)t.push(n);return t}),gt:h(function(t,e,i){for(var n=i<0?i+e:i;++n2&&"ID"===(s=r[0]).type&&x.getById&&9===e.nodeType&&$&&_.relative[r[1].type]){if(e=(_.find.ID(s.matches[0].replace(xt,_t),e)||[])[0],!e)return i;h&&(e=e.parentNode),t=t.slice(r.shift().value.length)}for(o=ft.needsContext.test(t)?0:r.length;o--&&(s=r[o],!_.relative[a=s.type]);)if((l=_.find[a])&&(n=l(s.matches[0].replace(xt,_t),yt.test(r[0].type)&&c(e.parentNode)||e))){if(r.splice(o,1),t=n.length&&d(r),!t)return J.apply(i,n),i;break}}return(h||k(t,u))(n,e,!$,i,yt.test(t)&&c(e.parentNode)||e),i},x.sortStable=N.split("").sort(q).join("")===N,x.detectDuplicates=!!M,T(),x.sortDetached=o(function(t){return 1&t.compareDocumentPosition(B.createElement("div"))}),o(function(t){return t.innerHTML="","#"===t.firstChild.getAttribute("href")})||r("type|href|height|width",function(t,e,i){if(!i)return t.getAttribute(e,"type"===e.toLowerCase()?1:2)}),x.attributes&&o(function(t){return t.innerHTML="",t.firstChild.setAttribute("value",""),""===t.firstChild.getAttribute("value")})||r("value",function(t,e,i){if(!i&&"input"===t.nodeName.toLowerCase())return t.defaultValue}),o(function(t){return null==t.getAttribute("disabled")})||r(et,function(t,e,i){var n;if(!i)return t[e]===!0?e.toLowerCase():(n=t.getAttributeNode(e))&&n.specified?n.value:null}),e}(i);nt.find=lt,nt.expr=lt.selectors,nt.expr[":"]=nt.expr.pseudos,nt.unique=lt.uniqueSort,nt.text=lt.getText,nt.isXMLDoc=lt.isXML,nt.contains=lt.contains;var ht=nt.expr.match.needsContext,ct=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,ut=/^.[^:#\[\.,]*$/;nt.filter=function(t,e,i){var n=e[0];return i&&(t=":not("+t+")"),1===e.length&&1===n.nodeType?nt.find.matchesSelector(n,t)?[n]:[]:nt.find.matches(t,nt.grep(e,function(t){return 1===t.nodeType}))},nt.fn.extend({find:function(t){var e,i=this.length,n=[],o=this;if("string"!=typeof t)return this.pushStack(nt(t).filter(function(){for(e=0;e1?nt.unique(n):n),n.selector=this.selector?this.selector+" "+t:t,n},filter:function(t){return this.pushStack(a(this,t||[],!1))},not:function(t){return this.pushStack(a(this,t||[],!0))},is:function(t){return!!a(this,"string"==typeof t&&ht.test(t)?nt(t):t||[],!1).length}});var dt,pt=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,ft=nt.fn.init=function(t,e){var i,n;if(!t)return this;if("string"==typeof t){if(i="<"===t[0]&&">"===t[t.length-1]&&t.length>=3?[null,t,null]:pt.exec(t),!i||!i[1]&&e)return!e||e.jquery?(e||dt).find(t):this.constructor(e).find(t);if(i[1]){if(e=e instanceof nt?e[0]:e,nt.merge(this,nt.parseHTML(i[1],e&&e.nodeType?e.ownerDocument||e:et,!0)),ct.test(i[1])&&nt.isPlainObject(e))for(i in e)nt.isFunction(this[i])?this[i](e[i]):this.attr(i,e[i]);return this}return n=et.getElementById(i[2]),n&&n.parentNode&&(this.length=1,this[0]=n),this.context=et,this.selector=t,this}return t.nodeType?(this.context=this[0]=t,this.length=1,this):nt.isFunction(t)?"undefined"!=typeof dt.ready?dt.ready(t):t(nt):(void 0!==t.selector&&(this.selector=t.selector,this.context=t.context),nt.makeArray(t,this))};ft.prototype=nt.fn,dt=nt(et);var gt=/^(?:parents|prev(?:Until|All))/,mt={children:!0,contents:!0,next:!0,prev:!0};nt.extend({dir:function(t,e,i){for(var n=[],o=void 0!==i;(t=t[e])&&9!==t.nodeType;)if(1===t.nodeType){if(o&&nt(t).is(i))break;n.push(t)}return n},sibling:function(t,e){for(var i=[];t;t=t.nextSibling)1===t.nodeType&&t!==e&&i.push(t);return i}}),nt.fn.extend({has:function(t){var e=nt(t,this),i=e.length;return this.filter(function(){for(var t=0;t-1:1===i.nodeType&&nt.find.matchesSelector(i,t))){r.push(i);break}return this.pushStack(r.length>1?nt.unique(r):r)},index:function(t){return t?"string"==typeof t?Q.call(nt(t),this[0]):Q.call(this,t.jquery?t[0]:t):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(t,e){return this.pushStack(nt.unique(nt.merge(this.get(),nt(t,e))))},addBack:function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}}),nt.each({parent:function(t){var e=t.parentNode;return e&&11!==e.nodeType?e:null},parents:function(t){return nt.dir(t,"parentNode")},parentsUntil:function(t,e,i){return nt.dir(t,"parentNode",i)},next:function(t){return l(t,"nextSibling")},prev:function(t){return l(t,"previousSibling")},nextAll:function(t){return nt.dir(t,"nextSibling")},prevAll:function(t){return nt.dir(t,"previousSibling")},nextUntil:function(t,e,i){return nt.dir(t,"nextSibling",i)},prevUntil:function(t,e,i){return nt.dir(t,"previousSibling",i)},siblings:function(t){return nt.sibling((t.parentNode||{}).firstChild,t)},children:function(t){return nt.sibling(t.firstChild)},contents:function(t){return t.contentDocument||nt.merge([],t.childNodes)}},function(t,e){nt.fn[t]=function(i,n){var o=nt.map(this,e,i);return"Until"!==t.slice(-5)&&(n=i),n&&"string"==typeof n&&(o=nt.filter(n,o)),this.length>1&&(mt[t]||nt.unique(o),gt.test(t)&&o.reverse()),this.pushStack(o)}});var vt=/\S+/g,bt={};nt.Callbacks=function(t){t="string"==typeof t?bt[t]||h(t):nt.extend({},t);var e,i,n,o,r,s,a=[],l=!t.once&&[],c=function(h){for(e=t.memory&&h,i=!0,s=o||0,o=0,r=a.length,n=!0;a&&s-1;)a.splice(i,1),n&&(i<=r&&r--,i<=s&&s--)}),this},has:function(t){return t?nt.inArray(t,a)>-1:!(!a||!a.length)},empty:function(){return a=[],r=0,this},disable:function(){return a=l=e=void 0,this},disabled:function(){return!a},lock:function(){return l=void 0,e||u.disable(),this},locked:function(){return!l},fireWith:function(t,e){return!a||i&&!l||(e=e||[],e=[t,e.slice?e.slice():e],n?l.push(e):c(e)),this},fire:function(){return u.fireWith(this,arguments),this},fired:function(){return!!i}};return u},nt.extend({Deferred:function(t){var e=[["resolve","done",nt.Callbacks("once memory"),"resolved"],["reject","fail",nt.Callbacks("once memory"),"rejected"],["notify","progress",nt.Callbacks("memory")]],i="pending",n={state:function(){return i},always:function(){return o.done(arguments).fail(arguments),this},then:function(){var t=arguments;return nt.Deferred(function(i){nt.each(e,function(e,r){var s=nt.isFunction(t[e])&&t[e];o[r[1]](function(){var t=s&&s.apply(this,arguments);t&&nt.isFunction(t.promise)?t.promise().done(i.resolve).fail(i.reject).progress(i.notify):i[r[0]+"With"](this===n?i.promise():this,s?[t]:arguments)})}),t=null}).promise()},promise:function(t){return null!=t?nt.extend(t,n):n}},o={};return n.pipe=n.then,nt.each(e,function(t,r){var s=r[2],a=r[3];n[r[1]]=s.add,a&&s.add(function(){i=a},e[1^t][2].disable,e[2][2].lock),o[r[0]]=function(){return o[r[0]+"With"](this===o?n:this,arguments),this},o[r[0]+"With"]=s.fireWith}),n.promise(o),t&&t.call(o,o),o},when:function(t){var e,i,n,o=0,r=K.call(arguments),s=r.length,a=1!==s||t&&nt.isFunction(t.promise)?s:0,l=1===a?t:nt.Deferred(),h=function(t,i,n){return function(o){i[t]=this,n[t]=arguments.length>1?K.call(arguments):o,n===e?l.notifyWith(i,n):--a||l.resolveWith(i,n)}};if(s>1)for(e=new Array(s),i=new Array(s),n=new Array(s);o0||(yt.resolveWith(et,[nt]),nt.fn.triggerHandler&&(nt(et).triggerHandler("ready"),nt(et).off("ready"))))}}),nt.ready.promise=function(t){return yt||(yt=nt.Deferred(),"complete"===et.readyState?setTimeout(nt.ready):(et.addEventListener("DOMContentLoaded",c,!1),i.addEventListener("load",c,!1))),yt.promise(t)},nt.ready.promise();var wt=nt.access=function(t,e,i,n,o,r,s){var a=0,l=t.length,h=null==i;if("object"===nt.type(i)){o=!0;for(a in i)nt.access(t,e,a,i[a],!0,r,s)}else if(void 0!==n&&(o=!0,nt.isFunction(n)||(s=!0),h&&(s?(e.call(t,n),e=null):(h=e,e=function(t,e,i){return h.call(nt(t),i)})),e))for(;a1,null,!0)},removeData:function(t){return this.each(function(){_t.remove(this,t)})}}),nt.extend({queue:function(t,e,i){var n;if(t)return e=(e||"fx")+"queue",n=xt.get(t,e),i&&(!n||nt.isArray(i)?n=xt.access(t,e,nt.makeArray(i)):n.push(i)),n||[]},dequeue:function(t,e){e=e||"fx";var i=nt.queue(t,e),n=i.length,o=i.shift(),r=nt._queueHooks(t,e),s=function(){nt.dequeue(t,e)};"inprogress"===o&&(o=i.shift(),n--),o&&("fx"===e&&i.unshift("inprogress"),delete r.stop,o.call(t,s,r)),!n&&r&&r.empty.fire()},_queueHooks:function(t,e){var i=e+"queueHooks";return xt.get(t,i)||xt.access(t,i,{empty:nt.Callbacks("once memory").add(function(){xt.remove(t,[e+"queue",i])})})}}),nt.fn.extend({queue:function(t,e){var i=2;return"string"!=typeof t&&(e=t,t="fx",i--),arguments.lengthx",tt.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue}();var St="undefined";tt.focusinBubbles="onfocusin"in i;var Mt=/^key/,Tt=/^(?:mouse|pointer|contextmenu)|click/,Bt=/^(?:focusinfocus|focusoutblur)$/,Lt=/^([^.]*)(?:\.(.+)|)$/;nt.event={global:{},add:function(t,e,i,n,o){var r,s,a,l,h,c,u,d,p,f,g,m=xt.get(t);if(m)for(i.handler&&(r=i,i=r.handler,o=r.selector),i.guid||(i.guid=nt.guid++),(l=m.events)||(l=m.events={}),(s=m.handle)||(s=m.handle=function(e){return typeof nt!==St&&nt.event.triggered!==e.type?nt.event.dispatch.apply(t,arguments):void 0}),e=(e||"").match(vt)||[""],h=e.length;h--;)a=Lt.exec(e[h])||[],p=g=a[1],f=(a[2]||"").split(".").sort(),p&&(u=nt.event.special[p]||{},p=(o?u.delegateType:u.bindType)||p,u=nt.event.special[p]||{},c=nt.extend({type:p,origType:g,data:n,handler:i,guid:i.guid,selector:o,needsContext:o&&nt.expr.match.needsContext.test(o),namespace:f.join(".")},r),(d=l[p])||(d=l[p]=[],d.delegateCount=0,u.setup&&u.setup.call(t,n,f,s)!==!1||t.addEventListener&&t.addEventListener(p,s,!1)),u.add&&(u.add.call(t,c),c.handler.guid||(c.handler.guid=i.guid)),o?d.splice(d.delegateCount++,0,c):d.push(c),nt.event.global[p]=!0)},remove:function(t,e,i,n,o){var r,s,a,l,h,c,u,d,p,f,g,m=xt.hasData(t)&&xt.get(t);if(m&&(l=m.events)){for(e=(e||"").match(vt)||[""],h=e.length;h--;)if(a=Lt.exec(e[h])||[],p=g=a[1],f=(a[2]||"").split(".").sort(),p){for(u=nt.event.special[p]||{},p=(n?u.delegateType:u.bindType)||p,d=l[p]||[],a=a[2]&&new RegExp("(^|\\.)"+f.join("\\.(?:.*\\.|)")+"(\\.|$)"),s=r=d.length;r--;)c=d[r],!o&&g!==c.origType||i&&i.guid!==c.guid||a&&!a.test(c.namespace)||n&&n!==c.selector&&("**"!==n||!c.selector)||(d.splice(r,1),c.selector&&d.delegateCount--,u.remove&&u.remove.call(t,c));s&&!d.length&&(u.teardown&&u.teardown.call(t,f,m.handle)!==!1||nt.removeEvent(t,p,m.handle),delete l[p])}else for(p in l)nt.event.remove(t,p+e[h],i,n,!0);nt.isEmptyObject(l)&&(delete m.handle,xt.remove(t,"events"))}},trigger:function(t,e,n,o){var r,s,a,l,h,c,u,d=[n||et],p=Z.call(t,"type")?t.type:t,f=Z.call(t,"namespace")?t.namespace.split("."):[];if(s=a=n=n||et,3!==n.nodeType&&8!==n.nodeType&&!Bt.test(p+nt.event.triggered)&&(p.indexOf(".")>=0&&(f=p.split("."),p=f.shift(),f.sort()),h=p.indexOf(":")<0&&"on"+p,t=t[nt.expando]?t:new nt.Event(p,"object"==typeof t&&t),t.isTrigger=o?2:3, t.namespace=f.join("."),t.namespace_re=t.namespace?new RegExp("(^|\\.)"+f.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=n),e=null==e?[t]:nt.makeArray(e,[t]),u=nt.event.special[p]||{},o||!u.trigger||u.trigger.apply(n,e)!==!1)){if(!o&&!u.noBubble&&!nt.isWindow(n)){for(l=u.delegateType||p,Bt.test(l+p)||(s=s.parentNode);s;s=s.parentNode)d.push(s),a=s;a===(n.ownerDocument||et)&&d.push(a.defaultView||a.parentWindow||i)}for(r=0;(s=d[r++])&&!t.isPropagationStopped();)t.type=r>1?l:u.bindType||p,c=(xt.get(s,"events")||{})[t.type]&&xt.get(s,"handle"),c&&c.apply(s,e),c=h&&s[h],c&&c.apply&&nt.acceptData(s)&&(t.result=c.apply(s,e),t.result===!1&&t.preventDefault());return t.type=p,o||t.isDefaultPrevented()||u._default&&u._default.apply(d.pop(),e)!==!1||!nt.acceptData(n)||h&&nt.isFunction(n[p])&&!nt.isWindow(n)&&(a=n[h],a&&(n[h]=null),nt.event.triggered=p,n[p](),nt.event.triggered=void 0,a&&(n[h]=a)),t.result}},dispatch:function(t){t=nt.event.fix(t);var e,i,n,o,r,s=[],a=K.call(arguments),l=(xt.get(this,"events")||{})[t.type]||[],h=nt.event.special[t.type]||{};if(a[0]=t,t.delegateTarget=this,!h.preDispatch||h.preDispatch.call(this,t)!==!1){for(s=nt.event.handlers.call(this,t,l),e=0;(o=s[e++])&&!t.isPropagationStopped();)for(t.currentTarget=o.elem,i=0;(r=o.handlers[i++])&&!t.isImmediatePropagationStopped();)t.namespace_re&&!t.namespace_re.test(r.namespace)||(t.handleObj=r,t.data=r.data,n=((nt.event.special[r.origType]||{}).handle||r.handler).apply(o.elem,a),void 0!==n&&(t.result=n)===!1&&(t.preventDefault(),t.stopPropagation()));return h.postDispatch&&h.postDispatch.call(this,t),t.result}},handlers:function(t,e){var i,n,o,r,s=[],a=e.delegateCount,l=t.target;if(a&&l.nodeType&&(!t.button||"click"!==t.type))for(;l!==this;l=l.parentNode||this)if(l.disabled!==!0||"click"!==t.type){for(n=[],i=0;i=0:nt.find(o,this,null,[l]).length),n[o]&&n.push(r);n.length&&s.push({elem:l,handlers:n})}return a]*)\/>/gi,Rt=/<([\w:]+)/,It=/<|&#?\w+;/,zt=/<(?:script|style|link)/i,Ot=/checked\s*(?:[^=]|=\s*.checked.)/i,Nt=/^$|\/(?:java|ecma)script/i,Pt=/^true\/(.*)/,Wt=/^\s*\s*$/g,Ht={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};Ht.optgroup=Ht.option,Ht.tbody=Ht.tfoot=Ht.colgroup=Ht.caption=Ht.thead,Ht.th=Ht.td,nt.extend({clone:function(t,e,i){var n,o,r,s,a=t.cloneNode(!0),l=nt.contains(t.ownerDocument,t);if(!(tt.noCloneChecked||1!==t.nodeType&&11!==t.nodeType||nt.isXMLDoc(t)))for(s=x(a),r=x(t),n=0,o=r.length;n0&&y(s,!l&&x(t,"script")),a},buildFragment:function(t,e,i,n){for(var o,r,s,a,l,h,c=e.createDocumentFragment(),u=[],d=0,p=t.length;d")+a[2],h=a[0];h--;)r=r.lastChild;nt.merge(u,r.childNodes),r=c.firstChild,r.textContent=""}else u.push(e.createTextNode(o));for(c.textContent="",d=0;o=u[d++];)if((!n||nt.inArray(o,n)===-1)&&(l=nt.contains(o.ownerDocument,o),r=x(c.appendChild(o),"script"),l&&y(r),i))for(h=0;o=r[h++];)Nt.test(o.type||"")&&i.push(o);return c},cleanData:function(t){for(var e,i,n,o,r=nt.event.special,s=0;void 0!==(i=t[s]);s++){if(nt.acceptData(i)&&(o=i[xt.expando],o&&(e=xt.cache[o]))){if(e.events)for(n in e.events)r[n]?nt.event.remove(i,n):nt.removeEvent(i,n,e.handle);xt.cache[o]&&delete xt.cache[o]}delete _t.cache[i[_t.expando]]}}}),nt.fn.extend({text:function(t){return wt(this,function(t){return void 0===t?nt.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=t)})},null,t,arguments.length)},append:function(){return this.domManip(arguments,function(t){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var e=m(this,t);e.appendChild(t)}})},prepend:function(){return this.domManip(arguments,function(t){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var e=m(this,t);e.insertBefore(t,e.firstChild)}})},before:function(){return this.domManip(arguments,function(t){this.parentNode&&this.parentNode.insertBefore(t,this)})},after:function(){return this.domManip(arguments,function(t){this.parentNode&&this.parentNode.insertBefore(t,this.nextSibling)})},remove:function(t,e){for(var i,n=t?nt.filter(t,this):this,o=0;null!=(i=n[o]);o++)e||1!==i.nodeType||nt.cleanData(x(i)),i.parentNode&&(e&&nt.contains(i.ownerDocument,i)&&y(x(i,"script")),i.parentNode.removeChild(i));return this},empty:function(){for(var t,e=0;null!=(t=this[e]);e++)1===t.nodeType&&(nt.cleanData(x(t,!1)),t.textContent="");return this},clone:function(t,e){return t=null!=t&&t,e=null==e?t:e,this.map(function(){return nt.clone(this,t,e)})},html:function(t){return wt(this,function(t){var e=this[0]||{},i=0,n=this.length;if(void 0===t&&1===e.nodeType)return e.innerHTML;if("string"==typeof t&&!zt.test(t)&&!Ht[(Rt.exec(t)||["",""])[1].toLowerCase()]){t=t.replace($t,"<$1>");try{for(;i1&&"string"==typeof d&&!tt.checkClone&&Ot.test(d))return this.each(function(i){var n=c.eq(i);p&&(t[0]=d.call(this,i,n.html())),n.domManip(t,e)});if(h&&(i=nt.buildFragment(t,this[0].ownerDocument,!1,this),n=i.firstChild,1===i.childNodes.length&&(i=n),n)){for(o=nt.map(x(i,"script"),v),r=o.length;l1)},show:function(){return T(this,!0)},hide:function(){return T(this)},toggle:function(t){return"boolean"==typeof t?t?this.show():this.hide():this.each(function(){Ft(this)?nt(this).show():nt(this).hide()})}}),nt.Tween=B,B.prototype={constructor:B,init:function(t,e,i,n,o,r){this.elem=t,this.prop=i,this.easing=o||"swing",this.options=e,this.start=this.now=this.cur(),this.end=n,this.unit=r||(nt.cssNumber[i]?"":"px")},cur:function(){var t=B.propHooks[this.prop];return t&&t.get?t.get(this):B.propHooks._default.get(this)},run:function(t){var e,i=B.propHooks[this.prop];return this.options.duration?this.pos=e=nt.easing[this.easing](t,this.options.duration*t,0,1,this.options.duration):this.pos=e=t,this.now=(this.end-this.start)*e+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),i&&i.set?i.set(this):B.propHooks._default.set(this),this}},B.prototype.init.prototype=B.prototype,B.propHooks={_default:{get:function(t){var e;return null==t.elem[t.prop]||t.elem.style&&null!=t.elem.style[t.prop]?(e=nt.css(t.elem,t.prop,""),e&&"auto"!==e?e:0):t.elem[t.prop]},set:function(t){nt.fx.step[t.prop]?nt.fx.step[t.prop](t):t.elem.style&&(null!=t.elem.style[nt.cssProps[t.prop]]||nt.cssHooks[t.prop])?nt.style(t.elem,t.prop,t.now+t.unit):t.elem[t.prop]=t.now}}},B.propHooks.scrollTop=B.propHooks.scrollLeft={set:function(t){t.elem.nodeType&&t.elem.parentNode&&(t.elem[t.prop]=t.now)}},nt.easing={linear:function(t){return t},swing:function(t){return.5-Math.cos(t*Math.PI)/2}},nt.fx=B.prototype.init,nt.fx.step={};var te,ee,ie=/^(?:toggle|show|hide)$/,ne=new RegExp("^(?:([+-])=|)("+Et+")([a-z%]*)$","i"),oe=/queueHooks$/,re=[I],se={"*":[function(t,e){var i=this.createTween(t,e),n=i.cur(),o=ne.exec(e),r=o&&o[3]||(nt.cssNumber[t]?"":"px"),s=(nt.cssNumber[t]||"px"!==r&&+n)&&ne.exec(nt.css(i.elem,t)),a=1,l=20;if(s&&s[3]!==r){r=r||s[3],o=o||[],s=+n||1;do a=a||".5",s/=a,nt.style(i.elem,t,s+r);while(a!==(a=i.cur()/n)&&1!==a&&--l)}return o&&(s=i.start=+s||+n||0,i.unit=r,i.end=o[1]?s+(o[1]+1)*o[2]:+o[2]),i}]};nt.Animation=nt.extend(O,{tweener:function(t,e){nt.isFunction(t)?(e=t,t=["*"]):t=t.split(" ");for(var i,n=0,o=t.length;n1)},removeAttr:function(t){return this.each(function(){nt.removeAttr(this,t)})}}),nt.extend({attr:function(t,e,i){var n,o,r=t.nodeType;if(t&&3!==r&&8!==r&&2!==r)return typeof t.getAttribute===St?nt.prop(t,e,i):(1===r&&nt.isXMLDoc(t)||(e=e.toLowerCase(),n=nt.attrHooks[e]||(nt.expr.match.bool.test(e)?le:ae)),void 0===i?n&&"get"in n&&null!==(o=n.get(t,e))?o:(o=nt.find.attr(t,e),null==o?void 0:o):null!==i?n&&"set"in n&&void 0!==(o=n.set(t,i,e))?o:(t.setAttribute(e,i+""),i):void nt.removeAttr(t,e))},removeAttr:function(t,e){var i,n,o=0,r=e&&e.match(vt);if(r&&1===t.nodeType)for(;i=r[o++];)n=nt.propFix[i]||i,nt.expr.match.bool.test(i)&&(t[n]=!1),t.removeAttribute(i)},attrHooks:{type:{set:function(t,e){if(!tt.radioValue&&"radio"===e&&nt.nodeName(t,"input")){var i=t.value;return t.setAttribute("type",e),i&&(t.value=i),e}}}}}),le={set:function(t,e,i){return e===!1?nt.removeAttr(t,i):t.setAttribute(i,i),i}},nt.each(nt.expr.match.bool.source.match(/\w+/g),function(t,e){var i=he[e]||nt.find.attr;he[e]=function(t,e,n){var o,r;return n||(r=he[e],he[e]=o,o=null!=i(t,e,n)?e.toLowerCase():null,he[e]=r),o}});var ce=/^(?:input|select|textarea|button)$/i;nt.fn.extend({prop:function(t,e){return wt(this,nt.prop,t,e,arguments.length>1)},removeProp:function(t){return this.each(function(){delete this[nt.propFix[t]||t]})}}),nt.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(t,e,i){var n,o,r,s=t.nodeType;if(t&&3!==s&&8!==s&&2!==s)return r=1!==s||!nt.isXMLDoc(t),r&&(e=nt.propFix[e]||e,o=nt.propHooks[e]),void 0!==i?o&&"set"in o&&void 0!==(n=o.set(t,i,e))?n:t[e]=i:o&&"get"in o&&null!==(n=o.get(t,e))?n:t[e]},propHooks:{tabIndex:{get:function(t){return t.hasAttribute("tabindex")||ce.test(t.nodeName)||t.href?t.tabIndex:-1}}}}),tt.optSelected||(nt.propHooks.selected={get:function(t){var e=t.parentNode;return e&&e.parentNode&&e.parentNode.selectedIndex,null}}),nt.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){nt.propFix[this.toLowerCase()]=this});var ue=/[\t\r\n\f]/g;nt.fn.extend({addClass:function(t){var e,i,n,o,r,s,a="string"==typeof t&&t,l=0,h=this.length;if(nt.isFunction(t))return this.each(function(e){nt(this).addClass(t.call(this,e,this.className))});if(a)for(e=(t||"").match(vt)||[];l=0;)n=n.replace(" "+o+" "," ");s=t?nt.trim(n):"",i.className!==s&&(i.className=s)}return this},toggleClass:function(t,e){var i=typeof t;return"boolean"==typeof e&&"string"===i?e?this.addClass(t):this.removeClass(t):nt.isFunction(t)?this.each(function(i){nt(this).toggleClass(t.call(this,i,this.className,e),e)}):this.each(function(){if("string"===i)for(var e,n=0,o=nt(this),r=t.match(vt)||[];e=r[n++];)o.hasClass(e)?o.removeClass(e):o.addClass(e);else i!==St&&"boolean"!==i||(this.className&&xt.set(this,"__className__",this.className),this.className=this.className||t===!1?"":xt.get(this,"__className__")||"")})},hasClass:function(t){for(var e=" "+t+" ",i=0,n=this.length;i=0)return!0;return!1}});var de=/\r/g;nt.fn.extend({val:function(t){var e,i,n,o=this[0];{if(arguments.length)return n=nt.isFunction(t),this.each(function(i){var o;1===this.nodeType&&(o=n?t.call(this,i,nt(this).val()):t,null==o?o="":"number"==typeof o?o+="":nt.isArray(o)&&(o=nt.map(o,function(t){return null==t?"":t+""})),e=nt.valHooks[this.type]||nt.valHooks[this.nodeName.toLowerCase()],e&&"set"in e&&void 0!==e.set(this,o,"value")||(this.value=o))});if(o)return e=nt.valHooks[o.type]||nt.valHooks[o.nodeName.toLowerCase()],e&&"get"in e&&void 0!==(i=e.get(o,"value"))?i:(i=o.value,"string"==typeof i?i.replace(de,""):null==i?"":i)}}}),nt.extend({valHooks:{option:{get:function(t){var e=nt.find.attr(t,"value");return null!=e?e:nt.trim(nt.text(t))}},select:{get:function(t){for(var e,i,n=t.options,o=t.selectedIndex,r="select-one"===t.type||o<0,s=r?null:[],a=r?o+1:n.length,l=o<0?a:r?o:0;l=0)&&(i=!0);return i||(t.selectedIndex=-1),r}}}}),nt.each(["radio","checkbox"],function(){nt.valHooks[this]={set:function(t,e){if(nt.isArray(e))return t.checked=nt.inArray(nt(t).val(),e)>=0}},tt.checkOn||(nt.valHooks[this].get=function(t){return null===t.getAttribute("value")?"on":t.value})}),nt.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(t,e){nt.fn[e]=function(t,i){return arguments.length>0?this.on(e,null,t,i):this.trigger(e)}}),nt.fn.extend({hover:function(t,e){return this.mouseenter(t).mouseleave(e||t)},bind:function(t,e,i){return this.on(t,null,e,i)},unbind:function(t,e){return this.off(t,null,e)},delegate:function(t,e,i,n){return this.on(e,t,i,n)},undelegate:function(t,e,i){return 1===arguments.length?this.off(t,"**"):this.off(e,t||"**",i)}});var pe=nt.now(),fe=/\?/;nt.parseJSON=function(t){return JSON.parse(t+"")},nt.parseXML=function(t){var e,i;if(!t||"string"!=typeof t)return null;try{i=new DOMParser,e=i.parseFromString(t,"text/xml")}catch(n){e=void 0}return e&&!e.getElementsByTagName("parsererror").length||nt.error("Invalid XML: "+t),e};var ge=/#.*$/,me=/([?&])_=[^&]*/,ve=/^(.*?):[ \t]*([^\r\n]*)$/gm,be=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,ye=/^(?:GET|HEAD)$/,we=/^\/\//,xe=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,_e={},Ae={},Ce="*/".concat("*"),Ee=i.location.href,ke=xe.exec(Ee.toLowerCase())||[];nt.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Ee,type:"GET",isLocal:be.test(ke[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Ce,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":nt.parseJSON,"text xml":nt.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(t,e){return e?W(W(t,nt.ajaxSettings),e):W(nt.ajaxSettings,t)},ajaxPrefilter:N(_e),ajaxTransport:N(Ae),ajax:function(t,e){function i(t,e,i,s){var l,c,v,b,w,_=e;2!==y&&(y=2,a&&clearTimeout(a),n=void 0,r=s||"",x.readyState=t>0?4:0,l=t>=200&&t<300||304===t,i&&(b=H(u,x,i)),b=U(u,b,x,l),l?(u.ifModified&&(w=x.getResponseHeader("Last-Modified"),w&&(nt.lastModified[o]=w),w=x.getResponseHeader("etag"),w&&(nt.etag[o]=w)),204===t||"HEAD"===u.type?_="nocontent":304===t?_="notmodified":(_=b.state,c=b.data,v=b.error,l=!v)):(v=_,!t&&_||(_="error",t<0&&(t=0))),x.status=t,x.statusText=(e||_)+"",l?f.resolveWith(d,[c,_,x]):f.rejectWith(d,[x,_,v]),x.statusCode(m),m=void 0,h&&p.trigger(l?"ajaxSuccess":"ajaxError",[x,u,l?c:v]),g.fireWith(d,[x,_]),h&&(p.trigger("ajaxComplete",[x,u]),--nt.active||nt.event.trigger("ajaxStop")))}"object"==typeof t&&(e=t,t=void 0),e=e||{};var n,o,r,s,a,l,h,c,u=nt.ajaxSetup({},e),d=u.context||u,p=u.context&&(d.nodeType||d.jquery)?nt(d):nt.event,f=nt.Deferred(),g=nt.Callbacks("once memory"),m=u.statusCode||{},v={},b={},y=0,w="canceled",x={readyState:0,getResponseHeader:function(t){var e;if(2===y){if(!s)for(s={};e=ve.exec(r);)s[e[1].toLowerCase()]=e[2];e=s[t.toLowerCase()]}return null==e?null:e},getAllResponseHeaders:function(){return 2===y?r:null},setRequestHeader:function(t,e){var i=t.toLowerCase();return y||(t=b[i]=b[i]||t,v[t]=e),this},overrideMimeType:function(t){return y||(u.mimeType=t),this},statusCode:function(t){var e;if(t)if(y<2)for(e in t)m[e]=[m[e],t[e]];else x.always(t[x.status]);return this},abort:function(t){var e=t||w;return n&&n.abort(e),i(0,e),this}};if(f.promise(x).complete=g.add,x.success=x.done,x.error=x.fail,u.url=((t||u.url||Ee)+"").replace(ge,"").replace(we,ke[1]+"//"),u.type=e.method||e.type||u.method||u.type,u.dataTypes=nt.trim(u.dataType||"*").toLowerCase().match(vt)||[""],null==u.crossDomain&&(l=xe.exec(u.url.toLowerCase()),u.crossDomain=!(!l||l[1]===ke[1]&&l[2]===ke[2]&&(l[3]||("http:"===l[1]?"80":"443"))===(ke[3]||("http:"===ke[1]?"80":"443")))),u.data&&u.processData&&"string"!=typeof u.data&&(u.data=nt.param(u.data,u.traditional)),P(_e,u,e,x),2===y)return x;h=nt.event&&u.global,h&&0===nt.active++&&nt.event.trigger("ajaxStart"),u.type=u.type.toUpperCase(),u.hasContent=!ye.test(u.type),o=u.url,u.hasContent||(u.data&&(o=u.url+=(fe.test(o)?"&":"?")+u.data,delete u.data),u.cache===!1&&(u.url=me.test(o)?o.replace(me,"$1_="+pe++):o+(fe.test(o)?"&":"?")+"_="+pe++)),u.ifModified&&(nt.lastModified[o]&&x.setRequestHeader("If-Modified-Since",nt.lastModified[o]),nt.etag[o]&&x.setRequestHeader("If-None-Match",nt.etag[o])),(u.data&&u.hasContent&&u.contentType!==!1||e.contentType)&&x.setRequestHeader("Content-Type",u.contentType),x.setRequestHeader("Accept",u.dataTypes[0]&&u.accepts[u.dataTypes[0]]?u.accepts[u.dataTypes[0]]+("*"!==u.dataTypes[0]?", "+Ce+"; q=0.01":""):u.accepts["*"]);for(c in u.headers)x.setRequestHeader(c,u.headers[c]);if(u.beforeSend&&(u.beforeSend.call(d,x,u)===!1||2===y))return x.abort();w="abort";for(c in{success:1,error:1,complete:1})x[c](u[c]);if(n=P(Ae,u,e,x)){x.readyState=1,h&&p.trigger("ajaxSend",[x,u]),u.async&&u.timeout>0&&(a=setTimeout(function(){x.abort("timeout")},u.timeout));try{y=1,n.send(v,i)}catch(_){if(!(y<2))throw _;i(-1,_)}}else i(-1,"No Transport");return x},getJSON:function(t,e,i){return nt.get(t,e,i,"json")},getScript:function(t,e){return nt.get(t,void 0,e,"script")}}),nt.each(["get","post"],function(t,e){nt[e]=function(t,i,n,o){return nt.isFunction(i)&&(o=o||n,n=i,i=void 0),nt.ajax({url:t,type:e,dataType:o,data:i,success:n})}}),nt._evalUrl=function(t){return nt.ajax({url:t,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},nt.fn.extend({wrapAll:function(t){var e;return nt.isFunction(t)?this.each(function(e){nt(this).wrapAll(t.call(this,e))}):(this[0]&&(e=nt(t,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&e.insertBefore(this[0]),e.map(function(){for(var t=this;t.firstElementChild;)t=t.firstElementChild;return t}).append(this)),this)},wrapInner:function(t){return nt.isFunction(t)?this.each(function(e){nt(this).wrapInner(t.call(this,e))}):this.each(function(){var e=nt(this),i=e.contents();i.length?i.wrapAll(t):e.append(t)})},wrap:function(t){var e=nt.isFunction(t);return this.each(function(i){nt(this).wrapAll(e?t.call(this,i):t)})},unwrap:function(){return this.parent().each(function(){ -nt.nodeName(this,"body")||nt(this).replaceWith(this.childNodes)}).end()}}),nt.expr.filters.hidden=function(t){return t.offsetWidth<=0&&t.offsetHeight<=0},nt.expr.filters.visible=function(t){return!nt.expr.filters.hidden(t)};var Fe=/%20/g,De=/\[\]$/,Se=/\r?\n/g,Me=/^(?:submit|button|image|reset|file)$/i,Te=/^(?:input|select|textarea|keygen)/i;nt.param=function(t,e){var i,n=[],o=function(t,e){e=nt.isFunction(e)?e():null==e?"":e,n[n.length]=encodeURIComponent(t)+"="+encodeURIComponent(e)};if(void 0===e&&(e=nt.ajaxSettings&&nt.ajaxSettings.traditional),nt.isArray(t)||t.jquery&&!nt.isPlainObject(t))nt.each(t,function(){o(this.name,this.value)});else for(i in t)j(i,t[i],e,o);return n.join("&").replace(Fe,"+")},nt.fn.extend({serialize:function(){return nt.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var t=nt.prop(this,"elements");return t?nt.makeArray(t):this}).filter(function(){var t=this.type;return this.name&&!nt(this).is(":disabled")&&Te.test(this.nodeName)&&!Me.test(t)&&(this.checked||!Dt.test(t))}).map(function(t,e){var i=nt(this).val();return null==i?null:nt.isArray(i)?nt.map(i,function(t){return{name:e.name,value:t.replace(Se,"\r\n")}}):{name:e.name,value:i.replace(Se,"\r\n")}}).get()}}),nt.ajaxSettings.xhr=function(){try{return new XMLHttpRequest}catch(t){}};var Be=0,Le={},$e={0:200,1223:204},Re=nt.ajaxSettings.xhr();i.attachEvent&&i.attachEvent("onunload",function(){for(var t in Le)Le[t]()}),tt.cors=!!Re&&"withCredentials"in Re,tt.ajax=Re=!!Re,nt.ajaxTransport(function(t){var e;if(tt.cors||Re&&!t.crossDomain)return{send:function(i,n){var o,r=t.xhr(),s=++Be;if(r.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(o in t.xhrFields)r[o]=t.xhrFields[o];t.mimeType&&r.overrideMimeType&&r.overrideMimeType(t.mimeType),t.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");for(o in i)r.setRequestHeader(o,i[o]);e=function(t){return function(){e&&(delete Le[s],e=r.onload=r.onerror=null,"abort"===t?r.abort():"error"===t?n(r.status,r.statusText):n($e[r.status]||r.status,r.statusText,"string"==typeof r.responseText?{text:r.responseText}:void 0,r.getAllResponseHeaders()))}},r.onload=e(),r.onerror=e("error"),e=Le[s]=e("abort");try{r.send(t.hasContent&&t.data||null)}catch(a){if(e)throw a}},abort:function(){e&&e()}}}),nt.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(t){return nt.globalEval(t),t}}}),nt.ajaxPrefilter("script",function(t){void 0===t.cache&&(t.cache=!1),t.crossDomain&&(t.type="GET")}),nt.ajaxTransport("script",function(t){if(t.crossDomain){var e,i;return{send:function(n,o){e=nt("