mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'feat-8539' of https://github.com/alexanderzobnin/grafana
This commit is contained in:
commit
1507c02ebb
300
public/app/plugins/panel/heatmap/color_legend.ts
Normal file
300
public/app/plugins/panel/heatmap/color_legend.ts
Normal file
@ -0,0 +1,300 @@
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
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: '<div class="heatmap-color-legend"><svg width="16.8rem" height="24px"></svg></div>',
|
||||
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);
|
||||
drawSimpleColorLegend(elem, colorScale);
|
||||
} else if (panel.color.mode === 'opacity') {
|
||||
let colorOptions = panel.color;
|
||||
drawSimpleOpacityLegend(elem, colorOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Heatmap legend with scale values.
|
||||
*/
|
||||
module.directive('heatmapLegend', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: '<div class="heatmap-color-legend"><svg width="100px" height="14px"></svg></div>',
|
||||
link: function(scope, elem, attrs) {
|
||||
let ctrl = scope.ctrl;
|
||||
let panel = scope.ctrl.panel;
|
||||
|
||||
render();
|
||||
ctrl.events.on('render', function() {
|
||||
render();
|
||||
});
|
||||
|
||||
function render() {
|
||||
clearLegend(elem);
|
||||
if (!_.isEmpty(ctrl.data) && !_.isEmpty(ctrl.data.cards)) {
|
||||
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});
|
||||
drawColorLegend(elem, colorScheme, rangeFrom, rangeTo, maxValue, minValue);
|
||||
} else if (panel.color.mode === 'opacity') {
|
||||
let colorOptions = panel.color;
|
||||
drawOpacityLegend(elem, colorOptions, 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;
|
||||
let legendHeight = legendElem.attr("height");
|
||||
|
||||
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 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
|
||||
.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');
|
||||
let legend = d3.select(legendElem.get(0));
|
||||
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 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 => opacityScale(d));
|
||||
|
||||
drawLegendValues(elem, opacityScale, 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));
|
||||
|
||||
if (legendWidth <= 0 || legendElem.get(0).childNodes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
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(2);
|
||||
|
||||
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 + ")")
|
||||
.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 drawSimpleOpacityLegend(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");
|
||||
|
||||
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 = 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));
|
||||
}
|
||||
}
|
||||
|
||||
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' ||
|
||||
(colorScheme.invert === 'dark' && !contextSrv.user.lightTheme);
|
||||
|
||||
let start = colorScaleInverted ? maxValue : minValue;
|
||||
let end = colorScaleInverted ? minValue : maxValue;
|
||||
|
||||
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) {
|
||||
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) {
|
||||
let range = rangeTo - rangeFrom;
|
||||
let tickStepSize = tickStep(rangeFrom, rangeTo, 3);
|
||||
let ticksNum = Math.round(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;
|
||||
}
|
@ -1,4 +1,10 @@
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
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;
|
||||
|
@ -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;
|
||||
@ -26,6 +26,9 @@ let panelDefaults = {
|
||||
exponent: 0.5,
|
||||
colorScheme: 'interpolateOranges',
|
||||
},
|
||||
legend: {
|
||||
show: false
|
||||
},
|
||||
dataFormat: 'timeseries',
|
||||
xAxis: {
|
||||
show: true,
|
||||
@ -188,11 +191,15 @@ export class HeatmapCtrl extends MetricsPanelCtrl {
|
||||
yBucketSize = 1;
|
||||
}
|
||||
|
||||
let {cards, cardStats} = convertToCards(bucketsData);
|
||||
|
||||
this.data = {
|
||||
buckets: bucketsData,
|
||||
heatmapStats: heatmapStats,
|
||||
xBucketSize: xBucketSize,
|
||||
yBucketSize: yBucketSize
|
||||
yBucketSize: yBucketSize,
|
||||
cards: cards,
|
||||
cardStats: cardStats
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -7,5 +7,8 @@
|
||||
|
||||
<div class="heatmap-panel" ng-dblclick="ctrl.zoomOut()"></div>
|
||||
</div>
|
||||
<div class="heatmap-legend-wrapper" ng-if="ctrl.panel.legend.show">
|
||||
<heatmap-legend></heatmap-legend>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import './color_legend';
|
||||
import {HeatmapCtrl} from './heatmap_ctrl';
|
||||
|
||||
export {
|
||||
|
@ -25,9 +25,6 @@
|
||||
<label class="gf-form-label width-9">Exponent</label>
|
||||
<input type="number" class="gf-form-input width-8" placeholder="auto" data-placement="right" bs-tooltip="''" ng-model="ctrl.panel.color.exponent" ng-change="ctrl.refresh()" ng-model-onblur>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<svg id="heatmap-opacity-legend" width="19em" height="2em"></svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-show="ctrl.panel.color.mode === 'spectrum'">
|
||||
@ -37,10 +34,31 @@
|
||||
<select class="input-small gf-form-input" ng-model="ctrl.panel.color.colorScheme" ng-options="s.value as s.name for s in ctrl.colorSchemes" ng-change="ctrl.render()"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<svg id="heatmap-color-legend" width="19em" height="2em"></svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<color-legend></color-legend>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section gf-form-group">
|
||||
<h5 class="section-heading">Color scale</h5>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-8">Min</label>
|
||||
<input type="number" ng-model="ctrl.panel.color.min" class="gf-form-input width-5" placeholder="auto" data-placement="right" bs-tooltip="''" ng-change="ctrl.refresh()" ng-model-onblur>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-8">Max</label>
|
||||
<input type="number" ng-model="ctrl.panel.color.max" class="gf-form-input width-5" placeholder="auto" data-placement="right" bs-tooltip="''" ng-change="ctrl.refresh()" ng-model-onblur>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section gf-form-group">
|
||||
<h5 class="section-heading">Legend</h5>
|
||||
<gf-form-switch class="gf-form" label-class="width-8"
|
||||
label="Show legend"
|
||||
checked="ctrl.panel.legend.show" on-change="ctrl.render()">
|
||||
</gf-form-switch>
|
||||
</div>
|
||||
|
||||
<div class="section gf-form-group">
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 = `
|
||||
<div class="heatmap-wrapper">
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user