kepler.gl.geoiq
Version:
kepler.gl is a webgl based application to visualize large scale location data in the browser
426 lines (389 loc) • 12.4 kB
JavaScript
// Copyright (c) 2023 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 styled from 'styled-components';
import PropTypes from 'prop-types';
import {sortable} from 'react-anything-sortable';
import _ from 'lodash';
import WidgetConfigurator from './widget-configurator';
import WidgetPanelHeader from './widget-panel-header';
import LoadingSpinner from 'components/common/loading-spinner';
import SliderHandle from 'components/common/slider/slider-bar-handle';
// import layers from '../../../../dist/components/common/icons/layers';
const PanelWrapper = styled.div`
font-size: 12px;
border-radius: 1px;
margin-bottom: 8px;
&.dragging {
cursor: move;
}
`;
const StyledWidgetContent = styled.div`
background-color: ${props => props.theme.panelBackground};
padding: 12px;
`;
const StyledWidgetContentHeader = styled.div`
background-color: ${props => props.theme.panelBackground};
color: #6a7485;
text-align: center;
font-size: 14px;
`;
const StyledWidgetContentValue = styled.div`
background-color: ${props => props.theme.panelBackground};
text-align: center;
font-size: 30px;
`;
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
}
function WidgetPanelFactory() {
@sortable
class WidgetPanel extends Component {
static propTypes = {
widget: PropTypes.object.isRequired,
datasets: PropTypes.object.isRequired,
idx: PropTypes.number.isRequired,
widgetConfigChange: PropTypes.func.isRequired,
widgetTypeChange: PropTypes.func.isRequired,
// layers: propTypes.arrayOf(propTypes.any),
openModal: PropTypes.func.isRequired,
removeWidget: PropTypes.func.isRequired,
onCloseConfig: PropTypes.func,
widgetTypeOptions: PropTypes.arrayOf(PropTypes.any),
widgetVisConfigChange: PropTypes.func,
widgetVisualChannelConfigChange: PropTypes.func,
mapState: PropTypes.object.isRequired,
auth: PropTypes.object.isRequired,
project: PropTypes.object.isRequired
};
state = {
aggregatedData: null
};
inDebounce = 0;
componentDidMount() {
const {
auth,
project,
mapState,
datasets,
filters,
widget,
widgetConfigChange
} = this.props;
const result = widget.calculateAggregationData(
widget,
filters,
datasets,
mapState,
auth,
project
);
if (result) {
result.then(function(result) {
// widget.updateWidgetConfig({
// aggregatedData: result
// });
widgetConfigChange(widget, {aggregatedData: result});
});
}
}
componentWillReceiveProps(nextProps) {
const {
auth,
project,
widget,
filters,
datasets,
mapState,
widgetConfigChange
} = nextProps;
if (widget.config.apiCallRequest) {
widgetConfigChange(widget, {apiCallRequest: false});
this.calculateWidgetAggregationData(
widget,
filters,
datasets,
mapState,
auth,
project,
widgetConfigChange
);
}
if (
!_.isEqual(this.props.mapState, nextProps.mapState) &&
// nextProps.layers &&
nextProps.widget.config.bounds
) {
nextProps.widgetConfigChange(
nextProps.widget,
{
isCalculating: true
},
auth,
project
);
clearTimeout(this.inDebounce);
this.inDebounce = setTimeout(() => {
const result = widget.calculateAggregationData(
widget,
filters,
datasets,
mapState,
auth,
project
);
result.then(function(result) {
widgetConfigChange(widget, {
aggregatedData: result,
isCalculating: false
});
});
// nextProps.widgetConfigChange(
// nextProps.widget,
// {
// mapState: nextProps.mapState
// },
// auth,
// project
// );
clearTimeout(this.inDebounce);
this.inDebounce = 0;
}, 300);
}
}
updateState = func => {
this.setState({aggregatedData: func});
};
calculateWidgetAggregationData(
widget,
filters,
datasets,
mapState,
auth,
project,
widgetConfigChange
) {
const result = widget.calculateAggregationData(
widget,
filters,
datasets,
mapState,
auth,
project
);
if (result) {
result.then(function(result) {
widgetConfigChange(widget, {
aggregatedData: result,
apiCallRequest: false
});
});
}
}
updateWidgetConfig = newProp => {
const {
auth,
widget,
project,
mapState,
datasets,
filters,
widgetConfigChange
} = this.props;
const props = Object.keys(newProp);
if (widget.shouldCalculateAggregatedData(props)) {
// this.calculateWidgetAggregationData(
// widget,
// filters,
// datasets,
// mapState,
// auth,
// project,
// widgetConfigChange
// );
widgetConfigChange(widget, {apiCallRequest: true});
}
newProp = {
...newProp,
mapState
};
this.props.widgetConfigChange(widget, newProp, auth, project);
};
updateWidgetType = newType => {
this.props.widgetTypeChange(this.props.widget, newType);
};
updateWidgetVisConfig = newVisConfig => {
this.props.widgetVisConfigChange(this.props.widget, newVisConfig);
};
updateWidgetVisualChannelConfig = (newConfig, channel, scaleKey) => {
this.props.widgetVisualChannelConfigChange(
this.props.widget,
newConfig,
channel,
scaleKey
);
};
_updateWidgetLabel = ({target: {value}}) => {
this.updateWidgetConfig({label: value});
};
_toggleVisibility = e => {
e.stopPropagation();
const isVisible = !this.props.widget.config.isVisible;
this.updateWidgetConfig({isVisible});
};
_toggleEnableConfig = e => {
e.stopPropagation();
const {
widget: {
config: {isConfigActive}
}
} = this.props;
this.updateWidgetConfig({isConfigActive: !isConfigActive});
};
_removeWidget = e => {
e.stopPropagation();
this.props.removeWidget(this.props.idx);
};
_renderLayerName(id) {
var placeholder = this.props.layers.map(l => {
if (l.id === id) {
return l.config.label;
}
});
return placeholder;
}
render() {
const {widget, layers, idx, datasets, widgetTypeOptions} = this.props;
const {config} = widget;
const {isConfigActive} = config;
return (
<PanelWrapper
active={isConfigActive}
className={`layer-panel ${this.props.className}`}
style={this.props.style}
onMouseDown={this.props.onMouseDown}
onTouchStart={this.props.onTouchStart}
>
<WidgetPanelHeader
isConfigActive={isConfigActive}
id={widget.id}
idx={idx}
isVisible={config.isVisible}
label={config.label}
labelRCGColorValues={datasets[config.dataId].color}
widgetType={widget.name}
onToggleEnableConfig={this._toggleEnableConfig}
onToggleVisibility={this._toggleVisibility}
onUpdateWidgetLabel={this._updateWidgetLabel}
onRemoveWidget={this._removeWidget}
/>
{isConfigActive && (
<WidgetConfigurator
widget={widget}
layers={layers}
datasets={datasets}
widgetTypeOptions={widgetTypeOptions}
openModal={this.props.openModal}
updateLayerConfig={this.updateWidgetConfig}
updateLayerVisualChannelConfig={
this.updateWidgetVisualChannelConfig
}
updateLayerType={this.updateWidgetType}
updateLayerVisConfig={this.updateWidgetVisConfig}
/>
)}
{(widget.type === 'Function' &&
config.isVisible &&
config.aggregatedData &&
config.aggregationType &&
typeof config.aggregatedData !== 'object') ||
config.aggregatedData === 0 ? (
<StyledWidgetContent>
<StyledWidgetContentHeader>
{capitalizeFirstLetter(config.aggregationType)} :{' '}
{/* {this._renderLayerName(config.selectedLayer.id)} :{' '} */}
{config.fieldName}
</StyledWidgetContentHeader>
<StyledWidgetContentValue>
{config.isCalculating ? (
<div style={{display: 'inline-block', marginTop: '5px'}}>
<LoadingSpinner />
</div>
) : (
<div style={{color: '#6a7485'}}>{config.aggregatedData}</div>
)}
</StyledWidgetContentValue>
</StyledWidgetContent>
) : null}
{widget.type === 'Category' &&
config.isVisible &&
config.aggregatedData &&
config.aggregatedData.length &&
config.aggregationType ? (
<StyledWidgetContent>
{config.aggregatedData.map(ad => {
return (
<React.Fragment>
<StyledWidgetContentHeader style={{textAlign: 'inherit'}}>
<div style={{display: 'flex', color: '#6a7485'}}>
{ad.y}{' '}
<div style={{marginLeft: 'auto'}}>{ad.x}</div>
</div>
</StyledWidgetContentHeader>
<SliderHandle
type={'category'}
width={(ad.x / config.aggregatedData[0].x) * 100}
sliderBarListener={null}
/>
<StyledWidgetContentValue />
</React.Fragment>
);
})}
</StyledWidgetContent>
) : null}
</PanelWrapper>
);
}
}
return WidgetPanel;
}
export default WidgetPanelFactory;
{
/* <StyledWidgetContent>
{config.aggregatedData.map(ad => {
return (
<React.Fragment>
<StyledWidgetContentHeader style={{textAlign: 'inherit'}}>
{ad[0]}-{ad[1]}
</StyledWidgetContentHeader>
<StyledWidgetContentValue />
</React.Fragment>
);
})}
</StyledWidgetContent> */
}
{
/* <WidgetBarChart
data={config.aggregatedData}
height={96.9}
width={191.6}
margin={0}
/> */
}