# Shared geometry
FIGSIZE_BOTH = (16.5, 8)
HAX_RECT = [0.09, 0.18, 0.55, 0.77]
CBAR_RECT = [0.09, 0.035, 0.275, 0.038]
_H_SPARK = 0.17; _SPARK_X = 0.685; _SPARK_W = 0.26
_SG = 0.02; _LG = 0.06; _SB = HAX_RECT[1]
SPARK_RECTS = [
[_SPARK_X, _SB + 3*_H_SPARK + 2*_SG + _LG, _SPARK_W, _H_SPARK], # a1 (top)
[_SPARK_X, _SB + 2*_H_SPARK + 1*_SG + _LG, _SPARK_W, _H_SPARK], # a2
[_SPARK_X, _SB + 1*_H_SPARK + 1*_SG, _SPARK_W, _H_SPARK], # b1
[_SPARK_X, _SB, _SPARK_W, _H_SPARK], # b2 (bottom)
]
fig_ci = plt.figure(figsize=FIGSIZE_BOTH)
ax_ci = fig_ci.add_axes(HAX_RECT)
cbar_ax_ci = fig_ci.add_axes(CBAR_RECT)
with warnings.catch_warnings():
warnings.simplefilter('ignore')
_, _ = multi_gtex.whorlmap(
cmap=CMAP, chop_tail=2.5, vmin=-vabs, vmax=vabs,
ax=ax_ci, heatmap_kwargs={'cbar': False, 'linewidths': 0},
)
ax_ci.set_aspect('equal')
for _c in ax_ci.collections:
_c.set_rasterized(True)
ax_ci.tick_params(axis='x', rotation=45, labelsize=11)
ax_ci.tick_params(axis='y', rotation=0, labelsize=11)
_add_shared_cbar(cbar_ax_ci, orientation='horizontal')
# Extend limits by 3 units on all sides so edge indicator squares aren't clipped
_yl = ax_ci.get_ylim()
_xl = ax_ci.get_xlim()
ax_ci.set_ylim(_yl[0] + 3, _yl[1] - 3)
ax_ci.set_xlim(_xl[0] - 3, _xl[1] + 3)
_mn = mean_df.values
for _idx, (_ri, _ci, _pi, _wi, _lbl) in enumerate(_ORDERED_CELLS):
_mean_val = float(_mn[_ri, _ci])
_bs = _pair_bs[_pi][_wi]
_hl_col = _PAIR_COL[_pi]
_cx0, _cy0 = _ci * 21, _ri * 21
# Expand rect by 1 unit on each side so it clears the outermost ring
ax_ci.add_patch(_Rect((_cx0 - 1, _cy0 - 1), 23, 23,
fill=False, edgecolor=_hl_col, linewidth=2.0, zorder=12))
ax_ci.text(_cx0 + 2.5, _cy0 + 5, _lbl,
fontsize=5.5, fontweight='bold', color=_hl_col, zorder=13, va='top', ha='left')
_sr = SPARK_RECTS[_idx]
_sax = fig_ci.add_axes(_sr)
_xlo, _xhi = _pair_xlims[_pi]
_xr = np.linspace(_xlo, _xhi, 300)
_yr = _gkde(_bs, bw_method='silverman')(_xr)
# Fill KDE with cmap colours keyed to x-value, matching Panel B histogram style
_dx = _xr[1] - _xr[0]
_bar_cols = CMAP(shared_norm(_xr[:-1] + _dx / 2))
for _x, _y, _bc in zip(_xr[:-1], _yr[:-1], _bar_cols):
_sax.bar(_x, _y, width=_dx, align='edge', color=_bc, edgecolor='none')
_sax.plot(_xr, _yr, color=_hl_col, lw=0.9)
_sax.axvline(0, color='#aaaaaa', lw=0.6, ls=(0, (3, 3)))
_sax.axvline(_mean_val, color=_hl_col, lw=1.2)
_sax.set_xlim(_xlo, _xhi); _sax.set_ylim(0, _pair_ymax[_pi])
_sax.set_yticks([]); _sax.set_xticks([0])
_sax.set_xticklabels(['0'], fontsize=5, color='#888')
for _sp in ['top', 'right', 'left', 'bottom']:
_sax.spines[_sp].set_visible(False)
_sax.tick_params(length=2, width=0.5, pad=1)
_sax.text(0.02, 0.97, _lbl, transform=_sax.transAxes,
fontsize=7, fontweight='bold', ha='left', va='top', color=_hl_col)
# Gene · region name in top-right corner, inside the axes
_sax.text(0.98, 0.88, f'{RIGHT_GENES[_ci]} · {region_order[_ri]}',
transform=_sax.transAxes, fontsize=7, ha='right', va='top',
style='italic', color='#444')
# Orthogonal connection lines
fig_ci.canvas.draw()
def _d2f(_xd, _yd):
_disp = ax_ci.transData.transform((_xd, _yd))
return tuple(fig_ci.transFigure.inverted().transform(_disp))
_xlim_r = ax_ci.get_xlim()[1]
for _idx, (_ri, _ci, _pi, _wi, _lbl) in enumerate(_ORDERED_CELLS):
_hl_col = _PAIR_COL[_pi]
_sr = SPARK_RECTS[_idx]
_p1 = _d2f((_ci + 1) * 21, _ri * 21 + 10.5)
_p2 = _d2f(_xlim_r, _ri * 21 + 10.5)
_p3 = (_sr[0], _sr[1] + _sr[3] / 2)
for _xs, _ys in [([_p1[0], _p2[0]], [_p1[1], _p2[1]]),
([_p2[0], _p3[0]], [_p2[1], _p3[1]])]:
fig_ci.add_artist(_L2D(_xs, _ys, transform=fig_ci.transFigure,
color=_hl_col, lw=0.85, zorder=15, solid_capstyle='round'))
fig_ci.savefig(IMAGES / 'panel_ci.svg', dpi=600, bbox_inches='tight')
fig_ci.savefig(IMAGES / 'panel_ci.png', dpi=600, bbox_inches='tight')
plt.show()