An option I found appealing was using a "dummy" heatmap from Plots.jl and combining the graphs using @layout.
using Plots, DataFrames, Random
# Data
df = DataFrame(a='a':'e', b = rand(5), c=rand(5))
# Line plot
plot_lines = plot(df.a, df.b)
plot!(df.a, df.c)
# Dummy heatmap
# Get column names and row names
row_labels = df[:, 1]
col_labels = names(df)[2:end]
col_labels = replace.(col_labels, " Change" => "\nChange")
# Extract matrix of values (excluding the first column which is the label)
matrix_vals = Matrix(df[:, 2:end])
# Prepare annotations for each cell
ann = [(j - .5, i - .5, text("$(round(matrix_vals[i,j], digits=2))", 8, :black, :center)) for i in 1:size(matrix_vals,1), j in 1:size(matrix_vals,2)]
ann = reduce(vcat, ann)
# Annotated heatmap
plot_df = heatmap(
string.(col_labels),
string.(row_labels),
fill(0.92, size(matrix_vals)), # all tiles are light gray
xlabel = "", ylabel = "",
color = cgrad([RGB(0.92,0.92,0.92), RGB(0.92,0.92,0.92)]), # enforce light gray
xticks=:auto,
yticks=:auto,
yflip=true,
framestyle = :box,
annotations=ann,
colorbar=false
)
# Adding grid lines
n_rows, n_cols = size(matrix_vals)
for v in 1:n_cols-1
vline!([v], c=:black, lw=.5, label=nothing)
end
for h in 1:n_rows-1
hline!([h], c=:black, lw=.5, label=nothing)
end
# Simple layout
l = @layout [
a{0.7h}
b{0.3h}
]
plot(plot_lines, plot_df, layout = l)