kepler.gl
Version:
kepler.gl is a webgl based application to visualize large scale location data in the browser
1,449 lines (1,209 loc) • 195 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.updateStateOnLayerVisibilityChange = updateStateOnLayerVisibilityChange;
exports.layerConfigChangeUpdater = layerConfigChangeUpdater;
exports.layerTextLabelChangeUpdater = layerTextLabelChangeUpdater;
exports.layerTypeChangeUpdater = layerTypeChangeUpdater;
exports.layerVisualChannelChangeUpdater = layerVisualChannelChangeUpdater;
exports.layerVisConfigChangeUpdater = layerVisConfigChangeUpdater;
exports.interactionConfigChangeUpdater = interactionConfigChangeUpdater;
exports.setFilterUpdater = setFilterUpdater;
exports.loadNextFileUpdater = loadNextFileUpdater;
exports.makeLoadFileTask = makeLoadFileTask;
exports.addDefaultLayers = addDefaultLayers;
exports.addDefaultTooltips = addDefaultTooltips;
exports.updateAllLayerDomainData = updateAllLayerDomainData;
exports.updateAnimationDomain = updateAnimationDomain;
exports.setFeaturesUpdater = setFeaturesUpdater;
exports.deleteFeatureUpdater = deleteFeatureUpdater;
exports.setPolygonFilterLayerUpdater = setPolygonFilterLayerUpdater;
exports.sortTableColumnUpdater = sortTableColumnUpdater;
exports.pinTableColumnUpdater = pinTableColumnUpdater;
exports.copyTableColumnUpdater = copyTableColumnUpdater;
exports.toggleEditorVisibility = toggleEditorVisibility;
exports.setSelectedFeatureUpdater = exports.setEditorModeUpdater = exports.setMapInfoUpdater = exports.applyCPUFilterUpdater = exports.loadFilesErrUpdater = exports.loadFilesUpdater = exports.updateVisDataUpdater = exports.toggleLayerForMapUpdater = exports.toggleSplitMapUpdater = exports.mouseMoveUpdater = exports.mapClickUpdater = exports.layerClickUpdater = exports.layerHoverUpdater = exports.receiveMapConfigUpdater = exports.resetMapConfigUpdater = exports.showDatasetTableUpdater = exports.updateLayerBlendingUpdater = exports.removeDatasetUpdater = exports.reorderLayerUpdater = exports.removeLayerUpdater = exports.addLayerUpdater = exports.removeFilterUpdater = exports.toggleFilterFeatureUpdater = exports.enlargeFilterUpdater = exports.updateLayerAnimationSpeedUpdater = exports.updateAnimationTimeUpdater = exports.updateFilterAnimationSpeedUpdater = exports.toggleFilterAnimationUpdater = exports.layerColorUIChangeUpdater = exports.addFilterUpdater = exports.setFilterPlotUpdater = exports.INITIAL_VIS_STATE = exports.DEFAULT_EDITOR = exports.defaultAnimationConfig = void 0;
var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
var _toArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toArray"));
var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _window = require("global/window");
var _tasks = require("react-palm/tasks");
var _lodash = _interopRequireDefault(require("lodash.clonedeep"));
var _lodash2 = _interopRequireDefault(require("lodash.uniq"));
var _lodash3 = _interopRequireDefault(require("lodash.get"));
var _lodash4 = _interopRequireDefault(require("lodash.xor"));
var _copyToClipboard = _interopRequireDefault(require("copy-to-clipboard"));
var _dataUtils = require("../utils/data-utils");
var _tasks2 = require("../tasks/tasks");
var _visStateActions = require("../actions/vis-state-actions");
var _interactionUtils = require("../utils/interaction-utils");
var _filterUtils = require("../utils/filter-utils");
var _gpuFilterUtils = require("../utils/gpu-filter-utils");
var _datasetUtils = require("../utils/dataset-utils");
var _utils = require("../utils/utils");
var _layerUtils = require("../utils/layer-utils/layer-utils");
var _visStateMerger = require("./vis-state-merger");
var _splitMapUtils = require("../utils/split-map-utils");
var _layers = require("../layers");
var _layerFactory = require("../layers/layer-factory");
var _defaultSettings = require("../constants/default-settings");
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return (0, _typeof2["default"])(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if ((0, _typeof2["default"])(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if ((0, _typeof2["default"])(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2["default"])(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
// react-palm
// disable capture exception for react-palm call to withTask
(0, _tasks.disableStackCapturing)();
/**
* Updaters for `visState` reducer. Can be used in your root reducer to directly modify kepler.gl's state.
* Read more about [Using updaters](../advanced-usage/using-updaters.md)
*
* @public
* @example
*
* import keplerGlReducer, {visStateUpdaters} from 'kepler.gl/reducers';
* // Root Reducer
* const reducers = combineReducers({
* keplerGl: keplerGlReducer,
* app: appReducer
* });
*
* const composedReducer = (state, action) => {
* switch (action.type) {
* case 'CLICK_BUTTON':
* return {
* ...state,
* keplerGl: {
* ...state.keplerGl,
* foo: {
* ...state.keplerGl.foo,
* visState: visStateUpdaters.enlargeFilterUpdater(
* state.keplerGl.foo.visState,
* {idx: 0}
* )
* }
* }
* };
* }
* return reducers(state, action);
* };
*
* export default composedReducer;
*/
/* eslint-disable no-unused-vars */
var visStateUpdaters = null;
/* eslint-enable no-unused-vars */
var defaultAnimationConfig = {
domain: null,
currentTime: null,
speed: 1
};
exports.defaultAnimationConfig = defaultAnimationConfig;
var DEFAULT_EDITOR = {
mode: _defaultSettings.EDITOR_MODES.DRAW_POLYGON,
features: [],
selectedFeature: null,
visible: true
};
/**
* Default initial `visState`
* @memberof visStateUpdaters
* @constant
* @type {Object}
* @property {Array} layers
* @property {Array} layerData
* @property {Array} layerToBeMerged
* @property {Array} layerOrder
* @property {Array} filters
* @property {Array} filterToBeMerged
* @property {Array} datasets
* @property {string} editingDataset
* @property {Object} interactionConfig
* @property {Object} interactionToBeMerged
* @property {string} layerBlending
* @property {Object} hoverInfo
* @property {Object} clicked
* @property {Object} mousePos
* @property {Array} splitMaps - a list of objects of layer availabilities and visibilities for each map
* @property {Object} layerClasses
* @property {Object} animationConfig
* @property {Object} editor
* @public
*/
exports.DEFAULT_EDITOR = DEFAULT_EDITOR;
var INITIAL_VIS_STATE = {
// map info
mapInfo: {
title: '',
description: ''
},
// layers
layers: [],
layerData: [],
layerToBeMerged: [],
layerOrder: [],
// filters
filters: [],
filterToBeMerged: [],
// a collection of multiple dataset
datasets: {},
editingDataset: undefined,
interactionConfig: (0, _interactionUtils.getDefaultInteraction)(),
interactionToBeMerged: undefined,
layerBlending: 'normal',
hoverInfo: undefined,
clicked: undefined,
mousePos: {},
// this is used when user split maps
splitMaps: [// this will contain a list of objects to
// describe the state of layer availability and visibility for each map
// [
// {
// layers: {layer_id: true | false}
// }
// ]
],
//
// defaults layer classes
layerClasses: _layers.LayerClasses,
// default animation
// time in unix timestamp (milliseconds) (the number of seconds since the Unix Epoch)
animationConfig: defaultAnimationConfig,
editor: DEFAULT_EDITOR
};
exports.INITIAL_VIS_STATE = INITIAL_VIS_STATE;
function updateStateWithLayerAndData(state, _ref) {
var layerData = _ref.layerData,
layer = _ref.layer,
idx = _ref.idx;
return _objectSpread({}, state, {
layers: state.layers.map(function (lyr, i) {
return i === idx ? layer : lyr;
}),
layerData: layerData ? state.layerData.map(function (d, i) {
return i === idx ? layerData : d;
}) : state.layerData
});
}
function updateStateOnLayerVisibilityChange(state, layer) {
var newState = state;
if (state.splitMaps.length) {
newState = _objectSpread({}, state, {
splitMaps: layer.config.isVisible ? (0, _splitMapUtils.addNewLayersToSplitMap)(state.splitMaps, layer) : (0, _splitMapUtils.removeLayerFromSplitMaps)(state.splitMaps, layer)
});
}
if (layer.config.animation.enabled) {
newState = updateAnimationDomain(state);
}
return newState;
}
/**
* Update layer base config: dataId, label, column, isVisible
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @param {Object} action action
* @param {Object} action.oldLayer layer to be updated
* @param {Object} action.newConfig new config
* @returns {Object} nextState
*/
function layerConfigChangeUpdater(state, action) {
var oldLayer = action.oldLayer;
var idx = state.layers.findIndex(function (l) {
return l.id === oldLayer.id;
});
var props = Object.keys(action.newConfig);
var newLayer = oldLayer.updateLayerConfig(action.newConfig);
var layerData; // let newLayer;
if (newLayer.shouldCalculateLayerData(props)) {
var oldLayerData = state.layerData[idx];
var updateLayerDataResult = (0, _layerUtils.calculateLayerData)(newLayer, state, oldLayerData);
layerData = updateLayerDataResult.layerData;
newLayer = updateLayerDataResult.layer;
}
var newState = state;
if ('isVisible' in action.newConfig) {
newState = updateStateOnLayerVisibilityChange(state, newLayer);
}
return updateStateWithLayerAndData(newState, {
layer: newLayer,
layerData: layerData,
idx: idx
});
}
function addOrRemoveTextLabels(newFields, textLabel) {
var newTextLabel = textLabel.slice();
var currentFields = textLabel.map(function (tl) {
return tl.field && tl.field.name;
}).filter(function (d) {
return d;
});
var addFields = newFields.filter(function (f) {
return !currentFields.includes(f.name);
});
var deleteFields = currentFields.filter(function (f) {
return !newFields.find(function (fd) {
return fd.name === f;
});
}); // delete
newTextLabel = newTextLabel.filter(function (tl) {
return tl.field && !deleteFields.includes(tl.field.name);
});
newTextLabel = !newTextLabel.length ? [_layerFactory.DEFAULT_TEXT_LABEL] : newTextLabel; // add
newTextLabel = [].concat((0, _toConsumableArray2["default"])(newTextLabel.filter(function (tl) {
return tl.field;
})), (0, _toConsumableArray2["default"])(addFields.map(function (af) {
return _objectSpread({}, _layerFactory.DEFAULT_TEXT_LABEL, {
field: af
});
})));
return newTextLabel;
}
function updateTextLabelPropAndValue(idx, prop, value, textLabel) {
if (!textLabel[idx].hasOwnProperty(prop)) {
return textLabel;
}
var newTextLabel = textLabel.slice();
if (prop && (value || textLabel.length === 1)) {
newTextLabel = textLabel.map(function (tl, i) {
return i === idx ? _objectSpread({}, tl, (0, _defineProperty2["default"])({}, prop, value)) : tl;
});
} else if (prop === 'field' && value === null && textLabel.length > 1) {
// remove label when field value is set to null
newTextLabel.splice(idx, 1);
}
return newTextLabel;
}
function layerTextLabelChangeUpdater(state, action) {
var oldLayer = action.oldLayer,
idx = action.idx,
prop = action.prop,
value = action.value;
var textLabel = oldLayer.config.textLabel;
var newTextLabel = textLabel.slice();
if (!textLabel[idx] && idx === textLabel.length) {
// if idx is set to length, add empty text label
newTextLabel = [].concat((0, _toConsumableArray2["default"])(textLabel), [_layerFactory.DEFAULT_TEXT_LABEL]);
}
if (idx === 'all' && prop === 'fields') {
newTextLabel = addOrRemoveTextLabels(value, textLabel);
} else {
newTextLabel = updateTextLabelPropAndValue(idx, prop, value, newTextLabel);
} // update text label prop and value
return layerConfigChangeUpdater(state, {
oldLayer: oldLayer,
newConfig: {
textLabel: newTextLabel
}
});
}
/**
* Update layer type. Previews layer config will be copied if applicable.
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @param {Object} action action
* @param {Object} action.oldLayer layer to be updated
* @param {string} action.newType new type
* @returns {Object} nextState
* @public
*/
function layerTypeChangeUpdater(state, action) {
var oldLayer = action.oldLayer,
newType = action.newType;
if (!oldLayer) {
return state;
}
var oldId = oldLayer.id;
var idx = state.layers.findIndex(function (l) {
return l.id === oldId;
});
if (!state.layerClasses[newType]) {
_window.console.error("".concat(newType, " is not a valid layer type"));
return state;
} // get a mint layer, with new id and type
// because deck.gl uses id to match between new and old layer.
// If type has changed but id is the same, it will break
var newLayer = new state.layerClasses[newType]();
newLayer.assignConfigToLayer(oldLayer.config, oldLayer.visConfigSettings); // if (newLayer.config.dataId) {
// const dataset = state.datasets[newLayer.config.dataId];
// newLayer.updateLayerDomain(dataset);
// }
newLayer.updateLayerDomain(state.datasets);
var _calculateLayerData = (0, _layerUtils.calculateLayerData)(newLayer, state),
layerData = _calculateLayerData.layerData,
layer = _calculateLayerData.layer;
var newState = updateStateWithLayerAndData(state, {
layerData: layerData,
layer: layer,
idx: idx
});
if (layer.config.animation.enabled || oldLayer.config.animation.enabled) {
newState = updateAnimationDomain(newState);
} // update splitMap layer id
if (state.splitMaps.length) {
newState = _objectSpread({}, newState, {
splitMaps: newState.splitMaps.map(function (settings) {
var _settings$layers = settings.layers,
oldLayerMap = _settings$layers[oldId],
otherLayers = (0, _objectWithoutProperties2["default"])(_settings$layers, [oldId].map(_toPropertyKey));
return oldId in settings.layers ? _objectSpread({}, settings, {
layers: _objectSpread({}, otherLayers, (0, _defineProperty2["default"])({}, layer.id, oldLayerMap))
}) : settings;
})
});
}
return newState;
}
/**
* Update layer visual channel
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @param {Object} action action
* @param {Object} action.oldLayer layer to be updated
* @param {Object} action.newConfig new visual channel config
* @param {string} action.channel channel to be updated
* @returns {Object} nextState
* @public
*/
function layerVisualChannelChangeUpdater(state, action) {
var oldLayer = action.oldLayer,
newConfig = action.newConfig,
channel = action.channel;
var dataset = state.datasets[oldLayer.config.dataId];
var idx = state.layers.findIndex(function (l) {
return l.id === oldLayer.id;
});
var newLayer = oldLayer.updateLayerConfig(newConfig);
newLayer.updateLayerVisualChannel(dataset, channel);
var oldLayerData = state.layerData[idx];
var _calculateLayerData2 = (0, _layerUtils.calculateLayerData)(newLayer, state, oldLayerData),
layerData = _calculateLayerData2.layerData,
layer = _calculateLayerData2.layer;
return updateStateWithLayerAndData(state, {
layerData: layerData,
layer: layer,
idx: idx
});
}
/**
* Update layer `visConfig`
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @param {Object} action action
* @param {Object} action.oldLayer layer to be updated
* @param {Object} action.newVisConfig new visConfig as a key value map: e.g. `{opacity: 0.8}`
* @returns {Object} nextState
* @public
*/
function layerVisConfigChangeUpdater(state, action) {
var oldLayer = action.oldLayer;
var idx = state.layers.findIndex(function (l) {
return l.id === oldLayer.id;
});
var props = Object.keys(action.newVisConfig);
var newVisConfig = _objectSpread({}, oldLayer.config.visConfig, {}, action.newVisConfig);
var newLayer = oldLayer.updateLayerConfig({
visConfig: newVisConfig
});
if (newLayer.shouldCalculateLayerData(props)) {
var oldLayerData = state.layerData[idx];
var _calculateLayerData3 = (0, _layerUtils.calculateLayerData)(newLayer, state, oldLayerData),
layerData = _calculateLayerData3.layerData,
layer = _calculateLayerData3.layer;
return updateStateWithLayerAndData(state, {
layerData: layerData,
layer: layer,
idx: idx
});
}
return updateStateWithLayerAndData(state, {
layer: newLayer,
idx: idx
});
}
/* eslint-enable max-statements */
/**
* Update `interactionConfig`
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @param {Object} action action
* @param {Object} action.config new config as key value map: `{tooltip: {enabled: true}}`
* @returns {Object} nextState
* @public
*/
function interactionConfigChangeUpdater(state, action) {
var config = action.config;
var interactionConfig = _objectSpread({}, state.interactionConfig, {}, (0, _defineProperty2["default"])({}, config.id, config)); // Don't enable tooltip and brush at the same time
// but coordinates can be shown at all time
var contradict = ['brush', 'tooltip'];
if (contradict.includes(config.id) && config.enabled && !state.interactionConfig[config.id].enabled) {
// only enable one interaction at a time
contradict.forEach(function (k) {
if (k !== config.id) {
interactionConfig[k] = _objectSpread({}, interactionConfig[k], {
enabled: false
});
}
});
}
return _objectSpread({}, state, {
interactionConfig: interactionConfig
});
}
/**
* Update filter property
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @param {Object} action action
* @param {Number} action.idx `idx` of filter to be updated
* @param {string} action.prop `prop` of filter, e,g, `dataId`, `name`, `value`
* @param {*} action.value new value
* @param {string} datasetId used when updating a prop (dataId, name) that can be linked to multiple datasets
* @returns {Object} nextState
* @public
*/
function setFilterUpdater(state, action) {
var idx = action.idx,
prop = action.prop,
value = action.value,
_action$valueIndex = action.valueIndex,
valueIndex = _action$valueIndex === void 0 ? 0 : _action$valueIndex;
var oldFilter = state.filters[idx];
var newFilter = (0, _utils.set)([prop], value, oldFilter);
var newState = state;
var _newFilter = newFilter,
dataId = _newFilter.dataId; // Ensuring backward compatibility
var datasetIds = (0, _utils.toArray)(dataId);
switch (prop) {
// TODO: Next PR for UI if we update dataId, we need to consider two cases:
// 1. dataId is empty: create a default filter
// 2. Add a new dataset id
case _filterUtils.FILTER_UPDATER_PROPS.dataId:
// if trying to update filter dataId. create an empty new filter
newFilter = (0, _filterUtils.updateFilterDataId)(dataId);
break;
case _filterUtils.FILTER_UPDATER_PROPS.name:
// we are supporting the current functionality
// TODO: Next PR for UI filter name will only update filter name but it won't have side effects
// we are gonna use pair of datasets and fieldIdx to update the filter
var datasetId = newFilter.dataId[valueIndex];
var _applyFilterFieldName = (0, _filterUtils.applyFilterFieldName)(newFilter, state.datasets[datasetId], value, valueIndex, {
mergeDomain: false
}),
updatedFilter = _applyFilterFieldName.filter,
newDataset = _applyFilterFieldName.dataset;
if (!updatedFilter) {
return state;
}
newFilter = updatedFilter;
if (newFilter.gpu) {
newFilter = (0, _gpuFilterUtils.setFilterGpuMode)(newFilter, state.filters);
newFilter = (0, _gpuFilterUtils.assignGpuChannel)(newFilter, state.filters);
}
newState = (0, _utils.set)(['datasets', datasetId], newDataset, state); // only filter the current dataset
break;
case _filterUtils.FILTER_UPDATER_PROPS.layerId:
// We need to update only datasetId/s if we have added/removed layers
// - check for layerId changes (XOR works because of string values)
// if no differences between layerIds, don't do any filtering
var layerIdDifference = (0, _lodash4["default"])(newFilter.layerId, oldFilter.layerId);
var layerDataIds = (0, _lodash2["default"])(layerIdDifference.map(function (lid) {
return (0, _lodash3["default"])(state.layers.find(function (l) {
return l.id === lid;
}), ['config', 'dataId']);
}).filter(function (d) {
return d;
})); // only filter datasetsIds
datasetIds = layerDataIds; // Update newFilter dataIds
var newDataIds = (0, _lodash2["default"])(newFilter.layerId.map(function (lid) {
return (0, _lodash3["default"])(state.layers.find(function (l) {
return l.id === lid;
}), ['config', 'dataId']);
}).filter(function (d) {
return d;
}));
newFilter = _objectSpread({}, newFilter, {
dataId: newDataIds
});
break;
default:
break;
}
var enlargedFilter = state.filters.find(function (f) {
return f.enlarged;
});
if (enlargedFilter && enlargedFilter.id !== newFilter.id) {
// there should be only one enlarged filter
newFilter.enlarged = false;
} // save new filters to newState
newState = (0, _utils.set)(['filters', idx], newFilter, newState); // if we are currently setting a prop that only requires to filter the current
// dataset we will pass only the current dataset to applyFiltersToDatasets and
// updateAllLayerDomainData otherwise we pass the all list of datasets as defined in dataId
var datasetIdsToFilter = _filterUtils.LIMITED_FILTER_EFFECT_PROPS[prop] ? [datasetIds[valueIndex]] : datasetIds; // filter data
var filteredDatasets = (0, _filterUtils.applyFiltersToDatasets)(datasetIdsToFilter, newState.datasets, newState.filters, newState.layers);
newState = (0, _utils.set)(['datasets'], filteredDatasets, newState); // dataId is an array
// pass only the dataset we need to update
newState = updateAllLayerDomainData(newState, datasetIdsToFilter, newFilter);
return newState;
}
/**
* Set the property of a filter plot
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @param {Object} action action
* @param {Number} action.idx
* @param {Object} action.newProp key value mapping of new prop `{yAxis: 'histogram'}`
* @returns {Object} nextState
* @public
*/
var setFilterPlotUpdater = function setFilterPlotUpdater(state, _ref2) {
var idx = _ref2.idx,
newProp = _ref2.newProp;
var newFilter = _objectSpread({}, state.filters[idx], {}, newProp);
var prop = Object.keys(newProp)[0];
if (prop === 'yAxis') {
var plotType = (0, _filterUtils.getDefaultFilterPlotType)(newFilter);
if (plotType) {
newFilter = _objectSpread({}, newFilter, {}, (0, _filterUtils.getFilterPlot)(_objectSpread({}, newFilter, {
plotType: plotType
}), state.datasets[newFilter.dataId].allData), {
plotType: plotType
});
}
}
return _objectSpread({}, state, {
filters: state.filters.map(function (f, i) {
return i === idx ? newFilter : f;
})
});
};
/**
* Add a new filter
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @param {Object} action action
* @param {string} action.dataId dataset `id` this new filter is associated with
* @returns {Object} nextState
* @public
*/
exports.setFilterPlotUpdater = setFilterPlotUpdater;
var addFilterUpdater = function addFilterUpdater(state, action) {
return !action.dataId ? state : _objectSpread({}, state, {
filters: [].concat((0, _toConsumableArray2["default"])(state.filters), [(0, _filterUtils.getDefaultFilter)(action.dataId)])
});
};
/**
* Set layer color palette ui state
* @memberof visStateUpdaters
* @param {Object} state
* @param {Object} action
* @param {Object} action.prop
* @param {Object} action.newConfig
*/
exports.addFilterUpdater = addFilterUpdater;
var layerColorUIChangeUpdater = function layerColorUIChangeUpdater(state, _ref3) {
var oldLayer = _ref3.oldLayer,
prop = _ref3.prop,
newConfig = _ref3.newConfig;
var newLayer = oldLayer.updateLayerColorUI(prop, newConfig);
return _objectSpread({}, state, {
layers: state.layers.map(function (l) {
return l.id === oldLayer.id ? newLayer : l;
})
});
};
/**
* Start and end filter animation
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @param {Object} action action
* @param {Number} action.idx idx of filter
* @returns {Object} nextState
* @public
*/
exports.layerColorUIChangeUpdater = layerColorUIChangeUpdater;
var toggleFilterAnimationUpdater = function toggleFilterAnimationUpdater(state, action) {
return _objectSpread({}, state, {
filters: state.filters.map(function (f, i) {
return i === action.idx ? _objectSpread({}, f, {
isAnimating: !f.isAnimating
}) : f;
})
});
};
/**
* Change filter animation speed
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @param {Object} action action
* @param {Number} action.idx `idx` of filter
* @param {Number} action.speed `speed` to change it to. `speed` is a multiplier
* @returns {Object} nextState
* @public
*/
exports.toggleFilterAnimationUpdater = toggleFilterAnimationUpdater;
var updateFilterAnimationSpeedUpdater = function updateFilterAnimationSpeedUpdater(state, action) {
return _objectSpread({}, state, {
filters: state.filters.map(function (f, i) {
return i === action.idx ? _objectSpread({}, f, {
speed: action.speed
}) : f;
})
});
};
/**
* Reset animation config current time to a specified value
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @param {Object} action action
* @param {Number} action.value the value current time will be set to
* @returns {Object} nextState
* @public
*
*/
exports.updateFilterAnimationSpeedUpdater = updateFilterAnimationSpeedUpdater;
var updateAnimationTimeUpdater = function updateAnimationTimeUpdater(state, _ref4) {
var value = _ref4.value;
return _objectSpread({}, state, {
animationConfig: _objectSpread({}, state.animationConfig, {
currentTime: value
})
});
};
/**
* Update animation speed with the vertical speed slider
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @param {Object} action action
* @param {Number} action.speed the updated speed of the animation
* @returns {Object} nextState
* @public
*
*/
exports.updateAnimationTimeUpdater = updateAnimationTimeUpdater;
var updateLayerAnimationSpeedUpdater = function updateLayerAnimationSpeedUpdater(state, _ref5) {
var speed = _ref5.speed;
return _objectSpread({}, state, {
animationConfig: _objectSpread({}, state.animationConfig, {
speed: speed
})
});
};
/**
* Show larger time filter at bottom for time playback (apply to time filter only)
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @param {Object} action action
* @param {Number} action.idx index of filter to enlarge
* @returns {Object} nextState
* @public
*/
exports.updateLayerAnimationSpeedUpdater = updateLayerAnimationSpeedUpdater;
var enlargeFilterUpdater = function enlargeFilterUpdater(state, action) {
var isEnlarged = state.filters[action.idx].enlarged;
return _objectSpread({}, state, {
filters: state.filters.map(function (f, i) {
f.enlarged = !isEnlarged && i === action.idx;
return f;
})
});
};
/**
* Toggles filter feature visibility
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @param {Object} action action
* @param {Number} action.idx index of filter to enlarge
* @returns {Object} nextState
*/
exports.enlargeFilterUpdater = enlargeFilterUpdater;
var toggleFilterFeatureUpdater = function toggleFilterFeatureUpdater(state, action) {
var filter = state.filters[action.idx];
var isVisible = (0, _lodash3["default"])(filter, ['value', 'properties', 'isVisible']);
var newFilter = _objectSpread({}, filter, {
value: (0, _filterUtils.featureToFilterValue)(filter.value, filter.id, {
isVisible: !isVisible
})
});
return _objectSpread({}, state, {
filters: Object.assign([].concat(state.filters), (0, _defineProperty2["default"])({}, action.idx, newFilter))
});
};
/**
* Remove a filter
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @param {Object} action action
* @param {Number} action.idx index of filter to b e removed
* @returns {Object} nextState
* @public
*/
exports.toggleFilterFeatureUpdater = toggleFilterFeatureUpdater;
var removeFilterUpdater = function removeFilterUpdater(state, action) {
var idx = action.idx;
var _state$filters$idx = state.filters[idx],
dataId = _state$filters$idx.dataId,
id = _state$filters$idx.id;
var newFilters = [].concat((0, _toConsumableArray2["default"])(state.filters.slice(0, idx)), (0, _toConsumableArray2["default"])(state.filters.slice(idx + 1, state.filters.length)));
var filteredDatasets = (0, _filterUtils.applyFiltersToDatasets)(dataId, state.datasets, newFilters, state.layers);
var newEditor = (0, _filterUtils.getFilterIdInFeature)(state.editor.selectedFeature) === id ? _objectSpread({}, state.editor, {
selectedFeature: null
}) : state.editor;
var newState = (0, _utils.set)(['filters'], newFilters, state);
newState = (0, _utils.set)(['datasets'], filteredDatasets, newState);
newState = (0, _utils.set)(['editor'], newEditor, newState);
return updateAllLayerDomainData(newState, dataId);
};
/**
* Add a new layer
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @param {Object} action action
* @param {Object} action.props - new layer props
* @returns {Object} nextState
* @public
*/
exports.removeFilterUpdater = removeFilterUpdater;
var addLayerUpdater = function addLayerUpdater(state, action) {
var defaultDataset = Object.keys(state.datasets)[0];
var newLayer = new _layers.Layer(_objectSpread({
isVisible: true,
isConfigActive: true,
dataId: defaultDataset
}, action.props));
return _objectSpread({}, state, {
layers: [].concat((0, _toConsumableArray2["default"])(state.layers), [newLayer]),
layerData: [].concat((0, _toConsumableArray2["default"])(state.layerData), [{}]),
layerOrder: [].concat((0, _toConsumableArray2["default"])(state.layerOrder), [state.layerOrder.length]),
splitMaps: (0, _splitMapUtils.addNewLayersToSplitMap)(state.splitMaps, newLayer)
});
};
/**
* remove layer
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @param {Object} action action
* @param {Number} action.idx index of layer to b e removed
* @returns {Object} nextState
* @public
*/
exports.addLayerUpdater = addLayerUpdater;
var removeLayerUpdater = function removeLayerUpdater(state, _ref6) {
var idx = _ref6.idx;
var layers = state.layers,
layerData = state.layerData,
clicked = state.clicked,
hoverInfo = state.hoverInfo;
var layerToRemove = state.layers[idx];
var newMaps = (0, _splitMapUtils.removeLayerFromSplitMaps)(state.splitMaps, layerToRemove);
var newState = _objectSpread({}, state, {
layers: [].concat((0, _toConsumableArray2["default"])(layers.slice(0, idx)), (0, _toConsumableArray2["default"])(layers.slice(idx + 1, layers.length))),
layerData: [].concat((0, _toConsumableArray2["default"])(layerData.slice(0, idx)), (0, _toConsumableArray2["default"])(layerData.slice(idx + 1, layerData.length))),
layerOrder: state.layerOrder.filter(function (i) {
return i !== idx;
}).map(function (pid) {
return pid > idx ? pid - 1 : pid;
}),
clicked: layerToRemove.isLayerHovered(clicked) ? undefined : clicked,
hoverInfo: layerToRemove.isLayerHovered(hoverInfo) ? undefined : hoverInfo,
splitMaps: newMaps // TODO: update filters, create helper to remove layer form filter (remove layerid and dataid) if mapped
});
return updateAnimationDomain(newState);
};
/**
* Reorder layer
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @param {Object} action action
* @param {Array<Number>} action.order an array of layer indexes
* @returns {Object} nextState
* @public
*/
exports.removeLayerUpdater = removeLayerUpdater;
var reorderLayerUpdater = function reorderLayerUpdater(state, _ref7) {
var order = _ref7.order;
return _objectSpread({}, state, {
layerOrder: order
});
};
/**
* Remove a dataset and all layers, filters, tooltip configs that based on it
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @param {Object} action action
* @param {string} action.key dataset id
* @returns {Object} nextState
* @public
*/
exports.reorderLayerUpdater = reorderLayerUpdater;
var removeDatasetUpdater = function removeDatasetUpdater(state, action) {
// extract dataset key
var datasetKey = action.key;
var datasets = state.datasets; // check if dataset is present
if (!datasets[datasetKey]) {
return state;
}
/* eslint-disable no-unused-vars */
var layers = state.layers,
_state$datasets = state.datasets,
dataset = _state$datasets[datasetKey],
newDatasets = (0, _objectWithoutProperties2["default"])(_state$datasets, [datasetKey].map(_toPropertyKey));
/* eslint-enable no-unused-vars */
var indexes = layers.reduce(function (listOfIndexes, layer, index) {
if (layer.config.dataId === datasetKey) {
listOfIndexes.push(index);
}
return listOfIndexes;
}, []); // remove layers and datasets
var _indexes$reduce = indexes.reduce(function (_ref8, idx) {
var currentState = _ref8.newState,
indexCounter = _ref8.indexCounter;
var currentIndex = idx - indexCounter;
currentState = removeLayerUpdater(currentState, {
idx: currentIndex
});
indexCounter++;
return {
newState: currentState,
indexCounter: indexCounter
};
}, {
newState: _objectSpread({}, state, {
datasets: newDatasets
}),
indexCounter: 0
}),
newState = _indexes$reduce.newState; // remove filters
var filters = state.filters.filter(function (filter) {
return !filter.dataId.includes(datasetKey);
}); // update interactionConfig
var interactionConfig = state.interactionConfig;
var _interactionConfig = interactionConfig,
tooltip = _interactionConfig.tooltip;
if (tooltip) {
var config = tooltip.config;
/* eslint-disable no-unused-vars */
var _config$fieldsToShow = config.fieldsToShow,
fields = _config$fieldsToShow[datasetKey],
fieldsToShow = (0, _objectWithoutProperties2["default"])(_config$fieldsToShow, [datasetKey].map(_toPropertyKey));
/* eslint-enable no-unused-vars */
interactionConfig = _objectSpread({}, interactionConfig, {
tooltip: _objectSpread({}, tooltip, {
config: _objectSpread({}, config, {
fieldsToShow: fieldsToShow
})
})
});
}
return _objectSpread({}, newState, {
filters: filters,
interactionConfig: interactionConfig
});
};
/**
* update layer blending mode
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @param {Object} action action
* @param {string} action.mode one of `additive`, `normal` and `subtractive`
* @returns {Object} nextState
* @public
*/
exports.removeDatasetUpdater = removeDatasetUpdater;
var updateLayerBlendingUpdater = function updateLayerBlendingUpdater(state, action) {
return _objectSpread({}, state, {
layerBlending: action.mode
});
};
/**
* Display dataset table in a modal
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @param {Object} action action
* @param {string} action.dataId dataset id to show in table
* @returns {Object} nextState
* @public
*/
exports.updateLayerBlendingUpdater = updateLayerBlendingUpdater;
var showDatasetTableUpdater = function showDatasetTableUpdater(state, action) {
return _objectSpread({}, state, {
editingDataset: action.dataId
});
};
/**
* reset visState to initial State
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @returns {Object} nextState
* @public
*/
exports.showDatasetTableUpdater = showDatasetTableUpdater;
var resetMapConfigUpdater = function resetMapConfigUpdater(state) {
return _objectSpread({}, INITIAL_VIS_STATE, {}, state.initialState, {
initialState: state.initialState
});
};
/**
* Propagate `visState` reducer with a new configuration. Current config will be override.
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @param {Object} action action
* @param {Object} action.payload map config to be propagated
* @param {Object} action.payload.config map config to be propagated
* @param {Object} action.payload.option {keepExistingConfig: true | false}
* @returns {Object} nextState
* @public
*/
exports.resetMapConfigUpdater = resetMapConfigUpdater;
var receiveMapConfigUpdater = function receiveMapConfigUpdater(state, _ref9) {
var _ref9$payload = _ref9.payload,
_ref9$payload$config = _ref9$payload.config,
config = _ref9$payload$config === void 0 ? {} : _ref9$payload$config,
_ref9$payload$options = _ref9$payload.options,
options = _ref9$payload$options === void 0 ? {} : _ref9$payload$options;
if (!config.visState) {
return state;
}
var _config$visState = config.visState,
filters = _config$visState.filters,
layers = _config$visState.layers,
interactionConfig = _config$visState.interactionConfig,
layerBlending = _config$visState.layerBlending,
splitMaps = _config$visState.splitMaps,
animationConfig = _config$visState.animationConfig;
var keepExistingConfig = options.keepExistingConfig; // reset config if keepExistingConfig is falsy
var mergedState = !keepExistingConfig ? resetMapConfigUpdater(state) : state;
mergedState = (0, _visStateMerger.mergeLayers)(mergedState, layers);
mergedState = (0, _visStateMerger.mergeFilters)(mergedState, filters);
mergedState = (0, _visStateMerger.mergeInteractions)(mergedState, interactionConfig);
mergedState = (0, _visStateMerger.mergeLayerBlending)(mergedState, layerBlending);
mergedState = (0, _visStateMerger.mergeSplitMaps)(mergedState, splitMaps);
mergedState = (0, _visStateMerger.mergeAnimationConfig)(mergedState, animationConfig);
return mergedState;
};
/**
* Trigger layer hover event with hovered object
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @param {Object} action action
* @param {Object} action.info Object hovered, returned by deck.gl
* @returns {Object} nextState
* @public
*/
exports.receiveMapConfigUpdater = receiveMapConfigUpdater;
var layerHoverUpdater = function layerHoverUpdater(state, action) {
return _objectSpread({}, state, {
hoverInfo: action.info
});
};
/**
* Trigger layer click event with clicked object
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @param {Object} action action
* @param {Object} action.info Object clicked, returned by deck.gl
* @returns {Object} nextState
* @public
*/
exports.layerHoverUpdater = layerHoverUpdater;
var layerClickUpdater = function layerClickUpdater(state, action) {
return _objectSpread({}, state, {
mousePos: state.interactionConfig.coordinate.enabled ? _objectSpread({}, state.mousePos, {
pinned: state.mousePos.pinned ? null : (0, _lodash["default"])(state.mousePos)
}) : state.mousePos,
clicked: action.info && action.info.picked ? action.info : null
});
};
/**
* Trigger map click event, unselect clicked object
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @returns {Object} nextState
* @public
*/
exports.layerClickUpdater = layerClickUpdater;
var mapClickUpdater = function mapClickUpdater(state) {
return _objectSpread({}, state, {
clicked: null
});
};
exports.mapClickUpdater = mapClickUpdater;
var mouseMoveUpdater = function mouseMoveUpdater(state, _ref10) {
var evt = _ref10.evt;
if (Object.values(state.interactionConfig).some(function (config) {
return config.enabled;
})) {
return _objectSpread({}, state, {
mousePos: _objectSpread({}, state.mousePos, {
mousePosition: (0, _toConsumableArray2["default"])(evt.point),
coordinate: (0, _toConsumableArray2["default"])(evt.lngLat)
})
});
}
return state;
};
/**
* Toggle visibility of a layer for a split map
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @param {Object} action action
* @param {Number|undefined} action.payload index of the split map
* @returns {Object} nextState
* @public
*/
exports.mouseMoveUpdater = mouseMoveUpdater;
var toggleSplitMapUpdater = function toggleSplitMapUpdater(state, action) {
return state.splitMaps && state.splitMaps.length === 0 ? _objectSpread({}, state, {
// maybe we should use an array to store state for a single map as well
// if current maps length is equal to 0 it means that we are about to split the view
splitMaps: (0, _splitMapUtils.computeSplitMapLayers)(state.layers)
}) : closeSpecificMapAtIndex(state, action);
};
/**
* Toggle visibility of a layer in a split map
* @memberof visStateUpdaters
* @param {Object} state
* @param {Object} action
* @param {Number} action.mapIndex index of the split map
* @param {string} action.layerId id of the layer
* @returns {Object} nextState
* @public
*/
exports.toggleSplitMapUpdater = toggleSplitMapUpdater;
var toggleLayerForMapUpdater = function toggleLayerForMapUpdater(state, _ref11) {
var mapIndex = _ref11.mapIndex,
layerId = _ref11.layerId;
var splitMaps = state.splitMaps;
return _objectSpread({}, state, {
splitMaps: splitMaps.map(function (sm, i) {
return i === mapIndex ? _objectSpread({}, splitMaps[i], {
layers: _objectSpread({}, splitMaps[i].layers, (0, _defineProperty2["default"])({}, layerId, !splitMaps[i].layers[layerId]))
}) : sm;
})
});
};
/**
* Add new dataset to `visState`, with option to load a map config along with the datasets
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @param {Object} action action
* @param {Array<Object>|Object} action.datasets - ***required** datasets can be a dataset or an array of datasets
* Each dataset object needs to have `info` and `data` property.
* @param {Object} action.datasets.info -info of a dataset
* @param {string} action.datasets.info.id - id of this dataset. If config is defined, `id` should matches the `dataId` in config.
* @param {string} action.datasets.info.label - A display name of this dataset
* @param {Object} action.datasets.data - ***required** The data object, in a tabular format with 2 properties `fields` and `rows`
* @param {Array<Object>} action.datasets.data.fields - ***required** Array of fields,
* @param {string} action.datasets.data.fields.name - ***required** Name of the field,
* @param {Array<Array>} action.datasets.data.rows - ***required** Array of rows, in a tabular format with `fields` and `rows`
* @param {Object} action.options option object `{centerMap: true, keepExistingConfig: false}`
* @param {Object} action.config map config
* @returns {Object} nextState
* @public
*/
/* eslint-disable max-statements */
exports.toggleLayerForMapUpdater = toggleLayerForMapUpdater;
var updateVisDataUpdater = function updateVisDataUpdater(state, action) {
// datasets can be a single data entries or an array of multiple data entries
var config = action.config,
options = action.options;
var datasets = (0, _utils.toArray)(action.datasets);
var newDataEntries = datasets.reduce(function (accu, _ref12) {
var _ref12$info = _ref12.info,
info = _ref12$info === void 0 ? {} : _ref12$info,
data = _ref12.data;
return _objectSpread({}, accu, {}, (0, _datasetUtils.createNewDataEntry)({
info: info,
data: data
}, state.datasets) || {});
}, {});
if (!Object.keys(newDataEntries).length) {
return state;
} // apply config if passed from action
var previousState = config ? receiveMapConfigUpdater(state, {
payload: {
config: config,
options: options
}
}) : state;
var stateWithNewData = _objectSpread({}, previousState, {
datasets: _objectSpread({}, previousState.datasets, {}, newDataEntries)
}); // previously saved config before data loaded
var _stateWithNewData$fil = stateWithNewData.filterToBeMerged,
filterToBeMerged = _stateWithNewData$fil === void 0 ? [] : _stateWithNewData$fil,
_stateWithNewData$lay = stateWithNewData.layerToBeMerged,
layerToBeMerged = _stateWithNewData$lay === void 0 ? [] : _stateWithNewData$lay,
_stateWithNewData$int = stateWithNewData.interactionToBeMerged,
interactionToBeMerged = _stateWithNewData$int === void 0 ? {} : _stateWithNewData$int,
_stateWithNewData$spl = stateWithNewData.splitMapsToBeMerged,
splitMapsToBeMerged = _stateWithNewData$spl === void 0 ? [] : _stateWithNewData$spl; // We need to merge layers before filters because polygon filters requires layers to be loaded
var mergedState = (0, _visStateMerger.mergeLayers)(stateWithNewData, layerToBeMerged);
mergedState = (0, _visStateMerger.mergeFilters)(mergedState, filterToBeMerged); // merge state with saved splitMaps
mergedState = (0, _visStateMerger.mergeSplitMaps)(mergedState, splitMapsToBeMerged);
var newLayers = mergedState.layers.filter(function (l) {
return l.config.dataId in newDataEntries;
});
if (!newLayers.length) {
// no layer merged, find defaults
var result = addDefaultLayers(mergedState, newDataEntries);
mergedState = result.state;
newLayers = result.newLayers;
}
if (mergedState.splitMaps.length) {
// if map is split, add new layers to splitMaps
newLayers = mergedState.layers.filter(function (l) {
return l.config.dataId in newDataEntries;
});
mergedState = _objectSpread({}, mergedState, {
splitMaps: (0, _splitMapUtils.addNewLayersToSplitMap)(mergedState.splitMaps, newLayers)
});
} // merge state with saved interactions
mergedState = (0, _visStateMerger.mergeInteractions)(mergedState, interactionToBeMerged); // if no tooltips merged add default tooltips
Object.keys(newDataEntries).forEach(function (dataId) {
var tooltipFields = mergedState.interactionConfig.tooltip.config.fieldsToShow[dataId];
if (!Array.isArray(tooltipFields) || !tooltipFields.length) {
mergedState = addDefaultTooltips(mergedState, newDataEntries[dataId]);
}
});
var updatedState = updateAllLayerDomainData(mergedState, Object.keys(newDataEntries)); // register layer animation domain,
// need to be called after layer data is calculated
updatedState = updateAnimationDomain(updatedState);
return updatedState;
};
/* eslint-enable max-statements */
/**
* When a user clicks on the specific map closing icon
* the application will close the selected map
* and will merge the remaining one with the global state
* TODO: i think in the future this action should be called merge map layers with global settings
* @param {Object} state `visState`
* @param {Object} action action
* @returns {Object} nextState
*/
exports.updateVisDataUpdater = updateVisDataUpdater;
function closeSpecificMapAtIndex(state, action) {
// retrieve layers meta data from the remaining map that we need to keep
var indexToRetrieve = 1 - action.payload;
var mapLayers = state.splitMaps[indexToRetrieve].layers;
var layers = state.layers; // update layer visibility
var newLayers = layers.map(function (layer) {
return !mapLayers[layer.id] && layer.config.isVisible ? layer.updateLayerConfig({
// if layer.id is not in mapLayers, it should be inVisible
isVisible: false
}) : layer;
}); // delete map
return _objectSpread({}, state, {
layers: newLayers,
splitMaps: []
});
}
/**
* Trigger file loading dispatch `addDataToMap` if succeed, or `loadFilesErr` if failed
* @memberof visStateUpdaters
* @param {Object} state `visState`
* @param {Object} action action
* @param {Array<Object>} action.files array of fileblob
* @param {Function} action.onFinish action creator to execute after load file su