Commit e3dd70bc by Torkel Ödegaard Committed by GitHub

Merge pull request #15937 from alexanderzobnin/heatmap-fixes

Heatmap fixes and improvements
parents ab2caf9e c248c1f4
......@@ -5,12 +5,15 @@
### Minor
* **Cloudwatch**: Add AWS RDS MaximumUsedTransactionIDs metric [#15077](https://github.com/grafana/grafana/pull/15077), thx [@activeshadow](https://github.com/activeshadow)
* **Heatmap**: `Middle` bucket bound option [#15683](https://github.com/grafana/grafana/issues/15683)
* **Heatmap**: `Reverse order` option for changing order of buckets [#15683](https://github.com/grafana/grafana/issues/15683)
### Bug Fixes
* **Api**: Invalid org invite code [#10506](https://github.com/grafana/grafana/issues/10506)
* **Datasource**: Handles nil jsondata field gracefully [#14239](https://github.com/grafana/grafana/issues/14239)
* **Gauge**: Interpolate scoped variables in repeated gauges [#15739](https://github.com/grafana/grafana/issues/15739)
* **Datasource**: Empty user/password was not updated when updating datasources [#15608](https://github.com/grafana/grafana/pull/15608), thx [@Maddin-619](https://github.com/Maddin-619)
* **Heatmap**: legend shows wrong colors for small values [#14019](https://github.com/grafana/grafana/issues/14019)
# 6.0.1 (2019-03-06)
......
......@@ -32,6 +32,7 @@ export class AxesEditorCtrl {
Auto: 'auto',
Upper: 'upper',
Lower: 'lower',
Middle: 'middle',
};
}
......
......@@ -11,6 +11,8 @@ const LEGEND_HEIGHT_PX = 6;
const LEGEND_WIDTH_PX = 100;
const LEGEND_TICK_SIZE = 0;
const LEGEND_VALUE_MARGIN = 0;
const LEGEND_PADDING_LEFT = 10;
const LEGEND_SEGMENT_WIDTH = 10;
/**
* Color legend for heatmap editor.
......@@ -95,27 +97,27 @@ function drawColorLegend(elem, colorScheme, rangeFrom, rangeTo, maxValue, minVal
const legendWidth = Math.floor(legendElem.outerWidth()) - 30;
const legendHeight = legendElem.attr('height');
let rangeStep = 1;
if (rangeTo - rangeFrom > legendWidth) {
rangeStep = Math.floor((rangeTo - rangeFrom) / legendWidth);
}
const rangeStep = ((rangeTo - rangeFrom) / legendWidth) * LEGEND_SEGMENT_WIDTH;
const widthFactor = legendWidth / (rangeTo - rangeFrom);
const valuesRange = d3.range(rangeFrom, rangeTo, rangeStep);
const colorScale = getColorScale(colorScheme, contextSrv.user.lightTheme, maxValue, minValue);
legend
.append('g')
.attr('class', 'legend-color-bar')
.attr('transform', 'translate(' + LEGEND_PADDING_LEFT + ',0)')
.selectAll('.heatmap-color-legend-rect')
.data(valuesRange)
.enter()
.append('rect')
.attr('x', d => d * widthFactor)
.attr('x', d => Math.round(d * widthFactor))
.attr('y', 0)
.attr('width', rangeStep * widthFactor + 1) // Overlap rectangles to prevent gaps
.attr('width', Math.round(rangeStep * widthFactor + 1)) // Overlap rectangles to prevent gaps
.attr('height', legendHeight)
.attr('stroke-width', 0)
.attr('fill', d => colorScale(d));
drawLegendValues(elem, colorScale, rangeFrom, rangeTo, maxValue, minValue, legendWidth);
drawLegendValues(elem, rangeFrom, rangeTo, maxValue, minValue, legendWidth, valuesRange);
}
function drawOpacityLegend(elem, options, rangeFrom, rangeTo, maxValue, minValue) {
......@@ -126,31 +128,31 @@ function drawOpacityLegend(elem, options, rangeFrom, rangeTo, maxValue, minValue
const legendWidth = Math.floor(legendElem.outerWidth()) - 30;
const legendHeight = legendElem.attr('height');
let rangeStep = 1;
if (rangeTo - rangeFrom > legendWidth) {
rangeStep = Math.floor((rangeTo - rangeFrom) / legendWidth);
}
const rangeStep = ((rangeTo - rangeFrom) / legendWidth) * LEGEND_SEGMENT_WIDTH;
const widthFactor = legendWidth / (rangeTo - rangeFrom);
const valuesRange = d3.range(rangeFrom, rangeTo, rangeStep);
const opacityScale = getOpacityScale(options, maxValue, minValue);
legend
.append('g')
.attr('class', 'legend-color-bar')
.attr('transform', 'translate(' + LEGEND_PADDING_LEFT + ',0)')
.selectAll('.heatmap-opacity-legend-rect')
.data(valuesRange)
.enter()
.append('rect')
.attr('x', d => d * widthFactor)
.attr('x', d => Math.round(d * widthFactor))
.attr('y', 0)
.attr('width', rangeStep * widthFactor)
.attr('width', Math.round(rangeStep * widthFactor))
.attr('height', legendHeight)
.attr('stroke-width', 0)
.attr('fill', options.cardColor)
.style('opacity', d => opacityScale(d));
drawLegendValues(elem, opacityScale, rangeFrom, rangeTo, maxValue, minValue, legendWidth);
drawLegendValues(elem, rangeFrom, rangeTo, maxValue, minValue, legendWidth, valuesRange);
}
function drawLegendValues(elem, colorScale, rangeFrom, rangeTo, maxValue, minValue, legendWidth) {
function drawLegendValues(elem, rangeFrom, rangeTo, maxValue, minValue, legendWidth, valuesRange) {
const legendElem = $(elem).find('svg');
const legend = d3.select(legendElem.get(0));
......@@ -171,7 +173,7 @@ function drawLegendValues(elem, colorScale, rangeFrom, rangeTo, maxValue, minVal
const colorRect = legendElem.find(':first-child');
const posY = getSvgElemHeight(legendElem) + LEGEND_VALUE_MARGIN;
const posX = getSvgElemX(colorRect);
const posX = getSvgElemX(colorRect) + LEGEND_PADDING_LEFT;
d3.select(legendElem.get(0))
.append('g')
......
......@@ -34,6 +34,7 @@ const panelDefaults = {
},
dataFormat: 'timeseries',
yBucketBound: 'auto',
reverseYBuckets: false,
xAxis: {
show: true,
},
......@@ -109,7 +110,7 @@ export class HeatmapCtrl extends MetricsPanelCtrl {
selectionActivated: boolean;
unitFormats: any;
data: any;
series: any;
series: any[];
timeSrv: any;
dataWarning: any;
decimals: number;
......@@ -147,7 +148,7 @@ export class HeatmapCtrl extends MetricsPanelCtrl {
}
onRender() {
if (!this.range) {
if (!this.range || !this.series) {
return;
}
......@@ -226,13 +227,20 @@ export class HeatmapCtrl extends MetricsPanelCtrl {
this.series.sort(sortSeriesByLabel);
}
if (this.panel.reverseYBuckets) {
this.series.reverse();
}
// Convert histogram to heatmap. Each histogram bucket represented by the series which name is
// a top (or bottom, depends of datasource) bucket bound. Further, these values will be used as X axis labels.
// a top (or bottom, depends of datasource) bucket bound. Further, these values will be used as Y axis labels.
bucketsData = histogramToHeatmap(this.series);
tsBuckets = _.map(this.series, 'label');
const yBucketBound = this.panel.yBucketBound;
if ((panelDatasource === 'prometheus' && yBucketBound !== 'lower') || yBucketBound === 'upper') {
if (
(panelDatasource === 'prometheus' && yBucketBound !== 'lower' && yBucketBound !== 'middle') ||
yBucketBound === 'upper'
) {
// Prometheus labels are upper inclusive bounds, so add empty bottom bucket label.
tsBuckets = [''].concat(tsBuckets);
} else {
......
......@@ -114,7 +114,9 @@ export class HeatmapTooltip {
};
boundBottom = tickFormatter(yBucketIndex);
boundTop = yBucketIndex < data.tsBuckets.length - 1 ? tickFormatter(yBucketIndex + 1) : '';
if (this.panel.yBucketBound !== 'middle') {
boundTop = yBucketIndex < data.tsBuckets.length - 1 ? tickFormatter(yBucketIndex + 1) : '';
}
} else {
// Display 0 if bucket is a special 'zero' bucket
const bottom = yData.y ? yData.bounds.bottom : 0;
......@@ -122,8 +124,9 @@ export class HeatmapTooltip {
boundTop = bucketBoundFormatter(yData.bounds.top);
}
valuesNumber = countValueFormatter(yData.count);
const boundStr = boundTop && boundBottom ? `${boundBottom} - ${boundTop}` : boundBottom || boundTop;
tooltipHtml += `<div>
bucket: <b>${boundBottom} - ${boundTop}</b> <br>
bucket: <b>${boundStr}</b> <br>
count: <b>${valuesNumber}</b> <br>
</div>`;
} else {
......
......@@ -40,6 +40,11 @@
</select>
</div>
</div>
<gf-form-switch ng-if="ctrl.panel.dataFormat == 'tsbuckets'"
class="gf-form" label-class="width-8"
label="Reverse order"
checked="ctrl.panel.reverseYBuckets" on-change="ctrl.refresh()">
</gf-form-switch>
</div>
<div class="section gf-form-group" ng-if="ctrl.panel.dataFormat == 'timeseries'">
......
......@@ -379,6 +379,12 @@ export class HeatmapRenderer {
const posX = this.getYAxisWidth(this.heatmap) + Y_AXIS_TICK_PADDING;
this.heatmap.select('.axis-y').attr('transform', 'translate(' + posX + ',' + posY + ')');
if (this.panel.yBucketBound === 'middle' && tickValues && tickValues.length) {
// Shift Y axis labels to the middle of bucket
const tickShift = 0 - this.chartHeight / (tickValues.length - 1) / 2;
this.heatmap.selectAll('.axis-y text').attr('transform', 'translate(' + 0 + ',' + tickShift + ')');
}
// Remove vertical line in the right of axis labels (called domain in d3)
this.heatmap
.select('.axis-y')
......@@ -615,8 +621,8 @@ export class HeatmapRenderer {
w = this.cardWidth;
}
// Card width should be MIN_CARD_SIZE at least
w = Math.max(w, MIN_CARD_SIZE);
// Card width should be MIN_CARD_SIZE at least, but cut cards shouldn't be displayed
w = w > 0 ? Math.max(w, MIN_CARD_SIZE) : 0;
return w;
}
......
......@@ -66,7 +66,6 @@ $font-size-heatmap-tick: 11px;
height: 18px;
float: left;
white-space: nowrap;
padding-left: 10px;
}
.heatmap-legend-values {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment