84 lines
7.8 KiB
Markdown
84 lines
7.8 KiB
Markdown
# Phase 1: Графики + интеракции
|
||
|
||
**Status:** ✅ Implemented (не закоммичено — коммит за оркестратором)
|
||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||
**Domain:** frontend
|
||
|
||
## Objective
|
||
Добавить в рантайм графики (plot-объекты), перетаскиваемые ручки (drag → параметр),
|
||
векторы и числовые readout. После фазы спека со слайдером, draggable-точкой и live-графиком работает.
|
||
|
||
## Tasks
|
||
- [x] Plot-объект в спеке: `{ type:'plot', expr:'...', var:'x', range:[a,b], samples, trace? }` —
|
||
рисует график выражения; `trace:true` — накапливает след по `t`. Решение: рисуем на canvas движка в мир-координатах (НЕ тянем `GraphPanelUI` — он stacked time-series в фикс. оверлее, не `y=f(x)` инлайн). `samples` деф. 200, клампится 2..2000.
|
||
- [x] Draggable-ручка: `point`/`circle` с `drag:{ param, axis:'x|y|xy', min,max, paramY? }` — перетаскивание мышью/тачем (pointer events) меняет параметр(ы); позиция ручки следует за параметром (x/y объекта = тот же параметр). Хит-тест в экранных px (допуск 16px), приоритет ручек. Clamp по `drag.min/max` И по диапазону самого параметра.
|
||
- [x] Readout: `{ type:'readout', label, expr, unit, precision, x?, y? }` — живой бейдж; мягкая ошибка через `evalSafe` (NaN/синтаксис → «—», не роняет цикл). Без позиции — авто-столбик в верх-правом углу.
|
||
- [x] Vector-объект с привязкой к `origin:[ox,oy]` + `dx`/`dy`-выражениям + стрелка (x1/y1/x2/y2 тоже поддержаны). Стрелка уже была в Ф0.
|
||
- [x] Тач-поддержка drag (pointer events + `touchAction:none`), не ломая логику лабы (слушатели только на canvas движка, снимаются в destroy).
|
||
- [x] Обновить демо-спеку: +слайдеры x0/y0, draggable-старт (axis xy), plot траектории y(x), 2 readout (дальность R, высота H).
|
||
|
||
## Files to Modify/Create
|
||
- `frontend/js/labs/_sim_engine.js` — типы plot/readout/vector, drag-интеракции (modify)
|
||
- `frontend/js/labs/_sim_demo.js` — расширить демо (modify)
|
||
|
||
## Acceptance Criteria
|
||
- Перетаскивание ручки меняет параметр; зависимые объекты/график обновляются.
|
||
- График строится по выражению; trace накапливает след во времени.
|
||
- Readout показывает живое значение. Тач работает.
|
||
|
||
## Notes
|
||
- Drag не должен конфликтовать с pan/zoom рантайма (если есть). Приоритет хит-теста — ручки.
|
||
- Сэмплинг графика разумный (без фриза на больших range).
|
||
|
||
## Review Checklist
|
||
- [x] Все задачи выполнены
|
||
- [x] Drag работает мышью и тачем (pointer events; headless-тест clamp/sync OK)
|
||
- [x] Нет регрессий рантайма Ф0 (рендер всех 8 типов демо × 6 кадров без ошибок; point/segment/circle/rect/polyline/path/vector/label не тронуты в логике)
|
||
- [x] Нет эмодзи, стиль проекта (скан кодпойнтов — чисто; IIFE-стиль)
|
||
|
||
## Handoff to Next Phase
|
||
|
||
### Что готово (Phase 1) — только `_sim_engine.js` + `_sim_demo.js`
|
||
- **plot** — график выражения `f(var)` на отрезке, рисуется на canvas движка в мир-координатах.
|
||
- **drag** — `point`/`circle` становятся ручками; pointer events (мышь+тач); clamp двойной (drag.min/max + диапазон параметра).
|
||
- **readout** — живой бейдж на оверлее (тот же `_labelLayer`), мягкая ошибка через `SimExpr.evalSafe`.
|
||
- **vector** — добавлена форма `origin:[ox,oy]+dx/dy` (конец = origin + (dx,dy)); старая `x1/y1/x2/y2` сохранена; стрелка из Ф0.
|
||
|
||
### Формат новых типов спеки (точные поля)
|
||
```jsonc
|
||
// график выражения (мир-координаты)
|
||
{ type:'plot', expr:'sin(x)', var:'x', // var деф. 'x'
|
||
range:[a,b], // числа/выражения; деф. xmin..xmax
|
||
samples?:200, // клампится 2..2000
|
||
trace?:false, // true: точка (var=t) пишется в trail по времени;
|
||
// при trace без range статич. кривая НЕ рисуется
|
||
color?, width? }
|
||
|
||
// перетаскиваемая ручка (на point/circle)
|
||
{ type:'point', x:'x0', y:'y0',
|
||
drag:{ param:'x0', // axis x|y -> этот параметр; xy -> X
|
||
axis:'x'|'y'|'xy', // деф. 'x'
|
||
paramY:'y0', // ТОЛЬКО axis:'xy' -> Y (обязателен для 2D)
|
||
min?, max? } } // деф. ±Infinity; доп. clamp по диапазону параметра
|
||
|
||
// живой числовой бейдж
|
||
{ type:'readout', expr:'...', label?:'R', unit?:'м', precision?:2, // precision 0..8, деф.2
|
||
x?, y?, // мир-коорд.; без них — авто-столбик верх-право
|
||
color? }
|
||
|
||
// вектор (новая форма)
|
||
{ type:'vector', origin:[ox,oy], dx:'...', dy:'...', color?, width? }
|
||
```
|
||
|
||
### API инстанса (без изменений сигнатуры)
|
||
`mount(host,spec) -> { play, pause, reset, setParam, getParam, isRunning, destroy, el }`.
|
||
Добавлены внутр.: `_toWorld(px,py)`, `_setupDrag`, `_applyDrag`, `_setParamClamped`, `_drawPlot`, `_drawReadout`, `_accumPlotTrace`, `_paramRange`.
|
||
|
||
### Осталось / риски / на Фазу 2 (физика)
|
||
- **Однопроходный env** (`obj.x/obj.y`): из Ф0 — взаимные «вперёд»-ссылки дают значение прошлого кадра. Не трогал; при физике может потребоваться топосорт.
|
||
- **Drag только point/circle.** Тащить за конец вектора/вершину polyline — не реализовано (не требовалось).
|
||
- **readout позиционирование** на canvas — через DOM-оверлей (`_labelLayer`), как label. На сервере (Ф3) `label`/`unit` readout надо санитизировать как текст.
|
||
- **plot и trace на больших range**: ограничены `samples<=2000` и trail `<=2000` точек — без фриза. Очень большие range с тонкой кривой при экстремальном zoom могут ступенчатить — норм для учебных сцен.
|
||
- **Физика (`body`/`physics`)** — Фаза 2. Plot/drag/readout/vector полностью совместимы с физ-объектами (drag может задавать начальные условия, readout — читать body-величины, если их положить в env в Ф2).
|
||
- ⛔ `lab.html` и `lab-glue.js` НЕ трогались (зона параллельной сессии). Новых файлов не создавал — всё в `_sim_engine.js`/`_sim_demo.js`.
|