Skip to content

Commit 06cc30b

Browse files
committed
Merge pull request #149 from erundle/heatmap
Heatmap
2 parents b4f6509 + 9159272 commit 06cc30b

File tree

11 files changed

+723
-3
lines changed

11 files changed

+723
-3
lines changed

dist/angular-patternfly.js

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,6 +1075,252 @@ angular.module('patternfly.charts').directive('pfDonutPctChart', ["c3ChartDefaul
10751075
}
10761076
};
10771077
}]);
1078+
;angular.module('patternfly.charts').directive('pfHeatmapLegend',
1079+
function () {
1080+
'use strict';
1081+
return {
1082+
restrict: 'A',
1083+
scope: {
1084+
legend: '=',
1085+
legendColors: '='
1086+
},
1087+
templateUrl: 'charts/heatmap/heatmap-legend.html',
1088+
link: function ($scope) {
1089+
var items = [];
1090+
var index;
1091+
for (index = $scope.legend.length - 1; index >= 0; index--) {
1092+
items.push({
1093+
text: $scope.legend[index],
1094+
color: $scope.legendColors[index]
1095+
});
1096+
}
1097+
$scope.legendItems = items;
1098+
}
1099+
};
1100+
}
1101+
);
1102+
;/**
1103+
* @ngdoc directive
1104+
* @name patternfly.charts.directive:pfHeatMap
1105+
*
1106+
* @description
1107+
* Directive for rendering a heatmap chart.
1108+
*
1109+
* @param {object} data data for the chart:<br/>
1110+
* <ul style='list-style-type: none'>
1111+
* <li>.id - the id of the measurement
1112+
* <li>.value - the value of the measurement
1113+
* <li>.tooltip - message to be displayed on hover
1114+
* </ul>
1115+
*
1116+
* @param {string=} height height of the chart (no units) - default: "200"
1117+
* @param {string=} chartTitle title of the chart
1118+
* @param {array=} legendLabels the labels for the legend - defaults: ['< 70%', '70-80%' ,'80-90%', '> 90%']
1119+
* @param {array=} thresholds the threshold values for the heapmap - defaults: [0.7, 0.8, 0.9]
1120+
* @param {array=} heatmapColorPattern the colors that correspond to the various threshold values (lowest to hightest value ex: <70& to >90%) - defaults: ['#d4f0fa', '#F9D67A', '#EC7A08', '#CE0000']
1121+
* @param {function=} clickAction function(block) function to call when a block is clicked on
1122+
* @example
1123+
<example module="patternfly.charts">
1124+
<file name="script.js">
1125+
angular.module( 'patternfly.charts' ).controller( 'ChartCtrl', function( $scope) {
1126+
$scope.title = 'Utilization - Using Defaults';
1127+
$scope.data = [
1128+
{'id': 9,'value': 0.96,'tooltip': 'Node 8 : My OpenShift Provider<br\>96% : 96 Used of 100 Total<br\>4 Available'},
1129+
{'id': 44, 'value': 0.94, 'tooltip': 'Node 19 : My Kubernetes Provider<br\>94% : 94 Used of 100 Total<br\>6 Available'},
1130+
{'id': 0, 'value': 0.91, 'tooltip': 'Node 9 : My OpenShift Provider<br\>91% : 91 Used of 100 Total<br\>9 Available'},
1131+
{'id': 43, 'value': 0.9, 'tooltip': 'Node 18 : My Kubernetes Provider<br\>90% : 90 Used of 100 Total<br\>10 Available'},
1132+
{'id': 7, 'value': 0.89, 'tooltip': 'Node 12 : My OpenShift Provider<br\>89% : 89 Used of 100 Total<br\>11 Available'},
1133+
{'id': 41, 'value': 0.82, 'tooltip': 'Node 16 : My Kubernetes Provider<br\>82% : 82 Used of 100 Total<br\>18 Available'},
1134+
{'id': 21, 'value': 0.81, 'tooltip': 'Node 21 : My OpenShift Provider<br\>81% : 81 Used of 100 Total<br\>19 Available'},
1135+
{'id': 26, 'value': 0.8, 'tooltip': 'Node 1 : My Kubernetes Provider<br\>80% : 80 Used of 100 Total<br\>20 Available'},
1136+
{'id': 48, 'value': 0.74, 'tooltip': 'Node 23 : My Kubernetes Provider<br\>74% : 74 Used of 100 Total<br\>26 Available'},
1137+
{'id': 27, 'value': 0.72, 'tooltip': 'Node 2 : My Kubernetes Provider<br\>72% : 72 Used of 100 Total<br\>28 Available'},
1138+
{'id': 42, 'value': 0.71, 'tooltip': 'Node 17 : My Kubernetes Provider<br\>71% : 71 Used of 100 Total<br\>29 Available'},
1139+
{'id': 23, 'value': 0.71, 'tooltip': 'Node 23 : My OpenShift Provider<br\>71% : 71 Used of 100 Total<br\>29 Available'},
1140+
{'id': 22, 'value': 0.69, 'tooltip': 'Node 22 : My OpenShift Provider<br\>69% : 69 Used of 100 Total<br\>31 Available'},
1141+
{'id': 2, 'value': 0.66, 'tooltip': 'Node 2 : M8y OpenShift Provider<br\>66% : 66 Used of 100 Total<br\>34 Available'},
1142+
{'id': 39, 'value': 0.66, 'tooltip': 'Node 14 : My Kubernetes Provider<br\>66% : 66 Used of 100 Total<br\>34 Available'},
1143+
{'id': 3, 'value': 0.65, 'tooltip': 'Node 39 : My OpenShift Provider<br\>65% : 65 Used of 100 Total<br\>35 Available'},
1144+
{'id': 29, 'value': 0.65, 'tooltip': 'Node 4 : My Kubernetes Provider<br\>65% : 65 Used of 100 Total<br\>35 Available'},
1145+
{'id': 32, 'value': 0.56, 'tooltip': 'Node 7 : My Kubernetes Provider<br\>56% : 56 Used of 100 Total<br\>44 Available'},
1146+
{'id': 13, 'value': 0.56, 'tooltip': 'Node 13 : My OpenShift Provider<br\>56% : 56 Used of 100 Total<br\>44 Available'},
1147+
{'id': 49, 'value': 0.52, 'tooltip': 'Node 24 : My Kubernetes Provider<br\>52% : 52 Used of 100 Total<br\>48 Available'},
1148+
{'id': 36, 'value': 0.5, 'tooltip': 'Node 11 : My Kubernetes Provider<br\>50% : 50 Used of 100 Total<br\>50 Available'},
1149+
{'id': 6, 'value': 0.5, 'tooltip': 'Node 5 : My OpenShift Provider<br\>50% : 50 Used of 100 Total<br\>50 Available'},
1150+
{'id': 38, 'value': 0.49, 'tooltip': 'Node 13 : My Kubernetes Provider<br\>49% : 49 Used of 100 Total<br\>51 Available'},
1151+
{'id': 15, 'value': 0.48, 'tooltip': 'Node 15 : My OpenShift Provider<br\>48% : 48 Used of 100 Total<br\>52 Available'},
1152+
{'id': 30, 'value': 0.48, 'tooltip': 'Node 5 : My Kubernetes Provider<br\>48% : 48 Used of 100 Total<br\>52 Available'},
1153+
{'id': 11, 'value': 0.47, 'tooltip': 'Node 11 : My OpenShift Provider<br\>47% : 47 Used of 100 Total<br\>53 Available'},
1154+
{'id': 17, 'value': 0.46, 'tooltip': 'Node 17 : My OpenShift Provider<br\>46% : 46 Used of 100 Total<br\>54 Available'},
1155+
{'id': 25, 'value': 0.45, 'tooltip': 'Node 0 : My Kubernetes Provider<br\>45% : 45 Used of 100 Total<br\>55 Available'},
1156+
{'id': 50, 'value': 0.45, 'tooltip': 'Node 25 : My Kubernetes Provider<br\>45% : 45 Used of 100 Total<br\>55 Available'},
1157+
{'id': 46, 'value': 0.45, 'tooltip': 'Node 21 : My Kubernetes Provider<br\>45% : 45 Used of 100 Total<br\>55 Available'},
1158+
{'id': 47, 'value': 0.45, 'tooltip': 'Node 22 : My Kubernetes Provider<br\>45% : 45 Used of 100 Total<br\>55 Available'},
1159+
{'id': 1, 'value': 0.44, 'tooltip': 'Node 1 : My OpenShift Provider<br\>44% : 44 Used of 100 Total<br\>56 Available'},
1160+
{'id': 31, 'value': 0.44, 'tooltip': 'Node 6 : My Kubernetes Provider<br\>44% : 44 Used of 100 Total<br\>56 Available'},
1161+
{'id': 37, 'value': 0.44, 'tooltip': 'Node 12 : My Kubernetes Provider<br\>44% : 44 Used of 100 Total<br\>56 Available'},
1162+
{'id': 24, 'value': 0.44, 'tooltip': 'Node 24 : My OpenShift Provider<br\>44% : 44 Used of 100 Total<br\>56 Available'},
1163+
{'id': 40, 'value': 0.43, 'tooltip': 'Node 40 : My Kubernetes Provider<br\>43% : 43 Used of 100 Total<br\>57 Available'},
1164+
{'id': 20, 'value': 0.39, 'tooltip': 'Node 20 : My OpenShift Provider<br\>39% : 39 Used of 100 Total<br\>61 Available'},
1165+
{'id': 8, 'value': 0.39, 'tooltip': 'Node 8 : My OpenShift Provider<br\>39% : 39 Used of 100 Total<br\>61 Available'},
1166+
{'id': 5, 'value': 0.38, 'tooltip': 'Node 5 : My OpenShift Provider<br\>38% : 38 Used of 100 Total<br\>62 Available'},
1167+
{'id': 45, 'value': 0.37, 'tooltip': 'Node 20 : My Kubernetes Provider<br\>37% : 37 Used of 100 Total<br\>63 Available'},
1168+
{'id': 12, 'value': 0.37, 'tooltip': 'Node 12 : My OpenShift Provider<br\>37% : 37 Used of 100 Total<br\>63 Available'},
1169+
{'id': 34, 'value': 0.37, 'tooltip': 'Node 9 : My Kubernetes Provider<br\>37% : 37 Used of 100 Total<br\>63 Available'},
1170+
{'id': 33, 'value': 0.33, 'tooltip': 'Node 8 : My Kubernetes Provider<br\>33% : 33 Used of 100 Total<br\>67 Available'},
1171+
{'id': 16, 'value': 0.32, 'tooltip': 'Node 16 : My OpenShift Provider<br\>32% : 32 Used of 100 Total<br\>68 Available'},
1172+
{'id': 10, 'value': 0.29, 'tooltip': 'Node 10 : My OpenShift Provider<br\>28% : 29 Used of 100 Total<br\>71 Available'},
1173+
{'id': 35, 'value': 0.28, 'tooltip': 'Node 35 : My Kubernetes Provider<br\>28% : 28 Used of 100 Total<br\>72 Available'},
1174+
{'id': 18, 'value': 0.27, 'tooltip': 'Node 18 : My OpenShift Provider<br\>27% : 27 Used of 100 Total<br\>73 Available'},
1175+
{'id': 4, 'value': 0.26, 'tooltip': 'Node 4 : My OpenShift Provider<br\>26% : 26 Used of 100 Total<br\>74 Available'},
1176+
{'id': 19, 'value': 0.25, 'tooltip': 'Node 19 : My OpenShift Provider<br\>25% : 25 Used of 100 Total<br\>75 Available'},
1177+
{'id': 28, 'value': 0.25, 'tooltip': 'Node 3 : My Kubernetes Provider<br\>25% : 25 Used of 100 Total<br\>75 Available'},
1178+
{'id': 51, 'value': 0.22, 'tooltip': 'Node 26 : My Kubernetes Provider<br\>22% : 22 Used of 100 Total<br\>78 Available'},
1179+
{'id': 14, 'value': 0.2, 'tooltip': 'Node 14 : My OpenShift Provider<br\>20% : 20 Used of 100 Total<br\>80 Available'}];
1180+
1181+
$scope.titleAlt = 'Utilization - Overriding Defaults';
1182+
$scope.legendLabels = ['< 60%','70%', '70-80%' ,'80-90%', '> 90%'];
1183+
$scope.thresholds = [0.6, 0.7, 0.8, 0.9];
1184+
$scope.heatmapColorPattern = ['#d4f0fa', '#F9D67A', '#EC7A08', '#CE0000', '#f00'];
1185+
1186+
var clickAction = function (block) {
1187+
console.log(block);
1188+
};
1189+
$scope.clickAction = clickAction;
1190+
});
1191+
</file>
1192+
<file name="index.html">
1193+
<div ng-controller="ChartCtrl" class="row">
1194+
<div class="col-md-4">
1195+
<div pf-heatmap id="id" chart-title="title" data="data"></div>
1196+
</div>
1197+
<div class="col-md-4">
1198+
<div pf-heatmap id="id" chart-title="titleAlt" data="data" legend-labels="legendLabels" heatmap-color-pattern="heatmapColorPattern" thresholds="thresholds" click-action="clickAction"></div>
1199+
</div>
1200+
</div>
1201+
</file>
1202+
</example>
1203+
*/
1204+
angular.module('patternfly.charts').directive('pfHeatmap', ["$compile", function ($compile) {
1205+
'use strict';
1206+
return {
1207+
restrict: 'A',
1208+
scope: {
1209+
data: '=',
1210+
height: '=',
1211+
chartTitle: '=?',
1212+
legendLabels: '=?',
1213+
thresholds: '=?',
1214+
heatmapColorPattern: '=?',
1215+
clickAction: '=?'
1216+
},
1217+
templateUrl: 'charts/heatmap/heatmap.html',
1218+
link: function (scope, element, attrs) {
1219+
var thisComponent = element[0].querySelector('.pf-heatmap-svg');
1220+
var containerWidth, containerHeight, blockSize, numberOfRows;
1221+
var thresholdDefaults = [0.7, 0.8, 0.9];
1222+
var heatmapColorPatternDefaults = ['#d4f0fa', '#F9D67A', '#EC7A08', '#CE0000'];
1223+
var legendLabelDefaults = ['< 70%', '70-80%' ,'80-90%', '> 90%'];
1224+
var heightDefault = 200;
1225+
1226+
var setSizes = function () {
1227+
var parentContainer = element[0].querySelector('.heatmap-container');
1228+
containerWidth = parentContainer.clientWidth;
1229+
containerHeight = parentContainer.clientHeight;
1230+
blockSize = determineBlockSize();
1231+
numberOfRows = (blockSize === 0) ? 0 : Math.floor(containerHeight / blockSize);
1232+
};
1233+
1234+
var determineBlockSize = function () {
1235+
var x = containerWidth;
1236+
var y = containerHeight;
1237+
var n = scope.data.length;
1238+
var px = Math.ceil(Math.sqrt(n * x / y));
1239+
var py = Math.ceil(Math.sqrt(n * y / x));
1240+
var sx, sy;
1241+
1242+
if (Math.floor(px * y / x) * px < n) {
1243+
sx = y / Math.ceil(px * y / x);
1244+
} else {
1245+
sx = x / px;
1246+
}
1247+
1248+
if (Math.floor(py * x / y) * py < n) {
1249+
sy = x / Math.ceil(x * py / y);
1250+
} else {
1251+
sy = y / py;
1252+
}
1253+
return Math.max(sx, sy);
1254+
};
1255+
1256+
var redraw = function () {
1257+
var data = scope.data;
1258+
var blockPadding = 1;
1259+
var color = d3.scale.threshold().domain(scope.thresholds).range(scope.heatmapColorPattern);
1260+
var component = thisComponent;
1261+
var blocks;
1262+
var highlightBlock = function (block, active) {
1263+
block.style('fill-opacity', active ? 1 : 0.4);
1264+
};
1265+
var svg = window.d3.select(thisComponent);
1266+
svg.selectAll('*').remove();
1267+
1268+
blocks = svg.selectAll('rect').data(data).enter().append('rect');
1269+
blocks.attr('x', function (d, i) {
1270+
return (Math.floor(i / numberOfRows) * blockSize) + blockPadding;
1271+
}).attr('y', function (d, i) {
1272+
return (i % numberOfRows * blockSize) + blockPadding;
1273+
}).attr('width', blockSize - (2 * blockPadding)).attr('height', blockSize - (2 * blockPadding)).style('fill', function (d) {
1274+
return color(d.value);
1275+
}).attr('tooltip-html-unsafe', function (d, i) { //tooltip-html is throwing an exception
1276+
return d.tooltip;
1277+
}).attr('tooltip-append-to-body', function (d, i) {
1278+
return true;
1279+
}).attr('tooltip-animation', function (d, i) {
1280+
return false;
1281+
});
1282+
1283+
//Adding events
1284+
blocks.on('mouseover', function () {
1285+
blocks.call(highlightBlock, false);
1286+
d3.select(this).call(highlightBlock, true);
1287+
});
1288+
blocks.on('click', function (d) {
1289+
if (scope.clickAction) {
1290+
scope.clickAction(d);
1291+
}
1292+
});
1293+
1294+
//Compiles the tooltips
1295+
angular.forEach(angular.element(blocks), function (block) {
1296+
var el = angular.element(block);
1297+
$compile(el)(scope);
1298+
});
1299+
1300+
svg.on('mouseleave', function () {
1301+
blocks.call(highlightBlock, true);
1302+
});
1303+
};
1304+
1305+
//Allow overriding of defaults
1306+
scope.thresholds = angular.extend([], thresholdDefaults, scope.thresholds);
1307+
scope.heatmapColorPattern = angular.extend([], heatmapColorPatternDefaults, scope.heatmapColorPattern);
1308+
scope.legendLabels = angular.extend([], legendLabelDefaults, scope.legendLabels);
1309+
scope.height = scope.height || heightDefault;
1310+
1311+
//Shows loading indicator
1312+
scope.loadingDone = false;
1313+
1314+
scope.$watch('data', function (newVal, oldVal) {
1315+
if (typeof(newVal) !== 'undefined') {
1316+
scope.loadingDone = true;
1317+
setSizes();
1318+
redraw();
1319+
}
1320+
});
1321+
}
1322+
};
1323+
}]);
10781324
;/**
10791325
* @ngdoc directive
10801326
* @name patternfly.charts.directive:pfSparklineChart
@@ -4936,6 +5182,16 @@ angular.module('patternfly.views').directive('pfDataToolbar', function () {
49365182
);
49375183

49385184

5185+
$templateCache.put('charts/heatmap/heatmap-legend.html',
5186+
"<ul class=pf-heatmap-legend-container><li ng-repeat=\"item in legendItems\" class=pf-heatmap-legend-items><span class=pf-legend-color-box ng-style=\"{background: item.color}\"></span> <span class=pf-legend-text>{{item.text}}</span></li></ul>"
5187+
);
5188+
5189+
5190+
$templateCache.put('charts/heatmap/heatmap.html',
5191+
"<div class=pf-heatmap-container><h3>{{chartTitle}}</h3><div class=heatmap-container style=\"height: {{height}}px\"><svg class=pf-heatmap-svg></svg></div><div ng-if=!loadingDone class=\"spinner spinner-lg loading\"></div><div pf-heatmap-legend legend=legendLabels legend-colors=heatmapColorPattern></div></div>"
5192+
);
5193+
5194+
49395195
$templateCache.put('charts/sparkline/sparkline-chart.html',
49405196
"<span><div pf-c3-chart id={{sparklineChartId}} config=config></div></span>"
49415197
);

dist/angular-patternfly.min.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/styles/angular-patternfly.css

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,3 +424,45 @@
424424
.pf-utilization-bar-chart .progress-bar.animate {
425425
width: 0% !important;
426426
}
427+
428+
/* Heap map */
429+
.pf-heatmap-container {
430+
position: relative;
431+
}
432+
433+
.pf-heatmap-container .loading {
434+
position: absolute;
435+
top: 100px;
436+
right: 50%;
437+
z-index: 10;
438+
}
439+
440+
.pf-heatmap-svg {
441+
width: 100%;
442+
height: 100%;
443+
}
444+
445+
.pf-heatmap-legend-container{
446+
list-style-type: none;
447+
padding: 0;
448+
overflow: auto;
449+
}
450+
451+
.pf-heatmap-legend-items {
452+
float: left;
453+
}
454+
455+
.pf-legend-color-box {
456+
width: 11px;
457+
height: 11px;
458+
margin-left: 5px;
459+
margin-right: 5px;
460+
display: inline-block;
461+
}
462+
463+
.pf-legend-text {
464+
font-size: 11px;
465+
font-weight: 400;
466+
line-height: 11px;
467+
padding-right: 5px;
468+
}

0 commit comments

Comments
 (0)