diff --git a/server.py b/server.py index d649965..a8d0417 100644 --- a/server.py +++ b/server.py @@ -19,7 +19,7 @@ logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) class VisualizationElement: - """ + """ Defines an element of the visualization. Attributes: package_includes: A list of external JavaScript files to include that @@ -32,190 +32,197 @@ class VisualizationElement: to the client. """ - package_includes = [] - local_includes = [] - js_code = '' - render_args = {} + package_includes = [] + local_includes = [] + js_code = '' + render_args = {} - def __init__(self): - pass + def __init__(self): + pass - def render(self, model): - return 'VisualizationElement goes here.' + def render(self, model): + return 'VisualizationElement goes here.' class PageHandler(tornado.web.RequestHandler): - """ Handler for the HTML template which holds the visualization. """ + """ Handler for the HTML template which holds the visualization. """ - def get(self): - self.render('index.html', port=self.application.port, - model_name=self.application.model_name, - package_includes=self.application.package_includes, - local_includes=self.application.local_includes, - scripts=self.application.js_code) + def get(self): + self.render('index.html', port=self.application.port, + model_name=self.application.model_name, + package_includes=self.application.package_includes, + local_includes=self.application.local_includes, + scripts=self.application.js_code) class SocketHandler(tornado.websocket.WebSocketHandler): - def open(self): - if self.application.verbose: - logger.info('Socket opened!') - + def open(self): + if self.application.verbose: + logger.info('Socket opened!') - def check_origin(self, origin): - return True + def check_origin(self, origin): + return True - def on_message(self, message): - """ Receiving a message from the websocket, parse, and act accordingly. """ + def on_message(self, message): + """ Receiving a message from the websocket, parse, and act accordingly. """ - msg = tornado.escape.json_decode(message) + msg = tornado.escape.json_decode(message) - if msg['type'] == 'config_file': + if msg['type'] == 'config_file': - if self.application.verbose: - print(msg['data']) + if self.application.verbose: + print(msg['data']) - config = list(yaml.load_all(msg['data'])) + self.config = list(yaml.load_all(msg['data'])) - if len(config) > 1: - error = 'Please, provide only one configuration.' - if self.application.verbose: - logger.error(error) - self.write_message({'type': 'error', - 'error': error}) - return - else: - config = config[0] - self.send_log('INFO.soil', 'Using config: {name}'.format(name=config['name'])) + if len(self.config) > 1: + error = 'Please, provide only one configuration.' + if self.application.verbose: + logger.error(error) + self.write_message({'type': 'error', + 'error': error}) + return + else: + self.config = self.config[0] + self.send_log('INFO.soil', 'Using config: {name}'.format(name=self.config['name'])) - self.name = config['name'] + self.name = self.config['name'] - settings = [] - for key in config['environment_params']: - if type(config['environment_params'][key]) == float: - setting_type = 'number' - elif type(config['environment_params'][key]) == bool: - setting_type = 'boolean' - else: - setting_type = 'undefined' + self.run_simulation() - settings.append({ - 'label': key, - 'type': setting_type, - 'value': config['environment_params'][key] - }) + settings = [] + for key in self.config['environment_params']: + if type(self.config['environment_params'][key]) == float: + setting_type = 'number' + elif type(self.config['environment_params'][key]) == bool: + setting_type = 'boolean' + else: + setting_type = 'undefined' - self.write_message({'type': 'settings', - 'data': settings}) - - # Run simulation and capture logs - with self.logging(self.application.model.name): - self.application.model.run(config) + settings.append({ + 'label': key, + 'type': setting_type, + 'value': self.config['environment_params'][key] + }) - trials = [] - for i in range(config['num_trials']): - trials.append('{}_trial_{}'.format(self.name, i)) - self.write_message({'type': 'trials', - 'data': trials }) + self.write_message({'type': 'settings', + 'data': settings}) - elif msg['type'] == 'get_trial': - if self.application.verbose: - logger.info('Trial {} requested!'.format(msg['data'])) - self.send_log('INFO.user', 'Trial {} requested!'.format(msg['data'])) - self.write_message({'type': 'get_trial', - 'data': self.application.model.get_trial(self.name, msg['data']) }) + elif msg['type'] == 'get_trial': + if self.application.verbose: + logger.info('Trial {} requested!'.format(msg['data'])) + self.send_log('INFO.user', 'Trial {} requested!'.format(msg['data'])) + self.write_message({'type': 'get_trial', + 'data': self.application.model.get_trial(self.name, msg['data']) }) - else: - if self.application.verbose: - logger.info('Unexpected message!') + elif msg['type'] == 'run_simulation': + self.send_log('INFO.soil', 'Running new simulation for {name}'.format(name=self.config['name'])) + self.config['environment_params'] = msg['data'] + self.run_simulation() - def update_logging(self): - try: - if (not self.log_capture_string.closed and self.log_capture_string.getvalue()): - self.send_log('INFO.soil', self.log_capture_string.getvalue()) - self.log_capture_string.truncate(0) - self.log_capture_string.seek(0) - finally: - if self.capture_logging: - thread = threading.Timer(0.001, self.update_logging) - thread.start() + else: + if self.application.verbose: + logger.info('Unexpected message!') - def on_close(self): - logger.info('Socket closed!') + def update_logging(self): + try: + if (not self.log_capture_string.closed and self.log_capture_string.getvalue()): + self.send_log('INFO.soil', self.log_capture_string.getvalue()) + self.log_capture_string.truncate(0) + self.log_capture_string.seek(0) + finally: + if self.capture_logging: + thread = threading.Timer(0.001, self.update_logging) + thread.start() - def send_log(self, logger, logging): - self.write_message({'type': 'log', - 'logger': logger, - 'logging': logging }) + def on_close(self): + logger.info('Socket closed!') - @contextmanager - def logging(self, logger): - self.capture_logging = True - self.logger_application = logging.getLogger(logger) - self.log_capture_string = io.StringIO() - ch = logging.StreamHandler(self.log_capture_string) - self.logger_application.addHandler(ch) - self.update_logging() - yield self.capture_logging + def send_log(self, logger, logging): + self.write_message({'type': 'log', + 'logger': logger, + 'logging': logging }) - self.log_capture_string.close() - self.logger_application.removeHandler(ch) - self.capture_logging = False - return self.capture_logging - + def run_simulation(self): + # Run simulation and capture logs + with self.logging(self.application.model.name): + self.application.model.run(self.config) + + trials = [] + for i in range(self.config['num_trials']): + trials.append('{}_trial_{}'.format(self.name, i)) + self.write_message({'type': 'trials', + 'data': trials }) + + @contextmanager + def logging(self, logger): + self.capture_logging = True + self.logger_application = logging.getLogger(logger) + self.log_capture_string = io.StringIO() + ch = logging.StreamHandler(self.log_capture_string) + self.logger_application.addHandler(ch) + self.update_logging() + yield self.capture_logging + + self.log_capture_string.close() + self.logger_application.removeHandler(ch) + self.capture_logging = False + return self.capture_logging + class ModularServer(tornado.web.Application): - """ Main visualization application. """ + """ Main visualization application. """ - portrayal_method = None - port = 8001 - model_args = () - model_kwargs = {} - page_handler = (r'/', PageHandler) - socket_handler = (r'/ws', SocketHandler) - static_handler = (r'/(.*)', tornado.web.StaticFileHandler, - {'path': 'templates'}) - local_handler = (r'/local/(.*)', tornado.web.StaticFileHandler, - {'path': ''}) + portrayal_method = None + port = 8001 + model_args = () + model_kwargs = {} + page_handler = (r'/', PageHandler) + socket_handler = (r'/ws', SocketHandler) + static_handler = (r'/(.*)', tornado.web.StaticFileHandler, + {'path': 'templates'}) + local_handler = (r'/local/(.*)', tornado.web.StaticFileHandler, + {'path': ''}) - handlers = [page_handler, socket_handler, static_handler, local_handler] - settings = {'debug': True, - 'template_path': os.path.dirname(__file__) + '/templates'} + handlers = [page_handler, socket_handler, static_handler, local_handler] + settings = {'debug': True, + 'template_path': os.path.dirname(__file__) + '/templates'} - def __init__(self, model, visualization_element, name='SOIL Model', verbose=True, - *args, **kwargs): - - self.verbose = verbose - self.package_includes = set() - self.local_includes = set() - self.js_code = [] - - self.visualization_element = visualization_element + def __init__(self, model, visualization_element, name='SOIL Model', verbose=True, + *args, **kwargs): + + self.verbose = verbose + self.package_includes = set() + self.local_includes = set() + self.js_code = [] + + self.visualization_element = visualization_element - self.model_name = name - self.model = model - self.model_args = args - self.model_kwargs = kwargs - #self.reset_model() + self.model_name = name + self.model = model + self.model_args = args + self.model_kwargs = kwargs + #self.reset_model() - # Initializing the application itself: - super().__init__(self.handlers, **self.settings) + # Initializing the application itself: + super().__init__(self.handlers, **self.settings) - ''' - def reset_model(self): - self.model = self.model_cls(*self.model_args, **self.model_kwargs) - ''' + ''' + def reset_model(self): + self.model = self.model_cls(*self.model_args, **self.model_kwargs) + ''' - def render_model(self): - return self.visualization_element.render(self.model) + def render_model(self): + return self.visualization_element.render(self.model) - def launch(self, port=None): - """ Run the app. """ - - if port is not None: - self.port = port - url = 'http://127.0.0.1:{PORT}'.format(PORT=self.port) - print('Interface starting at {url}'.format(url=url)) - self.listen(self.port) - # webbrowser.open(url) - tornado.ioloop.IOLoop.instance().start() + def launch(self, port=None): + """ Run the app. """ + + if port is not None: + self.port = port + url = 'http://127.0.0.1:{PORT}'.format(PORT=self.port) + print('Interface starting at {url}'.format(url=url)) + self.listen(self.port) + # webbrowser.open(url) + tornado.ioloop.IOLoop.instance().start() diff --git a/templates/css/main.css b/templates/css/main.css index e329697..6168a65 100644 --- a/templates/css/main.css +++ b/templates/css/main.css @@ -24,6 +24,10 @@ html, body { margin-right: 10px !important; } +.nav.navbar-right a { + outline: none !important; +} + .dropdown-menu > li > a:hover { background-color: #d4d3d3; cursor: pointer; @@ -398,3 +402,9 @@ table#link-distance .max { justify-content: center; align-items: center; } + +/** MODAL **/ +.modal-footer, +.modal-header { + border: none !important; +} diff --git a/templates/index.html b/templates/index.html index c81c55d..f51c8eb 100644 --- a/templates/index.html +++ b/templates/index.html @@ -97,40 +97,38 @@ $('.config-item #properties').change(function() { self.GraphVisualization.update_graph($(this).val(), slider.value(), function() { update_statistics_table(); - }) + }); }); - chart_nodes = c3.generate({ - size: { - width: width_chart, - height: height_chart - }, - data: { - columns: [], - type: 'area-spline' - }, - axis: { - x: { label: 'Time' }, - y: { label: 'Number of nodes' } - }, - point: { show: false }, - bindto: '#chart_nodes' + // Run simulation + $('#simulation_modal .btn-success').click(function() { + if ( !jQuery.isEmptyObject(run_simulation()) ) { + self.GraphVisualization.reset(); + $('#load').show().addClass('loader');; + _socket.send(run_simulation(), 'run_simulation'); + $('.console').append('
'); + } + $('#simulation_modal').modal('hide') }); - chart_attrs = c3.generate({ - size: { - width: width_chart, - height: height_chart - }, - data: { - columns: [], - type: 'area-spline' - }, - axis: { - x: { label: 'Time' }, - y: { label: 'Attributes' } - }, - point: { show: false }, - bindto: '#chart_attrs' + + chart_nodes = create_chart(width_chart, height_chart, 'Time', 'Number of nodes', '#chart_nodes'); + chart_attrs = create_chart(width_chart, height_chart, 'Time', 'Attributes', '#chart_attrs'); + + // Fill modal window + $('#simulation_modal').on('show.bs.modal', function(e) { + var variables = run_simulation() + var x = 0, + row; + for (var i in variables) { + if ( x % 2 === 0 ) row = $('').appendTo('#simulation_modal table tbody'); + $('').text(i).appendTo(row); + $('').text(variables[i]).appendTo(row); + x++; + } + }); + + $('#simulation_modal').on('hide.bs.modal', function(e) { + $('#simulation_modal table tbody').empty(); }); } @@ -190,7 +188,7 @@