kepler.gl.geoiq
Version:
kepler.gl is a webgl based application to visualize large scale location data in the browser
451 lines (416 loc) • 13.3 kB
JavaScript
// Copyright (c) 2019 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 LayerTypeSelector from './layer-type-selector';
import DimensionScaleSelector from './dimension-scale-selector';
import ColorSelector from './color-selector';
import SourceDataSelector from 'components/side-panel/source-data-selector';
import LayerConfigGroup, {
ConfigGroupCollapsibleContent
} from './layer-config-group';
import WidgetLayerSelector from './widget-layer-selector';
import WidgetAggregationSelector from './widget-aggregation-selector';
import WidgetFieldSelector from './widget-field-selector';
import WidgetConfigGroup from './widget-config-group';
import {capitalizeFirstLetter} from 'utils/utils';
import {CHANNEL_SCALE_SUPPORTED_FIELDS} from 'constants/default-settings';
// import files from '../../../../dist/components/common/icons/files';
// import layerPanel from '../../../../dist/components/side-panel/layer-panel/layer-panel';
const StyledLayerConfigurator = styled.div.attrs({
className: 'layer-panel__config'
})`
position: relative;
margin-top: 12px;
`;
const StyledWidgetVisualConfigurator = styled.div.attrs({
className: 'layer-panel__config__visualC-config'
})`
margin-top: 12px;
`;
export default class WidgetConfigurator extends Component {
static propTypes = {
widget: PropTypes.object.isRequired,
datasets: PropTypes.object.isRequired,
widgetTypeOptions: 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
};
_renderFunctionWidgetConfig(props) {
return this._renderAggregationWidgetConfig(props);
}
_renderAggregationWidgetConfig({
layers,
widget,
datasets,
dataId,
widgetConfiguratorProps
}) {
const {fields} = widget.config.dataId ? datasets[widget.config.dataId] : {};
return (
<StyledWidgetVisualConfigurator>
<LayerConfigGroup label={'layer'}>
<WidgetLayerSelector
widget={widget}
layers={layers}
dataId={dataId}
onSelect={value =>
this.props.updateLayerConfig({selectedLayer: value})
}
/>
</LayerConfigGroup>
<LayerConfigGroup label={'Aggregation'}>
<WidgetAggregationSelector
widget={widget}
{...widgetConfiguratorProps}
/>
{widget.config.aggregationType ? (
widget.config.aggregationType === 'count' ? null : (
<WidgetFieldSelector
fields={fields}
widget={widget}
placeholder={'empty'}
onChange={this.props.updateLayerConfig}
/>
)
) : null}
</LayerConfigGroup>
<WidgetConfigGroup
label={'Dynamic'}
property={'bounds'}
description={'Calculation on the basis of bounds of map on screen'}
{...widgetConfiguratorProps}
/>
</StyledWidgetVisualConfigurator>
);
}
_renderCategoryWidgetConfig(props) {
return this._renderCategoryConfig(props);
}
_renderCategoryConfig({
layers,
widget,
datasets,
dataId,
widgetConfiguratorProps
}) {
const {fields} = widget.config.dataId ? datasets[widget.config.dataId] : {};
return (
<StyledWidgetVisualConfigurator>
<LayerConfigGroup label={'layer'}>
<WidgetLayerSelector
widget={widget}
layers={layers}
dataId={dataId}
onSelect={value =>
this.props.updateLayerConfig({selectedLayer: value})
}
/>
</LayerConfigGroup>
<LayerConfigGroup label={'Categorize by'}>
<WidgetFieldSelector
fields={fields}
widget={widget}
placeholder={'empty'}
type={'category'}
onChange={this.props.updateLayerConfig}
/>
</LayerConfigGroup>
<LayerConfigGroup label={'Aggregation'}>
<WidgetAggregationSelector
widget={widget}
{...widgetConfiguratorProps}
/>
{widget.config.aggregationType ? (
widget.config.aggregationType === 'count' ? null : (
<WidgetFieldSelector
fields={fields}
widget={widget}
placeholder={'empty'}
onChange={this.props.updateLayerConfig}
/>
)
) : null}
</LayerConfigGroup>
<WidgetConfigGroup
label={'Dynamic'}
property={'bounds'}
description={'Calculation on the basis of bounds of map on screen'}
{...widgetConfiguratorProps}
/>
</StyledWidgetVisualConfigurator>
);
}
_findLayerIdx = (layers, id) => {
// console.log('inside_findLayerIdx', layers, id);
layers.map((key, lidget) => {
if (lidget.id === id) {
// console.log(key);
}
});
};
render() {
const {
widget,
layers,
datasets,
updateLayerConfig,
widgetTypeOptions,
updateLayerType
} = this.props;
const {fields = []} = widget.config.dataId
? datasets[widget.config.dataId]
: {};
const {config} = widget;
const dataId = config.dataId;
const commonConfigProp = {
widget,
fields
};
const visConfiguratorProps = {
...commonConfigProp,
onChange: this.props.updateLayerVisConfig
};
const widgetConfiguratorProps = {
...commonConfigProp,
onChange: updateLayerConfig
};
const layerChannelConfigProps = {
...commonConfigProp,
onChange: this.props.updateLayerVisualChannelConfig
};
const renderTemplate =
widget.type && `_render${capitalizeFirstLetter(widget.type)}WidgetConfig`;
// console.log('layer inside widget-configurator', layer);
return (
<StyledLayerConfigurator>
{widget.layerInfoModal ? (
<HowToButton
onClick={() => this.props.openModal(widget.layerInfoModal)}
/>
) : null}
<LayerConfigGroup
label={'basic'}
collapsible
expanded={Object.keys(datasets).length > 1 && widget.type}
>
<LayerTypeSelector
layer={widget}
widgetTypeOptions={widgetTypeOptions}
onSelect={updateLayerType}
/>
<ConfigGroupCollapsibleContent>
{Object.keys(datasets).length > 1 && (
<SourceDataSelector
datasets={datasets}
id={widget.id}
disabled={widget.tyep && config.selectedField}
dataId={config.dataId}
onSelect={value =>
updateLayerConfig({
dataId: value,
aggregationType: null,
selectedLayer: null,
aggregatedData: null
})
}
/>
)}
</ConfigGroupCollapsibleContent>
</LayerConfigGroup>
{this[renderTemplate] &&
this[renderTemplate]({
widget,
layers,
datasets,
dataId,
visConfiguratorProps,
layerChannelConfigProps,
widgetConfiguratorProps
})}
</StyledLayerConfigurator>
);
}
}
/*
* Componentize config component into pure functional components
*/
const StyledHowToButton = styled.div`
position: absolute;
right: 12px;
top: -4px;
`;
export const HowToButton = ({onClick}) => (
<StyledHowToButton>
<Button link small onClick={onClick}>
How to
</Button>
</StyledHowToButton>
);
export const LayerColorSelector = ({layer, onChange}) => (
<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 = ({
widget,
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 = widget.getScaleOptions(channel.key);
const showScale =
!widget.isAggregated && widget.config[scale] && scaleOptions.length > 1;
const defaultDescription = `Calculate ${property} based on selected field`;
return (
<VisConfigByFieldSelector
channel={channel.key}
description={description || defaultDescription}
domain={widget.config[domain]}
fields={supportedFields}
id={widget.id}
key={`${key}-channel-selector`}
property={property}
placeholder={defaultMeasure || 'Select a field'}
range={widget.config.visConfig[range]}
scaleOptions={scaleOptions}
scaleType={scale ? widget.config[scale] : null}
selectedField={widget.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 WidgetAggregationSelectors = () => {
return <LayerConfigGroup label={'Aggregation'} collapsible />;
};
export const AggregationTypeSelector = ({widget, channel, onChange}) => {
const {field, aggregation, key} = channel;
const selectedField = widget.config[field];
const {visConfig} = widget.config;
// aggregation should only be selectable when field is selected
const aggregationOptions = widget.getAggregationOptions(key);
return (
<SidePanelSection>
<PanelLabel>{`Aggregate ${selectedField.name} by`}</PanelLabel>
<ItemSelector
selectedItems={visConfig[aggregation]}
options={aggregationOptions}
multiSelect={false}
searchable={false}
onChange={value =>
onChange(
{
visConfig: {
...widget.config.visConfig,
[aggregation]: value
}
},
channel.key
)
}
/>
</SidePanelSection>
);
};
/* eslint-enable max-params */