#!/usr/bin/env python from dash.dependencies import Input, Output import dash import dash_core_components as dcc import dash_html_components as html import pandas as pd from functools import lru_cache import argparse import sqlite3 import time import sys import datetime app = dash.Dash(__name__) def get_topics(db): cursor = db.cursor() query = 'SELECT name FROM sqlite_master WHERE type="table"' q = cursor.execute(query) topics = [t[0] for t in q.fetchall()] return topics # Since we're adding callbacks to elements that don't exist in the app.layout, # Dash will raise an exception to warn us that we might be # doing something wrong. # In this case, we're adding the elements through a callback, so we can ignore # the exception. app.config.suppress_callback_exceptions = True app.layout = html.Div([ dcc.Location(id='url', refresh=False), html.Div(id='page-content') ]) @lru_cache(maxsize=10) def get_data(topic, timerange, ts): start = time.time() conn = sqlite3.connect(db) table = topic earliest = datetime.datetime.now() - timerange query = f"SELECT * FROM \"{table}\" WHERE timestamp > '{earliest}'" df = pd.read_sql(query, conn, parse_dates=[ 'timestamp'], index_col='timestamp') print(f"Obtained data in {time.time()-start}") return df plot_page = html.Div([ dcc.Interval( id='interval-component', interval=4*1000, # in milliseconds n_intervals=0), html.Div(id='range-controls', children=[ dcc.Input(id="range_count", type="number", value=1, debounce=True, min=1), dcc.Dropdown( id='timerange', options=[ {'label': 'hours', 'value': 'h'}, {'label': 'days', 'value': 'd'}, {'label': 'weeks', 'value': 'w'}, {'label': 'all', 'value': 'all'} ], value='d' ), ]), dcc.Graph(id='graph', animate=False, responsive=True), dcc.Checklist(id='topic-checklist', className="checklist-container", inputClassName="checklist-input"), dcc.Link('Back', href='/') ]) def get_timedelta(range_str, count): range_to_delta = { 'h': datetime.timedelta(hours=count), 'd': datetime.timedelta(days=count), 'w': datetime.timedelta(weeks=count), 'all': datetime.timedelta(weeks=10000), } return range_to_delta[range_str] @app.callback([Output('topic-checklist', 'options'), Output('topic-checklist', 'value')], [Input('url', 'pathname')]) def update_topic_checklist(pathname): pathname = pathname.strip('/') if pathname: topics = pathname.strip('/').split('+') selected = topics else: topics = all_topics selected = [] return [{"label":topic, "value":topic, "disabled":False} for topic in sorted(topics)], selected @app.callback(Output('graph', 'figure'), [ Input('timerange', 'value'), Input('range_count', 'value'), Input('topic-checklist', 'value')]) def update_graph(timerange, range_count, topics): if topics: print(f"topics: {topics}") delta = get_timedelta(timerange, range_count) now = int(time.time()) def make_data(df, name): return { 'x': df.index, 'y': df['value'], 'type': 'scatter', 'mode': 'lines', 'name': name } return { 'data': [make_data(get_data(topic, delta, now), topic) for topic in topics]} else: return {}, [] # Update the index @app.callback(dash.dependencies.Output('page-content', 'children'), [dash.dependencies.Input('url', 'pathname')]) def display_page(pathname): return plot_page # You could also return a 404 "URL not found" page here if __name__ == '__main__': options = argparse.ArgumentParser() options.add_argument('--debug', '-d', action='store_true') options.add_argument('db') args = options.parse_args() db = args.db conn = sqlite3.connect(db) all_topics = get_topics(conn) index_page = html.Div(sum([[ dcc.Link(f'Go to {page}', href=f'/{page}'), html.Br()] for page in all_topics], [])) app.run_server(debug=args.debug, host='0.0.0.0')