Issues with table in Dash 0.39.0

After upgrading to this version my app has some issues with some widgets

Deletable rows in table don’t delete when clicking on X
Some but not all radio items are not responsive: I click on a button and the change doesn’t happen

Rolled back to previous version and app working fine again

I am the only one with this compatibility problem ?

@jelkhoury Had a look at a few examples using these functionalities in the guideline and I’m not experiencing the behavior you are describing. If you can provide an example that reproduces the problems you are experiencing, that would help a lot. Thanks.

Thanks Marc Andre for following up on this.
I don’t want to be left behind unable to use the great new features least of which is not multiple outputs

The current versions I am using and which work fine:

  • dash 0.35.0
  • dash-core-components 0.41.0
  • dash-html-components 0.14.0
  • dash-renderer 0.16.1
  • dash-table 3.1.11
  • plotly 3.4.2

My code is ~500 lines and the script is using pandas numpy and ccxt: ([https://github.com/ccxt/ccxt])
I’ve tested the behavior on Linux Ubuntu and Windows 10 and it is the same. So probably not an OS portability issue.
Unfortunately I cannot upload a .py file here so I will paste the code here and hope it is not too long.
The problems I see after update of all dash libraries and plotly:

Graph not showing titles
Radio Item on the right of the page(id=‘agg-level’) not active

PS: I have another app where a table with deletable rows doesn’t respond to X click and doesn’t delete the row after update.
but it has a lot of modules dependencies and difficult to share here.
Please let me know if there is a better way to share the code with you

this is code , I hope you ll be able to replicate

import dash
import dash_core_components as dcc 
import dash_html_components as html 
import dash_table  
from dash.dependencies import Input, Output
import plotly.graph_objs as go 
import plotly.figure_factory as ffimport 
import pandas as pd
import plotly.graph_objs as go
import numpy as np
import itertools
import ccxt
import json
import datetime as dt 


spot_exchanges = ['bitfinex','bitstamp','coinbasepro','kraken','liquid','gemini','binance',
                    'bitbank','huobipro','poloniex','bithumb','bittrex','kucoin2']

exch_dict={}
for x in spot_exchanges:
    exec('exch_dict[x]=ccxt.{}()'.format(x))

Xpto= ['BTC','ETH','XRP','XMR','BCH','EOS','USDT','USDC','TRX','XLM','BSV','XBT','CSP','DAI']
Fiat=['USD','EUR','GBP','CHF','HKD','JPY','CNH','KRW']
xpto_fiat = [xpto+'/'+ fiat for xpto in Xpto for fiat in Fiat]
xpto_xpto = [p[0]+'/'+p[1] for p in itertools.permutations(Xpto,2)]

all_pairs = set(sum(itertools.chain([*exch_dict[x].load_markets()] for x in exch_dict),[])) 
pairs = list(set(xpto_fiat + xpto_xpto) & set(all_pairs))
pairs.sort()

def get_exchanges_for_pair(pair):
    '''input: a pair
    output: a dictionary of ccxt exchange objects of the exchanges listing the pair
    '''
    return {x:exch_dict[x] for x in exch_dict if pair in list(exch_dict[x].load_markets().keys())}
def get_pairs_for_exchange(ex):
    '''input: an exchange
    output: a list of pairs '''
    d={}
    exec('d[ex]=ccxt.{}()'.format(ex))
    d[ex].load_markets()
    return d[ex].symbols

def get_order_books(pair,ex):
    '''pair is the pair string ,'BTC/USD'...
        returns a dictionary of order books for the pair
        special case for binance which API fails if # of parmaeters > 2
    '''
    nobinance= {key:value for key, value in ex.items() if key != 'binance'and  key != 'bitfinex'}
    order_books = {key: value.fetch_order_book(pair,limit=2000 if key!='bithumb' else 50,
                        params={'full':1,'level':3,'limit_bids':0,'limit_asks':0,'type':'both'})
                        for key,value in nobinance.items() }
    if 'binance' in ex:
        order_books['binance'] =  ex['binance'].fetch_order_book(pair,limit=1000)
    if 'bitfinex' in ex:
        order_books['bitfinex'] =  ex['bitfinex'].fetch_order_book(pair,limit=2000)
    return order_books

def aggregate_order_books(dict_of_order_books):
    '''dict_of_order_books is a dict of ccxt like order_books
        retuns a ccxt like dictionary order book sorted by prices 
    '''
    agg_dict_order_book = {}
    bids = []
    for x in dict_of_order_books:
        for bid in dict_of_order_books[x]['bids']:
            bids.append(bid+[x])
    asks = []
    for x in dict_of_order_books:
        for ask in dict_of_order_books[x]['asks']:
            asks.append(ask+[x])
    agg_dict_order_book['bids'] = (pd.DataFrame(bids)).sort_values(by=0,ascending=False).values.tolist()
    agg_dict_order_book['asks'] = (pd.DataFrame(asks)).sort_values(by=0,ascending=True).values.tolist()
    return agg_dict_order_book

def normalize_order_book(order_book,cutoff=.1,step=.001):
    '''order_book is a dictionary with keys bids asks timestamp datetime ...
    where bids is a list of list [[bid,bid_size]] and 
    asks is a list of list [[ask,ask_size]]
    this is returned by ccxt.'exchange'.fetch_order_book()
    returns a dataframe with columns [ask, ask_size, ask_size_$, cum_ask_size_$, bid_, bid_size, bid_size_$, cum_bid_size_$]
    and an index of shape np.linspace(1 - cutoff,1 + cutoff ,step =.001 ~ 10 bps)  
    '''
    try:
        rounding = int(np.ceil(-np.log(step)/np.log(10)))
        agg = True
    except:
        agg = False
    bid_side = pd.DataFrame(order_book['bids'],columns=['bid','bid_size','exc'])
    bid_side['cum_bid_size'] = bid_side['bid_size'].cumsum()
    ask_side = pd.DataFrame(order_book['asks'],columns=['ask','ask_size','exc'])
    ask_side['cum_ask_size'] = ask_side['ask_size'].cumsum()
    ref = (bid_side['bid'][0]+ask_side['ask'][0])/2
    bid_side['bid%'] = round(bid_side['bid']/ref,rounding) if agg else bid_side['bid']/ref
    ask_side['ask%'] = round(ask_side['ask']/ref,rounding) if agg else ask_side['ask']/ref
    bid_side = bid_side[bid_side['bid%']>=1-cutoff]
    ask_side = ask_side[ask_side['ask%']<=1+cutoff]
    bid_side['bid_size_$'] = bid_side['bid_size']*bid_side['bid']
    bid_side['cum_bid_size_$'] = bid_side['bid_size_$'].cumsum()
    ask_side['ask_size_$'] = ask_side['ask_size']*ask_side['ask']
    ask_side['cum_ask_size_$'] = ask_side['ask_size_$'].cumsum()
    normalized_bids = pd.DataFrame(bid_side.groupby('bid%',sort=False).mean()['bid'])
    normalized_bids.columns = ['bid']
    normalized_bids['bid_size'] = bid_side.groupby('bid%',sort=False).sum()['bid_size']
    normalized_bids['cum_bid_size'] = normalized_bids['bid_size'].cumsum()
    normalized_bids['bid_size_$'] = bid_side.groupby('bid%',sort=False).sum()['bid_size_$']
    normalized_bids['cum_bid_size_$'] = normalized_bids['bid_size_$'].cumsum()
    normalized_bids['average_bid_fill'] = normalized_bids['cum_bid_size_$']/normalized_bids['cum_bid_size']
    normalized_bids['bids_exc']=bid_side.groupby('bid%',sort=False).apply(lambda x: x['exc'].loc[x['bid_size'].idxmax()])
    normalized_asks = pd.DataFrame(ask_side.groupby('ask%',sort=False).mean()['ask'])
    normalized_asks.columns = ['ask']
    normalized_asks['ask_size'] = ask_side.groupby('ask%',sort=False).sum()['ask_size']
    normalized_asks['cum_ask_size'] = normalized_asks['ask_size'].cumsum()
    normalized_asks['ask_size_$'] = ask_side.groupby('ask%',sort=False).sum()['ask_size_$']
    normalized_asks['cum_ask_size_$'] = normalized_asks['ask_size_$'].cumsum()
    normalized_asks['average_ask_fill']=normalized_asks['cum_ask_size_$']/normalized_asks['cum_ask_size']
    normalized_asks['asks_exc']=ask_side.groupby('ask%',sort=False).apply(lambda x: x['exc'].loc[x['ask_size'].idxmax()])
    book=pd.concat([normalized_asks,normalized_bids],sort=False)
    return book

def build_book(order_books,pair,exchanges,cutoff=.1,step=0.001):
    ''' gets order books aggreagtes them then normalizes
        returns a dataframe
    '''
    return normalize_order_book(aggregate_order_books({key:order_books[key] for key in exchanges}),cutoff,step)

def plot_book(order_books,pair, exc, relative=True, currency=True, cutoff=.1):
    ''' plots the order book as a v shape chart '''
    order_book = build_book(order_books,pair,exc,cutoff)
    best_bid = round(order_book['bid'].max(),4)
    best_ask = round(order_book['ask'].min(),4)
    if currency:
        col_to_chart = '_$'
    else:
        col_to_chart = ''
    if relative:
        trace_asks=go.Scatter(x=order_book.index,y=order_book['cum_ask_size'+col_to_chart],
                        name='asks',marker=dict(color='rgba(255,0,0,0.6)'),fill='tozeroy',fillcolor='rgba(255,0,0,0.2)')
        trace_bids=go.Scatter(x=order_book.index,y=order_book['cum_bid_size'+col_to_chart],
                        name='asks',marker=dict(color='rgba(0,0,255,0.6)'),fill='tozeroy',fillcolor='rgba(0,0,255,0.2)')     
    else:
        trace_asks=go.Scatter(x=order_book['ask'].fillna(0)+order_book['bid'].fillna(0),y=order_book['cum_ask_size'+col_to_chart],
                        name='asks',marker=dict(color='rgba(255,0,0,0.6)'),fill='tozeroy',fillcolor='rgba(255,0,0,0.15)')
        trace_bids=go.Scatter(x=order_book['ask'].fillna(0)+order_book['bid'].fillna(0),y=order_book['cum_bid_size'+col_to_chart],
                        name='asks',marker=dict(color='rgba(0,0,255,0.6)'),fill='tozeroy',fillcolor='rgba(0,0,255,0.15)')
        
    layout = go.Layout(title = ' - '.join(exc), xaxis = dict(title= pair +'  ' + str(best_bid)+' - '+ str(best_ask)))
    data=[trace_asks,trace_bids]
    figure = go.Figure(data=data,layout=layout)
    return figure

def plot_depth(order_books,pair, exc, relative=True, currency=True, cutoff=.1):
    if currency:
        col_to_chart = '_$'
    else:
        col_to_chart = ''
    order_book = build_book(order_books,pair,exc,cutoff)
    mid = (order_book['bid'].max()+order_book['ask'].min())/2 if relative else 1
    trace_asks = go.Scatter(x=order_book['cum_ask_size'+col_to_chart],y=order_book['average_ask_fill']/mid,
                        name='ask depth',marker=dict(color='rgba(255,0,0,0.6)'),fill='tozerox',fillcolor='rgba(255,0,0,0.15)')
    trace_bids = go.Scatter(x=-order_book['cum_bid_size'+col_to_chart],y=order_book['average_bid_fill']/mid,
                        name='bid depth',marker=dict(color='rgba(0,0,255,0.6)'),fill='tozerox',fillcolor='rgba(0,0,255,0.15)')
    data = [trace_asks,trace_bids]
    figure = go.Figure(data=data, layout={'title': 'Market Depth'})
    return figure

def order_fill(order_book_df, order_sizes,in_ccy=True):
    '''takes in an order book dataframe and an np.array of order sizes
        with size in currecncy by default else in coin
        returns an np.array of the purchase costs or the sale proceeds of an order
    '''
    average_fills = np.zeros(order_sizes.shape)
    mid=(order_book_df['ask'].min()+order_book_df['bid'].max())/2
    if in_ccy:
        order_sizes=order_sizes/mid
    for i , order_size in enumerate(order_sizes):
        if order_size > 0:
            try:
                last_line = order_book_df[order_book_df['cum_ask_size']>order_size].iloc[0]
                ccy_fill = last_line['cum_ask_size_$']+(order_size-last_line['cum_ask_size'])*last_line['ask']
                average_fill=ccy_fill/order_size
            except:
                average_fill=np.nan
        elif order_size < 0:
            try:
                last_line = order_book_df[order_book_df['cum_bid_size'] > -order_size].iloc[0]
                ccy_fill=last_line['cum_bid_size_$']+(-order_size-last_line['cum_bid_size'])*last_line['bid']
                average_fill = -ccy_fill/order_size
            except:
                average_fill = np.nan
        average_fills[i] = average_fill
    return average_fills/mid
 
def get_liq_params(normalized,pair):
    coinmar = ccxt.coinmarketcap()
    coindata=coinmar.load_markets()
    total_coins=float(coindata[pair]['info']['available_supply'])  #number of coins floating
    order_span = (1,10,20,30,40)
    clip = total_coins/100000                                      #my standard order size 
    ordersizes=np.array([clip* i for i in order_span]+[-clip* i for i in order_span]).astype(int)
    slippage = ((order_fill(normalized,ordersizes,False)-1)*100).round(2)
    best_bid = normalized['bid'].max()
    best_ask = normalized['ask'].min()
    mid= (best_bid + best_ask)/2
    spread = best_ask-best_bid
    spread_pct = spread/mid*100
    cross = min(0,spread)
    cross_pct = min(0,spread_pct)
    ordersizes_dollar=ordersizes*mid
    arb_ask = normalized[normalized['ask'] < best_bid]
    arb_bid = normalized[normalized['bid'] > best_ask]
    if len(arb_bid) == 0:
        arb_dollar = 0
        size_arb = 0
    else:
        size_bid = arb_bid.iloc[-1]['cum_bid_size']
        size_ask = arb_ask.iloc[-1]['cum_ask_size']
        sell_first = size_bid <= size_ask
        if sell_first:
            size_arb = size_bid
            arb_dollar = -order_fill(normalized,np.array([size_bid]),False)[0]*mid*size_bid + arb_bid.iloc[-1]['cum_bid_size_$']
        else:
            size_arb = size_ask
            arb_dollar =-order_fill(normalized,np.array([-size_ask]),False)[0]*mid*size_ask + arb_ask.iloc[-1]['cum_ask_size_$']

    result1 = pd.DataFrame([best_bid,best_ask,mid,spread,spread_pct,cross,cross_pct,arb_dollar,size_arb,100*arb_dollar/(size_arb*mid) if size_arb!=0 else 0],
    index=['bid','ask','mid','spread','spread%','cross','cross%','arb $','size arb','arb%']).T
    result2 = pd.DataFrame(index=[str(o) for o in ordersizes])
    result2 [0]=(ordersizes_dollar/1e6).round(1)
    result2[1] = slippage
    result2=result2.T
    info = coindata[pair]['info']
    select_info=['symbol','rank','24h_volume_usd','market_cap_usd',
                'available_supply','percent_change_1h','percent_change_24h','percent_change_7d']
    selected_info={key:value for key,value in info.items() if key in select_info}
    result3 = pd.DataFrame(pd.Series(selected_info)).T
    result3.columns=['Coin','Rank','24H % Volume','USD Market Cap M$','Coins Supply M','% 1h','% 24h','% 7d']
    result3['24H % Volume']= round(float(result3['24H % Volume'])/float(result3['USD Market Cap M$'])*100,1)
    result3['USD Market Cap M$'] = round(float(result3['USD Market Cap M$'])/(1000*1000),0)
    result3['Coins Supply M'] = round(float(result3['Coins Supply M'])/(1000*1000),1)
    return [result1,result2,result3]
    

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__,external_stylesheets=external_stylesheets)
app.title = 'Order Books'
app.layout = html.Div(style={'marginLeft':25,'marginRight':25},
                    children=[ html.Div(className='row',children=[
                                        html.Div(className='six columns',
                                        children =[html.H6('Choose Pair'),
                                                    dcc.Dropdown(id='pairs',
                                                                options=[{'label':pair,'value':pair} for pair in pairs],
                                                                value='BTC/USD'),
                                                    html.Div(className='row',children=[
                                                    html.Div(className='three columns',children = [html.H6('\n \n X scale :')]),
                                                    html.Div(className='three columns',children=[dcc.RadioItems(id='x-scale',
                                                                options=[{'label':scale,'value':scale} for scale in ['Relative','Absolute']], 
                                                                value ='Relative',
                                                                labelStyle={'display':'inline-block'})])
                                                    ]),
                                                    html.Div(className='row',children=[
                                                    html.Div(className='three columns',children = [html.H6('\n \n Y scale :')]),
                                                    html.Div(className='three columns',children=[dcc.RadioItems(id='y-scale',
                                                                options=[{'label':scale,'value':scale} for scale in ['Currency','Coin']], 
                                                                value ='Currency',
                                                                labelStyle={'display':'inline-block'})])
                                                    ]),
                                                    html.H6('\n \n Cutoff'),
                                                    html.Div(style={'width' :'50%','align':'right'},children =[
                                                    dcc.Slider(id='cutoff',
                                                            min=.05,max=.3,step=.05,value=.1,
                                                            marks={round(j,2): str(round(j,2)) for j in list(np.arange(.05,.35,.05))})]),
                                                    html.H6(' ... '),        
                                                    html.H6(' Book Chart'),
                                                    dcc.Graph(id='order-book-chart'),
                                                    dcc.Graph(id='market-depth'),
                                                    html.H6(id='time')

                                        ]),
                                html.Div(className='six columns',
                                    children =[html.H6('Choose Exchange'),
                                                dcc.Dropdown(id='exchanges',multi=True),
                                                html.Div(children = [
                                                html.Div(className='three columns',children = [html.H6('Order Book')]),
                                                html.Div(className='three columns',children = [html.H6('Bps Aggregation Level:')]),
                                                html.P(className = 'three columns',children =
                                                                                        [dcc.RadioItems(id='agg-level',
                                                                                        options=[{'label':i,'value':i/10000} for i in [0,.1,1,10,100,1000,10000]],
                                                                                        value=10/10000,
                                                                                        labelStyle={'display':'inline-block'})])]),
                                                html.Div(id='order-table'),
                                                html.H6('Liquidity Metrics'),
                                                html.H6('----------------------'),
                                                html.P(id='liquidity-table'),
                                                html.H6('Slippage %'),
                                                html.H6('--------------'),
                                                html.P(id='depth-table'),
                                                html.H6('Coin Stats'),
                                                html.H6('--------------'),
                                                html.P(id='stat-table')]),
                                html.Div(id='the-data',style={'display':'none'}),
                                dcc.Interval(
                                    id='interval-component',
                                    interval=10*1000, # in milliseconds= 10 seconds
                                    n_intervals=0
                                    ),
                                dcc.Interval(
                                    id='second',
                                    interval = 1000,
                                    n_intervals=0
                                )     
                        ])
                        ])

@app.callback(Output('time','children'),
            [Input('second','n_intervals'),Input('the-data','children')])
def update_time(n,order_books):
    #return   20* ' '  +  '{}'.format(dt.datetime.now().strftime("%Y-%m-%d  %H:%M:%S"))
    time_snap = json.loads(order_books)[1]
    return (dt.datetime.now()-dt.datetime.strptime(time_snap,"%Y-%m-%d  %H:%M:%S")).seconds

@app.callback(Output('exchanges','options'),
            [Input('pairs','value')])
def update_exchanges_options(pair):
    return [{'label':exch,'value':exch} for exch in get_exchanges_for_pair(pair).keys()]

@app.callback(Output('exchanges','value'),
            [Input('pairs','value')])
def update_exchanges_values(pair):
    return list(get_exchanges_for_pair(pair).keys())

@app.callback(Output('the-data','children'),
            [Input('pairs','value'),Input('exchanges','value'),Input('interval-component','n_intervals')])
def update_data(pair,ex,n):
    now = dt.datetime.now()
    ex = {x:exch_dict[x] for x in ex}
    order_books = get_order_books(pair,ex)
    save_this = (order_books,now.strftime("%Y-%m-%d  %H:%M:%S"))
    return json.dumps(save_this)

@app.callback(Output('order-book-chart','figure'),
            [Input('the-data','children'),
            Input('pairs','value'),Input('exchanges','value'),
            Input('x-scale','value'),Input('y-scale','value'),
            Input('cutoff','value'),Input('agg-level','value')])
def update_graph(order_books,pair,exchanges,x_scale,y_scale,cutoff,step):
    relative = x_scale == 'Relative'
    currency = y_scale == 'Currency'
    order_books = json.loads(order_books)[0]
    order_books= {key:order_books[key] for key in order_books if key in exchanges}
    return plot_book(order_books,pair,exchanges,relative,currency,cutoff)

@app.callback(Output('market-depth','figure'),
            [Input('the-data','children'),
            Input('pairs','value'),Input('exchanges','value'),
            Input('x-scale','value'),Input('y-scale','value'),
            Input('cutoff','value'),Input('agg-level','value')])
def update_depth(order_books,pair,exchanges,x_scale,y_scale,cutoff,step):
    relative = x_scale == 'Relative'
    currency = y_scale == 'Currency'
    order_books = json.loads(order_books)[0]
    order_books= {key:order_books[key] for key in order_books if key in exchanges}
    return plot_depth(order_books,pair,exchanges,relative,currency,cutoff)

@app.callback(Output('order-table','children'),
            [Input('the-data','children'),
            Input('pairs','value'),Input('exchanges','value'),
            Input('x-scale','value'),Input('y-scale','value'),
            Input('cutoff','value'),Input('agg-level','value')])
def update_table(order_books,pair,exchanges,x_scale,y_scale,cutoff,step):
    order_books = json.loads(order_books)[0]
    order_books= {key:order_books[key] for key in order_books if key in exchanges}
    df =  build_book(order_books,pair,exchanges,cutoff,step)
    df_bids = df[[i for i in df.columns if 'bid' in i]].dropna().iloc[:12]
    df_bids['side'] = 'bid'
    df_bids.columns=['price','size','cum_size','size_$','cum_size_$','average_fill','exc','side']
    df_asks = df[[i for i in df.columns if 'ask' in i]].dropna().iloc[:12]
    df_asks['side'] = 'ask'
    df_asks.columns = ['price','size','cum_size','size_$','cum_size_$','average_fill','exc','side']
    mid=(df_bids['price'].max() + df_asks['price'].min())/2
    df_all=pd.concat([df_asks.sort_values(by='price',ascending=False),df_bids]).rename_axis('from_mid')
    step2 = step if step !=0 else .000001
    rounding = [int(np.ceil(-np.log(mid*step2)/np.log(10)))+1] 
    decimals=pd.Series(rounding+[2]*4+rounding+[2]*2,index=df_all.columns)
    df_all=df_all.round(decimals).reset_index()
    return dash_table.DataTable(
        data=df_all.to_dict('rows'),
        columns=[{'id': c,'name':c} for c in df_all.columns[:-1]] + [{'id':'side','name':'side','hidden':True}],
        style_table={'border': 'thin lightgrey solid'},
        style_header={'backgroundColor':'lightgrey','fontWeight':'bold'},
        style_cell={'textAlign':'center','width':'12%'},
        style_data_conditional=[{
            'if' : {'filter':  'side eq "bid"' },
            'color':'blue'
                    }
            ]+[
            {
            'if' : {'filter': 'side eq "ask"' },
            'color':'red'
        }]+[
            { 'if': {'row_index':'odd'},
            'backgroundColor':'rgb(242,242,242)'}
        ]+[
            {'if':{'column_id':'price'},
            'fontWeight':'bold',
            'border': 'thin lightgrey solid'}
        ],
        style_as_list_view=True
    )

@app.callback(Output('liquidity-table','children'),
            [Input('the-data','children'),
            Input('pairs','value'),Input('exchanges','value'),
            Input('x-scale','value'),Input('y-scale','value'),
            Input('cutoff','value'),Input('agg-level','value')])
def update_liquidity_table(order_books,pair,exchanges,x_scale,y_scale,cutoff,step):
    order_books = json.loads(order_books)[0]
    order_books= {key:order_books[key] for key in order_books if key in exchanges}
    df =  build_book(order_books,pair,exchanges,cutoff,step)
    liq_df = get_liq_params(df,pair)[0].round(4)
    return dash_table.DataTable(
            data=liq_df.to_dict('rows'),
            columns=[{'id': c,'name':c} for c in liq_df.columns],
            style_header={'backgroundColor':'lightgrey','fontWeight':'bold'},
            style_cell={'textAlign':'center','width':'10%'},
            style_table={'border': 'thin lightgrey solid'},
            style_as_list_view=True)

@app.callback(Output('depth-table','children'),
            [Input('the-data','children'),
            Input('pairs','value'),Input('exchanges','value'),
            Input('x-scale','value'),Input('y-scale','value'),
            Input('cutoff','value'),Input('agg-level','value')])
def update_depth_table(order_books,pair,exchanges,x_scale,y_scale,cutoff,step):
    order_books = json.loads(order_books)[0]
    order_books= {key:order_books[key] for key in order_books if key in exchanges}
    df =  build_book(order_books,pair,exchanges,cutoff,step)
    liq_df = get_liq_params(df,pair)[1].round(4)
    return dash_table.DataTable(
            data=liq_df.to_dict('rows'),
            columns=[{'id': c,'name':c} for c in liq_df.columns],
            style_header={'backgroundColor':'lightgrey','fontWeight':'bold'},
            style_cell={'textAlign':'center','width':'10%'},
            style_table={'border': 'thin lightgrey solid'},
            style_as_list_view=True)

@app.callback(Output('stat-table','children'),
            [Input('the-data','children'),
            Input('pairs','value'),Input('exchanges','value'),
            Input('x-scale','value'),Input('y-scale','value'),
            Input('cutoff','value'),Input('agg-level','value')])
def update_stat_table(order_books,pair,exchanges,x_scale,y_scale,cutoff,step):
    order_books = json.loads(order_books)[0]
    order_books= {key:order_books[key] for key in order_books if key in exchanges}
    df =  build_book(order_books,pair,exchanges,cutoff,step)
    liq_df = get_liq_params(df,pair)[2].round(4)
    return dash_table.DataTable(
            data=liq_df.to_dict('rows'),
            columns=[{'id': c,'name':c} for c in liq_df.columns],
            style_header={'backgroundColor':'lightgrey','fontWeight':'bold'},
            style_cell={'textAlign':'center','width':'10%'},
            style_table={'border': 'thin lightgrey solid'},
            style_as_list_view=True)

if __name__ == '__main__':
    app.run_server(debug=True,port = 8055)

I’m having trouble in the bootstrapping portion and ccxt. Could you provide the versions of the other packages you are using? Thanks.

Good morning

ccxt == 1.18.356
pandas == 0.24.1
numpy == 1.16.2

sometimes some ccxt calls to the exchanges time out
for the purpose of this test if you have an error message like this one:
: Read timed out. (read timeout=10)**
just change the first line of the code
spot_exchanges = [‘bitfinex’,‘bitstamp’,‘coinbasepro’,‘kraken’,‘liquid’,‘gemini’,‘binance’,
‘bitbank’,‘huobipro’,‘poloniex’,‘bithumb’,‘bittrex’,‘kucoin2’]

to
spot_exchanges = [‘liquid’,‘coinbasepro’]

hope this works

Thank you

Marc André

My page is in two 6 columns div
I moved the widget from the right to the left of the page without changing any other logic and now it is working
Does this make any sense ?
Can it be related to the page format ?

If I leave the widget on the right side but I comment out the external_stylesheets :

external_stylesheets = [‘https://codepen.io/chriddyp/pen/bWLwgP.css’]
app = dash.Dash(name) #,external_stylesheets=external_stylesheets)

The layout is messed up , as expected I lose the columns Divs but the RadioItem works
I tested this on my other app where the deletable row in a table was not deleting and it worked too

I have removed all dependencies from the previous code I had sent you .
Here I have a very simple code snippet isolating the problem.
It is related to tables and layouts with 2 columns :
In summary If I have a table updated by a widget which is on the right side of my layout this widget is frozen
if I remove the table and replace it with simple text the widget works. And the text is updated by the widget
if I move the widget to the left side of the page it works and updates the table
if I roll back to earlier Dash versions all works
please see the code below: Hope you will be able to identify the source of the problem.

Code:

import dash
import dash_core_components as dcc 
import dash_html_components as html 
import dash_table  
from dash.dependencies import Input, Output
import plotly.graph_objs as go 
import plotly.figure_factory as ffimport 
import pandas as pd
import plotly.graph_objs as go
import numpy as np
import itertools
import json
import datetime as dt

spot_exchanges = ['bitstamp','coinbasepro','kraken','liquid','bitfinex','gemini','binance',
                    'bitbank','huobipro','poloniex','bithumb','bittrex']
spot_exchanges_2 = ['bitstamp','coinbasepro']

Xpto= ['BTC','ETH','XRP','XMR','BCH','EOS','USDT','USDC','TRX','XLM','BSV','XBT','CSP']
Fiat=['USD','EUR','GBP','CHF','HKD','JPY','CNH','KRW']
xpto_fiat = [xpto+'/'+ fiat for xpto in Xpto for fiat in Fiat]
xpto_xpto = [p[0]+'/'+p[1] for p in itertools.permutations(Xpto,2)]
pairs = xpto_fiat + xpto_xpto
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.title = 'Order Books'
app.layout = html.Div(style={'marginLeft':25,'marginRight':25},
                    children=[ html.Div(className='row',children=[
                                html.Div(className='six columns',
                                children =[html.H6('Choose Pair'),
                                            dcc.Dropdown(id='pairs',
                                                        options=[{'label':pair,'value':pair} for pair in pairs],
                                                        value='BTC/USD'),
                                            html.Div(className='row',children=[
                                            html.Div(className='three columns',children = [html.H6('\n \n X scale :')]),
                                            html.Div(className='three columns',children=[dcc.RadioItems(id='x-scale',
                                                        options=[{'label':scale,'value':scale} for scale in ['Relative','Absolute']], 
                                                        value ='Relative',
                                                        labelStyle={'display':'inline-block'})])
                                            ]),
                                            html.Div(className='row',children=[
                                            html.Div(className='three columns',children = [html.H6('\n \n Y scale :')]),
                                            html.Div(className='three columns',children=[dcc.RadioItems(id='y-scale',
                                                        options=[{'label':scale,'value':scale} for scale in ['Currency','Coin']], 
                                                        value ='Currency',
                                                        labelStyle={'display':'inline-block'})])
                                            ]),
                                            html.H6('\n \n Cutoff'),
                                            html.Div(style={'width' :'50%','align':'right'},children =[
                                            dcc.Slider(id='cutoff',
                                                    min=.05,max=.3,step=.05,value=.1,
                                                    marks={round(j,2): str(round(j,2)) for j in list(np.arange(.05,.35,.05))})]),
                                            html.H6(' ... '),        
                                            html.H6(' Book Chart'),
                                            html.H6(id='order-book-chart'),
                                            html.H6(id='market-depth'),
                                            html.H6(id='time')
                                             ]),
                                html.Div(className='six columns',
                                    children =[html.H6('Choose Exchange'),
                                                dcc.Dropdown(id='exchanges',multi=True),
                                                html.Div(children = [
                                                html.Div(className='three columns',children = [html.Div('Order Book')]),
                                                html.Div(className='three columns',children = [html.H6('Bps Aggregation Level:')]),
                                                html.Div(className = 'three columns', children =
                                                                                        [dcc.RadioItems(id='agg-level',
                                                                                    options=[{'label':i,'value':i/10000} for i in [0,.1,1,10,100,1000,10000]],value=10/10000,
                                                                                    labelStyle={'display':'inline-block'})])]),
                                                html.H6(id='order-table'),
                                                html.Div('Liquidity Metrics'),
                                                html.H6('-----------------'),
                                                html.H6(id='liquidity-table')]),
                                html.Div(id='the-data',style={'display':'none'}),
                                dcc.Interval(
                                    id='interval-component',
                                    interval=10*1000, # in milliseconds= 6 seconds
                                    n_intervals=0
                                    ),
                                dcc.Interval(
                                    id='second',
                                    interval = 1000,
                                    n_intervals=0
                                )     
                        ])
                        ])

@app.callback(Output('time','children'),
            [Input('second','n_intervals'),Input('the-data','children')])
def update_time(n,order_books):
    time_snap = json.loads(order_books)[1]
    return (dt.datetime.now()-dt.datetime.strptime(time_snap,"%Y-%m-%d  %H:%M:%S")).seconds

@app.callback(Output('exchanges','options'),
            [Input('pairs','value')])
def update_exchanges_options(pair):
    return [{'label':exch,'value':exch} for exch in spot_exchanges]

@app.callback(Output('exchanges','value'),
            [Input('pairs','value')])
def update_exchanges_values(pair):
    return spot_exchanges

@app.callback(Output('the-data','children'),
            [Input('pairs','value'),Input('exchanges','value'),Input('interval-component','n_intervals')])
def update_data(pair,ex,n):
    now = dt.datetime.now() 
    save_this = ('saved',now.strftime("%Y-%m-%d  %H:%M:%S"))
    return json.dumps(save_this)

@app.callback(Output('order-book-chart','children'),
            [Input('the-data','children'),
            Input('pairs','value'),Input('exchanges','value'),
            Input('x-scale','value'),Input('y-scale','value'),
            Input('cutoff','value'),Input('agg-level','value')])
def update_graph(order_books,pair,exchanges,x_scale,y_scale,cutoff,step):
    
    return 'xscale is {}'.format(x_scale)

@app.callback(Output('market-depth','children'),
            [Input('the-data','children'),
            Input('pairs','value'),Input('exchanges','value'),
            Input('x-scale','value'),Input('y-scale','value'),
            Input('cutoff','value'),Input('agg-level','value')])
def update_depth(order_books,pair,exchanges,x_scale,y_scale,cutoff,step):
    
    return 'yscale is {}'.format(y_scale)

@app.callback(Output('order-table','children'),
            [Input('the-data','children'),
            Input('pairs','value'),Input('exchanges','value'),
            Input('x-scale','value'),Input('y-scale','value'),
            Input('cutoff','value'),Input('agg-level','value')])
def update_table(order_books,pair,exchanges,x_scale,y_scale,cutoff,step):
    df = pd.DataFrame([x_scale,y_scale,cutoff,step])
    return dash_table.DataTable(
            data=df.to_dict('rows'),
            columns=[{'id': c,'name':c} for c in df.columns],
            style_header={'backgroundColor':'lightgrey','fontWeight':'bold'},
            style_cell={'textAlign':'center','width':'10%'},
            style_table={'border': 'thin lightgrey solid'},
            style_as_list_view=True) 

@app.callback(Output('liquidity-table','children'),
            [Input('the-data','children'),
            Input('pairs','value'),Input('exchanges','value'),
            Input('x-scale','value'),Input('y-scale','value'),
            Input('cutoff','value'),Input('agg-level','value')])
def update_liquidity_table(order_books,pair,exchanges,x_scale,y_scale,cutoff,step):
    return 'agg level is {}'.format(step)

if __name__ == '__main__':
    app.run_server(debug=True,port = 8056)

@jelkhoury Thanks for the simplified example, having a go at it right now.

PS: I have another app where a table with deletable rows doesn’t respond to X click and doesn’t delete the row after update.

I’ll take an educated guess for that one: The table is updated by callbacks but no callback requires a prop from the table as an input? If that’s your case, a fix for that issue is coming in the next release. Temporarily you can have some callback consume a table property as an input.

@jelkhoury So far it seems like choosing the pair, x scale, y scale, cutoff, and exchange works fine. I’m only encountering problems with “Order Book” – this seems to be related to a HTML structure problem that I have yet to identified. Dev tools show the table wrapping div to be out of place and the “Order Book” wrapper div to have height 0:


As a hack, resizing it / moving it allows for the “Order Book” radio items to be clicked normally.

By updating that wrapper div to:
html.Div(style={'overflow': 'hidden' }, children = [...])

It should style itself properly and allow for those radio buttons to be clicked normally.

I’m not sure what would cause this to change between dash 0.35.0 and 0.39.0 but as a general guideline, a div wrapping only position: float elements will have a height of 0.

Thank you Marc Andre

First the hack html.Div(style={‘overflow’: ‘hidden’ }, children = […]) worked
Second in the other app
The table data is updated by a callback is also used in another callback (State) to update a second table after a click of a button
I will send you a simplified version of this app with dependencies removed later today for you to test
Please be aware that this app was working fine in previous versions too so the two problems might be related:
e.g. Dev tools show the table wrapping div to be out of place