Deck.gl предоставляет высокопроизводительную архитектуру для работы с пространственными и временными данными, где ключевым фактором становится возможность рендеринга больших массивов точек и линий на GPU без потери интерактивности. Временные ряды в таком контексте рассматриваются не как статические наборы значений, а как непрерывные или дискретные изменения состояния объектов во времени, требующие эффективного представления, фильтрации и анимации.
Временные ряды в визуализации обычно задаются структурами вида:
Типичная форма данных для Deck.gl:
const data = [
{
id: "sensor-1",
values: [
{ t: 1710000000000, v: 12.3 },
{ t: 1710003600000, v: 15.1 },
{ t: 1710007200000, v: 13.8 }
]
}
];
При работе с WebGL критически важно избегать избыточной вложенности в рендер-цикле. Поэтому данные часто предварительно нормализуются:
const flatData = [
{ id: "sensor-1", t: 1710000000000, v: 12.3 },
{ id: "sensor-1", t: 1710003600000, v: 15.1 },
{ id: "sensor-1", t: 1710007200000, v: 13.8 }
];
Такой формат лучше подходит для GPU-ориентированных слоёв.
Основной инструмент отображения временных рядов значений — LineLayer. Он соединяет точки в порядке времени, формируя непрерывную кривую.
Базовая конфигурация:
import { LineLayer } from '@deck.gl/layers';
const layer = new LineLayer({
id: 'time-series-line',
data: flatData,
getSourcePosition: d => [d.t, d.v],
getTargetPosition: (d, {index, data}) => {
const next = data[index + 1];
return next ? [next.t, next.v] : [d.t, d.v];
},
getColor: [0, 140, 255],
widthMinPixels: 2
});
Временная ось часто отображается как X-координата, однако Deck.gl не ограничивает семантику осей: время может быть закодировано в любой компоненте пространства.
Временные ряды редко бывают полностью непрерывными. Пропуски данных требуют явного управления сегментацией.
Подходы:
Пример сегментации:
function segmentByGap(data, maxGap) {
const segments = [];
let current = [];
for (let i = 0; i < data.length; i++) {
const prev = data[i - 1];
if (prev && data[i].t - prev.t > maxGap) {
segments.push(current);
current = [];
}
current.push(data[i]);
}
if (current.length) segments.push(current);
return segments;
}
Каждый сегмент затем передаётся как отдельный набор в слой.
Ключевая особенность временной визуализации — проигрывание данных во времени. В Deck.gl это реализуется через обновление фильтров или uniform-параметров.
Простейшая схема:
const layer = new LineLayer({
id: 'animated-series',
data: flatData,
getSourcePosition: d => [d.t, d.v],
getTargetPosition: d => [d.t, d.v],
getColor: d => (d.v > 15 ? [255, 0, 0] : [0, 200, 100]),
filterRange: [startTime, currentTime],
extensions: []
});
Обновление текущего времени:
function tick() {
currentTime += 60000;
layer.setProps({
filterRange: [startTime, currentTime]
});
requestAnimationFrame(tick);
}
Этот подход позволяет реализовать эффект “протекания времени” без пересоздания геометрии.
Временные ряды часто содержат десятки миллионов точек. CPU-фильтрация становится узким местом, поэтому используется GPU-based filtering.
Deck.gl поддерживает фильтрацию через шейдеры, где каждое значение сравнивается с временным окном прямо на GPU.
Ключевые преимущества:
Типичный паттерн:
Когда временной ряд описывает движение объекта в пространстве, используется PathLayer.
import { PathLayer } from '@deck.gl/layers';
const layer = new PathLayer({
id: 'trajectory',
data: trajectories,
getPath: d => d.path,
getColor: [30, 200, 0],
widthMinPixels: 2
});
Формат данных:
const trajectories = [
{
id: 1,
path: [
[37.78, -122.41, 1710000000000],
[37.79, -122.42, 1710003600000],
[37.80, -122.43, 1710007200000]
]
}
];
Третья координата часто используется как временная метка, позволяя синхронизировать визуализацию с внешним таймлайном.
При отображении нескольких источников данных важно согласование временной шкалы. Используется единый time domain:
Пример нормализации:
function normalizeTime(t, minT, maxT) {
return (t - minT) / (maxT - minT);
}
Это позволяет использовать единый shader-параметр вместо множества независимых вычислений.
При больших объёмах данных применяется временная агрегация:
Пример агрегации:
function aggregate(data, interval) {
const map = new Map();
for (const d of data) {
const bucket = Math.floor(d.t / interval) * interval;
if (!map.has(bucket)) map.set(bucket, []);
map.get(bucket).push(d.v);
}
return Array.from(map.entries()).map(([t, values]) => ({
t,
v: values.reduce((a, b) => a + b, 0) / values.length
}));
}
Агрегация часто выполняется до передачи в слой, но в высоконагруженных системах может быть реализована на GPU или в worker-потоках.
Когда данные представляют собой события во времени, используется ScatterplotLayer.
import { ScatterplotLayer } from '@deck.gl/layers';
const layer = new ScatterplotLayer({
id: 'events',
data: events,
getPosition: d => [d.t, d.value],
getRadius: 5,
getFillColor: d => d.critical ? [255, 0, 0] : [0, 120, 255]
});
Такой подход применяется для:
Временные ряды часто приходят потоково. Deck.gl поддерживает частичные обновления через обновление props без пересоздания слоя.
function appendData(newPoints) {
data.push(...newPoints);
layer.setProps({
data
});
}
Для высокочастотных потоков используется буферизация:
GPU-слои требуют компактного представления:
Пример подготовки:
const positions = new Float32Array(data.length * 2);
data.forEach((d, i) => {
positions[i * 2] = d.t;
positions[i * 2 + 1] = d.v;
});
Это снижает нагрузку на garbage collector и ускоряет передачу в WebGL буферы.
Во многих задачах время становится дополнительной осью в пространстве:
Deck.gl позволяет комбинировать эти измерения без изменения базовой архитектуры слоя, используя accessor-функции и shader-параметры.