[SIP-6] Migrate visualizations to new directory structure (part 2) (#5997)
* migrate MapBox * migrate bignumber * migrate timeseries table * migrate EventFlow * add default null * fix linting * use shortid instead of passing containerId
This commit is contained in:
parent
a9ef0aeaf5
commit
9f028ccc7b
|
|
@ -1,8 +1,7 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import shortid from 'shortid';
|
||||
import { XYChart, AreaSeries, CrossHair, LinearGradient } from '@data-ui/xy-chart';
|
||||
|
||||
import { brandColor } from '../../modules/colors';
|
||||
import { formatDateVerbose } from '../../modules/dates';
|
||||
import { computeMaxFontSize } from '../../modules/visUtils';
|
||||
|
|
@ -51,30 +50,33 @@ const propTypes = {
|
|||
bigNumber: PropTypes.number.isRequired,
|
||||
formatBigNumber: PropTypes.func,
|
||||
subheader: PropTypes.string,
|
||||
showTrendline: PropTypes.bool,
|
||||
showTrendLine: PropTypes.bool,
|
||||
startYAxisAtZero: PropTypes.bool,
|
||||
trendlineData: PropTypes.array,
|
||||
trendLineData: PropTypes.array,
|
||||
mainColor: PropTypes.string,
|
||||
gradientId: PropTypes.string,
|
||||
renderTooltip: PropTypes.func,
|
||||
};
|
||||
const defaultProps = {
|
||||
className: '',
|
||||
formatBigNumber: identity,
|
||||
subheader: '',
|
||||
showTrendline: false,
|
||||
showTrendLine: false,
|
||||
startYAxisAtZero: true,
|
||||
trendlineData: null,
|
||||
trendLineData: null,
|
||||
mainColor: brandColor,
|
||||
gradientId: '',
|
||||
renderTooltip: renderTooltipFactory(identity),
|
||||
};
|
||||
|
||||
class BigNumberVis extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.gradientId = shortid.generate();
|
||||
}
|
||||
|
||||
getClassName() {
|
||||
const { className, showTrendline } = this.props;
|
||||
const { className, showTrendLine } = this.props;
|
||||
const names = `big_number ${className}`;
|
||||
if (showTrendline) {
|
||||
if (showTrendLine) {
|
||||
return names;
|
||||
}
|
||||
return `${names} no_trendline`;
|
||||
|
|
@ -148,11 +150,10 @@ class BigNumberVis extends React.Component {
|
|||
renderTrendline(maxHeight) {
|
||||
const {
|
||||
width,
|
||||
trendlineData,
|
||||
trendLineData,
|
||||
mainColor,
|
||||
subheader,
|
||||
renderTooltip,
|
||||
gradientId,
|
||||
startYAxisAtZero,
|
||||
} = this.props;
|
||||
return (
|
||||
|
|
@ -170,13 +171,13 @@ class BigNumberVis extends React.Component {
|
|||
snapTooltipToDataX
|
||||
>
|
||||
<LinearGradient
|
||||
id={gradientId}
|
||||
id={this.gradientId}
|
||||
from={mainColor}
|
||||
to="#fff"
|
||||
/>
|
||||
<AreaSeries
|
||||
data={trendlineData}
|
||||
fill={`url(#${gradientId})`}
|
||||
data={trendLineData}
|
||||
fill={`url(#${this.gradientId})`}
|
||||
stroke={mainColor}
|
||||
/>
|
||||
<CrossHair
|
||||
|
|
@ -192,10 +193,10 @@ class BigNumberVis extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { showTrendline, height } = this.props;
|
||||
const { showTrendLine, height } = this.props;
|
||||
const className = this.getClassName();
|
||||
|
||||
if (showTrendline) {
|
||||
if (showTrendLine) {
|
||||
const chartHeight = Math.floor(PROPORTION.TRENDLINE * height);
|
||||
const allTextHeight = height - chartHeight;
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,86 +1,5 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import * as color from 'd3-color';
|
||||
import d3 from 'd3';
|
||||
import createAdaptor from '../../utils/createAdaptor';
|
||||
import Component from './BigNumber';
|
||||
import transformProps from './transformProps';
|
||||
|
||||
import BigNumberVis, { renderTooltipFactory } from './BigNumber';
|
||||
import { d3FormatPreset } from '../../modules/utils';
|
||||
|
||||
const TIME_COLUMN = '__timestamp';
|
||||
|
||||
function transform(data, formData) {
|
||||
let bigNumber;
|
||||
let trendlineData;
|
||||
const metricName = formData.metric.label || formData.metric;
|
||||
const compareSuffix = formData.compare_suffix || '';
|
||||
const compareLag = +formData.compare_lag || 0;
|
||||
const supportTrendline = formData.viz_type === 'big_number';
|
||||
const showTrendline = supportTrendline && formData.show_trend_line;
|
||||
let percentChange = 0;
|
||||
const subheader = formData.subheader || '';
|
||||
let formattedSubheader = subheader;
|
||||
if (supportTrendline) {
|
||||
const sortedData = [...data].sort((a, b) => a[TIME_COLUMN] - b[TIME_COLUMN]);
|
||||
bigNumber = sortedData[sortedData.length - 1][metricName];
|
||||
if (compareLag > 0) {
|
||||
const compareIndex = sortedData.length - (compareLag + 1);
|
||||
if (compareIndex >= 0) {
|
||||
const compareValue = sortedData[compareIndex][metricName];
|
||||
percentChange = compareValue === 0
|
||||
? 0 : (bigNumber - compareValue) / Math.abs(compareValue);
|
||||
const formatPercentChange = d3.format('+.1%');
|
||||
formattedSubheader = `${formatPercentChange(percentChange)} ${compareSuffix}`;
|
||||
}
|
||||
}
|
||||
trendlineData = showTrendline
|
||||
? sortedData.map(point => ({ x: point[TIME_COLUMN], y: point[metricName] }))
|
||||
: null;
|
||||
} else {
|
||||
bigNumber = data[0][metricName];
|
||||
trendlineData = null;
|
||||
}
|
||||
|
||||
let className = '';
|
||||
if (percentChange > 0) {
|
||||
className = 'positive';
|
||||
} else if (percentChange < 0) {
|
||||
className = 'negative';
|
||||
}
|
||||
|
||||
return {
|
||||
bigNumber,
|
||||
trendlineData,
|
||||
className,
|
||||
subheader: formattedSubheader,
|
||||
showTrendline,
|
||||
};
|
||||
}
|
||||
|
||||
function adaptor(slice, payload) {
|
||||
const { formData, containerId } = slice;
|
||||
|
||||
const transformedData = transform(payload.data, formData);
|
||||
const startYAxisAtZero = formData.start_y_axis_at_zero;
|
||||
const formatValue = d3FormatPreset(formData.y_axis_format);
|
||||
let userColor;
|
||||
if (formData.color_picker) {
|
||||
const { r, g, b } = formData.color_picker;
|
||||
userColor = color.rgb(r, g, b).hex();
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<BigNumberVis
|
||||
width={slice.width()}
|
||||
height={slice.height()}
|
||||
formatBigNumber={formatValue}
|
||||
startYAxisAtZero={startYAxisAtZero}
|
||||
mainColor={userColor}
|
||||
gradientId={`big_number_${containerId}`}
|
||||
renderTooltip={renderTooltipFactory(formatValue)}
|
||||
{...transformedData}
|
||||
/>,
|
||||
document.getElementById(containerId),
|
||||
);
|
||||
}
|
||||
|
||||
export default adaptor;
|
||||
export default createAdaptor(Component, transformProps);
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
import adaptor from './adaptor';
|
||||
import BigNumber from './BigNumber';
|
||||
|
||||
export { BigNumber };
|
||||
export default adaptor;
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
import * as color from 'd3-color';
|
||||
import d3 from 'd3';
|
||||
import { d3FormatPreset } from '../../modules/utils';
|
||||
import { renderTooltipFactory } from './BigNumber';
|
||||
|
||||
const TIME_COLUMN = '__timestamp';
|
||||
|
||||
export default function transformProps(basicChartInput) {
|
||||
const { formData, payload } = basicChartInput;
|
||||
const {
|
||||
colorPicker,
|
||||
compareLag: compareLagInput,
|
||||
compareSuffix = '',
|
||||
metric,
|
||||
showTrendLine,
|
||||
startYAxisAtZero,
|
||||
subheader = '',
|
||||
vizType,
|
||||
yAxisFormat,
|
||||
} = formData;
|
||||
const { data } = payload;
|
||||
|
||||
let mainColor;
|
||||
if (colorPicker) {
|
||||
const { r, g, b } = colorPicker;
|
||||
mainColor = color.rgb(r, g, b).hex();
|
||||
}
|
||||
|
||||
let bigNumber;
|
||||
let trendLineData;
|
||||
const metricName = metric.label || metric;
|
||||
const compareLag = +compareLagInput || 0;
|
||||
const supportTrendLine = vizType === 'big_number';
|
||||
const supportAndShowTrendLine = supportTrendLine && showTrendLine;
|
||||
let percentChange = 0;
|
||||
let formattedSubheader = subheader;
|
||||
if (supportTrendLine) {
|
||||
const sortedData = [...data].sort((a, b) => a[TIME_COLUMN] - b[TIME_COLUMN]);
|
||||
bigNumber = sortedData[sortedData.length - 1][metricName];
|
||||
if (compareLag > 0) {
|
||||
const compareIndex = sortedData.length - (compareLag + 1);
|
||||
if (compareIndex >= 0) {
|
||||
const compareValue = sortedData[compareIndex][metricName];
|
||||
percentChange = compareValue === 0
|
||||
? 0 : (bigNumber - compareValue) / Math.abs(compareValue);
|
||||
const formatPercentChange = d3.format('+.1%');
|
||||
formattedSubheader = `${formatPercentChange(percentChange)} ${compareSuffix}`;
|
||||
}
|
||||
}
|
||||
trendLineData = supportAndShowTrendLine
|
||||
? sortedData.map(point => ({ x: point[TIME_COLUMN], y: point[metricName] }))
|
||||
: null;
|
||||
} else {
|
||||
bigNumber = data[0][metricName];
|
||||
trendLineData = null;
|
||||
}
|
||||
|
||||
let className = '';
|
||||
if (percentChange > 0) {
|
||||
className = 'positive';
|
||||
} else if (percentChange < 0) {
|
||||
className = 'negative';
|
||||
}
|
||||
|
||||
const formatValue = d3FormatPreset(yAxisFormat);
|
||||
|
||||
return {
|
||||
bigNumber,
|
||||
className,
|
||||
formatBigNumber: formatValue,
|
||||
mainColor,
|
||||
renderTooltip: renderTooltipFactory(formatValue),
|
||||
showTrendLine: supportAndShowTrendLine,
|
||||
startYAxisAtZero,
|
||||
subheader: formattedSubheader,
|
||||
trendLineData,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import {
|
||||
App,
|
||||
withParentSize,
|
||||
cleanEvents,
|
||||
TS,
|
||||
EVENT_NAME,
|
||||
ENTITY_ID,
|
||||
} from '@data-ui/event-flow';
|
||||
import { t } from '../locales';
|
||||
|
||||
/*
|
||||
* This function takes the slice object and json payload as input and renders a
|
||||
* responsive <EventFlow /> component using the json data.
|
||||
*/
|
||||
function renderEventFlow(slice, json) {
|
||||
const container = document.querySelector(slice.selector);
|
||||
const hasData = json.data && json.data.length > 0;
|
||||
|
||||
// the slice container overflows ~80px in explorer, so we have to correct for this
|
||||
const isExplorer = (/explore/).test(window.location.pathname);
|
||||
|
||||
const ResponsiveVis = withParentSize(({
|
||||
parentWidth,
|
||||
parentHeight,
|
||||
...rest
|
||||
}) => (
|
||||
<App
|
||||
width={parentWidth}
|
||||
height={parentHeight - (isExplorer ? 80 : 0)}
|
||||
{...rest}
|
||||
/>
|
||||
));
|
||||
|
||||
// render the component if we have data, otherwise render a no-data message
|
||||
let Component;
|
||||
if (hasData) {
|
||||
const userKey = json.form_data.entity;
|
||||
const eventNameKey = json.form_data.all_columns_x;
|
||||
|
||||
// map from the Superset form fields to <EventFlow />'s expected data keys
|
||||
const accessorFunctions = {
|
||||
[TS]: datum => new Date(datum.__timestamp), // eslint-disable-line no-underscore-dangle
|
||||
[EVENT_NAME]: datum => datum[eventNameKey],
|
||||
[ENTITY_ID]: datum => String(datum[userKey]),
|
||||
};
|
||||
|
||||
const dirtyData = json.data;
|
||||
const cleanData = cleanEvents(dirtyData, accessorFunctions);
|
||||
const minEventCount = slice.formData.min_leaf_node_event_count;
|
||||
|
||||
Component = <ResponsiveVis data={cleanData} initialMinEventCount={minEventCount} />;
|
||||
} else {
|
||||
Component = <div>{t('Sorry, there appears to be no data')}</div>;
|
||||
}
|
||||
|
||||
ReactDOM.render(Component, container);
|
||||
}
|
||||
|
||||
module.exports = renderEventFlow;
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { App, withParentSize } from '@data-ui/event-flow';
|
||||
import { t } from '../../locales';
|
||||
|
||||
const propTypes = {
|
||||
className: PropTypes.string,
|
||||
data: PropTypes.array,
|
||||
initialMinEventCount: PropTypes.number,
|
||||
};
|
||||
const defaultProps = {
|
||||
className: '',
|
||||
data: null,
|
||||
};
|
||||
|
||||
function isExplorer() {
|
||||
return (/explore/).test(window.location.pathname);
|
||||
}
|
||||
|
||||
// The slice container overflows ~80px in explorer,
|
||||
// so we have to correct for this.
|
||||
const ResponsiveVis = withParentSize(({
|
||||
parentWidth,
|
||||
parentHeight,
|
||||
...rest
|
||||
}) => (
|
||||
<App
|
||||
width={parentWidth}
|
||||
height={parentHeight - (isExplorer() ? 80 : 0)}
|
||||
{...rest}
|
||||
/>
|
||||
));
|
||||
|
||||
function CustomEventFlow(props) {
|
||||
const { data, initialMinEventCount } = props;
|
||||
if (data) {
|
||||
return (
|
||||
<ResponsiveVis
|
||||
data={data}
|
||||
initialMinEventCount={initialMinEventCount}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>{t('Sorry, there appears to be no data')}</div>
|
||||
);
|
||||
}
|
||||
|
||||
CustomEventFlow.propTypes = propTypes;
|
||||
CustomEventFlow.defaultProps = defaultProps;
|
||||
|
||||
export default CustomEventFlow;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import createAdaptor from '../../utils/createAdaptor';
|
||||
import Component from './EventFlow';
|
||||
import transformProps from './transformProps';
|
||||
|
||||
export default createAdaptor(Component, transformProps);
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import {
|
||||
cleanEvents,
|
||||
TS,
|
||||
EVENT_NAME,
|
||||
ENTITY_ID,
|
||||
} from '@data-ui/event-flow';
|
||||
|
||||
export default function transformProps(basicChartInput) {
|
||||
const { formData, payload } = basicChartInput;
|
||||
const {
|
||||
allColumnsX,
|
||||
entity,
|
||||
minLeafNodeEventCount,
|
||||
} = formData;
|
||||
const { data } = payload;
|
||||
|
||||
const hasData = data && data.length > 0;
|
||||
if (hasData) {
|
||||
const userKey = entity;
|
||||
const eventNameKey = allColumnsX;
|
||||
|
||||
// map from the Superset form fields to <EventFlow />'s expected data keys
|
||||
const accessorFunctions = {
|
||||
[TS]: datum => new Date(datum.__timestamp), // eslint-disable-line no-underscore-dangle
|
||||
[EVENT_NAME]: datum => datum[eventNameKey],
|
||||
[ENTITY_ID]: datum => String(datum[userKey]),
|
||||
};
|
||||
|
||||
const cleanData = cleanEvents(data, accessorFunctions);
|
||||
return {
|
||||
data: cleanData,
|
||||
initialMinEventCount: minLeafNodeEventCount,
|
||||
};
|
||||
}
|
||||
return { data: null };
|
||||
}
|
||||
|
|
@ -1,16 +1,14 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactDOM from 'react-dom';
|
||||
import MapGL from 'react-map-gl';
|
||||
import Immutable from 'immutable';
|
||||
import supercluster from 'supercluster';
|
||||
import ViewportMercator from 'viewport-mercator-project';
|
||||
import ScatterPlotGlowOverlay from './ScatterPlotGlowOverlay';
|
||||
import './MapBox.css';
|
||||
|
||||
const NOOP = () => {};
|
||||
const DEFAULT_POINT_RADIUS = 60;
|
||||
const DEFAULT_MAX_ZOOM = 16;
|
||||
export const DEFAULT_MAX_ZOOM = 16;
|
||||
export const DEFAULT_POINT_RADIUS = 60;
|
||||
|
||||
const propTypes = {
|
||||
width: PropTypes.number,
|
||||
|
|
@ -124,86 +122,4 @@ class MapBox extends React.Component {
|
|||
MapBox.propTypes = propTypes;
|
||||
MapBox.defaultProps = defaultProps;
|
||||
|
||||
function mapbox(slice, payload, setControlValue) {
|
||||
const { formData, selector } = slice;
|
||||
const {
|
||||
hasCustomMetric,
|
||||
geoJSON,
|
||||
bounds,
|
||||
mapboxApiKey,
|
||||
} = payload.data;
|
||||
const {
|
||||
clustering_radius: clusteringRadius,
|
||||
global_opacity: globalOpacity,
|
||||
mapbox_color: color,
|
||||
mapbox_style: mapStyle,
|
||||
pandas_aggfunc: aggregatorName,
|
||||
point_radius: pointRadius,
|
||||
point_radius_unit: pointRadiusUnit,
|
||||
render_while_dragging: renderWhileDragging,
|
||||
} = formData;
|
||||
|
||||
// Validate mapbox color
|
||||
const rgb = /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/
|
||||
.exec(color);
|
||||
if (rgb === null) {
|
||||
slice.error('Color field must be of form \'rgb(%d, %d, %d)\'');
|
||||
return;
|
||||
}
|
||||
|
||||
const opts = {
|
||||
radius: clusteringRadius,
|
||||
maxZoom: DEFAULT_MAX_ZOOM,
|
||||
};
|
||||
if (hasCustomMetric) {
|
||||
opts.initial = () => ({
|
||||
sum: 0,
|
||||
squaredSum: 0,
|
||||
min: Infinity,
|
||||
max: -Infinity,
|
||||
});
|
||||
opts.map = prop => ({
|
||||
sum: prop.metric,
|
||||
squaredSum: Math.pow(prop.metric, 2),
|
||||
min: prop.metric,
|
||||
max: prop.metric,
|
||||
});
|
||||
opts.reduce = (accu, prop) => {
|
||||
// Temporarily disable param-reassignment linting to work with supercluster's api
|
||||
/* eslint-disable no-param-reassign */
|
||||
accu.sum += prop.sum;
|
||||
accu.squaredSum += prop.squaredSum;
|
||||
accu.min = Math.min(accu.min, prop.min);
|
||||
accu.max = Math.max(accu.max, prop.max);
|
||||
/* eslint-enable no-param-reassign */
|
||||
};
|
||||
}
|
||||
const clusterer = supercluster(opts);
|
||||
clusterer.load(geoJSON.features);
|
||||
|
||||
ReactDOM.render(
|
||||
<MapBox
|
||||
width={slice.width()}
|
||||
height={slice.height()}
|
||||
hasCustomMetric={hasCustomMetric}
|
||||
aggregatorName={aggregatorName}
|
||||
clusterer={clusterer}
|
||||
globalOpacity={globalOpacity}
|
||||
mapStyle={mapStyle}
|
||||
mapboxApiKey={mapboxApiKey}
|
||||
onViewportChange={({ latitude, longitude, zoom }) => {
|
||||
setControlValue('viewport_longitude', longitude);
|
||||
setControlValue('viewport_latitude', latitude);
|
||||
setControlValue('viewport_zoom', zoom);
|
||||
}}
|
||||
pointRadius={pointRadius === 'Auto' ? DEFAULT_POINT_RADIUS : pointRadius}
|
||||
pointRadiusUnit={pointRadiusUnit}
|
||||
renderWhileDragging={renderWhileDragging}
|
||||
rgb={rgb}
|
||||
bounds={bounds}
|
||||
/>,
|
||||
document.querySelector(selector),
|
||||
);
|
||||
}
|
||||
|
||||
export default mapbox;
|
||||
export default MapBox;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
import createAdaptor from '../../utils/createAdaptor';
|
||||
import Component from './MapBox';
|
||||
import transformProps from './transformProps';
|
||||
|
||||
export default createAdaptor(Component, transformProps);
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
import supercluster from 'supercluster';
|
||||
import { DEFAULT_POINT_RADIUS, DEFAULT_MAX_ZOOM } from './MapBox';
|
||||
|
||||
export default function transformProps(basicChartInput) {
|
||||
const { formData, onError, payload, setControlValue } = basicChartInput;
|
||||
const {
|
||||
bounds,
|
||||
geoJSON,
|
||||
hasCustomMetric,
|
||||
mapboxApiKey,
|
||||
} = payload.data;
|
||||
const {
|
||||
clusteringRadius,
|
||||
globalOpacity,
|
||||
mapboxColor,
|
||||
mapboxStyle,
|
||||
pandasAggfunc,
|
||||
pointRadius,
|
||||
pointRadiusUnit,
|
||||
renderWhileDragging,
|
||||
} = formData;
|
||||
|
||||
// Validate mapbox color
|
||||
const rgb = /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/
|
||||
.exec(mapboxColor);
|
||||
if (rgb === null) {
|
||||
onError('Color field must be of form \'rgb(%d, %d, %d)\'');
|
||||
return {};
|
||||
}
|
||||
|
||||
const opts = {
|
||||
radius: clusteringRadius,
|
||||
maxZoom: DEFAULT_MAX_ZOOM,
|
||||
};
|
||||
if (hasCustomMetric) {
|
||||
opts.initial = () => ({
|
||||
sum: 0,
|
||||
squaredSum: 0,
|
||||
min: Infinity,
|
||||
max: -Infinity,
|
||||
});
|
||||
opts.map = prop => ({
|
||||
sum: prop.metric,
|
||||
squaredSum: Math.pow(prop.metric, 2),
|
||||
min: prop.metric,
|
||||
max: prop.metric,
|
||||
});
|
||||
opts.reduce = (accu, prop) => {
|
||||
// Temporarily disable param-reassignment linting to work with supercluster's api
|
||||
/* eslint-disable no-param-reassign */
|
||||
accu.sum += prop.sum;
|
||||
accu.squaredSum += prop.squaredSum;
|
||||
accu.min = Math.min(accu.min, prop.min);
|
||||
accu.max = Math.max(accu.max, prop.max);
|
||||
/* eslint-enable no-param-reassign */
|
||||
};
|
||||
}
|
||||
const clusterer = supercluster(opts);
|
||||
clusterer.load(geoJSON.features);
|
||||
|
||||
return {
|
||||
aggregatorName: pandasAggfunc,
|
||||
bounds,
|
||||
clusterer,
|
||||
globalOpacity,
|
||||
hasCustomMetric,
|
||||
mapboxApiKey,
|
||||
mapStyle: mapboxStyle,
|
||||
onViewportChange({ latitude, longitude, zoom }) {
|
||||
setControlValue('viewport_longitude', longitude);
|
||||
setControlValue('viewport_latitude', latitude);
|
||||
setControlValue('viewport_zoom', zoom);
|
||||
},
|
||||
pointRadius: pointRadius === 'Auto' ? DEFAULT_POINT_RADIUS : pointRadius,
|
||||
pointRadiusUnit,
|
||||
renderWhileDragging,
|
||||
rgb,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
import ReactDOM from 'react-dom';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import d3 from 'd3';
|
||||
|
|
@ -270,58 +269,4 @@ class TimeTable extends React.PureComponent {
|
|||
TimeTable.propTypes = propTypes;
|
||||
TimeTable.defaultProps = defaultProps;
|
||||
|
||||
function adaptor(slice, payload) {
|
||||
const { containerId, formData, datasource } = slice;
|
||||
const {
|
||||
column_collection: columnConfigs,
|
||||
groupby,
|
||||
metrics,
|
||||
url,
|
||||
} = formData;
|
||||
const { records, columns } = payload.data;
|
||||
const isGroupBy = groupby.length > 0;
|
||||
|
||||
// When there is a "group by",
|
||||
// each row in the table is a database column
|
||||
// Otherwise,
|
||||
// each row in the table is a metric
|
||||
let rows;
|
||||
if (isGroupBy) {
|
||||
rows = columns.map(column => (typeof column === 'object')
|
||||
? column
|
||||
: { label: column });
|
||||
} else {
|
||||
const metricMap = datasource.metrics
|
||||
.reduce((acc, current) => {
|
||||
const map = acc;
|
||||
map[current.metric_name] = current;
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
rows = metrics.map(metric => (typeof metric === 'object')
|
||||
? metric
|
||||
: metricMap[metric]);
|
||||
}
|
||||
|
||||
// TODO: Better parse this from controls instead of mutative value here.
|
||||
columnConfigs.forEach((column) => {
|
||||
const c = column;
|
||||
if (c.timeLag !== undefined && c.timeLag !== null && c.timeLag !== '') {
|
||||
c.timeLag = parseInt(c.timeLag, 10);
|
||||
}
|
||||
});
|
||||
|
||||
ReactDOM.render(
|
||||
<TimeTable
|
||||
height={slice.height()}
|
||||
data={records}
|
||||
columnConfigs={columnConfigs}
|
||||
rows={rows}
|
||||
rowType={isGroupBy ? 'column' : 'metric'}
|
||||
url={url}
|
||||
/>,
|
||||
document.getElementById(containerId),
|
||||
);
|
||||
}
|
||||
|
||||
export default adaptor;
|
||||
export default TimeTable;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
import createAdaptor from '../../utils/createAdaptor';
|
||||
import Component from './TimeTable';
|
||||
import transformProps from './transformProps';
|
||||
|
||||
export default createAdaptor(Component, transformProps);
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
export default function transformProps(basicChartInput) {
|
||||
const { datasource, formData, payload } = basicChartInput;
|
||||
const {
|
||||
columnCollection,
|
||||
groupby,
|
||||
metrics,
|
||||
url,
|
||||
} = formData;
|
||||
const { records, columns } = payload.data;
|
||||
const isGroupBy = groupby.length > 0;
|
||||
|
||||
// When there is a "group by",
|
||||
// each row in the table is a database column
|
||||
// Otherwise,
|
||||
// each row in the table is a metric
|
||||
let rows;
|
||||
if (isGroupBy) {
|
||||
rows = columns.map(column => (typeof column === 'object')
|
||||
? column
|
||||
: { label: column });
|
||||
} else {
|
||||
const metricMap = datasource.metrics
|
||||
.reduce((acc, current) => {
|
||||
const map = acc;
|
||||
map[current.metric_name] = current;
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
rows = metrics.map(metric => (typeof metric === 'object')
|
||||
? metric
|
||||
: metricMap[metric]);
|
||||
}
|
||||
|
||||
// TODO: Better parse this from controls instead of mutative value here.
|
||||
columnCollection.forEach((column) => {
|
||||
const c = column;
|
||||
if (c.timeLag !== undefined && c.timeLag !== null && c.timeLag !== '') {
|
||||
c.timeLag = parseInt(c.timeLag, 10);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
data: records,
|
||||
columnConfigs: columnCollection,
|
||||
rows,
|
||||
rowType: isGroupBy ? 'column' : 'metric',
|
||||
url,
|
||||
};
|
||||
}
|
||||
|
|
@ -65,9 +65,9 @@ const vizMap = {
|
|||
[VIZ_TYPES.area]: loadNvd3,
|
||||
[VIZ_TYPES.bar]: loadNvd3,
|
||||
[VIZ_TYPES.big_number]: () =>
|
||||
loadVis(import(/* webpackChunkName: 'big_number' */ './BigNumber/index.js')),
|
||||
loadVis(import(/* webpackChunkName: 'big_number' */ './BigNumber/adaptor.jsx')),
|
||||
[VIZ_TYPES.big_number_total]: () =>
|
||||
loadVis(import(/* webpackChunkName: "big_number" */ './BigNumber/index.js')),
|
||||
loadVis(import(/* webpackChunkName: "big_number" */ './BigNumber/adaptor.jsx')),
|
||||
[VIZ_TYPES.box_plot]: loadNvd3,
|
||||
[VIZ_TYPES.bubble]: loadNvd3,
|
||||
[VIZ_TYPES.bullet]: loadNvd3,
|
||||
|
|
@ -89,7 +89,7 @@ const vizMap = {
|
|||
[VIZ_TYPES.line_multi]: () =>
|
||||
loadVis(import(/* webpackChunkName: "line_multi" */ './nvd3/LineMulti.js')),
|
||||
[VIZ_TYPES.time_pivot]: loadNvd3,
|
||||
[VIZ_TYPES.mapbox]: () => loadVis(import(/* webpackChunkName: "mapbox" */ './MapBox/MapBox.jsx')),
|
||||
[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')),
|
||||
|
|
@ -101,7 +101,7 @@ const vizMap = {
|
|||
[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/TimeTable.jsx')),
|
||||
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')),
|
||||
|
|
@ -111,7 +111,7 @@ const vizMap = {
|
|||
loadVis(import(/* webpackChunkName: "world_map" */ './WorldMap/adaptor.jsx')),
|
||||
[VIZ_TYPES.dual_line]: loadNvd3,
|
||||
[VIZ_TYPES.event_flow]: () =>
|
||||
loadVis(import(/* webpackChunkName: "EventFlow" */ './EventFlow.jsx')),
|
||||
loadVis(import(/* webpackChunkName: "EventFlow" */ './EventFlow/adaptor.jsx')),
|
||||
[VIZ_TYPES.paired_ttest]: () =>
|
||||
loadVis(import(/* webpackChunkName: "paired_ttest" */ './PairedTTest/adaptor.jsx')),
|
||||
[VIZ_TYPES.partition]: () =>
|
||||
|
|
|
|||
Loading…
Reference in New Issue