交叉表.png
组件
import React from "react";
import { withStyles } from "@material-ui/core";
import { xAxisMockData, measureLabelMockData, mockYAxisColLabel, mockXAxisColLabel, yAxisMockData, mockTaskResult } from "../data";
import { cloneDeep, findIndex, filter, find, isEqual, forEach } from "lodash";
const style: object = (theme: any) => ({
th: {
border: '1px solid',
},
tr: {
}
});
const generateHeader: any = (xAxisData: any[], classes: any) => {
const xAxisDataCopy = cloneDeep(xAxisData);
const xAxisDataLength: number = xAxisDataCopy.length;
const maxXAxisColsCount: number = xAxisDataCopy[xAxisDataLength - 1].length;
const result = [];
const measureLabelLength: number = measureLabelMockData.length;
let measureRow: any = [];
const yAxisColLength: number = mockYAxisColLabel.length;
const yAxisDataLength: number = yAxisMockData.length;
// 生成x轴坐标
for(let i = 0; i < xAxisDataLength; i++){
xAxisDataCopy[i].unshift(
{
name: mockXAxisColLabel[i],
colSpan: 1,
rowSpan: 1,
},
)
}
// 生成表格左上角占位单元格
xAxisDataCopy[0].unshift(
{
name: '',
colSpan: yAxisColLength - 1,
rowSpan: xAxisDataLength,
},
);
// 整理度量标签
measureLabelMockData.forEach(measure => {
measureRow.push(
<th className={classes.th}>{measure}</th>
)
});
// 根据放在x轴最后一个维度数量重复生成度量标签
const basicMeasureRow = cloneDeep(measureRow);
for (let i = 1; i < maxXAxisColsCount; i++){
measureRow = measureRow.concat(basicMeasureRow);
}
// 整理y轴刻度
const yColLabelTds: JSX.Element[] = [];
mockYAxisColLabel.forEach(yCol => yColLabelTds.push(
<th className={classes.th}>{yCol}</th>
));
measureRow.unshift(yColLabelTds);
result.push(
<tr>
{measureRow}
</tr>
);
// row.push(<th className={classes.th} colSpan={yAxisColLength} rowSpan={xAxisDataLength} />);
// 整理表头中x轴的数据
for(let i = xAxisDataLength - 1; i >= 0; i--){
const row: JSX.Element[] = [];
for(const data of xAxisDataCopy[i]){
let colSpan = data.colSpan * measureLabelLength;
const index = findIndex(xAxisDataCopy[i], d => d === data);
// 第一行维度的前两个和每行维度的第一个
if((index === 0) || (i === 0 && index === 1)){
colSpan = data.colSpan;
}
row.push(
<th className={classes.th} colSpan={colSpan} rowSpan={data.rowSpan}>{data.name}</th>
);
}
result.unshift(
<tr>
{row}
</tr>
);
}
// 递归查找需要添加的父级数据
const addParentData = (self: any, level: number, selfIndex: number, row: JSX.Element[]) => {
row.unshift(<td className={classes.th} colSpan={self.colSpan} rowSpan={self.rowSpan}>{self.name}</td>);
if(selfIndex === 0){
// @ts-ignore
const parent = find(yAxisMockData[level - 1], d => d.name === self.parent);
// @ts-ignore
const uncles = level === 1? yAxisMockData[0]: filter(yAxisMockData[level - 1], d => d.parent = parent.parent);
const parentIndex = findIndex(uncles, d => isEqual(d, parent));
if(level !== 0){
addParentData(parent, level - 1, parentIndex, row);
}
}
};
const body = [];
// 整理表格体中y轴数据
const lastLevelIndex = yAxisDataLength - 1;
const rowCopy = cloneDeep(yAxisMockData[lastLevelIndex]);
rowCopy.reverse();
// 生成一个二维数组 宽是x轴字段最后一个数据量 * 度量数量,高是y轴最后一个数据量
const results = new Array(rowCopy.length);
for(let i = 0; i < results.length; i++){
results[i] = new Array(xAxisData[xAxisDataLength - 1].length * measureLabelLength).fill(null);
}
// 整理taskResult
for(const row of mockTaskResult){
let x = 0;
let y = 0;
const yAxisColCount = mockYAxisColLabel.length;
const xAxisColCount = mockXAxisColLabel.length;
const lastXColData = find(row, data => isEqual(data.colName, mockXAxisColLabel[xAxisColCount - 1]));
const lastYColData = find(row, data => isEqual(data.colName, mockYAxisColLabel[yAxisColCount - 1]));
// 找到纵坐标
y = findIndex(yAxisMockData[yAxisColCount - 1], data => isEqual(data.name, lastYColData && lastYColData.value));
// @ts-ignore
x = findIndex(xAxisData[xAxisColCount - 1], data => isEqual(data.name, lastXColData && lastXColData.value)) * measureLabelLength;
for(let i = 0; i < measureLabelLength; i++){
const measure = find(row, data => isEqual(data.colName, measureLabelMockData[i]));
// @ts-ignore
results[y][x + i] = measure.value;
}
}
results.reverse();
// 整理y轴数据和度量数据
for(let i = 0; i < rowCopy.length; i++){
const data = rowCopy[i];
const rowElements: JSX.Element[] = [];
// @ts-ignore
const brothers = filter(yAxisMockData[lastLevelIndex], d => d.parent === data.parent);
const index = findIndex(brothers, d => isEqual(d, data));
addParentData(data, lastLevelIndex, index, rowElements);
const measureThList: JSX.Element[] = [];
forEach(results[i], (value: string) => measureThList.push(<th className={classes.th}>{value}</th>));
// @ts-ignore
rowElements.push(measureThList);
body.unshift(
<tr>
{rowElements}
</tr>
);
}
// @ts-ignore
return result.concat(body);
};
const SummaryTable = (props: any) => {
const { classes } = props;
// 生成表格
const Table: any = generateHeader(xAxisMockData, classes);
return (
<table>
{Table}
</table>
)
};
export default withStyles(style)(SummaryTable);
data.js
const xAxisMockData = [
[
{
name: '黑龙江',
colSpan: 10,
rowSpan: 1,
},
{
name: '吉林',
colSpan: 3,
rowSpan: 1,
},
{
name: '辽宁',
colSpan: 1,
rowSpan: 1,
},
],
[
{
name: '哈尔滨',
parent: '黑龙江',
colSpan: 4,
rowSpan: 1,
},
{
name: '大庆',
parent: '黑龙江',
colSpan: 4,
rowSpan: 1,
},
{
name: '鹤岗',
parent: '黑龙江',
colSpan: 2,
rowSpan: 1,
},
{
name: '长春',
parent: '吉林',
colSpan: 1,
rowSpan: 1,
},
{
name: '四平',
parent: '吉林',
colSpan: 2,
rowSpan: 1,
},
{
name: '大连',
parent: '辽宁',
colSpan: 1,
rowSpan: 1,
},
],
[
{
name: '道里区',
parent: '哈尔滨',
colSpan: 1,
rowSpan: 1,
},
{
name: '南岗区',
parent: '哈尔滨',
colSpan: 1,
rowSpan: 1,
},
{
name: '道外区',
parent: '哈尔滨',
colSpan: 1,
rowSpan: 1,
},
{
name: '平房区',
parent: '哈尔滨',
colSpan: 1,
rowSpan: 1,
},
{
name: '萨尔图区',
parent: '大庆',
colSpan: 1,
rowSpan: 1,
},
{
name: '龙凤区',
parent: '大庆',
colSpan: 1,
rowSpan: 1,
},
{
name: '肇州县',
parent: '大庆',
colSpan: 1,
rowSpan: 1,
},
{
name: '林甸县',
parent: '大庆',
colSpan: 1,
rowSpan: 1,
},
{
name: '东山区',
parent: '鹤岗',
colSpan: 1,
rowSpan: 1,
},
{
name: '南山区',
parent: '鹤岗',
colSpan: 1,
rowSpan: 1,
},
{
name: '朝阳区',
parent: '长春',
colSpan: 1,
rowSpan: 1,
},
{
name: '铁西区',
parent: '四平',
colSpan: 1,
rowSpan: 1,
},
{
name: '铁东区',
parent: '四平',
colSpan: 1,
rowSpan: 1,
},
{
name: '中山区',
parent: '大连',
colSpan: 1,
rowSpan: 1,
},
]
];
const yAxisMockData = [
[
{
name: '菜鸟驿站',
colSpan: 1,
rowSpan: 2,
},
{
name: '京东',
colSpan: 1,
rowSpan: 2
},
],
[
{
name: '韵达',
parent: '菜鸟驿站',
colSpan: 1,
rowSpan: 1
},
{
name: '申通',
parent: '菜鸟驿站',
colSpan: 1,
rowSpan: 1
},
{
name: '京东物流',
parent: '京东',
colSpan: 1,
rowSpan: 1
},
{
name: '京东派',
parent: '京东',
colSpan: 1,
rowSpan: 1
},
]
];
const measureLabelMockData = [
'好评度', '首重价格', '续重价格'
];
const mockYAxisColLabel = [
'快递类别', '快递名称',
];
const mockXAxisColLabel = [
'省', '市', '区'
];
const mockTaskResult = [
[
{
colName: '省',
value: '黑龙江'
},
{
colName: '市',
value: '大庆',
},
{
colName: '区',
value: '林甸县',
},
{
colName: '快递类别',
value: '京东'
},
{
colName: '快递名称',
value: '京东物流',
},
{
colName: '好评度',
value: 4.0,
},
{
colName: '首重价格',
value: 12,
},
{
colName: '续重价格',
value: 8,
}
],
[
{
colName: '省',
value: '吉林'
},
{
colName: '市',
value: '长春',
},
{
colName: '区',
value: '朝阳区',
},
{
colName: '快递类别',
value: '菜鸟驿站'
},
{
colName: '快递名称',
value: '韵达',
},
{
colName: '好评度',
value: 4.5,
},
{
colName: '首重价格',
value: 10,
},
{
colName: '续重价格',
value: 10,
}
],
];
export { measureLabelMockData, xAxisMockData, yAxisMockData, mockYAxisColLabel, mockXAxisColLabel, mockTaskResult };
网友评论