2017-08-14 6 views
0

私の質問はthis questionに似ていますが、私はコード例があります。私は、プロット時間が時間をかけて競争力の水泳イベントで泳いだDjangoのアプリでボケチャートを作成し、1つのプロットBokehダイナミック軸スケーリング

plot = figure(
     title='Event Progress', 
     x_axis_label='Date', 
     y_axis_label='Time', 
     x_axis_type='datetime', 
     plot_width=400, 
     plot_height=200, 
     tools=tools, 
     responsive=True, 
    ) 

Selectウィジェット、そして一度に表示1行にCustomJS機能を使用していますしています。問題は、1つのイベントに10分の時間があり、別のイベントに25秒の時間がある場合、y軸(時間)は両方が表示されるようにスケーリングされます。それは便利です場合、私は

plot_lines[event] = plot.line('x_'+event, 
           'y_'+event, 
           line_width=4, 
           source=source) 

などの各ラインと

multi_select = Select(title="Select Event:", 
         value=events[0], 
         options=events, 
         callback=callback) 

ようSelectウィジェットを宣言します。私はy軸を、正確に時刻を表示するように

plot.yaxis.formatter = FuncTickFormatter(code=""" 
    return Math.floor(tick/60) + ":" + tick.toFixed(2) 
""") 

という形式でカスタム設定しています。それ以外は、データを書式設定し、行を可視にするかどうかとコールバック関数に設定するだけです。各行の1つの軸を動的にスケールする方法はありますか?私は理想的には、グラフの境界線内に収めるために、各行のy-maxとy-minを設定できると思いますが、可能かどうか、または他のオプションが何であるかわかりません。

更新:ここに私の完全なコードがあります。

def graph_event(swimmer): 
hover_tool = date_time_hover_tool() 
tools = ['pan', 'box_zoom', hover_tool, 'reset', 'save'] 
plot = figure(
    title='Event Progress', 
    x_axis_label='Date', 
    y_axis_label='Time', 
    x_axis_type='datetime', 
    plot_width=400, 
    plot_height=200, 
    tools=tools, 
    responsive=True, 
) 
# format datetime.timedelta objects to MM:ss.mm 
plot.yaxis.formatter = FuncTickFormatter(code= 
    """ 
    return Math.floor(tick/60) + ":" + tick.toFixed(2) 
    """ 
) 

data_source = {} 
events = [] 
first_event = None 
for event in EVENT_CHOICE: 
    e = '_'.join([word.lower() for word in event[0].split()]) 

    results = Event.objects.filter(swimmer=swimmer).filter(event=event[0]).order_by('date') 
    if results.exists(): 
     if len(results) == 1: # one point will not display well on graph 
      data_source['x_'+e] = None 
      data_source['y_'+e] = None 
      data_source['date_'+e] = None 
      data_source['time_'+e] = None 
      continue 

     events.append(event[1]) 
     if first_event == None: 
      first_event = e 

     x, y = [], [] 
     date, time = [], [] 
     for r in results.iterator(): 
      d = r.date 
      t = r.time.total_seconds() 
      x.append(d) 
      y.append(t) 
      date.append(d.strftime('%m/%d/%y')) # date to string for hover 
      time.append('{:d}:{:.2f}'.format(int(t)/60, t)) # time to string for hover 

     data_source['x_'+e] = x 
     data_source['y_'+e] = y 
     data_source['date_'+e] = date 
     data_source['time_'+e] = time 

    else: 
     # eliminates KeyError exceptions 
     data_source['x_'+e] = None 
     data_source['y_'+e] = None 
     data_source['date_'+e] = None 
     data_source['time_'+e] = None 

# set initial graph 
source = ColumnDataSource(data=dict(
    x=data_source['x_'+first_event], 
    y=data_source['y_'+first_event], 
    date=data_source['date_'+first_event], 
    time=data_source['time_'+first_event] 
)) 
plot.line('x', 'y', source=source) 

try: 
    select = Select(title="Select Event:", value=events[0], options=events) 
except IndexError: 
    return None, None 

# callback modifies data source depending on Select box 
callback = CustomJS(args=dict(source=source, select=select), code=""" 
     data = %s; 

     if (select.value == "50 Freestyle") { 
      source.data['x'] = data.x_50_free; 
      source.data['y'] = data.y_50_free; 
      source.data['date'] = data.date_50_free; 
      source.data['time'] = data.time_50_free; 
     } else if (select.value == "100 Freestyle") { 
      source.data['x'] = data.x_100_free; 
      source.data['y'] = data.y_100_free; 
      source.data['date'] = data.date_100_free; 
      source.data['time'] = data.time_100_free; 
     } else if (select.value == "200 Freestyle") { 
      console.log(select.value); 
      source.data['x'] = data.x_200_free; 
      source.data['y'] = data.y_200_free; 
      source.data['date'] = data.date_200_free; 
      source.data['time'] = data.time_200_free; 
     } else if (select.value == "500 Freestyle") { 
      source.data['x'] = data.x_500_free; 
      source.data['y'] = data.y_500_free; 
      source.data['date'] = data.date_500_free; 
      source.data['time'] = data.time_500_free; 
     } else if (select.value == "1000 Freestyle") { 
      source.data['x'] = data.x_1000_free; 
      source.data['y'] = data.y_1000_free; 
      source.data['date'] = data.date_1000_free; 
      source.data['time'] = data.time_1000_free; 
     } else if (select.value == "50 Backstroke") { 
      source.data['x'] = data.x_50_back; 
      source.data['y'] = data.y_50_back; 
      source.data['date'] = data.date_50_back; 
      source.data['time'] = data.time_50_back; 
     } else if (select.value == "100 Backstroke") { 
      source.data['x'] = data.x_100_back; 
      source.data['y'] = data.y_100_back; 
      source.data['date'] = data.date_100_back; 
      source.data['time'] = data.time_100_back; 
     } else if (select.value == "200 Backstroke") { 
      source.data['x'] = data.x_200_back; 
      source.data['y'] = data.y_200_back; 
      source.data['date'] = data.date_200_back; 
      source.data['time'] = data.time_200_back; 
     } else if (select.value == "50 Breaststroke") { 
      source.data['x'] = data.x_50_breast; 
      source.data['y'] = data.y_50_breast; 
      source.data['date'] = data.date_50_breast; 
      source.data['time'] = data.time_50_breast; 
     } else if (select.value == "100 Breaststroke") { 
      source.data['x'] = data.x_100_breast; 
      source.data['y'] = data.y_100_breast; 
      source.data['date'] = data.date_100_breast; 
      source.data['time'] = data.time_100_breast; 
     } else if (select.value == "200 Breaststroke") { 
      source.data['x'] = data.x_200_breast; 
      source.data['y'] = data.y_200_breast; 
      source.data['date'] = data.date_200_breast; 
      source.data['time'] = data.time_200_breast; 
     } else if (select.value == "50 Butterfly") { 
      source.data['x'] = data.x_50_fly; 
      source.data['y'] = data.y_50_fly; 
      source.data['date'] = data.date_50_fly; 
      source.data['time'] = data.time_50_fly; 
     } else if (select.value == "100 Butterfly") { 
      source.data['x'] = data.x_100_fly; 
      source.data['y'] = data.y_100_fly; 
      source.data['date'] = data.date_100_fly; 
      source.data['time'] = data.time_100_fly; 
     } else if (select.value == "200 Butterfly") { 
      source.data['x'] = data.x_200_fly; 
      source.data['y'] = data.y_200_fly; 
      source.data['date'] = data.date_200_fly; 
      source.data['time'] = data.time_200_fly; 
     } else if (select.value == "100 IM") { 
      source.data['x'] = data.x_100_im; 
      source.data['y'] = data.y_100_im; 
      source.data['date'] = data.date_100_im; 
      source.data['time'] = data.time_100_im; 
     } else if (select.value == "200 IM") { 
      source.data['x'] = data.x_200_im; 
      source.data['y'] = data.y_200_im; 
      source.data['date'] = data.date_200_im; 
      source.data['time'] = data.time_200_im; 
     } else if (select.value == "400 IM") { 
      source.data['x'] = data.x_400_im; 
      source.data['y'] = data.y_400_im; 
      source.data['date'] = data.date_400_im; 
      source.data['time'] = data.time_400_im; 
     } else if (select.value == "Base Freestyle") { 
      source.data['x'] = data.x_base_free; 
      source.data['y'] = data.y_base_free; 
      source.data['date'] = data.date_base_free; 
      source.data['time'] = data.time_base_free; 
     } else if (select.value == "Base Backstroke") { 
      source.data['x'] = data.x_base_back; 
      source.data['y'] = data.y_base_back; 
      source.data['date'] = data.date_base_back; 
      source.data['time'] = data.time_base_back; 
     } else if (select.value == "Base Breaststroke") { 
      source.data['x'] = data.x_base_breast; 
      source.data['y'] = data.y_base_breast; 
      source.data['date'] = data.date_base_breast; 
      source.data['time'] = data.time_base_breast; 
     } else if (select.value == "Base Butterfly") { 
      source.data['x'] = data.x_base_fly; 
      source.data['y'] = data.y_base_fly; 
      source.data['date'] = data.date_base_fly; 
      source.data['time'] = data.time_base_fly; 
     } else if (select.value == "Base IM") { 
      source.data['x'] = data.x_base_im; 
      source.data['y'] = data.y_base_im; 
      source.data['date'] = data.date_base_im; 
      source.data['time'] = data.time_base_im; 
     } 

     source.change.emit() 
""" % json.dumps(data_source, cls=DatetimeEncoder)) 

select.callback = callback 

return components(column(select, plot, responsive=True)) 

ここでは、ホバーツールとJSON datetimeシリアライザについて説明します。

class DatetimeEncoder(json.JSONEncoder): 
""" 
Encodes Python datetime.date objects to make compatible with JSON serialization. 
""" 
def default(self, obj): 
    try: 
     return super(DatetimeEncoder, obj).default(obj) 
    except TypeError: 
     return str(obj) 

def date_time_hover_tool(): 
    """ 
    Generates the HTML for the Bokeh's hover data tool on our graph. 
    """ 
    hover_html = """ 
     <div> 
     <span class="hover-tooltip">@date</span> 
     </div> 
     <div> 
     <span class="hover-tooltip">@time</span> 
     </div> 
    """ 
    return HoverTool(tooltips=hover_html) 

これがあまりにも混乱しないことを望みます。基本的には、そのアスリートの各イベントをループし、データがあればそれを取得し、次にソースデータを設定するだけのコールバックで処理します。

答えて

0

一度に1行だけ表示する場合は、1行分のデータを更新する方が簡単な解決策になることがあります。 1つのアプローチは、データを直接コールバックテキストに「テンプレート化」することです。私はいつもこの方法をお勧めしませんが、-巨大ではないデータはサイズのために、それは非常にうまく機能することができます。

import json 
from math import sin 

from bokeh.io import output_file, show 
from bokeh.layouts import column 
from bokeh.models import ColumnDataSource, CustomJS, Select 
from bokeh.plotting import figure 

output_file("foo.html") 

xl, yl = list(range(100)), [sin(x) for x in xl] 
xs, ys = list(range(500)), [sin(x) for x in xs] 
source = ColumnDataSource(data=dict(x=xl, y=yl)) 

plot = figure() 
plot.line('x', 'y', source=source) 

select = Select(title="Event:", value="long", options=["long", "short"]) 

callback = CustomJS(args=dict(source=source, select=select), code=""" 
    data = %s; 
    if (select.value == "long") { 
     source.data['x'] = data.xl 
     source.data['y'] = data.yl 
    } else { 
     source.data['x'] = data.xs 
     source.data['y'] = data.ys 
    } 
    source.change.emit() 
""" % json.dumps(dict(xl=xl, yl=yl, xs=xs, ys=ys))) 

select.callback = callback 

show(column(select, plot)) 

あなた本当には別のラインレンダラーを持っている必要がある場合は、DataRange1dモデルが指定.renderers性質を持っていますレンダラーは自動的にレンダーされます。しかし、起動時とリセット時以外は反応しないように設定されています。したがって、あなたのJSコールバックは、範囲上で.renderersを適切に設定する必要があり、またその変更を有効にするには範囲の.resetメソッドを呼び出す必要があります。私はと思って、期待どおりに動作すると考えています。

+0

これにより、選択値に応じてy軸のスケールが変更されますか?もしそうなら、私がとにかくやっていたよりも簡単かもしれないから。 –

+0

私はあなたの質問を理解していません。上記の例は完全であり、完全なユースケースのコンテキストで動作する方法を評価するためにそのまま実行することができます。 – bigreddot

+1

ああ、こんにちは、申し訳ありませんが、おかげでトンを考えていなかった! –

関連する問題