g6/demos/system-graph-demo.html

2038 lines
68 KiB
HTML
Raw Normal View History

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>系统架构Demo</title>
<style>::-webkit-scrollbar{display:none;}html,body{overflow:hidden;margin:0;}</style>
</head>
<body>
<div id="mountNode"></div>
<div class="timeControllerOuterContainer" >
<div id="timeControllerContainer" >
</div>
</div>
<div class="detailPannel">
<button id="btn_error">分析错误源节点</button>
<div id="infoDetail"></div>
</div>
<div class="description">
<p>
Demo说明
</p>
<p>
本例用假数据展示了某次系统发生异常时,异常系统和其涉及系统的状态,其中发生异常的系统时红色的;
</p>
<p>
系统间依赖关系用边表示,出现问题的依赖也对应映射成红色;
</p>
<p>
标记出了疑似的问题源头系统,当点击右上角分析时,聚焦到该系统对应的节点上,并将系统近一小时的错误量作为扩展信息展示;
</p>
<p>
时序分析:下方时时间轴,某个时间点系统出现的错误数量,点击选择某个时间点,对应关系图切换到选择时间对应的数据(这里因为时示例,仅模拟了两个时刻的数据);
</p>
</div>
<script>/*Fixing iframe window.innerHeight 0 issue in Safari*/document.body.clientHeight;</script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://gw.alipayobjects.com/os/antv/pkg/_antv.g6-3.1.1/build/g6.js"></script>
<script src="https://cdn.bootcss.com/dagre/0.8.4/dagre.js"></script>
<script src="http://momentjs.cn/downloads/moment.min.js"></script>
<style>
#mountNode {
background:#001528;
}
.timeControllerOuterContainer{
text-align: center;
height: 100px;
background-color: beige;
position: relative;
background:#001528;
}
#timeControllerContainer{
position: absolute;
background-color: #002242;
position:absolute;
bottom:16px;
left: 50%;
text-align: center;
transform: translate(-50%, 0%);
padding-bottom: 10px;
padding-top: 10px;
}
.g6-tooltip {
border: 1px solid #e2e2e2;
border-radius: 4px;
font-size: 12px;
color: #545454;
background-color: rgba(255, 255, 255, 0.9);
padding: 10px 8px;
box-shadow: rgb(174, 174, 174) 0px 0px 10px;
}
.detailPannel{
position: absolute;
right: 64px;
top: 32px;
}
#btn_error{
color: white;
background: #f75a15;
border: 2px solid #f75a15;
border-radius: 6px;
cursor: pointer;
}
.description{
position: absolute;
left: 64px;
top: 12px;
color: white;
width: 250px;
font-size: 13px;
}
</style>
<script>
// ============================
// 大图假数据 1
const mockGraphData1 = {
"code": "0",
"data": {
"nodes": [
{
"app": "system1",
"abnormal": false,
"label": "system1",
"id": "system1"
},
{
"app": "system2",
"abnormal": false,
"label": "system2",
"id": "system2"
},
{
"app": "system3",
"abnormal": false,
"label": "system3",
"id": "system3"
},
{
"app": "system4",
"abnormal": false,
"label": "system4",
"id": "system4"
},
{
"app": "system5",
"abnormal": true,
"label": "system5",
"id": "system5"
},
{
"app": "system6",
"abnormal": false,
"label": "system6",
"id": "system6"
},
{
"app": "system7",
"abnormal": false,
"label": "system7",
"id": "system7"
},
{
"app": "system8",
"abnormal": false,
"label": "system8",
"id": "system8"
},
{
"app": "system9",
"abnormal": false,
"label": "system9",
"id": "system9"
},
{
"app": "source_system",
"abnormal": true,
"label": "source_system",
"id": "source_system"
},
{
"app": "system11",
"abnormal": false,
"label": "system11",
"id": "system11"
},
{
"app": "system12",
"abnormal": false,
"label": "system12",
"id": "system12"
},
{
"app": "system13",
"abnormal": true,
"label": "system13",
"id": "system13"
},
{
"app": "sysX",
"abnormal": false,
"label": "sysX",
"id": "sysX"
},
{
"app": "system15",
"abnormal": true,
"label": "system15",
"id": "system15"
},
{
"app": "sysZ",
"abnormal": false,
"label": "sysZ",
"id": "sysZ"
},
{
"app": "sysY",
"abnormal": false,
"label": "sysY",
"id": "sysY"
}
],
"edges": [
{
"abnormal": true,
"serviceList": [
"com.abcd.efghi.jk.service.apple.pear.banana.rice.noodle.tea.drink",
"com.abcd.efghi.jk.service.apple.pear.banana.rice.hi"
],
"source": "system3",
"target": "system13"
},
{
"abnormal": false,
"serviceList": [],
"source": "system6",
"target": "system5"
},
{
"abnormal": true,
"serviceList": [
"com.abcd.efghi.jk.service.apple.pear.banana.rice.fruit.eat.drink.findAll",
"com.abcd.efghi.jk.service.apple.pear.banana.rice.fruit.eat.drink.findAllList"
],
"source": "system7",
"target": "source_system"
},
{
"abnormal": true,
"serviceList": [
"com.abcd.efghi.jk.service.apple.pear.banana.rice.fruit.eat.drink.findAllList"
],
"source": "source_system",
"target": "source_system"
},
{
"abnormal": true,
"serviceList": [
"com.abcd.efghi.jk.service.apple.pear.banana.james.kobe.allen"
],
"source": "system5",
"target": "source_system"
},
{
"abnormal": true,
"serviceList": [
"com.abcd.efghi.jk.service.apple.pear.banana.playBall"
],
"source": "system12",
"target": "source_system"
},
{
"abnormal": false,
"serviceList": [],
"source": "system7",
"target": "system9"
},
{
"abnormal": false,
"serviceList": [],
"source": "system1",
"target": "system11"
},
{
"abnormal": false,
"serviceList": [],
"source": "system9",
"target": "system6"
},
{
"abnormal": true,
"serviceList": [
"com.abcd.efghi.jk.service.apple.pear.banana.james.kobe.allen"
],
"source": "system8",
"target": "system5"
},
{
"abnormal": true,
"serviceList": [
"com.abcd.efghi.jk.service.apple.pear.banana.james.kobe.allen",
"com.abcd.efghi.jk.service.apple.pear.banana.james.kobe.allen"
],
"source": "system8",
"target": "source_system"
},
{
"abnormal": false,
"serviceList": [],
"source": "sysX",
"target": "system1"
},
{
"abnormal": false,
"serviceList": [],
"source": "system11",
"target": "system8"
},
{
"abnormal": false,
"serviceList": [],
"source": "system7",
"target": "system11"
},
{
"abnormal": true,
"serviceList": [
"com.abcd.efghi.jk.service.apple.pear.banana.james.kobe.allen"
],
"source": "system13",
"target": "system15"
},
{
"abnormal": false,
"serviceList": [],
"source": "system9",
"target": "system2"
},
{
"abnormal": false,
"serviceList": [],
"source": "sysZ",
"target": "sysY"
},
{
"abnormal": false,
"serviceList": [],
"source": "system4",
"target": "sysX"
},
{
"abnormal": false,
"serviceList": [],
"source": "sysY",
"target": "system11"
},
{
"abnormal": true,
"serviceList": [
"com.abcd.efghi.jk.service.apple.pear.banana.james.kobe.allen"
],
"source": "system2",
"target": "source_system"
},
{
"abnormal": true,
"serviceList": [
"com.abcd.efghi.jk.service.apple.pear.banana.james.kobe.cater"
],
"source": "system6",
"target": "source_system"
},
{
"abnormal": false,
"serviceList": [],
"source": "system7",
"target": "system12"
},
{
"abnormal": false,
"serviceList": [],
"source": "sysZ",
"target": "system7"
},
{
"abnormal": false,
"serviceList": [],
"source": "system7",
"target": "system2"
}
]
},
"errorMsg": "",
"success": true,
"successMsg": ""
};
// ============================
// 大图假数据 2
const mockGraphData2 = {
"code": "0",
"data": {
"nodes": [
{
"app": "system1",
"abnormal": false,
"label": "system1",
"id": "system1"
},
{
"app": "source_system",
"abnormal": true,
"label": "source_system",
"id": "source_system"
},
{
"app": "system3",
"abnormal": false,
"label": "system3",
"id": "system3"
},
{
"app": "system4",
"abnormal": false,
"label": "system4",
"id": "system4"
},
{
"app": "system5",
"abnormal": false,
"label": "system5",
"id": "system5"
},
{
"app": "system6",
"abnormal": false,
"label": "system6",
"id": "system6"
},
{
"app": "system7",
"abnormal": false,
"label": "system7",
"id": "system7"
}
],
"edges": [
{
"abnormal": false,
"serviceList": [],
"source": "system7",
"target": "system6"
},
{
"abnormal": true,
"serviceList": [
"com.abcd.ef.ghi.jklmn.service.abcd.efg.api.x1234.y12345"
],
"source": "system7",
"target": "source_system"
},
{
"abnormal": false,
"serviceList": [],
"source": "system1",
"target": "system7"
},
{
"abnormal": false,
"serviceList": [],
"source": "system5",
"target": "system1"
},
{
"abnormal": false,
"serviceList": [],
"source": "system6",
"target": "system3"
},
{
"abnormal": false,
"serviceList": [],
"source": "system4",
"target": "system5"
},
]
},
"errorMsg": "",
"success": true,
"successMsg": ""
};
// 节点详细假数据
const appDetail = [
{
"count": "0",
"time": "1570995300000",
"type": "0015"
},
{
"count": "2",
"time": "1570995360000",
"type": "0015"
},
{
"count": "3",
"time": "1570995420000",
"type": "0015"
},
{
"count": "0",
"time": "1570995480000",
"type": "0015"
},
{
"count": "1",
"time": "1570995540000",
"type": "0015"
},
{
"count": "12",
"time": "1570995600000",
"type": "0015"
},
{
"count": "12",
"time": "1570995660000",
"type": "0015"
},
{
"count": "14",
"time": "1570995720000",
"type": "0015"
},
{
"count": "16",
"time": "1570995780000",
"type": "0015"
},
{
"count": "12",
"time": "1570995840000",
"type": "0015"
},
{
"count": "14",
"time": "1570995900000",
"type": "0015"
},
{
"count": "15",
"time": "1570995960000",
"type": "0015"
},
{
"count": "16",
"time": "1570996020000",
"type": "0015"
},
{
"count": "12",
"time": "1570996080000",
"type": "0015"
},
{
"count": "9",
"time": "1570996140000",
"type": "0015"
},
{
"count": "16",
"time": "1570996200000",
"type": "0015"
},
{
"count": "17",
"time": "1570996260000",
"type": "0015"
},
{
"count": "20",
"time": "1570996320000",
"type": "0015"
},
{
"count": "22",
"time": "1570996380000",
"type": "0015"
},
{
"count": "19",
"time": "1570996440000",
"type": "0015"
},
{
"count": "18",
"time": "1570996500000",
"type": "0015"
},
{
"count": "16",
"time": "1570996560000",
"type": "0015"
},
{
"count": "17",
"time": "1570996620000",
"type": "0015"
},
{
"count": "16",
"time": "1570996680000",
"type": "0015"
},
{
"count": "17",
"time": "1570996740000",
"type": "0015"
},
{
"count": "19",
"time": "1570996800000",
"type": "0015"
},
{
"count": "21",
"time": "1570996860000",
"type": "0015"
},
{
"count": "22",
"time": "1570996920000",
"type": "0015"
},
{
"count": "23",
"time": "1570996980000",
"type": "0015"
},
{
"count": "20",
"time": "1570997040000",
"type": "0015"
},
{
"count": "18",
"time": "1570997100000",
"type": "0015"
},
{
"count": "12",
"time": "1570997160000",
"type": "0015"
},
{
"count": "12",
"time": "1570997220000",
"type": "0015"
},
{
"count": "12",
"time": "1570997280000",
"type": "0015"
},
{
"count": "13",
"time": "1570997340000",
"type": "0015"
},
{
"count": "14",
"time": "1570997400000",
"type": "0015"
},
{
"count": "17",
"time": "1570997460000",
"type": "0015"
},
{
"count": "12",
"time": "1570997520000",
"type": "0015"
},
{
"count": "19",
"time": "1570997580000",
"type": "0015"
},
{
"count": "10",
"time": "1570997640000",
"type": "0015"
},
{
"count": "9",
"time": "1570997700000",
"type": "0015"
},
{
"count": "3",
"time": "1570997760000",
"type": "0015"
},
{
"count": "2",
"time": "1570997820000",
"type": "0015"
},
{
"count": "1",
"time": "1570997880000",
"type": "0015"
},
{
"count": "0",
"time": "1570997940000",
"type": "0015"
},
{
"count": "1",
"time": "1570998000000",
"type": "0015"
},
{
"count": "0",
"time": "1570998060000",
"type": "0015"
},
{
"count": "0",
"time": "1570998120000",
"type": "0015"
},
{
"count": "0",
"time": "1570998180000",
"type": "0015"
},
{
"count": "0",
"time": "1570998240000",
"type": "0015"
},
{
"count": "0",
"time": "1570998300000",
"type": "0015"
},
{
"count": "1",
"time": "1570998360000",
"type": "0015"
},
{
"count": "0",
"time": "1570998420000",
"type": "0015"
},
{
"count": "0",
"time": "1570998480000",
"type": "0015"
},
{
"count": "0",
"time": "1570998540000",
"type": "0015"
},
{
"count": "0",
"time": "1570998600000",
"type": "0015"
},
{
"count": "0",
"time": "1570998660000",
"type": "0015"
},
{
"count": "2",
"time": "1570998720000",
"type": "0015"
},
{
"count": "0",
"time": "1570998780000",
"type": "0015"
},
{
"count": "0",
"time": "1570998840000",
"type": "0015"
},
{
"count": "0",
"time": "1570998900000",
"type": "0015"
}
];
// G6注册样式
const LABEL_TAG = 'LABEL_TAG';
const ADD_TAG = 'ADD_TAG';
const BALL_TAG = 'BALL_TAG';
const TOOLTIP_TAG = 'TOOLTIP_TAG';
// 节点样式的配置参数定义
const NODE_CONFIG = {
// 大圆
nodesRadius:15, // 点的半径
colorHealthy:'#1563FF', // 正常颜色
colorError:'#f75a15', // 非正常颜色
fadedOpacity:0.2, // 褪色了的透明度数
commenOpacity:1, // 正常的了的透明度数
// 大院的聚焦状态
foucsRadusMin:18, // 聚焦状态的指示圆最小半径
foucsRadusMax:25, // 聚焦状态的指示圆最大半径
foucsColor:'lightgray',
foucsOpacity:0.4,
foucsWidth:0.4,
foucsAnimateDelay:800,
// 文本
labalFontSize:10,
labalFontSizeFaded:5,
labalFontFillColor:'#fff',
labalFontStrokeColorHealthy:"#2529e8",
labalFontStrokeColorError:"#f75a15",
// 细节信息
sectionCount:60, // 圆周细化的区域数
// 时间指示器
timeIndicatorstartRadius:100, // 起始半径
timeIndicatorEndRadius:100.5, // 终点半径
timeIndicatorHoverOffset: 4 , // hover时候的终点半径
timeIndicatorColor:'lightgray',
dataSectionColorDataOpacity:0.7,
timeIndicatorAnimateDelay:1200,
timeIndicatorHoverEnterAnimateDelay:50,
timeIndicatorHoverLeaveAnimateDelay:250,
// 时间提示
timeTextOffsetX:0, // X方面偏移
timeTextOffsetY:8, // Y方面偏移
timeTextRadius:115, // 文字距离中心的半径
timeTextFontSize:9, // 文字大小
timeTextFontWeight:1, //粗细
timeTextColor:"white", // 颜色
timeTextHideOpacity:0, // 隐藏时透明度(已废弃)
timeTextShowOpacity:0.7, // 显示透明度
//数据
dataSectionStartRaduis:25, // 数据部分的起始半径
dataSectionEndRaduis:80, // 数据部分的终至半径
dataBarWidthAngle:2, // 数据的宽度
dataSectionColorBG:'white',
dataSectionColorBGOpacity:0.2,
dataSectionColorData:'#A8071A',
dataSectionColorDataOpacity:0.7,
dataSectionAnimateDelay:1200,
// 开始位置标记
makerStartColor:'lightgray',
makerStartOpacity:0.7,
makerStartPositionX:0,
makerStartPositionY:-101,
makerStartRaduis: 2,
// 关闭详情(细节)按钮
closeBtnPositionX:0,
// closeBtnPositionY:11,
closeBtnPositionY:-130,
closeBtnFontSize:8,
closeBtnHoverFontSize:10,
closeBtnOpacity:1,
closeBtnFontWeight:1,
closeBtnCommenColor:'red',
closeBtnHoverColor:'white',
hoverAnimateTime:100,
};
// 边样式的配置参数定义
const EDGE_CONFIG = {
// 虚线设置
DASH:{
dashArray:[
[0,1],
[0,2],
[1,2],
[0,1,1,2],
[0,2,1,2],
[1,2,1,2],
[2,2,1,2],
[3,2,1,2],
[4,2,1,2]
],
lineDash:[4, 2, 1, 2],
interval:9,
},
// 特殊线的横向偏移量,这个值越大,特殊线的弧度越大
specialArcOffsetX: 30,
// 正常颜色
colorOk:'#ccc',
// 异常颜色
colorAbnoraml:'#A8071A',
// 正常线宽
lineWidthCommon: 1.6,
// Hover时增长线宽
lineWidthHoverIncrease: 2,
// 正常透明度
opacityCommon:0.7,
// faded状态的透明度数
opacityFaded:0.1,
// Hover时透明度
opacityHover:0.9,
// 边上的错误提示半径
errorTipRadius:5,
// 边上的错误外部圆提示颜色
errorTipBallColor:"#A8071A",
// 边上的错误文本文字大小
errorTipTextFontSize:10,
// 边上的错误文本颜色
errorTipTextColor:"white",
errorTipTextFontWeight:600,
errorTipTextAlign: 'center',
errorTipTextBaseline: 'center',
// 流动的指示小球
// 颜色
flowingBallColorCommon:'darkgray',
flowingBallColorAbnormal:'#A8071A',
// 半径
flowingBallRadius:2,
// 时间间隔
flowingTimeInterval:3000,
};
// 调用函数注册样式
// 节点
registerNodesStyle();
// 边
registerEdgesStyle();
let data = mockGraphData1.data;
// 图的尺寸定义 canvas大小
window.graphSize = {
width: window.innerWidth,
height: window.innerHeight - 110,
}
// 构造G6对象
const graph = new G6.Graph({
container: 'mountNode',
width: window.graphSize.width,
height: window.graphSize .height,
autoPaint: true,
modes: {
default: ['drag-canvas', {
type: 'zoom-canvas',
sensitivity: 0.8
}, {
type: 'edge-tooltip',
formatText: function formatText(model) {
console.log('model', model)
const { serviceList=[] } = model;
if(serviceList.length === 0){
return "链路无异常"
}
let text = '异常链路是:<br> ' + serviceList.map(item =>(item + '<br>' ));
return text;
},
shouldUpdate: function shouldUpdate(e) {
return true;
}
}]
},
fitView: true
});
window.graph = graph;
// 布局数据
// 使用dagre
const layoutedData = layoutByDagre(data);
// 给数据添加样式
// 原始数据中没有shape属性
const styledData = addDataStyle(layoutedData);
// 读取并绘制
window.graph.read(styledData);
/** 更新数据的方法 */
function updateGraph(data){
if(data.nodes.length === 0){
window.graph.clear();
}
const layoutedData = layoutByDagre(data);
const styledData = addDataStyle( layoutedData );
window.graph.read(styledData);
}
/** 使用dagre 布局的函数 */
function layoutByDagre(data){
const { nodes, edges } = data;
const g = new dagre.graphlib.Graph();
// 给dagre设置方向
g.setGraph({
rankdir: 'TB' // 表示方向可以是TB, BT, LR, RL
});
g.setGraph({});
g.setDefaultEdgeLabel(function() { return {}; });
let labelNodeMap = {};
// 给dagre设置点和边
nodes.forEach((node) => {
g.setNode(node.id, {width: 60, height: 60});
labelNodeMap[node.id] = node;
});
edges.forEach(function(edge) {
g.setEdge(edge.source, edge.target);
});
// 布局
dagre.layout(g);
// 将布局信息即x和y写入到原对象中
g.nodes().forEach(v => {
let originNode = labelNodeMap[v];
let layoutedNode = g.node(v);
originNode.x = layoutedNode.x;
originNode.y = layoutedNode.y;
});
// id -> point的映射
this.nodeIdPointObjMap = labelNodeMap;
return data;
}
/** 构造节点和边样式信息 */
function addDataStyle(data){
// 节点的样式
data.nodes.forEach(node =>{
node.shape = 'app-node-emergency';
});
// 边的样式
data.edges.forEach(edge =>{
edge.shape = 'ant-edge-emergency';
});
return data;
}
// ================================
// 节点样式注册
function registerNodesStyle(){
// app-node
G6.registerNode('app-node-emergency', {
draw:(cfg, group)=> {
const {abnormal} = cfg;
// 外侧圆形
const itemBox = group.addShape('circle', {
attrs: {
x: 0,
y: 0,
r: NODE_CONFIG.nodesRadius,
fill: abnormal ? NODE_CONFIG.colorError: NODE_CONFIG.colorHealthy, // 2529e8
},
});
// 文本
const labelShape = group.addShape('text', {
attrs: {
x: 0,
y: 2,
text: cfg.app,
fontSize: NODE_CONFIG.labalFontSize,
fill: NODE_CONFIG.labalFontFillColor,
fontWeight: 600,
textAlign: 'center',
textBaseline: 'center',
stroke: abnormal ? NODE_CONFIG.labalFontStrokeColorError : NODE_CONFIG.labalFontStrokeColorHealthy,
},
tag:LABEL_TAG,
})
return itemBox
},
afterDraw:(cfg, group)=> { },
setState:(name, value, item)=> {
const shape = item.get('keyShape');
const group = item.get('group');
switch (name) {
case 'faded':
if (value) {
shape.attr('opacity', NODE_CONFIG.fadedOpacity);
const labels = group.findAll(item => {
return item._cfg.tag && item._cfg.tag === LABEL_TAG;
});
labels.forEach(lable =>{
lable.attr('fontSize', NODE_CONFIG.labalFontSizeFaded);
})
}
else {
shape.attr('opacity', NODE_CONFIG.commenOpacity);
const labels = group.findAll(item => {
return item._cfg.tag && item._cfg.tag === LABEL_TAG;
});
labels.forEach(lable =>{
lable.attr('fontSize', NODE_CONFIG.labalFontSize);
})
}
break;
case 'focus':
if(value){ // 聚焦状态
addFocus(shape, item);
} else { // 移除
removeAdded(shape, item);
}
break;
case 'detailing':
if(value){ // 展示细节
addDetail(shape, item);
} else { // 移除
removeAdded(shape, item);
}
break;
}
},
}, 'circle');
// 移除菜单函数
const removeAdded = (shape, item)=>{
const group = shape.getParent();
// 移除所有添加
const addEle = group.findAll(item => {
return item._cfg.tag && item._cfg.tag === ADD_TAG;
});
addEle.forEach(ele => {
// group.removeChild(ele);
const type = ele._cfg.type;
if(type === 'fan'){ // 扇形的时候加上动画
ele.animate({
re: NODE_CONFIG.foucsRadusMin,
rs: NODE_CONFIG.foucsRadusMin,
repeat: false
}, 500, 'easeLinear', ()=>{
group.removeChild(ele);
});
} else {
group.removeChild(ele);
}
});
}
// 添加细节信息状态
const addDetail = (shape, item) =>{
if(appDetail.length === 0 ){
// 无详细数据或者数据中没有count字段
console.log('请求到的详情中没有可绘制字段...');
return;
}
const group = shape.getParent();
const { _cfg } = item;
// 计算区域角度
const perSectionAngle = (2 * Math.PI * (360 / NODE_CONFIG.sectionCount)) / 360;
const startAngle = (-1) * (2 * Math.PI / 4); // -90度弧度制的其实是12点钟方向
let currentAngle = startAngle;
const timeIndicatorArr = []; // 时间指示器的图像实例集合
const timeIndicatorTextArr = []; // 时间指示器的图像实例集合
const dataSectionBgArr = []; // 数据块背景图像实例集合
const dataSectionDataArr = []; // 数据块DATA的图像实例集合
// 绘制数据
// 分成两个部分
// 一个是:背景部分 ;另一个是数据部分
const dataBarWidthAngle = 2 * Math.PI * NODE_CONFIG.dataBarWidthAngle / 360; // 转化成弧度制
// 数据映射比例计算出来
let maxValue = Math.max(...appDetail.map(i=>(Number(i.count))));
// console.log('drawmaxValueing maxValue...', maxValue);
const dataRaduisScale = (NODE_CONFIG.dataSectionEndRaduis - NODE_CONFIG.dataSectionStartRaduis) / maxValue;
// appDetail
// const { projects }
// 拿到数据的最大值
for(let i = 0; i < NODE_CONFIG.sectionCount; i++ ){
// console.log('drawing data ...', i);
// 数据准备
const sectionCenterAngle = currentAngle + perSectionAngle / 2 ;
const realStartAngle = sectionCenterAngle - dataBarWidthAngle / 2;
const realEndAngle = sectionCenterAngle + dataBarWidthAngle / 2;
const thisStartAngle = currentAngle;
const thisEndAngle = currentAngle + perSectionAngle;
// 背景部分
const fanBg = group.addShape('fan', {
attrs: {
x: 0,
y: 0,
rs: NODE_CONFIG.dataSectionStartRaduis,
re: NODE_CONFIG.dataSectionEndRaduis,
startAngle: realStartAngle,
endAngle: realEndAngle,
clockwise: false,
// stroke: NODE_CONFIG.timeIndicatorColor,
fill: NODE_CONFIG.dataSectionColorBG,
opacity:NODE_CONFIG.dataSectionColorBGOpacity,
},
tag: ADD_TAG,
cursor: "pointer",
});
dataSectionBgArr.push(fanBg);
// 数据部分
// 数据映射
const odata = Number(appDetail[i].count);
const dataHeigth = odata * dataRaduisScale;
const dataRadusEnd = NODE_CONFIG.dataSectionStartRaduis + dataHeigth;
// console.log('odata', odata );
// console.log('dataHeigth', dataHeigth );
// console.log('dataRadusEnd', dataRadusEnd );
const fanData = group.addShape('fan', {
attrs: {
x: 0,
y: 0,
rs: NODE_CONFIG.dataSectionStartRaduis,
re: NODE_CONFIG.dataSectionStartRaduis,
startAngle: realStartAngle,
endAngle: realEndAngle,
clockwise: false,
// stroke: NODE_CONFIG.timeIndicatorColor,
fill: NODE_CONFIG.dataSectionColorData,
opacity:NODE_CONFIG.dataSectionColorDataOpacity,
},
tag: ADD_TAG,
cursor: "pointer",
});
fanData.animate({
re: dataRadusEnd,
repeat: false
}, NODE_CONFIG.dataSectionAnimateDelay);
// --------------------------不好用就注释
// 监听
const { time, count } = appDetail[i];
[fanData, fanBg].forEach(item=>{
item.on('mouseenter', function(evt) {
const fanTimer = timeIndicatorArr[i];
// 自身动画
fanTimer.animate({
rs: NODE_CONFIG.timeIndicatorstartRadius - NODE_CONFIG.timeIndicatorHoverOffset / 2,
re: NODE_CONFIG.timeIndicatorEndRadius + NODE_CONFIG.timeIndicatorHoverOffset / 2,
repeat: false
}, NODE_CONFIG.timeIndicatorHoverEnterAnimateDelay);
// 纵向指示器的变化
fanBg.animate({
startAngle: thisStartAngle,
endAngle: thisEndAngle ,
re: NODE_CONFIG.timeIndicatorstartRadius - NODE_CONFIG.timeIndicatorHoverOffset / 2,
repeat: false
}, NODE_CONFIG.timeIndicatorHoverEnterAnimateDelay);
// 提示文本
// 准备文本信息
// const { time, count } = appDetail[i];
const timeString = moment(Number(time)).format('YYYY-MM-DD HH:mm:ss');
const countString = `错误量 : ${count}`;
const textTimeX = NODE_CONFIG.timeTextRadius * Math.cos(sectionCenterAngle);
const textTimeY = NODE_CONFIG.timeTextRadius * Math.sin(sectionCenterAngle);
let textAlign = 'left';
if(textTimeX < 0){
textAlign = 'right';
}
// add 文本
// 用这种在on事件中addShape的方式比opacity改变的方式好在哪里
// 这样避免一个opacity为0但实际元素存在干扰操作的问题
group.addShape('text', {
attrs: {
x: textTimeX + NODE_CONFIG.timeTextOffsetX,
y: textTimeY + NODE_CONFIG.timeTextOffsetY,
text:`${timeString}\n${countString}`,
fontSize: NODE_CONFIG.timeTextFontSize,
fill: NODE_CONFIG.timeTextColor,
opacity:NODE_CONFIG.timeTextShowOpacity,
fontWeight: NODE_CONFIG.timeTextFontWeight,
textAlign: textAlign,
textBaseline: 'center',
// stroke: 'white',
},
tag:TOOLTIP_TAG,
});
});
item.on('mouseleave', function(evt) {
const fanTimer = timeIndicatorArr[i];
// 自身动画
fanTimer.animate({
rs: NODE_CONFIG.timeIndicatorstartRadius,
re: NODE_CONFIG.timeIndicatorEndRadius,
repeat: false
}, NODE_CONFIG.timeIndicatorHoverLeaveAnimateDelay);
// 径向指示动画
fanBg.animate({
startAngle: realStartAngle,
endAngle: realEndAngle ,
re: NODE_CONFIG.dataSectionEndRaduis ,
repeat: false
}, NODE_CONFIG.timeIndicatorHoverEnterAnimateDelay);
// 移除文字
const tooltips = group.findAll(item => {
return item._cfg.tag && item._cfg.tag === TOOLTIP_TAG;
});
tooltips.forEach(tooltip =>{
tooltip.remove();
})
});
})
// --------------------------不好用就注释 <--
dataSectionDataArr.push(fanData);
currentAngle += perSectionAngle;
}
// 绘制 时间指示器
for(let i = 0; i < NODE_CONFIG.sectionCount; i++ ){
const odata = appDetail[i];
// 数据准备
const bkGraphObj = dataSectionBgArr[i];
const dataGraphObj = dataSectionDataArr[i];
const thisStartAngle = currentAngle;
const thisEndAngle = currentAngle + perSectionAngle;
const sectionCenterAngle = currentAngle + perSectionAngle / 2 ;
const realStartAngle = sectionCenterAngle - dataBarWidthAngle / 2;
const realEndAngle = sectionCenterAngle + dataBarWidthAngle / 2;
// 小扇形
const fanTimer = group.addShape('fan', {
attrs: {
x: 0,
y: 0,
rs: NODE_CONFIG.timeIndicatorstartRadius,
re: NODE_CONFIG.timeIndicatorEndRadius,
startAngle: currentAngle,
endAngle: currentAngle ,
clockwise: false,
// stroke: NODE_CONFIG.timeIndicatorColor,
fill: NODE_CONFIG.timeIndicatorColor,
opacity:NODE_CONFIG.dataSectionColorDataOpacity,
},
tag: ADD_TAG,
cursor: "pointer",
});
// 小扇形的动画
fanTimer.animate({
endAngle: currentAngle + perSectionAngle,
repeat: false
}, NODE_CONFIG.timeIndicatorAnimateDelay);
// 监听交互事件
fanTimer.on('mouseenter', function(evt) {
// 自身动画
fanTimer.animate({
rs: NODE_CONFIG.timeIndicatorstartRadius - NODE_CONFIG.timeIndicatorHoverOffset / 2,
re: NODE_CONFIG.timeIndicatorEndRadius + NODE_CONFIG.timeIndicatorHoverOffset / 2,
repeat: false
}, NODE_CONFIG.timeIndicatorHoverEnterAnimateDelay);
// 纵向指示器的变化
bkGraphObj.animate({
startAngle: thisStartAngle,
endAngle: thisEndAngle ,
re: NODE_CONFIG.timeIndicatorstartRadius - NODE_CONFIG.timeIndicatorHoverOffset / 2,
repeat: false
}, NODE_CONFIG.timeIndicatorHoverEnterAnimateDelay);
// 提示文本
// 准备文本信息
const { time, count } = odata;
const timeString = moment(Number(time)).format('YYYY-MM-DD HH:mm:ss');
const countString = `错误量 : ${count}`;
const textTimeX = NODE_CONFIG.timeTextRadius * Math.cos(sectionCenterAngle);
const textTimeY = NODE_CONFIG.timeTextRadius * Math.sin(sectionCenterAngle);
let textAlign = 'left';
if(textTimeX < 0){
textAlign = 'right';
}
// add 文本
// 用这种在on事件中addShape的方式比opacity改变的方式好在哪里
// 这样避免一个opacity为0但实际元素存在干扰操作的问题
group.addShape('text', {
attrs: {
x: textTimeX + NODE_CONFIG.timeTextOffsetX,
y: textTimeY + NODE_CONFIG.timeTextOffsetY,
text:`${timeString}\n${countString}`,
fontSize: NODE_CONFIG.timeTextFontSize,
fill: NODE_CONFIG.timeTextColor,
opacity:NODE_CONFIG.timeTextShowOpacity,
fontWeight: NODE_CONFIG.timeTextFontWeight,
textAlign: textAlign,
textBaseline: 'center',
// stroke: 'white',
},
tag:TOOLTIP_TAG,
});
});
// 鼠标移开的事件监听
fanTimer.on('mouseleave', function(evt) {
// 自身动画
fanTimer.animate({
rs: NODE_CONFIG.timeIndicatorstartRadius,
re: NODE_CONFIG.timeIndicatorEndRadius,
repeat: false
}, NODE_CONFIG.timeIndicatorHoverLeaveAnimateDelay);
// 径向指示动画
bkGraphObj.animate({
startAngle: realStartAngle,
endAngle: realEndAngle ,
re: NODE_CONFIG.dataSectionEndRaduis ,
repeat: false
}, NODE_CONFIG.timeIndicatorHoverEnterAnimateDelay);
// 移除文字
const tooltips = group.findAll(item => {
return item._cfg.tag && item._cfg.tag === TOOLTIP_TAG;
});
tooltips.forEach(tooltip =>{
tooltip.remove();
})
});
// 图形引用入栈
timeIndicatorArr.push(fanTimer);
currentAngle += perSectionAngle;
}
// 绘制“关闭详情”提示文字
const closeText = "关闭扩展"
const closeBtn = group.addShape('text', {
attrs: {
x: NODE_CONFIG.closeBtnPositionX,
y: NODE_CONFIG.closeBtnPositionY,
text:closeText,
fontSize: NODE_CONFIG.closeBtnFontSize,
fill: NODE_CONFIG.closeBtnCommenColor,
opacity:NODE_CONFIG.closeBtnOpacity,
fontWeight: NODE_CONFIG.closeBtnCommenColor,
textAlign: 'center',
textBaseline: 'center',
cursor:"pointer",
// stroke: 'white',
},
tag:ADD_TAG,
});
closeBtn.on('click', ()=>{
// this.graphWapper.emit('close_detail', _cfg.id);
closeDetail();
});
closeBtn.on('mouseenter', ()=>{
closeBtn.animate({
fill: NODE_CONFIG.closeBtnHoverColor,
stroke: NODE_CONFIG.closeBtnHoverColor,
fontSize: NODE_CONFIG.closeBtnHoverFontSize,
repeat: false
}, NODE_CONFIG.hoverAnimateTime);
});
closeBtn.on('mouseleave', ()=>{
closeBtn.animate({
fill: NODE_CONFIG.closeBtnCommenColor,
fontSize: NODE_CONFIG.closeBtnFontSize,
stroke: null,
repeat: false
}, NODE_CONFIG.hoverAnimateTime);
});
// 绘制开始方向提示
const marker = group.addShape('marker', {
attrs: {
// x: NODE_CONFIG.makerStartPositionX,
// y: NODE_CONFIG.makerStartPositionY,
x:0,
y:0,
r: NODE_CONFIG.makerStartRaduis,
fill: NODE_CONFIG.makerStartColor,
symbol:'triangle',
opacity:NODE_CONFIG.makerStartOpacity
},
tag: ADD_TAG,
});
// 偏移和旋转这个marker
marker.rotate( - Math.PI / 6 )
marker.translate(NODE_CONFIG.makerStartPositionX, NODE_CONFIG.makerStartPositionY)
}
// 添加Focus的状态
const addFocus = (shape, item) => {
const group = shape.getParent();
const { _cfg } = item;
const foucsRing = group.addShape('circle', {
attrs: {
x: 0,
y: 0,
r: NODE_CONFIG.foucsRadusMax,
// fill: abnormal ? NODE_CONFIG.colorError: NODE_CONFIG.colorHealthy, // 2529e8
stroke:NODE_CONFIG.foucsColor,
lineWidth:NODE_CONFIG.foucsWidth,
opacity:NODE_CONFIG.foucsOpacity,
},
tag: ADD_TAG,
});
foucsRing.animate({
r: NODE_CONFIG.foucsRadusMin,
repeat: true,
}, NODE_CONFIG.foucsAnimateDelay);
};
}
// ================================
// 边样式注册
function registerEdgesStyle(){
if(!G6){
return;
}
const dashArray = EDGE_CONFIG.DASH.dashArray;
const lineDash = EDGE_CONFIG.DASH.lineDash;
const interval = EDGE_CONFIG.DASH.interval;
G6.registerEdge('ant-edge-emergency', {
draw: function draw(cfg, group) {
const startPoint = cfg.startPoint;
const endPoint = cfg.endPoint;
const centerPoint = {
x: startPoint.x + (endPoint.x - startPoint.x) / 2,
y: startPoint.y + (endPoint.y - startPoint.y) / 2
};
const controlPoint = {
x: startPoint.x,
y: (startPoint.y + centerPoint.y) / 2,
};
let specialArcOffsetX = EDGE_CONFIG.specialArcOffsetX;
const lineColor = cfg.abnormal ? EDGE_CONFIG.colorAbnoraml : EDGE_CONFIG.colorOk;
let path;
if(cfg.isSpecial){
if(endPoint.y < startPoint.y){
specialArcOffsetX *= (-1);
}
path = group.addShape("path", {
attrs: {
path: [["M", startPoint.x, startPoint.y], ["Q", centerPoint.x - specialArcOffsetX, centerPoint.y, endPoint.x , endPoint.y ], ],
stroke: lineColor,
lineWidth: EDGE_CONFIG.lineWidthCommon,
opacity:EDGE_CONFIG.opacityCommon,
endArrow: {
path: "M 4,0 L -4,-4 L -4,4 Z",
d: 5
}
}
});
} else {
path = group.addShape("path", {
attrs: {
path: [["M", startPoint.x, startPoint.y], ["Q", controlPoint.x , controlPoint.y, centerPoint.x, centerPoint.y], ["T", endPoint.x , endPoint.y ], ],
stroke: lineColor,
lineWidth: EDGE_CONFIG.lineWidthCommon,
opacity:EDGE_CONFIG.opacityCommon,
endArrow: {
path: "M 4,0 L -4,-4 L -4,4 Z",
d: 5
}
}
});
}
// 不正常的时候需要显示提示
if(cfg.abnormal){ //
let thePoint = {}; // 要显示的位置
if(cfg.isSpecial){
thePoint = {
x:centerPoint.x - specialArcOffsetX / 2,
y:centerPoint.y,
}
} else {
thePoint={
x: centerPoint.x,
y: centerPoint.y,
}
}
const itemBox = group.addShape('circle', {
attrs: {
x: thePoint.x,
y: thePoint.y,
r: EDGE_CONFIG.errorTipRadius,
fill: EDGE_CONFIG.errorTipBallColor, // 2529e8
opacity:EDGE_CONFIG.opacityCommon,
},
tag:BALL_TAG,
});
// 文本
const labelShape = group.addShape('text', {
attrs: {
x: thePoint.x,
y: thePoint.y + 2,
text: cfg.serviceList.length,
fontSize: EDGE_CONFIG.errorTipTextFontSize,
fill:EDGE_CONFIG.errorTipTextColor,
fontWeight: EDGE_CONFIG.errorTipTextFontWeight,
textAlign: EDGE_CONFIG.errorTipTextAlign,
textBaseline: EDGE_CONFIG.errorTipTextBaseline,
opacity:EDGE_CONFIG.opacityCommon,
// stroke: !isHealthy ? NODE_CONFIG.labalFontStrokeColorError : NODE_CONFIG.labalFontStrokeColorHealthy,
},
});
}
return path;
},
setState(name, value, item) {
const shape = item.get('keyShape');
const group = shape.getParent();
switch (name) {
case 'faded':
if (value) {
const opacity = EDGE_CONFIG.opacityFaded;
shape.attr('opacity', opacity);
const balls = group.findAll(item => {
return item._cfg.tag && item._cfg.tag === BALL_TAG;
});
balls.forEach(ball =>{
ball.attr('opacity', opacity);
})
}
else {
const opacity = EDGE_CONFIG.opacityCommon;
shape.attr('opacity', opacity);
const balls = group.findAll(item => {
return item._cfg.tag && item._cfg.tag === BALL_TAG;
});
balls.forEach(ball =>{
ball.attr('opacity', opacity);
})
}
break;
case 'hoverStyle':
const { _cfg } = item;
const { originStyle } = _cfg;
const { lineWidth } = originStyle;
const increaseWidth = EDGE_CONFIG.lineWidthHoverIncrease;
// console.log("_cfg", _cfg);
if (value) {
shape.attr({
lineWidth:lineWidth + increaseWidth,
opacity: EDGE_CONFIG.opacityHover,
})
}
else {
shape.attr({
lineWidth,
opacity: EDGE_CONFIG.opacityCommon,
})
}
break;
}
},
afterDraw(cfg, group) {
const shape = group.get('children')[0];
const startPoint = shape.getPoint(0);
const circle = group.addShape('circle', {
attrs: {
x: startPoint.x,
y: startPoint.y,
fill: cfg.abnormal?EDGE_CONFIG.flowingBallColorAbnormal:EDGE_CONFIG.flowingBallColorCommon,
r: 2
},
tag: BALL_TAG,
});
circle.animate({
onFrame(ratio) {
const tmpPoint = shape.getPoint(ratio);
return {
x: tmpPoint.x,
y: tmpPoint.y
};
},
repeat: true
}, EDGE_CONFIG.flowingTimeInterval);
const length = shape.getTotalLength(); // G 增加了 totalLength 的接口
let totalArray = [];
for (var i = 0; i < length; i += interval) {
totalArray = totalArray.concat(lineDash);
}
let index = 0;
shape.animate({
onFrame(ratio) {
const cfg = {
lineDash: dashArray[index].concat(totalArray)
};
index = (index + 1) % interval;
return cfg;
},
repeat: true
}, EDGE_CONFIG.flowingTimeInterval);
},
});
}
function focusOnNodePoint(id, zoomViewScale){
const { width, height } = window.graphSize;
const item = window.graph.findById(id);
const model = item.get('model');
const matrix = window.graph.get('group').getMatrix();
const x = model.x * matrix[0] + matrix[6];
const y = model.y * matrix[4] + matrix[7];
window.graph.translate(width / 2 - x, height / 2 - y);
if(zoomViewScale){
zoomByCenter(zoomViewScale);
}
}
function zoomByCenter(scale){
const { width, height } = this.graphSize;
const centerPosition = {x: width / 2, y: height / 2}
window.graph.zoom(scale / window.graph.getZoom(), centerPosition);
}
function fadeAllButOne(besideNodeId){
window.graph.getNodes().forEach(node =>{
const nodeid = node._cfg.id;
if(besideNodeId !== nodeid){
window.graph.setItemState(nodeid, 'faded', true);
}
});
window.graph.getEdges().forEach(edge =>{
const edgeid = edge._cfg.id;
window.graph.setItemState(edgeid, 'faded', true);
})
}
function showInitialGraph(){
window.graph.getNodes().forEach(node=>{
const nodeid = node._cfg.id;
window.graph.setItemState(nodeid, 'faded', false);
});
window.graph.getEdges().forEach(edge=>{
const edgeid = edge._cfg.id;
window.graph.showItem(edgeid);
window.graph.setItemState(edgeid, 'faded', false);
});
}
// 分析按钮的监听回调
document.getElementById('btn_error').addEventListener("click", ()=>{
// 模拟系统详情
document.getElementById('infoDetail').innerHTML = `
<div style="color: white;">
<p>source_system</p>
<hr >
<p>系统ownercjj</p>
<p>系统等级Level High</p>
<hr >
<p>问题原因是XXX</p>
<p>最近趋势图YYY</p>
<p>解决方案是ZZZ</p>
</div>
`;
// 聚焦到节点
fadeAllButOne("source_system");
focusOnNodePoint("source_system", 2);
window.graph.setItemState("source_system", 'detailing', true);
});
function closeDetail(){
window.graph.setItemState("source_system", 'detailing', false);
showInitialGraph();
document.getElementById('infoDetail').innerHTML = '';
}
// =========================
// 时间轴部分
const timeMockData = [
{time: "23:07", value: 0, date: "2019-10-14", timeStamp: 1571065620000},
{time: "23:08", value: 0, date: "2019-10-14", timeStamp: 1571065680000},
{time: "23:09", value: 0, date: "2019-10-14", timeStamp: 1571065740000},
{time: "23:10", value: 0, date: "2019-10-14", timeStamp: 1571065800000},
{time: "23:11", value: 0, date: "2019-10-14", timeStamp: 1571065860000},
{time: "23:12", value: 0, date: "2019-10-14", timeStamp: 1571065920000},
{time: "23:13", value: 0, date: "2019-10-14", timeStamp: 1571065980000},
{time: "23:14", value: 0, date: "2019-10-14", timeStamp: 1571066040000},
{time: "23:15", value: 0, date: "2019-10-14", timeStamp: 1571066100000},
{time: "23:16", value: 0, date: "2019-10-14", timeStamp: 1571066160000},
{time: "23:17", value: 0, date: "2019-10-14", timeStamp: 1571066220000},
{time: "23:18", value: 0, date: "2019-10-14", timeStamp: 1571066280000},
{time: "23:19", value: 0, date: "2019-10-14", timeStamp: 1571066340000},
{time: "23:20", value: 0, date: "2019-10-14", timeStamp: 1571066400000},
{time: "23:21", value: 0, date: "2019-10-14", timeStamp: 1571066460000},
{time: "23:22", value: 0, date: "2019-10-14", timeStamp: 1571066520000},
{time: "23:23", value: 0, date: "2019-10-14", timeStamp: 1571066580000},
{time: "23:24", value: 0, date: "2019-10-14", timeStamp: 1571066640000},
{time: "23:25", value: 0, date: "2019-10-14", timeStamp: 1571066700000},
{time: "23:26", value: 0, date: "2019-10-14", timeStamp: 1571066760000},
{time: "23:27", value: 0, date: "2019-10-14", timeStamp: 1571066820000},
{time: "23:28", value: 0, date: "2019-10-14", timeStamp: 1571066880000},
{time: "23:29", value: 0, date: "2019-10-14", timeStamp: 1571066940000},
{time: "23:30", value: 0, date: "2019-10-14", timeStamp: 1571067000000},
{time: "23:31", value: 0, date: "2019-10-14", timeStamp: 1571067060000},
{time: "23:32", value: 0, date: "2019-10-14", timeStamp: 1571067120000},
{time: "23:33", value: 0, date: "2019-10-14", timeStamp: 1571067180000},
{time: "23:34", value: 0, date: "2019-10-14", timeStamp: 1571067240000},
{time: "23:35", value: 0, date: "2019-10-14", timeStamp: 1571067300000},
{time: "23:36", value: 0, date: "2019-10-14", timeStamp: 1571067360000},
{time: "23:37", value: 2, date: "2019-10-14", timeStamp: 1571067420000},
{time: "23:38", value: 11, date: "2019-10-14", timeStamp: 1571067480000},
{time: "23:39", value: 11, date: "2019-10-14", timeStamp: 1571067540000},
{time: "23:40", value: 11, date: "2019-10-14", timeStamp: 1571067600000},
{time: "23:41", value: 10, date: "2019-10-14", timeStamp: 1571067660000},
{time: "23:42", value: 0, date: "2019-10-14", timeStamp: 1571067720000},
{time: "23:43", value: 0, date: "2019-10-14", timeStamp: 1571067780000},
{time: "23:44", value: 0, date: "2019-10-14", timeStamp: 1571067840000},
{time: "23:45", value: 0, date: "2019-10-14", timeStamp: 1571067900000},
{time: "23:46", value: 0, date: "2019-10-14", timeStamp: 1571067960000},
{time: "23:47", value: 0, date: "2019-10-14", timeStamp: 1571068020000},
{time: "23:48", value: 0, date: "2019-10-14", timeStamp: 1571068080000},
{time: "23:49", value: 0, date: "2019-10-14", timeStamp: 1571068140000},
{time: "23:50", value: 0, date: "2019-10-14", timeStamp: 1571068200000},
{time: "23:51", value: 0, date: "2019-10-14", timeStamp: 1571068260000},
{time: "23:52", value: 0, date: "2019-10-14", timeStamp: 1571068320000},
{time: "23:53", value: 5, date: "2019-10-14", timeStamp: 1571068380000},
{time: "23:54", value: 5, date: "2019-10-14", timeStamp: 1571068440000},
{time: "23:55", value: 0, date: "2019-10-14", timeStamp: 1571068500000},
{time: "23:56", value: 0, date: "2019-10-14", timeStamp: 1571068560000},
{time: "23:57", value: 0, date: "2019-10-14", timeStamp: 1571068620000},
{time: "23:58", value: 0, date: "2019-10-14", timeStamp: 1571068680000},
{time: "23:59", value: 0, date: "2019-10-14", timeStamp: 1571068740000},
{time: "00:00", value: 0, date: "2019-10-15", timeStamp: 1571068800000},
{time: "00:01", value: 0, date: "2019-10-15", timeStamp: 1571068860000},
{time: "00:02", value: 0, date: "2019-10-15", timeStamp: 1571068920000},
{time: "00:03", value: 0, date: "2019-10-15", timeStamp: 1571068980000},
{time: "00:04", value: 0, date: "2019-10-15", timeStamp: 1571069040000},
{time: "00:05", value: 0, date: "2019-10-15", timeStamp: 1571069100000},
{time: "00:06", value: 0, date: "2019-10-15", timeStamp: 1571069160000},
{time: "00:07", value: 0, date: "2019-10-15", timeStamp: 1571069220000},
];
const TIME_CONTROLLER_CONFIG = {
// 模块整体宽度
width:640,
// 模块整体高
height:64,
// padding 边距
padding:{
top:4,
bottom:8,
left:8,
right:8,
},
// 控制按钮大小
controlButtonR:10,
// 时间轴轴线宽
axisLineWidth:2,
// 柱子区域的水平间距
barSectionPaddingH:8 ,
// 柱子间距
barPadding:2,
// icon 线的水平位置偏移
iconXOffset:2,
// icon 线的纵偏移
iconYOffset:5,
// icon 的线
iconStrokeWidth:1,
// 各种颜色定义
COLORS:{
// x轴
axis:"#597EF7",
// 控制按钮
controlBtn:"#69C0FF",
// 按钮上icon的颜色
iconInBtn:"#080C16",
}
};
const {
width,
height,
padding,
controlButtonR,
axisLineWidth,
barSectionPaddingH,
barPadding,
iconXOffset,
iconYOffset,
iconStrokeWidth,
COLORS,
} = TIME_CONTROLLER_CONFIG;
// 时间轴控制按钮位置计算
const leftControlButtonPosition = {
x: padding.left + controlButtonR,
y: height - padding.bottom - controlButtonR
};
const rightControlButtonPosition = {
x: width - padding.right - controlButtonR,
y: height - padding.bottom - controlButtonR
};
const barSectionHeight = height - padding.top - padding.bottom - axisLineWidth - controlButtonR;
const barSectionWidth = width - padding.left - padding.right - controlButtonR * 4 - barSectionPaddingH * 2;
// svg
const svg = d3.select('#timeControllerContainer')
.append("svg")
.attr("height", height)
.attr("width", width);
window.svg = svg;
// 画轴
svg.append('line')
.attr('x1', leftControlButtonPosition.x )
.attr('y1', leftControlButtonPosition.y )
.attr('x2', rightControlButtonPosition.x )
.attr('y2', rightControlButtonPosition.y )
.attr('stroke-width', axisLineWidth)
.attr('stroke', COLORS.axis);
// 控制器left
const leftControlG = svg.append('g');
leftControlG.append('circle').attr('id', 'circleBtn')
.attr('r', controlButtonR)
.attr('cx', leftControlButtonPosition.x)
.attr('cy', leftControlButtonPosition.y)
.attr('fill', COLORS.controlBtn)
.style("cursor", "pointer");
leftControlG.append('line')
.attr('x1', leftControlButtonPosition.x + iconXOffset )
.attr('y1', leftControlButtonPosition.y - iconYOffset )
.attr('x2', leftControlButtonPosition.x - iconXOffset )
.attr('y2', leftControlButtonPosition.y )
.attr('stroke-width', iconStrokeWidth)
.attr('stroke', COLORS.iconInBtn)
.style("cursor", "pointer");
leftControlG.append('line')
.attr('x1', leftControlButtonPosition.x - iconXOffset )
.attr('y1', leftControlButtonPosition.y )
.attr('x2', leftControlButtonPosition.x + iconXOffset )
.attr('y2', leftControlButtonPosition.y + iconYOffset )
.attr('stroke-width', iconStrokeWidth)
.attr('stroke', COLORS.iconInBtn)
.style("cursor", "pointer");
// 左边button的点击回调
leftControlG.on('click', function(d){
// console.log('d3.select(this)', d3.select('#circleBtn').transition())
// d3.select(this)
// .select('#circleBtn')
// .transition() //要实现单元素连续动画就直接加在后面
// // .duration(100)
// .attr('r', controlButtonR + 2)
// .transition() //要实现单元素连续动画就直接加在后面
// .duration(100)
// .attr('r', controlButtonR );
// 点击回调
handleClickBtn('back');
});
// 控制器right
const rightControlG = svg.append('g');
rightControlG.append('circle')
.attr('r', controlButtonR)
.attr('cx', rightControlButtonPosition.x)
.attr('cy', rightControlButtonPosition.y)
.attr('fill', COLORS.controlBtn)
.style("cursor", "pointer");
rightControlG.append('line')
.attr('x1', rightControlButtonPosition.x - iconXOffset )
.attr('y1', rightControlButtonPosition.y - iconYOffset )
.attr('x2', rightControlButtonPosition.x + iconXOffset )
.attr('y2', rightControlButtonPosition.y )
.attr('stroke-width', iconStrokeWidth)
.attr('stroke', COLORS.iconInBtn)
.style("cursor", "pointer");
rightControlG.append('line')
.attr('x1', rightControlButtonPosition.x + iconXOffset )
.attr('y1', rightControlButtonPosition.y )
.attr('x2', rightControlButtonPosition.x - iconXOffset )
.attr('y2', rightControlButtonPosition.y + iconYOffset )
.attr('stroke-width', iconStrokeWidth)
.attr('stroke', COLORS.iconInBtn)
.style("cursor", "pointer");
// 右边button的点击回调
rightControlG.on('click', (d)=>{
// 点击回调
handleClickBtn('front');
});
drawData( timeMockData );
function drawData( data ){
const lastIndex = data.length - 1;
const svg = window.svg;
const yScale = barSectionHeight / d3.max(data.map(i=>i.value))
const barsGroup = svg.append("g").attr("id", "oneBarGroup");
const oneBarGroup = barsGroup.selectAll("rect")
.data(data)
.enter()
.append("g");
// 背景
oneBarGroup.append("rect")
.attr('id', 'rect_bg')
.attr("x", (d, i)=>{
return i * (barSectionWidth / data.length) + padding.left + controlButtonR * 2 + barSectionPaddingH;
})
.attr("y", (d, i)=>{
return padding.top
})
.attr("width", barSectionWidth / data.length - barPadding )
.attr("height", barSectionHeight)
.attr("fill", "#597EF7")
.attr("opacity", 0.2)
.attr("stroke", "white")
.attr("stroke-width", (d, i) =>{
return i === lastIndex ? 2 : 0;
});
// bar
oneBarGroup.append("rect")
.attr('id', 'rect')
.attr("x", (d, i)=>{
return i * (barSectionWidth / data.length) + padding.left + controlButtonR * 2 + barSectionPaddingH;
})
.attr("y", (d, i)=>{
const maybeValue = barSectionHeight - d.value * yScale + padding.top;
const yValue = isNaN(maybeValue)? 0 : maybeValue;
return yValue;
})
.attr("width", barSectionWidth / data.length - barPadding )
.attr("height", (d, i)=>{
const maybeValue = d.value * yScale ;
const yValue = isNaN(maybeValue)? 0 : maybeValue;
return yValue;
})
.attr("fill", "#69C0FF")
.attr("opacity", 0.75)
.attr("stroke", "white")
.attr("stroke-width", (d, i) =>{
return i === lastIndex ? 2 : 0;
});
// 添加event
oneBarGroup
.style("cursor", "pointer")
.on('click', function(d){
let selectIdTag = '#rect';
if(d.value === 0){
selectIdTag = '#rect_bg';
}
// 更新选择状态
d3.selectAll('#rect_bg')
.style("stroke-width", 0);
d3.selectAll('#rect')
.style("stroke-width", 0);
//
const selectRect = d3.select(this).select(selectIdTag);
selectRect
.style("stroke-width", 2);
//
clickRectQuery(d);
})
.on("mouseover", function(d) {
const selectRect = d3.select(this).select('#rect');
selectRect.style("opacity", 1);
// console.log(d3.select(this).select('#rect').nodes())
// console.log(d3.event)
const { offsetX, offsetY } = d3.event;
setTooltipState({
isTooltipVisable:true,
toolTipData:selectRect.nodes()[0].__data__,
relativePosition:{
x:offsetX,
y:offsetY,
},
});
})
.on("mouseout", function(d) {
d3.select(this).select('#rect').style("opacity", 0.75);
setTooltipState({
isTooltipVisable:false,
});
});
// text
const textAxisGroup = svg.append('g').attr("id", "textAxisGroup");
textAxisGroup.selectAll("rect")
.data(data)
.enter()
.append("g")
.append("text")
.attr("fill","white")
.attr("font-size","12px")
.attr("text-anchor","middle")
.attr("x",function(d,i){ //与矩形的X坐标一样
return i * (barSectionWidth / data.length) + padding.left + controlButtonR * 2 + barSectionPaddingH + 8
})
.attr("y",function(d){
return barSectionHeight + 20 + padding.top
})
.text(function(d, i){ //要显示的文字内容
if(i % 6 === 0){
return d.time
}
return '';
});
// mouse enter的回调
const setTooltipState = ({isTooltipVisable, toolTipData, relativePosition})=>{
};
const clickRectQuery = (d) =>{
updateGraph(mockGraphData2.data);
}
}
</script>
</body>
</html>