# Graph Component

## Описание

График - это UI-компонент, который позволяет визуализировать данные в виде динамических графиков. Компонент поддерживает два режима работы:

- **Живой (потоковый) режим** — точки добавляются по одной по мере поступления `streamingData`, X-ось показывает "N секунд назад" относительно текущего момента.
- **Статический (исторический) режим** — компонент получает уже готовый массив точек через `historyData` (каждая со своим реальным `timestamp`) и рисует весь ряд разом, используя настоящие дату/время на оси X.

Режим определяется автоматически: если проп `historyData` задан и непустой, компонент переходит в статический режим (`streamingData`/`refreshRate` в этом случае игнорируются).

Отдельного пропа для масштаба вертикальной оси нет — в обоих режимах Y-ось масштабируется автоматически по фактическим данным (±10% отступ). В живом режиме диапазон плавно "подъезжает" к новому min/max буфера (сглаживание), а не пересчитывается резко на каждый семпл, чтобы сетка не дёргалась.

## Пропсы

| Название                           | Тип                                         | По умолчанию          | Описание                                                                                                                                                               |
| ---------------------------------- | ------------------------------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                               | `string`                                    | `crypto.randomUUID()` | Уникальный идентификатор компонента                                                                                                                                    |
| `wrapperClass`                     | `string`                                    | `""`                  | Дополнительные CSS-классы для обертки компонента                                                                                                                       |
| `label`                            | `object`                                    | `-`                   | Настройки заголовка компонента                                                                                                                                         |
| `label.name`                       | `string`                                    | `""`                  | Текст заголовка компонента                                                                                                                                             |
| `label.class`                      | `string`                                    | `""`                  | CSS-классы для стилизации текста заголовка                                                                                                                             |
| `streamingData`                    | `object`                                    | `-`                   | Потоковые данные для живого режима                                                                                                                                     |
| `streamingData.data`               | `{ name: string; value: number }[] \| null` | `[]`                  | Массив данных для отображения. Режим AUTO (`refreshRate: 0`) определяет появление новых данных сравнением самих значений с предыдущим пакетом, а не по временной метке |
| `streamingData.data[].name`        | `string`                                    | `-`                   | Имя серии                                                                                                                                                              |
| `streamingData.data[].value`       | `number`                                    | `-`                   | Текущее значение серии                                                                                                                                                 |
| `isTest`                           | `boolean`                                   | `false`               | Режим тестирования (генерация случайных данных); также форсирует `refreshRate` = 50мс                                                                                  |
| `refreshRate`                      | `number`                                    | `0`                   | Интервал отрисовки точки в живом режиме, мс. `0` = **AUTO** — точка рисуется, когда значения реально изменились (без таймера)                                          |
| `historyData`                      | `IGraphHistorySeries[]`                     | `[]`                  | Готовый исторический ряд — если задан (непустой), включает статический режим (см. ниже)                                                                                |
| `historyData[].name`               | `string`                                    | `-`                   | Имя серии (отображается в подписи и в таблице значений)                                                                                                                |
| `historyData[].points`             | `IGraphHistoryPoint[]`                      | `-`                   | Точки серии                                                                                                                                                            |
| `historyData[].points[].value`     | `number`                                    | `-`                   | Значение точки                                                                                                                                                         |
| `historyData[].points[].timestamp` | `number`                                    | `-`                   | Unix-эпоха в миллисекундах (как `Date.now()`). Точки сортируются компонентом по `timestamp` при построении графика — исходный порядок в массиве не важен               |

`refreshRate` относится только к живому режиму и сохраняется вместе с компонентом при сборке интерфейса в конструкторе (редактируется через `GraphProps.svelte`, см. ниже) — это стартовое значение при монтировании; остаётся переключаемым "на лету" прямо на самом графике через встроенный `Select`. В статическом режиме (`historyData`) этот селект скрыт, так как не имеет эффекта.

## События

Компонент не генерирует специфические события.

## Примеры

### Базовое использование

```svelte
<script>
  import * as UI from "poe-svelte-ui-lib"

  const dataForGraph = [
    { name: "Roll", value: 0 },
    { name: "Pitch", value: 0 },
    { name: "Yaw", value: 0 },
  ]
</script>

<UI.Graph label={{ name: "Пример компонента график" }} streamingData={{ data: dataForGraph }} isTest={true} />
```

### С потоковыми данными

```svelte
<script>
  import * as UI from "poe-svelte-ui-lib"

  let data = $state({
    data: [
      { name: "Temperature", value: 25 },
      { name: "Humidity", value: 60 },
      { name: "Pressure", value: 1013 },
    ],
  })

  // Обновление данных в реальном времени — при refreshRate: 0 (AUTO) новая точка
  // рисуется на каждое изменение значений, без привязки к частоте setInterval здесь
  setInterval(() => {
    data = {
      data: [
        { name: "Temperature", value: Math.random() * 50 },
        { name: "Humidity", value: Math.random() * 100 },
        { name: "Pressure", value: 1000 + Math.random() * 50 },
      ],
    }
  }, 1000)
</script>

<UI.Graph label={{ name: "График с потоковыми данными" }} streamingData={data} refreshRate={0} />
```

### Статический режим (historyData)

```svelte
<script>
  import * as UI from "poe-svelte-ui-lib"

  const now = Date.now()
  const hourMs = 60 * 60 * 1000

  // Готовый исторический ряд, например потребление электроэнергии за сутки с шагом в час
  const historyExample = [
    {
      name: "P, кВт·ч",
      points: Array.from({ length: 24 }, (_, i) => ({
        timestamp: now - (23 - i) * hourMs,
        value: 30 + Math.round(Math.sin(i / 3) * 20),
      })),
    },
  ]
</script>

<UI.Graph label={{ name: "Потребление за сутки" }} historyData={historyExample} />
```

При наведении курсора на график появляется всплывающая подсказка с точным временем и значениями всех серий в ближайшей к курсору временной точке.

## Внутренняя архитектура

### Реактивность

- Переменная `graphData` хранит точки графиков для отображения (в обоих режимах)
- Переменная `currentValues` хранит текущие значения переменных — в живом режиме это последний полученный пакет, в статическом — последняя точка каждой серии из `historyData`
- Переменная `previousValues` хранит значения предыдущего пакета — используется в режиме AUTO, чтобы отличить "пришли новые данные" от повторного опроса
- Переменная `isHistoryMode` — `$derived` от наличия непустого `historyData`; определяет режим работы компонента целиком (без отдельного булевого пропа)
- Переменная `selectedRefreshRate` — `$derived` от пропа `refreshRate` (и `isTest`); присвоение через встроенный `Select` временно переопределяет значение, пока не изменится сам проп; в статическом режиме `Select` скрыт
- Переменная `liveYRange` хранит текущий (сглаженный) диапазон Y-оси живого режима; обновляется функцией `updateLiveYRange()`, вызываемой один раз за тик (после добавления новой точки в интервале), а не за кадр — целевой диапазон берётся из фактических min/max буфера (±10% отступ, `computeAutoYRange()`) и приближается к нему экспоненциальным сглаживанием (коэффициент 0.15), чтобы сетка не дёргалась на каждый семпл. В статическом режиме диапазон считается разово по всему массиву той же `computeAutoYRange()`
- Объект `REFRESH_OPTIONS` экспортируется из `<script module>` компонента — переиспользуется в `GraphProps.svelte`, чтобы не дублировать список вариантов
- Переменная `tooltip` хранит состояние всплывающей подсказки (позиция, время, значения серий); обновляется по `onmousemove`/`onmouseleave` канваса, поиск ближайшей точки — бинарным поиском по отсортированному массиву точек серии

### Сторы и зависимости

- Используется `ResizeObserver` для отслеживания изменений размера контейнера
- Используется `CanvasRenderingContext2D` для отрисовки графиков
- Применяется `devicePixelRatio` для корректного отображения на устройствах с высоким DPI

### Директивы

- `bind:this` для получения ссылок на DOM-элементы (container, canvas)
- `$effect` для обработки изменений данных и настроек
- `onMount` для инициализации данных графика

### Слоты

- Компонент не использует внешних слотов

## Конструктор свойств (GraphProps.svelte)

### Описание

Компонент `GraphProps.svelte` предоставляет визуальный интерфейс для редактирования свойств компонента графика. Поддерживает два режима отображения: для конструктора и для редактирования.

### Пропсы конструктора

| Название           | Тип                                                                                                                                 | По умолчанию | Описание                                                |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | ------------ | ------------------------------------------------------- |
| `component`        | `UIComponent & { properties: Partial<IGraphProps> }`                                                                                | `-`          | Объект компонента с его свойствами                      |
| `onPropertyChange` | `(updates: Partial<{ properties?: string \| object; name?: string; access?: string; eventHandler?: IUIComponentHandler }>) => void` | `-`          | Коллбэк для обновления свойств компонента               |
| `forConstructor`   | `boolean`                                                                                                                           | `true`       | Режим отображения (для конструктора или редактирования) |

### Особенности конструктора

- Предоставляет визуальные элементы управления для всех доступных свойств, включая `refreshRate` (`constructor.props.refreshrate`) через `Select` с теми же вариантами, что и на самом графике (`REFRESH_OPTIONS` из `Graph.svelte`)
- Поддерживает настройку переменной для отображения
- Включает переключатель для режима тестирования
- Имеет различные наборы полей в зависимости от режима (`forConstructor`)
- Использует систему локализации через `$T('constructor.props.*')`

## Заметки

### Адаптивность

- Компонент использует Tailwind CSS для адаптивного дизайна
- График автоматически масштабируется под размер контейнера
- Используются относительные единицы измерения для обеспечения масштабируемости

### Ограничения

- Компонент ограничен максимальным количеством отображаемых графиков (6)
- При отсутствии данных график остается пустым
- Частота обновления влияет на производительность при большом объеме данных (актуально только для живого режима)
- В статическом режиме (`historyData`) данные рисуются один раз при получении массива — последующие изменения `historyData` (например, замена на новый массив) перестраивают график заново, инкрементального добавления точек нет

### Производительность

- Компонент использует оптимизации Svelte для минимизации перерисовок
- Используется Canvas API для эффективной отрисовки графиков
- Автоматическая очистка точек при превышении лимита для предотвращения утечки памяти
