diff --git a/superset/assets/javascripts/explore/components/Control.jsx b/superset/assets/javascripts/explore/components/Control.jsx
index cd9e732dfd998..ec7d1169ac009 100644
--- a/superset/assets/javascripts/explore/components/Control.jsx
+++ b/superset/assets/javascripts/explore/components/Control.jsx
@@ -7,6 +7,7 @@ import HiddenControl from './controls/HiddenControl';
import SelectControl from './controls/SelectControl';
import TextAreaControl from './controls/TextAreaControl';
import TextControl from './controls/TextControl';
+import VizTypeControl from './controls/VizTypeControl';
const controlMap = {
CheckboxControl,
@@ -15,6 +16,7 @@ const controlMap = {
SelectControl,
TextAreaControl,
TextControl,
+ VizTypeControl,
};
const controlTypes = Object.keys(controlMap);
diff --git a/superset/assets/javascripts/explore/components/controls/VizTypeControl.jsx b/superset/assets/javascripts/explore/components/controls/VizTypeControl.jsx
new file mode 100644
index 0000000000000..807aeac335bff
--- /dev/null
+++ b/superset/assets/javascripts/explore/components/controls/VizTypeControl.jsx
@@ -0,0 +1,108 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Label, Row, Col, FormControl, Modal } from 'react-bootstrap';
+import visTypes from '../../stores/visTypes';
+import ControlHeader from '../ControlHeader';
+
+const propTypes = {
+ description: PropTypes.string,
+ label: PropTypes.string,
+ name: PropTypes.string.isRequired,
+ onChange: PropTypes.func,
+ value: PropTypes.string.isRequired,
+};
+
+const defaultProps = {
+ onChange: () => {},
+};
+
+export default class VizTypeControl extends React.PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = {
+ showModal: false,
+ filter: '',
+ };
+ this.toggleModal = this.toggleModal.bind(this);
+ this.changeSearch = this.changeSearch.bind(this);
+ }
+ onChange(vizType) {
+ this.props.onChange(vizType);
+ this.setState({ showModal: false });
+ }
+ toggleModal() {
+ this.setState({ showModal: !this.state.showModal });
+ }
+ changeSearch(event) {
+ this.setState({ filter: event.target.value });
+ }
+ renderVizType(vizType) {
+ const vt = vizType;
+ return (
+
+
+
+ {visTypes[vt].label}
+
+
);
+ }
+ render() {
+ const filter = this.state.filter;
+ const filteredVizTypes = Object.keys(visTypes)
+ .filter(vt => filter.length === 0 || visTypes[vt].label.toLowerCase().includes(filter));
+
+ const imgPerRow = 4;
+ const rows = [];
+ for (let i = 0; i <= filteredVizTypes.length; i += imgPerRow) {
+ rows.push(
+
+ {filteredVizTypes.slice(i, i + imgPerRow).map(vt => (
+
+ {this.renderVizType(vt)}
+
+ ))}
+
);
+ }
+ return (
+
+
edit
+ }
+ />
+
+
+
+ Select a visualization type
+
+
+
+
+
+ {rows}
+
+
+ );
+ }
+}
+
+VizTypeControl.propTypes = propTypes;
+VizTypeControl.defaultProps = defaultProps;
diff --git a/superset/assets/javascripts/explore/main.css b/superset/assets/javascripts/explore/main.css
index 403221cb3de6b..ab04eaa45688a 100644
--- a/superset/assets/javascripts/explore/main.css
+++ b/superset/assets/javascripts/explore/main.css
@@ -34,3 +34,28 @@
.background-transparent {
background-color: transparent !important;
}
+
+.viztype-label {
+ text-align: center;
+}
+
+.viztype-selector-container {
+ cursor: pointer;
+ margin-top: 10px;
+ margin-bottom: 10px;
+ padding: 5px;
+ border: 1px solid #aaa;
+}
+
+.viztype-selector-container:hover {
+ border: 1px solid #000;
+}
+
+.viztype-selector-container.selected {
+ cursor: not-allowed;
+ opacity: 0.5;
+}
+.viztype-selector-container.selected {
+ cursor: not-allowed;
+ border: 1px solid #aaa;
+}
diff --git a/superset/assets/javascripts/explore/stores/controls.jsx b/superset/assets/javascripts/explore/stores/controls.jsx
index 693201d5c4914..9a96bbbb43d35 100644
--- a/superset/assets/javascripts/explore/stores/controls.jsx
+++ b/superset/assets/javascripts/explore/stores/controls.jsx
@@ -1,6 +1,5 @@
import React from 'react';
import { formatSelectOptionsForRange, formatSelectOptions } from '../../modules/utils';
-import visTypes from './visTypes';
import * as v from '../validators';
const D3_FORMAT_DOCS = 'D3 format syntax: https://github.com/d3/d3-format';
@@ -48,15 +47,9 @@ export const controls = {
},
viz_type: {
- type: 'SelectControl',
+ type: 'VizTypeControl',
label: 'Visualization Type',
- clearable: false,
default: 'table',
- choices: Object.keys(visTypes).map(vt => [
- vt,
- visTypes[vt].label,
- `/static/assets/images/viz_thumbnails/${vt}.png`,
- ]),
description: 'The type of visualization to display',
},
diff --git a/superset/assets/javascripts/explore/stores/visTypes.js b/superset/assets/javascripts/explore/stores/visTypes.js
index 83a5afb3d8ccb..8e57633c89126 100644
--- a/superset/assets/javascripts/explore/stores/visTypes.js
+++ b/superset/assets/javascripts/explore/stores/visTypes.js
@@ -141,7 +141,7 @@ const visTypes = {
},
dual_line: {
- label: 'Time Series - Dual Axis Line Chart',
+ label: 'Dual Axis Line Chart',
requiresTime: true,
controlPanelSections: [
{
diff --git a/superset/assets/package.json b/superset/assets/package.json
index ca453c0408b00..37f784959822e 100644
--- a/superset/assets/package.json
+++ b/superset/assets/package.json
@@ -9,7 +9,7 @@
},
"scripts": {
"test": "mocha --require ignore-styles --compilers js:babel-core/register --require spec/helpers/browser.js --recursive spec/**/*_spec.*",
- "cover": "babel-node ./node_modules/.bin/istanbul cover _mocha -- --require spec/helpers/browser.js --recursive spec/**/*_spec.*",
+ "cover": "babel-node node_modules/.bin/babel-istanbul cover _mocha -- --require spec/helpers/browser.js --recursive spec/**/*_spec.*",
"dev": "NODE_ENV=dev webpack --watch --colors --progress --debug --output-pathinfo --devtool inline-source-map",
"prod": "NODE_ENV=production node --max_old_space_size=4096 ./node_modules/webpack/bin/webpack.js -p --colors --progress",
"build": "NODE_ENV=production webpack --colors --progress",
@@ -59,6 +59,7 @@
"immutability-helper": "^2.0.0",
"immutable": "^3.8.1",
"jquery": "^3.2.1",
+ "jsdom": "9.12.0",
"lodash.throttle": "^4.1.1",
"mapbox-gl": "^0.26.0",
"moment": "^2.14.1",
@@ -98,9 +99,10 @@
"devDependencies": {
"babel-cli": "^6.14.0",
"babel-core": "^6.10.4",
+ "babel-istanbul": "^0.12.2",
"babel-loader": "^6.2.4",
"babel-plugin-css-modules-transform": "^1.1.0",
- "babel-polyfill": "^6.14.0",
+ "babel-polyfill": "^6.23.0",
"babel-preset-airbnb": "^2.1.1",
"babel-preset-es2015": "^6.14.0",
"babel-preset-react": "^6.11.1",
@@ -119,7 +121,7 @@
"ignore-styles": "^5.0.1",
"imports-loader": "^0.7.1",
"istanbul": "^1.0.0-alpha",
- "jsdom": "^9.12.0",
+ "jsdom": "9.12.0",
"json-loader": "^0.5.4",
"less": "^2.6.1",
"less-loader": "^4.0.3",
diff --git a/superset/assets/spec/javascripts/explorev2/components/VizTypeControl_spec.jsx b/superset/assets/spec/javascripts/explorev2/components/VizTypeControl_spec.jsx
new file mode 100644
index 0000000000000..ba0ba5814630f
--- /dev/null
+++ b/superset/assets/spec/javascripts/explorev2/components/VizTypeControl_spec.jsx
@@ -0,0 +1,37 @@
+import React from 'react';
+import sinon from 'sinon';
+import { expect } from 'chai';
+import { describe, it, beforeEach } from 'mocha';
+import { shallow } from 'enzyme';
+import { Modal } from 'react-bootstrap';
+import VizTypeControl from '../../../../javascripts/explore/components/controls/VizTypeControl';
+
+const defaultProps = {
+ name: 'viz_type',
+ label: 'Visualization Type',
+ value: 'table',
+ onChange: sinon.spy(),
+};
+
+describe('VizTypeControl', () => {
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = shallow();
+ });
+
+ it('renders a Modal', () => {
+ expect(wrapper.find(Modal)).to.have.lengthOf(1);
+ });
+
+ it('calls onChange when toggled', () => {
+ const select = wrapper.find('.viztype-selector-container').first();
+ select.simulate('click');
+ expect(defaultProps.onChange.called).to.equal(true);
+ });
+ it('filters images based on text input', () => {
+ expect(wrapper.find('img').length).to.be.above(20);
+ wrapper.setState({ filter: 'time' });
+ expect(wrapper.find('img').length).to.be.below(10);
+ });
+});
diff --git a/superset/assets/stylesheets/less/cosmo/bootswatch.less b/superset/assets/stylesheets/less/cosmo/bootswatch.less
index 7cb1eb4641b67..402dd5dcdb24a 100644
--- a/superset/assets/stylesheets/less/cosmo/bootswatch.less
+++ b/superset/assets/stylesheets/less/cosmo/bootswatch.less
@@ -422,3 +422,8 @@ hr {
}
}
.space-loop(6);
+
+a {
+ cursor: pointer;
+}
+