Merge pull request #3010 from influxdata/feature/table-graph-time-axis
Feature/table graph time axispull/10616/head
commit
d179d6f723
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -187,3 +187,7 @@ export const validateGaugeColors = colors => {
|
||||||
|
|
||||||
return formattedColors
|
return formattedColors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const stringifyColorValues = colors => {
|
||||||
|
return colors.map(color => ({...color, value: `${color.value}`}))
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue