From 9165979b49175f2254ce9769ccfe01cc591b85ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20Fernando=20S=C3=A1nchez?= Date: Fri, 7 Dec 2018 18:28:19 +0100 Subject: [PATCH] merge visualization branch The web server is included as a submodule. The dependencies for the web (tornado) are not installed by default, but they can be installed as an extra: ``` pip install soil[web] ``` Once installed, the soil web can be used like this: ``` soil-web OR python -m soil.web ``` There are other minor changes: * History re-connects to the sqlite database if it is used from a different thread. * Environment accepts additional parameters (so it can run simulations with `visualization_params` or any other in the future). * The simulator class is no longer necessary * Logging is done in the same thread, and the simulation is run in a separate one. This had to be done because it was creating some problems with tornado not being able to find the current thread during logs, which caused hundreds of repeated lines in the web "console". * The player is slightly modified in this version. I noticed that when the visualization was playing, if you clicked somewhere it would change for a second, and then go back to the previous place. The code for the playback seemed too complex, especially speed control, so I rewrote some parts. I might've introduced new bugs. --- Dockerfile | 2 + docker-compose.yml | 2 + setup.py | 7 +- soil/VERSION | 2 +- soil/history.py | 50 +++++--- soil/simulation.py | 2 +- soil/web/{server.py => __init__.py} | 108 +++++++++++++----- soil/web/__main__.py | 5 + soil/web/simulator.py | 36 ------ soil/web/{templates => static}/css/main.css | 0 .../{templates => static}/css/timeline.css | 0 .../img/background/map.png | Bin .../img/background/map_4800x2860.jpg | Bin .../{templates => static}/img/logo_gsi.svg | 0 .../{templates => static}/img/logo_soil.png | Bin .../{templates => static}/img/svg/home.svg | 0 .../{templates => static}/img/svg/person.svg | 0 .../{templates => static}/img/svg/plus.svg | 0 .../{templates => static}/img/svg/target.svg | 0 .../{templates => static}/img/svg/time.svg | 0 soil/web/{templates => static}/js/socket.js | 85 +++++++------- soil/web/{templates => static}/js/template.js | 0 soil/web/{templates => static}/js/timeline.js | 0 .../{templates => static}/js/visualization.js | 0 soil/web/templates/index.html | 4 +- 25 files changed, 174 insertions(+), 129 deletions(-) rename soil/web/{server.py => __init__.py} (67%) create mode 100644 soil/web/__main__.py delete mode 100644 soil/web/simulator.py rename soil/web/{templates => static}/css/main.css (100%) rename soil/web/{templates => static}/css/timeline.css (100%) rename soil/web/{templates => static}/img/background/map.png (100%) rename soil/web/{templates => static}/img/background/map_4800x2860.jpg (100%) rename soil/web/{templates => static}/img/logo_gsi.svg (100%) rename soil/web/{templates => static}/img/logo_soil.png (100%) rename soil/web/{templates => static}/img/svg/home.svg (100%) rename soil/web/{templates => static}/img/svg/person.svg (100%) rename soil/web/{templates => static}/img/svg/plus.svg (100%) rename soil/web/{templates => static}/img/svg/target.svg (100%) rename soil/web/{templates => static}/img/svg/time.svg (100%) rename soil/web/{templates => static}/js/socket.js (90%) rename soil/web/{templates => static}/js/template.js (100%) rename soil/web/{templates => static}/js/timeline.js (100%) rename soil/web/{templates => static}/js/visualization.js (100%) diff --git a/Dockerfile b/Dockerfile index 5391343..2feb5a3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,5 @@ FROM python:3.4-onbuild +RUN pip install '.[web]' + ENTRYPOINT ["python", "-m", "soil"] diff --git a/docker-compose.yml b/docker-compose.yml index eccac5a..dfb338e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,3 +6,5 @@ services: - .:/usr/src/app tty: true entrypoint: /bin/bash + ports: + - '8001:8001' diff --git a/setup.py b/setup.py index af30134..4896213 100644 --- a/setup.py +++ b/setup.py @@ -40,10 +40,15 @@ setup( 'Operating System :: POSIX', 'Programming Language :: Python :: 3'], install_requires=install_reqs, + extras_require={ + 'web': ['tornado'] + + }, tests_require=test_reqs, setup_requires=['pytest-runner', ], include_package_data=True, entry_points={ 'console_scripts': - ['soil = soil.__init__:main'] + ['soil = soil.__init__:main', + 'soil-web = soil.web.__init__:main'] }) diff --git a/soil/VERSION b/soil/VERSION index 2bb6a82..d33c3a2 100644 --- a/soil/VERSION +++ b/soil/VERSION @@ -1 +1 @@ -0.11.3 \ No newline at end of file +0.12.0 \ No newline at end of file diff --git a/soil/history.py b/soil/history.py index 72ced57..37720a5 100644 --- a/soil/history.py +++ b/soil/history.py @@ -23,16 +23,14 @@ class History: if backup and os.path.exists(db_path): newname = db_path + '.backup{}.sqlite'.format(time.time()) os.rename(db_path, newname) - self._db_path = db_path - if isinstance(db_path, str): - self._db = sqlite3.connect(db_path) - else: - self._db = db_path + self.db_path = db_path + + self.db = db_path - with self._db: - self._db.execute('''CREATE TABLE IF NOT EXISTS history (agent_id text, t_step int, key text, value text text)''') - self._db.execute('''CREATE TABLE IF NOT EXISTS value_types (key text, value_type text)''') - self._db.execute('''CREATE UNIQUE INDEX IF NOT EXISTS idx_history ON history (agent_id, t_step, key);''') + with self.db: + self.db.execute('''CREATE TABLE IF NOT EXISTS history (agent_id text, t_step int, key text, value text text)''') + self.db.execute('''CREATE TABLE IF NOT EXISTS value_types (key text, value_type text)''') + self.db.execute('''CREATE UNIQUE INDEX IF NOT EXISTS idx_history ON history (agent_id, t_step, key);''') self._dtypes = {} self._tups = [] @@ -41,6 +39,22 @@ class History: if key not in self._dtypes: self.read_types() return self._dtypes[key] + + @property + def db(self): + try: + self._db.cursor() + except sqlite3.ProgrammingError: + self.db = None # Reset the database + return self._db + + @db.setter + def db(self, db_path=None): + db_path = db_path or self.db_path + if isinstance(db_path, str): + self._db = sqlite3.connect(db_path) + else: + self._db = db_path @property def dtypes(self): @@ -50,7 +64,7 @@ class History: self.save_records(Record(*tup) for tup in tuples) def save_records(self, records): - with self._db: + with self.db: for rec in records: if not isinstance(rec, Record): rec = Record(*rec) @@ -59,8 +73,8 @@ class History: serializer = utils.serializer(name) deserializer = utils.deserializer(name) self._dtypes[rec.key] = (name, serializer, deserializer) - self._db.execute("replace into value_types (key, value_type) values (?, ?)", (rec.key, name)) - self._db.execute("replace into history(agent_id, t_step, key, value) values (?, ?, ?, ?)", (rec.agent_id, rec.t_step, rec.key, rec.value)) + self.db.execute("replace into value_types (key, value_type) values (?, ?)", (rec.key, name)) + self.db.execute("replace into history(agent_id, t_step, key, value) values (?, ?, ?, ?)", (rec.agent_id, rec.t_step, rec.key, rec.value)) def save_record(self, *args, **kwargs): self._tups.append(Record(*args, **kwargs)) @@ -77,16 +91,16 @@ class History: def to_tuples(self): self.flush_cache() - with self._db: - res = self._db.execute("select agent_id, t_step, key, value from history ").fetchall() + with self.db: + res = self.db.execute("select agent_id, t_step, key, value from history ").fetchall() for r in res: agent_id, t_step, key, value = r _, _ , des = self.conversors(key) yield agent_id, t_step, key, des(value) def read_types(self): - with self._db: - res = self._db.execute("select key, value_type from value_types ").fetchall() + with self.db: + res = self.db.execute("select key, value_type from value_types ").fetchall() for k, v in res: serializer = utils.serializer(v) deserializer = utils.deserializer(v) @@ -143,7 +157,7 @@ class History: h1.key = h2.key and h1.t_step = h2.t_step '''.format(condition=condition) - last_df = pd.read_sql_query(last_query, self._db) + last_df = pd.read_sql_query(last_query, self.db) filters.append("t_step >= '{}' and t_step <= '{}'".format(min_step, max(t_steps))) @@ -151,7 +165,7 @@ class History: if filters: condition = 'where {} '.format(' and '.join(filters)) query = 'select * from history {} limit {}'.format(condition, limit) - df = pd.read_sql_query(query, self._db) + df = pd.read_sql_query(query, self.db) if last_df is not None: df = pd.concat([df, last_df]) diff --git a/soil/simulation.py b/soil/simulation.py index da5dbdf..f9aad8e 100644 --- a/soil/simulation.py +++ b/soil/simulation.py @@ -49,7 +49,7 @@ class SoilSimulation(NetworkSimulation): default_state=None, interval=1, dump=None, dry_run=False, dir_path=None, num_trials=1, max_time=100, agent_module=None, load_module=None, seed=None, - environment_agents=None, environment_params=None): + environment_agents=None, environment_params=None, **kwargs): if topology is None: topology = utils.load_network(network_params, diff --git a/soil/web/server.py b/soil/web/__init__.py similarity index 67% rename from soil/web/server.py rename to soil/web/__init__.py index e6d2695..97902d9 100644 --- a/soil/web/server.py +++ b/soil/web/__init__.py @@ -1,8 +1,10 @@ import io +import threading +import asyncio import logging import networkx as nx import os -import threading +import sys import tornado.ioloop import tornado.web import tornado.websocket @@ -14,9 +16,20 @@ from contextlib import contextmanager from time import sleep from xml.etree.ElementTree import tostring +from tornado.concurrent import run_on_executor +from concurrent.futures import ThreadPoolExecutor + +from ..simulation import SoilSimulation logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) +ROOT = os.path.abspath(os.path.dirname(__file__)) + +MAX_WORKERS = 4 +LOGGING_INTERVAL = 0.5 + +# Workaround to let Soil load the required modules +sys.path.append(ROOT) class PageHandler(tornado.web.RequestHandler): """ Handler for the HTML template which holds the visualization. """ @@ -28,6 +41,7 @@ class PageHandler(tornado.web.RequestHandler): class SocketHandler(tornado.websocket.WebSocketHandler): """ Handler for websocket. """ + executor = ThreadPoolExecutor(max_workers=MAX_WORKERS) def open(self): if self.application.verbose: @@ -55,9 +69,10 @@ class SocketHandler(tornado.websocket.WebSocketHandler): self.write_message({'type': 'error', 'error': error}) return - else: - self.config = self.config[0] - self.send_log('INFO.' + self.application.simulator.name, 'Using config: {name}'.format(name=self.config['name'])) + + self.config = self.config[0] + self.send_log('INFO.' + self.simulation_name, + 'Using config: {name}'.format(name=self.config['name'])) if 'visualization_params' in self.config: self.write_message({'type': 'visualization_params', @@ -91,17 +106,17 @@ class SocketHandler(tornado.websocket.WebSocketHandler): logger.info('Trial {} requested!'.format(msg['data'])) self.send_log('INFO.' + __name__, 'Trial {} requested!'.format(msg['data'])) self.write_message({'type': 'get_trial', - 'data': self.get_trial( int(msg['data']) ) }) + 'data': self.get_trial(int(msg['data']))}) elif msg['type'] == 'run_simulation': if self.application.verbose: logger.info('Running new simulation for {name}'.format(name=self.config['name'])) - self.send_log('INFO.' + self.application.simulator.name, 'Running new simulation for {name}'.format(name=self.config['name'])) + self.send_log('INFO.' + self.simulation_name, 'Running new simulation for {name}'.format(name=self.config['name'])) self.config['environment_params'] = msg['data'] self.run_simulation() elif msg['type'] == 'download_gexf': - G = self.simulation[ int(msg['data']) ].history_to_graph() + G = self.trials[ int(msg['data']) ].history_to_graph() for node in G.nodes(): if 'pos' in G.node[node]: G.node[node]['viz'] = {"position": {"x": G.node[node]['pos'][0], "y": G.node[node]['pos'][1], "z": 0.0}} @@ -113,7 +128,7 @@ class SocketHandler(tornado.websocket.WebSocketHandler): 'data': tostring(writer.xml).decode(writer.encoding) }) elif msg['type'] == 'download_json': - G = self.simulation[ int(msg['data']) ].history_to_graph() + G = self.trials[ int(msg['data']) ].history_to_graph() for node in G.nodes(): if 'pos' in G.node[node]: G.node[node]['viz'] = {"position": {"x": G.node[node]['pos'][0], "y": G.node[node]['pos'][1], "z": 0.0}} @@ -130,13 +145,13 @@ class SocketHandler(tornado.websocket.WebSocketHandler): try: if (not self.log_capture_string.closed and self.log_capture_string.getvalue()): for i in range(len(self.log_capture_string.getvalue().split('\n')) - 1): - self.send_log('INFO.' + self.application.simulator.name, self.log_capture_string.getvalue().split('\n')[i]) + self.send_log('INFO.' + self.simulation_name, self.log_capture_string.getvalue().split('\n')[i]) 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() + tornado.ioloop.IOLoop.current().call_later(LOGGING_INTERVAL, self.update_logging) + def on_close(self): if self.application.verbose: @@ -144,29 +159,45 @@ class SocketHandler(tornado.websocket.WebSocketHandler): def send_log(self, logger, logging): self.write_message({'type': 'log', - 'logger': logger, - 'logging': logging }) + 'logger': logger, + 'logging': logging}) + + @property + def simulation_name(self): + return self.config.get('name', 'NoSimulationRunning') + + @run_on_executor + def nonblocking(self, config): + simulation = SoilSimulation(**config) + return simulation.run() + @tornado.gen.coroutine def run_simulation(self): # Run simulation and capture logs + logger.info('Running simulation!') if 'visualization_params' in self.config: del self.config['visualization_params'] - with self.logging(self.application.simulator.name): + with self.logging(self.simulation_name): try: - self.simulation = self.application.simulator.run(self.config) - trials = [] - for i in range(self.config['num_trials']): - trials.append('{}_trial_{}'.format(self.name, i)) + config = dict(**self.config) + config['dir_path'] = os.path.join(self.application.dir_path, config['name']) + config['dump'] = self.application.dump + self.trials = yield self.nonblocking(config) + self.write_message({'type': 'trials', - 'data': trials }) - except: - error = 'Something went wrong. Please, try again.' + 'data': list(trial.name for trial in self.trials) }) + except Exception as ex: + error = 'Something went wrong:\n\t{}'.format(ex) + logging.info(error) self.write_message({'type': 'error', - 'error': error}) - self.send_log('ERROR.' + self.application.simulator.name, error) + 'error': error}) + self.send_log('ERROR.' + self.simulation_name, error) def get_trial(self, trial): - G = self.simulation[trial].history_to_graph() + logger.info('Available trials: %s ' % len(self.trials)) + logger.info('Ask for : %s' % trial) + trial = self.trials[trial] + G = trial.history_to_graph() return nx.node_link_data(G) @contextmanager @@ -193,19 +224,20 @@ class ModularServer(tornado.web.Application): page_handler = (r'/', PageHandler) socket_handler = (r'/ws', SocketHandler) static_handler = (r'/(.*)', tornado.web.StaticFileHandler, - {'path': 'templates'}) + {'path': os.path.join(ROOT, 'static')}) 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'} + 'template_path': ROOT + '/templates'} - def __init__(self, simulator, name='SOIL', verbose=True, *args, **kwargs): + def __init__(self, dump=False, dir_path='output', name='SOIL', verbose=True, *args, **kwargs): self.verbose = verbose self.name = name - self.simulator = simulator + self.dump = dump + self.dir_path = dir_path # Initializing the application itself: super().__init__(self.handlers, **self.settings) @@ -220,3 +252,23 @@ class ModularServer(tornado.web.Application): self.listen(self.port) # webbrowser.open(url) tornado.ioloop.IOLoop.instance().start() + + +def run(*args, **kwargs): + asyncio.set_event_loop(asyncio.new_event_loop()) + server = ModularServer(*args, **kwargs) + server.launch() + + +def main(): + import argparse + + parser = argparse.ArgumentParser(description='Visualization of a Graph Model') + + parser.add_argument('--name', '-n', nargs=1, default='SOIL', help='name of the simulation') + parser.add_argument('--dump', '-d', help='dumping results in folder output', action='store_true') + parser.add_argument('--port', '-p', nargs=1, default=8001, help='port for launching the server') + parser.add_argument('--verbose', '-v', help='verbose mode', action='store_true') + args = parser.parse_args() + + run(name=args.name, port=(args.port[0] if isinstance(args.port, list) else args.port), verbose=args.verbose) diff --git a/soil/web/__main__.py b/soil/web/__main__.py new file mode 100644 index 0000000..5c211a8 --- /dev/null +++ b/soil/web/__main__.py @@ -0,0 +1,5 @@ +from . import main + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/soil/web/simulator.py b/soil/web/simulator.py deleted file mode 100644 index 5ff9009..0000000 --- a/soil/web/simulator.py +++ /dev/null @@ -1,36 +0,0 @@ -import os -import networkx as nx -from soil.simulation import SoilSimulation - - -class Simulator(): - """ Simulator for running simulations. Using SOIL.""" - - def __init__(self, dump=False, dir_path='output'): - self.name = 'soil' - self.dump = dump - self.dir_path = dir_path - - def run(self, config): - name = config['name'] - print('Using config(s): {name}'.format(name=name)) - - sim = SoilSimulation(**config) - sim.dir_path = os.path.join(self.dir_path, name) - sim.dump = self.dump - - print('Dumping results to {} : {}'.format(sim.dir_path, sim.dump)) - - simulation_results = sim.run_simulation() - - # G = simulation_results[0].history_to_graph() - # for node in G.nodes(): - # if 'pos' in G.node[node]: - # G.node[node]['viz'] = {"position": {"x": G.node[node]['pos'][0], "y": G.node[node]['pos'][1], "z": 0.0}} - # del (G.node[node]['pos']) - # nx.write_gexf(G, 'test.gexf', version='1.2draft') - - return simulation_results - - def reset(self): - pass diff --git a/soil/web/templates/css/main.css b/soil/web/static/css/main.css similarity index 100% rename from soil/web/templates/css/main.css rename to soil/web/static/css/main.css diff --git a/soil/web/templates/css/timeline.css b/soil/web/static/css/timeline.css similarity index 100% rename from soil/web/templates/css/timeline.css rename to soil/web/static/css/timeline.css diff --git a/soil/web/templates/img/background/map.png b/soil/web/static/img/background/map.png similarity index 100% rename from soil/web/templates/img/background/map.png rename to soil/web/static/img/background/map.png diff --git a/soil/web/templates/img/background/map_4800x2860.jpg b/soil/web/static/img/background/map_4800x2860.jpg similarity index 100% rename from soil/web/templates/img/background/map_4800x2860.jpg rename to soil/web/static/img/background/map_4800x2860.jpg diff --git a/soil/web/templates/img/logo_gsi.svg b/soil/web/static/img/logo_gsi.svg similarity index 100% rename from soil/web/templates/img/logo_gsi.svg rename to soil/web/static/img/logo_gsi.svg diff --git a/soil/web/templates/img/logo_soil.png b/soil/web/static/img/logo_soil.png similarity index 100% rename from soil/web/templates/img/logo_soil.png rename to soil/web/static/img/logo_soil.png diff --git a/soil/web/templates/img/svg/home.svg b/soil/web/static/img/svg/home.svg similarity index 100% rename from soil/web/templates/img/svg/home.svg rename to soil/web/static/img/svg/home.svg diff --git a/soil/web/templates/img/svg/person.svg b/soil/web/static/img/svg/person.svg similarity index 100% rename from soil/web/templates/img/svg/person.svg rename to soil/web/static/img/svg/person.svg diff --git a/soil/web/templates/img/svg/plus.svg b/soil/web/static/img/svg/plus.svg similarity index 100% rename from soil/web/templates/img/svg/plus.svg rename to soil/web/static/img/svg/plus.svg diff --git a/soil/web/templates/img/svg/target.svg b/soil/web/static/img/svg/target.svg similarity index 100% rename from soil/web/templates/img/svg/target.svg rename to soil/web/static/img/svg/target.svg diff --git a/soil/web/templates/img/svg/time.svg b/soil/web/static/img/svg/time.svg similarity index 100% rename from soil/web/templates/img/svg/time.svg rename to soil/web/static/img/svg/time.svg diff --git a/soil/web/templates/js/socket.js b/soil/web/static/js/socket.js similarity index 90% rename from soil/web/templates/js/socket.js rename to soil/web/static/js/socket.js index a490116..5aa8fe2 100755 --- a/soil/web/templates/js/socket.js +++ b/soil/web/static/js/socket.js @@ -239,17 +239,19 @@ var reset_configuration = function() { $('#download_json').off(); } +var slider; + var set_timeline = function(graph) { // 'Timeline' slider var [min, max] = get_limits(graph); - var stepUnix = (max - min) / 200; + var stepUnix = 1; var minUnix = (min !== Math.min()) ? min : 0; var maxUnix = (max !== Math.max()) ? max : minUnix + 20; slider = d3.slider(); d3.select('#slider3').attr('width', width).call( - slider.axis(true).min(minUnix).max(maxUnix).step(stepUnix).value(maxUnix) + slider.axis(true).min(minUnix).max(maxUnix).step(stepUnix).value(minUnix) .on('slide', function(evt, value) { self.GraphVisualization.update_graph($('.config-item #properties').val(), value, function() { update_statistics_table(); @@ -281,65 +283,64 @@ var set_timeline = function(graph) { // Button 'Play' $('button#button_play').on('click', function() { + play(); - $('button#button_play').addClass('pressed').prop("disabled", true); - $('#speed-slider').slider('disable'); - slider.step( 1 / speed ); - - if (slider.value() >= maxUnix) { - slider.value(minUnix); - self.GraphVisualization.update_graph($('.config-item #properties').val(), slider.value(), function() { - update_statistics_table(); - }); - setTimeout(player, 1000); - } else { - player(); - } - - var i = slider.value(); - function player() { - clearInterval(play); - play = setInterval(function() { - self.GraphVisualization.update_graph($('.config-item #properties').val(), slider.value(), function() { - update_statistics_table(); - }); - - if (slider.value() + slider.step() >= maxUnix) { - slider.value(maxUnix); - slider.step(stepUnix); - clearInterval(play); - $('button#button_play').removeClass('pressed').prop("disabled", false); - $('#speed-slider').slider('enable'); - } else { - slider.value(i); - i += slider.step(); - } - - }, 5); - } }); // Button 'Pause' $('button#button_pause').on('click', function() { - clearInterval(play); - slider.step(stepUnix); + stop(); $('button#button_play').removeClass('pressed').prop("disabled", false); - $('#speed-slider').slider('enable'); }); // Button 'Zoom to Fit' $('button#button_zoomFit').click(function() { self.GraphVisualization.fit(); }); } +var player; + +function play(){ + $('button#button_play').addClass('pressed').prop("disabled", true); + + if (slider.value() >= slider.max()) { + slider.value(slider.min()); + } + + var FRAME_INTERVAL = 100; + var speed_ratio = FRAME_INTERVAL / 1000 // speed=1 => 1 step per second + + nextStep = function() { + newvalue = Math.min(slider.value() + speed*speed_ratio, slider.max()); + console.log("new time value", newvalue); + slider.value(newvalue); + + self.GraphVisualization.update_graph($('.config-item #properties').val(), slider.value(), function () { + update_statistics_table(); + }); + + if (newvalue < slider.max()) { + player = setTimeout(nextStep, FRAME_INTERVAL); + } else { + $('button#button_play').removeClass('pressed').prop("disabled", false); + } + } + + player = setTimeout(nextStep, FRAME_INTERVAL); +} + +function stop() { + clearTimeout(player); +} + var reset_timeline = function() { // 'Timeline' slider $('#slider3').html(''); // 'Speed' slider - $('#speed-slider').slider('disable').slider('setValue', 1000); + // $('#speed-slider').slider('disable').slider('setValue', 1000); // Buttons - clearInterval(play); + stop(); $('button#button_play').off().removeClass('pressed').prop("disabled", false); $('button#button_pause').off(); $('button#button_zoomFit').off(); diff --git a/soil/web/templates/js/template.js b/soil/web/static/js/template.js similarity index 100% rename from soil/web/templates/js/template.js rename to soil/web/static/js/template.js diff --git a/soil/web/templates/js/timeline.js b/soil/web/static/js/timeline.js similarity index 100% rename from soil/web/templates/js/timeline.js rename to soil/web/static/js/timeline.js diff --git a/soil/web/templates/js/visualization.js b/soil/web/static/js/visualization.js similarity index 100% rename from soil/web/templates/js/visualization.js rename to soil/web/static/js/visualization.js diff --git a/soil/web/templates/index.html b/soil/web/templates/index.html index eb88b6b..13a373c 100644 --- a/soil/web/templates/index.html +++ b/soil/web/templates/index.html @@ -41,7 +41,7 @@ var width = window.innerWidth * 0.75, height = window.innerHeight * 3 / 5, - speed = 1000, + speed = 1, play, slider; @@ -255,7 +255,7 @@ max
- +