From 076bfea3628861189a41c6e363d3311bbfe4f49b Mon Sep 17 00:00:00 2001 From: Tobias Skarhed Date: Fri, 10 Aug 2018 15:35:47 +0200 Subject: [PATCH] Rewrite heatmap to class --- .../app/plugins/panel/heatmap/heatmap_ctrl.ts | 2 +- public/app/plugins/panel/heatmap/rendering.ts | 696 +++++++++--------- 2 files changed, 353 insertions(+), 345 deletions(-) diff --git a/public/app/plugins/panel/heatmap/heatmap_ctrl.ts b/public/app/plugins/panel/heatmap/heatmap_ctrl.ts index 1d35ff2ea84..1749403edf0 100644 --- a/public/app/plugins/panel/heatmap/heatmap_ctrl.ts +++ b/public/app/plugins/panel/heatmap/heatmap_ctrl.ts @@ -358,6 +358,6 @@ export class HeatmapCtrl extends MetricsPanelCtrl { } link(scope, elem, attrs, ctrl) { - rendering(scope, elem, attrs, ctrl); + let render = new rendering(scope, elem, attrs, ctrl); } } diff --git a/public/app/plugins/panel/heatmap/rendering.ts b/public/app/plugins/panel/heatmap/rendering.ts index 54d17146532..d54eb5750cd 100644 --- a/public/app/plugins/panel/heatmap/rendering.ts +++ b/public/app/plugins/panel/heatmap/rendering.ts @@ -19,56 +19,91 @@ let MIN_CARD_SIZE = 1, Y_AXIS_TICK_PADDING = 5, MIN_SELECTION_WIDTH = 2; -export default function link(scope, elem, attrs, ctrl) { - let data, timeRange, panel, heatmap; +export default class Link { + width: number; + height: number; + yScale: any; + xScale: any; + chartWidth: number; + chartHeight: number; + chartTop: number; + chartBottom: number; + yAxisWidth: number; + xAxisHeight: number; + cardPadding: number; + cardRound: number; + cardWidth: number; + cardHeight: number; + colorScale: any; + opacityScale: any; + mouseUpHandler: any; + data: any; + panel: any; + $heatmap: any; + tooltip: HeatmapTooltip; + heatmap: any; + timeRange: any; - // $heatmap is JQuery object, but heatmap is D3 - let $heatmap = elem.find('.heatmap-panel'); - let tooltip = new HeatmapTooltip($heatmap, scope); + selection: any; + padding: any; + margin: any; + dataRangeWidingFactor: number; + constructor(private scope, private elem, attrs, private ctrl) { + // $heatmap is JQuery object, but heatmap is D3 + this.$heatmap = elem.find('.heatmap-panel'); + this.tooltip = new HeatmapTooltip(this.$heatmap, this.scope); - let width, - height, - yScale, - xScale, - chartWidth, - chartHeight, - chartTop, - chartBottom, - yAxisWidth, - xAxisHeight, - cardPadding, - cardRound, - cardWidth, - cardHeight, - colorScale, - opacityScale, - mouseUpHandler; + this.selection = { + active: false, + x1: -1, + x2: -1, + }; - let selection = { - active: false, - x1: -1, - x2: -1, - }; + this.padding = { left: 0, right: 0, top: 0, bottom: 0 }; + this.margin = { left: 25, right: 15, top: 10, bottom: 20 }; + this.dataRangeWidingFactor = DATA_RANGE_WIDING_FACTOR; - let padding = { left: 0, right: 0, top: 0, bottom: 0 }, - margin = { left: 25, right: 15, top: 10, bottom: 20 }, - dataRangeWidingFactor = DATA_RANGE_WIDING_FACTOR; + this.ctrl.events.on('render', this.onRender.bind(this)); - ctrl.events.on('render', () => { - render(); - ctrl.renderingCompleted(); - }); + this.ctrl.tickValueFormatter = this.tickValueFormatter; + ///////////////////////////// + // Selection and crosshair // + ///////////////////////////// - function setElementHeight() { + // Shared crosshair and tooltip + appEvents.on('graph-hover', this.onGraphHover.bind(this), this.scope); + + appEvents.on('graph-hover-clear', this.onGraphHoverClear.bind(this), this.scope); + + // Register selection listeners + this.$heatmap.on('mousedown', this.onMouseDown.bind(this)); + this.$heatmap.on('mousemove', this.onMouseMove.bind(this)); + this.$heatmap.on('mouseleave', this.onMouseLeave.bind(this)); + } + + onGraphHoverClear() { + this.clearCrosshair(); + } + + onGraphHover(event) { + this.drawSharedCrosshair(event.pos); + } + + onRender() { + this.render(); + this.ctrl.renderingCompleted(); + } + + setElementHeight() { try { - var height = ctrl.height || panel.height || ctrl.row.height; + var height = this.ctrl.height || this.panel.height || this.ctrl.row.height; if (_.isString(height)) { height = parseInt(height.replace('px', ''), 10); } - height -= panel.legend.show ? 28 : 11; // bottom padding and space for legend + height -= this.panel.legend.show ? 28 : 11; // bottom padding and space for legend - $heatmap.css('height', height + 'px'); + this.$heatmap.css('height', height + 'px'); return true; } catch (e) { @@ -77,7 +112,7 @@ export default function link(scope, elem, attrs, ctrl) { } } - function getYAxisWidth(elem) { + getYAxisWidth(elem) { let axis_text = elem.selectAll('.axis-y text').nodes(); let max_text_width = _.max( _.map(axis_text, text => { @@ -89,7 +124,7 @@ export default function link(scope, elem, attrs, ctrl) { return max_text_width; } - function getXAxisHeight(elem) { + getXAxisHeight(elem) { let axis_line = elem.select('.axis-x line'); if (!axis_line.empty()) { let axis_line_position = parseFloat(elem.select('.axis-x line').attr('y2')); @@ -101,16 +136,16 @@ export default function link(scope, elem, attrs, ctrl) { } } - function addXAxis() { - scope.xScale = xScale = d3 + addXAxis() { + this.scope.xScale = this.xScale = d3 .scaleTime() - .domain([timeRange.from, timeRange.to]) - .range([0, chartWidth]); + .domain([this.timeRange.from, this.timeRange.to]) + .range([0, this.chartWidth]); - let ticks = chartWidth / DEFAULT_X_TICK_SIZE_PX; - let grafanaTimeFormatter = ticksUtils.grafanaTimeFormat(ticks, timeRange.from, timeRange.to); + let ticks = this.chartWidth / DEFAULT_X_TICK_SIZE_PX; + let grafanaTimeFormatter = ticksUtils.grafanaTimeFormat(ticks, this.timeRange.from, this.timeRange.to); let timeFormat; - let dashboardTimeZone = ctrl.dashboard.getTimezone(); + let dashboardTimeZone = this.ctrl.dashboard.getTimezone(); if (dashboardTimeZone === 'utc') { timeFormat = d3.utcFormat(grafanaTimeFormatter); } else { @@ -118,100 +153,100 @@ export default function link(scope, elem, attrs, ctrl) { } let xAxis = d3 - .axisBottom(xScale) + .axisBottom(this.xScale) .ticks(ticks) .tickFormat(timeFormat) .tickPadding(X_AXIS_TICK_PADDING) - .tickSize(chartHeight); + .tickSize(this.chartHeight); - let posY = margin.top; - let posX = yAxisWidth; - heatmap + let posY = this.margin.top; + let posX = this.yAxisWidth; + this.heatmap .append('g') .attr('class', 'axis axis-x') .attr('transform', 'translate(' + posX + ',' + posY + ')') .call(xAxis); // Remove horizontal line in the top of axis labels (called domain in d3) - heatmap + this.heatmap .select('.axis-x') .select('.domain') .remove(); } - function addYAxis() { - let ticks = Math.ceil(chartHeight / DEFAULT_Y_TICK_SIZE_PX); - let tick_interval = ticksUtils.tickStep(data.heatmapStats.min, data.heatmapStats.max, ticks); - let { y_min, y_max } = wideYAxisRange(data.heatmapStats.min, data.heatmapStats.max, tick_interval); + addYAxis() { + let ticks = Math.ceil(this.chartHeight / DEFAULT_Y_TICK_SIZE_PX); + let tick_interval = ticksUtils.tickStep(this.data.heatmapStats.min, this.data.heatmapStats.max, ticks); + let { y_min, y_max } = this.wideYAxisRange(this.data.heatmapStats.min, this.data.heatmapStats.max, tick_interval); // Rewrite min and max if it have been set explicitly - y_min = panel.yAxis.min !== null ? panel.yAxis.min : y_min; - y_max = panel.yAxis.max !== null ? panel.yAxis.max : y_max; + y_min = this.panel.yAxis.min !== null ? this.panel.yAxis.min : y_min; + y_max = this.panel.yAxis.max !== null ? this.panel.yAxis.max : y_max; // Adjust ticks after Y range widening tick_interval = ticksUtils.tickStep(y_min, y_max, ticks); ticks = Math.ceil((y_max - y_min) / tick_interval); let decimalsAuto = ticksUtils.getPrecision(tick_interval); - let decimals = panel.yAxis.decimals === null ? decimalsAuto : panel.yAxis.decimals; + let decimals = this.panel.yAxis.decimals === null ? decimalsAuto : this.panel.yAxis.decimals; // Calculate scaledDecimals for log scales using tick size (as in jquery.flot.js) let flot_tick_size = ticksUtils.getFlotTickSize(y_min, y_max, ticks, decimalsAuto); let scaledDecimals = ticksUtils.getScaledDecimals(decimals, flot_tick_size); - ctrl.decimals = decimals; - ctrl.scaledDecimals = scaledDecimals; + this.ctrl.decimals = decimals; + this.ctrl.scaledDecimals = scaledDecimals; // Set default Y min and max if no data - if (_.isEmpty(data.buckets)) { + if (_.isEmpty(this.data.buckets)) { y_max = 1; y_min = -1; ticks = 3; decimals = 1; } - data.yAxis = { + this.data.yAxis = { min: y_min, max: y_max, ticks: ticks, }; - scope.yScale = yScale = d3 + this.scope.yScale = this.yScale = d3 .scaleLinear() .domain([y_min, y_max]) - .range([chartHeight, 0]); + .range([this.chartHeight, 0]); let yAxis = d3 - .axisLeft(yScale) + .axisLeft(this.yScale) .ticks(ticks) - .tickFormat(tickValueFormatter(decimals, scaledDecimals)) - .tickSizeInner(0 - width) + .tickFormat(this.tickValueFormatter(decimals, scaledDecimals)) + .tickSizeInner(0 - this.width) .tickSizeOuter(0) .tickPadding(Y_AXIS_TICK_PADDING); - heatmap + this.heatmap .append('g') .attr('class', 'axis axis-y') .call(yAxis); // Calculate Y axis width first, then move axis into visible area - let posY = margin.top; - let posX = getYAxisWidth(heatmap) + Y_AXIS_TICK_PADDING; - heatmap.select('.axis-y').attr('transform', 'translate(' + posX + ',' + posY + ')'); + let posY = this.margin.top; + let posX = this.getYAxisWidth(this.heatmap) + Y_AXIS_TICK_PADDING; + this.heatmap.select('.axis-y').attr('transform', 'translate(' + posX + ',' + posY + ')'); // Remove vertical line in the right of axis labels (called domain in d3) - heatmap + this.heatmap .select('.axis-y') .select('.domain') .remove(); } // Wide Y values range and anjust to bucket size - function wideYAxisRange(min, max, tickInterval) { - let y_widing = (max * (dataRangeWidingFactor - 1) - min * (dataRangeWidingFactor - 1)) / 2; + wideYAxisRange(min, max, tickInterval) { + let y_widing = (max * (this.dataRangeWidingFactor - 1) - min * (this.dataRangeWidingFactor - 1)) / 2; let y_min, y_max; if (tickInterval === 0) { - y_max = max * dataRangeWidingFactor; - y_min = min - min * (dataRangeWidingFactor - 1); + y_max = max * this.dataRangeWidingFactor; + y_min = min - min * (this.dataRangeWidingFactor - 1); tickInterval = (y_max - y_min) / 2; } else { y_max = Math.ceil((max + y_widing) / tickInterval) * tickInterval; @@ -226,152 +261,153 @@ export default function link(scope, elem, attrs, ctrl) { return { y_min, y_max }; } - function addLogYAxis() { - let log_base = panel.yAxis.logBase; - let { y_min, y_max } = adjustLogRange(data.heatmapStats.minLog, data.heatmapStats.max, log_base); + addLogYAxis() { + let log_base = this.panel.yAxis.logBase; + let { y_min, y_max } = this.adjustLogRange(this.data.heatmapStats.minLog, this.data.heatmapStats.max, log_base); - y_min = panel.yAxis.min && panel.yAxis.min !== '0' ? adjustLogMin(panel.yAxis.min, log_base) : y_min; - y_max = panel.yAxis.max !== null ? adjustLogMax(panel.yAxis.max, log_base) : y_max; + y_min = + this.panel.yAxis.min && this.panel.yAxis.min !== '0' ? this.adjustLogMin(this.panel.yAxis.min, log_base) : y_min; + y_max = this.panel.yAxis.max !== null ? this.adjustLogMax(this.panel.yAxis.max, log_base) : y_max; // Set default Y min and max if no data - if (_.isEmpty(data.buckets)) { + if (_.isEmpty(this.data.buckets)) { y_max = Math.pow(log_base, 2); y_min = 1; } - scope.yScale = yScale = d3 + this.scope.yScale = this.yScale = d3 .scaleLog() - .base(panel.yAxis.logBase) + .base(this.panel.yAxis.logBase) .domain([y_min, y_max]) - .range([chartHeight, 0]); + .range([this.chartHeight, 0]); - let domain = yScale.domain(); - let tick_values = logScaleTickValues(domain, log_base); + let domain = this.yScale.domain(); + let tick_values = this.logScaleTickValues(domain, log_base); let decimalsAuto = ticksUtils.getPrecision(y_min); - let decimals = panel.yAxis.decimals || decimalsAuto; + let decimals = this.panel.yAxis.decimals || decimalsAuto; // Calculate scaledDecimals for log scales using tick size (as in jquery.flot.js) let flot_tick_size = ticksUtils.getFlotTickSize(y_min, y_max, tick_values.length, decimalsAuto); let scaledDecimals = ticksUtils.getScaledDecimals(decimals, flot_tick_size); - ctrl.decimals = decimals; - ctrl.scaledDecimals = scaledDecimals; + this.ctrl.decimals = decimals; + this.ctrl.scaledDecimals = scaledDecimals; - data.yAxis = { + this.data.yAxis = { min: y_min, max: y_max, ticks: tick_values.length, }; let yAxis = d3 - .axisLeft(yScale) + .axisLeft(this.yScale) .tickValues(tick_values) - .tickFormat(tickValueFormatter(decimals, scaledDecimals)) - .tickSizeInner(0 - width) + .tickFormat(this.tickValueFormatter(decimals, scaledDecimals)) + .tickSizeInner(0 - this.width) .tickSizeOuter(0) .tickPadding(Y_AXIS_TICK_PADDING); - heatmap + this.heatmap .append('g') .attr('class', 'axis axis-y') .call(yAxis); // Calculate Y axis width first, then move axis into visible area - let posY = margin.top; - let posX = getYAxisWidth(heatmap) + Y_AXIS_TICK_PADDING; - heatmap.select('.axis-y').attr('transform', 'translate(' + posX + ',' + posY + ')'); + let posY = this.margin.top; + let posX = this.getYAxisWidth(this.heatmap) + Y_AXIS_TICK_PADDING; + this.heatmap.select('.axis-y').attr('transform', 'translate(' + posX + ',' + posY + ')'); // Set first tick as pseudo 0 if (y_min < 1) { - heatmap + this.heatmap .select('.axis-y') .select('.tick text') .text('0'); } // Remove vertical line in the right of axis labels (called domain in d3) - heatmap + this.heatmap .select('.axis-y') .select('.domain') .remove(); } - function addYAxisFromBuckets() { - const tsBuckets = data.tsBuckets; + addYAxisFromBuckets() { + const tsBuckets = this.data.tsBuckets; - scope.yScale = yScale = d3 + this.scope.yScale = this.yScale = d3 .scaleLinear() .domain([0, tsBuckets.length - 1]) - .range([chartHeight, 0]); + .range([this.chartHeight, 0]); const tick_values = _.map(tsBuckets, (b, i) => i); const decimalsAuto = _.max(_.map(tsBuckets, ticksUtils.getStringPrecision)); - const decimals = panel.yAxis.decimals === null ? decimalsAuto : panel.yAxis.decimals; - ctrl.decimals = decimals; + const decimals = this.panel.yAxis.decimals === null ? decimalsAuto : this.panel.yAxis.decimals; + this.ctrl.decimals = decimals; function tickFormatter(valIndex) { let valueFormatted = tsBuckets[valIndex]; if (!_.isNaN(_.toNumber(valueFormatted)) && valueFormatted !== '') { // Try to format numeric tick labels - valueFormatted = tickValueFormatter(decimals)(_.toNumber(valueFormatted)); + valueFormatted = this.tickValueFormatter(decimals)(_.toNumber(valueFormatted)); } return valueFormatted; } const tsBucketsFormatted = _.map(tsBuckets, (v, i) => tickFormatter(i)); - data.tsBucketsFormatted = tsBucketsFormatted; + this.data.tsBucketsFormatted = tsBucketsFormatted; let yAxis = d3 - .axisLeft(yScale) + .axisLeft(this.yScale) .tickValues(tick_values) .tickFormat(tickFormatter) - .tickSizeInner(0 - width) + .tickSizeInner(0 - this.width) .tickSizeOuter(0) .tickPadding(Y_AXIS_TICK_PADDING); - heatmap + this.heatmap .append('g') .attr('class', 'axis axis-y') .call(yAxis); // Calculate Y axis width first, then move axis into visible area - const posY = margin.top; - const posX = getYAxisWidth(heatmap) + Y_AXIS_TICK_PADDING; - heatmap.select('.axis-y').attr('transform', 'translate(' + posX + ',' + posY + ')'); + const posY = this.margin.top; + const posX = this.getYAxisWidth(this.heatmap) + Y_AXIS_TICK_PADDING; + this.heatmap.select('.axis-y').attr('transform', 'translate(' + posX + ',' + posY + ')'); // Remove vertical line in the right of axis labels (called domain in d3) - heatmap + this.heatmap .select('.axis-y') .select('.domain') .remove(); } // Adjust data range to log base - function adjustLogRange(min, max, logBase) { + adjustLogRange(min, max, logBase) { let y_min, y_max; - y_min = data.heatmapStats.minLog; - if (data.heatmapStats.minLog > 1 || !data.heatmapStats.minLog) { + y_min = this.data.heatmapStats.minLog; + if (this.data.heatmapStats.minLog > 1 || !this.data.heatmapStats.minLog) { y_min = 1; } else { - y_min = adjustLogMin(data.heatmapStats.minLog, logBase); + y_min = this.adjustLogMin(this.data.heatmapStats.minLog, logBase); } // Adjust max Y value to log base - y_max = adjustLogMax(data.heatmapStats.max, logBase); + y_max = this.adjustLogMax(this.data.heatmapStats.max, logBase); return { y_min, y_max }; } - function adjustLogMax(max, base) { + adjustLogMax(max, base) { return Math.pow(base, Math.ceil(ticksUtils.logp(max, base))); } - function adjustLogMin(min, base) { + adjustLogMin(min, base) { return Math.pow(base, Math.floor(ticksUtils.logp(min, base))); } - function logScaleTickValues(domain, base) { + logScaleTickValues(domain, base) { let domainMin = domain[0]; let domainMax = domain[1]; let tickValues = []; @@ -393,8 +429,8 @@ export default function link(scope, elem, attrs, ctrl) { return tickValues; } - function tickValueFormatter(decimals, scaledDecimals = null) { - let format = panel.yAxis.format; + tickValueFormatter(decimals, scaledDecimals = null) { + let format = this.panel.yAxis.format; return function(value) { try { return format !== 'none' ? kbn.valueFormats[format](value, decimals, scaledDecimals) : value; @@ -405,181 +441,179 @@ export default function link(scope, elem, attrs, ctrl) { }; } - ctrl.tickValueFormatter = tickValueFormatter; - - function fixYAxisTickSize() { - heatmap + fixYAxisTickSize() { + this.heatmap .select('.axis-y') .selectAll('.tick line') - .attr('x2', chartWidth); + .attr('x2', this.chartWidth); } - function addAxes() { - chartHeight = height - margin.top - margin.bottom; - chartTop = margin.top; - chartBottom = chartTop + chartHeight; + addAxes() { + this.chartHeight = this.height - this.margin.top - this.margin.bottom; + this.chartTop = this.margin.top; + this.chartBottom = this.chartTop + this.chartHeight; - if (panel.dataFormat === 'tsbuckets') { - addYAxisFromBuckets(); + if (this.panel.dataFormat === 'tsbuckets') { + this.addYAxisFromBuckets(); } else { - if (panel.yAxis.logBase === 1) { - addYAxis(); + if (this.panel.yAxis.logBase === 1) { + this.addYAxis(); } else { - addLogYAxis(); + this.addLogYAxis(); } } - yAxisWidth = getYAxisWidth(heatmap) + Y_AXIS_TICK_PADDING; - chartWidth = width - yAxisWidth - margin.right; - fixYAxisTickSize(); + this.yAxisWidth = this.getYAxisWidth(this.heatmap) + Y_AXIS_TICK_PADDING; + this.chartWidth = this.width - this.yAxisWidth - this.margin.right; + this.fixYAxisTickSize(); - addXAxis(); - xAxisHeight = getXAxisHeight(heatmap); + this.addXAxis(); + this.xAxisHeight = this.getXAxisHeight(this.heatmap); - if (!panel.yAxis.show) { - heatmap + if (!this.panel.yAxis.show) { + this.heatmap .select('.axis-y') .selectAll('line') .style('opacity', 0); } - if (!panel.xAxis.show) { - heatmap + if (!this.panel.xAxis.show) { + this.heatmap .select('.axis-x') .selectAll('line') .style('opacity', 0); } } - function addHeatmapCanvas() { - let heatmap_elem = $heatmap[0]; + addHeatmapCanvas() { + let heatmap_elem = this.$heatmap[0]; - width = Math.floor($heatmap.width()) - padding.right; - height = Math.floor($heatmap.height()) - padding.bottom; + this.width = Math.floor(this.$heatmap.width()) - this.padding.right; + this.height = Math.floor(this.$heatmap.height()) - this.padding.bottom; - cardPadding = panel.cards.cardPadding !== null ? panel.cards.cardPadding : CARD_PADDING; - cardRound = panel.cards.cardRound !== null ? panel.cards.cardRound : CARD_ROUND; + this.cardPadding = this.panel.cards.cardPadding !== null ? this.panel.cards.cardPadding : CARD_PADDING; + this.cardRound = this.panel.cards.cardRound !== null ? this.panel.cards.cardRound : CARD_ROUND; - if (heatmap) { - heatmap.remove(); + if (this.heatmap) { + this.heatmap.remove(); } - heatmap = d3 + this.heatmap = d3 .select(heatmap_elem) .append('svg') - .attr('width', width) - .attr('height', height); + .attr('width', this.width) + .attr('height', this.height); } - function addHeatmap() { - addHeatmapCanvas(); - addAxes(); + addHeatmap() { + this.addHeatmapCanvas(); + this.addAxes(); - if (panel.yAxis.logBase !== 1 && panel.dataFormat !== 'tsbuckets') { - let log_base = panel.yAxis.logBase; - let domain = yScale.domain(); - let tick_values = logScaleTickValues(domain, log_base); - data.buckets = mergeZeroBuckets(data.buckets, _.min(tick_values)); + if (this.panel.yAxis.logBase !== 1 && this.panel.dataFormat !== 'tsbuckets') { + let log_base = this.panel.yAxis.logBase; + let domain = this.yScale.domain(); + let tick_values = this.logScaleTickValues(domain, log_base); + this.data.buckets = mergeZeroBuckets(this.data.buckets, _.min(tick_values)); } - let cardsData = data.cards; - let maxValueAuto = data.cardStats.max; - let maxValue = panel.color.max || maxValueAuto; - let minValue = panel.color.min || 0; + let cardsData = this.data.cards; + let maxValueAuto = this.data.cardStats.max; + let maxValue = this.panel.color.max || maxValueAuto; + let minValue = this.panel.color.min || 0; - let colorScheme = _.find(ctrl.colorSchemes, { - value: panel.color.colorScheme, + let colorScheme = _.find(this.ctrl.colorSchemes, { + value: this.panel.color.colorScheme, }); - colorScale = getColorScale(colorScheme, contextSrv.user.lightTheme, maxValue, minValue); - opacityScale = getOpacityScale(panel.color, maxValue); - setCardSize(); + this.colorScale = getColorScale(colorScheme, contextSrv.user.lightTheme, maxValue, minValue); + this.opacityScale = getOpacityScale(this.panel.color, maxValue); + this.setCardSize(); - let cards = heatmap.selectAll('.heatmap-card').data(cardsData); + let cards = this.heatmap.selectAll('.heatmap-card').data(cardsData); cards.append('title'); cards = cards .enter() .append('rect') - .attr('x', getCardX) - .attr('width', getCardWidth) - .attr('y', getCardY) - .attr('height', getCardHeight) - .attr('rx', cardRound) - .attr('ry', cardRound) + .attr('x', this.getCardX) + .attr('width', this.getCardWidth) + .attr('y', this.getCardY) + .attr('height', this.getCardHeight) + .attr('rx', this.cardRound) + .attr('ry', this.cardRound) .attr('class', 'bordered heatmap-card') - .style('fill', getCardColor) - .style('stroke', getCardColor) + .style('fill', this.getCardColor) + .style('stroke', this.getCardColor) .style('stroke-width', 0) - .style('opacity', getCardOpacity); + .style('opacity', this.getCardOpacity); - let $cards = $heatmap.find('.heatmap-card'); + let $cards = this.$heatmap.find('.heatmap-card'); $cards .on('mouseenter', event => { - tooltip.mouseOverBucket = true; - highlightCard(event); + this.tooltip.mouseOverBucket = true; + this.highlightCard(event); }) .on('mouseleave', event => { - tooltip.mouseOverBucket = false; - resetCardHighLight(event); + this.tooltip.mouseOverBucket = false; + this.resetCardHighLight(event); }); } - function highlightCard(event) { + highlightCard(event) { let color = d3.select(event.target).style('fill'); let highlightColor = d3.color(color).darker(2); let strokeColor = d3.color(color).brighter(4); let current_card = d3.select(event.target); - tooltip.originalFillColor = color; + this.tooltip.originalFillColor = color; current_card .style('fill', highlightColor.toString()) .style('stroke', strokeColor.toString()) .style('stroke-width', 1); } - function resetCardHighLight(event) { + resetCardHighLight(event) { d3 .select(event.target) - .style('fill', tooltip.originalFillColor) - .style('stroke', tooltip.originalFillColor) + .style('fill', this.tooltip.originalFillColor) + .style('stroke', this.tooltip.originalFillColor) .style('stroke-width', 0); } - function setCardSize() { - let xGridSize = Math.floor(xScale(data.xBucketSize) - xScale(0)); - let yGridSize = Math.floor(yScale(yScale.invert(0) - data.yBucketSize)); + setCardSize() { + let xGridSize = Math.floor(this.xScale(this.data.xBucketSize) - this.xScale(0)); + let yGridSize = Math.floor(this.yScale(this.yScale.invert(0) - this.data.yBucketSize)); - if (panel.yAxis.logBase !== 1) { - let base = panel.yAxis.logBase; - let splitFactor = data.yBucketSize || 1; - yGridSize = Math.floor((yScale(1) - yScale(base)) / splitFactor); + if (this.panel.yAxis.logBase !== 1) { + let base = this.panel.yAxis.logBase; + let splitFactor = this.data.yBucketSize || 1; + yGridSize = Math.floor((this.yScale(1) - this.yScale(base)) / splitFactor); } - cardWidth = xGridSize - cardPadding * 2; - cardHeight = yGridSize ? yGridSize - cardPadding * 2 : 0; + this.cardWidth = xGridSize - this.cardPadding * 2; + this.cardHeight = yGridSize ? yGridSize - this.cardPadding * 2 : 0; } - function getCardX(d) { + getCardX(d) { let x; - if (xScale(d.x) < 0) { + if (this.xScale(d.x) < 0) { // Cut card left to prevent overlay - x = yAxisWidth + cardPadding; + x = this.yAxisWidth + this.cardPadding; } else { - x = xScale(d.x) + yAxisWidth + cardPadding; + x = this.xScale(d.x) + this.yAxisWidth + this.cardPadding; } return x; } - function getCardWidth(d) { + getCardWidth(d) { let w; - if (xScale(d.x) < 0) { + if (this.xScale(d.x) < 0) { // Cut card left to prevent overlay - let cutted_width = xScale(d.x) + cardWidth; + let cutted_width = this.xScale(d.x) + this.cardWidth; w = cutted_width > 0 ? cutted_width : 0; - } else if (xScale(d.x) + cardWidth > chartWidth) { + } else if (this.xScale(d.x) + this.cardWidth > this.chartWidth) { // Cut card right to prevent overlay - w = chartWidth - xScale(d.x) - cardPadding; + w = this.chartWidth - this.xScale(d.x) - this.cardPadding; } else { - w = cardWidth; + w = this.cardWidth; } // Card width should be MIN_CARD_SIZE at least @@ -587,138 +621,117 @@ export default function link(scope, elem, attrs, ctrl) { return w; } - function getCardY(d) { - let y = yScale(d.y) + chartTop - cardHeight - cardPadding; - if (panel.yAxis.logBase !== 1 && d.y === 0) { - y = chartBottom - cardHeight - cardPadding; + getCardY(d) { + let y = this.yScale(d.y) + this.chartTop - this.cardHeight - this.cardPadding; + if (this.panel.yAxis.logBase !== 1 && d.y === 0) { + y = this.chartBottom - this.cardHeight - this.cardPadding; } else { - if (y < chartTop) { - y = chartTop; + if (y < this.chartTop) { + y = this.chartTop; } } return y; } - function getCardHeight(d) { - let y = yScale(d.y) + chartTop - cardHeight - cardPadding; - let h = cardHeight; + getCardHeight(d) { + let y = this.yScale(d.y) + this.chartTop - this.cardHeight - this.cardPadding; + let h = this.cardHeight; - if (panel.yAxis.logBase !== 1 && d.y === 0) { - return cardHeight; + if (this.panel.yAxis.logBase !== 1 && d.y === 0) { + return this.cardHeight; } // Cut card height to prevent overlay - if (y < chartTop) { - h = yScale(d.y) - cardPadding; - } else if (yScale(d.y) > chartBottom) { - h = chartBottom - y; - } else if (y + cardHeight > chartBottom) { - h = chartBottom - y; + if (y < this.chartTop) { + h = this.yScale(d.y) - this.cardPadding; + } else if (this.yScale(d.y) > this.chartBottom) { + h = this.chartBottom - y; + } else if (y + this.cardHeight > this.chartBottom) { + h = this.chartBottom - y; } // Height can't be more than chart height - h = Math.min(h, chartHeight); + h = Math.min(h, this.chartHeight); // Card height should be MIN_CARD_SIZE at least h = Math.max(h, MIN_CARD_SIZE); return h; } - function getCardColor(d) { - if (panel.color.mode === 'opacity') { - return panel.color.cardColor; + getCardColor(d) { + if (this.panel.color.mode === 'opacity') { + return this.panel.color.cardColor; } else { - return colorScale(d.count); + return this.colorScale(d.count); } } - function getCardOpacity(d) { - if (panel.color.mode === 'opacity') { - return opacityScale(d.count); + getCardOpacity(d) { + if (this.panel.color.mode === 'opacity') { + return this.opacityScale(d.count); } else { return 1; } } - ///////////////////////////// - // Selection and crosshair // - ///////////////////////////// + onMouseDown(event) { + this.selection.active = true; + this.selection.x1 = event.offsetX; - // Shared crosshair and tooltip - appEvents.on( - 'graph-hover', - event => { - drawSharedCrosshair(event.pos); - }, - scope - ); - - appEvents.on( - 'graph-hover-clear', - () => { - clearCrosshair(); - }, - scope - ); - - function onMouseDown(event) { - selection.active = true; - selection.x1 = event.offsetX; - - mouseUpHandler = function() { - onMouseUp(); + this.mouseUpHandler = () => { + this.onMouseUp(); }; - $(document).one('mouseup', mouseUpHandler); + $(document).one('mouseup', this.mouseUpHandler); } - function onMouseUp() { - $(document).unbind('mouseup', mouseUpHandler); - mouseUpHandler = null; - selection.active = false; + onMouseUp() { + $(document).unbind('mouseup', this.mouseUpHandler); + this.mouseUpHandler = null; + this.selection.active = false; - let selectionRange = Math.abs(selection.x2 - selection.x1); - if (selection.x2 >= 0 && selectionRange > MIN_SELECTION_WIDTH) { - let timeFrom = xScale.invert(Math.min(selection.x1, selection.x2) - yAxisWidth); - let timeTo = xScale.invert(Math.max(selection.x1, selection.x2) - yAxisWidth); + let selectionRange = Math.abs(this.selection.x2 - this.selection.x1); + if (this.selection.x2 >= 0 && selectionRange > MIN_SELECTION_WIDTH) { + let timeFrom = this.xScale.invert(Math.min(this.selection.x1, this.selection.x2) - this.yAxisWidth); + let timeTo = this.xScale.invert(Math.max(this.selection.x1, this.selection.x2) - this.yAxisWidth); - ctrl.timeSrv.setTime({ + this.ctrl.timeSrv.setTime({ from: moment.utc(timeFrom), to: moment.utc(timeTo), }); } - clearSelection(); + this.clearSelection(); } - function onMouseLeave() { + onMouseLeave() { appEvents.emit('graph-hover-clear'); - clearCrosshair(); + this.clearCrosshair(); } - function onMouseMove(event) { - if (!heatmap) { + onMouseMove(event) { + if (!this.heatmap) { return; } - if (selection.active) { + if (this.selection.active) { // Clear crosshair and tooltip - clearCrosshair(); - tooltip.destroy(); + this.clearCrosshair(); + this.tooltip.destroy(); - selection.x2 = limitSelection(event.offsetX); - drawSelection(selection.x1, selection.x2); + this.selection.x2 = this.limitSelection(event.offsetX); + this.drawSelection(this.selection.x1, this.selection.x2); } else { - emitGraphHoverEvent(event); - drawCrosshair(event.offsetX); - tooltip.show(event, data); + this.emitGraphHoverEvent(event); + this.drawCrosshair(event.offsetX); + this.tooltip.show(event, this.data); } } - function emitGraphHoverEvent(event) { - let x = xScale.invert(event.offsetX - yAxisWidth).valueOf(); - let y = yScale.invert(event.offsetY); + emitGraphHoverEvent(event) { + let x = this.xScale.invert(event.offsetX - this.yAxisWidth).valueOf(); + let y = this.yScale.invert(event.offsetY); let pos = { pageX: event.pageX, pageY: event.pageY, @@ -730,105 +743,100 @@ export default function link(scope, elem, attrs, ctrl) { }; // Set minimum offset to prevent showing legend from another panel - pos.panelRelY = Math.max(event.offsetY / height, 0.001); + pos.panelRelY = Math.max(event.offsetY / this.height, 0.001); // broadcast to other graph panels that we are hovering - appEvents.emit('graph-hover', { pos: pos, panel: panel }); + appEvents.emit('graph-hover', { pos: pos, panel: this.panel }); } - function limitSelection(x2) { - x2 = Math.max(x2, yAxisWidth); - x2 = Math.min(x2, chartWidth + yAxisWidth); + limitSelection(x2) { + x2 = Math.max(x2, this.yAxisWidth); + x2 = Math.min(x2, this.chartWidth + this.yAxisWidth); return x2; } - function drawSelection(posX1, posX2) { - if (heatmap) { - heatmap.selectAll('.heatmap-selection').remove(); + drawSelection(posX1, posX2) { + if (this.heatmap) { + this.heatmap.selectAll('.heatmap-selection').remove(); let selectionX = Math.min(posX1, posX2); let selectionWidth = Math.abs(posX1 - posX2); if (selectionWidth > MIN_SELECTION_WIDTH) { - heatmap + this.heatmap .append('rect') .attr('class', 'heatmap-selection') .attr('x', selectionX) .attr('width', selectionWidth) - .attr('y', chartTop) - .attr('height', chartHeight); + .attr('y', this.chartTop) + .attr('height', this.chartHeight); } } } - function clearSelection() { - selection.x1 = -1; - selection.x2 = -1; + clearSelection() { + this.selection.x1 = -1; + this.selection.x2 = -1; - if (heatmap) { - heatmap.selectAll('.heatmap-selection').remove(); + if (this.heatmap) { + this.heatmap.selectAll('.heatmap-selection').remove(); } } - function drawCrosshair(position) { - if (heatmap) { - heatmap.selectAll('.heatmap-crosshair').remove(); + drawCrosshair(position) { + if (this.heatmap) { + this.heatmap.selectAll('.heatmap-crosshair').remove(); let posX = position; - posX = Math.max(posX, yAxisWidth); - posX = Math.min(posX, chartWidth + yAxisWidth); + posX = Math.max(posX, this.yAxisWidth); + posX = Math.min(posX, this.chartWidth + this.yAxisWidth); - heatmap + this.heatmap .append('g') .attr('class', 'heatmap-crosshair') .attr('transform', 'translate(' + posX + ',0)') .append('line') .attr('x1', 1) - .attr('y1', chartTop) + .attr('y1', this.chartTop) .attr('x2', 1) - .attr('y2', chartBottom) + .attr('y2', this.chartBottom) .attr('stroke-width', 1); } } - function drawSharedCrosshair(pos) { - if (heatmap && ctrl.dashboard.graphTooltip !== 0) { - let posX = xScale(pos.x) + yAxisWidth; - drawCrosshair(posX); + drawSharedCrosshair(pos) { + if (this.heatmap && this.ctrl.dashboard.graphTooltip !== 0) { + let posX = this.xScale(pos.x) + this.yAxisWidth; + this.drawCrosshair(posX); } } - function clearCrosshair() { - if (heatmap) { - heatmap.selectAll('.heatmap-crosshair').remove(); + clearCrosshair() { + if (this.heatmap) { + this.heatmap.selectAll('.heatmap-crosshair').remove(); } } - function render() { - data = ctrl.data; - panel = ctrl.panel; - timeRange = ctrl.range; + render() { + this.data = this.ctrl.data; + this.panel = this.ctrl.panel; + this.timeRange = this.ctrl.range; - if (!setElementHeight() || !data) { + if (!this.setElementHeight() || !this.data) { return; } // Draw default axes and return if no data - if (_.isEmpty(data.buckets)) { - addHeatmapCanvas(); - addAxes(); + if (_.isEmpty(this.data.buckets)) { + this.addHeatmapCanvas(); + this.addAxes(); return; } - addHeatmap(); - scope.yAxisWidth = yAxisWidth; - scope.xAxisHeight = xAxisHeight; - scope.chartHeight = chartHeight; - scope.chartWidth = chartWidth; - scope.chartTop = chartTop; + this.addHeatmap(); + this.scope.yAxisWidth = this.yAxisWidth; + this.scope.xAxisHeight = this.xAxisHeight; + this.scope.chartHeight = this.chartHeight; + this.scope.chartWidth = this.chartWidth; + this.scope.chartTop = this.chartTop; } - - // Register selection listeners - $heatmap.on('mousedown', onMouseDown); - $heatmap.on('mousemove', onMouseMove); - $heatmap.on('mouseleave', onMouseLeave); }