繁体   English   中英

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

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

我刚刚在 Django 网站上试用了 Bokeh。 那里有很多很好的教程,而且很容易快速运行示例。 例如,我有一个 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})

    ...

然后在 Django 模板中,我只有:

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

我有一个可爱的数据直方图,否则会在该视图的表格中显示。 我喜欢。

现在,与许多视图一样,同一个视图有一堆主要用于过滤数据的设置和一个刷新按钮。 刷新按钮触发 AJAX 回调到视图,该视图及时提供 JSON 中的日期,并且页面上的表格和其他所有内容都在 JS 中更新以反映新数据。 所有相当标准的东西。

las,图表不会更新。 这不是新的,我可以找到很多关于它的问题和答案。 但是,紧缩是他们还没有让我满意,而且我已经用完了可找到的答案。 我查看了 Bokeh 文档,但还没有找到乐趣。

因此,要分解桌面上已经不完全令我满意的解决方案:

  1. 运行散景服务器 这将与 Django UWSGI 服务并行,它有一些好处,但恕我直言,这里有点矫枉过正。 我知道它使用 Tornado(和 web 套接字)与客户端 JS 进行通信(包含在{{ graph_script| safe }}中,当然根据所有文档,它是来自 CDN 的依赖项)。 但这是我的 Django 应用程序获得了 AJAX 对新数据的请求,即使我想添加这一点基础设施(当然我以后可能会有其他好处)我仍然坚持使用 Django 应用程序告诉 Bokeh 服务器该图有新数据。 所以他们也需要谈谈。

  2. 使用 散景 las,这看起来像是向构建图形客户端的范式转变。 确实更具吸引力和回退选项,我只是将数据发送到客户端(通过页面加载时呈现的模板和更新时之后的 AJAX)并呈现图形客户端。 缺点是帮助中的警告(这正在开发中并且可能会更改),以及在某种意义上找不到似乎符合标准 Bokeh 演示和教程的更简单解决方案的挫败感。

  3. 配置图形以使用AjaxDataSource 老实说,我不太确定如何将它与上面的示例集成,但还没有对此进行深入研究,因为它不令人满意,在它自己的 Ajax 调用和轮询间隔上工作。 当用户更改设置并请求更新时,我已经按需拨打了 Ajax 电话,我希望图表从我的电话返回中获取数据,而不是它自己的数据。 基本上,我将此处的图表插入到现有上下文中,并采用其操作方法。

  4. 使用CustomJS 回调 这真的很有希望。 在服务器端,我可以访问具有js_on_change方法的对象,但是帮助没有提供关于如何从自己的 JS 代码(尚未访问任何 Bokeh 对象)触发 CustomJS 定义的此类回调的真正线索 function。 简而言之,它是与非 Bokeh 小部件的交互,页面上现有的刷新按钮执行 AJAX 调用,获取数据,现在想要更新图形。 但是那个小部件没有(看似很好的文档或我发现的)用新数据和轴刻度等重新配置图形的方法。

那么我理想的解决方案是什么样的呢? 那么,在我上面的 Python 视图中,我添加了数据:

    # as above ... except:

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

然后在 JavaScript 中的客户端,当我获取表数据并对其进行处理以更新表时,我也可以通过某种方式对“graph_div”说类似的话:“嘿,我有新的 x 和 y 数据供您使用以及轴配置”。

简而言之,一点 BokehJS(能够引用图形客户端并更新它)和一点标准 Bokeh(为第一页加载生成图形服务器端)。

这当然需要graph_script (由bokeh.embed.components 生成)包含图形 object 的名称(由组件提供),它向 JS 公开图形的 BokehJS 类接口。

我可以查看graph_div并看到它被赋予了一个id和一个data-root-id

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

看似由 Bokeh 分配,当然需要 BokehJS 找到我猜的 div。 我同样可以查看graph_script并看到它确实在根目录中嵌入了两个变量docs_jsonrender_items并且还打破了浏览器调试器并检查并看到有一个很好的全局 object Bokeh非常可用,非常希望它可以用于当它通过我们自己的 Ajax 调用(提供更多内容)到达时,将 JS 中的新数据和轴配置提供给现有的 Bokeh 图。

我怀疑如果我在这里找到解决方案,或者有人可以帮助我找到一个解决方案,那么我可能会写一个小教程是值得的; ;-)

我使用你的博客在 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