Skip to content

Commit

Permalink
Add dynamic colors to graphics and responsive layout
Browse files Browse the repository at this point in the history
  • Loading branch information
jbiset committed Jan 8, 2024
1 parent c23ee4f commit a9969e6
Show file tree
Hide file tree
Showing 10 changed files with 233 additions and 145 deletions.
190 changes: 118 additions & 72 deletions plugins/main/public/components/common/charts/visualizations/basic.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
import React, { useCallback, useState } from "react";
import { ChartLegend } from "./legend";
import React, { useCallback, useState } from 'react';
import { ChartLegend } from './legend';
import { ChartDonut, ChartDonutProps } from '../charts/donut';
import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiLoadingChart, EuiText, EuiSelect, EuiSpacer } from '@elastic/eui';
import { useAsyncActionRunOnStart } from "../../hooks";
import {
EuiEmptyPrompt,
EuiFlexGroup,
EuiFlexItem,
EuiLoadingChart,
EuiText,
EuiSelect,
EuiSpacer,
} from '@elastic/eui';
import { useAsyncActionRunOnStart } from '../../hooks';

export type VisualizationBasicProps = ChartDonutProps & {
type: 'donut',
size: number | string | { width: number | string, height: number | string }
showLegend?: boolean
isLoading?: boolean
noDataTitle?: string
noDataMessage?: string | (() => React.node)
errorTitle?: string
errorMessage?: string | (() => React.node)
error?: { message: string }
}
type: 'donut';
size: number | string | { width: number | string; height: number | string };
showLegend?: boolean;
isLoading?: boolean;
noDataTitle?: string;
noDataMessage?: string | (() => React.node);
errorTitle?: string;
errorMessage?: string | (() => React.node);
error?: { message: string };
};

const chartTypes = {
'donut': ChartDonut
donut: ChartDonut,
};

/**
Expand All @@ -34,131 +42,169 @@ export const VisualizationBasic = ({
noDataMessage,
errorTitle = 'Error',
errorMessage,
error
error,
}: VisualizationBasicProps) => {
const { width, height } = typeof size === 'object' ? size : { width: size, height: size };
const { width, height } =
typeof size === 'object' ? size : { width: size, height: size };

let visualization = null;

if (isLoading) {
visualization = (
<div style={{ textAlign: "center", width, height, position: 'relative' }}>
<EuiLoadingChart size="xl" style={{ top: '50%', transform: 'translate(-50%, -50%)', position: 'absolute' }} />
<div style={{ textAlign: 'center', width, height, position: 'relative' }}>
<EuiLoadingChart
size='xl'
style={{
top: '50%',
transform: 'translate(-50%, -50%)',
position: 'absolute',
}}
/>
</div>
)
);
} else if (errorMessage || error?.message) {
visualization = (
<EuiEmptyPrompt
iconType="alert"
iconType='alert'
title={<h4>{errorTitle}</h4>}
body={errorMessage || error?.message}
/>
)
);
} else if (!data || (Array.isArray(data) && !data.length)) {
visualization = (
<EuiEmptyPrompt
iconType="stats"
iconType='stats'
title={<h4>{noDataTitle}</h4>}
body={typeof noDataMessage === 'function' ? noDataMessage() : noDataMessage}
body={
typeof noDataMessage === 'function' ? noDataMessage() : noDataMessage
}
/>
)
);
} else {
const Chart = chartTypes[type];
const chartFlexStyle = {
alignItems: 'flex-end',
paddingRight: '1em'
}
paddingRight: '1em',
};
const legendFlexStyle = {
height: '100%',
paddingLeft: '1em'
}
paddingLeft: '1em',
};
visualization = (
<EuiFlexGroup responsive={false} style={{ height: '100%' }} gutterSize='none'>
<EuiFlexGroup
responsive={false}
style={{ height: '100%' }}
gutterSize='none'
>
<EuiFlexItem style={chartFlexStyle}>
<Chart data={data} />
</EuiFlexItem>
{showLegend && (
<EuiFlexItem style={legendFlexStyle}>
<ChartLegend
data={data.map(({ color, ...rest }) => ({ ...rest, labelColor: color, color: 'text' }))}
data={data.map(({ color, ...rest }) => ({
...rest,
labelColor: color,
color: 'text',
}))}
/>
</EuiFlexItem>
)}
</EuiFlexGroup>
)
);
}

return (
<div style={{ width, height }}>
{visualization}
</div>
)

}
return <div style={{ width, height }}>{visualization}</div>;
};

type VisualizationBasicWidgetProps = VisualizationBasicProps & {
onFetch: (...dependencies) => any[]
onFetchDependencies?: any[]
}
onFetch: (...dependencies) => any[];
onFetchDependencies?: any[];
};

/**
* Component that fetch the data and renders the visualization using the visualization basic component
*/
export const VisualizationBasicWidget = ({ onFetch, onFetchDependencies, ...rest }: VisualizationBasicWidgetProps) => {
const { running, ...restAsyncAction } = useAsyncActionRunOnStart(onFetch, onFetchDependencies);
export const VisualizationBasicWidget = ({
onFetch,
onFetchDependencies,
...rest
}: VisualizationBasicWidgetProps) => {
const { running, ...restAsyncAction } = useAsyncActionRunOnStart(
onFetch,
onFetchDependencies,
);

return <VisualizationBasic {...rest} {...restAsyncAction} isLoading={running} />
}
return (
<VisualizationBasic {...rest} {...restAsyncAction} isLoading={running} />
);
};

type VisualizationBasicWidgetSelectorProps = VisualizationBasicWidgetProps & {
selectorOptions: { value: any, text: string }[]
title?: string
onFetchExtraDependencies?: any[]
}
selectorOptions: { value: any; text: string }[];
title?: string;
onFetchExtraDependencies?: any[];
};

/**
* Renders a visualization that has a selector to change the resource to fetch data and display it. Use the visualization basic.
* Renders a visualization that has a selector to change the resource to fetch data and display it. Use the visualization basic.
*/
export const VisualizationBasicWidgetSelector = ({ selectorOptions, title, onFetchExtraDependencies, ...rest }: VisualizationBasicWidgetSelectorProps) => {
const [selectedOption, setSelectedOption] = useState(selectorOptions[0].value);
export const VisualizationBasicWidgetSelector = ({
selectorOptions,
title,
onFetchExtraDependencies,
...rest
}: VisualizationBasicWidgetSelectorProps) => {
const [selectedOption, setSelectedOption] = useState(
selectorOptions[0].value,
);

const onChange = useCallback((e) => setSelectedOption(e.target.value));
const onChange = useCallback(e => setSelectedOption(e.target.value));

return (
<>
<EuiFlexGroup
className="embPanel__header" gutterSize='none'
>
<EuiFlexGroup className='embPanel__header' gutterSize='none'>
<EuiFlexItem>
{title && (
<h2 className="embPanel__title wz-headline-title">
<EuiText size="xs"><h2>{title}</h2></EuiText>
<h2 className='embPanel__title wz-headline-title'>
<EuiText size='xs'>
<h2>{title}</h2>
</EuiText>
</h2>
)}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiSelect style={{ fontSize: '0.793rem' }}
<EuiSelect
style={{ fontSize: '0.793rem' }}
compressed={true}
options={selectorOptions}
value={selectedOption}
onChange={onChange}
aria-label="Select options"
aria-label='Select options'
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size='s' />
<VisualizationBasicWidget
{...rest}
{...(rest.noDataMessage ?
{
noDataMessage: typeof rest.noDataMessage === 'function' ?
rest.noDataMessage(selectedOption, selectorOptions.find(option => option.value === selectedOption))
: rest.noDataMessage
}
: {}
)}
onFetchDependencies={[selectedOption, ...(onFetchExtraDependencies || [])]}
{...(rest.noDataMessage
? {
noDataMessage:
typeof rest.noDataMessage === 'function'
? rest.noDataMessage(
selectedOption,
selectorOptions.find(
option => option.value === selectedOption,
),
)
: rest.noDataMessage,
}
: {})}
onFetchDependencies={[
selectedOption,
...(onFetchExtraDependencies || []),
]}
/>
</>
)
}
);
};
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
.chart-legend {
width: 150px;
}

.chart-legend > li {
margin-top: 0px;
}

.chart-legend > li > * {
padding: 0px;
overflow: hidden;
}
Original file line number Diff line number Diff line change
@@ -1,32 +1,41 @@
import React from "react";
import { EuiIcon } from "@elastic/eui";
import { EuiListGroup } from "@elastic/eui";
import React from 'react';
import { EuiIcon } from '@elastic/eui';
import { EuiListGroup } from '@elastic/eui';
import './legend.scss';

type ChartLegendProps = {
data: {
label: string
value: any
color: string
labelColor: string
}[]
}
label: string;
value: any;
color: string;
labelColor: string;
}[];
};

/**
* Create the legend to use with charts in visualizations.
*/
export function ChartLegend({ data }: ChartLegendProps) {
const list = data.map(({label, labelColor, value, ...rest}, idx) => ({
label: <div style={{fontSize: '0.875rem'}}>{`${label} (${value})`}</div>,
icon: <EuiIcon type="dot" size='l' color={labelColor} />,
...rest
const list = data.map(({ label, labelColor, value, ...rest }, idx) => ({
label: (
<div
style={{
fontSize: '0.875rem',
textOverflow: 'ellipsis',
overflow: 'hidden',
}}
>{`${label} (${value})`}</div>
),
icon: <EuiIcon type='dot' size='l' color={labelColor} />,
...rest,
}));

return (
<EuiListGroup
className="chart-legend"
className='chart-legend'
listItems={list}
color='text'
flush />
flush
/>
);
}
}
Loading

0 comments on commit a9969e6

Please sign in to comment.