Add stack chart, line chart, circle packing and tree

Change-Id: I20092ff35377b4005644c69726d63e501ac9b310
This commit is contained in:
jiahuay 2014-08-22 10:50:40 -07:00
parent 5f6e0118b0
commit 713efa67a3
13 changed files with 664 additions and 5 deletions

59
v2/assets/css/chart.css Normal file
View File

@ -0,0 +1,59 @@
/* circle packing */
text {
font-size: 14px !important;
font-family: "Arial, Helvetica, sans-serif";
pointer-events: none;
}
text.parent {
fill: #1f77b4;
}
circle {
fill: #ccc;
stroke: #999;
fill-opacity: .8;
pointer-events: all;
}
circle.parent {
fill: #1f77b4;
fill-opacity: .3;
stroke: steelblue;
}
circle.parent:hover {
stroke: #ff7f0e;
stroke-width: 5px;
}
circle.child {
pointer-events: none;
}
circle[depth='0'] {
fill: #428bca;
}
circle[depth='1'] {
fill: #ffb752;
}
circle[depth='2'] {
fill: #d6487e;
}
circle[depth='3'] {
fill: #fee188;
}
circle[depth='4'] {
fill: #9585bf;
}
/* tree */
.node circle {
cursor: pointer;
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node text {
font-size: 11px;
}
path.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}

BIN
v2/assets/img/router.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
v2/assets/img/server.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
v2/assets/img/switch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1,4 +1,10 @@
[{
"name": "cluster.monitoring.overview",
"url": "/overview",
"display": "Overview",
"controller": "",
"templateUrl": ""
}, {
"name": "cluster.monitoring.hostgroups",
"url": "/hostgroups",
"display": "Host Groups",

View File

@ -8,11 +8,13 @@
<link rel="stylesheet" type="text/css" href="vendor/bootstrap/css/bootstrap.css">
<link rel="stylesheet" type="text/css" href="vendor/font-awesome/css/font-awesome.min.css" />
<link rel="stylesheet" type="text/css" href="vendor/rickshaw/rickshaw.min.css" />
<link rel="stylesheet" type="text/css" href="vendor/nvd3/nv.d3.min.css">
<link rel="stylesheet" type="text/css" href="assets/css/ace.min.css">
<link rel="stylesheet" type="text/css" href="assets/css/ace-skins.min.css">
<link rel="stylesheet" type="text/css" href="assets/css/ace-fonts.css" />
<link rel="stylesheet" type="text/css" href="assets/css/style.css">
<link rel="stylesheet" type="text/css" href="assets/css/chart.css">
<script type="text/javascript" src="vendor/jquery/jquery-1.11.1.min.js"></script>
<script type="text/javascript" src="vendor/bootstrap/js/bootstrap.min.js"></script>
@ -29,13 +31,15 @@
<script type="text/javascript" src="vendor/angular-dragdrop/draganddrop.js"></script>
<script type="text/javascript" src="vendor/ng-table/ng-table.min.js"></script>
<script type="text/javascript" src="vendor/d3/d3.min.js"></script>
<script type="text/javascript" src="vendor/nvd3/nv.d3.min.js"></script>
<script type="text/javascript" src="vendor/angular-nvd3/angularjs-nvd3-directives.min.js"></script>
<script type="text/javascript" src="vendor/rickshaw/rickshaw.min.js"></script>
<script type="text/javascript" src="vendor/angular-rickshaw/rickshaw.min.js"></script>
<script type="text/javascript" src="vendor/angular-spinner/spin.min.js"></script>
<script type="text/javascript" src="vendor/angular-spinner/angular-spinner.min.js"></script>
<script type="text/javascript" src="src/app/app.js"></script>
<script type="text/javascript" src="src/app/appDev.js"></script>
<script type="text/javascript" src="src/app/login/login.js"></script>

View File

@ -1,3 +1,7 @@
charts
<rickshaw rickshaw-options="options2" rickshaw-features="features2" rickshaw-series="series2">
</rickshaw>
<div class="page-header">
<h1>
Charts
</h1>
</div>
<div class="row side-padding-10 padding-top-30">
</div>

View File

@ -3,7 +3,8 @@ angular.module('compass.monitoring', [
'ui.bootstrap',
'compass.charts',
'ngAnimate',
'angular-rickshaw'
'angular-rickshaw',
'nvd3ChartDirectives'
])
.config(function($stateProvider, $urlRouterProvider) {
@ -124,3 +125,190 @@ angular.module('compass.monitoring', [
}];
}
])
.controller('moniOverviewCtrl', function($scope) {
$scope.logicalTopoData = {
"name": "cluster1",
"children": [{
"name": "compute",
"state": "error",
"children": [{
"name": "host1",
"state": "error",
"children": [{
"name": "service1"
}, {
"name": "service2"
}, {
"name": "service3"
}, {
"name": "service4"
}]
}, {
"name": "host2",
"state": "error",
"children": [{
"name": "service1"
}, {
"name": "service2"
}]
}, {
"name": "host3",
"state": "error",
"children": [{
"name": "service1",
"children": [{
"name": "metric1"
}, {
"name": "metric2"
}]
}]
}]
}, {
"name": "storage",
"state": "ok",
"children": [{
"name": "host7",
"state": "ok",
"children": [{
"name": "service5",
"state": "ok",
"children": [{
"name": "metric1"
}, {
"name": "metric2"
}]
}, {
"name": "service6",
"state": "ok",
"children": [{
"name": "metric1"
}, {
"name": "metric2"
}]
}]
}, {
"name": "host8",
"state": "ok",
"children": [{
"name": "service7",
"state": "ok",
"children": [{
"name": "metric1"
}]
}, {
"name": "service8"
}]
}]
}, {
"name": "network",
"state": "warning",
"children": [{
"name": "host10",
"state": "warning",
"children": []
}, {
"name": "host11",
"state": "warning",
"children": []
}]
}, {
"name": "controller",
"state": "warning",
"children": [{
"name": "host12",
"state": "warning",
"children": []
}, {
"name": "host13",
"state": "warning",
"children": []
}]
}, {
"name": "database",
"state": "warning",
"children": [{
"name": "host14",
"children": []
}, {
"name": "host15",
"children": []
}]
}, {
"name": "image",
"state": "ok",
"children": [{
"name": "host16",
"children": []
}, {
"name": "host17",
"children": []
}]
}]
};
$scope.physicalTopoData = angular.copy($scope.logicalTopoData);
$scope.exampleData = [{
"key": "Series 1",
"values": [
[1, 0],
[2, 6],
[3, 5],
[4, 11],
[5, 5]
]
}, {
"key": "Series 2",
"values": [
[1, 0],
[2, 10],
[3, 5],
[4, 5],
[5, 0]
]
}, {
"key": "Series 3",
"values": [
[1, 0],
[2, 6],
[3, 5],
[4, 11],
[5, 5]
]
}, {
"key": "Series 4",
"values": [
[1, 7],
[2, 14],
[3, 14],
[4, 23],
[5, 16]
]
}];
$scope.xAxisTickFormat = function() {
return function(d) {
return d3.time.format('%x')(new Date(d));
}
};
$scope.toolTipContentFunction = function() {
return function(key, x, y, e, graph) {
console.log('tooltip content');
return 'Super New Tooltip' +
'<h1>' + key + '</h1>' +
'<p>' + y + ' at ' + x + '</p>'
}
};
/* // customize stack/line chart colors
$scope.colorFunction = function() {
var colors = ["#68bc31", "#2091cf", "#6fb3e0", "#fee074", "#f89406", "#af4e96"];
return function(d, i) {
return colors[i%6];
};
}
*/
})

View File

@ -0,0 +1,61 @@
<div class="page-header">
<h1>
Monitoring Overview
</h1>
</div>
<div class="row side-padding-10 top-padding-10" ng-controller="moniOverviewCtrl">
<div class="col-xs-12">
<div ng-repeat="role in logicalTopoData.children" class="well pull-left">
<span>{{role.name}}</span>
<span>{{role.children.length}}</span>
<span>{{role.state}}</span>
</div>
</div>
<div class="col-xs-12 top-padding-10" style="height: 300px">
<nvd3-stacked-area-chart
data="exampleData"
id="exampleId"
showXAxis="true"
showYAxis="true"
tooltips="true"
interactive="true"
useInteractiveGuideline="true"
toolTipContent="toolTipContentFunction()"
forceY="[0]">
<svg></svg>
</nvd3-stacked-area-chart>
</div>
<div class="col-xs-12 top-padding-10">
<div class="row">
<div class="col-lg-7">
<circlepacking data="logicalTopoData"></circlepacking>
</div>
<div class="col-lg-5">
<nvd3-line-chart
data="exampleData"
id="exampleId2"
showXAxis="true"
showYAxis="true"
tooltips="true"
interactive="true"
useInteractiveGuideline="true"
toolTipContent="toolTipContentFunction()"
margin="{left:50,top:50,bottom:50,right:50}"
forceY="[0]"
showLegend="true"
legendWidth="200"
legendHeight="100">
<svg></svg>
</nvd3-line-chart>
</div>
</div>
</div>
<div class="col-xs-12 top-padding-10">
<tree data="logicalTopoData"></tree>
</div>
</div>

View File

@ -119,3 +119,330 @@ angular.module('compass.charts', [])
}
})
.directive('circlepacking', function() {
return {
restrict: 'EAC',
scope: {
data: '='
},
link: function(scope, elem, attrs) {
//console.log(scope, elem, attrs)
var data = scope.data;
var w = 600,
h = 600,
r = 550,
x = d3.scale.linear().range([0, r]),
y = d3.scale.linear().range([0, r]),
node,
root;
var pack = d3.layout.pack()
.size([r, r])
.value(function(d) {
if (d.children === undefined || d.children.length == 0) {
return 500;
} else {
return undefined;
}
})
var vis = d3.select("circlepacking").append("svg:svg", "h2") //("svg:svg", "h2")
.attr("width", w)
.attr("height", h)
.append("svg:g")
.attr("transform", "translate(" + (w - r) / 2 + "," + (h - r) / 2 + ")");
node = root = data;
var nodes = pack.nodes(root);
vis.selectAll("circle")
.data(nodes)
.enter().append("svg:circle")
.attr("class", function(d) {
return d.children ? "parent" : "child";
})
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", function(d) {
return d.r;
})
.attr("depth", function(d) {
return d.depth;
})
.on("click", function(d) {
return zoom(node == d ? root : d);
})
.on("mouseover", function(d) {
//console.log("mouseover ", d)
});
vis.selectAll("text")
.data(nodes)
.enter().append("svg:text")
.attr("class", function(d) {
return d.children ? "parent" : "child";
})
.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.children ? d.y + d.r + 10 : d.y;
})
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.style("opacity", function(d) {
return d.r > 20 ? 1 : 0;
})
.text(function(d) {
return d.name;
});
d3.select(window).on("click", function() {
zoom(root);
});
function zoom(d, i) {
var k = r / d.r / 2;
x.domain([d.x - d.r, d.x + d.r]);
y.domain([d.y - d.r, d.y + d.r]);
var t = vis.transition()
.duration(d3.event.altKey ? 7500 : 750);
t.selectAll("circle")
.attr("cx", function(d) {
return x(d.x);
})
.attr("cy", function(d) {
return y(d.y);
})
.attr("r", function(d) {
return k * d.r;
});
t.selectAll("text")
.attr("x", function(d) {
return x(d.x);
})
.attr("y", function(d) {
return d.children ? y(d.y + d.r + 10) : y(d.y);
})
.style("opacity", function(d) {
return k * d.r > 20 ? 1 : 0;
});
node = d;
d3.event.stopPropagation();
}
}
};
})
.directive('tree', function() {
return {
restrict: 'EAC',
scope: {
data: '='
},
link: function(scope, elem, attrs) {
//console.log(scope, elem, attrs);
var json = scope.data;
var imgWidth = 163;
var imgHeight = 32;
var m = [20, 50, 20, 50],
w = 1280 - m[1] - m[3],
h = 800 - m[0] - m[2],
i = 0,
root;
var tree = d3.layout.tree()
.size([h, w]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var vis = d3.select("tree").append("svg:svg")
.attr("width", w + m[1] + m[3])
.attr("height", h + m[0] + m[2])
.append("svg:g")
.attr("transform", "translate(" + m[3] + "," + m[0] + ")");
root = json;
root.x0 = h / 2;
root.y0 = 0;
function toggleAll(d) {
if (d.children) {
d.children.forEach(toggleAll);
toggle(d);
}
}
root.children.forEach(toggleAll);
update(root);
function update(source) {
var duration = d3.event && d3.event.altKey ? 5000 : 500;
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse();
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 180;
});
// Update the nodes…
var node = vis.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("svg:g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on("click", function(d) {
toggle(d);
update(d);
});
nodeEnter.append("image")
.attr("xlink:href", function(d) {
if (d.depth == 0)
return "assets/img/router.png";
else if (d.depth == 1)
return "assets/img/switch.png";
else
return "assets/img/server.png";
})
//.attr("class", function(d) {
// return d.type;
//})
.attr("width", imgWidth)
.attr("height", imgHeight);
nodeEnter.append("svg:text")
.attr("x", function(d) {
return d.children || d._children ? -10 : 10;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1e-6);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
nodeUpdate.select("circle")
.attr("r", 4.5)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "#fff";
});
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = vis.selectAll("path.link")
.data(tree.links(nodes), function(d) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
link.enter().insert("svg:path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
})
.transition()
.duration(duration)
.attr("d", diagonal);
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// Toggle children.
function toggle(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
}
}
}
})

File diff suppressed because one or more lines are too long

1
v2/vendor/nvd3/nv.d3.min.css vendored Normal file

File diff suppressed because one or more lines are too long

6
v2/vendor/nvd3/nv.d3.min.js vendored Normal file

File diff suppressed because one or more lines are too long