2017-08-14 56 views
0

我的問題與this question類似,但我有代碼示例。我已經創造了一個Django應用程序,情節次游泳比賽的事件隨着時間的推移遊散景圖,並使用一個情節散景Dynamic Axis Scaling

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小部件,並在時間CustomJS功能,只顯示一行。問題是,如果一個事件的時間爲10分鐘,另一個事件的時間爲25秒,則y軸(時間)會縮放,以便兩者在可見時都可以看到。我宣佈各行像

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

Select小部件一樣

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

如果這是有幫助的。我也自定義格式化Y軸爲

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

,以便它正確顯示時間。其他一切都只是格式化數據,將行設置爲可見或不可用以及回調函數。是否有辦法爲每條線動態縮放單個軸?我想最理想的情況是我可以爲每一行設置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日期時間序列化器。

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

如果您只想一次顯示一行,則更簡單的解決方案可能是更新單行數據。一種方法是將數據直接「模板化」到回調文本中。我並不總是推薦這種方法,但不-龐大的數據量也可以工作得很好:

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

這樣就會根據Select值改變y軸比例嗎?因爲如果是這樣,那可能比我以前做的更容易。 –

+0

我不明白你的問題。上面的例子是完整的,可以按照原樣運行,以評估它在完整用例的情況下的運行方式。 – bigreddot

+1

哦,對不起,不是在那裏感謝一噸! –