mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Panels: Progress on new singlestat / BigValue (#19374)
* POC: friday hack * exploring new singlestat styles * minor changes * Testing bizcharts * style tweaks * Updated * minor progress * updated * Updated layout handling * Updated editor * added editor options * adding mode * progress on new display mode * tweaks * Added classic style * Added final mode * Minor tweak * tweaks * minor tweak * Singlestat: Adjust colors for light theme * fixed build issues with bizcharts * fixed typescript issue * updated snapshot * Added demo dashboard
This commit is contained in:
@@ -29,6 +29,7 @@
|
||||
"@grafana/slate-react": "0.22.9-grafana",
|
||||
"@torkelo/react-select": "2.1.1",
|
||||
"@types/react-color": "2.17.0",
|
||||
"bizcharts": "^3.5.5",
|
||||
"@types/slate": "0.47.1",
|
||||
"@types/slate-react": "0.22.5",
|
||||
"classnames": "2.2.6",
|
||||
|
||||
@@ -57,6 +57,7 @@ const buildCjsPackage = ({ env }) => {
|
||||
],
|
||||
'node_modules/immutable/dist/immutable.js': ['Record', 'Set', 'Map', 'List', 'OrderedSet', 'is', 'Stack'],
|
||||
'../../node_modules/esrever/esrever.js': ['reverse'],
|
||||
'../../node_modules/bizcharts/es6/index.js': ['Chart', 'Geom', 'View', 'Tooltip', 'Legend'],
|
||||
},
|
||||
}),
|
||||
resolve(),
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { number, text } from '@storybook/addon-knobs';
|
||||
import { BigValue } from './BigValue';
|
||||
import { text } from '@storybook/addon-knobs';
|
||||
import { BigValue, SingleStatDisplayMode } from './BigValue';
|
||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||
import { renderComponentWithTheme } from '../../utils/storybook/withTheme';
|
||||
|
||||
const getKnobs = () => {
|
||||
return {
|
||||
value: text('value', 'Hello'),
|
||||
valueFontSize: number('valueFontSize', 120),
|
||||
prefix: text('prefix', ''),
|
||||
value: text('value', '$5022'),
|
||||
title: text('title', 'Total Earnings'),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -16,22 +15,37 @@ const BigValueStories = storiesOf('UI/BigValue', module);
|
||||
|
||||
BigValueStories.addDecorator(withCenteredStory);
|
||||
|
||||
BigValueStories.add('Singlestat viz', () => {
|
||||
const { value, prefix, valueFontSize } = getKnobs();
|
||||
interface StoryOptions {
|
||||
mode: SingleStatDisplayMode;
|
||||
width?: number;
|
||||
height?: number;
|
||||
noSparkline?: boolean;
|
||||
}
|
||||
|
||||
return renderComponentWithTheme(BigValue, {
|
||||
width: 300,
|
||||
height: 250,
|
||||
value: {
|
||||
text: value,
|
||||
numeric: NaN,
|
||||
fontSize: valueFontSize + '%',
|
||||
},
|
||||
prefix: prefix
|
||||
? {
|
||||
text: prefix,
|
||||
numeric: NaN,
|
||||
}
|
||||
: null,
|
||||
function addStoryForMode(options: StoryOptions) {
|
||||
BigValueStories.add(`Mode: ${SingleStatDisplayMode[options.mode]}`, () => {
|
||||
const { value, title } = getKnobs();
|
||||
|
||||
return renderComponentWithTheme(BigValue, {
|
||||
width: options.width || 400,
|
||||
height: options.height || 300,
|
||||
displayMode: options.mode,
|
||||
value: {
|
||||
text: value,
|
||||
numeric: 5022,
|
||||
color: 'red',
|
||||
title,
|
||||
},
|
||||
sparkline: {
|
||||
minX: 0,
|
||||
maxX: 5,
|
||||
data: [[0, 10], [1, 20], [2, 15], [3, 25], [4, 5], [5, 10]],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
addStoryForMode({ mode: SingleStatDisplayMode.Classic });
|
||||
addStoryForMode({ mode: SingleStatDisplayMode.Classic2 });
|
||||
addStoryForMode({ mode: SingleStatDisplayMode.Vibrant });
|
||||
addStoryForMode({ mode: SingleStatDisplayMode.Vibrant2 });
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { BigValue, Props } from './BigValue';
|
||||
import { BigValue, Props, SingleStatDisplayMode } from './BigValue';
|
||||
import { getTheme } from '../../themes/index';
|
||||
|
||||
jest.mock('jquery', () => ({
|
||||
@@ -11,6 +11,7 @@ const setup = (propOverrides?: object) => {
|
||||
const props: Props = {
|
||||
height: 300,
|
||||
width: 300,
|
||||
displayMode: SingleStatDisplayMode.Classic,
|
||||
value: {
|
||||
text: '25',
|
||||
numeric: 25,
|
||||
@@ -29,7 +30,7 @@ const setup = (propOverrides?: object) => {
|
||||
};
|
||||
};
|
||||
|
||||
describe('Render BarGauge with basic options', () => {
|
||||
describe('Render SingleStat with basic options', () => {
|
||||
it('should render', () => {
|
||||
const { wrapper } = setup();
|
||||
expect(wrapper).toBeDefined();
|
||||
|
||||
@@ -1,168 +1,413 @@
|
||||
// Library
|
||||
import React, { PureComponent, ReactNode, CSSProperties } from 'react';
|
||||
import $ from 'jquery';
|
||||
import { css, cx } from 'emotion';
|
||||
import React, { PureComponent, CSSProperties } from 'react';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { Chart, Geom } from 'bizcharts';
|
||||
import { DisplayValue } from '@grafana/data';
|
||||
|
||||
// Utils
|
||||
import { getColorFromHexRgbOrName } from '../../utils';
|
||||
|
||||
// Types
|
||||
import { Themeable } from '../../types';
|
||||
import { stylesFactory } from '../../themes/stylesFactory';
|
||||
import { Themeable, GrafanaTheme } from '../../types';
|
||||
|
||||
export interface BigValueSparkline {
|
||||
data: any[][]; // [[number,number]]
|
||||
data: any[][];
|
||||
minX: number;
|
||||
maxX: number;
|
||||
full: boolean; // full height
|
||||
fillColor: string;
|
||||
lineColor: string;
|
||||
}
|
||||
|
||||
export enum SingleStatDisplayMode {
|
||||
Classic,
|
||||
Classic2,
|
||||
Vibrant,
|
||||
Vibrant2,
|
||||
}
|
||||
|
||||
export interface Props extends Themeable {
|
||||
height: number;
|
||||
width: number;
|
||||
value: DisplayValue;
|
||||
prefix?: DisplayValue;
|
||||
suffix?: DisplayValue;
|
||||
sparkline?: BigValueSparkline;
|
||||
backgroundColor?: string;
|
||||
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||
className?: string;
|
||||
displayMode: SingleStatDisplayMode;
|
||||
}
|
||||
|
||||
const getStyles = stylesFactory(() => {
|
||||
return {
|
||||
wrapper: css`
|
||||
position: 'relative';
|
||||
display: 'table';
|
||||
`,
|
||||
title: css`
|
||||
line-height: 1;
|
||||
text-align: 'center';
|
||||
z-index: 1;
|
||||
display: 'block';
|
||||
width: '100%';
|
||||
position: 'absolute';
|
||||
`,
|
||||
value: css`
|
||||
line-height: 1;
|
||||
text-align: 'center';
|
||||
z-index: 1;
|
||||
display: 'table-cell';
|
||||
vertical-align: 'middle';
|
||||
position: 'relative';
|
||||
font-size: '3em';
|
||||
font-weight: 500;
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
/*
|
||||
* This visualization is still in POC state, needed more tests & better structure
|
||||
*/
|
||||
export class BigValue extends PureComponent<Props> {
|
||||
canvasElement: any;
|
||||
|
||||
componentDidMount() {
|
||||
this.draw();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.draw();
|
||||
}
|
||||
|
||||
draw() {
|
||||
const { sparkline, theme } = this.props;
|
||||
|
||||
if (sparkline && this.canvasElement) {
|
||||
const { data, minX, maxX, fillColor, lineColor } = sparkline;
|
||||
|
||||
const options = {
|
||||
legend: { show: false },
|
||||
series: {
|
||||
lines: {
|
||||
show: true,
|
||||
fill: 1,
|
||||
zero: false,
|
||||
lineWidth: 1,
|
||||
fillColor: getColorFromHexRgbOrName(fillColor, theme.type),
|
||||
},
|
||||
},
|
||||
yaxes: { show: false },
|
||||
xaxis: {
|
||||
show: false,
|
||||
min: minX,
|
||||
max: maxX,
|
||||
},
|
||||
grid: { hoverable: false, show: false },
|
||||
};
|
||||
|
||||
const plotSeries = {
|
||||
data,
|
||||
color: getColorFromHexRgbOrName(lineColor, theme.type),
|
||||
};
|
||||
|
||||
try {
|
||||
$.plot(this.canvasElement, [plotSeries], options);
|
||||
} catch (err) {
|
||||
console.log('sparkline rendering error', err, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
renderText = (value?: DisplayValue, padding?: string): ReactNode => {
|
||||
if (!value || !value.text) {
|
||||
return null;
|
||||
}
|
||||
const css: CSSProperties = {};
|
||||
if (padding) {
|
||||
css.padding = padding;
|
||||
}
|
||||
if (value.color) {
|
||||
css.color = value.color;
|
||||
}
|
||||
if (value.fontSize) {
|
||||
css.fontSize = value.fontSize;
|
||||
}
|
||||
|
||||
return <span style={css}>{value.text}</span>;
|
||||
};
|
||||
|
||||
renderSparkline(sparkline: BigValueSparkline) {
|
||||
const { height, width } = this.props;
|
||||
|
||||
const plotCss: CSSProperties = {};
|
||||
plotCss.position = 'absolute';
|
||||
plotCss.bottom = '0px';
|
||||
plotCss.left = '0px';
|
||||
plotCss.width = width + 'px';
|
||||
|
||||
if (sparkline.full) {
|
||||
const dynamicHeightMargin = height <= 100 ? 5 : Math.round(height / 100) * 15 + 5;
|
||||
plotCss.height = height - dynamicHeightMargin + 'px';
|
||||
} else {
|
||||
plotCss.height = Math.floor(height * 0.25) + 'px';
|
||||
}
|
||||
return <div style={plotCss} ref={element => (this.canvasElement = element)} />;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { height, width, value, prefix, suffix, sparkline, backgroundColor, onClick, className } = this.props;
|
||||
const styles = getStyles();
|
||||
const { value, onClick, className, sparkline } = this.props;
|
||||
|
||||
const layout = calculateLayout(this.props);
|
||||
const panelStyles = getPanelStyles(layout);
|
||||
const valueAndTitleContainerStyles = getValueAndTitleContainerStyles(layout);
|
||||
const valueStyles = getValueStyles(layout);
|
||||
const titleStyles = getTitleStyles(layout);
|
||||
|
||||
return (
|
||||
<div className={cx(styles.wrapper, className)} style={{ width, height, backgroundColor }} onClick={onClick}>
|
||||
{value.title && <div className={styles.title}>{value.title}</div>}
|
||||
|
||||
<span className={styles.value}>
|
||||
{this.renderText(prefix, '0px 2px 0px 0px')}
|
||||
{this.renderText(value)}
|
||||
{this.renderText(suffix)}
|
||||
</span>
|
||||
|
||||
{sparkline && this.renderSparkline(sparkline)}
|
||||
<div className={className} style={panelStyles} onClick={onClick}>
|
||||
<div style={valueAndTitleContainerStyles}>
|
||||
{value.title && <div style={titleStyles}>{value.title}</div>}
|
||||
<div style={valueStyles}>{value.text}</div>
|
||||
</div>
|
||||
{renderGraph(layout, sparkline)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const MIN_VALUE_FONT_SIZE = 20;
|
||||
const MAX_VALUE_FONT_SIZE = 50;
|
||||
const MIN_TITLE_FONT_SIZE = 14;
|
||||
const TITLE_VALUE_RATIO = 0.45;
|
||||
const VALUE_HEIGHT_RATIO = 0.25;
|
||||
const VALUE_HEIGHT_RATIO_WIDE = 0.3;
|
||||
const LINE_HEIGHT = 1.2;
|
||||
const PANEL_PADDING = 16;
|
||||
const CHART_TOP_MARGIN = 8;
|
||||
|
||||
interface LayoutResult {
|
||||
titleFontSize: number;
|
||||
valueFontSize: number;
|
||||
chartHeight: number;
|
||||
chartWidth: number;
|
||||
type: LayoutType;
|
||||
width: number;
|
||||
height: number;
|
||||
displayMode: SingleStatDisplayMode;
|
||||
theme: GrafanaTheme;
|
||||
valueColor: string;
|
||||
}
|
||||
|
||||
enum LayoutType {
|
||||
Stacked,
|
||||
StackedNoChart,
|
||||
Wide,
|
||||
WideNoChart,
|
||||
}
|
||||
|
||||
export function calculateLayout(props: Props): LayoutResult {
|
||||
const { width, height, sparkline, displayMode, theme, value } = props;
|
||||
const useWideLayout = width / height > 2.8;
|
||||
const valueColor = getColorFromHexRgbOrName(value.color || 'green', theme.type);
|
||||
|
||||
// handle wide layouts
|
||||
if (useWideLayout) {
|
||||
const valueFontSize = Math.min(
|
||||
Math.max(height * VALUE_HEIGHT_RATIO_WIDE, MIN_VALUE_FONT_SIZE),
|
||||
MAX_VALUE_FONT_SIZE
|
||||
);
|
||||
const titleFontSize = Math.max(valueFontSize * TITLE_VALUE_RATIO, MIN_TITLE_FONT_SIZE);
|
||||
|
||||
const chartHeight = height - PANEL_PADDING * 2;
|
||||
const chartWidth = width / 2;
|
||||
let type = !!sparkline ? LayoutType.Wide : LayoutType.WideNoChart;
|
||||
|
||||
if (height < 80 || !sparkline) {
|
||||
type = LayoutType.WideNoChart;
|
||||
}
|
||||
|
||||
return {
|
||||
valueFontSize,
|
||||
titleFontSize,
|
||||
chartHeight,
|
||||
chartWidth,
|
||||
type,
|
||||
width,
|
||||
height,
|
||||
displayMode,
|
||||
theme,
|
||||
valueColor,
|
||||
};
|
||||
}
|
||||
|
||||
// handle stacked layouts
|
||||
const valueFontSize = Math.min(Math.max(height * VALUE_HEIGHT_RATIO, MIN_VALUE_FONT_SIZE), MAX_VALUE_FONT_SIZE);
|
||||
const titleFontSize = Math.max(valueFontSize * TITLE_VALUE_RATIO, MIN_TITLE_FONT_SIZE);
|
||||
const valueHeight = valueFontSize * LINE_HEIGHT;
|
||||
const titleHeight = titleFontSize * LINE_HEIGHT;
|
||||
|
||||
let chartHeight = height - valueHeight - titleHeight - PANEL_PADDING * 2 - CHART_TOP_MARGIN;
|
||||
let chartWidth = width - PANEL_PADDING * 2;
|
||||
let type = LayoutType.Stacked;
|
||||
|
||||
if (height < 100 || !sparkline) {
|
||||
type = LayoutType.StackedNoChart;
|
||||
}
|
||||
|
||||
switch (displayMode) {
|
||||
case SingleStatDisplayMode.Vibrant2:
|
||||
case SingleStatDisplayMode.Classic:
|
||||
case SingleStatDisplayMode.Classic2:
|
||||
chartWidth = width;
|
||||
chartHeight += PANEL_PADDING;
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
valueFontSize,
|
||||
titleFontSize,
|
||||
chartHeight,
|
||||
chartWidth,
|
||||
type,
|
||||
width,
|
||||
height,
|
||||
displayMode,
|
||||
theme,
|
||||
valueColor,
|
||||
};
|
||||
}
|
||||
|
||||
export function getTitleStyles(layout: LayoutResult) {
|
||||
const styles: CSSProperties = {
|
||||
fontSize: `${layout.titleFontSize}px`,
|
||||
textShadow: '#333 1px 1px 5px',
|
||||
color: '#EEE',
|
||||
};
|
||||
|
||||
if (layout.theme.isLight) {
|
||||
styles.color = 'white';
|
||||
}
|
||||
|
||||
return styles;
|
||||
}
|
||||
|
||||
export function getValueStyles(layout: LayoutResult) {
|
||||
const styles: CSSProperties = {
|
||||
fontSize: `${layout.valueFontSize}px`,
|
||||
color: '#EEE',
|
||||
textShadow: '#333 1px 1px 5px',
|
||||
lineHeight: LINE_HEIGHT,
|
||||
};
|
||||
|
||||
switch (layout.displayMode) {
|
||||
case SingleStatDisplayMode.Classic:
|
||||
case SingleStatDisplayMode.Classic2:
|
||||
styles.color = layout.valueColor;
|
||||
}
|
||||
|
||||
return styles;
|
||||
}
|
||||
|
||||
export function getValueAndTitleContainerStyles(layout: LayoutResult): CSSProperties {
|
||||
switch (layout.type) {
|
||||
case LayoutType.Wide:
|
||||
return {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
};
|
||||
case LayoutType.WideNoChart:
|
||||
return {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
flexGrow: 1,
|
||||
};
|
||||
case LayoutType.StackedNoChart:
|
||||
return {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
};
|
||||
case LayoutType.Stacked:
|
||||
default:
|
||||
return {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function getPanelStyles(layout: LayoutResult) {
|
||||
const panelStyles: CSSProperties = {
|
||||
width: `${layout.width}px`,
|
||||
height: `${layout.height}px`,
|
||||
padding: `${PANEL_PADDING}px`,
|
||||
borderRadius: '3px',
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
};
|
||||
|
||||
const themeFactor = layout.theme.isDark ? 1 : -0.7;
|
||||
|
||||
switch (layout.displayMode) {
|
||||
case SingleStatDisplayMode.Vibrant:
|
||||
case SingleStatDisplayMode.Vibrant2:
|
||||
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 SingleStatDisplayMode.Classic:
|
||||
case SingleStatDisplayMode.Classic2:
|
||||
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.alignItems = 'center';
|
||||
panelStyles.justifyContent = 'space-between';
|
||||
break;
|
||||
case LayoutType.WideNoChart:
|
||||
panelStyles.alignItems = 'center';
|
||||
break;
|
||||
}
|
||||
|
||||
return panelStyles;
|
||||
}
|
||||
|
||||
function renderGraph(layout: LayoutResult, sparkline?: BigValueSparkline) {
|
||||
if (!sparkline) {
|
||||
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 = {
|
||||
marginTop: `${CHART_TOP_MARGIN}`,
|
||||
};
|
||||
|
||||
// default to line graph
|
||||
let geomRender = renderLineGeom;
|
||||
|
||||
switch (layout.type) {
|
||||
case LayoutType.Wide:
|
||||
chartStyles.width = `${layout.chartWidth}px`;
|
||||
chartStyles.height = `${layout.chartHeight}px`;
|
||||
break;
|
||||
case LayoutType.Stacked:
|
||||
chartStyles.position = 'relative';
|
||||
chartStyles.top = '8px';
|
||||
break;
|
||||
case LayoutType.WideNoChart:
|
||||
case LayoutType.StackedNoChart:
|
||||
return null;
|
||||
}
|
||||
|
||||
if (layout.chartWidth === layout.width) {
|
||||
chartStyles.position = 'absolute';
|
||||
chartStyles.bottom = 0;
|
||||
chartStyles.right = 0;
|
||||
chartStyles.left = 0;
|
||||
chartStyles.right = 0;
|
||||
chartStyles.top = 'unset';
|
||||
}
|
||||
|
||||
switch (layout.displayMode) {
|
||||
case SingleStatDisplayMode.Vibrant2:
|
||||
geomRender = renderVibrant2Geom;
|
||||
break;
|
||||
case SingleStatDisplayMode.Classic:
|
||||
geomRender = renderClassicAreaGeom;
|
||||
break;
|
||||
case SingleStatDisplayMode.Classic2:
|
||||
geomRender = renderAreaGeom;
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<Chart
|
||||
height={layout.chartHeight}
|
||||
width={layout.chartWidth}
|
||||
data={data}
|
||||
animate={false}
|
||||
padding={[4, 0, 0, 0]}
|
||||
scale={scales}
|
||||
style={chartStyles}
|
||||
>
|
||||
{geomRender(layout)}
|
||||
</Chart>
|
||||
);
|
||||
}
|
||||
|
||||
function renderLineGeom(layout: LayoutResult) {
|
||||
const lineStyle: any = {
|
||||
stroke: '#CCC',
|
||||
lineWidth: 2,
|
||||
shadowBlur: 15,
|
||||
shadowColor: '#444',
|
||||
shadowOffsetY: 7,
|
||||
};
|
||||
|
||||
return <Geom type="line" position="time*value" size={2} color="white" style={lineStyle} shape="smooth" />;
|
||||
}
|
||||
|
||||
function renderVibrant2Geom(layout: LayoutResult) {
|
||||
const lineStyle: any = {
|
||||
stroke: '#CCC',
|
||||
lineWidth: 2,
|
||||
shadowBlur: 15,
|
||||
shadowColor: '#444',
|
||||
shadowOffsetY: -5,
|
||||
};
|
||||
|
||||
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="white" style={lineStyle} shape="smooth" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function renderClassicAreaGeom(layout: LayoutResult) {
|
||||
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" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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" />;
|
||||
}
|
||||
|
||||
@@ -164,6 +164,8 @@ exports[`Render should render with base threshold 1`] = `
|
||||
"md": "32px",
|
||||
"sm": "24px",
|
||||
},
|
||||
"isDark": true,
|
||||
"isLight": false,
|
||||
"name": "Grafana Dark",
|
||||
"panelHeaderHeight": 28,
|
||||
"panelPadding": 8,
|
||||
@@ -331,6 +333,8 @@ exports[`Render should render with base threshold 1`] = `
|
||||
"md": "32px",
|
||||
"sm": "24px",
|
||||
},
|
||||
"isDark": true,
|
||||
"isLight": false,
|
||||
"name": "Grafana Dark",
|
||||
"panelHeaderHeight": 28,
|
||||
"panelPadding": 8,
|
||||
|
||||
@@ -46,7 +46,7 @@ export { Table } from './Table/Table';
|
||||
export { TableInputCSV } from './Table/TableInputCSV';
|
||||
|
||||
// Visualizations
|
||||
export { BigValue } from './BigValue/BigValue';
|
||||
export { BigValue, SingleStatDisplayMode } from './BigValue/BigValue';
|
||||
export { Gauge } from './Gauge/Gauge';
|
||||
export { Graph } from './Graph/Graph';
|
||||
export { GraphLegend } from './Graph/GraphLegend';
|
||||
|
||||
@@ -42,6 +42,8 @@ const basicColors = {
|
||||
const darkTheme: GrafanaTheme = {
|
||||
...defaultTheme,
|
||||
type: GrafanaThemeType.Dark,
|
||||
isDark: true,
|
||||
isLight: false,
|
||||
name: 'Grafana Dark',
|
||||
colors: {
|
||||
...basicColors,
|
||||
|
||||
@@ -42,6 +42,8 @@ const basicColors = {
|
||||
const lightTheme: GrafanaTheme = {
|
||||
...defaultTheme,
|
||||
type: GrafanaThemeType.Light,
|
||||
isDark: false,
|
||||
isLight: true,
|
||||
name: 'Grafana Light',
|
||||
colors: {
|
||||
...basicColors,
|
||||
|
||||
@@ -92,6 +92,8 @@ export interface GrafanaThemeCommons {
|
||||
|
||||
export interface GrafanaTheme extends GrafanaThemeCommons {
|
||||
type: GrafanaThemeType;
|
||||
isDark: boolean;
|
||||
isLight: boolean;
|
||||
background: {
|
||||
dropdown: string;
|
||||
scrollbar: string;
|
||||
|
||||
Reference in New Issue
Block a user