Skip to content

Commit 0d4bfe7

Browse files
authored
Add average labels to monitoring sparklines and graphs (#1224)
* Adding average labels to graphs in monitoring module In the monitoring module, when hovering a given graph, show the corresponding average value on a label outside the sparkline. * avg label improvements - matching graph color - smaller size - fixing positioning bug when window content is scrolled * adding mesurement unit * average label for small graphs - average label added to small graphs - average label function moved to its own file - passing new required attributes to everywhere small graphs are used: endpoint details, monitored endpoints list, monitored endpoint list grouped - css improvements for showing all the text inside the label aligned to the left and to make the suffix unit of measure to be a bit smaller than the displaying average value * avg label styling improvements
1 parent 190f7e9 commit 0d4bfe7

File tree

7 files changed

+235
-33
lines changed

7 files changed

+235
-33
lines changed

src/ServicePulse.Host/app/css/particular.css

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3526,4 +3526,58 @@ g.error .node-text .lead.saga {
35263526

35273527
g.error .node-text a:hover {
35283528
text-decoration: underline;
3529+
}
3530+
3531+
:root {
3532+
--avg-tooltip-background-color: #000;
3533+
}
3534+
3535+
div.avg-tooltip {
3536+
position: absolute;
3537+
text-align: left;
3538+
padding: .5rem;
3539+
line-height: 1;
3540+
background: var(--avg-tooltip-background-color);
3541+
color: #ffffff;
3542+
border-radius: 8px 1px 1px 8px;
3543+
pointer-events: none;
3544+
font-size: 11px;
3545+
white-space: nowrap;
3546+
}
3547+
3548+
div.avg-tooltip.left {
3549+
border-radius: 1px 8px 8px 1px;
3550+
}
3551+
3552+
div.avg-tooltip:before {
3553+
content: '';
3554+
display: block;
3555+
z-index: -1;
3556+
right: 0;
3557+
position: absolute;
3558+
top: 50%;
3559+
background-color: var(--avg-tooltip-background-color);
3560+
width: 24px;
3561+
height: 24px;
3562+
margin-top: -12px;
3563+
margin-right: -12px;
3564+
3565+
transform: rotate(45deg);
3566+
}
3567+
3568+
div.avg-tooltip.left:before {
3569+
right: inherit;
3570+
margin-right: inherit;
3571+
margin-left: -12px;
3572+
left:0;
3573+
}
3574+
3575+
div.avg-tooltip .value {
3576+
font-size: 14px;
3577+
font-weight: bold;
3578+
}
3579+
3580+
div.avg-tooltip .value span {
3581+
font-size: 11px;
3582+
font-weight: normal;
35293583
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
export default function ArrowLabel({ pointToTheLeft = false, caption = '' }) {
2+
3+
var div = document.createElement('div');
4+
div.style.position = 'absolute';
5+
div.style.zIndex = 10;
6+
div.style.visibility = 'hidden';
7+
div.classList = `avg-tooltip${pointToTheLeft && ' left' || ''}`;
8+
div.innerHTML = `<div>
9+
${caption}
10+
</div>
11+
<div class="value">
12+
0 <span></span>
13+
</div>`;
14+
document.body.appendChild(div);
15+
16+
return {
17+
displayAt: function ({ x, y, color}) {
18+
var lableDimensions = getComputedStyle(div);
19+
//align the label vertically.
20+
div.style.top = `${Math.trunc(y - lableDimensions.height.replace('px', '') / 2)}px`;
21+
22+
//align the label horizontally.
23+
//get the label tip dimensions. The label tip is composed by a roated square of the ::before pseudo-element.
24+
var labelTipWidth = getComputedStyle(div, ':before').width.replace('px', '');
25+
var labelTipHeight = getComputedStyle(div, ':before').height.replace('px', '');
26+
var lalbeTipHypotenuse = Math.trunc(Math.hypot(labelTipWidth, labelTipHeight));
27+
28+
if (pointToTheLeft == false) {
29+
div.style.left = 'inherit';
30+
div.style.right = `calc(100% - ${x}px + ${lalbeTipHypotenuse / 2}px)`;
31+
} else {
32+
div.style.right = 'inherit';
33+
div.style.left = `${x + (lalbeTipHypotenuse / 2)}px`;
34+
}
35+
36+
div.style.visibility = 'visible';
37+
div.style.setProperty('--avg-tooltip-background-color', color); //by using properties the color of the 'before' content pseudo element can be updated.
38+
},
39+
40+
hide: function () {
41+
div.style.visibility = 'hidden';
42+
},
43+
44+
value: function (value, unit) {
45+
div.querySelector('.value').innerHTML = `${value} <span>${unit}</span>`;
46+
},
47+
48+
pointingToTheLeft: pointToTheLeft
49+
};
50+
}

src/ServicePulse.Host/app/modules/monitoring/js/directives/ui.particular.graph.js

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,33 @@
1-
(function(window, angular, d3) {
1+
import ArrowLabel from './ui.particular.arrowLabel';
2+
3+
(function(window, angular, d3) {
24
'use strict';
35

6+
const averageDecimalsDefault = 2;
7+
const avgLabelColorDefault = '#2700CB';
8+
const avgLabelSuffixDefault = '';
9+
10+
var averageLabelToTheRight = ArrowLabel({pointToTheLeft: false, caption: 'AVG'});
11+
12+
413
angular.module('ui.particular.graph', [])
514
.directive('graph',
6-
function() {
15+
function(formatter) {
716
return {
817
restrict: 'E',
918
scope: {
1019
plotData: '=',
1120
formatter: '&',
12-
minimumYaxis: '@'
21+
minimumYaxis: '@',
22+
isDurationGraph: '=isDurationGraph',
23+
avgDecimals: '@'
1324
},
1425
template: '<svg></svg>',
1526
link: function link(scope, element, attrs) {
27+
attrs.avgLabelColor = attrs.avgLabelColor || avgLabelColorDefault;
28+
attrs.metricSuffix = attrs.metricSuffix || avgLabelSuffixDefault;
29+
scope.avgDecimals = scope.avgDecimals || averageDecimalsDefault;
30+
1631
scope.plotData = scope.plotData || { points: [], average: 0 };
1732

1833
scope.$watch('plotData',
@@ -92,13 +107,39 @@
92107
.attr('class', 'graph-data-line');
93108
}
94109

95-
chart.append('path')
110+
var averageLine = chart.append('path')
96111
.datum(Array(numberOfPoints).fill(average))
97112
.attr('d', line)
98113
.attr('class', 'graph-avg-line');
114+
115+
var displayAverageLabel = function(averageLine, label, value, color, unit) {
116+
var {x, y, width} = averageLine.node().getBoundingClientRect();
117+
label.value(value, unit);
118+
119+
if (label.pointingToTheLeft) {
120+
label.displayAt({x:x + width + window.pageXOffset, y:y + window.pageYOffset, color});
121+
} else {
122+
label.displayAt({x:x + window.pageXOffset, y:y + window.pageYOffset, color});
123+
}
124+
}
125+
126+
chart.on("mouseover", function() {
127+
var value = `${formatter.formatLargeNumber(average,scope.avgDecimals)}`;
128+
var suffix = attrs.metricSuffix;
129+
130+
if (scope.isDurationGraph) {
131+
value = `${formatter.formatTime(average).value}`;
132+
suffix = formatter.formatTime(average).unit.toUpperCase();
133+
}
134+
135+
displayAverageLabel(averageLine, averageLabelToTheRight, value, attrs.avgLabelColor, suffix);
136+
})
137+
.on("mouseout", function(){
138+
averageLabelToTheRight.hide();
139+
});
99140
});
100141
}
101142
};
102-
});
143+
});
103144

104145
}(window, window.angular, window.d3));

src/ServicePulse.Host/app/modules/monitoring/js/directives/ui.particular.largeGraph.js

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1-
(function(window, angular, d3) {
1+
import ArrowLabel from './ui.particular.arrowLabel';
2+
3+
(function(window, angular, d3) {
24
'use strict';
35

6+
const averageDecimalsDefault = 2;
7+
const avgLabelSuffixDefault = '';
8+
9+
var averageLabelToTheRight = ArrowLabel({pointToTheLeft: false, caption: 'AVG'});
10+
var averageLabelToTheLeft = ArrowLabel({pointToTheLeft: true, caption: 'AVG'});
11+
412
function drawDataSeries(chart, data, color, fillColor, scaleX, scaleY) {
513

614
var area = d3.area()
@@ -40,13 +48,15 @@
4048

4149
var group = chart.append('g').attr('class', 'dataAverage');
4250

43-
group.append('path')
51+
var avgLine = group.append('path')
4452
.datum(Array(data.points.length).fill(data.average))
4553
.attr('d', line)
4654
.attr('stroke', color)
4755
.attr('stroke-width', 1.5)
4856
.attr('opacity', 0.5)
4957
.attr('stroke-dasharray', '10,10');
58+
59+
return avgLine;
5060
}
5161

5262
function padToWholeValue(value) {
@@ -78,10 +88,14 @@
7888
isDurationGraph: '=isDurationGraph',
7989
minimumYaxis: '@',
8090
width: '=plotWidth',
81-
height: '=plotHeight'
91+
height: '=plotHeight',
92+
avgDecimals: '@'
8293
},
8394
template: '<svg></svg>',
84-
link: function link(scope, element, attrs) {
95+
link: function link(scope, element, attrs) {
96+
scope.avgDecimals = scope.avgDecimals || averageDecimalsDefault;
97+
attrs.metricSuffix = attrs.metricSuffix || avgLabelSuffixDefault;
98+
8599
scope.$watch('firstDataSeries', function () {
86100

87101
var svg = element.find('svg')[0];
@@ -163,7 +177,18 @@
163177
}
164178

165179
var drawAverage = function(data, lineColor, fillColor) {
166-
drawAverageLine(chart, data, lineColor, fillColor, scaleX, scaleY);
180+
return drawAverageLine(chart, data, lineColor, fillColor, scaleX, scaleY);
181+
}
182+
183+
var displayAverageLabel = function(averageLine, label, value, color, unit) {
184+
var {x, y, width} = averageLine.node().getBoundingClientRect();
185+
label.value(value, unit);
186+
187+
if (label.pointingToTheLeft) {
188+
label.displayAt({x:x + width + window.pageXOffset, y:y + window.pageYOffset, color});
189+
} else {
190+
label.displayAt({x:x + window.pageXOffset, y:y + window.pageYOffset, color});
191+
}
167192
}
168193

169194
drawSeries(firstSeries, attrs.firstSeriesColor, attrs.firstSeriesFillColor);
@@ -172,14 +197,43 @@
172197
drawSeries(secondSeries, attrs.secondSeriesColor,attrs.secondSeriesFillColor);
173198
}
174199

175-
drawAverage(firstSeries, attrs.firstSeriesColor, attrs.firstSeriesFillColor );
200+
var firstAverageLine = drawAverage(firstSeries, attrs.firstSeriesColor, attrs.firstSeriesFillColor );
201+
202+
var secondAverageLine = null;
176203

177204
if (secondSeries) {
178-
drawAverage(secondSeries, attrs.secondSeriesColor, attrs.secondSeriesFillColor);
205+
secondAverageLine = drawAverage(secondSeries, attrs.secondSeriesColor, attrs.secondSeriesFillColor);
179206
}
207+
208+
chart.on("mouseover", function() {
209+
var value = `${formatter.formatLargeNumber(firstSeries.average, scope.avgDecimals)}`;
210+
var suffix = attrs.metricSuffix;
211+
212+
if (scope.isDurationGraph) {
213+
value = `${formatter.formatTime(firstSeries.average).value}`;
214+
suffix = formatter.formatTime(firstSeries.average).unit.toUpperCase();
215+
}
216+
217+
displayAverageLabel(firstAverageLine, averageLabelToTheRight, value, attrs.firstSeriesColor, suffix);
218+
219+
if (secondAverageLine && secondSeries.points.length > 0) {
220+
value = `${formatter.formatLargeNumber(secondSeries.average, scope.avgDecimals)}`;
221+
222+
if (scope.isDurationGraph) {
223+
value = `${formatter.formatTime(secondSeries.average).value}`;
224+
suffix = formatter.formatTime(secondSeries.average).unit.toUpperCase();
225+
}
226+
227+
displayAverageLabel(secondAverageLine, averageLabelToTheLeft, value, attrs.secondSeriesColor, suffix);
228+
}
229+
})
230+
.on("mouseout", function() {
231+
averageLabelToTheRight.hide();
232+
averageLabelToTheLeft.hide();
233+
});
180234
});
181235
}
182236
};
183237
});
184238

185-
}(window, window.angular, window.d3));
239+
}(window, window.angular, window.d3));

0 commit comments

Comments
 (0)