UNPKG

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
"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