Merge pull request #14534 from grafana/14409/threshold-ux-changes

14409/threshold ux changes
This commit is contained in:
Torkel Ödegaard
2018-12-18 11:30:48 +01:00
committed by GitHub
15 changed files with 270 additions and 382 deletions

View File

@@ -186,16 +186,18 @@ func getPanelSort(id string) int {
sort = 1
case "singlestat":
sort = 2
case "table":
case "gauge":
sort = 3
case "text":
case "table":
sort = 4
case "heatmap":
case "text":
sort = 5
case "alertlist":
case "heatmap":
sort = 6
case "dashlist":
case "alertlist":
sort = 7
case "dashlist":
sort = 8
}
return sort
}

View File

@@ -3,7 +3,7 @@ import Select from './Select';
import kbn from 'app/core/utils/kbn';
interface Props {
onChange: (item: any) => {} | void;
onChange: (item: any) => void;
defaultValue?: string;
width?: number;
}

View File

@@ -1,37 +1,46 @@
import React, { PureComponent } from 'react';
import { Switch } from 'app/core/components/Switch/Switch';
import { OptionModuleProps } from './module';
import { Label } from '../../../core/components/Label/Label';
export default class GaugeOptions extends PureComponent<OptionModuleProps> {
toggleThresholdLabels = () =>
onToggleThresholdLabels = () =>
this.props.onChange({ ...this.props.options, showThresholdLabels: !this.props.options.showThresholdLabels });
toggleThresholdMarkers = () =>
onToggleThresholdMarkers = () =>
this.props.onChange({ ...this.props.options, showThresholdMarkers: !this.props.options.showThresholdMarkers });
onMinValueChange = ({ target }) => this.props.onChange({ ...this.props.options, minValue: target.value });
onMaxValueChange = ({ target }) => this.props.onChange({ ...this.props.options, maxValue: target.value });
render() {
const { showThresholdLabels, showThresholdMarkers } = this.props.options;
const { maxValue, minValue, showThresholdLabels, showThresholdMarkers } = this.props.options;
return (
<div className="section gf-form-group">
<h5 className="page-heading">Gauge</h5>
<div className="gf-form-inline">
<h5 className="section-heading">Gauge</h5>
<div className="gf-form">
<Label width={8}>Min value</Label>
<input type="text" className="gf-form-input width-12" onChange={this.onMinValueChange} value={minValue} />
</div>
<div className="gf-form">
<Label width={8}>Max value</Label>
<input type="text" className="gf-form-input width-12" onChange={this.onMaxValueChange} value={maxValue} />
</div>
<Switch
label="Threshold labels"
labelClass="width-10"
label="Show labels"
labelClass="width-8"
checked={showThresholdLabels}
onChange={this.toggleThresholdLabels}
onChange={this.onToggleThresholdLabels}
/>
</div>
<div className="gf-form-inline">
<Switch
label="Threshold markers"
labelClass="width-10"
label="Show markers"
labelClass="width-8"
checked={showThresholdMarkers}
onChange={this.toggleThresholdMarkers}
onChange={this.onToggleThresholdMarkers}
/>
</div>
</div>
);
}
}

View File

@@ -62,60 +62,51 @@ export default class MappingRow extends PureComponent<Props, State> {
if (type === MappingType.RangeToText) {
return (
<>
<div className="gf-form">
<div className="gf-form-inline mapping-row-input">
<Label width={4}>From</Label>
<div>
<input
className="gf-form-input"
className="gf-form-input width-8"
value={from}
onBlur={this.updateMapping}
onChange={this.onMappingFromChange}
/>
</div>
</div>
<div className="gf-form-inline mapping-row-input">
<div className="gf-form">
<Label width={4}>To</Label>
<div>
<input
className="gf-form-input"
className="gf-form-input width-8"
value={to}
onBlur={this.updateMapping}
onChange={this.onMappingToChange}
/>
</div>
</div>
<div className="gf-form-inline mapping-row-input">
<div className="gf-form">
<Label width={4}>Text</Label>
<div>
<input
className="gf-form-input"
className="gf-form-input width-10"
value={text}
onBlur={this.updateMapping}
onChange={this.onMappingTextChange}
/>
</div>
</div>
</div>
</>
);
}
return (
<>
<div className="gf-form">
<div className="gf-form-inline mapping-row-input">
<Label width={4}>Value</Label>
<div>
<input
className="gf-form-input"
className="gf-form-input width-8"
onBlur={this.updateMapping}
onChange={this.onMappingValueChange}
value={value}
/>
</div>
</div>
<div className="gf-form-inline mapping-row-input">
<div className="gf-form gf-form--grow">
<Label width={4}>Text</Label>
<div>
<input
className="gf-form-input"
onBlur={this.updateMapping}
@@ -123,8 +114,7 @@ export default class MappingRow extends PureComponent<Props, State> {
onChange={this.onMappingTextChange}
/>
</div>
</div>
</div>
</>
);
}
@@ -132,8 +122,8 @@ export default class MappingRow extends PureComponent<Props, State> {
const { type } = this.state;
return (
<div className="mapping-row">
<div className="gf-form-inline mapping-row-type">
<div className="gf-form-inline">
<div className="gf-form">
<Label width={5}>Type</Label>
<Select
placeholder="Choose type"
@@ -144,9 +134,11 @@ export default class MappingRow extends PureComponent<Props, State> {
width={7}
/>
</div>
<div>{this.renderRow()}</div>
<div onClick={this.props.removeMapping} className="threshold-row-remove">
{this.renderRow()}
<div className="gf-form">
<button onClick={this.props.removeMapping} className="gf-form-label gf-form-label--btn">
<i className="fa fa-times" />
</button>
</div>
</div>
);

View File

@@ -2,17 +2,14 @@ import React from 'react';
import { shallow } from 'enzyme';
import Thresholds from './Thresholds';
import { defaultProps, OptionsProps } from './module';
import { PanelOptionsProps } from '../../../types';
import { BasicGaugeColor, PanelOptionsProps } from 'app/types';
const setup = (propOverrides?: object) => {
const props: PanelOptionsProps<OptionsProps> = {
onChange: jest.fn(),
options: {
...defaultProps.options,
thresholds: [
{ index: 0, label: 'Min', value: 0, canRemove: false, color: 'rgba(50, 172, 45, 0.97)' },
{ index: 1, label: 'Max', value: 100, canRemove: false },
],
thresholds: [],
},
};
@@ -22,121 +19,51 @@ const setup = (propOverrides?: object) => {
};
describe('Add threshold', () => {
it('should add threshold between min and max', () => {
it('should add threshold', () => {
const instance = setup();
instance.onAddThreshold(1);
instance.onAddThreshold(0);
expect(instance.state.thresholds).toEqual([
{ index: 0, label: 'Min', value: 0, canRemove: false, color: 'rgba(50, 172, 45, 0.97)' },
{ index: 1, label: '', value: 50, canRemove: true, color: 'rgba(237, 129, 40, 0.89)' },
{ index: 2, label: 'Max', value: 100, canRemove: false },
]);
expect(instance.state.thresholds).toEqual([{ index: 0, value: 50, color: 'rgb(127, 115, 64)' }]);
});
it('should add threshold between min and added threshold', () => {
it('should add another threshold above a first', () => {
const instance = setup({
options: {
...defaultProps.options,
thresholds: [
{ index: 0, label: 'Min', value: 0, canRemove: false, color: 'rgba(50, 172, 45, 0.97)' },
{ index: 1, label: '', value: 50, canRemove: true, color: 'rgba(237, 129, 40, 0.89)' },
{ index: 2, label: 'Max', value: 100, canRemove: false },
],
thresholds: [{ index: 0, value: 50, color: 'rgb(127, 115, 64)' }],
},
});
instance.onAddThreshold(1);
expect(instance.state.thresholds).toEqual([
{ index: 0, label: 'Min', value: 0, canRemove: false, color: 'rgba(50, 172, 45, 0.97)' },
{ index: 1, label: '', value: 25, canRemove: true, color: 'rgba(237, 129, 40, 0.89)' },
{ index: 2, label: '', value: 50, canRemove: true, color: 'rgba(237, 129, 40, 0.89)' },
{ index: 3, label: 'Max', value: 100, canRemove: false },
{ index: 1, value: 75, color: 'rgb(170, 95, 61)' },
{ index: 0, value: 50, color: 'rgb(127, 115, 64)' },
]);
});
});
describe('Add at index', () => {
it('should return 1, no added thresholds', () => {
const instance = setup();
const result = instance.insertAtIndex(1);
expect(result).toEqual(1);
});
it('should return 1, one added threshold', () => {
const instance = setup();
instance.state = {
thresholds: [
{ index: 0, label: 'Min', value: 0, canRemove: false },
{ index: 1, label: '', value: 50, canRemove: true },
{ index: 2, label: 'Max', value: 100, canRemove: false },
],
};
const result = instance.insertAtIndex(1);
expect(result).toEqual(1);
});
it('should return 2, two added thresholds', () => {
const instance = setup({
options: {
thresholds: [
{ index: 0, label: 'Min', value: 0, canRemove: false },
{ index: 1, label: '', value: 25, canRemove: true },
{ index: 2, label: '', value: 50, canRemove: true },
{ index: 3, label: 'Max', value: 100, canRemove: false },
],
},
});
const result = instance.insertAtIndex(2);
expect(result).toEqual(2);
});
it('should return 2, one added threshold', () => {
const instance = setup();
instance.state = {
thresholds: [
{ index: 0, label: 'Min', value: 0, canRemove: false },
{ index: 1, label: '', value: 50, canRemove: true },
{ index: 2, label: 'Max', value: 100, canRemove: false },
],
};
const result = instance.insertAtIndex(2);
expect(result).toEqual(2);
});
});
describe('change threshold value', () => {
it('should update value and resort rows', () => {
const instance = setup();
const mockThresholds = [
{ index: 0, label: 'Min', value: 0, canRemove: false, color: 'rgba(50, 172, 45, 0.97)' },
{ index: 1, label: '', value: 50, canRemove: true, color: 'rgba(237, 129, 40, 0.89)' },
{ index: 2, label: '', value: 75, canRemove: true, color: 'rgba(237, 129, 40, 0.89)' },
{ index: 3, label: 'Max', value: 100, canRemove: false },
{ index: 0, value: 50, color: 'rgba(237, 129, 40, 0.89)' },
{ index: 1, value: 75, color: 'rgba(237, 129, 40, 0.89)' },
];
instance.state = {
baseColor: BasicGaugeColor.Green,
thresholds: mockThresholds,
};
const mockEvent = { target: { value: 78 } };
instance.onChangeThresholdValue(mockEvent, mockThresholds[1]);
instance.onChangeThresholdValue(mockEvent, mockThresholds[0]);
expect(instance.state.thresholds).toEqual([
{ index: 0, label: 'Min', value: 0, canRemove: false, color: 'rgba(50, 172, 45, 0.97)' },
{ index: 1, label: '', value: 78, canRemove: true, color: 'rgba(237, 129, 40, 0.89)' },
{ index: 2, label: '', value: 75, canRemove: true, color: 'rgba(237, 129, 40, 0.89)' },
{ index: 3, label: 'Max', value: 100, canRemove: false },
{ index: 0, value: 78, color: 'rgba(237, 129, 40, 0.89)' },
{ index: 1, value: 75, color: 'rgba(237, 129, 40, 0.89)' },
]);
});
});

View File

@@ -1,11 +1,12 @@
import React, { PureComponent } from 'react';
import classNames from 'classnames/bind';
import tinycolor from 'tinycolor2';
import { ColorPicker } from 'app/core/components/colorpicker/ColorPicker';
import { OptionModuleProps } from './module';
import { BasicGaugeColor, Threshold } from 'app/types';
interface State {
thresholds: Threshold[];
baseColor: string;
}
export default class Thresholds extends PureComponent<OptionModuleProps, State> {
@@ -14,10 +15,12 @@ export default class Thresholds extends PureComponent<OptionModuleProps, State>
this.state = {
thresholds: props.options.thresholds,
baseColor: props.options.baseColor,
};
}
onAddThreshold = index => {
const { maxValue, minValue } = this.props.options;
const { thresholds } = this.state;
const newThresholds = thresholds.map(threshold => {
@@ -28,15 +31,28 @@ export default class Thresholds extends PureComponent<OptionModuleProps, State>
return threshold;
});
// Setting value to a value between the new threshold.
const value = newThresholds[index].value - (newThresholds[index].value - newThresholds[index - 1].value) / 2;
// Setting value to a value between the previous thresholds
let value;
if (index === 0 && thresholds.length === 0) {
value = maxValue - (maxValue - minValue) / 2;
} else if (index === 0 && thresholds.length > 0) {
value = newThresholds[index + 1].value - (newThresholds[index + 1].value - minValue) / 2;
} else if (index > newThresholds[newThresholds.length - 1].index) {
value = maxValue - (maxValue - newThresholds[index - 1].value) / 2;
}
// Set a color that lies between the previous thresholds
let color;
if (index === 0 && thresholds.length === 0) {
color = tinycolor.mix(BasicGaugeColor.Green, BasicGaugeColor.Red, 50).toRgbString();
} else {
color = tinycolor.mix(thresholds[index - 1].color, BasicGaugeColor.Red, 50).toRgbString();
}
this.setState(
{
thresholds: this.sortThresholds([
...newThresholds,
{ index: index, label: '', value: value, canRemove: true, color: BasicGaugeColor.Orange },
]),
thresholds: this.sortThresholds([...newThresholds, { index: index, value: value, color: color }]),
},
() => this.updateGauge()
);
@@ -86,6 +102,7 @@ export default class Thresholds extends PureComponent<OptionModuleProps, State>
);
};
onChangeBaseColor = color => this.props.onChange({ ...this.props.options, baseColor: color });
onBlur = () => {
this.setState(prevState => ({
thresholds: this.sortThresholds(prevState.thresholds),
@@ -100,78 +117,16 @@ export default class Thresholds extends PureComponent<OptionModuleProps, State>
sortThresholds = thresholds => {
return thresholds.sort((t1, t2) => {
return t1.value - t2.value;
return t2.value - t1.value;
});
};
getIndicatorColor = index => {
const { thresholds } = this.state;
if (index === 0) {
return thresholds[0].color;
}
return index < thresholds.length ? thresholds[index].color : BasicGaugeColor.Red;
};
renderNoThresholds() {
const { thresholds } = this.state;
const min = thresholds[0];
const max = thresholds[1];
return [
<div className="threshold-row threshold-row-min" key="min">
<div className="threshold-row-inner">
<div className="threshold-row-color">
<div className="threshold-row-color-inner">
<ColorPicker color={min.color} onChange={color => this.onChangeThresholdColor(min, color)} />
</div>
</div>
<input
className="threshold-row-input"
onBlur={this.onBlur}
onChange={event => this.onChangeThresholdValue(event, min)}
value={min.value}
/>
<div className="threshold-row-label">{min.label}</div>
</div>
</div>,
<div className="threshold-row" key="add">
<div className="threshold-row-inner">
<div onClick={() => this.onAddThreshold(1)} className="threshold-row-add">
<i className="fa fa-plus" />
</div>
<div className="threshold-row-add-label">Add new threshold by clicking the line.</div>
</div>
</div>,
<div className="threshold-row threshold-row-max" key="max">
<div className="threshold-row-inner">
<div className="threshold-row-color" />
<input
className="threshold-row-input"
onBlur={this.onBlur}
onChange={event => this.onChangeThresholdValue(event, max)}
value={max.value}
/>
<div className="threshold-row-label">{max.label}</div>
</div>
</div>,
];
}
renderThresholds() {
const { thresholds } = this.state;
return thresholds.map((threshold, index) => {
const rowStyle = classNames({
'threshold-row': true,
'threshold-row-min': index === 0,
'threshold-row-max': index === thresholds.length - 1,
});
return (
<div className={rowStyle} key={`${threshold.index}-${index}`}>
<div className="threshold-row" key={`${threshold.index}-${index}`}>
<div className="threshold-row-inner">
<div className="threshold-row-color">
{threshold.color && (
@@ -190,103 +145,80 @@ export default class Thresholds extends PureComponent<OptionModuleProps, State>
value={threshold.value}
onBlur={this.onBlur}
/>
{threshold.canRemove ? (
<div onClick={() => this.onRemoveThreshold(threshold)} className="threshold-row-remove">
<i className="fa fa-times" />
</div>
) : (
<div className="threshold-row-label">{threshold.label}</div>
)}
</div>
</div>
);
});
}
insertAtIndex(index) {
const { thresholds } = this.state;
// If thresholds.length is greater or equal to 3
// it means a user has added one threshold
if (thresholds.length < 3 || index < 0) {
return 1;
}
return index;
}
renderIndicatorSection(index) {
const { thresholds } = this.state;
const indicators = thresholds.length - 1;
if (index === 0 || index === thresholds.length) {
return (
<div
key={index}
className="indicator-section"
style={{
height: `calc(100%/${indicators})`,
}}
>
<div
onClick={() => this.onAddThreshold(this.insertAtIndex(index - 1))}
style={{
height: '100%',
background: this.getIndicatorColor(index),
}}
/>
</div>
);
}
return (
<div
key={index}
className="indicator-section"
style={{
height: `calc(100%/${indicators})`,
}}
>
<div
onClick={() => this.onAddThreshold(this.insertAtIndex(index))}
style={{
height: '50%',
background: this.getIndicatorColor(index),
}}
/>
<div
onClick={() => this.onAddThreshold(this.insertAtIndex(index + 1))}
style={{
height: `50%`,
background: this.getIndicatorColor(index),
}}
/>
</div>
);
}
renderIndicator() {
const { thresholds } = this.state;
return thresholds.map((t, i) => {
if (i <= thresholds.length - 1) {
return this.renderIndicatorSection(i);
}
return null;
return (
<div key={`${t.value}-${i}`} className="indicator-section">
<div
onClick={() => this.onAddThreshold(t.index + 1)}
style={{
height: '50%',
backgroundColor: t.color,
}}
/>
<div
onClick={() => this.onAddThreshold(t.index)}
style={{
height: '50%',
backgroundColor: t.color,
}}
/>
</div>
);
});
}
render() {
const { thresholds } = this.state;
renderBaseIndicator() {
return (
<div className="indicator-section" style={{ height: '100%' }}>
<div
onClick={() => this.onAddThreshold(0)}
style={{ height: '100%', backgroundColor: this.props.options.baseColor }}
/>
</div>
);
}
renderBase() {
const { baseColor } = this.props.options;
return (
<div className="threshold-row threshold-row-base">
<div className="threshold-row-inner threshold-row-inner--base">
<div className="threshold-row-color">
<div className="threshold-row-color-inner">
<ColorPicker color={baseColor} onChange={color => this.onChangeBaseColor(color)} />
</div>
</div>
<div className="threshold-row-label">Base</div>
</div>
</div>
);
}
render() {
return (
<div className="section gf-form-group">
<h5 className="page-heading">Thresholds</h5>
<h5 className="section-heading">Thresholds</h5>
<div className="thresholds">
<div className="color-indicators">{this.renderIndicator()}</div>
<div className="color-indicators">
{this.renderIndicator()}
{this.renderBaseIndicator()}
</div>
<div className="threshold-rows">
{thresholds.length > 2 ? this.renderThresholds() : this.renderNoThresholds()}
{this.renderThresholds()}
{this.renderBase()}
</div>
</div>
</div>

View File

@@ -76,7 +76,7 @@ export default class ValueMappings extends PureComponent<OptionModuleProps, Stat
return (
<div className="section gf-form-group">
<h5 className="page-heading">Value mappings</h5>
<h5 className="section-heading">Value mappings</h5>
<div>
{mappings.length > 0 &&
mappings.map((mapping, index) => (

View File

@@ -40,8 +40,8 @@ export default class ValueOptions extends PureComponent<OptionModuleProps> {
return (
<div className="section gf-form-group">
<h5 className="page-heading">Value</h5>
<div className="gf-form-inline">
<h5 className="section-heading">Value</h5>
<div className="gf-form">
<Label width={labelWidth}>Stat</Label>
<Select
width={12}
@@ -50,11 +50,11 @@ export default class ValueOptions extends PureComponent<OptionModuleProps> {
value={statOptions.find(option => option.value === stat)}
/>
</div>
<div className="gf-form-inline">
<div className="gf-form">
<Label width={labelWidth}>Unit</Label>
<UnitPicker defaultValue={unit} onChange={this.onUnitChange} />
</div>
<div className="gf-form-inline">
<div className="gf-form">
<Label width={labelWidth}>Decimals</Label>
<input
className="gf-form-input width-12"
@@ -64,11 +64,11 @@ export default class ValueOptions extends PureComponent<OptionModuleProps> {
onChange={this.onDecimalChange}
/>
</div>
<div className="gf-form-inline">
<div className="gf-form">
<Label width={labelWidth}>Prefix</Label>
<input className="gf-form-input width-12" type="text" value={prefix || ''} onChange={this.onPrefixChange} />
</div>
<div className="gf-form-inline">
<div className="gf-form">
<Label width={labelWidth}>Suffix</Label>
<input className="gf-form-input width-12" type="text" value={suffix || ''} onChange={this.onSuffixChange} />
</div>

View File

@@ -5,7 +5,7 @@ exports[`Render should render component 1`] = `
className="section gf-form-group"
>
<h5
className="page-heading"
className="section-heading"
>
Value mappings
</h5>

View File

@@ -16,15 +16,18 @@ import {
} from 'app/types';
export interface OptionsProps {
baseColor: string;
decimals: number;
mappings: Array<RangeMap | ValueMap>;
maxValue: number;
minValue: number;
prefix: string;
showThresholdLabels: boolean;
showThresholdMarkers: boolean;
stat: string;
suffix: string;
unit: string;
thresholds: Threshold[];
mappings: Array<RangeMap | ValueMap>;
unit: string;
}
export interface OptionModuleProps {
@@ -34,6 +37,7 @@ export interface OptionModuleProps {
export const defaultProps = {
options: {
baseColor: BasicGaugeColor.Green,
minValue: 0,
maxValue: 100,
prefix: '',
@@ -44,10 +48,7 @@ export const defaultProps = {
stat: '',
unit: '',
mappings: [],
thresholds: [
{ index: 0, label: 'Min', value: 0, canRemove: false, color: BasicGaugeColor.Green },
{ index: 1, label: 'Max', value: 100, canRemove: false },
],
thresholds: [],
},
};

View File

@@ -31,10 +31,8 @@ export interface PanelMenuItem {
export interface Threshold {
index: number;
label: string;
value: number;
color?: string;
canRemove: boolean;
}
export enum MappingType {
@@ -43,9 +41,8 @@ export enum MappingType {
}
export enum BasicGaugeColor {
Green = 'rgba(50, 172, 45, 0.97)',
Orange = 'rgba(237, 129, 40, 0.89)',
Red = 'rgb(212, 74, 58)',
Green = '#299c46',
Red = '#d44a3a',
}
interface BaseMap {

View File

@@ -1,10 +1,11 @@
import React, { PureComponent } from 'react';
import $ from 'jquery';
import { MappingType, RangeMap, Threshold, TimeSeriesVMs, ValueMap } from 'app/types';
import { BasicGaugeColor, MappingType, RangeMap, Threshold, TimeSeriesVMs, ValueMap } from 'app/types';
import config from '../core/config';
import kbn from '../core/utils/kbn';
interface Props {
baseColor: string;
decimals: number;
height: number;
mappings: Array<RangeMap | ValueMap>;
@@ -25,6 +26,7 @@ export class Gauge extends PureComponent<Props> {
canvasElement: any;
static defaultProps = {
baseColor: BasicGaugeColor.Green,
maxValue: 100,
mappings: [],
minValue: 0,
@@ -32,11 +34,9 @@ export class Gauge extends PureComponent<Props> {
showThresholdMarkers: true,
showThresholdLabels: false,
suffix: '',
thresholds: [
{ label: 'Min', value: 0, color: 'rgba(50, 172, 45, 0.97)' },
{ label: 'Max', value: 100, color: 'rgba(245, 54, 54, 0.9)' },
],
thresholds: [],
unit: 'none',
stat: 'avg',
};
componentDidMount() {
@@ -92,12 +92,44 @@ export class Gauge extends PureComponent<Props> {
return `${prefix} ${formattedValue} ${suffix}`;
}
getFontColor(value) {
const { baseColor, maxValue, thresholds } = this.props;
const atThreshold = thresholds.filter(threshold => value <= threshold.value);
if (atThreshold.length > 0) {
return atThreshold[0].color;
} else if (value <= maxValue) {
return BasicGaugeColor.Red;
}
return baseColor;
}
draw() {
const { timeSeries, showThresholdLabels, showThresholdMarkers, thresholds, width, height, stat } = this.props;
const {
baseColor,
maxValue,
minValue,
timeSeries,
showThresholdLabels,
showThresholdMarkers,
thresholds,
width,
height,
stat,
} = this.props;
let value: string | number = '';
if (timeSeries[0]) {
value = timeSeries[0].stats[stat];
} else {
value = 'N/A';
}
const dimension = Math.min(width, height * 1.3);
const backgroundColor = config.bootData.user.lightTheme ? 'rgb(230,230,230)' : 'rgb(38,38,38)';
const fontColor = config.bootData.user.lightTheme ? 'rgb(38,38,38)' : 'rgb(230,230,230)';
const fontScale = parseInt('80', 10) / 100;
const fontSize = Math.min(dimension / 5, 100) * fontScale;
const gaugeWidthReduceRatio = showThresholdLabels ? 1.5 : 1;
@@ -105,20 +137,26 @@ export class Gauge extends PureComponent<Props> {
const thresholdMarkersWidth = gaugeWidth / 5;
const thresholdLabelFontSize = fontSize / 2.5;
const formattedThresholds = thresholds.map((threshold, index) => {
const formattedThresholds = [
{ value: minValue, color: BasicGaugeColor.Green },
...thresholds.map((threshold, index) => {
return {
value: threshold.value,
// Hacky way to get correct color for threshold.
color: index === 0 ? threshold.color : thresholds[index - 1].color,
color: index === 0 ? threshold.color : thresholds[index].color,
};
});
}),
{
value: maxValue,
color: thresholds.length > 0 ? BasicGaugeColor.Red : baseColor,
},
];
const options = {
series: {
gauges: {
gauge: {
min: thresholds[0].value,
max: thresholds[thresholds.length - 1].value,
min: minValue,
max: maxValue,
background: { color: backgroundColor },
border: { color: null },
shadow: { show: false },
@@ -139,13 +177,9 @@ export class Gauge extends PureComponent<Props> {
width: thresholdMarkersWidth,
},
value: {
color: fontColor,
color: this.getFontColor(value),
formatter: () => {
if (timeSeries[0]) {
return this.formatValue(timeSeries[0].stats[stat]);
}
return '';
return this.formatValue(value);
},
font: {
size: fontSize,
@@ -157,11 +191,6 @@ export class Gauge extends PureComponent<Props> {
},
};
let value: string | number = 'N/A';
if (timeSeries.length) {
value = timeSeries[0].stats[stat];
}
const plotSeries = {
data: [[0, value]],
};

View File

@@ -123,6 +123,15 @@ $input-border: 1px solid $input-border-color;
padding-left: 0px;
}
&--btn {
border-right: $input-btn-border-width solid $input-label-border-color;
border-radius: $border-radius;
&:hover {
background: $list-item-hover-bg;
}
}
&:disabled {
color: $text-color-weak;
}

View File

@@ -1,6 +1,5 @@
.thresholds {
display: flex;
margin-top: 30px;
}
.threshold-rows {
@@ -10,7 +9,7 @@
.threshold-row {
display: flex;
align-items: center;
margin: 5px 0;
margin-top: 3px;
padding: 5px;
&::before {
@@ -25,8 +24,11 @@
border-radius: $border-radius;
display: flex;
overflow: hidden;
width: 300px;
height: 37px;
&--base {
width: auto;
}
}
.threshold-row-color {
@@ -48,13 +50,12 @@
.threshold-row-input {
padding: 8px 10px;
width: 230px;
width: 150px;
}
.threshold-row-label {
background-color: $input-label-bg;
padding: 5px;
width: 36px;
display: flex;
align-items: center;
}
@@ -65,12 +66,7 @@
padding: 5px 8px;
}
.threshold-row-min {
margin-top: -22px;
}
.threshold-row-max {
margin-bottom: -22px;
.threshold-row-base {
}
.threshold-row-remove {
@@ -98,11 +94,13 @@
.indicator-section {
width: 100%;
height: 50px;
cursor: pointer;
}
.color-indicators {
width: 15px;
border-radius: $border-radius;
border-bottom-left-radius: $border-radius;
border-bottom-right-radius: $border-radius;
overflow: hidden;
}

View File

@@ -3,14 +3,6 @@
margin-bottom: 10px;
}
.mapping-row-type {
margin-right: 5px;
}
.mapping-row-input {
margin-right: 5px;
}
.add-mapping-row {
display: flex;
overflow: hidden;