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