JavaScript обратный вызов для получения выбранного индекса глифа в Bokeh

Я создал визуальный график с использованием Bokeh, который показывает сеть, которую я создал с помощью Networkx. Теперь я хочу использовать TapTool для отображения информации, относящейся к любому узлу на графике, на котором я нажимаю. Граф это просто узлы и ребра. Я знаю, что я должен быть в состоянии использовать var inds = cb_obj.selected['1d'].indices; в функции обратного вызова Ja vaScript, чтобы получить индексы узлов (глифов), по которым щелкнули, но это как-то не работает, и я получаю сообщение об ошибке, Uncaught TypeError: Cannot read property '1d' of undefined, Сдвиг в правильном направлении будет принята с благодарностью.

Ниже мой код. Обратите внимание, что я определил свой график как график (), а не как рисунок (). Я не думаю, что это является причиной проблемы, но просто хотел упомянуть об этом. Также я пользуюсь window.alert(inds); просто чтобы посмотреть, какие ценности я получаю. Это не моя конечная цель, но я ожидаю, что это все равно сработает.

def draw_graph_____(self, my_network):
    self.graph_height, self.graph_width, self.graph_nodes, self.graph_edges, self.node_coords, self.node_levels = self.compute_graph_layout(my_network)

    graph = nx.DiGraph()
    graph.add_nodes_from(self.graph_nodes)
    graph.add_edges_from(self.graph_edges)

    plot            = Plot(plot_width = self.graph_width, plot_height = self.graph_height, x_range = Range1d(0.0, 1.0), y_range = Range1d(0.0, 1.0))
    plot.title.text = "Graph Demonstration"

    graph_renderer = from_networkx(graph, self.graph_layout, scale = 1, center = (-100, 100))
    graph_renderer.node_renderer.data_source.data["node_names"] = self.graph_nodes
    graph_renderer.node_renderer.data_source.data["index"]      = self.graph_nodes

    graph_renderer.node_renderer.glyph              = Circle(size = 40, fill_color = Spectral4[0])
    graph_renderer.node_renderer.selection_glyph    = Circle(size = 40, fill_color = Spectral4[2])
    graph_renderer.node_renderer.hover_glyph        = Circle(size = 40, fill_color = Spectral4[1])

    graph_renderer.edge_renderer.glyph              = MultiLine(line_color = "#CCCCCC", line_alpha = 0.8, line_width = 5)
    graph_renderer.edge_renderer.selection_glyph    = MultiLine(line_color = Spectral4[2], line_width = 5)
    graph_renderer.edge_renderer.hover_glyph        = MultiLine(line_color = Spectral4[1], line_width = 5)

    graph_renderer.selection_policy     = NodesAndLinkedEdges()
    graph_renderer.inspection_policy    = NodesAndLinkedEdges()

    x_coord = [coord[0] for coord in self.node_coords]
    y_coord = [coord[1] for coord in self.node_coords]
    y_offset = []

    for level in self.node_levels:
        for item in self.node_levels[level]:
            if self.node_levels[level].index(item) % 2 == 0:
                y_offset.append(20)
            else:
                y_offset.append(-40)

    graph_renderer.node_renderer.data_source.data["x_coord"]    = x_coord
    graph_renderer.node_renderer.data_source.data["y_coord"]    = y_coord
    graph_renderer.node_renderer.data_source.data["y_offset"]   = y_offset

    labels_source   = graph_renderer.node_renderer.data_source
    labels          = LabelSet(x = "x_coord", y = "y_coord", text = 'node_names', text_font_size = "12pt", level = 'glyph',
                               x_offset = -50, y_offset = "y_offset", source = labels_source, render_mode = 'canvas')
    plot.add_layout(labels)

    callback = CustomJS(args = dict(source = graph_renderer.node_renderer.data_source), code =
    """
    console.log(cb_obj)
    var inds = cb_obj.selected['1d'].indices;
    window.alert(inds);
    """)

    plot.add_tools(HoverTool(tooltips = [("Node", "@node_names"), ("Recomm", "Will put a sample recommendation message here later")]))
    plot.add_tools(TapTool(callback = callback))

    plot.renderers.append(graph_renderer)

    output_file("interactive_graphs.html")

    show(plot)

Кстати, мой импорт выглядит следующим образом:

import collections
import networkx             as nx
import numpy                as np

from bokeh.io               import output_file, show
from bokeh.models           import Circle, ColumnDataSource, CustomJS, Div, HoverTool, LabelSet, MultiLine, OpenURL, Plot, Range1d, TapTool
from bokeh.models.graphs    import from_networkx, NodesAndLinkedEdges
from bokeh.palettes         import Spectral4

Я извиняюсь за то, что не публикую весь код, но для этого потребуется немало изменений, чтобы сделать фиктивные данные и показать другие файлы и функции (которые у меня должны быть), но я подумал, что одной этой функции может быть достаточно для выявления проблемы, Если нет, я с удовольствием поделюсь большим количеством кода. Спасибо!

1 ответ

Решение

Проблема в том, что обратный вызов не привязан к источнику данных. Значение cb_obj любой объект вызывает обратный вызов. Но только ColumnDataSource объекты имеют selected свойство, поэтому только обратные вызовы в источниках данных будут иметь cb_obj.selected, Если вы хотите, чтобы обратный вызов срабатывал при изменении выбора, т. Е. При каждом щелчке по узлу, тогда вы захотите иметь обратный вызов в источнике данных. [1]

Однако, если вы хотите иметь обратный вызов, когда узел просто наводится (но не нажимается), это проверка, а не выбор. Вы хотите следовать этому примеру:

https://bokeh.pydata.org/en/latest/docs/user_guide/interaction/callbacks.html

Хотя он не часто используется (и, следовательно, не очень хорошо документирован), обратный вызов для инструментов наведения получает дополнительную информацию в cb_data параметр. это cb_data Параметр используется в качестве универсального механизма для инструментов, чтобы иметь возможность передавать дополнительные вещи, специфичные для инструмента, в обратный вызов. В случае инструментов для наведения, cb_data это объект, который имеет .index а также .geometry атрибутов. Так cb_data.index['1d'].indices имеет индексы точек, которые в данный момент находятся над. .geometry атрибут как информация о том, какой тип теста был выполнен (т. е. была ли одна точка? или вертикальный или горизонтальный промежуток? и каково было расположение точки или пролета?)

[1] В качестве альтернативы, инструменты крана также проходят специализированный cb_data как описано выше. Это объект с .source свойство, что источник данных, который сделал выбор. Так cb_data.source.selected должно сработать. На практике я никогда не использую это, поскольку обратный вызов источника данных работает одинаково хорошо.

Другие вопросы по тегам