Архитектура Kepler.gl построена вокруг связки React и Redux, где
визуальный интерфейс представляется набором изолированных компонентов,
управляемых единым состоянием. Основные слои системы разделены на три
ключевые области: visState (данные и визуализация),
mapState (параметры камеры и карты) и uiState
(интерфейсные элементы). Замена стандартных компонентов выполняется
именно через слой uiState и механизм внедрения
пользовательских React-компонентов поверх базовой реализации.
UI Kepler.gl не является монолитным. Каждый крупный элемент интерфейса выделен в самостоятельный компонент:
Каждый из этих элементов подписан на Redux-состояние и получает данные через селекторы. Это позволяет заменять отдельные части интерфейса без изменения логики визуализации.
Ключевая особенность — отсутствие жёсткой привязки UI к внутренним структурам Kepler.gl. Вместо этого используется регистрация компонентов через механизм инъекции.
Основной способ замены стандартных элементов интерфейса — функция
injectComponents. Она позволяет подменять или расширять
внутренние React-компоненты библиотеки.
import {injectComponents} from 'kepler.gl/components';
Инъекция выполняется до рендера основного приложения:
injectComponents([
[KeplerGlLayerManager, CustomLayerManager],
[KeplerGlPanelHeader, CustomPanelHeader]
]);
Каждая пара представляет собой:
После регистрации Kepler.gl автоматически использует новые компоненты вместо встроенных.
Механизм работает на уровне dependency injection, а не через наследование, что позволяет полностью заменить поведение UI без форка библиотеки.
Панель слоёв — один из наиболее часто кастомизируемых элементов, так как она тесно связана с доменной логикой приложения.
Стандартный LayerManager получает список слоёв из
visState.layers и управляет их конфигурацией. При замене
компонента важно сохранить контракт данных:
function CustomLayerManager(props) {
const {layers, onLayerChange, dataset} = props;
return (
<div className="custom-layer-manager">
{layers.map(layer => (
<div key={layer.id}>
<span>{layer.config.label}</span>
<button onCl ick={() => onLayerChange(layer, {visible: false})}>
скрыть
</button>
</div>
))}
</div>
);
}
Ключевым моментом является сохранение callback-интерфейса:
onLayerChangedispatchupdateLayerConfigНарушение этих контрактов приводит к потере синхронизации с Redux-состоянием.
Контролы карты (zoom, rotation, pitch) реализованы как отдельный слой
UI, взаимодействующий с mapState.
Переопределение позволяет полностью изменить поведение навигации:
function CustomMapControls({mapState, setMapControl}) {
const zoomIn = () => setMapControl('zoom', mapState.zoom + 1);
const zoomOut = () => setMapControl('zoom', mapState.zoom - 1);
return (
<div className="controls">
<button onCl ick={zoomIn}>+</button>
<button onCl ick={zoomOut}>-</button>
</div>
);
}
Важно учитывать, что mapState является единственным
источником истины. Любые локальные состояния компонентов должны
синхронизироваться через actions Kepler.gl, иначе карта и UI
расходятся.
Оверлеи используются для модальных окон, тултипов и всплывающих панелей. Их замена требует работы с порталами React.
Kepler.gl использует отдельный DOM-узел для оверлеев, обычно
привязанный к document.body.
Пример кастомного контейнера:
function CustomModalContainer({children}) {
return (
<div className="custom-modal-root">
{children}
</div>
);
}
При инъекции важно сохранить механизм рендеринга через
ReactDOM.createPortal, иначе модальные окна теряют
позиционирование относительно карты.
uiState управляет видимостью панелей и состоянием
интерфейса. При замене компонентов необходимо учитывать следующие
поля:
uiState.currentModaluiState.activeSidePaneluiState.readOnlyПри создании кастомного интерфейса часто возникает необходимость
вручную читать эти значения через mapStateToProps.
Пример интеграции:
const mapStateToProps = state => ({
isReadOnly: state.keplerGl.uiState.readOnly,
activePanel: state.keplerGl.uiState.activeSidePanel
});
Игнорирование этих флагов приводит к расхождению поведения между стандартными и кастомными компонентами.
FilterPanel тесно связан с visState.filters. Каждый
фильтр описывается как объект с диапазоном значений, типом и состоянием
активности.
Кастомная реализация может полностью изменить UI представление фильтра, сохранив только логику:
function CustomFilterPanel({filters, updateFilter}) {
return (
<div>
{filters.map(f => (
<div key={f.id}>
<label>{f.name}</label>
<input
type="range"
min={f.domain[0]}
max={f.domain[1]}
value={f.value}
onCha nge={e =>
updateFilter(f.id, {value: Number(e.target.value)})
}
/>
</div>
))}
</div>
);
}
Важный аспект — фильтры могут иметь разные типы
(timeRange, range, multiSelect),
и кастомный компонент должен учитывать их структуру.
Kepler.gl использует namespace редьюсеров, обычно подключаемых как:
import keplerGlReducer from 'kepler.gl/reducers';
При замене UI-компонентов важно не создавать отдельное состояние, дублирующее Redux, а использовать существующие action creators:
addLayerremoveLayerupdateLayersetFiltertoggleSidePanelПример подключения:
import {updateMap} from 'kepler.gl/actions';
dispatch(updateMap({latitude: 50, longitude: 30, zoom: 10}));
Это обеспечивает согласованность между кастомным интерфейсом и ядром визуализации.
Header каждого блока (слои, фильтры, датасеты) обычно содержит базовые действия: сворачивание, закрытие, меню.
Замена header-компонента позволяет встроить бизнес-логику:
function CustomPanelHeader({title, onClose}) {
return (
<div className="panel-header">
<h3>{title}</h3>
<button onCl ick={onClose}>закрыть</button>
</div>
);
}
При этом важно учитывать, что заголовки часто управляют состоянием
uiState, и прямое изменение DOM без dispatch приводит к
визуальным артефактам.
При глубокой кастомизации UI возникают системные ограничения:
visStateОсобенно критично соблюдение формы объектов слоёв и фильтров. Любое
расхождение в структуре приводит к ошибкам сериализации схемы
(KeplerGlSchema).
Также следует учитывать, что некоторые компоненты зависят от
внутренних HOC Kepler.gl. Их замена без повторного подключения контекста
приводит к потере доступа к dispatch и селекторам.
На практике редко выполняется полная замена UI. Чаще применяется частичный подход:
Такой подход позволяет сохранить стабильность ядра и одновременно расширить функциональность интерфейса без нарушения архитектуры библиотеки.