From cc5499908c9f585b8a3f7e5f899a2c2a9860de66 Mon Sep 17 00:00:00 2001 From: Gustavo Homem Date: Sun, 12 Sep 2021 23:13:29 +0100 Subject: [PATCH 1/4] abstract layout producing code --- main.py | 128 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 81 insertions(+), 47 deletions(-) diff --git a/main.py b/main.py index 8c3badf..8ffdb8e 100644 --- a/main.py +++ b/main.py @@ -414,6 +414,84 @@ def get_y_limits ( source, date_i, date_f ): # return the minimum of the minimuns for the interval, same for maximum return min(y_min_list), max(y_max_list) +def make_layouts( horizontal = True): + + control_spacer = Spacer(width=10, height=10, width_policy='auto', height_policy='fixed') + controls1 = row (date_slider1, control_spacer, clines_switch, name="section1_controls" ) + + # first + + if horizontal: + grid = gridplot([ + [ plot1, plot3, plot5, plot7 ], + [ plot2, plot4, plot6, plot8 ] ], + plot_width=PLOT_WIDTH, plot_height=PLOT_HEIGHT, toolbar_location=None, sizing_mode='scale_width') + else: + grid = gridplot([ + [ plot1, plot3 ], + [ plot5, plot7 ], + [ plot2, plot4 ], + [ plot6, plot8 ] ], + plot_width=PLOT_WIDTH, plot_height=PLOT_HEIGHT, toolbar_location=None, sizing_mode='scale_width') + + layout1 = layout(grid, name='section1', sizing_mode='scale_width') + + # second + + controls2 = row (date_slider2, name="section2_controls" ) + + if horizontal: + grid2 = gridplot ([ + [plot9, plot11], + [plot10, plot12] ], + plot_width=PLOT_WIDTH, plot_height=PLOT_HEIGHT2, toolbar_location=None, sizing_mode='scale_width') + else: + grid2 = gridplot ([ + [plot9 ], + [plot11], + [plot10], + [plot12] ], + plot_width=PLOT_WIDTH, plot_height=PLOT_HEIGHT2, toolbar_location=None, sizing_mode='scale_width') + + layout2 = layout( grid2, name='section2', sizing_mode='scale_width') + + # third + + # we create plot identical to plot1 (incidence), using the existing data source + plot1_copy = make_plot ('incidence', PLOT1_TITLE, days, 'datetime') + plot1_copy.line('x','y', source=source_plot1, line_width=PLOT_LINE_WIDTH, line_alpha=PLOT_LINE_ALPHA, line_color=PLOT_LINE_COLOR, ) + set_plot_details(plot1_copy, 'Date', 'Count', '@x{%F}', '@y{0.00}', 'vline', False, False) + set_plot_date_details(plot1_copy, source_plot1) + + # but we change the range + # we can't do this on a directy copy of plot1, because it is shallow + plot1_copy.x_range.start = pd.to_datetime(map_date_i) + plot1_copy.x_range.end = pd.to_datetime(map_date_f) + + notes = Div(text=TEXT_NOTES, width=TEXT_WIDTH) + + # now the layout + + if horizontal: + slider_spacer = Spacer(width=30, height=50, width_policy='auto', height_policy='fixed') + + column_section3_map = column(plot_map) + column_section3_others = column( [plot1_copy, row( [slider_spacer, date_slider_map] ), row( [ slider_spacer, notes] ) ] ) + + row_section3 = row ( column_section3_map , column_section3_others ) + layout3 = layout( row_section3, name='section3') + else: + slider_spacer = Spacer(width=30, height=50, width_policy='auto', height_policy='fixed') + + column_section3_map = column( [plot_map, row( [slider_spacer, date_slider_map] ), row( [ slider_spacer, notes] ), plot1_copy ] ) + + layout3 = layout( column_section3_map, name='section3') + + + return layout1, layout2, layout3, controls1, controls2 + + + # main curdoc().title = PAGE_TITLE @@ -657,60 +735,16 @@ def get_y_limits ( source, date_i, date_f ): # the layout name is added here then invoked from the HTML template # all roots added here must be invoked on the HTML -# section 1 - -control_spacer = Spacer(width=10, height=10, width_policy='auto', height_policy='fixed') +# by default layouts are created assuming we have enough width for the ideal visualization mode +layout1, layout2, layout3, controls1, controls2 = make_layouts(True) -controls1 = row (date_slider1, control_spacer, clines_switch, name="section1_controls" ) +# section 1 curdoc().add_root(controls1) - -grid = gridplot([ - [ plot1, plot3, plot5, plot7 ], - [ plot2, plot4, plot6, plot8 ] ], - plot_width=PLOT_WIDTH, plot_height=PLOT_HEIGHT, toolbar_location=None, sizing_mode='scale_width') - -layout1 = layout( grid, name='section1', sizing_mode='scale_width') - curdoc().add_root(layout1) # section 2 - -controls2 = row (date_slider2, name="section2_controls" ) curdoc().add_root(controls2) - -grid2 = gridplot ([ - [plot9, plot11], - [plot10, plot12] ], - plot_width=PLOT_WIDTH, plot_height=PLOT_HEIGHT2, toolbar_location=None, sizing_mode='scale_width') - -layout2 = layout( grid2, name='section2', sizing_mode='scale_width') - curdoc().add_root(layout2) # section 3 - -# we create plot identical to plot1 (incidence), using the existing data source -plot1_copy = make_plot ('incidence', PLOT1_TITLE, days, 'datetime') -plot1_copy.line('x','y', source=source_plot1, line_width=PLOT_LINE_WIDTH, line_alpha=PLOT_LINE_ALPHA, line_color=PLOT_LINE_COLOR, ) -set_plot_details(plot1_copy, 'Date', 'Count', '@x{%F}', '@y{0.00}', 'vline', False, False) -set_plot_date_details(plot1_copy, source_plot1) - -# but we change the range -# we can't do this on a directy copy of plot1, because it is shallow -plot1_copy.x_range.start = pd.to_datetime(map_date_i) -plot1_copy.x_range.end = pd.to_datetime(map_date_f) - -notes = Div(text=TEXT_NOTES, width=TEXT_WIDTH) - -# now the layout - -slider_spacer = Spacer(width=30, height=50, width_policy='auto', height_policy='fixed') - -column_section3_map = column(plot_map) -column_section3_others = column( [plot1_copy, row( [slider_spacer, date_slider_map] ), row( [ slider_spacer, notes] ) ] ) - -row_section3 = row ( column_section3_map , column_section3_others ) - -layout3 = layout( row_section3, name='section3') - curdoc().add_root(layout3) From f1b335d6a45ac8b36f78a8db72da011956710e04 Mon Sep 17 00:00:00 2001 From: Gustavo Homem Date: Sun, 12 Sep 2021 22:26:44 +0000 Subject: [PATCH 2/4] improve vertical layout --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 8ffdb8e..1b3bc0d 100644 --- a/main.py +++ b/main.py @@ -448,8 +448,8 @@ def make_layouts( horizontal = True): else: grid2 = gridplot ([ [plot9 ], - [plot11], [plot10], + [plot11], [plot12] ], plot_width=PLOT_WIDTH, plot_height=PLOT_HEIGHT2, toolbar_location=None, sizing_mode='scale_width') From 29afe1e237f3e297f6b12fc5a0e0bb16ec48a149 Mon Sep 17 00:00:00 2001 From: Gustavo Homem Date: Mon, 13 Sep 2021 00:19:28 +0000 Subject: [PATCH 3/4] ongoing work on code reflow --- main.py | 175 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 128 insertions(+), 47 deletions(-) diff --git a/main.py b/main.py index 1b3bc0d..0b38493 100644 --- a/main.py +++ b/main.py @@ -9,9 +9,10 @@ from datetime import datetime from bokeh.io import curdoc from bokeh.layouts import layout,gridplot, column, row -from bokeh.models import Button, Toggle, CategoricalColorMapper, ColumnDataSource, HoverTool, Label, SingleIntervalTicker, Slider, Spacer, GlyphRenderer, DatetimeTickFormatter, DateRangeSlider, DataRange1d, Range1d, DateSlider, LinearColorMapper, Div +from bokeh.models import Button, Toggle, CategoricalColorMapper, ColumnDataSource, HoverTool, Label, SingleIntervalTicker, Slider, Spacer, GlyphRenderer, DatetimeTickFormatter, DateRangeSlider, DataRange1d, Range1d, DateSlider, LinearColorMapper, Div, CustomJS from bokeh.palettes import Inferno256, Magma256, Turbo256, Plasma256, Cividis256, Viridis256, OrRd from bokeh.plotting import figure +from bokeh.events import DocumentReady from .data import process_data, process_data_counties @@ -94,6 +95,16 @@ TEXT_NOTES ='Important: use the mouse for the initial selection and the cursors for fine tuning. The plot takes ~ 1s to update after each date selection.' +# layout related variables +MIN_HORIZONTAL_WIDTH = 1400 + +window_size_data_source = ColumnDataSource( data = { 'width' :[0] , 'height': [0] } ) + +# by default we assume the layout is horizontal +current_horizontal = True + +# functions + def make_age_labels ( nr_labels ): labels = [] @@ -314,7 +325,7 @@ def reverse_palette ( original_palette ): return palette -# callbacks +#### callbacks #### # for the toggle button action def update_state(new): @@ -395,6 +406,70 @@ def update_map(attr, old, new): # we refresh the tooltips using the Colormap column as the list plot_map.hover.tooltips = [ ('County', '@NAME_2'), ('Incidence', '@Colormap'), ] +# after document load +def on_document_ready(evt): + # here we change some property on the fake_toggle widget + print('document is ready') + fake_slider.value = ( date_i, date_f ) + +# this callbacks takes action on the server side upon dimensions change +def on_dimensions_change(attr, old, new): + + width = new['width'][0] + height = new['height'][0] + + global current_horizontal + + print('current dimensions', width, height) + + if width >= height and width > MIN_HORIZONTAL_WIDTH: + print('orientation is horizontal') + horizontal = True + else: + print('orientation is vertical') + horizontal = False + + if (current_horizontal != horizontal): + print ('redoing layouts') + + curdoc().clear() + + curdoc().add_root(controls1) + + if horizontal: + curdoc().add_root(layout1_h) + else: + curdoc().add_root(layout1_v) + + curdoc().add_root(controls2) + + if horizontal: + curdoc().add_root(layout2_h) + curdoc().add_root(layout3_h) + else: + curdoc().add_root(layout2_v) + curdoc().add_root(layout3_v) + + # store the horizontalness state + current_horizontal = horizontal + +dimensions_callback = CustomJS( args=dict(ds=window_size_data_source), code=""" +var width, height; +var new_data = {}; +height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; +width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; +console.log("Javascript callback", height, width); + +// needs to be a list, otherwise we have a server side error +new_data['height'] = [height]; +new_data['width' ] = [width]; +ds.data = new_data; +ds.change.emit(); + +""") + +#### end of call backs #### + def get_y_limits ( source, date_i, date_f ): # calculate indexes in the y data @@ -414,46 +489,40 @@ def get_y_limits ( source, date_i, date_f ): # return the minimum of the minimuns for the interval, same for maximum return min(y_min_list), max(y_max_list) -def make_layouts( horizontal = True): +def make_layouts( ): control_spacer = Spacer(width=10, height=10, width_policy='auto', height_policy='fixed') - controls1 = row (date_slider1, control_spacer, clines_switch, name="section1_controls" ) + controls1 = row (date_slider1, control_spacer, fake_slider, clines_switch, name="section1_controls" ) # first - if horizontal: - grid = gridplot([ - [ plot1, plot3, plot5, plot7 ], - [ plot2, plot4, plot6, plot8 ] ], - plot_width=PLOT_WIDTH, plot_height=PLOT_HEIGHT, toolbar_location=None, sizing_mode='scale_width') - else: - grid = gridplot([ - [ plot1, plot3 ], - [ plot5, plot7 ], - [ plot2, plot4 ], - [ plot6, plot8 ] ], - plot_width=PLOT_WIDTH, plot_height=PLOT_HEIGHT, toolbar_location=None, sizing_mode='scale_width') + grid_h = gridplot([ + [ plot1, plot3, plot5, plot7 ], + [ plot2, plot4, plot6, plot8 ] ], + plot_width=PLOT_WIDTH, plot_height=PLOT_HEIGHT, toolbar_location=None, sizing_mode='scale_width') - layout1 = layout(grid, name='section1', sizing_mode='scale_width') + grid_v = gridplot([ + [ plot1, plot3 ], + [ plot5, plot7 ], + [ plot2, plot4 ], + [ plot6, plot8 ] ], + plot_width=PLOT_WIDTH, plot_height=PLOT_HEIGHT, toolbar_location=None, sizing_mode='scale_width') # second controls2 = row (date_slider2, name="section2_controls" ) - if horizontal: - grid2 = gridplot ([ - [plot9, plot11], - [plot10, plot12] ], - plot_width=PLOT_WIDTH, plot_height=PLOT_HEIGHT2, toolbar_location=None, sizing_mode='scale_width') - else: - grid2 = gridplot ([ - [plot9 ], - [plot10], - [plot11], - [plot12] ], - plot_width=PLOT_WIDTH, plot_height=PLOT_HEIGHT2, toolbar_location=None, sizing_mode='scale_width') + grid2_h = gridplot ([ + [plot9, plot11], + [plot10, plot12] ], + plot_width=PLOT_WIDTH, plot_height=PLOT_HEIGHT2, toolbar_location=None, sizing_mode='scale_width') - layout2 = layout( grid2, name='section2', sizing_mode='scale_width') + grid2_v = gridplot ([ + [plot9 ], + [plot10], + [plot11], + [plot12] ], + plot_width=PLOT_WIDTH, plot_height=PLOT_HEIGHT2, toolbar_location=None, sizing_mode='scale_width') # third @@ -472,31 +541,30 @@ def make_layouts( horizontal = True): # now the layout - if horizontal: - slider_spacer = Spacer(width=30, height=50, width_policy='auto', height_policy='fixed') + slider_spacer = Spacer(width=30, height=50, width_policy='auto', height_policy='fixed') - column_section3_map = column(plot_map) - column_section3_others = column( [plot1_copy, row( [slider_spacer, date_slider_map] ), row( [ slider_spacer, notes] ) ] ) + layout1_h = layout(grid_h, name='section1', sizing_mode='scale_width') + layout2_h = layout(grid2_h, name='section2', sizing_mode='scale_width') - row_section3 = row ( column_section3_map , column_section3_others ) - layout3 = layout( row_section3, name='section3') - else: - slider_spacer = Spacer(width=30, height=50, width_policy='auto', height_policy='fixed') - - column_section3_map = column( [plot_map, row( [slider_spacer, date_slider_map] ), row( [ slider_spacer, notes] ), plot1_copy ] ) + column_section3_map = column(plot_map) + column_section3_others = column( [plot1_copy, row( [slider_spacer, date_slider_map] ), row( [ slider_spacer, notes] ) ] ) - layout3 = layout( column_section3_map, name='section3') + row_section3 = row ( column_section3_map , column_section3_others ) + layout3_h = layout( row_section3, name='section3') + layout1_v = layout(grid_v, name='section1', sizing_mode='scale_width') + layout2_v = layout(grid2_v, name='section2', sizing_mode='scale_width') - return layout1, layout2, layout3, controls1, controls2 + column_section3_map = column( [plot_map, row( [slider_spacer, date_slider_map] ), row( [ slider_spacer, notes] ), plot1_copy ] ) + layout3_v = layout( column_section3_map, name='section3') + return layout1_h, layout2_h, layout3_h, layout1_v, layout2_v, layout3_v, controls1, controls2 # main curdoc().title = PAGE_TITLE - # fetch data from files # regular plots data @@ -732,19 +800,32 @@ def make_layouts( horizontal = True): #### Plot layout section ### +## handling different layout orientations + +# register on_document_ready callback +curdoc().on_event(DocumentReady, on_document_ready) + +fake_slider = DateRangeSlider(title="Fake Range: ", start=date_i, end=date_f, value=( date_i, date_f ), step=1) +fake_slider.js_on_change('value', dimensions_callback) + +# register callback to be called upon JS callback executions +window_size_data_source.on_change('data', on_dimensions_change) + # the layout name is added here then invoked from the HTML template # all roots added here must be invoked on the HTML +layout1_h, layout2_h, layout3_h, layout1_v, layout2_v, layout3_v, controls1, controls2 = make_layouts() + # by default layouts are created assuming we have enough width for the ideal visualization mode -layout1, layout2, layout3, controls1, controls2 = make_layouts(True) +# that is, we start with horizontal layouts # section 1 curdoc().add_root(controls1) -curdoc().add_root(layout1) +curdoc().add_root(layout1_h) # section 2 curdoc().add_root(controls2) -curdoc().add_root(layout2) +curdoc().add_root(layout2_h) # section 3 -curdoc().add_root(layout3) +curdoc().add_root(layout3_h) From fecda603a9993ca954f66dfb098c3091ffa5bcd7 Mon Sep 17 00:00:00 2001 From: Gustavo Homem Date: Mon, 13 Sep 2021 00:32:01 +0000 Subject: [PATCH 4/4] ongoing work on code reflow --- main.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index 0b38493..12ad40c 100644 --- a/main.py +++ b/main.py @@ -409,7 +409,10 @@ def update_map(attr, old, new): # after document load def on_document_ready(evt): # here we change some property on the fake_toggle widget - print('document is ready') + print('document is ready, refreshing fake widget') + + # this forces a change on the fake slider, which then invokes the JS callback + fake_slider.value = ( date_i, date_i ) fake_slider.value = ( date_i, date_f ) # this callbacks takes action on the server side upon dimensions change @@ -492,8 +495,12 @@ def get_y_limits ( source, date_i, date_f ): def make_layouts( ): control_spacer = Spacer(width=10, height=10, width_policy='auto', height_policy='fixed') + + # use this line for debugging with the fake slider controls1 = row (date_slider1, control_spacer, fake_slider, clines_switch, name="section1_controls" ) + fake_slider.visible = False + # first grid_h = gridplot([