Merge pull request #3010 from influxdata/feature/table-graph-time-axis

Feature/table graph time axis
pull/10616/head
Deniz Kusefoglu 2018-03-19 15:03:04 -07:00 committed by GitHub
commit d179d6f723
8 changed files with 99 additions and 58 deletions

View File

@ -1,29 +1,30 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
const VERTICAL = 'VERTICAL' const GraphOptionsTimeAxis = ({verticalTimeAxis, onToggleVerticalTimeAxis}) =>
const HORIZONTAL = 'HORIZONTAL'
const GraphOptionsTimeAxis = ({TimeAxis, onToggleTimeAxis}) =>
<div className="form-group col-xs-12 col-sm-6"> <div className="form-group col-xs-12 col-sm-6">
<label>Time Axis</label> <label>Time Axis</label>
<ul className="nav nav-tablist nav-tablist-sm"> <ul className="nav nav-tablist nav-tablist-sm">
<li <li
className={`${TimeAxis === VERTICAL ? 'active' : ''}`} className={verticalTimeAxis ? 'active' : ''}
onClick={onToggleTimeAxis} onClick={onToggleVerticalTimeAxis(true)}
> >
Vertical Vertical
</li> </li>
<li <li
className={`${TimeAxis === HORIZONTAL ? 'active' : ''}`} className={verticalTimeAxis ? '' : 'active'}
onClick={onToggleTimeAxis} onClick={onToggleVerticalTimeAxis(false)}
> >
Horizontal Horizontal
</li> </li>
</ul> </ul>
</div> </div>
const {func, string} = PropTypes const {bool, func} = PropTypes
GraphOptionsTimeAxis.propTypes = {TimeAxis: string, onToggleTimeAxis: func} GraphOptionsTimeAxis.propTypes = {
verticalTimeAxis: bool,
onToggleVerticalTimeAxis: func,
}
export default GraphOptionsTimeAxis export default GraphOptionsTimeAxis

View File

@ -10,6 +10,7 @@ import GraphOptionsTimeAxis from 'src/dashboards/components/GraphOptionsTimeAxis
import GraphOptionsSortBy from 'src/dashboards/components/GraphOptionsSortBy' import GraphOptionsSortBy from 'src/dashboards/components/GraphOptionsSortBy'
import GraphOptionsTextWrapping from 'src/dashboards/components/GraphOptionsTextWrapping' import GraphOptionsTextWrapping from 'src/dashboards/components/GraphOptionsTextWrapping'
import GraphOptionsCustomizeColumns from 'src/dashboards/components/GraphOptionsCustomizeColumns' import GraphOptionsCustomizeColumns from 'src/dashboards/components/GraphOptionsCustomizeColumns'
import ThresholdsList from 'src/shared/components/ThresholdsList' import ThresholdsList from 'src/shared/components/ThresholdsList'
import ThresholdsListTypeToggle from 'src/shared/components/ThresholdsListTypeToggle' import ThresholdsListTypeToggle from 'src/shared/components/ThresholdsListTypeToggle'
@ -51,12 +52,14 @@ export class TableOptions extends PureComponent<Props, {}> {
get columnNames() { get columnNames() {
const {tableOptions: {columnNames}} = this.props const {tableOptions: {columnNames}} = this.props
return columnNames || [] return columnNames || []
} }
get timeColumn() { get timeColumn() {
return (this.columnNames.find(c => c.internalName === 'time')) || TIME_COLUMN_DEFAULT return (
this.columnNames.find(c => c.internalName === 'time') ||
TIME_COLUMN_DEFAULT
)
} }
get computedColumnNames() { get computedColumnNames() {
@ -71,24 +74,20 @@ export class TableOptions extends PureComponent<Props, {}> {
) )
return existing || {internalName, displayName: ''} return existing || {internalName, displayName: ''}
}) })
})) })
)
return [this.timeColumn, ...queryFields] return [this.timeColumn, ...queryFields]
} }
componentWillMount() { componentWillMount() {
const {handleUpdateTableOptions, tableOptions} = this.props const {handleUpdateTableOptions, tableOptions} = this.props
handleUpdateTableOptions({...tableOptions, columnNames: this.computedColumnNames}) handleUpdateTableOptions({
...tableOptions,
columnNames: this.computedColumnNames
})
} }
handleToggleSingleStatType = () => {}
handleAddThreshold = () => {}
handleDeleteThreshold = () => () => {}
handleChooseColor = () => () => {}
handleChooseSortBy = option => { handleChooseSortBy = option => {
const {tableOptions, handleUpdateTableOptions} = this.props const {tableOptions, handleUpdateTableOptions} = this.props
const sortBy = {displayName: option.text, internalName: option.key} const sortBy = {displayName: option.text, internalName: option.key}
@ -101,7 +100,10 @@ export class TableOptions extends PureComponent<Props, {}> {
handleUpdateTableOptions({...tableOptions, timeFormat}) handleUpdateTableOptions({...tableOptions, timeFormat})
} }
handleToggleTimeAxis = () => {} onToggleVerticalTimeAxis = verticalTimeAxis => () => {
const {tableOptions, handleUpdateTableOptions} = this.props
handleUpdateTableOptions({...tableOptions, verticalTimeAxis})
}
handleToggleTextWrapping = () => {} handleToggleTextWrapping = () => {}
@ -116,16 +118,14 @@ export class TableOptions extends PureComponent<Props, {}> {
render() { render() {
const { const {
tableOptions: {timeFormat, columnNames: columns}, tableOptions: {timeFormat, columnNames: columns, verticalTimeAxis},
onResetFocus, onResetFocus,
tableOptions, tableOptions
} = this.props } = this.props
const TimeAxis = 'vertical'
const tableSortByOptions = this.computedColumnNames.map(col => ({ const tableSortByOptions = this.computedColumnNames.map(col => ({
text: col.displayName || col.internalName, text: col.displayName || col.internalName,
key: col.internalName, key: col.internalName
})) }))
return ( return (
@ -141,8 +141,8 @@ export class TableOptions extends PureComponent<Props, {}> {
onTimeFormatChange={this.handleTimeFormatChange} onTimeFormatChange={this.handleTimeFormatChange}
/> />
<GraphOptionsTimeAxis <GraphOptionsTimeAxis
TimeAxis={TimeAxis} verticalTimeAxis={verticalTimeAxis}
onToggleTimeAxis={this.handleToggleTimeAxis} onToggleVerticalTimeAxis={this.onToggleVerticalTimeAxis}
/> />
<GraphOptionsSortBy <GraphOptionsSortBy
selected={tableOptions.sortBy || TIME_COLUMN_DEFAULT} selected={tableOptions.sortBy || TIME_COLUMN_DEFAULT}
@ -169,11 +169,11 @@ export class TableOptions extends PureComponent<Props, {}> {
} }
const mapStateToProps = ({cellEditorOverlay: {cell: {tableOptions}}}) => ({ const mapStateToProps = ({cellEditorOverlay: {cell: {tableOptions}}}) => ({
tableOptions, tableOptions
}) })
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
handleUpdateTableOptions: bindActionCreators(updateTableOptions, dispatch), handleUpdateTableOptions: bindActionCreators(updateTableOptions, dispatch)
}) })
export default connect(mapStateToProps, mapDispatchToProps)(TableOptions) export default connect(mapStateToProps, mapDispatchToProps)(TableOptions)

View File

@ -1,3 +1,5 @@
import {DEFAULT_TABLE_OPTIONS} from 'src/shared/constants/tableGraph'
export const EMPTY_DASHBOARD = { export const EMPTY_DASHBOARD = {
id: 0, id: 0,
name: '', name: '',
@ -20,6 +22,7 @@ export const NEW_DEFAULT_DASHBOARD_CELL = {
name: 'Untitled Cell', name: 'Untitled Cell',
type: 'line', type: 'line',
queries: [], queries: [],
tableOptions: DEFAULT_TABLE_OPTIONS,
} }
export const NEW_DASHBOARD = { export const NEW_DASHBOARD = {

View File

@ -1,3 +1,5 @@
import _ from 'lodash'
import { import {
THRESHOLD_TYPE_TEXT, THRESHOLD_TYPE_TEXT,
DEFAULT_THRESHOLDS_LIST_COLORS, DEFAULT_THRESHOLDS_LIST_COLORS,
@ -28,7 +30,11 @@ export default function cellEditorOverlay(state = initialState, action) {
) )
const gaugeColors = validateGaugeColors(colors) const gaugeColors = validateGaugeColors(colors)
const tableOptions = cell.tableOptions || initializeOptions('table') const tableOptions = _.get(
cell,
'tableOptions',
initializeOptions('table')
)
return { return {
...state, ...state,

View File

@ -22,6 +22,7 @@ class TableGraph extends Component {
super(props) super(props)
this.state = { this.state = {
data: [[]], data: [[]],
unzippedData: [[]],
hoveredColumnIndex: NULL_COLUMN_INDEX, hoveredColumnIndex: NULL_COLUMN_INDEX,
hoveredRowIndex: NULL_ROW_INDEX, hoveredRowIndex: NULL_ROW_INDEX,
sortByColumnIndex: -1, sortByColumnIndex: -1,
@ -29,32 +30,45 @@ class TableGraph extends Component {
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
const {data} = timeSeriesToTableGraph(nextProps.data) const {data, unzippedData} = timeSeriesToTableGraph(nextProps.data)
const {tableOptions: {sortBy: {internalName}}} = nextProps const {tableOptions: {sortBy: {internalName}}} = nextProps
const sortByColumnIndex = _.indexOf(data[0], internalName) const sortByColumnIndex = _.indexOf(data[0], internalName)
const sortedData = _.sortBy(_.drop(data, 1), sortByColumnIndex) const sortedData = _.sortBy(_.drop(data, 1), sortByColumnIndex)
this.setState({data: [data[0], ...sortedData], sortByColumnIndex}) this.setState({
data: [data[0], ...sortedData],
unzippedData,
sortByColumnIndex,
})
} }
calcHoverTimeRow = (data, hoverTime) => calcHoverTimeIndex = (data, hoverTime, verticalTimeAxis) => {
!isEmpty(data) && hoverTime !== NULL_HOVER_TIME if (isEmpty(data) || hoverTime === NULL_HOVER_TIME) {
? data.findIndex( return undefined
}
if (verticalTimeAxis) {
return data.findIndex(
row => row[0] && _.isNumber(row[0]) && row[0] >= hoverTime row => row[0] && _.isNumber(row[0]) && row[0] >= hoverTime
) )
: undefined }
return data[0].findIndex(d => _.isNumber(d) && d >= hoverTime)
}
handleHover = (columnIndex, rowIndex) => () => { handleHover = (columnIndex, rowIndex) => () => {
if (this.props.onSetHoverTime) { const {onSetHoverTime, tableOptions} = this.props
const {data} = this.state const {data} = this.state
this.props.onSetHoverTime(data[rowIndex][0].toString()) if (onSetHoverTime) {
const hoverTime = tableOptions.verticalTimeAxis
? data[rowIndex][0]
: data[0][columnIndex]
onSetHoverTime(hoverTime.toString())
}
this.setState({ this.setState({
hoveredColumnIndex: columnIndex, hoveredColumnIndex: columnIndex,
hoveredRowIndex: rowIndex, hoveredRowIndex: rowIndex,
}) })
} }
}
handleMouseOut = () => { handleMouseOut = () => {
if (this.props.onSetHoverTime) { if (this.props.onSetHoverTime) {
@ -66,10 +80,12 @@ class TableGraph extends Component {
} }
} }
cellRenderer = ({columnIndex, rowIndex, key, style, parent}) => { cellRenderer = ({columnIndex, rowIndex, key, parent, style}) => {
const {hoveredColumnIndex, hoveredRowIndex, data} = this.state const data = _.get(this.props, ['tableOptions', 'verticalTimeAxis'], true)
? this.state.data
: this.state.unzippedData
const {hoveredColumnIndex, hoveredRowIndex} = this.state
const {colors} = this.props const {colors} = this.props
const columnCount = _.get(data, ['0', 'length'], 0) const columnCount = _.get(data, ['0', 'length'], 0)
const rowCount = data.length const rowCount = data.length
const {tableOptions} = this.props const {tableOptions} = this.props
@ -80,12 +96,15 @@ class TableGraph extends Component {
const isFixedRow = rowIndex === 0 && columnIndex > 0 const isFixedRow = rowIndex === 0 && columnIndex > 0
const isFixedColumn = rowIndex > 0 && columnIndex === 0 const isFixedColumn = rowIndex > 0 && columnIndex === 0
const isTimeData = isFixedColumn const isTimeData = tableOptions.verticalTimeAxis
? isFixedColumn
: isFixedRow
const isFixedCorner = rowIndex === 0 && columnIndex === 0 const isFixedCorner = rowIndex === 0 && columnIndex === 0
const isLastRow = rowIndex === rowCount - 1 const isLastRow = rowIndex === rowCount - 1
const isLastColumn = columnIndex === columnCount - 1 const isLastColumn = columnIndex === columnCount - 1
const isHighlighted = const isHighlighted =
rowIndex === parent.props.scrollToRow || rowIndex === parent.props.scrollToRow ||
columnIndex === parent.props.scrollToColumn ||
(rowIndex === hoveredRowIndex && hoveredRowIndex !== 0) || (rowIndex === hoveredRowIndex && hoveredRowIndex !== 0) ||
(columnIndex === hoveredColumnIndex && hoveredColumnIndex !== 0) (columnIndex === hoveredColumnIndex && hoveredColumnIndex !== 0)
const dataIsNumerical = _.isNumber(data[rowIndex][columnIndex]) const dataIsNumerical = _.isNumber(data[rowIndex][columnIndex])
@ -139,16 +158,20 @@ class TableGraph extends Component {
render() { render() {
const {sortByColumnIndex, hoveredColumnIndex, hoveredRowIndex} = this.state const {sortByColumnIndex, hoveredColumnIndex, hoveredRowIndex} = this.state
const {hoverTime, tableOptions, colors} = this.props const {hoverTime, tableOptions, colors} = this.props
const {data} = this.state
const verticalTimeAxis = _.get(tableOptions, 'verticalTimeAxis', true)
const data = verticalTimeAxis ? this.state.data : this.state.unzippedData
const columnCount = _.get(data, ['0', 'length'], 0) const columnCount = _.get(data, ['0', 'length'], 0)
const rowCount = data.length const rowCount = data.length
const COLUMN_WIDTH = 300 const COLUMN_WIDTH = 300
const ROW_HEIGHT = 30 const ROW_HEIGHT = 30
const tableWidth = this.gridContainer ? this.gridContainer.clientWidth : 0 const tableWidth = this.gridContainer ? this.gridContainer.clientWidth : 0
const tableHeight = this.gridContainer ? this.gridContainer.clientHeight : 0 const tableHeight = this.gridContainer ? this.gridContainer.clientHeight : 0
const hoverTimeRow = const hoverTimeIndex =
hoveredRowIndex === NULL_ROW_INDEX hoveredRowIndex === NULL_ROW_INDEX
? this.calcHoverTimeRow(data, hoverTime) ? this.calcHoverTimeIndex(data, hoverTime, verticalTimeAxis)
: hoveredRowIndex : hoveredRowIndex
return ( return (
@ -175,8 +198,10 @@ class TableGraph extends Component {
columnNames={ columnNames={
tableOptions ? tableOptions.columnNames : [TIME_COLUMN_DEFAULT] tableOptions ? tableOptions.columnNames : [TIME_COLUMN_DEFAULT]
} }
scrollToRow={verticalTimeAxis ? hoverTimeIndex : undefined}
scrollToColumn={verticalTimeAxis ? undefined : hoverTimeIndex}
verticalTimeAxis={verticalTimeAxis}
sortByColumnIndex={sortByColumnIndex} sortByColumnIndex={sortByColumnIndex}
scrollToRow={hoverTimeRow}
cellRenderer={this.cellRenderer} cellRenderer={this.cellRenderer}
hoveredColumnIndex={hoveredColumnIndex} hoveredColumnIndex={hoveredColumnIndex}
hoveredRowIndex={hoveredRowIndex} hoveredRowIndex={hoveredRowIndex}

View File

@ -21,8 +21,8 @@ export const FORMAT_OPTIONS = [
] ]
export const DEFAULT_TABLE_OPTIONS = { export const DEFAULT_TABLE_OPTIONS = {
timeFormat: 'MM/DD/YYYY HH:mm:ss.ss',
verticalTimeAxis: true, verticalTimeAxis: true,
timeFormat: TIME_FORMAT_DEFAULT,
sortBy: TIME_COLUMN_DEFAULT, sortBy: TIME_COLUMN_DEFAULT,
wrapping: 'truncate', wrapping: 'truncate',
columnNames: [TIME_COLUMN_DEFAULT], columnNames: [TIME_COLUMN_DEFAULT],

View File

@ -187,3 +187,7 @@ export const validateGaugeColors = colors => {
return formattedColors return formattedColors
} }
export const stringifyColorValues = colors => {
return colors.map(color => ({...color, value: `${color.value}`}))
}

View File

@ -177,9 +177,11 @@ export const timeSeriesToTableGraph = raw => {
const labels = ['time', ...map(sortedLabels, ({label}) => label)] const labels = ['time', ...map(sortedLabels, ({label}) => label)]
const tableData = map(sortedTimeSeries, ({time, values}) => [time, ...values]) const tableData = map(sortedTimeSeries, ({time, values}) => [time, ...values])
const data = tableData.length ? [labels, ...tableData] : [[]]
const unzippedData = _.unzip(data)
return { return {
data: tableData.length ? [labels, ...tableData] : [[]], data,
unzippedData,
} }
} }