diff --git a/CHANGELOG.md b/CHANGELOG.md index 343e3ccfbb1..5483529f75e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/public/app/plugins/panel/heatmap/axes_editor.ts b/public/app/plugins/panel/heatmap/axes_editor.ts index 81df957e2ea..cec3df9f8c5 100644 --- a/public/app/plugins/panel/heatmap/axes_editor.ts +++ b/public/app/plugins/panel/heatmap/axes_editor.ts @@ -32,6 +32,7 @@ export class AxesEditorCtrl { Auto: 'auto', Upper: 'upper', Lower: 'lower', + Middle: 'middle', }; } diff --git a/public/app/plugins/panel/heatmap/color_legend.ts b/public/app/plugins/panel/heatmap/color_legend.ts index c90b394fb02..a86a498b723 100644 --- a/public/app/plugins/panel/heatmap/color_legend.ts +++ b/public/app/plugins/panel/heatmap/color_legend.ts @@ -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') diff --git a/public/app/plugins/panel/heatmap/heatmap_ctrl.ts b/public/app/plugins/panel/heatmap/heatmap_ctrl.ts index 809c6718a9c..99741a0c6d7 100644 --- a/public/app/plugins/panel/heatmap/heatmap_ctrl.ts +++ b/public/app/plugins/panel/heatmap/heatmap_ctrl.ts @@ -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 { diff --git a/public/app/plugins/panel/heatmap/heatmap_tooltip.ts b/public/app/plugins/panel/heatmap/heatmap_tooltip.ts index c447825eef8..90cbecd6657 100644 --- a/public/app/plugins/panel/heatmap/heatmap_tooltip.ts +++ b/public/app/plugins/panel/heatmap/heatmap_tooltip.ts @@ -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 += `