feat: playground embedding
This commit is contained in:
@@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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,5 +1,6 @@
|
||||
export interface ChartProps {
|
||||
seriesData: any[];
|
||||
showEmpty?: boolean;
|
||||
xAxisData: string[];
|
||||
legendData?: string[];
|
||||
labelFormatter?: (val?: any) => string;
|
||||
|
||||
Reference in New Issue
Block a user