ライブラリ
react@18.2.0
recharts@2.8.0
@mui/material@5.14.12
サンプルデータ確認
recharts公式ドキュメントにあるサンプルデータを使う。
ついでに型を作る。
export type Data = {
name: string
uv: number
pv: number
amt: number
}
export type Column = {
key: string | number
}
export type Row = Data & {
key: string | number
}
const data: Data[] = [
{
"name": "Page A",
"uv": 4000,
"pv": 2400,
"amt": 2400
},
{
"name": "Page B",
"uv": 3000,
"pv": 1398,
"amt": 2210
},
{
"name": "Page C",
"uv": 2000,
"pv": 9800,
"amt": 2290
},
{
"name": "Page D",
"uv": 2780,
"pv": 3908,
"amt": 2000
},
{
"name": "Page E",
"uv": 1890,
"pv": 4800,
"amt": 2181
},
{
"name": "Page F",
"uv": 2390,
"pv": 3800,
"amt": 2500
},
{
"name": "Page G",
"uv": 3490,
"pv": 4300,
"amt": 2100
}
]
データの加工
テーブルの列と行に合わせてデータを加工する。
※この例ではヘッダー列を設けています。
let originalColumns: Column[] = data.map(d => ({ key: d.name }))
originalColumns.unshift({ key: 'header' })
const [ columns, setColumns ] = useState<Column[]>(originalColumns)
let rows: Row[] = [ 'uv', 'pv', 'amt' ].map(r => {
let row = { key: r }
columns.forEach(c => {
const originalData = data.find(d => d.name === c.key)
row[c.key] = originalData ? originalData[r] : ''
})
return row as Row
})
テーブルを用意
// ヘッダー列のスタイル
const ColumnSticky: CSSProperties = {
position: 'sticky',
left: 0,
backgroundColor: '#fff',
zIndex: 1,
}
return (
<>
<TableContainer>
<Table>
<TableHead>
{/* ここにグラフを描画する */}
<TableRow>
{columns.map(column => column.key === 'header'
? <TableCell
key={column.key}
width={160}
style={ColumnSticky}
></TableCell>
: <TableCell key={column.key}>{column.key}</TableCell>
)}
</TableRow>
</TableHead>
<TableBody>
{rows.map(row => (
<TableRow key={row.key}>
{columns.map(column => column.key === 'header'
? <TableCell
component="th"
key={`${row.key}___${column.key}`}
width={160}
style={ColumnSticky}
>{row.key}</TableCell>
: <TableCell key={`${row.key}___${column.key}`}>{row[column.key]}</TableCell>
)}
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</>
)
列の幅を計算し、ステートを更新する
副作用フックと、ResizeObserverを使う。
verticalPointsがX軸線の位置、graphData.xが棒グラフ等の位置となる。
<Table ref={table}>
も忘れずに。
const table: MutableRefObject<HTMLTableElement | null> = useRef(null)
const [ verticalPoints, setVerticalPoints ] = useState<number[]>([])
const [ graphData, setGraphData ] = useState(data)
useEffect(() => {
const observer = new ResizeObserver((entries) => {
entries.forEach((e) => {
let columnWidths: number[] = Array.from(e.target.querySelector('thead tr:last-child')?.children || [])
.map((th: HTMLElement) => th.getBoundingClientRect().width)
const verticalPoints = columnWidths.reduce((acc, cur, idx) => {
if(idx === 0) return acc
const accumulated = (acc[idx - 2] || 0) + cur
acc.push(accumulated)
return acc
}, [] as number[])
setVerticalPoints(verticalPoints)
setGraphData(graphData.map((d,i) => {
const x = verticalPoints[i] - ( columnWidths[i + 1] / 2 )
return {
...d,
x,
}
}))
})
})
if(table.current) observer.observe(table.current)
return () => observer.disconnect()
}, [])
return (
<TableContainer>
<Table ref={table}>
{/* 略 */}
</Table>
</TableContainer>
)
グラフコンポーネント
TestChartsコンポーネントの作成。
type=numberのX軸XAxis
を用意し、graphData.xの位置と合わせる。
export interface Props {
data: Data[]
verticalPoints: number[]
}
export const TestCharts:FC<Props> = ({ data, verticalPoints }) => {
return (
<TableRow>
<TableCell
padding="none"
style={ColumnSticky}
>
{/* 目盛用のグラフ */}
<ResponsiveContainer width="100%" height={200}>
<ComposedChart
data={data}
margin={{right: 0, left: 0}}
>
<YAxis
type="number"
includeHidden
domain={['auto', 'auto']}
orientation="right"
mirror
/>
<Bar hide dataKey="uv" />
<Bar hide dataKey="pv" />
<Line hide dataKey="amt" />
</ComposedChart>
</ResponsiveContainer>
</TableCell>
<TableCell
colSpan={verticalPoints.length}
padding="none"
>
{/* 本グラフ */}
<ResponsiveContainer width="100%" height={200}>
<ComposedChart
data={data}
barGap={5}
margin={{right: 0, left: 0}}
>
<CartesianGrid
strokeDasharray="4 1 2"
stroke="#333"
verticalPoints={verticalPoints}
fillOpacity={.6}
/>
<XAxis
hide
dataKey="x"
type="number"
domain={[0, verticalPoints[verticalPoints.length - 1]]}
/>
<Bar dataKey="uv" barSize={20} fill="#00552e" />
<Bar dataKey="pv" barSize={20} fill="#a03e41" />
<Line dataKey="amt" strokeWidth={2} stroke="#33ccff" />
</ComposedChart>
</ResponsiveContainer>
</TableCell>
</TableRow>
)
}
これを先ほどのテーブルに差し込む。
<TableContainer>
<Table ref={table}>
<TableHead>
<TestCharts data={graphData} verticalPoints={verticalPoints} />
{/* 略 */}
</TableHead>
</Table>
</TableContainer>
テーブルの列幅を変えても崩れない、グラフ&テーブルの完成。