Created
March 9, 2018 10:52
-
-
Save Zhenmao/052247612a41f84535b83dad5b7bc399 to your computer and use it in GitHub Desktop.
Datacenter simulation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title></title> | |
<link href="https://fonts.googleapis.com/css?family=Orbitron" rel="stylesheet"> | |
<link rel="stylesheet" type="text/css" href="styles.css"> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
</head> | |
<body> | |
<div id="chart"></div> | |
<script src="index.js"></script> | |
<script> | |
(function () { | |
show("sampledata.json"); | |
})(); | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function show(dataURL) { | |
const margin = { top: 40, right: 40, bottom: 40, left: 40 }, | |
width = 1200, | |
nodeHeight = 120, | |
nodeRadius = 25, | |
vmNodeHeight = 8, | |
vmNodeRadius = 3, | |
colorBackground = '#0C0C0C' | |
colorMute = '#81FFFE', | |
colorError = '#F07053', | |
colorHeighlight = '#FAE84C', | |
duration = 300; | |
// Hard code types and their positions in case some of types are not present in the dataset | |
const types = { | |
'FIREWALL': '1', 'ROUTER': '2', 'SWITCH': '3', 'LB': '4', 'HV': '5', 'VM': '6' | |
}; | |
const x = d3.scaleBand() | |
.domain(Object.keys(types)) | |
.range([0, width]); | |
const flow = d3.scaleLinear() | |
.domain([0, 100]) | |
.range([0, 10]); | |
let node, link, label; | |
d3.json(dataURL, (error, data) => { | |
if (error) throw error; | |
const nodes = data.nodes; | |
const links = data.links; | |
// Compute number of nodes for each type | |
const numNodesbyType = d3.nest() | |
.key(d => d.hierarchy[0]) | |
.rollup(d => d.length) | |
.entries(nodes); | |
const height = Math.max( | |
d3.max(numNodesbyType.filter(d => d.key !== '6'), d => d.value * nodeHeight), | |
numNodesbyType.find(d => d.key === '6').value * vmNodeHeight | |
); | |
// Nest nodes by type, and assign it x, y position | |
const nestedNodes = d3.nest() | |
.key(d => d.type) | |
.entries(nodes); | |
nestedNodes.forEach(type => { | |
const numNodesOfType = type.values.length; | |
type.values.forEach((node, i) => { | |
node.x = x(node.type) + x.bandwidth() / 2; | |
if (node.type !== 'VM') { | |
node.y = height / 2 + (i - numNodesOfType / 2) * nodeHeight; | |
} else { | |
node.y = height / 2 + (i - numNodesOfType / 2) * vmNodeHeight; | |
}; | |
// Add long id for each node (id used in links) | |
node.longID = `${node.id}-${types[node.type]}`; | |
}); | |
}); | |
// Add nodes to links | |
links.forEach(link => { | |
link.sourceNode = nodes.find(node => node.longID === link.source); | |
link.targetNode = nodes.find(node => node.longID === link.target); | |
}); | |
// Set up svg containers | |
const g = d3.select('#chart').append('svg') | |
.attr('width', width + margin.left + margin.right) | |
.attr('height', height + margin.top + margin.bottom) | |
.append('g') | |
.attr('transform', `translate(${margin.left},${margin.top})`); | |
// Add node circles | |
node = g.append('g') | |
.attr('class', 'nodes') | |
.selectAll('circle') | |
.data(nodes) | |
.enter().append('circle') | |
.attr('class', 'node-cirlce') | |
.attr('cx', d => d.x) | |
.attr('cy', d => d.y) | |
.attr('r', d => d.type === 'VM' ? vmNodeRadius : nodeRadius) | |
.style('stroke', d => d.status && d.status === 'error' ? colorError : colorMute) | |
.style('fill', colorBackground) | |
.on('mouseover', highlight) | |
.on('mouseout', mute); | |
// Add node labels | |
label = g.append('g') | |
.attr('class', 'node-labels') | |
.selectAll('text') | |
.data(nodes) | |
.enter().append('text') | |
.attr('class', 'node-label') | |
.attr('x', d => d.x) | |
.attr('y', d => d.y + nodeRadius) | |
.attr('dy', '1.5em') | |
.style('text-anchor', 'middle') | |
.style('fill', d => d.status && d.status === 'error' ? colorError : colorMute) | |
.style('display', d => d.type === 'VM' ? 'none' : null) | |
.text(d => `${d.type}-${d.id}-${d.hierarchy[0]}`); | |
// Add links | |
link = g.insert('g', '.nodes') | |
.attr('class', 'links') | |
.selectAll('line') | |
.data(links) | |
.enter().append('line') | |
.attr('class', 'link') | |
.attr('x1', d => d.sourceNode.x) | |
.attr('y1', d => d.sourceNode.y) | |
.attr('x2', d => d.targetNode.x) | |
.attr('y2', d => d.targetNode.y) | |
.style('stroke', colorMute) | |
.style('stroke-width', d => flow(d.value)) | |
.style('display', d => d.targetNode.type === 'VM' ? 'none' : null); | |
}); | |
function highlight(d) { | |
const connectedNodesIDs = [d.longID]; | |
link | |
.style('opacity', 0) | |
.filter(link => { | |
if (link.source === d.longID) { | |
connectedNodesIDs.push(link.target); | |
return true; | |
} else if (link.target === d.longID) { | |
connectedNodesIDs.push(link.source); | |
return true; | |
} | |
return false; | |
}) | |
.style('opacity', 1) | |
.transition() | |
.duration(duration) | |
.style('stroke', colorHeighlight) | |
.style('display', null); | |
node | |
.style('opacity', 0) | |
.filter(node => connectedNodesIDs.indexOf(node.longID) !== -1) | |
.style('opacity', 1) | |
.transition() | |
.duration(duration) | |
.style('stroke', d => d.status && d.status === 'error' ? colorError : colorHeighlight) | |
.attr('r', nodeRadius); | |
label | |
.style('opacity', 0) | |
.filter(label => connectedNodesIDs.indexOf(label.longID) !== -1) | |
.style('opacity', 1) | |
.transition() | |
.duration(duration) | |
.style('fill', d => d.status && d.status === 'error' ? colorError : colorHeighlight) | |
.style('display', null); | |
} | |
function mute(d) { | |
link | |
.style('opacity', 1) | |
.transition() | |
.duration(duration) | |
.style('stroke', colorMute) | |
.style('display', e => e.targetNode.type === 'VM' ? 'none' : null); | |
node | |
.style('opacity', 1) | |
.transition() | |
.duration(duration) | |
.style('stroke', e => e.status && e.status === 'error' ? colorError : colorMute) | |
.attr('r', e => e.type === 'VM' ? vmNodeRadius : nodeRadius); | |
label | |
.style('opacity', 1) | |
.transition() | |
.duration(duration) | |
.style('fill', d => d.status && d.status === 'error' ? colorError : colorMute) | |
.style('display', e => e.type === 'VM' ? 'none' : null); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{"nodes":[{"type":"FIREWALL","id":"1","hierarchy":[1]},{"type":"FIREWALL","id":"2","hierarchy":[1]},{"type":"ROUTER","id":"3","hierarchy":[2]},{"type":"ROUTER","id":"4","hierarchy":[2]},{"type":"ROUTER","id":"5","hierarchy":[2]},{"type":"ROUTER","id":"6","hierarchy":[2]},{"type":"ROUTER","id":"7","hierarchy":[2]},{"type":"SWITCH","id":"35","hierarchy":[3]},{"type":"SWITCH","id":"36","hierarchy":[3]},{"type":"LB","id":"37","hierarchy":[4], "status":"error"}, {"type":"HV","id":"38","hierarchy":[5]}, {"type":"VM","id":"8","hierarchy":[6]},{"type":"VM","id":"9","hierarchy":[6]},{"type":"VM","id":"10","hierarchy":[6]},{"type":"VM","id":"11","hierarchy":[6]},{"type":"VM","id":"12","hierarchy":[6]},{"type":"VM","id":"13","hierarchy":[6]},{"type":"VM","id":"14","hierarchy":[6]},{"type":"VM","id":"15","hierarchy":[6]},{"type":"VM","id":"16","hierarchy":[6]},{"type":"VM","id":"17","hierarchy":[6]},{"type":"VM","id":"18","hierarchy":[6]},{"type":"VM","id":"19","hierarchy":[6], "status": "error"},{"type":"VM","id":"20","hierarchy":[6]},{"type":"VM","id":"21","hierarchy":[6]},{"type":"VM","id":"22","hierarchy":[6]},{"type":"VM","id":"23","hierarchy":[6]},{"type":"VM","id":"24","hierarchy":[6]},{"type":"VM","id":"25","hierarchy":[6]},{"type":"VM","id":"26","hierarchy":[6]},{"type":"VM","id":"27","hierarchy":[6], "status": "error"},{"type":"VM","id":"28","hierarchy":[6]},{"type":"VM","id":"29","hierarchy":[6]},{"type":"VM","id":"30","hierarchy":[6]},{"type":"VM","id":"31","hierarchy":[6]},{"type":"VM","id":"32","hierarchy":[6]},{"type":"VM","id":"33","hierarchy":[6]}],"links":[{"source":"1-1","target":"5-2", "value": 10},{"source":"1-1","target":"4-2", "value": 25},{"source":"1-1","target":"6-2", "value": 15},{"source":"2-1","target":"3-2", "value": 20},{"source":"2-1","target":"7-2", "value": 35},{"source":"3-2","target":"16-6", "value": 5},{"source":"3-2","target":"36-3", "value": 15},{"source":"3-2","target":"25-6", "value": 25},{"source":"4-2","target":"35-3", "value": 50},{"source":"6-2","target":"21-6", "value": 25},{"source":"6-2","target":"30-6", "value": 35},{"source":"7-2","target":"28-6", "value": 10}]} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
:root { | |
--color-background: #0C0C0C; | |
} | |
body { | |
background-color: var(--color-background); | |
font-family: 'Orbitron', sans-serif; | |
font-size: 10px; | |
} | |
.node-label { | |
text-shadow: 1px 1px var(--color-background), 1px -1px var(--color-background), -1px 1px var(--color-background), -1px -1px var(--color-background); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment