UNPKG

kepler.gl

Version:

kepler.gl is a webgl based application to visualize large scale location data in the browser

216 lines (193 loc) 6.77 kB
// Copyright (c) 2020 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // Copyright (c) 2020 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to tdahe following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import {hexToRgb} from './color-utils'; import uniq from 'lodash.uniq'; import {TRIP_POINT_FIELDS, SORT_ORDER} from 'constants/default-settings'; import {generateHashId} from './utils'; import {validateInputData} from 'processors/data-processor'; import {getGpuFilterProps} from 'utils/gpu-filter-utils'; import {ascending, descending} from 'd3-array'; // apply a color for each dataset // to use as label colors const datasetColors = [ '#8F2FBF', '#005CFF', '#C06C84', '#F8B195', '#547A82', '#3EACA8', '#A2D4AB' ].map(hexToRgb); /** * Random color generator */ function* generateColor() { let index = 0; while (index < datasetColors.length + 1) { if (index === datasetColors.length) { index = 0; } yield datasetColors[index++]; } } export const datasetColorMaker = generateColor(); function getNewDatasetColor(datasets) { const presetColors = datasetColors.map(String); const usedColors = uniq(Object.values(datasets).map(d => String(d.color))).filter(c => presetColors.includes(c) ); if (usedColors.length === presetColors.length) { // if we already depleted the pool of color return datasetColorMaker.next().value; } let color = datasetColorMaker.next().value; while (usedColors.includes(String(color))) { color = datasetColorMaker.next().value; } return color; } export function createNewDataEntry({info = {}, data}, datasets = {}) { const validatedData = validateInputData(data); if (!validatedData) { return {}; } const allData = validatedData.rows; const datasetInfo = { id: generateHashId(4), label: 'new dataset', ...info }; const dataId = datasetInfo.id; // add tableFieldIndex and id to fields // TODO: don't need id and name and tableFieldIndex anymore // Add value accessor instead const fields = validatedData.fields.map((f, i) => ({ ...f, id: f.name, tableFieldIndex: i + 1 })); const allIndexes = allData.map((_, i) => i); return { [dataId]: { ...datasetInfo, color: datasetInfo.color || getNewDatasetColor(datasets), id: dataId, allData, allIndexes, filteredIndex: allIndexes, filteredIndexForDomain: allIndexes, fieldPairs: findPointFieldPairs(fields), fields, gpuFilter: getGpuFilterProps([], dataId, fields) } }; } export function removeSuffixAndDelimiters(layerName, suffix) { return layerName .replace(new RegExp(suffix, 'ig'), '') .replace(/[_,.]+/g, ' ') .trim(); } /** * Find point fields pairs from fields * * @param {Array} fields * @returns {Array} found point fields */ export function findPointFieldPairs(fields) { const allNames = fields.map(f => f.name.toLowerCase()); // get list of all fields with matching suffixes return allNames.reduce((carry, fieldName, idx) => { // This search for pairs will early exit if found. for (const suffixPair of TRIP_POINT_FIELDS) { // match first suffix``` if (fieldName.endsWith(suffixPair[0])) { // match second suffix const otherPattern = new RegExp(`${suffixPair[0]}\$`); const partner = fieldName.replace(otherPattern, suffixPair[1]); const partnerIdx = allNames.findIndex(d => d === partner); if (partnerIdx > -1) { const defaultName = removeSuffixAndDelimiters(fieldName, suffixPair[0]); carry.push({ defaultName, pair: { lat: { fieldIdx: idx, value: fields[idx].name }, lng: { fieldIdx: partnerIdx, value: fields[partnerIdx].name } }, suffix: suffixPair }); return carry; } } } return carry; }, []); } export function sortDatasetByColumn(dataset, column, mode) { const {allIndexes, fields, allData} = dataset; const fieldIndex = fields.findIndex(f => f.name === column); if (fieldIndex < 0) { return dataset; } const sortBy = SORT_ORDER[mode] || SORT_ORDER.ASCENDING; if (sortBy === SORT_ORDER.UNSORT) { return { ...dataset, sortColumn: {}, sortOrder: null }; } const sortFunction = sortBy === SORT_ORDER.ASCENDING ? ascending : descending; const sortOrder = allIndexes .slice() .sort((a, b) => sortFunction(allData[a][fieldIndex], allData[b][fieldIndex])); return { ...dataset, sortColumn: { [column]: sortBy }, sortOrder }; }