tweaking rangeslider height in plotly to prevent it from overlapping with subplots? - plotly-python

The rangeslider is too thick and overlaps with the next subplot. I can increase the vertical spacing between the subplots, or the overall plot height (as suggested here: https://community.plotly.com/t/rangeslider-overlaps-with-subplots/35169) but this makes the plot disproportional, and gets worse as the number of subplots increases. The rangeslider is just too thick.
Can I change just the height of the rangeslider?
Here's an MWE:
import numpy as np
import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
n_plots = 2
data = pd.DataFrame()
for i in range(n_plots):
data[i] = np.random.randn(100)
data
fig = make_subplots(
rows = n_plots,
cols = 1,
vertical_spacing = 0.1,
shared_xaxes = True
)
for i in range(n_plots):
fig.add_trace(
go.Scatter(
y = data[i]
),
row = 1+i, col = 1
)
fig.update_layout(
height = 800,
width = 800,
xaxis_rangeslider_visible = True,
)

yes, you can change the rangeslider height (thickness) using:
fig.update_layout(
xaxis_rangeslider_visible = True,
xaxis_rangeslider_thickness = 0.04
)
For the rangeslider associated with the n-th x-axis, you would use: xaxisn_rangeslider_thickness

Related

How do I invert matplotlib bars at a specific point instead of when negative?

I'd like to invert the bars in this diagram when they are below 1, not when they are negative. Additionally I'd like to have even spacing between the ticks/steps on the y-axis
Here is my current code
import matplotlib.pyplot as plt
import numpy as np
labels = ['A','B','C']
Vals1 = [28.3232, 12.232, 9.6132]
Vals2 = [0.00456, 17.868, 13.453]
Vals3 = [0.0032, 1.234, 0.08214]
x = np.arange(len(labels))
width = 0.2
fig, ax = plt.subplots()
rects1 = ax.bar(x - width, Vals1, width, label='V1')
rects2 = ax.bar(x, Vals2, width, label='V2')
rects3 = ax.bar(x + width, Vals3, width, label='V3')
ax.set_xticks(x)
ax.set_xticklabels(labels)
plt.xticks(rotation=90)
ax.legend()
yScale = [0.0019531,0.0039063,0.0078125,0.015625,0.03125,0.0625,0.125,0.25,0.5,1,2,4,8,16,32]
ax.set_yticks(yScale)
plt.show()
I believe I've stumbled upon the answer, here it is for anyone else looking for the solution. Add the argument bottom='1' to ax.bar instantiation, and then flip the values in the array.
for i in range(len(Vals1)):
Vals1[i] = (1 - Vals1[i]) * -1
As you mentioned, the key is the bottom param of Axes.bar:
bottom (default: 0): The y coordinate(s) of the bars bases.
But beyond that, you can simplify your plotting code using pandas:
Put your data into a DataFrame:
import pandas as pd
df = pd.DataFrame({'V1': Vals1, 'V2': Vals2, 'V3': Vals3}, index=labels)
# V1 V2 V3
# A 28.3232 0.00456 0.00320
# B 12.2320 17.86800 1.23400
# C 9.6132 13.45300 0.08214
Then use DataFrame.sub to subtract the offset and DataFrame.plot.bar with the bottom param:
bottom = 1
ax = df.sub(bottom).plot.bar(bottom=bottom)

Series plot - Geopandas

I dont have a working code - but a snipet of my code can be as follows. I'm trying to use geopandas with mathplotlib, and trying to plot a map with links and points.
shape_file = os.path.join(os.getcwd(), "Healthboard")
healthboard = gp.read_file(os.path.join(shape_file, "healthboard.shp"))
healthboard = healthboard.to_crs({'init': 'epsg:4326'}) # re-projection
geo_df1 = geo_df1[geo_df1['HealthBoardArea2019Code'] == string1]
geo = geo_df[geo_df['Healthboard '] == string2]
new_shape_file = os.path.join(os.getcwd(), "Council_Shapefile")
council_to_healtboard = pd.read_csv("council_to_healthboard.csv")
council_to_healthboard = council_to_healtboard.rename(columns = {'CA': 'Council_area_code'})
council = gp.read_file(os.path.join(new_shape_file, "Council_shapefile.shp"))
council = council.to_crs({'init': 'epsg:4326'})
council = council.rename(columns = {'la_s_code':'Council_area_code'})
df = council.merge(council_to_healthboard, on = 'Council_area_code', how ='inner')
# Plotting stuff
fig, ax = plt.subplots(figsize=(15,15))
geo_df1.plot(ax = ax, markersize=35, color = "blue", marker = "*", label = "Postcode Sector")
geo.geometry.plot(ax = ax, color = "red", markersize=20, alpha = 0.8, label = 'SiteName')
#healthboard[healthboard["HBName"]=="Lothian"].plot(ax = ax, alpha = 0.6)
#healthboard[healthboard["HBName"]=="Lothian"].boundary.plot(ax = ax, color = "black", alpha = 0.6)
df[df["HB"]=="S08000024"].boundary.plot(ax =ax, color = "black", alpha = 0.1)
df[df["HB"]=="S08000024"].plot(ax =ax, cmap = "viridis", alpha = 0.1)
links_gp.plot(ax =ax, alpha = 0.25, color='brown', linestyle = "-")
My links_gp.plot has 40 time periods, as a result I want to make one plot, and have a button to adjust the parameters of time. Or if not possible a series of 40 plots. I've tried numerous ways but keep failing on this. I would really appreciate if someone could guide me on this.
I'm aware that you are using matplotlib, but if you don't mind using bokeh instead, you could use the following. To create an interactive plot with a possibility to adjust a parameter, bokeh provides a slider widget which can be used to change the plot based on a custom filter function.
An example from a geopandas dataframe with LineString geometries similar to the one you posted:
import geopandas as gpd
from bokeh.io import show, output_notebook
from bokeh.models import (CDSView, ColumnDataSource, CustomJS,
CustomJSFilter, Slider, Column)
from bokeh.layouts import column
from bokeh.plotting import figure
# prepare data source
links_gp['x'] = links_gp.apply(lambda row: list(row['geometry'].coords.xy[0]), axis=1)
links_gp['y'] = links_gp.apply(lambda row: list(row['geometry'].coords.xy[1]), axis=1)
# drop geometry column, because it can't be serialized to ColumnDataSource
links_gp.drop('geometry', axis=1, inplace=True)
linesource = ColumnDataSource(links_gp)
p = figure(title = 'Bokeh Time Slider',
plot_height = 500,
plot_width = 600,
toolbar_location = 'below',
tools = "pan, wheel_zoom, box_zoom, reset")
slider = Slider(title='Time Period', start=1, end=40, step=1, value=1)
# Callback triggers the filter when the slider moves
callback = CustomJS(args=dict(source=linesource),
code="""source.change.emit();""")
slider.js_on_change('value', callback)
# Custom filter that selects all lines of the time period based on the slider value
custom_filter = CustomJSFilter(args=dict(slider=slider),
code="""
var indices = [];
// iterate through rows of data source and check if time period value equals the slider value
for (var i = 0; i < source.get_length(); i++){
if (source.data['Time Period'][i] == slider.value){
indices.push(true);
} else {
indices.push(false);
}
}
return indices;
""")
# Use filter to determine which lines are visible
view = CDSView(source=linesource, filters=[custom_filter])
# plot lines to map
p.multi_line('x', 'y', source=linesource, color='red', line_width=3, view=view)
layout = column(p, slider)
show(layout)
This will be the result of the above code.

define size of individual subplots side by side

I am using subplots side by side
plt.subplot(1, 2, 1)
# plot 1
plt.xlabel('MEM SET')
plt.ylabel('Memory Used')
plt.bar(inst_memory['MEMORY_SET_TYPE'], inst_memory['USED_MB'], alpha = 0.5, color = 'r')
# pol 2
plt.subplot(1, 2, 2)
plt.xlabel('MEM POOL')
plt.ylabel('Memory Used')
plt.bar(set_memory['POOL_TYPE'], set_memory['MEMORY_POOL_USED'], alpha = 0.5, color = 'g')
they have identical size - but is it possible to define the width for each subplot, so the right one could be wider as it has more entries and text would not squeeze or would it be possible to replace the bottom x-text by a number and have a legend with 1:means xx 2:means yyy
I find GridSpec helpful for subplot arrangements, see this demo at matplotlib.
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
import pandas as pd
N=24
inst_memory = pd.DataFrame({'MEMORY_SET_TYPE': np.random.randint(0,3,N),
'USED_MB': np.random.randint(0,1000,N)})
set_memory = pd.DataFrame({'MEMORY_POOL_USED': np.random.randint(0,1000,N),
'POOL_TYPE': np.random.randint(0,10,N)})
fig = plt.figure()
gs = GridSpec(1, 2, width_ratios=[1, 2],wspace=0.3)
ax1 = fig.add_subplot(gs[0])
ax2 = fig.add_subplot(gs[1])
ax1.bar(inst_memory['MEMORY_SET_TYPE'], inst_memory['USED_MB'], alpha = 0.5, color = 'r')
ax2.bar(set_memory['POOL_TYPE'], set_memory['MEMORY_POOL_USED'], alpha = 0.5, color = 'g')
You may need to adjust width_ratios and wspace to get the desired layout.
Also, rotating the text in x-axis might help, some info here.

Keeping subplot bar height constant when subplots are different height

Is it possible to have a subplot taller than other subplots in order to make space for the X axis tick labels, but the height of the bar chart inside to be the same as the bar height in the shorter subplots? When I add height parameter to df.plot() I get "TypeError: () got multiple values for keyword argument 'height'". Here is my code:
from collections import OrderedDict
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
data = OrderedDict()
data['Test Break'] = [0.1, 0.5, np.nan]
data['No Break'] = [0.9, 0.5, np.nan]
data['Not Tested'] = [0.0, 0.0, 1.0000]
index = ['Very very long name ' + str(x+1) for x in range(len(data))]
df = pd.DataFrame(data=data, index=index)
num_plots = 2
rows = num_plots + 1
cols = 1
layout = (rows, cols)
red, green, grey = '#FF0000', '#00FF00', '#888888'
light_grey = '#AAAAAA'
fig = plt.figure()
fig.set_size_inches(6, 3)
for z in range(num_plots):
is_last = z == num_plots - 1
rowspan = 2 if is_last else 1
ax = plt.subplot2grid(layout, (z, 0), rowspan=rowspan)
df.plot(ax=ax, kind='bar', stacked=True, yticks=[0,1], legend=False, color=[red, green, grey])
plt.subplots_adjust(left=0.05, right=0.95, bottom=0.02, top=0.98, hspace=0.5)
ax.grid(True, which='major', axis='y')
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['bottom'].set_edgecolor(light_grey)
ax.spines['left'].set_edgecolor(light_grey)
if not is_last:
for tick in ax.xaxis.get_major_ticks():
tick.set_visible(False)

matplotlib - imshow 'extents' definiton killed plt.text

I am quite the novice at matplotlib, so bear with me. I have the code below that plots a cylindrical equidistant grid of precipitation. I set the 'extents' limits that finally aligned my basemap with the data. Now, it appears to have "broken" my plt.text capability as I can no longer see the text 'Precipitation Rate (mm/hour)'. Thanks for any help.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
from pylab import *
import pickle
from mpl_toolkits.basemap import Basemap
fp = open('uneven_rgb.pkl', 'rb')
uneven_rgb = pickle.load(fp)
fp.close()
num_lon = 1440
num_lat = 400
precipfile = "/Users/bolvin/3B43.20111001.7.HDF_precip.bin"
fileobj = open(precipfile, mode='rb') # Open file as read only binary
data = np.fromfile (fileobj, dtype ='f')
datat = np.reshape(data, (num_lon, num_lat), order = 'FORTRAN')
datam = datat * 24.0
my_cmap = matplotlib.colors.LinearSegmentedColormap('my_colormap',uneven_rgb)
plt.figure(figsize = (20,10))
mapproj = Basemap(projection = 'cyl', llcrnrlat=-50.0, llcrnrlon=0.0, urcrnrlat=50.0,urcrnrlon=360.0)
mapproj.drawcoastlines()
mapproj.drawcountries()
mapproj.drawparallels(np.array([-30.0, 0.0, 30.0]), labels=[0,0,0,0])
mapproj.drawmeridians(np.array([90.0, 180.0, 270.0]), labels=[0,0,0,0])
myplot = plt.imshow(datam.T, interpolation = 'nearest', cmap = my_cmap, vmin = 0.0, vmax = 20.0, extent = (0.0, 360.0, -50.0, 50.0))
plt.title('October 2011 3B43 Precipitation', fontsize = 36, y = 1.03)
plt.text(1.0, 435.0, 'Precipitation Rate (mm/hour)', size = 20)
cbar = plt.colorbar(myplot, orientation='horizontal', shrink = 0.5, pad = 0.03)
cbar.ax.tick_params(labelsize=20)
plt.gca().axes.get_xaxis().set_visible(False)
plt.gca().axes.get_yaxis().set_visible(False)
plt.show()
fileobj.close()
plt.text gets as first argument the x and y coordinates on which your text will be put.
As you transformed your imshow plot into the bordes 0-360 for x and -50 to 50 for y, y=435 is not in the plot anymore.
You can check your limits with plt.gca().get_xlim().
You have to move it somewhere in your limits.
Your defining the units you are plotting with this text, right? So the natural place for this would be the label of the colorbar:
cbar = plt.colorbar(myplot, orientation='horizontal', shrink = 0.5,
pad = 0.03, label='Precipitation Rate (mm/hour)')