storybook/stories/annotations/rects/4_styling.story.tsx (179 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 { array, boolean, color, number } from '@storybook/addon-knobs';
import React, { memo, useEffect, useMemo, useState } from 'react';
import type { AnnotationAnimationConfig, ChartProps, LineAnnotationDatum, RectAnnotationStyle } from '@elastic/charts';
import {
AnnotationDomainType,
Axis,
Chart,
LineAnnotation,
RectAnnotation,
ScaleType,
Settings,
} from '@elastic/charts';
import { Icon } from '@elastic/charts/src/components/icons/icon';
import { getRandomNumberGenerator } from '@elastic/charts/src/mocks/utils';
import type { RecursivePartial } from '@elastic/charts/src/utils/common';
import { Position } from '@elastic/charts/src/utils/common';
import { TimeFunction } from '@elastic/charts/src/utils/time_functions';
import type { ChartsStory } from '../../../types';
import { useBaseTheme } from '../../../use_base_theme';
import { customKnobs } from '../../utils/knobs';
const rng = getRandomNumberGenerator();
const randomArray = new Array(100).fill(0).map(() => rng(0, 10, 2));
const ExampleChart = memo<{ animationOptions: AnnotationAnimationConfig['options'] } & ChartProps>(
({ animationOptions, ...chartProps }) => {
const debug = boolean('debug', false);
const [SeriesType] = customKnobs.enum.xySeries(undefined, 'line');
const xScaleType = customKnobs.enum.scaleType('Scale type', ScaleType.Linear, { include: ['Linear', 'Ordinal'] });
const rotation = customKnobs.enum.rotation();
const dataValues = [
{
coordinates: {
x0: -0.1,
x1: 0.25,
},
details: 'annotation 1',
},
{
coordinates: {
x0: 2,
x1: 3,
},
details: 'annotation 2',
},
{
coordinates: {
x0: 8,
x1: 9,
},
details: 'annotation 3',
},
];
const lineData = dataValues.flatMap<LineAnnotationDatum>(({ coordinates: { x0, x1 } }) => [
{ dataValue: x0, details: 'start' },
{ dataValue: x1, details: 'end' },
]);
const zIndex = number('annotation zIndex', 0, {}, 'Styles');
const rectStyle: RecursivePartial<RectAnnotationStyle> = {
strokeWidth: number('rect border stroke width', 1, {}, 'Styles'),
stroke: color('rect border stroke color', '#e5e5e5', 'Styles'),
fill: color('fill color', '#e5e5e5', 'Styles'),
opacity: number(
'annotation opacity',
0.5,
{
range: true,
min: 0,
max: 1,
step: 0.1,
},
'Styles',
),
};
const hasCustomTooltip = boolean('has custom tooltip render', false, 'Styles');
const customTooltip = ({ details }: { details?: string }) => (
<div>
<Icon type="alert" />
{details}
</div>
);
const isLeft = boolean('y-domain axis is Position.Left', true, 'Styles');
const yAxisTitle = isLeft ? 'y-domain axis (left)' : 'y-domain axis (right)';
const yAxisPosition = isLeft ? Position.Left : Position.Right;
const isBottom = boolean('x-domain axis is Position.Bottom', true, 'Styles');
const xAxisTitle = isBottom ? 'x-domain axis (botttom)' : 'x-domain axis (top)';
const xAxisPosition = isBottom ? Position.Bottom : Position.Top;
const hideTooltips = boolean('hide tooltips', false, 'Styles');
const showLineAnnotations = boolean('showLineAnnotations', true, 'Styles');
const minAnnoCount = lineData.length;
const annotationCount = number('annotation count', minAnnoCount, { min: minAnnoCount, max: 100 }, 'Styles');
randomArray.slice(0, annotationCount - minAnnoCount).forEach((dataValue) => {
lineData.push({ dataValue, details: `Autogen value: ${dataValue}` });
});
const fadeOnFocusingOthers = boolean('FadeOnFocusingOthers', true, 'Animations');
const animations: AnnotationAnimationConfig[] = [];
if (fadeOnFocusingOthers) {
animations.push({ trigger: 'FadeOnFocusingOthers', options: animationOptions });
}
return (
<Chart {...chartProps}>
<Settings debug={debug} rotation={rotation} baseTheme={useBaseTheme()} />
<RectAnnotation
dataValues={dataValues}
id="rect"
style={{ ...rectStyle }}
animations={animations}
customTooltip={hasCustomTooltip ? customTooltip : undefined}
zIndex={zIndex}
hideTooltips={hideTooltips}
/>
{showLineAnnotations && (
<LineAnnotation
id="annotation_1"
domainType={AnnotationDomainType.XDomain}
animations={animations}
dataValues={lineData}
marker={<Icon type="alert" />}
/>
)}
<Axis id="bottom" position={xAxisPosition} title={xAxisTitle} />
<Axis id="left" title={yAxisTitle} position={yAxisPosition} />
<SeriesType
id="series"
xScaleType={xScaleType}
yScaleType={ScaleType.Linear}
xAccessor="x"
yAccessors={['y']}
data={[
{ x: 0, y: 2 },
{ x: 1, y: 3 },
{ x: 2, y: 10 },
{ x: 3, y: 3 },
{ x: 4, y: 6 },
{ x: 5, y: 12 },
{ x: 6, y: 2 },
{ x: 7, y: 1 },
{ x: 8, y: 8 },
{ x: 9, y: 11 },
{ x: 10, y: 7 },
]}
/>
</Chart>
);
},
);
let prevAnimationStr = '';
export const Example: ChartsStory = (_, { title, description }) => {
const [mountCount, setMountCount] = useState(1);
// eslint-disable-next-line react-hooks/exhaustive-deps
const animationOptions: Partial<AnnotationAnimationConfig['options']> = {
enabled: boolean('enabled', true, 'Animations'),
delay: number('delay (ms)', 50, { min: 0, max: 10000, step: 50 }, 'Animations'),
duration: number('duration (ms)', 250, { min: 0, max: 10000, step: 50 }, 'Animations'),
timeFunction: customKnobs.fromEnum('time function', TimeFunction, TimeFunction.easeInOut, {
group: 'Animations',
}),
snapValues: array('snap values', [], undefined, 'Animations').map(Number),
};
// The following is a HACK to remount the chart when the animation options change, see description in markdown
const animationsStr = useMemo(() => JSON.stringify(animationOptions), [animationOptions]);
useEffect(() => {
prevAnimationStr = animationsStr;
}, []); // eslint-disable-line react-hooks/exhaustive-deps
useEffect(() => {
if (prevAnimationStr !== animationsStr) setMountCount(mountCount + 1);
prevAnimationStr = animationsStr;
}, [animationsStr]); // eslint-disable-line react-hooks/exhaustive-deps
if (mountCount % 2 === 0) {
return <ExampleChart title={title} description={description} animationOptions={animationOptions} />;
}
action('mounted new chart')();
setTimeout(() => setMountCount((c) => c + 1));
return <></>;
};
Example.parameters = {
markdown: `Annotations animations are configured via \`RectAnnotation.animations\` or \`LineAnnotation.animations\` which are only read once on intial
render.
> :warning: Animations options, excluding \`enabled\`, are set _only_ when the chart is _**mounted**_ and _**not**_ on every rerender. \
For demonstration purposes, the chart on this story is forced to re-mount whenever the animation options change, hence the flashing.
`,
};