I'm trying to use a combination of JupyterLab and the latest ipywidgets to have a basic graphic interface to explore some data. However, I cannot find a way to set up the figure height for the interactive plot
The notebook loads some file, then the user has the ability to pass some input using widgets (in the code below a text box). Finally, by pressing a button the graph is generated and passed to an Output widget.
This is the example code:
import pandas as pd
import matplotlib.pyplot as plt
from IPython.display import clear_output, display
import ipywidgets as widgets
data = pd.read_csv('data.csv')
def show_results(input):
if 'fig' in locals():
del fig
with out:
clear_output(True)
fig, ax = plt.subplots(1, 1)
ax.axis('equal')
data.loc[input.value].plot(cmap='inferno_r', column='probability', ax=ax, legend=True)
plt.show()
input = widgets.Text(placeholder='input field', description='Input:')
showMe = widgets.Button(
description='Show me the results',
tooltip='Show me',
)
out = widgets.Output(layout = {
'width': '100%',
'height': '900px',
'border': '1px solid black'
})
showMe.on_click(show_results)
display(input)
display(showMe)
display(out)
The problem is that the graph automatically resizes no matter how large is the Output widget, and no matter of the figsize parameter passed.
I would expect one of the two to control the output size particularly because there is no more the option to resize the graph manually.
As an additional info the HTML div dealing with the graph inside the Output widget is always 500px height no matter what I do
Found the solution after a lot of research. Currently there is an open issue on github discussing just how %matplotlib widget behaves differently compared to the rest of matplotlib. Here is the link to the discussion
Basically this backend takes advantage of jupyter widgets and passes the figure to an Output widget. In my case I was nesting this into another Output widget to capture the plot.
In this case neither the figsize attribute for the plot and the CSS layout attributes for the outer Output widget have an impact as the problem sits with this intermediate Output widget.
The good news is that the CSS layout attributes that we need to change are exposed. To modify them we can use:
fig.canvas.layout.width = '100%'
fig.canvas.layout.height = '900px'
The above solution did not work for me. Neither setting the plot dimension globally using rcParams still I managed to get it working setting the figure dimension inside the function using fig = plt.figure(figsize=(12,12)):
from ipywidgets import interactive
import matplotlib.pyplot as plt
import numpy as np
np.random.rand(33,20)
def f():
fig = plt.figure(figsize=(15,15)) # <-- Here the figure dimension is controlled
ax = fig.add_subplot()
ax.imshow(np.random.rand(33,20)/65536)
plt.show()
interactive_plot = interactive(f, r = (0,15),g = (0,15),b = (0,15))
interactive_plot
Related
I would like o add a plot figure based on smileys like this one:
dat will come from a dataframe pandas : dataframe.value_counts(normalize=True)
Can some one give me some clues.
use colorscale in normal way for a heatmap
use anotation_text to assign an emoji to a value
import plotly.figure_factory as ff
import plotly.graph_objects as go
import pandas as pd
import numpy as np
df = pd.DataFrame([[j*10+i for i in range(10)] for j in range(10)])
e=["đ","đ","đ","âšī¸"]
fig = go.Figure(ff.create_annotated_heatmap(
z=df.values, colorscale="rdylgn", reversescale=False,
annotation_text=np.select([df.values>75, df.values>50, df.values>25, df.values>=0], e),
))
fig.update_annotations(font_size=25)
# allows emoji to use background color
fig.update_annotations(opacity=0.7)
update coloured emoji
fundamentally you need emojicons that can accept colour styling
for this I switched to Font Awesome. This then also requires switching to dash, plotly's cousin so that external CSS can be used (to use FA)
then build a dash HTML table applying styling logic for picking emoticon and colour
from jupyter_dash import JupyterDash
import dash_html_components as html
import pandas as pd
import branca.colormap
# Load Data
df = pd.DataFrame([[j*10+i for i in range(10)] for j in range(10)])
external_stylesheets = [{
'href': 'https://use.fontawesome.com/releases/v5.8.1/css/all.css',
'rel': 'stylesheet', 'crossorigin': 'anonymous',
'integrity': 'sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf',
}]
# possibly could use a a different library for this - simple way to map a value to a colormap
cm = branca.colormap.LinearColormap(["red","yellow","green"], vmin=0, vmax=100, caption=None)
def mysmiley(v):
sm = ["far fa-grin", "far fa-smile", "far fa-meh", "far fa-frown"]
return html.Span(className=sm[3-(v//25)], style={"color":cm(v),"font-size": "2em"})
# Build App
app = JupyterDash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div([
html.Table([html.Tr([html.Td(mysmiley(c)) for c in r]) for r in df.values])
])
# Run app and display result inline in the notebook
app.run_server(mode='inline')
I am trying to zoom in on geopandas map with labels using set_xlim() in with matplotlib. I basically adapted this SO question to add labels to a map.
However, set_xlim() does not seem to work and did not zoom in on the given extent. (By the way, I've also tried to use text() instead of annotate(), to no avail.)
What I did was the following:
I used the same US county data as in the question linked above, extracted the files, and then executed the following in Jupyter notebook:
import geopandas as gpd
import matplotlib.pyplot as plt
%matplotlib inline
shpfile='shp/cb_2015_us_county_20m.shp'
gdf=gpd.read_file(shpfile)
gdf.plot()
, which gives a map of all US counties as expected:
Adding labels as with one of the answers also works:
ax = gdf.plot()
gdf.apply(lambda x: ax.annotate(s=x.NAME, xy=x.geometry.centroid.coords[0], ha='center'),axis=1);
However, when trying to zoom in to a particular geographic extent with set_xlim() and set_ylim() as follows:
ax = gdf.plot()
gdf.apply(lambda x: ax.annotate(s=x.NAME, xy=x.geometry.centroid.coords[0], ha='center'),axis=1);
ax.set_xlim(-84.2, -83.4)
ax.set_ylim(42, 42.55)
, the two functions do not seem to work. Instead of zooming in, they just trimmed everything outside of the given extent.
If the labeling code is dropped out (gdf.apply(lambda x: ax.annotate(s=x.NAME, xy=x.geometry.centroid.coords[0], ha='center'),axis=1);, the set_xlim() works as expected:
My question is:
What is the correct way to zoom in to an area when labels are present in a plot?
You need some coordinate transformation.
import cartopy.crs as ccrs
# relevant code follows
# set numbers in degrees of longitude
ax.set_xlim(-84.2, -83.4, ccrs.PlateCarree())
# set numbers in degrees of latitude
ax.set_ylim(42, 42.55, ccrs.PlateCarree())
plt.show()
with the option ccrs.PlateCarree(), the input values are transformed to proper data coordinates.
When I try it, I can't draw on matplotlib with the axes restricted. So it's possible to extract the data.
import geopandas as gpd
import matplotlib.pyplot as plt
%matplotlib inline
fig,ax = plt.subplots(1,1, figsize=(4,4), dpi=144)
shpfile = './cb_2015_us_county_20m/cb_2015_us_county_20m.shp'
gdf = gpd.read_file(shpfile)
# gdf = gdf.loc[gdf['STATEFP'] == '27']
gdf['coords'] = gdf['geometry'].apply(lambda x: x.representative_point().coords[:])
gdf['coords'] = [coords[0] for coords in gdf['coords']]
gdf = (gdf[(gdf['coords'].str[0] >= -84.2) & (gdf['coords'].str[0] <= -83.4)
& (gdf['coords'].str[1] >= 42) & (gdf['coords'].str[1] <= 42.55)])
gdf.plot(ax=ax)
gdf.apply(lambda x: ax.annotate(text=x.NAME, xy=x.geometry.centroid.coords[0], ha='center'),axis=1)
I have a script which uses the FuncAnimation routines in a loop to generate a lot of different animations which are saved in various directories. I'm working in Spyder, and every time an animation is generated, a .PNG image is displayed in the console.
Is there any way to NOT display the .PNG file with every animation? I'd like to turn off this image, since they will fill up my console in a long loop. When generating simple plots, it's easy to not display the image by just not calling plt.show. For animations, plt.show is called isn't called at all and the image still displays.
You can see that a .PNG image generated in the basic example:
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import matplotlib.animation as animation
def update_line(num, data, line):
line.set_data(data[..., :num])
return line,
# Fixing random state for reproducibility
np.random.seed(19680801)
# Set up formatting for the movie files
Writer = animation.writers['ffmpeg']
writer = Writer(fps=15, metadata=dict(artist='Me'), bitrate=1800)
fig = plt.figure()
data = np.random.rand(2, 25)
l, = plt.plot([], [], 'r-')
plt.xlim(0, 1)
plt.ylim(0, 1)
line_ani = animation.FuncAnimation(fig, update_line, 25, fargs=(data, l),
interval=50, blit=True)
line_ani.save('lines.mp4', writer=writer)
The .PNG image associated with the animation can be effectively hidden by calling plt.close(fig) after the animation is saved. This answer was inspired by a response by Demis to a similar question asking about simple plots instead of animations.
Is there a general way to change the font-family, and font size of holoviews plot (rendered by bokeh, and matplotlib, respectively). In particular, I wish to change the font-family and font-size for hv.Bars and hv.Sankey.
The current approach I use to change the font family for the x/y axis labels is to drop into bokeh/matplotlib and change it from there.
import numpy as np
import pandas as pd
import holoviews as hv
from bokeh.plotting import show
from matplotlib import rcParams
categoryA = np.random.choice(['Label1','Label2','Label3','Label4'],size=12)
categoryB = np.random.choice(['Target1','Target2'],size=12)
values = np.random.uniform(0,1,size=12)
dd = pd.DataFrame({'A':categoryA,'B':categoryB,'V':values})
In bokeh, this works okay for Bars
ww = hv.Bars(dd.groupby(['A','B'])['V'].mean().reset_index(),kdims=['A','B'],vdims=['V'])
ww.opts(width=1200)
ww_bokeh = hv.render(ww,backend='bokeh')
ww_bokeh.xaxis.major_label_text_font='arial'
ww_bokeh.xaxis.major_label_text_font_size='16pt'
ww_bokeh.xaxis.axis_label_text_font = 'arial'
ww_bokeh.xaxis.axis_label_text_font_size = '12pt'
ww_bokeh.xaxis.group_text_font='arial'
ww_bokeh = hv.render(ww,backend='bokeh')
show(ww_bokeh)
In matplotlib this doesn't work very well, as I believe the xticks labels are not two different objects.
rcParams['font.family'] = 'sans-serif'
rcParams['font.sans-serif'] = ['Arial']
rcParams['xtick.labelsize'] = '16'
mm = hv.Bars(dd.groupby(['A','B'])['V'].mean().reset_index(),kdims=['A','B'],vdims=['V'])
mm.opts(aspect=5,fig_size=600)
For Sankey, I have no idea how to change the label font size and family.
from holoviews import opts
hv.extension('bokeh')
tt = hv.Sankey(dd)
tt.opts(opts.Sankey(edge_color=dim('A').str(),label_position='outer'))
tt.opts(opts.Label(text_font_size='20pt')) #this does nothing, as I dont think the labels are Label objects
tt.opts(opts.Text(fontscale=3)) #this also does nothing, as I dont think the labels are Text objects either
tt.opts(height=600,width=800)
The font-family is important to change in my case, as well as the label size in the Sankey plot. For reference I am using: bokeh version 1.4.0, matplotlib version 3.1.3, holoviews version 1.13.0a22.post4+g26aeb5739, and python version 3.7 in a jupyter notebook.
I have tried to delve into the source code, but got lost. So any advice or direction would be appreciated.
You should use hook
http://holoviews.org/user_guide/Customizing_Plots.html#Plot-hooks
def hook(plot, element):
plot.handles['text_1_glyph'].text_font = 'arial'
plot.handles['text_1_glyph'].text_font_size = '16pt'
plot.handles['text_2_glyph'].text_font = 'arial'
plot.handles['text_2_glyph'].text_font_size = '16pt'
tt = hv.Sankey(dd)
tt.opts(opts.Sankey(edge_color=hv.dim('A').str(),label_position='outer'))
tt.opts(height=600,width=800)
tt.opts(hooks=[hook])
I use bokeh in an ipython notebook and would like to have a button next to a plot to switch on or off labels of the data points. I found a solution using IPython.html.widgets.interact, but this solution resets the plot for each update including zooming and padding
This is the minimal working code example:
from numpy.random import random
from bokeh.plotting import figure, show, output_notebook
from IPython.html.widgets import interact
def plot(label_flag):
p = figure()
N = 10
x = random(N)+2
y = random(N)+2
labels = range(N)
p.scatter(x, y)
if label_flag:
pass
p.text(x, y, labels)
output_notebook()
show(p)
interact(plot, label_flag=True)
p.s. If there is an easy way to do this in matplotlib I would also switch back again.
By using bokeh.models.ColumnDataSource to store and change the plot's data I was able to achieve what I wanted.
One caveat is, that I found no way to make it work w/o refresh w/o calling output_notebook twice in two different cells. If I remove one of the two output_notebook calls the gui of the tools-button looks breaks or changing a setting also results in a reset of the plot.
from numpy.random import random
from bokeh.plotting import figure, show, output_notebook
from IPython.html.widgets import interact
from bokeh.models import ColumnDataSource
output_notebook()
## <-- new cell -->
p = figure()
N = 10
x_data = random(N)+2
y_data = random(N)+2
labels = range(N)
source = ColumnDataSource(
data={
'x':x_data,
'y':y_data,
'desc':labels
}
)
p.scatter('x', 'y', source=source)
p.text('x', 'y', 'desc', source=source)
output_notebook()
def update_plot(label_flag=True):
if label_flag:
source.data['desc'] = range(N)
else:
source.data['desc'] = ['']*N
show(p)
interact(update_plot, label_flag=True)