2017-08-14 57 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

哦,对不起,不是在那里感谢一吨! –