예제 #1
0
    def test_js_on_change_executes(self, single_plot_page):
        source = ColumnDataSource(dict(x=[1, 2], y=[1, 1]))
        plot = Plot(plot_height=400, plot_width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0)
        plot.add_glyph(source, Circle(x='x', y='y', size=20))
        text_input = TextInput(css_classes=['foo'])
        text_input.js_on_change('value', CustomJS(code=RECORD("value", "cb_obj.value")))

        page = single_plot_page(column(text_input, plot))

        el = page.driver.find_element_by_class_name('foo')

        enter_text_in_element(page.driver, el, "val1")

        results = page.results
        assert results['value'] == 'val1'

        # double click to highlight and overwrite old text
        enter_text_in_element(page.driver, el, "val2", click=2)

        results = page.results
        assert results['value'] == 'val2'

        # Check clicking outside input also triggers
        enter_text_in_element(page.driver, el, "val3", click=2, enter=False)
        page.click_canvas_at_position(10, 10)
        results = page.results

        assert results['value'] == 'val3'

        assert page.has_no_console_errors()
예제 #2
0
    def test_js_on_change_executes(self, single_plot_page):
        source = ColumnDataSource(dict(x=[1, 2], y=[1, 1]))
        plot = Plot(plot_height=400, plot_width=400, x_range=Range1d(0, 1), y_range=Range1d(0, 1), min_border=0)
        plot.add_glyph(source, Circle(x='x', y='y', size=20))
        text_input = TextInput(css_classes=['foo'])
        text_input.js_on_change('value', CustomJS(code=RECORD("value", "cb_obj.value")))

        page = single_plot_page(column(text_input, plot))

        el = page.driver.find_element_by_css_selector('.foo input')
        enter_text_in_element(page.driver, el, "val1")

        results = page.results
        assert results['value'] == 'val1'

        # double click to highlight and overwrite old text
        enter_text_in_element(page.driver, el, "val2", click=2)

        results = page.results
        assert results['value'] == 'val2'

        # Check clicking outside input also triggers
        enter_text_in_element(page.driver, el, "val3", click=2, enter=False)
        page.click_canvas_at_position(10, 10)
        results = page.results

        assert results['value'] == 'val3'

        assert page.has_no_console_errors()
예제 #3
0
    def test_multi_row_copy(self, bokeh_model_page) -> None:
        data = {'x': [1,2,3,4], 'y': [0,1,2,3], 'd': ['foo', 'bar', 'baz', 'quux']}
        source = ColumnDataSource(data)
        table = DataTable(columns=[
            TableColumn(field="x", title="x"),
            TableColumn(field="y", title="y"),
            TableColumn(field="d", title="d"),
        ], source=source)

        text_input = TextInput(css_classes=["foo"])
        text_input.js_on_change('value', CustomJS(code=RECORD("value", "cb_obj.value")))

        page = bokeh_model_page(column(table, text_input))

        row = get_table_row(page.driver, 1)
        row.click()

        row = get_table_row(page.driver, 3)
        shift_click(page.driver, row)

        enter_text_in_element(page.driver, row, Keys.INSERT, mod=Keys.CONTROL, click=0, enter=False)

        input_el = page.driver.find_element_by_css_selector('.foo')
        enter_text_in_element(page.driver, input_el, Keys.INSERT, mod=Keys.SHIFT, enter=False)
        enter_text_in_element(page.driver, input_el, "")

        results = page.results

        # XXX (bev) these should be newlines with a TextAreaInput but TextAreaInput
        # is not working in tests for some reason presently
        assert results['value'] == '0\t1\t0\tfoo 1\t2\t1\tbar 2\t3\t2\tbaz'

        assert page.has_no_console_errors()
예제 #4
0
    def test_single_row_copy_with_zero(self, bokeh_model_page) -> None:
        data = {'x': [1,2,3,4], 'y': [0,0,0,0], 'd': ['foo', 'bar', 'baz', 'quux']}
        source = ColumnDataSource(data)
        table = DataTable(columns=[
            TableColumn(field="x", title="x"),
            TableColumn(field="y", title="y"),
            TableColumn(field="d", title="d"),
        ], source=source)

        text_input = TextInput(css_classes=["foo"])
        text_input.js_on_change('value', CustomJS(code=RECORD("value", "cb_obj.value")))

        page = bokeh_model_page(column(table, text_input))

        row = get_table_row(page.driver, 2)
        row.click()

        enter_text_in_element(page.driver, row, Keys.INSERT, mod=Keys.CONTROL, click=0, enter=False)

        input_el = page.driver.find_element_by_css_selector('.foo')
        enter_text_in_element(page.driver, input_el, Keys.INSERT, mod=Keys.SHIFT, enter=False)
        enter_text_in_element(page.driver, input_el, "")

        sleep(0.5)
        results = page.results

        assert results['value'] == '1\t2\t0\tbar'

        assert page.has_no_console_errors()
예제 #5
0
from bokeh.io import show
from bokeh.models import CustomJS, TextInput

text_input = TextInput(value="default", title="Label:")
text_input.js_on_change("value", CustomJS(code="""
    console.log('text_input: value=' + this.value, this.toString())
"""))

show(text_input)
예제 #6
0
    def generate_bokeh_umap(self, media_type):
        output_notebook()

        topics = []
        labels = []
        for key, value in self.top_words_map.items():
            topics.append(value)
            labels.append(key)

        if len(labels) >= 20000:
            reducer = umap.UMAP(n_neighbors=100, metric='hellinger')
        if len(labels) >= 5000:
            reducer = umap.UMAP(n_neighbors=50, metric='hellinger')
        else:
            reducer = umap.UMAP(metric='hellinger')

        X = self.vectorized_out.copy()
        X_embedded = reducer.fit_transform(X)

        # tsne = TSNE(verbose=1, perplexity=100, random_state=42)
        # X = self.vectorized_out
        # X_embedded = tsne.fit_transform(X.toarray())

        df_tmp = pd.DataFrame(self.doc_topic_dists)
        df_tmp['topic'] = df_tmp.idxmax(axis=1)
        y_labels = df_tmp['topic'].values
        y_labels_new = []
        for i in y_labels:
            y_labels_new.append(labels[i])

        df = self.original_df.copy()

        # data sources
        if media_type == 'videos':
            source = ColumnDataSource(
                data=dict(x=X_embedded[:, 0],
                          y=X_embedded[:, 1],
                          x_backup=X_embedded[:, 0],
                          y_backup=X_embedded[:, 1],
                          desc=y_labels,
                          ids=df['id'],
                          titles=df['title'],
                          published_times=df['first_airing'],
                          text=df['text'],
                          publication_end_times=df['publication_end_time'],
                          media_availables=df['media_available'],
                          duration_minutes=df['duration_minutes'],
                          finnpanel_genres=df['finnpanel_genre'],
                          labels=["Topic " + str(x) for x in y_labels_new],
                          links=df['link']))

            # hover over information
            hover = HoverTool(
                tooltips=[
                    ("Id", "@ids{safe}"),
                    ("Title", "@titles{safe}"),
                    ("Published", "@published_times{safe}"),
                    # ("Text", "@texts{safe}"),
                    ("Publication ends", "@publication_end_times{safe}"),
                    ("Currently available", "@media_availables{safe}"),
                    ("Duration (minutes)", "@duration_minutes{safe}"),
                    ("Finnpanel genres", "@finnpanel_genres{safe}"),
                    ("Link", "@links")
                ],
                point_policy="follow_mouse")

        elif media_type == 'articles':
            source = ColumnDataSource(
                data=dict(x=X_embedded[:, 0],
                          y=X_embedded[:, 1],
                          x_backup=X_embedded[:, 0],
                          y_backup=X_embedded[:, 1],
                          desc=y_labels,
                          ids=df.index,
                          titles=df['title'],
                          published_times=df['published_time'].dt.strftime(
                              '%Y-%m-%d %H:%M'),
                          text=df['text'],
                          labels=["Topic " + str(x) for x in y_labels_new],
                          links=df['link']))

            # hover over information
            hover = HoverTool(
                tooltips=[
                    ("Id", "@ids{safe}"),
                    ("Title", "@titles{safe}"),
                    ("Published", "@published_times{safe}"),
                    # ("Text", "@texts{safe}"),
                    ("Link", "@links")
                ],
                point_policy="follow_mouse")

        # map colors
        mapper = linear_cmap(field_name='desc',
                             palette=Category20[20],
                             low=min(y_labels),
                             high=max(y_labels))

        # prepare the figure
        plot = figure(plot_width=1200,
                      plot_height=850,
                      tools=[
                          hover, 'pan', 'wheel_zoom', 'box_zoom', 'reset',
                          'save', 'tap'
                      ],
                      title="Clustering of the content with UMAP and NMF",
                      toolbar_location="above")

        # plot settings
        plot.scatter('x',
                     'y',
                     size=5,
                     source=source,
                     fill_color=mapper,
                     line_alpha=0.3,
                     line_color="black",
                     legend='labels')
        plot.legend.background_fill_alpha = 0.6

        # Keywords
        text_banner = Paragraph(
            text='Keywords: Slide to specific cluster to see the keywords.',
            height=45)
        input_callback_1 = input_callback(plot, source, text_banner, topics,
                                          self.nr_of_topics)

        # currently selected article
        div_curr = Div(
            text="""Click on a plot to see the link to the article.""",
            height=150)
        if media_type == 'videos':
            callback_selected = CustomJS(args=dict(source=source,
                                                   current_selection=div_curr),
                                         code=selected_code_videos())
        elif media_type == 'articles':
            callback_selected = CustomJS(args=dict(source=source,
                                                   current_selection=div_curr),
                                         code=selected_code_articles())
        taptool = plot.select(type=TapTool)
        taptool.callback = callback_selected

        # WIDGETS
        slider = Slider(
            start=0,
            end=self.nr_of_topics,
            value=self.nr_of_topics,
            step=1,
            title="Topic #")  #, js_event_callbacks=input_callback_1)
        slider.js_on_change("value", input_callback_1)
        keyword = TextInput(
            title="Search:")  #, js_event_callbacks=input_callback_1)
        keyword.js_on_change("value", input_callback_1)

        # pass call back arguments
        input_callback_1.args["text"] = keyword
        input_callback_1.args["slider"] = slider

        # STYLE
        slider.sizing_mode = "stretch_width"
        slider.margin = 15

        keyword.sizing_mode = "scale_both"
        keyword.margin = 15

        div_curr.style = {
            'color': '#BF0A30',
            'font-family': 'Helvetica Neue, Helvetica, Arial, sans-serif;',
            'font-size': '1.1em'
        }
        div_curr.sizing_mode = "scale_both"
        div_curr.margin = 20

        text_banner.style = {
            'color': '#0269A4',
            'font-family': 'Helvetica Neue, Helvetica, Arial, sans-serif;',
            'font-size': '1.1em'
        }
        text_banner.sizing_mode = "scale_both"
        text_banner.margin = 20

        plot.sizing_mode = "scale_both"
        plot.margin = 5

        r = row(div_curr, text_banner)
        r.sizing_mode = "stretch_width"

        # LAYOUT OF THE PAGE
        l = layout([
            [slider, keyword],
            [text_banner],
            [div_curr],
            [plot],
        ])
        l.sizing_mode = "scale_both"

        # show
        output_file('t-sne_interactive_streamlit.html')
        show(l)

        return (l)
예제 #7
0
class Carte_tronçons(Fond_de_carte):
    """Carte de tronçons de lignes.

    Arguments:
        tronçons: GeoDataFrame, table des tronçons

    Attributs:
        tron: table des tronçons avec habillage
        tron_idx_name: le nom de l'index des tronçons transformé en colonne dans `tron`
    """

    titre_ref = "carte_tronçons"
    titre_tron = "Tronçons"
    # coordonnées arbitraires de la carte pour créer des objets de la légende
    x_legende, y_legende = [277409, 277409], [6248780, 6248780]
    tab_width = 500

    def __init__(self, tronçons: DataFrame, **kwargs) -> None:
        self.tron = tronçons
        self.tron["line_width"] = self.line_width
        self.tron["line_color"] = self.line_color
        self.tron.reset_index(inplace=True)
        self.tron_idx_name = self.tron.columns[0]
        self.cols = self.tron.columns.drop(
            ["geometry", "line_width", "line_color"], errors="ignore")
        super().__init__(**kwargs)

    @property
    def cols_tron(self) -> list[TableColumn]:
        return [TableColumn(field=c, title=c) for c in self.cols]

    @property
    def line_width(self) -> Series:
        """Épaisseur de l'affichage des tronçons.

        Doit renvoyer une série alignable avec l'argument tronçons
        et peut y faire référence via self.tron.
        """
        return 2

    @property
    def line_color(self) -> Series:
        """Couleur de l'affichage des tronçons.

        Doit renvoyer une série alignable avec l'argument tronçons
        et peut y faire référence via self.tron.
        """
        return "blue"

    @property
    def tooltips(self) -> list[tuple[str, str]]:
        return [(c, f"@{c}") for c in self.cols]

    @property
    def texte_aide(self) -> str:
        return """<b>Mode d'emploi</b>
        <p>La sélection d'une ou plusieurs lignes est possible directement sur la carte (shift + clic) ou dans la table (shift/ctrl + clic).</p>
        <p>On peut aussi sélectionner une ligne en indiquant son numéro de ligne et de rang dans l'entrée texte située en haut à droite. 
        Le bouton "Affiche les extrémités" permet de rendre visibles ou non les extrémités des lignes sélectionnées.</p>
        <p>En cas de problème, utiliser l'outil reset sur la droite de la carte.</p>
        """

    def ajoute_table_tron(self) -> None:
        view = CDSView(source=self.source_lines, filters=[self.filter])
        self.table_tron = DataTable(
            source=self.source_lines,
            view=view,
            columns=self.cols_tron,
            autosize_mode="none",
            sizing_mode="stretch_height",
            width=self.tab_width,
            height=200,
        )

    def ajoute_toggle_extrémités(self) -> None:
        size = 4
        fill_color = "DarkSlateGray"
        self.g = (self.tron.set_index(
            self.tron_idx_name).geometry.boundary.dropna().explode().droplevel(
                1).rename("geometry").reset_index().reset_index())
        idx_g = self.g.columns[0]  # colonne qui contient le numéro de ligne
        self.src_extr = GeoJSONDataSource(geojson=self.g.to_json())
        self.filter_extr = IndexFilter(list(range(self.g.shape[0])))
        self.index_extrémités_par_tron = (
            self.tron.reset_index()  # numéro de ligne dans la colonne idx_g
            .merge(
                self.g, on=self.tron_idx_name
            )  # inner join donc tous les tronçons non localisés n'y sont pas
            .groupby(f"{idx_g}_x").apply(
                lambda s: list(s[f"{idx_g}_y"])).to_dict())
        view = CDSView(source=self.src_extr, filters=[self.filter_extr])
        self.extr_renderer = self.p.circle(
            x="x",
            y="y",
            size=size,
            fill_color=fill_color,
            line_color=fill_color,
            source=self.src_extr,
            visible=False,
            view=view,
        )
        self.toggle_extr = Toggle(label="Affiche les extrémités",
                                  button_type="success",
                                  width=100)
        self.toggle_extr.js_link("active", self.extr_renderer, "visible")

    def ajoute_lignes(self) -> None:
        self.p.multi_line(
            xs="xs",
            ys="ys",
            line_color="line_color",
            line_width="line_width",
            source=self.source_lines,
            name="tronçons",
        )

    @property
    def callback_selected(self) -> CustomJS:
        return CustomJS(
            args=dict(
                src_lines=self.source_lines,
                src_extr=self.src_extr,
                filter=self.filter,
                filter_extr=self.filter_extr,
                index_extr_dict=self.index_extrémités_par_tron,
            ),
            code="""var sel = src_lines.selected.indices;
            if (sel.length == 0) {
                sel = [...Array(src_lines.length).keys()];
                var sel2 = [...Array(src_extr.length).keys()];
            } else {
                var sel2 = sel.flatMap(el => index_extr_dict[el]);
            }
            filter.indices = sel;
            filter_extr.indices = sel2;
            src_lines.change.emit();
            src_extr.change.emit();
            """,
        )

    def ajoute_input_num(self,
                         title: str = None,
                         groupby: str = None,
                         max_width=80) -> None:
        """Sélection du tronçon par une colonne de la table."""
        if groupby is None:
            groupby = self.tron_idx_name
        if title is None:
            title = self.tron_idx_name
        index_par_tron = DataFrame(self.tron).groupby(groupby).apply(
            lambda s: list(s.index)).to_dict()
        self.input_num = TextInput(value="", title=title, max_width=max_width)
        callback_text = CustomJS(
            args=dict(
                text=self.input_num,
                src_lines=self.source_lines,
                index_par_tron=index_par_tron,
            ),
            code="""if (text.value in index_par_tron) {
                src_lines.selected.indices = index_par_tron[text.value]
                src_lines.change.emit();
                }
                """,
        )
        self.input_num.js_on_change("value", callback_text)

    def ajoute_légende(self) -> None:
        pass

    @property
    def titre_tron_div(self) -> Div:
        return Div(text=f"<b>{self.titre_tron}</b>")

    def init_layout(self) -> None:
        super().init_layout()
        geojson = self.tron.to_json().replace(
            "null", '{"type":"Point","coordinates":[]}')
        self.source_lines = GeoJSONDataSource(geojson=geojson)
        self.filter = IndexFilter(list(range(self.tron.shape[0])))
        self.ajoute_lignes()
        self.ajoute_toggle_extrémités()
        self.source_lines.selected.js_on_change("indices",
                                                self.callback_selected)
        self.ajoute_table_tron()
        self.ajoute_input_num()
        self.hover_tool = self.p.select(type=HoverTool)
        self.hover_tool.names = ["tronçons"]
        self.ajoute_légende()
        self.première_ligne = row(self.input_num, self.toggle_extr)

    def cstr_layout(self) -> Box:
        return row(
            self.p,
            column(
                self.première_ligne,
                self.titre_tron_div,
                self.table_tron,
                self.questions,
                self.aide,
            ),
        )
예제 #8
0
                           value=1,
                           title='Allele Frequency Cutoff')
        slider_af.js_on_change(
            'value',
            sliderCallback(source_sample, depth_sample, slider, slider_af,
                           syngroup, unique_longitudinal_muts))
        slider.js_on_change(
            'value',
            sliderCallback(source_sample, depth_sample, slider, slider_af,
                           syngroup, unique_longitudinal_muts))
        syngroup.js_on_change(
            'active',
            sliderCallback(source_sample, depth_sample, slider, slider_af,
                           syngroup, unique_longitudinal_muts))
        ose.js_on_change(
            'value',
            sliderCallback2(source_sample, depth_sample, slider, slider_af,
                            syngroup, ose, unique_longitudinal_muts))

        # When mousing over Bokeh plot, allele frequency updated to user input.
        if (user_af != -123):
            slider_af.value = user_af
            ## g.js_on_event(events.PlotEvent, sliderCallback(source_sample, depth_sample, slider, slider_af, syngroup))
            genome_plot.js_on_event(
                events.MouseEnter,
                sliderCallback(source_sample, depth_sample, slider, slider_af,
                               syngroup, unique_longitudinal_muts))

        # Creates labels with read information
        reads_info = (reads.loc[reads['Sample'] == name.strip()])
        div = Div(text="""<font size="2" color="gray"><b>Total reads: </b>""" +
                  str(reads_info.iloc[0]["Total"]) +
예제 #9
0
    return ClosingPricesDataFrame


''''''
DataFrame = GetStockDataFrame(IbmUrl)
source = ColumnDataSource(DataFrame)
InputSymbol = "IBM"
''''''
'''This callback does not currently work :( '''
Callback = CustomJS(args=dict(source=source),
                    code="""
// JavaScript code goes here
// the model that triggered the callback is cb_obj:
// models passed as args are automagically available
    var InputSymbol = cb_obj.value
    var NewData = GetStockDataFrame(GetSubmitUrl(InputSymbol))
    var NewDataSource = ColumnDataSource(NewData)
    var x = NewDataSource.Dates
    var y = NewDataSource.Prices
    source.change.emit();
""")
''''''
PageGraph = figure(title="Last Month's Closing Prices",
                   plot_width=600,
                   plot_height=400,
                   x_axis_type='datetime')
PageGraph.line(x=DataFrame["Dates"], y=DataFrame["Prices"], line_width=2)
TextInputObject = TextInput(value="IBM", title=WelcomeMessage)
TextInputObject.js_on_change('value', Callback)
Page = Column(TextInputObject, PageGraph)
ShowIo(Page)
예제 #10
0
def home():
    #Open file and create sources
    dictionary = open_file_into_dictionary('SampleCSV2.csv')
    keys = list(key.title() for key in dictionary.keys())
    values = [value for value in dictionary.values()]
    xy_source = ColumnDataSource(data=dict(xs=[values[0]], ys=[values[1]], labels = [keys[1]],
    colors = ['red', 'green', 'blue', 'purple', 'brown', 'aqua']))
    variables_source = ColumnDataSource(data = dict(keys = keys, values = values))




    #Create general plot
    plot = figure(plot_width=800, plot_height=600, toolbar_location = 'left')
    plot.title.text_font= 'helvetica'
    plot.title.text_font_size = '24pt'
    plot.title.align = 'center'
    plot.title.text_font_style = 'normal'
    plot.multi_line(xs = 'xs', ys = 'ys', legend = 'labels', line_color = 'colors', source = xy_source)


    #Define callbacks
    x_axis_callback = CustomJS(args=dict(xy_source = xy_source, variables_source = variables_source,
     axis = plot.xaxis[0]), code="""
        var xy_data = xy_source.data;
        var variables_data = variables_source.data;
        var index = cb_obj.active;
        var values = variables_data.values;

        var y_length = xy_data['ys'].length;
        if (y_length == 0){
            y_length = 1}
        var new_list = [];
        for (i = 0; i < y_length; i++) {
        new_list.push(values[index])}
        xy_data['xs'] = new_list;

        xy_source.change.emit();

        var keys = variables_data.keys;
        var label = keys[index];
        axis.axis_label = label;
    """)

    y_axis_callback = CustomJS(args=dict(xy_source = xy_source, variables_source = variables_source,
     axis = plot.yaxis[0]), code="""

        var xy_data = xy_source.data;
        var variables_data = variables_source.data;
        var index_list = cb_obj.active;

        var values = variables_data.values;
        var index_length = index_list.length;
        var keys = variables_data.keys;

        var new_ys = [];
        var new_labels = [];
        for (i = 0; i < index_length; i++) {
            new_ys.push(values[index_list[i]]);
            new_labels.push(keys[index_list[i]])}
        xy_data['labels'] = new_labels;
        xy_data['ys'] = new_ys;

        if (index_length > 0){
            var x_variable = xy_data['xs'][0];
            var new_x = [];
            for (i = 0; i < index_length; i++) {
                new_x.push(x_variable)}
            xy_data['xs'] = new_x;}

        xy_source.change.emit();

        var y_axis_name = keys[[index_list[0]]];
        for (i = 1; i < index_length; i++) {
            y_axis_name += ", " + keys[[index_list[i]]];}
        axis.axis_label = y_axis_name;
    """)

    title_callback = CustomJS(args= dict(title = plot.title), code="""
        var title_text = cb_obj.value;
        title.text = title_text;

    """)
    x_name_callback = CustomJS(args=dict(axis = plot.xaxis[0]), code="""
        var label_text = cb_obj.value;
        axis.axis_label = label_text;
         """)

    y_name_callback = CustomJS(args=dict(axis = plot.yaxis[0]), code="""
        var label_text = cb_obj.value;
        axis.axis_label = label_text;
         """)


    #Create toolbox
    label_x = Div(text="""X-Axis""", width=200)
    x_axis = RadioButtonGroup(labels=keys, active=0, callback = x_axis_callback)
    label_y = Div(text="""Y-Axis""", width=200)
    y_axis = CheckboxButtonGroup(labels=keys, active=[1], callback = y_axis_callback)
    label_axes = Div(text="""<br />Modify Labels""", width=200)

    title_name = TextInput(title="Title", value='Default Title')
    plot.title.text = title_name.value
    title_name.js_on_change('value', title_callback)

    x_name = TextInput(title="X-Axis", value='Default X Label')
    plot.xaxis.axis_label = keys[0]
    x_name.js_on_change('value', x_name_callback)
    y_name = TextInput(title="Y-Axis", value='Default Y Label')
    plot.yaxis.axis_label = keys[1]
    y_name.js_on_change('value', y_name_callback)

    toolbox = widgetbox(label_x, x_axis, label_y, y_axis, label_axes, title_name, x_name, y_name)





    #Integrate with html
    parts = dict(toolbox = toolbox, plot = plot)
    script, div = components(parts, INLINE)
    return render_template('plotpage.html',
                           script=script,
                           toolbox_div=div['toolbox'],
                           plot_div=div['plot'],
                           js_resources=INLINE.render_js(),
                           css_resources=INLINE.render_css())
예제 #11
0
def create_html(distances, text_list, file_path, num_similar_shown):

    source = ColumnDataSource(data=dict(
        ids=range(len(text_list)),
        distances=distances.tolist(),
        text=text_list,
        display_text=text_list,
        display_ids=range(len(text_list)),
    ))

    display_source = ColumnDataSource(data=dict(
        closest_text=[""] * num_similar_shown,
        closest_dist=[0] * num_similar_shown,
    ))

    columns = [
        TableColumn(field="display_text", title="Text"),
    ]

    closest_columns = [
        TableColumn(field="closest_text",
                    title="Closest examples",
                    width=510,
                    editor=TextEditor()),
        TableColumn(field="closest_dist",
                    title="Distance",
                    width=10,
                    editor=TextEditor()),
    ]

    str_search_input = TextInput(value="", title="Search feedback")

    callback = CustomJS(args=dict(source=source,
                                  display_source=display_source,
                                  search_text=str_search_input),
                        code="""
        const data = source.data;
        // ##################
        // First search
        // ##################
        const search_text_str = search_text.value.toLowerCase();
        const display_texts = [];
        const display_ids = [];
        data['text'].map(function(e, i) {
            const text_val = data['text'][i];
            const text_id = data['ids'][i];
            if (text_val.toLowerCase().includes(search_text_str)){
                display_texts.push(text_val);
                display_ids.push(text_id);
            }
        });
        data['display_text'] = display_texts;
        data['display_ids'] = display_ids;
        source.change.emit();
        // ##################
        // Then show selected
        // ##################
        const num_similar_shown = data['num_similar'];
        if(source.selected.indices.length >= 1){
            const selected_table_idx = source.selected.indices[0];
            if (selected_table_idx >= data['display_ids'].length){
                console.log("Empty cell selected")
            }else{
                const selected_idx = data['display_ids'][selected_table_idx];
                console.log(selected_idx)
                const texts = data['text'];
                const list_of_dist = data['distances'];
                const selected_dist = list_of_dist[selected_idx];
                function indexOfNMin(arr, n) {
                    if (arr.length < n) {
                        return [arr, [...Array(arr.length).keys()]];
                    }
                    var min_arr = arr.slice(0, n);
                    var min_idxs = [...Array(n).keys()];
                    for (var i = n; i < arr.length; i++) {
                        const max_selected = Math.max(...min_arr);
                        if (arr[i] < max_selected) {
                            var idx_max = min_arr.indexOf(max_selected);
                            min_arr[idx_max] = arr[i];
                            min_idxs[idx_max] = i;
                        }
                    }
                    return [min_arr, min_idxs];
                }
                const closest_dist_values = indexOfNMin(selected_dist, """ +
                        str(num_similar_shown) + """);
                const closest_dist =  [].slice.call(closest_dist_values[0]);
                const closest_dist_idx = closest_dist_values[1];
                function sortWithIndices(inputArray) {
                    const toSort = inputArray.slice();
                    for (var i = 0; i < toSort.length; i++) {
                        toSort[i] = [toSort[i], i];
                    }
                    toSort.sort(function(left, right) {
                        return left[0] < right[0] ? -1 : 1;
                    });
                    var sortIndices = [];
                    for (var j = 0; j < toSort.length; j++) {
                        sortIndices.push(toSort[j][1]);
                    }
                    return sortIndices;
                }
                const sorted_closest_dist_idx_idx = sortWithIndices(closest_dist);
                const sorted_closest_dist_idx = sorted_closest_dist_idx_idx.map(i => closest_dist_idx[i]);
                const closest_texts = sorted_closest_dist_idx.map(i => texts[i]);
                const display_data = display_source.data;
                display_data['closest_text'] = closest_texts;
                display_data['closest_dist'] = closest_dist.sort(function(a, b){return a - b}).map(i => i.toFixed(3));
                display_source.change.emit();
            }
        }
    """)

    source.selected.js_on_change('indices', callback)
    str_search_input.js_on_change('value', callback)

    data_table = DataTable(source=source,
                           columns=columns,
                           width=600,
                           height=420,
                           selectable=True)
    closest_data_table = DataTable(source=display_source,
                                   columns=closest_columns,
                                   fit_columns=False,
                                   height=800,
                                   editable=True)

    title = Div(text="""<b>Feedback Finder</b><br><br>
    The left hand side will allow you to look at ALL feedback for this given app.<br><br>
    Click on a row to see the closest matches to this row (and the embedding distance of each match) on the right side.<br><br>
    Try using the search bar to narrow down feedback that you want to find. <br>For example, if you are looking for performance related bug reports, then try typing 'lag' into the search bar, and hitting enter.<br> Then click on one of the results on the left to see other related bits of feedback that do not explicitly mention the word 'lag' on the right.<br><br>""",
                width=1000,
                height=180)

    layout = column(
        title,
        row(column(str_search_input, data_table), column(closest_data_table)))

    # output to static HTML file
    output_file(f"{file_path}.html")

    save(layout)
예제 #12
0
파일: explorer.py 프로젝트: Vickzhang/hover
class BokehForLabeledText(Loggable, ABC):
    """
    Base class that keeps template explorer settings.

    Assumes:

    - in supplied dataframes
      - (always) text data in a `text` column
      - (always) xy coordinates in `x` and `y` columns
      - (always) an index for the rows
      - (likely) classification label in a `label` column

    Does not assume:

    - what the explorer serves to do.
    """

    DEFAULT_FIGURE_KWARGS = {
        "tools": [
            # change the scope
            "pan",
            "wheel_zoom",
            # make selections
            "tap",
            "poly_select",
            "lasso_select",
            # make inspections
            "hover",
            # navigate changes
            "undo",
            "redo",
        ],
        # inspection details
        "tooltips":
        bokeh_hover_tooltip(label=True,
                            text=True,
                            image=False,
                            coords=True,
                            index=True),
        # bokeh recommends webgl for scalability
        "output_backend":
        "webgl",
    }

    DATA_KEY_TO_KWARGS = {}

    MANDATORY_COLUMNS = ["text", "label", "x", "y"]

    def __init__(self, df_dict, **kwargs):
        """
        Operations shared by all child classes.

        - settle the figure settings by using child class defaults & kwargs overrides
        - settle the glyph settings by using child class defaults
        - create widgets that child classes can override
        - create data sources the correspond to class-specific data subsets.
        - activate builtin search callbacks depending on the child class.
        - create a (typically) blank figure under such settings
        """
        self.figure_kwargs = self.__class__.DEFAULT_FIGURE_KWARGS.copy()
        self.figure_kwargs.update(kwargs)
        self.glyph_kwargs = {
            _key: _dict["constant"].copy()
            for _key, _dict in self.__class__.DATA_KEY_TO_KWARGS.items()
        }
        self._setup_widgets()
        self._setup_dfs(df_dict)
        self._setup_sources()
        self._activate_search_builtin()
        self.figure = figure(**self.figure_kwargs)
        self.reset_figure()

    @classmethod
    def from_dataset(cls, dataset, subset_mapping, *args, **kwargs):
        """
        Construct from a SupervisableDataset.
        """
        # local import to avoid import cycles
        from hover.core.dataset import SupervisableDataset

        assert isinstance(dataset, SupervisableDataset)
        df_dict = {_v: dataset.dfs[_k] for _k, _v in subset_mapping.items()}
        return cls(df_dict, *args, **kwargs)

    def reset_figure(self):
        """Start over on the figure."""
        self._info("Resetting figure")
        self.figure.renderers.clear()

    def _setup_widgets(self):
        """
        Prepare widgets for interactive functionality.

        Create positive/negative text search boxes.
        """
        from bokeh.models import TextInput, CheckboxButtonGroup

        # set up text search widgets, without assigning callbacks yet
        # to provide more flexibility with callbacks
        self._info("Setting up widgets")
        self.search_pos = TextInput(
            title="Text contains (plain text, or /pattern/flag for regex):",
            width_policy="fit",
            height_policy="fit",
        )
        self.search_neg = TextInput(title="Text does not contain:",
                                    width_policy="fit",
                                    height_policy="fit")

        # set up subset display toggles which do have clearly defined callbacks
        data_keys = list(self.__class__.DATA_KEY_TO_KWARGS.keys())
        self.data_key_button_group = CheckboxButtonGroup(
            labels=data_keys, active=list(range(len(data_keys))))

        def update_data_key_display(active):
            visible_keys = {
                self.data_key_button_group.labels[idx]
                for idx in active
            }
            for _renderer in self.figure.renderers:
                # if the renderer has a name "on the list", update its visibility
                if _renderer.name in self.__class__.DATA_KEY_TO_KWARGS.keys():
                    _renderer.visible = _renderer.name in visible_keys

        # store the callback (useful, for example, during automated tests) and link it
        self.update_data_key_display = update_data_key_display
        self.data_key_button_group.on_click(self.update_data_key_display)

    def _layout_widgets(self):
        """Define the layout of widgets."""
        return column(self.search_pos, self.search_neg,
                      self.data_key_button_group)

    def view(self):
        """Define the layout of the whole explorer."""
        return column(self._layout_widgets(), self.figure)

    def _setup_dfs(self, df_dict, copy=False):
        """
        Check and store DataFrames BY REFERENCE BY DEFAULT.

        Intended to be extended in child classes for pre/post processing.
        """
        self._info("Setting up DataFrames")
        supplied_keys = set(df_dict.keys())
        expected_keys = set(self.__class__.DATA_KEY_TO_KWARGS.keys())

        # perform high-level df key checks
        supplied_not_expected = supplied_keys.difference(expected_keys)
        expected_not_supplied = expected_keys.difference(supplied_keys)

        for _key in supplied_not_expected:
            self._warn(
                f"{self.__class__.__name__}.__init__(): got unexpected df key {_key}"
            )
        for _key in expected_not_supplied:
            self._warn(
                f"{self.__class__.__name__}.__init__(): missing expected df key {_key}"
            )

        # create df with column checks
        self.dfs = dict()
        for _key, _df in df_dict.items():
            if _key in expected_keys:
                for _col in self.__class__.MANDATORY_COLUMNS:
                    if _col not in _df.columns:
                        # edge case: DataFrame has zero rows
                        assert (
                            _df.shape[0] == 0
                        ), f"Missing column '{_col}' from non-empty {_key} DataFrame: found {list(_df.columns)}"
                        _df[_col] = None

                self.dfs[_key] = _df.copy() if copy else _df

    def _setup_sources(self):
        """
        Create (NOT UPDATE) ColumnDataSource objects.

        Intended to be extended in child classes for pre/post processing.
        """
        self._info("Setting up sources")
        self.sources = {
            _key: ColumnDataSource(_df)
            for _key, _df in self.dfs.items()
        }

    def _update_sources(self):
        """
        Update the sources with the corresponding dfs.

        Note that it seems mandatory to re-activate the search widgets.
        This is because the source loses plotting kwargs.
        """
        for _key in self.dfs.keys():
            self.sources[_key].data = self.dfs[_key]
        self._activate_search_builtin(verbose=False)

    def _activate_search_builtin(self, verbose=True):
        """
        Typically called once during initialization.
        Highlight positive search results and mute negative search results.

        Note that this is a template method which heavily depends on class attributes.
        """
        for _key, _dict in self.__class__.DATA_KEY_TO_KWARGS.items():
            if _key in self.sources.keys():
                _responding = list(_dict["search"].keys())
                for _flag, _params in _dict["search"].items():
                    self.glyph_kwargs[_key] = self.activate_search(
                        self.sources[_key],
                        self.glyph_kwargs[_key],
                        altered_param=_params,
                    )
                if verbose:
                    self._info(
                        f"Activated {_responding} on subset {_key} to respond to the search widgets."
                    )

    def activate_search(self, source, kwargs,
                        altered_param=("size", 10, 5, 7)):
        """
        Enables string/regex search-and-highlight mechanism.

        Modifies the plotting source in-place.
        """
        assert isinstance(source, ColumnDataSource)
        assert isinstance(kwargs, dict)
        updated_kwargs = kwargs.copy()

        param_key, param_pos, param_neg, param_default = altered_param
        num_points = len(source.data["text"])
        default_param_list = [param_default] * num_points
        source.add(default_param_list, f"{param_key}")

        updated_kwargs[param_key] = param_key

        search_callback = CustomJS(
            args={
                "source": source,
                "key_pos": self.search_pos,
                "key_neg": self.search_neg,
                "param_pos": param_pos,
                "param_neg": param_neg,
                "param_default": param_default,
            },
            code=f"""
            const data = source.data;
            const text = data['text'];
            var arr = data['{param_key}'];
            """ + """
            var search_pos = key_pos.value;
            var search_neg = key_neg.value;
            var valid_pos = (search_pos.length > 0);
            var valid_neg = (search_neg.length > 0);

            function determineAttr(candidate)
            {
                var score = 0;
                if (valid_pos) {
                    if (candidate.search(search_pos) >= 0) {
                        score += 1;
                    } else {
                        score -= 2;
                    }
                };
                if (valid_neg) {
                    if (candidate.search(search_neg) < 0) {
                        score += 1;
                    } else {
                        score -= 2;
                    }
                };
                if (score > 0) {
                    return param_pos;
                } else if (score < 0) {
                    return param_neg;
                } else {return param_default;}
            }

            function toRegex(search_key) {
                var match = search_key.match(new RegExp('^/(.*?)/([gimy]*)$'));
                if (match) {
                    return new RegExp(match[1], match[2]);
                } else {
                    return search_key;
                }
            }

            if (valid_pos) {search_pos = toRegex(search_pos);}
            if (valid_neg) {search_neg = toRegex(search_neg);}
            for (var i = 0; i < arr.length; i++) {
                arr[i] = determineAttr(text[i]);
            }

            source.change.emit()
            """,
        )

        self.search_pos.js_on_change("value", search_callback)
        self.search_neg.js_on_change("value", search_callback)
        return updated_kwargs

    def _prelink_check(self, other):
        """
        Sanity check before linking two explorers.
        """
        assert other is not self, "Self-loops are fordidden"
        assert isinstance(
            other, BokehForLabeledText), "Must link to BokehForLabelText"

    def link_selection(self, key, other, other_key):
        """
        Sync the selected indices between specified sources.
        """
        self._prelink_check(other)
        # link selection in a bidirectional manner
        sl, sr = self.sources[key], other.sources[other_key]
        sl.selected.js_link("indices", sr.selected, "indices")
        sr.selected.js_link("indices", sl.selected, "indices")

    def link_xy_range(self, other):
        """
        Sync plotting ranges on the xy-plane.
        """
        self._prelink_check(other)
        # link coordinate ranges in a bidirectional manner
        for _attr in ["start", "end"]:
            self.figure.x_range.js_link(_attr, other.figure.x_range, _attr)
            self.figure.y_range.js_link(_attr, other.figure.y_range, _attr)
            other.figure.x_range.js_link(_attr, self.figure.x_range, _attr)
            other.figure.y_range.js_link(_attr, self.figure.y_range, _attr)

    @abstractmethod
    def plot(self, *args, **kwargs):
        """
        Plot something onto the figure.
        """
        pass

    def auto_labels_cmap(self):
        """
        Find all labels and an appropriate color map.
        """
        labels = set()
        for _key in self.dfs.keys():
            labels = labels.union(set(self.dfs[_key]["label"].values))
        labels.discard(module_config.ABSTAIN_DECODED)
        labels = sorted(labels, reverse=True)

        assert len(labels) <= 20, "Too many labels to support (max at 20)"
        cmap = "Category10_10" if len(labels) <= 10 else "Category20_20"
        return labels, cmap

    def auto_legend_correction(self):
        """
        Find legend items and deduplicate by label.
        """
        if not hasattr(self.figure, "legend"):
            self._fail(
                "Attempting auto_legend_correction when there is no legend")
            return
        # extract all items and start over
        items = self.figure.legend.items[:]
        self.figure.legend.items.clear()

        # use one item to hold all renderers matching its label
        label_to_item = OrderedDict()

        for _item in items:
            _label = _item.label.get("value", "")
            if _label not in label_to_item.keys():
                label_to_item[_label] = _item
            else:
                label_to_item[_label].renderers.extend(_item.renderers)

        # assign deduplicated items back to the legend
        self.figure.legend.items = list(label_to_item.values())
        return
예제 #13
0
def plot_bokeh(
    points,
    labels=None,
    hover_data=None,
    width=800,
    height=800,
    color_key_cmap="Spectral",
    point_size=None,
    alpha=None,
    interactive_text_search=False,
):
    data = pd.DataFrame(points, columns=("x", "y"))

    data["label"] = labels

    unique_labels = np.unique(labels)
    num_labels = unique_labels.shape[0]
    color_key = _to_hex(
        plt.get_cmap(color_key_cmap)(np.linspace(0, 1, num_labels))
    )

    if isinstance(color_key, dict):
        data["color"] = pd.Series(labels).map(color_key)
    else:
        unique_labels = np.unique(labels)
        if len(color_key) < unique_labels.shape[0]:
            raise ValueError(
                "Color key must have enough colors for the number of labels"
            )

        new_color_key = {k: color_key[i] for i, k in enumerate(unique_labels)}
        data["color"] = pd.Series(labels).map(new_color_key)

    colors = "color"

    if hover_data is not None:
        tooltip_dict = {}
        for col_name in hover_data:
            data[col_name] = hover_data[col_name]
            tooltip_dict[col_name] = "@{" + col_name + "}"
        tooltips = list(tooltip_dict.items())
    else:
        tooltips = None

    if alpha is not None:
        data["alpha"] = alpha
    else:
        data["alpha"] = 1

    data_source = bpl.ColumnDataSource(data)

    plot = bpl.figure(
        width=width,
        height=height,
        tooltips=tooltips
    )
    plot.circle(
        x="x",
        y="y",
        source=data_source,
        color=colors,
        size=point_size,
        alpha="alpha",
        legend_field='label'
    )

    plot.grid.visible = False
    plot.axis.visible = False

    if interactive_text_search:
        text_input = TextInput(value="", title="Search:")

        interactive_text_search_columns = []
        if hover_data is not None:
            interactive_text_search_columns.extend(hover_data.columns)
        if labels is not None:
            interactive_text_search_columns.append("label")

        callback = CustomJS(
            args=dict(
                source=data_source,
                matching_alpha=0.95,
                non_matching_alpha=1 - 0.95,
                search_columns=interactive_text_search_columns,
            ),
            code="""
            var data = source.data;
            var text_search = cb_obj.value;
            
            var search_columns_dict = {}
            for (var col in search_columns){
                search_columns_dict[col] = search_columns[col]
            }
            
            // Loop over columns and values
            // If there is no match for any column for a given row, change the alpha value
            var string_match = false;
            for (var i = 0; i < data.x.length; i++) {
                string_match = false
                for (var j in search_columns_dict) {
                    if (String(data[search_columns_dict[j]][i]).includes(text_search) ) {
                        string_match = true
                    }
                }
                if (string_match){
                    data['alpha'][i] = matching_alpha
                }else{
                    data['alpha'][i] = non_matching_alpha
                }
            }
            source.change.emit();
        """,
        )

        text_input.js_on_change("value", callback)

        plot = column(text_input, plot)

    return plot
예제 #14
0
    },
             code="""
        //console.log('select: value=' + this.value, this.toString())
        mpoly.glyph.fill_color.field = this.value
        mpoly.data_source.change.emit()
    """))
selectors_map.append(select)

# Range setting for map
map_range_widgets = []
text_input = TextInput(value=str(color_mapper.high), title="High Color")
text_input.js_on_change(
    "value",
    CustomJS(args={
        'ext_datafiles': ext_datafiles,
        'color_mapper': color_mapper,
    },
             code="""
    color_mapper.high = Number(this.value)
"""))
map_range_widgets.append(text_input)
text_input = TextInput(value=str(color_mapper.low), title="Low Color")
text_input.js_on_change(
    "value",
    CustomJS(args={
        'ext_datafiles': ext_datafiles,
        'color_mapper': color_mapper,
    },
             code="""
     color_mapper.low = Number(this.value)
"""))
예제 #15
0
def upload():
    """
    This function waits until the user uploads/crops an image, grabs the color palette and color codes,
    and loads the page with the image and palettes displayed.
    :return: rendered template of image page (known as 'image.html') with the image files and color codes passed in
    """
    if request.method == 'POST' or request.method == 'GET':
        if "csv" in request.files:
            filename = photos.save(request.files["csv"])
            fullname = os.path.join(app.config['UPLOADED_PHOTOS_DEST'], filename)

            dictionary = open_file_into_dictionary(fullname)
            keys = list(key.title() for key in dictionary.keys())
            values = [value for value in dictionary.values()]
            color_keys = list(key.title() for key in color_palettes.keys())
            color_values = [value for value in color_palettes.values()]

            xy_source = ColumnDataSource(data=dict(xs=[values[0]], ys=[values[1]], labels = [keys[1]],
            colors = color_palettes['default']))
            variables_source = ColumnDataSource(data = dict(keys = keys, values = values))
            colors_source = ColumnDataSource(data = dict(color_keys = color_keys, color_values = color_values))

            #Create general plot
            plot = figure(plot_width=800, plot_height=600, toolbar_location = 'above')
            plot.title.text_font= 'helvetica'
            plot.title.text_font_size = '18pt'
            plot.title.align = 'center'
            plot.title.text_font_style = 'normal'
            plot.multi_line(xs = 'xs', ys = 'ys', legend = 'labels', line_color = 'colors', source = xy_source)
            plot.min_border = 40


            #Define callbacks
            x_axis_callback = CustomJS(args=dict(xy_source = xy_source, variables_source = variables_source,
             axis = plot.xaxis[0]), code="""
                var xy_data = xy_source.data;
                var variables_data = variables_source.data;
                var string = cb_obj.value;
                var keys = variables_data.keys;
                var index = keys.indexOf(string);
                var values = variables_data.values;

                var y_length = xy_data['ys'].length;
                if (y_length == 0){
                    y_length = 1}
                var new_list = [];
                for (i = 0; i < y_length; i++) {
                new_list.push(values[index])}
                xy_data['xs'] = new_list;

                xy_source.change.emit();

                var label = keys[index];
                axis.axis_label = label;
            """)

            y_axis_callback = CustomJS(args=dict(xy_source = xy_source, variables_source = variables_source,
             axis = plot.yaxis[0]), code="""

                var xy_data = xy_source.data;
                var variables_data = variables_source.data;
                var index_list = cb_obj.active;

                var values = variables_data.values;
                var index_length = index_list.length;
                var keys = variables_data.keys;

                var new_ys = [];
                var new_labels = [];
                for (i = 0; i < index_length; i++) {
                    new_ys.push(values[index_list[i]]);
                    new_labels.push(keys[index_list[i]])}
                xy_data['labels'] = new_labels;
                xy_data['ys'] = new_ys;

                if (index_length > 0){
                    var x_variable = xy_data['xs'][0];
                    var new_x = [];
                    for (i = 0; i < index_length; i++) {
                        new_x.push(x_variable)}
                    xy_data['xs'] = new_x;}

                xy_source.change.emit();

                var y_axis_name = keys[[index_list[0]]];
                for (i = 1; i < index_length; i++) {
                    y_axis_name += ", " + keys[[index_list[i]]];}
                axis.axis_label = y_axis_name;
            """)

            title_callback = CustomJS(args= dict(title = plot.title), code="""
                var title_text = cb_obj.value;
                title.text = title_text;

            """)
            x_name_callback = CustomJS(args=dict(axis = plot.xaxis[0]), code="""
                var label_text = cb_obj.value;
                axis.axis_label = label_text;
                 """)

            y_name_callback = CustomJS(args=dict(axis = plot.yaxis[0]), code="""
                var label_text = cb_obj.value;
                axis.axis_label = label_text;
                 """)


            #Create toolbox
            label_x = Div(text="""<h1>X-Axis</h1>""")
            x_axis = Select(title= 'Click to change:', options=keys, value = keys[0] + ' (Click to change.)', callback = x_axis_callback)
            label_y = Div(text="""<br /> <h1>Y-Axis</h1>""")
            y_axis = CheckboxGroup(labels=keys, active=[1], callback = y_axis_callback)
            label_axes = Div(text="""<br /><h1>Modify Labels</h1>""")

            title_name = TextInput(title="Title", value='Default Title')
            plot.title.text = title_name.value
            title_name.js_on_change('value', title_callback)

            x_name = TextInput(title="X-Axis", value='Default X Label')
            plot.xaxis.axis_label = keys[0]
            x_name.js_on_change('value', x_name_callback)
            y_name = TextInput(title="Y-Axis", value='Default Y Label')
            plot.yaxis.axis_label = keys[1]
            y_name.js_on_change('value', y_name_callback)

            toolbox = widgetbox(label_x, x_axis, label_y, y_axis, label_axes, title_name, x_name, y_name)




            #Fine-tuning toolbox callbacks
            legend_labels_callback = CustomJS(args=dict(xy_source = xy_source), code="""
                var xy_data = xy_source.data;
                var labels = cb_obj.value;
                var new_labels = labels.split(", ");
                xy_data['labels'] = new_labels;
                xy_source.change.emit();
             """)

            color_picker_callback = CustomJS(args=dict(xy_source = xy_source, colors_source = colors_source), code="""
                 var xy_data = xy_source.data;
                 var colors_data = colors_source.data;
                 var index = cb_obj.active;
                 var palette = colors_data['color_values'][index];
                 xy_data['colors'] = palette;
                 xy_source.change.emit();
              """)
            #Toolbox
            colors_label = Div(text="""<h3>Change Color Scheme</h3>""", sizing_mode = 'scale_width')
            color_picker = RadioButtonGroup(labels=color_keys, active=0, callback = color_picker_callback)
            labels_label = Div(text="""<br><h3>Edit Legend Labels</h3>""", sizing_mode = 'scale_width')
            legend_labels = TextInput(title = 'Warning: Changing the variables will reset the legend labels.', value="Label 1, Label 2, Label 3...", sizing_mode =  'scale_width')
            legend_labels.js_on_change('value', legend_labels_callback)

            fine_toolbox = widgetbox(colors_label, color_picker, labels_label, legend_labels, sizing_mode = 'scale_width')



            #Integrate with html
            parts = dict(toolbox = toolbox, plot = plot, fine_toolbox = fine_toolbox)
            script, div = components(parts, INLINE)
            return render_template('plotpage.html',
                                   script=script,
                                   toolbox_div=div['toolbox'],
                                   plot_div=div['plot'],
                                   fine_toolbox_div = div['fine_toolbox'],
                                   js_resources=INLINE.render_js(),
                                   css_resources=INLINE.render_css())

        else:
            for infile in glob.glob('static/uploaded_csv/*'):
                os.remove(infile)

            return render_template('index.html')
예제 #16
0
def bokeh_plot(node, link, name='NetworkMap'):
    from bokeh.plotting import figure, from_networkx, save
    from bokeh.models import ColumnDataSource, HoverTool
    from bokeh.io import export_png
    from bokeh.models import CustomJS, TextInput, CustomJSFilter, CDSView, TapTool
    from bokeh.layouts import column
    from bokeh.plotting import output_file, show
    from bokeh.tile_providers import get_provider, Vendors
    from bokeh.models import Circle, MultiLine, LabelSet, Toggle, CheckboxGroup
    from bokeh.models.graphs import NodesAndLinkedEdges

    text_input = TextInput(value="", title="Filter Nodes:")

    wgs84_to_web_mercator(node)

    node_source_data = ColumnDataSource(
        data=dict(x=node['MX'], y=node['MY'], desc=node['id']))

    # link
    G = nx.from_pandas_edgelist(link, source='id', target='anode')
    nx.set_node_attributes(G, dict(zip(link.id, link.id)), 'desc')
    n_loc = {k: (x, y) for k, x, y in zip(node['id'], node['MX'], node['MY'])}
    nx.set_node_attributes(G, n_loc, 'pos')
    n_color = {k: 'orange' if 'C' in k else 'green' for k in node['id']}
    nx.set_node_attributes(G, n_color, 'color')
    n_alpha = {k: 1 if 'C' in k else 0 for k in node['id']}
    nx.set_node_attributes(G, n_alpha, 'alpha')
    e_color = {(s, t): 'red' if 'C' in s else 'black'
               for s, t in zip(link['id'], link['anode'])}
    nx.set_edge_attributes(G, e_color, 'color')
    e_line_type = {(s, t): 'dashed' if 'C' in s else 'solid'
                   for s, t in zip(link['id'], link['anode'])}
    nx.set_edge_attributes(G, e_line_type, 'line_type')

    tile_provider = get_provider(Vendors.CARTODBPOSITRON)

    bokeh_plot = figure(title="%s network map" %
                        name.split('/')[-1].split('.')[0],
                        sizing_mode="scale_height",
                        plot_width=1300,
                        x_range=(min(node['MX']), max(node['MX'])),
                        tools='pan,wheel_zoom',
                        active_drag="pan",
                        active_scroll="wheel_zoom")
    bokeh_plot.add_tile(tile_provider)

    # This callback is crucial, otherwise the filter will not be triggered when the slider changes
    callback = CustomJS(args=dict(source=node_source_data),
                        code="""
        source.change.emit();
    """)
    text_input.js_on_change('value', callback)

    # Define the custom filter to return the indices from 0 to the desired percentage of total data rows. You could
    # also compare against values in source.data
    js_filter = CustomJSFilter(args=dict(text_input=text_input),
                               code=f"""
    const z = source.data['desc'];
    var indices = ((() => {{
      var result = [];
      for (let i = 0, end = source.get_length(), asc = 0 <= end; asc ? i < end : i > end; asc ? i++ : i--) {{
        if (z[i].includes(text_input.value.toString(10))) {{
          result.push(i);
        }}
      }}
      return result;
    }})());
    return indices;""")

    # Use the filter in a view
    view = CDSView(source=node_source_data, filters=[js_filter])

    callback2 = CustomJS(args=dict(x_range=bokeh_plot.x_range,
                                   y_range=bokeh_plot.y_range,
                                   text_input=text_input,
                                   source=node_source_data),
                         code=f"""
    const z = source.data['desc'];
    const x = source.data['x'];
    const y = source.data['y'];
    var result = [];
    for (let i = 0, end = source.get_length(), asc = 0 <= end; asc ? i < end : i > end; asc ? i++ : i--) {{
      if (z[i].includes(text_input.value.toString(10))) {{
        result.push(i);
      }}
    }}
    var indices = result[0];
    var Xstart = x[indices];
    var Ystart = y[indices];
    y_range.setv({{"start": Ystart-280, "end": Ystart+280}});
    x_range.setv({{"start": Xstart-500, "end": Xstart+500}});
    x_range.change.emit();
    y_range.change.emit();
    """)

    text_input.js_on_change('value', callback2)

    graph = from_networkx(G,
                          nx.get_node_attributes(G, 'pos'),
                          scale=2,
                          center=(0, 0))
    graph.node_renderer.glyph = Circle(radius=15,
                                       fill_color='color',
                                       fill_alpha='alpha')
    graph.node_renderer.hover_glyph = Circle(radius=15, fill_color='red')

    graph.edge_renderer.glyph = MultiLine(
        line_alpha=1, line_color='color', line_width=1,
        line_dash='line_type')  # zero line alpha
    graph.edge_renderer.hover_glyph = MultiLine(line_color='#abdda4',
                                                line_width=5)
    graph.inspection_policy = NodesAndLinkedEdges()

    bokeh_plot.circle('x',
                      'y',
                      source=node_source_data,
                      radius=10,
                      color='green',
                      alpha=0.7,
                      view=view)

    labels = LabelSet(x='x',
                      y='y',
                      text='desc',
                      text_font_size="8pt",
                      text_color='black',
                      x_offset=5,
                      y_offset=5,
                      source=node_source_data,
                      render_mode='canvas')

    code = '''\
    if (toggle.active) {
        box.text_alpha = 0.0;
        console.log('enabling box');
    } else {
        box.text_alpha = 1.0;
        console.log('disabling box');
    }
    '''
    callback3 = CustomJS(code=code, args={})
    toggle = Toggle(label="Annotation", button_type="success")
    toggle.js_on_click(callback3)
    callback3.args = {'toggle': toggle, 'box': labels}

    bokeh_plot.add_tools(HoverTool(tooltips=[("id", "@desc")]), TapTool())
    # Output filepath
    bokeh_plot.renderers.append(graph)
    bokeh_plot.add_layout(labels)
    layout = column(toggle, text_input, bokeh_plot)

    # export_png(p, filename="plot.png")
    output_file(name)
    show(layout)