storybook/stories/metric/5_array_of_values.story.tsx (173 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 { action } from '@storybook/addon-actions'; import { number, boolean, button, color, select } from '@storybook/addon-knobs'; import React, { useEffect, useMemo, useState } from 'react'; import type { MetricDatum } from '@elastic/charts'; import { Chart, isMetricElementEvent, Metric, Settings } from '@elastic/charts'; import { getRandomNumberGenerator } from '@elastic/charts/src/mocks/utils'; import { KIBANA_METRICS } from '@elastic/charts/src/utils/data_samples/test_dataset_kibana'; import type { ChartsStory } from '../../types'; import { useBaseTheme } from '../../use_base_theme'; const rng = getRandomNumberGenerator(); function split(a: (any | undefined)[], size: number) { return Array.from(new Array(Math.ceil(a.length / size))).map((_, index) => a.slice(index * size, (index + 1) * size)); } const arrayToGrid: <T>(array: T[], nColumns: number) => T[][] = (array, nColumns) => { const ret = []; for (let i = 0; i < array.length; i += nColumns) { ret.push(array.slice(i, i + nColumns)); } return ret; }; const maxTileSideLength = 200; const getContainerWidth = (_data: (MetricDatum | undefined)[][]) => _data[0].length * maxTileSideLength; const getContainerHeight = (_data: (MetricDatum | undefined)[][]) => _data.length * maxTileSideLength; export const Example: ChartsStory = (_, { title, description }) => { const showGridBorder = boolean('show grid border', false); const addMetricClick = boolean('attach click handler', true); const maxDataPoints = number('max trend data points', 30, { min: 0, max: 50, step: 1 }); const emptyBackground = color('empty background', 'transparent'); 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 data = useMemo<(MetricDatum | undefined)[]>( () => [ { color: '#3c3c3c', title: 'Top machines', subtitle: 'Greatest throughput by ip', value: ['28.86.156.19', '', '103.23.205.126'], trend: KIBANA_METRICS.metrics.kibana_os_load.v1.data.slice(0, maxDataPoints).map(([x, y]) => ({ x, y })), trendShape: 'area', }, { color: '#FF7E62', title: 'Memory Usage', subtitle: 'Overall percentages', value: [33.57], valueFormatter: (d) => `${d}%`, }, { color: '#5e5e5e', title: 'Disk I/O', subtitle: 'Write', value: [4, 9], valueFormatter: (d) => `${d} Mb/s`, extra: ( <span> max <b>100 Mb/s</b> </span> ), }, { color: '#FFBDAF', title: 'Inbound Traffic', subtitle: 'Network eth0', extra: ( <span> last <b>5m</b> </span> ), value: [3, 1], valueFormatter: (d) => `${d}KBps`, }, undefined, { color: '#F1D86F', title: 'Cloud Revenue', subtitle: 'Quarterly', extra: ( <span> This Year <b>10M</b> </span> ), value: [32, NaN, 2], valueFormatter: (d) => `$${d}k`, trend: KIBANA_METRICS.metrics.kibana_os_load.v3.data.slice(0, maxDataPoints).map(([x, y]) => ({ x, y })), trendShape: 'bars', }, ], [maxDataPoints], ); const nColumns = number('number of columns', 3, { min: 1, max: data.length, step: 1 }); const [chartData, setChartData] = useState(arrayToGrid(data, nColumns)); const [containerHeight, setContainerHeight] = useState(getContainerHeight(chartData)); const [containerWidth, setContainerWidth] = useState(getContainerWidth(chartData)); useEffect(() => { const newData = arrayToGrid(data, nColumns); setChartData(newData); setContainerHeight(getContainerHeight(newData)); setContainerWidth(getContainerWidth(newData)); }, [data, maxDataPoints, nColumns]); button('randomize data', () => { setChartData( split( data .slice() .map((d) => { return rng(0, 1, 3) > 0.8 ? undefined : d; }) .slice(0, rng(1, data.length)), rng(1, data.length / 2), ), ); }); const debugRandomizedData = boolean('debug randomized data', false); const onEventClickAction = action('click'); const onEventOverAction = action('over'); const onEventOutAction = action('out'); return ( <div style={{ resize: 'both', maxWidth: '100%', maxHeight: '80vh', padding: '0px', overflow: 'auto', height: `${containerHeight}px`, width: `${containerWidth}px`, ...(showGridBorder && { boxShadow: '5px 5px 15px 5px rgba(0,0,0,0.29)', borderRadius: '6px', }), }} > {debugRandomizedData && chartData .flat() .map((d) => `[${d?.value}]`) .join(' ')} <Chart title={title} description={description}> <Settings theme={{ metric: { emptyBackground, valueFontSize: valueFontSizeMode === 'custom' ? valueFontSize : valueFontSizeMode, }, }} baseTheme={useBaseTheme()} onElementClick={ addMetricClick ? ([d]) => { if (isMetricElementEvent(d)) { const { rowIndex, columnIndex } = d; onEventClickAction( `row:${rowIndex} col:${columnIndex} value:${chartData[rowIndex][columnIndex]?.value}`, ); } } : undefined } onElementOver={([d]) => { if (isMetricElementEvent(d)) { const { rowIndex, columnIndex } = d; onEventOverAction(`row:${rowIndex} col:${columnIndex} value:${chartData[rowIndex][columnIndex]?.value}`); } }} onElementOut={() => onEventOutAction('out')} /> <Metric id="metric" data={chartData} /> </Chart> </div> ); };