我的問題與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)
希望這不是太混亂。基本上,我正在循環每個事件爲該運動員,並獲得數據,如果它存在,然後在回調只是設置源數據。
這樣就會根據Select值改變y軸比例嗎?因爲如果是這樣,那可能比我以前做的更容易。 –
我不明白你的問題。上面的例子是完整的,可以按照原樣運行,以評估它在完整用例的情況下的運行方式。 – bigreddot
哦,對不起,不是在那裏感謝一噸! –