Skip to content

Commit

Permalink
Merge pull request #2826 from andrewbaldwin44/feature/dashboard-as-a-…
Browse files Browse the repository at this point in the history
…module-2

Improve Echarts and Expose Line and Axis Configuration
  • Loading branch information
cyberw authored Aug 3, 2024
2 parents b477b2a + cbb1197 commit 0f4343b
Show file tree
Hide file tree
Showing 6 changed files with 245 additions and 242 deletions.
14 changes: 14 additions & 0 deletions locust/webui/src/components/LineChart/LineChart.constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const CHART_THEME = {
DARK: {
textColor: '#b3c3bc',
axisColor: '#5b6f66',
backgroundColor: '#27272a',
splitLine: '#4c4c52',
},
LIGHT: {
textColor: '#000',
axisColor: '#000',
backgroundColor: '#fff',
splitLine: '#ddd',
},
};
282 changes: 45 additions & 237 deletions locust/webui/src/components/LineChart/LineChart.tsx
Original file line number Diff line number Diff line change
@@ -1,157 +1,30 @@
import { useEffect, useState, useRef } from 'react';
import {
init,
dispose,
ECharts,
EChartsOption,
DefaultLabelFormatterCallbackParams,
connect,
TooltipComponentOption,
} from 'echarts';
import { init, dispose, ECharts, connect } from 'echarts';

import { CHART_THEME } from 'components/LineChart/LineChart.constants';
import {
ILineChartTimeAxis,
ILineChart,
ILineChartMarkers,
} from 'components/LineChart/LineChart.types';
import {
createMarkLine,
createOptions,
getSeriesData,
onChartZoom,
} from 'components/LineChart/LineChart.utils';
import { useSelector } from 'redux/hooks';
import { ICharts } from 'types/ui.types';
import { formatLocaleString, formatLocaleTime } from 'utils/date';

interface ILine<ChartType> {
name: string;
key: keyof ChartType;
}

export interface ILineChart<ChartType> {
charts: ChartType;
title: string;
lines: ILine<ChartType>[];
colors?: string[];
chartValueFormatter?: (value: string | number | string[] | number[]) => string | number;
}

interface ICreateOptions<ChartType> extends Omit<ILineChart<ChartType>, 'lines'> {
seriesData: EChartsOption['Series'][];
}

interface ILineChartZoomEvent {
batch?: { start: number; end: number }[];
}
interface IBaseChartType extends ILineChartTimeAxis, ILineChartMarkers {}

const createOptions = <ChartType extends Pick<ICharts, 'time'>>({
charts,
title,
seriesData,
colors,
chartValueFormatter,
}: ICreateOptions<ChartType>) => ({
title: {
text: title,
x: 10,
y: 10,
},
dataZoom: [
{
type: 'inside',
start: 0,
end: 100,
},
],
tooltip: {
trigger: 'axis',
formatter: (params: TooltipComponentOption) => {
if (
!!params &&
Array.isArray(params) &&
params.length > 0 &&
params.some(param => !!param.value)
) {
return params.reduce(
(tooltipText, { axisValue, color, seriesName, value }, index) => `
${index === 0 ? formatLocaleString(axisValue) : ''}
${tooltipText}
<br>
<span style="color:${color};">
${seriesName}:&nbsp${chartValueFormatter ? chartValueFormatter(value) : value}
</span>
`,
'',
);
} else {
return 'No data';
}
},
axisPointer: {
animation: true,
},
backgroundColor: 'rgba(21,35,28, 0.93)',
borderWidth: 0,
extraCssText: 'z-index:1;',
},
xAxis: {
type: 'category',
splitLine: {
show: false,
},
axisLabel: {
formatter: formatLocaleTime,
},
data: charts.time,
},
yAxis: {
type: 'value',
boundaryGap: [0, '5%'],
},
series: seriesData,
grid: { x: 60, y: 70, x2: 40, y2: 40 },
color: colors,
toolbox: {
feature: {
restore: {
show: false,
title: 'Reset',
},
saveAsImage: {
name: title.replace(/\s+/g, '_').toLowerCase() + '_' + new Date().getTime() / 1000,
title: 'Download as PNG',
emphasis: {
iconStyle: {
textPosition: 'left',
},
},
},
},
},
});

const getSeriesData = <ChartType,>({
charts,
lines,
}: {
charts: ChartType;
lines: ILine<ChartType>[];
}) =>
lines.map(({ key, name }) => ({
name,
type: 'line',
showSymbol: true,
data: charts[key],
}));

const createMarkLine = <ChartType extends Pick<ICharts, 'markers'>>(
charts: ChartType,
isDarkMode: boolean,
) => ({
symbol: 'none',
label: {
formatter: (params: DefaultLabelFormatterCallbackParams) => `Run #${params.dataIndex + 1}`,
},
lineStyle: { color: isDarkMode ? '#5b6f66' : '#000' },
data: (charts.markers || []).map((timeMarker: string) => ({ xAxis: timeMarker })),
});

export default function LineChart<ChartType extends Pick<ICharts, 'time' | 'markers'> = ICharts>({
export default function LineChart<ChartType extends IBaseChartType>({
charts,
title,
lines,
colors,
chartValueFormatter,
splitAxis,
yAxisLabels,
}: ILineChart<ChartType>) {
const [chart, setChart] = useState<ECharts | null>(null);
const isDarkMode = useSelector(({ theme: { isDarkMode } }) => isDarkMode);
Expand All @@ -163,73 +36,20 @@ export default function LineChart<ChartType extends Pick<ICharts, 'time' | 'mark
return;
}

const initChart = init(chartContainer.current, 'locust');
const initChart = init(chartContainer.current);

initChart.setOption(
createOptions<ChartType>({
charts,
title,
seriesData: getSeriesData<ChartType>({ charts, lines }),
lines,
colors,
chartValueFormatter,
splitAxis,
yAxisLabels,
}),
);
initChart.on('datazoom', datazoom => {
const { batch } = datazoom as ILineChartZoomEvent;
if (!batch) {
return;
}

const [{ start, end }] = batch;

if (start > 0 && end < 100) {
initChart.setOption({
toolbox: {
feature: {
restore: {
show: true,
},
},
},
dataZoom: [
{
type: 'inside',
start,
end,
},
{
type: 'slider',
show: true,
start,
end,
},
],
});
} else {
initChart.setOption({
toolbox: {
feature: {
restore: {
show: false,
},
},
},
dataZoom: [
{
type: 'inside',
start,
end,
},
{
type: 'slider',
show: false,
start,
end,
},
],
});
}
});
initChart.on('datazoom', onChartZoom(initChart));

const handleChartResize = () => initChart.resize();
window.addEventListener('resize', handleChartResize);
Expand All @@ -250,8 +70,10 @@ export default function LineChart<ChartType extends Pick<ICharts, 'time' | 'mark
if (chart && isChartDataDefined) {
chart.setOption({
xAxis: { data: charts.time },
series: lines.map(({ key }, index) => ({
series: lines.map(({ key, yAxisIndex, ...echartsOptions }, index) => ({
...echartsOptions,
data: charts[key],
...(splitAxis ? { yAxisIndex: yAxisIndex || index } : {}),
...(index === 0 ? { markLine: createMarkLine<ChartType>(charts, isDarkMode) } : {}),
})),
});
Expand All @@ -260,50 +82,36 @@ export default function LineChart<ChartType extends Pick<ICharts, 'time' | 'mark

useEffect(() => {
if (chart) {
const chartTextColor = isDarkMode ? '#b3c3bc' : '#000';
const chartAxisColor = isDarkMode ? '#5b6f66' : '#000';
const { textColor, axisColor, backgroundColor, splitLine } = isDarkMode
? CHART_THEME.DARK
: CHART_THEME.LIGHT;

chart.setOption({
backgroundColor: isDarkMode ? '#27272a' : '#fff',
textStyle: { color: chartTextColor },
title: {
textStyle: { color: chartTextColor },
},
backgroundColor,
textStyle: { color: textColor },
title: { textStyle: { color: textColor } },
legend: {
icon: 'circle',
inactiveColor: chartTextColor,
textStyle: {
color: chartTextColor,
},
},
tooltip: {
textStyle: {
color: chartTextColor,
fontSize: 13,
},
},
xAxis: {
axisLine: {
lineStyle: {
color: chartAxisColor,
},
},
inactiveColor: textColor,
textStyle: { color: textColor },
},
tooltip: { backgroundColor, textStyle: { color: textColor } },
xAxis: { axisLine: { lineStyle: { color: axisColor } } },
yAxis: {
axisLine: {
lineStyle: {
color: chartAxisColor,
},
},
splitLine: {
lineStyle: {
color: isDarkMode ? '#4c4c52' : '#ddd',
},
},
axisLine: { lineStyle: { color: axisColor } },
splitLine: { lineStyle: { color: splitLine } },
},
});
}
}, [chart, isDarkMode]);

useEffect(() => {
if (chart) {
chart.setOption({
series: getSeriesData<ChartType>({ charts, lines }),
});
}
}, [lines]);

return <div ref={chartContainer} style={{ width: '100%', height: '300px' }}></div>;
}
29 changes: 29 additions & 0 deletions locust/webui/src/components/LineChart/LineChart.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export interface ILine<ChartType> {
name: string;
key: keyof ChartType;
stack?: string;
areaStyle?: { [key: string]: any };
yAxisIndex?: number;
}

export interface ILineChart<ChartType> {
charts: ChartType;
title: string;
lines: ILine<ChartType>[];
colors?: string[];
chartValueFormatter?: (value: string | number | string[] | number[]) => string | number;
splitAxis?: boolean;
yAxisLabels?: string | [string, string];
}

export interface ILineChartZoomEvent {
batch?: { start: number; end: number }[];
}

export interface ILineChartTimeAxis {
time: string[];
}

export interface ILineChartMarkers {
markers?: string[];
}
Loading

0 comments on commit 0f4343b

Please sign in to comment.