Python {Article140}

ようこそ「Python」へ...

Python: DashのDataTableを使って仮想通貨を表形式で表示してホバリングしたときツールチップをポップアップさせるWebアプリを作る!【Dash実践】

ここでは、Dash(※)のDataTableを使用してGMO Coinの仮想通貨の収益率(累積リターン)を表示するWebアプリを作る方法を解説します。 Webページには、GMO Coinの全ての仮想通貨(コイン)のシンボル、コインの名称、累積リターンを表形式で表示します。 表のヘッダーにマウスをホバリングすると、ヘッダーのツールチップをポップアップさせます。 表の明細行の「シンボル」にマウスをホバリングすると、コインの名称と画像をツールチップとしてポップアップさせます。

さらに、仮想通貨の累積リターンの上位3位までに「絵文字」を表示させる方法についても説明します。 「絵文字」にマウスをホバリングするとツールチップをポップアップさせます。

※ Dashは、PythonでWebアプリケーションを作成するためのフレームワークです。 Plotlyのグラフ描画ライブラリを利用しており、データを視覚化するために最適化されています。 Dashは、PythonのWebフレームワークであるFlaskをベースにしており、 Pythonの標準ライブラリを使用しているので、 パフォーマンスが高く、実行速度が速いといった特徴があります。

Dashは、Pythonを使用したデータ分析やデータ視覚化のためのツールとして広く使用されており、 ビジネスインテリジェンス、科学、エンジニアリング、金融、医療、教育などのさまざまな分野で使用されています。 Dashを使用することで、カスタマイズされたWebアプリケーションを比較的簡単に作成できます。 Dashは、PythonによるWebアプリケーション開発において選択肢にいれておきたい開発ツールのひとつです。

説明文の左側に図の画像が表示されていますが縮小されています。 画像を拡大するにはマウスを画像上に移動してクリックします。 画像が拡大表示されます。拡大された画像を閉じるには右上の[X]をクリックします。 画像の任意の場所をクリックして閉じることもできます。
click image to zoom!
図A DataTable[1]
click image to zoom!
図B DataTable[2]
click image to zoom!
図C Tooltip[1]
click image to zoom!
図D Tooltip[2]
click image to zoom!
図E Tooltip[3]

DashのDataTableを使って仮想通貨の累積リターンを表示してツールチップをポップアップさせるWebアプリを作る

  1. まずは、Pythonの開発環境を準備する

    まずは、 「記事(Article137)」を参照して、 Pythonの開発環境を準備してください。 ここでは、Pythonのプロジェクトフォルダとして「Dash」を使用しています。

    click image to zoom!
    図1
    図1は、Visual Studio Code(VS Code)の「Terminal」メニューから「New Terminal」を選択して、 「Terminal」ウィンドウを開いたときの画面です。 緑色の「(venv)」が表示されていれば、 Pythonの仮想環境が正常に作成されていることになります。
  2. Visual Studio Codeを起動してプログラムファイルを作成する

    Pythonの開発環境の準備が完了したら、 VS Codeを起動して新規のPythonファイル(*.py)を作成します。 ここで作成したPythonのファイルには「リスト1」のコードをコピペします。

    リスト1:Article140.py
    # Dash DataTable Tooltips v00.py :  
    ### Import the libraries
    import os
    import math
    import numpy as np
    import pandas as pd
    import requests
    
    import datetime as dt
    from datetime import timedelta
    from time import sleep
    
    from dash import Dash, dash_table, html, Output, Input, State, dcc, ctx
    from dash.dash_table import DataTable, FormatTemplate
    from dash.dash_table.Format import Format, Scheme, Align, Trim
    import dash_bootstrap_components as dbc
    
    from lib.com_lib import get_data, calculate_cum_log_return, add_coin_name
    from lib.gmo_api import get_crypto, get_crypto_symbols
    
    import warnings
    warnings.simplefilter('ignore')
    
    #################################
    # Main
    #################################
    
    ### Load the data from gmo coin
    
    # get all symbols from gmo coin 
    df = get_crypto_symbols()
    symbols = df['symbol'].values.tolist()
    
    interval = '1day' 
    date_list = ['2020','2021','2022','2023'] 
    
    symbol_list = []
    cum_log_return_list = []
    cum_log_return_pct_list = []
    for symbol in symbols:
        csv_file = f"datasets/csv/gmo_crypto_2020_2023({symbol})_{interval}.csv"  
        isFile = os.path.isfile(csv_file)
        if not isFile: 
            for date in date_list:  # '2020','2021','2022','2023'
                df = get_crypto(symbol, interval, date)    # get n rows from starting date
                if not df.empty: 
                    df.to_csv(csv_file, index=True)
        # end of if not isFile:
        isFile = os.path.isfile(csv_file)
        if isFile:
            df = get_data(csv_file)
            if not df.empty:
                df = calculate_cum_log_return(df)
                df.replace([np.inf, -np.inf], np.nan).dropna(axis=1, inplace=True)
                cum_log_return = df.iloc[-1]['cum_log_return']
                cum_log_return_pct = df.iloc[-1]['cum_log_return_pct']
                symbol_list.append(symbol)
                cum_log_return_list.append(cum_log_return)
                cum_log_return_pct_list.append(cum_log_return_pct)
    # end of for symbol in symbols:
    
    ### Create DataFrame from dict
    data = {
        'symbol': symbol_list,
        'cum_log_return': cum_log_return_list,
        'cum_log_return_pct': cum_log_return_pct_list
    }
    
    raw_df = pd.DataFrame(data)
    if raw_df.empty:
        print(f"Quit the program due to raw_df is empty: {raw_df.empty=}")
        quit()
    
    ### Replace np.inf or -np.inf (positive or negative infinity) with np.nan(Not A Number)
    df = raw_df.replace([np.inf, -np.inf], np.nan)
    ### Drop rows if np.nan (Not A Number)
    df.dropna(axis=0, inplace=True)
    df['cum_log_return_pct'] = df['cum_log_return']
    
    ### Add a coin name
    add_coin_name(df)  
    
    ### Instantiate Dash Class
    app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])   
    
    ### Set Dash DataTable Properties
    data = df.to_dict('records')
    
    columns = [
        dict(id='symbol', name='Symbol', type='text'),
        dict(id='coin_name', name='Coin Name', type='text'),    
        dict(id='cum_log_return', name='Cum Log Return', type='numeric'),
        dict(id='cum_log_return_pct', name='Cum Log Return (%)', type='numeric')
    ]
    
    ### Layout a Web Page
    app.layout = dbc.Container(
        [ 
            dbc.Row([
                html.H3('GMO Coin: Cryptocurrencies by Cumulative Log Return'),   
                html.H5('Dash DataTable Tooltips Default Style'),
            ]),
            dbc.Row(html.Br()),
            
            dbc.Row(
                dash_table.DataTable(
                    id='dt',
                    data=data,
                    columns=columns,        
                ),
            ),
            dbc.Row(html.Br()),
          
        ], # fluid=False,
    )  
    
    ### Run the server
    if __name__ == '__main__':
        app.run_server(debug=True)   
    click image to zoom!
    図2
    図2は、VS Codeの編集画面にプログラムのソースコードが表示されている画面です。
  3. プログラムを起動してブラウザにアプリを表示する

    VS Codeの右上から「▶」ボタンをクリックしてアプリを起動します。 以下、手順は図の説明で解説します。

    click image to zoom!
    図3-1
    アプリが起動すると「Dash is running on http://....」のメッセージが表示されます。 [Ctrl]を押しながらマウスで「URL」のリンクをクリックします。 しばらくすると、デフォルトのブラウザにアプリのWebページが表示されます。

    click image to zoom!
    図3-2
    図3-2には、ブラウザ(MS Edge)にアプリのWebページが表示されています。 Webページには、GMOコインの全ての仮想通貨(コイン)のシンボル、コインの名称、累積リターン(収益率)が表示されています。
  4. Webページをダークモードにする

    ここでは、Bootstrapのテーマを「BOOTSTRAP」から「CYBORG」に変えてWebページをダークモードにします。 リスト2に、変更箇所を「オレンジ色」でハイライトさせています。

    リスト2: Article140.py
    ### Instantiate Dash Class
    app = Dash(__name__, external_stylesheets=[dbc.themes.CYBORG])   # BOOTSTRAP => CYBORG
    
    ### Set Dash DataTable properties
    data = df.to_dict('records')
    
    columns = [
        dict(id='symbol', name='Symbol', type='text'),
        dict(id='coin_name', name='Coin Name', type='text'),
        dict(id='cum_log_return', name='Cum Log Return', type='numeric'),
        dict(id='cum_log_return_pct', name='Cum Log Return (%)', type='numeric')
    ]
    
    style_header = {
        'backgroundColor': 'rgb(30, 30, 30)',
        'color': 'white',    
        'fontWeight': 'bold'     
    }
    
    style_data = {  
        'backgroundColor': 'rgb(50, 50, 50)',
        'color': 'white'               
    }
    
    ### Layout a Web Page
    app.layout = dbc.Container(
        [ 
            dbc.Row([
                html.H3('GMO Coin: Cryptocurrencies by Cumulative Log Return'),   
                html.H5('Dash DataTable Tooltips Dark Theme'),
            ]),
            dbc.Row(html.Br()),
            
            dbc.Row(
                dash_table.DataTable(
                    id='dt',
                    data=data,
                    columns=columns,        
                    style_header=style_header,  
                    style_data=style_data,                          
                ),
            ),
            dbc.Row(html.Br()),
          
        ], # fluid=False,
    )  
    
    ### Run the server
    if __name__ == '__main__':
        app.run_server(debug=True)
    click image to zoom!
    図4
    図4には、Webページがダークモードで表示されています。 表のヘッダーは「太字」で強調しています。
  5. Webページに表示されている表の数値をフォーマットしたりテキストを左詰めにする

    ここでは、表の「Cum Log Return: 累積リターン」と「Cum Log Return (%): 累積リターン(%)」をフォーマットしています。 累積リターンは、小数点以下6桁に固定しています。 累積リターン(%)は、小数点以下2桁に固定しています。さらに「%」も付加しています。

    「Symbol」と「Coin Name」のテキスト文字は、左詰めにしています。 リスト3に、変更箇所を「オレンジ色」でハイライトさせています。

    リスト3: Article140.py
    ### Instantiate Dash Class
    app = Dash(__name__, external_stylesheets=[dbc.themes.CYBORG])    
    
    data = df.to_dict('records')
    
    log_return = FormatTemplate.Format(precision=6, scheme=Scheme.fixed)
    log_return_pct = FormatTemplate.percentage(2)
    
    columns = [
        dict(id='symbol', name='Symbol', type='text'),
        dict(id='coin_name', name='Coin Name', type='text'),    
        dict(id='cum_log_return', name='Cum Log Return', type='numeric', format=log_return),
        dict(id='cum_log_return_pct', name='Cum Log Return (%)', type='numeric', format=log_return_pct)
    ]
    
    style_header = {
        'backgroundColor': 'rgb(30, 30, 30)',
        'color': 'white',
        'fontWeight': 'bold'         
    }
    
    style_data = {  
        'backgroundColor': 'rgb(50, 50, 50)',
        'color': 'white'               
    }
    
    style_cell_conditional= [
        {
            'if': {'column_id': 'symbol'},
            'textAlign': 'left'
        },
        {
            'if': {'column_id': 'coin_name'},
            'textAlign': 'left'
        },    
    ]
    
    ### Layout a Web Page
    app.layout = dbc.Container(
        [ 
            dbc.Row([
                html.H3('GMO Coin: Cryptocurrencies by Cumulative Log Return'),   
                html.H5('Dash DataTable Tooltips Formatting & Align Column'),
            ]),
            dbc.Row(html.Br()),
            
            dbc.Row(
                dash_table.DataTable(
                    id='dt',
                    data=data,
                    columns=columns,        
                    style_header=style_header,  
                    style_data=style_data,                           
                    style_cell_conditional=style_cell_conditional,  
                ),
            ),
            dbc.Row(html.Br()),
          
        ], # fluid=False,
    )  
    
    ### Run the server
    if __name__ == '__main__':
        app.run_server(debug=True)  
    click image to zoom!
    図5
    図5には、表の「Cum Log Return」と「Cum Log Return (%)」の小数点以下の桁数が固定されて表示されています。 さらに、表の「Symbol」と「Coin Name」が左詰めで表示されています。 これで表が見やすくなりました。
  6. Webページに表示されている表のヘッダーにマウスをホバリングさせたときツールチップを表示させる

    ここでは、表のヘッダーにマウスをホバリングさせたときに、ツールチップをポップアップさせています。 リスト4に、変更箇所を「オレンジ色」でハイライトさせています。

    リスト4:Article140.py
    ### Instantiate Dash Class
    app = Dash(__name__, external_stylesheets=[dbc.themes.CYBORG])    
    
    data = df.to_dict('records')
    
    log_return = FormatTemplate.Format(precision=6, scheme=Scheme.fixed)
    log_return_pct = FormatTemplate.percentage(2)
    
    columns = [
        dict(id='symbol', name='Symbol', type='text'),
        dict(id='cum_log_return', name='Cum Log Return', type='numeric', deletable=False, format=log_return),
        dict(id='cum_log_return_pct', name='Cum Log Return (%)', type='numeric', format=log_return_pct)
    ]
    
    tooltip_header = {
        'symbol': 'Crypto Symbol',
        'cum_log_return': 'Cumulative Log Return',
        'cum_log_return_pct': 'Cumulative Log Return (%)',
    }
    
    style_header = {
        'backgroundColor': 'rgb(30, 30, 30)',
        'color': 'white',
        'fontWeight': 'bold'          
    }
    
    style_data = {  
        'backgroundColor': 'rgb(50, 50, 50)',
        'color': 'white'               
    }
    
    style_cell_conditional= [{
            'if': {'column_id': 'symbol'},
            'textAlign': 'left'
    }]
    
    ### Layout a Web Page
    app.layout = dbc.Container(
        [ 
            dbc.Row([
                html.H3('GMO Coin: Cryptocurrencies by Cumulative Log Return'),   
                html.H5('Dash DataTable Add Header Tooltips'),
            ]),
            dbc.Row(html.Br()),
            
            dbc.Row(
                dash_table.DataTable(
                    id='dt',
                    data=data,
                    columns=columns,        
                    tooltip_header=tooltip_header, 
                    tooltip_delay=0,
                    tooltip_duration=None,                 
                    style_header=style_header,  
                    style_data=style_data,                           
                    style_cell_conditional=style_cell_conditional,  
                ),
            ),
            dbc.Row(html.Br()),
          
        ], # fluid=False,
    )  
    
    ### Run the server
    if __name__ == '__main__':
        app.run_server(debug=True)
    
    click image to zoom!
    図6-1
    図6-1には、表のヘッダー「Symbol」にマウスをホバリングさせたとき「ツールチップ」が表示されています。 ツールチップの背景色は、デフォルトの色を使用しています。
    click image to zoom!
    図6-2
    図6-2では、ツールチップの背景色を「DodgerBlue」にカスタマイズしています。
    click image to zoom!
    図6-3
    図6-3では、ツールチップに「Markdown」を追加して表示させる情報を増やしています。 ここでは、ツールチップを2行に改行させて表示しています。
  7. 表(DataTable)に「絵文字」を追加してさらにツールチップをポップアップさせる

    ここでは、表に「🔥」の絵文字を表示させています。 累積リターンが1位のコインには🔥を3個、 2位のコインには🔥を2個、 3位のコインには🔥を1個表示させます。 さらに、表の明細行の「絵文字」のセルにマウスをホバリングさせるとツールチップを表示させます。 リスト5に、変更箇所を「オレンジ色」でハイライトさせています。

    リスト5: Article140.py
    ### Sort and add a ranking columns
    df = df.sort_values(by=['cum_log_return'], ascending=False).reset_index() 
    df['ranking'] = df.index + 1
    
    ### Add an emoji column
    df['emoji'] = df['ranking'].apply(lambda x:
        '🔥🔥🔥' if x == 1 else (
        '🔥🔥' if x == 2 else (
        '🔥' if x == 3 else ''
    )))
    
    ### Instantiate Dash Class
    app = Dash(__name__, external_stylesheets=[dbc.themes.CYBORG])   
    
    ### Set Dash DataTable 
    data = df.to_dict('records')
    
    log_return = FormatTemplate.Format(precision=6, scheme=Scheme.fixed)
    log_return_pct = FormatTemplate.percentage(2)
    
    columns = [
        dict(id='symbol', name='Symbol', type='text'),
        dict(id='emoji', name='Emoji', type='text'),
        dict(id='cum_log_return', name='Cum Log Return', type='numeric', deletable=False, format=log_return),
        dict(id='cum_log_return_pct', name='Cum Log Return (%)', type='numeric', format=log_return_pct)
    ]
    
    markdown_symbol = 'Symbol\n\n仮想通貨のシンボル'
    markdown_emoji = 'Emoji\n\n仮想通貨の絵文字ランキング'
    markdown_cum_log_return = 'Cumulative Log Return\n\n累積ログリターン'
    markdown_cum_log_return_pct = 'Cumulative Log Return (%)\n\n累積ログリターン (%)'
    
    tooltip_header = {
        'symbol': {'value': markdown_symbol, 'type': 'markdown'},
        'emoji': {'value': markdown_emoji, 'type': 'markdown'},
        'cum_log_return': {'value': markdown_cum_log_return, 'type': 'markdown'},
        'cum_log_return_pct': {'value': markdown_cum_log_return_pct, 'type': 'markdown'},
    }
    
    tooltip_conditional = [
        {
            'if': {
                'filter_query': '{emoji} = "🔥🔥🔥"',
                'column_id': 'emoji'
            },    
            'value': 'Ranking 1: Cumultive Log Return\n\n累積ログリターン 1位',      
            'type': 'markdown'
        },
        {
            'if': {
                'filter_query': '{emoji} = "🔥🔥"',
                'column_id': 'emoji'
            },
            'value': 'Ranking 2: Cumultive Log Return\n\n累積ログリターン 2位',      
            'type': 'markdown'
        },
        {
            'if': {
                'filter_query': '{emoji} = "🔥"',
                'column_id': 'emoji'
            },
            'value': 'Ranking 3: Cumultive Log Return\n\n累積ログリターン 3位',      
            'type': 'markdown'
        }       
    ]
    
    style_header = {
        'backgroundColor': 'rgb(30, 30, 30)',
        'color': 'white',
        'fontWeight': 'bold'    
    }
    
    style_data = {  
        'backgroundColor': 'rgb(50, 50, 50)',
        'color': 'white'               
    }
    
    style_cell_conditional= [{
            'if': {'column_id': 'symbol'},
            'textAlign': 'left'
    }]
    
    css = [{
        'selector': '.dash-table-tooltip',
        'rule': 'background-color: dodgerblue ; color: white'
    }]
    
    ### Layout a Web Page
    app.layout = dbc.Container(
        [ 
            dbc.Row([
                html.H3('GMO Coin: Cryptocurrencies by Cumulative Log Return'),   
                html.H5('Add Emoji and Tooltips'),
            ]),
            dbc.Row(html.Br()),
            
            dbc.Row(
                dash_table.DataTable(
                    id='dt',
                    data=data,
                    columns=columns,        
                    tooltip_header=tooltip_header, 
                    tooltip_conditional=tooltip_conditional,                  
                    tooltip_delay=0,
                    tooltip_duration=None,         
                    style_header=style_header,  
                    style_data=style_data,                           
                    style_cell_conditional=style_cell_conditional,                  
                    css=css,                 
                ),
            ),
            dbc.Row(html.Br()),
          
        ], # fluid=False,
    )  
    
    ### Run the server
    if __name__ == '__main__':
        app.run_server(debug=True)  
    click image to zoom!
    図7-1
    図7-1には、表に絵文字「🔥」が表示されています。 絵文字が3個のコインは累積リターンが1位、 絵文字が2個のコインは2位、 絵文字が1個のコインは3位を意味します。
    click image to zoom!
    図7-2
    図7-2では、マウスを「絵文字」にホバリングさせたときにツールチップを表示させています。 これで「🔥」が3個のときの意味が理解できます。
  8. 表(DataTable)のツールチップに画像を表示させる

    ここでは、コインのシンボルにマウスをホバリングさせるとツールチップを表示させています。 ツールチップには、コインの「名前」と「画像」を表示させています。 画像は「assets/images/crypto」フォルダに格納します。 画像は「![alt](src)」の書式で指定します。 「alt」は画像の「alt text」、「src」は画像のパス名を意味します。 リスト6に、変更箇所を「オレンジ色」でハイライトさせています。

    リスト6: Article140py
    ### Instantiate Dash Class
    app = Dash(__name__, external_stylesheets=[dbc.themes.CYBORG])   # BOOTSTRAP 
    
    ### Set Dash DataTable Properties
    data = df.to_dict('records')
    
    log_return = FormatTemplate.Format(precision=6, scheme=Scheme.fixed)
    log_return_pct = FormatTemplate.percentage(2)
    
    columns = [
        dict(id='symbol', name='Symbol', type='text'),
        dict(id='cum_log_return', name='Cum Log Return', type='numeric', deletable=False, format=log_return),
        dict(id='cum_log_return_pct', name='Cum Log Return (%)', type='numeric', format=log_return_pct)
    ]
    
    markdown_symbol = 'Symbol\n\n仮想通貨のシンボル'
    markdown_cum_log_return = 'Cumulative Log Return\n\n累積ログリターン'
    markdown_cum_log_return_pct = 'Cumulative Log Return (%)\n\n累積ログリターン (%)'
    
    tooltip_header = {
        'symbol': {'value': markdown_symbol, 'type': 'markdown'},
        'cum_log_return': {'value': markdown_cum_log_return, 'type': 'markdown'},
        'cum_log_return_pct': {'value': markdown_cum_log_return_pct, 'type': 'markdown'},
    }
    
    tooltip_conditional = [
        {
            'if': {
                'column_id': 'symbol',
                'filter_query': '{symbol} = "BTC_JPY"'
            },    
            'value': 'Bitcoin JPY\n\n ![BTC_JPY]({})'.format(app.get_relative_path('/assets/images/crypto/BTC_JPY.jpg')),
            'type': 'markdown'
        },    
    
        {
            'if': {
                'column_id': 'symbol',
                'filter_query': '{symbol} = "ETH_JPY"'
            },    
            'value': 'Ethereum JPY\n\n ![ETH_JPY]({})'.format(app.get_relative_path('/assets/images/crypto/ETH_JPY.jpg')),
            'type': 'markdown'
        },    
    
        {
            'if': {
                'column_id': 'symbol',
                'filter_query': '{symbol} = "LTC_JPY"'
            },    
            'value': 'Litecoin JPY\n\n ![LTC_JPY]({})'.format(app.get_relative_path('/assets/images/crypto/LTC_JPY.jpg')),
            'type': 'markdown'
        },            
       
        {
            'if': {
                'filter_query': '{symbol} = "ENJ"'
            },
            'value': 'エンジンコイン (ENJ)\n\n ![ENJ]({})'.format(app.get_relative_path('/assets/images/crypto/ENJ.jpg')),
            'type': 'markdown'
        },   
    
        {
            'if': {
                'filter_query': '{symbol} = "MKR"'
            },
            'value': 'メイカー (MKR)\n\n ![MKR]({})'.format(app.get_relative_path('/assets/images/crypto/MKR.jpg')),
            'type': 'markdown'
        },   
    
        {
            'if': {
                'filter_query': '{symbol} = "QTUM"'
            },
            'value': 'クアンタム (QTUM)\n\n ![QTTM]({})'.format(app.get_relative_path('/assets/images/crypto/QTUM.jpg')),
            'type': 'markdown'
        },               
    ]
    
    style_header = {
        'backgroundColor': 'rgb(30, 30, 30)',
        'color': 'white',
        'fontWeight': 'bold'    
    }
    
    style_data = {  
        'backgroundColor': 'rgb(50, 50, 50)',
        'color': 'white'               
    }
    
    style_cell_conditional= [{
            'if': {'column_id': 'symbol'},
            'textAlign': 'left'
    }]
    
    css = [{
        'selector': '.dash-table-tooltip',
        'rule': 'background-color: dodgerblue ; color: white'
    }]
    
    ### Layout a Web Page
    app.layout = dbc.Container(
        [ 
            dbc.Row([
                html.H3('GMO Coin: Cryptocurrencies by Cumulative Log Return'),   
                html.H5('Add Images in Tooltips'),
            ]),
            dbc.Row(html.Br()),
            
            dbc.Row(
                dash_table.DataTable(
                    id='dt',
                    data=data,
                    columns=columns,        
                    tooltip_header=tooltip_header,  
                    tooltip_conditional=tooltip_conditional,                  
                    tooltip_delay=0,
                    tooltip_duration=None,         
                    style_header=style_header,  
                    style_data=style_data,                           
                    style_cell_conditional=style_cell_conditional,                  
                    css=css,                 
                ),
            ),
            dbc.Row(html.Br()),
          
        ], # fluid=False,
    )  
    
    ### Run the server
    if __name__ == '__main__':
        app.run_server(debug=True)  
    click image to zoom!
    図8-1
    図8-1には、マウスを「BTC_JPY」のシンボルにホバリングさせたときのツールチップが表示されています。 ツールチップには、コインの「名前」と「アイコン」が表示されています。
    click image to zoom!
    図8-2
    図8-2には、マウスを「ETH_JPY」のシンボルにホバリングさせたときのツールチップが表示されています。 ツールチップには、コインの「名前」と「アイコン」が表示されています。
  9. 最終版の全てのコードを掲載

    ここでは、最終版のプログラムのソースコードと、 ライブラリのソースコード、そしてCSVファイルの内容を掲載しています。

    リスト7: Article140.py
    # Dash DataTable Tooltips v51.py :  
    # Web Link: https://dash.plotly.com/datatable/tooltips
    
    ### Import the libraries
    import os
    import math
    import numpy as np
    import pandas as pd
    import requests
    
    import datetime as dt
    from datetime import timedelta
    from time import sleep
    
    from dash import Dash, dash_table, html, Output, Input, State, dcc, ctx
    from dash.dash_table import DataTable, FormatTemplate
    from dash.dash_table.Format import Format, Scheme, Align, Trim
    import dash_bootstrap_components as dbc
    
    from lib.com_lib import get_data, calculate_cum_log_return, add_coin_name
    from lib.gmo_api import get_crypto, get_crypto_symbols
    
    import warnings
    warnings.simplefilter('ignore')
    
    #################################
    # Main
    #################################
    
    ### Load the data from gmo coin
    
    # get all symbols from gmo coin 
    df = get_crypto_symbols()
    symbols = df['symbol'].values.tolist()
    
    interval = '1day' 
    date_list = ['2020','2021','2022','2023'] 
    
    symbol_list = []
    cum_log_return_list = []
    cum_log_return_pct_list = []
    for symbol in symbols:
        csv_file = f"datasets/csv/gmo_crypto_2020_2023({symbol})_{interval}.csv"  
        isFile = os.path.isfile(csv_file)
        if not isFile: 
            for date in date_list:  # '2020','2021','2022','2023'
                df = get_crypto(symbol, interval, date)    # get n rows from starting date
                if not df.empty: 
                    df.to_csv(csv_file, index=True)
        # end of if not isFile:
        isFile = os.path.isfile(csv_file)
        if isFile:
            df = get_data(csv_file)
            if not df.empty:
                df = calculate_cum_log_return(df)
                df.replace([np.inf, -np.inf], np.nan).dropna(axis=1, inplace=True)
                cum_log_return = df.iloc[-1]['cum_log_return']
                cum_log_return_pct = df.iloc[-1]['cum_log_return_pct']
                symbol_list.append(symbol)
                cum_log_return_list.append(cum_log_return)
                cum_log_return_pct_list.append(cum_log_return_pct)
    # end of for symbol in symbols:
    
    ### Create DataFrame from dict
    data = {
        'symbol': symbol_list,
        'cum_log_return': cum_log_return_list,
        'cum_log_return_pct': cum_log_return_pct_list
    }
    
    raw_df = pd.DataFrame(data)
    if raw_df.empty:
        print(f"Quit the program due to raw_df is empty: {raw_df.empty=}")
        quit()
    
    ### Replace np.inf or -np.inf (positive or negative infinity) with np.nan(Not A Number)
    df = raw_df.replace([np.inf, -np.inf], np.nan)
    ### Drop rows if np.nan (Not A Number)
    df.dropna(axis=0, inplace=True)
    df['cum_log_return_pct'] = df['cum_log_return']
    ### Add a coin name
    add_coin_name(df) 
    
    ### Instantiate Dash Class
    app = Dash(__name__, external_stylesheets=[dbc.themes.DARKLY])  # DARKLY, CYBORG   
    
    ### Set Dash DataTable Properties
    data = df.to_dict('records')
    
    log_return = FormatTemplate.Format(precision=6, scheme=Scheme.fixed)
    log_return_pct = FormatTemplate.percentage(2)
    
    columns = [
        dict(id='symbol', name='Symbol', type='text'),
        dict(id='cum_log_return', name='Cum Log Return', type='numeric', deletable=False, format=log_return),
        dict(id='cum_log_return_pct', name='Cum Log Return (%)', type='numeric', format=log_return_pct)
    ]
    
    markdown_symbol = 'Symbol\n\n仮想通貨のシンボル'
    markdown_cum_log_return = 'Cumulative Log Return\n\n累積ログリターン'
    markdown_cum_log_return_pct = 'Cumulative Log Return (%)\n\n累積ログリターン (%)'
    
    tooltip_header = {
        'symbol': {'value': markdown_symbol, 'type': 'markdown'},
        'cum_log_return': {'value': markdown_cum_log_return, 'type': 'markdown'},
        'cum_log_return_pct': {'value': markdown_cum_log_return_pct, 'type': 'markdown'},
    }
    
    symbols = []
    
    for ix, row in df.iterrows():
        symbol = row['symbol']
        coin_name = row['coin_name']
        coin_dict = {
            'symbol': f'{symbol}', 
            'name': f'{coin_name}', 
            'image_path': f'/assets/images/crypto/{symbol}.jpg'   
        }
        symbols.append(coin_dict)
    
    tooltip_conditional = []
    for symbol in symbols:
        tooltip_conditional.append({
            'if': {
                'column_id': 'symbol',
                'filter_query': "{{symbol}} = '{}'".format(symbol['symbol'])    
                # 'filter_query': "symbol = 'BTC_JPY'"
            },
            'value': "{}\n\n ![{}]({})".format(
                symbol['name'], symbol['symbol'], app.get_relative_path(symbol['image_path'])
                # 'value': "Bitcoin JPY\n\n![BTC_JPY](app.get_relative_path('/assets/images/crypto/BTC_JPY.jpg')"
            ),
            'type': 'markdown'
        })
    
    style_header = {
        'backgroundColor': 'rgb(30, 30, 30)',
        'color': 'white',
        'fontWeight': 'bold'    
    }
    
    style_data = {  
        'backgroundColor': 'rgb(50, 50, 50)',
        'color': 'white'               
    }
    
    style_cell_conditional= [{
            'if': {'column_id': 'symbol'},
            'textAlign': 'left'
    }]
    
    css = [{
        'selector': '.dash-table-tooltip',
        'rule': 'background-color: dodgerblue ; color: white'
    }]
    
    ### Layout a Web Page
    app.layout = dbc.Container(
        [ 
            dbc.Row([
                html.H3('GMO Coin: Cryptocurrencies by Cumulative Log Return'),   
                html.H5('Add Images in Tooltips'),
            ]),
            dbc.Row(html.Br()),
            
            dbc.Row(
                dash_table.DataTable(
                    id='dt',
                    data=data,
                    columns=columns,        
                    tooltip_header=tooltip_header,  
                    tooltip_conditional=tooltip_conditional,                  
                    tooltip_delay=0,
                    tooltip_duration=None,         
                    style_header=style_header,  
                    style_data=style_data,                           
                    style_cell_conditional=style_cell_conditional,                  
                    css=css,                 
                ),
            ),
            dbc.Row(html.Br()),
          
        ], # fluid=False,
    )  
    
    ### Run the server
    if __name__ == '__main__':
        app.run_server(debug=True)  


    リスト8: lib/gmo_api.py
    # gmo_api.py
    
    import math
    import numpy as np
    import pandas as pd
    import requests
    
    import datetime as dt
    from datetime import timedelta
    from time import sleep
    
    import warnings
    warnings.simplefilter('ignore')
    
    ####################################################
    def get_crypto_symbols(symbol=None) -> pd.DataFrame:
        # Define GMO Coin API endpoint
        endpoint = 'https://api.coin.z.com/public/v1/ticker'    
        params = {}    
        if symbol is not None:    
            # add a new element(symbol) to the params dictionary
            params['symbol'] = symbol
            
        res_dict  = requests.get(endpoint, params=params) 
        dict = res_dict.json()
        status = dict.get('status')
        # no error ?
        if status == 0:
            data_list = dict.get('data') 
            df = pd.DataFrame(data_list)    # ask, bid, high, last, low, symbol, timestamp, volume       
            return df
        else:
            print(f"get_crypto_symbols() error => {status=}")     
            return pd.DataFrame()
    
    ############################################################ Load data from GMO Coin
    def get_crypto(symbol='BTC', interval='1day', start='2018'):
        '''
        interval=1min 5min 10min 15min 30min 1hour for YYYYMMDD
        interval=4hour 8hour 12hour 1day 1week 1month  YYYY             
        start='2018'       
        '''      
        url = 'https://api.coin.z.com/public/v1/klines'       
       
        params = {
            'symbol': symbol,
            'interval': interval,    
            'date': start
        }        
        
        try:
            res_dict  = requests.get(url, params=params)    
            dict = res_dict.json()
            status = dict.get('status')
            # no error ?
            if status == 0:
                data_list = dict.get('data') 
                df = pd.DataFrame(data_list)            
                df.columns = ['date', 'open', 'high', 'low', 'close', 'volume']
                df['date'] = pd.to_datetime(df['date'], unit='ms')
                df.set_index('date', inplace=True)
                df = df.astype(float)
                df['symbol'] = symbol
                # df.reset_index(inplace=True)
                # print(f"get_crypto({symbol=}, {interval=}, {start=}) => {df.shape[0]=}")        
                return df
            else:
                print(f"get_crypto({symbol=}, {interval=}, {start=}) error => {status=}")     
                return pd.DataFrame()
        except requests.exceptions.HTTPError as e:
            print(f"get_crypto({symbol=}, {interval=}, {start=}) HTTP error: {e}") 
        except Exception as e:   
            print(f"get_crypto({symbol=}, {interval=}, {start=}) exception error: {e}")
        
        return pd.DataFrame()   


    リスト9: lib/com_lib.py
    # com_lib.py
    
    import math
    import numpy as np
    import pandas as pd
    import requests
    
    import datetime as dt
    from datetime import timedelta
    from time import sleep
    
    import warnings
    warnings.simplefilter('ignore')
    
    ############################################
    def get_data(csv_file: str) -> pd.DataFrame:
        # print(f"Loading data: {csv_file} ")
        df = pd.read_csv(csv_file)       
        # date,open,high,low,close,adj close,volume,symbol
        df['date'] = pd.to_datetime(df['date'])            
        df.set_index(['date'], inplace=True)
        return df
    
    ###############################################################
    def calculate_cum_log_return(df: pd.DataFrame) -> pd.DataFrame:
        # Calculate log return
        df['log_return'] = np.log(df['close'] / df['close'].shift(1))  
     
        # Calculate cumulative log return
        df['cum_log_return'] = np.exp(df['log_return'].cumsum()) - 1
        df['cum_log_return_pct'] = df['cum_log_return'] * 100
    
        # Preview the resulting dataframe 
        # print(f"Cumulative Log Return for {df.iloc[-1]['symbol']} = {df.iloc[-1]['cum_log_return_pct']:.2%}")     
        return df
    
    ############################################
    def add_coin_name(df: pd.DataFrame) -> None:
        csv_symbol = 'datasets/csv/gmo_coin_symbols.csv'
        symbol_df = pd.read_csv(csv_symbol)       
        # Rename Pandas columns to lower case
        symbol_df.columns = symbol_df.columns.str.lower()
        # symbol, description, release_date
        symbol_df['release_date'] = pd.to_datetime(symbol_df['release_date'])            
    
        #  #   Column        Non-Null Count  Dtype
        # ---  ------        --------------  -----
        #  0   symbol        26 non-null     object        
        #  1   description   26 non-null     object        
        #  2   release_date  26 non-null     datetime64[ns]
    
        for _, row in symbol_df.iterrows():
            symbol = row['symbol']
            description = row['description']
            find_mask = df['symbol'] == symbol
            dfx = df[find_mask]
            if not dfx.empty:
                ix = dfx.index.values[0]   
                df.loc[ix, 'coin_name'] = description   # add a new column
        # end of for _, row in symbol_df.iterrows():
    
        return  


    リスト10: datasets/csv/gmo_coin_symbols.csv
    Symbol,Description,Release_Date
    BTC,ビットコイン(現物取引),2018/09/05
    ETH,イーサリアム(現物取引),2019/01/30
    BCH,ビットコインキャッシュ(現物取引),2019/01/30
    LTC,ライトコイン(現物取引),2019/01/30
    XRP,リップル(現物取引),2019/01/30
    XEM,ネム(現物取引),2021/06/23
    XLM,ステラルーメン(現物取引),2021/08/18
    BAT,ベーシックアテンショントークン(現物取引),2022/03/30
    OMG,オーエムジー(現物取引),2022/06/08
    XTZ,テゾス(現物取引),2022/06/08
    QTUM,クアンタム(現物取引),2022/03/30
    ENJ,エンジンコイン(現物取引),2022/06/08
    DOT,ポルカドット(現物取引),2022/06/08
    ATOM,コスモス(現物取引),2022/06/08
    MKR,メイカー(現物取引),2022/07/13
    DAI,ダイ(現物取引),2022/07/13
    XYM,シンボル(現物取引),2021/10/20
    MONA,モナコイン(現物取引),2021/12/01
    FCR,FCRコイン(現物取引),2022/05/18
    ADA,カルダノ(現物取引),2022/07/13
    LINK,チェーンリンク(現物取引),2022/07/13
    BTC_JPY,ビットコイン/円(レバレッジ取引),2018/09/05
    ETH_JPY,イーサリアム/円(レバレッジ取引),2019/01/30
    BCH_JPY,ビットコインキャッシュ/円(レバレッジ取引),2019/01/30
    LTC_JPY,ライトコイン/円(レバレッジ取引),2019/01/30
    XRP_JPY,リップル/円(レバレッジ取引),2019/01/30