From ef9dd014c783548c0b3df3e2460f63f75ceac2a6 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 31 Jul 2017 14:03:03 +0300 Subject: [PATCH 01/12] heatmap: add color scale options, issue #8539 --- .../panel/heatmap/partials/display_editor.html | 12 ++++++++++++ public/app/plugins/panel/heatmap/rendering.ts | 12 +++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/public/app/plugins/panel/heatmap/partials/display_editor.html b/public/app/plugins/panel/heatmap/partials/display_editor.html index 863fcc49d07..2c66b90b949 100644 --- a/public/app/plugins/panel/heatmap/partials/display_editor.html +++ b/public/app/plugins/panel/heatmap/partials/display_editor.html @@ -43,6 +43,18 @@ +
+
Color scale
+
+ + +
+
+ + +
+
+
Buckets
diff --git a/public/app/plugins/panel/heatmap/rendering.ts b/public/app/plugins/panel/heatmap/rendering.ts index de2c2fde719..c6f898fcb28 100644 --- a/public/app/plugins/panel/heatmap/rendering.ts +++ b/public/app/plugins/panel/heatmap/rendering.ts @@ -385,9 +385,11 @@ export default function link(scope, elem, attrs, ctrl) { } let cardsData = convertToCards(data.buckets); - let maxValue = d3.max(cardsData, card => card.count); + let maxValueAuto = d3.max(cardsData, card => card.count); + 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]); } From 55b24be11585737911fc990c7bc2e46ebae13bc6 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 31 Jul 2017 17:45:05 +0300 Subject: [PATCH 02/12] heatmap: refactor, directive for color legend --- .../app/plugins/panel/heatmap/color_legend.ts | 136 ++++++++++++++++++ .../plugins/panel/heatmap/display_editor.ts | 6 + public/app/plugins/panel/heatmap/module.ts | 2 +- .../heatmap/partials/display_editor.html | 10 +- public/app/plugins/panel/heatmap/rendering.ts | 67 --------- 5 files changed, 147 insertions(+), 74 deletions(-) create mode 100644 public/app/plugins/panel/heatmap/color_legend.ts 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..572d26c7330 --- /dev/null +++ b/public/app/plugins/panel/heatmap/color_legend.ts @@ -0,0 +1,136 @@ +/// +import angular from 'angular'; +import _ from 'lodash'; +import $ from 'jquery'; +import d3 from 'd3'; +import {contextSrv} from 'app/core/core'; + +let module = angular.module('grafana.directives'); +module.directive('colorLegend', function() { + return { + restrict: 'E', + template: '
', + link: function(scope, elem, attrs) { + let ctrl = scope.ctrl; + let panel = scope.ctrl.panel; + + render(); + + ctrl.events.on('render', function() { + render(); + }); + + function render() { + let legendElem = $(elem).find('svg'); + let legendWidth = Math.floor(legendElem.outerWidth()); + + if (panel.color.mode === 'spectrum') { + let colorScheme = _.find(ctrl.colorSchemes, {value: panel.color.colorScheme}); + let colorScale = getColorScale(colorScheme, legendWidth); + drawColorLegend(elem, colorScale); + } else if (panel.color.mode === 'opacity') { + let colorOptions = panel.color; + drawOpacityLegend(elem, colorOptions); + } + } + } + }; +}); + +module.directive('heatmapLegend', function() { + return { + restrict: 'E', + template: '
', + link: function(scope, elem, attrs) { + let ctrl = scope.ctrl; + let panel = scope.ctrl.panel; + + ctrl.events.on('render', function() { + if (!_.isEmpty(ctrl.data)) { + let legendElem = $(elem).find('svg'); + let legendWidth = Math.floor(legendElem.outerWidth()); + + if (panel.color.mode === 'spectrum') { + let colorScheme = _.find(ctrl.colorSchemes, {value: panel.color.colorScheme}); + let colorScale = getColorScale(colorScheme, legendWidth); + drawColorLegend(elem, colorScale); + } else if (panel.color.mode === 'opacity') { + let colorOptions = panel.color; + drawOpacityLegend(elem, colorOptions); + } + } + }); + } + }; +}); + +function drawColorLegend(elem, colorScale) { + let legendElem = $(elem).find('svg'); + legendElem.find("rect").remove(); + + let legendWidth = Math.floor(legendElem.outerWidth()); + let legendHeight = legendElem.attr("height"); + + let rangeStep = 2; + let valuesRange = d3.range(0, legendWidth, rangeStep); + + let legend = d3.select(legendElem.get(0)); + 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 => colorScale(d)); +} + +function clearLegend(elem) { + let legendElem = $(elem).find('svg'); + legendElem.find("rect").remove(); +} + +function drawOpacityLegend(elem, options) { + let legendElem = $(elem).find('svg'); + clearLegend(elem); + + let legend = d3.select(legendElem.get(0)); + let legendWidth = Math.floor(legendElem.outerWidth()); + let legendHeight = legendElem.attr("height"); + + let legendOpacityScale; + if (options.colorScale === 'linear') { + legendOpacityScale = d3.scaleLinear() + .domain([0, legendWidth]) + .range([0, 1]); + } else if (options.colorScale === 'sqrt') { + legendOpacityScale = d3.scalePow().exponent(options.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", options.cardColor) + .style("opacity", d => legendOpacityScale(d)); +} + +function getColorScale(colorScheme, maxValue, minValue = 0) { + let colorInterpolator = d3[colorScheme.value]; + let colorScaleInverted = colorScheme.invert === 'always' || + (colorScheme.invert === 'dark' && !contextSrv.user.lightTheme); + + let start = colorScaleInverted ? maxValue : minValue; + let end = colorScaleInverted ? minValue : maxValue; + + return d3.scaleSequential(colorInterpolator).domain([start, end]); +} diff --git a/public/app/plugins/panel/heatmap/display_editor.ts b/public/app/plugins/panel/heatmap/display_editor.ts index 775017b00da..1cea9505567 100644 --- a/public/app/plugins/panel/heatmap/display_editor.ts +++ b/public/app/plugins/panel/heatmap/display_editor.ts @@ -1,4 +1,10 @@ /// +import _ from 'lodash'; +import $ from 'jquery'; +import d3 from 'd3'; +import {contextSrv} from 'app/core/core'; + +const COLOR_LEGEND_SELECTOR = '.heatmap-color-legend'; export class HeatmapDisplayEditorCtrl { panel: any; diff --git a/public/app/plugins/panel/heatmap/module.ts b/public/app/plugins/panel/heatmap/module.ts index d6926455563..d5ef7291308 100644 --- a/public/app/plugins/panel/heatmap/module.ts +++ b/public/app/plugins/panel/heatmap/module.ts @@ -1,5 +1,5 @@ /// - +import './color_legend'; import {HeatmapCtrl} from './heatmap_ctrl'; export { diff --git a/public/app/plugins/panel/heatmap/partials/display_editor.html b/public/app/plugins/panel/heatmap/partials/display_editor.html index 2c66b90b949..4f215f3fcce 100644 --- a/public/app/plugins/panel/heatmap/partials/display_editor.html +++ b/public/app/plugins/panel/heatmap/partials/display_editor.html @@ -25,9 +25,6 @@
-
- -
@@ -37,9 +34,10 @@
-
- -
+ + +
+
diff --git a/public/app/plugins/panel/heatmap/rendering.ts b/public/app/plugins/panel/heatmap/rendering.ts index c6f898fcb28..74b433653f1 100644 --- a/public/app/plugins/panel/heatmap/rendering.ts +++ b/public/app/plugins/panel/heatmap/rendering.ts @@ -706,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; } From c79a68dcd173395859b56b29ca35eba46a6f6037 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 31 Jul 2017 18:15:03 +0300 Subject: [PATCH 03/12] heatmap: refactor, build cards before rendering --- public/app/plugins/panel/heatmap/heatmap_ctrl.ts | 14 ++++++++++++-- public/app/plugins/panel/heatmap/rendering.ts | 6 +++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/public/app/plugins/panel/heatmap/heatmap_ctrl.ts b/public/app/plugins/panel/heatmap/heatmap_ctrl.ts index 37aa9410ea8..03bb46a104f 100644 --- a/public/app/plugins/panel/heatmap/heatmap_ctrl.ts +++ b/public/app/plugins/panel/heatmap/heatmap_ctrl.ts @@ -7,7 +7,7 @@ import TimeSeries from 'app/core/time_series'; import {axesEditor} from './axes_editor'; import {heatmapDisplayEditor} from './display_editor'; import rendering from './rendering'; -import { convertToHeatMap, elasticHistogramToHeatmap, calculateBucketSize, getMinLog} from './heatmap_data_converter'; +import {convertToHeatMap, convertToCards, elasticHistogramToHeatmap, calculateBucketSize, getMinLog} from './heatmap_data_converter'; let X_BUCKET_NUMBER_DEFAULT = 30; let Y_BUCKET_NUMBER_DEFAULT = 10; @@ -188,11 +188,21 @@ export class HeatmapCtrl extends MetricsPanelCtrl { yBucketSize = 1; } + let cardsData = convertToCards(bucketsData); + let maxCardsValue = _.max(_.map(cardsData, 'count')); + let minCardsValue = _.min(_.map(cardsData, 'count')); + let cardStats = { + max: maxCardsValue, + min: minCardsValue + }; + this.data = { buckets: bucketsData, heatmapStats: heatmapStats, xBucketSize: xBucketSize, - yBucketSize: yBucketSize + yBucketSize: yBucketSize, + cards: cardsData, + cardStats: cardStats }; } diff --git a/public/app/plugins/panel/heatmap/rendering.ts b/public/app/plugins/panel/heatmap/rendering.ts index 74b433653f1..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,8 +384,8 @@ export default function link(scope, elem, attrs, ctrl) { data.buckets = mergeZeroBuckets(data.buckets, _.min(tick_values)); } - let cardsData = convertToCards(data.buckets); - let maxValueAuto = 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; From 2aa26c98b6ba41bebb0671cb0c57c356d8040ae4 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 31 Jul 2017 20:11:55 +0300 Subject: [PATCH 04/12] heatmap: initial legend --- .../app/plugins/panel/heatmap/color_legend.ts | 227 ++++++++++++++---- public/app/plugins/panel/heatmap/module.html | 3 + public/sass/components/_panel_heatmap.scss | 37 +++ 3 files changed, 226 insertions(+), 41 deletions(-) diff --git a/public/app/plugins/panel/heatmap/color_legend.ts b/public/app/plugins/panel/heatmap/color_legend.ts index 572d26c7330..0b23a10e000 100644 --- a/public/app/plugins/panel/heatmap/color_legend.ts +++ b/public/app/plugins/panel/heatmap/color_legend.ts @@ -4,12 +4,13 @@ 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'); module.directive('colorLegend', function() { return { restrict: 'E', - template: '
', + template: '
', link: function(scope, elem, attrs) { let ctrl = scope.ctrl; let panel = scope.ctrl.panel; @@ -27,10 +28,10 @@ module.directive('colorLegend', function() { if (panel.color.mode === 'spectrum') { let colorScheme = _.find(ctrl.colorSchemes, {value: panel.color.colorScheme}); let colorScale = getColorScale(colorScheme, legendWidth); - drawColorLegend(elem, colorScale); + drawSimpleColorLegend(elem, colorScale); } else if (panel.color.mode === 'opacity') { let colorOptions = panel.color; - drawOpacityLegend(elem, colorOptions); + drawSimpleOpacityLegend(elem, colorOptions); } } } @@ -40,7 +41,7 @@ module.directive('colorLegend', function() { module.directive('heatmapLegend', function() { return { restrict: 'E', - template: '
', + template: '
', link: function(scope, elem, attrs) { let ctrl = scope.ctrl; let panel = scope.ctrl.panel; @@ -50,13 +51,19 @@ module.directive('heatmapLegend', function() { let legendElem = $(elem).find('svg'); let legendWidth = Math.floor(legendElem.outerWidth()); + // let maxValue = ctrl.data.cardStats.max || legendWidth; + let rangeFrom = ctrl.data.cardStats.min; + let rangeTo = ctrl.data.cardStats.max; + let maxValue = panel.color.max || rangeTo; + let minValue = panel.color.min || 0; + if (panel.color.mode === 'spectrum') { let colorScheme = _.find(ctrl.colorSchemes, {value: panel.color.colorScheme}); - let colorScale = getColorScale(colorScheme, legendWidth); - drawColorLegend(elem, colorScale); + let colorScale = getColorScale(colorScheme, maxValue, minValue); + drawColorLegend(elem, colorScale, rangeFrom, rangeTo, maxValue, minValue); } else if (panel.color.mode === 'opacity') { let colorOptions = panel.color; - drawOpacityLegend(elem, colorOptions); + drawOpacityLegend(elem, colorOptions, rangeFrom, rangeTo, maxValue, minValue); } } }); @@ -64,34 +71,116 @@ module.directive('heatmapLegend', function() { }; }); -function drawColorLegend(elem, colorScale) { +function drawColorLegend(elem, colorScale, rangeFrom, rangeTo, maxValue, minValue) { let legendElem = $(elem).find('svg'); - legendElem.find("rect").remove(); + clearLegend(elem); - let legendWidth = Math.floor(legendElem.outerWidth()); + let legendWidth = Math.floor(legendElem.outerWidth()) - 30; let legendHeight = legendElem.attr("height"); - let rangeStep = 2; - let valuesRange = d3.range(0, legendWidth, rangeStep); + let rangeStep = 1; + if (rangeTo - rangeFrom > legendWidth) { + rangeStep = Math.floor((rangeTo - rangeFrom) / legendWidth); + } + let widthFactor = legendWidth / (rangeTo - rangeFrom); + let valuesRange = d3.range(rangeFrom, rangeTo, rangeStep); let legend = d3.select(legendElem.get(0)); 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 => colorScale(d)); + .attr("x", d => d * widthFactor) + .attr("y", 0) + .attr("width", 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); +} + +function drawOpacityLegend(elem, options, rangeFrom, rangeTo, maxValue, minValue) { + let legendElem = $(elem).find('svg'); + clearLegend(elem); + + let legendWidth = Math.floor(legendElem.outerWidth()) - 30; + let legendHeight = legendElem.attr("height"); + + let rangeStep = 10; + let widthFactor = legendWidth / (rangeTo - rangeFrom); + let valuesRange = d3.range(rangeFrom, rangeTo, rangeStep); + let legend = d3.select(legendElem.get(0)); + var legendRects = legend.selectAll(".heatmap-opacity-legend-rect").data(valuesRange); + + let legendOpacityScale = getOpacityScale(options, maxValue, minValue); + legendRects.enter().append("rect") + .attr("x", d => d * widthFactor) + .attr("y", 0) + .attr("width", rangeStep * widthFactor) + .attr("height", legendHeight) + .attr("stroke-width", 0) + .attr("fill", options.cardColor) + .style("opacity", d => legendOpacityScale(d)); + + drawLegendValues(elem, legendOpacityScale, rangeFrom, rangeTo, maxValue, minValue, legendWidth); +} + +function drawLegendValues(elem, colorScale, rangeFrom, rangeTo, maxValue, minValue, legendWidth) { + let legendElem = $(elem).find('svg'); + let legend = d3.select(legendElem.get(0)); + + let legendValueDomain = _.sortBy(colorScale.domain()); + let legendValueScale = d3.scaleLinear() + .domain([0, rangeTo]) + .range([0, legendWidth]); + + let ticks = buildLegendTicks(0, rangeTo, maxValue, minValue); + let xAxis = d3.axisBottom(legendValueScale) + .tickValues(ticks) + .tickSize(3); + + let legendElemHeight = legendElem.height(); + let posY = legendElemHeight - 23; + let posX = getSvgElemX(legendElem.find(":first-child")); + d3.select(legendElem.get(0)).append("g") + .attr("class", "axis") + .attr("transform", "translate(" + posX + "," + posY + ")") + .call(xAxis); + + legend.select(".axis").select(".domain").remove(); +} + +function drawSimpleColorLegend(elem, colorScale) { + let legendElem = $(elem).find('svg'); + clearLegend(elem); + + let legendWidth = Math.floor(legendElem.outerWidth()); + let legendHeight = legendElem.attr("height"); + + if (legendWidth) { + let valuesNumber = Math.floor(legendWidth / 2); + let rangeStep = Math.floor(legendWidth / valuesNumber); + let valuesRange = d3.range(0, legendWidth, rangeStep); + + let legend = d3.select(legendElem.get(0)); + 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 => colorScale(d)); + } } function clearLegend(elem) { let legendElem = $(elem).find('svg'); - legendElem.find("rect").remove(); + legendElem.empty(); } -function drawOpacityLegend(elem, options) { +function drawSimpleOpacityLegend(elem, options) { let legendElem = $(elem).find('svg'); clearLegend(elem); @@ -99,29 +188,31 @@ function drawOpacityLegend(elem, options) { let legendWidth = Math.floor(legendElem.outerWidth()); let legendHeight = legendElem.attr("height"); - let legendOpacityScale; - if (options.colorScale === 'linear') { - legendOpacityScale = d3.scaleLinear() - .domain([0, legendWidth]) - .range([0, 1]); - } else if (options.colorScale === 'sqrt') { - legendOpacityScale = d3.scalePow().exponent(options.exponent) - .domain([0, legendWidth]) - .range([0, 1]); - } + if (legendWidth) { + let legendOpacityScale; + if (options.colorScale === 'linear') { + legendOpacityScale = d3.scaleLinear() + .domain([0, legendWidth]) + .range([0, 1]); + } else if (options.colorScale === 'sqrt') { + legendOpacityScale = d3.scalePow().exponent(options.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); + let rangeStep = 10; + 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", options.cardColor) - .style("opacity", d => legendOpacityScale(d)); + legendRects.enter().append("rect") + .attr("x", d => d) + .attr("y", 0) + .attr("width", rangeStep) + .attr("height", legendHeight) + .attr("stroke-width", 0) + .attr("fill", options.cardColor) + .style("opacity", d => legendOpacityScale(d)); + } } function getColorScale(colorScheme, maxValue, minValue = 0) { @@ -134,3 +225,57 @@ function getColorScale(colorScheme, maxValue, minValue = 0) { return d3.scaleSequential(colorInterpolator).domain([start, end]); } + +function getOpacityScale(options, maxValue, minValue = 0) { + let legendOpacityScale; + if (options.colorScale === 'linear') { + legendOpacityScale = d3.scaleLinear() + .domain([minValue, maxValue]) + .range([0, 1]); + } else if (options.colorScale === 'sqrt') { + legendOpacityScale = d3.scalePow().exponent(options.exponent) + .domain([minValue, maxValue]) + .range([0, 1]); + } + return legendOpacityScale; +} + +function getSvgElemX(elem) { + return elem.get(0).x.baseVal.value; +} + +function buildLegendTicks(rangeFrom, rangeTo, maxValue, minValue) { + let range = rangeTo - rangeFrom; + let tickStepSize = tickStep(rangeFrom, rangeTo, 3); + let ticksNum = Math.floor(range / tickStepSize); + let ticks = []; + + for (let i = 0; i < ticksNum; i++) { + let current = tickStepSize * i; + // Add user-defined min and max if it had been set + if (isValueCloseTo(minValue, current, tickStepSize)) { + ticks.push(minValue); + continue; + } else if (minValue < current) { + ticks.push(minValue); + } + if (isValueCloseTo(maxValue, current, tickStepSize)) { + ticks.push(maxValue); + continue; + } else if (maxValue < current) { + ticks.push(maxValue); + } + ticks.push(tickStepSize * i); + } + if (!isValueCloseTo(maxValue, rangeTo, tickStepSize)) { + ticks.push(maxValue); + } + ticks.push(rangeTo); + ticks = _.sortBy(_.uniq(ticks)); + return ticks; +} + +function isValueCloseTo(val, valueTo, step) { + let diff = Math.abs(val - valueTo); + return diff < step * 0.3; +} diff --git a/public/app/plugins/panel/heatmap/module.html b/public/app/plugins/panel/heatmap/module.html index 6cb89f2e1f2..ac9652a5afc 100644 --- a/public/app/plugins/panel/heatmap/module.html +++ b/public/app/plugins/panel/heatmap/module.html @@ -7,5 +7,8 @@
+
+ +
diff --git a/public/sass/components/_panel_heatmap.scss b/public/sass/components/_panel_heatmap.scss index 9d07e6a363c..9ca7b6f4bba 100644 --- a/public/sass/components/_panel_heatmap.scss +++ b/public/sass/components/_panel_heatmap.scss @@ -46,3 +46,40 @@ stroke-width: 1; } } + +.heatmap-legend-wrapper { + @include clearfix(); + margin: 0 $spacer; + padding-top: 10px; + + svg { + width: 100%; + max-width: 300px; + height: 38px; + 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; + } + } +} From e72baca4a714076ffb375eb8cd6e6c638d08bbda Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 1 Aug 2017 13:54:09 +0300 Subject: [PATCH 05/12] heatmap: fix black selection area --- public/sass/components/_panel_heatmap.scss | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/public/sass/components/_panel_heatmap.scss b/public/sass/components/_panel_heatmap.scss index 9ca7b6f4bba..e593f6e2bf8 100644 --- a/public/sass/components/_panel_heatmap.scss +++ b/public/sass/components/_panel_heatmap.scss @@ -47,6 +47,13 @@ } } +.heatmap-selection { + stroke-width: 1; + opacity: 0.3; + fill: #828282; + stroke: darken($red,15%); +} + .heatmap-legend-wrapper { @include clearfix(); margin: 0 $spacer; From 91a921e12d51f1d75275a1ea556102cedabeb37b Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 1 Aug 2017 14:07:52 +0300 Subject: [PATCH 06/12] heatmap: add show legend option --- public/app/plugins/panel/heatmap/color_legend.ts | 14 ++++++++++++-- public/app/plugins/panel/heatmap/heatmap_ctrl.ts | 3 +++ public/app/plugins/panel/heatmap/module.html | 2 +- .../panel/heatmap/partials/display_editor.html | 8 ++++++++ 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/public/app/plugins/panel/heatmap/color_legend.ts b/public/app/plugins/panel/heatmap/color_legend.ts index 0b23a10e000..2d52acb8ddb 100644 --- a/public/app/plugins/panel/heatmap/color_legend.ts +++ b/public/app/plugins/panel/heatmap/color_legend.ts @@ -46,7 +46,12 @@ module.directive('heatmapLegend', function() { let ctrl = scope.ctrl; let panel = scope.ctrl.panel; + render(); ctrl.events.on('render', function() { + render(); + }); + + function render() { if (!_.isEmpty(ctrl.data)) { let legendElem = $(elem).find('svg'); let legendWidth = Math.floor(legendElem.outerWidth()); @@ -66,7 +71,7 @@ module.directive('heatmapLegend', function() { drawOpacityLegend(elem, colorOptions, rangeFrom, rangeTo, maxValue, minValue); } } - }); + } } }; }); @@ -241,7 +246,12 @@ function getOpacityScale(options, maxValue, minValue = 0) { } function getSvgElemX(elem) { - return elem.get(0).x.baseVal.value; + let svgElem = elem.get(0); + if (svgElem && svgElem.x && svgElem.x.baseVal) { + return elem.get(0).x.baseVal.value; + } else { + return 0; + } } function buildLegendTicks(rangeFrom, rangeTo, maxValue, minValue) { diff --git a/public/app/plugins/panel/heatmap/heatmap_ctrl.ts b/public/app/plugins/panel/heatmap/heatmap_ctrl.ts index 03bb46a104f..3b8d1fa9d41 100644 --- a/public/app/plugins/panel/heatmap/heatmap_ctrl.ts +++ b/public/app/plugins/panel/heatmap/heatmap_ctrl.ts @@ -26,6 +26,9 @@ let panelDefaults = { exponent: 0.5, colorScheme: 'interpolateOranges', }, + legend: { + show: false + }, dataFormat: 'timeseries', xAxis: { show: true, diff --git a/public/app/plugins/panel/heatmap/module.html b/public/app/plugins/panel/heatmap/module.html index ac9652a5afc..272aac99772 100644 --- a/public/app/plugins/panel/heatmap/module.html +++ b/public/app/plugins/panel/heatmap/module.html @@ -8,7 +8,7 @@
- +
diff --git a/public/app/plugins/panel/heatmap/partials/display_editor.html b/public/app/plugins/panel/heatmap/partials/display_editor.html index 4f215f3fcce..f161bf6cab4 100644 --- a/public/app/plugins/panel/heatmap/partials/display_editor.html +++ b/public/app/plugins/panel/heatmap/partials/display_editor.html @@ -53,6 +53,14 @@ +
+
Legend
+ + +
+
Buckets
From f64ebf538c11672bc389ebad59cca0a8ea0b26b8 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 1 Aug 2017 15:25:52 +0300 Subject: [PATCH 07/12] heatmap: minor legend fixes --- .../app/plugins/panel/heatmap/color_legend.ts | 17 +++++++++++------ public/sass/components/_panel_heatmap.scss | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/public/app/plugins/panel/heatmap/color_legend.ts b/public/app/plugins/panel/heatmap/color_legend.ts index 2d52acb8ddb..0cbbddd0317 100644 --- a/public/app/plugins/panel/heatmap/color_legend.ts +++ b/public/app/plugins/panel/heatmap/color_legend.ts @@ -52,7 +52,8 @@ module.directive('heatmapLegend', function() { }); function render() { - if (!_.isEmpty(ctrl.data)) { + clearLegend(elem); + if (!_.isEmpty(ctrl.data) && !_.isEmpty(ctrl.data.cards)) { let legendElem = $(elem).find('svg'); let legendWidth = Math.floor(legendElem.outerWidth()); @@ -131,6 +132,10 @@ function drawOpacityLegend(elem, options, rangeFrom, rangeTo, maxValue, minValue } function drawLegendValues(elem, colorScale, rangeFrom, rangeTo, maxValue, minValue, legendWidth) { + if (legendWidth <= 0) { + return; + } + let legendElem = $(elem).find('svg'); let legend = d3.select(legendElem.get(0)); @@ -142,11 +147,11 @@ function drawLegendValues(elem, colorScale, rangeFrom, rangeTo, maxValue, minVal let ticks = buildLegendTicks(0, rangeTo, maxValue, minValue); let xAxis = d3.axisBottom(legendValueScale) .tickValues(ticks) - .tickSize(3); + .tickSize(2); - let legendElemHeight = legendElem.height(); - let posY = legendElemHeight - 23; - let posX = getSvgElemX(legendElem.find(":first-child")); + let colorRect = legendElem.find(":first-child"); + let posY = colorRect.height() + 2; + let posX = getSvgElemX(colorRect); d3.select(legendElem.get(0)).append("g") .attr("class", "axis") .attr("transform", "translate(" + posX + "," + posY + ")") @@ -257,7 +262,7 @@ function getSvgElemX(elem) { function buildLegendTicks(rangeFrom, rangeTo, maxValue, minValue) { let range = rangeTo - rangeFrom; let tickStepSize = tickStep(rangeFrom, rangeTo, 3); - let ticksNum = Math.floor(range / tickStepSize); + let ticksNum = Math.round(range / tickStepSize); let ticks = []; for (let i = 0; i < ticksNum; i++) { diff --git a/public/sass/components/_panel_heatmap.scss b/public/sass/components/_panel_heatmap.scss index e593f6e2bf8..3fa2e331c7b 100644 --- a/public/sass/components/_panel_heatmap.scss +++ b/public/sass/components/_panel_heatmap.scss @@ -62,7 +62,7 @@ svg { width: 100%; max-width: 300px; - height: 38px; + height: 33px; float: left; white-space: nowrap; padding-left: 10px; From b01a4e65839878b99c889bee37171d8366d5ca77 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 1 Aug 2017 16:21:40 +0300 Subject: [PATCH 08/12] heatmap: use the same color for selection as for graph --- public/sass/components/_panel_heatmap.scss | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/public/sass/components/_panel_heatmap.scss b/public/sass/components/_panel_heatmap.scss index 3fa2e331c7b..134186f25a2 100644 --- a/public/sass/components/_panel_heatmap.scss +++ b/public/sass/components/_panel_heatmap.scss @@ -49,9 +49,8 @@ .heatmap-selection { stroke-width: 1; - opacity: 0.3; - fill: #828282; - stroke: darken($red,15%); + fill: rgba(102, 102, 102, 0.4); + stroke: rgba(102, 102, 102, 0.8); } .heatmap-legend-wrapper { From 663a3293eed813c746617dfeedac37ad55fcbde4 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 1 Aug 2017 16:23:01 +0300 Subject: [PATCH 09/12] heatmap: some legend fixes --- .../app/plugins/panel/heatmap/color_legend.ts | 58 ++++++++++--------- public/app/plugins/panel/heatmap/module.html | 4 +- 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/public/app/plugins/panel/heatmap/color_legend.ts b/public/app/plugins/panel/heatmap/color_legend.ts index 0cbbddd0317..60bbe38d689 100644 --- a/public/app/plugins/panel/heatmap/color_legend.ts +++ b/public/app/plugins/panel/heatmap/color_legend.ts @@ -7,6 +7,10 @@ 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', @@ -38,6 +42,9 @@ module.directive('colorLegend', function() { }; }); +/** + * Heatmap legend with scale values. + */ module.directive('heatmapLegend', function() { return { restrict: 'E', @@ -54,19 +61,14 @@ module.directive('heatmapLegend', function() { function render() { clearLegend(elem); if (!_.isEmpty(ctrl.data) && !_.isEmpty(ctrl.data.cards)) { - let legendElem = $(elem).find('svg'); - let legendWidth = Math.floor(legendElem.outerWidth()); - - // let maxValue = ctrl.data.cardStats.max || legendWidth; - let rangeFrom = ctrl.data.cardStats.min; + let rangeFrom = 0; let rangeTo = ctrl.data.cardStats.max; let maxValue = panel.color.max || rangeTo; let minValue = panel.color.min || 0; if (panel.color.mode === 'spectrum') { let colorScheme = _.find(ctrl.colorSchemes, {value: panel.color.colorScheme}); - let colorScale = getColorScale(colorScheme, maxValue, minValue); - drawColorLegend(elem, colorScale, rangeFrom, rangeTo, maxValue, minValue); + drawColorLegend(elem, colorScheme, rangeFrom, rangeTo, maxValue, minValue); } else if (panel.color.mode === 'opacity') { let colorOptions = panel.color; drawOpacityLegend(elem, colorOptions, rangeFrom, rangeTo, maxValue, minValue); @@ -77,8 +79,9 @@ module.directive('heatmapLegend', function() { }; }); -function drawColorLegend(elem, colorScale, rangeFrom, rangeTo, maxValue, minValue) { +function drawColorLegend(elem, colorScheme, rangeFrom, rangeTo, maxValue, minValue) { let legendElem = $(elem).find('svg'); + let legend = d3.select(legendElem.get(0)); clearLegend(elem); let legendWidth = Math.floor(legendElem.outerWidth()) - 30; @@ -91,10 +94,10 @@ function drawColorLegend(elem, colorScale, rangeFrom, rangeTo, maxValue, minValu let widthFactor = legendWidth / (rangeTo - rangeFrom); let valuesRange = d3.range(rangeFrom, rangeTo, rangeStep); - let legend = d3.select(legendElem.get(0)); - var legendRects = legend.selectAll(".heatmap-color-legend-rect").data(valuesRange); - - legendRects.enter().append("rect") + let colorScale = getColorScale(colorScheme, maxValue, minValue); + legend.selectAll(".heatmap-color-legend-rect") + .data(valuesRange) + .enter().append("rect") .attr("x", d => d * widthFactor) .attr("y", 0) .attr("width", rangeStep * widthFactor + 1) // Overlap rectangles to prevent gaps @@ -107,6 +110,7 @@ function drawColorLegend(elem, colorScale, rangeFrom, rangeTo, maxValue, minValu function drawOpacityLegend(elem, options, rangeFrom, rangeTo, maxValue, minValue) { let legendElem = $(elem).find('svg'); + let legend = d3.select(legendElem.get(0)); clearLegend(elem); let legendWidth = Math.floor(legendElem.outerWidth()) - 30; @@ -115,30 +119,30 @@ function drawOpacityLegend(elem, options, rangeFrom, rangeTo, maxValue, minValue let rangeStep = 10; let widthFactor = legendWidth / (rangeTo - rangeFrom); let valuesRange = d3.range(rangeFrom, rangeTo, rangeStep); - let legend = d3.select(legendElem.get(0)); - var legendRects = legend.selectAll(".heatmap-opacity-legend-rect").data(valuesRange); - let legendOpacityScale = getOpacityScale(options, maxValue, minValue); - legendRects.enter().append("rect") + let opacityScale = getOpacityScale(options, maxValue, minValue); + legend.selectAll(".heatmap-opacity-legend-rect") + .data(valuesRange) + .enter().append("rect") .attr("x", d => d * widthFactor) .attr("y", 0) .attr("width", rangeStep * widthFactor) .attr("height", legendHeight) .attr("stroke-width", 0) .attr("fill", options.cardColor) - .style("opacity", d => legendOpacityScale(d)); + .style("opacity", d => opacityScale(d)); - drawLegendValues(elem, legendOpacityScale, rangeFrom, rangeTo, maxValue, minValue, legendWidth); + drawLegendValues(elem, opacityScale, rangeFrom, rangeTo, maxValue, minValue, legendWidth); } function drawLegendValues(elem, colorScale, rangeFrom, rangeTo, maxValue, minValue, legendWidth) { - if (legendWidth <= 0) { - return; - } - let legendElem = $(elem).find('svg'); let legend = d3.select(legendElem.get(0)); + if (legendWidth <= 0 || legendElem.get(0).childNodes.length === 0) { + return; + } + let legendValueDomain = _.sortBy(colorScale.domain()); let legendValueScale = d3.scaleLinear() .domain([0, rangeTo]) @@ -185,11 +189,6 @@ function drawSimpleColorLegend(elem, colorScale) { } } -function clearLegend(elem) { - let legendElem = $(elem).find('svg'); - legendElem.empty(); -} - function drawSimpleOpacityLegend(elem, options) { let legendElem = $(elem).find('svg'); clearLegend(elem); @@ -225,6 +224,11 @@ function drawSimpleOpacityLegend(elem, options) { } } +function clearLegend(elem) { + let legendElem = $(elem).find('svg'); + legendElem.empty(); +} + function getColorScale(colorScheme, maxValue, minValue = 0) { let colorInterpolator = d3[colorScheme.value]; let colorScaleInverted = colorScheme.invert === 'always' || diff --git a/public/app/plugins/panel/heatmap/module.html b/public/app/plugins/panel/heatmap/module.html index 272aac99772..5b5c5296ca1 100644 --- a/public/app/plugins/panel/heatmap/module.html +++ b/public/app/plugins/panel/heatmap/module.html @@ -7,8 +7,8 @@
-
- +
+
From f7ea08dba710cfe950d94595a3bc4d4ece89b300 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 1 Aug 2017 16:51:55 +0300 Subject: [PATCH 10/12] heatmap: fix rendering tests --- .../plugins/panel/heatmap/specs/renderer_specs.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/public/app/plugins/panel/heatmap/specs/renderer_specs.ts b/public/app/plugins/panel/heatmap/specs/renderer_specs.ts index 5d3eb665e55..884ee1e81f4 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,15 @@ 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 cardsData = convertToCards(bucketsData); + let maxCardsValue = _.max(_.map(cardsData, 'count')); + let minCardsValue = _.min(_.map(cardsData, 'count')); + let cardStats = { + max: maxCardsValue, + min: minCardsValue + }; + ctx.data.cards = cardsData; + ctx.data.cardStats = cardStats; let elemHtml = `
From c7e8b98d144ee492b10b2161c65d3bd09cf3e161 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 2 Aug 2017 21:00:05 +0300 Subject: [PATCH 11/12] heatmap: minor refactor, don't repeat cards stats calculation --- public/app/plugins/panel/heatmap/heatmap_ctrl.ts | 10 ++-------- .../plugins/panel/heatmap/heatmap_data_converter.ts | 12 +++++++++++- .../plugins/panel/heatmap/specs/renderer_specs.ts | 10 ++-------- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/public/app/plugins/panel/heatmap/heatmap_ctrl.ts b/public/app/plugins/panel/heatmap/heatmap_ctrl.ts index 3b8d1fa9d41..b564339673f 100644 --- a/public/app/plugins/panel/heatmap/heatmap_ctrl.ts +++ b/public/app/plugins/panel/heatmap/heatmap_ctrl.ts @@ -191,20 +191,14 @@ export class HeatmapCtrl extends MetricsPanelCtrl { yBucketSize = 1; } - let cardsData = convertToCards(bucketsData); - let maxCardsValue = _.max(_.map(cardsData, 'count')); - let minCardsValue = _.min(_.map(cardsData, 'count')); - let cardStats = { - max: maxCardsValue, - min: minCardsValue - }; + let {cards, cardStats} = convertToCards(bucketsData); this.data = { buckets: bucketsData, heatmapStats: heatmapStats, xBucketSize: xBucketSize, yBucketSize: yBucketSize, - cards: cardsData, + cards: cards, cardStats: cardStats }; } diff --git a/public/app/plugins/panel/heatmap/heatmap_data_converter.ts b/public/app/plugins/panel/heatmap/heatmap_data_converter.ts index ef405ea5508..993c32c0fca 100644 --- a/public/app/plugins/panel/heatmap/heatmap_data_converter.ts +++ b/public/app/plugins/panel/heatmap/heatmap_data_converter.ts @@ -51,6 +51,7 @@ function elasticHistogramToHeatmap(seriesList) { * @return {Array} Array of "card" objects */ function convertToCards(buckets) { + let min = 0, max = 0; let cards = []; _.forEach(buckets, xBucket => { _.forEach(xBucket.buckets, yBucket=> { @@ -62,10 +63,19 @@ function convertToCards(buckets) { count: yBucket.count, }; cards.push(card); + + if (cards.length === 1) { + min = yBucket.count; + max = yBucket.count; + } + + min = yBucket.count < min ? yBucket.count : min; + max = yBucket.count > max ? yBucket.count : max; }); }); - return cards; + let cardStats = {min, max}; + return {cards, cardStats}; } /** diff --git a/public/app/plugins/panel/heatmap/specs/renderer_specs.ts b/public/app/plugins/panel/heatmap/specs/renderer_specs.ts index 884ee1e81f4..01d09a84228 100644 --- a/public/app/plugins/panel/heatmap/specs/renderer_specs.ts +++ b/public/app/plugins/panel/heatmap/specs/renderer_specs.ts @@ -114,14 +114,8 @@ describe('grafanaHeatmap', function () { let bucketsData = convertToHeatMap(ctx.series, ctx.data.yBucketSize, ctx.data.xBucketSize, logBase); ctx.data.buckets = bucketsData; - let cardsData = convertToCards(bucketsData); - let maxCardsValue = _.max(_.map(cardsData, 'count')); - let minCardsValue = _.min(_.map(cardsData, 'count')); - let cardStats = { - max: maxCardsValue, - min: minCardsValue - }; - ctx.data.cards = cardsData; + let {cards, cardStats} = convertToCards(bucketsData); + ctx.data.cards = cards; ctx.data.cardStats = cardStats; let elemHtml = ` From 77b7f4b3767a447295778b9a0f84401a76e5b62c Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 2 Aug 2017 21:27:10 +0300 Subject: [PATCH 12/12] heatmap: add unit tests for convertToCards() --- .../specs/heatmap_data_converter_specs.ts | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) 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