storybook/stories/metric/1_basic.story.tsx (160 lines of code) (raw):
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EuiIcon } from '@elastic/eui';
import { action } from '@storybook/addon-actions';
import { select, boolean, text, color, number } from '@storybook/addon-knobs';
import React from 'react';
import type { MetricWProgress, MetricWTrend, MetricWText, MetricWNumber } from '@elastic/charts';
import { Chart, isMetricElementEvent, Metric, MetricTrendShape, Settings } from '@elastic/charts';
import { KIBANA_METRICS } from '@elastic/charts/src/utils/data_samples/test_dataset_kibana';
import type { ChartsStory } from '../../types';
import { useBaseTheme } from '../../use_base_theme';
import { customKnobs } from '../utils/knobs';
type TextAlign = 'left' | 'center' | 'right';
const getTextAlignKnob = (name: string, defaultValue: TextAlign): TextAlign =>
select(
name,
{
Left: 'left',
Center: 'center',
Right: 'right',
},
defaultValue,
);
export const Example: ChartsStory = (_, { title: storyTitle, description }) => {
const title = text('title', '21d7f8b7-92ea-41a0-8c03-0db0ec7e11b9');
const subtitle = text('subtitle', 'Cluster CPU usage');
const progressOrTrend = select(
'progress or trend',
{
trend: 'trend',
bar: 'bar',
none: 'none',
},
'trend',
);
const progressBarDirection = select(
'progress bar direction',
{ horizontal: 'horizontal', vertical: 'vertical' },
'vertical',
);
const maxDataPoints = number('trend data points', 30, { min: 0, max: 50, step: 1 });
const trendShape = customKnobs.fromEnum('trend shape', MetricTrendShape, MetricTrendShape.Area);
const trendA11yTitle = text('trend a11y title', 'The Cluster CPU Usage trend');
const trendA11yDescription = text(
'trend a11y description',
'The trend shows a peak of CPU usage in the last 5 minutes',
);
let extra = text('extra', 'last <b>5m</b>');
const progressMax = number('progress max', 100);
const numberTextSwitch = boolean('is numeric metric', true);
const value = text('value', '55.23');
const valuePrefix = text('value prefix', '');
const valuePostfix = text('value postfix', ' %');
const metricColor = color('color', '#3c3c3c');
const useValueColor = boolean('use value color', false);
const valueColor = color('value color', '#3c3c3c');
extra = extra.replace('<b>', '<b>');
extra = extra.replace('</b>', '</b>');
const showIcon = boolean('show icon', false);
const iconType = customKnobs.eui.getIconTypeKnob('EUI icon glyph name', 'warning');
const showValueIcon = boolean('show value icon', false);
const valueIconType = customKnobs.eui.getIconTypeKnob('EUI value icon glyph name', 'sortUp');
const useBlendingBackground = boolean('use blending background', false);
const blendingBackground = color('blending background', 'rgba(255,255,255,1)');
const valueFontSizeMode = select(
'value font mode',
{
Default: 'default',
Fit: 'fit',
Custom: 'custom',
},
'default',
);
const valueFontSize = number('value font size (px)', 40, { min: 0, step: 10 });
const titlesTextAlign = getTextAlignKnob('title text-align', 'left');
const valuesTextAlign = getTextAlignKnob('values text-align', 'right');
const iconAlign = select(
'icon align',
{
Left: 'left',
Right: 'right',
},
'right',
);
const getIcon =
(type: string) =>
({ width, height, color }: { width: number; height: number; color: string }) => (
<EuiIcon type={type} width={width} height={height} fill={color} style={{ width, height }} />
);
const data = {
color: metricColor,
title,
valueColor: useValueColor ? valueColor : undefined,
subtitle,
extra: <span dangerouslySetInnerHTML={{ __html: extra }}></span>,
...(showIcon ? { icon: getIcon(iconType) } : {}),
...(showValueIcon ? { valueIcon: getIcon(valueIconType) } : {}),
};
const numericData: MetricWProgress | MetricWNumber | MetricWTrend = {
...data,
value: Number.parseFloat(value),
valueFormatter: (d: number) => `${valuePrefix}${d}${valuePostfix}`,
...(progressOrTrend === 'bar' ? { domainMax: progressMax, progressBarDirection } : {}),
...(progressOrTrend === 'trend'
? {
trend: KIBANA_METRICS.metrics.kibana_os_load.v2.data.slice(0, maxDataPoints).map(([x, y]) => ({ x, y })),
trendShape,
trendA11yTitle,
trendA11yDescription,
}
: {}),
};
const textualData: MetricWText | MetricWTrend = {
...data,
value,
...(progressOrTrend === 'bar' ? { domainMax: progressMax, progressBarDirection } : {}),
...(progressOrTrend === 'trend'
? {
trend: KIBANA_METRICS.metrics.kibana_os_load.v2.data.slice(0, maxDataPoints).map(([x, y]) => ({ x, y })),
trendShape,
trendA11yTitle,
trendA11yDescription,
}
: {}),
};
const onEventClickAction = action('click');
const onEventOverAction = action('over');
const onEventOutAction = action('out');
const configuredData = [[numberTextSwitch ? numericData : textualData]];
return (
<Chart title={storyTitle} description={description}>
<Settings
theme={{
metric: {
blendingBackground: useBlendingBackground ? blendingBackground : undefined,
valueFontSize: valueFontSizeMode === 'custom' ? valueFontSize : valueFontSizeMode,
titlesTextAlign,
valuesTextAlign,
iconAlign,
},
}}
baseTheme={useBaseTheme()}
onElementClick={([d]) => {
if (isMetricElementEvent(d)) {
const { rowIndex, columnIndex } = d;
onEventClickAction(
`row:${rowIndex} col:${columnIndex} value:${configuredData[rowIndex][columnIndex].value}`,
);
}
}}
onElementOver={([d]) => {
if (isMetricElementEvent(d)) {
const { rowIndex, columnIndex } = d;
onEventOverAction(
`row:${rowIndex} col:${columnIndex} value:${configuredData[rowIndex][columnIndex].value}`,
);
}
}}
onElementOut={() => onEventOutAction('out')}
/>
<Metric id="1" data={configuredData} />
</Chart>
);
};
Example.parameters = {
resize: {
height: '200px',
width: '200px',
},
};