StatPanel: Refactoring & fixes (#21437)

* refactoring BigValue layout to use polymorphism

* Renamed file

* StatPanel: Fixed min & max handling, white theme background

* Typescript and test fixes

* Fixed justify center logic

* Updated test dashboard

* Updated test dashboard

* Adjust time xMax to align with last data point if it's close

* Fixed color issue

* removed text shadow

* Removed unused stuff

* Fixed auto min max

* Fixed strict typescript issues

* Updated
This commit is contained in:
Torkel Ödegaard 2020-01-13 09:41:35 +01:00 committed by GitHub
parent 4f0fa776be
commit 27a77f588c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 485 additions and 489 deletions

View File

@ -33,8 +33,6 @@
"calcs": ["mean"],
"defaults": {
"mappings": [],
"max": 100,
"min": 0,
"thresholds": [
{
"color": "blue",
@ -57,7 +55,7 @@
"value": 80
}
],
"unit": "percent"
"unit": "areaM2"
},
"override": {},
"values": false
@ -124,8 +122,6 @@
"calcs": ["mean"],
"defaults": {
"mappings": [],
"max": 100,
"min": 0,
"thresholds": [
{
"color": "blue",
@ -215,8 +211,6 @@
"calcs": ["mean"],
"defaults": {
"mappings": [],
"max": 100,
"min": 0,
"thresholds": [
{
"color": "blue",
@ -239,7 +233,7 @@
"value": 80
}
],
"unit": "percent"
"unit": "areaM2"
},
"override": {},
"values": false
@ -300,8 +294,6 @@
"calcs": ["mean"],
"defaults": {
"mappings": [],
"max": 100,
"min": 0,
"thresholds": [
{
"color": "blue",
@ -324,7 +316,7 @@
"value": 80
}
],
"unit": "percent"
"unit": "areaM2"
},
"override": {},
"values": false
@ -388,8 +380,6 @@
"calcs": ["mean"],
"defaults": {
"mappings": [],
"max": 100,
"min": 0,
"thresholds": [
{
"color": "blue",
@ -412,7 +402,7 @@
"value": 80
}
],
"unit": "percent"
"unit": "areaM2"
},
"override": {},
"values": false
@ -476,8 +466,6 @@
"calcs": ["mean"],
"defaults": {
"mappings": [],
"max": 100,
"min": 0,
"thresholds": [
{
"color": "blue",
@ -500,7 +488,7 @@
"value": 80
}
],
"unit": "percent"
"unit": "areaM2"
},
"override": {},
"values": false

View File

@ -4,21 +4,15 @@ import { DisplayValue, GraphSeriesValue, DisplayValueAlignmentFactors } from '@g
// Types
import { Themeable } from '../../types';
import {
calculateLayout,
getPanelStyles,
getValueAndTitleContainerStyles,
getValueStyles,
getTitleStyles,
} from './styles';
import { renderGraph } from './renderGraph';
import { buildLayout } from './BigValueLayout';
import { FormattedValueDisplay } from '../FormattedValueDisplay/FormattedValueDisplay';
export interface BigValueSparkline {
data: GraphSeriesValue[][];
minX: number;
maxX: number;
xMin?: number | null;
xMax?: number | null;
yMin?: number | null;
yMax?: number | null;
highlightIndex?: number;
}
@ -57,13 +51,13 @@ export class BigValue extends PureComponent<Props> {
};
render() {
const { value, onClick, className, sparkline } = this.props;
const { value, onClick, className } = this.props;
const layout = calculateLayout(this.props);
const panelStyles = getPanelStyles(layout);
const valueAndTitleContainerStyles = getValueAndTitleContainerStyles(layout);
const valueStyles = getValueStyles(layout);
const titleStyles = getTitleStyles(layout);
const layout = buildLayout(this.props);
const panelStyles = layout.getPanelStyles();
const valueAndTitleContainerStyles = layout.getValueAndTitleContainerStyles();
const valueStyles = layout.getValueStyles();
const titleStyles = layout.getTitleStyles();
return (
<div className={className} style={panelStyles} onClick={onClick}>
@ -71,7 +65,7 @@ export class BigValue extends PureComponent<Props> {
{value.title && <div style={titleStyles}>{value.title}</div>}
<FormattedValueDisplay value={value} style={valueStyles} />
</div>
{renderGraph(layout, sparkline)}
{layout.renderChart()}
</div>
);
}

View File

@ -1,5 +1,5 @@
import { Props, BigValueColorMode, BigValueGraphMode } from './BigValue';
import { calculateLayout, LayoutType } from './styles';
import { buildLayout, StackedWithChartLayout, WideWithChartLayout } from './BigValueLayout';
import { getTheme } from '../../themes';
function getProps(propOverrides?: Partial<Props>): Props {
@ -17,8 +17,8 @@ function getProps(propOverrides?: Partial<Props>): Props {
[10, 10],
[10, 10],
],
minX: 0,
maxX: 100,
xMin: 0,
xMax: 100,
},
theme: getTheme(),
};
@ -27,26 +27,26 @@ function getProps(propOverrides?: Partial<Props>): Props {
return props;
}
describe('BigValue styles', () => {
describe('calculateLayout', () => {
describe('BigValueLayout', () => {
describe('buildLayout', () => {
it('should auto select to stacked layout', () => {
const layout = calculateLayout(
const layout = buildLayout(
getProps({
width: 300,
height: 300,
})
);
expect(layout.type).toBe(LayoutType.Stacked);
expect(layout).toBeInstanceOf(StackedWithChartLayout);
});
it('should auto select to wide layout', () => {
const layout = calculateLayout(
const layout = buildLayout(
getProps({
width: 300,
height: 100,
})
);
expect(layout.type).toBe(LayoutType.Wide);
expect(layout).toBeInstanceOf(WideWithChartLayout);
});
});
});

View File

@ -0,0 +1,453 @@
// Libraries
import React, { CSSProperties } from 'react';
import tinycolor from 'tinycolor2';
import { Chart, Geom } from 'bizcharts';
// Utils
import { getColorFromHexRgbOrName, formattedValueToString } from '@grafana/data';
import { calculateFontSize } from '../../utils/measureText';
// Types
import { BigValueColorMode, Props, BigValueJustifyMode } from './BigValue';
const LINE_HEIGHT = 1.2;
const MAX_TITLE_SIZE = 30;
export abstract class BigValueLayout {
titleFontSize: number;
valueFontSize: number;
chartHeight: number;
chartWidth: number;
valueColor: string;
panelPadding: number;
justifyCenter: boolean;
titleToAlignTo?: string;
valueToAlignTo: string;
maxTextWidth: number;
maxTextHeight: number;
constructor(private props: Props) {
const { width, height, value, alignmentFactors, theme } = props;
this.valueColor = getColorFromHexRgbOrName(value.color || 'green', theme.type);
this.justifyCenter = shouldJustifyCenter(props);
this.panelPadding = height > 100 ? 12 : 8;
this.titleToAlignTo = alignmentFactors ? alignmentFactors.title : value.title;
this.valueToAlignTo = formattedValueToString(alignmentFactors ? alignmentFactors : value);
this.titleFontSize = 14;
this.valueFontSize = 14;
this.chartHeight = 0;
this.chartWidth = 0;
this.maxTextWidth = width - this.panelPadding * 2;
this.maxTextHeight = height - this.panelPadding * 2;
}
getTitleStyles(): CSSProperties {
const styles: CSSProperties = {
fontSize: `${this.titleFontSize}px`,
lineHeight: LINE_HEIGHT,
};
if (this.props.colorMode === BigValueColorMode.Background) {
styles.color = 'white';
}
return styles;
}
getValueStyles(): CSSProperties {
const styles: CSSProperties = {
fontSize: this.valueFontSize,
fontWeight: 500,
lineHeight: LINE_HEIGHT,
};
switch (this.props.colorMode) {
case BigValueColorMode.Value:
styles.color = this.valueColor;
break;
case BigValueColorMode.Background:
styles.color = 'white';
}
return styles;
}
getValueAndTitleContainerStyles() {
const styles: CSSProperties = {
display: 'flex',
};
if (this.justifyCenter) {
styles.alignItems = 'center';
styles.justifyContent = 'center';
styles.flexGrow = 1;
}
return styles;
}
getPanelStyles(): CSSProperties {
const { width, height, theme, colorMode } = this.props;
const panelStyles: CSSProperties = {
width: `${width}px`,
height: `${height}px`,
padding: `${this.panelPadding}px`,
borderRadius: '3px',
position: 'relative',
display: 'flex',
};
const themeFactor = theme.isDark ? 1 : -0.7;
switch (colorMode) {
case BigValueColorMode.Background:
const bgColor2 = tinycolor(this.valueColor)
.darken(15 * themeFactor)
.spin(8)
.toRgbString();
const bgColor3 = tinycolor(this.valueColor)
.darken(5 * themeFactor)
.spin(-8)
.toRgbString();
panelStyles.background = `linear-gradient(120deg, ${bgColor2}, ${bgColor3})`;
break;
case BigValueColorMode.Value:
panelStyles.background = `${theme.colors.panelBg}`;
break;
}
if (this.justifyCenter) {
panelStyles.alignItems = 'center';
panelStyles.flexDirection = 'row';
}
return panelStyles;
}
renderChart(): JSX.Element | null {
const { sparkline } = this.props;
if (!sparkline || sparkline.data.length === 0) {
return null;
}
const data = sparkline.data.map(values => {
return { time: values[0], value: values[1], name: 'A' };
});
const scales = {
time: {
type: 'time',
min: sparkline.xMin,
max: sparkline.xMax,
},
value: {
min: sparkline.yMin,
max: sparkline.yMax,
},
};
if (sparkline.xMax && sparkline.xMin) {
// Having the last data point align with the edge of the panel looks good
// So if it's close adjust time.max to the last data point time
const timeDelta = sparkline.xMax - sparkline.xMin;
const lastDataPointTime = data[data.length - 1].time || 0;
const lastTimeDiffFromMax = Math.abs(sparkline.xMax - lastDataPointTime);
// if last data point is just 5% or lower from the edge adjust it
if (lastTimeDiffFromMax / timeDelta < 0.05) {
scales.time.max = lastDataPointTime;
}
}
return (
<Chart
height={this.chartHeight}
width={this.chartWidth}
data={data}
animate={false}
padding={[4, 0, 0, 0]}
scale={scales}
style={this.getChartStyles()}
>
{this.renderGeom()}
</Chart>
);
}
renderGeom(): JSX.Element {
const { colorMode } = this.props;
const lineStyle: any = {
opacity: 1,
fillOpacity: 1,
lineWidth: 2,
};
let fillColor: string;
let lineColor: string;
switch (colorMode) {
case BigValueColorMode.Value:
lineColor = this.valueColor;
fillColor = tinycolor(this.valueColor)
.setAlpha(0.2)
.toRgbString();
break;
case BigValueColorMode.Background:
fillColor = 'rgba(255,255,255,0.4)';
lineColor = tinycolor(this.valueColor)
.brighten(40)
.toRgbString();
}
lineStyle.stroke = lineColor;
return (
<>
<Geom type="area" position="time*value" size={0} color={fillColor} style={lineStyle} shape="smooth" />
<Geom type="line" position="time*value" size={1} color={lineColor} style={lineStyle} shape="smooth" />
</>
);
}
getChartStyles(): CSSProperties {
return {
position: 'absolute',
right: 0,
bottom: 0,
};
}
}
export class WideNoChartLayout extends BigValueLayout {
constructor(props: Props) {
super(props);
const valueWidthPercent = 0.3;
if (this.titleToAlignTo && this.titleToAlignTo.length > 0) {
// initial value size
this.valueFontSize = calculateFontSize(
this.valueToAlignTo,
this.maxTextWidth * valueWidthPercent,
this.maxTextHeight,
LINE_HEIGHT
);
// How big can we make the title and still have it fit
this.titleFontSize = calculateFontSize(
this.titleToAlignTo,
this.maxTextWidth * 0.6,
this.maxTextHeight,
LINE_HEIGHT,
MAX_TITLE_SIZE
);
// make sure it's a bit smaller than valueFontSize
this.titleFontSize = Math.min(this.valueFontSize * 0.7, this.titleFontSize);
} else {
// if no title wide
this.valueFontSize = calculateFontSize(this.valueToAlignTo, this.maxTextWidth, this.maxTextHeight, LINE_HEIGHT);
}
}
getValueAndTitleContainerStyles() {
const styles = super.getValueAndTitleContainerStyles();
styles.flexDirection = 'row';
styles.alignItems = 'center';
styles.flexGrow = 1;
if (!this.justifyCenter) {
styles.justifyContent = 'space-between';
}
return styles;
}
renderChart(): JSX.Element | null {
return null;
}
getPanelStyles() {
const panelStyles = super.getPanelStyles();
panelStyles.alignItems = 'center';
return panelStyles;
}
}
export class WideWithChartLayout extends BigValueLayout {
constructor(props: Props) {
super(props);
const { width, height } = props;
const chartHeightPercent = 0.5;
const titleWidthPercent = 0.6;
const valueWidthPercent = 1 - titleWidthPercent;
const textHeightPercent = 0.4;
this.chartWidth = width;
this.chartHeight = height * chartHeightPercent;
if (this.titleToAlignTo && this.titleToAlignTo.length > 0) {
this.titleFontSize = calculateFontSize(
this.titleToAlignTo,
this.maxTextWidth * titleWidthPercent,
this.maxTextHeight * textHeightPercent,
LINE_HEIGHT,
MAX_TITLE_SIZE
);
}
this.valueFontSize = calculateFontSize(
this.valueToAlignTo,
this.maxTextWidth * valueWidthPercent,
this.maxTextHeight * chartHeightPercent,
LINE_HEIGHT
);
}
getValueAndTitleContainerStyles() {
const styles = super.getValueAndTitleContainerStyles();
styles.flexDirection = 'row';
styles.flexGrow = 1;
if (!this.justifyCenter) {
styles.justifyContent = 'space-between';
}
return styles;
}
getPanelStyles() {
const styles = super.getPanelStyles();
styles.flexDirection = 'row';
styles.justifyContent = 'space-between';
return styles;
}
}
export class StackedWithChartLayout extends BigValueLayout {
constructor(props: Props) {
super(props);
const { width, height } = props;
const titleHeightPercent = 0.15;
const chartHeightPercent = 0.25;
let titleHeight = 0;
this.chartHeight = height * chartHeightPercent;
this.chartWidth = width;
if (this.titleToAlignTo && this.titleToAlignTo.length > 0) {
this.titleFontSize = calculateFontSize(
this.titleToAlignTo,
this.maxTextWidth,
height * titleHeightPercent,
LINE_HEIGHT,
MAX_TITLE_SIZE
);
titleHeight = this.titleFontSize * LINE_HEIGHT;
}
this.valueFontSize = calculateFontSize(
this.valueToAlignTo,
this.maxTextWidth,
this.maxTextHeight - this.chartHeight - titleHeight,
LINE_HEIGHT
);
// make title fontsize it's a bit smaller than valueFontSize
this.titleFontSize = Math.min(this.valueFontSize * 0.7, this.titleFontSize);
}
getValueAndTitleContainerStyles() {
const styles = super.getValueAndTitleContainerStyles();
styles.flexDirection = 'column';
styles.justifyContent = 'center';
return styles;
}
getPanelStyles() {
const styles = super.getPanelStyles();
styles.flexDirection = 'column';
return styles;
}
}
export class StackedWithNoChartLayout extends BigValueLayout {
constructor(props: Props) {
super(props);
const { height } = props;
const titleHeightPercent = 0.15;
let titleHeight = 0;
if (this.titleToAlignTo && this.titleToAlignTo.length > 0) {
this.titleFontSize = calculateFontSize(
this.titleToAlignTo,
this.maxTextWidth,
height * titleHeightPercent,
LINE_HEIGHT,
MAX_TITLE_SIZE
);
titleHeight = this.titleFontSize * LINE_HEIGHT;
}
this.valueFontSize = calculateFontSize(
this.valueToAlignTo,
this.maxTextWidth,
this.maxTextHeight - titleHeight,
LINE_HEIGHT
);
// make title fontsize it's a bit smaller than valueFontSize
this.titleFontSize = Math.min(this.valueFontSize * 0.7, this.titleFontSize);
}
getValueAndTitleContainerStyles() {
const styles = super.getValueAndTitleContainerStyles();
styles.flexDirection = 'column';
styles.flexGrow = 1;
return styles;
}
getPanelStyles() {
const styles = super.getPanelStyles();
styles.alignItems = 'center';
return styles;
}
}
export function buildLayout(props: Props): BigValueLayout {
const { width, height, sparkline } = props;
const useWideLayout = width / height > 2.5;
if (useWideLayout) {
if (height > 50 && !!sparkline) {
return new WideWithChartLayout(props);
} else {
return new WideNoChartLayout(props);
}
}
// stacked layouts
if (height > 100 && !!sparkline) {
return new StackedWithChartLayout(props);
} else {
return new StackedWithNoChartLayout(props);
}
}
export function shouldJustifyCenter(props: Props) {
const { value, justifyMode } = props;
if (justifyMode === BigValueJustifyMode.Center) {
return true;
}
return (value.title ?? '').length === 0;
}

View File

@ -30,11 +30,10 @@ exports[`BigValue Render with basic options should render 1`] = `
<FormattedDisplayValue
style={
Object {
"color": "#EEE",
"color": "white",
"fontSize": 230,
"fontWeight": 500,
"lineHeight": 1.2,
"textShadow": "#333 0px 0px 1px",
}
}
value={

View File

@ -1,151 +0,0 @@
import React, { CSSProperties } from 'react';
import tinycolor from 'tinycolor2';
import { Chart, Geom, Guide } from 'bizcharts';
import { LayoutResult, LayoutType } from './styles';
import { BigValueSparkline, BigValueColorMode } from './BigValue';
const { DataMarker } = Guide;
export function renderGraph(layout: LayoutResult, sparkline?: BigValueSparkline) {
if (!sparkline || layout.type === LayoutType.WideNoChart || layout.type === LayoutType.StackedNoChart) {
return null;
}
const data = sparkline.data.map(values => {
return { time: values[0], value: values[1], name: 'A' };
});
const scales = {
time: {
type: 'time',
},
};
const chartStyles: CSSProperties = {
position: 'absolute',
};
// default to line graph
const geomRender = getGraphGeom(layout.colorMode);
if (layout.type === LayoutType.Wide) {
// Area chart
chartStyles.bottom = 0;
chartStyles.right = 0;
} else {
// need some top padding
chartStyles.width = `${layout.chartWidth}px`;
chartStyles.height = `${layout.chartHeight}px`;
chartStyles.bottom = 0;
chartStyles.right = 0;
chartStyles.left = 0;
chartStyles.right = 0;
}
return (
<Chart
height={layout.chartHeight}
width={layout.chartWidth}
data={data}
animate={false}
padding={[4, 0, 0, 0]}
scale={scales}
style={chartStyles}
>
{geomRender(layout, sparkline)}
</Chart>
);
}
function getGraphGeom(colorMode: BigValueColorMode) {
// background color mode
if (colorMode === BigValueColorMode.Background) {
return renderAreaGeomOnColoredBackground;
}
return renderClassicAreaGeom;
}
function renderAreaGeomOnColoredBackground(layout: LayoutResult, sparkline: BigValueSparkline) {
const lineColor = tinycolor(layout.valueColor)
.brighten(40)
.toRgbString();
const lineStyle: any = {
stroke: lineColor,
lineWidth: 2,
};
return (
<>
<Geom type="area" position="time*value" size={0} color="rgba(255,255,255,0.4)" style={lineStyle} shape="smooth" />
<Geom type="line" position="time*value" size={1} color={lineColor} style={lineStyle} shape="smooth" />
{highlightPoint(lineColor, sparkline)}
</>
);
}
function highlightPoint(lineColor: string, sparkline: BigValueSparkline) {
if (!sparkline.highlightIndex) {
return null;
}
const pointPos = sparkline.data[sparkline.highlightIndex];
return (
<Guide>
<DataMarker
top
position={pointPos}
lineLength={0}
display={{ point: true }}
style={{
point: {
color: lineColor,
stroke: lineColor,
r: 2,
},
}}
/>
</Guide>
);
}
function renderClassicAreaGeom(layout: LayoutResult, sparkline: BigValueSparkline) {
const lineStyle: any = {
opacity: 1,
fillOpacity: 1,
};
const fillColor = tinycolor(layout.valueColor)
.setAlpha(0.2)
.toRgbString();
lineStyle.stroke = layout.valueColor;
return (
<>
<Geom type="area" position="time*value" size={0} color={fillColor} style={lineStyle} shape="smooth" />
<Geom type="line" position="time*value" size={1} color={layout.valueColor} style={lineStyle} shape="smooth" />
{highlightPoint('#EEE', sparkline)}
</>
);
}
/* function renderAreaGeom(layout: LayoutResult) { */
/* const lineStyle: any = { */
/* opacity: 1, */
/* fillOpacity: 1, */
/* }; */
/* */
/* const color1 = tinycolor(layout.valueColor) */
/* .darken(0) */
/* .spin(20) */
/* .toRgbString(); */
/* const color2 = tinycolor(layout.valueColor) */
/* .lighten(0) */
/* .spin(-20) */
/* .toRgbString(); */
/* */
/* const fillColor = `l (0) 0:${color1} 1:${color2}`; */
/* */
/* return <Geom type="area" position="time*value" size={0} color={fillColor} style={lineStyle} shape="smooth" />; */
/* } */

View File

@ -1,290 +0,0 @@
// Libraries
import { CSSProperties } from 'react';
import tinycolor from 'tinycolor2';
// Utils
import { getColorFromHexRgbOrName, GrafanaTheme, formattedValueToString } from '@grafana/data';
import { calculateFontSize } from '../../utils/measureText';
// Types
import { BigValueColorMode, BigValueGraphMode, Props, BigValueJustifyMode } from './BigValue';
const LINE_HEIGHT = 1.2;
export interface LayoutResult {
titleFontSize: number;
valueFontSize: number;
chartHeight: number;
chartWidth: number;
type: LayoutType;
width: number;
height: number;
colorMode: BigValueColorMode;
graphMode: BigValueGraphMode;
theme: GrafanaTheme;
valueColor: string;
panelPadding: number;
justifyCenter: boolean;
}
export enum LayoutType {
Stacked,
StackedNoChart,
Wide,
WideNoChart,
}
export function shouldJustifyCenter(props: Props) {
const { value, justifyMode } = props;
if (justifyMode === BigValueJustifyMode.Center) {
return true;
}
return (value.title ?? '').length === 0;
}
export function calculateLayout(props: Props): LayoutResult {
const { width, height, sparkline, colorMode, theme, value, graphMode, alignmentFactors } = props;
const useWideLayout = width / height > 2.5;
const valueColor = getColorFromHexRgbOrName(value.color || 'green', theme.type);
const justifyCenter = shouldJustifyCenter(props);
const panelPadding = height > 100 ? 12 : 8;
const titleToAlignTo = alignmentFactors ? alignmentFactors.title : value.title;
const valueToAlignTo = formattedValueToString(alignmentFactors ? alignmentFactors : value);
const maxTitleFontSize = 30;
const maxTextWidth = width - panelPadding * 2;
const maxTextHeight = height - panelPadding * 2;
let layoutType = LayoutType.Stacked;
let chartHeight = 0;
let chartWidth = 0;
let titleHeight = 0;
let titleFontSize = 0;
let valueFontSize = 14;
if (useWideLayout) {
// Detect auto wide layout type
layoutType = height > 50 && !!sparkline ? LayoutType.Wide : LayoutType.WideNoChart;
// Wide no chart mode
if (layoutType === LayoutType.WideNoChart) {
const valueWidthPercent = 0.3;
if (titleToAlignTo && titleToAlignTo.length > 0) {
// initial value size
valueFontSize = calculateFontSize(valueToAlignTo, maxTextWidth * valueWidthPercent, maxTextHeight, LINE_HEIGHT);
// How big can we make the title and still have it fit
titleFontSize = calculateFontSize(
titleToAlignTo,
maxTextWidth * 0.6,
maxTextHeight,
LINE_HEIGHT,
maxTitleFontSize
);
// make sure it's a bit smaller than valueFontSize
titleFontSize = Math.min(valueFontSize * 0.7, titleFontSize);
titleHeight = titleFontSize * LINE_HEIGHT;
} else {
// if no title wide
valueFontSize = calculateFontSize(valueToAlignTo, maxTextWidth, maxTextHeight, LINE_HEIGHT);
}
} else {
// wide with chart
const chartHeightPercent = 0.5;
const titleWidthPercent = 0.6;
const valueWidthPercent = 1 - titleWidthPercent;
const textHeightPercent = 0.4;
chartWidth = width;
chartHeight = height * chartHeightPercent;
if (titleToAlignTo && titleToAlignTo.length > 0) {
titleFontSize = calculateFontSize(
titleToAlignTo,
maxTextWidth * titleWidthPercent,
maxTextHeight * textHeightPercent,
LINE_HEIGHT,
maxTitleFontSize
);
titleHeight = titleFontSize * LINE_HEIGHT;
}
valueFontSize = calculateFontSize(
valueToAlignTo,
maxTextWidth * valueWidthPercent,
maxTextHeight * chartHeightPercent,
LINE_HEIGHT
);
}
} else {
// Stacked layout (title, value, chart)
const titleHeightPercent = 0.15;
const chartHeightPercent = 0.25;
// Does a chart fit or exist?
if (height < 100 || !sparkline) {
layoutType = LayoutType.StackedNoChart;
} else {
// we have chart
chartHeight = height * chartHeightPercent;
chartWidth = width;
}
if (titleToAlignTo && titleToAlignTo.length > 0) {
titleFontSize = calculateFontSize(
titleToAlignTo,
maxTextWidth,
height * titleHeightPercent,
LINE_HEIGHT,
maxTitleFontSize
);
titleHeight = titleFontSize * LINE_HEIGHT;
}
valueFontSize = calculateFontSize(
valueToAlignTo,
maxTextWidth,
maxTextHeight - chartHeight - titleHeight,
LINE_HEIGHT
);
// make title fontsize it's a bit smaller than valueFontSize
titleFontSize = Math.min(valueFontSize * 0.7, titleFontSize);
}
return {
valueFontSize,
titleFontSize,
chartHeight,
chartWidth,
type: layoutType,
width,
height,
colorMode,
graphMode,
theme,
valueColor,
justifyCenter,
panelPadding,
};
}
export function getTitleStyles(layout: LayoutResult) {
const styles: CSSProperties = {
fontSize: `${layout.titleFontSize}px`,
textShadow: '#333 0px 0px 1px',
color: '#EEE',
lineHeight: LINE_HEIGHT,
};
if (layout.theme.isLight) {
styles.color = 'white';
}
return styles;
}
export function getValueStyles(layout: LayoutResult) {
const styles: CSSProperties = {
fontSize: layout.valueFontSize,
color: '#EEE',
textShadow: '#333 0px 0px 1px',
fontWeight: 500,
lineHeight: LINE_HEIGHT,
};
switch (layout.colorMode) {
case BigValueColorMode.Value:
styles.color = layout.valueColor;
}
return styles;
}
export function getValueAndTitleContainerStyles(layout: LayoutResult): CSSProperties {
const styles: CSSProperties = {
display: 'flex',
};
switch (layout.type) {
case LayoutType.Wide:
styles.flexDirection = 'row';
styles.justifyContent = 'space-between';
styles.flexGrow = 1;
break;
case LayoutType.WideNoChart:
styles.flexDirection = 'row';
styles.justifyContent = 'space-between';
styles.alignItems = 'center';
styles.flexGrow = 1;
break;
case LayoutType.StackedNoChart:
styles.flexDirection = 'column';
styles.flexGrow = 1;
break;
case LayoutType.Stacked:
default:
styles.flexDirection = 'column';
styles.justifyContent = 'center';
}
if (layout.justifyCenter) {
styles.alignItems = 'center';
styles.justifyContent = 'center';
styles.flexGrow = 1;
}
return styles;
}
export function getPanelStyles(layout: LayoutResult) {
const panelStyles: CSSProperties = {
width: `${layout.width}px`,
height: `${layout.height}px`,
padding: `${layout.panelPadding}px`,
borderRadius: '3px',
position: 'relative',
display: 'flex',
};
const themeFactor = layout.theme.isDark ? 1 : -0.7;
switch (layout.colorMode) {
case BigValueColorMode.Background:
const bgColor2 = tinycolor(layout.valueColor)
.darken(15 * themeFactor)
.spin(8)
.toRgbString();
const bgColor3 = tinycolor(layout.valueColor)
.darken(5 * themeFactor)
.spin(-8)
.toRgbString();
panelStyles.background = `linear-gradient(120deg, ${bgColor2}, ${bgColor3})`;
break;
case BigValueColorMode.Value:
panelStyles.background = `${layout.theme.colors.dark4}`;
break;
}
switch (layout.type) {
case LayoutType.Stacked:
panelStyles.flexDirection = 'column';
break;
case LayoutType.StackedNoChart:
panelStyles.alignItems = 'center';
break;
case LayoutType.Wide:
panelStyles.flexDirection = 'row';
panelStyles.justifyContent = 'space-between';
break;
case LayoutType.WideNoChart:
panelStyles.alignItems = 'center';
break;
}
if (layout.justifyCenter) {
panelStyles.alignItems = 'center';
panelStyles.flexDirection = 'row';
}
return panelStyles;
}

View File

@ -33,8 +33,10 @@ export class StatPanel extends PureComponent<PanelProps<StatPanelOptions>> {
if (value.sparkline) {
sparkline = {
data: value.sparkline,
minX: timeRange.from.valueOf(),
maxX: timeRange.to.valueOf(),
xMin: timeRange.from.valueOf(),
xMax: timeRange.to.valueOf(),
yMin: value.field.min,
yMax: value.field.max,
};
const calc = options.fieldOptions.calcs[0];
@ -75,6 +77,7 @@ export class StatPanel extends PureComponent<PanelProps<StatPanelOptions>> {
theme: config.theme,
data: data.series,
sparkline: options.graphMode !== BigValueGraphMode.None,
autoMinMax: true,
});
};