| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860 |
- import _ from 'lodash';
- import $ from 'jquery';
- import moment from 'moment';
- import * as d3 from 'd3';
- import kbn from 'app/core/utils/kbn';
- import { appEvents, contextSrv } from 'app/core/core';
- import { tickStep, getScaledDecimals, getFlotTickSize } from 'app/core/utils/ticks';
- import { HeatmapTooltip } from './heatmap_tooltip';
- import { mergeZeroBuckets } from './heatmap_data_converter';
- import { getColorScale, getOpacityScale } from './color_scale';
- let MIN_CARD_SIZE = 1,
- CARD_PADDING = 1,
- CARD_ROUND = 0,
- DATA_RANGE_WIDING_FACTOR = 1.2,
- DEFAULT_X_TICK_SIZE_PX = 100,
- DEFAULT_Y_TICK_SIZE_PX = 50,
- X_AXIS_TICK_PADDING = 10,
- Y_AXIS_TICK_PADDING = 5,
- MIN_SELECTION_WIDTH = 2;
- export default function link(scope, elem, attrs, ctrl) {
- let data, timeRange, panel, heatmap;
- // $heatmap is JQuery object, but heatmap is D3
- let $heatmap = elem.find('.heatmap-panel');
- let tooltip = new HeatmapTooltip($heatmap, scope);
- let width,
- height,
- yScale,
- xScale,
- chartWidth,
- chartHeight,
- chartTop,
- chartBottom,
- yAxisWidth,
- xAxisHeight,
- cardPadding,
- cardRound,
- cardWidth,
- cardHeight,
- colorScale,
- opacityScale,
- mouseUpHandler;
- let selection = {
- active: false,
- x1: -1,
- x2: -1,
- };
- let padding = { left: 0, right: 0, top: 0, bottom: 0 },
- margin = { left: 25, right: 15, top: 10, bottom: 20 },
- dataRangeWidingFactor = DATA_RANGE_WIDING_FACTOR;
- ctrl.events.on('render', () => {
- render();
- ctrl.renderingCompleted();
- });
- function setElementHeight() {
- try {
- var height = ctrl.height || panel.height || ctrl.row.height;
- if (_.isString(height)) {
- height = parseInt(height.replace('px', ''), 10);
- }
- height -= panel.legend.show ? 28 : 11; // bottom padding and space for legend
- $heatmap.css('height', height + 'px');
- return true;
- } catch (e) {
- // IE throws errors sometimes
- return false;
- }
- }
- function getYAxisWidth(elem) {
- let axis_text = elem.selectAll('.axis-y text').nodes();
- let max_text_width = _.max(
- _.map(axis_text, text => {
- // Use SVG getBBox method
- return text.getBBox().width;
- })
- );
- return max_text_width;
- }
- function 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'));
- let canvas_width = parseFloat(elem.attr('height'));
- return canvas_width - axis_line_position;
- } else {
- // Default height
- return 30;
- }
- }
- function addXAxis() {
- scope.xScale = xScale = d3
- .scaleTime()
- .domain([timeRange.from, timeRange.to])
- .range([0, chartWidth]);
- let ticks = chartWidth / DEFAULT_X_TICK_SIZE_PX;
- let grafanaTimeFormatter = grafanaTimeFormat(ticks, timeRange.from, timeRange.to);
- let timeFormat;
- let dashboardTimeZone = ctrl.dashboard.getTimezone();
- if (dashboardTimeZone === 'utc') {
- timeFormat = d3.utcFormat(grafanaTimeFormatter);
- } else {
- timeFormat = d3.timeFormat(grafanaTimeFormatter);
- }
- let xAxis = d3
- .axisBottom(xScale)
- .ticks(ticks)
- .tickFormat(timeFormat)
- .tickPadding(X_AXIS_TICK_PADDING)
- .tickSize(chartHeight);
- let posY = margin.top;
- let posX = yAxisWidth;
- 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
- .select('.axis-x')
- .select('.domain')
- .remove();
- }
- function addYAxis() {
- let ticks = Math.ceil(chartHeight / DEFAULT_Y_TICK_SIZE_PX);
- let tick_interval = tickStep(data.heatmapStats.min, data.heatmapStats.max, ticks);
- let { y_min, y_max } = wideYAxisRange(data.heatmapStats.min, 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;
- // Adjust ticks after Y range widening
- tick_interval = tickStep(y_min, y_max, ticks);
- ticks = Math.ceil((y_max - y_min) / tick_interval);
- let decimalsAuto = getPrecision(tick_interval);
- let decimals = panel.yAxis.decimals === null ? decimalsAuto : panel.yAxis.decimals;
- // Calculate scaledDecimals for log scales using tick size (as in jquery.flot.js)
- let flot_tick_size = getFlotTickSize(y_min, y_max, ticks, decimalsAuto);
- let scaledDecimals = getScaledDecimals(decimals, flot_tick_size);
- ctrl.decimals = decimals;
- ctrl.scaledDecimals = scaledDecimals;
- // Set default Y min and max if no data
- if (_.isEmpty(data.buckets)) {
- y_max = 1;
- y_min = -1;
- ticks = 3;
- decimals = 1;
- }
- data.yAxis = {
- min: y_min,
- max: y_max,
- ticks: ticks,
- };
- scope.yScale = yScale = d3
- .scaleLinear()
- .domain([y_min, y_max])
- .range([chartHeight, 0]);
- let yAxis = d3
- .axisLeft(yScale)
- .ticks(ticks)
- .tickFormat(tickValueFormatter(decimals, scaledDecimals))
- .tickSizeInner(0 - width)
- .tickSizeOuter(0)
- .tickPadding(Y_AXIS_TICK_PADDING);
- 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 + ')');
- // Remove vertical line in the right of axis labels (called domain in d3)
- 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;
- let y_min, y_max;
- if (tickInterval === 0) {
- y_max = max * dataRangeWidingFactor;
- y_min = min - min * (dataRangeWidingFactor - 1);
- tickInterval = (y_max - y_min) / 2;
- } else {
- y_max = Math.ceil((max + y_widing) / tickInterval) * tickInterval;
- y_min = Math.floor((min - y_widing) / tickInterval) * tickInterval;
- }
- // Don't wide axis below 0 if all values are positive
- if (min >= 0 && y_min < 0) {
- y_min = 0;
- }
- 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);
- 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;
- // Set default Y min and max if no data
- if (_.isEmpty(data.buckets)) {
- y_max = Math.pow(log_base, 2);
- y_min = 1;
- }
- scope.yScale = yScale = d3
- .scaleLog()
- .base(panel.yAxis.logBase)
- .domain([y_min, y_max])
- .range([chartHeight, 0]);
- let domain = yScale.domain();
- let tick_values = logScaleTickValues(domain, log_base);
- let decimalsAuto = getPrecision(y_min);
- let decimals = panel.yAxis.decimals || decimalsAuto;
- // Calculate scaledDecimals for log scales using tick size (as in jquery.flot.js)
- let flot_tick_size = getFlotTickSize(y_min, y_max, tick_values.length, decimalsAuto);
- let scaledDecimals = getScaledDecimals(decimals, flot_tick_size);
- ctrl.decimals = decimals;
- ctrl.scaledDecimals = scaledDecimals;
- data.yAxis = {
- min: y_min,
- max: y_max,
- ticks: tick_values.length,
- };
- let yAxis = d3
- .axisLeft(yScale)
- .tickValues(tick_values)
- .tickFormat(tickValueFormatter(decimals, scaledDecimals))
- .tickSizeInner(0 - width)
- .tickSizeOuter(0)
- .tickPadding(Y_AXIS_TICK_PADDING);
- 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 + ')');
- // Set first tick as pseudo 0
- if (y_min < 1) {
- heatmap
- .select('.axis-y')
- .select('.tick text')
- .text('0');
- }
- // Remove vertical line in the right of axis labels (called domain in d3)
- heatmap
- .select('.axis-y')
- .select('.domain')
- .remove();
- }
- function addYAxisFromBuckets() {
- const tsBuckets = data.tsBuckets;
- scope.yScale = yScale = d3
- .scaleLinear()
- .domain([0, tsBuckets.length - 1])
- .range([chartHeight, 0]);
- const tick_values = _.map(tsBuckets, (b, i) => i);
- function tickFormatter(valIndex) {
- let valueFormatted = tsBuckets[valIndex];
- if (!_.isNaN(_.toNumber(valueFormatted)) && valueFormatted !== '') {
- // Try to format numeric tick labels
- valueFormatted = tickValueFormatter(0)(valueFormatted);
- }
- return valueFormatted;
- }
- let yAxis = d3
- .axisLeft(yScale)
- .tickValues(tick_values)
- .tickFormat(tickFormatter)
- .tickSizeInner(0 - width)
- .tickSizeOuter(0)
- .tickPadding(Y_AXIS_TICK_PADDING);
- 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 + ')');
- // Remove vertical line in the right of axis labels (called domain in d3)
- heatmap
- .select('.axis-y')
- .select('.domain')
- .remove();
- }
- // Adjust data range to log base
- function adjustLogRange(min, max, logBase) {
- let y_min, y_max;
- y_min = data.heatmapStats.minLog;
- if (data.heatmapStats.minLog > 1 || !data.heatmapStats.minLog) {
- y_min = 1;
- } else {
- y_min = adjustLogMin(data.heatmapStats.minLog, logBase);
- }
- // Adjust max Y value to log base
- y_max = adjustLogMax(data.heatmapStats.max, logBase);
- return { y_min, y_max };
- }
- function adjustLogMax(max, base) {
- return Math.pow(base, Math.ceil(logp(max, base)));
- }
- function adjustLogMin(min, base) {
- return Math.pow(base, Math.floor(logp(min, base)));
- }
- function logScaleTickValues(domain, base) {
- let domainMin = domain[0];
- let domainMax = domain[1];
- let tickValues = [];
- if (domainMin < 1) {
- let under_one_ticks = Math.floor(logp(domainMin, base));
- for (let i = under_one_ticks; i < 0; i++) {
- let tick_value = Math.pow(base, i);
- tickValues.push(tick_value);
- }
- }
- let ticks = Math.ceil(logp(domainMax, base));
- for (let i = 0; i <= ticks; i++) {
- let tick_value = Math.pow(base, i);
- tickValues.push(tick_value);
- }
- return tickValues;
- }
- function tickValueFormatter(decimals, scaledDecimals = null) {
- let format = panel.yAxis.format;
- return function(value) {
- return kbn.valueFormats[format](value, decimals, scaledDecimals);
- };
- }
- function fixYAxisTickSize() {
- heatmap
- .select('.axis-y')
- .selectAll('.tick line')
- .attr('x2', chartWidth);
- }
- function addAxes() {
- chartHeight = height - margin.top - margin.bottom;
- chartTop = margin.top;
- chartBottom = chartTop + chartHeight;
- if (panel.dataFormat === 'tsbuckets') {
- addYAxisFromBuckets();
- } else {
- if (panel.yAxis.logBase === 1) {
- addYAxis();
- } else {
- addLogYAxis();
- }
- }
- yAxisWidth = getYAxisWidth(heatmap) + Y_AXIS_TICK_PADDING;
- chartWidth = width - yAxisWidth - margin.right;
- fixYAxisTickSize();
- addXAxis();
- xAxisHeight = getXAxisHeight(heatmap);
- if (!panel.yAxis.show) {
- heatmap
- .select('.axis-y')
- .selectAll('line')
- .style('opacity', 0);
- }
- if (!panel.xAxis.show) {
- heatmap
- .select('.axis-x')
- .selectAll('line')
- .style('opacity', 0);
- }
- }
- function addHeatmapCanvas() {
- let heatmap_elem = $heatmap[0];
- width = Math.floor($heatmap.width()) - padding.right;
- height = Math.floor($heatmap.height()) - padding.bottom;
- cardPadding = panel.cards.cardPadding !== null ? panel.cards.cardPadding : CARD_PADDING;
- cardRound = panel.cards.cardRound !== null ? panel.cards.cardRound : CARD_ROUND;
- if (heatmap) {
- heatmap.remove();
- }
- heatmap = d3
- .select(heatmap_elem)
- .append('svg')
- .attr('width', width)
- .attr('height', height);
- }
- function addHeatmap() {
- addHeatmapCanvas();
- 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));
- }
- let cardsData = data.cards;
- let maxValueAuto = data.cardStats.max;
- let maxValue = panel.color.max || maxValueAuto;
- let minValue = panel.color.min || 0;
- let colorScheme = _.find(ctrl.colorSchemes, {
- value: panel.color.colorScheme,
- });
- colorScale = getColorScale(colorScheme, contextSrv.user.lightTheme, maxValue, minValue);
- opacityScale = getOpacityScale(panel.color, maxValue);
- setCardSize();
- let cards = 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('class', 'bordered heatmap-card')
- .style('fill', getCardColor)
- .style('stroke', getCardColor)
- .style('stroke-width', 0)
- .style('opacity', getCardOpacity);
- let $cards = $heatmap.find('.heatmap-card');
- $cards
- .on('mouseenter', event => {
- tooltip.mouseOverBucket = true;
- highlightCard(event);
- })
- .on('mouseleave', event => {
- tooltip.mouseOverBucket = false;
- resetCardHighLight(event);
- });
- }
- function 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;
- current_card
- .style('fill', highlightColor.toString())
- .style('stroke', strokeColor.toString())
- .style('stroke-width', 1);
- }
- function resetCardHighLight(event) {
- d3
- .select(event.target)
- .style('fill', tooltip.originalFillColor)
- .style('stroke', 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));
- if (panel.yAxis.logBase !== 1) {
- let base = panel.yAxis.logBase;
- let splitFactor = data.yBucketSize || 1;
- yGridSize = Math.floor((yScale(1) - yScale(base)) / splitFactor);
- }
- cardWidth = xGridSize - cardPadding * 2;
- cardHeight = yGridSize ? yGridSize - cardPadding * 2 : 0;
- }
- function getCardX(d) {
- let x;
- if (xScale(d.x) < 0) {
- // Cut card left to prevent overlay
- x = yAxisWidth + cardPadding;
- } else {
- x = xScale(d.x) + yAxisWidth + cardPadding;
- }
- return x;
- }
- function getCardWidth(d) {
- let w;
- if (xScale(d.x) < 0) {
- // Cut card left to prevent overlay
- let cutted_width = xScale(d.x) + cardWidth;
- w = cutted_width > 0 ? cutted_width : 0;
- } else if (xScale(d.x) + cardWidth > chartWidth) {
- // Cut card right to prevent overlay
- w = chartWidth - xScale(d.x) - cardPadding;
- } else {
- w = cardWidth;
- }
- // Card width should be MIN_CARD_SIZE at least
- w = Math.max(w, MIN_CARD_SIZE);
- 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;
- } else {
- if (y < chartTop) {
- y = chartTop;
- }
- }
- return y;
- }
- function getCardHeight(d) {
- let y = yScale(d.y) + chartTop - cardHeight - cardPadding;
- let h = cardHeight;
- if (panel.yAxis.logBase !== 1 && d.y === 0) {
- return 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;
- }
- // Height can't be more than chart height
- h = Math.min(h, 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;
- } else {
- return colorScale(d.count);
- }
- }
- function getCardOpacity(d) {
- if (panel.color.mode === 'opacity') {
- return opacityScale(d.count);
- } else {
- return 1;
- }
- }
- /////////////////////////////
- // Selection and crosshair //
- /////////////////////////////
- // 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();
- };
- $(document).one('mouseup', mouseUpHandler);
- }
- function onMouseUp() {
- $(document).unbind('mouseup', mouseUpHandler);
- mouseUpHandler = null;
- 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);
- ctrl.timeSrv.setTime({
- from: moment.utc(timeFrom),
- to: moment.utc(timeTo),
- });
- }
- clearSelection();
- }
- function onMouseLeave() {
- appEvents.emit('graph-hover-clear');
- clearCrosshair();
- }
- function onMouseMove(event) {
- if (!heatmap) {
- return;
- }
- if (selection.active) {
- // Clear crosshair and tooltip
- clearCrosshair();
- tooltip.destroy();
- selection.x2 = limitSelection(event.offsetX);
- drawSelection(selection.x1, selection.x2);
- } else {
- emitGraphHoverEvet(event);
- drawCrosshair(event.offsetX);
- tooltip.show(event, data);
- }
- }
- function emitGraphHoverEvet(event) {
- let x = xScale.invert(event.offsetX - yAxisWidth).valueOf();
- let y = yScale.invert(event.offsetY);
- let pos = {
- pageX: event.pageX,
- pageY: event.pageY,
- x: x,
- x1: x,
- y: y,
- y1: y,
- panelRelY: null,
- };
- // Set minimum offset to prevent showing legend from another panel
- pos.panelRelY = Math.max(event.offsetY / height, 0.001);
- // broadcast to other graph panels that we are hovering
- appEvents.emit('graph-hover', { pos: pos, panel: panel });
- }
- function limitSelection(x2) {
- x2 = Math.max(x2, yAxisWidth);
- x2 = Math.min(x2, chartWidth + yAxisWidth);
- return x2;
- }
- function drawSelection(posX1, posX2) {
- if (heatmap) {
- heatmap.selectAll('.heatmap-selection').remove();
- let selectionX = Math.min(posX1, posX2);
- let selectionWidth = Math.abs(posX1 - posX2);
- if (selectionWidth > MIN_SELECTION_WIDTH) {
- heatmap
- .append('rect')
- .attr('class', 'heatmap-selection')
- .attr('x', selectionX)
- .attr('width', selectionWidth)
- .attr('y', chartTop)
- .attr('height', chartHeight);
- }
- }
- }
- function clearSelection() {
- selection.x1 = -1;
- selection.x2 = -1;
- if (heatmap) {
- heatmap.selectAll('.heatmap-selection').remove();
- }
- }
- function drawCrosshair(position) {
- if (heatmap) {
- heatmap.selectAll('.heatmap-crosshair').remove();
- let posX = position;
- posX = Math.max(posX, yAxisWidth);
- posX = Math.min(posX, chartWidth + yAxisWidth);
- heatmap
- .append('g')
- .attr('class', 'heatmap-crosshair')
- .attr('transform', 'translate(' + posX + ',0)')
- .append('line')
- .attr('x1', 1)
- .attr('y1', chartTop)
- .attr('x2', 1)
- .attr('y2', chartBottom)
- .attr('stroke-width', 1);
- }
- }
- function drawSharedCrosshair(pos) {
- if (heatmap && ctrl.dashboard.graphTooltip !== 0) {
- let posX = xScale(pos.x) + yAxisWidth;
- drawCrosshair(posX);
- }
- }
- function clearCrosshair() {
- if (heatmap) {
- heatmap.selectAll('.heatmap-crosshair').remove();
- }
- }
- function render() {
- data = ctrl.data;
- panel = ctrl.panel;
- timeRange = ctrl.range;
- if (!setElementHeight() || !data) {
- return;
- }
- // Draw default axes and return if no data
- if (_.isEmpty(data.buckets)) {
- addHeatmapCanvas();
- addAxes();
- return;
- }
- addHeatmap();
- scope.yAxisWidth = yAxisWidth;
- scope.xAxisHeight = xAxisHeight;
- scope.chartHeight = chartHeight;
- scope.chartWidth = chartWidth;
- scope.chartTop = chartTop;
- }
- // Register selection listeners
- $heatmap.on('mousedown', onMouseDown);
- $heatmap.on('mousemove', onMouseMove);
- $heatmap.on('mouseleave', onMouseLeave);
- }
- function grafanaTimeFormat(ticks, min, max) {
- if (min && max && ticks) {
- let range = max - min;
- let secPerTick = range / ticks / 1000;
- let oneDay = 86400000;
- let oneYear = 31536000000;
- if (secPerTick <= 45) {
- return '%H:%M:%S';
- }
- if (secPerTick <= 7200 || range <= oneDay) {
- return '%H:%M';
- }
- if (secPerTick <= 80000) {
- return '%m/%d %H:%M';
- }
- if (secPerTick <= 2419200 || range <= oneYear) {
- return '%m/%d';
- }
- return '%Y-%m';
- }
- return '%H:%M';
- }
- function logp(value, base) {
- return Math.log(value) / Math.log(base);
- }
- function getPrecision(num) {
- let str = num.toString();
- let dot_index = str.indexOf('.');
- if (dot_index === -1) {
- return 0;
- } else {
- return str.length - dot_index - 1;
- }
- }
|