UNPKG

kepler.gl

Version:

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

835 lines (774 loc) 25.1 kB
// Copyright (c) 2018 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. import React, {Component} from 'react'; import PropTypes from 'prop-types'; import styled from 'styled-components'; import { Button, PanelLabel, SidePanelSection } from 'components/common/styled-components'; import ItemSelector from 'components/common/item-selector/item-selector'; import VisConfigByFieldSelector from './vis-config-by-field-selector'; import LayerColumnConfig from './layer-column-config'; import LayerTypeSelector from './layer-type-selector'; import DimensionScaleSelector from './dimension-scale-selector'; import ColorSelector from './color-selector'; import SourceDataSelector from '../source-data-selector'; import VisConfigSwitch from './vis-config-switch'; import VisConfigSlider from './vis-config-slider'; import LayerConfigGroup from './layer-config-group'; import TextLabelPanel from './text-label-panel'; import {LAYER_VIS_CONFIGS} from 'layers/layer-factory'; import {capitalizeFirstLetter} from 'utils/utils'; import { LAYER_TYPES, CHANNEL_SCALE_SUPPORTED_FIELDS } from 'constants/default-settings'; const StyledLayerConfigurator = styled.div.attrs({ className: 'layer-panel__config' })` position: relative; margin-top: 12px; `; const StyledLayerVisualConfigurator = styled.div.attrs({ className: 'layer-panel__config__visualC-config' })` margin-top: 12px; `; export default class LayerConfigurator extends Component { static propTypes = { layer: PropTypes.object.isRequired, datasets: PropTypes.object.isRequired, layerTypeOptions: PropTypes.arrayOf(PropTypes.any).isRequired, openModal: PropTypes.func.isRequired, updateLayerConfig: PropTypes.func.isRequired, updateLayerType: PropTypes.func.isRequired, updateLayerVisConfig: PropTypes.func.isRequired, updateLayerVisualChannelConfig: PropTypes.func.isRequired }; _renderPointLayerConfig(props) { return this._renderScatterplotLayerConfig(props); } _renderIconLayerConfig(props) { return this._renderScatterplotLayerConfig(props); } _renderScatterplotLayerConfig({ layer, visConfiguratorProps, layerChannelConfigProps, layerConfiguratorProps }) { return ( <StyledLayerVisualConfigurator> {/* Color */} <LayerConfigGroup label={'color'}> {layer.config.colorField ? ( <ColorRangeConfig {...visConfiguratorProps} /> ) : ( <LayerColorSelector {...layerConfiguratorProps} /> )} <ChannelByValueSelector channel={layer.visualChannels.color} {...layerChannelConfigProps} /> <VisConfigSlider {...LAYER_VIS_CONFIGS.opacity} {...visConfiguratorProps} /> </LayerConfigGroup> {/* Radius */} <LayerConfigGroup label={'radius'}> {!layer.config.sizeField ? ( <VisConfigSlider {...LAYER_VIS_CONFIGS.radius} {...visConfiguratorProps} label={false} disabled={Boolean(layer.config.sizeField)} /> ) : ( <VisConfigSlider {...LAYER_VIS_CONFIGS.radiusRange} {...visConfiguratorProps} disabled={ !layer.config.sizeField || layer.config.visConfig.fixedRadius } /> )} <ChannelByValueSelector channel={layer.visualChannels.size} {...layerChannelConfigProps} /> {layer.config.sizeField ? ( <VisConfigSwitch {...LAYER_VIS_CONFIGS.fixedRadius} {...visConfiguratorProps} disabled={!layer.config.sizeField} /> ) : null} </LayerConfigGroup> {/* outline */} {layer.type === LAYER_TYPES.point ? ( <LayerConfigGroup {...LAYER_VIS_CONFIGS.outline} {...visConfiguratorProps} > <VisConfigSlider {...LAYER_VIS_CONFIGS.thickness} {...visConfiguratorProps} label={false} disabled={!layer.config.visConfig.outline} /> </LayerConfigGroup> ) : null} {/* text label */} <TextLabelPanel visConfiguratorProps={visConfiguratorProps} layerConfiguratorProps={layerConfiguratorProps} textLabel={layer.config.textLabel} /> {/* high precision */} <LayerConfigGroup {...LAYER_VIS_CONFIGS['hi-precision']} {...visConfiguratorProps} /> </StyledLayerVisualConfigurator> ); } _renderClusterLayerConfig({ layer, visConfiguratorProps, layerConfiguratorProps, layerChannelConfigProps }) { return ( <StyledLayerVisualConfigurator> {/* Color */} <LayerConfigGroup label={'color'}> <ColorRangeConfig {...visConfiguratorProps} /> <AggrColorScaleSelector {...layerConfiguratorProps} /> <ChannelByValueSelector channel={layer.visualChannels.color} {...layerChannelConfigProps} /> {layer.visConfigSettings.colorAggregation.condition(layer.config) ? <AggregationTypeSelector {...layer.visConfigSettings.colorAggregation} {...layerChannelConfigProps} channel={layer.visualChannels.color} /> : null} <VisConfigSlider {...layer.visConfigSettings.opacity} {...visConfiguratorProps} /> </LayerConfigGroup> {/* Cluster Radius */} <LayerConfigGroup label={'radius'}> <VisConfigSlider {...layer.visConfigSettings.clusterRadius} {...visConfiguratorProps} /> <VisConfigSlider {...layer.visConfigSettings.radiusRange} {...visConfiguratorProps} /> </LayerConfigGroup> </StyledLayerVisualConfigurator> ); } _renderHeatmapLayerConfig({ layer, visConfiguratorProps, layerConfiguratorProps, layerChannelConfigProps }) { return ( <StyledLayerVisualConfigurator> {/* Color */} <LayerConfigGroup label={'color'}> <ColorRangeConfig {...visConfiguratorProps} /> <VisConfigSlider {...layer.visConfigSettings.opacity} {...visConfiguratorProps} /> </LayerConfigGroup> {/* Radius */} <LayerConfigGroup label={'radius'}> <VisConfigSlider {...layer.visConfigSettings.radius} {...visConfiguratorProps} label={false} /> </LayerConfigGroup> {/* Weight */} <LayerConfigGroup label={'weight'}> <ChannelByValueSelector channel={layer.visualChannels.weight} {...layerChannelConfigProps} /> </LayerConfigGroup> </StyledLayerVisualConfigurator> ); } _renderGridLayerConfig(props) { return this._renderAggregationLayerConfig(props); } _renderHexagonLayerConfig(props) { return this._renderAggregationLayerConfig(props); } _renderAggregationLayerConfig({ layer, visConfiguratorProps, layerConfiguratorProps, layerChannelConfigProps }) { const {config} = layer; const { visConfig: {enable3d} } = config; const elevationByDescription = 'When off, height is based on count of points'; const colorByDescription = 'When off, color is based on count of points'; return ( <StyledLayerVisualConfigurator> {/* Color */} <LayerConfigGroup label={'color'}> <ColorRangeConfig {...visConfiguratorProps} /> <AggrColorScaleSelector {...layerConfiguratorProps} /> <ChannelByValueSelector channel={layer.visualChannels.color} {...layerChannelConfigProps} /> {layer.visConfigSettings.colorAggregation.condition(layer.config) ? ( <AggregationTypeSelector {...layer.visConfigSettings.colorAggregation} {...layerChannelConfigProps} descreiption={colorByDescription} channel={layer.visualChannels.color} /> ) : null} {layer.visConfigSettings.percentile && layer.visConfigSettings.percentile.condition(layer.config) ? ( <VisConfigSlider {...layer.visConfigSettings.percentile} {...visConfiguratorProps} /> ) : null} <VisConfigSlider {...layer.visConfigSettings.opacity} {...visConfiguratorProps} /> </LayerConfigGroup> {/* Cell size */} <LayerConfigGroup label={'radius'}> <VisConfigSlider {...layer.visConfigSettings.worldUnitSize} {...visConfiguratorProps} /> <VisConfigSlider {...layer.visConfigSettings.coverage} {...visConfiguratorProps} /> </LayerConfigGroup> {/* Elevation */} {layer.visConfigSettings.enable3d ? <LayerConfigGroup {...layer.visConfigSettings.enable3d} {...visConfiguratorProps} > <VisConfigSlider {...layer.visConfigSettings.elevationScale} {...visConfiguratorProps} /> <ChannelByValueSelector {...layerChannelConfigProps} channel={layer.visualChannels.size} description={elevationByDescription} disabled={!enable3d} /> {layer.visConfigSettings.sizeAggregation.condition(layer.config) ? ( <AggregationTypeSelector {...layer.visConfigSettings.sizeAggregation} {...layerChannelConfigProps} channel={layer.visualChannels.size} /> ) : null} {layer.visConfigSettings.elevationPercentile.condition( layer.config ) ? ( <VisConfigSlider {...layer.visConfigSettings.elevationPercentile} {...visConfiguratorProps} /> ) : null} </LayerConfigGroup> : null} {/* High Precision */} <LayerConfigGroup {...layer.visConfigSettings['hi-precision']} {...visConfiguratorProps} /> </StyledLayerVisualConfigurator> ); } // TODO: Shan move these into layer class _renderHexagonIdLayerConfig({ layer, visConfiguratorProps, layerConfiguratorProps, layerChannelConfigProps }) { return ( <StyledLayerVisualConfigurator> {/* Color */} <LayerConfigGroup label={'color'}> {layer.config.colorField ? ( <ColorRangeConfig {...visConfiguratorProps} /> ) : ( <LayerColorSelector {...layerConfiguratorProps} /> )} <ChannelByValueSelector channel={layer.visualChannels.color} {...layerChannelConfigProps} /> <VisConfigSlider {...LAYER_VIS_CONFIGS.opacity} {...visConfiguratorProps} /> </LayerConfigGroup> {/* Coverage */} <LayerConfigGroup label={'coverage'}> {!layer.config.coverageField ? ( <VisConfigSlider {...layer.visConfigSettings.coverage} {...visConfiguratorProps} /> ) : ( <VisConfigSlider {...layer.visConfigSettings.coverageRange} {...visConfiguratorProps} /> )} <ChannelByValueSelector channel={layer.visualChannels.coverage} {...layerChannelConfigProps} /> </LayerConfigGroup> {/* height */} <LayerConfigGroup {...LAYER_VIS_CONFIGS.enable3d} {...visConfiguratorProps} > <ChannelByValueSelector channel={layer.visualChannels.size} {...layerChannelConfigProps} /> <VisConfigSlider {...LAYER_VIS_CONFIGS.elevationRange} {...visConfiguratorProps} /> </LayerConfigGroup> {/* high precision */} <LayerConfigGroup {...LAYER_VIS_CONFIGS['hi-precision']} {...visConfiguratorProps} /> </StyledLayerVisualConfigurator> ); } _renderArcLayerConfig(args) { return this._renderLineLayerConfig(args); } _renderLineLayerConfig({ layer, visConfiguratorProps, layerConfiguratorProps, layerChannelConfigProps }) { return ( <StyledLayerVisualConfigurator> {/* Color */} <LayerConfigGroup label={'color'}> {layer.config.colorField ? ( <ColorRangeConfig {...visConfiguratorProps} /> ) : ( <ArcLayerColorSelector layer={layer} onChangeConfig={layerConfiguratorProps.onChange} onChangeVisConfig={visConfiguratorProps.onChange} /> )} <ChannelByValueSelector channel={layer.visualChannels.color} {...layerChannelConfigProps} /> <VisConfigSlider {...LAYER_VIS_CONFIGS.opacity} {...visConfiguratorProps} /> </LayerConfigGroup> {/* thickness */} <LayerConfigGroup label={'stroke'}> {layer.config.sizeField ? ( <VisConfigSlider {...LAYER_VIS_CONFIGS.strokeWidthRange} {...visConfiguratorProps} disabled={!layer.config.sizeField} /> ) : ( <VisConfigSlider {...LAYER_VIS_CONFIGS.thickness} {...visConfiguratorProps} /> )} <ChannelByValueSelector channel={layer.visualChannels.size} {...layerChannelConfigProps} /> </LayerConfigGroup> {/* high precision */} <LayerConfigGroup {...LAYER_VIS_CONFIGS['hi-precision']} {...visConfiguratorProps} /> </StyledLayerVisualConfigurator> ); } _renderGeojsonLayerConfig({ layer, visConfiguratorProps, layerConfiguratorProps, layerChannelConfigProps }) { const { meta: {featureTypes = {}}, config: {visConfig} } = layer; return ( <StyledLayerVisualConfigurator> {/* Color By */} <LayerConfigGroup label={'color'}> {featureTypes.polygon ? ( <VisConfigSwitch {...visConfiguratorProps} {...LAYER_VIS_CONFIGS.filled} /> ) : null} {layer.config.colorField ? ( <ColorRangeConfig {...visConfiguratorProps} /> ) : ( <LayerColorSelector {...layerConfiguratorProps} /> )} <ChannelByValueSelector channel={layer.visualChannels.color} {...layerChannelConfigProps} /> <VisConfigSlider {...LAYER_VIS_CONFIGS.opacity} {...visConfiguratorProps} /> </LayerConfigGroup> {/* Stroke Width */} {featureTypes.line || featureTypes.polygon ? ( <LayerConfigGroup label="stroke" {...visConfiguratorProps} {...(featureTypes.polygon ? LAYER_VIS_CONFIGS.stroked : {})} > {visConfig.stroked ? <div> <VisConfigSlider {...LAYER_VIS_CONFIGS.thickness} {...visConfiguratorProps} /> <ChannelByValueSelector channel={layer.visualChannels.size} {...layerChannelConfigProps} /> <VisConfigSlider {...LAYER_VIS_CONFIGS.strokeWidthRange} {...visConfiguratorProps} disabled={!layer.config.sizeField} /> </div> : null} </LayerConfigGroup> ) : null} {/* Elevation */} {featureTypes.polygon && visConfig.filled ? ( <LayerConfigGroup {...visConfiguratorProps} {...LAYER_VIS_CONFIGS.enable3d} > <VisConfigSlider {...LAYER_VIS_CONFIGS.elevationScale} {...visConfiguratorProps} /> <ChannelByValueSelector channel={layer.visualChannels.height} {...layerChannelConfigProps} /> <VisConfigSwitch {...visConfiguratorProps} {...LAYER_VIS_CONFIGS.wireframe} /> </LayerConfigGroup> ) : null} {/* Radius */} {featureTypes.point ? ( <div> <VisConfigSlider {...LAYER_VIS_CONFIGS.radius} {...visConfiguratorProps} label="Point Radius" disabled={Boolean(layer.config.radiusField)} /> <ChannelByValueSelector channel={layer.visualChannels.radius} {...layerChannelConfigProps} /> <VisConfigSlider {...LAYER_VIS_CONFIGS.radiusRange} {...visConfiguratorProps} disabled={!layer.config.radiusField} /> </div> ) : null} {/* high precision */} <LayerConfigGroup {...LAYER_VIS_CONFIGS['hi-precision']} {...visConfiguratorProps} /> </StyledLayerVisualConfigurator> ); } render() { const { layer, datasets, updateLayerConfig, layerTypeOptions, updateLayerType } = this.props; const {fields = [], fieldPairs} = layer.config.dataId ? datasets[layer.config.dataId] : {}; const {config} = layer; const commonConfigProp = { layer, fields }; const visConfiguratorProps = { ...commonConfigProp, onChange: this.props.updateLayerVisConfig }; const layerConfiguratorProps = { ...commonConfigProp, onChange: updateLayerConfig }; const layerChannelConfigProps = { ...commonConfigProp, onChange: this.props.updateLayerVisualChannelConfig }; const renderTemplate = layer.type && `_render${capitalizeFirstLetter(layer.type)}LayerConfig`; return ( <StyledLayerConfigurator> {layer.layerInfoModal ? <HowToButton onClick={() => this.props.openModal(layer.layerInfoModal)}/> : null} <LayerConfigGroup label={'basic'}> {Object.keys(datasets).length > 1 && ( <SourceDataSelector datasets={datasets} id={layer.id} disabled={layer.tyep && config.columns} dataId={config.dataId} onSelect={value => updateLayerConfig({dataId: value})} /> )} <LayerTypeSelector layer={layer} layerTypeOptions={layerTypeOptions} onSelect={updateLayerType} /> <LayerColumnConfig layer={layer} fields={fields} fieldPairs={fieldPairs} updateLayerConfig={updateLayerConfig} updateLayerType={this.props.updateLayerType} /> </LayerConfigGroup> {this[renderTemplate] && this[renderTemplate]({ layer, visConfiguratorProps, layerChannelConfigProps, layerConfiguratorProps })} </StyledLayerConfigurator> ); } } /* * Componentize config component into pure functional components */ const StyledHowToButton = styled.div` position: absolute; right: 0; top: 0; `; export const HowToButton = ({onClick}) => ( <StyledHowToButton> <Button secondary small onClick={onClick}>How to</Button> </StyledHowToButton> ); export const LayerColorSelector = ({layer, onChange, label}) => ( <SidePanelSection disabled={layer.config.colorField}> <ColorSelector colorSets={[ { selectedColor: layer.config.color, setColor: rgbValue => onChange({color: rgbValue}) } ]} /> </SidePanelSection> ); export const ArcLayerColorSelector = ({ layer, onChangeConfig, onChangeVisConfig }) => ( <SidePanelSection> <ColorSelector colorSets={[ { selectedColor: layer.config.color, setColor: rgbValue => onChangeConfig({color: rgbValue}), label: 'Source' }, { selectedColor: layer.config.visConfig.targetColor || layer.config.color, setColor: rgbValue => onChangeVisConfig({targetColor: rgbValue}), label: 'Target' } ]} /> </SidePanelSection> ); export const ColorRangeConfig = ({layer, onChange}) => ( <SidePanelSection> <ColorSelector colorSets={[ { selectedColor: layer.config.visConfig.colorRange, isRange: true, setColor: colorRange => onChange({colorRange}) } ]} /> </SidePanelSection> ); export const ChannelByValueSelector = ({ layer, channel, onChange, fields, description }) => { const { channelScaleType, domain, field, key, property, range, scale, defaultMeasure, supportedFieldTypes } = channel; const channelSupportedFieldTypes = supportedFieldTypes || CHANNEL_SCALE_SUPPORTED_FIELDS[channelScaleType]; const supportedFields = fields.filter(({type}) => channelSupportedFieldTypes.includes(type) ); const scaleOptions = layer.getScaleOptions(channel.key); const showScale = !layer.isAggregated && layer.config[scale] && scaleOptions.length > 1; const defaultDescription = `Calculate ${property} based on selected field`; return ( <VisConfigByFieldSelector channel={channel.key} description={description || defaultDescription} domain={layer.config[domain]} fields={supportedFields} id={layer.id} key={`${key}-channel-selector`} property={property} placeholder={defaultMeasure || 'Select a field'} range={layer.config.visConfig[range]} scaleOptions={scaleOptions} scaleType={scale ? layer.config[scale] : null} selectedField={layer.config[field]} showScale={showScale} updateField={val => onChange({[field]: val}, key)} updateScale={val => onChange({[scale]: val}, key)} /> ); }; export const AggrColorScaleSelector = ({layer, onChange}) => { const scaleOptions = layer.getScaleOptions('color'); return ( Array.isArray(scaleOptions) && scaleOptions.length > 1 ? <DimensionScaleSelector label="Color Scale" options={scaleOptions} scaleType={layer.config.colorScale} onSelect={val => onChange({colorScale: val}, 'color')} /> : null ); }; export const AggregationTypeSelector = ({layer, channel, onChange}) => { const {field, aggregation, key} = channel; const selectedField = layer.config[field]; const {visConfig} = layer.config; // aggregation should only be selectable when field is selected const aggregationOptions = layer.getAggregationOptions(key); return ( <SidePanelSection> <PanelLabel>{`Aggregate ${selectedField.name} by`}</PanelLabel> <ItemSelector selectedItems={visConfig[aggregation]} options={aggregationOptions} multiSelect={false} searchable={false} onChange={value => onChange( { visConfig: { ...layer.config.visConfig, [aggregation]: value } }, channel.key ) } /> </SidePanelSection> ); }; /* eslint-enable max-params */