Commit dd5a4269 by Torkel Ödegaard

heatmap: refactoring heatmap

parent 43c6f749
...@@ -11,26 +11,20 @@ weight = 20 ...@@ -11,26 +11,20 @@ weight = 20
# Grafana TestData # Grafana TestData
> NOTE: This plugin is disable by default.
The purpose of this data sources is to make it easier to create fake data for any panel. The purpose of this data sources is to make it easier to create fake data for any panel.
Using `Grafana TestData` you can build your own time series and have any panel render it. Using `Grafana TestData` you can build your own time series and have any panel render it.
This make is much easier to verify functionally since the data can be shared very This make is much easier to verify functionally since the data can be shared very
## Enable ## Enable
`Grafana TestData` is not enabled by default. To enable it you have to go to `/plugins/testdata/edit` and click the enable button to enable it for each server. `Grafana TestData` is not enabled by default. To enable it you have to go to `/plugins/testdata/edit` and click the enable button to enable.
## Create mock data. ## Create mock data.
Once `Grafana TestData` is enabled you use it as a datasource in the metric panel. Once `Grafana TestData` is enabled you can use it as a data source in any metric panel.
![](/img/docs/v41/test_data_add.png) ![](/img/docs/v41/test_data_add.png)
## Scenarios
You can now choose different scenario that you want rendered in the drop down menu. If you have scenarios that you think should be added, please add them to `` and submit a pull request.
## CSV ## CSV
The comma separated values scenario is the most powerful one since it lets you create any kind of graph you like. The comma separated values scenario is the most powerful one since it lets you create any kind of graph you like.
...@@ -38,7 +32,6 @@ Once you provided the numbers `Grafana TestData` will distribute them evenly bas ...@@ -38,7 +32,6 @@ Once you provided the numbers `Grafana TestData` will distribute them evenly bas
![](/img/docs/v41/test_data_csv_example.png) ![](/img/docs/v41/test_data_csv_example.png)
## Dashboards ## Dashboards
`Grafana TestData` also contains some dashboards with example. `/plugins/testdata/edit` `Grafana TestData` also contains some dashboards with example. `/plugins/testdata/edit`
......
+++
title = "Heatmap Panel"
description = "Heatmap panel documentation"
keywords = ["grafana", "heatmap", "panel", "documentation"]
type = "docs"
[menu.docs]
parent = "panels"
weight = 3
+++
# Heatmap Panel
> New panel only available in Grafana v4.3+
![](/img/docs/v43/heatmap_panel.png)
...@@ -16,56 +16,38 @@ interface YBucket { ...@@ -16,56 +16,38 @@ interface YBucket {
values: number[]; values: number[];
} }
function elasticHistogramToHeatmap(series) { function elasticHistogramToHeatmap(seriesList) {
let seriesBuckets = _.map(series, (s: TimeSeries) => { let heatmap = {};
return convertEsSeriesToHeatmap(s);
});
let buckets = mergeBuckets(seriesBuckets);
return buckets;
}
function convertEsSeriesToHeatmap(series: TimeSeries, saveZeroCounts = false) { for (let series of seriesList) {
let xBuckets: XBucket[] = []; let bound = Number(series.alias);
if (isNaN(bound)) {
_.forEach(series.datapoints, point => {
let bound = series.alias;
let count = point[VALUE_INDEX];
if (!count) {
return; return;
} }
let values = new Array(Math.round(count)); for (let point of series.datapoints) {
values.fill(Number(bound)); let count = point[VALUE_INDEX];
let time = point[TIME_INDEX];
let valueBuckets = {}; if (!_.isNumber(count)) {
valueBuckets[bound] = { continue;
y: Number(bound), }
values: values
};
let xBucket: XBucket = { let bucket = heatmap[time];
x: point[TIME_INDEX], if (!bucket) {
buckets: valueBuckets bucket = heatmap[time] = {x: time, buckets: {}};
}; }
// Don't push buckets with 0 count until saveZeroCounts flag is set bucket.buckets[bound] = {y: bound, count: count};
if (count !== 0 || (count === 0 && saveZeroCounts)) {
xBuckets.push(xBucket);
} }
}); }
let heatmap: any = {};
_.forEach(xBuckets, (bucket: XBucket) => {
heatmap[bucket.x] = bucket;
});
return heatmap; return heatmap;
} }
/** /**
* Convert set of time series into heatmap buckets * Convert set of time series into heatmap buckets
* @return {Object} Heatmap object: * @return {Object} Heatmap object:
* { * {
* xBucketBound_1: { * xBucketBound_1: {
* x: xBucketBound_1, * x: xBucketBound_1,
...@@ -109,18 +91,16 @@ function convertToHeatMap(series, yBucketSize, xBucketSize, logBase) { ...@@ -109,18 +91,16 @@ function convertToHeatMap(series, yBucketSize, xBucketSize, logBase) {
function convertToCards(buckets) { function convertToCards(buckets) {
let cards = []; let cards = [];
_.forEach(buckets, xBucket => { _.forEach(buckets, xBucket => {
_.forEach(xBucket.buckets, (yBucket, key) => { _.forEach(xBucket.buckets, yBucket=> {
if (yBucket.values.length) { let card = {
let card = { x: xBucket.x,
x: Number(xBucket.x), y: yBucket.y,
y: Number(key), yBounds: yBucket.bounds,
yBounds: yBucket.bounds, values: yBucket.values,
values: yBucket.values, count: yBucket.count,
seriesStat: getSeriesStat(yBucket.points) seriesStat: getSeriesStat(yBucket.points)
}; };
cards.push(card);
cards.push(card);
}
}); });
}); });
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<div class="section gf-form-group"> <div class="section gf-form-group">
<h5 class="section-heading">Y Axis</h5> <h5 class="section-heading">Y Axis</h5>
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label width-6">Unit</label> <label class="gf-form-label width-8">Unit</label>
<div class="gf-form-dropdown-typeahead max-width-10" <div class="gf-form-dropdown-typeahead max-width-10"
ng-model="ctrl.panel.yAxis.format" ng-model="ctrl.panel.yAxis.format"
dropdown-typeahead2="editor.unitFormats" dropdown-typeahead2="editor.unitFormats"
...@@ -10,21 +10,21 @@ ...@@ -10,21 +10,21 @@
</div> </div>
</div> </div>
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label width-6">Scale</label> <label class="gf-form-label width-8">Scale</label>
<div class="gf-form-select-wrapper max-width-10"> <div class="gf-form-select-wrapper max-width-10">
<select class="gf-form-input" ng-model="ctrl.panel.yAxis.logBase" ng-options="v as k for (k, v) in editor.logScales" ng-change="ctrl.refresh()"></select> <select class="gf-form-input" ng-model="ctrl.panel.yAxis.logBase" ng-options="v as k for (k, v) in editor.logScales" ng-change="ctrl.refresh()"></select>
</div> </div>
</div> </div>
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label width-6">Y-Min</label> <label class="gf-form-label width-8">Y-Min</label>
<input type="text" class="gf-form-input width-10" placeholder="auto" empty-to-null ng-model="ctrl.panel.yAxis.min" ng-change="ctrl.render()" ng-model-onblur> <input type="text" class="gf-form-input width-10" placeholder="auto" empty-to-null ng-model="ctrl.panel.yAxis.min" ng-change="ctrl.render()" ng-model-onblur>
</div> </div>
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label width-6">Y-Max</label> <label class="gf-form-label width-8">Y-Max</label>
<input type="text" class="gf-form-input width-10" placeholder="auto" empty-to-null ng-model="ctrl.panel.yAxis.max" ng-change="ctrl.render()" ng-model-onblur> <input type="text" class="gf-form-input width-10" placeholder="auto" empty-to-null ng-model="ctrl.panel.yAxis.max" ng-change="ctrl.render()" ng-model-onblur>
</div> </div>
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label width-6">Decimals</label> <label class="gf-form-label width-8">Decimals</label>
<input type="number" class="gf-form-input width-10" placeholder="auto" data-placement="right" <input type="number" class="gf-form-input width-10" placeholder="auto" data-placement="right"
bs-tooltip="'Override automatic decimal precision for axis.'" bs-tooltip="'Override automatic decimal precision for axis.'"
ng-model="ctrl.panel.yAxis.decimals" ng-change="ctrl.render()" ng-model-onblur> ng-model="ctrl.panel.yAxis.decimals" ng-change="ctrl.render()" ng-model-onblur>
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
<label class="gf-form-label">Buckets</label> <label class="gf-form-label">Buckets</label>
<input type="number" class="gf-form-input width-5" placeholder="auto" data-placement="right" <input type="number" class="gf-form-input width-5" placeholder="auto" data-placement="right"
bs-tooltip="'Number of buckets for Y axis.'" bs-tooltip="'Number of buckets for Y axis.'"
ng-model="ctrl.panel.yBucketNumber" ng-change="ctrl.refresh()" ng-model-onblur> ng-model="ctrl.panel.xBucketNumber" ng-change="ctrl.refresh()" ng-model-onblur>
</div> </div>
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label">Size</label> <label class="gf-form-label">Size</label>
......
...@@ -366,14 +366,12 @@ export default function link(scope, elem, attrs, ctrl) { ...@@ -366,14 +366,12 @@ export default function link(scope, elem, attrs, ctrl) {
data.buckets = mergeZeroBuckets(data.buckets, _.min(tick_values)); data.buckets = mergeZeroBuckets(data.buckets, _.min(tick_values));
} }
} }
let cardsData = convertToCards(data.buckets);
let max_value = d3.max(cardsData, card => { let cardsData = convertToCards(data.buckets);
return card.values.length; let maxValue = d3.max(cardsData, card => card.count);
});
colorScale = getColorScale(max_value); colorScale = getColorScale(maxValue);
setOpacityScale(max_value); setOpacityScale(maxValue);
setCardSize(); setCardSize();
let cards = heatmap.selectAll(".heatmap-card").data(cardsData); let cards = heatmap.selectAll(".heatmap-card").data(cardsData);
...@@ -431,14 +429,14 @@ export default function link(scope, elem, attrs, ctrl) { ...@@ -431,14 +429,14 @@ export default function link(scope, elem, attrs, ctrl) {
return d3.scaleSequential(colorInterpolator).domain([start, end]); return d3.scaleSequential(colorInterpolator).domain([start, end]);
} }
function setOpacityScale(max_value) { function setOpacityScale(maxValue) {
if (panel.color.colorScale === 'linear') { if (panel.color.colorScale === 'linear') {
opacityScale = d3.scaleLinear() opacityScale = d3.scaleLinear()
.domain([0, max_value]) .domain([0, maxValue])
.range([0, 1]); .range([0, 1]);
} else if (panel.color.colorScale === 'sqrt') { } else if (panel.color.colorScale === 'sqrt') {
opacityScale = d3.scalePow().exponent(panel.color.exponent) opacityScale = d3.scalePow().exponent(panel.color.exponent)
.domain([0, max_value]) .domain([0, maxValue])
.range([0, 1]); .range([0, 1]);
} }
} }
...@@ -529,13 +527,13 @@ export default function link(scope, elem, attrs, ctrl) { ...@@ -529,13 +527,13 @@ export default function link(scope, elem, attrs, ctrl) {
if (panel.color.mode === 'opacity') { if (panel.color.mode === 'opacity') {
return panel.color.cardColor; return panel.color.cardColor;
} else { } else {
return colorScale(d.values.length); return colorScale(d.count);
} }
} }
function getCardOpacity(d) { function getCardOpacity(d) {
if (panel.color.mode === 'opacity') { if (panel.color.mode === 'opacity') {
return opacityScale(d.values.length); return opacityScale(d.count);
} else { } else {
return 1; return 1;
} }
...@@ -831,8 +829,3 @@ function getPrecision(num) { ...@@ -831,8 +829,3 @@ function getPrecision(num) {
return str.length - dot_index - 1; return str.length - dot_index - 1;
} }
} }
function getTicksPrecision(values) {
let precisions = _.map(values, getPrecision);
return _.max(precisions);
}
...@@ -200,7 +200,7 @@ describe('ES Histogram converter', () => { ...@@ -200,7 +200,7 @@ describe('ES Histogram converter', () => {
alias: '1', label: '1' alias: '1', label: '1'
})); }));
ctx.series.push(new TimeSeries({ ctx.series.push(new TimeSeries({
datapoints: [[1, 1422774000000], [3, 1422774060000]], datapoints: [[5, 1422774000000], [3, 1422774060000]],
alias: '2', label: '2' alias: '2', label: '2'
})); }));
ctx.series.push(new TimeSeries({ ctx.series.push(new TimeSeries({
...@@ -219,21 +219,23 @@ describe('ES Histogram converter', () => { ...@@ -219,21 +219,23 @@ describe('ES Histogram converter', () => {
'1422774000000': { '1422774000000': {
x: 1422774000000, x: 1422774000000,
buckets: { buckets: {
'1': { y: 1, values: [1] }, '1': { y: 1, count: 1 },
'2': { y: 2, values: [2] } '2': { y: 2, count: 5 },
'3': { y: 3, count: 0 }
} }
}, },
'1422774060000': { '1422774060000': {
x: 1422774060000, x: 1422774060000,
buckets: { buckets: {
'2': { y: 2, values: [2, 2, 2] }, '1': { y: 1, count: 0 },
'3': { y: 3, values: [3] } '2': { y: 2, count: 3 },
'3': { y: 3, count: 1 }
} }
}, },
}; };
let heatmap = elasticHistogramToHeatmap(ctx.series); let heatmap = elasticHistogramToHeatmap(ctx.series);
expect(isHeatmapDataEqual(heatmap, expectedHeatmap)).to.be(true); expect(heatmap).to.eql(expectedHeatmap);
}); });
}); });
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment