mirror of
https://github.com/apache/superset.git
synced 2026-04-24 02:25:13 +00:00
* [geo] introduce "Auto Zoom" control On geospatial visualization, checking the "Auto Zoom" control makes it such that the viewport is fitted to the data upon rendering the chart. For dashboards with region filters, the map should jump to the right position. Eventually we should enhance this to fly and ease to the position in an animated way. * Added TODO notes
231 lines
6.3 KiB
JavaScript
231 lines
6.3 KiB
JavaScript
/* eslint camelcase: 0 */
|
|
import React from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import Mustache from 'mustache';
|
|
import { Tooltip } from 'react-bootstrap';
|
|
|
|
import { d3format } from '../modules/utils';
|
|
import ChartBody from './ChartBody';
|
|
import Loading from '../components/Loading';
|
|
import { Logger, LOG_ACTIONS_RENDER_EVENT } from '../logger';
|
|
import StackTraceMessage from '../components/StackTraceMessage';
|
|
import visMap from '../../visualizations/main';
|
|
import sandboxedEval from '../modules/sandbox';
|
|
import './chart.css';
|
|
|
|
const propTypes = {
|
|
annotationData: PropTypes.object,
|
|
actions: PropTypes.object,
|
|
chartKey: PropTypes.string.isRequired,
|
|
containerId: PropTypes.string.isRequired,
|
|
datasource: PropTypes.object.isRequired,
|
|
formData: PropTypes.object.isRequired,
|
|
height: PropTypes.number,
|
|
width: PropTypes.number,
|
|
setControlValue: PropTypes.func,
|
|
timeout: PropTypes.number,
|
|
vizType: PropTypes.string.isRequired,
|
|
// state
|
|
chartAlert: PropTypes.string,
|
|
chartStatus: PropTypes.string,
|
|
chartUpdateEndTime: PropTypes.number,
|
|
chartUpdateStartTime: PropTypes.number,
|
|
latestQueryFormData: PropTypes.object,
|
|
queryRequest: PropTypes.object,
|
|
queryResponse: PropTypes.object,
|
|
lastRendered: PropTypes.number,
|
|
triggerQuery: PropTypes.bool,
|
|
// dashboard callbacks
|
|
addFilter: PropTypes.func,
|
|
getFilters: PropTypes.func,
|
|
clearFilter: PropTypes.func,
|
|
removeFilter: PropTypes.func,
|
|
};
|
|
|
|
const defaultProps = {
|
|
addFilter: () => ({}),
|
|
getFilters: () => ({}),
|
|
clearFilter: () => ({}),
|
|
removeFilter: () => ({}),
|
|
};
|
|
|
|
class Chart extends React.PureComponent {
|
|
constructor(props) {
|
|
super(props);
|
|
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.clearFilter = this.clearFilter.bind(this);
|
|
this.removeFilter = this.removeFilter.bind(this);
|
|
this.height = this.height.bind(this);
|
|
this.width = this.width.bind(this);
|
|
}
|
|
|
|
componentDidMount() {
|
|
if (this.props.triggerQuery) {
|
|
this.props.actions.runQuery(this.props.formData, false,
|
|
this.props.timeout,
|
|
this.props.chartKey,
|
|
);
|
|
}
|
|
}
|
|
|
|
componentWillReceiveProps(nextProps) {
|
|
this.annotationData = nextProps.annotationData;
|
|
this.containerId = nextProps.containerId;
|
|
this.selector = `#${this.containerId}`;
|
|
this.formData = nextProps.formData;
|
|
this.datasource = nextProps.datasource;
|
|
}
|
|
|
|
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.renderViz();
|
|
}
|
|
}
|
|
|
|
getFilters() {
|
|
return this.props.getFilters();
|
|
}
|
|
|
|
setTooltip(tooltip) {
|
|
this.setState({ tooltip });
|
|
}
|
|
|
|
addFilter(col, vals, merge = true, refresh = true) {
|
|
this.props.addFilter(col, vals, merge, refresh);
|
|
}
|
|
|
|
clearFilter() {
|
|
this.props.clearFilter();
|
|
}
|
|
|
|
removeFilter(col, vals, refresh = true) {
|
|
this.props.removeFilter(col, vals, refresh);
|
|
}
|
|
|
|
clearError() {
|
|
this.setState({
|
|
errorMsg: null,
|
|
});
|
|
}
|
|
|
|
width() {
|
|
return this.props.width || this.container.el.offsetWidth;
|
|
}
|
|
|
|
height() {
|
|
return this.props.height || this.container.el.offsetHeight;
|
|
}
|
|
|
|
d3format(col, number) {
|
|
const { datasource } = this.props;
|
|
const format = (datasource.column_formats && datasource.column_formats[col]) || '0.3s';
|
|
|
|
return d3format(format, number);
|
|
}
|
|
|
|
render_template(s) {
|
|
const context = {
|
|
width: this.width(),
|
|
height: this.height(),
|
|
};
|
|
return Mustache.render(s, context);
|
|
}
|
|
|
|
renderTooltip() {
|
|
if (this.state.tooltip) {
|
|
/* eslint-disable react/no-danger */
|
|
return (
|
|
<Tooltip
|
|
className="chart-tooltip"
|
|
id="chart-tooltip"
|
|
placement="right"
|
|
positionTop={this.state.tooltip.y - 10}
|
|
positionLeft={this.state.tooltip.x + 30}
|
|
arrowOffsetTop={10}
|
|
>
|
|
<div dangerouslySetInnerHTML={{ __html: this.state.tooltip.content }} />
|
|
</Tooltip>
|
|
);
|
|
/* eslint-enable react/no-danger */
|
|
}
|
|
return null;
|
|
}
|
|
|
|
renderViz() {
|
|
const viz = visMap[this.props.vizType];
|
|
const fd = this.props.formData;
|
|
const qr = this.props.queryResponse;
|
|
const renderStart = Logger.getTimestamp();
|
|
try {
|
|
// Executing user-defined data mutator function
|
|
if (fd.js_data) {
|
|
qr.data = sandboxedEval(fd.js_data)(qr.data);
|
|
}
|
|
// [re]rendering the visualization
|
|
viz(this, qr, this.props.setControlValue);
|
|
Logger.append(LOG_ACTIONS_RENDER_EVENT, {
|
|
label: this.props.chartKey,
|
|
vis_type: this.props.vizType,
|
|
start_offset: renderStart,
|
|
duration: Logger.getTimestamp() - renderStart,
|
|
});
|
|
this.props.actions.chartRenderingSucceeded(this.props.chartKey);
|
|
} catch (e) {
|
|
console.error(e); // eslint-disable-line
|
|
this.props.actions.chartRenderingFailed(e, this.props.chartKey);
|
|
}
|
|
}
|
|
|
|
render() {
|
|
const isLoading = this.props.chartStatus === 'loading';
|
|
return (
|
|
<div className={`token col-md-12 ${isLoading ? 'is-loading' : ''}`}>
|
|
{this.renderTooltip()}
|
|
{isLoading &&
|
|
<Loading size={25} />
|
|
}
|
|
{this.props.chartAlert &&
|
|
<StackTraceMessage
|
|
message={this.props.chartAlert}
|
|
queryResponse={this.props.queryResponse}
|
|
/>
|
|
}
|
|
|
|
{!isLoading && !this.props.chartAlert &&
|
|
<ChartBody
|
|
containerId={this.containerId}
|
|
vizType={this.props.vizType}
|
|
height={this.height}
|
|
width={this.width}
|
|
ref={(inner) => {
|
|
this.container = inner;
|
|
}}
|
|
/>
|
|
}
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
Chart.propTypes = propTypes;
|
|
Chart.defaultProps = defaultProps;
|
|
|
|
export default Chart;
|