feat: playground embedding

This commit is contained in:
jialin
2024-11-22 10:00:34 +08:00
parent 01214077d3
commit 16e2ac5b6c
33 changed files with 1518 additions and 172 deletions
+14 -14
View File
@@ -10,7 +10,7 @@ import {
} from '@/components/echarts/config';
import EmptyData from '@/components/empty-data';
import _ from 'lodash';
import { memo } from 'react';
import { memo, useMemo } from 'react';
import { ChartProps } from './types';
const BarChart: React.FC<ChartProps> = (props) => {
@@ -24,10 +24,6 @@ const BarChart: React.FC<ChartProps> = (props) => {
title
} = props;
if (!seriesData.length) {
return <EmptyData height={height} title={title}></EmptyData>;
}
const options = {
title: {
text: ''
@@ -55,7 +51,7 @@ const BarChart: React.FC<ChartProps> = (props) => {
series: []
};
const setDataOptions = () => {
const dataOptions = useMemo((): any => {
const data = _.map(seriesData, (item: any) => {
return {
...item,
@@ -82,16 +78,20 @@ const BarChart: React.FC<ChartProps> = (props) => {
},
series: data
};
};
const dataOptions: any = setDataOptions();
}, [seriesData, xAxisData, title]);
return (
<Chart
height={height}
options={dataOptions}
width={width || '100%'}
></Chart>
<>
{!seriesData.length ? (
<EmptyData height={height} title={title}></EmptyData>
) : (
<Chart
height={height}
options={dataOptions}
width={width || '100%'}
></Chart>
)}
</>
);
};
+33 -14
View File
@@ -1,17 +1,30 @@
import { throttle } from 'lodash';
import { useCallback, useEffect, useRef } from 'react';
import {
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useRef
} from 'react';
import echarts, { ECOption } from '.';
const Chart: React.FC<{
options: ECOption;
height: number | string;
width: number | string;
}> = ({ options, width, height }) => {
ref?: any;
}> = forwardRef(({ options, width, height }, ref) => {
const container = useRef<HTMLDivElement>(null);
const chart = useRef<echarts.EChartsType>();
const resizeable = useRef(false);
const resizeObserver = useRef<ResizeObserver>();
useImperativeHandle(ref, () => {
return {
chart: chart.current
};
});
const init = useCallback(() => {
if (container.current) {
chart.current?.clear();
@@ -44,18 +57,9 @@ const Chart: React.FC<{
resizeable.current = false;
resize();
setOption(options);
resizeable.current = true;
}, [options]);
useEffect(() => {
let timer: any = null;
timer = setTimeout(() => {
resizeable.current = true;
}, 300);
return () => {
clearTimeout(timer);
};
}, []);
useEffect(() => {
const handleResize = throttle(() => {
if (resizeable.current) {
@@ -75,7 +79,22 @@ const Chart: React.FC<{
};
}, []);
return <div ref={container} style={{ width: width, height }}></div>;
};
// resize on window resize
// useEffect(() => {
// const handleResize = throttle(() => {
// chart.current?.resize();
// }, 100);
// window.addEventListener('resize', handleResize);
// return () => {
// window.removeEventListener('resize', handleResize);
// };
// }, []);
return (
<div className="chart-wrapper" style={{ width: width, height }}>
<div ref={container} style={{ width: width, height }}></div>
</div>
);
});
export default Chart;
+18 -15
View File
@@ -9,7 +9,7 @@ import {
} from '@/components/echarts/config';
import EmptyData from '@/components/empty-data';
import _ from 'lodash';
import { memo } from 'react';
import { memo, useMemo } from 'react';
import { ChartProps } from './types';
const BarChart: React.FC<ChartProps> = (props) => {
@@ -22,12 +22,12 @@ const BarChart: React.FC<ChartProps> = (props) => {
legendData,
title
} = props;
if (!seriesData.length) {
return <EmptyData height={height} title={title}></EmptyData>;
}
const options = {
title: titleConfig,
title: {
...titleConfig,
left: 'start'
},
grid,
tooltip: {
...tooltip
@@ -48,7 +48,7 @@ const BarChart: React.FC<ChartProps> = (props) => {
series: []
};
const setDataOptions = () => {
const dataOptions = useMemo((): any => {
const data = _.map(seriesData, (item: any) => {
return {
...item,
@@ -69,7 +69,6 @@ const BarChart: React.FC<ChartProps> = (props) => {
animation: false,
title: {
...options.title,
left: 'start',
text: title
},
yAxis: {
@@ -103,16 +102,20 @@ const BarChart: React.FC<ChartProps> = (props) => {
},
series: data
};
};
const dataOptions: any = setDataOptions();
}, [seriesData, xAxisData, title]);
return (
<Chart
height={height}
options={dataOptions}
width={width || '100%'}
></Chart>
<>
{!seriesData.length ? (
<EmptyData height={height} title={title}></EmptyData>
) : (
<Chart
height={height}
options={dataOptions}
width={width || '100%'}
></Chart>
)}
</>
);
};
+5 -2
View File
@@ -1,9 +1,10 @@
import type {
BarSeriesOption,
GaugeSeriesOption,
LineSeriesOption
LineSeriesOption,
ScatterSeriesOption
} from 'echarts/charts';
import { BarChart, GaugeChart, LineChart } from 'echarts/charts';
import { BarChart, GaugeChart, LineChart, ScatterChart } from 'echarts/charts';
import type {
DatasetComponentOption,
GridComponentOption,
@@ -32,6 +33,7 @@ type ECOption = ComposeOption<
| GridComponentOption
| DatasetComponentOption
| GaugeSeriesOption
| ScatterSeriesOption
>;
// register components and charts
@@ -44,6 +46,7 @@ echarts.use([
TransformComponent,
BarChart,
LineChart,
ScatterChart,
GaugeChart,
LabelLayout,
UniversalTransition,
+16 -14
View File
@@ -10,7 +10,7 @@ import {
} from '@/components/echarts/config';
import EmptyData from '@/components/empty-data';
import _ from 'lodash';
import { memo } from 'react';
import { memo, useMemo } from 'react';
import { ChartProps } from './types';
const LineChart: React.FC<ChartProps> = (props) => {
@@ -26,9 +26,6 @@ const LineChart: React.FC<ChartProps> = (props) => {
smooth,
title
} = props;
if (!seriesData.length) {
return <EmptyData height={height} title={title}></EmptyData>;
}
const options = {
title: {
@@ -59,7 +56,7 @@ const LineChart: React.FC<ChartProps> = (props) => {
series: []
};
const generateOptions = (): any => {
const dataOptions = useMemo((): any => {
const data = _.map(seriesData, (item: any) => {
return {
...item,
@@ -75,7 +72,7 @@ const LineChart: React.FC<ChartProps> = (props) => {
}
};
});
const optionsConfig = {
return {
...options,
animation: false,
title: {
@@ -92,15 +89,20 @@ const LineChart: React.FC<ChartProps> = (props) => {
},
series: data
};
return optionsConfig;
};
const dataOptions = generateOptions();
}, [seriesData, xAxisData, yAxisName, title, smooth, legendData]);
return (
<Chart
height={height}
options={dataOptions}
width={width || '100%'}
></Chart>
<>
{!seriesData.length ? (
<EmptyData height={height} title={title}></EmptyData>
) : (
<Chart
height={height}
options={dataOptions}
width={width || '100%'}
></Chart>
)}
</>
);
};
+189
View File
@@ -0,0 +1,189 @@
import Chart from '@/components/echarts/chart';
import { grid, title as titleConfig } from '@/components/echarts/config';
import EmptyData from '@/components/empty-data';
import _ from 'lodash';
import { memo, useCallback, useMemo, useRef } from 'react';
import { ChartProps } from './types';
const options: any = {
animation: false,
grid: {
...grid,
right: -1,
top: -1,
bottom: -1,
left: -1,
containLabel: true
},
xAxis: {
slient: true,
splitNumber: 15,
splitLine: {
lineStyle: {
color: 'rgba(5,5,5,0.06)'
}
},
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
show: false
}
},
yAxis: {
slient: true,
splitNumber: 10,
splitLine: {
lineStyle: {
color: 'rgba(5,5,5,0.06)'
}
},
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
show: false
}
},
symbol: 'roundRect',
label: {
show: true,
shadowColor: 'none',
textBorderColor: 'none',
formatter: (params: any) => {
return params.name;
}
},
series: []
};
const Scatter: React.FC<ChartProps> = (props) => {
const {
seriesData,
xAxisData,
height,
width,
showEmpty,
labelFormatter,
legendData,
title
} = props;
const chart = useRef<any>(null);
const findOverlappingPoints = useCallback(
(data: any[], currentPoint: any) => {
const overlappingPoints = [];
const symbolRadius = 16;
const [x1, y1] = chart.current.chart?.convertToPixel(
'grid',
currentPoint.value
);
const pixelPoints = data.map((point) => {
return {
...point,
value: chart.current.chart?.convertToPixel('grid', point.value)
};
});
for (let j = 0; j < pixelPoints.length; j++) {
if (currentPoint.name === pixelPoints[j].name) {
overlappingPoints.push({ ...pixelPoints[j] });
continue;
}
const [x2, y2] = pixelPoints[j].value;
const distance = Math.sqrt(
Math.pow(_.round(x2 - x1, 2), 2) + Math.pow(_.round(y2 - y1, 2), 2)
);
if (distance <= symbolRadius) {
overlappingPoints.push({ ...pixelPoints[j] });
}
}
return overlappingPoints;
},
[]
);
const dataOptions = useMemo((): any => {
if (!seriesData.length) {
options.xAxis.min = 0;
options.xAxis.max = 1;
options.yAxis.min = 0;
options.yAxis.max = 1;
} else {
options.xAxis.min = null;
options.xAxis.max = null;
options.yAxis.min = null;
options.yAxis.max = null;
}
const seriseDataList = seriesData.map((item: any, index: number) => {
return {
...item,
itemStyle: {
color: '#5470c6'
},
symbolSize: 16
};
});
return {
...options,
tooltip: {
trigger: 'item',
borderWidth: 0,
formatter(params: any, callback?: (val: any) => any) {
const dataList = findOverlappingPoints(seriseDataList, params.data);
let result = '';
dataList.forEach((item: any) => {
result += `
<span class="tooltip-item" style="justify-content: flex-start;">
<span class="tooltip-item-name">
<span style="display:flex;justify-content:center;align-items: center;color:#fff;
margin-right:0;border-radius:4px;width:14px;
height:14px;background-color:${item.itemStyle?.color};"
>${item.name}</span>
</span>
<span class="tooltip-value">${item.text}</span>
</span>`;
});
return `<div class="tooltip-wrapper">${result}</div>`;
}
},
title: {
...titleConfig,
text: title
},
series: {
type: 'scatter',
data: seriseDataList
}
};
}, [seriesData, xAxisData, title, findOverlappingPoints]);
return (
<>
{!seriesData.length && showEmpty ? (
<EmptyData height={height} title={title}></EmptyData>
) : (
<Chart
ref={chart}
height={height}
options={dataOptions}
width={width || '100%'}
></Chart>
)}
</>
);
};
export default memo(Scatter);
+1
View File
@@ -1,5 +1,6 @@
export interface ChartProps {
seriesData: any[];
showEmpty?: boolean;
xAxisData: string[];
legendData?: string[];
labelFormatter?: (val?: any) => string;