mirror of https://github.com/grafana/grafana
Merge branch 'feat-8539' of https://github.com/alexanderzobnin/grafana
commit
1507c02ebb
@ -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; |
||||
} |
Loading…
Reference in new issue