Add x and y column pickers to graph types (#14052)

* Add x and y column picker to line graph

* Use column selector in heatmap options

* Add column selector to histogram

* Add column selector to scatter plot

* Fix tests

* Add xColumn and yColumn to data structure in line graph tests

* Move scatter container defaults to visSwitcher

* Add fillcolumn validity checking to histogram

* regularize XYcontainer and render it and lineplussingle stat in visSwitcher

* Initialize all views with null columns

* Place Line and Scatter Options x-y column selector behind cloud feature flag

* Rename getGroupableColumnSelection

* Add defaults to x/y column selections in line graphs

* Add defaults to x/y column selections in scatter
pull/14029/head
Deniz Kusefoglu 2019-06-04 18:07:53 -07:00 committed by GitHub
parent f2d198c169
commit 9edf9bd6ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 465 additions and 213 deletions

View File

@ -591,6 +591,8 @@ type LinePlusSingleStatProperties struct {
DecimalPlaces DecimalPlaces `json:"decimalPlaces"`
Note string `json:"note"`
ShowNoteWhenEmpty bool `json:"showNoteWhenEmpty"`
XColumn string `json:"xColumn"`
YColumn string `json:"yColumn"`
}
// XYViewProperties represents options for line, bar, step, or stacked view in Chronograf
@ -603,6 +605,8 @@ type XYViewProperties struct {
ViewColors []ViewColor `json:"colors"`
Note string `json:"note"`
ShowNoteWhenEmpty bool `json:"showNoteWhenEmpty"`
XColumn string `json:"xColumn"`
YColumn string `json:"yColumn"`
}
// SingleStatViewProperties represents options for single stat view in Chronograf

View File

@ -47,7 +47,9 @@ func TestView_MarshalJSON(t *testing.T) {
"legend": {},
"geom": "",
"note": "",
"showNoteWhenEmpty": false
"showNoteWhenEmpty": false,
"xColumn": "",
"yColumn": ""
}
}
`,

View File

@ -0,0 +1,42 @@
// Libraries
import React, {FunctionComponent} from 'react'
// Components
import {Dropdown, Form, ComponentStatus} from 'src/clockface'
interface Props {
selectedColumn: string
availableColumns: string[]
axisName: string
onSelectColumn: (col: string) => void
}
const ColumnSelector: FunctionComponent<Props> = ({
selectedColumn,
onSelectColumn,
availableColumns,
axisName,
}) => {
return (
<Form.Element label={`${axisName.toUpperCase()} Column`}>
<Dropdown
selectedID={selectedColumn}
onChange={onSelectColumn}
status={
availableColumns.length == 0
? ComponentStatus.Disabled
: ComponentStatus.Default
}
titleText="None"
>
{availableColumns.map(columnName => (
<Dropdown.Item id={columnName} key={columnName} value={columnName}>
{columnName}
</Dropdown.Item>
))}
</Dropdown>
</Form.Element>
)
}
export default ColumnSelector

View File

@ -46,8 +46,10 @@ const HistogramContainer: FunctionComponent<Props> = ({
columnKeys.includes(xColumn) ? table.getColumn(xColumn, 'number') : []
)
const isValidView = xColumn && columnKeys.includes(xColumn)
fillColumns.every(col => columnKeys.includes(col))
const isValidView =
xColumn &&
columnKeys.includes(xColumn) &&
fillColumns.every(col => columnKeys.includes(col))
if (!isValidView) {
return <EmptyGraphMessage message={INVALID_DATA_COPY} />

View File

@ -65,14 +65,20 @@ const RefreshingViewSwitcher: FunctionComponent<Props> = ({
)
case ViewType.XY:
return (
<XYContainer
files={files}
viewProperties={properties}
loading={loading}
>
{config => <Plot config={config} />}
</XYContainer>
<VisTableTransform files={files}>
{({table, fluxGroupKeyUnion}) => (
<XYContainer
table={table}
fluxGroupKeyUnion={fluxGroupKeyUnion}
viewProperties={properties}
loading={loading}
>
{config => <Plot config={config} />}
</XYContainer>
)}
</VisTableTransform>
)
case ViewType.LinePlusSingleStat:
const xyProperties = {
...properties,
@ -88,24 +94,29 @@ const RefreshingViewSwitcher: FunctionComponent<Props> = ({
} as SingleStatView
return (
<XYContainer
files={files}
viewProperties={xyProperties}
loading={loading}
>
{config => (
<Plot config={config}>
<LatestValueTransform table={config.table} quiet={true}>
{latestValue => (
<SingleStat
stat={latestValue}
properties={singleStatProperties}
/>
)}
</LatestValueTransform>
</Plot>
<VisTableTransform files={files}>
{({table, fluxGroupKeyUnion}) => (
<XYContainer
table={table}
fluxGroupKeyUnion={fluxGroupKeyUnion}
viewProperties={xyProperties}
loading={loading}
>
{config => (
<Plot config={config}>
<LatestValueTransform table={config.table} quiet={true}>
{latestValue => (
<SingleStat
stat={latestValue}
properties={singleStatProperties}
/>
)}
</LatestValueTransform>
</Plot>
)}
</XYContainer>
)}
</XYContainer>
</VisTableTransform>
)
case ViewType.Histogram:
return (
@ -138,10 +149,9 @@ const RefreshingViewSwitcher: FunctionComponent<Props> = ({
case ViewType.Scatter:
return (
<VisTableTransform files={files}>
{({table, fluxGroupKeyUnion}) => (
{({table}) => (
<ScatterContainer
table={table}
fluxGroupKeyUnion={fluxGroupKeyUnion}
loading={loading}
viewProperties={properties}
>

View File

@ -1,6 +1,5 @@
// Libraries
import React, {FunctionComponent, useEffect} from 'react'
import {connect} from 'react-redux'
import React, {FunctionComponent} from 'react'
import {Config, Table} from '@influxdata/vis'
// Components
@ -9,7 +8,7 @@ import GraphLoadingDots from 'src/shared/components/GraphLoadingDots'
// Utils
import {useVisDomainSettings} from 'src/shared/utils/useVisDomainSettings'
import {getFormatter, chooseYColumn, chooseXColumn} from 'src/shared/utils/vis'
import {getFormatter, chooseXColumn, chooseYColumn} from 'src/shared/utils/vis'
// Constants
import {VIS_THEME} from 'src/shared/constants'
@ -18,9 +17,8 @@ import {INVALID_DATA_COPY} from 'src/shared/copy/cell'
// Types
import {RemoteDataState, ScatterView} from 'src/types'
import {setFillColumns, setSymbolColumns} from 'src/timeMachine/actions'
interface OwnProps {
interface Props {
table: Table
fluxGroupKeyUnion?: string[]
loading: RemoteDataState
@ -28,18 +26,10 @@ interface OwnProps {
children: (config: Config) => JSX.Element
}
interface DispatchProps {
onSetFillColumns: typeof setFillColumns
onSetSymbolColumns: typeof setSymbolColumns
}
type Props = OwnProps & DispatchProps
const ScatterContainer: FunctionComponent<Props> = ({
table,
loading,
children,
fluxGroupKeyUnion,
viewProperties: {
xAxisLabel,
yAxisLabel,
@ -50,39 +40,15 @@ const ScatterContainer: FunctionComponent<Props> = ({
colors,
xDomain: storedXDomain,
yDomain: storedYDomain,
xColumn: storedXColumn,
yColumn: storedYColumn,
},
onSetFillColumns,
onSetSymbolColumns,
}) => {
useEffect(() => {
if (fluxGroupKeyUnion && (!storedSymbol || !storedFill)) {
// if new view, maximize variations in symbol and color
const filteredGroupKeys = fluxGroupKeyUnion.filter(
k =>
![
'result',
'table',
'_measurement',
'_start',
'_stop',
'_field',
].includes(k)
)
if (!storedSymbol) {
onSetSymbolColumns(filteredGroupKeys)
}
if (!storedFill) {
onSetFillColumns(filteredGroupKeys)
}
}
})
const fillColumns = storedFill || []
const symbolColumns = storedSymbol || []
// TODO: allow xcolumn and ycolumn to be user selectable
const xColumn = chooseXColumn(table)
const yColumn = chooseYColumn(table)
const xColumn = storedXColumn || chooseXColumn(table)
const yColumn = storedYColumn || chooseYColumn(table)
const columnKeys = table.columnKeys
@ -150,12 +116,4 @@ const ScatterContainer: FunctionComponent<Props> = ({
)
}
const mdtp = {
onSetFillColumns: setFillColumns,
onSetSymbolColumns: setSymbolColumns,
}
export default connect<{}, DispatchProps, {}>(
null,
mdtp
)(ScatterContainer)
export default ScatterContainer

View File

@ -1,6 +1,6 @@
// Libraries
import React, {FunctionComponent, useMemo} from 'react'
import {Config, fromFlux} from '@influxdata/vis'
import {Config, Table} from '@influxdata/vis'
// Components
import EmptyGraphMessage from 'src/shared/components/EmptyGraphMessage'
@ -26,19 +26,23 @@ import {INVALID_DATA_COPY} from 'src/shared/copy/cell'
import {RemoteDataState, XYView} from 'src/types'
interface Props {
files: string[]
table: Table
fluxGroupKeyUnion: string[]
loading: RemoteDataState
viewProperties: XYView
children: (config: Config) => JSX.Element
}
const XYContainer: FunctionComponent<Props> = ({
files,
table,
fluxGroupKeyUnion,
loading,
children,
viewProperties: {
geom,
colors,
xColumn: storedXColumn,
yColumn: storedYColumn,
axes: {
x: {label: xAxisLabel, bounds: xBounds},
y: {
@ -51,18 +55,12 @@ const XYContainer: FunctionComponent<Props> = ({
},
},
}) => {
const {table, fluxGroupKeyUnion} = useMemo(
() => fromFlux(files.join('\n\n')),
[files]
)
// Eventually these will be configurable in the line graph options UI
const xColumn = chooseXColumn(table)
const yColumn = chooseYColumn(table)
const storedXDomain = useMemo(() => parseBounds(xBounds), [xBounds])
const storedYDomain = useMemo(() => parseBounds(yBounds), [yBounds])
const xColumn = storedXColumn || chooseXColumn(table)
const yColumn = storedYColumn || chooseYColumn(table)
const columnKeys = table.columnKeys
const [xDomain, onSetXDomain, onResetXDomain] = useVisDomainSettings(
@ -74,7 +72,14 @@ const XYContainer: FunctionComponent<Props> = ({
storedYDomain,
columnKeys.includes(yColumn) ? table.getColumn(yColumn, 'number') : []
)
if (!xColumn || !yColumn) {
const isValidView =
xColumn &&
columnKeys.includes(xColumn) &&
yColumn &&
columnKeys.includes(yColumn)
if (!isValidView) {
return <EmptyGraphMessage message={INVALID_DATA_COPY} />
}

View File

@ -81,6 +81,8 @@ export const myView: View = {
colors: [],
note: '',
showNoteWhenEmpty: false,
xColumn: null,
yColumn: null,
},
}

View File

@ -343,6 +343,8 @@ describe('resourceToTemplate', () => {
colors: [],
note: '',
showNoteWhenEmpty: false,
xColumn: null,
yColumn: null,
},
},
},

View File

@ -118,6 +118,8 @@ const NEW_VIEW_CREATORS = {
type: ViewType.XY,
shape: ViewShape.ChronografV2,
geom: XYViewGeom.Line,
xColumn: null,
yColumn: null,
},
}),
[ViewType.Histogram]: (): NewView<HistogramView> => ({
@ -126,7 +128,7 @@ const NEW_VIEW_CREATORS = {
queries: [],
type: ViewType.Histogram,
shape: ViewShape.ChronografV2,
xColumn: '_value',
xColumn: null,
xDomain: null,
xAxisLabel: '',
fillColumns: null,
@ -143,8 +145,8 @@ const NEW_VIEW_CREATORS = {
queries: [],
type: ViewType.Heatmap,
shape: ViewShape.ChronografV2,
xColumn: '_time',
yColumn: '_value',
xColumn: null,
yColumn: null,
xDomain: null,
yDomain: null,
xAxisLabel: '',
@ -182,6 +184,8 @@ const NEW_VIEW_CREATORS = {
...defaultSingleStatViewProperties(),
type: ViewType.LinePlusSingleStat,
shape: ViewShape.ChronografV2,
xColumn: null,
yColumn: null,
},
}),
[ViewType.Table]: (): NewView<TableView> => ({
@ -225,9 +229,9 @@ const NEW_VIEW_CREATORS = {
showNoteWhenEmpty: false,
fillColumns: null,
symbolColumns: null,
xColumn: '_time',
xColumn: null,
xDomain: null,
yColumn: '_value',
yColumn: null,
yDomain: null,
xAxisLabel: '',
yAxisLabel: '',

View File

@ -9,6 +9,7 @@ import {
getXColumnSelection,
getYColumnSelection,
getFillColumnsSelection,
getSymbolColumnsSelection,
} from 'src/timeMachine/selectors'
// Types
@ -19,15 +20,12 @@ interface StateProps {
xColumn: string
yColumn: string
fillColumns: string[]
symbolColumns: string[]
fluxGroupKeyUnion: string[]
}
interface OwnProps {
children: (props: {
table: Table
xColumn: string
yColumn: string
fillColumns: string[]
}) => JSX.Element
children: (props: StateProps) => JSX.Element
}
type Props = StateProps & OwnProps
@ -37,22 +35,34 @@ const VisDataTransform: FunctionComponent<Props> = ({
xColumn,
yColumn,
fillColumns,
symbolColumns,
children,
fluxGroupKeyUnion,
}) => {
return children({table, xColumn, yColumn, fillColumns})
return children({
table,
xColumn,
yColumn,
fillColumns,
symbolColumns,
fluxGroupKeyUnion,
})
}
const mstp = (state: AppState) => {
const table = getVisTable(state)
const mstp = (state: AppState): StateProps => {
const {table, fluxGroupKeyUnion} = getVisTable(state)
const xColumn = getXColumnSelection(state)
const yColumn = getYColumnSelection(state)
const fillColumns = getFillColumnsSelection(state)
const symbolColumns = getSymbolColumnsSelection(state)
return {
table,
xColumn,
yColumn,
fillColumns,
symbolColumns,
fluxGroupKeyUnion,
}
}

View File

@ -10,6 +10,9 @@ import HistogramContainer from 'src/shared/components/HistogramContainer'
import VisDataTransform from 'src/timeMachine/components/VisDataTransform'
import RefreshingViewSwitcher from 'src/shared/components/RefreshingViewSwitcher'
import HeatmapContainer from 'src/shared/components/HeatmapContainer'
import ScatterContainer from 'src/shared/components/ScatterContainer'
import SingleStat from 'src/shared/components/SingleStat'
import LatestValueTransform from 'src/shared/components/LatestValueTransform'
// Utils
import {getActiveTimeMachine, getTables} from 'src/timeMachine/selectors'
@ -21,7 +24,11 @@ import {
FluxTable,
RemoteDataState,
AppState,
XYViewGeom,
XYView,
SingleStatView,
} from 'src/types'
import XYContainer from 'src/shared/components/XYContainer'
interface StateProps {
files: string[]
@ -90,6 +97,94 @@ const VisSwitcher: FunctionComponent<StateProps> = ({
)
}
if (properties.type === ViewType.Scatter) {
return (
<VisDataTransform>
{({table, xColumn, yColumn, fillColumns, symbolColumns}) => (
<ScatterContainer
table={table}
loading={loading}
viewProperties={{
...properties,
xColumn,
yColumn,
fillColumns,
symbolColumns,
}}
>
{config => <Plot config={config} />}
</ScatterContainer>
)}
</VisDataTransform>
)
}
if (properties.type === ViewType.XY) {
return (
<VisDataTransform>
{({table, fluxGroupKeyUnion, xColumn, yColumn}) => (
<XYContainer
table={table}
fluxGroupKeyUnion={fluxGroupKeyUnion}
loading={loading}
viewProperties={{
...properties,
xColumn,
yColumn,
}}
>
{config => <Plot config={config} />}
</XYContainer>
)}
</VisDataTransform>
)
}
if (properties.type === ViewType.LinePlusSingleStat) {
const xyProperties = {
...properties,
colors: properties.colors.filter(c => c.type === 'scale'),
type: ViewType.XY,
geom: XYViewGeom.Line,
} as XYView
const singleStatProperties = {
...properties,
colors: properties.colors.filter(c => c.type !== 'scale'),
type: ViewType.SingleStat,
} as SingleStatView
return (
<VisDataTransform>
{({table, fluxGroupKeyUnion, xColumn, yColumn}) => (
<XYContainer
table={table}
fluxGroupKeyUnion={fluxGroupKeyUnion}
loading={loading}
viewProperties={{
...xyProperties,
xColumn,
yColumn,
}}
>
{config => (
<Plot config={config}>
<LatestValueTransform table={table} quiet={true}>
{latestValue => (
<SingleStat
stat={latestValue}
properties={singleStatProperties}
/>
)}
</LatestValueTransform>
</Plot>
)}
</XYContainer>
)}
</VisDataTransform>
)
}
return (
<RefreshingViewSwitcher
tables={tables}

View File

@ -2,18 +2,12 @@
import React, {FunctionComponent, ChangeEvent} from 'react'
import {connect} from 'react-redux'
import {VIRIDIS, MAGMA, INFERNO, PLASMA} from '@influxdata/vis'
import {
Dropdown,
Form,
Grid,
Input,
Columns,
InputType,
} from '@influxdata/clockface'
import {Form, Grid, Input, Columns, InputType} from '@influxdata/clockface'
// Components
import AutoDomainInput from 'src/shared/components/AutoDomainInput'
import HexColorSchemeDropdown from 'src/shared/components/HexColorSchemeDropdown'
import ColumnSelector from 'src/shared/components/ColumnSelector'
// Actions
import {
@ -37,7 +31,6 @@ import {
} from 'src/timeMachine/selectors'
// Types
import {ComponentStatus} from '@influxdata/clockface'
import {AppState} from 'src/types'
const HEATMAP_COLOR_SCHEMES = [
@ -82,10 +75,6 @@ interface OwnProps {
type Props = StateProps & DispatchProps & OwnProps
const HeatmapOptions: FunctionComponent<Props> = props => {
const dataDropdownStatus = props.numericColumns.length
? ComponentStatus.Default
: ComponentStatus.Disabled
const onSetBinSize = (e: ChangeEvent<HTMLInputElement>) => {
const val = +e.target.value
@ -100,34 +89,19 @@ const HeatmapOptions: FunctionComponent<Props> = props => {
<Grid.Column>
<h4 className="view-options--header">Customize Heatmap</h4>
<h5 className="view-options--header">Data</h5>
<Form.Element label="X Column">
<Dropdown
selectedID={props.xColumn}
onChange={props.onSetXColumn}
status={dataDropdownStatus}
titleText="None"
>
{props.numericColumns.map(columnName => (
<Dropdown.Item id={columnName} key={columnName} value={columnName}>
{columnName}
</Dropdown.Item>
))}
</Dropdown>
</Form.Element>
<Form.Element label="Y Column">
<Dropdown
selectedID={props.yColumn}
onChange={props.onSetYColumn}
status={dataDropdownStatus}
titleText="None"
>
{props.numericColumns.map(columnName => (
<Dropdown.Item id={columnName} key={columnName} value={columnName}>
{columnName}
</Dropdown.Item>
))}
</Dropdown>
</Form.Element>
<ColumnSelector
selectedColumn={props.xColumn}
onSelectColumn={props.onSetXColumn}
availableColumns={props.numericColumns}
axisName="x"
/>
<ColumnSelector
selectedColumn={props.yColumn}
onSelectColumn={props.onSetYColumn}
availableColumns={props.numericColumns}
axisName="y"
/>
<h5 className="view-options--header">Options</h5>
<Form.Element label="Color Scheme">
<HexColorSchemeDropdown

View File

@ -32,11 +32,12 @@ import {ComponentStatus} from '@influxdata/clockface'
import {HistogramPosition} from '@influxdata/vis'
import {Color} from 'src/types/colors'
import {AppState} from 'src/types'
import ColumnSelector from 'src/shared/components/ColumnSelector'
interface StateProps {
xColumn: string
fillColumns: string[]
availableXColumns: string[]
numericColumns: string[]
availableGroupColumns: string[]
}
@ -64,7 +65,7 @@ const HistogramOptions: SFC<Props> = props => {
const {
xColumn,
fillColumns,
availableXColumns,
numericColumns,
availableGroupColumns,
position,
binCount,
@ -80,10 +81,6 @@ const HistogramOptions: SFC<Props> = props => {
onSetXAxisLabel,
} = props
const xDropdownStatus = availableXColumns.length
? ComponentStatus.Default
: ComponentStatus.Disabled
const groupDropdownStatus = availableGroupColumns.length
? ComponentStatus.Default
: ComponentStatus.Disabled
@ -92,20 +89,12 @@ const HistogramOptions: SFC<Props> = props => {
<Grid.Column>
<h4 className="view-options--header">Customize Histogram</h4>
<h5 className="view-options--header">Data</h5>
<Form.Element label="Column">
<Dropdown
selectedID={xColumn}
onChange={onSetXColumn}
status={xDropdownStatus}
titleText="None"
>
{availableXColumns.map(columnName => (
<Dropdown.Item id={columnName} key={columnName} value={columnName}>
{columnName}
</Dropdown.Item>
))}
</Dropdown>
</Form.Element>
<ColumnSelector
selectedColumn={xColumn}
onSelectColumn={onSetXColumn}
availableColumns={numericColumns}
axisName="x"
/>
<Form.Element label="Group By">
<MultiSelectDropdown
selectedIDs={fillColumns}
@ -163,12 +152,12 @@ const HistogramOptions: SFC<Props> = props => {
}
const mstp = (state: AppState) => {
const availableXColumns = getNumericColumns(state)
const numericColumns = getNumericColumns(state)
const availableGroupColumns = getGroupableColumns(state)
const xColumn = getXColumnSelection(state)
const fillColumns = getFillColumnsSelection(state)
return {availableXColumns, availableGroupColumns, xColumn, fillColumns}
return {numericColumns, availableGroupColumns, xColumn, fillColumns}
}
const mdtp = {

View File

@ -10,6 +10,7 @@ import AxisAffixes from 'src/timeMachine/components/view_options/AxisAffixes'
import ColorSelector from 'src/timeMachine/components/view_options/ColorSelector'
import AutoDomainInput from 'src/shared/components/AutoDomainInput'
import YAxisBase from 'src/timeMachine/components/view_options/YAxisBase'
import ColumnSelector from 'src/shared/components/ColumnSelector'
// Actions
import {
@ -20,15 +21,24 @@ import {
setYAxisBounds,
setYAxisBase,
setGeom,
setXColumn,
setYColumn,
} from 'src/timeMachine/actions'
// Utils
import {parseBounds} from 'src/shared/utils/vis'
import {
getXColumnSelection,
getYColumnSelection,
getNumericColumns,
} from 'src/timeMachine/selectors'
// Types
import {ViewType} from 'src/types'
import {Axes, XYViewGeom} from 'src/types/dashboards'
import {Color} from 'src/types/colors'
import {AppState} from 'src/types'
import CloudExclude from 'src/shared/components/cloud/CloudExclude'
interface OwnProps {
type: ViewType
@ -37,6 +47,12 @@ interface OwnProps {
colors: Color[]
}
interface StateProps {
xColumn: string
yColumn: string
numericColumns: string[]
}
interface DispatchProps {
onUpdateYAxisLabel: typeof setYAxisLabel
onUpdateAxisPrefix: typeof setAxisPrefix
@ -44,10 +60,12 @@ interface DispatchProps {
onUpdateYAxisBounds: typeof setYAxisBounds
onUpdateYAxisBase: typeof setYAxisBase
onUpdateColors: typeof setColors
onSetXColumn: typeof setXColumn
onSetYColumn: typeof setYColumn
onSetGeom: typeof setGeom
}
type Props = OwnProps & DispatchProps
type Props = OwnProps & DispatchProps & StateProps
class LineOptions extends PureComponent<Props> {
public render() {
@ -63,12 +81,32 @@ class LineOptions extends PureComponent<Props> {
onUpdateAxisSuffix,
onUpdateYAxisBase,
onSetGeom,
onSetYColumn,
yColumn,
onSetXColumn,
xColumn,
numericColumns,
} = this.props
return (
<>
<Grid.Column>
<h4 className="view-options--header">Customize Line Graph</h4>
<CloudExclude>
<h5 className="view-options--header">Data</h5>
<ColumnSelector
selectedColumn={xColumn}
onSelectColumn={onSetXColumn}
availableColumns={numericColumns}
axisName="x"
/>
<ColumnSelector
selectedColumn={yColumn}
onSelectColumn={onSetYColumn}
availableColumns={numericColumns}
axisName="y"
/>
</CloudExclude>
<h5 className="view-options--header">Options</h5>
</Grid.Column>
{geom && <Geom geom={geom} onSetGeom={onSetGeom} />}
@ -116,17 +154,27 @@ class LineOptions extends PureComponent<Props> {
}
}
const mstp = (state: AppState) => {
const xColumn = getXColumnSelection(state)
const yColumn = getYColumnSelection(state)
const numericColumns = getNumericColumns(state)
return {xColumn, yColumn, numericColumns}
}
const mdtp: DispatchProps = {
onUpdateYAxisLabel: setYAxisLabel,
onUpdateAxisPrefix: setAxisPrefix,
onUpdateAxisSuffix: setAxisSuffix,
onUpdateYAxisBounds: setYAxisBounds,
onUpdateYAxisBase: setYAxisBase,
onSetXColumn: setXColumn,
onSetYColumn: setYColumn,
onUpdateColors: setColors,
onSetGeom: setGeom,
}
export default connect<{}, DispatchProps, OwnProps>(
null,
export default connect<StateProps, DispatchProps, OwnProps>(
mstp,
mdtp
)(LineOptions)

View File

@ -26,6 +26,8 @@ import {
setAxisSuffix,
setColorHexes,
setYDomain,
setXColumn,
setYColumn,
} from 'src/timeMachine/actions'
// Utils
@ -33,6 +35,9 @@ import {
getGroupableColumns,
getFillColumnsSelection,
getSymbolColumnsSelection,
getXColumnSelection,
getYColumnSelection,
getNumericColumns,
} from 'src/timeMachine/selectors'
// Types
@ -40,6 +45,8 @@ import {ComponentStatus} from '@influxdata/clockface'
import {AppState} from 'src/types'
import HexColorSchemeDropdown from 'src/shared/components/HexColorSchemeDropdown'
import AutoDomainInput from 'src/shared/components/AutoDomainInput'
import ColumnSelector from 'src/shared/components/ColumnSelector'
import CloudExclude from 'src/shared/components/cloud/CloudExclude'
const COLOR_SCHEMES = [
{name: 'Nineteen Eighty Four', colors: NINETEEN_EIGHTY_FOUR},
@ -55,6 +62,9 @@ interface StateProps {
fillColumns: string[]
symbolColumns: string[]
availableGroupColumns: string[]
xColumn: string
yColumn: string
numericColumns: string[]
}
interface DispatchProps {
@ -66,6 +76,8 @@ interface DispatchProps {
onUpdateAxisSuffix: typeof setAxisSuffix
onUpdateAxisPrefix: typeof setAxisPrefix
onSetYDomain: typeof setYDomain
onSetXColumn: typeof setXColumn
onSetYColumn: typeof setYColumn
}
interface OwnProps {
@ -106,6 +118,11 @@ const ScatterOptions: SFC<Props> = props => {
onUpdateAxisPrefix,
yDomain,
onSetYDomain,
xColumn,
yColumn,
numericColumns,
onSetXColumn,
onSetYColumn,
} = props
const groupDropdownStatus = availableGroupColumns.length
@ -151,6 +168,20 @@ const ScatterOptions: SFC<Props> = props => {
))}
</MultiSelectDropdown>
</Form.Element>
<CloudExclude>
<ColumnSelector
selectedColumn={xColumn}
onSelectColumn={onSetXColumn}
availableColumns={numericColumns}
axisName="x"
/>
<ColumnSelector
selectedColumn={yColumn}
onSelectColumn={onSetYColumn}
availableColumns={numericColumns}
axisName="y"
/>
</CloudExclude>
<h5 className="view-options--header">Options</h5>
<Form.Element label="Color Scheme">
<HexColorSchemeDropdown
@ -195,8 +226,18 @@ const mstp = (state: AppState): StateProps => {
const availableGroupColumns = getGroupableColumns(state)
const fillColumns = getFillColumnsSelection(state)
const symbolColumns = getSymbolColumnsSelection(state)
const xColumn = getXColumnSelection(state)
const yColumn = getYColumnSelection(state)
const numericColumns = getNumericColumns(state)
return {availableGroupColumns, fillColumns, symbolColumns}
return {
availableGroupColumns,
fillColumns,
symbolColumns,
xColumn,
yColumn,
numericColumns,
}
}
const mdtp = {
@ -208,6 +249,8 @@ const mdtp = {
onUpdateAxisPrefix: setAxisPrefix,
onUpdateAxisSuffix: setAxisSuffix,
onSetYDomain: setYDomain,
onSetXColumn: setXColumn,
onSetYColumn: setYColumn,
}
export default connect<StateProps, DispatchProps, OwnProps>(

View File

@ -5,6 +5,7 @@ import {fromFlux, Table} from '@influxdata/vis'
// Utils
import {parseResponse} from 'src/shared/parsing/flux/response'
import {chooseYColumn, chooseXColumn} from 'src/shared/utils/vis'
// Types
import {
@ -37,11 +38,13 @@ export const getTables = (state: AppState): FluxTable[] =>
const getVisTableMemoized = memoizeOne(fromFlux)
export const getVisTable = (state: AppState): Table => {
export const getVisTable = (
state: AppState
): {table: Table; fluxGroupKeyUnion: string[]} => {
const files = getActiveTimeMachine(state).queryResults.files || []
const {table} = getVisTableMemoized(files.join('\n\n'))
const {table, fluxGroupKeyUnion} = getVisTableMemoized(files.join('\n\n'))
return table
return {table, fluxGroupKeyUnion}
}
const getNumericColumnsMemoized = memoizeOne(
@ -63,14 +66,14 @@ const getNumericColumnsMemoized = memoizeOne(
)
export const getNumericColumns = (state: AppState): string[] => {
const table = getVisTable(state)
const {table} = getVisTable(state)
return getNumericColumnsMemoized(table)
}
const getGroupableColumnsMemoized = memoizeOne(
(table: Table): string[] => {
const invalidGroupColumns = new Set(['_value', '_start', '_stop', '_time'])
const invalidGroupColumns = new Set(['_value', '_time', 'table'])
const groupableColumns = table.columnKeys.filter(
name => !invalidGroupColumns.has(name)
)
@ -80,25 +83,21 @@ const getGroupableColumnsMemoized = memoizeOne(
)
export const getGroupableColumns = (state: AppState): string[] => {
const table = getVisTable(state)
const {table} = getVisTable(state)
return getGroupableColumnsMemoized(table)
}
const selectXYColumn = (validColumns: string[], preference: string): string => {
const selectXYColumn = (
validColumns: string[],
preference: string,
defaultSelection: string
): string => {
if (preference && validColumns.includes(preference)) {
return preference
}
if (validColumns.includes('_value')) {
return '_value'
}
if (validColumns.length) {
return validColumns[0]
}
return null
return defaultSelection
}
const getXColumnSelectionMemoized = memoizeOne(selectXYColumn)
@ -107,59 +106,83 @@ const getYColumnSelectionMemoized = memoizeOne(selectXYColumn)
export const getXColumnSelection = (state: AppState): string => {
const validXColumns = getNumericColumns(state)
const preference = get(getActiveTimeMachine(state), 'view.properties.xColumn')
return getXColumnSelectionMemoized(validXColumns, preference)
const {table} = getVisTable(state)
const defaultSelection = chooseXColumn(table)
return getXColumnSelectionMemoized(
validXColumns,
preference,
defaultSelection
)
}
export const getYColumnSelection = (state: AppState): string => {
const validYColumns = getNumericColumns(state)
const preference = get(getActiveTimeMachine(state), 'view.properties.yColumn')
return getYColumnSelectionMemoized(validYColumns, preference)
const {table} = getVisTable(state)
const defaultSelection = chooseYColumn(table)
return getYColumnSelectionMemoized(
validYColumns,
preference,
defaultSelection
)
}
const getFillColumnsSelectionMemoized = memoizeOne(
(validFillColumns: string[], preference: string[]): string[] => {
if (preference && preference.every(col => validFillColumns.includes(col))) {
return preference
}
return []
const getGroupableColumnSelection = (
validColumns: string[],
preference: string[],
fluxGroupKeyUnion: string[]
): string[] => {
if (preference && preference.every(col => validColumns.includes(col))) {
return preference
}
return fluxGroupKeyUnion
}
const getFillColumnsSelectionMemoized = memoizeOne(getGroupableColumnSelection)
const getSymbolColumnsSelectionMemoized = memoizeOne(
getGroupableColumnSelection
)
export const getFillColumnsSelection = (state: AppState): string[] => {
const validFillColumns = getGroupableColumns(state)
const preference = get(
getActiveTimeMachine(state),
'view.properties.fillColumns'
)
return getFillColumnsSelectionMemoized(validFillColumns, preference)
const {fluxGroupKeyUnion} = getVisTable(state)
return getFillColumnsSelectionMemoized(
validFillColumns,
preference,
fluxGroupKeyUnion
)
}
const getSymbolColumnsSelectionMemoized = memoizeOne(
(validSymbolColumns: string[], preference: string[]): string[] => {
if (
preference &&
preference.every(col => validSymbolColumns.includes(col))
) {
return preference
}
return []
}
)
export const getSymbolColumnsSelection = (state: AppState): string[] => {
const validSymbolColumns = getGroupableColumns(state)
const preference = get(
getActiveTimeMachine(state),
'view.properties.symbolColumns'
)
const {fluxGroupKeyUnion} = getVisTable(state)
return getSymbolColumnsSelectionMemoized(validSymbolColumns, preference)
return getSymbolColumnsSelectionMemoized(
validSymbolColumns,
preference,
fluxGroupKeyUnion
)
}
export const getSaveableView = (state: AppState): QueryView & {id?: string} => {
@ -195,5 +218,40 @@ export const getSaveableView = (state: AppState): QueryView & {id?: string} => {
}
}
if (saveableView.properties.type === ViewType.Scatter) {
saveableView = {
...saveableView,
properties: {
...saveableView.properties,
xColumn: getXColumnSelection(state),
yColumn: getYColumnSelection(state),
fillColumns: getFillColumnsSelection(state),
symbolColumns: getSymbolColumnsSelection(state),
},
}
}
if (saveableView.properties.type === ViewType.XY) {
saveableView = {
...saveableView,
properties: {
...saveableView.properties,
xColumn: getXColumnSelection(state),
yColumn: getYColumnSelection(state),
},
}
}
if (saveableView.properties.type === ViewType.LinePlusSingleStat) {
saveableView = {
...saveableView,
properties: {
...saveableView.properties,
xColumn: getXColumnSelection(state),
yColumn: getYColumnSelection(state),
},
}
}
return saveableView
}

View File

@ -173,6 +173,8 @@ export interface XYView {
queries: DashboardQuery[]
shape: ViewShape.ChronografV2
axes: Axes
xColumn: string
yColumn: string
colors: Color[]
legend: Legend
note: string
@ -188,6 +190,8 @@ export interface LinePlusSingleStatView {
legend: Legend
prefix: string
suffix: string
xColumn: string
yColumn: string
decimalPlaces: DecimalPlaces
note: string
showNoteWhenEmpty: boolean