diff --git a/public/app/core/utils/ticks.ts b/public/app/core/utils/ticks.ts
index 834b7bd0cc4..db65104cfc0 100644
--- a/public/app/core/utils/ticks.ts
+++ b/public/app/core/utils/ticks.ts
@@ -156,3 +156,61 @@ export function getFlotTickDecimals(data, axis) {
const scaledDecimals = tickDecimals - Math.floor(Math.log(size) / Math.LN10);
return { tickDecimals, scaledDecimals };
}
+
+/**
+ * Format timestamp similar to Grafana graph panel.
+ * @param ticks Number of ticks
+ * @param min Time from (in milliseconds)
+ * @param max Time to (in milliseconds)
+ */
+export 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';
+}
+
+/**
+ * Logarithm of value for arbitrary base.
+ */
+export function logp(value, base) {
+ return Math.log(value) / Math.log(base);
+}
+
+/**
+ * Get decimal precision of number (3.14 => 2)
+ */
+export function getPrecision(num: number): number {
+ let str = num.toString();
+ return getStringPrecision(str);
+}
+
+/**
+ * Get decimal precision of number stored as a string ("3.14" => 2)
+ */
+export function getStringPrecision(num: string): number {
+ let dot_index = num.indexOf('.');
+ if (dot_index === -1) {
+ return 0;
+ } else {
+ return num.length - dot_index - 1;
+ }
+}
diff --git a/public/app/plugins/panel/heatmap/heatmap_ctrl.ts b/public/app/plugins/panel/heatmap/heatmap_ctrl.ts
index 5a3b04905ef..47d3e6616b9 100644
--- a/public/app/plugins/panel/heatmap/heatmap_ctrl.ts
+++ b/public/app/plugins/panel/heatmap/heatmap_ctrl.ts
@@ -89,6 +89,8 @@ let colorSchemes = [
{ name: 'YlOrRd', value: 'interpolateYlOrRd', invert: 'darm' },
];
+const ds_support_histogram_sort = ['prometheus', 'elasticsearch'];
+
export class HeatmapCtrl extends MetricsPanelCtrl {
static templateUrl = 'module.html';
@@ -207,15 +209,20 @@ export class HeatmapCtrl extends MetricsPanelCtrl {
}
convertHistogramToHeatmapData() {
+ const panelDatasource = this.getPanelDataSourceType();
let xBucketSize, yBucketSize, bucketsData, tsBuckets;
+ // Try to sort series by bucket bound, if datasource doesn't do it.
+ if (!_.includes(ds_support_histogram_sort, panelDatasource)) {
+ this.series.sort(sortSeriesByLabel);
+ }
+
// Convert histogram to heatmap. Each histogram bucket represented by the series which name is
// a top (or bottom, depends of datasource) bucket bound. Further, these values will be used as X axis labels.
- this.series.sort(sortSeriesByLabel);
bucketsData = histogramToHeatmap(this.series);
tsBuckets = _.map(this.series, 'label');
- if (this.datasource && this.datasource.type === 'prometheus') {
+ if (panelDatasource === 'prometheus') {
// Prometheus labels are upper inclusive bounds, so add empty bottom bucket label.
tsBuckets = [''].concat(tsBuckets);
} else {
@@ -241,6 +248,14 @@ export class HeatmapCtrl extends MetricsPanelCtrl {
};
}
+ getPanelDataSourceType() {
+ if (this.datasource.meta && this.datasource.meta.id) {
+ return this.datasource.meta.id;
+ } else {
+ return 'unknown';
+ }
+ }
+
onDataReceived(dataList) {
this.series = dataList.map(this.seriesHandler.bind(this));
diff --git a/public/app/plugins/panel/heatmap/heatmap_data_converter.ts b/public/app/plugins/panel/heatmap/heatmap_data_converter.ts
index 133193e1369..048b19de911 100644
--- a/public/app/plugins/panel/heatmap/heatmap_data_converter.ts
+++ b/public/app/plugins/panel/heatmap/heatmap_data_converter.ts
@@ -67,7 +67,7 @@ function sortSeriesByLabel(s1, s2) {
label1 = parseHistogramLabel(s1.label);
label2 = parseHistogramLabel(s2.label);
} catch (err) {
- console.log(err);
+ console.log(err.message || err);
return 0;
}
@@ -83,10 +83,14 @@ function sortSeriesByLabel(s1, s2) {
}
function parseHistogramLabel(label: string): number {
- if (label === '+Inf') {
+ if (label === '+Inf' || label === 'inf') {
return +Infinity;
}
- return Number(label);
+ const value = Number(label);
+ if (isNaN(value)) {
+ throw new Error(`Error parsing histogram label: ${label} is not a number`);
+ }
+ return value;
}
/**
diff --git a/public/app/plugins/panel/heatmap/heatmap_tooltip.ts b/public/app/plugins/panel/heatmap/heatmap_tooltip.ts
index 0ef1832e7e6..1caa9ae9c69 100644
--- a/public/app/plugins/panel/heatmap/heatmap_tooltip.ts
+++ b/public/app/plugins/panel/heatmap/heatmap_tooltip.ts
@@ -100,14 +100,14 @@ export class HeatmapTooltip {
let countValueFormatter, bucketBoundFormatter;
if (_.isNumber(this.panel.tooltipDecimals)) {
countValueFormatter = this.countValueFormatter(this.panel.tooltipDecimals, null);
- bucketBoundFormatter = this.bucketBoundFormatter(this.panel.tooltipDecimals, null);
+ bucketBoundFormatter = this.panelCtrl.tickValueFormatter(this.panelCtrl.decimals, null);
} else {
// auto decimals
// legend and tooltip gets one more decimal precision
// than graph legend ticks
let decimals = (this.panelCtrl.decimals || -1) + 1;
countValueFormatter = this.countValueFormatter(decimals, this.panelCtrl.scaledDecimals + 2);
- bucketBoundFormatter = this.bucketBoundFormatter(decimals, this.panelCtrl.scaledDecimals + 2);
+ bucketBoundFormatter = this.panelCtrl.tickValueFormatter(decimals, this.panelCtrl.scaledDecimals + 2);
}
let tooltipHtml = `
${time}
@@ -116,19 +116,13 @@ export class HeatmapTooltip {
if (yData) {
if (yData.bounds) {
if (data.tsBuckets) {
- const decimals = this.panelCtrl.decimals || 0;
+ // Use Y-axis labels
const tickFormatter = valIndex => {
- let valueFormatted = data.tsBuckets[valIndex];
- if (!_.isNaN(_.toNumber(valueFormatted)) && valueFormatted !== '') {
- // Try to format numeric tick labels
- valueFormatted = this.bucketBoundFormatter(decimals)(_.toNumber(valueFormatted));
- }
- return valueFormatted;
+ return data.tsBucketsFormatted ? data.tsBucketsFormatted[valIndex] : data.tsBuckets[valIndex];
};
- const tsBucketsTickFormatter = tickFormatter.bind(this);
- boundBottom = tsBucketsTickFormatter(yBucketIndex);
- boundTop = yBucketIndex < data.tsBuckets.length - 1 ? tsBucketsTickFormatter(yBucketIndex + 1) : '';
+ boundBottom = tickFormatter(yBucketIndex);
+ boundTop = yBucketIndex < data.tsBuckets.length - 1 ? tickFormatter(yBucketIndex + 1) : '';
} else {
// Display 0 if bucket is a special 'zero' bucket
let bottom = yData.y ? yData.bounds.bottom : 0;
@@ -282,21 +276,9 @@ export class HeatmapTooltip {
}
countValueFormatter(decimals, scaledDecimals = null) {
- let format = 'none';
+ let format = 'short';
return function(value) {
return kbn.valueFormats[format](value, decimals, scaledDecimals);
};
}
-
- bucketBoundFormatter(decimals, scaledDecimals = null) {
- let format = this.panel.yAxis.format;
- return function(value) {
- try {
- return format !== 'none' ? kbn.valueFormats[format](value, decimals, scaledDecimals) : value;
- } catch (err) {
- console.error(err.message || err);
- return value;
- }
- };
- }
}
diff --git a/public/app/plugins/panel/heatmap/rendering.ts b/public/app/plugins/panel/heatmap/rendering.ts
index 0ccba2952cd..55dd91f4b63 100644
--- a/public/app/plugins/panel/heatmap/rendering.ts
+++ b/public/app/plugins/panel/heatmap/rendering.ts
@@ -4,7 +4,7 @@ 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 * as ticksUtils from 'app/core/utils/ticks';
import { HeatmapTooltip } from './heatmap_tooltip';
import { mergeZeroBuckets } from './heatmap_data_converter';
import { getColorScale, getOpacityScale } from './color_scale';
@@ -108,7 +108,7 @@ export default function link(scope, elem, attrs, ctrl) {
.range([0, chartWidth]);
let ticks = chartWidth / DEFAULT_X_TICK_SIZE_PX;
- let grafanaTimeFormatter = grafanaTimeFormat(ticks, timeRange.from, timeRange.to);
+ let grafanaTimeFormatter = ticksUtils.grafanaTimeFormat(ticks, timeRange.from, timeRange.to);
let timeFormat;
let dashboardTimeZone = ctrl.dashboard.getTimezone();
if (dashboardTimeZone === 'utc') {
@@ -141,7 +141,7 @@ export default function link(scope, elem, attrs, ctrl) {
function addYAxis() {
let ticks = Math.ceil(chartHeight / DEFAULT_Y_TICK_SIZE_PX);
- let tick_interval = tickStep(data.heatmapStats.min, data.heatmapStats.max, ticks);
+ 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);
// Rewrite min and max if it have been set explicitly
@@ -149,14 +149,14 @@ export default function link(scope, elem, attrs, ctrl) {
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);
+ tick_interval = ticksUtils.tickStep(y_min, y_max, ticks);
ticks = Math.ceil((y_max - y_min) / tick_interval);
- let decimalsAuto = getPrecision(tick_interval);
+ let decimalsAuto = ticksUtils.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);
+ 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;
@@ -248,12 +248,12 @@ export default function link(scope, elem, attrs, ctrl) {
let domain = yScale.domain();
let tick_values = logScaleTickValues(domain, log_base);
- let decimalsAuto = getPrecision(y_min);
+ let decimalsAuto = ticksUtils.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);
+ 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;
@@ -305,7 +305,7 @@ export default function link(scope, elem, attrs, ctrl) {
.range([chartHeight, 0]);
const tick_values = _.map(tsBuckets, (b, i) => i);
- const decimalsAuto = _.max(_.map(tsBuckets, getStringPrecision));
+ const decimalsAuto = _.max(_.map(tsBuckets, ticksUtils.getStringPrecision));
const decimals = panel.yAxis.decimals === null ? decimalsAuto : panel.yAxis.decimals;
ctrl.decimals = decimals;
@@ -318,6 +318,9 @@ export default function link(scope, elem, attrs, ctrl) {
return valueFormatted;
}
+ const tsBucketsFormatted = _.map(tsBuckets, (v, i) => tickFormatter(i));
+ data.tsBucketsFormatted = tsBucketsFormatted;
+
let yAxis = d3
.axisLeft(yScale)
.tickValues(tick_values)
@@ -361,11 +364,11 @@ export default function link(scope, elem, attrs, ctrl) {
}
function adjustLogMax(max, base) {
- return Math.pow(base, Math.ceil(logp(max, base)));
+ return Math.pow(base, Math.ceil(ticksUtils.logp(max, base)));
}
function adjustLogMin(min, base) {
- return Math.pow(base, Math.floor(logp(min, base)));
+ return Math.pow(base, Math.floor(ticksUtils.logp(min, base)));
}
function logScaleTickValues(domain, base) {
@@ -374,14 +377,14 @@ export default function link(scope, elem, attrs, ctrl) {
let tickValues = [];
if (domainMin < 1) {
- let under_one_ticks = Math.floor(logp(domainMin, base));
+ let under_one_ticks = Math.floor(ticksUtils.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));
+ let ticks = Math.ceil(ticksUtils.logp(domainMax, base));
for (let i = 0; i <= ticks; i++) {
let tick_value = Math.pow(base, i);
tickValues.push(tick_value);
@@ -402,6 +405,8 @@ export default function link(scope, elem, attrs, ctrl) {
};
}
+ ctrl.tickValueFormatter = tickValueFormatter;
+
function fixYAxisTickSize() {
heatmap
.select('.axis-y')
@@ -827,46 +832,3 @@ export default function link(scope, elem, attrs, ctrl) {
$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: number): number {
- let str = num.toString();
- return getStringPrecision(str);
-}
-
-function getStringPrecision(num: string): number {
- let dot_index = num.indexOf('.');
- if (dot_index === -1) {
- return 0;
- } else {
- return num.length - dot_index - 1;
- }
-}