Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[geospatial] provide more flexible Spatial controls #4032

Merged
merged 1 commit into from
Dec 15, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def get_git_sha():
'flask-wtf==0.14.2',
'flower==0.9.1',
'future>=0.16.0, <0.17',
'python-geohash==0.8.5',
'humanize==0.5.1',
'gunicorn==19.7.1',
'idna==2.5',
Expand Down
2 changes: 1 addition & 1 deletion superset/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def parse_manifest_json():
with open(MANIFEST_FILE, 'r') as f:
manifest = json.load(f)
except Exception:
print('no manifest file found at ' + MANIFEST_FILE)
pass


def get_manifest_file(filename):
Expand Down
2 changes: 1 addition & 1 deletion superset/assets/javascripts/components/PopoverSection.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default function PopoverSection({ title, isSelected, children, onSelect,
&nbsp;
<i className={isSelected ? 'fa fa-check text-primary' : ''} />
</div>
<div>
<div className="m-t-5 m-l-5">
{children}
</div>
</div>);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export default class DateFilterControl extends React.Component {
renderPopover() {
return (
<Popover id="filter-popover">
<div style={{ width: '240px' }}>
<div style={{ width: '250px' }}>
<PopoverSection
title="Fixed"
isSelected={this.state.type === 'fix'}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const propTypes = {
valueRenderer: PropTypes.func,
valueKey: PropTypes.string,
options: PropTypes.array,
placeholder: PropTypes.string,
};

const defaultProps = {
Expand Down Expand Up @@ -105,10 +106,11 @@ export default class SelectControl extends React.PureComponent {
}
render() {
// Tab, comma or Enter will trigger a new option created for FreeFormSelect
const placeholder = this.props.placeholder || t('Select %s', this.state.options.length);
const selectProps = {
multi: this.props.multi,
name: `select-${this.props.name}`,
placeholder: t('Select %s', this.state.options.length),
placeholder,
options: this.state.options,
value: this.props.value,
labelKey: 'label',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Row, Col, Button, FormControl, Label, OverlayTrigger, Popover,
} from 'react-bootstrap';
import 'react-datetime/css/react-datetime.css';

import ControlHeader from '../ControlHeader';
import SelectControl from './SelectControl';
import PopoverSection from '../../../components/PopoverSection';
import Checkbox from '../../../components/Checkbox';
import { t } from '../../../locales';

const spatialTypes = {
latlong: 'latlong',
delimited: 'delimited',
geohash: 'geohash',
};

const propTypes = {
onChange: PropTypes.func,
value: PropTypes.object,
animation: PropTypes.bool,
choices: PropTypes.array,
};

const defaultProps = {
onChange: () => {},
animation: true,
choices: [],
};

export default class SpatialControl extends React.Component {
constructor(props) {
super(props);
const v = props.value || {};
let defaultCol;
if (props.choices.length > 0) {
defaultCol = props.choices[0][0];
}
this.state = {
type: v.type || spatialTypes.latlong,
delimiter: v.delimiter || ',',
latCol: v.latCol || defaultCol,
lonCol: v.lonCol || defaultCol,
lonlatCol: v.lonlatCol || defaultCol,
reverseCheckbox: v.reverseCheckbox || false,
geohashCol: v.geohashCol || defaultCol,
value: null,
errors: [],
};
this.onDelimiterChange = this.onDelimiterChange.bind(this);
this.toggleCheckbox = this.toggleCheckbox.bind(this);
this.onChange = this.onChange.bind(this);
}
componentDidMount() {
this.onChange();
}
onChange() {
const type = this.state.type;
const value = { type };
const errors = [];
const errMsg = t('Invalid lat/long configuration.');
if (type === spatialTypes.latlong) {
value.latCol = this.state.latCol;
value.lonCol = this.state.lonCol;
if (!value.lonCol || !value.latCol) {
errors.push(errMsg);
}
} else if (type === spatialTypes.delimited) {
value.lonlatCol = this.state.lonlatCol;
value.delimiter = this.state.delimiter;
value.reverseCheckbox = this.state.reverseCheckbox;
if (!value.lonlatCol || !value.delimiter) {
errors.push(errMsg);
}
} else if (type === spatialTypes.geohash) {
value.geohashCol = this.state.geohashCol;
if (!value.geohashCol) {
errors.push(errMsg);
}
}
this.setState({ value, errors });
this.props.onChange(value, errors);
}
onDelimiterChange(event) {
this.setState({ delimiter: event.target.value }, this.onChange);
}
setType(type) {
this.setState({ type }, this.onChange);
}
close() {
this.refs.trigger.hide();
}
toggleCheckbox() {
this.setState({ reverseCheckbox: !this.state.reverseCheckbox }, this.onChange);
}
renderLabelContent() {
if (this.state.errors.length > 0) {
return 'N/A';
}
if (this.state.type === spatialTypes.latlong) {
return `${this.state.lonCol} | ${this.state.latCol}`;
} else if (this.state.type === spatialTypes.delimited) {
return `${this.state.lonlatCol}`;
} else if (this.state.type === spatialTypes.geohash) {
return `${this.state.geohashCol}`;
}
return null;
}
renderSelect(name, type) {
return (
<SelectControl
name={name}
choices={this.props.choices}
value={this.state[name]}
clearable={false}
onFocus={() => {
this.setType(type);
}}
onChange={(value) => {
this.setState({ [name]: value }, this.onChange);
}}
/>
);
}
renderPopover() {
return (
<Popover id="filter-popover">
<div style={{ width: '300px' }}>
<PopoverSection
title="Longitude & Latitude columns"
isSelected={this.state.type === spatialTypes.latlong}
onSelect={this.setType.bind(this, spatialTypes.latlong)}
>
<Row>
<Col md={6}>
Longitude
{this.renderSelect('lonCol', spatialTypes.latlong)}
</Col>
<Col md={6}>
Latitude
{this.renderSelect('latCol', spatialTypes.latlong)}
</Col>
</Row>
</PopoverSection>
<PopoverSection
title="Delimited long & lat single column"
isSelected={this.state.type === spatialTypes.delimited}
onSelect={this.setType.bind(this, spatialTypes.delimited)}
>
<Row>
<Col md={6}>
Column
{this.renderSelect('lonlatCol', spatialTypes.delimited)}
</Col>
<Col md={6}>
Delimiter
<FormControl
onFocus={this.setType.bind(this, spatialTypes.delimited)}
value={this.state.delimiter}
onChange={this.onDelimiterChange}
placeholder="delimiter"
bsSize="small"
/>
</Col>
</Row>
<div>
{t('Reverse lat/long ')}
<Checkbox checked={this.state.reverseCheckbox} onChange={this.toggleCheckbox} />
</div>
</PopoverSection>
<PopoverSection
title="Geohash"
isSelected={this.state.type === spatialTypes.geohash}
onSelect={this.setType.bind(this, spatialTypes.geohash)}
>
<Row>
<Col md={6}>
Column
{this.renderSelect('geohashCol', spatialTypes.geohash)}
</Col>
</Row>
</PopoverSection>
<div className="clearfix">
<Button
bsSize="small"
className="float-left ok"
bsStyle="primary"
onClick={this.close.bind(this)}
>
Ok
</Button>
</div>
</div>
</Popover>
);
}
render() {
return (
<div>
<ControlHeader {...this.props} />
<OverlayTrigger
animation={this.props.animation}
container={document.body}
trigger="click"
rootClose
ref="trigger"
placement="right"
overlay={this.renderPopover()}
>
<Label style={{ cursor: 'pointer' }}>
{this.renderLabelContent()}
</Label>
</OverlayTrigger>
</div>
);
}
}

SpatialControl.propTypes = propTypes;
SpatialControl.defaultProps = defaultProps;
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import FixedOrMetricControl from './FixedOrMetricControl';
import HiddenControl from './HiddenControl';
import SelectAsyncControl from './SelectAsyncControl';
import SelectControl from './SelectControl';
import SpatialControl from './SpatialControl';
import TextAreaControl from './TextAreaControl';
import TextControl from './TextControl';
import TimeSeriesColumnControl from './TimeSeriesColumnControl';
Expand All @@ -29,6 +30,7 @@ const controlMap = {
HiddenControl,
SelectAsyncControl,
SelectControl,
SpatialControl,
TextAreaControl,
TextControl,
TimeSeriesColumnControl,
Expand Down
10 changes: 10 additions & 0 deletions superset/assets/javascripts/explore/stores/controls.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,16 @@ export const controls = {
}),
},

spatial: {
type: 'SpatialControl',
label: t('Longitude & Latitude'),
validators: [v.nonEmpty],
description: t('Point to your spatial columns'),
mapStateToProps: state => ({
choices: (state.datasource) ? state.datasource.all_cols : [],
}),
},

longitude: {
type: 'SelectControl',
label: t('Longitude'),
Expand Down
32 changes: 11 additions & 21 deletions superset/assets/javascripts/explore/stores/visTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -346,9 +346,8 @@ export const visTypes = {
label: t('Query'),
expanded: true,
controlSetRows: [
['longitude', 'latitude'],
['groupby', 'size'],
['row_limit'],
['spatial', 'size'],
['groupby', 'row_limit'],
],
},
{
Expand All @@ -364,7 +363,6 @@ export const visTypes = {
size: {
label: t('Height'),
description: t('Metric used to control height'),
validators: [v.nonEmpty],
},
},
},
Expand All @@ -377,9 +375,8 @@ export const visTypes = {
label: t('Query'),
expanded: true,
controlSetRows: [
['longitude', 'latitude'],
['groupby', 'size'],
['row_limit'],
['spatial', 'size'],
['groupby', 'row_limit'],
],
},
{
Expand Down Expand Up @@ -408,9 +405,8 @@ export const visTypes = {
label: t('Query'),
expanded: true,
controlSetRows: [
['longitude', 'latitude'],
['groupby', 'size'],
['row_limit'],
['spatial', 'size'],
['groupby', 'row_limit'],
],
},
{
Expand Down Expand Up @@ -443,9 +439,8 @@ export const visTypes = {
label: t('Query'),
expanded: true,
controlSetRows: [
['longitude', 'latitude'],
['groupby'],
['row_limit'],
['spatial', null],
['groupby', 'row_limit'],
],
},
{
Expand All @@ -470,18 +465,13 @@ export const visTypes = {
},
],
controlOverrides: {
all_columns_x: {
label: t('Longitude Column'),
validators: [v.nonEmpty],
},
all_columns_y: {
label: t('Latitude Column'),
validators: [v.nonEmpty],
},
dimension: {
label: t('Categorical Color'),
description: t('Pick a dimension from which categorical colors are defined'),
},
size: {
validators: [],
},
},
},

Expand Down
Loading