kepler.gl.geoiq
Version:
kepler.gl is a webgl based application to visualize large scale location data in the browser
300 lines (272 loc) • 9.75 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 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() {
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
};
state = {
aggregatedData: null
};
inDebounce = 0;
componentDidMount() {
this.props.widgetConfigChange(this.props.widget, {
mapState: this.props.mapState
});
}
componentWillReceiveProps(nextProps) {
if (
!_.isEqual(this.props.mapState, nextProps.mapState) &&
nextProps.layers &&
nextProps.widget.config.bounds
) {
this.props.widgetConfigChange(this.props.widget, {
isCalculating: true
});
clearTimeout(this.inDebounce);
this.inDebounce = setTimeout(() => {
this.props.widgetConfigChange(this.props.widget, {
mapState: nextProps.mapState
});
clearTimeout(this.inDebounce);
this.inDebounce = 0;
}, 300);
}
}
updateState = func => {
this.setState({aggregatedData: func});
};
updateWidgetConfig = newProp => {
newProp = {
...newProp,
mapState: this.props.mapState
};
this.props.widgetConfigChange(this.props.widget, newProp);
};
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}
/> */
}