Commit 9d285bda by zhaochengxiang

数据地图

parent dc5c437f
......@@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@antv/g2": "^4.1.12",
"@antv/g6": "^4.2.1",
"@craco/craco": "^6.1.1",
"@testing-library/jest-dom": "^5.11.4",
......
......@@ -62,3 +62,7 @@ code {
.ant-layout {
background-color: #eee !important;
}
.ant-tabs-content {
height: 100% !important;
}
import React from 'react';
import { Chart } from '@antv/g2';
class Bar extends React.Component {
componentDidMount() {
const { data, type } = this.props;
setTimeout(() => {
const chart = new Chart({
container: `bar${type||''}`,
autoFit: true,
});
chart.data(data);
chart.scale('value', {
nice: true,
});
chart.tooltip({
showMarkers: false
});
chart.interaction('active-region');
chart.interval().position('name*value');
chart.render();
}, 100);
}
render() {
const { type } = this.props;
return (
<div id={`bar${type||''}`} style={{ width: '100%', height: 150 }}></div>
)
}
}
export default Bar;
\ No newline at end of file
import React from 'react';
import { Chart } from '@antv/g2';
class HorizontalBar extends React.Component {
componentDidMount() {
const { data, type } = this.props;
setTimeout(() => {
const chart = new Chart({
container: `horizontal-bar${type||''}`,
autoFit: true,
});
chart.data(data);
chart.scale('value', { nice: true });
chart.coordinate().transpose();
chart.tooltip({
showMarkers: false
});
chart.interaction('active-region');
chart.interval().position('name*value');
chart.render();
}, 100);
}
render() {
const { type } = this.props;
return (
<div id={`horizontal-bar${type||''}`} style={{ width: '100%', height: 350 }}></div>
)
}
}
export default HorizontalBar;
\ No newline at end of file
import React from 'react';
import G6 from '@antv/g6';
import { ContextPath } from '../../../../util';
const colors = [
'#BDD2FD',
'#BDEFDB',
'#C2C8D5',
'#FBE5A2',
'#F6C3B7',
'#B6E3F5',
'#D3C6EA',
'#FFD8B8',
'#AAD8D8',
'#FFD6E7',
];
const globalFontSize = 12;
class Relation extends React.Component {
componentDidMount() {
const { data, type, history } = this.props;
const container = document.getElementById(`container${type||''}`);
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const fittingString = (str, maxWidth, fontSize) => {
const ellipsis = '...';
const ellipsisLength = G6.Util.getTextSize(ellipsis, fontSize)[0];
let currentWidth = 0;
let res = str;
const pattern = new RegExp('[\u4E00-\u9FA5]+'); // distinguish the Chinese charactors and letters
str.split('').forEach((letter, i) => {
if (currentWidth > maxWidth - ellipsisLength) return;
if (pattern.test(letter)) {
// Chinese charactors
currentWidth += fontSize;
} else {
// get the width of single letter according to the fontSize
currentWidth += G6.Util.getLetterWidth(letter, fontSize);
}
if (currentWidth > maxWidth - ellipsisLength) {
res = `${str.substr(0, i)}${ellipsis}`;
}
});
return res;
};
function refreshDragedNodePosition(e) {
const model = e.item.get('model');
model.fx = e.x;
model.fy = e.y;
}
const tooltip = new G6.Tooltip({
offsetX: 10,
offsetY: 10,
// the types of items that allow the tooltip show up
// 允许出现 tooltip 的 item 类型
itemTypes: ['node'],
// custom the tooltip's content
// 自定义 tooltip 内容
getContent: (e) => {
const outDiv = document.createElement('div');
outDiv.style.width = 'fit-content';
//outDiv.style.padding = '0px 0px 20px 0px';
outDiv.innerHTML = `
<h4>${e.item.getModel().name||''}</h4>
`;
return outDiv;
},
});
const graph = new G6.Graph({
container: `container${type||''}`,
width,
height,
plugins: [tooltip],
layout: {
type: 'force',
preventOverlap: true,
linkDistance: (d) => {
return 200;
},
nodeStrength: (d) => {
if (d.isLeaf) {
return -50;
}
return -10;
},
edgeStrength: (d) => {
return 0.7;
},
},
defaultNode: {
color: '#5B8FF9',
},
modes: {
default: ['drag-canvas'],
},
});
const nodes = data.nodes;
const clusterMap = new Map();
let clusterId = 0;
nodes.forEach(function (node) {
// cluster
if (node.cluster && clusterMap.get(node.cluster) === undefined) {
clusterMap.set(node.cluster, clusterId);
clusterId++;
}
const cid = clusterMap.get(node.cluster);
if (!node.style) {
node.style = {};
}
node.style.fill = colors[cid % colors.length];
});
data.nodes.forEach(function (node) {
node.label = fittingString(node.label, node.size, globalFontSize);
});
graph.data({
nodes,
edges: data.edges.map(function (edge, i) {
edge.id = 'edge' + i;
return Object.assign({}, edge);
}),
});
graph.render();
graph.on('node:dragstart', function (e) {
graph.layout();
refreshDragedNodePosition(e);
});
graph.on('node:drag', function (e) {
refreshDragedNodePosition(e);
});
graph.on('node:dragend', function (e) {
e.item.get('model').fx = null;
e.item.get('model').fy = null;
});
graph.on('node:click', function (e) {
const node = e.item;
const model = node.getModel();
history && history.push(`${ContextPath}/home`);
});
if (typeof window !== 'undefined') {
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight);
};
}
}
render() {
const { type } = this.props;
return (
<>
relation
</>
<div id={`container${type||''}`} style={{ width: '100%', height: '100%' }}></div>
);
}
}
......
import React from 'react';
import { Card } from 'antd';
import { Card } from 'antd';
class SquareItem extends React.Component {
onItemClick = () => {
const { item, onClick } = this.props;
const { item, onClick, history } = this.props;
if (onClick) {
onClick(item);
}
}
}
render() {
......
import React from 'react';
import G6 from '@antv/g6';
import { ContextPath } from '../../../../util';
class Tree extends React.Component {
componentDidMount() {
const { data } = this.props;
const { data, type, history } = this.props;
const container = document.getElementById('mountNode');
const container = document.getElementById(`container${type||''}`);
const width = container.scrollWidth;
const height = container.scrollHeight || 500;
const graph = new G6.TreeGraph({
container: 'mountNode',
container: `container${type||''}`,
width,
height,
modes: {
......@@ -59,18 +61,27 @@ class Tree extends React.Component {
graph.render();
graph.fitView();
graph.on('node:click', function (e) {
const node = e.item;
const model = node.getModel();
history && history.push(`${ContextPath}/home`);
});
if (typeof window !== 'undefined') {
window.onresize = () => {
if (!graph || graph.get('destroyed')) return;
if (!container || !container.scrollWidth || !container.scrollHeight) return;
graph.changeSize(container.scrollWidth, container.scrollHeight);
};
}
}
render() {
const { type } = this.props;
return (
<div id="mountNode"></div>
<div id={`container${type||''}`} style={{ width: '100%', height: '100%' }}></div>
);
}
}
......
import React from 'react';
import { Row, Col, Breadcrumb } from 'antd';
import { Row, Col, Breadcrumb, Card } from 'antd';
import { HomeOutlined } from '@ant-design/icons';
import Bar from './Component/Bar';
import HorizontalBar from './Component/HorizontalBar';
import SquareItem from './Component/SquareItem';
import Tree from './Component/Tree';
import Relation from './Component/Relation';
const barData = [
{ name: '主体', value: 38 },
{ name: '品种', value: 52 },
{ name: '批量', value: 61 },
{ name: '融资', value: 145 },
{ name: '资讯', value: 48 },
{ name: '账户', value: 38 },
{ name: '财务', value: 38 },
{ name: '交易', value: 38 },
];
const horizontalBarData = [
{ name: '资产1', value: 38 },
{ name: '资产2', value: 52 },
{ name: '资产3', value: 61 },
{ name: '资产4', value: 145 },
{ name: '资产5', value: 48 },
{ name: '资产6', value: 38 },
{ name: '资产7', value: 38 },
{ name: '资产8', value: 38 },
{ name: '资产9', value: 38 },
{ name: '资产10', value: 38 },
];
const data = [
{
......@@ -122,9 +149,37 @@ const treeData = {
]
};
const relationData = {
nodes: [
{ id: 'node0', name: '深交所', label: '深交所', size: 80, cluster: 'a' },
{ id: 'node1', name: '公司业务与监管', label: '公司业务与监管', size: 60, cluster: 'b' },
{ id: 'node2', name: '市场交易', label: '市场交易', size: 60, cluster: 'c' },
{ id: 'node3', name: '清算交收', label: '清算交收', size: 60, cluster: 'd' },
{ id: 'node4', name: '固定业务与监管', label: '固定业务与监管', size: 60, isLeaf: true, cluster: 'e' },
{ id: 'node5', name: '基金业务与监管', label: '基金业务与监管', size: 60, isLeaf: true, cluster: 'f' },
{ id: 'node6', name: '会员页面与监管', label: '会员页面与监管', size: 60, isLeaf: true, cluster: 'g' },
{ id: 'node7', name: '公司资料', label: '公司资料', size: 40, isLeaf: true, cluster: 'b' },
{ id: 'node8', name: '公司活动', label: '公司活动', size: 40, isLeaf: true, cluster: 'b' },
{ id: 'node9', name: '公司监管', label: '公司监管', size: 40, isLeaf: true, cluster: 'b' },
{ id: 'node10', name: '信息批露', label: '信息批露', size: 40, isLeaf: true, cluster: 'b' },
],
edges: [
{ source: 'node0', target: 'node1' },
{ source: 'node0', target: 'node2' },
{ source: 'node0', target: 'node3' },
{ source: 'node0', target: 'node4' },
{ source: 'node0', target: 'node5' },
{ source: 'node0', target: 'node6' },
{ source: 'node1', target: 'node7' },
{ source: 'node1', target: 'node8' },
{ source: 'node7', target: 'node9' },
{ source: 'node7', target: 'node10' },
],
};
const column = 3;
class Business extends React.Component {
class MapContent extends React.Component {
constructor(props) {
super(props);
......@@ -155,7 +210,7 @@ class Business extends React.Component {
}
render() {
const { diagram } = this.props;
const { diagram, type } = this.props;
const { curData, breadcrumbContents } = this.state;
let groups = [];
......@@ -166,51 +221,64 @@ class Business extends React.Component {
}
return (
<>
{
diagram==='square' && <>
<Row gutter={10}>
<Col span={8}>
<Card title='数据资产数量'>
<Bar data={barData} />
</Card>
<Card title='数据资产访问TOP10' className='mt-3'>
<HorizontalBar data={horizontalBarData} />
</Card>
</Col>
<Col span={16}>
{
breadcrumbContents && breadcrumbContents.length>1 && <Breadcrumb className='mb-3'>
diagram==='square' && <>
{
breadcrumbContents.map((content, index) => {
breadcrumbContents && breadcrumbContents.length>1 && <Breadcrumb className='mb-3'>
{
breadcrumbContents.map((content, index) => {
return (
<Breadcrumb.Item key={index}>
{
index===0 ? <HomeOutlined onClick={() => { this.onBreadcrumbItemClick(content, index); }} /> : ((index===breadcrumbContents.length-1) ? <span>{content.name||''}</span> : <span className='pointer' onClick={() => { this.onBreadcrumbItemClick(content, index); }}>
{content.name||''}
</span>)
}
</Breadcrumb.Item>
)
})
}
</Breadcrumb>
}
{
groups && groups.map((group, index) => {
return (
<Breadcrumb.Item key={index}>
<Row gutter={10} key={index} className={index===0?'':'mt-3'}>
{
index===0 ? <HomeOutlined onClick={() => { this.onBreadcrumbItemClick(content, index); }} /> : ((index===breadcrumbContents.length-1) ? <span>{content.name||''}</span> : <span className='pointer' onClick={() => { this.onBreadcrumbItemClick(content, index); }}>
{content.name||''}
</span>)
group && group.map((item, _index) => {
return (
<Col key={_index} span={24/column}>
<SquareItem item={item} onClick={this.onSquareItemClick} {...this.props} />
</Col>
)
})
}
</Breadcrumb.Item>
</Row>
)
})
}
</Breadcrumb>
}
{
groups && groups.map((group, index) => {
return (
<Row gutter={10} key={index} className={index===0?'':'mt-3'}>
{
group && group.map((item, _index) => {
return (
<Col key={_index} span={24/column}>
<SquareItem item={item} onClick={this.onSquareItemClick} />
</Col>
)
})
}
</Row>
)
})
}
</>
}
{
diagram==='tree' && <Tree data={treeData} />
}
</>
}
</>
}
{
diagram==='tree' && <Tree data={treeData} type={type} {...this.props} />
}
{
diagram==='relation' && <Relation data={relationData} type={type} {...this.props} />
}
</Col>
</Row>
);
}
}
export default Business;
\ No newline at end of file
export default MapContent;
\ No newline at end of file
import React from 'react';
import { Tabs, Radio } from 'antd';
import Business from './Business';
import MapContent from './MapContent';
const { TabPane } = Tabs;
class Map extends React.Component {
constructor(props) {
super(props);
this.state = { diagram: "square" };
this.state = {
diagram: 'square',
tabKey: '1'
};
}
onChange = e => {
onRadioChange = e => {
this.setState({ diagram: e.target.value });
};
onTabChange = activeKey => {
this.setState({ tabKey: activeKey });
}
render() {
const { diagram } = this.state;
const { diagram, tabKey } = this.state;
return (
<div style={{ backgroundColor: '#fff', height: '100%' }}>
<Tabs
defaultActiveKey='1'
activeKey={tabKey}
size='large'
centered
style={{ height: '100%' }}
tabBarExtraContent={{
left: <Radio.Group className='m-3' size='small' value={diagram} onChange={this.onChange} >
left: <Radio.Group className='m-3' size='small' value={diagram} onChange={this.onRadioChange} >
<Radio.Button value='square'>方块图</Radio.Button>
<Radio.Button value='tree'>树形图</Radio.Button>
<Radio.Button value='relation'>关系图</Radio.Button>
</Radio.Group>,
right: <div style={{ width: 172 }}></div>
}}
onChange={this.onTabChange}
>
<TabPane tab='业务' key='1' className='p-3'>
<Business diagram={diagram} />
<TabPane tab='业务' key='1' className='p-3' style={{ height: '100%' }}>
{ tabKey==='1' && <MapContent diagram={diagram} type='business' {...this.props} /> }
</TabPane>
<TabPane tab='主题' key='2' className='p-3'>
Content of Tab Pane 2
<TabPane tab='主题' key='2' className='p-3' style={{ height: '100%' }}>
{ tabKey==='2' && <MapContent diagram={diagram} type='theme' {...this.props} /> }
</TabPane>
<TabPane tab='来源' key='3' className='p-3'>
Content of Tab Pane 3
<TabPane tab='来源' key='3' className='p-3' style={{ height: '100%' }}>
{ tabKey==='3' && <MapContent diagram={diagram} type='source' {...this.props} /> }
</TabPane>
</Tabs>
</div>
......
......@@ -24,13 +24,16 @@ class Manage extends Component {
<ManageLayout
{...this.props}
content={
session && session.userId ? (
<Switch>
<Route path={`${match.path}/map`} component={Map} />
</Switch>
) : (
<GetSession {...this.props} />
)
<Switch>
<Route path={`${match.path}/map`} component={Map} {...this.props} />
</Switch>
// session && session.userId ? (
// <Switch>
// <Route path={`${match.path}/map`} component={Map} />
// </Switch>
// ) : (
// <GetSession {...this.props} />
// )
}
/>
</React.Fragment>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment