§6 · Подписи и автораскладка

Подпись в AnimaGeo — отдельный TeX-объект, не часть элемента. Позиция задаётся парой: 9-точечный якорь + pixel-offset. Для сложных сцен работает автоматическая раскладка: жадный решатель минимизирует перекрытия, углы размещаются аналитически по биссектрисе.

Базовое управление

Ключ elem.styleТипDefaultЭффект
label_visibleboolзависит от типаОтрисовать подпись рядом с элементом.
label_textстрока"$name$"Текст (TeX или Unicode).
label_modelabel / value / label_valuelabelПоказывать текст подписи, значение или label = value.
label_value_precisionintrendering.label_value_precision / 1Округление численной части.
label_angle_unitdegree / radiandegreeЕдиницы для значений углов.
label_colorhexstyle.strongЦвет подписи.
label_anchorTL..BRrendering.label_anchor / BLКакая точка bbox «прибивается» к якорной позиции.
label_offset_px[dx, dy] px[0, 0]Доп. сдвиг после якоря. Положительный y — вверх.
font_size_pxfloatdefaults.<type>.font_size_pxPer-element размер шрифта в пикселях.
label_placement_lockedboolfalseЗащита от автораскладки — ручной label_offset_px остаётся.

9-точечный якорь

9-точечный якорь
Якорь — это та точка bbox, которую «прибивают» к позиции рядом с элементом.
TL
TC
TR
ML
MC
MR
BL
BC
BR

Как смотрится каждый якорь

BL
BL (справа-сверху)
BC
BC (сверху)
TC
TC (снизу)
MR
MR (слева)
MC
MC (поверх)
Сценовый default — rendering.label_anchor в JSON-стиле. Per-element elem.style['label_anchor'] его перекрывает.

Offset — сдвиг подписи

offset=[dx, dy] — в пикселях, после якорения. По конвенции математических координат: положительный y направлен вверх. При смене px_size offset остаётся pixel-invariant (делится на ptUnit_ggb, зафиксированный на момент импорта).

offset · правьте координаты; смещения подписей — через overrides
P = Point(-2, 0)
Q = Point(2, 0)

style(P, Q, label_visible=True)
P.style.label_offset_px = [0, 0]
Q.style.label_offset_px = [14, 16]
Label offsets
Скачать: 16-label-offsets.py

TeX и Unicode

Подписи компилируются через pdfLaTeX с шаблоном RusTex (T2A + babel russian + utf8). В ui.py есть кастомная обработка дробей, уменьшенные \angle и \triangle, и словарь автозамен Unicode → TeX.

ЗаписьЧто компилируетРезультат
'$A_1$'raw-TeXA₁ (с индексом)
r'$\alpha + \beta$'raw-TeXα + β
r'$\frac{a}{b}$'raw-TeXдробь a/b
r'$\triangle ABC$'raw-TeX△ABC
r'$x \in \mathbb{R}$'raw-TeXx ∈ ℝ
'α + β = γ'Unicode-autoreplace → TeXα + β = γ
TeX labels
шесть типичных TeX-подписей

Автозамена Unicode

В ui.py::correctedLabel() прописан словарь из 96 замен:

·   → \cdot         α → \alpha      △ → \triangle
×   → \times        β → \beta       ∠ → \angle
∈   → \in           π → \pi         ±  → \pm
⇔   → \Leftrightarrow                ∞  → \infty
...

Следствие: можно писать label = 'α + β = γ' обычным Unicode — TeX получит корректное $\alpha + \beta = \gamma$. Удобно, если источник подписей — таблица, CSV или GGB-файл с Unicode-текстом.

Автораскладка

Когда подписей много, они начинают перекрывать друг друга и сами элементы. AnimaGeo решает это автоматически: вызовите scene.autoPlaceLabels() перед exportSVG.

Алгоритм — жадный решатель с приоритетом:

  1. Для каждой подписи генерируется 8 кандидатов (E, NE, N, NW, W, SW, S, SE) на базовом расстоянии distance_px.
  2. Каждому кандидату назначается штраф: за направление относительно «идеального», за пересечение с другими подписями, за пересечение с геометрией.
  3. Подписи сортируются по «сложности» (мало свободных позиций → первыми) и размещаются жадно.
  4. Если все 8 кандидатов имеют штраф — радиус ступенчато увеличивается (до +10 px) пока не найдётся чистая позиция.

Для углов — аналитика вместо 8 вариантов: подпись садится на биссектрису, отодвигается на arc_r + half_label_size + gap_arc_px.

Подпись, расставленная в GeoGebra, остаётся на месте аплета для каждого типа элемента — отрезков, векторов, лучей, полигонов, окружностей, эллипсов, дуг и секторов (правила — animageo/label_anchor.py). При экспорте в другом масштабе подпись остаётся привязанной к своей части элемента и не уезжает. Окружности и эллипсы рисуют подписи.

До и после

Before autoPlaceLabels
до: offset из GGB или дефолты
After autoPlaceLabels
после autoPlaceLabels()
autoPlaceLabels · попробуйте сдвинуть точку C и пересчитать
A = Point(-2, 1.5)
B = Point(2, 1.5)
C = Point(0, -1.5)
s1 = Segment(A, C)
s2 = Segment(B, C)
s3 = Segment(A, B)
M = Midpoint(A, B)
D = Midpoint(A, C)
E = Midpoint(B, C)

style(A, B, C, D, E, M, label_visible=True)
Auto-placed labels
Скачать: guide_style.json

Углы: два независимых зазора

Подпись угла садится на биссектрисе снаружи дуги. Две независимые величины определяют, насколько далеко:

arc_r — это эффективный внешний радиус: для одной дуги равен arc_size_px; при lines ≥ 2 — arc_size_px + (lines − 1) × ang_rshift. Подпись автоматически уходит за самую внешнюю дугу, не налезая.

Ручная блокировка

scene.element('A').style['label_placement_locked'] = True
scene.element('A').style['label_offset_px'] = [10, -5]
# autoPlaceLabels обойдёт A стороной; ручной offset останется.

Конфигурация автораскладки

Все параметры — в overlay.label_placement в JSON-стиле:

ПараметрDefaultОписание
enabledfalseВызвать autoPlaceLabels() автоматически в конце loadGGB.
distance_px6Базовое расстояние якорь → центр подписи, пикселей.
padding_px2Зазор вокруг bbox для overlap-тестов.
angle_gap_arc_px3Зазор дуга угла → подпись, пикселей.
angle_gap_sides_px3Зазор стороны угла → bbox (narrow-angle clamp).
angle_gap_px3Legacy: fallback, если два параметра выше не заданы.
w_anchor1.0Штраф за отклонение от «идеального» направления.
w_label10.0Штраф за пересечение с другими подписями.
w_geom8.0Штраф за пересечение с геометрией.
dynamic_anglesfalseПересчитывать биссектрисы каждый кадр (нужен anim-режим, см. ниже).
keyframe_snapshotsfalseДля play_keyframes: snapshot на каждом keyframe + интерполяция. Работает только при enabled=true.
canonicalize_anchorfalseВсё в MC — убирает дискретные скачки якоря между кадрами.
interpolationlinearEasing offset между snapshot-ами: linear / smooth.
ema_alpha0.2Вес свежего решения в EMA (dynamic tracker).
anchor_flip_frames6Минимум кадров перед сменой якоря (hysteresis).
solver_every_n_frames2Throttle решателя в tracker-режиме.
dynamic_angles=true сам по себе ничего не делает. Ему нужен один из активных режимов: keyframe_snapshots=true вместе с enabled=true (для play_keyframes) или autoPlaceLabels(dynamic=True) (для addUpdater-анимаций). Это сделано сознательно: стоимость per-frame-вычислений должна быть явной.

Подписи в анимации

Три режима работы в анимации:

РежимКогдаЧто делает
Статикаscene.autoPlaceLabels()One-shot: решатель запускается один раз, результат замораживается.
Keyframe-snapshotoverlay.label_placement.keyframe_snapshots=truePre-pass на каждом keyframe (со сохранением/восстановлением состояния), offset между snapshot-ами интерполируется.
Per-frame trackerautoPlaceLabels(dynamic=True)Ставит LabelTracker в сцену: каждый кадр решатель перезапускается, результат сглаживается через EMA, якоря меняются только после N стабильных кадров (hysteresis).

Рекомендуемая конфигурация для keyframes

"rendering": {
    "label_placement": {
        "enabled":              true,
        "keyframe_snapshots":   true,
        "dynamic_angles":       true,
        "canonicalize_anchor":  true,
        "interpolation":        "smooth"
    }
}

Что получается: на каждом keyframe solver находит лучшую раскладку, все подписи приводятся к якорю MC (исключает скачки между кадрами), offset-ы плавно интерполируются smoothstep-ом, биссектрисы углов обновляются аналитически каждый кадр.

Живые примеры — в §8 (Анимации).