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
This commit is contained in:
Alexander Zobnin
2017-07-25 17:28:29 +03:00
committed by Torkel Ödegaard
parent 8ca08d65e7
commit cb8ecb2d5f
5 changed files with 55 additions and 40 deletions

View File

@@ -214,12 +214,14 @@ function pushToYBuckets(buckets, bucketNum, value, point, bounds) {
} }
if (buckets[bucketNum]) { if (buckets[bucketNum]) {
buckets[bucketNum].values.push(value); buckets[bucketNum].values.push(value);
buckets[bucketNum].points.push(point);
buckets[bucketNum].count += count; buckets[bucketNum].count += count;
} else { } else {
buckets[bucketNum] = { buckets[bucketNum] = {
y: bucketNum, y: bucketNum,
bounds: bounds, bounds: bounds,
values: [value], values: [value],
points: [point],
count: count, count: count,
}; };
} }

View File

@@ -83,7 +83,10 @@ export class HeatmapTooltip {
let boundBottom, boundTop, valuesNumber; let boundBottom, boundTop, valuesNumber;
let xData = data.buckets[xBucketIndex]; 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 tooltipTimeFormat = 'YYYY-MM-DD HH:mm:ss';
let time = this.dashboard.formatDate(xData.x, tooltipTimeFormat); let time = this.dashboard.formatDate(xData.x, tooltipTimeFormat);
@@ -105,7 +108,9 @@ export class HeatmapTooltip {
if (yData) { if (yData) {
if (yData.bounds) { 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); boundTop = valueFormatter(yData.bounds.top);
valuesNumber = yData.count; valuesNumber = yData.count;
tooltipHtml += `<div> tooltipHtml += `<div>
@@ -165,7 +170,7 @@ export class HeatmapTooltip {
let yBucketSize = this.scope.ctrl.data.yBucketSize; let yBucketSize = this.scope.ctrl.data.yBucketSize;
let {min, max, ticks} = this.scope.ctrl.data.yAxis; let {min, max, ticks} = this.scope.ctrl.data.yAxis;
let histogramData = _.map(xBucket.buckets, bucket => { let histogramData = _.map(xBucket.buckets, bucket => {
return [bucket.y, bucket.values.length]; return [bucket.bounds.bottom, bucket.values.length];
}); });
histogramData = _.filter(histogramData, d => { histogramData = _.filter(histogramData, d => {
return d[0] >= min && d[0] <= max; return d[0] >= min && d[0] <= max;
@@ -180,7 +185,8 @@ export class HeatmapTooltip {
if (this.panel.yAxis.logBase === 1) { if (this.panel.yAxis.logBase === 1) {
barWidth = Math.floor(HISTOGRAM_WIDTH / (max - min) * yBucketSize * 0.9); barWidth = Math.floor(HISTOGRAM_WIDTH / (max - min) * yBucketSize * 0.9);
} else { } 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); barWidth = Math.max(barWidth, 1);

View File

@@ -33,43 +33,26 @@
<div class="section gf-form-group" ng-if="ctrl.panel.dataFormat == 'timeseries'"> <div class="section gf-form-group" ng-if="ctrl.panel.dataFormat == 'timeseries'">
<h5 class="section-heading">Buckets</h5> <h5 class="section-heading">Buckets</h5>
<div ng-show="ctrl.panel.yAxis.logBase === 1"> <div class="gf-form-inline">
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label">Y Axis</label>
<label class="gf-form-label">Buckets</label>
<input type="number" class="gf-form-input width-5" placeholder="auto" data-placement="right"
bs-tooltip="'Number of buckets for Y axis.'"
ng-model="ctrl.panel.yBucketNumber" ng-change="ctrl.refresh()" ng-model-onblur>
</div>
<div class="gf-form">
<label class="gf-form-label">Size</label>
<input type="number" class="gf-form-input width-5" placeholder="auto" data-placement="right"
bs-tooltip="'Size of bucket. Has priority over Buckets option.'"
ng-model="ctrl.panel.yBucketSize" ng-change="ctrl.refresh()" ng-model-onblur>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label">X Axis</label>
<label class="gf-form-label">Buckets</label>
<input type="number" class="gf-form-input width-5" placeholder="auto" data-placement="right"
bs-tooltip="'Number of buckets for Y axis.'"
ng-model="ctrl.panel.xBucketNumber" ng-change="ctrl.refresh()" ng-model-onblur>
</div>
<div class="gf-form">
<label class="gf-form-label">Size</label>
<input type="text" class="gf-form-input width-5" placeholder="auto" data-placement="right"
bs-tooltip="'Size of bucket. Number or interval (10s, 5m, 1h, etc). Supported intervals: ms, s, m, h, d, w, M, y. Has priority over Buckets option.'"
ng-model="ctrl.panel.xBucketSize" ng-change="ctrl.refresh()" ng-model-onblur>
</div>
</div>
</div>
<div ng-show="ctrl.panel.yAxis.logBase !== 1">
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label width-7">Split Factor</label> <label class="gf-form-label width-5">Y Axis</label>
</div>
<div class="gf-form" ng-show="ctrl.panel.yAxis.logBase === 1">
<label class="gf-form-label width-5">Buckets</label>
<input type="number" class="gf-form-input width-5" placeholder="auto" data-placement="right"
bs-tooltip="'Number of buckets for Y axis.'"
ng-model="ctrl.panel.yBucketNumber" ng-change="ctrl.refresh()" ng-model-onblur>
</div>
<div class="gf-form" ng-show="ctrl.panel.yAxis.logBase === 1">
<label class="gf-form-label width-4">Size</label>
<input type="number" class="gf-form-input width-5" placeholder="auto" data-placement="right"
bs-tooltip="'Size of bucket. Has priority over Buckets option.'"
ng-model="ctrl.panel.yBucketSize" ng-change="ctrl.refresh()" ng-model-onblur>
</div>
<div class="gf-form" ng-show="ctrl.panel.yAxis.logBase !== 1">
<label class="gf-form-label width-10">Split Factor</label>
<input type="number" <input type="number"
class="gf-form-input width-3" class="gf-form-input width-9"
placeholder="1" placeholder="1"
data-placement="right" data-placement="right"
bs-tooltip="'For log scales only. By default Y values is splitted by integer powers of log base (1, 2, 4, 8, 16, ... for log2). This option allows to split each default bucket into specified number of buckets.'" bs-tooltip="'For log scales only. By default Y values is splitted by integer powers of log base (1, 2, 4, 8, 16, ... for log2). This option allows to split each default bucket into specified number of buckets.'"
@@ -77,6 +60,21 @@
</input> </input>
</div> </div>
</div> </div>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label width-5">X Axis</label>
<label class="gf-form-label width-5">Buckets</label>
<input type="number" class="gf-form-input width-5" placeholder="auto" data-placement="right"
bs-tooltip="'Number of buckets for X axis.'"
ng-model="ctrl.panel.xBucketNumber" ng-change="ctrl.refresh()" ng-model-onblur>
</div>
<div class="gf-form">
<label class="gf-form-label width-4">Size</label>
<input type="text" class="gf-form-input width-5" placeholder="auto" data-placement="right"
bs-tooltip="'Size of bucket. Number or interval (10s, 5m, 1h, etc). Supported intervals: ms, s, m, h, d, w, M, y. Has priority over Buckets option.'"
ng-model="ctrl.panel.xBucketSize" ng-change="ctrl.refresh()" ng-model-onblur>
</div>
</div>
</div> </div>
<div class="section gf-form-group"> <div class="section gf-form-group">

View File

@@ -210,7 +210,7 @@ export default function link(scope, elem, attrs, ctrl) {
let log_base = panel.yAxis.logBase; let log_base = panel.yAxis.logBase;
let {y_min, y_max} = adjustLogRange(data.heatmapStats.minLog, data.heatmapStats.max, log_base); 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; y_max = panel.yAxis.max !== null ? adjustLogMax(panel.yAxis.max, log_base) : y_max;
// Set default Y min and max if no data // Set default Y min and max if no data

View File

@@ -18,6 +18,15 @@
stroke: $text-color-weak; stroke: $text-color-weak;
} }
} }
// This hack prevents mouseenter/mouseleave events get fired too often
svg {
pointer-events: none;
rect {
pointer-events: visiblePainted;
}
}
} }
.heatmap-tooltip { .heatmap-tooltip {