Skip to content

Instantly share code, notes, and snippets.

@Zhenmao
Created March 9, 2018 10:52
Show Gist options
  • Save Zhenmao/052247612a41f84535b83dad5b7bc399 to your computer and use it in GitHub Desktop.
Save Zhenmao/052247612a41f84535b83dad5b7bc399 to your computer and use it in GitHub Desktop.
Datacenter simulation
<!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>
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);
}
}
{"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}]}
: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