2025, Sep 28 03:16

Границы тепловой карты Altair исчезают при экспорте: причина и решение

Разбираем, почему в Altair при экспорте PNG/SVG/PDF исчезают границы тепловой карты в Jupyter: причина в stroke=w. Решение — используйте white или #eeeeee.

Тепловые карты Altair могут безупречно выглядеть в Jupyter Notebook, но при сохранении через save() в PNG, SVG или PDF терять границы ячеек. Если в предпросмотре видно толстую сетку, а на сохранённых изображениях — сверхтонкие линии или вовсе нет обводки, причина нередко — безобидное на вид цветовое сокращение.

Как воспроизвести проблему

Пример ниже рисует тепловую карту с широкой обводкой в ноутбуке, но на экспортированных изображениях обводка пропадает или становится ультратонкой.

import altair as alt
import pandas as pd
# пример набора данных
frame_src = pd.DataFrame({
    'X': [1,2,3,4,5,6,7,8,9,10],
    'Y': [1,2,3,4,5,6,7,8,9,10],
    'Intensity': [0,0,0,0,0,1,1,1,1,1]
})
levels = [0, 1]
palette = ['#208943', '#c0e6ba']
# тепловая карта с толстой обводкой; в ноутбуке выглядит корректно
map_fig = alt.Chart(frame_src).mark_rect(stroke='w', strokeWidth=5).encode(
    x=alt.X('X:N'),
    y=alt.Y('Y:N'),
    color=alt.Color('Intensity:N', scale=alt.Scale(domain=levels, range=palette))
)
# при экспорте границы могут исчезать или истончаться
map_fig.save('test.png')
map_fig.save('test.svg')
map_fig.save('test.pdf')
map_fig

Почему так происходит

Такое поведение соответствует известной проблеме в Altair/Vega, которая возникает при использовании stroke='w'. Значение w не является корректной строкой цвета HTML. Рендерер в ноутбуке отображает границы как ожидается, но конвейер экспорта не распознаёт w как валидный цвет, и обводка либо пропадает, либо рисуется непоследовательно. Поэтому в PNG/SVG иногда видны сверхтонкие линии, а в PDF границы могут исчезать полностью.

Как исправить

Используйте корректное значение цвета для обводки. Достаточно заменить на нормальное имя цвета, например 'white', — и границы станут одинаковыми в предпросмотре и в сохранённых файлах. Если чисто белый сливается с фоном в вашей среде, можно взять светло‑серый вроде '#eeeeee': он улучшит заметность, сохранив ту же компоновку.

import altair as alt
import pandas as pd
# пример набора данных
frame_src = pd.DataFrame({
    'X': [1,2,3,4,5,6,7,8,9,10],
    'Y': [1,2,3,4,5,6,7,8,9,10],
    'Intensity': [0,0,0,0,0,1,1,1,1,1]
})
levels = [0, 1]
palette = ['#208943', '#c0e6ba']
# исправлено: используйте валидное значение цвета для обводки
map_fig_fixed = alt.Chart(frame_src).mark_rect(stroke='white', strokeWidth=5).encode(
    x=alt.X('X:N'),
    y=alt.Y('Y:N'),
    color=alt.Color('Intensity:N', scale=alt.Scale(domain=levels, range=palette))
)
map_fig_fixed.save('test.png')
map_fig_fixed.save('test.svg')
map_fig_fixed.save('test.pdf')
map_fig_fixed

Альтернатива для лучшей читаемости:

map_fig_gray = alt.Chart(frame_src).mark_rect(stroke='#eeeeee', strokeWidth=5).encode(
    x=alt.X('X:N'),
    y=alt.Y('Y:N'),
    color=alt.Color('Intensity:N', scale=alt.Scale(domain=levels, range=palette))
)
map_fig_gray.save('test.png')

Зачем это важно

Графики часто встраивают в отчёты, PDF и дашборды. Если стиль корректен в ноутбуке, но исчезает в экспортированных файлах, это ухудшает читаемость и нарушает единообразие. Использование валидных цветовых значений предотвращает расхождения и сохраняет визуализацию одинаковой во всех средах и форматах.

Выводы

Если при экспорте Altair границы исчезают или выглядят неодинаково, в первую очередь проверьте указанные цвета. Не используйте сокращения вроде w, выбирайте корректные HTML‑имена или hex‑коды. Небольшое изменение — stroke='white' или деликатный stroke='#eeeeee' — уже позволяет сделать сохранённые изображения такими же, как в Jupyter.

Статья основана на вопросе с StackOverflow от Yuli и ответе Wayne.