Add precision to gauge and fix single stat bugs
parent
b5a07a5854
commit
44a24e4634
|
@ -1,31 +1,124 @@
|
|||
import React, {Component} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React, {PureComponent} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import {bindActionCreators} from 'redux'
|
||||
|
||||
import _ from 'lodash'
|
||||
import uuid from 'uuid'
|
||||
|
||||
import FancyScrollbar from 'shared/components/FancyScrollbar'
|
||||
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
|
||||
import Threshold from 'src/dashboards/components/Threshold'
|
||||
import GraphOptionsDecimalPlaces from 'src/dashboards/components/GraphOptionsDecimalPlaces'
|
||||
|
||||
import {
|
||||
COLOR_TYPE_THRESHOLD,
|
||||
THRESHOLD_COLORS,
|
||||
MAX_THRESHOLDS,
|
||||
MIN_THRESHOLDS,
|
||||
} from 'shared/constants/thresholds'
|
||||
} from 'src/shared/constants/thresholds'
|
||||
|
||||
import {
|
||||
changeDecimalPlaces,
|
||||
updateGaugeColors,
|
||||
updateAxes,
|
||||
} from 'src/dashboards/actions/cellEditorOverlay'
|
||||
import {colorsNumberSchema} from 'shared/schemas'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import {Axes} from 'src/types'
|
||||
import {DecimalPlaces} from 'src/types/dashboards'
|
||||
import {ColorNumber} from 'src/types/colors'
|
||||
|
||||
interface Props {
|
||||
axes: Axes
|
||||
gaugeColors: ColorNumber[]
|
||||
decimalPlaces: DecimalPlaces
|
||||
onResetFocus: () => void
|
||||
handleUpdateAxes: (a: Axes) => void
|
||||
onUpdateDecimalPlaces: (d: DecimalPlaces) => void
|
||||
handleUpdateGaugeColors: (d: ColorNumber[]) => void
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class GaugeOptions extends Component {
|
||||
handleAddThreshold = () => {
|
||||
class GaugeOptions extends PureComponent<Props> {
|
||||
public render() {
|
||||
const {gaugeColors, axes, decimalPlaces} = this.props
|
||||
const {y} = axes
|
||||
|
||||
return (
|
||||
<FancyScrollbar
|
||||
className="display-options--cell y-axis-controls"
|
||||
autoHide={false}
|
||||
>
|
||||
<div className="display-options--cell-wrapper">
|
||||
<h5 className="display-options--header">Gauge Controls</h5>
|
||||
<div className="thresholds-list">
|
||||
<button
|
||||
className="btn btn-sm btn-primary"
|
||||
onClick={this.handleAddThreshold}
|
||||
disabled={this.disableAddThreshold}
|
||||
>
|
||||
<span className="icon plus" /> Add Threshold
|
||||
</button>
|
||||
{this.sortedGaugeColors.map((color, index) => (
|
||||
<Threshold
|
||||
isMin={index === 0}
|
||||
isMax={index === gaugeColors.length - 1}
|
||||
visualizationType="gauge"
|
||||
threshold={color}
|
||||
key={uuid.v4()}
|
||||
disableMaxColor={this.disableMaxColor}
|
||||
onChooseColor={this.handleChooseColor}
|
||||
onValidateColorValue={this.handleValidateColorValue}
|
||||
onUpdateColorValue={this.handleUpdateColorValue}
|
||||
onDeleteThreshold={this.handleDeleteThreshold}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="graph-options-group form-group-wrapper">
|
||||
<div className="form-group col-xs-6">
|
||||
<label>Prefix</label>
|
||||
<input
|
||||
className="form-control input-sm"
|
||||
placeholder="%, MPH, etc."
|
||||
defaultValue={y.prefix}
|
||||
onChange={this.handleUpdatePrefix}
|
||||
maxLength={5}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group col-xs-6">
|
||||
<label>Suffix</label>
|
||||
<input
|
||||
className="form-control input-sm"
|
||||
placeholder="%, MPH, etc."
|
||||
defaultValue={y.suffix}
|
||||
onChange={this.handleUpdateSuffix}
|
||||
maxLength={5}
|
||||
/>
|
||||
</div>
|
||||
<GraphOptionsDecimalPlaces
|
||||
digits={decimalPlaces.digits}
|
||||
isEnforced={decimalPlaces.isEnforced}
|
||||
onDecimalPlacesChange={this.handleDecimalPlacesChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</FancyScrollbar>
|
||||
)
|
||||
}
|
||||
|
||||
private get disableMaxColor(): boolean {
|
||||
const {gaugeColors} = this.props
|
||||
return gaugeColors.length > MIN_THRESHOLDS
|
||||
}
|
||||
|
||||
private get disableAddThreshold(): boolean {
|
||||
const {gaugeColors} = this.props
|
||||
return gaugeColors.length > MAX_THRESHOLDS
|
||||
}
|
||||
|
||||
private handleDecimalPlacesChange = (decimalPlaces: DecimalPlaces) => {
|
||||
const {onUpdateDecimalPlaces} = this.props
|
||||
onUpdateDecimalPlaces(decimalPlaces)
|
||||
}
|
||||
|
||||
private handleAddThreshold = () => {
|
||||
const {gaugeColors, handleUpdateGaugeColors, onResetFocus} = this.props
|
||||
const sortedColors = _.sortBy(gaugeColors, color => color.value)
|
||||
|
||||
|
@ -50,7 +143,7 @@ class GaugeOptions extends Component {
|
|||
name: THRESHOLD_COLORS[randomColor].name,
|
||||
}
|
||||
|
||||
const updatedColors = _.sortBy(
|
||||
const updatedColors: ColorNumber[] = _.sortBy<ColorNumber>(
|
||||
[...gaugeColors, newThreshold],
|
||||
color => color.value
|
||||
)
|
||||
|
@ -61,7 +154,7 @@ class GaugeOptions extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleDeleteThreshold = threshold => {
|
||||
private handleDeleteThreshold = threshold => {
|
||||
const {handleUpdateGaugeColors, onResetFocus} = this.props
|
||||
const gaugeColors = this.props.gaugeColors.filter(
|
||||
color => color.id !== threshold.id
|
||||
|
@ -72,7 +165,7 @@ class GaugeOptions extends Component {
|
|||
onResetFocus()
|
||||
}
|
||||
|
||||
handleChooseColor = threshold => {
|
||||
private handleChooseColor = threshold => {
|
||||
const {handleUpdateGaugeColors} = this.props
|
||||
const gaugeColors = this.props.gaugeColors.map(
|
||||
color =>
|
||||
|
@ -84,7 +177,7 @@ class GaugeOptions extends Component {
|
|||
handleUpdateGaugeColors(gaugeColors)
|
||||
}
|
||||
|
||||
handleUpdateColorValue = (threshold, value) => {
|
||||
private handleUpdateColorValue = (threshold, value) => {
|
||||
const {handleUpdateGaugeColors} = this.props
|
||||
const gaugeColors = this.props.gaugeColors.map(
|
||||
color => (color.id === threshold.id ? {...color, value} : color)
|
||||
|
@ -93,7 +186,7 @@ class GaugeOptions extends Component {
|
|||
handleUpdateGaugeColors(gaugeColors)
|
||||
}
|
||||
|
||||
handleValidateColorValue = (threshold, targetValue) => {
|
||||
private handleValidateColorValue = (threshold, targetValue) => {
|
||||
const {gaugeColors} = this.props
|
||||
|
||||
const thresholdValue = threshold.value
|
||||
|
@ -134,14 +227,14 @@ class GaugeOptions extends Component {
|
|||
return allowedToUpdate
|
||||
}
|
||||
|
||||
handleUpdatePrefix = e => {
|
||||
private handleUpdatePrefix = e => {
|
||||
const {handleUpdateAxes, axes} = this.props
|
||||
const newAxes = {...axes, y: {...axes.y, prefix: e.target.value}}
|
||||
|
||||
handleUpdateAxes(newAxes)
|
||||
}
|
||||
|
||||
handleUpdateSuffix = e => {
|
||||
private handleUpdateSuffix = e => {
|
||||
const {handleUpdateAxes, axes} = this.props
|
||||
const newAxes = {...axes, y: {...axes.y, suffix: e.target.value}}
|
||||
|
||||
|
@ -154,98 +247,23 @@ class GaugeOptions extends Component {
|
|||
|
||||
return sortedColors
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
gaugeColors,
|
||||
axes: {
|
||||
y: {prefix, suffix},
|
||||
},
|
||||
} = this.props
|
||||
|
||||
const disableMaxColor = gaugeColors.length > MIN_THRESHOLDS
|
||||
const disableAddThreshold = gaugeColors.length > MAX_THRESHOLDS
|
||||
|
||||
return (
|
||||
<FancyScrollbar
|
||||
className="display-options--cell y-axis-controls"
|
||||
autoHide={false}
|
||||
>
|
||||
<div className="display-options--cell-wrapper">
|
||||
<h5 className="display-options--header">Gauge Controls</h5>
|
||||
<div className="thresholds-list">
|
||||
<button
|
||||
className="btn btn-sm btn-primary"
|
||||
onClick={this.handleAddThreshold}
|
||||
disabled={disableAddThreshold}
|
||||
>
|
||||
<span className="icon plus" /> Add Threshold
|
||||
</button>
|
||||
{this.sortedGaugeColors.map((color, index) => (
|
||||
<Threshold
|
||||
isMin={index === 0}
|
||||
isMax={index === gaugeColors.length - 1}
|
||||
visualizationType="gauge"
|
||||
threshold={color}
|
||||
key={uuid.v4()}
|
||||
disableMaxColor={disableMaxColor}
|
||||
onChooseColor={this.handleChooseColor}
|
||||
onValidateColorValue={this.handleValidateColorValue}
|
||||
onUpdateColorValue={this.handleUpdateColorValue}
|
||||
onDeleteThreshold={this.handleDeleteThreshold}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="graph-options-group form-group-wrapper">
|
||||
<div className="form-group col-xs-6">
|
||||
<label>Prefix</label>
|
||||
<input
|
||||
className="form-control input-sm"
|
||||
placeholder="%, MPH, etc."
|
||||
defaultValue={prefix}
|
||||
onChange={this.handleUpdatePrefix}
|
||||
maxLength="5"
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group col-xs-6">
|
||||
<label>Suffix</label>
|
||||
<input
|
||||
className="form-control input-sm"
|
||||
placeholder="%, MPH, etc."
|
||||
defaultValue={suffix}
|
||||
onChange={this.handleUpdateSuffix}
|
||||
maxLength="5"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FancyScrollbar>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {func, shape} = PropTypes
|
||||
|
||||
GaugeOptions.propTypes = {
|
||||
gaugeColors: colorsNumberSchema,
|
||||
handleUpdateGaugeColors: func.isRequired,
|
||||
handleUpdateAxes: func.isRequired,
|
||||
axes: shape({}).isRequired,
|
||||
onResetFocus: func.isRequired,
|
||||
}
|
||||
|
||||
const mapStateToProps = ({
|
||||
cellEditorOverlay: {
|
||||
gaugeColors,
|
||||
cell: {axes},
|
||||
cell: {axes, decimalPlaces},
|
||||
},
|
||||
}) => ({
|
||||
decimalPlaces,
|
||||
gaugeColors,
|
||||
axes,
|
||||
})
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
handleUpdateGaugeColors: bindActionCreators(updateGaugeColors, dispatch),
|
||||
handleUpdateAxes: bindActionCreators(updateAxes, dispatch),
|
||||
})
|
||||
const mapDispatchToProps = {
|
||||
handleUpdateGaugeColors: updateGaugeColors,
|
||||
handleUpdateAxes: updateAxes,
|
||||
onUpdateDecimalPlaces: changeDecimalPlaces,
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(GaugeOptions)
|
|
@ -283,6 +283,7 @@ class Gauge extends Component {
|
|||
drawGaugeValue = (ctx, radius, labelValueFontSize) => {
|
||||
const {gaugePosition, prefix, suffix} = this.props
|
||||
const {valueColor} = GAUGE_SPECS
|
||||
const maximumFractionDigits = 20
|
||||
|
||||
ctx.font = `${labelValueFontSize}px Roboto`
|
||||
ctx.fillStyle = valueColor
|
||||
|
@ -290,7 +291,9 @@ class Gauge extends Component {
|
|||
ctx.textAlign = 'center'
|
||||
|
||||
const textY = radius
|
||||
const textContent = `${prefix}${gaugePosition.toLocaleString()}${suffix}`
|
||||
const textContent = `${prefix}${gaugePosition.toLocaleString(undefined, {
|
||||
maximumFractionDigits,
|
||||
})}${suffix}`
|
||||
ctx.fillText(textContent, 0, textY)
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import {DEFAULT_GAUGE_COLORS} from 'src/shared/constants/thresholds'
|
|||
import {stringifyColorValues} from 'src/shared/constants/colorOperations'
|
||||
import {DASHBOARD_LAYOUT_ROW_HEIGHT} from 'src/shared/constants'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import {DecimalPlaces} from 'src/types/dashboards'
|
||||
|
||||
interface Color {
|
||||
type: string
|
||||
|
@ -19,6 +20,7 @@ interface Color {
|
|||
|
||||
interface Props {
|
||||
data: TimeSeriesResponse[]
|
||||
decimalPlaces: DecimalPlaces
|
||||
isFetchingInitially: boolean
|
||||
cellID: string
|
||||
cellHeight?: number
|
||||
|
@ -76,11 +78,19 @@ class GaugeChart extends PureComponent<Props> {
|
|||
}
|
||||
|
||||
private get lastValueForGauge(): number {
|
||||
const {data} = this.props
|
||||
const {data, decimalPlaces} = this.props
|
||||
const {lastValues} = getLastValues(data)
|
||||
const precision = 100.0
|
||||
let lastValue = _.get(lastValues, 0, 0)
|
||||
|
||||
return Math.round(_.get(lastValues, 0, 0) * precision) / precision
|
||||
if (lastValue === null) {
|
||||
return 0
|
||||
}
|
||||
|
||||
if (decimalPlaces.isEnforced) {
|
||||
lastValue = +lastValue.toFixed(decimalPlaces.digits)
|
||||
}
|
||||
|
||||
return lastValue
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -83,20 +83,21 @@ const RefreshingGraph = ({
|
|||
if (type === 'gauge') {
|
||||
return (
|
||||
<RefreshingGaugeChart
|
||||
source={source}
|
||||
type={type}
|
||||
source={source}
|
||||
cellID={cellID}
|
||||
prefix={prefix}
|
||||
suffix={suffix}
|
||||
inView={inView}
|
||||
colors={colors}
|
||||
key={manualRefresh}
|
||||
queries={[queries[0]]}
|
||||
templates={templates}
|
||||
autoRefresh={autoRefresh}
|
||||
cellHeight={cellHeight}
|
||||
decimalPlaces={decimalPlaces}
|
||||
resizerTopHeight={resizerTopHeight}
|
||||
editQueryStatus={editQueryStatus}
|
||||
cellID={cellID}
|
||||
prefix={prefix}
|
||||
suffix={suffix}
|
||||
inView={inView}
|
||||
onSetResolution={onSetResolution}
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -75,6 +75,11 @@ class SingleStat extends PureComponent<Props> {
|
|||
|
||||
private get roundedLastValue(): string {
|
||||
const {decimalPlaces} = this.props
|
||||
|
||||
if (this.lastValue === null) {
|
||||
return `${0}`
|
||||
}
|
||||
|
||||
let roundedValue = `${this.lastValue}`
|
||||
|
||||
if (decimalPlaces.isEnforced) {
|
||||
|
@ -85,7 +90,8 @@ class SingleStat extends PureComponent<Props> {
|
|||
}
|
||||
|
||||
private formatToLocale(n: number): string {
|
||||
return n.toLocaleString()
|
||||
const maximumFractionDigits = 20
|
||||
return n.toLocaleString(undefined, {maximumFractionDigits})
|
||||
}
|
||||
|
||||
private get containerStyle(): CSSProperties {
|
||||
|
|
|
@ -26,6 +26,10 @@ const defaultProps = {
|
|||
cellID: '',
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
decimalPlaces: {
|
||||
digits: 10,
|
||||
isEnforced: false,
|
||||
},
|
||||
}
|
||||
|
||||
const setup = (overrides = {}) => {
|
||||
|
|
Loading…
Reference in New Issue