I have multiple categorical heatmap plots that are in a single display that have identical shapes and x,y coordinates. When hovering on any of the subplots I would like the inspection on one plot to trigger a new inspection on all other plots in the grid and display multiple tooltips simultaneously.
I have researched this topic and found similar posts such as:
Bokeh: Synchronizing hover tooltips in linked plots
Takeaway from link above: There are 2 suggested answers to this question, which attempt to mimic hover tooltips with text glyphs, however these implementations are not successful when I copy and run the code on my own computer (the graphs display correctly but the hover text glyphs don't appear). I assume this could be because of Bokeh API updates, but I am unsure. My reputation doesn't allow comments or I'd address this issue there.
Coordinate tooltips across multiple plots #1547
Takeaway from link above: There is no reproducible data so I am not able to recreate the plot listed here, however bryevdv summarizes what I am trying to do quite efficiently which I'll quote below:
Link on geometry. You might want the geometry of the inspection on one plot to trigger a completely new inspection (using that same geometry) on another plot. So if the cursor is at (10.5, 7) on one plot, then the additional plots do a hit test at (10.5, 7) and if there are glyphs that have any hovers a that point, then a hover gets drawn there.
I have created some generalized data to illustrate my problem:
from bokeh.io import show, output_notebook
from bokeh.layouts import gridplot
from bokeh.models import LinearColorMapper, HoverTool
from bokeh.plotting import figure, show, output_file
from bokeh.transform import transform
import numpy as np
import pandas as pd
data1 = [['A','A',100], ['A','B',175], ['B','A',75], ['B','B',200]]
data2 = [['A','A',25], ['A','B',100], ['B','A',50], ['B','B',75]]
data3 = [['A','A',150], ['A','B',75], ['B','A',25], ['B','B',125]]
df1 = pd.DataFrame(data1, columns = ['Left','Right','Value'])
df2 = pd.DataFrame(data2, columns = ['Left','Right','Value'])
df3 = pd.DataFrame(data3, columns = ['Left','Right','Value'])
def heatmap(df, title):
letters = ['A','B']
mapper = LinearColorMapper(palette=['#225ea8', '#41b6c4', '#a1dab4', '#ffffcc'], low=0, high=200)
TOOLS = 'reset'
p = figure(plot_width=255, plot_height=250, title=title,
x_range=letters,
y_range=list(reversed(letters)), x_axis_location='above',
tools=TOOLS, toolbar_location='below')
p.grid.grid_line_color = None
p.grid.grid_line_width = 0.5
p.axis.axis_line_color = None
p.axis.major_tick_line_color = None
p.axis.major_label_text_font_size = '9pt'
p.axis.major_label_standoff = 0
p.xaxis.major_label_orientation = 0
hover = HoverTool()
p.rect(x='Right', y='Left', width=1, height=1, line_color=None, source=df,
fill_color={'field': 'Value', 'transform': mapper})
hover.tooltips = [('Group','#Left #Right'), ('Value','#Value')]
p.tools.append(hover)
return p
output_notebook()
p1 = heatmap(df1, 'Plot 1')
p2 = heatmap(df2, 'Plot 2')
p3 = heatmap(df3, 'Plot 3')
grid = gridplot([[p1,p2,p3]])
show(grid)
Output:
My goal is to be able to observe the values across multiple plots at one time without having to be directed to another page or source, so I am open to alternative ways of doing this that doesn't involve hover tooltips. Thanks!
Related
I have four goals:
Connect to a Postgresql database and pull some data
Gloss up a table with some colour and formatting
Include an image (company logo) above it
Export as PDF
1 and 2 are easy enough and 4 seems possible even if not convenient, but I don't think R was designed to add and position images. I've attached some sample code of how I envision creating the table, and then a mockup of what I think the final version might look like. Can anyone advise on the best way to accomplish this?
Sample data:
data(mtcars)
df <- head(mtcars)
HTML approach: flexible and portable to other apps
library(tableHTML)
html_table <- df %>%
tableHTML(rownames = FALSE, border = 0) %>%
add_css_row(css = list(c('font-family', 'text-align'), c('sans-serif', 'center'))) %>%
add_css_header(css = list(c('background-color', 'color'), c('#173ACC', 'white')), headers = 1:ncol(df))
Grob approach: Creating a ggplot-like image. I've seen recommendations to use grid.arrange to place an image on top and export as a PDF
library(ggpubr)
tbody.style = tbody_style(color = "black",
fill = "white", hjust=1, x=0.9)
grob_table <- ggtexttable(df, rows = NULL,
theme = ttheme(
colnames.style = colnames_style(color = "white", fill = "#173ACC"),
tbody.style = tbody.style
)
)
grid.arrange(table_image)
You are almost there. You just need to import your image (could be png, jpeg or svg) then pass it to grid::rasterGrob. Use the options in rasterGrob to adjust size etc. Then pass your grob table to gridExtra::grid.arrange
logo_imported <- png::readPNG(system.file("img", "Rlogo.png", package="png"), TRUE)
lg <- grid::rasterGrob(logo_imported)
gridExtra::grid.arrange(lg, grob_table)
You can then either render this to pdf by adding it to an rmarkdown report (probably best), or you can save directly to pdf via
gridExtra::grid.arrange(lg, grob_table)
pdf(file = "My Plot.pdf",
width = 4, # The width of the plot in inches
height = 4)
I have many GeoJson layers in the folium map, some are overlapped and some are not, at one point. And I would like to know how I can click on the map and receive a popup (probably) with the name of the layer that exists at this point of the click.
It is area of specie distribution, and I want to click and know what species are there.
I try:
import geopandas as gpd
import folium
import webbrowser
maptotal=folium.Map()
#loading
sp1 = gpd.read_file('sp1.json', driver='GeoJSON')
sp2 = gpd.read_file('sp2.json', driver='GeoJSON')
sp3 = gpd.read_file('sp3.json', driver='GeoJSON')
sp4 = gpd.read_file('sp4.json', driver='GeoJSON')
#Try to create popup
for index, in sp1.iterrows():
mapsp1 = folium.features.GeoJson(sp1.geometry)
popup=folium.Popup("""Specie one""")
popup.add_to(mapsp1)
mapsp1.add_to(maptotal)
for index, in sp2.iterrows():
mapsp2 = folium.features.GeoJson(sp2.geometry)
popup=folium.Popup("""Specie two""")
popup.add_to(mapsp2)
mapsp2.add_to(maptotal)
for index, in sp3.iterrows():
mapsp3 = folium.features.GeoJson(sp3.geometry)
popup=folium.Popup("""Specie three""")
popup.add_to(mapsp3)
mapsp3.add_to(maptotal)
for index, in sp4.iterrows():
mapsp4 = folium.features.GeoJson(sp4.geometry)
popup=folium.Popup("""Specie four""")
popup.add_to(mapsp4)
mapsp4.add_to(maptotal)
#create layers
principal=folium.plugins.MarkerCluster(control=False)
maptotal.add_child(principal)
camadasub1=plugins.FeatureGroupSubGroup(camadaprincipal,'sp1')
maptotal.add_child(camadasub1)
camadasub2=plugins.FeatureGroupSubGroup(camadaprincipal,'sp2')
maptotal.add_child(camadasub2)
camadasub3=plugins.FeatureGroupSubGroup(camadaprincipal,'sp3')
maptotal.add_child(camadasub3)
camadasub4=plugins.FeatureGroupSubGroup(camadaprincipal,'sp4')
maptotal.add_child(camadasub4)
folium.GeoJson('sp1.json').add_to(camadasub1)
folium.GeoJson('sp2.json').add_to(camadasub2)
folium.GeoJson('sp3.json').add_to(camadasub3)
folium.GeoJson('sp4.json').add_to(camadasub4)
folium.LayerControl().add_to(maptotal)
#save and open
maptotal.save("map.html")
webbrowser.open("map.html")
My idea of result:
Map
I would like to add to my plot a text or a legend box with comments.
At the moment my legend is plot at northeastoutside and i would like to add the new legend (or textbox) to the position southeastoutside.
Thanks!
Lacking more information about your case:
To the best of my knowledge one axes object can only have a single legend object. You can create a second legend with a second axes object. Each legend will only list data elements associated with each axes. Adapted from Matlab Newsgroup thread
a = [1:0.01:2*pi]; %create sample data
b = sin(a);
linehandle1 = line(a,b); %creates a line plot with handle object
axeshandle1 = gca; % gets the handle for the axes object just created
legendhandle1 = legend('y = sin(x)', 'location', 'northeastoutside'); %makes first legend
axeshandle2 = axes('Position',get(axeshandle1,'Position'),'xlim',get(axeshandle1,'xlim'),'ylim',get(axeshandle1,'ylim'),'Visible','off','Color','none'); %makes invisible axes with same position and scaling
linehandle2 = line(pi/2,1,'Color','r','Marker','o','Parent',axeshandle2); %puts data set on 2nd axes
linehandle3 = line(pi,0,'Color','b','Marker','x','Parent',axeshandle2);
legend_handle2 = legend('peak','zero','location','southeastoutside'); %creates legend to go with 2nd axes
If you just want text in that 2nd box, not necessarily legend info or data labels, you can play around with annotation as described above. This has the advantage of being simpler to call, but maybe harder to get the exact position/result you want. There are a large number of property options that can be adjusted to get the desired appearance. A few are shown in the example. It may be there are easier ways to set the size/position based on the legendhandle.
a = [1:0.01:2*pi]; %create sample data
b = sin(a);
plot(a,b);
legendhandle = legend('y = sin(x)','location','northeastoutside');
annotation('textbox',[0.875 0.1 0.1 0.1],'string','my text','edgecolor','k','linewidth',1,'fitboxtotext','off');
I'm having trouble creating widgets in a Jupyter notebook that update when other widget values are changed. This is the code I've been playing around with:
from ipywidgets import interact, interactive, fixed
import ipywidgets as widgets
from IPython.display import display
def func(arg1,arg2):
print arg1
print arg2
choice = widgets.ToggleButtons(description='Choice:',options=['A','B'])
display(choice)
metric = widgets.Dropdown(options=['mercury','venus','earth'],description='Planets:')
text = widgets.Text(description='Text:')
a = interactive(func,
arg1=metric,
arg2=text,
__manual=True)
def update(*args):
if choice.value == 'A':
metric = widgets.Dropdown(options=['mercury','venus','earth'],description='Planets:')
text = widgets.Text(description='Text:')
a.children = (metric,text)
else:
metric = widgets.Dropdown(options=['monday','tuesday','wednesday'],description='Days:')
text2 = widgets.Textarea(description='Text2:')
a.children = (metric,text2)
choice.observe(update,'value')
display(a)
The resulting widgets metric and text do change based whether A or B is selected, but the problem is that the "Run func" button goes away as soon as I change to B. I've tried adding the __manual attribute immediately before display(a), adding it within update, and several other places. How do I change the children of the widget box without overwriting the fact that I want to manually run the function?
How can I left align, rather than right align, the label in an iPython 3 widget, such as IntSlider? My ultimate goal is to left align a set of labeled widgets. This entails left aligning their labels, since the label is the leftmost element of each widget.
I've read the discussion in
Aligning TextBox Widgets in IPython Notebooks, but (a) it focuses on making more space for a right-aligned label, and (b) the proposed solution does not seem to affect the label width. (As an aside, I would be interested in finding cell-executable code that can reset the minimum label width.)
I've also read the discussion in Change the size of the label in an IPython notebook widget, but it doesn't seem to offer a simple solution.
Thanks for your help.
Addendum (2015-06-02):
Looks like widget.interactive() does not play nicely with the solution suggested by Jakob. Example:
from IPython.html import widgets
from IPython.display import display
def mySlider(text='', twidth=100, min=0, max=10, value=5):
c1 = widgets.HBox()
ints = widgets.IntSlider(min=min, max=max, value=value)
text = widgets.HTML(text, width=twidth)
c1.children = (text, ints)
return c1
s1 = mySlider('Test')
s2 = mySlider('TestTest')
s3 = mySlider('TestTestTest')
def process(a, b, c):
print([a, b, c])
widgets.interactive(
process,
a=s1.children[1].value,
b=s2.children[1].value,
c=s3.children[1].value
)
yields slider labels a, b, c with the usual alignment.
You can simply combine an IntSlider with an Html widget to create your custom widget like
from IPython.html import widgets
from IPython.display import display
def mySlider(text='', twidth=100):
c1 = widgets.HBox()
ints = widgets.IntSlider()
text = widgets.HTML(text, width=twidth)
c1.children = (text, ints)
return c1
Using this method, some widgets could look like
s1 = mySlider('Test')
s2 = mySlider('TestTest')
s3 = mySlider('TestTestTest')
display(s1,s2,s3)
Update to work with interact
To use these custom widgets with interact it is necessary to add some properties and callbacks. The interact method requires the widget.description and the widget.value arguments to setup the interactive widgets. As our container widget does not have these arguments, they are added manually. Moreover it is necessary to link the container.value with the IntSlider.value. This is once realized by a simple assignment, and more important via the on_trait_change method.
Finally, the interact methods calls the process function on widget.on_trait_change callbacks, thus the container.on_trait_change method is replaced by the IntSlider.on_trait_change call.
The updated code looks like:
def mySlider2(text='', twidth=100, min=0, max=10, value=5):
c1 = widgets.HBox()
ints = widgets.IntSlider(min=min, max=max, value=value)
text = widgets.HTML(text, width=twidth)
c1.children = (text, ints)
c1.description = text
c1.value = ints.value
def update(name, value):
c1.value = value
ints.on_trait_change(update,'value')
c1.on_trait_change = ints.on_trait_change
return c1
s1a = mySlider2('Test')
s2a = mySlider2('TestTest')
s3a = mySlider2('TestTestTest')
widgets.interactive(
process,
a=s1a,
b=s2a,
c=s3a
)