From cb8ecb2d5faec9c1ac8b3f4d8dcc466c65da3fa3 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 25 Jul 2017 17:28:29 +0300 Subject: [PATCH 1/2] Heatmap fixes (adapted for v4.4.x branch) (#8920) * heatmap: fix converting error when series contains 0 and log scale is selected, issue #8884 * heatmap: fix app crash when Y min set to 0 with log scale * heatmap: fix tooltip for 'zero' buckets in log scale * heatmap: fix tooltip histogram for log scales * heatmap: fix flicker of the highlighted element this was caused by too often fired mouseenter/mouseleave events * heatmap: fix missing X axis option for log scales * heatmap: fix missing zero bucket in tooltip histogram --- .../panel/heatmap/heatmap_data_converter.ts | 2 + .../plugins/panel/heatmap/heatmap_tooltip.ts | 14 ++-- .../panel/heatmap/partials/axes_editor.html | 68 +++++++++---------- public/app/plugins/panel/heatmap/rendering.ts | 2 +- public/sass/components/_panel_heatmap.scss | 9 +++ 5 files changed, 55 insertions(+), 40 deletions(-) diff --git a/public/app/plugins/panel/heatmap/heatmap_data_converter.ts b/public/app/plugins/panel/heatmap/heatmap_data_converter.ts index d9e6bbb7d43..ef405ea5508 100644 --- a/public/app/plugins/panel/heatmap/heatmap_data_converter.ts +++ b/public/app/plugins/panel/heatmap/heatmap_data_converter.ts @@ -214,12 +214,14 @@ function pushToYBuckets(buckets, bucketNum, value, point, bounds) { } if (buckets[bucketNum]) { buckets[bucketNum].values.push(value); + buckets[bucketNum].points.push(point); buckets[bucketNum].count += count; } else { buckets[bucketNum] = { y: bucketNum, bounds: bounds, values: [value], + points: [point], count: count, }; } diff --git a/public/app/plugins/panel/heatmap/heatmap_tooltip.ts b/public/app/plugins/panel/heatmap/heatmap_tooltip.ts index 5ec0a8fa308..0d3f65cbf4d 100644 --- a/public/app/plugins/panel/heatmap/heatmap_tooltip.ts +++ b/public/app/plugins/panel/heatmap/heatmap_tooltip.ts @@ -83,7 +83,10 @@ export class HeatmapTooltip { let boundBottom, boundTop, valuesNumber; let xData = data.buckets[xBucketIndex]; - let yData = xData.buckets[yBucketIndex]; + // Search in special 'zero' bucket also + let yData = _.find(xData.buckets, (bucket, bucketIndex) => { + return bucket.bounds.bottom === yBucketIndex || bucketIndex === yBucketIndex; + }); let tooltipTimeFormat = 'YYYY-MM-DD HH:mm:ss'; let time = this.dashboard.formatDate(xData.x, tooltipTimeFormat); @@ -105,7 +108,9 @@ export class HeatmapTooltip { if (yData) { if (yData.bounds) { - boundBottom = valueFormatter(yData.bounds.bottom); + // Display 0 if bucket is a special 'zero' bucket + let bottom = yData.y ? yData.bounds.bottom : 0; + boundBottom = valueFormatter(bottom); boundTop = valueFormatter(yData.bounds.top); valuesNumber = yData.count; tooltipHtml += `
@@ -165,7 +170,7 @@ export class HeatmapTooltip { let yBucketSize = this.scope.ctrl.data.yBucketSize; let {min, max, ticks} = this.scope.ctrl.data.yAxis; let histogramData = _.map(xBucket.buckets, bucket => { - return [bucket.y, bucket.values.length]; + return [bucket.bounds.bottom, bucket.values.length]; }); histogramData = _.filter(histogramData, d => { return d[0] >= min && d[0] <= max; @@ -180,7 +185,8 @@ export class HeatmapTooltip { if (this.panel.yAxis.logBase === 1) { barWidth = Math.floor(HISTOGRAM_WIDTH / (max - min) * yBucketSize * 0.9); } else { - barWidth = Math.floor(HISTOGRAM_WIDTH / ticks / yBucketSize * 0.9); + let barNumberFactor = yBucketSize ? yBucketSize : 1; + barWidth = Math.floor(HISTOGRAM_WIDTH / ticks / barNumberFactor * 0.9); } barWidth = Math.max(barWidth, 1); diff --git a/public/app/plugins/panel/heatmap/partials/axes_editor.html b/public/app/plugins/panel/heatmap/partials/axes_editor.html index 10af1977af7..5d25bedd667 100644 --- a/public/app/plugins/panel/heatmap/partials/axes_editor.html +++ b/public/app/plugins/panel/heatmap/partials/axes_editor.html @@ -33,43 +33,26 @@
Buckets
-
-
-
- - - -
-
- - -
-
-
-
- - - -
-
- - -
-
-
-
+
- + +
+
+ + +
+
+ + +
+
+
+
+
+ + + +
+
+ + +
+
diff --git a/public/app/plugins/panel/heatmap/rendering.ts b/public/app/plugins/panel/heatmap/rendering.ts index b928173eafe..de2c2fde719 100644 --- a/public/app/plugins/panel/heatmap/rendering.ts +++ b/public/app/plugins/panel/heatmap/rendering.ts @@ -210,7 +210,7 @@ export default function link(scope, elem, attrs, ctrl) { let log_base = panel.yAxis.logBase; let {y_min, y_max} = adjustLogRange(data.heatmapStats.minLog, data.heatmapStats.max, log_base); - y_min = panel.yAxis.min !== null ? adjustLogMin(panel.yAxis.min, log_base) : y_min; + y_min = panel.yAxis.min && panel.yAxis.min !== '0' ? adjustLogMin(panel.yAxis.min, log_base) : y_min; y_max = panel.yAxis.max !== null ? adjustLogMax(panel.yAxis.max, log_base) : y_max; // Set default Y min and max if no data diff --git a/public/sass/components/_panel_heatmap.scss b/public/sass/components/_panel_heatmap.scss index cf55c91ab7c..9d07e6a363c 100644 --- a/public/sass/components/_panel_heatmap.scss +++ b/public/sass/components/_panel_heatmap.scss @@ -18,6 +18,15 @@ stroke: $text-color-weak; } } + + // This hack prevents mouseenter/mouseleave events get fired too often + svg { + pointer-events: none; + + rect { + pointer-events: visiblePainted; + } + } } .heatmap-tooltip { From 7b768bca3fa623c4c0a30408b4a0b01586772c30 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 26 Jul 2017 15:18:10 +0200 Subject: [PATCH 2/2] singlestat: change threshold check in flot gauge Fixes #5515. Flot gauge has its own threshold check that is different from the threshold check in singlestat. This changes the flot check to be exclusive (same as the singlestat check). E.g. if the threshold is 10, then the value 10 is over the threshold. --- .../singlestat/specs/singlestat_panel_spec.ts | 20 +++++- .../singlestat/specs/singlestat_specs.ts | 2 +- public/vendor/flot/jquery.flot.gauge.js | 68 +++++++++---------- 3 files changed, 53 insertions(+), 37 deletions(-) diff --git a/public/app/plugins/panel/singlestat/specs/singlestat_panel_spec.ts b/public/app/plugins/panel/singlestat/specs/singlestat_panel_spec.ts index 979972c6272..87922a3523b 100644 --- a/public/app/plugins/panel/singlestat/specs/singlestat_panel_spec.ts +++ b/public/app/plugins/panel/singlestat/specs/singlestat_panel_spec.ts @@ -14,11 +14,27 @@ describe('grafanaSingleStat', function() { expect(getColorForValue(data, 5)).to.be('green'); }); - it('25 should return green', () => { + it('19.9 should return green', () => { + expect(getColorForValue(data, 19.9)).to.be('green'); + }); + + it('20 should return yellow', () => { + expect(getColorForValue(data, 20)).to.be('yellow'); + }); + + it('20.1 should return yellow', () => { + expect(getColorForValue(data, 20.1)).to.be('yellow'); + }); + + it('25 should return yellow', () => { expect(getColorForValue(data, 25)).to.be('yellow'); }); - it('55 should return green', () => { + it('50 should return red', () => { + expect(getColorForValue(data, 50)).to.be('red'); + }); + + it('55 should return red', () => { expect(getColorForValue(data, 55)).to.be('red'); }); }); diff --git a/public/app/plugins/panel/singlestat/specs/singlestat_specs.ts b/public/app/plugins/panel/singlestat/specs/singlestat_specs.ts index 210196a24cc..48b13d66c84 100644 --- a/public/app/plugins/panel/singlestat/specs/singlestat_specs.ts +++ b/public/app/plugins/panel/singlestat/specs/singlestat_specs.ts @@ -108,7 +108,7 @@ describe('SingleStatCtrl', function() { }); }); - singleStatScenario('When range to text mapping is specifiedfor first range', function(ctx) { + singleStatScenario('When range to text mapping is specified for first range', function(ctx) { ctx.setup(function() { ctx.data = [ {target: 'test.cpu1', datapoints: [[41,50]]} diff --git a/public/vendor/flot/jquery.flot.gauge.js b/public/vendor/flot/jquery.flot.gauge.js index d8ed958e990..14866dbef20 100644 --- a/public/vendor/flot/jquery.flot.gauge.js +++ b/public/vendor/flot/jquery.flot.gauge.js @@ -85,17 +85,17 @@ * @return the calculated layout properties */ Gauge.prototype.calculateLayout = function() { - + var canvasWidth = placeholder.width(); var canvasHeight = placeholder.height(); - - + + // calculate cell size var columns = Math.min(series.length, gaugeOptions.layout.columns); var rows = Math.ceil(series.length / columns); - - + + var margin = gaugeOptions.layout.margin; var hMargin = gaugeOptions.layout.hMargin; @@ -107,8 +107,8 @@ cellWidth = cell; cellHeight = cell; } - - + + // calculate 'auto' values calculateAutoValues(gaugeOptions, cellWidth); @@ -155,13 +155,13 @@ var maxRadiusV = outerRadiusV - (thresholdLabelMargin * 2) - thresholdLabelFontSize - thresholdWidth; var radius = Math.min(maxRadiusH, maxRadiusV); - + var width = gaugeOptions.gauge.width; if (width >= radius) { width = Math.max(3, radius / 3); } - + var outerRadius = (thresholdLabelMargin * 2) + thresholdLabelFontSize + thresholdWidth + radius; var gaugeOuterHeight = Math.max(outerRadius * (1 + heightRatioV), outerRadius + valueMargin + (valueFontSize / 2)); @@ -198,7 +198,7 @@ * @param {Number} cellWidth the width of cell */ function calculateAutoValues(gaugeOptionsi, cellWidth) { - + if (gaugeOptionsi.gauge.width === "auto") { gaugeOptionsi.gauge.width = Math.max(5, cellWidth / 8); } @@ -223,7 +223,7 @@ if (gaugeOptionsi.threshold.label.font.size === "auto") { gaugeOptionsi.threshold.label.font.size = Math.max(5, cellWidth / 15); } - + } Gauge.prototype.calculateAutoValues = calculateAutoValues; @@ -237,7 +237,7 @@ * @return the calculated cell layout properties */ Gauge.prototype.calculateCellLayout = function(gaugeOptionsi, layout, i) { - + // calculate top, left and center var c = col(layout.columns, i); var r = row(layout.columns, i); @@ -276,7 +276,7 @@ * @param {Object} layout the layout properties */ Gauge.prototype.drawBackground = function(layout) { - + if (!gaugeOptions.frame.show) { return; } @@ -299,7 +299,7 @@ * @param {Object} cellLayout the cell layout properties */ Gauge.prototype.drawCellBackground = function(gaugeOptionsi, cellLayout) { - + context.save(); if (gaugeOptionsi.cell.border && gaugeOptionsi.cell.border.show && gaugeOptionsi.cell.border.color && gaugeOptionsi.cell.border.width) { context.strokeStyle = gaugeOptionsi.cell.border.color; @@ -324,10 +324,10 @@ * @param {Number} data the value of the gauge */ Gauge.prototype.drawGauge = function(gaugeOptionsi, layout, cellLayout, label, data) { - + var blur = gaugeOptionsi.gauge.shadow.show ? gaugeOptionsi.gauge.shadow.blur : 0; - + // draw gauge frame drawArcWithShadow( @@ -371,7 +371,7 @@ for (var i = 0; i < gaugeOptionsi.threshold.values.length; i++) { var threshold = gaugeOptionsi.threshold.values[i]; color = threshold.color; - if (data <= threshold.value) { + if (data < threshold.value) { break; } } @@ -410,7 +410,7 @@ * @param {Object} cellLayout the cell layout properties */ Gauge.prototype.drawThreshold = function(gaugeOptionsi, layout, cellLayout) { - + var a1 = gaugeOptionsi.gauge.startAngle; for (var i = 0; i < gaugeOptionsi.threshold.values.length; i++) { var threshold = gaugeOptionsi.threshold.values[i]; @@ -478,7 +478,7 @@ * @param {Object} item the item of the series */ Gauge.prototype.drawLable = function(gaugeOptionsi, layout, cellLayout, i, item) { - + drawText( cellLayout.cx, cellLayout.y + cellLayout.cellMargin + layout.labelMargin + cellLayout.offsetY, @@ -498,7 +498,7 @@ * @param {Object} item the item of the series */ Gauge.prototype.drawValue = function(gaugeOptionsi, layout, cellLayout, i, item) { - + drawText( cellLayout.cx, cellLayout.cy - (gaugeOptionsi.value.font.size / 2), @@ -517,7 +517,7 @@ * @param {Number} i the index of the series */ Gauge.prototype.drawThresholdValues = function(gaugeOptionsi, layout, cellLayout, i) { - + // min, max drawThresholdValue(gaugeOptionsi, layout, cellLayout, "Min" + i, gaugeOptionsi.gauge.min, gaugeOptionsi.gauge.startAngle); drawThresholdValue(gaugeOptionsi, layout, cellLayout, "Max" + i, gaugeOptionsi.gauge.max, gaugeOptionsi.gauge.endAngle); @@ -729,8 +729,8 @@ plot.hooks.processOptions.push(function(plot, options) { var logger = getLogger(options.series.gauges.debug); - - + + // turn 'grid' and 'legend' off if (options.series.gauges.show) { @@ -740,7 +740,7 @@ // sort threshold var thresholds = options.series.gauges.threshold.values; - + thresholds.sort(function(a, b) { if (a.value < b.value) { return -1; @@ -750,9 +750,9 @@ return 0; } }); - - + + }); // add draw hook @@ -761,14 +761,14 @@ var gaugeOptions = options.series.gauges; var logger = getLogger(gaugeOptions.debug); - + if (!gaugeOptions.show) { return; } var series = plot.getData(); - + if (!series || !series.length) { return; // if no series were passed } @@ -777,10 +777,10 @@ // calculate layout var layout = gauge.calculateLayout(); - + // debug layout if (gaugeOptions.debug.layout) { - + } // draw background @@ -789,21 +789,21 @@ // draw cells (label, gauge, value, threshold) for (var i = 0; i < series.length; i++) { var item = series[i]; - + var gaugeOptionsi = $.extend({}, gaugeOptions, item.gauges); if (item.gauges) { // re-calculate 'auto' values gauge.calculateAutoValues(gaugeOptionsi, layout.cellWidth); } - + // calculate cell layout var cellLayout = gauge.calculateCellLayout(gaugeOptionsi, layout, i); - + // draw cell background gauge.drawCellBackground(gaugeOptionsi, cellLayout) // debug layout if (gaugeOptionsi.debug.layout) { - + } // draw label if (gaugeOptionsi.label.show) {