コード例 #1
0
ファイル: test_checkbox_group.py プロジェクト: zebulon2/bokeh
    def test_js_on_change_executes(self,
                                   bokeh_model_page: BokehModelPage) -> None:
        group = CheckboxGroup(labels=LABELS)
        group.js_on_change('active',
                           CustomJS(code=RECORD("active", "cb_obj.active")))

        page = bokeh_model_page(group)

        el = find_element_for(page.driver, group, 'input[value="2"]')
        el.click()

        results = page.results
        assert results['active'] == [2]

        el = find_element_for(page.driver, group, 'input[value="0"]')
        el.click()

        results = page.results
        assert results['active'] == [0, 2]

        el = find_element_for(page.driver, group, 'input[value="2"]')
        el.click()

        results = page.results
        assert results['active'] == [0]

        assert page.has_no_console_errors()
コード例 #2
0
def get_checkboxes_with_filter(labels: List[str],
                               column_label: str,
                               source,
                               select_all_btn=True,
                               clear_all_btn=True):
    """
    :param labels: names for checkbox labels as list (dataframe must contain it as value to filter)
    :param column_label: name of column in dataframe
    :param source: dataframe
    :return: checkboxes and filter for graph
    """
    # routes checkboxes
    checkboxes = CheckboxGroup(labels=labels, active=list(range(len(labels))))
    filter = CustomJSFilter(code='''
    var selected = checkboxes.active.map(i=>checkboxes.labels[i]);
    var indices = [];
    var column = source.data[column_label];

    // iterate through rows of data source and see if each satisfies some constraint
    for (var i = 0; i < column.length; i++){
        if(selected.includes(column[i])){
            indices.push(true);
        } else {
            indices.push(false);
        }
    }
    console.log("filter completed");
    return indices;
    ''',
                            args=dict(checkboxes=checkboxes,
                                      column_label=column_label))
    checkboxes.js_on_change(
        "active",
        CustomJS(code="source.change.emit();", args=dict(source=source)))

    widgets = [checkboxes]

    if select_all_btn:
        select_all = Button(label="выбрать все", width=65, height=30)
        select_all.js_on_click(
            CustomJS(args=dict(checkboxes=checkboxes,
                               all_active=list(range(len(labels)))),
                     code="""
            checkboxes.active = all_active
        """))
        widgets.append(select_all)

    if clear_all_btn:
        clear_all = Button(label="отчистить все", width=65, height=30)
        clear_all.js_on_click(
            CustomJS(args=dict(checkboxes=checkboxes),
                     code="""
            checkboxes.active = []
        """))
        widgets.append(clear_all)

    return Column(*widgets), filter
コード例 #3
0
from bokeh.plotting import figure

output_file("line_on_off.html", title="line_on_off.py example")

p = figure()
props = dict(line_width=4, line_alpha=0.7)
x = np.linspace(0, 4 * np.pi, 100)
l0 = p.line(x, np.sin(x), color=Viridis3[0], legend_label="Line 0", **props)
l1 = p.line(x,
            4 * np.cos(x),
            color=Viridis3[1],
            legend_label="Line 1",
            **props)
l2 = p.line(x, np.tan(x), color=Viridis3[2], legend_label="Line 2", **props)

checkbox = CheckboxGroup(labels=["Line 0", "Line 1", "Line 2"],
                         active=[0, 1, 2],
                         width=100)

callback = CustomJS(args=dict(l0=l0, l1=l1, l2=l2, checkbox=checkbox),
                    code="""
l0.visible = 0 in checkbox.active;
l1.visible = 1 in checkbox.active;
l2.visible = 2 in checkbox.active;
""")

checkbox.js_on_change('active', callback)

layout = row(checkbox, p)
show(layout)
コード例 #4
0
def plot(tables, output_filename):
    '''
    This is the plot function that uses Bokeh functions and widgets to make an interactive hexagon plot.

    This function recieves:
    - tables: dictionary with tables used to create arrays of repeated x, y coordinates (depending on the counts) for the hexagon plot.
    - output_filename: filename of .html output in the plots folder

    The coordinate arrays are used to create a pandas dataframe with Bokeh functions. This dataframe contains the q, r coordinates and counts used to plot the
    hexagons. To this dataframe, extra information is added (e.g. most common chemicals), which is displayed in the hover tooltip.

    Gaussian blur is added to copies of this dataframe and given as input to the Bokeh slider widget.
    Other widgets are added as well, for saturation, normalisation etc. Bokeh allows to customize these widges with javascript code.

    The hexagon plot is saved as a .html file and also shown in the browser.
    '''

    file_name = 'plots/' + str(output_filename) + '.html'
    output_file(file_name)

    # Blur and saturation values
    BLUR_MAX = 3
    BLUR_STEP_SIZE = 1
    SATURATION_MAX = 5
    SATURATION_STEP_SIZE = 0.25

    # First, create array for plot properties ( ratio, size of hexagons etc.)
    default_term = list(tables.keys())[0]
    x, y, ids = create_array(tables[default_term]['table'],
                             normalisation=False)

    # Hexagon plot properties
    length = len(x)
    orientation = 'flattop'
    ratio = ((max(y) - min(y)) / (max(x) - min(x)))
    size = 10 / ratio
    h = sqrt(3) * size
    h = h * ratio
    title = 'Hexbin plot for ' + str(
        length) + ' annotated chemicals with query ' + str(default_term)

    # make figure
    p = figure(title=title,
               x_range=[min(x) - 0.5, max(x) + 0.5],
               y_range=[0 - (h / 2), max(y) + 100],
               tools="wheel_zoom,reset,save",
               background_fill_color='#440154')

    p.grid.visible = False
    p.xaxis.axis_label = "log(P)"
    p.yaxis.axis_label = "mass in Da"
    p.xaxis.axis_label_text_font_style = 'normal'
    p.yaxis.axis_label_text_font_style = 'normal'

    # source for plot
    term_to_source, term_to_metadata, options = make_plot_sources(
        tables, size, ratio, orientation, BLUR_MAX, BLUR_STEP_SIZE)

    # start source for plot, this is the source that is first displayed in the hexagon figure
    x, y, ids = create_array(tables[default_term]['table'],
                             normalisation=False)
    df = hexbin(x, y, ids, size, aspect_scale=ratio, orientation=orientation)
    df = add_counts(df, tables[default_term]['table'])
    source = ColumnDataSource(df)
    metadata = term_to_metadata[default_term]
    metadata = return_html(metadata)

    # color mapper
    mapper = linear_cmap('scaling', 'Viridis256', 0,
                         max(source.data['scaling']))

    # plot
    hex = p.hex_tile(q="q",
                     r="r",
                     size=size,
                     line_color=None,
                     source=source,
                     aspect_scale=ratio,
                     orientation=orientation,
                     fill_color=mapper)

    # HOVER
    TOOLTIPS = return_tooltip()
    code_callback_hover = return_code('hover')
    callback_hover = CustomJS(code=code_callback_hover)
    hover = HoverTool(tooltips=TOOLTIPS,
                      callback=callback_hover,
                      show_arrow=False)
    p.add_tools(hover)

    # WIDGETS
    slider1 = Slider(start=1,
                     end=SATURATION_MAX,
                     value=1,
                     step=SATURATION_STEP_SIZE,
                     title="Saturation",
                     width=100)
    slider2 = Slider(start=0,
                     end=BLUR_MAX,
                     value=0,
                     step=BLUR_STEP_SIZE,
                     title="Blur",
                     width=100)
    checkbox = CheckboxGroup(labels=["TFIDF"], active=[])
    radio_button_group = RadioGroup(labels=["Viridis256", "Greys256"],
                                    active=0)
    button = Button(label="Metadata", button_type="default", width=100)
    multi_select = MultiSelect(title=output_filename,
                               value=[default_term],
                               options=options,
                               width=100,
                               height=300)

    # WIDGETS CODE FOR CALLBACK
    code_callback_slider1 = return_code('slider1')
    code_callback_slider2 = return_code('slider2')
    code_callback_checkbox = return_code('checkbox')
    code_callback_rbg = return_code('rbg')
    code_callback_button = return_code('button')
    code_callback_ms = return_code('multi_select')

    # WIDGETS CALLBACK
    callback_slider1 = CustomJS(args={
        'source': source,
        'mapper': mapper
    },
                                code=code_callback_slider1)
    callback_slider2 = CustomJS(args={
        'source': source,
        'mapper': mapper,
        'slider1': slider1,
        'multi_select': multi_select,
        'checkbox': checkbox,
        'term_to_source': term_to_source,
        'step_size': BLUR_STEP_SIZE
    },
                                code=code_callback_slider2)
    callback_checkbox = CustomJS(args={
        'source': source,
        'term_to_source': term_to_source,
        'multi_select': multi_select,
        'step_size': BLUR_STEP_SIZE,
        'slider1': slider1,
        'slider2': slider2,
        'mapper': mapper
    },
                                 code=code_callback_checkbox)
    callback_radio_button_group = CustomJS(args={
        'p': p,
        'mapper': mapper,
        'Viridis256': Viridis256,
        'Greys256': Greys256
    },
                                           code=code_callback_rbg)
    callback_button = CustomJS(args={
        'term_to_metadata': term_to_metadata,
        'multi_select': multi_select
    },
                               code=code_callback_button)
    callback_ms = CustomJS(args={
        'source': source,
        'term_to_source': term_to_source,
        'checkbox': checkbox,
        'metadata': metadata,
        'step_size': BLUR_STEP_SIZE,
        'slider2': slider2,
        'slider1': slider1,
        'p': p,
        'mapper': mapper
    },
                           code=code_callback_ms)

    # # WIDGETS INTERACTION
    slider1.js_on_change('value', callback_slider1)
    slider2.js_on_change('value', callback_slider2)
    checkbox.js_on_change('active', callback_checkbox)
    radio_button_group.js_on_change('active', callback_radio_button_group)
    button.js_on_event(events.ButtonClick, callback_button)
    multi_select.js_on_change("value", callback_ms)

    # LAYOUT
    layout = row(
        multi_select, p,
        column(slider1, slider2, checkbox, radio_button_group, button))

    show(layout)
コード例 #5
0
    def _show_graph(self, data_frame):

        #create the plot panels for the Agent_Tasks
        panels = self._create_task_panels(data_frame)

        top_left_x_range = panels[list(panels.keys())[0]]['panel1'].x_range

        #Altitude over ground
        pAltitude = figure(plot_width=800,
                           plot_height=300,
                           x_range=top_left_x_range)
        alti_legend = []
        # Setting the second y axis range name and range
        pAltitude.extra_y_ranges = {"speed": Range1d(50, 120)}
        # Adding the second axis to the plot.
        pAltitude.add_layout(
            LinearAxis(y_range_name="speed", axis_label="IAS, TAS [Knots]"),
            'right')

        altitudeLine = pAltitude.line(data_frame.index * self.step_time,
                                      data_frame['position_h_sl_ft'],
                                      line_width=2,
                                      color=Viridis4[2])
        alti_legend.append(("Altitude [ftsl]", [altitudeLine]))
        kiasLine = pAltitude.line(data_frame.index * self.step_time,
                                  data_frame['velocities_vc_kts'],
                                  line_width=2,
                                  y_range_name="speed",
                                  color=Viridis4[1])
        alti_legend.append(("Indicated Airspeed [KIAS]", [kiasLine]))
        tasLine = pAltitude.line(data_frame.index * self.step_time,
                                 data_frame['velocities_vtrue_kts'],
                                 line_width=2,
                                 y_range_name="speed",
                                 color=Viridis4[0])
        alti_legend.append(("True Airspeed [KAS]", [tasLine]))
        pAltitude.extra_y_ranges.renderers = [
            kiasLine, tasLine
        ]  #this does not quite work: https://stackoverflow.com/questions/48631530/bokeh-twin-axes-with-datarange1d-not-well-scaling
        pAltitude.y_range.renderers = [altitudeLine]

        lg_alti = Legend(items=alti_legend,
                         location=(0, 10),
                         glyph_width=25,
                         label_width=190)
        lg_alti.click_policy = "hide"
        pAltitude.add_layout(lg_alti, 'right')

        tAlti = Title()
        tAlti.text = 'Altitude and Speed [IAS, TAS] over Timesteps'
        pAltitude.title = tAlti
        pAltitude.xaxis.axis_label = 'timestep [s]'
        pAltitude.yaxis[0].axis_label = 'Altitude [ftsl]'
        pAltitude.legend.location = "center_right"

        pSideslip = figure(plot_width=800,
                           plot_height=300,
                           x_range=top_left_x_range)
        slip_legend = []

        slip_skid_line = pSideslip.line(data_frame.index * self.step_time,
                                        data_frame['aero_beta_deg'],
                                        line_width=2,
                                        color=Viridis4[2])
        slip_legend.append(("Sideslip", [slip_skid_line]))
        pSideslip.y_range.renderers = [slip_skid_line]

        lg_slip = Legend(items=slip_legend,
                         location=(0, 10),
                         glyph_width=25,
                         label_width=190)
        lg_slip.click_policy = "hide"
        pSideslip.add_layout(lg_slip, 'right')

        tSlip = Title()
        tSlip.text = 'Sideslip'
        pSideslip.title = tSlip
        pSideslip.xaxis.axis_label = 'timestep [s]'
        pSideslip.yaxis[0].axis_label = 'Sideslip [deg]'
        pSideslip.legend.location = "center_right"

        #activate the zooming on all plots
        #this is not nice, but this not either: https://stackoverflow.com/questions/49282688/how-do-i-set-default-active-tools-for-a-bokeh-gridplot
        pAltitude.toolbar.active_scroll = pAltitude.toolbar.tools[
            1]  #this selects the WheelZoomTool instance
        pSideslip.toolbar.active_scroll = pSideslip.toolbar.tools[
            1]  #this selects the WheelZoomTool instance

        reset_output()

        # if self.env.meta_dict['model_type'] == 'trained':
        #     discriminator = self.env.meta_dict['model_base_name']+"_%+.2f" % (data_frame['reward'].sum())
        #     self.env.meta_dict['model_discriminator'] = discriminator
        # else:
        #     discriminator = self.env.meta_dict['model_discriminator']

        ts = time.time()
        overshoot_frames_per_task = self._analyze_overshoot(data_frame)
        overshoot_divs = [
            Div(text=ovs_fr.round(3).to_html(), width=600)
            for ovs_fr in overshoot_frames_per_task
        ]
        print("Overshoot analysis done in %.2f sec" % (time.time() - ts))

        ts = time.time()
        settlement_times_per_task = self._analyze_settle_times(data_frame)
        settlement_divs = [
            Div(text=settle_fr.round(3).to_html(), width=600)
            for settle_fr in settlement_times_per_task
        ]
        print("Settlement analysis done in %.2f sec" % (time.time() - ts))

        panel_grid = []
        panel_grid.append([
            Div(text='<h3>' + t.name + '</h3>', id='div_' + t.name)
            for t in self.env.task_list
        ])

        # to switch on and off the statistics panels, this is unfortuntely the best, I could achive
        # https://stackoverflow.com/a/52416676/2682209
        cols = []
        checkbox = CheckboxGroup(
            labels=["show stats"], active=[],
            width=100)  #checkbox is added to header_col later on

        for i, t in enumerate(self.env.task_list):
            # overshoot_stat = overshoot_divs[i]
            c = column(Div())  #empty for the beginning
            cols.append(c)

        callback = CustomJS(args=dict(overshoot_divs=overshoot_divs,
                                      settlement_divs=settlement_divs,
                                      cols=cols,
                                      checkbox=checkbox),
                            code="""
                    for (var j = 0; j < cols.length; j++) {
                        console.log('col', j)
                        const children = []
                        for (const i of checkbox.active) {
                            console.log('active', i)
                            children.push(overshoot_divs[j])
                            children.push(settlement_divs[j])
                        } 
                        console.log('children', children)
                        cols[j].children = children
                    }
                    """)
        checkbox.js_on_change('active', callback)

        # show_stats_btn = [Div(text="""
        # <button onclick="display_event(%s)">Try it</button>
        # """ %t.name for t in self.env.task_list]
        # for t, b in zip(self.env.task_list, show_stats_btn):
        #     b.tags = ['id', 'btn_'+t.name]
        # panel_grid.append(show_stats_btn)
        # [b.js_on_event(events.ButtonClick, display_event(b, t.name)) for t, b in zip(self.env.task_list, show_stats_btn)]

        # panel_grid.append(chkbxs)
        panel_grid.append(cols)

        panel_grid_t = [[
            panels[name]['panel1'], panels[name]['panel2'],
            panels[name]['panel3']
        ] for name in self.task_names]
        [panel_grid.append(fig) for fig in list(zip(*panel_grid_t))]

        # add the additional plots
        panel_grid.append([pAltitude, pSideslip])

        panel_grid_plot = gridplot(panel_grid,
                                   toolbar_location='right',
                                   sizing_mode='stretch_width')

        #for string formatting look here: https://pyformat.info/
        titleString = ''
        if 'experiment_name' in self.env.meta_dict:
            titleString += "{}: ".format(self.env.meta_dict['experiment_name'])
        titleString += "Run Date: {}; ".format(
            datetime.datetime.now().strftime("%c"))

        if 'train_step' in self.env.meta_dict:
            titleString += "Training Step: {}; ".format(
                self.env.meta_dict['train_step'])
        if 'episode_number' in self.env.meta_dict:
            titleString += "Episode: {}; ".format(
                self.env.meta_dict['episode_number'])
        if 'csv_line_nr' in self.env.meta_dict:
            titleString += "Env in CSV line: {}; ".format(
                self.env.meta_dict['csv_line_nr'])
        # titleString += "Total Reward: {:.2f}; ".format(data_frame['reward'].sum())
        # titleString += "Model Discriminator: {};".format(self.env.meta_dict['model_discriminator'])
        header_col = column(
            Div(text="<h1>" + self.env.unwrapped.spec.id +
                (" - " + self.env.meta_dict['env_info']) if 'env_info' in
                self.meta_dict else "" + "</h1>"),
            row(Div(text="<h2>" + titleString + "</h2>", width=1200),
                checkbox))

        webpage = gridplot([[header_col], [panel_grid_plot]],
                           toolbar_location=None,
                           sizing_mode='stretch_width')

        base_filename = 'Run_' + '_'.join([tsk.name for tsk in self.task_list])
        html_output_name = os.path.join(self.save_path, 'plots',
                                        base_filename + '_latest.html')
        os.makedirs(os.path.dirname(html_output_name), exist_ok=True)

        if self.showNextPlotFlag:
            output_file(
                html_output_name, mode='inline'
            )  #mode='absolute') #use mode='absolute' to make it work offline with the js and css installed in the bokeh package locally
            if self.firstRun:
                show(webpage)  #opens up a new browser window
                self.firstRun = False
            else:
                save(
                    webpage
                )  #just updates the HTML; Manual F5 in browser required :-(, (There must be a way to push...)

        if self.exportNextPlotFlag and self.save_path:
            #build the filename including the individual rewards
            task_rewards = [
                self.reward_variables[i].get_legal_name()
                for i in range(len(self.task_list))
            ]
            task_names_with_rewards = [
                t.name + '_' + f'{data_frame[task_rewards[i]].sum():.2f}'
                for i, t in enumerate(self.task_list)
            ]
            name_with_rewards = 'Run_' + '_'.join(
                task_names_with_rewards) + 'time_{}'.format(
                    datetime.datetime.now().strftime("%H-%M-%S"))
            base_filename = os.path.join(
                self.save_path, 'plots', name_with_rewards
            )  # 'glideAngle_Elevator_Reward_{:.2f}_time_{}'.format(data_frame['reward'].sum(), datetime.datetime.now().strftime("%H-%M-%S")))
            if self.showNextPlotFlag:
                #we keep the html as well for easy exploration
                shutil.copyfile(html_output_name, base_filename + '.html')

            def export(webpage):
                png_filename = base_filename + '.png'
                webpage.width = 1800  #set the width of the page instead of passing a width parameter to the export; https://stackoverflow.com/a/61563173/2682209
                export_png(
                    webpage, filename=png_filename
                )  #TODO: the width parameter is ignored in bokeh/io/export.py get_layout_html() as webpage isn't a Plot

            export(
                gridplot([[header_col], [panel_grid_plot]],
                         toolbar_location=None))

        self.showNextPlotFlag = False  #only show the plot once and then reset
        self.exportNextPlotFlag = False
        print("Output Plot generated: " + titleString)
コード例 #6
0
def plot(tables, output_filename, xmin, xmax, ymin, ymax, superterm):
    '''
    This is the plot function that uses Bokeh functions and widgets to make an interactive hexagon plot.

    This function recieves:
    - tables: dictionary with tables used to create arrays of repeated x, y coordinates (depending on the counts) for the hexagon plot.
    - output_filename: filename of .html output in the plots folder

    The coordinate arrays are used to create a pandas dataframe with Bokeh functions. This dataframe contains the q, r coordinates and counts used to plot the
    hexagons. To this dataframe, extra information is added (e.g. most common chemicals), which is displayed in the hover tooltip.

    Gaussian blur is added to copies of this dataframe and given as input to the Bokeh slider widget.
    Other widgets are added as well, for saturation, normalization etc. Bokeh allows to customize these widges with javascript code.

    The hexagon plot is saved as a .html file and also shown in the browser.
    '''

    file_name = 'plots/' + str(output_filename) + '.html'
    output_file(file_name)

    # Blur and saturation values
    BLUR_MAX = 4
    BLUR_STEP_SIZE = 0.25
    SATURATION_MAX = 5
    SATURATION_STEP_SIZE = 0.25

    # Hexagon plot properties
    SIZE_HEXAGONS = 10
    orientation = 'flattop'  #bokeh alows 2 different hexagon orientations which also influences hexagon size calculations, but we currently have only calculated blur distances for this orientation
    ratio = ((ymax - ymin) / (xmax - xmin))
    size = SIZE_HEXAGONS / ratio
    hexagon_height = sqrt(3) * size
    hexagon_height = hexagon_height * ratio

    # make figure
    p = figure(x_range=[xmin, xmax],
               y_range=[ymin - (hexagon_height / 2), ymax],
               tools="wheel_zoom,reset,save",
               background_fill_color='#440154')

    p.grid.visible = False
    p.xaxis.axis_label = "log(P)"
    p.yaxis.axis_label = "mass in Da"
    p.xaxis.axis_label_text_font_style = 'normal'
    p.yaxis.axis_label_text_font_style = 'normal'

    # term_to_source, term_to_metadata, options = make_plot_sources(tables, size, ratio, orientation, BLUR_MAX, BLUR_STEP_SIZE)

    # source for widgets
    term_to_source = dict()
    term_to_class = dict()
    term_to_metadata = dict()
    options = []

    for term in tables.keys():
        options.append((term, term))
        table = tables[term]['table']
        if superterm:
            source = create_class_source(table, term, size, ratio, orientation,
                                         superterm)
            term_to_class[term] = {}
            term_to_class[term]['show_class'] = True
            term_to_class[term]['source'] = source
        else:
            term_to_class[term] = {'show_class': False}
        source, title = create_data_source(table, term, size, ratio,
                                           orientation, BLUR_MAX,
                                           BLUR_STEP_SIZE)
        metadata = return_html(tables[term]['metadata'])
        term_to_source[term] = {'source': source, 'title': title}
        term_to_metadata[term] = metadata

    # hex = p.hex_tile(q='q', r="r", size=size, line_color=None, source=source, aspect_scale=ratio,orientation=orientation,
    # fill_color='pink' )

    # show(p)

    # make default souce for plot, this is the first source shown in the plot, and also works like a container. Old data is thrown out and new data is thrown in.
    default_term = list(tables.keys())[0]  # pick the first one
    metadata = tables[default_term]['metadata']
    metadata = return_html(metadata)
    table = tables[default_term]['table']
    source, title = create_data_source(table, default_term, size, ratio,
                                       orientation, BLUR_MAX, BLUR_STEP_SIZE)
    p.title.text = title

    # color mapper
    mapper = linear_cmap('scaling', 'Viridis256', 0,
                         max(source.data['scaling']))

    # plot
    hex = p.hex_tile(q="q",
                     r="r",
                     size=size,
                     line_color=None,
                     source=source,
                     aspect_scale=ratio,
                     orientation=orientation,
                     fill_color=mapper)
    if superterm:
        source_class = term_to_class[default_term]['source']
        class_hex = p.hex_tile(q='q',
                               r="r",
                               size=size,
                               line_color=None,
                               source=source_class,
                               aspect_scale=ratio,
                               orientation=orientation,
                               fill_color='pink',
                               fill_alpha=0.7)
        class_hex.visible = False
    # HOVER
    TOOLTIPS = return_JS_code('tooltips')
    TOOLTIPS_tfidf = return_JS_code('tooltips_tfidf')
    code_callback_hover = return_JS_code('hover')
    callback_hover = CustomJS(code=code_callback_hover)
    hover = HoverTool(tooltips=TOOLTIPS,
                      callback=callback_hover,
                      show_arrow=False)
    p.add_tools(hover)

    # WIDGETS
    slider1 = Slider(start=1,
                     end=SATURATION_MAX,
                     value=1,
                     step=SATURATION_STEP_SIZE,
                     title="Saturation",
                     width=100)
    slider2 = Slider(start=0,
                     end=BLUR_MAX,
                     value=0,
                     step=BLUR_STEP_SIZE,
                     title="Blur",
                     width=100)
    checkbox = CheckboxGroup(labels=["TFIDF"], active=[])
    radio_button_group = RadioGroup(labels=["Viridis256", "Greys256"],
                                    active=0)
    button = Button(label="Metadata", button_type="default", width=100)
    multi_select = MultiSelect(title=output_filename,
                               value=[default_term],
                               options=options,
                               width=100,
                               height=300)
    if superterm:
        label = "Show " + str(superterm)
        checkbox_class = CheckboxGroup(labels=[label], active=[])

    # WIDGETS CODE FOR CALLBACK
    code_callback_slider1 = return_JS_code('slider1')
    code_callback_slider2 = return_JS_code('slider2')
    code_callback_checkbox = return_JS_code('checkbox')
    code_callback_rbg = return_JS_code('rbg')
    code_callback_button = return_JS_code('button')
    code_callback_ms = return_JS_code('multi_select')
    if superterm:
        code_callback_class = return_JS_code('class')

    # WIDGETS CALLBACK
    callback_slider1 = CustomJS(args={
        'source': source,
        'mapper': mapper,
        'slider2': slider2,
        'checkbox': checkbox
    },
                                code=code_callback_slider1)
    callback_slider2 = CustomJS(args={
        'source': source,
        'mapper': mapper,
        'slider1': slider1,
        'checkbox': checkbox
    },
                                code=code_callback_slider2)
    callback_checkbox = CustomJS(args={
        'source': source,
        'slider1': slider1,
        'slider2': slider2,
        'mapper': mapper,
        'hover': hover,
        'tooltips': TOOLTIPS,
        'tooltips_tfidf': TOOLTIPS_tfidf
    },
                                 code=code_callback_checkbox)
    callback_radio_button_group = CustomJS(args={
        'p': p,
        'multi_select': multi_select,
        'mapper': mapper,
        'term_to_class': term_to_class,
        'Viridis256': Viridis256,
        'Greys256': Greys256
    },
                                           code=code_callback_rbg)
    callback_button = CustomJS(args={
        'term_to_metadata': term_to_metadata,
        'multi_select': multi_select
    },
                               code=code_callback_button)
    callback_ms = CustomJS(args={
        'source': source,
        'term_to_source': term_to_source,
        'term_to_class': term_to_class,
        'checkbox': checkbox,
        'slider2': slider2,
        'slider1': slider1,
        'p': p,
        'mapper': mapper
    },
                           code=code_callback_ms)
    if superterm:
        callback_radio_button_group = CustomJS(args={
            'p': p,
            'multi_select': multi_select,
            'class_hex': class_hex,
            'term_to_class': term_to_class,
            'mapper': mapper,
            'Viridis256': Viridis256,
            'Greys256': Greys256
        },
                                               code=code_callback_rbg)
        callback_class = CustomJS(args={
            'multi_select': multi_select,
            'term_to_class': term_to_class,
            'class_hex': class_hex
        },
                                  code=code_callback_class)
        callback_ms = CustomJS(args={
            'source': source,
            'term_to_source': term_to_source,
            'checkbox': checkbox,
            'slider2': slider2,
            'slider1': slider1,
            'p': p,
            'mapper': mapper,
            'checkbox_class': checkbox_class,
            'class_hex': class_hex,
            'term_to_class': term_to_class
        },
                               code=code_callback_ms)

    # WIDGETS INTERACTION
    slider1.js_on_change('value', callback_slider1)
    slider2.js_on_change('value', callback_slider2)
    checkbox.js_on_change('active', callback_checkbox)
    radio_button_group.js_on_change('active', callback_radio_button_group)
    button.js_on_event(events.ButtonClick, callback_button)
    multi_select.js_on_change("value", callback_ms)
    if superterm:
        checkbox_class.js_on_change('active', callback_class)

    # LAYOUT
    if superterm:
        layout = row(
            multi_select, p,
            column(slider1, slider2, checkbox, checkbox_class,
                   radio_button_group, button))
    else:
        layout = row(
            multi_select, p,
            column(slider1, slider2, checkbox, radio_button_group, button))

    show(layout)
コード例 #7
0
def make_plot(emperor, df, column=None, value=None):

    df = df[df['Emperor'] == emperor]

    source = ColumnDataSource(df)

    select_den = Select(title="Denomination",
                        value="All",
                        options=["All", "Aureus", "Denarius"])

    select_grd = Select(title="Grade",
                        value="All",
                        options=[
                            'All', 'Fine', 'Good Fine', 'Near VF', 'VF',
                            'Good VF', 'Near EF', 'EF', 'Superb EF',
                            'Choice EF', 'FDC'
                        ])

    check_attractive = CheckboxGroup(labels=["Attractively Toned"], active=[])

    check_cabinet = CheckboxGroup(labels=["Cabinet Toning"], active=[])

    check_lusterous = CheckboxGroup(labels=["Lusterous"], active=[])

    check_centered = CheckboxGroup(labels=["Well Centered"], active=[])

    check_portrait = CheckboxGroup(labels=["Artistic Portrait"], active=[])

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

    select_den.js_on_change('value', callback)
    select_grd.js_on_change('value', callback)

    check_attractive.js_on_change('active', callback)
    check_cabinet.js_on_change('active', callback)
    check_lusterous.js_on_change('active', callback)
    check_centered.js_on_change('active', callback)
    check_portrait.js_on_change('active', callback)

    custom_filter_grd = CustomJSFilter(args=dict(select=select_grd,
                                                 source=source),
                                       code='''
        var indices = [];
        console.log(select.value);
        // no select cuts applied
        if (select.value == 'All') {
            for (var i = 0; i < source.get_length(); i++){
                indices.push(true);
            }
            return indices;
        }
        // iterate through rows of data source and see if each satisfies some constraint
        for (var i = 0; i < source.get_length(); i++){
            if (source.data['Grade'][i] == select.value){
                indices.push(true);
            } else {
                indices.push(false)
            }
        }
        return indices;
    ''')

    custom_filter_den = CustomJSFilter(args=dict(select=select_den,
                                                 source=source),
                                       code='''
        var indices = [];
        console.log(select.value);
        // no select cuts applied
        if (select.value == 'All') {
            for (var i = 0; i < source.get_length(); i++){
                indices.push(true);
            }
            return indices;
        }
        // iterate through rows of data source and see if each satisfies some constraint
        for (var i = 0; i < source.get_length(); i++){
            if (source.data['Denomination'][i] == select.value){
                indices.push(true);
            } else {
                indices.push(false)
            }
        }
        //console.log(indices)
        return indices;
    ''')

    custom_filter_attractive = CustomJSFilter(args=dict(
        checkbox=check_attractive, source=source),
                                              code='''
        var indices = [];
        //console.log(checkbox.active);
        if (checkbox.active.includes(0)) {
            //console.log('0 on')
            //console.log(checkbox.active.includes(0));
            for (var i = 0; i < source.get_length(); i++) {
                if (source.data['Attractively Toned'][i] == 1) {
                    indices.push(true);
                } else {
                    indices.push(false)
                }
            }
        } else {
            //console.log('0 off')
            for (var i = 0; i < source.get_length(); i++) {
                indices.push(true);
            }
        }
        return indices;
    ''')

    custom_filter_cabinet = CustomJSFilter(args=dict(checkbox=check_cabinet,
                                                     source=source),
                                           code='''
        var indices = [];
        //console.log(checkbox.active);
        if (checkbox.active.includes(0)) {
            //console.log('0 on')
            //console.log(checkbox.active.includes(0));
            for (var i = 0; i < source.get_length(); i++) {
                if (source.data['Cabinet Toning'][i] == 1) {
                    indices.push(true);
                } else {
                    indices.push(false)
                }
            }
        } else {
            //console.log('0 off')
            for (var i = 0; i < source.get_length(); i++) {
                indices.push(true);
            }
        }
        return indices;
    ''')

    custom_filter_lusterous = CustomJSFilter(args=dict(
        checkbox=check_lusterous, source=source),
                                             code='''
        var indices = [];
        //console.log(checkbox.active);
        if (checkbox.active.includes(0)) {
            //console.log('0 on')
            //console.log(checkbox.active.includes(0));
            for (var i = 0; i < source.get_length(); i++) {
                if (source.data['Lusterous'][i] == 1) {
                    indices.push(true);
                } else {
                    indices.push(false)
                }
            }
        } else {
            //console.log('0 off')
            for (var i = 0; i < source.get_length(); i++) {
                indices.push(true);
            }
        }
        return indices;
    ''')

    custom_filter_centered = CustomJSFilter(args=dict(checkbox=check_centered,
                                                      source=source),
                                            code='''
        var indices = [];
        //console.log(checkbox.active);
        if (checkbox.active.includes(0)) {
            //console.log('0 on')
            //console.log(checkbox.active.includes(0));
            for (var i = 0; i < source.get_length(); i++) {
                if (source.data['Cabinet Toning'][i] == 1) {
                    indices.push(true);
                } else {
                    indices.push(false)
                }
            }
        } else {
            //console.log('0 off')
            for (var i = 0; i < source.get_length(); i++) {
                indices.push(true);
            }
        }
        return indices;
    ''')

    custom_filter_portrait = CustomJSFilter(args=dict(checkbox=check_portrait,
                                                      source=source),
                                            code='''
        var indices = [];
        //console.log(checkbox.active);
        if (checkbox.active.includes(0)) {
            //console.log('0 on')
            //console.log(checkbox.active.includes(0));
            for (var i = 0; i < source.get_length(); i++) {
                if (source.data['Quality Portrait'][i] == 1) {
                    indices.push(true);
                } else {
                    indices.push(false)
                }
            }
        } else {
            //console.log('0 off')
            for (var i = 0; i < source.get_length(); i++) {
                indices.push(true);
            }
        }
        return indices;
    ''')

    view = CDSView(source=source,
                   filters=[
                       custom_filter_grd, custom_filter_den,
                       custom_filter_attractive, custom_filter_cabinet,
                       custom_filter_lusterous, custom_filter_centered,
                       custom_filter_portrait
                   ])

    TOOLS = ["pan, wheel_zoom, box_zoom, reset, save"]

    TOOLTIPS = [("Auction", "@AuctionID"), ("Lot", "@LotNumber"),
                ("Emperor", "@Emperor"), ("RIC Number", "@RIC"),
                ("Estimate [USD]", "@Estimate"), ("Sale [USD]", "@Sale")]

    plot = figure(
        title='CNG Auctions through 2019 for Coins of Emperor ' + emperor,
        plot_width=500,
        plot_height=300,
        tools=TOOLS,
        tooltips=TOOLTIPS,
        x_range=Range1d(start=20, end=80000, bounds=(None, None)),
        y_range=[
            'Fine', 'Good Fine', 'Near VF', 'VF', 'Good VF', 'Near EF', 'EF',
            'Superb EF', 'Choice EF', 'FDC'
        ],
        x_axis_type='log')

    color_mapper = CategoricalColorMapper(factors=['Aureus', 'Denarius'],
                                          palette=['#FFD700', '#C0C0C0'])

    plot.circle(x='Sale',
                y=jitter('Grade', 0.4, range=plot.y_range),
                source=source,
                view=view,
                fill_alpha=0.8,
                size=5,
                legend='data',
                line_color=None,
                color={
                    'field': 'Denomination',
                    'transform': color_mapper
                },
                hover_color='red')

    plot.xaxis.axis_label = "Sale Price (USD)"
    plot.yaxis.axis_label = "Grade"
    plot.xaxis.formatter = BasicTickFormatter(use_scientific=False)
    plot.xaxis.formatter = NumeralTickFormatter(format='$0,0')
    plot.legend.visible = False

    return row(
        plot,
        widgetbox([
            select_den, select_grd, check_attractive, check_cabinet,
            check_lusterous, check_centered, check_portrait
        ]))
コード例 #8
0
p_answered.add_tools(hover_answered)
p_nanswered.add_tools(hover_answered)

#
# Widgets
#

# data filters
filter_answered = checkbox_categorical_filter(src_ans, src_ans_orig)
filter_nanswered = checkbox_categorical_filter(src_nans, src_nans_orig)

# checkbox object
checkbox_group = CheckboxGroup(labels=LABELS, active=[i for i in range(len(LABELS))])

# event handlers
checkbox_group.js_on_change('active', filter_answered)
checkbox_group.js_on_change('active', filter_nanswered)

# output_file("color_scatter.html", title="color_scatter.py example")

# layout specificationns 
lay = layout(
        children = [
            [p_answered, p_nanswered],
            [checkbox_group],
        ]
)

tab = Panel(child=lay, title = 'Clusters')
tabs = Tabs(tabs=[tab])
コード例 #9
0
def get_dashboard(local_data=False):
    """
    Assemble the dashboard. Define the layout, controls and 
    its callbacks, as well as data sources.
    """
    # Load data
    video_data = {}
    if local_data:
        data = pd.json_normalize(
            pd.read_json('data/channel/processed_data.json')['items'])
        with open('data/channel/processed_data.json', 'r') as f:
            data = json.load(f)
            video_data = data['items']
    else:
        video_data = handle_channel_data.get_data_firebase()['items']

    # X axis categories
    x_axis_map = {
        "Climber": "climber",
        "Zone": "zone",
        "Grade": "grade",
    }
    # Y axis categories
    y_axis_map = {
        "Count": "count",
        "Views": "viewCount",
        # "Favourites": "favoriteCount",
        "Likes": "likeCount",
        "Dislikes": "dislikeCount",
        "Comments": "commentCount"
    }

    # get ready to plot data
    barchart_data = prepare_barchart_data(video_data, x_axis_map)

    # html template to place the plots
    desc = Div(text=open(join(dirname(__file__),
                              "templates/stats.html")).read(),
               sizing_mode="stretch_width")

    # initial data source fill
    data_to_plot = barchart_data['grade']['raw']

    od = collections.OrderedDict(
        sorted(data_to_plot.items(), key=lambda x: x[0]))

    x_to_plot = np.array([key for key, _ in od.items()])
    y_to_plot = np.array([val['count'] for _, val in od.items()])
    source = ColumnDataSource(data=dict(x=x_to_plot, y=y_to_plot))
    # initial data
    x_init = x_to_plot[0:NUM_RESULTS]
    y_init = y_to_plot[0:NUM_RESULTS]

    # Create Input controls
    checkbox_limit_results = CheckboxGroup(
        labels=["Show only first 50 results"], active=[0])

    label_slider = Slider(start=0,
                          end=90,
                          value=90,
                          step=1,
                          title="Label Angle")

    range_slider = RangeSlider(title="Value Range",
                               start=0,
                               end=max(y_to_plot),
                               value=(0, max(y_to_plot)),
                               step=1)

    min_year = Slider(title="From", start=2015, end=2020, value=2015, step=1)

    max_year = Slider(title="To", start=2015, end=2020, value=2020, step=1)

    sort_order = RadioButtonGroup(
        labels=["Alphabetically", "Decreasing", "Increasing"], active=0)
    x_axis = Select(title="X Axis",
                    options=sorted(x_axis_map.keys()),
                    value="Grade")

    y_axis = Select(title="Y Axis",
                    options=sorted(y_axis_map.keys()),
                    value="Count")

    checkbox = CheckboxGroup(
        labels=["Show ratio with respect to number of videos"], active=[])

    # show number of categories
    x_count_source = ColumnDataSource(
        data=dict(x_count=[len(x_init)], category=[x_axis.value]))

    columns = [
        TableColumn(field="category", title="Category"),
        TableColumn(field="x_count", title="Count"),
    ]

    x_count_data_table = DataTable(source=x_count_source,
                                   columns=columns,
                                   width=320,
                                   height=280)

    # Generate the actual plot
    # p = figure(x_range=x_to_plot, y_range=(0, max(y_to_plot)), plot_height=250, title="{} {}".format(x_axis.value, y_axis.value),
    #            toolbar_location="above")
    p = figure(x_range=x_init,
               y_range=(0, max(y_init)),
               plot_height=250,
               title="{} {}".format(x_axis.value, y_axis.value),
               toolbar_location="above")

    # Fill it with data and format it
    p.vbar(x='x', top='y', width=0.9, source=source)
    p.xaxis.major_label_orientation = math.pi / 2
    p.add_tools(HoverTool(tooltips=[("name", "@x"), ("count", "@y")]))

    # Controls
    controls = [
        checkbox_limit_results, range_slider, min_year, max_year, sort_order,
        x_axis, y_axis, checkbox, label_slider, x_count_data_table
    ]

    # Callbacks for controls
    label_callback = CustomJS(args=dict(axis=p.xaxis[0]),
                              code="""
        axis.major_label_orientation = cb_obj.value * Math.PI / 180;
        """)
    label_slider.js_on_change('value', label_callback)

    # limit checkbox
    checkbox_limit_results_callback = CustomJS(
        args=dict(source=source,
                  x_source=x_count_source,
                  o_data=barchart_data,
                  sort_order=sort_order,
                  x_axis_map=x_axis_map,
                  x_axis=x_axis,
                  y_axis_map=y_axis_map,
                  y_axis=y_axis,
                  range_slider=range_slider,
                  checkbox=checkbox,
                  fig=p,
                  title=p.title),
        code=SORT_FUNCTION + JS_NUM_RESULTS + """
            var data = o_data[x_axis_map[x_axis.value]];
            var x = data['x'];
            var y = data['y'];
            var apply_limit = cb_obj.active.length > 0;
            var is_ratio = checkbox.active.length > 0;
            title.text = x_axis.value.concat(" ", y_axis.value);
            // Sort data
            var sorted_data = sortData(data['raw'], sort_order.active, y_axis_map[y_axis.value], is_ratio);
            var new_y = [];
            var new_x = [];
            var final_x = [];
            var final_y = [];
            for (var i = 0; i < x.length; i++) {
                if (apply_limit)
                {
                    if(sorted_data[i][1] >= range_slider.value[0] && sorted_data[i][1] <= range_slider.value[1]) 
                    {
                        new_x.push(sorted_data[i][0]);
                        new_y.push(sorted_data[i][1]);
                    } 
                } else {
                    new_x.push(sorted_data[i][0]);
                    new_y.push(sorted_data[i][1]);                    
                }
            }
            if (apply_limit) { 
                final_x = new_x.slice(0, num_results);
                final_y = new_y.slice(0, num_results);
                window.should_update_range = false;
            } else {
                final_x = new_x;
                final_y = new_y;
                window.should_update_range = true;
            }
            x_source.data['x_count'] = [final_x.length];
            x_source.data['category'] = [x_axis.value];
            x_source.change.emit();
            source.data['x'] = new_x;
            source.data['y'] = new_y;
            source.change.emit();
            fig.x_range.factors = [];
            fig.x_range.factors = final_x;
            if (Array.isArray(new_y) && new_y.length) {
                // range init and end cannot have same value
                var range_end = Math.max.apply(Math, new_y);
                if (range_end == 0 || range_end == -Infinity) {
                    range_end = 1;
                }
                range_slider.value = [0, Math.max.apply(Math, final_y)]; 
                range_slider.end = range_end;
                fig.y_range.end = Math.max.apply(Math, final_y);
                fig.change.emit();
            }        
        """)
    checkbox_limit_results.js_on_change('active',
                                        checkbox_limit_results_callback)

    # ratio checkbox
    checkbox_callback = CustomJS(args=dict(
        source=source,
        x_source=x_count_source,
        o_data=barchart_data,
        sort_order=sort_order,
        x_axis_map=x_axis_map,
        x_axis=x_axis,
        y_axis_map=y_axis_map,
        y_axis=y_axis,
        range_slider=range_slider,
        checkbox_limit_results=checkbox_limit_results,
        fig=p,
        title=p.title),
                                 code=SORT_FUNCTION + JS_NUM_RESULTS + """
            var data = o_data[x_axis_map[x_axis.value]];
            var x = data['x'];
            var y = data['y'];
            var is_ratio = cb_obj.active.length > 0;
            title.text = x_axis.value.concat(" ", y_axis.value);
            if (is_ratio)
            {
                title.text = x_axis.value.concat(" ", y_axis.value, " per video");   
            }
            // Sort data
            var sorted_data = sortData(data['raw'], sort_order.active, y_axis_map[y_axis.value], is_ratio);
            var new_y = [];
            var new_x = [];
            var final_x = [];
            var final_y = [];
            for (var i = 0; i < x.length; i++) {
                if (checkbox_limit_results.active.length <= 0)
                {
                    if(sorted_data[i][1] >= range_slider.value[0] && sorted_data[i][1] <= range_slider.value[1]) 
                    {
                        new_x.push(sorted_data[i][0]);
                        new_y.push(sorted_data[i][1]);
                    } 
                } else {
                    new_x.push(sorted_data[i][0]);
                    new_y.push(sorted_data[i][1]);                    
                }
            }
            if (checkbox_limit_results.active.length > 0) { 
                final_x = new_x.slice(0, num_results);
                final_y = new_y.slice(0, num_results);
                window.should_update_range = false;
            } else {
                final_x = new_x;
                final_y = new_y;
                window.should_update_range = true;
            }
            x_source.data['x_count'] = [final_x.length];
            x_source.data['category'] = [x_axis.value];
            x_source.change.emit();
            source.data['x'] = new_x;
            source.data['y'] = new_y;
            source.change.emit();
            fig.x_range.factors = [];
            fig.x_range.factors = final_x;
            if (Array.isArray(new_y) && new_y.length) {
                // range init and end cannot have same value
                var range_end = Math.max.apply(Math, new_y);
                if (range_end == 0 || range_end == -Infinity) {
                    range_end = 1;
                }
                range_slider.value = [0, Math.max.apply(Math, final_y)]; 
                range_slider.end = range_end;
                fig.y_range.end = Math.max.apply(Math, final_y);
                fig.change.emit();
            }
        """)
    checkbox.js_on_change('active', checkbox_callback)

    # range slider
    range_callback = CustomJS(args=dict(
        source=source,
        x_source=x_count_source,
        o_data=barchart_data,
        sort_order=sort_order,
        x_axis_map=x_axis_map,
        x_axis=x_axis,
        y_axis_map=y_axis_map,
        y_axis=y_axis,
        checkbox=checkbox,
        checkbox_limit_results=checkbox_limit_results,
        fig=p),
                              code=SORT_FUNCTION + """
            if (window.should_update_range == true) {
                var data = o_data[x_axis_map[x_axis.value]];
                var x = data['x'];
                var y = data['y'];
                var is_ratio = checkbox.active.length > 0;
                // Sort data
                var sorted_data = sortData(data['raw'], sort_order.active, y_axis_map[y_axis.value], is_ratio);
                var new_y = [];
                var new_x = [];
                for (var i = 0; i < x.length; i++) {
                    if (checkbox_limit_results.active.length <= 0)
                    {
                        if (sorted_data[i][1] >= cb_obj.value[0] && sorted_data[i][1] <= cb_obj.value[1]) 
                        {
                            new_x.push(sorted_data[i][0]);
                            new_y.push(sorted_data[i][1]);
                        }
                    } else {
                        new_x.push(sorted_data[i][0]);
                        new_y.push(sorted_data[i][1]);                        
                    }
                }
                x_source.data['x_count'] = [new_x.length];
                x_source.data['category'] = [x_axis.value];
                x_source.change.emit();
                source.data['x'] = new_x;
                source.data['y'] = new_y;
                source.change.emit();
                fig.x_range.factors = [];
                fig.x_range.factors = new_x;
                if (Array.isArray(new_y) && new_y.length) {
                    fig.y_range.end = Math.max.apply(Math, new_y);
                }
            } else {
                window.should_update_range = true;
            }
        """)
    range_slider.js_on_change('value', range_callback)

    # variable to group data
    x_axis_callback = CustomJS(args=dict(
        source=source,
        x_source=x_count_source,
        o_data=barchart_data,
        x_axis_map=x_axis_map,
        y_axis_map=y_axis_map,
        y_axis=y_axis,
        range_slider=range_slider,
        sort_order=sort_order,
        checkbox=checkbox,
        checkbox_limit_results=checkbox_limit_results,
        fig=p,
        title=p.title),
                               code=SORT_FUNCTION + JS_NUM_RESULTS + """
            title.text = cb_obj.value.concat(" ", y_axis.value);
            var data = o_data[x_axis_map[cb_obj.value]];
            var x = data['x'];
            var y = data['y'];
            var is_ratio = checkbox.active.length > 0;
            if (is_ratio)
            {
                title.text = title.text.concat(" per video");   
            }
            var sorted_data = sortData(data['raw'], sort_order.active, y_axis_map[y_axis.value], is_ratio);
            var new_y = [];
            var new_x = [];
            var final_x = [];
            var final_y = [];
            for (var i = 0; i < x.length; i++) {
                new_x.push(sorted_data[i][0]);
                new_y.push(sorted_data[i][1]);
            }
            if (checkbox_limit_results.active.length > 0) {
                final_x = new_x.slice(0, num_results);
                final_y = new_y.slice(0, num_results);
                window.should_update_range = false;
            } else {
                final_x = new_x;
                final_y = new_y;
                window.should_update_range = true;
            }
            x_source.data['x_count'] = [final_x.length];
            x_source.data['category'] = [cb_obj.value];
            x_source.change.emit();
            source.data['x'] = new_x;
            source.data['y'] = new_y;
            source.change.emit();
            fig.x_range.factors = [];
            fig.x_range.factors = final_x;
            if (new_y && Array.isArray(new_y) && new_y.length) {
                // range init and end cannot have same value
                var range_end = Math.max.apply(Math, new_y);
                if (range_end == 0 || range_end == -Infinity) {
                    range_end = 1;
                }
                range_slider.value = [0, Math.max.apply(Math, final_y)]; 
                range_slider.end = range_end;
                fig.y_range.end = Math.max.apply(Math, final_y);
                fig.change.emit();
            }
        """)

    x_axis.js_on_change('value', x_axis_callback)

    # variable to group data
    y_axis_callback = CustomJS(args=dict(
        source=source,
        x_source=x_count_source,
        o_data=barchart_data,
        x_axis_map=x_axis_map,
        x_axis=x_axis,
        y_axis_map=y_axis_map,
        range_slider=range_slider,
        sort_order=sort_order,
        checkbox=checkbox,
        checkbox_limit_results=checkbox_limit_results,
        fig=p,
        title=p.title),
                               code=SORT_FUNCTION + JS_NUM_RESULTS + """
            title.text = x_axis.value.concat(" ", cb_obj.value);
            var data = o_data[x_axis_map[x_axis.value]];
            var x = data['x'];
            var y = data['y'];
            var is_ratio = checkbox.active.length > 0;
            if (is_ratio)
            {
                title.text = x_axis.value.concat(" ", cb_obj.value, " per video");   
            }
            var sorted_data = sortData(data['raw'], sort_order.active, y_axis_map[cb_obj.value], is_ratio);
            var new_y = [];
            var new_x = [];
            var final_x = [];
            var final_y = [];
            for (var i = 0; i < x.length; i++) {
                new_x.push(sorted_data[i][0]);
                new_y.push(sorted_data[i][1]);
            }
            if (checkbox_limit_results.active.length > 0) {
                final_x = new_x.slice(0, num_results);
                final_y = new_y.slice(0, num_results);
                window.should_update_range = false;
            } else {
                final_x = new_x;
                final_y = new_y;
                window.should_update_range = true;
            }
            x_source.data['x_count'] = [final_x.length];
            x_source.data['category'] = [x_axis.value];
            x_source.change.emit();
            source.data['x'] = new_x;
            source.data['y'] = new_y;
            source.change.emit();
            fig.x_range.factors = [];
            fig.x_range.factors = final_x;
            if (new_y && Array.isArray(new_y) && new_y.length) {
                // range init and end cannot have same value
                var range_end = Math.max.apply(Math, new_y);
                if (range_end == 0 || range_end == -Infinity) {
                    range_end = 1;
                }
                range_slider.value = [0, Math.max.apply(Math, final_y)]; 
                range_slider.end = range_end;
                fig.y_range.end = Math.max.apply(Math, final_y);
            }
        """)

    y_axis.js_on_change('value', y_axis_callback)

    # sort order control
    sort_order_callback = CustomJS(args=dict(
        source=source,
        x_source=x_count_source,
        o_data=barchart_data,
        x_axis_map=x_axis_map,
        x_axis=x_axis,
        y_axis_map=y_axis_map,
        y_axis=y_axis,
        range_slider=range_slider,
        checkbox=checkbox,
        checkbox_limit_results=checkbox_limit_results,
        fig=p),
                                   code=SORT_FUNCTION + JS_NUM_RESULTS + """
            var data = o_data[x_axis_map[x_axis.value]];
            var x = data['x'];
            var y = data['y'];
            // Sort data
            var is_ratio = checkbox.active.length > 0;
            var sorted_data = sortData(data['raw'], cb_obj.active, y_axis_map[y_axis.value], is_ratio);
            var new_y = [];
            var new_x = [];
            var final_x = [];
            var final_y = [];
            // push data if it lies inside range
            for (var i = 0; i < x.length; i++) {
                    if (checkbox_limit_results.active.length <= 0)
                    {
                        if (sorted_data[i][1] >= cb_obj.value[0] && sorted_data[i][1] <= cb_obj.value[1]) 
                        {
                            new_x.push(sorted_data[i][0]);
                            new_y.push(sorted_data[i][1]);
                        }
                    } else {
                        new_x.push(sorted_data[i][0]);
                        new_y.push(sorted_data[i][1]);                        
                    }
            }
            if (checkbox_limit_results.active.length > 0)
            { 
                final_x = new_x.slice(0, 50);
                final_y = new_y.slice(0, 50);
                window.should_update_range = false;
            } else {
                final_x = new_x;
                final_y = new_y;
                window.should_update_range = true;
            }
            x_source.data['x_count'] = [final_x.length];
            x_source.data['category'] = [x_axis.value];
            x_source.change.emit();
            source.data['x'] = new_x;
            source.data['y'] = new_y;
            source.change.emit();
            fig.x_range.factors = [];
            fig.x_range.factors = final_x;
            if (new_y && Array.isArray(new_y) && new_y.length) {
                // range init and end cannot have same value
                var range_end = Math.max.apply(Math, new_y);
                if (range_end == 0 || range_end == -Infinity) {
                    range_end = 1;
                }
                range_slider.value = [0, Math.max.apply(Math, final_y)]; 
                range_slider.end = range_end;
                fig.y_range.end = Math.max.apply(Math, final_y);
                fig.change.emit();
            }
        """)

    sort_order.js_on_change('active', sort_order_callback)

    # Define layout
    inputs = column(*controls, width=320, height=1000)
    inputs.sizing_mode = "fixed"
    l = layout([
        [desc],
        [inputs, p],
    ], sizing_mode="scale_both")

    return l
コード例 #10
0
ファイル: produce.py プロジェクト: rsbyrne/mobility-aus
def bokeh_spacetimepop(
        frm,
        geometry,
        title = '',
        preamble = '',
        varNames = None,
        varNotes = dict(),
        pw = 700,
        ph = 700,
        xZones = dict(),
        ):

    import numpy as np

    import pandas as pd
    df = pd.DataFrame
    idx = pd.IndexSlice
    import geopandas as gpd
    gdf = gpd.GeoDataFrame

    from bokeh.models import ColumnDataSource, HoverTool, Legend, LegendItem, CDSView, IndexFilter
    from bokeh.plotting import figure, show
    from bokeh.io import output_notebook

    #     frm = frm.reset_index().pivot(index = frm.index.names[0], columns = frm.index.names[1])
    frm = frm.copy()
    frm = frm.sort_index()
    #     geometry = geometry.copy()

    from bokeh.models import Div

    title = f'<h1>{title}</h1>'
    title = Div(
        text = title,
        width = pw,
        )
    preamble = Div(
        text = preamble,
        width = pw,
        )

    if varNames is None:
        varNames = frm.columns.sort_values()
        varMetaName = varNames.name
    else:
        varMetaName = 'variable'
    varNames = list(varNames)
    seriesNames = frm.index.levels[1].sort_values()
    seriesMetaName = seriesNames.name
    seriesNames = list(seriesNames)
    dates = [str(int(round(i.to_numpy().astype(int) / 1e6))) for i in frm.index.levels[0]]
    frm.index = frm.index.set_levels(dates, level = 0)
    defaultVar = varNames[0]
    defaultDate = dates[-1]
    pivotFrm = frm.reset_index() \
        .pivot(index = frm.index.names[0], columns = frm.index.names[1]) \
        .sort_index()

    defaultVar = varNames[0]
    defaultDate = dates[-1]

    for key in varNames:
        if not key in varNotes:
            varNotes[key] = ''
        else:
            varNotes[key] = f'<i>{varNotes[key]}</i>'

    varNote = Div(
        text = varNotes[defaultVar],
        width = pw - 120,
        )

    lineSources = {
        key: ColumnDataSource(pivotFrm[key])
            for key in pivotFrm.columns.levels[0]
        }
    lineSource = ColumnDataSource(pivotFrm[defaultVar])
    lineSource.name = defaultVar

    barSources = dict()
    for varName in varNames:
        for index, date in zip(sorted(pivotFrm.index), dates):
            series = pivotFrm.loc[index, varName]
            subFrm = df(dict(
                name = series.index,
                value = series.values,
                height = abs(series.values),
                offset = series.values / 2.
                ))
            barSources[varName + '_' + date] = ColumnDataSource(subFrm)
    barSource = ColumnDataSource(barSources[defaultVar + '_' + defaultDate].data)
    barSource.name = ', '.join([str(defaultVar), str(defaultDate)])

    bounds = geometry.bounds
    minx = np.min(bounds['minx'])
    maxx = np.max(bounds['maxx'])
    miny = np.min(bounds['miny'])
    maxy = np.max(bounds['maxy'])
    aspect = (maxx - minx) / (maxy - miny)
    from shapely.geometry import Polygon
    import itertools
    corners = list(itertools.product(geometry.total_bounds[::2], geometry.total_bounds[1::2]))
    allPoly = Polygon([corners[0], corners[1], corners[3], corners[2]])
    allPoly = allPoly.centroid.buffer(np.sqrt(allPoly.area) / 1e6)
    for name in frm.index.levels[1]:
        if not name in geometry.index:
            geometry[name] = allPoly
    geometry = geometry.simplify(np.sqrt(geometry.area).min() * 10. ** 3.5)
    geoFrm = frm.reset_index().pivot(index = frm.index.names[1], columns = frm.index.names[0])
    geoFrm.columns = geoFrm.columns.map('_'.join).str.strip('_')
    geoFrm['geometry'] = geometry
    geoFrm = gdf(geoFrm)
    from bokeh.models import GeoJSONDataSource
    geoJSON = geoFrm.reset_index().to_json()
    geoSource = GeoJSONDataSource(geojson = geoJSON)
    mins = {n: frm[n].min() for n in varNames}
    maxs = {n: frm[n].max() for n in varNames}

    xName = frm.index.names[0]

    lineFig = figure(
        x_axis_type = 'datetime',
        y_range = (mins[defaultVar], maxs[defaultVar]),
        plot_height = int((ph - 100) * 1. / 3.),
        plot_width = pw,
        toolbar_location = 'left',
        tools = 'save, xpan, box_zoom, reset, xwheel_zoom',
        active_scroll = 'auto',
    #         title = title,
        )

    barFig = figure(
        x_range = seriesNames,
        plot_height = int((ph - 100) * 1. / 2.),
        plot_width = pw,
    #         title = "Scores on my birthday",
        toolbar_location = None,
        tools = ""
        )
    barFig.xgrid.grid_line_color = None
    barFig.xaxis.major_label_orientation = 'vertical'

    mapFig = figure(
        plot_width = pw - 20,
        plot_height = int(round((pw - 20) / aspect)),
        toolbar_location = 'right',
        tools = 'pan, wheel_zoom, reset',
        background_fill_color = "lightgrey"
        )
    mapFig.xgrid.grid_line_color = None
    mapFig.ygrid.grid_line_color = None

    from matplotlib.pyplot import get_cmap
    from matplotlib.colors import rgb2hex
    cmap = get_cmap('nipy_spectral')
    cs = [rgb2hex(cmap(i / len(seriesNames), alpha = 0.5)) for i in range(len(seriesNames))]

    lines = []

    for seriesName, colour in zip(seriesNames, cs):

        line = lineFig.line(
            xName,
            seriesName,
            source = lineSource,
            color = colour,
            alpha = 0.8,
            muted_color = 'gray',
            muted_alpha = 0.3,
            muted = True,
            line_width = 2,
    #             legend_label = seriesName,
            )

        from bokeh.models import HoverTool
        lineFig.add_tools(HoverTool(
            renderers = [
                line,
                ],
            tooltips = [
                (seriesMetaName.capitalize(), seriesName),
                (xName.capitalize(), f'@{xName}' + '{%Y-%m-%d}'),
                ('Value', f'@{{{seriesName}}}'),
                ],
            formatters = {
                f'@{xName}': 'datetime',
                seriesName: 'numeral',
                },
            toggleable = False
            ))

        lines.append(line)

    bars = []
    for i, (seriesName, colour) in enumerate(zip(seriesNames, cs)):
        view = CDSView(source = barSource, filters = [IndexFilter([i,]),])
        bar = barFig.rect(
            source = barSource,
            view = view,
            x = 'name',
            y = 'offset',
            height = 'height',
            width = 0.9,
            color = colour,
            muted_color = 'gray',
            muted_alpha = 0.3,
            muted = True,
            )
        bars.append(bar)

    from bokeh.palettes import Viridis256
    from bokeh.models import LinearColorMapper, ColorBar
    palette = Viridis256
    mapColourMapper = LinearColorMapper(
        palette = palette,
        low = frm.loc[idx[defaultDate, :], defaultVar].min(),
        high = frm.loc[idx[defaultDate, :], defaultVar].max(),
        )
    mapColourBar = ColorBar(
        color_mapper = mapColourMapper, 
        label_standoff = 8,
        width = 30,
        height = int(round(mapFig.plot_height * 0.9)),
        border_line_color = None,
        location = (0, 0), 
        orientation = 'vertical',
        )
    mapFig.add_layout(mapColourBar, 'left')

    patches = []
    for i, seriesName in enumerate(seriesNames):
        view = CDSView(source = geoSource, filters = [IndexFilter([i,]),])
        patch = mapFig.patches(
            'xs',
            'ys',
            source = geoSource,
            view = view,
            fill_color = dict(
                field = '_'.join([defaultVar, defaultDate]),
                transform = mapColourMapper,
                ),
            line_color = 'grey', 
            line_width = 0.25,
            fill_alpha = 0.,
            name = '_'.join([defaultVar, defaultDate])
            )
        patches.append(patch)

    from bokeh.models import HoverTool
    mapHover = HoverTool(
        renderers = patches,
        tooltips = [
            (seriesMetaName.capitalize(), f'@{seriesMetaName}'),
            ('Value', '@$name'),
            ]
        )
    mapFig.add_tools(mapHover)

    from bokeh.models import BoxAnnotation
    from bokeh.models import Label
    for name, zone in xZones.items():
        convD = lambda x: int(round(pd.Timestamp(x).to_numpy().astype(int) / 1e6))
        left, right = [None if val is None else convD(val) for val in zone]
        zone = BoxAnnotation(
            left = left,
            right = right,
            fill_alpha = 0.1,
            fill_color = 'gray',
            )
        zoneLabel = Label(
            text = name + ' (end)' if left is None else name,
            text_font_size = '8pt',
            x = right if left is None else left,
            y = 10,
            x_units = 'data',
            y_units = 'screen',
            angle = -90 if left is None else 90,
            angle_units = 'deg',
            x_offset = -10 if left is None else 10,
            y_offset = 5 * (len(name) + 6) if left is None else 0
            )
        lineFig.add_layout(zone)
        lineFig.add_layout(zoneLabel)

    from bokeh.models import Span
    span = Span(
        location = int(defaultDate),
        dimension = 'height',
        line_color = 'red',
    #         line_dash = 'dashed',
        line_width = 1
        )
    lineFig.add_layout(span)

    from bokeh.models.widgets import DateSlider
    slider = DateSlider(
        title = 'Date',
        start = int(dates[0]),
        end = int(dates[-1]),
        step = int(8.64 * 1e7), # days
        value = int(defaultDate),
        width = pw - 60,
        align = 'end'
        )

    from bokeh.models.widgets import Select
    select = Select(
        title = "Choose data:",
        options = varNames,
        value = defaultVar,
        width = 100,
        )

    from bokeh.models import CheckboxGroup
    checkboxes = CheckboxGroup(
        labels = seriesNames,
        active = [],
        )
    checkboxAll = CheckboxGroup(
        labels = ['All',],
        active = [],
        )

    from bokeh.models import CustomJS
    callback = CustomJS(
        args = dict(
            y_range = lineFig.y_range,
            lineSources = lineSources,
            lineSource = lineSource,
            barSources = barSources,
            barSource = barSource,
            bars = bars,
            lines = lines,
            patches = patches,
            select = select,
            slider = slider,
            span = span,
            checkboxes = checkboxes,
            varNote = varNote,
            varNotes = varNotes,
            geoSource = geoSource,
            mapColourMapper = mapColourMapper,
            mins = mins,
            maxs = maxs,
            ),
        code = """
            lineSource.data = lineSources[select.value].data
            lineSource.name = select.value
            lineSource.change.emit()
            span.location = slider.value
            span.change.emit()
            y_range.setv({'start': mins[select.value], 'end': maxs[select.value]})
            varNote.text = varNotes[select.value]
            varNote.change.emit()
            const barChoice = select.value + '_' + slider.value
            barSource.data = barSources[barChoice].data
            barSource.name = select.value.toString() + ', ' + slider.value.toString()
            barSource.change.emit()
            for (let i = 0; i < lines.length; i++){
                let checked = checkboxes.active.includes(i)
                lines[i].muted = !(checked)
                bars[i].muted = !(checked)
                var alpha = checked ? 1 : 0;
                patches[i].glyph.fill_alpha = alpha
            }
            const newCol = select.value + '_' + slider.value
            for (let i = 0; i < lines.length; i++){
                patches[i].glyph.fill_color['field'] = newCol
                patches[i].name = newCol
            }
            mapColourMapper.low = mins[select.value]
            mapColourMapper.high = maxs[select.value]
            geoSource.change.emit()
            """,
        )

    allCheckCallback = CustomJS(
        args = dict(
            lines = lines,
            checkboxes = checkboxes,
            checkboxAll = checkboxAll,
            callback = callback
            ),
        code = """
            checkboxes.active.length = 0
            if (checkboxAll.active.length > 0) {
                let arr = []
                for (let i = 0; i < lines.length; i++){
                    arr.push(i)
                    }
                checkboxes.active.push(...arr)
            }
            checkboxes.change.emit()
            callback.execute()
            """
        )

    slider.js_on_change('value', callback)
    select.js_on_change('value', callback)
    checkboxes.js_on_change('active', callback)
    checkboxAll.js_on_change('active', allCheckCallback)

    from bokeh.layouts import column, row
    layout = column(
        title,
        preamble,
        row(select, varNote),
        row(column(lineFig, slider, barFig), column(checkboxes, checkboxAll)),
        mapFig
        )

    return layout
コード例 #11
0
ファイル: buttons.py プロジェクト: nimishbongale/bokeh
menu = [("Item 1", "item_1_value"), ("Item 2", "item_2_value"), None, ("Item 3", "item_3_value")]

dropdown = Dropdown(label="Dropdown button", button_type="warning", menu=menu)
dropdown.js_on_event("button_click", CustomJS(code="console.log('dropdown: click ' + this.toString())"))
dropdown.js_on_event("menu_item_click", CustomJS(code="console.log('dropdown: ' + this.item, this.toString())"))

dropdown_disabled = Dropdown(label="Dropdown button (disabled)", button_type="warning", disabled=True, menu=menu)
dropdown_disabled.js_on_event("button_click", CustomJS(code="console.log('dropdown(disabled): click ' + this.toString())"))
dropdown_disabled.js_on_event("menu_item_click", CustomJS(code="console.log('dropdown(disabled): ' + this.item, this.toString())"))

dropdown_split = Dropdown(label="Split button", split=True, button_type="danger", menu=menu)
dropdown_split.js_on_event("button_click", CustomJS(code="console.log('dropdown(split): click ' + this.toString())"))
dropdown_split.js_on_event("menu_item_click", CustomJS(code="console.log('dropdown(split): ' + this.item, this.toString())"))

checkbox_group = CheckboxGroup(labels=["Option 1", "Option 2", "Option 3"], active=[0, 1])
checkbox_group.js_on_change('active', CustomJS(code="console.log('checkbox_group: active=' + this.active, this.toString())"))

radio_group = RadioGroup(labels=["Option 1", "Option 2", "Option 3"], active=0)
radio_group.js_on_change('active', CustomJS(code="console.log('radio_group: active=' + this.active, this.toString())"))

checkbox_button_group = CheckboxButtonGroup(labels=["Option 1", "Option 2", "Option 3"], active=[0, 1])
checkbox_button_group.js_on_event("button_click", CustomJS(code="console.log('checkbox_button_group: active=' + this.origin.active, this.toString())"))

radio_button_group = RadioButtonGroup(labels=["Option 1", "Option 2", "Option 3"], active=0)
radio_button_group.js_on_event("button_click", CustomJS(code="console.log('radio_button_group: active=' + this.origin.active, this.toString())"))

widget_box = Column(children=[
    button, button_disabled,
    toggle_inactive, toggle_active,
    dropdown, dropdown_disabled, dropdown_split,
    checkbox_group, radio_group,
コード例 #12
0
def index():
    form = CategoriseButton()
    cnx = app.config.get('SQLALCHEMY_DATABASE_URI')

    if form.validate_on_submit():
        rules = {}
        categories = [
            x[0]
            for x in db.session.query(CategoryRule.category.distinct()).all()
        ]
        for cat in categories:
            cat_rules = CategoryRule.query.filter_by(category=cat).all()
            rules[cat] = []
            for rule in cat_rules:
                rules[cat].append({
                    'str': rule.string_match,
                    'date': rule.date_match,
                    'exact': rule.exact_rule,
                    'max_amt': rule.max_amount
                })

        df_transactions = pd.read_sql_query(
            '''
            SELECT 
                *
            FROM 
                "transaction"''', cnx)

        df_transactions['transaction_date'] = pd.to_datetime(
            df_transactions['transaction_date'])
        df_transactions['transaction_date'] = df_transactions[
            'transaction_date'].dt.date

        def categorise(name, date, amount, categories):
            for category in categories:
                for rule in categories[category]:
                    if rule['exact'] == True:
                        if (rule['max_amt']
                                == None) or (amount < rule['max_amt']):
                            if rule['date'] == None and rule['str'] == name:
                                return category
                            if rule['date'] == date and rule['str'] == name:
                                return category
                    else:
                        if (rule['max_amt']
                                == None) or (amount < rule['max_amt']):
                            if rule['date'] == None and rule['str'] in name:
                                return category
                            if rule['date'] == date and rule['str'] in name:
                                return category

        df_transactions['category'] = df_transactions.apply(
            lambda x: categorise(x['description'], x['transaction_date'], x[
                'debits'], rules),
            axis=1)

        transactions = Transaction.query.all()
        for i, t in enumerate(transactions):
            t.category = df_transactions['category'].iloc[i]

        db.session.commit()

    ###############################################
    # Create the plot
    ###############################################

    df_transactions = pd.read_sql_query(
        '''
            SELECT 
                *
            FROM 
                "transaction"
            ORDER BY
                transaction_date DESC,
                description ASC''', cnx)

    df_transactions['transaction_date'] = pd.to_datetime(
        df_transactions['transaction_date'])
    df_transactions = df_transactions[
        df_transactions['transaction_date'] >= '2019-01-01']
    df_transactions[
        'MonthBegin'] = df_transactions['transaction_date'] - pd.Timedelta(
            '1d') * (df_transactions['transaction_date'].dt.day - 1)
    df = df_transactions.groupby(['MonthBegin', 'category']).agg({
        'debits':
        'sum',
        'credits':
        'sum'
    }).reset_index()
    df['NetSpend'] = df['debits'] - df['credits']
    df['left'] = df['MonthBegin'] + pd.Timedelta(days=8)
    df['right'] = df['MonthBegin'] + pd.Timedelta(days=22)
    df['top'] = df['NetSpend']
    df['bottom'] = 0.0

    categories = sorted(df['category'].unique().tolist())

    for i, cat in enumerate(categories):
        idx = i
        while idx > 19:
            idx -= 20
        df.loc[df['category'] == cat, 'colour'] = d3['Category20'][20][idx]

    df = df.set_index(['MonthBegin'])
    df = df.drop(['debits', 'credits'], axis=1)
    df.sort_index(inplace=True)
    source = ColumnDataSource(data=df)

    category = 'Groceries'

    df_filtered = df.copy()
    df_filtered.loc[df_filtered['category'] != category] = np.nan
    source_filtered = ColumnDataSource(data=df_filtered)

    plot = make_plot(source_filtered)

    callback = CustomJS(args=dict(source=source,
                                  source_filtered=source_filtered),
                        code="""
        var chosen_idxs = cb_obj.active;
        var labels = cb_obj.labels;
        var month_sum = {};
        var category_bottom = {};
        var chosen_categories = [];
        for (var i = 0; i < chosen_idxs.length; i++) {
            chosen_categories.push(labels[chosen_idxs[i]]);
        }
        for (var i = 0; i < chosen_categories.length; i++) {
            for (var j = 0; j < source.data['category'].length; j++) {
                if (source.data['category'][j]==chosen_categories[i]) {
                    if (!month_sum.hasOwnProperty(source.data['MonthBegin'][j].toString())) {
                        month_sum[source.data['MonthBegin'][j].toString()] = 0.0;
                    }
                    if (!category_bottom.hasOwnProperty(i)) {
                        category_bottom[i] = {}
                    }
                    category_bottom[i][source.data['MonthBegin'][j].toString()] = JSON.parse(JSON.stringify(month_sum[source.data['MonthBegin'][j].toString()]));
                    month_sum[source.data['MonthBegin'][j].toString()] += Math.abs(source.data['NetSpend'][j]);
                }
            }
        } 
        for (var i = 0; i < source.data['category'].length; i++) {
            if (chosen_categories.includes(source.data['category'][i])) {
                source_filtered.data['category'][i] = source.data['category'][i];
                source_filtered.data['MonthBegin'][i] = source.data['MonthBegin'][i];
                source_filtered.data['NetSpend'][i] = source.data['NetSpend'][i];
                source_filtered.data['left'][i] = source.data['left'][i];
                source_filtered.data['right'][i] = source.data['right'][i];
                if (category_bottom[chosen_categories.indexOf(source.data['category'][i])].hasOwnProperty(source.data['MonthBegin'][i].toString())) {
                    source_filtered.data['bottom'][i] = category_bottom[chosen_categories.indexOf(source.data['category'][i])][source.data['MonthBegin'][i].toString()];
                } else {
                    source_filtered.data['bottom'][i] = 0.0;
                }
                source_filtered.data['top'][i] = source_filtered.data['bottom'][i] + Math.abs(source_filtered.data['NetSpend'][i]);
                if (source_filtered.data['NetSpend'][i] < 0.0) {
                    source_filtered.data['colour'][i] = '#ffffff';
                } else {
                    source_filtered.data['colour'][i] = source.data['colour'][i];
                }
            } else {
                source_filtered.data['category'][i] = undefined;
                source_filtered.data['MonthBegin'][i] = undefined;
                source_filtered.data['NetSpend'][i] = undefined;
                source_filtered.data['left'][i] = undefined;
                source_filtered.data['right'][i] = undefined;
                source_filtered.data['bottom'][i] = undefined;
                source_filtered.data['top'][i] = undefined;
                source_filtered.data['colour'][i] = undefined;
            } 
        }
        source_filtered.change.emit();
    """)

    category_select = CheckboxGroup(labels=categories,
                                    active=[categories.index(category)])

    category_select.js_on_change('active', callback)
    plotgrid = grid([plot, category_select], ncols=2)

    ###############################################

    # Embed plot into HTML via Flask Render
    script, div = components(plotgrid)

    df_transactions = pd.read_sql_query(
        '''
            SELECT 
                *
            FROM 
                "transaction"
            WHERE
                category IS NULL
            ORDER BY
                transaction_date DESC,
                description ASC''', cnx)

    return render_template('index.html',
                           title='Home',
                           table=df_transactions,
                           form=form,
                           script=script,
                           div=div)