Implement SuperChart and enable the chart plugins (#6154)

* setup plugin and add SuperChart

* Integrate SuperChart into Chart.jsx

* Add vizType as class name

* Remove .slice_container

* add snakeCase to sanitize class name

* Remove old code to load charts

* Fix supportedAnnotationTypes

* Update AnnotationTypes, remove unnecessary imports and dependency from VIZ_TYPES

* remove index.js and update unit test

* resolve tree map issue

* fix issue with annotation types

* fix proptypes

* add )

* address a few comments

* create bound functions

* bound more functions

* add reselect

* use reselect for chartprops

* improve performance with reselect

* remove unused props

* Remove getFilters() and update table test

* Remove unused code

* Delete adaptors

* switch to react-loadable

* Rewrite with reloadable

* Add timeout back

* remove loading

* update table unit test

* remove pastDelay
This commit is contained in:
Krist Wongsuphasawat 2018-10-26 11:38:30 -07:00 committed by Chris Williams
parent 9580103c22
commit 5c02e3199b
45 changed files with 526 additions and 2681 deletions

View File

@ -104,6 +104,7 @@
"react-dom": "^16.4.1",
"react-gravatar": "^2.6.1",
"react-hot-loader": "^4.3.6",
"react-loadable": "^5.5.0",
"react-map-gl": "^3.0.4",
"react-markdown": "^3.3.0",
"react-redux": "^5.0.2",
@ -122,6 +123,7 @@
"redux-localstorage": "^0.4.1",
"redux-thunk": "^2.1.0",
"redux-undo": "^1.0.0-beta9-9-7",
"reselect": "^4.0.0",
"shortid": "^2.2.6",
"sprintf-js": "^1.1.1",
"srcdoc-polyfill": "^1.0.0",

View File

@ -1,81 +0,0 @@
import React from 'react';
import { shallow } from 'enzyme';
import sinon from 'sinon';
import { chart as initChart } from '../../../src/chart/chartReducer';
import Chart from '../../../src/chart/Chart';
import ChartBody from '../../../src/chart/ChartBody';
import Loading from '../../../src/components/Loading';
describe('Chart', () => {
const chart = {
...initChart,
queryResponse: {
form_data: {},
error: null,
status: 'success',
},
};
const mockedProps = {
...chart,
id: 223,
containerId: 'slice-container-223',
datasource: {},
formData: {},
vizType: 'pie',
height: 300,
width: 400,
actions: {
runQuery: () => {},
},
};
let wrapper;
beforeEach(() => {
wrapper = shallow(
<Chart {...mockedProps} />,
);
});
describe('renderVis', () => {
let stub;
beforeEach(() => {
stub = sinon.stub(wrapper.instance(), 'renderVis');
});
afterEach(() => {
stub.restore();
});
it('should not call when loading', () => {
const prevProp = wrapper.props();
wrapper.setProps({
height: 100,
});
wrapper.instance().componentDidUpdate(prevProp);
expect(stub.callCount).toBe(0);
});
it('should call after chart stop loading', () => {
const prevProp = wrapper.props();
wrapper.setProps({
chartStatus: 'success',
});
wrapper.instance().componentDidUpdate(prevProp);
expect(stub.callCount).toBe(1);
});
it('should call after resize', () => {
wrapper.setProps({
chartStatus: 'rendered',
height: 100,
});
expect(stub.callCount).toBe(1);
});
});
describe('render', () => {
it('should render ChartBody after loading is completed', () => {
expect(wrapper.find(Loading)).toHaveLength(1);
expect(wrapper.find(ChartBody)).toHaveLength(0);
});
});
});

View File

@ -78,10 +78,4 @@ describe('Chart', () => {
wrapper.instance().addFilter();
expect(addFilter.callCount).toBe(1);
});
it('should return props.filters when its getFilters method is called', () => {
const filters = { column: ['value'] };
const wrapper = setup({ filters });
expect(wrapper.instance().getFilters()).toBe(filters);
});
});

View File

@ -1,100 +1,100 @@
import $ from 'jquery';
import '../../helpers/shim';
import tableVis from '../../../src/visualizations/Table/adaptor';
import Table from '../../../src/visualizations/Table/Table';
import transformProps from '../../../src/visualizations/Table/transformProps';
describe('table viz', () => {
const div = '<div id="slice-container"><div class="dataTables_wrapper"></div></div>';
const baseSlice = {
selector: '#slice-container',
const div = '<div id="slice-container"></div>';
const BASE_CHART_PROPS = {
height: 100,
datasource: {
verboseMap: {},
},
filters: {},
formData: {
metrics: ['count'],
timeseries_limit_metric: null,
timeseriesLimitMetric: null,
},
datasource: {
verbose_map: {},
},
getFilters: () => ({}),
removeFilter() {},
addFilter() {},
width: () => 0,
height: () => 0,
};
const basePayload = {
data: {
records: [
{ gender: 'boy', count: 39245 },
{ gender: 'girl', count: 36446 },
],
columns: ['gender', 'count'],
onAddFilter() {},
payload: {
data: {
records: [
{ gender: 'boy', count: 39245 },
{ gender: 'girl', count: 36446 },
],
columns: ['gender', 'count'],
},
},
};
it('renders into a container', () => {
const PAYLOAD2 = {
data: {
records: [
{ gender: 'boy', count: 39245, 'SUM(sum_boys)': 48133355 },
{ gender: 'girl', count: 36446, 'SUM(sum_boys)': 0 },
],
columns: ['gender', 'count', 'SUM(sum_boys)'],
},
};
let container;
let $container;
beforeEach(() => {
$('body').html(div);
const container = $(baseSlice.selector);
expect(container).toHaveLength(1);
container = document.getElementById('slice-container');
$container = $(container);
});
it('renders into a container', () => {
expect($container.children()).toHaveLength(0);
Table(container, transformProps(BASE_CHART_PROPS));
expect($container.children()).toHaveLength(1);
});
it('renders header and body datatables in container', () => {
$('body').html(div);
const container = $(baseSlice.selector);
expect($container.find('.dataTable')).toHaveLength(0);
Table(container, transformProps(BASE_CHART_PROPS));
expect($container.find('.dataTable')).toHaveLength(2);
expect(container.find('.dataTable')).toHaveLength(0);
tableVis(baseSlice, basePayload);
expect(container.find('.dataTable')).toHaveLength(2);
const tableHeader = container.find('.dataTable')[0];
const tableHeader = $container.find('.dataTable')[0];
expect($(tableHeader).find('thead tr')).toHaveLength(1);
expect($(tableHeader).find('th')).toHaveLength(2);
const tableBody = container.find('.dataTable')[1];
const tableBody = $container.find('.dataTable')[1];
expect($(tableBody).find('tbody tr')).toHaveLength(2);
expect($(tableBody).find('th')).toHaveLength(2);
});
it('hides the sort by column', () => {
$('body').html(div);
const slice = { ...baseSlice };
slice.formData = { ...baseSlice.formData,
timeseries_limit_metric: {
label: 'SUM(sum_boys)',
const chartProps = {
...BASE_CHART_PROPS,
formData: {
...BASE_CHART_PROPS.formData,
timeseriesLimitMetric: {
label: 'SUM(sum_boys)',
},
},
payload: PAYLOAD2,
};
const payload = {
data: {
records: [
{ gender: 'boy', count: 39245, 'SUM(sum_boys)': 48133355 },
{ gender: 'girl', count: 36446, 'SUM(sum_boys)': 0 },
],
columns: ['gender', 'count', 'SUM(sum_boys)'],
},
};
tableVis(slice, payload);
const container = $(slice.selector);
const tableHeader = container.find('.dataTable')[0];
Table(container, transformProps(chartProps));
const tableHeader = $container.find('.dataTable')[0];
expect($(tableHeader).find('th')).toHaveLength(2);
});
it('works with empty list for sort by', () => {
$('body').html(div);
const slice = { ...baseSlice };
slice.formData = { ...baseSlice.formData,
timeseries_limit_metric: [],
};
const payload = {
data: {
records: [
{ gender: 'boy', count: 39245, 'SUM(sum_boys)': 48133355 },
{ gender: 'girl', count: 36446, 'SUM(sum_boys)': 0 },
],
columns: ['gender', 'count', 'SUM(sum_boys)'],
const chartProps = {
...BASE_CHART_PROPS,
formData: {
...BASE_CHART_PROPS.formData,
timeseriesLimitMetric: [],
},
payload: PAYLOAD2,
};
tableVis(slice, payload);
const container = $(slice.selector);
const tableBody = container.find('.dataTable')[1];
Table(container, transformProps(chartProps));
const tableBody = $container.find('.dataTable')[1];
expect($(tableBody).find('th')).toHaveLength(3);
});
});

View File

@ -1,25 +1,23 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Tooltip } from 'react-bootstrap';
import dompurify from 'dompurify';
import ChartBody from './ChartBody';
import Loading from '../components/Loading';
import { snakeCase } from 'lodash';
import PropTypes from 'prop-types';
import React from 'react';
import { Tooltip } from 'react-bootstrap';
import { Logger, LOG_ACTIONS_RENDER_CHART } from '../logger';
import StackTraceMessage from '../components/StackTraceMessage';
import Loading from '../components/Loading';
import RefreshChartOverlay from '../components/RefreshChartOverlay';
import visPromiseLookup from '../visualizations';
import sandboxedEval from '../modules/sandbox';
import StackTraceMessage from '../components/StackTraceMessage';
import ChartProps from '../visualizations/core/models/ChartProps';
import SuperChart from '../visualizations/core/components/SuperChart';
import './chart.css';
const propTypes = {
annotationData: PropTypes.object,
actions: PropTypes.object,
chartId: PropTypes.number.isRequired,
containerId: PropTypes.string.isRequired,
datasource: PropTypes.object.isRequired,
filters: PropTypes.object,
formData: PropTypes.object.isRequired,
headerHeight: PropTypes.number,
height: PropTypes.number,
width: PropTypes.number,
setControlValue: PropTypes.func,
@ -28,46 +26,34 @@ const propTypes = {
// state
chartAlert: PropTypes.string,
chartStatus: PropTypes.string,
chartUpdateEndTime: PropTypes.number,
chartUpdateStartTime: PropTypes.number,
latestQueryFormData: PropTypes.object,
queryResponse: PropTypes.object,
lastRendered: PropTypes.number,
triggerQuery: PropTypes.bool,
refreshOverlayVisible: PropTypes.bool,
errorMessage: PropTypes.node,
// dashboard callbacks
addFilter: PropTypes.func,
getFilters: PropTypes.func,
onQuery: PropTypes.func,
onDismissRefreshOverlay: PropTypes.func,
};
const BLANK = {};
const defaultProps = {
addFilter: () => ({}),
getFilters: () => ({}),
addFilter: () => BLANK,
filters: BLANK,
setControlValue() {},
};
class Chart extends React.PureComponent {
constructor(props) {
super(props);
// visualizations are lazy-loaded with promises that resolve to a renderVis function
this.state = {
renderVis: null,
};
this.state = {};
// these properties are used by visualizations
this.annotationData = props.annotationData;
this.containerId = props.containerId;
this.selector = `#${this.containerId}`;
this.formData = props.formData;
this.datasource = props.datasource;
this.addFilter = this.addFilter.bind(this);
this.getFilters = this.getFilters.bind(this);
this.headerHeight = this.headerHeight.bind(this);
this.height = this.height.bind(this);
this.width = this.width.bind(this);
this.visPromise = null;
this.createChartProps = ChartProps.createSelector();
this.handleAddFilter = this.handleAddFilter.bind(this);
this.handleRenderSuccess = this.handleRenderSuccess.bind(this);
this.handleRenderFailure = this.handleRenderFailure.bind(this);
this.setTooltip = this.setTooltip.bind(this);
}
componentDidMount() {
@ -78,110 +64,82 @@ class Chart extends React.PureComponent {
this.props.timeout,
this.props.chartId,
);
} else {
// when drag/dropping in a dashboard, a chart may be unmounted/remounted but still have data
this.renderVis();
}
this.loadAsyncVis(this.props.vizType);
}
componentWillReceiveProps(nextProps) {
this.annotationData = nextProps.annotationData;
this.containerId = nextProps.containerId;
this.selector = `#${this.containerId}`;
this.formData = nextProps.formData;
this.datasource = nextProps.datasource;
if (nextProps.vizType !== this.props.vizType) {
this.setState(() => ({ renderVis: null }));
this.loadAsyncVis(nextProps.vizType);
}
}
componentDidUpdate(prevProps) {
if (
this.props.queryResponse &&
['success', 'rendered'].indexOf(this.props.chartStatus) > -1 &&
!this.props.queryResponse.error &&
(prevProps.annotationData !== this.props.annotationData ||
prevProps.queryResponse !== this.props.queryResponse ||
prevProps.height !== this.props.height ||
prevProps.width !== this.props.width ||
prevProps.lastRendered !== this.props.lastRendered)
) {
this.renderVis();
}
}
componentWillUnmount() {
this.visPromise = null;
}
getFilters() {
return this.props.getFilters();
}
setTooltip(tooltip) {
this.setState({ tooltip });
}
loadAsyncVis(visType) {
this.visPromise = visPromiseLookup[visType];
this.visPromise()
.then((renderVis) => {
// ensure Component is still mounted
if (this.visPromise) {
this.setState({ renderVis }, this.renderVis);
}
})
.catch((error) => {
console.warn(error); // eslint-disable-line
this.props.actions.chartRenderingFailed(error, this.props.chartId);
});
}
addFilter(col, vals, merge = true, refresh = true) {
handleAddFilter(col, vals, merge = true, refresh = true) {
this.props.addFilter(col, vals, merge, refresh);
}
clearError() {
this.setState({ errorMsg: null });
handleRenderSuccess() {
const { actions, chartStatus, chartId, vizType } = this.props;
if (chartStatus !== 'rendered') {
actions.chartRenderingSucceeded(chartId);
}
Logger.append(LOG_ACTIONS_RENDER_CHART, {
slice_id: chartId,
viz_type: vizType,
start_offset: this.renderStartTime,
duration: Logger.getTimestamp() - this.renderStartTime,
});
}
width() {
return this.props.width;
handleRenderFailure(e) {
const { actions, chartId } = this.props;
console.warn(e); // eslint-disable-line
actions.chartRenderingFailed(e, chartId);
}
headerHeight() {
return this.props.headerHeight || 0;
}
prepareChartProps() {
const {
width,
height,
annotationData,
datasource,
filters,
formData,
queryResponse,
setControlValue,
} = this.props;
height() {
return this.props.height;
}
error(e) {
this.props.actions.chartRenderingFailed(e, this.props.chartId);
return this.createChartProps({
width,
height,
annotationData,
datasource,
filters,
formData,
onAddFilter: this.handleAddFilter,
onError: this.handleRenderFailure,
payload: queryResponse,
setControlValue,
setTooltip: this.setTooltip,
});
}
renderTooltip() {
if (this.state.tooltip) {
const { tooltip } = this.state;
if (tooltip) {
return (
<Tooltip
className="chart-tooltip"
id="chart-tooltip"
placement="right"
positionTop={this.state.tooltip.y + 30}
positionLeft={this.state.tooltip.x + 30}
positionTop={tooltip.y + 30}
positionLeft={tooltip.x + 30}
arrowOffsetTop={10}
>
{typeof (this.state.tooltip.content) === 'string' ?
{typeof (tooltip.content) === 'string' ?
<div // eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: dompurify.sanitize(this.state.tooltip.content) }}
dangerouslySetInnerHTML={{ __html: dompurify.sanitize(tooltip.content) }}
/>
:
this.state.tooltip.content
: tooltip.content
}
</Tooltip>
);
@ -189,85 +147,61 @@ class Chart extends React.PureComponent {
return null;
}
renderVis() {
const { chartStatus } = this.props;
const hasVisPromise = !!this.state.renderVis;
// check that we have the render function and data
if (hasVisPromise && ['success', 'rendered'].indexOf(chartStatus) > -1) {
const { vizType, formData, queryResponse, setControlValue, chartId } = this.props;
const renderStart = Logger.getTimestamp();
try {
// Executing user-defined data mutator function
if (formData.js_data) {
queryResponse.data = sandboxedEval(formData.js_data)(queryResponse.data);
}
// [re]rendering the visualization
this.state.renderVis(this, queryResponse, setControlValue);
if (chartStatus !== 'rendered') {
this.props.actions.chartRenderingSucceeded(chartId);
}
Logger.append(LOG_ACTIONS_RENDER_CHART, {
slice_id: chartId,
viz_type: vizType,
start_offset: renderStart,
duration: Logger.getTimestamp() - renderStart,
});
} catch (e) {
console.warn(e); // eslint-disable-line
this.props.actions.chartRenderingFailed(e, chartId);
}
}
}
render() {
const isLoading = this.props.chartStatus === 'loading' || !this.state.renderVis;
const {
width,
height,
chartAlert,
chartStatus,
errorMessage,
onDismissRefreshOverlay,
onQuery,
queryResponse,
refreshOverlayVisible,
vizType,
} = this.props;
const isLoading = chartStatus === 'loading';
// this allows <Loading /> to be positioned in the middle of the chart
const containerStyles = isLoading ? { height: this.height(), width: this.width() } : null;
const containerStyles = isLoading ? { height, width } : null;
const isFaded = refreshOverlayVisible && !errorMessage;
const skipChartRendering = isLoading || !!chartAlert;
this.renderStartTime = Logger.getTimestamp();
return (
<div className={`chart-container ${isLoading ? 'is-loading' : ''}`} style={containerStyles}>
<div
className={`chart-container ${isLoading ? 'is-loading' : ''}`}
style={containerStyles}
>
{this.renderTooltip()}
{isLoading && <Loading size={50} />}
{this.props.chartAlert && (
{chartAlert && (
<StackTraceMessage
message={this.props.chartAlert}
queryResponse={this.props.queryResponse}
message={chartAlert}
queryResponse={queryResponse}
/>
)}
{!isLoading &&
!this.props.chartAlert &&
this.props.refreshOverlayVisible &&
!this.props.errorMessage &&
this.container && (
<RefreshChartOverlay
height={this.height()}
width={this.width()}
onQuery={this.props.onQuery}
onDismiss={this.props.onDismissRefreshOverlay}
/>
)}
{!isLoading && !chartAlert && isFaded && (
<RefreshChartOverlay
width={width}
height={height}
onQuery={onQuery}
onDismiss={onDismissRefreshOverlay}
/>
)}
{!isLoading &&
!this.props.chartAlert && (
<ChartBody
containerId={this.containerId}
vizType={this.props.vizType}
height={this.height}
width={this.width}
faded={
this.props.refreshOverlayVisible && !this.props.errorMessage
}
ref={(inner) => {
this.container = inner;
}}
/>
)}
<SuperChart
className={`slice_container ${snakeCase(vizType)} ${isFaded ? ' faded' : ''}`}
chartType={vizType}
chartProps={skipChartRendering ? null : this.prepareChartProps()}
onRenderSuccess={this.handleRenderSuccess}
onRenderFailure={this.handleRenderFailure}
skipRendering={skipChartRendering}
/>
</div>
);
}

View File

@ -1,34 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
const propTypes = {
containerId: PropTypes.string.isRequired,
vizType: PropTypes.string.isRequired,
height: PropTypes.func.isRequired,
width: PropTypes.func.isRequired,
faded: PropTypes.bool,
};
class ChartBody extends React.PureComponent {
height() {
return this.props.height();
}
width() {
return this.props.width();
}
render() {
return (
<div
id={this.props.containerId}
className={`slice_container ${this.props.vizType}${this.props.faded ? ' faded' : ''}`}
ref={(el) => { this.el = el; }}
/>
);
}
}
ChartBody.propTypes = propTypes;
export default ChartBody;

View File

@ -5,6 +5,10 @@ import { SupersetClient } from '@superset-ui/core';
import { toggleCheckbox } from './modules/utils';
import setupClient from './setup/setupClient';
import setupColors from './setup/setupColors';
import setupPlugins from './setup/setupPlugins';
setupColors();
setupPlugins();
$(document).ready(function () {
$(':checkbox[data-checkbox-api-prefix]').change(function () {
@ -29,7 +33,6 @@ $(document).ready(function () {
export function appSetup() {
setupClient();
setupColors();
// A set of hacks to allow apps to run within a FAB template
// this allows for the server side generated menus to function

View File

@ -1,14 +1,12 @@
import cx from 'classnames';
import React from 'react';
import PropTypes from 'prop-types';
import { exportChart } from '../../../explore/exploreUtils';
import SliceHeader from '../SliceHeader';
import ChartContainer from '../../../chart/ChartContainer';
import MissingChart from '../MissingChart';
import { chartPropType } from '../../../chart/chartReducer';
import { slicePropShape } from '../../util/propShapes';
import { VIZ_TYPES } from '../../../visualizations';
const propTypes = {
id: PropTypes.number.isRequired,
@ -39,7 +37,7 @@ const RESIZE_TIMEOUT = 350;
const SHOULD_UPDATE_ON_PROP_CHANGES = Object.keys(propTypes).filter(
prop => prop !== 'width' && prop !== 'height',
);
const OVERFLOWABLE_VIZ_TYPES = new Set([VIZ_TYPES.filter_box]);
const OVERFLOWABLE_VIZ_TYPES = new Set(['filter_box']);
class Chart extends React.Component {
constructor(props) {
@ -53,7 +51,6 @@ class Chart extends React.Component {
this.exploreChart = this.exploreChart.bind(this);
this.exportCSV = this.exportCSV.bind(this);
this.forceRefresh = this.forceRefresh.bind(this);
this.getFilters = this.getFilters.bind(this);
this.resize = this.resize.bind(this);
this.setDescriptionRef = this.setDescriptionRef.bind(this);
this.setHeaderRef = this.setHeaderRef.bind(this);
@ -92,10 +89,6 @@ class Chart extends React.Component {
clearTimeout(this.resizeTimeout);
}
getFilters() {
return this.props.filters;
}
getChartHeight() {
const headerHeight = this.getHeaderHeight();
const descriptionHeight =
@ -147,6 +140,7 @@ class Chart extends React.Component {
datasource,
isExpanded,
editMode,
filters,
formData,
updateSliceName,
sliceName,
@ -214,27 +208,20 @@ class Chart extends React.Component {
)}
>
<ChartContainer
containerId={`slice-container-${id}`}
chartId={id}
datasource={datasource}
formData={formData}
headerHeight={this.getHeaderHeight()}
height={this.getChartHeight()}
width={width}
timeout={timeout}
vizType={slice.viz_type}
height={this.getChartHeight()}
addFilter={this.addFilter}
getFilters={this.getFilters}
annotationData={chart.annotationData}
chartAlert={chart.chartAlert}
chartId={id}
chartStatus={chart.chartStatus}
chartUpdateEndTime={chart.chartUpdateEndTime}
chartUpdateStartTime={chart.chartUpdateStartTime}
latestQueryFormData={chart.latestQueryFormData}
lastRendered={chart.lastRendered}
datasource={datasource}
filters={filters}
formData={formData}
queryResponse={chart.queryResponse}
queryController={chart.queryController}
timeout={timeout}
triggerQuery={chart.triggerQuery}
vizType={slice.viz_type}
/>
</div>
</div>

View File

@ -39,30 +39,23 @@ class ExploreChartPanel extends React.PureComponent {
<ParentSize>
{({ width, height }) => (width > 0 && height > 0) && (
<ChartContainer
chartId={chart.id}
containerId={this.props.containerId}
datasource={this.props.datasource}
formData={this.props.form_data}
width={Math.floor(width)}
height={parseInt(this.props.height, 10) - headerHeight}
slice={this.props.slice}
setControlValue={this.props.actions.setControlValue}
timeout={this.props.timeout}
vizType={this.props.vizType}
refreshOverlayVisible={this.props.refreshOverlayVisible}
errorMessage={this.props.errorMessage}
onQuery={this.props.onQuery}
onDismissRefreshOverlay={this.props.onDismissRefreshOverlay}
annotationData={chart.annotationData}
chartAlert={chart.chartAlert}
chartId={chart.id}
chartStatus={chart.chartStatus}
chartUpdateEndTime={chart.chartUpdateEndTime}
chartUpdateStartTime={chart.chartUpdateStartTime}
latestQueryFormData={chart.latestQueryFormData}
lastRendered={chart.lastRendered}
datasource={this.props.datasource}
errorMessage={this.props.errorMessage}
formData={this.props.form_data}
onDismissRefreshOverlay={this.props.onDismissRefreshOverlay}
onQuery={this.props.onQuery}
queryResponse={chart.queryResponse}
queryController={chart.queryController}
refreshOverlayVisible={this.props.refreshOverlayVisible}
setControlValue={this.props.actions.setControlValue}
timeout={this.props.timeout}
triggerQuery={chart.triggerQuery}
vizType={this.props.vizType}
/>
)}
</ParentSize>

View File

@ -9,20 +9,18 @@ import SelectControl from './SelectControl';
import TextControl from './TextControl';
import CheckboxControl from './CheckboxControl';
import AnnotationTypes, {
DEFAULT_ANNOTATION_TYPE,
import ANNOTATION_TYPES, {
ANNOTATION_SOURCE_TYPES,
getAnnotationSourceTypeLabels,
getAnnotationTypeLabel,
getSupportedSourceTypes,
getSupportedAnnotationTypes,
ANNOTATION_TYPES_METADATA,
DEFAULT_ANNOTATION_TYPE,
requiresQuery,
ANNOTATION_SOURCE_TYPES_METADATA,
} from '../../../modules/AnnotationTypes';
import PopoverSection from '../../../components/PopoverSection';
import ControlHeader from '../ControlHeader';
import { nonEmpty } from '../../validators';
import vizTypes from '../../visTypes';
import getChartMetadataRegistry from '../../../visualizations/core/registries/ChartMetadataRegistrySingleton';
import { t } from '../../../locales';
import getCategoricalSchemeRegistry from '../../../modules/colors/CategoricalSchemeRegistrySingleton';
@ -148,8 +146,23 @@ export default class AnnotationLayer extends React.PureComponent {
}
}
getSupportedSourceTypes(annotationType) {
// Get vis types that can be source.
const sources = getChartMetadataRegistry().entries()
.filter(({ value: chartMetadata }) => chartMetadata.canBeAnnotationType(annotationType))
.map(({ key, value: chartMetadata }) => ({
value: key,
label: chartMetadata.name,
}));
// Prepend native source if applicable
if (ANNOTATION_TYPES_METADATA[annotationType].supportNativeSource) {
sources.unshift(ANNOTATION_SOURCE_TYPES_METADATA.NATIVE);
}
return sources;
}
isValidFormula(value, annotationType) {
if (annotationType === AnnotationTypes.FORMULA) {
if (annotationType === ANNOTATION_TYPES.FORMULA) {
try {
mathjs
.parse(value)
@ -166,10 +179,10 @@ export default class AnnotationLayer extends React.PureComponent {
const { name, annotationType, sourceType, value, timeColumn, intervalEndColumn } = this.state;
const errors = [nonEmpty(name), nonEmpty(annotationType), nonEmpty(value)];
if (sourceType !== ANNOTATION_SOURCE_TYPES.NATIVE) {
if (annotationType === AnnotationTypes.EVENT) {
if (annotationType === ANNOTATION_TYPES.EVENT) {
errors.push(nonEmpty(timeColumn));
}
if (annotationType === AnnotationTypes.INTERVAL) {
if (annotationType === ANNOTATION_TYPES.INTERVAL) {
errors.push(nonEmpty(timeColumn));
errors.push(nonEmpty(intervalEndColumn));
}
@ -223,14 +236,18 @@ export default class AnnotationLayer extends React.PureComponent {
});
});
} else if (requiresQuery(sourceType)) {
SupersetClient.get({ endpoint: '/superset/user_slices' }).then(({ json }) =>
SupersetClient.get({ endpoint: '/superset/user_slices' }).then(({ json }) => {
const registry = getChartMetadataRegistry();
this.setState({
isLoadingOptions: false,
valueOptions: json
.filter(x => getSupportedSourceTypes(annotationType).find(v => v === x.viz_type))
.filter((x) => {
const metadata = registry.get(x.viz_type);
return metadata && metadata.canBeAnnotationType(annotationType);
})
.map(x => ({ value: x.id, label: x.title, slice: x })),
}),
);
});
});
} else {
this.setState({
isLoadingOptions: false,
@ -282,11 +299,11 @@ export default class AnnotationLayer extends React.PureComponent {
label = label = t('Chart');
description = `Use a pre defined Superset Chart as a source for annotations and overlays.
your chart must be one of these visualization types:
[${getSupportedSourceTypes(annotationType)
.map(x => (x in vizTypes && 'label' in vizTypes[x] ? vizTypes[x].label : ''))
[${this.getSupportedSourceTypes(annotationType)
.map(x => x.label)
.join(', ')}]`;
}
} else if (annotationType === AnnotationTypes.FORMULA) {
} else if (annotationType === ANNOTATION_TYPES.FORMULA) {
label = 'Formula';
description = `Expects a formula with depending time parameter 'x'
in milliseconds since epoch. mathjs is used to evaluate the formulas.
@ -309,7 +326,7 @@ export default class AnnotationLayer extends React.PureComponent {
/>
);
}
if (annotationType === AnnotationTypes.FORMULA) {
if (annotationType === ANNOTATION_TYPES.FORMULA) {
return (
<TextControl
name="annotation-layer-value"
@ -356,13 +373,13 @@ export default class AnnotationLayer extends React.PureComponent {
info={`This section allows you to configure how to use the slice
to generate annotations.`}
>
{(annotationType === AnnotationTypes.EVENT ||
annotationType === AnnotationTypes.INTERVAL) && (
{(annotationType === ANNOTATION_TYPES.EVENT ||
annotationType === ANNOTATION_TYPES.INTERVAL) && (
<SelectControl
hovered
name="annotation-layer-time-column"
label={
annotationType === AnnotationTypes.INTERVAL
annotationType === ANNOTATION_TYPES.INTERVAL
? 'Interval Start column'
: 'Event Time Column'
}
@ -374,7 +391,7 @@ export default class AnnotationLayer extends React.PureComponent {
onChange={v => this.setState({ timeColumn: v })}
/>
)}
{annotationType === AnnotationTypes.INTERVAL && (
{annotationType === ANNOTATION_TYPES.INTERVAL && (
<SelectControl
hovered
name="annotation-layer-intervalEnd"
@ -395,7 +412,7 @@ export default class AnnotationLayer extends React.PureComponent {
value={titleColumn}
onChange={v => this.setState({ titleColumn: v })}
/>
{annotationType !== AnnotationTypes.TIME_SERIES && (
{annotationType !== ANNOTATION_TYPES.TIME_SERIES && (
<SelectControl
hovered
name="annotation-layer-title"
@ -553,7 +570,7 @@ export default class AnnotationLayer extends React.PureComponent {
value={width}
onChange={v => this.setState({ width: v })}
/>
{annotationType === AnnotationTypes.TIME_SERIES && (
{annotationType === ANNOTATION_TYPES.TIME_SERIES && (
<CheckboxControl
hovered
name="annotation-layer-show-markers"
@ -563,7 +580,7 @@ export default class AnnotationLayer extends React.PureComponent {
onChange={v => this.setState({ showMarkers: v })}
/>
)}
{annotationType === AnnotationTypes.TIME_SERIES && (
{annotationType === ANNOTATION_TYPES.TIME_SERIES && (
<CheckboxControl
hovered
name="annotation-layer-hide-line"
@ -580,6 +597,13 @@ export default class AnnotationLayer extends React.PureComponent {
render() {
const { isNew, name, annotationType, sourceType, show } = this.state;
const isValid = this.isValidForm();
const metadata = getChartMetadataRegistry().get(this.props.vizType);
const supportedAnnotationTypes = metadata
? metadata.supportedAnnotationTypes.map(type => ANNOTATION_TYPES_METADATA[type])
: [];
const supportedSourceTypes = this.getSupportedSourceTypes(annotationType);
return (
<div>
{this.props.error && <span style={{ color: 'red' }}>ERROR: {this.props.error}</span>}
@ -610,23 +634,17 @@ export default class AnnotationLayer extends React.PureComponent {
description={t('Choose the Annotation Layer Type')}
label={t('Annotation Layer Type')}
name="annotation-layer-type"
options={getSupportedAnnotationTypes(this.props.vizType).map(x => ({
value: x,
label: getAnnotationTypeLabel(x),
}))}
options={supportedAnnotationTypes}
value={annotationType}
onChange={this.handleAnnotationType}
/>
{!!getSupportedSourceTypes(annotationType).length && (
{!!supportedSourceTypes.length && (
<SelectControl
hovered
description="Choose the source of your annotations"
label="Annotation Source"
name="annotation-source-type"
options={getSupportedSourceTypes(annotationType).map(x => ({
value: x,
label: getAnnotationSourceTypeLabels(x),
}))}
options={supportedSourceTypes}
value={sourceType}
onChange={this.handleAnnotationSourceType}
/>

View File

@ -1,81 +1,50 @@
import { VIZ_TYPES } from '../visualizations';
import vizTypes from '../explore/visTypes';
export const ANNOTATION_TYPES = {
FORMULA: 'FORMULA',
EVENT: 'EVENT',
INTERVAL: 'INTERVAL',
TIME_SERIES: 'TIME_SERIES',
};
export const ANNOTATION_TYPE_LABELS = {
FORMULA: 'Formula ',
EVENT: 'Event',
INTERVAL: 'Interval',
TIME_SERIES: 'Time Series',
};
export function getAnnotationTypeLabel(annotationType) {
return ANNOTATION_TYPE_LABELS[annotationType];
function extractTypes(metadata) {
return Object.keys(metadata)
.reduce((prev, key) => {
const result = prev;
result[key] = key;
return result;
}, {});
}
export const ANNOTATION_TYPES_METADATA = {
FORMULA: {
value: 'FORMULA',
label: 'Formula',
},
EVENT: {
value: 'EVENT',
label: 'Event',
supportNativeSource: true,
},
INTERVAL: {
value: 'INTERVAL',
label: 'Interval',
supportNativeSource: true,
},
TIME_SERIES: {
value: 'TIME_SERIES',
label: 'Time Series',
},
};
export const ANNOTATION_TYPES = extractTypes(ANNOTATION_TYPES_METADATA);
export const DEFAULT_ANNOTATION_TYPE = ANNOTATION_TYPES.FORMULA;
export const ANNOTATION_SOURCE_TYPES = {
NATIVE: 'NATIVE',
...VIZ_TYPES,
export const ANNOTATION_SOURCE_TYPES_METADATA = {
NATIVE: {
value: 'NATIVE',
label: 'Superset annotation',
},
};
export function getAnnotationSourceTypeLabels(sourceType) {
return ANNOTATION_SOURCE_TYPES.NATIVE === sourceType ? 'Superset annotation' :
vizTypes[sourceType].label;
}
export const ANNOTATION_SOURCE_TYPES = extractTypes(ANNOTATION_SOURCE_TYPES_METADATA);
export function requiresQuery(annotationSourceType) {
return !!annotationSourceType;
}
// Map annotation type to annotation source type
const SUPPORTED_SOURCE_TYPE_MAP = {
[ANNOTATION_TYPES.EVENT]: [
ANNOTATION_SOURCE_TYPES.NATIVE,
ANNOTATION_SOURCE_TYPES.table,
],
[ANNOTATION_TYPES.INTERVAL]: [
ANNOTATION_SOURCE_TYPES.NATIVE,
ANNOTATION_SOURCE_TYPES.table,
],
[ANNOTATION_TYPES.TIME_SERIES]: [
ANNOTATION_SOURCE_TYPES.line,
],
};
export function getSupportedSourceTypes(annotationType) {
return SUPPORTED_SOURCE_TYPE_MAP[annotationType] || [];
}
// Map from viz type to supported annotation
const SUPPORTED_ANNOTATIONS = {
[VIZ_TYPES.line]: [
ANNOTATION_TYPES.TIME_SERIES,
ANNOTATION_TYPES.INTERVAL,
ANNOTATION_TYPES.EVENT,
ANNOTATION_TYPES.FORMULA,
],
[VIZ_TYPES.bar]: [
ANNOTATION_TYPES.INTERVAL,
ANNOTATION_TYPES.EVENT,
],
[VIZ_TYPES.area]: [
ANNOTATION_TYPES.INTERVAL,
ANNOTATION_TYPES.EVENT,
],
};
export function getSupportedAnnotationTypes(vizType) {
return SUPPORTED_ANNOTATIONS[vizType] || [];
}
const NATIVE_COLUMN_NAMES = {
timeColumn: 'start_dttm',
intervalEndColumn: 'end_dttm',
@ -91,4 +60,3 @@ export function applyNativeColumns(annotation) {
}
export default ANNOTATION_TYPES;

View File

@ -199,20 +199,6 @@ export function d3format(format, number) {
}
}
// Slice objects interact with their context through objects that implement
// this controllerInterface (dashboard, explore, standalone)
export const controllerInterface = {
type: null,
done: () => {},
error: () => {},
always: () => {},
addFiler: () => {},
setFilter: () => {},
getFilters: () => false,
removeFilter: () => {},
filters: {},
};
export function formatSelectOptionsForRange(start, end) {
// outputs array of arrays
// formatSelectOptionsForRange(1, 5)

View File

@ -0,0 +1,5 @@
import LegacyChartPreset from '../visualizations/presets/LegacyChartPreset';
export default function setupPlugins() {
new LegacyChartPreset().register();
}

View File

@ -1,5 +0,0 @@
import createAdaptor from '../../utils/createAdaptor';
import Component from './BigNumber';
import transformProps from './transformProps';
export default createAdaptor(Component, transformProps);

View File

@ -1,10 +1,10 @@
.slice_container.cal_heatmap {
.cal_heatmap {
padding: 10px;
position: static !important;
overflow: auto !important;
}
.cal_heatmap .slice_container .ch-tooltip {
.cal_heatmap .ch-tooltip {
margin-left: 20px;
margin-top: 5px;
}

View File

@ -1,5 +0,0 @@
import createAdaptor from '../../utils/createAdaptor';
import Component from './ReactCalendar';
import transformProps from './transformProps';
export default createAdaptor(Component, transformProps);

View File

@ -1,5 +0,0 @@
import createAdaptor from '../../utils/createAdaptor';
import Component from './ReactChord';
import transformProps from './transformProps';
export default createAdaptor(Component, transformProps);

View File

@ -1,5 +0,0 @@
import createAdaptor from '../../utils/createAdaptor';
import Component from './ReactCountryMap';
import transformProps from './transformProps';
export default createAdaptor(Component, transformProps);

View File

@ -1,5 +0,0 @@
import createAdaptor from '../../utils/createAdaptor';
import Component from './EventFlow';
import transformProps from './transformProps';
export default createAdaptor(Component, transformProps);

View File

@ -26,7 +26,7 @@ ul.select2-results div.filter_box{
border-width: 1px;
border-color: transparent;
}
.filter_box.slice_container {
.filter_box {
padding: 10px;
overflow: visible !important;
}

View File

@ -1,5 +0,0 @@
import createAdaptor from '../../utils/createAdaptor';
import Component from './FilterBox';
import transformProps from './transformProps';
export default createAdaptor(Component, transformProps);

View File

@ -1,5 +0,0 @@
import createAdaptor from '../../utils/createAdaptor';
import Component from './ReactForceDirected';
import transformProps from './transformProps';
export default createAdaptor(Component, transformProps);

View File

@ -1,5 +0,0 @@
import createAdaptor from '../../utils/createAdaptor';
import Component from './ReactHeatmap';
import transformProps from './transformProps';
export default createAdaptor(Component, transformProps);

View File

@ -1,5 +0,0 @@
import createAdaptor from '../../utils/createAdaptor';
import Component from './Histogram';
import transformProps from './transformProps';
export default createAdaptor(Component, transformProps);

View File

@ -1,5 +0,0 @@
import createAdaptor from '../../utils/createAdaptor';
import Component from './HorizonChart';
import transformProps from './transformProps';
export default createAdaptor(Component, transformProps);

View File

@ -1,5 +0,0 @@
import createAdaptor from '../../utils/createAdaptor';
import Component from './MapBox';
import transformProps from './transformProps';
export default createAdaptor(Component, transformProps);

View File

@ -1,5 +0,0 @@
import createAdaptor from '../../utils/createAdaptor';
import Component from './PairedTTest';
import transformProps from './transformProps';
export default createAdaptor(Component, transformProps);

View File

@ -1,5 +0,0 @@
import createAdaptor from '../../utils/createAdaptor';
import Component from './ReactParallelCoordinates';
import transformProps from './transformProps';
export default createAdaptor(Component, transformProps);

View File

@ -1,5 +0,0 @@
import createAdaptor from '../../utils/createAdaptor';
import Component from './ReactPartition';
import transformProps from './transformProps';
export default createAdaptor(Component, transformProps);

View File

@ -1,5 +0,0 @@
import createAdaptor from '../../utils/createAdaptor';
import Component from './ReactPivotTable';
import transformProps from './transformProps';
export default createAdaptor(Component, transformProps);

View File

@ -1,5 +0,0 @@
import createAdaptor from '../../utils/createAdaptor';
import Component from './ReactRose';
import transformProps from './transformProps';
export default createAdaptor(Component, transformProps);

View File

@ -1,5 +0,0 @@
import createAdaptor from '../../utils/createAdaptor';
import Component from './ReactSankey';
import transformProps from './transformProps';
export default createAdaptor(Component, transformProps);

View File

@ -1,5 +0,0 @@
import createAdaptor from '../../utils/createAdaptor';
import Component from './ReactSunburst';
import transformProps from './transformProps';
export default createAdaptor(Component, transformProps);

View File

@ -1,5 +0,0 @@
import createAdaptor from '../../utils/createAdaptor';
import Component from './ReactTable';
import transformProps from './transformProps';
export default createAdaptor(Component, transformProps);

View File

@ -1,5 +0,0 @@
import createAdaptor from '../../utils/createAdaptor';
import Component from './TimeTable';
import transformProps from './transformProps';
export default createAdaptor(Component, transformProps);

View File

@ -48,10 +48,17 @@ const DEFAULT_MARGIN = {
left: 0,
};
function clone(children) {
return children.map(x => ({
...x,
children: x.children ? clone(x.children) : null,
}));
}
/* Modified from http://bl.ocks.org/ganeshv/6a8e9ada3ab7f2d88022 */
function Treemap(element, props) {
const {
data,
data: rawData,
width,
height,
margin = DEFAULT_MARGIN,
@ -62,6 +69,7 @@ function Treemap(element, props) {
const div = d3.select(element);
const formatNumber = d3.format(numberFormat);
const colorFn = getScale(colorScheme).toFunction();
const data = clone(rawData);
function draw(data, eltWidth, eltHeight) {
const navBarHeight = 36;

View File

@ -1,5 +0,0 @@
import createAdaptor from '../../utils/createAdaptor';
import Component from './ReactTreemap';
import transformProps from './transformProps';
export default createAdaptor(Component, transformProps);

View File

@ -1,5 +0,0 @@
import createAdaptor from '../../utils/createAdaptor';
import Component from './ReactWorldMap';
import transformProps from './transformProps';
export default createAdaptor(Component, transformProps);

View File

@ -0,0 +1,163 @@
import React from 'react';
import Loadable from 'react-loadable';
import PropTypes from 'prop-types';
import { createSelector } from 'reselect';
import getChartComponentRegistry from '../registries/ChartComponentRegistrySingleton';
import getChartTransformPropsRegistry from '../registries/ChartTransformPropsRegistrySingleton';
import ChartProps from '../models/ChartProps';
const IDENTITY = x => x;
const propTypes = {
id: PropTypes.string,
className: PropTypes.string,
chartProps: PropTypes.instanceOf(ChartProps),
chartType: PropTypes.string.isRequired,
preTransformProps: PropTypes.func,
overrideTransformProps: PropTypes.func,
postTransformProps: PropTypes.func,
onRenderSuccess: PropTypes.func,
onRenderFailure: PropTypes.func,
skipRendering: PropTypes.bool,
};
const defaultProps = {
id: '',
className: '',
preTransformProps: IDENTITY,
overrideTransformProps: undefined,
postTransformProps: IDENTITY,
onRenderSuccess() {},
onRenderFailure() {},
skipRendering: false,
};
class SuperChart extends React.PureComponent {
constructor(props) {
super(props);
this.renderChart = this.renderChart.bind(this);
this.renderLoading = this.renderLoading.bind(this);
// memoized function so it will not recompute
// and return previous value
// unless one of
// - preTransformProps
// - transformProps
// - postTransformProps
// - chartProps
// is changed.
this.processChartProps = createSelector(
input => input.preTransformProps,
input => input.transformProps,
input => input.postTransformProps,
input => input.chartProps,
(pre, transform, post, chartProps) => post(transform(pre(chartProps))),
);
const componentRegistry = getChartComponentRegistry();
const transformPropsRegistry = getChartTransformPropsRegistry();
// memoized function so it will not recompute
// and return previous value
// unless one of
// - chartType
// - overrideTransformProps
// is changed.
this.createLoadableRenderer = createSelector(
input => input.chartType,
input => input.overrideTransformProps,
(chartType, overrideTransformProps) => {
if (chartType) {
return Loadable.Map({
loader: {
Chart: () => componentRegistry.getAsPromise(chartType),
transformProps: overrideTransformProps
? () => Promise.resolve(overrideTransformProps)
: () => transformPropsRegistry.getAsPromise(chartType),
},
loading: loadingProps => this.renderLoading(loadingProps, chartType),
render: this.renderChart,
});
}
return null;
},
);
}
renderChart(loaded, props) {
const Chart = loaded.Chart.default || loaded.Chart;
const transformProps = loaded.transformProps;
const {
chartProps,
preTransformProps,
postTransformProps,
} = props;
const result = (
<Chart
{...this.processChartProps({
preTransformProps,
transformProps,
postTransformProps,
chartProps,
})}
/>
);
setTimeout(() => this.props.onRenderSuccess(), 0);
return result;
}
renderLoading(loadableProps, chartType) {
const { error } = loadableProps;
if (error) {
const result = (
<div className="alert alert-warning" role="alert">
<strong>ERROR</strong>&nbsp;
<code>chartType="{chartType}"</code> &mdash;
{JSON.stringify(error)}
</div>
);
setTimeout(() => this.props.onRenderFailure(error), 0);
return result;
}
return null;
}
render() {
const {
id,
className,
preTransformProps,
postTransformProps,
chartProps,
skipRendering,
} = this.props;
const LoadableRenderer = this.createLoadableRenderer(this.props);
// Use this to allow loading the vis components
// without rendering (while waiting for data)
if (skipRendering || !chartProps) {
return null;
}
return (
<div id={id} className={className}>
{LoadableRenderer && (
<LoadableRenderer
preTransformProps={preTransformProps}
postTransformProps={postTransformProps}
chartProps={chartProps}
/>
)}
</div>
);
}
}
SuperChart.propTypes = propTypes;
SuperChart.defaultProps = defaultProps;
export default SuperChart;

View File

@ -1,3 +1,4 @@
import { createSelector } from 'reselect';
import convertKeysToCamelCase from '../../../utils/convertKeysToCamelCase';
export default class ChartProps {
@ -29,3 +30,44 @@ export default class ChartProps {
this.setTooltip = setTooltip;
}
}
ChartProps.createSelector = function () {
return createSelector(
input => input.width,
input => input.height,
input => input.annotationData,
input => input.datasource,
input => input.filters,
input => input.formData,
input => input.onAddFilter,
input => input.onError,
input => input.payload,
input => input.setControlValue,
input => input.setTooltip,
(
width,
height,
annotationData,
datasource,
filters,
formData,
onAddFilter,
onError,
payload,
setControlValue,
setTooltip,
) => new ChartProps({
width,
height,
annotationData,
datasource,
filters,
formData,
onAddFilter,
onError,
payload,
setControlValue,
setTooltip,
}),
);
};

View File

@ -1,148 +0,0 @@
import createAdaptor from '../utils/createAdaptor';
import transformProps from './deckgl/transformProps';
/* eslint-disable global-require */
// You ***should*** use these to reference viz_types in code
export const VIZ_TYPES = {
area: 'area',
bar: 'bar',
big_number: 'big_number',
big_number_total: 'big_number_total',
box_plot: 'box_plot',
bubble: 'bubble',
bullet: 'bullet',
cal_heatmap: 'cal_heatmap',
compare: 'compare',
directed_force: 'directed_force',
chord: 'chord',
dist_bar: 'dist_bar',
filter_box: 'filter_box',
heatmap: 'heatmap',
histogram: 'histogram',
horizon: 'horizon',
iframe: 'iframe',
line: 'line',
line_multi: 'line_multi',
mapbox: 'mapbox',
markup: 'markup',
para: 'para',
pie: 'pie',
pivot_table: 'pivot_table',
sankey: 'sankey',
separator: 'separator',
sunburst: 'sunburst',
table: 'table',
time_table: 'time_table',
time_pivot: 'time_pivot',
treemap: 'treemap',
country_map: 'country_map',
word_cloud: 'word_cloud',
world_map: 'world_map',
dual_line: 'dual_line',
event_flow: 'event_flow',
paired_ttest: 'paired_ttest',
partition: 'partition',
deck_scatter: 'deck_scatter',
deck_screengrid: 'deck_screengrid',
deck_grid: 'deck_grid',
deck_hex: 'deck_hex',
deck_path: 'deck_path',
deck_geojson: 'deck_geojson',
deck_multi: 'deck_multi',
deck_arc: 'deck_arc',
deck_polygon: 'deck_polygon',
rose: 'rose',
};
const loadVis = promise =>
promise.then((module) => {
const defaultExport = module.default || module;
// deckgl visualizations don't use esModules, fix it?
return defaultExport.default || defaultExport;
});
const loadDeckGLVis = promise =>
loadVis(promise).then(module => createAdaptor(module, transformProps));
const loadNvd3 = () => loadVis(import(/* webpackChunkName: "nvd3_vis" */ './nvd3/adaptor.jsx'));
const vizMap = {
[VIZ_TYPES.area]: loadNvd3,
[VIZ_TYPES.bar]: loadNvd3,
[VIZ_TYPES.big_number]: () =>
loadVis(import(/* webpackChunkName: 'big_number' */ './BigNumber/adaptor.jsx')),
[VIZ_TYPES.big_number_total]: () =>
loadVis(import(/* webpackChunkName: "big_number" */ './BigNumber/adaptor.jsx')),
[VIZ_TYPES.box_plot]: loadNvd3,
[VIZ_TYPES.bubble]: loadNvd3,
[VIZ_TYPES.bullet]: loadNvd3,
[VIZ_TYPES.cal_heatmap]: () =>
loadVis(import(/* webpackChunkName: "cal_heatmap" */ './Calendar/adaptor.jsx')),
[VIZ_TYPES.compare]: loadNvd3,
[VIZ_TYPES.directed_force]: () =>
loadVis(import(/* webpackChunkName: "directed_force" */ './ForceDirected/adaptor.jsx')),
[VIZ_TYPES.chord]: () => loadVis(import(/* webpackChunkName: "chord" */ './Chord/adaptor.jsx')),
[VIZ_TYPES.dist_bar]: loadNvd3,
[VIZ_TYPES.filter_box]: () =>
loadVis(import(/* webpackChunkName: "filter_box" */ './FilterBox/adaptor.jsx')),
[VIZ_TYPES.heatmap]: () => loadVis(import(/* webpackChunkName: "heatmap" */ './Heatmap/adaptor.jsx')),
[VIZ_TYPES.histogram]: () =>
loadVis(import(/* webpackChunkName: "histogram" */ './Histogram/adaptor.jsx')),
[VIZ_TYPES.horizon]: () => loadVis(import(/* webpackChunkName: "horizon" */ './Horizon/adaptor.jsx')),
[VIZ_TYPES.iframe]: () => loadVis(import(/* webpackChunkName: "iframe" */ './iframe.js')),
[VIZ_TYPES.line]: loadNvd3,
[VIZ_TYPES.line_multi]: () =>
loadVis(import(/* webpackChunkName: "line_multi" */ './nvd3/LineMulti/adaptor.jsx')),
[VIZ_TYPES.time_pivot]: loadNvd3,
[VIZ_TYPES.mapbox]: () => loadVis(import(/* webpackChunkName: "mapbox" */ './MapBox/adaptor.jsx')),
[VIZ_TYPES.markup]: () => loadVis(import(/* webpackChunkName: "markup" */ './markup.js')),
[VIZ_TYPES.para]: () =>
loadVis(import(/* webpackChunkName: "parallel_coordinates" */ './ParallelCoordinates/adaptor.jsx')),
[VIZ_TYPES.pie]: loadNvd3,
[VIZ_TYPES.pivot_table]: () =>
loadVis(import(/* webpackChunkName: "pivot_table" */ './PivotTable/adaptor.jsx')),
[VIZ_TYPES.sankey]: () => loadVis(import(/* webpackChunkName: "sankey" */ './Sankey/adaptor.jsx')),
[VIZ_TYPES.separator]: () => loadVis(import(/* webpackChunkName: "markup" */ './markup.js')),
[VIZ_TYPES.sunburst]: () => loadVis(import(/* webpackChunkName: "sunburst" */ './Sunburst/adaptor.jsx')),
[VIZ_TYPES.table]: () => loadVis(import(/* webpackChunkName: "table" */ './Table/adaptor.jsx')),
[VIZ_TYPES.time_table]: () =>
loadVis(import(/* webpackChunkName: "time_table" */ './TimeTable/adaptor.jsx')),
[VIZ_TYPES.treemap]: () => loadVis(import(/* webpackChunkName: "treemap" */ './Treemap/adaptor.jsx')),
[VIZ_TYPES.country_map]: () =>
loadVis(import(/* webpackChunkName: "country_map" */ './CountryMap/adaptor.jsx')),
[VIZ_TYPES.word_cloud]: () =>
loadVis(import(/* webpackChunkName: "word_cloud" */ './wordcloud/adaptor.jsx')),
[VIZ_TYPES.world_map]: () =>
loadVis(import(/* webpackChunkName: "world_map" */ './WorldMap/adaptor.jsx')),
[VIZ_TYPES.dual_line]: loadNvd3,
[VIZ_TYPES.event_flow]: () =>
loadVis(import(/* webpackChunkName: "EventFlow" */ './EventFlow/adaptor.jsx')),
[VIZ_TYPES.paired_ttest]: () =>
loadVis(import(/* webpackChunkName: "paired_ttest" */ './PairedTTest/adaptor.jsx')),
[VIZ_TYPES.partition]: () =>
loadVis(import(/* webpackChunkName: "partition" */ './Partition/adaptor.jsx')),
[VIZ_TYPES.deck_scatter]: () =>
loadDeckGLVis(import(/* webpackChunkName: "deckgl/layers/scatter" */ './deckgl/layers/Scatter/Scatter.jsx')),
[VIZ_TYPES.deck_screengrid]: () =>
loadDeckGLVis(
import(/* webpackChunkName: "deckgl/layers/screengrid" */ './deckgl/layers/Screengrid/Screengrid.jsx'),
),
[VIZ_TYPES.deck_grid]: () =>
loadDeckGLVis(import(/* webpackChunkName: "deckgl/layers/grid" */ './deckgl/layers/Grid/Grid.jsx')),
[VIZ_TYPES.deck_hex]: () =>
loadDeckGLVis(import(/* webpackChunkName: "deckgl/layers/hex" */ './deckgl/layers/Hex/Hex.jsx')),
[VIZ_TYPES.deck_path]: () =>
loadDeckGLVis(import(/* webpackChunkName: "deckgl/layers/path" */ './deckgl/layers/Path/Path.jsx')),
[VIZ_TYPES.deck_geojson]: () =>
loadDeckGLVis(import(/* webpackChunkName: "deckgl/layers/geojson" */ './deckgl/layers/Geojson/Geojson.jsx')),
[VIZ_TYPES.deck_arc]: () =>
loadDeckGLVis(import(/* webpackChunkName: "deckgl/layers/arc" */ './deckgl/layers/Arc/Arc.jsx')),
[VIZ_TYPES.deck_polygon]: () =>
loadDeckGLVis(import(/* webpackChunkName: "deckgl/layers/polygon" */ './deckgl/layers/Polygon/Polygon.jsx')),
[VIZ_TYPES.deck_multi]: () =>
loadDeckGLVis(import(/* webpackChunkName: "deckgl/multi" */ './deckgl/Multi/Multi.jsx')),
[VIZ_TYPES.rose]: () => loadVis(import(/* webpackChunkName: "rose" */ './Rose/adaptor.jsx')),
};
export default vizMap;

View File

@ -7,7 +7,7 @@ import PropTypes from 'prop-types';
import 'nvd3/build/nv.d3.min.css';
import { t } from '../../locales';
import AnnotationTypes, { applyNativeColumns } from '../../modules/AnnotationTypes';
import ANNOTATION_TYPES, { applyNativeColumns } from '../../modules/AnnotationTypes';
import { getScale, getColor } from '../../modules/colors/CategoricalColorNamespace';
import { formatDateVerbose } from '../../modules/dates';
import { d3TimeFormatPreset, d3FormatPreset } from '../../modules/utils';
@ -646,7 +646,7 @@ function nvd3Vis(element, props) {
// Time series annotations add additional data
const timeSeriesAnnotations = annotationLayers
.filter(layer => layer.show)
.filter(layer => layer.annotationType === AnnotationTypes.TIME_SERIES)
.filter(layer => layer.annotationType === ANNOTATION_TYPES.TIME_SERIES)
.reduce((bushel, a) =>
bushel.concat((annotationData[a.name] || []).map((series) => {
if (!series) {
@ -680,7 +680,7 @@ function nvd3Vis(element, props) {
if (isTimeSeries && annotationLayers.length > 0) {
// Formula annotations
const formulas = annotationLayers
.filter(a => a.annotationType === AnnotationTypes.FORMULA)
.filter(a => a.annotationType === ANNOTATION_TYPES.FORMULA)
.map(a => ({ ...a, formula: mathjs.parse(a.value) }));
let xMax;
@ -750,7 +750,7 @@ function nvd3Vis(element, props) {
if (annotationData) {
// Event annotations
annotationLayers.filter(x => (
x.annotationType === AnnotationTypes.EVENT &&
x.annotationType === ANNOTATION_TYPES.EVENT &&
annotationData && annotationData[x.name]
)).forEach((config, index) => {
const e = applyNativeColumns(config);
@ -809,7 +809,7 @@ function nvd3Vis(element, props) {
// Interval annotations
annotationLayers.filter(x => (
x.annotationType === AnnotationTypes.INTERVAL &&
x.annotationType === ANNOTATION_TYPES.INTERVAL &&
annotationData && annotationData[x.name]
)).forEach((config, index) => {
const e = applyNativeColumns(config);

View File

@ -1,5 +0,0 @@
import createAdaptor from '../../utils/createAdaptor';
import Component from './ReactNVD3';
import transformProps from './transformProps';
export default createAdaptor(Component, transformProps);

View File

@ -1,5 +0,0 @@
import createAdaptor from '../../utils/createAdaptor';
import Component from './ReactWordCloud';
import transformProps from './transformProps';
export default createAdaptor(Component, transformProps);

File diff suppressed because it is too large Load Diff