Merge pull request #15937 from alexanderzobnin/heatmap-fixes
Heatmap fixes and improvements
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user