继上次实现图谱后,后续发现如果要继续加入不同样式的图谱实现起来太过麻烦,因此考虑将配置项全部提取封装到js文件中,图谱组件只专注于实现各种不同的组件,其中主要封装的点就是各个节点的横坐标(x),纵坐标(y),以及节点宽(width),节点高(height),节点数组(nodes)和节点相连的边(edges),其他如颜色,字体大小等
封装效果如下:
mapData对象中保存每种金属对应的产业链,通过判断来选择取哪部分数据去显示对应的图谱,比如MapData['CU']即为显示铜的图谱,mapHeight为图谱的默认高度,mapPadding为图谱的父节点要显示的padding,因为图谱不一定需要占满显示,因此加了padding,desc为图谱的描述,nodes和edges为节点和边
实现效果如下:
其中mapData.js内容:
//有色金属
export const mapData = {
CU: {
//铜
mapHeight: 450,
mapPadding: 35,
desc: '<span>铜产业链逻辑:</span>铜产业链分为上、中、下游。上游主要是采选、冶炼,中游是铜材加工,下游是终端消费。铜价主要受产业供需和宏观因素两个方面影响,但从研究分析上来讲产业可看供给,需求看宏观。宏观因素(核心):经济周期、政策周期、通胀/通缩;基本面因素:供需平衡表、库存周期、价差结构与区域溢价(短期)其他因素:商品大环境、资金面(博弈)、情绪面(事件、避险)',
nodes: [
{
id: 'node1',
x: 138,
y: 135,
width: 176,
height: 60,
label: '全球铜矿山产能',
breedCode: 'CU',
indexCode: 'OFFRDE0678557898',
text: '全球铜矿山产能',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node2',
x: 138,
y: 209,
width: 176,
height: 60,
label: '全球铜矿产能利用率',
breedCode: 'CU',
indexCode: 'ID00303168',
text: '全球铜矿产能利用率',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node3',
x: 394,
y: 172,
width: 134,
height: 60,
label: '全球铜矿供应',
breedCode: 'CU',
indexCode: 'OFFRDE0789849953',
text: '全球铜矿供应',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node4',
x: 394,
y: 264,
width: 134,
height: 60,
label: '加工费',
breedCode: 'CU',
indexCode: 'OFFRDE0407946063',
text: '加工费',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node5',
x: 394,
y: 357,
width: 134,
height: 60,
label: '国内铜矿供应',
breedCode: 'CU',
indexCode: 'OFFRDE0503146446',
text: '国内铜矿供应',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node6',
x: 591,
y: 172,
width: 158,
height: 60,
label: '精炼铜供应',
breedCode: 'CU',
indexCode: 'ID00407927',
text: '精炼铜供应',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node7',
x: 591,
y: 234,
width: 158,
height: 40,
label: '再生(废杂)铜供应',
text: '各类政策因素',
breedCode: 'JM',
text: '再生(废杂)铜供应',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node8',
x: 591,
y: 286,
width: 158,
height: 40,
label: '各类政策因素',
text: '各类政策因素',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node9',
x: 838,
y: 31,
width: 134,
height: 60,
label: '全球社会库存',
text: '全球社会库存',
breedCode: 'CU',
indexCode: 'OFFRDE0416122129',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node10',
x: 984,
y: 31,
width: 134,
height: 60,
label: 'LME铜库存',
text: 'LME铜库存',
breedCode: 'CU',
indexCode: 'OFFRDE0099802675',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node11',
x: 1130,
y: 31,
width: 134,
height: 60,
label: 'SHFE铜库存',
text: 'SHFE铜库存',
breedCode: 'CU',
indexCode: 'OFFRDE0900823051',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node12',
x: 1281,
y: 31,
width: 145,
height: 60,
label: 'COMEX铜库存',
text: 'COMEX铜库存',
breedCode: 'CU',
indexCode: 'OFFRDE0470038275',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node13',
x: 838,
y: 136,
width: 134,
height: 60,
label: '总库存变化',
breedCode: 'CU',
indexCode: 'OFFRDE0704512926',
text: '总库存变化',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node14',
x: 838,
y: 234,
width: 134,
height: 60,
label: '铜现货价',
breedCode: 'CU',
indexCode: 'ID00303957',
text: '铜现货价',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node15',
x: 838,
y: 326,
width: 134,
height: 60,
label: '比价与价差',
text: '比价与价差',
indexCode: 'FU00015882',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node16',
x: 838,
y: 418,
width: 134,
height: 60,
label: '铜期货价',
text: '铜期货价',
indexCode: 'FU00015764',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node17',
x: 1023,
y: 136,
width: 134,
height: 60,
label: '库存消费比',
breedCode: 'CU',
indexCode: 'OFFRDE0881777811',
text: '库存消费比',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node18',
x: 1023,
y: 234,
width: 134,
height: 60,
label: '表观需求',
breedCode: 'CU',
indexCode: 'ID01167294',
text: '表观需求',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node19',
x: 1023,
y: 326,
width: 134,
height: 40,
label: '贸易流向',
text: '贸易流向',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node20',
x: 1023,
y: 418,
width: 134,
height: 40,
label: '资金因素',
text: '资金因素',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node21',
x: 1257,
y: 157,
width: 134,
height: 40,
label: '房地产开工',
text: '房地产开工',
type: 'word-rect',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node22',
x: 1257,
y: 209,
width: 134,
height: 40,
label: '汽车产销',
text: '汽车产销',
type: 'word-rect',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node23',
x: 1257,
y: 261,
width: 134,
height: 40,
label: '基建投资',
text: '基建投资',
type: 'word-rect',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node24',
x: 1257,
y: 313,
width: 134,
height: 40,
label: '家电等终端',
text: '家电等终端',
type: 'word-rect',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
],
edges: [
{ source: 'node1', target: 'node3' },
{ source: 'node2', target: 'node3' },
{ source: 'node3', target: 'node4', type: 'hvh3' },
{ source: 'node4', target: 'node5', type: 'hvh3' },
{ source: 'node3', target: 'node6' },
{ source: 'node6', target: 'node14' },
{ source: 'node7', target: 'node14' },
{ source: 'node8', target: 'node14' },
{ source: 'node9', target: 'node13', type: 'hvh3' },
{
source: 'node10',
target: 'node13',
type: 'hvh3',
sourceAnchor: 2,
targetAnchor: 0,
},
{
source: 'node11',
target: 'node13',
type: 'hvh3',
sourceAnchor: 2,
targetAnchor: 0,
},
{
source: 'node12',
target: 'node13',
type: 'hvh3',
sourceAnchor: 2,
targetAnchor: 0,
},
{ source: 'node13', target: 'node17' },
{ source: 'node13', target: 'node14', type: 'hvh3' },
{ source: 'node14', target: 'node18' },
{ source: 'node14', target: 'node15', type: 'hvh3' },
{ source: 'node15', target: 'node19' },
{ source: 'node15', target: 'node16', type: 'hvh3' },
{ source: 'node16', target: 'node20' },
{ source: 'node18', target: 'node21' },
{ source: 'node18', target: 'node22' },
{ source: 'node18', target: 'node23' },
{ source: 'node18', target: 'node24' },
],
},
AL: {
//铝
mapHeight: 229,
mapPadding: 25,
desc: '<span>铝产业链逻辑:</span>铝的产业链主要由铝土矿开采、氧化铝提炼、原铝生产和铝材加工四个环节组成。首先是铝土矿开采,再通过对铝土矿溶解、过滤、酸化和灼烧等工序提炼出氧化铝,然后通过电解熔融的方式制备电解铝。电解铝经过重熔提纯后可进一步加工成各种铝材、铝合金以及铝粉等。铝的上游产业链包括铝土矿开采、氧化铝提炼和原铝生产。原铝经过加工,制成铝加工材,应用于下游各行各业。',
nodes: [
{
id: 'block1',
x: 176,
y: 120,
width: 353,
height: 218,
type: 'block-rect',
label: '上游',
},
{
id: 'block2',
x: 530,
y: 120,
width: 330,
height: 218,
type: 'block-rect',
label: '中游',
},
{
id: 'block3',
x: 1020,
y: 120,
width: 626,
height: 218,
type: 'block-rect',
label: '下游',
},
{
id: 'node1',
x: 80,
y: 63,
width: 134,
height: 60,
label: '铝土矿',
breedCode: 'AL',
indexCode: 'OFFRDE0116552998',
text: '铝土矿',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node2',
x: 80,
y: 127,
width: 134,
height: 40,
label: '煤炭',
text: '煤炭',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node3',
x: 80,
y: 179,
width: 134,
height: 40,
label: '石灰石和碱',
text: '石灰石和碱',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node4',
x: 274,
y: 127,
width: 134,
height: 60,
label: '氧化铝',
breedCode: 'AL',
indexCode: 'ID00188139',
text: '氧化铝',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node5',
x: 444,
y: 75,
width: 132,
height: 40,
label: '电力',
text: '电力',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node6',
x: 444,
y: 127,
width: 132,
height: 40,
label: '氧化铝',
text: '氧化铝',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node7',
x: 444,
y: 185,
width: 132,
height: 52,
label: '碳素阳极、氟化盐、冰晶石等',
text: '碳素阳极、氟化盐、冰晶石等',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node8',
x: 616,
y: 127,
width: 134,
height: 60,
label: '电解铝',
breedCode: 'AL',
indexCode: 'ID00188823',
text: '电解铝',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node9',
x: 795,
y: 127,
width: 134,
height: 60,
label: '铝合金锭',
breedCode: 'AL',
indexCode: 'ID01224399',
text: '铝合金锭',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node10',
x: 970,
y: 63,
width: 74,
height: 40,
label: '挤压材',
text: '挤压材',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node11',
x: 970,
y: 127,
width: 74,
height: 40,
label: '压制材',
text: '压制材',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node12',
x: 970,
y: 191,
width: 74,
height: 40,
label: '铸造材',
text: '铸造材',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node13',
x: 1106,
y: 63,
width: 116,
height: 52,
label: '管、棒、型、线等',
text: '管、棒、型、线等',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node14',
x: 1106,
y: 127,
width: 116,
height: 52,
label: '板、带、片、箔等',
text: '板、带、片、箔等',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node15',
x: 1106,
y: 191,
width: 116,
height: 52,
label: '汽车轮毂、发动机等',
text: '汽车轮毂、发动机等',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node16',
x: 1263,
y: 63,
width: 116,
height: 52,
label: '建筑、光伏、线缆',
text: '建筑、光伏、线缆',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node17',
x: 1263,
y: 127,
width: 116,
height: 52,
label: '包装、汽车、家电',
text: '包装、汽车、家电',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node18',
x: 1263,
y: 191,
width: 116,
height: 52,
label: '汽车、五金、电器电子',
text: '汽车、五金、电器电子',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
],
edges: [
{ source: 'node1', target: 'node4' },
{ source: 'node2', target: 'node4' },
{ source: 'node3', target: 'node4' },
{ source: 'node4', target: 'node6' },
{ source: 'node5', target: 'node8' },
{ source: 'node6', target: 'node8' },
{ source: 'node7', target: 'node8' },
{ source: 'node8', target: 'node9' },
{ source: 'node9', target: 'node10' },
{ source: 'node9', target: 'node11' },
{ source: 'node9', target: 'node12' },
{ source: 'node10', target: 'node13' },
{ source: 'node11', target: 'node14' },
{ source: 'node12', target: 'node15' },
{ source: 'node13', target: 'node16' },
{ source: 'node14', target: 'node17' },
{ source: 'node15', target: 'node18' },
],
},
AO: {
//氧化铝,与铝一样
mapHeight: 229,
mapPadding: 25,
desc: '<span>铝产业链逻辑:</span>铝的产业链主要由铝土矿开采、氧化铝提炼、原铝生产和铝材加工四个环节组成。首先是铝土矿开采,再通过对铝土矿溶解、过滤、酸化和灼烧等工序提炼出氧化铝,然后通过电解熔融的方式制备电解铝。电解铝经过重熔提纯后可进一步加工成各种铝材、铝合金以及铝粉等。铝的上游产业链包括铝土矿开采、氧化铝提炼和原铝生产。原铝经过加工,制成铝加工材,应用于下游各行各业。',
nodes: [
{
id: 'block1',
x: 176,
y: 120,
width: 353,
height: 218,
type: 'block-rect',
label: '上游',
},
{
id: 'block2',
x: 530,
y: 120,
width: 330,
height: 218,
type: 'block-rect',
label: '中游',
},
{
id: 'block3',
x: 1020,
y: 120,
width: 626,
height: 218,
type: 'block-rect',
label: '下游',
},
{
id: 'node1',
x: 80,
y: 63,
width: 134,
height: 60,
label: '铝土矿',
breedCode: 'AL',
indexCode: 'OFFRDE0116552998',
text: '铝土矿',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node2',
x: 80,
y: 127,
width: 134,
height: 40,
label: '煤炭',
text: '煤炭',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node3',
x: 80,
y: 179,
width: 134,
height: 40,
label: '石灰石和碱',
text: '石灰石和碱',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node4',
x: 274,
y: 127,
width: 134,
height: 60,
label: '氧化铝',
breedCode: 'AL',
indexCode: 'ID00188139',
text: '氧化铝',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node5',
x: 444,
y: 75,
width: 132,
height: 40,
label: '电力',
text: '电力',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node6',
x: 444,
y: 127,
width: 132,
height: 40,
label: '氧化铝',
text: '氧化铝',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node7',
x: 444,
y: 185,
width: 132,
height: 52,
label: '碳素阳极、氟化盐、冰晶石等',
text: '碳素阳极、氟化盐、冰晶石等',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node8',
x: 616,
y: 127,
width: 134,
height: 60,
label: '电解铝',
breedCode: 'AL',
indexCode: 'ID00188823',
text: '电解铝',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node9',
x: 795,
y: 127,
width: 134,
height: 60,
label: '铝合金锭',
breedCode: 'AL',
indexCode: 'ID01224399',
text: '铝合金锭',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node10',
x: 970,
y: 63,
width: 74,
height: 40,
label: '挤压材',
text: '挤压材',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node11',
x: 970,
y: 127,
width: 74,
height: 40,
label: '压制材',
text: '压制材',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node12',
x: 970,
y: 191,
width: 74,
height: 40,
label: '铸造材',
text: '铸造材',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node13',
x: 1106,
y: 63,
width: 116,
height: 52,
label: '管、棒、型、线等',
text: '管、棒、型、线等',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node14',
x: 1106,
y: 127,
width: 116,
height: 52,
label: '板、带、片、箔等',
text: '板、带、片、箔等',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node15',
x: 1106,
y: 191,
width: 116,
height: 52,
label: '汽车轮毂、发动机等',
text: '汽车轮毂、发动机等',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node16',
x: 1263,
y: 63,
width: 116,
height: 52,
label: '建筑、光伏、线缆',
text: '建筑、光伏、线缆',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node17',
x: 1263,
y: 127,
width: 116,
height: 52,
label: '包装、汽车、家电',
text: '包装、汽车、家电',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node18',
x: 1263,
y: 191,
width: 116,
height: 52,
label: '汽车、五金、电器电子',
text: '汽车、五金、电器电子',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
],
edges: [
{ source: 'node1', target: 'node4' },
{ source: 'node2', target: 'node4' },
{ source: 'node3', target: 'node4' },
{ source: 'node4', target: 'node6' },
{ source: 'node5', target: 'node8' },
{ source: 'node6', target: 'node8' },
{ source: 'node7', target: 'node8' },
{ source: 'node8', target: 'node9' },
{ source: 'node9', target: 'node10' },
{ source: 'node9', target: 'node11' },
{ source: 'node9', target: 'node12' },
{ source: 'node10', target: 'node13' },
{ source: 'node11', target: 'node14' },
{ source: 'node12', target: 'node15' },
{ source: 'node13', target: 'node16' },
{ source: 'node14', target: 'node17' },
{ source: 'node15', target: 'node18' },
],
},
PB: {
//铅
mapHeight: 501,
mapPadding: 100,
desc: '',
nodes: [
{
id: 'block1',
x: 165,
y: 255,
width: 328,
height: 490,
type: 'block-rect',
label: '原料端',
labelCfg: { style: { width: 52, height: 20 } },
},
{
id: 'block2',
x: 475,
y: 255,
width: 270,
height: 490,
type: 'block-rect',
label: '冶炼端',
labelCfg: { style: { width: 52, height: 20 } },
},
{
id: 'block3',
x: 751,
y: 255,
width: 258,
height: 490,
type: 'block-rect',
label: '初端下游产品',
labelCfg: { style: { width: 88, height: 20 } },
},
{
id: 'block4',
x: 1037,
y: 255,
width: 290,
height: 490,
type: 'block-rect',
label: '终端需求',
labelCfg: { style: { width: 64, height: 20 } },
},
{
id: 'node1',
x: 50,
y: 183,
width: 74,
height: 40,
label: '铅原矿',
text: '铅原矿',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node2',
x: 164,
y: 183,
width: 74,
height: 40,
label: '铅精矿',
text: '铅精矿',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node3',
x: 279,
y: 183,
width: 74,
height: 40,
label: '粗铅',
text: '粗铅',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node4',
x: 432,
y: 131,
width: 74,
height: 40,
label: '阳极泥',
text: '阳极泥',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node5',
x: 554,
y: 131,
width: 88,
height: 40,
label: '金银锡锑',
text: '金银锡锑',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node6',
x: 554,
y: 183,
width: 88,
height: 40,
label: '原生铅',
text: '原生铅',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node7',
x: 778,
y: 104,
width: 156,
height: 40,
label: '其他',
text: '其他',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node8',
x: 778,
y: 156,
width: 156,
height: 40,
label: '蓄电池企业 (>85%)',
text: '蓄电池企业 (>85%)',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
no_compute: true,
},
{
id: 'node9',
x: 778,
y: 312,
width: 156,
height: 40,
label: '氧化铅',
text: '氧化铅',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node10',
x: 778,
y: 364,
width: 156,
height: 40,
label: '铅合金',
text: '铅合金',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node11',
x: 778,
y: 416,
width: 156,
height: 40,
label: '铅材',
text: '铅材',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node12',
x: 1044,
y: 52,
width: 172,
height: 40,
label: '汽车启动',
text: '汽车启动',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
fill: 'l(90) 0:#FBFCFF 1:#E9EDF3',
strock: '#D4DBE7',
},
{
id: 'node13',
x: 1044,
y: 104,
width: 172,
height: 40,
label: '电动车',
text: '电动车',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
fill: 'l(90) 0:#FBFCFF 1:#E9EDF3',
strock: '#D4DBE7',
},
{
id: 'node14',
x: 1044,
y: 156,
width: 172,
height: 40,
label: '通信基站',
text: '通信基站',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
fill: 'l(90) 0:#FBFCFF 1:#E9EDF3',
strock: '#D4DBE7',
},
{
id: 'node15',
x: 1044,
y: 208,
width: 172,
height: 40,
label: '电力',
text: '电力',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
fill: 'l(90) 0:#FBFCFF 1:#E9EDF3',
strock: '#D4DBE7',
},
{
id: 'node16',
x: 1044,
y: 260,
width: 172,
height: 40,
label: '其他',
text: '其他',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
fill: 'l(90) 0:#FBFCFF 1:#E9EDF3',
strock: '#D4DBE7',
},
{
id: 'node17',
x: 1044,
y: 312,
width: 172,
height: 40,
label: '铝盐、稳定剂、助溶剂',
text: '铝盐、稳定剂、助溶剂',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
fill: 'l(90) 0:#FBFCFF 1:#E9EDF3',
strock: '#D4DBE7',
},
{
id: 'node18',
x: 1044,
y: 364,
width: 172,
height: 40,
label: '轴承、焊料、铅弹',
text: '轴承、焊料、铅弹',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
fill: 'l(90) 0:#FBFCFF 1:#E9EDF3',
strock: '#D4DBE7',
},
{
id: 'node19',
x: 1044,
y: 416,
width: 172,
height: 40,
label: '铅板、铅管、电缆护套',
text: '铅板、铅管、电缆护套',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
fill: 'l(90) 0:#FBFCFF 1:#E9EDF3',
strock: '#D4DBE7',
},
{
id: 'node20',
x: 778,
y: 468,
width: 156,
height: 40,
label: '回收',
text: '回收',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
fill: 'l(90) 0:#FFF4F4 1:#FFE1E1',
strock: '#F8C3C3',
},
{
id: 'node21',
x: 49,
y: 312,
width: 74,
height: 40,
label: '铅废料',
text: '铅废料',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
fill: 'l(90) 0:#FFF4F4 1:#FFE1E1',
strock: '#F8C3C3',
},
{
id: 'node22',
x: 279,
y: 312,
width: 74,
height: 40,
label: '还原铅',
text: '还原铅',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
fill: 'l(90) 0:#FFF4F4 1:#FFE1E1',
strock: '#F8C3C3',
},
{
id: 'node23',
x: 554,
y: 312,
width: 88,
height: 40,
label: '再生铅',
text: '再生铅',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
fill: 'l(90) 0:#FFF4F4 1:#FFE1E1',
strock: '#F8C3C3',
},
],
edges: [
{ source: 'node1', target: 'node2' },
{ source: 'node2', target: 'node3' },
{ source: 'node3', target: 'node4' },
{
source: 'node3',
target: 'node6',
label: '火法',
subLabel: '电解',
labelCfg: { style: { fontSize: 12, fill: '#245A9A' } },
subLabelCfg: { style: { fontSize: 12, fill: '#245A9A' } },
},
{ source: 'node4', target: 'node5' },
{ source: 'node6', target: 'node7' },
{ source: 'node6', target: 'node8' },
{
source: 'node6',
target: 'node9',
sourceAnchor: 1,
targetAnchor: 3,
},
{
source: 'node6',
target: 'node10',
sourceAnchor: 1,
targetAnchor: 3,
},
{
source: 'node6',
target: 'node11',
sourceAnchor: 1,
targetAnchor: 3,
},
{ source: 'node8', target: 'node12' },
{ source: 'node8', target: 'node13' },
{ source: 'node8', target: 'node14' },
{ source: 'node8', target: 'node15' },
{ source: 'node8', target: 'node16' },
{ source: 'node9', target: 'node17' },
{ source: 'node10', target: 'node18' },
{ source: 'node11', target: 'node19' },
{
source: 'node12',
target: 'node20',
sourceAnchor: 1,
targetAnchor: 1,
type: 'hvh_custom',
direction: 'left',
path: [
[1150, 52],
[1150, 234],
[1170, 234],
[1170, 468],
],
},
{
source: 'node19',
target: 'node20',
sourceAnchor: 1,
targetAnchor: 1,
type: 'hvh_custom',
path: [
[1150, 416],
[1150, 234],
[1170, 234],
[1170, 468],
],
},
{
source: 'node20',
target: 'node21',
sourceAnchor: 3,
targetAnchor: 2,
type: 'hvh2',
corner: true,
},
{
source: 'node21',
target: 'node22',
label: '火法',
subLabel: '湿法',
labelCfg: { style: { fontSize: 12, fill: '#245A9A' } },
subLabelCfg: { style: { fontSize: 12, fill: '#245A9A' } },
},
{ source: 'node22', target: 'node23' },
],
},
ZN: {
//锌
mapHeight: 323,
mapPadding: 0,
desc: '<span>锌产业链逻辑:</span>锌生产过程以及实际的行业结构来看,行业价值链主要有三个主体:矿山、锌冶炼商、锌消费企业。矿山负责勘查和开采锌精矿,并将锌精矿出售给下游的锌冶炼企业,锌冶炼企业根据锌消费市场的需求冶炼出符合市场需要的精炼锌以及相关的副产品,锌的最终消费企业从锌冶炼企业购买所需的锌产品。价值链的分析主要侧重在各环节成本、收入的分析,由于矿山的收入基本等于冶炼商原料采购的成本,因此锌精矿的定价机制是研究各环节盈利能力的关键所在。由于锌精矿以加工费(TC/RC)的方式核算,因此锌金属价格和加工费(TC/RC)的高低是影响矿山和冶炼商收入的主要因素。',
nodes: [
{
id: 'block1',
x: 878.5,
y: 54,
width: 411,
height: 108,
type: 'block-rect',
label: '',
},
{
id: 'block2',
x: 1155.5,
y: 265,
width: 425,
height: 108,
type: 'block-rect',
label: '',
},
{
id: 'block3',
x: 696,
y: 265,
width: 454,
height: 108,
type: 'custom-node',
label: '',
strock: '#BCD0EE',
shadowColor: 'rgba(54,78,128,0.1)',
pieceList: [
{
type: 'rect',
x: -227,
y: -54,
width: 26,
height: 108,
fill: '#4580D9',
stroke: '',
lineWidth: 0,
radius: [4, 0, 0, 4],
label: '一吨锌锭成本',
},
{
type: 'text',
x: -220,
y: -40,
label: '一',
fontSize: 12,
align: 'left',
fill: '#fff',
weight: 400
},
{
type: 'text',
x: -220,
y: -24,
label: '吨',
fontSize: 12,
align: 'left',
fill: '#fff',
weight: 400
},
{
type: 'text',
x: -220,
y: -8,
label: '锌',
fontSize: 12,
align: 'left',
fill: '#fff',
weight: 400
},
{
type: 'text',
x: -220,
y: 8,
label: '锭',
fontSize: 12,
align: 'left',
fill: '#fff',
weight: 400
},
{
type: 'text',
x: -220,
y: 24,
label: '成',
fontSize: 12,
align: 'left',
fill: '#fff',
weight: 400
},
{
type: 'text',
x: -220,
y: 40,
label: '本',
fontSize: 12,
align: 'left',
fill: '#fff',
weight: 400
},
{
type: 'text',
x: -191,
y: -35,
label: '原料成本:70%-80%、加工成本:20%-30%',
fontSize: 12,
align: 'left',
fill: '#D91212',
weight: 400
},
{
type: 'text',
x: -191,
y: -11,
label: '锌精矿:1.042吨 用电量:3500-3700kwh 硫酸:300-400kg',
fontSize: 12,
align: 'left',
fill: '#333',
weight: 400
},
{
type: 'text',
x: -191,
y: 11,
label: '锌辅料:250元 人工费:300-500元 锌粉:(55-65kg)*40%',
fontSize: 12,
align: 'left',
fill: '#333',
weight: 400
},
{
type: 'text',
x: -191,
y: 33,
label: '其他成本:(20-30%) ',
fontSize: 12,
align: 'left',
fill: '#333',
weight: 400
},
]
},
{
id: 'node1',
x: 37,
y: 89,
width: 74,
height: 40,
label: '锌矿石',
text: '锌矿石',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node2',
x: 152,
y: 89,
width: 74,
height: 40,
label: '锌精矿',
text: '锌精矿',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node3',
x: 290,
y: 89,
width: 88,
height: 40,
label: '加工酸浸',
text: '加工酸浸',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node4',
x: 290,
y: 159,
width: 88,
height: 40,
label: '焙烧',
text: '焙烧',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node5',
x: 290,
y: 230,
width: 88,
height: 40,
label: '常压酸浸',
text: '常压酸浸',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node6',
x: 412,
y: 159,
width: 74,
height: 40,
label: '浸出液',
text: '浸出液',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node7',
x: 527,
y: 159,
width: 74,
height: 40,
label: '净化',
text: '净化',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node8',
x: 642,
y: 159,
width: 74,
height: 40,
label: '电积',
text: '电积',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node9',
x: 757,
y: 159,
width: 74,
height: 40,
label: '电解锌',
text: '电解锌',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node10',
x: 879,
y: 159,
width: 88,
height: 40,
label: '初级消费',
text: '初级消费',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node11',
x: 1155,
y: 159,
width: 88,
height: 40,
label: '终端消费',
text: '终端消费',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node12',
x: 754.5,
y: 28,
width: 147,
height: 40,
label: '镀锌 (30%)',
text: '镀锌 (30%)',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
no_compute: true,
fill: 'l(90) 0:#FBFCFF 1:#E9EDF3',
strock: '#D4DBE7',
},
{
id: 'node13',
x: 754.5,
y: 80,
width: 147,
height: 40,
label: '压铸锌合金 (30%)',
text: '压铸锌合金 (30%)',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
no_compute: true,
fill: 'l(90) 0:#FBFCFF 1:#E9EDF3',
strock: '#D4DBE7',
},
{
id: 'node14',
x: 899.5,
y: 28,
width: 119,
height: 40,
label: '黄铜 (22%)',
text: '黄铜 (22%)',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
no_compute: true,
fill: 'l(90) 0:#FBFCFF 1:#E9EDF3',
strock: '#D4DBE7',
},
{
id: 'node15',
x: 899.5,
y: 80,
width: 119,
height: 40,
label: '氧化锌 (22%)',
text: '氧化锌 (22%)',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
no_compute: true,
fill: 'l(90) 0:#FBFCFF 1:#E9EDF3',
strock: '#D4DBE7',
},
{
id: 'node16',
x: 1023.5,
y: 28,
width: 105,
height: 40,
label: '电池 (22%)',
text: '电池 (22%)',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
no_compute: true,
fill: 'l(90) 0:#FBFCFF 1:#E9EDF3',
strock: '#D4DBE7',
},
{
id: 'node17',
x: 1023.5,
y: 80,
width: 105,
height: 40,
label: '其它 (22%)',
text: '其它 (22%)',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
no_compute: true,
fill: 'l(90) 0:#FBFCFF 1:#E9EDF3',
strock: '#D4DBE7',
},
{
id: 'node18',
x: 1038.5,
y: 239,
width: 175,
height: 40,
label: '房地产及建筑业 (30%)',
text: '房地产及建筑业 (30%)',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
no_compute: true,
fill: 'l(90) 0:#FBFCFF 1:#E9EDF3',
strock: '#D4DBE7',
},
{
id: 'node19',
x: 1038.5,
y: 291,
width: 175,
height: 40,
label: '基础设施建设 (22%)',
text: '基础设施建设 (22%)',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
no_compute: true,
fill: 'l(90) 0:#FBFCFF 1:#E9EDF3',
strock: '#D4DBE7',
},
{
id: 'node20',
x: 1190.5,
y: 239,
width: 105,
height: 40,
label: '锌合金 (22%)',
text: '锌合金 (22%)',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
no_compute: true,
fill: 'l(90) 0:#FBFCFF 1:#E9EDF3',
strock: '#D4DBE7',
},
{
id: 'node21',
x: 1190.5,
y: 291,
width: 105,
height: 40,
label: '汽车 (22%)',
text: '汽车 (22%)',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
no_compute: true,
fill: 'l(90) 0:#FBFCFF 1:#E9EDF3',
strock: '#D4DBE7',
},
{
id: 'node22',
x: 1307.5,
y: 239,
width: 105,
height: 40,
label: '化工 (22%)',
text: '化工 (22%)',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
no_compute: true,
fill: 'l(90) 0:#FBFCFF 1:#E9EDF3',
strock: '#D4DBE7',
},
{
id: 'node23',
x: 1307.5,
y: 291,
width: 105,
height: 40,
label: '其它 (22%)',
text: '其它 (22%)',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
no_compute: true,
fill: 'l(90) 0:#FBFCFF 1:#E9EDF3',
strock: '#D4DBE7',
},
],
edges: [
{ source: 'node1', target: 'node2' },
{ source: 'node2', target: 'node3' },
{ source: 'node2', target: 'node4' },
{
source: 'node3',
target: 'node6',
sourceAnchor: 1,
targetAnchor: 0,
type: 'hvh3',
corner: true,
},
{ source: 'node4', target: 'node5', type: 'hvh3' },
{ source: 'node4', target: 'node6' },
{
source: 'node5',
target: 'node6',
sourceAnchor: 1,
targetAnchor: 2,
type: 'hvh2',
corner: true,
},
{ source: 'node6', target: 'node7' },
{ source: 'node7', target: 'node8' },
{ source: 'node8', target: 'node9' },
{ source: 'node9', target: 'node10' },
{ source: 'node10', target: 'node11' },
{ source: 'node10', target: 'block1', type: 'hvh2' },
{ source: 'node11', target: 'block2', type: 'hvh3' },
],
},
NI: {//镍
mapHeight: 362,
mapPadding: 40,
desc: '<span>镍产业链逻辑:</span>镍按照生产原料的不同可分为原生镍和再生镍,原生镍的生产原料来自于 镍矿,再生镍的生产原料来自于含镍废料。按照镍金属的含量,原生镍可以分为 四大产品系列,分别是电解镍、含镍生铁、镍铁、其它(镍盐、通用镍等)。',
nodes: [
{
id: 'block1',
x: 152.5,
y: 184,
width: 305,
height: 350,
type: 'block-rect',
label: '上游',
labelCfg: { style: { width: 42, height: 20 } },
},
{
id: 'block2',
x: 644.5,
y: 184,
width: 657,
height: 350,
type: 'block-rect',
label: '中游',
labelCfg: { style: { width: 40, height: 20 } },
},
{
id: 'block3',
x: 1145.5,
y: 184,
width: 323,
height: 350,
type: 'block-rect',
label: '下游',
labelCfg: { style: { width: 40, height: 20 } },
},
{
id: 'block4',
x: 56,
y: 115,
width: 88,
height: 56,
type: 'custom-node',
label: '',
strock: '#BCD0EE',
shadowColor: 'rgba(54,78,128,0.1)',
fill: 'rgba(255,255,255,0.9)',
pieceList: [
{
type: 'text',
x: 0,
y: -11,
label: '自有矿 (19%)',
fontSize: 12,
align: 'center',
fill: '#333',
weight: 400
},
{
type: 'text',
x: 0,
y: 11,
label: '进口矿 (81%)',
fontSize: 12,
align: 'center',
fill: '#333',
weight: 400
},
]
},
{
id: 'block5',
x: 868,
y: 330,
width: 177,
height: 34,
type: 'custom-node',
label: '',
strock: '#BCD0EE',
shadowColor: 'rgba(54,78,128,0.1)',
fill: 'rgba(255,255,255,0.9)',
pieceList: [
{
type: 'text',
x: 0,
y: 0,
label: '进口 (30%) 国内产量 (70%)',
fontSize: 12,
align: 'center',
fill: '#333',
weight: 400
},
]
},
{
id: 'node1',
x: 56,
y: 195,
width: 74,
height: 40,
label: '镍矿',
text: '镍矿',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node2',
x: 229,
y: 127,
width: 128,
height: 50,
label: '硫化锂矿',
text: '硫化锂矿',
subLabel: '(全球产量占36%)',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node3',
x: 229,
y: 261,
width: 128,
height: 54,
label: '氧化锂矿',
text: '氧化锂矿',
subLabel: '(全球产量占64%)',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node4',
x: 389,
y: 127,
width: 102,
height: 50,
label: '采矿',
text: '采矿',
subLabel: '(含镍1.5%)',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node5',
x: 389,
y: 261,
width: 102,
height: 50,
label: '采矿',
text: '采矿',
subLabel: '(含镍1%-2%)',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node6',
x: 546,
y: 127,
width: 102,
height: 50,
label: '镍精矿',
text: '镍精矿',
subLabel: '(含镍7-10%)',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node7',
x: 703,
y: 127,
width: 102,
height: 50,
label: '高冰镍',
text: '高冰镍',
subLabel: '(含镍70%)',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node8',
x: 868,
y: 94,
width: 114,
height: 50,
label: '镍盐',
text: '镍盐',
subLabel: '(含镍22%)',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node9',
x: 868,
y: 156,
width: 114,
height: 50,
label: '电解镍',
text: '电解镍',
subLabel: '(含镍99.96%)',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node10',
x: 868,
y: 261,
width: 114,
height: 40,
label: '镍铁',
text: '镍铁',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node11',
x: 1040,
y: 156,
width: 74,
height: 40,
label: '原生镍',
text: '原生镍',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
fill: 'l(90) 0:#FBFCFF 1:#E9EDF3',
strock: '#D4DBE7',
},
{
id: 'node12',
x: 1217,
y: 52,
width: 156,
height: 40,
label: '不锈钢 (84%)',
text: '不锈钢 (84%)',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
fill: 'l(90) 0:#FBFCFF 1:#E9EDF3',
strock: '#D4DBE7',
no_compute: true,
},
{
id: 'node13',
x: 1217,
y: 104,
width: 156,
height: 40,
label: '电池 (4%)',
text: '电池 (4%)',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
fill: 'l(90) 0:#FBFCFF 1:#E9EDF3',
strock: '#D4DBE7',
no_compute: true,
},
{
id: 'node14',
x: 1217,
y: 156,
width: 156,
height: 40,
label: '电镀 (4%)',
text: '电镀 (4%)',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
fill: 'l(90) 0:#FBFCFF 1:#E9EDF3',
strock: '#D4DBE7',
no_compute: true,
},
{
id: 'node15',
x: 1217,
y: 208,
width: 156,
height: 40,
label: '镍造和镍合金 (5%)',
text: '镍造和镍合金 (5%)',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
fill: 'l(90) 0:#FBFCFF 1:#E9EDF3',
strock: '#D4DBE7',
no_compute: true,
},
{
id: 'node16',
x: 1217,
y: 260,
width: 156,
height: 40,
label: '其他 (2%)',
text: '其他 (2%)',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
fill: 'l(90) 0:#FBFCFF 1:#E9EDF3',
strock: '#D4DBE7',
no_compute: true,
},
],
edges: [
{ source: 'node1', target: 'block4', type: 'hvh2',lineDash: [2,2]},// 自定义虚线模式
{ source: 'node1', target: 'node2' },
{ source: 'node1', target: 'node3' },
{ source: 'node2', target: 'node4' },
{ source: 'node3', target: 'node5' },
{
source: 'node4', target: 'node6', label: '选矿',
subLabel: '',
labelCfg: { style: { fontSize: 12, fill: '#245A9A' } },
subLabelCfg: { style: { fontSize: 12, fill: '#245A9A' } },
},
{
source: 'node4', target: 'node9', sourceAnchor: 2,
type: 'hvh_custom',
direction: 'top',
path: [
[389, 201],
[868, 201],
],
targetAnchor: 2, label: '湿法',
subLabel: '',
labelCfg: { style: { fontSize: 12, fill: '#245A9A' }, startPoint: { x: 389, y: 201 }, endPoint: { x: 868, y: 201 } },//自定义节点传文本所在边的起始点和终点
subLabelCfg: { style: { fontSize: 12, fill: '#245A9A' } },
},
{
source: 'node5', target: 'node10', label: '火法',
subLabel: '',
labelCfg: { style: { fontSize: 12, fill: '#245A9A' } },
subLabelCfg: { style: { fontSize: 12, fill: '#245A9A' } },
},
{
source: 'node6', target: 'node7', label: '湿法',
subLabel: '',
labelCfg: { style: { fontSize: 12, fill: '#245A9A' } },
subLabelCfg: { style: { fontSize: 12, fill: '#245A9A' } },
},
{ source: 'node7', target: 'node8' },
{ source: 'node7', target: 'node9' },
{ source: 'node8', target: 'node11' },
{ source: 'node9', target: 'node11' },
{ source: 'node10', target: 'node11' },
{ source: 'node10', target: 'block5', type: 'hvh3',lineDash: [2,2] },
{
source: 'node11', target: 'node12', sourceAnchor: 1,
targetAnchor: 3,
},
{ source: 'node11', target: 'node13' },
{ source: 'node11', target: 'node14' },
{ source: 'node11', target: 'node15' },
{
source: 'node11', target: 'node16', sourceAnchor: 1,
targetAnchor: 3,
},
]
}
}
//默认黑色品种
export const defaultData = {
mapHeight: 366,
mapPadding: 0,
desc: '',
nodes: [
{
id: 'block1',
x: 487,
y: 117,
width: 972,
height: 232,
type: 'block-rect',
label: '长流程',
},
{
id: 'block2',
x: 670,
y: 304,
width: 604,
height: 122,
type: 'block-rect',
label: '短流程',
},
{
id: 'node1',
x: 78,
y: 42,
label: '焦煤',
breedCode: 'JM',
indexCode: 'FU00008663',
text: '焦煤',
dataValue: '8302',
chg: '0.16',
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
},
{
id: 'node2',
x: 244,
y: 42,
label: '焦炭',
breedCode: 'J',
indexCode: 'FU00010732',
text: '焦炭',
dataValue: '8302',
chg: '0.16',
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
},
{
id: 'node3',
x: 244,
y: 115,
label: '铁矿石',
breedCode: 'I',
indexCode: 'FU00005559',
text: '铁矿石',
dataValue: '8302',
chg: '-0.82',
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
},
{
id: 'node4',
x: 466,
y: 81,
label: '高炉炼铁',
dataValue: '8302',
chg: '0.16',
type: 'breed-rect',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node5',
x: 394,
y: 189,
label: '硅铁',
breedCode: 'SF',
indexCode: 'FU00000314',
text: '硅铁',
dataValue: '8302',
chg: '0',
anchorPoints: [
[0, 0],
[0.5, 0],
],
},
{
id: 'node6',
x: 540,
y: 189,
label: '硅锰',
breedCode: 'SM',
indexCode: 'FU00000202',
text: '硅锰',
dataValue: '8302',
chg: '-0.16',
anchorPoints: [
[0, 0.5],
[0.5, 0],
],
},
{
id: 'node7',
x: 669,
y: 81,
dataValue: '8302',
chg: '0.16',
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
codes: [
{
label: '产能利用率',
chg: '88.54',
desc: '历史低位',
indexCode: 'ID00183114',
text: '高炉炼铁-产能利用率',
},
{
label: '开工率',
chg: '88.54',
desc: '历史低位',
indexCode: 'ID00183109',
text: '高炉炼铁-开工率',
},
{
label: '钢厂盈利率',
chg: '88.54',
desc: '历史低位',
indexCode: 'ID00183126',
text: '高炉炼铁-钢厂盈利率',
},
],
type: 'rate-rect',
},
{
id: 'node8',
x: 886,
y: 81,
label: '生铁产量',
indexCode: 'ID00184088',
text: '生铁产量',
dataValue: '6669.9',
chg: '2.3',
type: 'ratio-rect',
desc: '高于近一年平均增速',
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
},
{
id: 'node9',
x: 466,
y: 284,
label: '废钢',
indexCode: 'OFFRDE0554363373',
text: '废钢',
dataValue: '8302',
chg: '0.16',
type: 'breed-rect',
desc1: '相对高位',
desc2: '利好',
desc3: '独立电炉企业',
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
},
{
id: 'node10',
x: 660,
y: 284,
label: '电炉炼钢',
dataValue: '8302',
chg: '0.16',
type: 'breed-rect',
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
},
{
id: 'node11',
x: 854,
y: 284,
label: '',
codes: [
{
label: '产能利用率',
chg: '88.54',
desc: '历史低位',
indexCode: 'ID01302473',
text: '电炉炼钢-产能利用率',
},
{
label: '电炉炼钢利润',
chg: '88.54',
desc: '历史低位',
indexCode: 'ID01040556',
text: '电炉炼钢-电炉炼钢利率',
},
],
type: 'rate-rect',
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
},
{
id: 'node12',
x: 1100,
y: 188,
label: '粗钢',
indexCode: 'ID00182958',
text: '粗钢',
dataValue: '6669.9',
chg: '3.3',
type: 'ratio-rect',
desc: '高于近一年平均增速',
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
},
{
id: 'node13',
x: 1307,
y: 42,
label: '螺纹钢',
breedCode: 'RB',
indexCode: 'FU00001454',
text: '螺纹钢',
dataValue: '8302',
chg: '0.16',
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
},
{
id: 'node14',
x: 1307,
y: 115,
label: '线材',
breedCode: 'WR',
indexCode: 'FU00005821',
noClick: true, //是否节点可点击
text: '线材',
dataValue: '8302',
chg: '0.16',
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
},
{
id: 'node15',
x: 1307,
y: 188,
label: '热轧板卷',
breedCode: 'HC',
indexCode: 'FU00002440',
text: '热轧板卷',
dataValue: '8302',
chg: '0.16',
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
},
{
id: 'node16',
x: 1307,
y: 261,
label: '不锈钢',
breedCode: 'SS',
indexCode: 'FU00000075',
text: '不锈钢',
dataValue: '8302',
chg: '0.16',
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
},
{
id: 'node17',
x: 1307,
y: 324,
label: '其他',
dataValue: '8302',
chg: '0.16',
type: 'breed-rect',
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
},
],
edges: [
{ source: 'node1', target: 'node2' },
{ source: 'node2', target: 'node4' },
{ source: 'node3', target: 'node4' },
{ source: 'node5', target: 'node4', type: 'hvh2' },
{ source: 'node6', target: 'node4', type: 'hvh2' },
{ source: 'node4', target: 'node7', disableArrow: true },
{ source: 'node7', target: 'node8' },
{ source: 'node9', target: 'node10' },
{ source: 'node10', target: 'node11', disableArrow: true },
{ source: 'node8', target: 'node12' },
{ source: 'node11', target: 'node12' },
{ source: 'node12', target: 'node13' },
{ source: 'node12', target: 'node14' },
{ source: 'node12', target: 'node15' },
{ source: 'node12', target: 'node16' },
{ source: 'node12', target: 'node17' },
],
}
图谱组件link-map.vue:
<template>
<div class="link-map">
<div class="link-title">{{ breedName + props.elementName }}</div>
<div class="link-desc" v-if="desc !== ''" v-html="desc"></div>
<gl-spin :spinning="mapLoading">
<div
id="mountNode"
:style="{
height: mapHeight + 'px',
padding: isBlank ? '' : `0 ${mapPadding}px`,
maxHeight: maxHeight + 'px',
}"
></div>
</gl-spin>
</div>
</template>
<script setup>
import { onMounted, getCurrentInstance } from 'vue'
import { formatNumValue } from '@/utils/index'
import G6 from '@antv/g6/dist/g6.min'
import { mapData, defaultData } from './mapData'
import { map } from 'lodash'
const { proxy } = getCurrentInstance()
const route = useRoute()
const props = defineProps({
secCode: {
type: String,
default: '',
},
elementName: {
type: String,
default: '',
},
elementCode: {
type: String,
default: '',
},
})
const emit = defineEmits(['breed-select'])
const nodes = ref([])
const edges = ref([])
const mapLoading = ref(false)
const graph2 = ref(null)
const color_breed = ref(['CU', 'AL', 'PB', 'ZN', 'SN', 'NI', 'LC', 'SI', 'AO']) //铜、铝、铅、锌、锡、镍、碳酸锂、工业硅、氧化铝
const mapHeight = ref(
mapData[window.breedCode]
? mapData[window.breedCode].mapHeight
: defaultData.mapHeight
)
const maxHeight = ref(
mapData[window.breedCode]
? mapData[window.breedCode].mapHeight
: defaultData.mapHeight
)
const isBlank = ref(color_breed.value.includes(window.breedCode) ? false : true) //是否黑色品种
const mapPadding = ref(
mapData[window.breedCode]
? mapData[window.breedCode].mapPadding
: defaultData.mapPadding
)
const breedName = ref(window.breedName)
const isDragging = ref(false)
const desc = ref('')
const tong_nodes = ref([
{
id: 'node1',
x: 138,
y: 136,
width: 176,
height: 61,
label: '全球铜矿山产能',
breedCode: 'JM',
indexCode: 'OFFRDE0678557898',
text: '全球铜矿山产能',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node2',
x: 138,
y: 209,
width: 176,
height: 61,
label: '全球铜矿产能利用率',
breedCode: 'JM',
indexCode: 'ID00303168',
text: '全球铜矿产能利用率',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node3',
x: 394,
y: 172,
width: 134,
height: 61,
label: '全球铜矿供应',
breedCode: 'JM',
indexCode: 'OFFRDE0789849953',
text: '全球铜矿供应',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node4',
x: 394,
y: 264,
width: 134,
height: 61,
label: '加工费',
breedCode: 'JM',
indexCode: 'OFFRDE0407946063',
text: '加工费',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node5',
x: 394,
y: 357,
width: 134,
height: 61,
label: '国内铜矿供应',
breedCode: 'JM',
indexCode: 'OFFRDE0503146446',
text: '国内铜矿供应',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node6',
x: 591,
y: 172,
width: 158,
height: 61,
label: '精炼铜供应',
breedCode: 'JM',
indexCode: 'ID00407927',
text: '精炼铜供应',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node7',
x: 591,
y: 234,
width: 158,
height: 39,
label: '再生(废杂)铜供应',
text: '各类政策因素',
breedCode: 'JM',
text: '再生(废杂)铜供应',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node8',
x: 591,
y: 286,
width: 158,
height: 39,
label: '各类政策因素',
text: '各类政策因素',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node9',
x: 838,
y: 31,
width: 134,
height: 61,
label: '全球社会库存',
text: '全球社会库存',
breedCode: 'JM',
indexCode: 'OFFRDE0416122129',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node10',
x: 984,
y: 31,
width: 134,
height: 61,
label: 'LME铜库存',
text: 'LME铜库存',
breedCode: 'JM',
indexCode: 'OFFRDE0099802675',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node11',
x: 1130,
y: 31,
width: 134,
height: 61,
label: 'SHFE铜库存',
text: 'SHFE铜库存',
breedCode: 'JM',
indexCode: 'OFFRDE0900823051',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node12',
x: 1281,
y: 31,
width: 145,
height: 61,
label: 'COMEX铜库存',
text: 'COMEX铜库存',
breedCode: 'JM',
indexCode: 'OFFRDE0470038275',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node13',
x: 838,
y: 136,
width: 134,
height: 61,
label: '总库存变化',
breedCode: 'JM',
indexCode: 'OFFRDE0704512926',
text: '总库存变化',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node14',
x: 838,
y: 234,
width: 134,
height: 61,
label: '铜现货价',
breedCode: 'JM',
indexCode: 'ID00303957',
text: '铜现货价',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node15',
x: 838,
y: 326,
width: 134,
height: 61,
label: '比价与价差',
text: '比价与价差',
indexCode: 'FU00015882',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node16',
x: 838,
y: 418,
width: 134,
height: 61,
label: '铜期货价',
text: '铜期货价',
indexCode: 'FU00015764',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node17',
x: 1023,
y: 136,
width: 134,
height: 61,
label: '库存消费比',
breedCode: 'JM',
indexCode: 'OFFRDE0881777811',
text: '库存消费比',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node18',
x: 1023,
y: 234,
width: 134,
height: 61,
label: '表观需求',
breedCode: 'JM',
indexCode: 'ID01167294',
text: '表观需求',
dataValue: '-',
chg: '-',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node19',
x: 1023,
y: 326,
width: 134,
height: 39,
label: '贸易流向',
text: '贸易流向',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node20',
x: 1023,
y: 418,
width: 134,
height: 39,
label: '资金因素',
text: '资金因素',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
type: 'word-rect',
},
{
id: 'node21',
x: 1257,
y: 157,
width: 134,
height: 39,
label: '房地产开工',
text: '房地产开工',
type: 'word-rect',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node22',
x: 1257,
y: 209,
width: 134,
height: 39,
label: '汽车产销',
text: '汽车产销',
type: 'word-rect',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node23',
x: 1257,
y: 261,
width: 134,
height: 39,
label: '基建投资',
text: '基建投资',
type: 'word-rect',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
{
id: 'node24',
x: 1257,
y: 313,
width: 134,
height: 39,
label: '家电等终端',
text: '家电等终端',
type: 'word-rect',
anchorPoints: [
[0.5, 0], // 上
[1, 0.5], // 右
[0.5, 1], // 下
[0, 0.5], // 左
],
},
])
const tong_edges = ref([
{ source: 'node1', target: 'node3' },
{ source: 'node2', target: 'node3' },
{ source: 'node3', target: 'node4', type: 'hvh3' },
{ source: 'node4', target: 'node5', type: 'hvh3' },
{ source: 'node3', target: 'node6' },
{ source: 'node6', target: 'node14' },
{ source: 'node7', target: 'node14' },
{ source: 'node8', target: 'node14' },
{ source: 'node9', target: 'node13', type: 'hvh3' },
{
source: 'node10',
target: 'node13',
type: 'hvh3',
sourceAnchor: 2,
targetAnchor: 0,
},
{
source: 'node11',
target: 'node13',
type: 'hvh3',
sourceAnchor: 2,
targetAnchor: 0,
},
{
source: 'node12',
target: 'node13',
type: 'hvh3',
sourceAnchor: 2,
targetAnchor: 0,
},
{ source: 'node13', target: 'node17' },
{ source: 'node13', target: 'node14', type: 'hvh3' },
{ source: 'node14', target: 'node18' },
{ source: 'node14', target: 'node15', type: 'hvh3' },
{ source: 'node15', target: 'node19' },
{ source: 'node15', target: 'node16', type: 'hvh3' },
{ source: 'node16', target: 'node20' },
{ source: 'node18', target: 'node21' },
{ source: 'node18', target: 'node22' },
{ source: 'node18', target: 'node23' },
{ source: 'node18', target: 'node24' },
])
const getSvg = (type, iconColor) => {
// 动态生成SVG内容
const generateSVG1 = (iconColor) => {
return `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<title>Icon/涨</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Q2_5_4期限分析_供给分析_最小宽度1180" transform="translate(-388.000000, -226.000000)">
<g id="价格" transform="translate(164.000000, 102.000000)">
<g id="编组-13" transform="translate(46.000000, 116.000000)">
<g id="产业链/期货/涨备份-3" transform="translate(70.000000, 0.000000)">
<g id="Icon/涨" transform="translate(108.000000, 8.000000)">
<rect id="矩形" x="0" y="0" width="14" height="14"/>
<path d="M13.234199,3.55970481 C13.5098844,3.54382543 13.7462444,3.75443985 13.7621238,4.03012528 C13.7633238,4.05095907 13.7632183,4.07184754 13.761808,4.09266815 L13.5387743,7.38534552 C13.5201122,7.66085657 13.2816378,7.86907379 13.0061267,7.8504117 C12.8875582,7.84238031 12.775727,7.7923795 12.6906807,7.70937277 L11.522514,6.56902231 C11.5039313,6.59161598 11.4837258,6.61329794 11.4619021,6.63390921 L7.52440214,10.3526592 C7.19748321,10.661416 6.66840549,10.5862535 6.44041171,10.1986641 L4.67251566,7.193375 L1.15091069,10.1604378 C0.879839379,10.3887084 0.486186576,10.3759255 0.230649525,10.1444346 L0.164577825,10.075895 C-0.0844446196,9.78018087 -0.0465935113,9.33858461 0.249120641,9.08956216 L4.40537064,5.58956216 C4.73689384,5.31038473 5.23987001,5.39651159 5.45961961,5.77008591 L7.20126566,8.731625 L10.5006292,5.61609079 L10.525,5.59589343 L9.5175239,4.6123147 C9.3199065,4.41943657 9.31606456,4.10287739 9.5089427,3.90525999 C9.59608394,3.81597757 9.71345538,3.76249833 9.83800849,3.75532412 L13.234199,3.55970481 Z" id="形状结合" fill="${iconColor}" fill-rule="nonzero"/>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>
`
}
// 下跌
const generateSVG2 = (iconColor) => {
return `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<title>Icon/跌</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Q2_5_4期限分析_供给分析_最小宽度1180" transform="translate(-547.000000, -226.000000)">
<g id="价格" transform="translate(164.000000, 102.000000)">
<g id="编组-13" transform="translate(46.000000, 116.000000)">
<g id="产业链/期货/跌备份-4" transform="translate(229.000000, 0.000000)">
<g id="Icon/跌" transform="translate(108.000000, 8.000000)">
<rect id="矩形" x="0" y="0" width="14" height="14"/>
<path d="M13.234199,3.50481138 C13.5098844,3.48893201 13.7462444,3.69954643 13.7621238,3.97523186 C13.7633238,3.99606564 13.7632183,4.01695412 13.761808,4.03777472 L13.5387743,7.3304521 C13.5201122,7.60596314 13.2816378,7.81418036 13.0061267,7.79551827 C12.8875582,7.78748688 12.775727,7.73748607 12.6906807,7.65447934 L11.522514,6.51412888 C11.5039313,6.53672256 11.4837258,6.55840451 11.4619021,6.57901578 L7.52440214,10.2977658 C7.19748321,10.6065225 6.66840549,10.5313601 6.44041171,10.1437707 L4.67251566,7.13848157 L1.15091069,10.1055444 C0.879839379,10.333815 0.486186576,10.3210321 0.230649525,10.0895412 L0.164577825,10.0210016 C-0.0844446196,9.72528744 -0.0465935113,9.28369118 0.249120641,9.03466874 L4.40537064,5.53466874 C4.73689384,5.25549131 5.23987001,5.34161816 5.45961961,5.71519249 L7.20126566,8.67673157 L10.5006292,5.56119737 L10.525,5.541 L9.5175239,4.55742128 C9.3199065,4.36454314 9.31606456,4.04798397 9.5089427,3.85036657 C9.59608394,3.76108415 9.71345538,3.70760491 9.83800849,3.70043069 L13.234199,3.50481138 Z" id="形状结合" fill="${iconColor}" fill-rule="nonzero" transform="translate(6.883798, 6.994464) scale(1, -1) translate(-6.883798, -6.994464) "/>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>
`
}
// 持平
const generateSVG3 = (iconColor) => {
return `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<title>Icon/跌</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Q2_5_4期限分析_供给分析_最小宽度1180" transform="translate(-706.000000, -226.000000)">
<g id="价格" transform="translate(164.000000, 102.000000)">
<g id="编组-13" transform="translate(46.000000, 116.000000)">
<g id="产业链/期货/跌备份-3" transform="translate(388.000000, 0.000000)">
<g id="Icon/跌" transform="translate(108.000000, 8.000000)">
<rect id="矩形" x="0" y="0" width="14" height="14"/>
<path d="M2.8,6 L11.2,6 C11.6418278,6 12,6.3581722 12,6.8 C12,7.2418278 11.6418278,7.6 11.2,7.6 L2.8,7.6 C2.3581722,7.6 2,7.2418278 2,6.8 C2,6.3581722 2.3581722,6 2.8,6 Z" id="矩形" fill="${iconColor}"/>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>
`
}
return (
'data:image/svg+xml;charset=utf-8,' +
encodeURIComponent(
type > 0
? generateSVG1(iconColor)
: type === 0
? generateSVG3(iconColor)
: generateSVG2(iconColor)
)
)
}
const linkData = ref({
link1: {
indexCode: 'FU00008663',
text: '焦煤',
dataValue: '',
dataChg: '',
desc: '',
},
link2: {
indexCode: 'FU00010732',
text: '焦炭',
dataValue: '',
dataChg: '',
desc: '',
},
link3: {
indexCode: 'FU00005559',
text: '铁矿石',
dataValue: '',
dataChg: '',
desc: '',
},
link4: {
indexCode: 'FU00000314',
text: '硅铁',
dataValue: '',
dataChg: '',
desc: '',
},
link5: {
indexCode: 'FU00000202',
text: '硅锰',
dataValue: '',
dataChg: '',
desc: '',
},
link6: {
indexCode: 'ID00183114',
text: '高炉炼铁-产能利用率',
dataValue: '',
dataChg: '',
desc: '',
},
link7: {
indexCode: 'ID00183109',
text: '高炉炼铁-开工率',
dataValue: '',
dataChg: '',
desc: '',
},
link8: {
indexCode: 'ID00183126',
text: '高炉炼铁-钢厂盈利率',
dataValue: '',
dataChg: '',
desc: '',
},
link9: {
indexCode: 'ID00184088',
text: '生铁产量',
dataValue: '',
dataChg: '',
desc: '',
},
link10: {
indexCode: 'OFFRDE0554363373',
text: '废钢',
dataValue: '',
dataChg: '',
desc: '',
},
link11: {
indexCode: 'ID01302473',
text: '电炉炼钢-产能利用率',
dataValue: '',
dataChg: '',
desc: '',
},
link12: {
indexCode: 'ID01040556',
text: '电炉炼钢-电炉炼钢利率',
dataValue: '',
dataChg: '',
desc: '',
},
link13: {
indexCode: 'ID00182958',
text: '粗钢',
dataValue: '',
dataChg: '',
desc: '',
},
link14: {
indexCode: 'FU00001454',
text: '螺纹钢',
dataValue: '',
dataChg: '',
desc: '',
},
link15: {
indexCode: 'FU00005821',
text: '线材',
dataValue: '',
dataChg: '',
desc: '',
},
link16: {
indexCode: 'FU00002440',
text: '热轧板卷',
dataValue: '',
dataChg: '',
desc: '',
},
link17: {
indexCode: 'FU00000075',
text: '不锈钢',
dataValue: '',
dataChg: '',
desc: '',
},
})
//查询指定品种下链路图数据
const loadGraphData = (menuCode) => {
mapLoading.value = true
proxy.$request
.get(
`/futures-api/api/futures/data/chain?breedCode=${window.breedCode}&elementCode=${props.elementCode}`
)
.then((res) => {
console.log('品种下链路图数据', res)
mapLoading.value = false
if (res.data) {
const data = res.data
// if (!isBlank.value) {
// console.log('显示铜产业链')
// edges.value = tong_edges.value
// nodes.value = tong_nodes.value
// }
console.log('nodes.value', nodes.value)
nodes.value.forEach((val) => {
if (val.indexCode) {
let list = data.filter((item) => item.indexCode == val.indexCode)
if (list.length > 0) {
val.dataValue = list[0].dataValue
val.chg = list[0].dataChg
val.desc = list[0].desc
val.unit = list[0].unit
val.sumYoy = list[0].sumYoy
if (val.indexCode == 'OFFRDE0554363373') {
let descList = list[0].desc.replace(';<br>', '').split(' ')
val.desc1 = descList[0]
val.desc2 = descList[1]
val.desc3 = descList[2]
}
}
} else if (val.codes) {
val.codes.forEach((ele) => {
if (ele.indexCode) {
let list = data.filter(
(item) => item.indexCode == ele.indexCode
)
if (list.length > 0) {
ele.dataValue = list[0].dataValue
ele.chg = list[0].dataChg
ele.desc = list[0].desc
ele.unit = list[0].unit
}
}
})
}
})
nextTick(() => {
initGrah()
})
}
})
}
const initGrah = () => {
//创建模块节点
G6.registerNode('block-rect', {
draw(cfg, group) {
// 主矩形配置
const width = cfg.width
const height = cfg.height
const fill = '#FAFCFF'
const stroke = '#4376CE'
// 创建主矩形
const mainRect = group.addShape('rect', {
attrs: {
x: -width / 2,
y: -height / 2,
width,
height,
stroke,
fill,
lineDash: [2, 2], // 激活状态用实线,非激活用虚线
lineWidth: 1,
radius: [4, 4, 4, 4],
padding: 0,
zIndex: -99,
},
name: 'main-rect',
})
if (cfg.label == '长流程') {
group.addShape('rect', {
attrs: {
x: -width / 2 + 12,
y: height / 2 - 10,
width: 52,
height: 20,
fill: '#4580D9',
stroke: '',
lineWidth: 0,
radius: [2, 2, 2, 2],
zIndex: -99,
},
name: 'top-rect',
})
group.addShape('text', {
attrs: {
x: -width / 2 + 37,
y: height / 2 + 6,
text: '长流程',
textAlign: 'center',
fill: '#fff',
fontSize: 12,
zIndex: -99,
},
name: 'text1',
})
}
if (cfg.label == '短流程') {
group.addShape('rect', {
attrs: {
x: -width / 2 - 26,
y: -10,
width: 52,
height: 20,
fill: '#4580D9',
stroke: '',
lineWidth: 0,
radius: [2, 2, 2, 2],
zIndex: -99,
},
name: 'top-rect',
})
group.addShape('text', {
attrs: {
x: -width / 2,
y: 7,
text: '短流程',
textAlign: 'center',
fill: '#fff',
fontSize: 12,
zIndex: -99,
},
name: 'text1',
})
}
let list = [
'上游',
'中游',
'下游',
'原料端',
'冶炼端',
'初端下游产品',
'终端需求',
]
if (list.includes(cfg.label)) {
group.addShape('rect', {
attrs: {
x: -(cfg.labelCfg?.style?.width / 2 || 20),
y: -height / 2 - (cfg.labelCfg?.style?.height / 2 || 10),
width: cfg.labelCfg?.style?.width || 40,
height: cfg.labelCfg?.style?.height || 20,
fill: '#4580D9',
stroke: '#4580D9',
lineWidth: 1,
radius: [2, 2, 2, 2],
zIndex: -99,
},
name: 'top-rect',
})
group.addShape('text', {
attrs: {
x: 0,
y: -height / 2 + 6,
text: cfg.label,
textAlign: 'center',
fill: '#fff',
fontSize: 12,
zIndex: -99,
},
name: 'text1',
})
}
return mainRect
},
})
//创建上下分割节点
G6.registerNode('split-rect', {
draw(cfg, group) {
// 主矩形配置
const width = 135
const height = 60
const fill = cfg.chg > 0 ? '#FBEAEC' : cfg.chg < 0 ? '#DFF5EB' : '#F1F2FA'
const stroke =
cfg.chg > 0 ? '#F8DDE0' : cfg.chg < 0 ? '#B8DDCC' : '#D7DCEA'
// 创建主矩形
const mainRect = group.addShape('rect', {
attrs: {
x: -width / 2,
y: -height / 2,
width,
height,
fill,
stroke,
lineWidth: 1,
radius: [4, 4, 4, 4],
padding: 12,
},
name: 'main-rect',
})
// 添加分割线(水平中线)
group.addShape('line', {
attrs: {
x1: -width / 2 + 12,
y1: 0,
x2: width / 2 - 12,
y2: 0,
stroke: cfg.chg > 0 ? '#E6C5C6' : cfg.chg < 0 ? '#ACD5C3' : '#D3D9E8',
lineWidth: 1,
cursor: cfg.text !== '线材' ? 'pointer' : '',
},
name: 'divider-line',
})
// 添加上半部分小矩形
group.addShape('rect', {
attrs: {
x: -width / 2,
y: -height / 2,
width: width,
height: 30,
fill: '',
stroke: '#666',
lineWidth: 0,
radius: [4, 4, 0, 0],
cursor: cfg.text !== '线材' ? 'pointer' : '',
},
name: 'top-rect',
})
// 添加下半部分小矩形
group.addShape('rect', {
attrs: {
x: -width / 2,
y: 0, // 从分割线下方开始
width: width,
height: 30,
fill: '',
stroke: '',
lineWidth: 0,
radius: [0, 0, 4, 4],
cursor: cfg.text !== '线材' ? 'pointer' : '',
},
name: 'bottom-rect',
})
if (cfg.label) {
group.addShape('text', {
attrs: {
text: cfg.label,
x: -width / 2 + 12,
y: -8,
textAlign: 'left',
fill: '#333',
fontSize: 12,
fontWeight: 600,
cursor: cfg.text !== '线材' ? 'pointer' : '',
},
name: 'text1',
})
// 添加SVG图像
group.addShape('image', {
attrs: {
x: width / 2 - 24,
y: -22,
width: 14,
height: 14,
img:
cfg.chg > 0
? getSvg(1, '#D91212')
: cfg.chg < 0
? getSvg(-1, '#12A96E')
: getSvg(0, '#999'),
cursor: cfg.text !== '线材' ? 'pointer' : '',
},
name: 'img-1',
})
group.addShape('text', {
attrs: {
text: formatNumValue(cfg.dataValue),
x: -width / 2 + 12,
y: 22,
textAlign: 'left',
fill: cfg.chg > 0 ? '#D91212' : cfg.chg < 0 ? '#12A96E' : '#666',
fontSize: 12,
fontWeight: 600,
cursor: cfg.text !== '线材' ? 'pointer' : '',
},
name: 'text3',
})
group.addShape('text', {
attrs: {
text: (cfg.chg > 0 ? '+' : cfg.chg < 0 ? '' : '') + cfg.chg + '%',
x: width / 2 - 12,
y: 22,
textAlign: 'right',
fill: cfg.chg > 0 ? '#D91212' : cfg.chg < 0 ? '#12A96E' : '#666',
fontSize: 12,
fontWeight: 600,
cursor: cfg.text !== '线材' ? 'pointer' : '',
},
name: 'text4',
})
}
return mainRect
},
// 响应状态变化
setState(name, value, item) {
// console.log('name', name, value, item)
let cfg = item._cfg.model
if (name === 'highlight') {
const group = item.getContainer()
const main_rect = group.find((ele) => ele.get('name') === 'main-rect')
main_rect.attr(
'stroke',
value
? cfg.chg > 0
? '#D12323'
: cfg.chg < 0
? '#12A96E'
: '#8E92A1'
: cfg.chg > 0
? '#F8DDE0'
: cfg.chg < 0
? '#B8DDCC'
: '#D7DCEA'
)
main_rect.attr('cursor', 'pointer')
main_rect.attr('lineWidth', value ? 2 : 1)
const top_rect = group.find((ele) => ele.get('name') === 'top-rect')
// 改变背景色
top_rect.attr(
'fill',
value
? cfg.chg > 0
? '#D12323'
: cfg.chg < 0
? '#12A96E'
: '#8E92A1'
: ''
)
const bottom_rect = group.find(
(ele) => ele.get('name') === 'bottom-rect'
)
// 改变背景色
bottom_rect.attr(
'fill',
value
? cfg.chg > 0
? '#FFF6F7'
: cfg.chg < 0
? '#E9FBF3'
: '#F8F9FE'
: ''
)
const text1 = group.find((ele) => ele.get('name') === 'text1')
text1.attr('fill', value ? '#FFF' : '#333')
const img_1 = group.find((ele) => ele.get('name') === 'img-1')
img_1.attr(
'img',
value
? cfg.chg > 0
? getSvg(1, '#fff')
: cfg.chg < 0
? getSvg(-1, '#fff')
: getSvg(0, '#fff')
: cfg.chg > 0
? getSvg(1, '#D91212')
: cfg.chg < 0
? getSvg(-1, '#12A96E')
: getSvg(0, '#999')
)
}
if (name === 'hover' && cfg.text != '线材') {
const group = item.getContainer()
const main_rect = group.find((ele) => ele.get('name') === 'main-rect')
main_rect.attr(
'stroke',
value || item.getStates().includes('highlight')
? cfg.chg > 0
? '#D12323'
: cfg.chg < 0
? '#12A96E'
: '#8E92A1'
: cfg.chg > 0
? '#F8DDE0'
: cfg.chg < 0
? '#B8DDCC'
: '#D7DCEA'
)
main_rect.attr('cursor', 'pointer')
}
},
getAnchorPoints() {
return [
[0.5, 0],
[1, 0.5],
[0.5, 1],
[0, 0.5], // 四边中点
]
},
})
//创建品种文本节点
G6.registerNode('breed-rect', {
draw(cfg, group) {
// 主矩形配置
const width = cfg.width ? cfg.width : cfg.label == '其他' ? 134 : 120
const height = cfg.height ? cfg.height : 40
const fill = '#FFF'
const stroke = '#C3CDE8'
// 创建主矩形
const mainRect = group.addShape('rect', {
attrs: {
x: -width / 2,
y: -height / 2,
width,
height,
fill,
stroke,
lineWidth: 1,
radius: [4, 4, 4, 4],
padding: 0,
shadowOffsetX: 0,
shadowOffsetY: 2,
shadowBlur: 6,
spread: 2,
shadowColor: 'rgba(149,157,174,0.1)',
},
name: 'main-rect',
})
if (cfg.label) {
group.addShape('text', {
attrs: {
text: cfg.label,
x: 0,
y: 7,
textAlign: 'center',
fill: '#333',
fontSize: 14,
fontWeight: 600,
},
name: 'text1',
})
if (cfg.label == '废钢') {
group.addShape('text', {
attrs: {
text: `螺废差:${cfg.dataValue}`,
x: -width / 2,
y: 46,
textAlign: 'left',
fill: '#333',
fontSize: 12,
},
name: 'text2',
})
group.addShape('text', {
attrs: {
text: `(${cfg.desc1})`,
x: -width / 2 + (4 * 12 + (cfg.dataValue.length + '') * 6) + 8,
y: 46,
textAlign: 'left',
fill: '#D91212',
fontSize: 12,
},
name: 'text2-desc1',
})
group.addShape('text', {
attrs: {
text: `${cfg.desc2}:`,
x: -width / 2,
y: 68,
textAlign: 'left',
fill: '#D91212',
fontSize: 12,
},
name: 'text2-desc2',
})
group.addShape('text', {
attrs: {
text: `${cfg.desc3}`,
x: -width / 2 + cfg.desc2.length * 12 + 8,
y: 68,
textAlign: 'left',
fill: '#333',
fontSize: 12,
},
name: 'text2-desc3',
})
}
}
return mainRect
},
setState(name, value, item) {
if (name === 'hover') {
const group = item.getContainer()
const main_rect = group.find((ele) => ele.get('name') === 'main-rect')
main_rect.attr(
'shadowColor',
value ? 'rgba(149,157,174,0.2)' : 'rgba(149,157,174,0.1)'
)
}
},
})
//创建文本利率节点
G6.registerNode('rate-rect', {
draw(cfg, group) {
// 主矩形配置
const width = cfg.codes && cfg.codes.length > 2 ? 204 : 212
const height = 12 + cfg.codes.length * 22
const fill = '#FFF'
const stroke = '#fff'
// 创建主矩形
const mainRect = group.addShape('rect', {
attrs: {
x: -width / 2,
y: -height / 2,
width,
height,
fill,
stroke,
lineWidth: 0,
radius: [4, 4, 4, 4],
padding: 0,
shadowOffsetX: 0,
shadowOffsetY: 2,
shadowBlur: 6,
spread: 2,
shadowColor: 'rgba(54,78,128,0.1)',
},
name: 'main-rect',
})
if (cfg.codes) {
cfg.codes.forEach((item, index) => {
group.addShape('text', {
attrs: {
text: `${item.label}: ${item.chg}%`,
x: -width / 2 + 12,
y: -height / 2 + 24 + 22 * index,
textAlign: 'left',
fill: '#333',
fontSize: 12,
},
name: `text-${index + 1}`,
})
group.addShape('text', {
attrs: {
text: `(${item.desc})`,
x:
-width / 2 +
12 +
((item.label.length + 1) * 12 +
((item.chg + '').length + 1) * 6) +
12,
y: -height / 2 + 24 + 22 * index,
textAlign: 'left',
fill: '#D91212',
fontSize: 12,
},
name: `text-${index + 1}-desc`,
})
})
}
return mainRect
},
})
//创建产量同比节点
G6.registerNode('ratio-rect', {
draw(cfg, group) {
// 主矩形配置
const width = 147
const height = 86
const fill = '#FFF'
const stroke = '#C3CDE8'
// 创建主矩形
const mainRect = group.addShape('rect', {
attrs: {
x: -width / 2,
y: -height / 2,
width,
height,
fill,
stroke,
lineWidth: 1,
radius: [4, 4, 4, 4],
padding: 0,
shadowOffsetX: 0,
shadowOffsetY: 2,
shadowBlur: 6,
spread: 2,
shadowColor: 'rgba(149,157,174,0.1)',
},
name: 'main-rect',
})
group.addShape('text', {
attrs: {
text: cfg.label,
x: 0,
y: -height / 2 + 24,
textAlign: 'center',
fill: '#333',
fontSize: 14,
fontWeight: 600,
},
name: 'text1',
})
group.addShape('line', {
attrs: {
x1: -width / 2 + 12,
y1: -14,
x2: width / 2 - 12,
y2: -14,
stroke: '#DDE3F2',
lineWidth: 1,
},
name: 'divider-line',
})
group.addShape('text', {
attrs: {
text: `${cfg.dataValue}`,
x: 16,
y: -height / 2 + 55,
textAlign: 'right',
fill: '#333',
fontSize: 16,
fontWeight: 600,
},
name: 'text2',
})
group.addShape('text', {
attrs: {
text: `${cfg.unit}`,
x: 20,
y: -height / 2 + 53,
textAlign: 'left',
fill: '#666',
fontSize: 12,
},
name: 'text2',
})
group.addShape('text', {
attrs: {
text: `累计同比${
cfg.sumYoy > 0 ? '上涨' : cfg.sumYoy < 0 ? '下降' : '持平'
}`,
x: -width / 2 + 12,
y: -height / 2 + 75,
textAlign: 'left',
fill: '#666',
fontSize: 12,
},
name: 'text3',
})
group.addShape('text', {
attrs: {
text: `${Math.abs(cfg.sumYoy).toFixed(2)}%`,
x: width / 2 - 14,
y: -height / 2 + 76,
textAlign: 'right',
fill:
cfg.sumYoy > 0 ? '#D91212' : cfg.sumYoy < 0 ? '#12A96E' : '#666666',
fontSize: 14,
fontWeight: 600,
},
name: 'text3-chg',
})
group.addShape('text', {
attrs: {
text: `${cfg.desc}`,
x: 0,
y: -height / 2 + 85 + 22,
textAlign: 'center',
fill: '#D91212',
fontSize: 12,
},
name: 'text4',
})
return mainRect
},
setState(name, value, item) {
if (name === 'hover') {
const group = item.getContainer()
const main_rect = group.find((ele) => ele.get('name') === 'main-rect')
main_rect.attr(
'shadowColor',
value ? 'rgba(149,157,174,0.2)' : 'rgba(149,157,174,0.1)'
)
}
},
})
//创建产量价格节点
G6.registerNode('product-rect', {
draw(cfg, group) {
// 主矩形配置
const width = cfg.width ? cfg.width : 134
const height = cfg.height ? cfg.height : 60
const fill = cfg.chg > 0 ? '#FBEAEC' : cfg.chg < 0 ? '#DFF5EB' : '#F1F2FA'
const stroke =
cfg.chg > 0 ? '#F8DDE0' : cfg.chg < 0 ? '#B8DDCC' : '#D7DCEA'
// 创建主矩形
const mainRect = group.addShape('rect', {
attrs: {
x: -width / 2,
y: -height / 2,
width,
height,
fill,
stroke,
lineWidth: 1,
radius: [4, 4, 4, 4],
padding: 12,
},
name: 'main-rect',
})
// 添加分割线(水平中线)
group.addShape('line', {
attrs: {
x1: -width / 2 + 12,
y1: 0,
x2: width / 2 - 12,
y2: 0,
stroke: cfg.chg > 0 ? '#E6C5C6' : cfg.chg < 0 ? '#ACD5C3' : '#D3D9E8',
lineWidth: 1,
},
name: 'divider-line',
})
// 添加上半部分小矩形
group.addShape('rect', {
attrs: {
x: -width / 2,
y: -height / 2,
width: width,
height: 30,
fill: '',
stroke: '#666',
lineWidth: 0,
radius: [4, 4, 0, 0],
},
name: 'top-rect',
})
// 添加下半部分小矩形
group.addShape('rect', {
attrs: {
x: -width / 2,
y: 0, // 从分割线下方开始
width: width,
height: 30,
fill: '',
stroke: '',
lineWidth: 0,
radius: [0, 0, 4, 4],
},
name: 'bottom-rect',
})
if (cfg.label) {
group.addShape('text', {
attrs: {
text: cfg.label,
x: -width / 2 + 12,
y: -8,
textAlign: 'left',
fill: '#333',
fontSize: 12,
fontWeight: 600,
},
name: 'text1',
})
// 添加SVG图像
group.addShape('image', {
attrs: {
x: width / 2 - 24,
y: -22,
width: 14,
height: 14,
img:
cfg.chg > 0
? getSvg(1, '#D91212')
: cfg.chg < 0
? getSvg(-1, '#12A96E')
: getSvg(0, '#999'),
cursor: cfg.text !== '线材' ? 'pointer' : '',
},
name: 'img-1',
})
group.addShape('text', {
attrs: {
text: formatNumValue(cfg.dataValue),
x: -width / 2 + 12,
y: 22,
textAlign: 'left',
fill: cfg.chg > 0 ? '#D91212' : cfg.chg < 0 ? '#12A96E' : '#666',
fontSize: 12,
fontWeight: 600,
},
name: 'text3',
})
group.addShape('text', {
attrs: {
text:
(cfg.chg > 0 ? '+' : cfg.chg < 0 ? '' : '') +
cfg.chg +
(cfg.chg && cfg.chg != '-' ? '%' : ''),
x: width / 2 - 12,
y: 22,
textAlign: 'right',
fill: cfg.chg > 0 ? '#D91212' : cfg.chg < 0 ? '#12A96E' : '#666',
fontSize: 12,
fontWeight: 600,
},
name: 'text4',
})
}
return mainRect
},
})
//创建文本节点
G6.registerNode('word-rect', {
draw(cfg, group) {
// 主矩形配置
const width = cfg.width ? cfg.width : 120
const height = cfg.height ? cfg.height : 40
let fill = cfg.fill ? cfg.fill : 'l(90) 0:#F5F9FF 1:#E0EDFF'
const stroke = cfg.strock ? cfg.strock : '#BCD0EE'
// 创建主矩形
const mainRect = group.addShape('rect', {
attrs: {
x: -width / 2,
y: -height / 2,
width,
height,
fill: fill,
stroke,
lineWidth: 1,
radius: [4, 4, 4, 4],
padding: 0,
shadowOffsetX: 0,
shadowOffsetY: 2,
shadowBlur: 6,
spread: 2,
shadowColor: 'rgba(149,157,174,0.1)',
},
name: 'main-rect',
})
//no_compute,是否不需要计算换行
//如果文本长度大于矩形宽度,则进行换行
if (cfg.label.length * 16 > cfg.width && !cfg.no_compute) {
let startLength = Math.floor((cfg.width - 20) / 16)
let lines = Math.ceil(cfg.label.length / startLength)
let startTop = (cfg.height - 16 * lines + 2 * (lines - 1)) / 2
for (let i = 0; i < lines; i++) {
group.addShape('text', {
attrs: {
x: 0,
y: -height / 2 + 6 + startTop + i * 18,
text: cfg.label.substring(i * startLength, (i + 1) * startLength),
fontSize: 14,
textAlign: 'center',
textBaseline: 'middle',
fill: '#333',
fontSize: 14,
fontWeight: 600,
},
name: 'text-shape',
})
}
} else {
group.addShape('text', {
attrs: {
text: cfg.label,
x: 0,
y: cfg.subLabel ? -2 : 8,
textAlign: 'center',
fill: '#333',
fontSize: 14,
fontWeight: 600,
},
name: 'text1',
})
if (cfg.subLabel) {
group.addShape('text', {
attrs: {
text: cfg.subLabel,
x: 0,
y: 17,
textAlign: 'center',
fill: cfg.subLabel?.style?.fill || '#666',
fontSize: cfg.subLabel?.style?.fontSize || 12,
fontWeight: cfg.subLabel?.style?.weight || 400,
},
name: 'text2',
})
}
}
return mainRect
},
setState(name, value, item) {
if (name === 'hover') {
const group = item.getContainer()
const main_rect = group.find((ele) => ele.get('name') === 'main-rect')
main_rect.attr(
'shadowColor',
value ? 'rgba(149,157,174,0.2)' : 'rgba(149,157,174,0.1)'
)
}
},
})
//创建自定义节点
G6.registerNode('custom-node', {
draw(cfg, group) {
// 主矩形配置
const width = cfg.width
const height = cfg.height
const fill = cfg.fill || '#fff'
const stroke = cfg.strock
// 创建主矩形
const mainRect = group.addShape('rect', {
attrs: {
x: -width / 2,
y: -height / 2,
width,
height,
fill: fill,
stroke,
lineWidth: cfg.lineWidth || 1,
radius: cfg.radius || [4, 4, 4, 4],
padding: 0,
shadowOffsetX: 0,
shadowOffsetY: 2,
shadowBlur: 6,
spread: 2,
shadowColor: cfg.shadowColor || 'rgba(149,157,174,0.1)',
},
name: 'main-rect',
})
cfg.pieceList &&
cfg.pieceList.forEach((e, i) => {
if (e.type == 'rect') {
group.addShape('rect', {
attrs: {
x: e.x,
y: e.y,
width: e.width,
height: e.height,
fill: e.fill || '#fff',
stroke: e.strock || '#fff',
lineWidth: e.lineWidth || 0,
radius: e.radius,
},
name: 'top-rect' + i,
})
}
if (e.type == 'text') {
group.addShape('text', {
attrs: {
x: e.x,
y: e.y,
text: e.label,
fontSize: e.fontSize,
textAlign: e.align,
textBaseline: 'middle',
fill: e.fill,
fontWeight: e.weight,
},
name: 'text-shape',
})
}
})
return mainRect
},
setState(name, value, item) {
if (name === 'hover') {
const group = item.getContainer()
const main_rect = group.find((ele) => ele.get('name') === 'main-rect')
main_rect.attr(
'shadowColor',
value ? 'rgba(149,157,174,0.2)' : 'rgba(149,157,174,0.1)'
)
}
},
})
//从左往右的箭头
G6.registerEdge('hvh', {
draw(cfg, group) {
const startPoint = cfg.startPoint
const endPoint = cfg.endPoint
const shape = group.addShape('path', {
attrs: {
stroke: '#8993AA',
path: [
['M', startPoint.x, startPoint.y],
['L', endPoint.x / 2 + (1 / 2) * startPoint.x, startPoint.y], // 二分之一处
['L', endPoint.x / 2 + (1 / 2) * startPoint.x, endPoint.y], // 二分之一处
['L', endPoint.x, endPoint.y],
],
lineWidth: 1,
lineDash: cfg.lineDash ? cfg.lineDash : '', // 自定义虚线模式
zIndex: 10, // 确保边在顶部
// startArrow: {
// path: "M 10,0 L -10,-10 L -10,10 Z",
// d: 10,
// },
// endArrow: {
// path: "M 10,0 L -10,-10 L -10,10 Z",
// d: 5,
// },
// endArrow: {
// path: G6.Arrow.triangle(10, 12, 25), // 三角形箭头
// fill: "#333", // 填充颜色
// stroke: "#333", // 边框颜色
// },
},
})
// 计算箭头方向
const angle = Math.atan2(
endPoint.y - startPoint.y,
endPoint.x - startPoint.x
)
// 自定义箭头路径生成方法
function getArrowPath(x, y, angle) {
const length = 9 // 箭头长度
const width = 4 // 箭头宽度
// 计算箭头三个点的坐标
const x1 = x - length * Math.cos(angle)
const y1 = y - length * Math.sin(angle)
const x2 = x1 - width * Math.cos(angle + Math.PI / 2)
const y2 = y1 - width * Math.sin(angle + Math.PI / 2)
const x3 = x1 - width * Math.cos(angle - Math.PI / 2)
const y3 = y1 - width * Math.sin(angle - Math.PI / 2)
return [['M', x, y], ['L', x2, y2], ['L', x3, y3], ['Z']]
}
//是否不需要显示箭头
if (!cfg.disableArrow) {
// 在终点绘制自定义箭头
group.addShape('path', {
attrs: {
path: getArrowPath(endPoint.x, endPoint.y, 0),
fill: '#8993AA',
stroke: '#8993AA',
lineWidth: 1,
},
})
}
// //在边上画循环点
const circle = group.addShape('ellipse', {
attrs: {
x: cfg.startPoint.x,
y: cfg.startPoint.y,
fill: 'l(0) 0:rgba(102,212,255,0.7) 1:#1A56FF',
rx: 4,
ry: 1,
shadowColor: '#1A56FF',
shadowBlur: 4,
shadowOffsetY: 1,
},
name: 'circle-shape',
})
const time =
(Math.abs(cfg.endPoint.x - cfg.startPoint.x) +
Math.abs(cfg.endPoint.y - cfg.startPoint.y)) /
0.05
circle.show()
circle.animate(
(ratio) => {
const tmpPoint = shape.getPoint(ratio)
const pos = G6.Util.getLabelPosition(shape, ratio)
let matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]
matrix = G6.Util.transform(matrix, [
['t', -tmpPoint.x, -tmpPoint.y],
['r', pos.angle],
['t', tmpPoint.x, tmpPoint.y],
])
return {
x: tmpPoint.x,
y: tmpPoint.y,
matrix,
}
},
{
repeat: true,
duration: time,
easing: 'easeLinear',
delay: 0,
}
)
// 计算边的中点
const midX = (startPoint.x + endPoint.x) / 2
const midY = (startPoint.y + endPoint.y) / 2
// 计算边的角度(用于文本旋转)
const angle2 = Math.atan2(
endPoint.y - startPoint.y,
endPoint.x - startPoint.x
)
// 添加上方文本
if (cfg.label) {
group.addShape('text', {
attrs: {
x: midX,
y: midY - 6, // 上方偏移
text: cfg.label,
fill: cfg.labelCfg?.style?.fill || '#333',
fontSize: cfg.labelCfg?.style?.fontSize || 12,
textAlign: 'center',
textBaseline: 'bottom',
rotate: angle2,
},
})
}
// 添加下方文本
if (cfg.subLabel) {
group.addShape('text', {
attrs: {
x: midX,
y: midY + 6, // 下方偏移
text: cfg.subLabel,
fill: cfg.subLabelCfg?.style?.fill || '#666',
fontSize: cfg.subLabelCfg?.style?.fontSize || 12,
textAlign: 'center',
textBaseline: 'top',
rotate: angle2,
},
})
}
return shape
},
})
//从下往上的边
G6.registerEdge('hvh2', {
draw(cfg, group) {
const startPoint = cfg.startPoint
const endPoint = cfg.endPoint
//默认显示2个拐角
let shape = null
if (cfg.corner) {
//只显示一个拐角
shape = group.addShape('path', {
attrs: {
stroke: '#8993AA',
path: [
['M', startPoint.x, startPoint.y],
['L', endPoint.x, startPoint.y],
['L', endPoint.x, endPoint.y],
],
lineWidth: 1,
lineDash: cfg.lineDash ? cfg.lineDash : '', // 自定义虚线模式
zIndex: 10, // 确保边在顶部
},
})
} else {
shape = group.addShape('path', {
attrs: {
stroke: '#8993AA',
path: [
['M', startPoint.x, startPoint.y],
[
'L',
startPoint.x,
(2 / 3) * startPoint.y + (1 / 3) * endPoint.y,
], // 三分之一处
['L', endPoint.x, (2 / 3) * startPoint.y + (1 / 3) * endPoint.y], // 三分之二处
['L', endPoint.x, endPoint.y],
],
lineWidth: 1,
lineDash: cfg.lineDash ? cfg.lineDash : '', // 自定义虚线模式
zIndex: 10, // 确保边在顶部
},
})
}
// 计算箭头方向
const angle = Math.atan2(
endPoint.y - startPoint.y,
endPoint.x - startPoint.x
)
// 自定义箭头路径生成方法
function getArrowPath(x, y, angle) {
const length = 9 // 箭头长度
const width = 4 // 箭头宽度
// 计算箭头三个点的坐标
const x1 = x
const y1 = y
const x2 = x1 - width
const y2 = y1 + length
const x3 = x1 + width
const y3 = y1 + length
return [['M', x, y], ['L', x2, y2], ['L', x3, y3], ['Z']]
}
// 在终点绘制自定义箭头
group.addShape('path', {
attrs: {
path: getArrowPath(endPoint.x, endPoint.y, 0),
fill: '#8993AA',
stroke: '#8993AA',
lineWidth: 1,
},
})
// //在边上画循环点
const circle = group.addShape('ellipse', {
attrs: {
x: cfg.startPoint.x,
y: cfg.startPoint.y,
fill: 'l(0) 0:rgba(102,212,255,0.7) 1:#1A56FF',
rx: 4,
ry: 1,
shadowColor: '#1A56FF',
shadowBlur: 4,
shadowOffsetY: 1,
},
name: 'circle-shape',
})
const time =
(Math.abs(cfg.endPoint.x - cfg.startPoint.x) +
Math.abs(cfg.endPoint.y - cfg.startPoint.y)) /
0.05
circle.show()
circle.animate(
(ratio) => {
const tmpPoint = shape.getPoint(ratio)
const pos = G6.Util.getLabelPosition(shape, ratio)
let matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]
matrix = G6.Util.transform(matrix, [
['t', -tmpPoint.x, -tmpPoint.y],
['r', pos.angle],
['t', tmpPoint.x, tmpPoint.y],
])
return {
x: tmpPoint.x,
y: tmpPoint.y,
matrix,
}
},
{
repeat: true,
duration: time,
easing: 'easeLinear',
delay: 0,
}
)
return shape
},
})
//从上往下的边
G6.registerEdge('hvh3', {
draw(cfg, group) {
const startPoint = cfg.startPoint
const endPoint = cfg.endPoint
//默认显示2个拐角
let shape = null
if (cfg.corner) {
//只显示一个拐角
shape = group.addShape('path', {
attrs: {
stroke: '#8993AA',
path: [
['M', startPoint.x, startPoint.y],
['L', endPoint.x, startPoint.y],
['L', endPoint.x, endPoint.y],
],
lineWidth: 1,
lineDash: cfg.lineDash ? cfg.lineDash : '', // 自定义虚线模式
},
})
} else {
shape = group.addShape('path', {
attrs: {
stroke: '#8993AA',
path: [
['M', startPoint.x, startPoint.y],
[
'L',
startPoint.x,
(2 / 3) * startPoint.y + (1 / 3) * endPoint.y,
], // 三分之一处
['L', endPoint.x, (2 / 3) * startPoint.y + (1 / 3) * endPoint.y], // 三分之二处
['L', endPoint.x, endPoint.y],
],
lineWidth: 1,
lineDash: cfg.lineDash ? cfg.lineDash : '', // 自定义虚线模式
},
})
}
// 计算箭头方向
const angle = Math.atan2(
endPoint.y - startPoint.y,
endPoint.x - startPoint.x
)
// 自定义箭头路径生成方法
function getArrowPath(x, y, angle) {
const length = 9 // 箭头长度
const width = 4 // 箭头宽度
// 计算箭头三个点的坐标
const x1 = x
const y1 = y
const x2 = x1 - width
const y2 = y1 - length
const x3 = x1 + width
const y3 = y1 - length
return [['M', x, y], ['L', x2, y2], ['L', x3, y3], ['Z']]
}
// 在终点绘制自定义箭头
group.addShape('path', {
attrs: {
path: getArrowPath(endPoint.x, endPoint.y, 0),
fill: '#8993AA',
stroke: '#8993AA',
lineWidth: 1,
},
})
// //在边上画循环点
const circle = group.addShape('ellipse', {
attrs: {
x: cfg.startPoint.x,
y: cfg.startPoint.y,
fill: 'l(0) 0:rgba(102,212,255,0.7) 1:#1A56FF',
rx: 4,
ry: 1,
shadowColor: '#1A56FF',
shadowBlur: 4,
shadowOffsetY: 1,
},
name: 'circle-shape',
})
const time =
(Math.abs(cfg.endPoint.x - cfg.startPoint.x) +
Math.abs(cfg.endPoint.y - cfg.startPoint.y)) /
0.05
circle.show()
circle.animate(
(ratio) => {
const tmpPoint = shape.getPoint(ratio)
const pos = G6.Util.getLabelPosition(shape, ratio)
let matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]
matrix = G6.Util.transform(matrix, [
['t', -tmpPoint.x, -tmpPoint.y],
['r', pos.angle],
['t', tmpPoint.x, tmpPoint.y],
])
return {
x: tmpPoint.x,
y: tmpPoint.y,
matrix,
}
},
{
repeat: true,
duration: time < 1000 ? 1000 : time,
easing: 'easeLinear',
delay: 0,
}
)
return shape
},
})
//从右往左的边
G6.registerEdge('hvh4', {
draw(cfg, group) {
const startPoint = cfg.startPoint
const endPoint = cfg.endPoint
const shape = group.addShape('path', {
attrs: {
stroke: '#8993AA',
path: [
['M', startPoint.x, startPoint.y],
['L', startPoint.x - 20, startPoint.y], // 三分之一处
[
'L',
startPoint.x - 20,
(1 / 3) * startPoint.y + (2 / 3) * endPoint.y,
], // 三分之二处
['L', endPoint.x, (1 / 3) * startPoint.y + (2 / 3) * endPoint.y], // 三分之二处
['L', endPoint.x, endPoint.y],
],
lineWidth: 1,
lineDash: cfg.lineDash ? cfg.lineDash : '', // 自定义虚线模式
},
})
// 计算箭头方向
const angle = Math.atan2(
endPoint.y - startPoint.y,
endPoint.x - startPoint.x
)
// 自定义箭头路径生成方法
function getArrowPath(x, y, angle) {
const length = 9 // 箭头长度
const width = 4 // 箭头宽度
// 计算箭头三个点的坐标
const x1 = x
const y1 = y
const x2 = x1 - width
const y2 = y1 - length
const x3 = x1 + width
const y3 = y1 - length
return [['M', x, y], ['L', x2, y2], ['L', x3, y3], ['Z']]
}
// 在终点绘制自定义箭头
group.addShape('path', {
attrs: {
path: getArrowPath(endPoint.x, endPoint.y, 0),
fill: '#8993AA',
stroke: '#8993AA',
lineWidth: 1,
},
})
// //在边上画循环点
const circle = group.addShape('ellipse', {
attrs: {
x: cfg.startPoint.x,
y: cfg.startPoint.y,
fill: 'l(0) 0:rgba(102,212,255,0.7) 1:#1A56FF',
rx: 4,
ry: 1,
shadowColor: '#1A56FF',
shadowBlur: 4,
shadowOffsetY: 1,
},
name: 'circle-shape',
})
const time =
(Math.abs(cfg.endPoint.x - cfg.startPoint.x) +
Math.abs(cfg.endPoint.y - cfg.startPoint.y)) /
0.05
circle.show()
circle.animate(
(ratio) => {
const tmpPoint = shape.getPoint(ratio)
const pos = G6.Util.getLabelPosition(shape, ratio)
let matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]
matrix = G6.Util.transform(matrix, [
['t', -tmpPoint.x, -tmpPoint.y],
['r', pos.angle],
['t', tmpPoint.x, tmpPoint.y],
])
return {
x: tmpPoint.x,
y: tmpPoint.y,
matrix,
}
},
{
repeat: true,
duration: time,
easing: 'easeLinear',
delay: 0,
}
)
return shape
},
})
//自定义边
G6.registerEdge('hvh_custom', {
draw(cfg, group) {
const startPoint = cfg.startPoint
const endPoint = cfg.endPoint
let path = []
//默认连接起始点和终端,中间的线段通过传入的数组连接
path.push(['M', startPoint.x, startPoint.y])
for (let i = 0; i < cfg.path.length; i++) {
path.push(['L', cfg.path[i][0], cfg.path[i][1]])
}
path.push(['L', endPoint.x, endPoint.y])
const shape = group.addShape('path', {
attrs: {
stroke: '#8993AA',
path: path,
lineWidth: 1,
lineDash: cfg.lineDash ? cfg.lineDash : '', // 自定义虚线模式
},
})
// 自定义箭头路径生成方法
function getArrowPath(x, y, direction) {
const length = 9 // 箭头长度
const width = 4 // 箭头宽度
// 计算箭头三个点的坐标
const x1 = x
const y1 = y
let x2, y2, x3, y3
if (direction == 'top') {
x2 = x1 - width
y2 = y1 + length
x3 = x1 + width
y3 = y1 + length
} else if (direction == 'right') {
x2 = x1 - length
y2 = y1 - width
x3 = x1 - length
y3 = y1 + width
} else if (direction == 'bottom') {
x2 = x1 - width
y2 = y1 - length
x3 = x1 + width
y3 = y1 - length
} else {
//默认箭头往左
x2 = x1 + length
y2 = y1 - width
x3 = x1 + length
y3 = y1 + width
}
return [['M', x, y], ['L', x2, y2], ['L', x3, y3], ['Z']]
}
// 在终点绘制自定义箭头
group.addShape('path', {
attrs: {
path: getArrowPath(endPoint.x, endPoint.y, cfg.direction),
fill: '#8993AA',
stroke: '#8993AA',
lineWidth: 1,
},
})
// //在边上画循环点
const circle = group.addShape('ellipse', {
attrs: {
x: cfg.startPoint.x,
y: cfg.startPoint.y,
fill: 'l(0) 0:rgba(102,212,255,0.7) 1:#1A56FF',
rx: 4,
ry: 1,
shadowColor: '#1A56FF',
shadowBlur: 4,
shadowOffsetY: 1,
},
name: 'circle-shape',
})
const time =
(Math.abs(cfg.endPoint.x - cfg.startPoint.x) +
Math.abs(cfg.endPoint.y - cfg.startPoint.y)) /
0.05
circle.show()
circle.animate(
(ratio) => {
const tmpPoint = shape.getPoint(ratio)
const pos = G6.Util.getLabelPosition(shape, ratio)
let matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1]
matrix = G6.Util.transform(matrix, [
['t', -tmpPoint.x, -tmpPoint.y],
['r', pos.angle],
['t', tmpPoint.x, tmpPoint.y],
])
return {
x: tmpPoint.x,
y: tmpPoint.y,
matrix,
}
},
{
repeat: true,
duration: time,
easing: 'easeLinear',
delay: 0,
}
)
// 计算边的中点
let midX = (startPoint.x + endPoint.x) / 2
let midY = (startPoint.y + endPoint.y) / 2
//自定义边传入的起始点及结束点
if (cfg.labelCfg && cfg.labelCfg.startPoint && cfg.labelCfg.endPoint) {
midX = (cfg.labelCfg.startPoint.x + cfg.labelCfg.endPoint.x) / 2
midY = (cfg.labelCfg.startPoint.y + cfg.labelCfg.endPoint.y) / 2
}
// 计算边的角度(用于文本旋转)
const angle2 = Math.atan2(
endPoint.y - startPoint.y,
endPoint.x - startPoint.x
)
// 添加上方文本
if (cfg.label) {
group.addShape('text', {
attrs: {
x: midX,
y: midY - 6, // 上方偏移
text: cfg.label,
fill: cfg.labelCfg?.style?.fill || '#333',
fontSize: cfg.labelCfg?.style?.fontSize || 12,
textAlign: 'center',
textBaseline: 'bottom',
rotate: angle2,
},
})
}
// 添加下方文本
if (cfg.subLabel) {
group.addShape('text', {
attrs: {
x: midX,
y: midY + 6, // 下方偏移
text: cfg.subLabel,
fill: cfg.subLabelCfg?.style?.fill || '#666',
fontSize: cfg.subLabelCfg?.style?.fontSize || 12,
textAlign: 'center',
textBaseline: 'top',
rotate: angle2,
},
})
}
return shape
},
})
const graph = new G6.Graph({
container: 'mountNode',
width: 1380,
height: mapHeight.value,
minZoom: 0.4,
maxZoom: 1,
modes: {
default: [
'click-select',
{ type: 'drag-canvas', enableOptimize: true },
{
type: 'zoom-canvas',
optimizeZoom: true,
shouldUpdate: () => false,
onWheel: (e) => {
e.preventDefault()
const currentZoom = graph.getZoom()
let ratio =
e.wheelDelta > 0
? Math.min(currentZoom + 0.1, 1)
: Math.max(currentZoom - 0.1, 0.4)
graph.zoomTo(ratio, {
x: graph.getWidth() / 2,
y: graph.getHeight() / 2,
})
if (e.wheelDelta > 0 && ratio >= graph.getMaxZoom()) {
graph.moveTo(0, 0)
}
},
},
],
},
// defaultNode: {
// type: 'split-rect',
// size: [120, 80],
// style: { fill: '#FBEAEC', stroke: '#F8DDE0' },
// label: '',
// },
defaultNode: {
type: 'product-rect',
size: [120, 80],
style: { fill: '#FBEAEC', stroke: '#F8DDE0' },
label: '',
},
defaultEdge: {
type: 'hvh',
zIndex: 2,
},
})
graph.data({
edges: edges.value,
nodes: nodes.value,
})
graph.render()
nextTick(() => {
const edgeGroup = graph.get('edgeGroup')
edgeGroup.toFront()
graph.paint()
})
// 提取公共方法
function resetNodeStates() {
graph.getNodes().forEach((node) => {
if (node.getStates().includes('highlight')) {
node.clearStates('highlight')
}
})
}
function handleNodeClick(evt) {
console.log('handleNodeClick')
const { item } = evt
const model = item.getModel()
if (item._cfg.currentShape === 'split-rect' && !item._cfg.model.noClick) {
resetNodeStates()
item.setState('highlight', true)
if (item._cfg.model?.breedCode) {
emit('breed-select', item._cfg.model.breedCode)
}
}
}
function handleNodeHover(ev, state) {
const { item } = ev
if (
['split-rect', 'breed-rect', 'ratio-rect', 'word-rect'].includes(
item._cfg.currentShape
)
) {
graph.setItemState(item, 'hover', state)
}
}
graph.on('node:click', handleNodeClick)
graph.on('node:mouseenter', (ev) => {
graph.setAutoPaint(false)
handleNodeHover(ev, true)
graph.paint()
graph.setAutoPaint(true)
})
graph.on('node:mouseleave', (ev) => {
handleNodeHover(ev, false)
})
let startX = 0
let startY = 0
graph.on('node:mousedown', (e) => {
if (e.item._cfg.currentShape === 'block-rect') {
isDragging.value = true
startX = e.canvasX
startY = e.canvasY
}
})
graph.on('node:mousemove', (e) => {
if (isDragging.value) {
const dx = e.canvasX - startX
const dy = e.canvasY - startY
graph.translate(dx, dy, false, { duration: 500, easing: 'easeLinear' })
startX = e.canvasX
startY = e.canvasY
}
})
graph.on('node:mouseup', () => {
console.log('node:mouseup')
isDragging.value = false
//document.removeEventListener('mousemove', initDrag)
})
graph.on('canvas:mousemove', () => {
isDragging.value = false
//document.removeEventListener('mousemove', initDrag)
})
graph.on('canvas:mouseup', () => {
isDragging.value = false
//document.removeEventListener('mousemove', initDrag)
})
graph.on('viewportchange', () => {
graph.refresh()
graph.paint()
})
//默认选中品种
graph.getNodes().forEach((node) => {
// console.log('node', node._cfg.model.label)
if (node._cfg.model.breedCode == window.breedCode) {
node.setState('highlight', true)
}
})
graph2.value = graph
nextTick(() => {
mapResize()
})
}
const mapResize = () => {
console.log('mapResize')
nextTick(() => {
let parentWidth = document.querySelector('.supply-analysis').clientWidth
let width = parentWidth
let autoHeight =
((!isBlank.value
? mapData[window.breedCode].mapHeight
: defaultData.mapHeight) *
width) /
1426
mapHeight.value = autoHeight
mapPadding.value =
((!isBlank.value
? mapData[window.breedCode].mapPadding
: defaultData.mapPadding) *
width) /
1376
console.log('缩小比例', width / 1426)
let ratio = document.getElementById('mountNode').clientWidth / 1376
console.log('ratio', ratio)
graph2.value &&
graph2.value.zoomTo(ratio, {
x: graph2.value.getWidth() / 2,
y: graph2.value.getHeight() / 2,
})
graph2.value && graph2.value.moveTo(0, 0)
})
}
//在移动节点过程中有时会丢失鼠标状态,因此通过页面监听鼠标移动事件获取鼠标状态
const initDrag = (e) => {
//console.log('initDrag', e)
if (e) {
if (e.which == 0) {
isDragging.value = false
}
}
}
onMounted(() => {
console.log('mapData', mapData)
if (!isBlank.value) {
//console.log('显示铜产业链')
edges.value = mapData[window.breedCode].edges
nodes.value = mapData[window.breedCode].nodes
mapHeight.value = mapData[window.breedCode].mapHeight
mapPadding.value = mapData[window.breedCode].mapPadding
desc.value = mapData[window.breedCode].desc
} else {
edges.value = defaultData.edges
nodes.value = defaultData.nodes
mapHeight.value = defaultData.mapHeight
mapPadding.value = defaultData.mapPadding
}
const { menuCode } = route.query
menuCode && loadGraphData(menuCode)
//通用模块监听事件
proxy.$bus.on('echartResize', mapResize)
document.addEventListener('mousemove', initDrag)
})
onBeforeUnmount(() => {
proxy.$bus.off('echartResize', mapResize)
document.removeEventListener('mousemove', initDrag)
})
</script>
<style lang="less" scoped>
.link-map {
padding: 12px;
.link-title {
font-weight: 600;
font-size: 16px;
color: #333333;
line-height: 24px;
text-align: center;
margin-bottom: 12px;
}
.link-desc {
font-size: 12px;
margin-top: -4px;
margin-bottom: 12px;
:deep(span:nth-child(1)) {
color: #245a9a;
}
}
}
#mountNode {
width: 100%;
height: 449px;
// border: 1px solid #efefef;
position: relative;
overflow: hidden;
max-width: 1376px;
margin: 0 auto;
//max-height: 379.299px;
}
</style>