Plotly dash grid layout using dbc.Col & dbc.Row without the use of the bootstraps themes - layout

Is it possible to use the dbc.Col and dbc.Row functions to set up the grid layout without using the bootstrap themes ?
When adding for example the codepen.io css stylesheet, even when specifying the rows and columns, it displays everything stacked vertically.
The reason for not using the dbc themes is that I would like to personalise an external stylesheet and use that.
If there is no way around it, is it possible to override the dbc themes ? or modify them ?
import dash
from dash import html
from dash import dcc
import dash_bootstrap_components
app = dash.Dash(__name__, external_stylesheets=['https://codepen.io/chriddyp/pen/bWLwgP.css'])
app.layout = dbc.Container([
dbc.Row([
dbc.Col(html.H5('row 1, col 1')),
dbc.Col(html.H5('row 1, col 2'))
]),
dbc.Row([
dbc.Col(html.H5('row 2, col 1')),
dbc.Col(html.H5('row 2, col 2'))
])
], fluid=True)
if __name__=='__main__':
app.run_server(debug=True)
it displays it as such :
row 1, col 1
row 1, col 2
row 2, col 1
row 2, col 2
Thank you !

dbc.themes.BOOTSTRAP is just a link to a css stylesheet
>>> print(dbc.themes.BOOTSTRAP)
https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css
So instead of passing dbc.themes.BOOTSTRAP you could just pass
"https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap-grid.min.css"
to external_stylesheets to only get the grid system and flex utilities.
https://getbootstrap.com/docs/4.1/getting-started/contents/#css-files
You could also go to the non-minified version of the bootstrap grid styles
https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap-grid.css
and copy paste it in a css file in the assets folder which you can then modify as you wish. See the documentation here for more info on that.

Related

Bokeh plot line not updating after checking CheckboxGroup in server mode (python callback)

I have just initiated myself to Bokeh library and I would like to add interactivity in my dashboard. To do so, I want to use CheckboxGroup widget in order to select which one of a pandas DataFrame column to plot.
I have followed tutorials but I must have misunderstood the use of ColumnDataSource as I cannot make a simple example work...
I am aware of previous questions on the matter, and one that seems relevant on the StackOverflow forum is the latter :
Bokeh not updating plot line update from CheckboxGroup
Sadly I did not succeed in reproducing the right behavior.
I have tried to reproduce an example following the same updating structure presented in Bokeh Server plot not updating as wanted, also it keeps shifting and axis information vanishes by #bigreddot without success.
import numpy as np
import pandas as pd
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
from bokeh.palettes import Spectral
from bokeh.layouts import row
from bokeh.models.widgets import CheckboxGroup
from bokeh.io import curdoc
# UPDATE FUNCTION ------------------------------------------------
# make update function
def update(attr, old, new):
feature_selected_test = [feature_checkbox.labels[i] for i in feature_checkbox.active]
# add index to plot
feature_selected_test.insert(0, 'index')
# create new DataFrame
new_df = dummy_df.filter(feature_selected_test)
plot_src.data = ColumnDataSource.from_df(data=new_df)
# CREATE DATA SOURCE ------------------------------------------------
# create dummy data for debugging purpose
index = list(range(0, 890))
index.extend(list(range(2376, 3618)))
feature_1 = np.random.rand(len(index))
feature_2 = np.random.rand(len(index))
feature_3 = np.random.rand(len(index))
feature_4 = np.random.rand(len(index))
dummy_df = pd.DataFrame(dict(index=index, feature_1=feature_1, feature_2=feature_2, feature_3=feature_3,feature_4=feature_4))
# CREATE CONTROL ------------------------------------------------------
# list available data to plot
available_feature = list(dummy_df.columns[1:])
# initialize control
feature_checkbox = CheckboxGroup(labels=available_feature, active=[0, 1], name='checkbox')
feature_checkbox.on_change('active', update)
# INITIALIZE DASHBOARD ---------------------------------------------------
# initialize ColumnDataSource object
plot_src = ColumnDataSource(dummy_df)
# create figure
line_fig = figure()
feature_selected = [feature_checkbox.labels[i] for i in feature_checkbox.active]
# feature_selected = ['feature_1', 'feature_2', 'feature_3', 'feature_4']
for index_int, col_name_str in enumerate(feature_selected):
line_fig.line(x='index', y=col_name_str, line_width=2, color=Spectral[11][index_int % 11], source=plot_src)
curdoc().add_root(row(feature_checkbox, line_fig))
The program should work with a copy/paste... well without interactivity...
Would someone please help me ? Thanks a lot in advance.
You are only adding glyphs for the initial subset of selected features:
for index_int, col_name_str in enumerate(feature_selected):
line_fig.line(x='index', y=col_name_str, line_width=2, color=Spectral[11][index_int % 11], source=plot_src)
So that is all that is ever going to show.
Adding new columns to the CDS does not automatically make anything in particular happen, it's just extra data that is available for glyphs or hover tools to potentially use. To actually show it, there have to be glyphs configured to display those columns. You could do that, add and remove glyphs dynamically, but it would be far simpler to just add everything once up front, and use the checkbox to toggle only the visibility. There is an example of just this in the repo:
https://github.com/bokeh/bokeh/blob/master/examples/app/line_on_off.py
That example passes the data as literals the the glyph function but you could put all the data in CDS up front, too.

How to control the number of stacked bars through single select widget in python bokeh

I have created a vertical stacked bar chart using python bokeh on an input dataset df using the following code -
print(df.head())
YearMonth A B C D E
0 Jan'18 1587.816 1586.544 856.000 1136.464 1615.360
1 Feb'18 2083.024 1847.808 1036.000 1284.016 2037.872
2 Mar'18 2193.420 1850.524 1180.000 1376.028 2076.464
3 Apr'18 2083.812 1811.636 1192.028 1412.028 2104.588
4 May'18 2379.976 2091.536 1452.000 1464.432 2400.876
Stacked Bar Chart Code -
products = ['python', 'pypy', 'jython']
customers = ['Cust 1', 'Cust 2']
colours = ['red', 'blue']
data = {
'products': products,
'Cust 1': [200, 850, 400],
'Cust 2': [600, 620, 550],
'Retail 1' : [100, 200, 300],
'Retail 2' : [400,500,600]
}
source = ColumnDataSource(data)
# Set up widgets
select=Select(options=['customers','retailers'],value='customers')
def make_plot() :
p=figure()
#p.title.text=select.value
if select.value=='customers' :
customers=['cust 1','cust 2']
else :
customers=['Retail 1','Retail 2']
p.hbar_stack(customers, y='products', height=0.5, source=source, color=colours)
return p
layout = column(select, make_plot())
# Set up callbacks
def update_data(attrname, old, new):
p = make_plot() # make a new plot
layout.children[1] = p
select.on_change('value', update_data)
# # Set up layouts and add to document
curdoc().add_root(layout)
Now I want to limit the number of segments(ie.stacked bars) by using a widget (preferrably by a single select widget). Can anyone please guide me how can i achieve using bokeh serve functionality. I don't want to use Javascript call back function.
This would take some non-trivial work to make happen. The vbar_stack method is a convenience function that actually creates multiple glyph renderers, one for each "row" in the initial stacking. What's more the renderers are all inter-related to one another, via the Stack transform that stacks all the previous renderers at each step. So there is not really any simple way to change the number of rows that are stacked after the fact. So much so that I would suggest simply deleting and re-creating the entire plot in each callback. (I would not normally recommend this approach, but this situation is one of the few exceptions.)
Since you have not given complete code or even mentioned what widget you want to use, all I can provide is a high level sketch of the code. Here is a complete example that updates a plot based on select widget:
from bokeh.layouts import column
from bokeh.models import Select
from bokeh.plotting import curdoc, figure
select = Select(options=["1", "2", "3", "4"], value="1")
def make_plot():
p = figure()
p.circle(x=[0,2], y=[0, 5], size=15)
p.circle(x=1, y=float(select.value), color="red", size=15)
return p
layout = column(select, make_plot())
def update(attr, old, new):
p = make_plot() # make a new plot
layout.children[1] = p # replace the old plot
select.on_change('value', update)
curdoc().add_root(layout)
Note I have changed your show call to curdoc().add_root since it is never useful to call show in a Bokeh server application. You might want to refer to and study the User Guide chapter Running a Bokeh Server for background information, if necessary.

Center alignment of text in PyX

I am using PyX to create rectangles and putting some (LaTex) text within the rectangle.
# python 3
from pyx import *
text.set(text.LatexRunner)
c = canvas.canvas()
c.stroke(path.rect(0, 0, 1, 1), [style.linewidth.Thick,
color.rgb.red,
deco.filled([color.rgb.green])])
c.text(0.25, 0.25, r"\LaTeX{}")
c.writePDFfile("text-centered") # creates a PDF file
The above code snippet creates a PDF:
As you can see, the text is not aligned. It is possible to try manually until it is (visibly) centered, but this is problematic for obvious reasons.
My question: Is it possible to align the text centered (both horizontally and vertically) automatically?
just use
c.text(0.5, 0.5, r"\LaTeX{}", [text.halign.center, text.vshift.mathaxis])
see
http://pyx.sourceforge.net/manual/text.html#text.halign.center
http://pyx.sourceforge.net/manual/text.html#text.vshift.mathaxis
http://pyx.sourceforge.net/examples/text/halign.html
http://pyx.sourceforge.net/examples/text/valign.html
for some documentation and examples

Captions for matshow()s in multiple-page pdf

I have been around this problem for quite a long time but I'm not able to find an answer.
So, I have a list with matrices which I want to plot (for the sake of this question I'm just having 2 random matrices:
list = [np.random.random((500, 500)), np.random.random((500, 500))]
I then want to plot each element of the list using matshow in a separate page of a pdf file:
with PdfPages('file.pdf') as pdf:
plt.rc('figure', figsize=(3,3), dpi=40)
for elem in list:
plt.matshow(elem, fignum=1)
plt.title("title")
plt.colorbar()
plt.text(0,640,"Caption")
pdf.savefig() # saves the current figure into a pdf page
plt.close()
The result is the following:
My problem is with the caption. You can see I put "Caption" in the edge of the document on purpose. This is because sometimes the actual captions I want to insert are too big to fit in one single pdf page.
So, how can I make each pdf page adjustable to the caption's content (that might vary in each page)? For example, would it be possible to set each page size to A4 or A3, and then plot/write everything in each page?
I've already tried setting up plt.figure(figsize=(X, X)) with a variable X size, but it just changes the resolution of the pdf I guess.
You may want to use the bbox_inches="tight" option when saving the file. This will adapt the figure size to its content. So it then suffices to place some text at position (0,0) in figure coordinates and align it to the top. This will then extent towards the bottom and outside the figure (so the figure when shown on screen would not contain that text), but with the bbox_inches="tight" option of savefig, the saved figure will become large enough to contain that text.
The use of the textwrap package will then also allow to limit the text in horizontal direction.
import numpy as np; np.random.seed(1)
import textwrap
import matplotlib.pyplot as plt
from matplotlib.backends.backend_pdf import PdfPages
p = np.ones(12); p[0] = 7
text2 = "".join(np.random.choice(list(" abcdefghijk"),p=p/p.sum(), size=1000))
text2 = textwrap.fill(text2, width=80)
texts = ["Caption: Example", "Caption 2: " + text2 ]
lis = [np.random.random((500, 500)), np.random.random((500, 500))]
with PdfPages('file.pdf') as pdf:
for elem,text in zip(lis,texts):
fig = plt.figure(dpi=100)
grid_size = (3,1)
plt.imshow(elem)
plt.title("title")
plt.colorbar()
fig.text(0,0, text, va="top")
plt.tight_layout()
pdf.savefig(bbox_inches="tight")
plt.close()
I think I have come up with an answer to this question myself, which solves the problem of having enough space for my text:
However, a perfect answer would be making each page's size dynamic, according to the amount of caption I put.
Anyway, my answer is the following (I essentially divided each page in a grid with 3 rows, making the upper 2 rows for the plots, and the last just for the caption) :
with PdfPages('file.pdf') as pdf:
for elem in list:
fig = plt.figure(figsize=(8.27, 11.69), dpi=100)
grid_size = (3,1)
plt.subplot2grid(grid_size, (0, 0), rowspan=2, colspan=1)
plt.imshow(elem)
plt.title("title")
plt.colorbar()
plt.subplot2grid(grid_size, (2, 0), rowspan=2, colspan=1)
plt.axis('off')
plt.text(0,1,"Caption")
plt.tight_layout()
pdf.savefig()
plt.close()
Which produces the following in each page:
Could someone find a better solution? :)

Control View in Bokeh State Map

I am trying to plot a state with county-level detail using Bokeh and want to be able to control the portion of the state that is visible. I've seen some users suggest deleting counties, but I want to have a rectangular area based on lat/long parameters that controls what portion is shown. Is this possible?
You can control the what is visible on the plot by specifying the x and y ranges. These can be specified either directly in the figure command or by setting the respective attributes using a Range1D. Bokeh will then allow interactive panning respecting while keeping the dimensions of the initial visible area.
If you want to then prevent the user from modifying the visible portion of the plot, you can simply create the figure without any zoom or resize tools.
Here's an example illustrating the above.
from bokeh.plotting import figure, output_file, show
from bokeh.models import Range1d
output_file("title.html")
# Specify tools for the plot
tools = "pan, reset, save"
# create a new plot with a range set with a tuple
p = figure(plot_width=400, plot_height=400,
x_range=(0, 20), tools=tools)
# set a range using a Range1d
p.y_range = Range1d(0, 15)
p.circle([1, 2, 3, 4, 5], [2, 5, 8, 2, 7], size=10)
show(p)

Resources