Long Title Cutoff

I am making a series of heatmaps with some being quite narrow (a couple blocks), I want them to have square blocks, so I am using the frame_widith and frame_height to basically set that exact right frame width and height to get the rectangles to be squares. This works, but if my is too long then it gets cut off, where I would like to have it take up some extra white space around it if needed for the title. (I am also turning off tools and toolbar since that isn’t wanted in my usecase). I have tried setting aspect ratio instead with a min width and height, but doesn’t keep the right aspect ratio

from bokeh.plotting import figure, show
from bokeh.transform import linear_cmap
from bokeh.layouts import row
import pandas as pd
import numpy as np

xs = ["a", "b", "c"]
ys = ["e", "f", "g", "h", "i", "j", "k", "l", "m"]

x = [x for x in xs for y in ys]
y = [y for x_ in xs for y in ys]

df = pd.DataFrame({"x": x,
                   "y": y,
                   "z": np.random.random(len(x))})

p = figure(
    frame_width=15 * len(df["x"].unique()), frame_height=15 * len(df["y"].unique()),
    toolbar_location=None,
    x_range=sorted(list(df["x"].unique())), y_range=list(df["y"].unique()),
    title="A title too long for this plot",
    # aspect_ratio=(len(df["x"].unique()) / len(df["y"].unique())),
)


fill_color = linear_cmap("z", palette="Cividis256", low=df["z"].min(), high=df["z"].max())

p.rect(x="x", y="y",
           width=1, height=1,
           source=df,
           color=fill_color,
           )

p2 = figure(
    frame_width=15 *len(df["x"].unique()), frame_height=15 *len(df["y"].unique()),
    toolbar_location=None,
    x_range=sorted(list(df["x"].unique())), y_range=list(df["y"].unique()),
    title="A title too long for this plot",
    # aspect_ratio=(len(df["x"].unique()) / len(df["y"].unique())),
)


fill_color2 = linear_cmap("z", palette="Cividis256", low=df["z"].min(), high=df["z"].max())

p2.rect(x="x", y="y",
           width=1, height=1,
           source=df,
           color=fill_color2,
           )

show(row(p, p2))

image

If I use aspect ratio with min_width and min_height then it doesn’t keep the square boxes

from bokeh.plotting import figure, show
from bokeh.transform import linear_cmap
from bokeh.layouts import row
import pandas as pd
import numpy as np

xs = ["a", "b", "c"]
ys = ["e", "f", "g", "h", "i", "j", "k", "l", "m"]

x = [x for x in xs for y in ys]
y = [y for x_ in xs for y in ys]

df = pd.DataFrame({"x": x,
                   "y": y,
                   "z": np.random.random(len(x))})

p = figure(
    min_width=len(df["x"].unique()), min_height=len(df["y"].unique()),
    toolbar_location=None,
    x_range=sorted(list(df["x"].unique())), y_range=list(df["y"].unique()),
    title="A title too long for this plot",
    aspect_ratio=(len(df["x"].unique()) / len(df["y"].unique())),
)

fill_color = linear_cmap("z", palette="Cividis256", low=df["z"].min(), high=df["z"].max())

p.rect(x="x", y="y",
           width=1, height=1,
           source=df,
           color=fill_color,
           )

p2 = figure(
    min_width=len(df["x"].unique()), min_height=len(df["y"].unique()),
    toolbar_location=None,
    x_range=sorted(list(df["x"].unique())), y_range=list(df["y"].unique()),
    title="A title too long for this plot",
    aspect_ratio=(len(df["x"].unique()) / len(df["y"].unique())),
)

fill_color2 = linear_cmap("z", palette="Cividis256", low=df["z"].min(), high=df["z"].max())

p2.rect(x="x", y="y",
           width=1, height=1,
           source=df,
           color=fill_color2,
           )

show(row(p, p2))

I also tried playing with width and height instead, min width/height max width/height, looking into the rectangle settings to see if there was something there, sizing modes, and nothing I looked into I could see what to do.

Any advice on how to keep the rectangles square while also if my title is too long have the plot provide some white space to either side before the next heatmap?

This is what I am going for more or less (I did this with panel, putting Markdown panes above the figure), but it isn’t ideal since it centers on the entire figure area (including labels and colorbars), not the frame area.

I’d probably try styling a Div widget in lieu of each figure’s title, then bundling them with the figure using column/row/layout etc. I think that could work for what you’re going for.

When I do that, and I center the text, it does the same thing that the Panel Markdown panes do, which is centering it over the entire column instead of the frame_width. What you suggested is basically what I was doing but with Panel. I want it centered over the frame, but this way centers over the area that includes any attached colorbar or y axis labels. The title is the only way I figured out so far to do it correctly (and dynamically since I won’t know how wide my heatmaps will be exactly since it will be variable user data).

I think I have a solution, I am using min_border_left and min_border_right (in combination with what I initially mentioned). I am trying to gauge how many x labels I have and compare that to my title length and then figure out how much I need to add to min_border_left/right based on trial and error. Not the best solution, but it works. Any other solutions would be great!

this is what I am trying so far in my heatmap function based on trial and error for scaling if it ends up helping anyone else later on.

    half_ttl_ln = len(title) / 2
    if half_ttl_ln > len(df[x_col].unique()) and "min_border_left" not in fig_kwargs and "min_border_right" not in fig_kwargs:
        left_over = (half_ttl_ln - len(df[x_col].unique()))
        fig_kwargs["min_border_left"] = int(left_over / 1.4) * HEATMAP_CELL_WIDTH
        fig_kwargs["min_border_right"] = int(left_over / 1.4) * HEATMAP_CELL_WIDTH