Как вывести координаты настраиваемой точки на графике в таблицу с боке - PullRequest
0 голосов
/ 21 января 2020

Исходный вопрос

У меня есть график, определенный функцией y / 100 = (x / 100) ^ n

Я хочу определить x с помощью ползунка на bokeh и ( i) пометьте точку пунктирными линиями на осях x и y и (ii) найдите соответствующую координату y в таблице под ползунком.

Вот то, что я пробовал, но без успех (ползунок, таблица и график все есть, но при перемещении ползунка ничего не происходит).

###  Slider:: Interactive Graph with Python Bokeh
###  Source:: https://docs.bokeh.org/en/latest/docs/gallery/slider.html

import numpy as np
import pandas as pd

from bokeh.layouts import row, column
from bokeh.models import CustomJS, Slider, Title
from bokeh.plotting import figure, output_file, show, ColumnDataSource
from bokeh.models.widgets import DataTable, TableColumn

#%%  Read data from graph

##  Define parameters and function

n = 0.333333333333
w1, h1 = 500, 400
w2, h2 = 200,  80

def x_to_y(x):
    y = 100*((x*0.01)**n)
    y = np.round(y, 2)
    return y


##  Initialize the graph 

x = np.linspace(0, 100, 1001)
y = x_to_y(x)

cust_slider = Slider(start=0, end=100, step=0.1, 
                     value=30, 
                     width=w2, height=h2,
                     title="% Customers")


##  Create a table with (x, y) values of the graph... 
##              read x from slider

tabl = dict(cust=[cust_slider.value],
            potn=[x_to_y(cust_slider.value)])

values = ColumnDataSource(tabl)

columns = [TableColumn(field="cust", title="% Customers"),
           TableColumn(field="potn", title="% Potential")]

data_table = DataTable(source=values, 
                       columns=columns,
                       width=w2, height=h2, 
                       editable=True)


##  Plot the graph from  function (later to be read from source)

source = ColumnDataSource(data=dict(x=x, y=y))

TOOLTIPS = [("Customers: ", "$x"),
            ("Potential: ", "$y")
           ]

plot = figure(x_range=(0,100),
              y_range=(0, 100), 
              tooltips=TOOLTIPS,
              plot_width=w1, 
              plot_height=h1)

plot.line('x', 'y', source=source)
plot.line([0,cust_slider.value,cust_slider.value], 
          [x_to_y(cust_slider.value),x_to_y(cust_slider.value),0],
          line_dash="dashed"
         )

plot.add_layout(Title(text="Customers (%)", align="center"), "below")
plot.add_layout(Title(text="Potential (%)", align="center"), "left")


##  Try to make table and plot interactive  (...table not interacting)

callback = CustomJS(args=dict(source=tabl, 
                              slider=cust_slider),
                    code="""const source = source.data;
                            const xx = source['cust'];
                            const yy = source['potn'];
                            xx = slider.value;
                            yy = x_to_y(xx);
                            source.change.emit();"""
                   )

cust_slider.js_on_change('value', callback)


layout = row(plot,
             column(cust_slider,
                    data_table)
            )

output_file("slider_mwe.html", title="Graph")

show(layout)

Вот снимок полученной мной фигуры: enter image description here

Редактировать: Решение, вдохновленное bigreddot

###  Slider:: Interactive Graph with Python Bokeh
###  Source:: https://docs.bokeh.org/en/latest/docs/gallery/slider.html

import numpy as np
import pandas as pd

from bokeh.layouts import row, column
from bokeh.models import CustomJS, Slider, Title
from bokeh.plotting import figure, output_file, show, ColumnDataSource
from bokeh.models.widgets import DataTable, TableColumn

#%%  Read data from graph

##  Define parameters and function

n = 0.333333333333
w1, h1 = 500, 400
w2, h2 = 200,  80

def x_to_y(x):
    y = 100*((x*0.01)**n)
    y = np.round(y, 2)
    return y


##  Initialize the graph 

x = np.linspace(0, 100, 1001)
y = x_to_y(x)

##  ...and the data-point in focus
x0 = np.linspace(30, 30, 1)
y0 = x_to_y(x0)


cust_slider = Slider(start=0, end=100, step=0.1, 
                     value=30, 
                     width=w2, height=h2,
                     title="% Customers")


##  Create a table with (x, y) values of the graph... 
##              read x from slider

tabl = dict(cust=[cust_slider.value],
            potn=[x_to_y(cust_slider.value)])
tlin = ColumnDataSource(data=dict(lin_cust=np.insert(x0, 0, [0, x0[0]]),
                                  lin_potn=np.insert(y0, 1, [y0[0], 0])))


values = ColumnDataSource(tabl)

columns = [TableColumn(field="cust", title="% Customers"),
           TableColumn(field="potn", title="% Potential")]

data_table = DataTable(source=values, 
                       columns=columns,
                       width=w2, height=h2, 
                       editable=True)


##  Plot the graph from  function (later to be read from source)

source = ColumnDataSource(data=dict(x=x, y=y))

TOOLTIPS = [("Customers: ", "$x"),
            ("Potential: ", "$y")
           ]

plot = figure(x_range=(0,100),
              y_range=(0, 100), 
              tooltips=TOOLTIPS,
              plot_width=w1, 
              plot_height=h1)

plot.line('x', 'y', source=source)
plot.line('lin_cust', 'lin_potn', source=tlin, line_dash="dashed")

plot.add_layout(Title(text="Customers (%)", align="center"), "below")
plot.add_layout(Title(text="Potential (%)", align="center"), "left")


##  Plot made interactive  by writing to values & tlin 
##                        (rather than declared constants)

callback = CustomJS(args=dict(source=source, 
                              values=values, 
                              tlin=tlin, 
                              slider=cust_slider),
                    code="""values.data['cust'][0] = slider.value;
                            for (var i = 0; i < source.data['x'].length; i++) 
                            {   
                                if (source.data['x'][i] == slider.value) 
                                {
                                    values.data['potn'][0] = source.data['y'][i];
                                }
                            }
                            tlin.data['lin_cust'][1] = values.data['cust'][0]
                            tlin.data['lin_cust'][2] = values.data['cust'][0]
                            tlin.data['lin_potn'][0] = values.data['potn'][0]
                            tlin.data['lin_potn'][1] = values.data['potn'][0]
                            values.change.emit();
                            tlin.change.emit();"""
                   )

cust_slider.js_on_change('value', callback)


layout = row(plot,
             column(cust_slider,
                    data_table)
            )

output_file("slider_mwe.html", title="Graph")

show(layout)

, которое дает все на месте:

enter image description here

... за исключением того, что время от времени угол пунктирной линии выпадает из кривой.


Ну, я подумал, что нужно дать эту небольшую свободу ради того, что все это имеет сделано для меня!

1 Ответ

1 голос
/ 22 января 2020

Есть несколько вопросов. Во-первых, ваш CustomJS код:

const source = source.data;
const xx = source['cust'];
const yy = source['potn'];
xx = slider.value;
yy = x_to_y(xx);
source.change.emit();

на самом деле не делает ничего, что имеет какой-либо эффект. Когда вы создаете локальные переменные xx и yy, а затем заново присваиваете им значения, это влияет только на локальные переменные и ничего больше. В частности, ничего об источнике данных Bokeh не изменилось (поэтому ничего о графике не изменится).

Кроме того, значения в ColumnDataSource должны быть фактическими столбцами (то есть списками или массивами), а не отдельными числами, как вы назначаете выше.

Вы, вероятно, намереваетесь что-то более похожее на:

source.data['cust'] = [0, slider.value, slider.value]

, которое фактически изменяет содержимое source.data.

Однако у вас также есть еще одна проблема, которая заключается в том, что для другого столбца вы пытаетесь вызвать функцию Python x_to_y в вашем коде обратного вызова CustomJS. Это никогда не может работать. CustomJS код обратного вызова выполняется в браузере , и браузеры знают только, как выполнить JavaScript код. Вам придется выполнить это преобразование с кодом JS в обратном вызове.

...