mirror of
https://github.com/grafana/grafana.git
synced 2025-01-07 22:53:56 -06:00
heatmap: initial legend
This commit is contained in:
parent
c79a68dcd1
commit
2aa26c98b6
@ -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: '<div class="heatmap-color-legend"><svg width="19em" height="2em"></svg></div>',
|
||||
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;
|
||||
@ -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: '<div class="heatmap-color-legend"><svg width="19em" height="2em"></svg></div>',
|
||||
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;
|
||||
@ -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 = 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));
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -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;
|
||||
}
|
||||
|
@ -7,5 +7,8 @@
|
||||
|
||||
<div class="heatmap-panel" ng-dblclick="ctrl.zoomOut()"></div>
|
||||
</div>
|
||||
<div class="heatmap-legend-wrapper">
|
||||
<heatmap-legend></heatmap-legend>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user