简体   繁体   English

Bokeh:生成图形服务器端,从 JS 客户端更新图形(更改数据源、轴......)

[英]Bokeh: Generate graph server side, update graph from JS client side (change data source, axes ...)

I just took Bokeh for a spin on a Django site.我刚刚在 Django 网站上试用了 Bokeh。 Lot of fine tutorials out there and really easy to get a quick example running.那里有很多很好的教程,而且很容易快速运行示例。 For example, I have a Django view that offers something like:例如,我有一个 Django 视图,它提供如下内容:

    ... 
    # This is part of a broader Django view, simply adding a graph to it.

    # Fetch the x and y data for a histogram using a custom function.
    # It takes a queryset, a field in that queryset and returns, two lists one 
    # containing the unique values that some field took on and the second containing
    # the count of times it took on that value. 
    (values, frequency) = FrequencyData(some_queryset, "some field")

    p = figure(height=350,
               x_axis_label="Count of Players",
               y_axis_label="Number of Events",
               background_fill_alpha=0,
               border_fill_alpha=0,
               tools="pan,wheel_zoom,box_zoom,save,reset")

    p.xaxis.ticker = values
    p.yaxis.ticker = list(range(max(frequency) + 1))
    p.toolbar.logo = None

    p.vbar(x=values, top=frequency, width=0.9)

    p.y_range.start = 0

    graph_script, graph_div = components(p)

    context.update({"graph_script": graph_script,"graph_div": graph_div})

    ...

Then in the Django template I simply have:然后在 Django 模板中,我只有:

{{ graph_div | safe }}
{{ graph_script| safe }}

And I have a lovely histogram of data that is otherwise presented in a table on that view.我有一个可爱的数据直方图,否则会在该视图的表格中显示。 I like it.我喜欢。

Now the same view, like many, has a pile of settings for filtering the data primarily and a refresh button.现在,与许多视图一样,同一个视图有一堆主要用于过滤数据的设置和一个刷新按钮。 The refresh button triggers an AJAX callback to the view which duly delivers the date in JSON, and the table and everything else on the page are updated in JS to reflect the new data.刷新按钮触发 AJAX 回调到视图,该视图及时提供 JSON 中的日期,并且页面上的表格和其他所有内容都在 JS 中更新以反映新数据。 All fairly standard stuff.所有相当标准的东西。

Alas, the graph does not update. las,图表不会更新。 And this is not new, I can find a lot of questions and answers about that.这不是新的,我可以找到很多关于它的问题和答案。 BUT, the crunch is none of them have provided me with satisfaction yet, and I've run out of findable answers.但是,紧缩是他们还没有让我满意,而且我已经用完了可找到的答案。 I've looked through Bokeh docs and also not found joy yet.我查看了 Bokeh 文档,但还没有找到乐趣。

So, to break down the solutions on the table already that don't please me fully:因此,要分解桌面上已经不完全令我满意的解决方案:

  1. Run a Bokeh server .运行散景服务器 That would be in parallel to the Django UWSGI service, and It has some benefits but is overkill here IMHO.这将与 Django UWSGI 服务并行,它有一些好处,但恕我直言,这里有点矫枉过正。 I understand it communicates using Tornado (and web sockets) with the client side JS (included with {{ graph_script| safe }} and it's dependencies from a CDN of course as per all the docs).我知道它使用 Tornado(和 web 套接字)与客户端 JS 进行通信(包含在{{ graph_script| safe }}中,当然根据所有文档,它是来自 CDN 的依赖项)。 But it's my Django app that gets the AJAX request for new data and even if I wanted to add this bit of infrastructure (and I may later of course of other benefits) I'm stuck with the Django app telling the Bokeh server that the graph has new data.但这是我的 Django 应用程序获得了 AJAX 对新数据的请求,即使我想添加这一点基础设施(当然我以后可能会有其他好处)我仍然坚持使用 Django 应用程序告诉 Bokeh 服务器该图有新数据。 So they also need to talk.所以他们也需要谈谈。

  2. Using BokehJS .使用 散景 Alas, that looks like a paradigm shift to building the graph client side. las,这看起来像是向构建图形客户端的范式转变。 More attractive and a fall back option indeed, I just send the data to the client side (via the template rendered on page load and AJAX afterwards when updating) and render the graph client side.确实更具吸引力和回退选项,我只是将数据发送到客户端(通过页面加载时呈现的模板和更新时之后的 AJAX)并呈现图形客户端。 Downsides are the warning on the help (that this is in development and may change), and the frustration, in a sense, of not finding a simpler solution that seems in line with standard Bokeh demos and tutorials.缺点是帮助中的警告(这正在开发中并且可能会更改),以及在某种意义上找不到似乎符合标准 Bokeh 演示和教程的更简单解决方案的挫败感。

  3. Configuring the graph to use an AjaxDataSource .配置图形以使用AjaxDataSource To be honest I'm not quite sure how to integrate that with the sample above yet, but haven't researched this deeply as it's unsatisfying, works on its own Ajax call and a polling interval.老实说,我不太确定如何将它与上面的示例集成,但还没有对此进行深入研究,因为它不令人满意,在它自己的 Ajax 调用和轮询间隔上工作。 I already do an Ajax call, on demand when a user changes settings and asks for an update, and I'd like the graph to source the data from the return of my call, not its own.当用户更改设置并请求更新时,我已经按需拨打了 Ajax 电话,我希望图表从我的电话返回中获取数据,而不是它自己的数据。 Basically, I'm plugging a graph here into an existing context with its modus operandi in place.基本上,我将此处的图表插入到现有上下文中,并采用其操作方法。

  4. Using a CustomJS callback .使用CustomJS 回调 And this is really hopeful.这真的很有希望。 And at the server side I have access to objectsthat have the js_on_change method, but the help provides no real clue on how trigger such a callback a CustomJS defined function, from one's own JS code (with no acces as yet to any Bokeh object).在服务器端,我可以访问具有js_on_change方法的对象,但是帮助没有提供关于如何从自己的 JS 代码(尚未访问任何 Bokeh 对象)触发 CustomJS 定义的此类回调的真正线索 function。 In short, it is interaction with a non-Bokeh widget, an existing refresh button on a page that performs an AJAX call, fetches data, and now wants to update the graph.简而言之,它是与非 Bokeh 小部件的交互,页面上现有的刷新按钮执行 AJAX 调用,获取数据,现在想要更新图形。 But that widget has no (seemingly well document or found by me) method of reconfiguring the graph with new data and axes ticks etc.但是那个小部件没有(看似很好的文档或我发现的)用新数据和轴刻度等重新配置图形的方法。

So what does my ideal solution look like?那么我理想的解决方案是什么样的呢? Well, in my Python view above, I add the data:那么,在我上面的 Python 视图中,我添加了数据:

    # as above ... except:

    context.update({"graph_script": graph_script,"graph_div": graph_div, "values": values, "frequency": frequency})

and then client side in JavaScript when I get the table data and process it to update the table I also have some way of saying to "graph_div" something like: "Hey, I have new x and y data for you and axis configs".然后在 JavaScript 中的客户端,当我获取表数据并对其进行处理以更新表时,我也可以通过某种方式对“graph_div”说类似的话:“嘿,我有新的 x 和 y 数据供您使用以及轴配置”。

In short, a bit of BokehJS (being able to refer to the graph client side and update it) and a bit of standard Bokeh (generating the graph sever side for the first page load).简而言之,一点 BokehJS(能够引用图形客户端并更新它)和一点标准 Bokeh(为第一页加载生成图形服务器端)。

What this would need of course is for graph_script (produced by bokeh.embed.components ) to contain a name (provided by components) for a graph object, that exposes a BokehJS like interface for the graph to JS.这当然需要graph_script (由bokeh.embed.components 生成)包含图形 object 的名称(由组件提供),它向 JS 公开图形的 BokehJS 类接口。

I can look into graph_div and see it's given an id and a data-root-id :我可以查看graph_div并看到它被赋予了一个id和一个data-root-id

<div class="bk-root" id="ce45dc38-0977-4d2c-a8ae-033dafad5fc8" data-root-id="1078">

seemingly assigned by Bokeh of course and needed for BokehJS to find the div I'd guess.看似由 Bokeh 分配,当然需要 BokehJS 找到我猜的 div。 And I can likewise look at graph_script and see that it does embed in the root two variables docs_json and render_items and also break in the browser Debugger and inspect and see there's a nice global object Bokeh quite available, lending enormous hope that it can be used to supply new data and axes configs in JS to an existing Bokeh graph when it arrives via our own Ajax call (that delivers more stuff).我同样可以查看graph_script并看到它确实在根目录中嵌入了两个变量docs_jsonrender_items并且还打破了浏览器调试器并检查并看到有一个很好的全局 object Bokeh非常可用,非常希望它可以用于当它通过我们自己的 Ajax 调用(提供更多内容)到达时,将 JS 中的新数据和轴配置提供给现有的 Bokeh 图。

I suspect if I find a solution here, or someone can help me find one, it's worth a small tutorial that I might write;我怀疑如果我在这里找到解决方案,或者有人可以帮助我找到一个解决方案,那么我可能会写一个小教程是值得的; ;-) ;-)

I use your blog to find an answer using json request in django. My web page is bokeh.traimaocv.fr/index/slider slider send request to django server and get x and y data using json我使用你的博客在 django 中使用 json 请求找到答案。我的 web 页面是 bokeh.traimaocv.fr/index/slider slider 向 django 服务器发送请求并使用 8814095863 获取 x 和 y 数据

def get_arg_post(request, list_var):
    """
    Récupération des données POST dans une requète
    request --> objet HTTPrequest
    list-var --> liste des variables à extraire de la requête
    valeur retour --> booléen et liste des valeurs
    booléen False si les valeurs n'ont pas été trouvées
    """
    resultat = []
    if len(request.POST) > 0:
        for v in list_var:  
            if v in request.POST:
                try:   
                    resultat.append(request.POST[v])
                except:
                    return False, []
            else:
                return False, []
    else:
        return False, []
    return True, resultat

@csrf_protect
def sinus_slider(request: HttpRequest) -> HttpResponse:
    freq = 1
    b_ok, val = get_arg_post(request, ['freq'])
    if b_ok:
        freq = float(val[0])

    x = np.linspace(0, 10, 500)
    y = np.sin(freq*x)+freq
    source = ColumnDataSource(data=dict(x=x, y=y))

    plot = figure(y_range=(-10, 10), width=400, height=400,title="Ma Courbe",name="Mes_donnees")

    plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6,name="Mon_sinus")

    amp_slider = Slider(start=0.1, end=10, value=freq, step=.1, title="Amplitude")
    callback = CustomJS(args=dict(source=source, amp=amp_slider),
                        code="""
        var csrfToken = '';
        var i=0;
        var inputElems = document.querySelectorAll('input');
        var reponse='';
        for (i = 0; i < inputElems.length; ++i) {
            if (inputElems[i].name === 'csrfmiddlewaretoken') {
                csrfToken = inputElems[i].value;
                break;
                }
            }
        var xhr = new XMLHttpRequest();
        
        xhr.open("POST", "/index/slider_change", true);
        xhr.setRequestHeader('mode', 'same-origin');
        var dataForm = new FormData();
        dataForm.append('csrfmiddlewaretoken', csrfToken);
        dataForm.append('freq', amp.value);
        xhr.responseType = 'json';
        xhr.onload = function() {    
            reponse =  xhr.response
            source.data.x = reponse['x'];
            source.data.y = reponse['y'];
            const plot = Bokeh.documents[0].get_model_by_name('Mes_donnees')
            source.change.emit();
            }
        xhr.send(dataForm);
        """)

    amp_slider.js_on_change('value', callback)
    layout = row(plot, column(amp_slider))
    script1, div1  = components(layout, "Graphique")
    pos = div1.find('data-root-id="')
    id = int(div1[pos+14:pos+18])
    #layout.update()
    #script2, div2  = components(amp_slider, "slider freq")
    #html2 = file_html(layout, CDN, "my plot")
    #html2 = html2.replace("</head>","{% csrf_token %}</head>")
    code_html = render(request,"sinus_slider.html", dict(script1=script1, div=div1))
    return code_html

@csrf_protect
def sinus_slider_change(request: HttpRequest) -> HttpResponse:
    freq = 1
    b_ok, val = get_arg_post(request, ['freq'])
    if b_ok:
        freq = float(val[0])

    x = np.linspace(0, 10, 500)
    y = np.sin(freq*x)+freq
    
    return JsonResponse(dict(x=x.tolist(),y=y.tolist()))
 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM