9
13

More than 5 years have passed since last update.

DashでグラフをLive Updateする(ver0.37へ対応)

Last updated at Posted at 2019-04-18

Live Updateしようとしたら、つまずいたので備忘のためのメモとして残すことにしました。

この記事の概要

  • Dash(plolty)で定期的にグラフを更新しようとしたら、エラーが出た
  • 原因:'Event'が無くなっていた
  • 対応:Interval componentをInputにする

Dashとは

Python製のWebアプリケーションのフレームワークです。Plotlyを使用して簡単に可視化ができます。
詳細は、公式を見てください。

経緯

このPlotly Dashで簡単に可視化ができるWebアプリを作るという記事を参考に、グラフのLive Updateを試そうとしたところ、
以下のエラーが出て、少し困りました。。
ImportError: cannot import name 'Event'

参考にした記事での該当部分は、以下の様なデコレータの箇所です。

@app.callback(Output('live-update-text', 'children'),
              events=[Event('interval-component', 'interval')])
def update_graph_live():

原因

Google先生に聞くと、何やらDashのversion 0.37から'Event'が無くなっているそうです。
他にも困ってる人いました。
How to fix ImportError: cannot import name 'Event' in Dash from plotly (python)?
どうしたらえぇや?と調べてると、ちゃんと公式にやり方が書いてありました。

対応

対応方法は、公式のLive Updating Components通りで、EventをInputに書き換えます。
具体的には、上記の該当部分を以下の様に編集しました。

@app.callback(Output('live-update-text', 'children'),
              [Input('interval-component', 'n_intervals')])
def update_graph_live(n):

参考記事の「システムモニタ」をver0.37で動作するように修正すると、以下の様になります。

import jupyterlab_dash #今回はJupyterlabを使用
import dash
import dash_html_components as html
#from dash.dependencies import Input, Output, Event
from dash.dependencies import Input, Output #Eventはもういない。。
import dash_core_components as dcc
import datetime
import plotly
import numpy as np
import pandas as pd
import psutil

viewer = jupyterlab_dash.AppViewer()

colors = {
    'background': '#111111',
    'text': '#7FDBFF'
}

app = dash.Dash(__name__)
app.layout = html.Div(children=[
    html.Div([
        html.H4('システムモニタ'),
        html.Div(id='live-update-text'),
        dcc.Graph(id='live-update-graph'),
        html.H4('プロセスリスト'),
        html.Div(id='live-update-proc'),
        dcc.Interval(
            id='interval-component',
            interval=1*1000, # in milliseconds
            n_intervals=0 #公式に沿って追加
        )
    ])
],
    style={'backgroundColor': colors['background'], 'color': colors['text']}
)


class Context:
    def __init__(self):
        self.t = []
        self.cpu = []
        self.per_cpu = [[] for x in range(psutil.cpu_count())]
        self.mem = []

    @classmethod
    def append_data(cls, d1, d2):
        n = len(d1)
        if n > 100:
            del d1[0:n - 99]
        d1.append(d2)


context = Context()

# The `dcc.Interval` component emits an event called "interval"
# every `interval` number of milliseconds.
# Subscribe to this event with the `events` argument of `app.callback`


@app.callback(Output('live-update-text', 'children'),
              #events=[Event('interval-component', 'interval')])
              [Input('interval-component', 'n_intervals')])
#def update_metrics():
def update_metrics(n):
    now = datetime.datetime.now()
    hour, minute, second = now.hour, now.minute, now.second
    style = {'padding': '5px', 'fontSize': '16px'}
    return [
        html.Span('CPU: {}%'.format(context.cpu[-1]), style=style),
        html.Span('Memory: {}%'.format(context.mem[-1]), style=style)
    ]


# Multiple components can update everytime interval gets fired.
@app.callback(Output('live-update-graph', 'figure'),
              #events=[Event('interval-component', 'interval')])
              [Input('interval-component', 'n_intervals')])
#def update_graph_live():
def update_graph_live(n):
    # global context
    context.append_data(context.t, datetime.datetime.now())
    context.append_data(context.cpu, psutil.cpu_percent())
    for data, pct in zip(context.per_cpu, psutil.cpu_percent(percpu=True)):
        context.append_data(data, pct)
    context.append_data(context.mem, psutil.virtual_memory().percent)

    # Create the graph with subplots
    fig = plotly.tools.make_subplots(rows=2, cols=1, vertical_spacing=0.2)
    fig['layout']['margin'] = {
        'l': 30, 'r': 10, 'b': 30, 't': 10
    }
    fig['layout']['plot_bgcolor'] = colors['background']
    fig['layout']['paper_bgcolor'] = colors['background']
    fig['layout']['font'] = {'color': colors['text']}
    fig['layout']['legend'] = {'x': 0, 'y': 1, 'xanchor': 'left'}
    fig['layout']['yaxis1'].update(range=[0, 100])
    fig['layout']['yaxis2'].update(range=[0, 100])

    fig.append_trace({
        'x': context.t,
        'y': context.cpu,
        'name': 'cpu',
        'mode': 'lines',
        'type': 'scatter',
    }, 1, 1)
    for i, y in enumerate(context.per_cpu):
        fig.append_trace({
            'x': context.t,
            'y': y,
            'name': 'cpu {}'.format(i),
            'mode': 'lines',
            'type': 'scatter',
        }, 1, 1)
    fig.append_trace({
        'x': context.t,
        'y': context.mem,
        'name': 'memory',
        'mode': 'lines',
        'type': 'scatter',
        'fill': 'tonexty',
    }, 2, 1)

    return fig


def get_proc_df():
    def get_proc(proc):
        try:
            pinfo = proc
        except psutil.NoSuchProcess:
            pass
        return (pinfo.pid, pinfo.name(), pinfo.memory_percent(), pinfo.cpu_percent())

    data = [get_proc(proc) for proc in psutil.process_iter()]
    df = pd.DataFrame(data, columns=['pid', 'name', 'memory', 'cpu'])
    df['memory'] = df['memory'].map(lambda x: '{:.2f}%'.format(x))
    df['cpu'] = df['cpu'] / psutil.cpu_count()
    df['cpu'] = df['cpu'].map(lambda x: '{:.2f}%'.format(x))
    return df.sort_values('cpu', ascending=False)


@app.callback(Output('live-update-proc', 'children'),
              #events=[Event('interval-component', 'interval')])
              [Input('interval-component', 'n_intervals')])
#def generate_table():
def generate_table(n):
    df = get_proc_df()
    max_rows = 10
    return html.Table(
        # Header
        [html.Tr([html.Th(col) for col in df.columns])] +

        # Body
        [html.Tr([
            html.Td(df.iloc[i][col], style={'width': '8em'}) for col in df.columns
        ]) for i in range(min(len(df), max_rows))]
    )

#if __name__ == '__main__':
#    app.run_server(debug=True)

viewer.show(app)
9
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
13