tsplot/tsplot

162 lines
4.4 KiB
Python
Executable File

#!/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')