@@ -1,6 +1,5 @@
import React from 'react' ;
import React , { useEffect , useRef , useState } from 'react' ;
import {
ResponsiveContainer ,
LineChart ,
Line ,
XAxis ,
@@ -15,8 +14,10 @@ import {
Bar ,
} from 'recharts' ;
// =========================
// 默认颜色池
// =========================
const DEFAULT _COLORS = [
'#1677ff' ,
'#52c41a' ,
@@ -29,11 +30,114 @@ const DEFAULT_COLORS = [
] ;
// =========================
// 通用自适应容器
// =========================
//
// 不再使用 ResponsiveContainer。
// 通过 ResizeObserver 获取真实尺寸,
// 只有 width / height > 0 时才渲染 Recharts。
// 这样可以彻底避免:
//
// width(-1) and height(-1)
//
// =========================
const ChartResizeContainer = ( {
children ,
width = '100%' ,
height = 360 ,
className ,
style ,
} ) => {
const containerRef = useRef ( null ) ;
const [ size , setSize ] = useState ( {
width : 0 ,
height : 0 ,
} ) ;
useEffect ( ( ) => {
const element = containerRef . current ;
if ( ! element ) {
return ;
}
const updateSize = ( ) => {
const rect = element . getBoundingClientRect ( ) ;
const nextWidth = Math . floor ( rect . width ) ;
const nextHeight = Math . floor ( rect . height ) ;
// 非法尺寸直接忽略
if ( nextWidth <= 0 || nextHeight <= 0 ) {
return ;
}
setSize ( ( prev ) => {
if (
prev . width === nextWidth &&
prev . height === nextHeight
) {
return prev ;
}
return {
width : nextWidth ,
height : nextHeight ,
} ;
} ) ;
} ;
// 首次主动测量
updateSize ( ) ;
// 后续监听尺寸变化
const observer = new ResizeObserver ( ( ) => {
updateSize ( ) ;
} ) ;
observer . observe ( element ) ;
return ( ) => {
observer . disconnect ( ) ;
} ;
} , [ ] ) ;
return (
< div
ref = { containerRef }
className = { className }
style = { {
width ,
height ,
minWidth : 0 ,
minHeight : 0 ,
boxSizing : 'border-box' ,
overflow : 'hidden' ,
... style ,
} }
>
{ size . width > 0 && size . height > 0
? React . cloneElement ( children , {
width : size . width ,
height : size . height ,
} )
: null }
< / div >
) ;
} ;
// =========================
// Universal Line Chart
// =========================
export const UniversalLineChart = ( {
data ,
data = [ ] ,
xKey ,
series ,
series = [ ] ,
width = '100%' ,
height = 360 ,
showGrid = true ,
@@ -43,6 +147,7 @@ export const UniversalLineChart = ({
smooth = true ,
dot = false ,
className ,
style ,
} ) => {
return (
< div
@@ -50,48 +155,80 @@ export const UniversalLineChart = ({
style = { {
width ,
height ,
minWidth : 0 ,
minHeight : 0 ,
boxSizing : 'border-box' ,
background : backgroundColor ,
borderRadius : 12 ,
padding : 12 ,
overflow : 'hidden' ,
... style ,
} }
>
< Responsiv eContainer width = "100%" height = "100%" minWidth = { 0 } minHeight = { 0 } >
< LineChart data = { data } minWidth = { 0 } minHeight = { 0 } width = "100%" height = "100%" >
< ChartResiz eContainer
width = "100%"
height = "100%"
>
< LineChart data = { data } >
{ showGrid && (
< CartesianGrid strokeDasharray = "3 3" opacity = { 0.25 } / >
< CartesianGrid
strokeDasharray = "3 3"
opacity = { 0.25 }
/ >
) }
< XAxis dataKey = { xKey } / >
< YAxis / >
{ showTooltip && < Tooltip / > }
{ showLegend && < Legend / > }
{ series . map ( ( item , index ) => (
< Line
key = { item . key }
type = { smooth ? 'monotone' : 'linear' }
type = {
smooth
? 'monotone'
: 'linear'
}
dataKey = { item . key }
name = { item . name }
stroke = { item . color || DEFAULT _COLORS [ index % DEFAULT _COLORS . length ] }
strokeWidth = { item . strokeWidth || 2 }
strokeDasharray = { item . dashed ? '6 6' : undefined }
stroke = {
item . color ||
DEFAULT _COLORS [
index %
DEFAULT _COLORS . length
]
}
strokeWidth = {
item . strokeWidth || 2
}
strokeDasharray = {
item . dashed
? '6 6'
: undefined
}
dot = { dot }
activeDot = { { r : 6 } }
/ >
) ) }
< / LineChart >
< / Responsiv eContainer>
< / ChartResiz eContainer>
< / div >
) ;
} ;
// =========================
// Universal Bar Chart
// =========================
export const UniversalBarChart = ( {
data ,
data = [ ] ,
xKey ,
series ,
series = [ ] ,
width = '100%' ,
height = 360 ,
showGrid = true ,
@@ -102,6 +239,7 @@ export const UniversalBarChart = ({
radius = 6 ,
stack = false ,
className ,
style ,
} ) => {
return (
< div
@@ -109,21 +247,34 @@ export const UniversalBarChart = ({
style = { {
width ,
height ,
minWidth : 0 ,
minHeight : 0 ,
boxSizing : 'border-box' ,
background : backgroundColor ,
borderRadius : 12 ,
padding : 12 ,
overflow : 'hidden' ,
... style ,
} }
>
< Responsiv eContainer width = "100%" height = "100%" minWidth = { 0 } minHeight = { 0 } >
< BarChart data = { data } minWidth = { 0 } minHeight = { 0 } width = "100%" height = "100%" >
< ChartResiz eContainer
width = "100%"
height = "100%"
>
< BarChart data = { data } >
{ showGrid && (
< CartesianGrid strokeDasharray = "3 3" opacity = { 0.25 } / >
< CartesianGrid
strokeDasharray = "3 3"
opacity = { 0.25 }
/ >
) }
< XAxis dataKey = { xKey } / >
< YAxis / >
{ showTooltip && < Tooltip / > }
{ showLegend && < Legend / > }
{ series . map ( ( item , index ) => (
@@ -131,22 +282,35 @@ export const UniversalBarChart = ({
key = { item . key }
dataKey = { item . key }
name = { item . name }
fill = { item . color || DEFAULT _COLORS [ index % DEFAULT _COLORS . length ] }
fill = {
item . color ||
DEFAULT _COLORS [
index %
DEFAULT _COLORS . length
]
}
barSize = { barSize }
radius = { radius }
stackId = { stack ? 'stack' : undefined }
stackId = {
stack
? 'stack'
: undefined
}
/ >
) ) }
< / BarChart >
< / Responsiv eContainer>
< / ChartResiz eContainer>
< / div >
) ;
} ;
// =========================
// Universal Pie Chart
// =========================
export const UniversalPieChart = ( {
data ,
data = [ ] ,
width = '100%' ,
height = 360 ,
showLegend = true ,
@@ -157,7 +321,7 @@ export const UniversalPieChart = ({
paddingAngle = 2 ,
showLabel = true ,
className ,
style
style ,
} ) => {
return (
< div
@@ -165,15 +329,23 @@ export const UniversalPieChart = ({
style = { {
width ,
height ,
minWidth : 0 ,
minHeight : 0 ,
boxSizing : 'border-box' ,
background : backgroundColor ,
borderRadius : 12 ,
padding : 12 ,
... style
overflow : 'hidden' ,
... style ,
} }
>
< Responsiv eContainer width = "100%" height = "100%" minWidth = { 0 } minHeight = { 0 } >
< PieChart minWidth = { 0 } minHeight = { 0 } width = "100%" height = "100%" >
< ChartResiz eContainer
width = "100%"
height = "100%"
>
< PieChart >
{ showTooltip && < Tooltip / > }
{ showLegend && < Legend / > }
< Pie
@@ -190,37 +362,86 @@ export const UniversalPieChart = ({
{ data . map ( ( entry , index ) => (
< Cell
key = { ` cell- ${ index } ` }
fill = { entry . color || DEFAULT _COLORS [ index % DEFAULT _COLORS . length ] }
fill = {
entry . color ||
DEFAULT _COLORS [
index %
DEFAULT _COLORS . length
]
}
/ >
) ) }
< / Pie >
< / PieChart >
< / Responsiv eContainer>
< / ChartResiz eContainer>
< / div >
) ;
} ;
// =========================
// 使用示例
// =========================
export const DemoCharts = ( ) => {
const lineData = [
{ month : '1月' , uv : 400 , pv : 240 , amt : 240 } ,
{ month : '2 月' , uv : 300 , pv : 456 , amt : 300 } ,
{ month : '3月' , uv : 5 00, pv : 139 , amt : 200 } ,
{ month : '4月' , uv : 278 , pv : 390 , amt : 278 } ,
{ month : '5月' , uv : 189 , pv : 480 , amt : 189 } ,
{
month : '1 月' ,
uv : 4 00,
pv : 240 ,
amt : 240 ,
} ,
{
month : '2月' ,
uv : 300 ,
pv : 456 ,
amt : 300 ,
} ,
{
month : '3月' ,
uv : 500 ,
pv : 139 ,
amt : 200 ,
} ,
{
month : '4月' ,
uv : 278 ,
pv : 390 ,
amt : 278 ,
} ,
{
month : '5月' ,
uv : 189 ,
pv : 480 ,
amt : 189 ,
} ,
] ;
const pieData = [
{ name : 'Vue' , value : 40 } ,
{ name : 'React' , value : 35 } ,
{ name : 'Angular' , value : 25 } ,
{
name : 'Vue' ,
value : 40 ,
} ,
{
name : 'React' ,
value : 35 ,
} ,
{
name : 'Angular' ,
value : 25 ,
} ,
] ;
return (
< div className = "grid grid-cols-1 gap-6 p-6" >
< div
className = "
grid
grid-cols-1
gap-6
p-6
min-w-0
"
>
< UniversalLineChart
data = { lineData }
xKey = "month"