diff --git a/public/app/plugins/panel/heatmap/color_legend.ts b/public/app/plugins/panel/heatmap/color_legend.ts
new file mode 100644
index 00000000000..60bbe38d689
--- /dev/null
+++ b/public/app/plugins/panel/heatmap/color_legend.ts
@@ -0,0 +1,300 @@
+///
+import angular from 'angular';
+import _ from 'lodash';
+import $ from 'jquery';
+import d3 from 'd3';
+import {contextSrv} from 'app/core/core';
+import {tickStep} from 'app/core/utils/ticks';
+
+let module = angular.module('grafana.directives');
+
+/**
+ * Color legend for heatmap editor.
+ */
+module.directive('colorLegend', function() {
+ return {
+ restrict: 'E',
+ template: '
diff --git a/public/app/plugins/panel/heatmap/rendering.ts b/public/app/plugins/panel/heatmap/rendering.ts
index de2c2fde719..8d85186b78e 100644
--- a/public/app/plugins/panel/heatmap/rendering.ts
+++ b/public/app/plugins/panel/heatmap/rendering.ts
@@ -8,7 +8,7 @@ import {appEvents, contextSrv} from 'app/core/core';
import {tickStep, getScaledDecimals, getFlotTickSize} from 'app/core/utils/ticks';
import d3 from 'd3';
import {HeatmapTooltip} from './heatmap_tooltip';
-import {convertToCards, mergeZeroBuckets} from './heatmap_data_converter';
+import {mergeZeroBuckets} from './heatmap_data_converter';
let MIN_CARD_SIZE = 1,
CARD_PADDING = 1,
@@ -384,10 +384,12 @@ export default function link(scope, elem, attrs, ctrl) {
data.buckets = mergeZeroBuckets(data.buckets, _.min(tick_values));
}
- let cardsData = convertToCards(data.buckets);
- let maxValue = d3.max(cardsData, card => card.count);
+ let cardsData = data.cards;
+ let maxValueAuto = data.cardStats.max;
+ let maxValue = panel.color.max || maxValueAuto;
+ let minValue = panel.color.min || 0;
- colorScale = getColorScale(maxValue);
+ colorScale = getColorScale(maxValue, minValue);
setOpacityScale(maxValue);
setCardSize();
@@ -434,14 +436,14 @@ export default function link(scope, elem, attrs, ctrl) {
.style("stroke-width", 0);
}
- function getColorScale(maxValue) {
+ function getColorScale(maxValue, minValue = 0) {
let colorScheme = _.find(ctrl.colorSchemes, {value: panel.color.colorScheme});
let colorInterpolator = d3[colorScheme.value];
let colorScaleInverted = colorScheme.invert === 'always' ||
(colorScheme.invert === 'dark' && !contextSrv.user.lightTheme);
- let start = colorScaleInverted ? maxValue : 0;
- let end = colorScaleInverted ? 0 : maxValue;
+ let start = colorScaleInverted ? maxValue : minValue;
+ let end = colorScaleInverted ? minValue : maxValue;
return d3.scaleSequential(colorInterpolator).domain([start, end]);
}
@@ -704,78 +706,11 @@ export default function link(scope, elem, attrs, ctrl) {
}
}
- function drawColorLegend() {
- d3.select("#heatmap-color-legend").selectAll("rect").remove();
-
- let legend = d3.select("#heatmap-color-legend");
- let legendWidth = Math.floor($(d3.select("#heatmap-color-legend").node()).outerWidth());
- let legendHeight = d3.select("#heatmap-color-legend").attr("height");
-
- let legendColorScale = getColorScale(legendWidth);
-
- let rangeStep = 2;
- let valuesRange = d3.range(0, legendWidth, rangeStep);
- var legendRects = legend.selectAll(".heatmap-color-legend-rect").data(valuesRange);
-
- legendRects.enter().append("rect")
- .attr("x", d => d)
- .attr("y", 0)
- .attr("width", rangeStep + 1) // Overlap rectangles to prevent gaps
- .attr("height", legendHeight)
- .attr("stroke-width", 0)
- .attr("fill", d => {
- return legendColorScale(d);
- });
- }
-
- function drawOpacityLegend() {
- d3.select("#heatmap-opacity-legend").selectAll("rect").remove();
-
- let legend = d3.select("#heatmap-opacity-legend");
- let legendWidth = Math.floor($(d3.select("#heatmap-opacity-legend").node()).outerWidth());
- let legendHeight = d3.select("#heatmap-opacity-legend").attr("height");
-
- let legendOpacityScale;
- if (panel.color.colorScale === 'linear') {
- legendOpacityScale = d3.scaleLinear()
- .domain([0, legendWidth])
- .range([0, 1]);
- } else if (panel.color.colorScale === 'sqrt') {
- legendOpacityScale = d3.scalePow().exponent(panel.color.exponent)
- .domain([0, legendWidth])
- .range([0, 1]);
- }
-
- let rangeStep = 1;
- let valuesRange = d3.range(0, legendWidth, rangeStep);
- var legendRects = legend.selectAll(".heatmap-opacity-legend-rect").data(valuesRange);
-
- legendRects.enter().append("rect")
- .attr("x", d => d)
- .attr("y", 0)
- .attr("width", rangeStep)
- .attr("height", legendHeight)
- .attr("stroke-width", 0)
- .attr("fill", panel.color.cardColor)
- .style("opacity", d => {
- return legendOpacityScale(d);
- });
- }
-
function render() {
data = ctrl.data;
panel = ctrl.panel;
timeRange = ctrl.range;
- // Draw only if color editor is opened
- if (!d3.select("#heatmap-color-legend").empty()) {
- drawColorLegend();
- }
-
- if (!d3.select("#heatmap-opacity-legend").empty()) {
- drawOpacityLegend();
- }
-
if (!setElementHeight() || !data) {
return;
}
diff --git a/public/app/plugins/panel/heatmap/specs/heatmap_data_converter_specs.ts b/public/app/plugins/panel/heatmap/specs/heatmap_data_converter_specs.ts
index 03adf1f9fa3..a898a14ff10 100644
--- a/public/app/plugins/panel/heatmap/specs/heatmap_data_converter_specs.ts
+++ b/public/app/plugins/panel/heatmap/specs/heatmap_data_converter_specs.ts
@@ -3,7 +3,8 @@
import _ from 'lodash';
import { describe, beforeEach, it, sinon, expect, angularMocks } from '../../../../../test/lib/common';
import TimeSeries from 'app/core/time_series2';
-import { convertToHeatMap, elasticHistogramToHeatmap, calculateBucketSize, isHeatmapDataEqual } from '../heatmap_data_converter';
+import {convertToHeatMap, convertToCards, elasticHistogramToHeatmap,
+ calculateBucketSize, isHeatmapDataEqual} from '../heatmap_data_converter';
describe('isHeatmapDataEqual', () => {
let ctx: any = {};
@@ -244,6 +245,47 @@ describe('ES Histogram converter', () => {
});
});
+describe('convertToCards', () => {
+ let buckets = {};
+
+ beforeEach(() => {
+ buckets = {
+ '1422774000000': {
+ x: 1422774000000,
+ buckets: {
+ '1': { y: 1, values: [1], count: 1, bounds: {} },
+ '2': { y: 2, values: [2], count: 1, bounds: {} }
+ }
+ },
+ '1422774060000': {
+ x: 1422774060000,
+ buckets: {
+ '2': { y: 2, values: [2, 3], count: 2, bounds: {} }
+ }
+ },
+ };
+ });
+
+ it('should build proper cards data', () => {
+ let expectedCards = [
+ {x: 1422774000000, y: 1, count: 1, values: [1], yBounds: {}},
+ {x: 1422774000000, y: 2, count: 1, values: [2], yBounds: {}},
+ {x: 1422774060000, y: 2, count: 2, values: [2, 3], yBounds: {}}
+ ];
+ let {cards, cardStats} = convertToCards(buckets);
+ expect(cards).to.eql(expectedCards);
+ });
+
+ it('should build proper cards stats', () => {
+ let expectedStats = {
+ min: 1,
+ max: 2
+ };
+ let {cards, cardStats} = convertToCards(buckets);
+ expect(cardStats).to.eql(expectedStats);
+ });
+});
+
/**
* Compare two numbers with given precision. Suitable for compare float numbers after conversions with precision loss.
* @param a
diff --git a/public/app/plugins/panel/heatmap/specs/renderer_specs.ts b/public/app/plugins/panel/heatmap/specs/renderer_specs.ts
index 5d3eb665e55..01d09a84228 100644
--- a/public/app/plugins/panel/heatmap/specs/renderer_specs.ts
+++ b/public/app/plugins/panel/heatmap/specs/renderer_specs.ts
@@ -11,8 +11,7 @@ import TimeSeries from 'app/core/time_series2';
import moment from 'moment';
import { Emitter } from 'app/core/core';
import rendering from '../rendering';
-import { convertToHeatMap } from '../heatmap_data_converter';
-// import d3 from 'd3';
+import {convertToHeatMap, convertToCards} from '../heatmap_data_converter';
describe('grafanaHeatmap', function () {
@@ -115,8 +114,9 @@ describe('grafanaHeatmap', function () {
let bucketsData = convertToHeatMap(ctx.series, ctx.data.yBucketSize, ctx.data.xBucketSize, logBase);
ctx.data.buckets = bucketsData;
- // console.log("bucketsData", bucketsData);
- // console.log("series", ctrl.panel.yAxis.logBase, ctx.series.length);
+ let {cards, cardStats} = convertToCards(bucketsData);
+ ctx.data.cards = cards;
+ ctx.data.cardStats = cardStats;
let elemHtml = `
diff --git a/public/sass/components/_panel_heatmap.scss b/public/sass/components/_panel_heatmap.scss
index 9d07e6a363c..134186f25a2 100644
--- a/public/sass/components/_panel_heatmap.scss
+++ b/public/sass/components/_panel_heatmap.scss
@@ -46,3 +46,46 @@
stroke-width: 1;
}
}
+
+.heatmap-selection {
+ stroke-width: 1;
+ fill: rgba(102, 102, 102, 0.4);
+ stroke: rgba(102, 102, 102, 0.8);
+}
+
+.heatmap-legend-wrapper {
+ @include clearfix();
+ margin: 0 $spacer;
+ padding-top: 10px;
+
+ svg {
+ width: 100%;
+ max-width: 300px;
+ height: 33px;
+ float: left;
+ white-space: nowrap;
+ padding-left: 10px;
+ }
+
+ .heatmap-legend-values {
+ display: inline-block;
+ }
+
+ .axis .tick {
+ text {
+ fill: $text-color;
+ color: $text-color;
+ font-size: $font-size-sm;
+ }
+
+ line {
+ opacity: 0.4;
+ stroke: $text-color-weak;
+ }
+
+ .domain {
+ opacity: 0.4;
+ stroke: $text-color-weak;
+ }
+ }
+}