49 Commits
Author SHA1 Message Date
Ivan MikheykinandGitHub 742e80d713 Merge pull request #63 from flant/fix_then_for_5_x
Fix then for grafana 5.x before 5.4
2019-03-22 17:58:01 +03:00
Ivan Mikheykin eb6f149f8b version 0.1.1 2019-03-22 17:56:52 +03:00
Ivan Mikheykin d4d297862e Create CHANGELOG.md 2019-03-22 17:55:18 +03:00
Ivan Mikheykin 06cb92bbb8 fix: do not use datasourcePromises for 5.x before 5.4 2019-03-22 17:37:32 +03:00
Ivan Mikheykin 5a758213c6 README: link to Planning issue 2019-03-14 18:00:56 +03:00
Ivan MikheykinandGitHub 20691c9aae Merge pull request #58 from flant/version_0_1_0
Version 0.1.0
2019-03-11 16:36:27 +03:00
Ivan Mikheykin 624be3450d Version 0.1.0 2019-03-11 16:35:16 +03:00
Ivan MikheykinandGitHub 63e36b1656 Merge pull request #57 from flant/readme_influx_mysql_example
README: update info for datasources
2019-03-11 16:18:05 +03:00
Ivan Mikheykin 433a059bcf README: update info for datasources 2019-03-11 16:16:23 +03:00
Ivan MikheykinandGitHub 1862c5ac17 Merge pull request #56 from flant/enable_annotations
Enable annotations
2019-03-11 15:53:53 +03:00
Ivan MikheykinandGitHub 6665b7e18a Merge pull request #39 from vrutkovs/example-dashboard
Create examples folder and add an example dashboard for k8s
2019-03-11 15:53:41 +03:00
Ivan Mikheykin 4994f2f956 feat: Support for annotations: only one time events 2019-03-11 15:38:47 +03:00
Ivan Mikheykin 8d2039bf37 Notifications for grunt watch 2019-03-11 15:35:09 +03:00
Ivan MikheykinandGitHub 12492216d6 Merge pull request #55 from flant/revisit_data_handler
fix: calculate cardWidth over max datapoints for target
2019-03-05 15:50:20 +03:00
Ivan Mikheykin 51623fdc72 fix: calculate cardWidth over max datapoints for target
- correct render of inconsistent timestamps between targets
2019-03-05 15:47:43 +03:00
Ivan MikheykinandGitHub 27b674640b Merge pull request #52 from flant/fix_ie11_removeChild
fix: removeChild error on ie11 fixed with d3 remove
2019-02-26 16:01:20 +03:00
Ivan Mikheykin 803504befd fix: removeChild error on ie11 fixed with d3 remove 2019-02-26 15:53:18 +03:00
Ivan MikheykinandGitHub 570fe11341 Merge pull request #51 from flant/fix_grafana_hangs
Fix grafana hangs and h spacing = 0
2019-02-26 09:45:12 +03:00
Ivan Mikheykin 0537671450 fix: plugin hangs on big values in opacity mode
- legend step is a constant now: 2 pixels
- do not calculate legend if 'No legend'
- remove unnecessary calculations in drawSimple*
2019-02-26 09:36:48 +03:00
Ivan Mikheykin 089f42d940 fix: better description for Rounding option 2019-02-21 20:48:57 +03:00
Ivan Mikheykin 2158753c5e fix: dataWarning div was preventing tooltips
- add ng-if and pointer-events attributs
2019-02-21 20:47:27 +03:00
Ivan Mikheykin dee408e7a3 fix: no more gaps if h spacing is zero
- fixes #31
2019-02-21 20:44:38 +03:00
Ivan MikheykinandGitHub c37d116550 Merge pull request #48 from flant/fix_no_method_remove
fix: no property or method remove on IE11
2019-02-21 15:36:35 +03:00
Ivan Mikheykin a1a06ea78a fix: no property or method remove on IE11
- fixes #40
2019-02-21 15:32:05 +03:00
Vadim Rutkovsky f813f4cea7 Create examples folder and add an example for prometheus + kube-state-metrics 2018-12-27 15:30:16 +01:00
Ivan MikheykinandGitHub 3ffe5554f3 Merge pull request #37 from flant/fix_color_null_error
Fix for null error on undefined values
2018-12-20 15:57:08 +03:00
Ivan Mikheykin e1abad408e Fix for null error on undefined values 2018-12-19 16:08:34 +03:00
Ivan Mikheykin 188d912653 Version 0.0.4 2018-12-13 09:12:16 +03:00
Ivan Mikheykin 8d917d0c1e Version 0.0.4 2018-12-13 09:10:47 +03:00
Ivan MikheykinandGitHub f6973a81b8 Merge pull request #34 from flant/fix_empty_buckets_with_normal_values
Fix display of multivalues buckets as an empty cell
2018-12-11 20:48:38 +03:00
Ivan Mikheykin f13adb369b Fix display of multivalues buckets as an empty cell 2018-12-11 15:58:45 +03:00
Ivan MikheykinandGitHub cb5d353b36 Merge pull request #29 from flant/add_display_options
Display options description in README
2018-11-29 19:20:02 +03:00
Ivan Mikheykin e5249b8852 Display options description in README 2018-11-29 19:19:01 +03:00
Ivan MikheykinandGitHub 8dde6da21a Merge pull request #28 from flant/fix_readme_images
Small fix
2018-11-29 17:09:15 +03:00
Ivan Mikheykin 3c3931b2c0 Small fix 2018-11-29 17:05:54 +03:00
Ivan MikheykinandGitHub 77ab254a5e Merge pull request #27 from flant/fix_readme_images
Images with new improvements in README.md
2018-11-29 17:02:11 +03:00
Ivan Mikheykin 12b974a2bc Images with new improvements in README.md 2018-11-29 17:01:17 +03:00
Ivan MikheykinandGitHub cc4b908afd Merge pull request #24 from flant/update_for_0_0_3
Set version 0.0.3 in plugin.json
2018-11-26 16:33:31 +03:00
Ivan Mikheykin cda8fdfe8d Set version 0.0.3 in plugin.json 2018-11-26 16:32:38 +03:00
Ivan MikheykinandGitHub ec8f8e386a Merge pull request #23 from flant/discrete_preset_of_solarized_colors
Solarized preset for discrete color mode
2018-11-26 16:31:02 +03:00
Ivan Mikheykin 76dc89b2dc Solarized preset for discrete color mode
- add remove all button
2018-11-26 16:28:33 +03:00
Ivan MikheykinandGitHub ca2fcb582a Merge pull request #22 from flant/separate_vertical_and_horizontal_spacing
Separate options for vertical and horizontal spacing of cards
2018-11-26 15:09:17 +03:00
Ivan Mikheykin 54539cb35f Separate options for vertical and horizontal spacing of cards 2018-11-26 15:03:21 +03:00
Ivan MikheykinandGitHub 8e12d35da9 Merge pull request #21 from flant/sort_for_y_axis
Sort options for Y axis
2018-11-26 11:07:16 +03:00
Ivan Mikheykin 34f65b1455 Sort options for Y axis
- sort as on metrics tab
- ascending and descending natural sort (1-2-10)
2018-11-26 11:02:48 +03:00
Ivan MikheykinandGitHub e783d32efa Merge pull request #19 from flant/fix_null_values_as_zero
Fix null values displayed as zeros
2018-11-26 09:32:32 +03:00
Ivan Mikheykin a51f237291 Fix null values displayed as zeros 2018-11-23 18:36:59 +03:00
Ivan MikheykinandGitHub 46ba4be810 Merge pull request #16 from flant/changelog_update
update changelog
2018-11-20 08:49:04 +03:00
Ivan Mikheykin 7ce802f3ef update changelog 2018-10-18 14:27:30 +03:00
49 changed files with 1652 additions and 279 deletions
+41
View File
@@ -0,0 +1,41 @@
# Changelog
## v0.1.1
- Fix for annotations in grafana 5.x before 5.4
## v0.1.0
- Tested with Grafana 6.0.0
- Tested with InfluxDB and Mysql datasources
- Add initial support for display annotations
- Add example for k8s statuses (thanks, @vrutkovs)
- Fix hanging on big values
- Fix horizontal spacing = 0
- Fix for "Object doesn't support property or method 'remove'"
- Fix card width for targets with different datapoints count
## v0.0.4
- Fix display of multivalues buckets as an empty cell
## v0.0.3
- Add solarized preset
- Reorganize statuses editor for discrete mode
- Separate options for vertical and horizontal spacing for cards
- Add simple sort options for Y axis
- Fix display null values as zero
## v0.0.2
- Install with GF_INSTALL_PLUGINS
- Fix legend overlap
- Fix colors for dark theme
- Fix panel rendering timeout error
## v0.0.1
- First public release
+12 -2
View File
@@ -3,8 +3,18 @@ module.exports = (grunt) => {
grunt.loadNpmTasks('grunt-execute'); grunt.loadNpmTasks('grunt-execute');
grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-notify');
grunt.initConfig({ grunt.initConfig({
notify: {
watch: {
options: {
message: 'grunt watch Complete',
title: 'flant-statusmap-panel rebuilded',
duration: 2
}
}
},
clean: ['dist'], clean: ['dist'],
@@ -17,7 +27,7 @@ module.exports = (grunt) => {
}, },
pluginDef: { pluginDef: {
expand: true, expand: true,
src: ['plugin.json', 'README.md'], src: ['plugin.json', 'README.md', 'CHANGELOG.md'],
dest: 'dist' dest: 'dist'
} }
}, },
@@ -25,7 +35,7 @@ module.exports = (grunt) => {
watch: { watch: {
rebuild_all: { rebuild_all: {
files: ['src/**/*', 'plugin.json'], files: ['src/**/*', 'plugin.json'],
tasks: ['default'], tasks: ['default', 'notify:watch'],
options: { options: {
spawn: false, spawn: false,
livereload: true livereload: true
+67 -14
View File
@@ -13,10 +13,18 @@
* Increasing rows/buckets' interval for better visual representation * Increasing rows/buckets' interval for better visual representation
* Representing null values as empty bucket or zero value * Representing null values as empty bucket or zero value
:calendar: New features are planned in [#62](https://github.com/flant/grafana-statusmap/issues/62)
### Supported environment ### Supported environment
* Prometheus datasource * Tested with datasources:
* Tested with Grafana 5.1.3 - Prometheus
- InfluxDB
- Mysql
* Tested with Grafana:
- 5.1.3
- 5.4.3
- 6.0.0
## Installation ## Installation
@@ -49,7 +57,18 @@ above. These objects should have _discrete statuses_ which are sets of predefine
## Configuration ## Configuration
### Prometheus ### Datasource notes
To create neat graphs your datasource should return good data. Plugin adjust `$__interval` variable depending on
bucket width in panel options. Your queries should aggregate statuses over `$__interval`.
To make multiple values mode works as expected you should define multiple queries: one query for each possible status.
Plugin doesn't aggregate data in time for now, it only renders input data as buckets. Because of this
data should contain points for each timestamp in time range and equal timestamps for every possible
target (y-axis label). This limitation is addressed by [issue #53](https://github.com/flant/grafana-statusmap/issues/53).
#### Prometheus
To work with data from Prometheus you will need to setup discrete statuses for your objects. To work with data from Prometheus you will need to setup discrete statuses for your objects.
Requirements to store these statuses in metrics are as follows: Requirements to store these statuses in metrics are as follows:
@@ -83,6 +102,28 @@ coffee_maker_status:discrete{status="3"} 1
Now, when Prometheus has `0` and `1` values for each status, all these metrics can be Now, when Prometheus has `0` and `1` values for each status, all these metrics can be
aggregated, so you will get all available statuses of your objects over time. aggregated, so you will get all available statuses of your objects over time.
#### InfluxDB
Choose 'Time series' for 'Format as' and use `GROUP BY ($__interval)` in query. `$tag_<tag name>` can be used in 'Alias by' to define y-axis labels.
#### Mysql
Example query with aggregation over `$__interval` is like this (you need one query for each possible status value):
```
SELECT
$__timeGroupAlias(date_insert,$__interval),
name AS metric,
min(statusi) AS "statusi"
FROM coffee_makers
WHERE
$__timeFilter(date_insert) AND statusi=1
GROUP BY 1,2
ORDER BY $__timeGroup(date_insert,$__interval)
```
`metric` column is used as y-axis label.
### Panel ### Panel
First of all, an individual query for each possible status' value should be created. First of all, an individual query for each possible status' value should be created.
@@ -94,11 +135,20 @@ Then, color mapping for status' values should be defined in __Discrete__ color m
![Color mapping](https://raw.githubusercontent.com/flant/grafana-statusmap/master/src/img/color-mapping.png) ![Color mapping](https://raw.githubusercontent.com/flant/grafana-statusmap/master/src/img/color-mapping.png)
Use can use presets to define a trafic light colors or 8 colors from [solarized](https://ethanschoonover.com/solarized/) palette:
![Color mapping empty](https://raw.githubusercontent.com/flant/grafana-statusmap/master/src/img/color-preset-01.png)
![Color mapping trafic lights](https://raw.githubusercontent.com/flant/grafana-statusmap/master/src/img/color-preset-02.png)
_Note: __Spectrum__ and __Opacity__ color modes function the same way they do in [Heatmap](https://grafana.com/plugins/heatmap) plugin._ _Note: __Spectrum__ and __Opacity__ color modes function the same way they do in [Heatmap](https://grafana.com/plugins/heatmap) plugin._
### More options ### More options
#### Bucket
![Bucket options](https://raw.githubusercontent.com/flant/grafana-statusmap/master/src/img/options-bucket.png) ![Bucket options](https://raw.githubusercontent.com/flant/grafana-statusmap/master/src/img/options-bucket.png)
__Multiple values__ checkbox specifies how they should be displayed: __Multiple values__ checkbox specifies how they should be displayed:
@@ -114,8 +164,19 @@ __Null values__ can be treated as empty buckets or displayed with the color of `
__Min width__ and __spacing__ are used to specify minimal bucket width and spacing between buckets. __Min width__ and __spacing__ are used to specify minimal bucket width and spacing between buckets.
__Rounding__ may be used to round edges. __Rounding__ may be used to round edges.
![Min width, spacing, rounding](https://raw.githubusercontent.com/flant/grafana-statusmap/master/src/img/min-width-spacing-rounding.png) ![Min width, spacing, rounding 1](https://raw.githubusercontent.com/flant/grafana-statusmap/master/src/img/min-width-spacing-rounding-01.png)
![Min width, spacing, rounding 2](https://raw.githubusercontent.com/flant/grafana-statusmap/master/src/img/min-width-spacing-rounding-02.png)
#### Display
![Display options](https://raw.githubusercontent.com/flant/grafana-statusmap/master/src/img/options-display.png)
__Show legend__ checkbox toggles legend at the bottom of the panel.
__Show tooltip__ toggles tooltip display on mouse over buckets.
__Y axis sort__ can be used to sort labels on Y axis. Metrics — sort y labels as they are defined on Metrics tab. a→z and z→a sort labels descending or ascending in a [natural](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare) order.
## Development ## Development
@@ -142,15 +203,7 @@ The first public release of this plugin has been fully made by [Flant](https://f
This plugin is based on "Heatmap" panel by Grafana and partly inspired by ideas from Carpet plot, Discrete panel, Status Panel, Status Dot, Status By Group. This plugin is based on "Heatmap" panel by Grafana and partly inspired by ideas from Carpet plot, Discrete panel, Status Panel, Status Dot, Status By Group.
#### Changelog
##### v0.0.2
- Install with GF_INSTALL_PLUGINS
- Fix legend overlap
##### v0.0.1
- First public release
### CHANGELOG
The latest changes can be found here: [CHANGELOG.md](https://github.com/flant/grafana-statusmap/blob/master/CHANGELOG.md)
+41
View File
@@ -0,0 +1,41 @@
# Changelog
## v0.1.1
- Fix for annotations in grafana 5.x before 5.4
## v0.1.0
- Tested with Grafana 6.0.0
- Tested with InfluxDB and Mysql datasources
- Add initial support for display annotations
- Add example for k8s statuses (thanks, @vrutkovs)
- Fix hanging on big values
- Fix horizontal spacing = 0
- Fix for "Object doesn't support property or method 'remove'"
- Fix card width for targets with different datapoints count
## v0.0.4
- Fix display of multivalues buckets as an empty cell
## v0.0.3
- Add solarized preset
- Reorganize statuses editor for discrete mode
- Separate options for vertical and horizontal spacing for cards
- Add simple sort options for Y axis
- Fix display null values as zero
## v0.0.2
- Install with GF_INSTALL_PLUGINS
- Fix legend overlap
- Fix colors for dark theme
- Fix panel rendering timeout error
## v0.0.1
- First public release
+67 -14
View File
@@ -13,10 +13,18 @@
* Increasing rows/buckets' interval for better visual representation * Increasing rows/buckets' interval for better visual representation
* Representing null values as empty bucket or zero value * Representing null values as empty bucket or zero value
:calendar: New features are planned in [#62](https://github.com/flant/grafana-statusmap/issues/62)
### Supported environment ### Supported environment
* Prometheus datasource * Tested with datasources:
* Tested with Grafana 5.1.3 - Prometheus
- InfluxDB
- Mysql
* Tested with Grafana:
- 5.1.3
- 5.4.3
- 6.0.0
## Installation ## Installation
@@ -49,7 +57,18 @@ above. These objects should have _discrete statuses_ which are sets of predefine
## Configuration ## Configuration
### Prometheus ### Datasource notes
To create neat graphs your datasource should return good data. Plugin adjust `$__interval` variable depending on
bucket width in panel options. Your queries should aggregate statuses over `$__interval`.
To make multiple values mode works as expected you should define multiple queries: one query for each possible status.
Plugin doesn't aggregate data in time for now, it only renders input data as buckets. Because of this
data should contain points for each timestamp in time range and equal timestamps for every possible
target (y-axis label). This limitation is addressed by [issue #53](https://github.com/flant/grafana-statusmap/issues/53).
#### Prometheus
To work with data from Prometheus you will need to setup discrete statuses for your objects. To work with data from Prometheus you will need to setup discrete statuses for your objects.
Requirements to store these statuses in metrics are as follows: Requirements to store these statuses in metrics are as follows:
@@ -83,6 +102,28 @@ coffee_maker_status:discrete{status="3"} 1
Now, when Prometheus has `0` and `1` values for each status, all these metrics can be Now, when Prometheus has `0` and `1` values for each status, all these metrics can be
aggregated, so you will get all available statuses of your objects over time. aggregated, so you will get all available statuses of your objects over time.
#### InfluxDB
Choose 'Time series' for 'Format as' and use `GROUP BY ($__interval)` in query. `$tag_<tag name>` can be used in 'Alias by' to define y-axis labels.
#### Mysql
Example query with aggregation over `$__interval` is like this (you need one query for each possible status value):
```
SELECT
$__timeGroupAlias(date_insert,$__interval),
name AS metric,
min(statusi) AS "statusi"
FROM coffee_makers
WHERE
$__timeFilter(date_insert) AND statusi=1
GROUP BY 1,2
ORDER BY $__timeGroup(date_insert,$__interval)
```
`metric` column is used as y-axis label.
### Panel ### Panel
First of all, an individual query for each possible status' value should be created. First of all, an individual query for each possible status' value should be created.
@@ -94,11 +135,20 @@ Then, color mapping for status' values should be defined in __Discrete__ color m
![Color mapping](https://raw.githubusercontent.com/flant/grafana-statusmap/master/src/img/color-mapping.png) ![Color mapping](https://raw.githubusercontent.com/flant/grafana-statusmap/master/src/img/color-mapping.png)
Use can use presets to define a trafic light colors or 8 colors from [solarized](https://ethanschoonover.com/solarized/) palette:
![Color mapping empty](https://raw.githubusercontent.com/flant/grafana-statusmap/master/src/img/color-preset-01.png)
![Color mapping trafic lights](https://raw.githubusercontent.com/flant/grafana-statusmap/master/src/img/color-preset-02.png)
_Note: __Spectrum__ and __Opacity__ color modes function the same way they do in [Heatmap](https://grafana.com/plugins/heatmap) plugin._ _Note: __Spectrum__ and __Opacity__ color modes function the same way they do in [Heatmap](https://grafana.com/plugins/heatmap) plugin._
### More options ### More options
#### Bucket
![Bucket options](https://raw.githubusercontent.com/flant/grafana-statusmap/master/src/img/options-bucket.png) ![Bucket options](https://raw.githubusercontent.com/flant/grafana-statusmap/master/src/img/options-bucket.png)
__Multiple values__ checkbox specifies how they should be displayed: __Multiple values__ checkbox specifies how they should be displayed:
@@ -114,8 +164,19 @@ __Null values__ can be treated as empty buckets or displayed with the color of `
__Min width__ and __spacing__ are used to specify minimal bucket width and spacing between buckets. __Min width__ and __spacing__ are used to specify minimal bucket width and spacing between buckets.
__Rounding__ may be used to round edges. __Rounding__ may be used to round edges.
![Min width, spacing, rounding](https://raw.githubusercontent.com/flant/grafana-statusmap/master/src/img/min-width-spacing-rounding.png) ![Min width, spacing, rounding 1](https://raw.githubusercontent.com/flant/grafana-statusmap/master/src/img/min-width-spacing-rounding-01.png)
![Min width, spacing, rounding 2](https://raw.githubusercontent.com/flant/grafana-statusmap/master/src/img/min-width-spacing-rounding-02.png)
#### Display
![Display options](https://raw.githubusercontent.com/flant/grafana-statusmap/master/src/img/options-display.png)
__Show legend__ checkbox toggles legend at the bottom of the panel.
__Show tooltip__ toggles tooltip display on mouse over buckets.
__Y axis sort__ can be used to sort labels on Y axis. Metrics — sort y labels as they are defined on Metrics tab. a→z and z→a sort labels descending or ascending in a [natural](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare) order.
## Development ## Development
@@ -142,15 +203,7 @@ The first public release of this plugin has been fully made by [Flant](https://f
This plugin is based on "Heatmap" panel by Grafana and partly inspired by ideas from Carpet plot, Discrete panel, Status Panel, Status Dot, Status By Group. This plugin is based on "Heatmap" panel by Grafana and partly inspired by ideas from Carpet plot, Discrete panel, Status Panel, Status Dot, Status By Group.
#### Changelog
##### v0.0.2
- Install with GF_INSTALL_PLUGINS
- Fix legend overlap
##### v0.0.1
- First public release
### CHANGELOG
The latest changes can be found here: [CHANGELOG.md](https://github.com/flant/grafana-statusmap/blob/master/CHANGELOG.md)
+183
View File
@@ -0,0 +1,183 @@
'use strict';
System.register(['d3', 'jquery', 'lodash', 'app/core/utils/kbn'], function (_export, _context) {
"use strict";
var d3, $, _, kbn, _createClass, TOOLTIP_PADDING_X, TOOLTIP_PADDING_Y, AnnotationTooltip;
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
return {
setters: [function (_d) {
d3 = _d.default;
}, function (_jquery) {
$ = _jquery.default;
}, function (_lodash) {
_ = _lodash.default;
}, function (_appCoreUtilsKbn) {
kbn = _appCoreUtilsKbn.default;
}],
execute: function () {
_createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
TOOLTIP_PADDING_X = 30;
TOOLTIP_PADDING_Y = 10;
_export('AnnotationTooltip', AnnotationTooltip = function () {
function AnnotationTooltip(elem, scope) {
_classCallCheck(this, AnnotationTooltip);
this.scope = scope;
this.dashboard = scope.ctrl.dashboard;
this.panelCtrl = scope.ctrl;
this.panel = scope.ctrl.panel;
this.mouseOverAnnotationTick = false;
elem.on("mouseover", this.onMouseOver.bind(this));
elem.on("mouseleave", this.onMouseLeave.bind(this));
}
_createClass(AnnotationTooltip, [{
key: 'onMouseOver',
value: function onMouseOver(e) {
if (!this.panel.tooltip.show || !this.scope.ctrl.data || _.isEmpty(this.scope.ctrl.data)) {
return;
}
if (!this.tooltip) {
this.add();
this.move(e);
}
}
}, {
key: 'onMouseLeave',
value: function onMouseLeave() {
this.destroy();
}
}, {
key: 'onMouseMove',
value: function onMouseMove(e) {
if (!this.panel.tooltip.show) {
return;
}
this.move(e);
}
}, {
key: 'add',
value: function add() {
this.tooltipBase = d3.select("body").append("div").attr("class", "statusmap-annotation-tooltip drop drop-popover drop-popover--annotation drop-element drop-enabled drop-target-attached-center drop-open drop-open-transitionend drop-after-open").style("position", "absolute");
this.tooltip = this.tooltipBase.append("div").attr("class", "drop-content").append("div").append("annotation-tooltip").append("div").attr("class", "graph-annotation");
}
}, {
key: 'destroy',
value: function destroy() {
if (this.tooltip) {
this.tooltip.remove();
}
this.tooltip = null;
if (this.tooltipBase) {
this.tooltipBase.remove();
}
this.tooltipBase = null;
}
}, {
key: 'show',
value: function show(pos) {
if (!this.panel.tooltip.show || !this.tooltip) {
return;
}
// shared tooltip mode
//if (pos.panelRelY) {
// return;
//}
var annoId = d3.select(pos.target).attr('annoId');
if (!annoId) {
this.destroy();
return;
}
var anno = this.panelCtrl.annotations[annoId];
if (!anno) {
this.destroy();
return;
}
var annoTitle = "";
var tooltipTimeFormat = 'YYYY-MM-DD HH:mm:ss';
var annoTime = this.dashboard.formatDate(anno.time, tooltipTimeFormat);
var annoText = anno.text;
var annoTags = [];
if (anno.tags) {
annoTags = _.map(anno.tags, function (t) {
return { "text": t, "backColor": "rgb(63, 43, 91)", "borderColor": "rgb(101, 81, 129)" };
});
}
var tooltipHtml = '<div class="graph-annotation__header">\n <span class="graph-annotation__title">' + annoTitle + '</span>\n <span class="graph-annotation__time">' + annoTime + '</span></div>\n <div class="graph-annotation__body">\n <div>' + annoText + '</div>\n ' + _.join(_.map(annoTags, function (t) {
return '<span class="label label-tag small" style="background-color: ' + t.backColor + '; border-color: ' + t.borderColor + '">' + t.text + '</span>';
}), "") + '\n </div>\n <div class="statusmap-histogram"></div>';
this.tooltip.html(tooltipHtml);
this.move(pos);
}
}, {
key: 'move',
value: function move(pos) {
if (!this.tooltipBase) {
return;
}
var elem = $(this.tooltipBase.node())[0];
var tooltipWidth = elem.clientWidth;
var tooltipHeight = elem.clientHeight;
var left = pos.pageX - tooltipWidth / 2;
var top = pos.pageY + TOOLTIP_PADDING_Y;
if (pos.pageX + tooltipWidth / 2 + 10 > window.innerWidth) {
left = pos.pageX - tooltipWidth - TOOLTIP_PADDING_X;
}
if (pos.pageY - window.pageYOffset + tooltipHeight + 20 > window.innerHeight) {
top = pos.pageY - tooltipHeight - TOOLTIP_PADDING_Y;
}
return this.tooltipBase.style("left", left + "px").style("top", top + "px");
}
}]);
return AnnotationTooltip;
}());
_export('AnnotationTooltip', AnnotationTooltip);
}
};
});
//# sourceMappingURL=annotations.js.map
+1
View File
File diff suppressed because one or more lines are too long
+37 -43
View File
@@ -3,7 +3,7 @@
System.register(['angular', 'lodash', 'jquery', 'd3', './libs/d3-scale-chromatic/index', 'app/core/core', 'app/core/utils/ticks'], function (_export, _context) { System.register(['angular', 'lodash', 'jquery', 'd3', './libs/d3-scale-chromatic/index', 'app/core/core', 'app/core/utils/ticks'], function (_export, _context) {
"use strict"; "use strict";
var angular, _, $, d3, d3ScaleChromatic, contextSrv, tickStep, mod, MIN_LEGEND_STEPS; var angular, _, $, d3, d3ScaleChromatic, contextSrv, tickStep, mod, LEGEND_STEP_WIDTH;
function drawColorLegend(elem, colorScheme, rangeFrom, rangeTo, maxValue, minValue) { function drawColorLegend(elem, colorScheme, rangeFrom, rangeTo, maxValue, minValue) {
var legendElem = $(elem).find('svg'); var legendElem = $(elem).find('svg');
@@ -13,24 +13,21 @@ System.register(['angular', 'lodash', 'jquery', 'd3', './libs/d3-scale-chromatic
var legendWidth = Math.floor(legendElem.outerWidth()) - 30; // narrow legendWidth by 30px to get space for first and last tick values var legendWidth = Math.floor(legendElem.outerWidth()) - 30; // narrow legendWidth by 30px to get space for first and last tick values
var legendHeight = legendElem.attr("height"); var legendHeight = legendElem.attr("height");
var rangeStep = 1; var rangeStep = (rangeTo - rangeFrom) / (legendWidth / LEGEND_STEP_WIDTH);
if (rangeTo - rangeFrom > legendWidth) { // width in pixels in legend space of unit segment in range space
rangeStep = Math.floor((rangeTo - rangeFrom) / legendWidth); // rangeStep * witdhFactor == width in pixels of one rangeStep
}
if (rangeStep * MIN_LEGEND_STEPS > rangeTo) {
rangeStep = rangeTo / MIN_LEGEND_STEPS;
}
var widthFactor = legendWidth / (rangeTo - rangeFrom); var widthFactor = legendWidth / (rangeTo - rangeFrom);
var valuesRange = d3.range(rangeFrom, rangeTo, rangeStep); var valuesRange = d3.range(rangeFrom, rangeTo, rangeStep);
var colorScale = getColorScale(colorScheme, maxValue, minValue); var colorScale = getColorScale(colorScheme, maxValue, minValue);
legend.selectAll(".status-heatmap-color-legend-rect").data(valuesRange).enter().append("rect").attr("x", function (d) { legend.selectAll(".status-heatmap-color-legend-rect").data(valuesRange).enter().append("rect")
// translate from range space into pixels
// and shift all rectangles to the right by 10
.attr("x", function (d) {
return d * widthFactor + 10; return d * widthFactor + 10;
}) // shift all color rectangles to the right }).attr("y", 0)
.attr("y", 0).attr("width", rangeStep * widthFactor + 1) // Overlap rectangles to prevent gaps // rectangles are slightly overlaped to prevent gaps
.attr("height", legendHeight).attr("stroke-width", 0).attr("fill", function (d) { .attr("width", LEGEND_STEP_WIDTH + 1).attr("height", legendHeight).attr("stroke-width", 0).attr("fill", function (d) {
return colorScale(d); return colorScale(d);
}); });
@@ -45,20 +42,21 @@ System.register(['angular', 'lodash', 'jquery', 'd3', './libs/d3-scale-chromatic
var legendWidth = Math.floor(legendElem.outerWidth()) - 30; // narrow legendWidth by 30px to get space for first and last tick values var legendWidth = Math.floor(legendElem.outerWidth()) - 30; // narrow legendWidth by 30px to get space for first and last tick values
var legendHeight = legendElem.attr("height"); var legendHeight = legendElem.attr("height");
var rangeStep = 10; var rangeStep = (rangeTo - rangeFrom) / (legendWidth / LEGEND_STEP_WIDTH);
// width in pixels in legend space of unit segment in range space
// rangeStep * witdhFactor == width in pixels of one rangeStep
var widthFactor = legendWidth / (rangeTo - rangeFrom); var widthFactor = legendWidth / (rangeTo - rangeFrom);
if (rangeStep * MIN_LEGEND_STEPS > rangeTo) {
rangeStep = rangeTo / MIN_LEGEND_STEPS;
}
var valuesRange = d3.range(rangeFrom, rangeTo, rangeStep); var valuesRange = d3.range(rangeFrom, rangeTo, rangeStep);
var opacityScale = getOpacityScale(options, maxValue, minValue); var opacityScale = getOpacityScale(options, maxValue, minValue);
legend.selectAll(".status-heatmap-opacity-legend-rect").data(valuesRange).enter().append("rect").attr("x", function (d) { legend.selectAll(".status-heatmap-opacity-legend-rect").data(valuesRange).enter().append("rect")
// translate from range space into pixels
// and shift all rectangles to the right by 10
.attr("x", function (d) {
return d * widthFactor + 10; return d * widthFactor + 10;
}) // shift all opacity rectangles to the right }).attr("y", 0)
.attr("y", 0).attr("width", rangeStep * widthFactor).attr("height", legendHeight).attr("stroke-width", 0).attr("fill", options.cardColor).style("opacity", function (d) { // rectangles are slightly overlaped to prevent gaps
.attr("width", LEGEND_STEP_WIDTH + 1).attr("height", legendHeight).attr("stroke-width", 0).attr("fill", options.cardColor).style("opacity", function (d) {
return opacityScale(d); return opacityScale(d);
}); });
@@ -82,13 +80,13 @@ System.register(['angular', 'lodash', 'jquery', 'd3', './libs/d3-scale-chromatic
// calculate max width of tooltip and use it as width for each item // calculate max width of tooltip and use it as width for each item
var textWidth = []; var textWidth = [];
legend.selectAll(".hidden-texts").data(tooltips).enter().append("text").attr("class", "axis tick").attr("font-family", "sans-serif").text(function (d) { legend.selectAll(".hidden-texts").data(tooltips).enter().append("text").attr("class", "axis tick hidden-texts").attr("font-family", "sans-serif").text(function (d) {
return d; return d;
}).each(function (d, i) { }).each(function (d, i) {
var thisWidth = this.getBBox().width; var thisWidth = this.getBBox().width;
textWidth.push(thisWidth); textWidth.push(thisWidth);
this.remove(); // remove them just after displaying them
}); });
legend.selectAll(".hidden-texts").remove();
var legendWidth = Math.floor(_.min([graphWidth - 30, (_.max(textWidth) + 3) * valuesNumber])); var legendWidth = Math.floor(_.min([graphWidth - 30, (_.max(textWidth) + 3) * valuesNumber]));
legendElem.attr("width", legendWidth); legendElem.attr("width", legendWidth);
@@ -180,22 +178,18 @@ System.register(['angular', 'lodash', 'jquery', 'd3', './libs/d3-scale-chromatic
function drawSimpleColorLegend(elem, colorScale) { function drawSimpleColorLegend(elem, colorScale) {
var legendElem = $(elem).find('svg'); var legendElem = $(elem).find('svg');
var legend = d3.select(legendElem.get(0));
clearLegend(elem); clearLegend(elem);
var legendWidth = Math.floor(legendElem.outerWidth()); var legendWidth = Math.floor(legendElem.outerWidth());
var legendHeight = legendElem.attr("height"); var legendHeight = legendElem.attr("height");
if (legendWidth) { if (legendWidth) {
var valuesNumber = Math.floor(legendWidth / 2); var valuesRange = d3.range(0, legendWidth, LEGEND_STEP_WIDTH);
var rangeStep = Math.floor(legendWidth / valuesNumber);
var valuesRange = d3.range(0, legendWidth, rangeStep);
var legend = d3.select(legendElem.get(0)); legend.selectAll(".status-heatmap-color-legend-rect").data(valuesRange).enter().append("rect").attr("x", function (d) {
var legendRects = legend.selectAll(".status-heatmap-color-legend-rect").data(valuesRange);
legendRects.enter().append("rect").attr("x", function (d) {
return d; return d;
}).attr("y", 0).attr("width", rangeStep + 1) // Overlap rectangles to prevent gaps }).attr("y", 0).attr("width", LEGEND_STEP_WIDTH + 1) // Overlap rectangles to prevent gaps
.attr("height", legendHeight).attr("stroke-width", 0).attr("fill", function (d) { .attr("height", legendHeight).attr("stroke-width", 0).attr("fill", function (d) {
return colorScale(d); return colorScale(d);
}); });
@@ -204,10 +198,9 @@ System.register(['angular', 'lodash', 'jquery', 'd3', './libs/d3-scale-chromatic
function drawSimpleOpacityLegend(elem, options) { function drawSimpleOpacityLegend(elem, options) {
var legendElem = $(elem).find('svg'); var legendElem = $(elem).find('svg');
var graphElem = $(elem); var legend = d3.select(legendElem.get(0));
clearLegend(elem); clearLegend(elem);
var legend = d3.select(legendElem.get(0));
var legendWidth = Math.floor(legendElem.outerWidth()); var legendWidth = Math.floor(legendElem.outerWidth());
var legendHeight = legendElem.attr("height"); var legendHeight = legendElem.attr("height");
@@ -219,13 +212,11 @@ System.register(['angular', 'lodash', 'jquery', 'd3', './libs/d3-scale-chromatic
legendOpacityScale = d3.scalePow().exponent(options.exponent).domain([0, legendWidth]).range([0, 1]); legendOpacityScale = d3.scalePow().exponent(options.exponent).domain([0, legendWidth]).range([0, 1]);
} }
var rangeStep = 10; var valuesRange = d3.range(0, legendWidth, LEGEND_STEP_WIDTH);
var valuesRange = d3.range(0, legendWidth, rangeStep);
var legendRects = legend.selectAll(".status-heatmap-opacity-legend-rect").data(valuesRange);
legendRects.enter().append("rect").attr("x", function (d) { legend.selectAll(".status-heatmap-opacity-legend-rect").data(valuesRange).enter().append("rect").attr("x", function (d) {
return d; return d;
}).attr("y", 0).attr("width", rangeStep).attr("height", legendHeight).attr("stroke-width", 0).attr("fill", options.cardColor).style("opacity", function (d) { }).attr("y", 0).attr("width", LEGEND_STEP_WIDTH + 1).attr("height", legendHeight).attr("stroke-width", 0).attr("fill", options.cardColor).style("opacity", function (d) {
return legendOpacityScale(d); return legendOpacityScale(d);
}); });
} }
@@ -331,11 +322,11 @@ System.register(['angular', 'lodash', 'jquery', 'd3', './libs/d3-scale-chromatic
}], }],
execute: function () { execute: function () {
mod = angular.module('grafana.directives'); mod = angular.module('grafana.directives');
MIN_LEGEND_STEPS = 10; LEGEND_STEP_WIDTH = 2;
/** /**
* Color legend for heatmap editor. * Bigger color legend for opacity and spectrum modes editor.
*/ */
mod.directive('optionsColorLegend', function () { mod.directive('optionsColorLegend', function () {
return { return {
@@ -369,7 +360,7 @@ System.register(['angular', 'lodash', 'jquery', 'd3', './libs/d3-scale-chromatic
}); });
/** /**
* Heatmap legend with scale values. * Graph legend with values.
*/ */
mod.directive('statusHeatmapLegend', function () { mod.directive('statusHeatmapLegend', function () {
return { return {
@@ -386,6 +377,9 @@ System.register(['angular', 'lodash', 'jquery', 'd3', './libs/d3-scale-chromatic
function render() { function render() {
clearLegend(elem); clearLegend(elem);
if (!ctrl.panel.legend.show) {
return;
}
if (!_.isEmpty(ctrl.cardsData) && !_.isEmpty(ctrl.cardsData.cards)) { if (!_.isEmpty(ctrl.cardsData) && !_.isEmpty(ctrl.cardsData.cards)) {
var rangeFrom = ctrl.cardsData.minValue; var rangeFrom = ctrl.cardsData.minValue;
var rangeTo = ctrl.cardsData.maxValue; var rangeTo = ctrl.cardsData.maxValue;
+1 -1
View File
File diff suppressed because one or more lines are too long
+27 -3
View File
@@ -101,9 +101,33 @@ System.register(["lodash"], function (_export, _context) {
value: function getBucketColor(values) { value: function getBucketColor(values) {
var thresholds = this.panel.color.thresholds; var thresholds = this.panel.color.thresholds;
if (!values || values.length == 0) {
// treat as null value
return this.getMatchedThreshold(null).color;
}
if (values.length == 1) {
var threshold = this.getMatchedThreshold(values[0]);
if (!threshold || !threshold.color || threshold.color == "") {
return 'rgba(0,0,0,1)';
} else {
return threshold.color;
}
}
var isAllValuesNulls = true;
for (var j = 0; j < values.length; j++) {
if (values[j] != null) {
isAllValuesNulls = false;
}
}
if (isAllValuesNulls) {
return this.getMatchedThreshold(null).color;
}
for (var i = 0; i < thresholds.length; i++) { for (var i = 0; i < thresholds.length; i++) {
for (var j = 0; j < values.length; j++) { for (var _j = 0; _j < values.length; _j++) {
if (values[j] == thresholds[i].value) { if (values[_j] == thresholds[i].value) {
return this.getDiscreteColor(i); return this.getDiscreteColor(i);
} }
} }
@@ -135,7 +159,7 @@ System.register(["lodash"], function (_export, _context) {
key: "getMatchedThreshold", key: "getMatchedThreshold",
value: function getMatchedThreshold(value) { value: function getMatchedThreshold(value) {
if (value == null) { if (value == null) {
if (this.panel.color.nullPointMode == 'as empty') { if (this.panel.nullPointMode == 'as empty') {
// FIXME: make this explicit for user // FIXME: make this explicit for user
// Right now this color never used because null as empty handles in getCardOpacity method. // Right now this color never used because null as empty handles in getCardOpacity method.
return { return {
+1 -1
View File
File diff suppressed because one or more lines are too long
+2
View File
@@ -1,5 +1,7 @@
.status-heatmap-canvas-wrapper { .status-heatmap-canvas-wrapper {
cursor: crosshair; } cursor: crosshair; }
.status-heatmap-canvas-wrapper .datapoints-warning {
pointer-events: none; }
.status-heatmap-canvas-wrapper .datapoints-warning span { .status-heatmap-canvas-wrapper .datapoints-warning span {
background-color: #333333; background-color: #333333;
color: #d8d9da; color: #d8d9da;
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"version": 3, "version": 3,
"mappings": "AAAA,8BAA+B;EAE7B,MAAM,EAAE,SAAS;EAEjB,uDAAyB;IACzB,gBAAgB,ECDL,OAAO;IDElB,KAAK,ECLM,OAAO;IDMlB,OAAO,EAAE,GAAG;;AAId,qBAAsB;EACpB,QAAQ,EAAE,QAAQ;EAGhB,sCAAK;IACH,IAAI,ECfG,OAAO;IDgBd,KAAK,EChBE,OAAO;IDiBd,SAAS,EEjBE,IAAI;EFoBjB,sCAAK;IACH,OAAO,EAAE,GAAG;IACZ,MAAM,ECrBM,OAAO;ED0BvB,yBAAI;IACF,cAAc,EAAE,IAAI;IAEpB,8BAAK;MACH,cAAc,EAAE,cAAc;;AAKpC,kBAAmB;EACjB,WAAW,EAAE,MAAM;EACnB,SAAS,EEvCI,IAAI;EFwCjB,gBAAgB,ECrCC,OAAO;EDsCxB,KAAK,ECxCM,OAAO;ED0ClB,iCAAe;IACb,KAAK,EAAE,OAAO;IACd,OAAO,EAAE,GAAG;IACZ,WAAW,EAAE,IAAI;IACjB,WAAW,EAAE,8CAA8C;;AAI/D,yBAA0B;EACxB,IAAI,EClDY,OAAO;;ADsDvB,8BAAK;EACH,MAAM,EAAE,OAAgB;EACxB,YAAY,EAAE,CAAC;;AAInB,yBAA0B;EACxB,YAAY,EAAE,CAAC;EACf,IAAI,EAAE,wBAAwB;EAC9B,MAAM,EAAE,wBAAwB;;AAIlC,8BAA+B;EAC7B,MAAM,EAAE,MAAM;EAEd,kCAAI;IACF,MAAM,EAAE,IAAI;IACZ,KAAK,EAAE,IAAI;IACX,WAAW,EAAE,MAAM;EAIrB,2DAA6B;IAC3B,UAAU,EAAE,GAAG;EAGjB,4DAA8B;IAC5B,OAAO,EAAE,YAAY;EAIrB,+CAAK;IACH,IAAI,ECxFG,OAAO;IDyFd,KAAK,ECzFE,OAAO;ID0Fd,SAAS,EE1FE,IAAI;EF6FjB,+CAAK;IACH,OAAO,EAAE,GAAG;IACZ,MAAM,EC9FM,OAAO;EDiGrB,kDAAQ;IACN,OAAO,EAAE,GAAG;IACZ,MAAM,ECnGM,OAAO", "mappings": "AAAA,8BAA+B;EAE7B,MAAM,EAAE,SAAS;EAGjB,kDAAoB;IAClB,cAAc,EAAE,IAAI;EAGtB,uDAAyB;IACzB,gBAAgB,ECNL,OAAO;IDOlB,KAAK,ECVM,OAAO;IDWlB,OAAO,EAAE,GAAG;;AAId,qBAAsB;EACpB,QAAQ,EAAE,QAAQ;EAGhB,sCAAK;IACH,IAAI,ECpBG,OAAO;IDqBd,KAAK,ECrBE,OAAO;IDsBd,SAAS,EEtBE,IAAI;EFyBjB,sCAAK;IACH,OAAO,EAAE,GAAG;IACZ,MAAM,EC1BM,OAAO;ED+BvB,yBAAI;IACF,cAAc,EAAE,IAAI;IAEpB,8BAAK;MACH,cAAc,EAAE,cAAc;;AAKpC,kBAAmB;EACjB,WAAW,EAAE,MAAM;EACnB,SAAS,EE5CI,IAAI;EF6CjB,gBAAgB,EC1CC,OAAO;ED2CxB,KAAK,EC7CM,OAAO;ED+ClB,iCAAe;IACb,KAAK,EAAE,OAAO;IACd,OAAO,EAAE,GAAG;IACZ,WAAW,EAAE,IAAI;IACjB,WAAW,EAAE,8CAA8C;;AAI/D,yBAA0B;EACxB,IAAI,ECvDY,OAAO;;AD2DvB,8BAAK;EACH,MAAM,EAAE,OAAgB;EACxB,YAAY,EAAE,CAAC;;AAInB,yBAA0B;EACxB,YAAY,EAAE,CAAC;EACf,IAAI,EAAE,wBAAwB;EAC9B,MAAM,EAAE,wBAAwB;;AAIlC,8BAA+B;EAC7B,MAAM,EAAE,MAAM;EAEd,kCAAI;IACF,MAAM,EAAE,IAAI;IACZ,KAAK,EAAE,IAAI;IACX,WAAW,EAAE,MAAM;EAIrB,2DAA6B;IAC3B,UAAU,EAAE,GAAG;EAGjB,4DAA8B;IAC5B,OAAO,EAAE,YAAY;EAIrB,+CAAK;IACH,IAAI,EC7FG,OAAO;ID8Fd,KAAK,EC9FE,OAAO;ID+Fd,SAAS,EE/FE,IAAI;EFkGjB,+CAAK;IACH,OAAO,EAAE,GAAG;IACZ,MAAM,ECnGM,OAAO;EDsGrB,kDAAQ;IACN,OAAO,EAAE,GAAG;IACZ,MAAM,ECxGM,OAAO",
"sources": ["../../src/css/_statusmap.scss","../../src/css/_variables.dark.scss","../../src/css/_variables.scss"], "sources": ["../../src/css/_statusmap.scss","../../src/css/_variables.dark.scss","../../src/css/_variables.scss"],
"names": [], "names": [],
"file": "statusmap.dark.css" "file": "statusmap.dark.css"
+2
View File
@@ -1,5 +1,7 @@
.status-heatmap-canvas-wrapper { .status-heatmap-canvas-wrapper {
cursor: crosshair; } cursor: crosshair; }
.status-heatmap-canvas-wrapper .datapoints-warning {
pointer-events: none; }
.status-heatmap-canvas-wrapper .datapoints-warning span { .status-heatmap-canvas-wrapper .datapoints-warning span {
background-color: #e9edf2; background-color: #e9edf2;
color: #52545c; color: #52545c;
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"version": 3, "version": 3,
"mappings": "AAAA,8BAA+B;EAE7B,MAAM,EAAE,SAAS;EAEjB,uDAAyB;IACzB,gBAAgB,ECDL,OAAO;IDElB,KAAK,ECLM,OAAO;IDMlB,OAAO,EAAE,GAAG;;AAId,qBAAsB;EACpB,QAAQ,EAAE,QAAQ;EAGhB,sCAAK;IACH,IAAI,ECfG,OAAO;IDgBd,KAAK,EChBE,OAAO;IDiBd,SAAS,EEjBE,IAAI;EFoBjB,sCAAK;IACH,OAAO,EAAE,GAAG;IACZ,MAAM,ECrBM,OAAO;ED0BvB,yBAAI;IACF,cAAc,EAAE,IAAI;IAEpB,8BAAK;MACH,cAAc,EAAE,cAAc;;AAKpC,kBAAmB;EACjB,WAAW,EAAE,MAAM;EACnB,SAAS,EEvCI,IAAI;EFwCjB,gBAAgB,ECrCC,OAAO;EDsCxB,KAAK,ECxCM,OAAO;ED0ClB,iCAAe;IACb,KAAK,EAAE,OAAO;IACd,OAAO,EAAE,GAAG;IACZ,WAAW,EAAE,IAAI;IACjB,WAAW,EAAE,8CAA8C;;AAI/D,yBAA0B;EACxB,IAAI,EClDY,OAAO;;ADsDvB,8BAAK;EACH,MAAM,EAAE,OAAgB;EACxB,YAAY,EAAE,CAAC;;AAInB,yBAA0B;EACxB,YAAY,EAAE,CAAC;EACf,IAAI,EAAE,wBAAwB;EAC9B,MAAM,EAAE,wBAAwB;;AAIlC,8BAA+B;EAC7B,MAAM,EAAE,MAAM;EAEd,kCAAI;IACF,MAAM,EAAE,IAAI;IACZ,KAAK,EAAE,IAAI;IACX,WAAW,EAAE,MAAM;EAIrB,2DAA6B;IAC3B,UAAU,EAAE,GAAG;EAGjB,4DAA8B;IAC5B,OAAO,EAAE,YAAY;EAIrB,+CAAK;IACH,IAAI,ECxFG,OAAO;IDyFd,KAAK,ECzFE,OAAO;ID0Fd,SAAS,EE1FE,IAAI;EF6FjB,+CAAK;IACH,OAAO,EAAE,GAAG;IACZ,MAAM,EC9FM,OAAO;EDiGrB,kDAAQ;IACN,OAAO,EAAE,GAAG;IACZ,MAAM,ECnGM,OAAO", "mappings": "AAAA,8BAA+B;EAE7B,MAAM,EAAE,SAAS;EAGjB,kDAAoB;IAClB,cAAc,EAAE,IAAI;EAGtB,uDAAyB;IACzB,gBAAgB,ECNL,OAAO;IDOlB,KAAK,ECVM,OAAO;IDWlB,OAAO,EAAE,GAAG;;AAId,qBAAsB;EACpB,QAAQ,EAAE,QAAQ;EAGhB,sCAAK;IACH,IAAI,ECpBG,OAAO;IDqBd,KAAK,ECrBE,OAAO;IDsBd,SAAS,EEtBE,IAAI;EFyBjB,sCAAK;IACH,OAAO,EAAE,GAAG;IACZ,MAAM,EC1BM,OAAO;ED+BvB,yBAAI;IACF,cAAc,EAAE,IAAI;IAEpB,8BAAK;MACH,cAAc,EAAE,cAAc;;AAKpC,kBAAmB;EACjB,WAAW,EAAE,MAAM;EACnB,SAAS,EE5CI,IAAI;EF6CjB,gBAAgB,EC1CC,OAAO;ED2CxB,KAAK,EC7CM,OAAO;ED+ClB,iCAAe;IACb,KAAK,EAAE,OAAO;IACd,OAAO,EAAE,GAAG;IACZ,WAAW,EAAE,IAAI;IACjB,WAAW,EAAE,8CAA8C;;AAI/D,yBAA0B;EACxB,IAAI,ECvDY,OAAO;;AD2DvB,8BAAK;EACH,MAAM,EAAE,OAAgB;EACxB,YAAY,EAAE,CAAC;;AAInB,yBAA0B;EACxB,YAAY,EAAE,CAAC;EACf,IAAI,EAAE,wBAAwB;EAC9B,MAAM,EAAE,wBAAwB;;AAIlC,8BAA+B;EAC7B,MAAM,EAAE,MAAM;EAEd,kCAAI;IACF,MAAM,EAAE,IAAI;IACZ,KAAK,EAAE,IAAI;IACX,WAAW,EAAE,MAAM;EAIrB,2DAA6B;IAC3B,UAAU,EAAE,GAAG;EAGjB,4DAA8B;IAC5B,OAAO,EAAE,YAAY;EAIrB,+CAAK;IACH,IAAI,EC7FG,OAAO;ID8Fd,KAAK,EC9FE,OAAO;ID+Fd,SAAS,EE/FE,IAAI;EFkGjB,+CAAK;IACH,OAAO,EAAE,GAAG;IACZ,MAAM,ECnGM,OAAO;EDsGrB,kDAAQ;IACN,OAAO,EAAE,GAAG;IACZ,MAAM,ECxGM,OAAO",
"sources": ["../../src/css/_statusmap.scss","../../src/css/_variables.light.scss","../../src/css/_variables.scss"], "sources": ["../../src/css/_statusmap.scss","../../src/css/_variables.light.scss","../../src/css/_variables.scss"],
"names": [], "names": [],
"file": "statusmap.light.css" "file": "statusmap.light.css"
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 12 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

+1 -1
View File
@@ -1,7 +1,7 @@
<div class="status-heatmap-wrapper"> <div class="status-heatmap-wrapper">
<div class="status-heatmap-canvas-wrapper"> <div class="status-heatmap-canvas-wrapper">
<div class="datapoints-warning"> <div class="datapoints-warning" ng-if="ctrl.multipleValues || ctrl.noColorDefined">
<span class="small" ng-if="ctrl.multipleValues" bs-tooltip="'{{ctrl.dataWarnings.multipleValues.tip}}'">{{ctrl.dataWarnings.multipleValues.title}}</span> <span class="small" ng-if="ctrl.multipleValues" bs-tooltip="'{{ctrl.dataWarnings.multipleValues.tip}}'">{{ctrl.dataWarnings.multipleValues.title}}</span>
<span class="small" ng-if="ctrl.noColorDefined" bs-tooltip="'{{ctrl.dataWarnings.noColorDefined.tip}}'">{{ctrl.dataWarnings.noColorDefined.title}}</span> <span class="small" ng-if="ctrl.noColorDefined" bs-tooltip="'{{ctrl.dataWarnings.noColorDefined.tip}}'">{{ctrl.dataWarnings.noColorDefined.title}}</span>
</div> </div>
+78 -35
View File
@@ -55,40 +55,70 @@
<strong>Note:</strong> Multiple values displayed using color with least index<br/> <strong>Note:</strong> Multiple values displayed using color with least index<br/>
</div> </div>
<div class="gf-form" ng-repeat="threshold in ctrl.panel.color.thresholds"> <div class="gf-form" ng-show="ctrl.panel.color.thresholds.length == 0">
<label class="gf-form-label width-2">0</label>
<label class="gf-form-label width-2">{{ $index }}</label> <label class="gf-form-input width-30">No statuses defined. Add new status or use presets below.</label>
<label class="gf-form-label width-9" ng-if="ctrl.panel.useMax">If bucket has value</label>
<label class="gf-form-label width-9" ng-if="!ctrl.panel.useMax">If bucket value ==</label>
<input type="text" class="input-small gf-form-input width-4" ng-model="threshold.value" ng-change="ctrl.render()"/>
<label class="gf-form-label">
fill
</label>
<label class="gf-form-label">
<spectrum-picker ng-model="threshold.color" ng-change="ctrl.render()"/>
</label>
<label class="gf-form-label">set tooltip</label>
<input type="text" class="input-small gf-form-input width-9" ng-model="threshold.tooltip" ng-change="ctrl.render()"/>
<label class="gf-form-label">
<a class="pointer" ng-click="ctrl.onEditorRemoveThreshold($index)">
<i class="fa fa-trash"/>
</a>
</label>
</div> </div>
<div class="gf-form"> <div class="gf-form-inline" ng-repeat="threshold in ctrl.panel.color.thresholds">
<label class="gf-form-label" ng-show="ctrl.panel.color.thresholds.length == 0"> <div class="gf-form">
<a class="pointer" ng-click="ctrl.onEditorAddThreeLights()"> <label class="gf-form-label width-2">{{ $index }}</label>
Add trafic lights </div>
</a> <div class="gf-form">
</label> <label class="gf-form-label width-9 query-keyword" ng-if="ctrl.panel.useMax">If bucket has value</label>
<label class="gf-form-label"> <label class="gf-form-label width-9 query-keyword" ng-if="!ctrl.panel.useMax">If bucket value ==</label>
<a class="pointer" ng-click="ctrl.onEditorAddThreshold()"> <input type="text" class="input-small gf-form-input width-4" ng-model="threshold.value" ng-change="ctrl.render()"/>
Add new status <label class="gf-form-label query-keyword">
</a> fill
</label> </label>
<label class="gf-form-label">
<spectrum-picker ng-model="threshold.color" ng-change="ctrl.render()"/>
</label>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword">set tooltip</label>
<input type="text" class="input-small gf-form-input width-9" ng-model="threshold.tooltip" ng-change="ctrl.render()"/>
</div>
<div class="gf-form">
<label class="gf-form-label">
<a class="pointer" tabindex="1" ng-click="ctrl.onEditorRemoveThreshold($index)">
<i class="fa fa-trash"/>
</a>
</label>
</div>
</div> </div>
<div class="gf-form-inline">
<div class="gf-form"></div>
<div class="gf-form">
<button class="btn btn-inverse" ng-click="ctrl.onEditorAddThreshold()">
<i class="fa fa-plus"></i> Add new status
</button>
</div>
<div class="gf-form">
<button class="btn btn-inverse" ng-click="ctrl.onEditorRemoveThresholds()">
<i class="fa fa-minus"></i> Remove all
</button>
</div>
</div>
<div class="gf-form-inline" ng-show="ctrl.panel.color.thresholds.length == 0">
<div class="gf-form"></div>
<div class="gf-form">
<label class="gf-form-label">Presets</label>
</div>
<div class="gf-form">
<button class="btn" ng-click="ctrl.onEditorAddThreeLights()">
Red-Yellow-Green
</button>
</div>
<div class="gf-form">
<button class="btn" ng-click="ctrl.onEditorAddSolarized()">
Solarized
</button>
</div>
</div>
</div> </div>
<div class="gf-form" ng-show="ctrl.panel.color.mode !== 'discrete'"> <div class="gf-form" ng-show="ctrl.panel.color.mode !== 'discrete'">
@@ -118,7 +148,16 @@
label="Show tooltip" label="Show tooltip"
checked="ctrl.panel.tooltip.show" on-change="ctrl.render()"> checked="ctrl.panel.tooltip.show" on-change="ctrl.render()">
</gf-form-switch> </gf-form-switch>
</div> <div class="gf-form">
<label class="gf-form-label width-8">Y axis sort</label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input max-width-8"
ng-model="ctrl.panel.yAxisSort"
ng-options="f for f in ['metrics', 'a → z', 'z → a']"
ng-change="ctrl.render()"></select>
</div>
</div>
</div>
<div class="section gf-form-group"> <div class="section gf-form-group">
<h5 class="section-heading">Bucket</h5> <h5 class="section-heading">Bucket</h5>
@@ -142,12 +181,16 @@
<input type="number" class="gf-form-input width-5" placeholder="5" data-placement="right" bs-tooltip="'Minimal card width for 1/1 resolution in pixels'" ng-model="ctrl.panel.cards.cardMinWidth" ng-change="ctrl.refresh()" ng-model-onblur> <input type="number" class="gf-form-input width-5" placeholder="5" data-placement="right" bs-tooltip="'Minimal card width for 1/1 resolution in pixels'" ng-model="ctrl.panel.cards.cardMinWidth" ng-change="ctrl.refresh()" ng-model-onblur>
</div> </div>
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label width-9">Spacing</label> <label class="gf-form-label width-9">V spacing</label>
<input type="number" class="gf-form-input width-5" placeholder="2" data-placement="right" bs-tooltip="'Card spacing in pixels'" ng-model="ctrl.panel.cards.cardSpacing" ng-change="ctrl.refresh()" ng-model-onblur> <input type="number" class="gf-form-input width-5" placeholder="2" data-placement="right" bs-tooltip="'Cards vertical spacing in pixels'" ng-model="ctrl.panel.cards.cardVSpacing" ng-change="ctrl.refresh()" ng-model-onblur>
</div>
<div class="gf-form">
<label class="gf-form-label width-9">H spacing</label>
<input type="number" class="gf-form-input width-5" placeholder="2" data-placement="right" bs-tooltip="'Cards horizontal spacing in pixels'" ng-model="ctrl.panel.cards.cardHSpacing" ng-change="ctrl.refresh()" ng-model-onblur>
</div> </div>
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label width-9">Rounding</label> <label class="gf-form-label width-9">Rounding</label>
<input type="number" class="gf-form-input width-5" placeholder="0" data-placement="right" bs-tooltip="'Angles rounding in percent'" ng-model="ctrl.panel.cards.cardRound" ng-change="ctrl.refresh()" ng-model-onblur> <input type="number" class="gf-form-input width-5" placeholder="0" data-placement="right" bs-tooltip="'Cards rounding radius in pixels'" ng-model="ctrl.panel.cards.cardRound" ng-change="ctrl.refresh()" ng-model-onblur>
</div> </div>
</div> </div>
+2 -2
View File
@@ -25,8 +25,8 @@
{"name": "Color mapping", "path": "img/color-mapping.png"}, {"name": "Color mapping", "path": "img/color-mapping.png"},
{"name": "Queries example", "path": "img/queries-example.png"} {"name": "Queries example", "path": "img/queries-example.png"}
], ],
"version": "0.0.2", "version": "0.1.1",
"updated": "2018-10-17" "updated": "2019-03-22"
}, },
"dependencies": { "dependencies": {
+93 -18
View File
@@ -1,9 +1,9 @@
'use strict'; 'use strict';
System.register(['lodash', 'jquery', 'moment', 'app/core/utils/kbn', 'app/core/core', 'app/core/utils/ticks', 'd3', './libs/d3-scale-chromatic/index', './tooltip'], function (_export, _context) { System.register(['lodash', 'jquery', 'moment', 'app/core/utils/kbn', 'app/core/core', 'app/core/utils/ticks', 'd3', './libs/d3-scale-chromatic/index', './tooltip', './annotations'], function (_export, _context) {
"use strict"; "use strict";
var _, $, moment, kbn, appEvents, contextSrv, tickStep, getScaledDecimals, getFlotTickSize, d3, d3ScaleChromatic, StatusHeatmapTooltip, MIN_CARD_SIZE, CARD_SPACING, CARD_ROUND, DATA_RANGE_WIDING_FACTOR, DEFAULT_X_TICK_SIZE_PX, DEFAULT_Y_TICK_SIZE_PX, X_AXIS_TICK_PADDING, Y_AXIS_TICK_PADDING, MIN_SELECTION_WIDTH; var _, $, moment, kbn, appEvents, contextSrv, tickStep, getScaledDecimals, getFlotTickSize, d3, d3ScaleChromatic, StatusHeatmapTooltip, AnnotationTooltip, MIN_CARD_SIZE, CARD_H_SPACING, CARD_V_SPACING, CARD_ROUND, DATA_RANGE_WIDING_FACTOR, DEFAULT_X_TICK_SIZE_PX, DEFAULT_Y_TICK_SIZE_PX, X_AXIS_TICK_PADDING, Y_AXIS_TICK_PADDING, MIN_SELECTION_WIDTH;
function link(scope, elem, attrs, ctrl) { function link(scope, elem, attrs, ctrl) {
var data = void 0, var data = void 0,
@@ -15,6 +15,7 @@ System.register(['lodash', 'jquery', 'moment', 'app/core/utils/kbn', 'app/core/c
// $heatmap is JQuery object, but heatmap is D3 // $heatmap is JQuery object, but heatmap is D3
var $heatmap = elem.find('.status-heatmap-panel'); var $heatmap = elem.find('.status-heatmap-panel');
var tooltip = new StatusHeatmapTooltip($heatmap, scope); var tooltip = new StatusHeatmapTooltip($heatmap, scope);
var annotationTooltip = new AnnotationTooltip($heatmap, scope);
var width = void 0, var width = void 0,
height = void 0, height = void 0,
@@ -26,7 +27,8 @@ System.register(['lodash', 'jquery', 'moment', 'app/core/utils/kbn', 'app/core/c
chartBottom = void 0, chartBottom = void 0,
yAxisWidth = void 0, yAxisWidth = void 0,
xAxisHeight = void 0, xAxisHeight = void 0,
cardSpacing = void 0, cardVSpacing = void 0,
cardHSpacing = void 0,
cardRound = void 0, cardRound = void 0,
cardWidth = void 0, cardWidth = void 0,
cardHeight = void 0, cardHeight = void 0,
@@ -77,7 +79,7 @@ System.register(['lodash', 'jquery', 'moment', 'app/core/utils/kbn', 'app/core/c
return text.getBBox().width; return text.getBBox().width;
})); }));
return max_text_width; return Math.ceil(max_text_width);
} }
function getXAxisHeight(elem) { function getXAxisHeight(elem) {
@@ -121,9 +123,10 @@ System.register(['lodash', 'jquery', 'moment', 'app/core/utils/kbn', 'app/core/c
function getYScale(ticks) { function getYScale(ticks) {
var range = []; var range = [];
var step = chartHeight / ticks.length; var step = chartHeight / ticks.length;
range.push(chartHeight); // svg has y=0 on the top, so top card should have a minimal value in range
range.push(step);
for (var i = 1; i < ticks.length; i++) { for (var i = 1; i < ticks.length; i++) {
range.push(chartHeight - step * i); range.push(step * (i + 1));
} }
return d3.scaleOrdinal().domain(ticks).range(range); return d3.scaleOrdinal().domain(ticks).range(range);
} }
@@ -132,9 +135,10 @@ System.register(['lodash', 'jquery', 'moment', 'app/core/utils/kbn', 'app/core/c
function getYAxisScale(ticks) { function getYAxisScale(ticks) {
var range = []; var range = [];
var step = chartHeight / ticks.length; var step = chartHeight / ticks.length;
range.push(chartHeight - yOffset); // svg has y=0 on the top, so top tick should have a minimal value in range
range.push(yOffset);
for (var i = 1; i < ticks.length; i++) { for (var i = 1; i < ticks.length; i++) {
range.push(chartHeight - step * i - yOffset); range.push(step * i + yOffset);
} }
return d3.scaleOrdinal().domain(ticks).range(range); return d3.scaleOrdinal().domain(ticks).range(range);
} }
@@ -149,6 +153,16 @@ System.register(['lodash', 'jquery', 'moment', 'app/core/utils/kbn', 'app/core/c
ticks = ['']; ticks = [''];
} }
if (panel.yAxisSort == 'a → z') {
ticks.sort(function (a, b) {
return a.localeCompare(b, 'en', { ignorePunctuation: false, numeric: true });
});
} else if (panel.yAxisSort == 'z → a') {
ticks.sort(function (b, a) {
return a.localeCompare(b, 'en', { ignorePunctuation: false, numeric: true });
});
}
var yAxisScale = getYAxisScale(ticks); var yAxisScale = getYAxisScale(ticks);
scope.yScale = yScale = getYScale(ticks); scope.yScale = yScale = getYScale(ticks);
@@ -216,12 +230,13 @@ System.register(['lodash', 'jquery', 'moment', 'app/core/utils/kbn', 'app/core/c
chartTop = margin.top; chartTop = margin.top;
chartBottom = chartTop + chartHeight; chartBottom = chartTop + chartHeight;
cardSpacing = panel.cards.cardSpacing !== null ? panel.cards.cardSpacing : CARD_SPACING; cardHSpacing = panel.cards.cardHSpacing !== null ? panel.cards.cardHSpacing : CARD_H_SPACING;
cardVSpacing = panel.cards.cardVSpacing !== null ? panel.cards.cardVSpacing : CARD_V_SPACING;
cardRound = panel.cards.cardRound !== null ? panel.cards.cardRound : CARD_ROUND; cardRound = panel.cards.cardRound !== null ? panel.cards.cardRound : CARD_ROUND;
// calculate yOffset for YAxis // calculate yOffset for YAxis
yGridSize = Math.floor(chartHeight / cardsData.yBucketSize); yGridSize = Math.floor(chartHeight / cardsData.yBucketSize);
cardHeight = yGridSize ? yGridSize - cardSpacing : 0; cardHeight = yGridSize ? yGridSize - cardVSpacing : 0;
yOffset = cardHeight / 2; yOffset = cardHeight / 2;
addYAxis(); addYAxis();
@@ -229,9 +244,10 @@ System.register(['lodash', 'jquery', 'moment', 'app/core/utils/kbn', 'app/core/c
yAxisWidth = getYAxisWidth(heatmap) + Y_AXIS_TICK_PADDING; yAxisWidth = getYAxisWidth(heatmap) + Y_AXIS_TICK_PADDING;
chartWidth = width - yAxisWidth - margin.right; chartWidth = width - yAxisWidth - margin.right;
// TODO allow per-y cardWidth!
// we need to fill chartWidth with xBucketSize cards. // we need to fill chartWidth with xBucketSize cards.
xGridSize = chartWidth / (cardsData.xBucketSize + 1); xGridSize = chartWidth / (cardsData.xBucketSize + 1);
cardWidth = xGridSize - cardSpacing; cardWidth = xGridSize - cardHSpacing;
addXAxis(); addXAxis();
xAxisHeight = getXAxisHeight(heatmap); xAxisHeight = getXAxisHeight(heatmap);
@@ -274,6 +290,8 @@ System.register(['lodash', 'jquery', 'moment', 'app/core/utils/kbn', 'app/core/c
resetCardHighLight(event); resetCardHighLight(event);
}); });
_renderAnnotations();
ctrl.events.emit('render-complete', { ctrl.events.emit('render-complete', {
"chartWidth": chartWidth "chartWidth": chartWidth
}); });
@@ -321,7 +339,7 @@ System.register(['lodash', 'jquery', 'moment', 'app/core/utils/kbn', 'app/core/c
var cx = xScale(d.x); var cx = xScale(d.x);
if (cx - cardWidth / 2 < 0) { if (cx - cardWidth / 2 < 0) {
x = yAxisWidth + cardSpacing / 2; x = yAxisWidth + cardHSpacing / 2;
} else { } else {
x = yAxisWidth + cx - cardWidth / 2; x = yAxisWidth + cx - cardWidth / 2;
} }
@@ -337,11 +355,11 @@ System.register(['lodash', 'jquery', 'moment', 'app/core/utils/kbn', 'app/core/c
if (cx < cardWidth / 2) { if (cx < cardWidth / 2) {
// Center should not exceed half of card. // Center should not exceed half of card.
// Cut card to the left to prevent overlay of y axis. // Cut card to the left to prevent overlay of y axis.
var cutted_width = cx - cardSpacing / 2 + cardWidth / 2; var cutted_width = cx - cardHSpacing / 2 + cardWidth / 2;
w = cutted_width > 0 ? cutted_width : 0; w = cutted_width > 0 ? cutted_width : 0;
} else if (chartWidth - cx < cardWidth / 2) { } else if (chartWidth - cx < cardWidth / 2) {
// Cut card to the right to prevent overlay of right graph edge. // Cut card to the right to prevent overlay of right graph edge.
w = cardWidth / 2 + (chartWidth - cx - cardSpacing / 2); w = cardWidth / 2 + (chartWidth - cx - cardHSpacing / 2);
} else { } else {
w = cardWidth; w = cardWidth;
} }
@@ -349,21 +367,25 @@ System.register(['lodash', 'jquery', 'moment', 'app/core/utils/kbn', 'app/core/c
// Card width should be MIN_CARD_SIZE at least // Card width should be MIN_CARD_SIZE at least
w = Math.max(w, MIN_CARD_SIZE); w = Math.max(w, MIN_CARD_SIZE);
if (cardHSpacing == 0) {
w = w + 1;
}
return w; return w;
} }
function getCardY(d) { function getCardY(d) {
return yScale(d.y) + chartTop - cardHeight - cardSpacing / 2; return yScale(d.y) + chartTop - cardHeight - cardVSpacing / 2;
} }
function getCardHeight(d) { function getCardHeight(d) {
var ys = yScale(d.y); var ys = yScale(d.y);
var y = ys + chartTop - cardHeight - cardSpacing / 2; var y = ys + chartTop - cardHeight - cardVSpacing / 2;
var h = cardHeight; var h = cardHeight;
// Cut card height to prevent overlay // Cut card height to prevent overlay
if (y < chartTop) { if (y < chartTop) {
h = ys - cardSpacing / 2; h = ys - cardVSpacing / 2;
} else if (ys > chartBottom) { } else if (ys > chartBottom) {
h = chartBottom - y; h = chartBottom - y;
} else if (y + cardHeight > chartBottom) { } else if (y + cardHeight > chartBottom) {
@@ -375,6 +397,10 @@ System.register(['lodash', 'jquery', 'moment', 'app/core/utils/kbn', 'app/core/c
// Card height should be MIN_CARD_SIZE at least // Card height should be MIN_CARD_SIZE at least
h = Math.max(h, MIN_CARD_SIZE); h = Math.max(h, MIN_CARD_SIZE);
if (cardVSpacing == 0) {
h = h + 1;
}
return h; return h;
} }
@@ -452,6 +478,7 @@ System.register(['lodash', 'jquery', 'moment', 'app/core/utils/kbn', 'app/core/c
function onMouseLeave() { function onMouseLeave() {
appEvents.emit('graph-hover-clear'); appEvents.emit('graph-hover-clear');
clearCrosshair(); clearCrosshair();
//annotationTooltip.destroy();
} }
function onMouseMove(event) { function onMouseMove(event) {
@@ -463,6 +490,7 @@ System.register(['lodash', 'jquery', 'moment', 'app/core/utils/kbn', 'app/core/c
// Clear crosshair and tooltip // Clear crosshair and tooltip
clearCrosshair(); clearCrosshair();
tooltip.destroy(); tooltip.destroy();
annotationTooltip.destroy();
selection.x2 = limitSelection(event.offsetX); selection.x2 = limitSelection(event.offsetX);
drawSelection(selection.x1, selection.x2); drawSelection(selection.x1, selection.x2);
@@ -470,6 +498,7 @@ System.register(['lodash', 'jquery', 'moment', 'app/core/utils/kbn', 'app/core/c
emitGraphHoverEvet(event); emitGraphHoverEvet(event);
drawCrosshair(event.offsetX); drawCrosshair(event.offsetX);
tooltip.show(event); //, data); tooltip.show(event); //, data);
annotationTooltip.show(event);
} }
} }
@@ -572,6 +601,49 @@ System.register(['lodash', 'jquery', 'moment', 'app/core/utils/kbn', 'app/core/c
$heatmap.on("mousedown", onMouseDown); $heatmap.on("mousedown", onMouseDown);
$heatmap.on("mousemove", onMouseMove); $heatmap.on("mousemove", onMouseMove);
$heatmap.on("mouseleave", onMouseLeave); $heatmap.on("mouseleave", onMouseLeave);
function _renderAnnotations() {
if (!ctrl.annotations || ctrl.annotations.length == 0) {
return;
}
if (!heatmap) {
return;
}
var annoData = _.map(ctrl.annotations, function (d, i) {
return { "x": Math.floor(yAxisWidth + xScale(d.time)), "id": i, "anno": d.source };
});
var anno = heatmap.append("g").attr("class", "statusmap-annotations").attr("transform", "translate(0.5,0)").selectAll(".statusmap-annotations").data(annoData).enter().append("g");
anno.append("line")
//.attr("class", "statusmap-annotation-tick")
.attr("x1", function (d) {
return d.x;
}).attr("y1", chartTop).attr("x2", function (d) {
return d.x;
}).attr("y2", chartBottom).style("stroke", function (d) {
return d.anno.iconColor;
}).style("stroke-width", 1).style("stroke-dasharray", "3,3");
anno.append("polygon").attr("points", function (d) {
return [[d.x, chartBottom + 1], [d.x - 5, chartBottom + 6], [d.x + 5, chartBottom + 6]].join(" ");
}).style("stroke-width", 0).style("fill", function (d) {
return d.anno.iconColor;
});
// Polygons didn't fire mouseevents
anno.append("rect").attr("x", function (d) {
return d.x - 5;
}).attr("width", 10).attr("y", chartBottom + 1).attr("height", 5).attr("class", "statusmap-annotation-tick").attr("annoId", function (d) {
return d.id;
}).style("opacity", 0);
var $ticks = $heatmap.find(".statusmap-annotation-tick");
$ticks.on("mouseenter", function (event) {
annotationTooltip.mouseOverAnnotationTick = true;
}).on("mouseleave", function (event) {
annotationTooltip.mouseOverAnnotationTick = false;
});
}
} }
_export('default', link); _export('default', link);
@@ -622,10 +694,13 @@ System.register(['lodash', 'jquery', 'moment', 'app/core/utils/kbn', 'app/core/c
d3ScaleChromatic = _libsD3ScaleChromaticIndex; d3ScaleChromatic = _libsD3ScaleChromaticIndex;
}, function (_tooltip) { }, function (_tooltip) {
StatusHeatmapTooltip = _tooltip.StatusHeatmapTooltip; StatusHeatmapTooltip = _tooltip.StatusHeatmapTooltip;
}, function (_annotations) {
AnnotationTooltip = _annotations.AnnotationTooltip;
}], }],
execute: function () { execute: function () {
MIN_CARD_SIZE = 5; MIN_CARD_SIZE = 5;
CARD_SPACING = 2; CARD_H_SPACING = 2;
CARD_V_SPACING = 2;
CARD_ROUND = 0; CARD_ROUND = 0;
DATA_RANGE_WIDING_FACTOR = 1.2; DATA_RANGE_WIDING_FACTOR = 1.2;
DEFAULT_X_TICK_SIZE_PX = 100; DEFAULT_X_TICK_SIZE_PX = 100;
+1 -1
View File
File diff suppressed because one or more lines are too long
+121 -26
View File
@@ -3,7 +3,7 @@
System.register(['app/plugins/sdk', 'lodash', 'app/core/core', 'app/core/utils/kbn', './color_legend', './rendering', './options_editor', './color_mode_discrete'], function (_export, _context) { System.register(['app/plugins/sdk', 'lodash', 'app/core/core', 'app/core/utils/kbn', './color_legend', './rendering', './options_editor', './color_mode_discrete'], function (_export, _context) {
"use strict"; "use strict";
var MetricsPanelCtrl, _, contextSrv, kbn, rendering, statusHeatmapOptionsEditor, ColorModeDiscrete, CANVAS, SVG, VALUE_INDEX, TIME_INDEX, panelDefaults, renderer, colorSchemes, colorModes, opacityScales, StatusHeatmapCtrl; var MetricsPanelCtrl, _, contextSrv, kbn, rendering, statusHeatmapOptionsEditor, ColorModeDiscrete, _get, CANVAS, SVG, VALUE_INDEX, TIME_INDEX, panelDefaults, renderer, colorSchemes, colorModes, opacityScales, StatusHeatmapCtrl;
function _classCallCheck(instance, Constructor) { function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) { if (!(instance instanceof Constructor)) {
@@ -52,6 +52,31 @@ System.register(['app/plugins/sdk', 'lodash', 'app/core/core', 'app/core/utils/k
ColorModeDiscrete = _color_mode_discrete.ColorModeDiscrete; ColorModeDiscrete = _color_mode_discrete.ColorModeDiscrete;
}], }],
execute: function () { execute: function () {
_get = function get(object, property, receiver) {
if (object === null) object = Function.prototype;
var desc = Object.getOwnPropertyDescriptor(object, property);
if (desc === undefined) {
var parent = Object.getPrototypeOf(object);
if (parent === null) {
return undefined;
} else {
return get(parent, property, receiver);
}
} else if ("value" in desc) {
return desc.value;
} else {
var getter = desc.get;
if (getter === undefined) {
return undefined;
}
return getter.call(receiver);
}
};
CANVAS = 'CANVAS'; CANVAS = 'CANVAS';
SVG = 'SVG'; SVG = 'SVG';
VALUE_INDEX = 0; VALUE_INDEX = 0;
@@ -71,7 +96,8 @@ System.register(['app/plugins/sdk', 'lodash', 'app/core/core', 'app/core/utils/k
}, },
cards: { cards: {
cardMinWidth: 5, cardMinWidth: 5,
cardSpacing: 2, cardVSpacing: 2,
cardHSpacing: 2,
cardRound: null cardRound: null
}, },
xAxis: { xAxis: {
@@ -97,6 +123,7 @@ System.register(['app/plugins/sdk', 'lodash', 'app/core/core', 'app/core/utils/k
}, },
// how null points should be handled // how null points should be handled
nullPointMode: 'as empty', nullPointMode: 'as empty',
yAxisSort: 'metrics',
highlightCards: true, highlightCards: true,
useMax: true useMax: true
}; };
@@ -116,7 +143,8 @@ System.register(['app/plugins/sdk', 'lodash', 'app/core/core', 'app/core/utils/k
_export('StatusHeatmapCtrl', StatusHeatmapCtrl = function (_MetricsPanelCtrl) { _export('StatusHeatmapCtrl', StatusHeatmapCtrl = function (_MetricsPanelCtrl) {
_inherits(StatusHeatmapCtrl, _MetricsPanelCtrl); _inherits(StatusHeatmapCtrl, _MetricsPanelCtrl);
function StatusHeatmapCtrl($scope, $injector, $rootScope, timeSrv) { /** @ngInject */
function StatusHeatmapCtrl($scope, $injector, $rootScope, timeSrv, annotationsSrv) {
_classCallCheck(this, StatusHeatmapCtrl); _classCallCheck(this, StatusHeatmapCtrl);
var _this = _possibleConstructorReturn(this, (StatusHeatmapCtrl.__proto__ || Object.getPrototypeOf(StatusHeatmapCtrl)).call(this, $scope, $injector)); var _this = _possibleConstructorReturn(this, (StatusHeatmapCtrl.__proto__ || Object.getPrototypeOf(StatusHeatmapCtrl)).call(this, $scope, $injector));
@@ -132,7 +160,7 @@ System.register(['app/plugins/sdk', 'lodash', 'app/core/core', 'app/core/utils/k
var chartWidth = _.max([panelWidth - 200, panelWidth / 2]); var chartWidth = _.max([panelWidth - 200, panelWidth / 2]);
var minCardWidth = _this.panel.cards.cardMinWidth; var minCardWidth = _this.panel.cards.cardMinWidth;
var minSpacing = _this.panel.cards.cardSpacing; var minSpacing = _this.panel.cards.cardHSpacing;
var maxCardsCount = Math.ceil((chartWidth - minCardWidth) / (minCardWidth + minSpacing)); var maxCardsCount = Math.ceil((chartWidth - minCardWidth) / (minCardWidth + minSpacing));
var intervalMs = void 0; var intervalMs = void 0;
@@ -167,11 +195,49 @@ System.register(['app/plugins/sdk', 'lodash', 'app/core/core', 'app/core/utils/k
_this.interval = kbn.secondsToHms(intervalMs / 1000); _this.interval = kbn.secondsToHms(intervalMs / 1000);
}; };
_this.issueQueries = function (datasource) {
_this.annotationsPromise = _this.annotationsSrv.getAnnotations({
dashboard: _this.dashboard,
panel: _this.panel,
range: _this.range
});
/* Wait for annotationSrv requests to get datasources to
* resolve before issuing queries. This allows the annotations
* service to fire annotations queries before graph queries
* (but not wait for completion). This resolves
* issue 11806.
*/
// 5.x before 5.4 doesn't have dataPromises
if ("undefined" !== typeof _this.annotationsSrv.datasourcePromises) {
return _this.annotationsSrv.datasourcePromises.then(function (r) {
return _get(StatusHeatmapCtrl.prototype.__proto__ || Object.getPrototypeOf(StatusHeatmapCtrl.prototype), 'issueQueries', _this).call(_this, datasource);
});
} else {
return _get(StatusHeatmapCtrl.prototype.__proto__ || Object.getPrototypeOf(StatusHeatmapCtrl.prototype), 'issueQueries', _this).call(_this, datasource);
}
};
_this.onDataReceived = function (dataList) { _this.onDataReceived = function (dataList) {
_this.data = dataList; _this.data = dataList;
_this.cardsData = _this.convertToCards(_this.data); _this.cardsData = _this.convertToCards(_this.data);
_this.render(); _this.annotationsPromise.then(function (result) {
_this.loading = false;
//this.alertState = result.alertState;
if (result.annotations && result.annotations.length > 0) {
_this.annotations = result.annotations;
} else {
_this.annotations = null;
}
_this.render();
}, function () {
_this.loading = false;
_this.annotations = null;
_this.render();
});
//this.render();
}; };
_this.onInitEditMode = function () { _this.onInitEditMode = function () {
@@ -207,6 +273,7 @@ System.register(['app/plugins/sdk', 'lodash', 'app/core/core', 'app/core/utils/k
_this.onDataError = function () { _this.onDataError = function () {
_this.data = []; _this.data = [];
_this.annotations = [];
_this.render(); _this.render();
}; };
@@ -224,6 +291,11 @@ System.register(['app/plugins/sdk', 'lodash', 'app/core/core', 'app/core/utils/k
_this.render(); _this.render();
}; };
_this.onEditorRemoveThresholds = function () {
_this.panel.color.thresholds = [];
_this.render();
};
_this.onEditorAddThreeLights = function () { _this.onEditorAddThreeLights = function () {
_this.panel.color.thresholds.push({ color: "red", value: 2, tooltip: "error" }); _this.panel.color.thresholds.push({ color: "red", value: 2, tooltip: "error" });
_this.panel.color.thresholds.push({ color: "yellow", value: 1, tooltip: "warning" }); _this.panel.color.thresholds.push({ color: "yellow", value: 1, tooltip: "warning" });
@@ -231,6 +303,18 @@ System.register(['app/plugins/sdk', 'lodash', 'app/core/core', 'app/core/utils/k
_this.render(); _this.render();
}; };
_this.onEditorAddSolarized = function () {
_this.panel.color.thresholds.push({ color: "#b58900", value: 0, tooltip: "yellow" });
_this.panel.color.thresholds.push({ color: "#cb4b16", value: 1, tooltip: "orange" });
_this.panel.color.thresholds.push({ color: "#dc322f", value: 2, tooltip: "red" });
_this.panel.color.thresholds.push({ color: "#d33682", value: 3, tooltip: "magenta" });
_this.panel.color.thresholds.push({ color: "#6c71c4", value: 4, tooltip: "violet" });
_this.panel.color.thresholds.push({ color: "#268bd2", value: 5, tooltip: "blue" });
_this.panel.color.thresholds.push({ color: "#2aa198", value: 6, tooltip: "cyan" });
_this.panel.color.thresholds.push({ color: "#859900", value: 7, tooltip: "green" });
_this.render();
};
_this.link = function (scope, elem, attrs, ctrl) { _this.link = function (scope, elem, attrs, ctrl) {
rendering(scope, elem, attrs, ctrl); rendering(scope, elem, attrs, ctrl);
}; };
@@ -243,51 +327,54 @@ System.register(['app/plugins/sdk', 'lodash', 'app/core/core', 'app/core/utils/k
maxValue: 0, maxValue: 0,
minValue: 0, minValue: 0,
multipleValues: false, multipleValues: false,
noColorDefined: false noColorDefined: false,
targets: [], // array of available unique targets
targetIndex: {} // indices in data array for each of available unique targets
}; };
if (!data || data.length == 0) { if (!data || data.length == 0) {
return cardsData; return cardsData;
} }
// collect uniq targets and their indexes in data array // Collect uniq timestamps from data and spread over targets and timestamps
cardsData.targetIndex = {};
for (var i = 0; i < data.length; i++) { // collect uniq targets and their indices
var ts = data[i]; _.map(data, function (d, i) {
var target = ts.target; cardsData.targetIndex[d.target] = _.concat(_.toArray(cardsData.targetIndex[d.target]), i);
if (cardsData.targetIndex[target] == undefined) { });
cardsData.targetIndex[target] = [];
}
cardsData.targetIndex[target].push(i);
}
// TODO add some logic for targets heirarchy // TODO add some logic for targets heirarchy
cardsData.targets = _.keys(cardsData.targetIndex); cardsData.targets = _.keys(cardsData.targetIndex);
cardsData.targets.sort();
cardsData.yBucketSize = cardsData.targets.length; cardsData.yBucketSize = cardsData.targets.length;
cardsData.xBucketSize = _.min(_.map(data, function (d) { // Maximum number of buckets over x axis
cardsData.xBucketSize = _.max(_.map(data, function (d) {
return d.datapoints.length; return d.datapoints.length;
})); }));
// Collect all values for each bucket from datapoints with similar target. // Collect all values for each bucket from datapoints with similar target.
for (var _i = 0; _i < cardsData.targets.length; _i++) { // TODO aggregate values into buckets over datapoint[TIME_INDEX] not over datapoint index (j).
var _target = cardsData.targets[_i]; for (var i = 0; i < cardsData.targets.length; i++) {
var target = cardsData.targets[i];
for (var j = 0; j < cardsData.xBucketSize; j++) { for (var j = 0; j < cardsData.xBucketSize; j++) {
var card = { var card = {
id: _i * cardsData.xBucketSize + j, id: i * cardsData.xBucketSize + j,
values: [], values: [],
multipleValues: false, multipleValues: false,
noColorDefined: false noColorDefined: false,
y: target,
x: -1
}; };
// collect values from all timeseries with target // collect values from all timeseries with target
for (var si = 0; si < cardsData.targetIndex[_target].length; si++) { for (var si = 0; si < cardsData.targetIndex[target].length; si++) {
var s = data[cardsData.targetIndex[_target][si]]; var s = data[cardsData.targetIndex[target][si]];
if (s.datapoints.length <= j) {
continue;
}
var datapoint = s.datapoints[j]; var datapoint = s.datapoints[j];
if (card.values.length === 0) { if (card.values.length === 0) {
card.x = datapoint[TIME_INDEX]; card.x = datapoint[TIME_INDEX];
card.y = s.target;
} }
card.values.push(datapoint[VALUE_INDEX]); card.values.push(datapoint[VALUE_INDEX]);
} }
@@ -305,7 +392,9 @@ System.register(['app/plugins/sdk', 'lodash', 'app/core/core', 'app/core/utils/k
if (cardsData.minValue > card.minValue) cardsData.minValue = card.minValue; if (cardsData.minValue > card.minValue) cardsData.minValue = card.minValue;
cardsData.cards.push(card); if (card.x != -1) {
cardsData.cards.push(card);
}
} }
} }
@@ -339,6 +428,9 @@ System.register(['app/plugins/sdk', 'lodash', 'app/core/core', 'app/core/utils/k
} }
}; };
_this.annotations = [];
_this.annotationsSrv = annotationsSrv;
_this.events.on('data-received', _this.onDataReceived); _this.events.on('data-received', _this.onDataReceived);
_this.events.on('data-snapshot-load', _this.onDataReceived); _this.events.on('data-snapshot-load', _this.onDataReceived);
_this.events.on('data-error', _this.onDataError); _this.events.on('data-error', _this.onDataError);
@@ -353,6 +445,9 @@ System.register(['app/plugins/sdk', 'lodash', 'app/core/core', 'app/core/utils/k
// override calculateInterval for discrete color mode // override calculateInterval for discrete color mode
/* https://ethanschoonover.com/solarized/ */
// group values into buckets by target // group values into buckets by target
+1 -1
View File
File diff suppressed because one or more lines are too long
+398
View File
@@ -0,0 +1,398 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"id": 8,
"iteration": 1545919919137,
"links": [],
"panels": [
{
"cards": {
"cardHSpacing": 2,
"cardMinWidth": 5,
"cardRound": null,
"cardVSpacing": 2
},
"color": {
"cardColor": "#b4ff00",
"colorScale": "sqrt",
"colorScheme": "interpolateOranges",
"defaultColor": "#757575",
"exponent": 0.5,
"max": 5,
"min": 1,
"mode": "discrete",
"thresholds": [
{
"color": "#cca300",
"tooltip": "Pending",
"value": "1"
},
{
"color": "#9ac48a",
"tooltip": "Running",
"value": "2"
},
{
"color": "#bf1b00",
"tooltip": "Failed",
"value": "3"
},
{
"color": "#3f6833",
"tooltip": "Succeeded",
"value": "4"
},
{
"color": "#f9e2d2",
"tooltip": "Unknown",
"value": "5"
}
]
},
"data": {
"decimals": null,
"unitFormat": "short"
},
"datasource": "prometheus",
"gridPos": {
"h": 16,
"w": 24,
"x": 0,
"y": 0
},
"highlightCards": true,
"id": 2,
"interval": "",
"legend": {
"show": true
},
"links": [],
"nullPointMode": "as empty",
"repeat": null,
"repeatDirection": null,
"targets": [
{
"expr": "kube_pod_status_phase{namespace=\"$namespace\", phase=\"Pending\"} == 1",
"format": "time_series",
"hide": false,
"instant": false,
"interval": "",
"intervalFactor": 1,
"legendFormat": "{{ pod }}",
"refId": "A"
},
{
"expr": "(kube_pod_status_phase{namespace=\"$namespace\", phase=\"Running\"} == 1) * 2",
"format": "time_series",
"hide": false,
"intervalFactor": 1,
"legendFormat": "{{ pod }}",
"refId": "B"
},
{
"expr": "(kube_pod_status_phase{namespace=\"$namespace\", phase=\"Failed\"} == 1) * 3",
"format": "time_series",
"hide": false,
"intervalFactor": 1,
"legendFormat": "{{ pod }}",
"refId": "C"
},
{
"expr": "(kube_pod_status_phase{namespace=\"$namespace\", phase=\"Succeeded\"} == 1) * 4",
"format": "time_series",
"hide": false,
"intervalFactor": 1,
"legendFormat": "{{ pod }}",
"refId": "D"
},
{
"expr": "(kube_pod_status_phase{namespace=\"$namespace\", phase=\"Unknown\"} == 1) * 5",
"format": "time_series",
"hide": false,
"intervalFactor": 1,
"legendFormat": "{{ pod }}",
"refId": "E"
}
],
"title": "Pod Status",
"tooltip": {
"show": true
},
"transparent": true,
"type": "flant-statusmap-panel",
"useMax": true,
"xAxis": {
"labelFormat": "%a %m/%d",
"minBucketWidthToShowWeekends": 4,
"show": true,
"showCrosshair": true,
"showWeekends": true
},
"yAxis": {
"show": true,
"showCrosshair": false
},
"yAxisSort": "a → z"
},
{
"cards": {
"cardHSpacing": 2,
"cardMinWidth": 5,
"cardRound": null,
"cardVSpacing": 2
},
"color": {
"cardColor": "#b4ff00",
"colorScale": "sqrt",
"colorScheme": "interpolateOranges",
"defaultColor": "#757575",
"exponent": 0.5,
"max": 5,
"min": 1,
"mode": "discrete",
"thresholds": [
{
"color": "#cca300",
"tooltip": "ContainerCreating",
"value": "1"
},
{
"color": "#890f02",
"tooltip": "CrashLoopBackOff",
"value": "2"
},
{
"color": "#bf1b00",
"tooltip": "ErrImagePull",
"value": "3"
},
{
"color": "#58140c",
"tooltip": "ImagePullBackOff",
"value": "4"
},
{
"color": "#7eb26d",
"tooltip": "Running",
"value": "5"
},
{
"color": "#052b51",
"tooltip": "Completed",
"value": "6"
},
{
"color": "#c15c17",
"tooltip": "ContainerCannotRun",
"value": "7"
},
{
"color": "#f9934e",
"tooltip": "Error",
"value": "8"
},
{
"color": "#eab839",
"tooltip": "OOMKilled",
"value": "9"
}
]
},
"data": {
"decimals": null,
"unitFormat": "short"
},
"datasource": "prometheus",
"gridPos": {
"h": 16,
"w": 24,
"x": 0,
"y": 16
},
"highlightCards": true,
"id": 3,
"interval": "",
"legend": {
"show": true
},
"links": [],
"nullPointMode": "as empty",
"repeatDirection": null,
"targets": [
{
"expr": "kube_pod_container_status_waiting_reason{namespace=\"$namespace\", reason=\"ContainerCreating\"} == 1",
"format": "time_series",
"hide": false,
"instant": false,
"interval": "",
"intervalFactor": 1,
"legendFormat": "{{ pod }} - {{ container }}",
"refId": "A"
},
{
"expr": "(kube_pod_container_status_waiting_reason{namespace=\"$namespace\", reason=\"CrashLoopBackOff\"} == 1) * 2",
"format": "time_series",
"hide": false,
"intervalFactor": 1,
"legendFormat": "{{ pod }} - {{ container }}",
"refId": "B"
},
{
"expr": "(kube_pod_container_status_waiting_reason{namespace=\"$namespace\", reason=\"ErrImagePull\"} == 1) * 3",
"format": "time_series",
"hide": false,
"intervalFactor": 1,
"legendFormat": "{{ pod }} - {{ container }}",
"refId": "C"
},
{
"expr": "(kube_pod_container_status_waiting_reason{namespace=\"$namespace\", reason=\"ImagePullBackOff\"} == 1) * 4",
"format": "time_series",
"hide": false,
"intervalFactor": 1,
"legendFormat": "{{ pod }} - {{ container }}",
"refId": "D"
},
{
"expr": "(kube_pod_container_status_running{namespace=\"$namespace\"} == 1) * 5",
"format": "time_series",
"hide": false,
"intervalFactor": 1,
"legendFormat": "{{ pod }} - {{ container }}",
"refId": "E"
},
{
"expr": "(kube_pod_container_status_terminated_reason{namespace=\"$namespace\", reason=\"Completed\"} == 1) * 6",
"format": "time_series",
"hide": false,
"intervalFactor": 1,
"legendFormat": "{{ pod }} - {{ container }}",
"refId": "F"
},
{
"expr": "(kube_pod_container_status_terminated_reason{namespace=\"$namespace\", reason=\"ContainerCannotRun\"} == 1) * 7",
"format": "time_series",
"hide": false,
"intervalFactor": 1,
"legendFormat": "{{ pod }} - {{ container }}",
"refId": "G"
},
{
"expr": "(kube_pod_container_status_terminated_reason{namespace=\"$namespace\", reason=\"Error\"} == 1) * 8",
"format": "time_series",
"hide": false,
"intervalFactor": 1,
"legendFormat": "{{ pod }} - {{ container }}",
"refId": "H"
},
{
"expr": "(kube_pod_container_status_terminated_reason{namespace=\"$namespace\", reason=\"OOMKilled\"} == 1) * 9",
"format": "time_series",
"hide": false,
"intervalFactor": 1,
"legendFormat": "{{ pod }} - {{ container }}",
"refId": "I"
}
],
"title": "Container Status",
"tooltip": {
"show": true
},
"transparent": true,
"type": "flant-statusmap-panel",
"useMax": true,
"xAxis": {
"labelFormat": "%a %m/%d",
"minBucketWidthToShowWeekends": 4,
"show": true,
"showCrosshair": true,
"showWeekends": true
},
"yAxis": {
"show": true,
"showCrosshair": false
},
"yAxisSort": "a → z"
}
],
"schemaVersion": 16,
"style": "dark",
"tags": [],
"templating": {
"list": [
{
"allValue": null,
"current": {
"tags": [],
"text": "openshift-monitoring",
"value": "openshift-monitoring"
},
"datasource": "prometheus",
"definition": "label_values(kube_pod_info, namespace)",
"hide": 0,
"includeAll": false,
"label": "Namespace",
"multi": false,
"name": "namespace",
"options": [],
"query": "label_values(kube_pod_info, namespace)",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 2,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
}
]
},
"time": {
"from": "now-3h",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"timezone": "",
"title": "Pod status",
"uid": "x3PAP_ymk",
"version": 10
}
+5 -4
View File
@@ -1,7 +1,7 @@
{ {
"name": "status-hetamap", "name": "flant-statusmap-panel",
"version": "0.0.1", "version": "0.1.0",
"description": "Status Heatmap panel plugin for grafana", "description": "Grafana panel plugin to visualize status of multiple objects over time",
"main": "README.md", "main": "README.md",
"scripts": { "scripts": {
"build": "grunt" "build": "grunt"
@@ -10,7 +10,7 @@
"license": "Apache-2.0", "license": "Apache-2.0",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/flant/grafana-status-heatmap.git" "url": "https://github.com/flant/grafana-statusmap.git"
}, },
"devDependencies": { "devDependencies": {
"babel": "^6.23.0", "babel": "^6.23.0",
@@ -27,6 +27,7 @@
"grunt-contrib-uglify": "^3.0.1", "grunt-contrib-uglify": "^3.0.1",
"grunt-contrib-watch": "^1.0.0", "grunt-contrib-watch": "^1.0.0",
"grunt-execute": "^0.2.2", "grunt-execute": "^0.2.2",
"grunt-notify": "^0.4.5",
"grunt-systemjs-builder": "^1.0.0", "grunt-systemjs-builder": "^1.0.0",
"load-grunt-tasks": "^3.5.2" "load-grunt-tasks": "^3.5.2"
}, },
+134
View File
@@ -0,0 +1,134 @@
import d3 from 'd3';
import $ from 'jquery';
import _ from 'lodash';
import kbn from 'app/core/utils/kbn';
let TOOLTIP_PADDING_X = 30;
let TOOLTIP_PADDING_Y = 10;
export class AnnotationTooltip {
constructor(elem, scope) {
this.scope = scope;
this.dashboard = scope.ctrl.dashboard;
this.panelCtrl = scope.ctrl;
this.panel = scope.ctrl.panel;
this.mouseOverAnnotationTick = false;
elem.on("mouseover", this.onMouseOver.bind(this));
elem.on("mouseleave", this.onMouseLeave.bind(this));
}
onMouseOver(e) {
if (!this.panel.tooltip.show || !this.scope.ctrl.data || _.isEmpty(this.scope.ctrl.data)) { return; }
if (!this.tooltip) {
this.add();
this.move(e);
}
}
onMouseLeave() {
this.destroy();
}
onMouseMove(e) {
if (!this.panel.tooltip.show) { return; }
this.move(e);
}
add() {
this.tooltipBase = d3.select("body")
.append("div")
.attr("class", "statusmap-annotation-tooltip drop drop-popover drop-popover--annotation drop-element drop-enabled drop-target-attached-center drop-open drop-open-transitionend drop-after-open")
.style("position", "absolute")
this.tooltip = this.tooltipBase
.append("div")
.attr("class", "drop-content")
.append("div")
.append("annotation-tooltip")
.append("div")
.attr("class", "graph-annotation");
}
destroy() {
if (this.tooltip) {
this.tooltip.remove();
}
this.tooltip = null;
if (this.tooltipBase) {
this.tooltipBase.remove();
}
this.tooltipBase = null;
}
show(pos) {
if (!this.panel.tooltip.show || !this.tooltip) { return; }
// shared tooltip mode
//if (pos.panelRelY) {
// return;
//}
let annoId = d3.select(pos.target).attr('annoId');
if (!annoId) {
this.destroy();
return;
}
let anno = this.panelCtrl.annotations[annoId];
if (!anno) {
this.destroy();
return;
}
let annoTitle = "";
let tooltipTimeFormat = 'YYYY-MM-DD HH:mm:ss';
let annoTime = this.dashboard.formatDate(anno.time, tooltipTimeFormat);
let annoText = anno.text;
let annoTags = [];
if (anno.tags) {
annoTags = _.map(anno.tags, t => ({"text": t, "backColor": "rgb(63, 43, 91)", "borderColor":"rgb(101, 81, 129)"}))
}
let tooltipHtml = `<div class="graph-annotation__header">
<span class="graph-annotation__title">${annoTitle}</span>
<span class="graph-annotation__time">${annoTime}</span></div>
<div class="graph-annotation__body">
<div>${annoText}</div>
${_.join(_.map(annoTags, t => `<span class="label label-tag small" style="background-color: ${t.backColor}; border-color: ${t.borderColor}">${t.text}</span>`), "")}
</div>
<div class="statusmap-histogram"></div>`;
this.tooltip.html(tooltipHtml);
this.move(pos);
}
move(pos) {
if (!this.tooltipBase) { return; }
let elem = $(this.tooltipBase.node())[0];
let tooltipWidth = elem.clientWidth;
let tooltipHeight = elem.clientHeight;
let left = pos.pageX - tooltipWidth/2;
let top = pos.pageY + TOOLTIP_PADDING_Y;
if (pos.pageX + tooltipWidth/2 + 10 > window.innerWidth) {
left = pos.pageX - tooltipWidth - TOOLTIP_PADDING_X;
}
if (pos.pageY - window.pageYOffset + tooltipHeight + 20 > window.innerHeight) {
top = pos.pageY - tooltipHeight - TOOLTIP_PADDING_Y;
}
return this.tooltipBase
.style("left", left + "px")
.style("top", top + "px");
}
}
+36 -39
View File
@@ -8,10 +8,10 @@ import {tickStep} from 'app/core/utils/ticks';
let mod = angular.module('grafana.directives'); let mod = angular.module('grafana.directives');
const MIN_LEGEND_STEPS = 10; const LEGEND_STEP_WIDTH = 2;
/** /**
* Color legend for heatmap editor. * Bigger color legend for opacity and spectrum modes editor.
*/ */
mod.directive('optionsColorLegend', function() { mod.directive('optionsColorLegend', function() {
return { return {
@@ -45,7 +45,7 @@ mod.directive('optionsColorLegend', function() {
}); });
/** /**
* Heatmap legend with scale values. * Graph legend with values.
*/ */
mod.directive('statusHeatmapLegend', function() { mod.directive('statusHeatmapLegend', function() {
return { return {
@@ -62,6 +62,9 @@ mod.directive('statusHeatmapLegend', function() {
function render() { function render() {
clearLegend(elem); clearLegend(elem);
if (!ctrl.panel.legend.show) {
return
}
if (!_.isEmpty(ctrl.cardsData) && !_.isEmpty(ctrl.cardsData.cards)) { if (!_.isEmpty(ctrl.cardsData) && !_.isEmpty(ctrl.cardsData.cards)) {
let rangeFrom = ctrl.cardsData.minValue; let rangeFrom = ctrl.cardsData.minValue;
let rangeTo = ctrl.cardsData.maxValue; let rangeTo = ctrl.cardsData.maxValue;
@@ -92,15 +95,9 @@ function drawColorLegend(elem, colorScheme, rangeFrom, rangeTo, maxValue, minVal
let legendWidth = Math.floor(legendElem.outerWidth()) - 30; // narrow legendWidth by 30px to get space for first and last tick values let legendWidth = Math.floor(legendElem.outerWidth()) - 30; // narrow legendWidth by 30px to get space for first and last tick values
let legendHeight = legendElem.attr("height"); let legendHeight = legendElem.attr("height");
let rangeStep = 1; let rangeStep = (rangeTo - rangeFrom) / (legendWidth/LEGEND_STEP_WIDTH);
if (rangeTo - rangeFrom > legendWidth) { // width in pixels in legend space of unit segment in range space
rangeStep = Math.floor((rangeTo - rangeFrom) / legendWidth); // rangeStep * witdhFactor == width in pixels of one rangeStep
}
if (rangeStep * MIN_LEGEND_STEPS > rangeTo) {
rangeStep = rangeTo / MIN_LEGEND_STEPS;
}
let widthFactor = legendWidth / (rangeTo - rangeFrom); let widthFactor = legendWidth / (rangeTo - rangeFrom);
let valuesRange = d3.range(rangeFrom, rangeTo, rangeStep); let valuesRange = d3.range(rangeFrom, rangeTo, rangeStep);
@@ -108,9 +105,12 @@ function drawColorLegend(elem, colorScheme, rangeFrom, rangeTo, maxValue, minVal
legend.selectAll(".status-heatmap-color-legend-rect") legend.selectAll(".status-heatmap-color-legend-rect")
.data(valuesRange) .data(valuesRange)
.enter().append("rect") .enter().append("rect")
.attr("x", d => d * widthFactor + 10) // shift all color rectangles to the right // translate from range space into pixels
// and shift all rectangles to the right by 10
.attr("x", d => d * widthFactor+10)
.attr("y", 0) .attr("y", 0)
.attr("width", rangeStep * widthFactor + 1) // Overlap rectangles to prevent gaps // rectangles are slightly overlaped to prevent gaps
.attr("width", LEGEND_STEP_WIDTH+1)
.attr("height", legendHeight) .attr("height", legendHeight)
.attr("stroke-width", 0) .attr("stroke-width", 0)
.attr("fill", d => colorScale(d)); .attr("fill", d => colorScale(d));
@@ -126,22 +126,22 @@ function drawOpacityLegend(elem, options, rangeFrom, rangeTo, maxValue, minValue
let legendWidth = Math.floor(legendElem.outerWidth()) - 30; // narrow legendWidth by 30px to get space for first and last tick values let legendWidth = Math.floor(legendElem.outerWidth()) - 30; // narrow legendWidth by 30px to get space for first and last tick values
let legendHeight = legendElem.attr("height"); let legendHeight = legendElem.attr("height");
let rangeStep = 10; let rangeStep = (rangeTo - rangeFrom) / (legendWidth/LEGEND_STEP_WIDTH);
// width in pixels in legend space of unit segment in range space
// rangeStep * witdhFactor == width in pixels of one rangeStep
let widthFactor = legendWidth / (rangeTo - rangeFrom); let widthFactor = legendWidth / (rangeTo - rangeFrom);
if (rangeStep * MIN_LEGEND_STEPS > rangeTo) {
rangeStep = rangeTo / MIN_LEGEND_STEPS;
}
let valuesRange = d3.range(rangeFrom, rangeTo, rangeStep); let valuesRange = d3.range(rangeFrom, rangeTo, rangeStep);
let opacityScale = getOpacityScale(options, maxValue, minValue); let opacityScale = getOpacityScale(options, maxValue, minValue);
legend.selectAll(".status-heatmap-opacity-legend-rect") legend.selectAll(".status-heatmap-opacity-legend-rect")
.data(valuesRange) .data(valuesRange)
.enter().append("rect") .enter().append("rect")
.attr("x", d => d * widthFactor + 10) // shift all opacity rectangles to the right // translate from range space into pixels
// and shift all rectangles to the right by 10
.attr("x", d => d * widthFactor+10)
.attr("y", 0) .attr("y", 0)
.attr("width", rangeStep * widthFactor) // rectangles are slightly overlaped to prevent gaps
.attr("width", LEGEND_STEP_WIDTH+1)
.attr("height", legendHeight) .attr("height", legendHeight)
.attr("stroke-width", 0) .attr("stroke-width", 0)
.attr("fill", options.cardColor) .attr("fill", options.cardColor)
@@ -168,14 +168,14 @@ function drawDiscreteColorLegend(elem, colorOptions, discreteHelper) {
legend.selectAll(".hidden-texts") legend.selectAll(".hidden-texts")
.data(tooltips) .data(tooltips)
.enter().append("text") .enter().append("text")
.attr("class", "axis tick") .attr("class", "axis tick hidden-texts")
.attr("font-family", "sans-serif") .attr("font-family", "sans-serif")
.text(d => d) .text(d => d)
.each(function(d,i) { .each(function(d,i) {
let thisWidth = this.getBBox().width; let thisWidth = this.getBBox().width;
textWidth.push(thisWidth); textWidth.push(thisWidth);
this.remove(); // remove them just after displaying them
}); });
legend.selectAll(".hidden-texts").remove();
let legendWidth = Math.floor(_.min([ let legendWidth = Math.floor(_.min([
graphWidth - 30, graphWidth - 30,
@@ -289,23 +289,21 @@ function drawDiscreteLegendValues(elem, colorOptions, legendWidth) {
function drawSimpleColorLegend(elem, colorScale) { function drawSimpleColorLegend(elem, colorScale) {
let legendElem = $(elem).find('svg'); let legendElem = $(elem).find('svg');
let legend = d3.select(legendElem.get(0));
clearLegend(elem); clearLegend(elem);
let legendWidth = Math.floor(legendElem.outerWidth()); let legendWidth = Math.floor(legendElem.outerWidth());
let legendHeight = legendElem.attr("height"); let legendHeight = legendElem.attr("height");
if (legendWidth) { if (legendWidth) {
let valuesNumber = Math.floor(legendWidth / 2); let valuesRange = d3.range(0, legendWidth, LEGEND_STEP_WIDTH);
let rangeStep = Math.floor(legendWidth / valuesNumber);
let valuesRange = d3.range(0, legendWidth, rangeStep);
let legend = d3.select(legendElem.get(0)); legend.selectAll(".status-heatmap-color-legend-rect")
var legendRects = legend.selectAll(".status-heatmap-color-legend-rect").data(valuesRange); .data(valuesRange)
.enter().append("rect")
legendRects.enter().append("rect")
.attr("x", d => d) .attr("x", d => d)
.attr("y", 0) .attr("y", 0)
.attr("width", rangeStep + 1) // Overlap rectangles to prevent gaps .attr("width", LEGEND_STEP_WIDTH + 1) // Overlap rectangles to prevent gaps
.attr("height", legendHeight) .attr("height", legendHeight)
.attr("stroke-width", 0) .attr("stroke-width", 0)
.attr("fill", d => colorScale(d)); .attr("fill", d => colorScale(d));
@@ -314,10 +312,9 @@ function drawSimpleColorLegend(elem, colorScale) {
function drawSimpleOpacityLegend(elem, options) { function drawSimpleOpacityLegend(elem, options) {
let legendElem = $(elem).find('svg'); let legendElem = $(elem).find('svg');
let graphElem = $(elem); let legend = d3.select(legendElem.get(0));
clearLegend(elem); clearLegend(elem);
let legend = d3.select(legendElem.get(0));
let legendWidth = Math.floor(legendElem.outerWidth()); let legendWidth = Math.floor(legendElem.outerWidth());
let legendHeight = legendElem.attr("height"); let legendHeight = legendElem.attr("height");
@@ -333,14 +330,14 @@ function drawSimpleOpacityLegend(elem, options) {
.range([0, 1]); .range([0, 1]);
} }
let rangeStep = 10; let valuesRange = d3.range(0, legendWidth, LEGEND_STEP_WIDTH);
let valuesRange = d3.range(0, legendWidth, rangeStep);
var legendRects = legend.selectAll(".status-heatmap-opacity-legend-rect").data(valuesRange);
legendRects.enter().append("rect") legend.selectAll(".status-heatmap-opacity-legend-rect")
.data(valuesRange)
.enter().append("rect")
.attr("x", d => d) .attr("x", d => d)
.attr("y", 0) .attr("y", 0)
.attr("width", rangeStep) .attr("width", LEGEND_STEP_WIDTH+1)
.attr("height", legendHeight) .attr("height", legendHeight)
.attr("stroke-width", 0) .attr("stroke-width", 0)
.attr("fill", options.cardColor) .attr("fill", options.cardColor)
+25 -1
View File
@@ -61,6 +61,30 @@ export class ColorModeDiscrete {
getBucketColor(values) { getBucketColor(values) {
let thresholds = this.panel.color.thresholds; let thresholds = this.panel.color.thresholds;
if (!values || values.length == 0) {
// treat as null value
return this.getMatchedThreshold(null).color;
}
if (values.length == 1) {
let threshold = this.getMatchedThreshold(values[0]);
if (!threshold || !threshold.color || threshold.color == "") {
return 'rgba(0,0,0,1)';
} else {
return threshold.color;
}
}
let isAllValuesNulls = true;
for (let j = 0; j < values.length; j++) {
if (values[j] != null) {
isAllValuesNulls = false;
}
}
if (isAllValuesNulls) {
return this.getMatchedThreshold(null).color;
}
for (let i = 0; i < thresholds.length; i++) { for (let i = 0; i < thresholds.length; i++) {
for (let j = 0; j < values.length; j++) { for (let j = 0; j < values.length; j++) {
if (values[j] == thresholds[i].value) { if (values[j] == thresholds[i].value) {
@@ -93,7 +117,7 @@ export class ColorModeDiscrete {
getMatchedThreshold(value) { getMatchedThreshold(value) {
if (value == null) { if (value == null) {
if (this.panel.color.nullPointMode == 'as empty') { if (this.panel.nullPointMode == 'as empty') {
// FIXME: make this explicit for user // FIXME: make this explicit for user
// Right now this color never used because null as empty handles in getCardOpacity method. // Right now this color never used because null as empty handles in getCardOpacity method.
return { return {
+5
View File
@@ -2,6 +2,11 @@
// position: relative; // position: relative;
cursor: crosshair; cursor: crosshair;
// allow tooltips on hover of warning text
.datapoints-warning {
pointer-events: none;
}
.datapoints-warning span { .datapoints-warning span {
background-color: $warning-bg; background-color: $warning-bg;
color: $text-color; color: $text-color;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

+1 -1
View File
@@ -1,7 +1,7 @@
<div class="status-heatmap-wrapper"> <div class="status-heatmap-wrapper">
<div class="status-heatmap-canvas-wrapper"> <div class="status-heatmap-canvas-wrapper">
<div class="datapoints-warning"> <div class="datapoints-warning" ng-if="ctrl.multipleValues || ctrl.noColorDefined">
<span class="small" ng-if="ctrl.multipleValues" bs-tooltip="'{{ctrl.dataWarnings.multipleValues.tip}}'">{{ctrl.dataWarnings.multipleValues.title}}</span> <span class="small" ng-if="ctrl.multipleValues" bs-tooltip="'{{ctrl.dataWarnings.multipleValues.tip}}'">{{ctrl.dataWarnings.multipleValues.title}}</span>
<span class="small" ng-if="ctrl.noColorDefined" bs-tooltip="'{{ctrl.dataWarnings.noColorDefined.tip}}'">{{ctrl.dataWarnings.noColorDefined.title}}</span> <span class="small" ng-if="ctrl.noColorDefined" bs-tooltip="'{{ctrl.dataWarnings.noColorDefined.tip}}'">{{ctrl.dataWarnings.noColorDefined.title}}</span>
</div> </div>
+78 -35
View File
@@ -55,40 +55,70 @@
<strong>Note:</strong> Multiple values displayed using color with least index<br/> <strong>Note:</strong> Multiple values displayed using color with least index<br/>
</div> </div>
<div class="gf-form" ng-repeat="threshold in ctrl.panel.color.thresholds"> <div class="gf-form" ng-show="ctrl.panel.color.thresholds.length == 0">
<label class="gf-form-label width-2">0</label>
<label class="gf-form-label width-2">{{ $index }}</label> <label class="gf-form-input width-30">No statuses defined. Add new status or use presets below.</label>
<label class="gf-form-label width-9" ng-if="ctrl.panel.useMax">If bucket has value</label>
<label class="gf-form-label width-9" ng-if="!ctrl.panel.useMax">If bucket value ==</label>
<input type="text" class="input-small gf-form-input width-4" ng-model="threshold.value" ng-change="ctrl.render()"/>
<label class="gf-form-label">
fill
</label>
<label class="gf-form-label">
<spectrum-picker ng-model="threshold.color" ng-change="ctrl.render()"/>
</label>
<label class="gf-form-label">set tooltip</label>
<input type="text" class="input-small gf-form-input width-9" ng-model="threshold.tooltip" ng-change="ctrl.render()"/>
<label class="gf-form-label">
<a class="pointer" ng-click="ctrl.onEditorRemoveThreshold($index)">
<i class="fa fa-trash"/>
</a>
</label>
</div> </div>
<div class="gf-form"> <div class="gf-form-inline" ng-repeat="threshold in ctrl.panel.color.thresholds">
<label class="gf-form-label" ng-show="ctrl.panel.color.thresholds.length == 0"> <div class="gf-form">
<a class="pointer" ng-click="ctrl.onEditorAddThreeLights()"> <label class="gf-form-label width-2">{{ $index }}</label>
Add trafic lights </div>
</a> <div class="gf-form">
</label> <label class="gf-form-label width-9 query-keyword" ng-if="ctrl.panel.useMax">If bucket has value</label>
<label class="gf-form-label"> <label class="gf-form-label width-9 query-keyword" ng-if="!ctrl.panel.useMax">If bucket value ==</label>
<a class="pointer" ng-click="ctrl.onEditorAddThreshold()"> <input type="text" class="input-small gf-form-input width-4" ng-model="threshold.value" ng-change="ctrl.render()"/>
Add new status <label class="gf-form-label query-keyword">
</a> fill
</label> </label>
<label class="gf-form-label">
<spectrum-picker ng-model="threshold.color" ng-change="ctrl.render()"/>
</label>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword">set tooltip</label>
<input type="text" class="input-small gf-form-input width-9" ng-model="threshold.tooltip" ng-change="ctrl.render()"/>
</div>
<div class="gf-form">
<label class="gf-form-label">
<a class="pointer" tabindex="1" ng-click="ctrl.onEditorRemoveThreshold($index)">
<i class="fa fa-trash"/>
</a>
</label>
</div>
</div> </div>
<div class="gf-form-inline">
<div class="gf-form"></div>
<div class="gf-form">
<button class="btn btn-inverse" ng-click="ctrl.onEditorAddThreshold()">
<i class="fa fa-plus"></i> Add new status
</button>
</div>
<div class="gf-form">
<button class="btn btn-inverse" ng-click="ctrl.onEditorRemoveThresholds()">
<i class="fa fa-minus"></i> Remove all
</button>
</div>
</div>
<div class="gf-form-inline" ng-show="ctrl.panel.color.thresholds.length == 0">
<div class="gf-form"></div>
<div class="gf-form">
<label class="gf-form-label">Presets</label>
</div>
<div class="gf-form">
<button class="btn" ng-click="ctrl.onEditorAddThreeLights()">
Red-Yellow-Green
</button>
</div>
<div class="gf-form">
<button class="btn" ng-click="ctrl.onEditorAddSolarized()">
Solarized
</button>
</div>
</div>
</div> </div>
<div class="gf-form" ng-show="ctrl.panel.color.mode !== 'discrete'"> <div class="gf-form" ng-show="ctrl.panel.color.mode !== 'discrete'">
@@ -118,7 +148,16 @@
label="Show tooltip" label="Show tooltip"
checked="ctrl.panel.tooltip.show" on-change="ctrl.render()"> checked="ctrl.panel.tooltip.show" on-change="ctrl.render()">
</gf-form-switch> </gf-form-switch>
</div> <div class="gf-form">
<label class="gf-form-label width-8">Y axis sort</label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input max-width-8"
ng-model="ctrl.panel.yAxisSort"
ng-options="f for f in ['metrics', 'a → z', 'z → a']"
ng-change="ctrl.render()"></select>
</div>
</div>
</div>
<div class="section gf-form-group"> <div class="section gf-form-group">
<h5 class="section-heading">Bucket</h5> <h5 class="section-heading">Bucket</h5>
@@ -142,12 +181,16 @@
<input type="number" class="gf-form-input width-5" placeholder="5" data-placement="right" bs-tooltip="'Minimal card width for 1/1 resolution in pixels'" ng-model="ctrl.panel.cards.cardMinWidth" ng-change="ctrl.refresh()" ng-model-onblur> <input type="number" class="gf-form-input width-5" placeholder="5" data-placement="right" bs-tooltip="'Minimal card width for 1/1 resolution in pixels'" ng-model="ctrl.panel.cards.cardMinWidth" ng-change="ctrl.refresh()" ng-model-onblur>
</div> </div>
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label width-9">Spacing</label> <label class="gf-form-label width-9">V spacing</label>
<input type="number" class="gf-form-input width-5" placeholder="2" data-placement="right" bs-tooltip="'Card spacing in pixels'" ng-model="ctrl.panel.cards.cardSpacing" ng-change="ctrl.refresh()" ng-model-onblur> <input type="number" class="gf-form-input width-5" placeholder="2" data-placement="right" bs-tooltip="'Cards vertical spacing in pixels'" ng-model="ctrl.panel.cards.cardVSpacing" ng-change="ctrl.refresh()" ng-model-onblur>
</div>
<div class="gf-form">
<label class="gf-form-label width-9">H spacing</label>
<input type="number" class="gf-form-input width-5" placeholder="2" data-placement="right" bs-tooltip="'Cards horizontal spacing in pixels'" ng-model="ctrl.panel.cards.cardHSpacing" ng-change="ctrl.refresh()" ng-model-onblur>
</div> </div>
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label width-9">Rounding</label> <label class="gf-form-label width-9">Rounding</label>
<input type="number" class="gf-form-input width-5" placeholder="0" data-placement="right" bs-tooltip="'Angles rounding in percent'" ng-model="ctrl.panel.cards.cardRound" ng-change="ctrl.refresh()" ng-model-onblur> <input type="number" class="gf-form-input width-5" placeholder="0" data-placement="right" bs-tooltip="'Cards rounding radius in pixels'" ng-model="ctrl.panel.cards.cardRound" ng-change="ctrl.refresh()" ng-model-onblur>
</div> </div>
</div> </div>
+2 -2
View File
@@ -25,8 +25,8 @@
{"name": "Color mapping", "path": "img/color-mapping.png"}, {"name": "Color mapping", "path": "img/color-mapping.png"},
{"name": "Queries example", "path": "img/queries-example.png"} {"name": "Queries example", "path": "img/queries-example.png"}
], ],
"version": "0.0.2", "version": "0.1.1",
"updated": "2018-10-17" "updated": "2019-03-22"
}, },
"dependencies": { "dependencies": {
+98 -16
View File
@@ -7,9 +7,11 @@ import {tickStep, getScaledDecimals, getFlotTickSize} from 'app/core/utils/ticks
import d3 from 'd3'; import d3 from 'd3';
import * as d3ScaleChromatic from './libs/d3-scale-chromatic/index'; import * as d3ScaleChromatic from './libs/d3-scale-chromatic/index';
import {StatusHeatmapTooltip} from './tooltip'; import {StatusHeatmapTooltip} from './tooltip';
import {AnnotationTooltip} from './annotations';
let MIN_CARD_SIZE = 5, let MIN_CARD_SIZE = 5,
CARD_SPACING = 2, CARD_H_SPACING = 2,
CARD_V_SPACING = 2,
CARD_ROUND = 0, CARD_ROUND = 0,
DATA_RANGE_WIDING_FACTOR = 1.2, DATA_RANGE_WIDING_FACTOR = 1.2,
DEFAULT_X_TICK_SIZE_PX = 100, DEFAULT_X_TICK_SIZE_PX = 100,
@@ -24,13 +26,14 @@ export default function link(scope, elem, attrs, ctrl) {
// $heatmap is JQuery object, but heatmap is D3 // $heatmap is JQuery object, but heatmap is D3
let $heatmap = elem.find('.status-heatmap-panel'); let $heatmap = elem.find('.status-heatmap-panel');
let tooltip = new StatusHeatmapTooltip($heatmap, scope); let tooltip = new StatusHeatmapTooltip($heatmap, scope);
let annotationTooltip = new AnnotationTooltip($heatmap, scope);
let width, height, let width, height,
yScale, xScale, yScale, xScale,
chartWidth, chartHeight, chartWidth, chartHeight,
chartTop, chartBottom, chartTop, chartBottom,
yAxisWidth, xAxisHeight, yAxisWidth, xAxisHeight,
cardSpacing, cardRound, cardVSpacing, cardHSpacing, cardRound,
cardWidth, cardHeight, cardWidth, cardHeight,
colorScale, opacityScale, colorScale, opacityScale,
mouseUpHandler, mouseUpHandler,
@@ -76,7 +79,7 @@ export default function link(scope, elem, attrs, ctrl) {
return text.getBBox().width; return text.getBBox().width;
})); }));
return max_text_width; return Math.ceil(max_text_width);
} }
function getXAxisHeight(elem) { function getXAxisHeight(elem) {
@@ -129,9 +132,10 @@ export default function link(scope, elem, attrs, ctrl) {
function getYScale(ticks) { function getYScale(ticks) {
let range = []; let range = [];
let step = chartHeight / ticks.length; let step = chartHeight / ticks.length;
range.push(chartHeight); // svg has y=0 on the top, so top card should have a minimal value in range
range.push(step);
for (let i = 1; i < ticks.length; i++) { for (let i = 1; i < ticks.length; i++) {
range.push(chartHeight - step * i); range.push(step * (i+1));
} }
return d3.scaleOrdinal() return d3.scaleOrdinal()
.domain(ticks) .domain(ticks)
@@ -142,9 +146,10 @@ export default function link(scope, elem, attrs, ctrl) {
function getYAxisScale(ticks) { function getYAxisScale(ticks) {
let range = []; let range = [];
let step = chartHeight / ticks.length; let step = chartHeight / ticks.length;
range.push(chartHeight - yOffset); // svg has y=0 on the top, so top tick should have a minimal value in range
range.push(yOffset);
for (let i = 1; i < ticks.length; i++) { for (let i = 1; i < ticks.length; i++) {
range.push(chartHeight - step * i - yOffset); range.push(step * i + yOffset);
} }
return d3.scaleOrdinal() return d3.scaleOrdinal()
.domain(ticks) .domain(ticks)
@@ -159,6 +164,12 @@ export default function link(scope, elem, attrs, ctrl) {
ticks = ['']; ticks = [''];
} }
if (panel.yAxisSort == 'a → z') {
ticks.sort((a, b) => a.localeCompare(b, 'en', {ignorePunctuation: false, numeric: true}));
} else if (panel.yAxisSort == 'z → a') {
ticks.sort((b, a) => a.localeCompare(b, 'en', {ignorePunctuation: false, numeric: true}));
}
let yAxisScale = getYAxisScale(ticks); let yAxisScale = getYAxisScale(ticks);
scope.yScale = yScale = getYScale(ticks); scope.yScale = yScale = getYScale(ticks);
@@ -231,12 +242,13 @@ export default function link(scope, elem, attrs, ctrl) {
chartTop = margin.top; chartTop = margin.top;
chartBottom = chartTop + chartHeight; chartBottom = chartTop + chartHeight;
cardSpacing = panel.cards.cardSpacing !== null ? panel.cards.cardSpacing : CARD_SPACING; cardHSpacing = panel.cards.cardHSpacing !== null ? panel.cards.cardHSpacing : CARD_H_SPACING;
cardVSpacing = panel.cards.cardVSpacing !== null ? panel.cards.cardVSpacing : CARD_V_SPACING;
cardRound = panel.cards.cardRound !== null ? panel.cards.cardRound : CARD_ROUND; cardRound = panel.cards.cardRound !== null ? panel.cards.cardRound : CARD_ROUND;
// calculate yOffset for YAxis // calculate yOffset for YAxis
yGridSize = Math.floor(chartHeight / cardsData.yBucketSize); yGridSize = Math.floor(chartHeight / cardsData.yBucketSize);
cardHeight = yGridSize ? yGridSize - cardSpacing : 0; cardHeight = yGridSize ? yGridSize - cardVSpacing : 0;
yOffset = cardHeight / 2; yOffset = cardHeight / 2;
addYAxis(); addYAxis();
@@ -244,9 +256,10 @@ export default function link(scope, elem, attrs, ctrl) {
yAxisWidth = getYAxisWidth(heatmap) + Y_AXIS_TICK_PADDING; yAxisWidth = getYAxisWidth(heatmap) + Y_AXIS_TICK_PADDING;
chartWidth = width - yAxisWidth - margin.right; chartWidth = width - yAxisWidth - margin.right;
// TODO allow per-y cardWidth!
// we need to fill chartWidth with xBucketSize cards. // we need to fill chartWidth with xBucketSize cards.
xGridSize = chartWidth / (cardsData.xBucketSize+1); xGridSize = chartWidth / (cardsData.xBucketSize+1);
cardWidth = xGridSize - cardSpacing; cardWidth = xGridSize - cardHSpacing;
addXAxis(); addXAxis();
xAxisHeight = getXAxisHeight(heatmap); xAxisHeight = getXAxisHeight(heatmap);
@@ -299,6 +312,8 @@ export default function link(scope, elem, attrs, ctrl) {
resetCardHighLight(event); resetCardHighLight(event);
}); });
_renderAnnotations();
ctrl.events.emit('render-complete', { ctrl.events.emit('render-complete', {
"chartWidth": chartWidth "chartWidth": chartWidth
}); });
@@ -356,7 +371,7 @@ export default function link(scope, elem, attrs, ctrl) {
let cx = xScale(d.x); let cx = xScale(d.x);
if (cx - cardWidth/2 < 0) { if (cx - cardWidth/2 < 0) {
x = yAxisWidth + cardSpacing/2; x = yAxisWidth + cardHSpacing/2;
} else { } else {
x = yAxisWidth + cx - cardWidth/2; x = yAxisWidth + cx - cardWidth/2;
} }
@@ -372,11 +387,11 @@ export default function link(scope, elem, attrs, ctrl) {
if (cx < cardWidth/2) { if (cx < cardWidth/2) {
// Center should not exceed half of card. // Center should not exceed half of card.
// Cut card to the left to prevent overlay of y axis. // Cut card to the left to prevent overlay of y axis.
let cutted_width = (cx - cardSpacing/2) + cardWidth/2; let cutted_width = (cx - cardHSpacing/2) + cardWidth/2;
w = cutted_width > 0 ? cutted_width : 0; w = cutted_width > 0 ? cutted_width : 0;
} else if (chartWidth - cx < cardWidth/2) { } else if (chartWidth - cx < cardWidth/2) {
// Cut card to the right to prevent overlay of right graph edge. // Cut card to the right to prevent overlay of right graph edge.
w = cardWidth/2 + (chartWidth - cx - cardSpacing/2); w = cardWidth/2 + (chartWidth - cx - cardHSpacing/2);
} else { } else {
w = cardWidth; w = cardWidth;
} }
@@ -384,21 +399,25 @@ export default function link(scope, elem, attrs, ctrl) {
// Card width should be MIN_CARD_SIZE at least // Card width should be MIN_CARD_SIZE at least
w = Math.max(w, MIN_CARD_SIZE); w = Math.max(w, MIN_CARD_SIZE);
if (cardHSpacing == 0) {
w = w+1;
}
return w; return w;
} }
function getCardY(d) { function getCardY(d) {
return yScale(d.y) + chartTop - cardHeight - cardSpacing/2; return yScale(d.y) + chartTop - cardHeight - cardVSpacing/2;
} }
function getCardHeight(d) { function getCardHeight(d) {
let ys = yScale(d.y); let ys = yScale(d.y);
let y = ys + chartTop - cardHeight - cardSpacing/2; let y = ys + chartTop - cardHeight - cardVSpacing/2;
let h = cardHeight; let h = cardHeight;
// Cut card height to prevent overlay // Cut card height to prevent overlay
if (y < chartTop) { if (y < chartTop) {
h = ys - cardSpacing/2; h = ys - cardVSpacing/2;
} else if (ys > chartBottom) { } else if (ys > chartBottom) {
h = chartBottom - y; h = chartBottom - y;
} else if (y + cardHeight > chartBottom) { } else if (y + cardHeight > chartBottom) {
@@ -410,6 +429,10 @@ export default function link(scope, elem, attrs, ctrl) {
// Card height should be MIN_CARD_SIZE at least // Card height should be MIN_CARD_SIZE at least
h = Math.max(h, MIN_CARD_SIZE); h = Math.max(h, MIN_CARD_SIZE);
if (cardVSpacing == 0) {
h = h+1
}
return h; return h;
} }
@@ -487,6 +510,7 @@ export default function link(scope, elem, attrs, ctrl) {
function onMouseLeave() { function onMouseLeave() {
appEvents.emit('graph-hover-clear'); appEvents.emit('graph-hover-clear');
clearCrosshair(); clearCrosshair();
//annotationTooltip.destroy();
} }
function onMouseMove(event) { function onMouseMove(event) {
@@ -496,6 +520,7 @@ export default function link(scope, elem, attrs, ctrl) {
// Clear crosshair and tooltip // Clear crosshair and tooltip
clearCrosshair(); clearCrosshair();
tooltip.destroy(); tooltip.destroy();
annotationTooltip.destroy();
selection.x2 = limitSelection(event.offsetX); selection.x2 = limitSelection(event.offsetX);
drawSelection(selection.x1, selection.x2); drawSelection(selection.x1, selection.x2);
@@ -503,6 +528,7 @@ export default function link(scope, elem, attrs, ctrl) {
emitGraphHoverEvet(event); emitGraphHoverEvet(event);
drawCrosshair(event.offsetX); drawCrosshair(event.offsetX);
tooltip.show(event); //, data); tooltip.show(event); //, data);
annotationTooltip.show(event);
} }
} }
@@ -618,6 +644,62 @@ export default function link(scope, elem, attrs, ctrl) {
$heatmap.on("mousedown", onMouseDown); $heatmap.on("mousedown", onMouseDown);
$heatmap.on("mousemove", onMouseMove); $heatmap.on("mousemove", onMouseMove);
$heatmap.on("mouseleave", onMouseLeave); $heatmap.on("mouseleave", onMouseLeave);
function _renderAnnotations() {
if (!ctrl.annotations || ctrl.annotations.length == 0) {
return;
}
if (!heatmap) {
return;
}
let annoData = _.map(ctrl.annotations, (d,i) => ({"x": Math.floor(yAxisWidth + xScale(d.time)), "id":i, "anno": d.source}));
let anno = heatmap
.append("g")
.attr("class", "statusmap-annotations")
.attr("transform", "translate(0.5,0)")
.selectAll(".statusmap-annotations")
.data(annoData)
.enter().append("g")
;
anno.append("line")
//.attr("class", "statusmap-annotation-tick")
.attr("x1", d => d.x)
.attr("y1", chartTop)
.attr("x2", d => d.x)
.attr("y2", chartBottom)
.style("stroke", d => d.anno.iconColor)
.style("stroke-width", 1)
.style("stroke-dasharray", "3,3")
;
anno.append("polygon")
.attr("points", d => [[d.x, chartBottom+1], [d.x-5, chartBottom+6], [d.x+5, chartBottom+6]].join(" "))
.style("stroke-width", 0)
.style("fill", d => d.anno.iconColor)
;
// Polygons didn't fire mouseevents
anno.append("rect")
.attr("x", d => d.x-5)
.attr("width", 10)
.attr("y", chartBottom+1)
.attr("height", 5)
.attr("class", "statusmap-annotation-tick")
.attr("annoId", d => d.id)
.style("opacity", 0)
;
let $ticks = $heatmap.find(".statusmap-annotation-tick");
$ticks.on("mouseenter", (event) => {
annotationTooltip.mouseOverAnnotationTick = true;
})
.on("mouseleave", (event) => {
annotationTooltip.mouseOverAnnotationTick = false;
});
}
} }
function grafanaTimeFormat(ticks, min, max) { function grafanaTimeFormat(ticks, min, max) {
+89 -17
View File
@@ -31,7 +31,8 @@ const panelDefaults = {
}, },
cards: { cards: {
cardMinWidth: 5, cardMinWidth: 5,
cardSpacing: 2, cardVSpacing: 2,
cardHSpacing: 2,
cardRound: null cardRound: null
}, },
xAxis: { xAxis: {
@@ -57,6 +58,7 @@ const panelDefaults = {
}, },
// how null points should be handled // how null points should be handled
nullPointMode: 'as empty', nullPointMode: 'as empty',
yAxisSort: 'metrics',
highlightCards: true, highlightCards: true,
useMax: true useMax: true
}; };
@@ -98,7 +100,8 @@ let opacityScales = ['linear', 'sqrt'];
export class StatusHeatmapCtrl extends MetricsPanelCtrl { export class StatusHeatmapCtrl extends MetricsPanelCtrl {
static templateUrl = 'module.html'; static templateUrl = 'module.html';
constructor($scope, $injector, $rootScope, timeSrv) { /** @ngInject */
constructor($scope, $injector, $rootScope, timeSrv, annotationsSrv) {
super($scope, $injector); super($scope, $injector);
_.defaultsDeep(this.panel, panelDefaults); _.defaultsDeep(this.panel, panelDefaults);
@@ -127,6 +130,9 @@ export class StatusHeatmapCtrl extends MetricsPanelCtrl {
} }
}; };
this.annotations = [];
this.annotationsSrv = annotationsSrv;
this.events.on('data-received', this.onDataReceived); this.events.on('data-received', this.onDataReceived);
this.events.on('data-snapshot-load', this.onDataReceived); this.events.on('data-snapshot-load', this.onDataReceived);
this.events.on('data-error', this.onDataError); this.events.on('data-error', this.onDataError);
@@ -152,7 +158,7 @@ export class StatusHeatmapCtrl extends MetricsPanelCtrl {
]); ]);
let minCardWidth = this.panel.cards.cardMinWidth; let minCardWidth = this.panel.cards.cardMinWidth;
let minSpacing = this.panel.cards.cardSpacing; let minSpacing = this.panel.cards.cardHSpacing;
let maxCardsCount = Math.ceil((chartWidth-minCardWidth) / (minCardWidth + minSpacing)); let maxCardsCount = Math.ceil((chartWidth-minCardWidth) / (minCardWidth + minSpacing));
let intervalMs; let intervalMs;
@@ -187,11 +193,53 @@ export class StatusHeatmapCtrl extends MetricsPanelCtrl {
this.interval = kbn.secondsToHms(intervalMs / 1000); this.interval = kbn.secondsToHms(intervalMs / 1000);
}; };
issueQueries = (datasource) => {
this.annotationsPromise = this.annotationsSrv.getAnnotations({
dashboard: this.dashboard,
panel: this.panel,
range: this.range,
});
/* Wait for annotationSrv requests to get datasources to
* resolve before issuing queries. This allows the annotations
* service to fire annotations queries before graph queries
* (but not wait for completion). This resolves
* issue 11806.
*/
// 5.x before 5.4 doesn't have dataPromises
if ("undefined" !== typeof(this.annotationsSrv.datasourcePromises)) {
return this.annotationsSrv.datasourcePromises.then(r => {
return super.issueQueries(datasource);
});
} else {
return super.issueQueries(datasource);
}
}
onDataReceived = (dataList) => { onDataReceived = (dataList) => {
this.data = dataList; this.data = dataList;
this.cardsData = this.convertToCards(this.data); this.cardsData = this.convertToCards(this.data);
this.render(); this.annotationsPromise.then(
result => {
this.loading = false;
//this.alertState = result.alertState;
if (result.annotations && result.annotations.length > 0) {
this.annotations = result.annotations;
} else {
this.annotations = null;
}
this.render();
},
() => {
this.loading = false;
this.annotations = null;
this.render();
}
);
//this.render();
}; };
onInitEditMode = () => { onInitEditMode = () => {
@@ -225,6 +273,7 @@ export class StatusHeatmapCtrl extends MetricsPanelCtrl {
onDataError = () => { onDataError = () => {
this.data = []; this.data = [];
this.annotations = [];
this.render(); this.render();
}; };
@@ -242,12 +291,30 @@ export class StatusHeatmapCtrl extends MetricsPanelCtrl {
this.render(); this.render();
}; };
onEditorRemoveThresholds = () => {
this.panel.color.thresholds = [];
this.render();
};
onEditorAddThreeLights = () => { onEditorAddThreeLights = () => {
this.panel.color.thresholds.push({color: "red", value: 2, tooltip: "error" }); this.panel.color.thresholds.push({color: "red", value: 2, tooltip: "error" });
this.panel.color.thresholds.push({color: "yellow", value: 1, tooltip: "warning" }); this.panel.color.thresholds.push({color: "yellow", value: 1, tooltip: "warning" });
this.panel.color.thresholds.push({color: "green", value: 0, tooltip: "ok" }); this.panel.color.thresholds.push({color: "green", value: 0, tooltip: "ok" });
this.render(); this.render();
}; };
/* https://ethanschoonover.com/solarized/ */
onEditorAddSolarized = () => {
this.panel.color.thresholds.push({color: "#b58900", value: 0, tooltip: "yellow" });
this.panel.color.thresholds.push({color: "#cb4b16", value: 1, tooltip: "orange" });
this.panel.color.thresholds.push({color: "#dc322f", value: 2, tooltip: "red" });
this.panel.color.thresholds.push({color: "#d33682", value: 3, tooltip: "magenta" });
this.panel.color.thresholds.push({color: "#6c71c4", value: 4, tooltip: "violet" });
this.panel.color.thresholds.push({color: "#268bd2", value: 5, tooltip: "blue" });
this.panel.color.thresholds.push({color: "#2aa198", value: 6, tooltip: "cyan" });
this.panel.color.thresholds.push({color: "#859900", value: 7, tooltip: "green" });
this.render();
}
link = (scope, elem, attrs, ctrl) => { link = (scope, elem, attrs, ctrl) => {
rendering(scope, elem, attrs, ctrl); rendering(scope, elem, attrs, ctrl);
@@ -263,28 +330,27 @@ export class StatusHeatmapCtrl extends MetricsPanelCtrl {
minValue: 0, minValue: 0,
multipleValues: false, multipleValues: false,
noColorDefined: false, noColorDefined: false,
targets: [], // array of available unique targets
targetIndex: {} // indices in data array for each of available unique targets
}; };
if (!data || data.length == 0) { return cardsData;} if (!data || data.length == 0) { return cardsData;}
// collect uniq targets and their indexes in data array // Collect uniq timestamps from data and spread over targets and timestamps
cardsData.targetIndex = {};
for (let i = 0; i < data.length; i++) { // collect uniq targets and their indices
let ts = data[i]; _.map(data, (d, i) => {
let target = ts.target; cardsData.targetIndex[d.target] = _.concat(_.toArray(cardsData.targetIndex[d.target]), i)
if (cardsData.targetIndex[target] == undefined) { });
cardsData.targetIndex[target] = []
}
cardsData.targetIndex[target].push(i);
}
// TODO add some logic for targets heirarchy // TODO add some logic for targets heirarchy
cardsData.targets = _.keys(cardsData.targetIndex); cardsData.targets = _.keys(cardsData.targetIndex);
cardsData.targets.sort();
cardsData.yBucketSize = cardsData.targets.length; cardsData.yBucketSize = cardsData.targets.length;
cardsData.xBucketSize = _.min(_.map(data, d => d.datapoints.length)); // Maximum number of buckets over x axis
cardsData.xBucketSize = _.max(_.map(data, d => d.datapoints.length));
// Collect all values for each bucket from datapoints with similar target. // Collect all values for each bucket from datapoints with similar target.
// TODO aggregate values into buckets over datapoint[TIME_INDEX] not over datapoint index (j).
for(let i = 0; i < cardsData.targets.length; i++) { for(let i = 0; i < cardsData.targets.length; i++) {
let target = cardsData.targets[i]; let target = cardsData.targets[i];
@@ -294,15 +360,19 @@ export class StatusHeatmapCtrl extends MetricsPanelCtrl {
values: [], values: [],
multipleValues: false, multipleValues: false,
noColorDefined: false, noColorDefined: false,
y: target,
x: -1,
}; };
// collect values from all timeseries with target // collect values from all timeseries with target
for (let si = 0; si < cardsData.targetIndex[target].length; si++) { for (let si = 0; si < cardsData.targetIndex[target].length; si++) {
let s = data[cardsData.targetIndex[target][si]]; let s = data[cardsData.targetIndex[target][si]];
if (s.datapoints.length <= j) {
continue;
}
let datapoint = s.datapoints[j]; let datapoint = s.datapoints[j];
if (card.values.length === 0) { if (card.values.length === 0) {
card.x = datapoint[TIME_INDEX]; card.x = datapoint[TIME_INDEX];
card.y = s.target;
} }
card.values.push(datapoint[VALUE_INDEX]); card.values.push(datapoint[VALUE_INDEX]);
} }
@@ -322,7 +392,9 @@ export class StatusHeatmapCtrl extends MetricsPanelCtrl {
if (cardsData.minValue > card.minValue) if (cardsData.minValue > card.minValue)
cardsData.minValue = card.minValue; cardsData.minValue = card.minValue;
if (card.x != -1) {
cardsData.cards.push(card); cardsData.cards.push(card);
}
} }
} }