diff --git a/run.py b/run.py index dd3abdd..7d57b95 100644 --- a/run.py +++ b/run.py @@ -11,5 +11,5 @@ def run(model, params=None): if __name__ == "__main__": - soil = Model(dump=False) + soil = Model(dump=True) run(soil) diff --git a/server.py b/server.py index 7ca841c..dd2cc4c 100644 --- a/server.py +++ b/server.py @@ -9,6 +9,12 @@ import webbrowser import yaml import logging +import logging +import threading +import io +from datetime import timedelta +from contextlib import contextmanager + logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) @@ -54,6 +60,7 @@ class SocketHandler(tornado.websocket.WebSocketHandler): def open(self): if self.application.verbose: logger.info('Socket opened!') + def check_origin(self, origin): return True @@ -79,9 +86,12 @@ class SocketHandler(tornado.websocket.WebSocketHandler): return else: config = config[0] + self.send_log('INFO.soil', 'Using config: {name}'.format(name=config['name'])) self.name = config['name'] - self.application.model.run(config) + + with self.logging(self.application.model.name): + self.application.model.run(config) trials = [] for i in range(config['num_trials']): @@ -92,6 +102,7 @@ class SocketHandler(tornado.websocket.WebSocketHandler): 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']) }) @@ -99,6 +110,41 @@ class SocketHandler(tornado.websocket.WebSocketHandler): if self.application.verbose: logger.info('Unexpected message!') + 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.01, self.update_logging) + thread.start() + + def on_close(self): + logger.info('Socket closed!') + + def send_log(self, logger, logging): + self.write_message({'type': 'log', + 'logger': logger, + 'logging': logging }) + + @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.capture_logging = False + self.log_capture_string.close() + self.logger_application.removeHandler(ch) + return self.capture_logging + + class ModularServer(tornado.web.Application): """ Main visualization application. """ @@ -110,7 +156,6 @@ class ModularServer(tornado.web.Application): page_handler = (r'/', PageHandler) socket_handler = (r'/ws', SocketHandler) static_handler = (r'/(.*)', tornado.web.StaticFileHandler, - # {'path': os.path.dirname(__file__)}) {'path': 'templates'}) local_handler = (r'/local/(.*)', tornado.web.StaticFileHandler, {'path': ''}) @@ -133,7 +178,6 @@ class ModularServer(tornado.web.Application): self.model = model self.model_args = args self.model_kwargs = kwargs - #self.reset_model() # Initializing the application itself: @@ -155,5 +199,5 @@ class ModularServer(tornado.web.Application): 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) + # webbrowser.open(url) tornado.ioloop.IOLoop.instance().start() diff --git a/templates/css/main.css b/templates/css/main.css index 75abcee..2e08cce 100644 --- a/templates/css/main.css +++ b/templates/css/main.css @@ -1,4 +1,20 @@ +html, body, .carousel-inner { + height: 100%; +} + +.carousel { + height: calc(100% - 150px); +} + +.carousel-indicators li { + border-color: black; +} + +.carousel-indicators li.active { + background-color: black; +} + .node { stroke: #fff; stroke-width: 1.5px; @@ -236,3 +252,42 @@ table#link-distance .min, table#link-distance .max { font-weight: normal !important; } + +/* Console */ + +#update, .console { + padding: 10px 15px; + height: 135px; + border: 1px solid #585858; +} + +#update { + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; +} + +.container-fluid.fixed { + padding-top: 15px; +} + +.console { + background-color: rgb(88,88,88); + font-family: "Ubuntu Mono"; + font-size: 14px; + font-weight: 500; + color: white; + line-height: 14px; + overflow: auto; + border-top-left-radius: 5px; + border-bottom-left-radius: 5px; +} + +.console::-webkit-scrollbar { + width: 6px; + background-color: #F5F5F5; +} + +.console::-webkit-scrollbar-thumb { + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); + background-color: #555; +} diff --git a/templates/index.html b/templates/index.html index 860177d..428ee62 100644 --- a/templates/index.html +++ b/templates/index.html @@ -28,13 +28,14 @@ + {{ model_name }} - - - -
- - - - - - -
- - - - -
-
- -
- - - -
-
- - - - - + + + + + \ No newline at end of file diff --git a/templates/js/socket.js b/templates/js/socket.js index dab0193..9527edb 100755 --- a/templates/js/socket.js +++ b/templates/js/socket.js @@ -34,6 +34,12 @@ ws.onmessage = function(message) { console.log(msg['error']); _socket.error(msg['error']); $('#load').removeClass('loader'); + break; + + case 'log': + console.log(msg['logging']) + $('.console').append(msg['logger'] + ': ' + msg['logging'] + '
'); + $('.console').animate({ scrollTop: $(window).height() }, 'slow'); default: console.log('Unexpected message!') diff --git a/templates/js/visualization.js b/templates/js/visualization.js index 2e865ed..3e423e0 100644 --- a/templates/js/visualization.js +++ b/templates/js/visualization.js @@ -12,13 +12,13 @@ */ // Private constants - var width = window.innerWidth * 0.75, - height = window.innerHeight * 4 / 5, - focus_opacity = 0.1, + var focus_opacity = 0.1, radius = 8; // Private variables - var graph, // JSON data for the graph + var width, + height, + graph, // JSON data for the graph model, // Definition of the attributes of the nodes linkedByIndex, // Nodes linked by index name, // Name of the graph (id for svg item) @@ -260,13 +260,16 @@ * @param {object} id The id of the svg item. * @return {object} This class. */ - function create(id, callback) { + function create(id, n_height, n_width, callback) { name = id; svg = d3.select('svg#' + name) - .attr('width', width) - .attr('height', height) + .attr('width', n_width) + .attr('height', n_height) .style('background-color', 'rgba(128,128,128,0.1)'); + height = n_height; + width = n_width + if (callback) { callback(this.GraphVisualization); } else { return this.GraphVisualization } } diff --git a/visualization.py b/visualization.py index 1ea6f45..5356aaa 100644 --- a/visualization.py +++ b/visualization.py @@ -8,6 +8,7 @@ from xml.etree import ElementTree class Model(): def __init__(self, dump=True, dir_path='output'): + self.name = 'soil' self.dump = dump self.dir_path = dir_path @@ -20,9 +21,10 @@ class Model(): sim.dump = self.dump print('Dumping results to {} : {}'.format(sim.dir_path, sim.dump)) - + sim.run_simulation() + def get_trial(self, name, trial): graph = nx.read_gexf(os.path.join(self.dir_path, name, '{}_trial_{}.gexf'.format(name, trial)))