├── .gitignore ├── .travis.yml ├── Gruntfile.js ├── LICENSE ├── README.md ├── bower.json ├── dist ├── README.md ├── css │ ├── heatmap.css │ └── heatmap.css.map ├── displayEditor.html ├── heatmapControl.js ├── heatmapControl.js.map ├── heatmapEditor.html ├── img │ ├── heatmap.PNG │ ├── icn-heatmap-panel.svg │ └── timestamp_data.PNG ├── libs │ ├── d3 │ │ ├── d3.js │ │ └── d3.min.js │ └── d3plus │ │ ├── d3plus.full.js │ │ ├── d3plus.full.min.js │ │ ├── d3plus.js │ │ └── d3plus.min.js ├── module.html ├── module.js ├── module.js.map ├── plugin.json ├── properties.js ├── properties.js.map ├── series_overrides_heatmap_ctrl.js └── series_overrides_heatmap_ctrl.js.map ├── package.json └── src ├── css └── heatmap.scss ├── displayEditor.html ├── heatmapControl.js ├── heatmapEditor.html ├── img ├── heatmap.PNG ├── icn-heatmap-panel.svg └── timestamp_data.PNG ├── module.html ├── module.js ├── plugin.json ├── properties.js └── series_overrides_heatmap_ctrl.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | /bower_components/ 39 | /.project 40 | /.externalToolBuilders/ 41 | /.settings/ 42 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | before_script: 5 | - npm install -g bower 6 | - npm install -g grunt -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = (grunt) => { 2 | require('load-grunt-tasks')(grunt); 3 | 4 | grunt.loadNpmTasks('grunt-execute'); 5 | grunt.loadNpmTasks('grunt-contrib-clean'); 6 | 7 | grunt.initConfig({ 8 | 9 | clean: ['dist'], 10 | 11 | copy: { 12 | src_to_dist: { 13 | cwd: 'src', 14 | expand: true, 15 | src: ['**/*', '!**/*.js', '!**/*.scss', '!img/**/*'], 16 | dest: 'dist' 17 | }, 18 | libs_to_dist: { 19 | cwd: 'bower_components', 20 | expand: true, 21 | src: ['d3/d3.js', 22 | 'd3/d3.min.js', 23 | 'd3plus/d3plus*js'], 24 | dest: 'dist/libs' 25 | }, 26 | readme: { 27 | expand: true, 28 | src: ['README.md'], 29 | dest: 'dist', 30 | }, 31 | img_to_dist: { 32 | cwd: 'src', 33 | expand: true, 34 | src: ['img/**/*'], 35 | dest: 'dist/' 36 | }, 37 | }, 38 | 39 | watch: { 40 | rebuild_all: { 41 | files: ['src/**/*', 'README.md'], 42 | tasks: ['default'], 43 | options: {spawn: false} 44 | }, 45 | }, 46 | 47 | sass: { 48 | options: { 49 | sourceMap: true 50 | }, 51 | dist: { 52 | files: { 53 | 'dist/css/heatmap.css': 'src/css/heatmap.scss' 54 | } 55 | } 56 | }, 57 | 58 | babel: { 59 | options: { 60 | sourceMap: true, 61 | presets: ['es2015'], 62 | plugins: ['transform-es2015-modules-systemjs', 'transform-es2015-for-of'], 63 | }, 64 | dist: { 65 | files: [{ 66 | cwd: 'src', 67 | expand: true, 68 | src: ['*.js'], 69 | dest: 'dist', 70 | ext: '.js' 71 | }] 72 | }, 73 | }, 74 | 75 | }); 76 | 77 | grunt.registerTask('default', ['clean', 'copy:src_to_dist', 'copy:libs_to_dist', 'copy:readme', 'copy:img_to_dist', 'sass', 'babel']); 78 | }; 79 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # grafana-heatmap 2 | [![Build Status](https://travis-ci.org/savantly-net/grafana-heatmap.svg?branch=master)](https://travis-ci.org/savantly-net/grafana-heatmap) 3 | 4 | Heatmap Panel Plugin for Grafana 5 | 6 | Example 7 | ![Heatmap](https://raw.githubusercontent.com/savantly-net/grafana-heatmap/master/src/img/heatmap.PNG?raw=true) 8 | 9 | Color By Value grouped by timestamp 10 | ![Timestamp Data](https://raw.githubusercontent.com/savantly-net/grafana-heatmap/master/src/img/timestamp_data.PNG?raw=true) -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grafana-heatmap", 3 | "homepage": "https://github.com/savantly-net/grafana-heatmap", 4 | "authors": [ 5 | "Jeremy " 6 | ], 7 | "description": "A heatmap panel plugin for grafana", 8 | "main": "module.js", 9 | "license": "APACHE 2.0", 10 | "ignore": [ 11 | "**/.*", 12 | "node_modules", 13 | "bower_components", 14 | "test", 15 | "tests" 16 | ], 17 | "dependencies": { 18 | "d3plus": "^1.9.8" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /dist/README.md: -------------------------------------------------------------------------------- 1 | # grafana-heatmap 2 | [![Build Status](https://travis-ci.org/savantly-net/grafana-heatmap.svg?branch=master)](https://travis-ci.org/savantly-net/grafana-heatmap) 3 | 4 | Heatmap Panel Plugin for Grafana 5 | 6 | Example 7 | ![Heatmap](https://raw.githubusercontent.com/savantly-net/grafana-heatmap/master/src/img/heatmap.PNG?raw=true) 8 | 9 | Color By Value grouped by timestamp 10 | ![Timestamp Data](https://raw.githubusercontent.com/savantly-net/grafana-heatmap/master/src/img/timestamp_data.PNG?raw=true) -------------------------------------------------------------------------------- /dist/css/heatmap.css: -------------------------------------------------------------------------------- 1 | .diagram { 2 | overflow: auto; } 3 | 4 | .gradient-values-container .gradient-value-max { 5 | float: right; } 6 | 7 | .diagram g.label div { 8 | color: #333; } 9 | 10 | .diagram div p { 11 | text-align: center; } 12 | 13 | .diagram svg { 14 | margin: auto; 15 | display: block; } 16 | 17 | g.node > * { 18 | fill: white; } 19 | 20 | svg > g { 21 | overflow: visible; 22 | display: block; } 23 | 24 | .diagram-value { 25 | background-color: white; 26 | padding: 3px; 27 | margin-top: 8px; } 28 | 29 | /** LEGEND **/ 30 | div.graph-legend-wrapper .graph-legend-table { 31 | display: inline-table; } 32 | 33 | /* Flowchart variables */ 34 | /* Sequence Diagram variables */ 35 | /* Gantt chart variables */ 36 | .mermaid .label { 37 | font-family: 'trebuchet ms', verdana, arial; 38 | color: #333; } 39 | 40 | .node rect, 41 | .node circle, 42 | .node ellipse, 43 | .node polygon { 44 | fill: #cde498; 45 | stroke: #13540c; 46 | stroke-width: 1px; } 47 | 48 | .edgePath .path { 49 | stroke: green; 50 | stroke-width: 1.5px; } 51 | 52 | .edgeLabel { 53 | background-color: #e8e8e8; } 54 | 55 | .cluster rect { 56 | fill: #cdffb2 !important; 57 | rx: 4 !important; 58 | stroke: #6eaa49 !important; 59 | stroke-width: 1px !important; } 60 | 61 | .cluster text { 62 | fill: #333; } 63 | 64 | .actor { 65 | stroke: #13540c; 66 | fill: #cde498; } 67 | 68 | text.actor { 69 | fill: black; 70 | stroke: none; } 71 | 72 | .actor-line { 73 | stroke: grey; } 74 | 75 | .messageLine0 { 76 | stroke-width: 1.5; 77 | stroke-dasharray: "2 2"; 78 | marker-end: "url(#arrowhead)"; 79 | stroke: green; } 80 | 81 | .messageLine1 { 82 | stroke-width: 1.5; 83 | stroke-dasharray: "2 2"; 84 | stroke: green; } 85 | 86 | #arrowhead { 87 | fill: green; } 88 | 89 | #crosshead path { 90 | fill: green !important; 91 | stroke: green !important; } 92 | 93 | .messageText { 94 | fill: #D8D9DA; 95 | stroke: none; } 96 | 97 | .labelBox { 98 | stroke: #326932; 99 | fill: #cde498; } 100 | 101 | .labelText { 102 | fill: black; 103 | stroke: none; } 104 | 105 | .loopText { 106 | fill: #D8D9DA; 107 | stroke: none; } 108 | 109 | .loopLine { 110 | stroke-width: 2; 111 | stroke-dasharray: "2 2"; 112 | marker-end: "url(#arrowhead)"; 113 | stroke: #326932; } 114 | 115 | .note { 116 | stroke: #6eaa49; 117 | fill: #fff5ad; } 118 | 119 | .noteText { 120 | fill: black; 121 | stroke: none; 122 | font-family: 'trebuchet ms', verdana, arial; 123 | font-size: 14px; } 124 | 125 | /** Section styling */ 126 | .diagram .section { 127 | stroke: none; 128 | opacity: 0.2; } 129 | 130 | .section0 { 131 | fill: #6eaa49; } 132 | 133 | .section2 { 134 | fill: #6eaa49; } 135 | 136 | .section1, 137 | .section3 { 138 | fill: white; 139 | opacity: 0.2; } 140 | 141 | .sectionTitle0 { 142 | fill: #333; } 143 | 144 | .sectionTitle1 { 145 | fill: #333; } 146 | 147 | .sectionTitle2 { 148 | fill: #333; } 149 | 150 | .sectionTitle3 { 151 | fill: #333; } 152 | 153 | .sectionTitle { 154 | text-anchor: start; 155 | font-size: 11px; 156 | text-height: 14px; } 157 | 158 | /* Grid and axis */ 159 | .grid .tick { 160 | stroke: lightgrey; 161 | opacity: 0.3; 162 | shape-rendering: crispEdges; } 163 | 164 | .grid path { 165 | stroke-width: 0; } 166 | 167 | /* Today line */ 168 | .today { 169 | fill: none; 170 | stroke: red; 171 | stroke-width: 2px; } 172 | 173 | /* Task styling */ 174 | /* Default task */ 175 | .task { 176 | stroke-width: 2; } 177 | 178 | .taskText { 179 | text-anchor: middle; 180 | font-size: 11px; } 181 | 182 | .taskTextOutsideRight { 183 | fill: black; 184 | text-anchor: start; 185 | font-size: 11px; } 186 | 187 | .taskTextOutsideLeft { 188 | fill: black; 189 | text-anchor: end; 190 | font-size: 11px; } 191 | 192 | /* Specific task settings for the sections*/ 193 | .taskText0, 194 | .taskText1, 195 | .taskText2, 196 | .taskText3 { 197 | fill: white; } 198 | 199 | .task0, 200 | .task1, 201 | .task2, 202 | .task3 { 203 | fill: #487e3a; 204 | stroke: #13540c; } 205 | 206 | .taskTextOutside0, 207 | .taskTextOutside2 { 208 | fill: black; } 209 | 210 | .taskTextOutside1, 211 | .taskTextOutside3 { 212 | fill: black; } 213 | 214 | /* Active task */ 215 | .active0, 216 | .active1, 217 | .active2, 218 | .active3 { 219 | fill: #cde498; 220 | stroke: #13540c; } 221 | 222 | .activeText0, 223 | .activeText1, 224 | .activeText2, 225 | .activeText3 { 226 | fill: black !important; } 227 | 228 | /* Completed task */ 229 | .done0, 230 | .done1, 231 | .done2, 232 | .done3 { 233 | stroke: grey; 234 | fill: lightgrey; 235 | stroke-width: 2; } 236 | 237 | .doneText0, 238 | .doneText1, 239 | .doneText2, 240 | .doneText3 { 241 | fill: black !important; } 242 | 243 | /* Tasks on the critical line */ 244 | .crit0, 245 | .crit1, 246 | .crit2, 247 | .crit3 { 248 | stroke: #ff8888; 249 | fill: red; 250 | stroke-width: 2; } 251 | 252 | .activeCrit0, 253 | .activeCrit1, 254 | .activeCrit2, 255 | .activeCrit3 { 256 | stroke: #ff8888; 257 | fill: #cde498; 258 | stroke-width: 2; } 259 | 260 | .doneCrit0, 261 | .doneCrit1, 262 | .doneCrit2, 263 | .doneCrit3 { 264 | stroke: #ff8888; 265 | fill: lightgrey; 266 | stroke-width: 2; 267 | cursor: pointer; 268 | shape-rendering: crispEdges; } 269 | 270 | .doneCritText0, 271 | .doneCritText1, 272 | .doneCritText2, 273 | .doneCritText3 { 274 | fill: black !important; } 275 | 276 | .activeCritText0, 277 | .activeCritText1, 278 | .activeCritText2, 279 | .activeCritText3 { 280 | fill: black !important; } 281 | 282 | .titleText { 283 | text-anchor: middle; 284 | font-size: 18px; 285 | fill: black; } 286 | 287 | /* 288 | 289 | 290 | */ 291 | g.classGroup text { 292 | fill: #13540c; 293 | stroke: none; 294 | font-family: 'trebuchet ms', verdana, arial; 295 | font-size: 14px; } 296 | 297 | g.classGroup rect { 298 | fill: #cde498; 299 | stroke: #13540c; } 300 | 301 | g.classGroup line { 302 | stroke: #13540c; 303 | stroke-width: 1; } 304 | 305 | svg .classLabel .box { 306 | stroke: none; 307 | stroke-width: 0; 308 | fill: #cde498; 309 | opacity: 0.5; } 310 | 311 | svg .classLabel .label { 312 | fill: #13540c; } 313 | 314 | .relation { 315 | stroke: #13540c; 316 | stroke-width: 1; 317 | fill: none; } 318 | 319 | .composition { 320 | fill: #13540c; 321 | stroke: #13540c; 322 | stroke-width: 1; } 323 | 324 | #compositionStart { 325 | fill: #13540c; 326 | stroke: #13540c; 327 | stroke-width: 1; } 328 | 329 | #compositionEnd { 330 | fill: #13540c; 331 | stroke: #13540c; 332 | stroke-width: 1; } 333 | 334 | .aggregation { 335 | fill: #cde498; 336 | stroke: #13540c; 337 | stroke-width: 1; } 338 | 339 | #aggregationStart { 340 | fill: #cde498; 341 | stroke: #13540c; 342 | stroke-width: 1; } 343 | 344 | #aggregationEnd { 345 | fill: #cde498; 346 | stroke: #13540c; 347 | stroke-width: 1; } 348 | 349 | #dependencyStart { 350 | fill: #13540c; 351 | stroke: #13540c; 352 | stroke-width: 1; } 353 | 354 | #dependencyEnd { 355 | fill: #13540c; 356 | stroke: #13540c; 357 | stroke-width: 1; } 358 | 359 | #extensionStart { 360 | fill: #13540c; 361 | stroke: #13540c; 362 | stroke-width: 1; } 363 | 364 | #extensionEnd { 365 | fill: #13540c; 366 | stroke: #13540c; 367 | stroke-width: 1; } 368 | 369 | .node text { 370 | font-family: 'trebuchet ms', verdana, arial; 371 | font-size: 14px; } 372 | 373 | div.mermaidTooltip { 374 | position: absolute; 375 | text-align: center; 376 | max-width: 200px; 377 | padding: 2px; 378 | font-family: 'trebuchet ms', verdana, arial; 379 | font-size: 12px; 380 | background: #cdffb2; 381 | border: 1px solid #6eaa49; 382 | border-radius: 2px; 383 | pointer-events: none; 384 | z-index: 100; } 385 | 386 | /*# sourceMappingURL=heatmap.css.map */ -------------------------------------------------------------------------------- /dist/css/heatmap.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "file": "heatmap.css", 4 | "sources": [ 5 | "../../src/css/heatmap.scss" 6 | ], 7 | "mappings": "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", 8 | "names": [] 9 | } -------------------------------------------------------------------------------- /dist/displayEditor.html: -------------------------------------------------------------------------------- 1 |
2 |
Legend
3 |
4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 | 15 |
16 |
17 | 18 |
19 |
Options
20 |
21 | 22 | 25 |
26 | 34 |
35 | 38 | 42 |
43 |
44 | 45 |
46 | 50 |
51 | 52 | 53 |   54 | 55 | 56 | 57 | 58 | Invert 59 | 60 | 61 |
62 |
63 | 64 | 65 |
66 |
67 | 68 | 72 |
73 |
74 | 75 |
76 |
77 |
78 |
Series specific overrides Regex match example: /server[0-3]/i
79 |
80 |
81 | 82 |
83 |
84 | 85 |
86 |
87 | 99 |
100 | 101 |
102 | 103 | 104 |
105 | 106 |
107 |
108 |
109 | 110 |
111 | 114 |
115 |
116 |
117 | 118 | 121 |
122 |
-------------------------------------------------------------------------------- /dist/heatmapControl.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | System.register(['./libs/d3/d3', 'app/core/time_series2', 'app/core/utils/kbn', 'app/plugins/sdk', './properties', 'lodash', 'moment', './series_overrides_heatmap_ctrl', './css/heatmap.css!'], function (_export, _context) { 4 | "use strict"; 5 | 6 | var TimeSeries, kbn, MetricsPanelCtrl, heatmapEditor, displayEditor, pluginName, _, moment, _createClass, panelOptions, panelDefaults, HeatmapCtrl; 7 | 8 | function _classCallCheck(instance, Constructor) { 9 | if (!(instance instanceof Constructor)) { 10 | throw new TypeError("Cannot call a class as a function"); 11 | } 12 | } 13 | 14 | function _possibleConstructorReturn(self, call) { 15 | if (!self) { 16 | throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 17 | } 18 | 19 | return call && (typeof call === "object" || typeof call === "function") ? call : self; 20 | } 21 | 22 | function _inherits(subClass, superClass) { 23 | if (typeof superClass !== "function" && superClass !== null) { 24 | throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); 25 | } 26 | 27 | subClass.prototype = Object.create(superClass && superClass.prototype, { 28 | constructor: { 29 | value: subClass, 30 | enumerable: false, 31 | writable: true, 32 | configurable: true 33 | } 34 | }); 35 | if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; 36 | } 37 | 38 | function ensureArrayContains(array, value) { 39 | if (array.indexOf(value) == -1) { 40 | array.push(value); 41 | } 42 | } 43 | 44 | function colorToHex(color) { 45 | if (color.substr(0, 1) === '#') { 46 | return color; 47 | } 48 | var digits = color.replace(/[rgba\(\)\ ]/g, '').split(','); 49 | while (digits.length < 3) { 50 | digits.push(255); 51 | } 52 | 53 | var red = parseInt(digits[0]); 54 | var green = parseInt(digits[1]); 55 | var blue = parseInt(digits[2]); 56 | 57 | var rgba = blue | green << 8 | red << 16; 58 | return '#' + rgba.toString(16); 59 | } 60 | 61 | function getColorByXPercentage(canvas, xPercent) { 62 | var x = canvas.width * xPercent || 0; 63 | var context = canvas.getContext("2d"); 64 | var p = context.getImageData(x, 1, 1, 1).data; 65 | var color = 'rgba(' + [p[0] + ',' + p[1] + ',' + p[2] + ',' + p[3]] + ')'; 66 | return color; 67 | } 68 | 69 | return { 70 | setters: [function (_libsD3D) {}, function (_appCoreTime_series) { 71 | TimeSeries = _appCoreTime_series.default; 72 | }, function (_appCoreUtilsKbn) { 73 | kbn = _appCoreUtilsKbn.default; 74 | }, function (_appPluginsSdk) { 75 | MetricsPanelCtrl = _appPluginsSdk.MetricsPanelCtrl; 76 | }, function (_properties) { 77 | heatmapEditor = _properties.heatmapEditor; 78 | displayEditor = _properties.displayEditor; 79 | pluginName = _properties.pluginName; 80 | }, function (_lodash) { 81 | _ = _lodash.default; 82 | }, function (_moment) { 83 | moment = _moment.default; 84 | }, function (_series_overrides_heatmap_ctrl) {}, function (_cssHeatmapCss) {}], 85 | execute: function () { 86 | _createClass = function () { 87 | function defineProperties(target, props) { 88 | for (var i = 0; i < props.length; i++) { 89 | var descriptor = props[i]; 90 | descriptor.enumerable = descriptor.enumerable || false; 91 | descriptor.configurable = true; 92 | if ("value" in descriptor) descriptor.writable = true; 93 | Object.defineProperty(target, descriptor.key, descriptor); 94 | } 95 | } 96 | 97 | return function (Constructor, protoProps, staticProps) { 98 | if (protoProps) defineProperties(Constructor.prototype, protoProps); 99 | if (staticProps) defineProperties(Constructor, staticProps); 100 | return Constructor; 101 | }; 102 | }(); 103 | 104 | panelOptions = { 105 | aggregationFunctions: ['avg', 'min', 'max', 'total', 'current', 'count'], 106 | treeMap: { 107 | modes: ['squarify', 'slice', 'dice', 'slice-dice'], 108 | aggregationFunctions: ['sum', 'min', 'max', 'extent', 'mean', 'median', 'quantile', 'variance', 'deviation'], 109 | timestampFormats: ['YYYY-MM-DDTHH', 'YYYY-MM-DDTHH:mm', 'YYYY-MM-DDTHH:mm:ss', 'YYYY-MM-DDTHH:mm:ss.sssZ'] 110 | } 111 | }; 112 | panelDefaults = { 113 | // other style overrides 114 | seriesOverrides: [], 115 | thresholds: '0,10', 116 | colors: ['rgba(50, 172, 45, 1)', 'rgba(241, 255, 0, 1)', 'rgba(245, 54, 54, 1)'], 117 | legend: { 118 | show: true, 119 | min: true, 120 | max: true, 121 | avg: true, 122 | current: true, 123 | total: true 124 | }, 125 | maxDataPoints: 100, 126 | mappingType: 1, 127 | nullPointMode: 'connected', 128 | format: 'none', 129 | valueMaps: [{ value: 'null', op: '=', text: 'N/A' }], 130 | treeMap: { 131 | mode: 'squarify', 132 | groups: [{ key: 'server', value: '/^.*\./g' }], 133 | colorByFunction: 'max', 134 | sizeByFunction: 'constant', 135 | enableTimeBlocks: false, 136 | enableGrouping: true, 137 | debug: false, 138 | depth: 0, 139 | ids: ['alias'], 140 | nodeSizeProperty: "value" 141 | } 142 | }; 143 | 144 | _export('MetricsPanelCtrl', _export('HeatmapCtrl', HeatmapCtrl = function (_MetricsPanelCtrl) { 145 | _inherits(HeatmapCtrl, _MetricsPanelCtrl); 146 | 147 | function HeatmapCtrl($scope, $injector, $sce) { 148 | _classCallCheck(this, HeatmapCtrl); 149 | 150 | var _this2 = _possibleConstructorReturn(this, (HeatmapCtrl.__proto__ || Object.getPrototypeOf(HeatmapCtrl)).call(this, $scope, $injector)); 151 | 152 | _.defaults(_this2.panel, panelDefaults); 153 | 154 | _this2.options = panelOptions; 155 | _this2.panel.chartId = 'chart_' + _this2.panel.id; 156 | _this2.containerDivId = 'container_' + _this2.panel.chartId; 157 | _this2.$sce = $sce; 158 | _this2.events.on('init-edit-mode', _this2.onInitEditMode.bind(_this2)); 159 | _this2.events.on('data-received', _this2.onDataReceived.bind(_this2)); 160 | _this2.events.on('data-snapshot-load', _this2.onDataReceived.bind(_this2)); 161 | _this2.initializePanel(); 162 | return _this2; 163 | } 164 | 165 | _createClass(HeatmapCtrl, [{ 166 | key: 'initializePanel', 167 | value: function initializePanel() { 168 | var d3plusPath = 'plugins/' + pluginName + '/libs/d3plus/d3plus.full.js'; 169 | var _this = this; 170 | var meta = {}; 171 | meta[d3plusPath] = { 172 | format: 'global' 173 | }; 174 | 175 | SystemJS.config({ 176 | meta: meta 177 | }); 178 | 179 | SystemJS.import(d3plusPath).then(function d3plusLoaded() { 180 | console.log('d3plus is loaded'); 181 | _this.events.emit('data-received'); 182 | }); 183 | } 184 | }, { 185 | key: 'handleError', 186 | value: function handleError(err) { 187 | this.getPanelContainer().html('

Error:

' + err + '
'); 188 | } 189 | }, { 190 | key: 'onInitEditMode', 191 | value: function onInitEditMode() { 192 | this.addEditorTab('Heatmap', heatmapEditor, 2); 193 | this.addEditorTab('Display', displayEditor, 3); 194 | } 195 | }, { 196 | key: 'getPanelContainer', 197 | value: function getPanelContainer() { 198 | return $(document.getElementById(this.containerDivId)); 199 | } 200 | }, { 201 | key: 'onDataReceived', 202 | value: function onDataReceived(dataList) { 203 | console.info('received data'); 204 | console.debug(dataList); 205 | if (undefined != dataList) { 206 | this.series = dataList.map(this.seriesHandler.bind(this)); 207 | console.info('mapped dataList to series'); 208 | } 209 | 210 | var preparedData = this.d3plusDataProcessor(this.series); 211 | this.render(preparedData); 212 | } 213 | }, { 214 | key: 'getGroupKeys', 215 | value: function getGroupKeys() { 216 | return this.panel.treeMap.groups.map(function (group) { 217 | return group.key; 218 | }); 219 | } 220 | }, { 221 | key: 'd3plusDataProcessor', 222 | value: function d3plusDataProcessor(dataArray) { 223 | var resultArray = []; 224 | var hasGroups = this.panel.treeMap.groups.length > 0; 225 | 226 | if (!hasGroups) { 227 | // just add the original items since there are no groups 228 | for (var dataIndex = 0; dataIndex < dataArray.length; dataIndex++) { 229 | var newDataItem = Object.assign({}, dataArray[dataIndex], dataArray[dataIndex].stats); 230 | resultArray.push(newDataItem); 231 | } 232 | } else { 233 | // Process Groups 234 | var groupArray = []; 235 | for (var groupIndex = 0; groupIndex < this.panel.treeMap.groups.length; groupIndex++) { 236 | groupArray.push({ 237 | key: this.panel.treeMap.groups[groupIndex].key, 238 | regex: kbn.stringToJsRegex(this.panel.treeMap.groups[groupIndex].value) 239 | }); 240 | } 241 | for (var dataIndex = 0; dataIndex < dataArray.length; dataIndex++) { 242 | var newDataItem = Object.assign({}, dataArray[dataIndex]); 243 | // only add the stats if we arent using granular timeblock data 244 | if (!this.panel.treeMap.enableTimeBlocks) { 245 | Object.assign(newDataItem, dataArray[dataIndex].stats); 246 | } 247 | delete newDataItem.stats; 248 | 249 | for (var groupIndex = 0; groupIndex < groupArray.length; groupIndex++) { 250 | var key = groupArray[groupIndex].key; 251 | var regex = groupArray[groupIndex].regex; 252 | var matches = newDataItem.alias.match(regex); 253 | if (matches && matches.length > 0) { 254 | newDataItem[key] = matches[0]; 255 | } else { 256 | newDataItem[key] = 'NA'; 257 | } 258 | } 259 | resultArray.push(newDataItem); 260 | } 261 | } 262 | 263 | // If we're using timeBlocks mode 264 | // replace the aggregated series with individual records 265 | if (this.panel.treeMap.enableTimeBlocks) { 266 | console.info('creating timeblock records'); 267 | var timeBlockArray = []; 268 | for (var dataIndex = 0; dataIndex < resultArray.length; dataIndex++) { 269 | console.debug('dataIndex:' + dataIndex + ', alias:' + resultArray[dataIndex].alias); 270 | var dataSeries = resultArray[dataIndex]; 271 | for (var dataPointIndex = 0; dataPointIndex < dataSeries.flotpairs.length; dataPointIndex++) { 272 | var dataSeriesCopy = Object.assign({}, dataSeries); 273 | delete dataSeriesCopy.datapoints; 274 | delete dataSeriesCopy.flotpairs; 275 | dataSeriesCopy.count = 1; 276 | dataSeriesCopy.timestamp = dataSeries.flotpairs[dataPointIndex][0]; 277 | dataSeriesCopy.value = dataSeries.flotpairs[dataPointIndex][1]; 278 | timeBlockArray.push(dataSeriesCopy); 279 | } 280 | } 281 | resultArray = timeBlockArray; 282 | } 283 | 284 | return resultArray; 285 | } 286 | }, { 287 | key: 'seriesHandler', 288 | value: function seriesHandler(seriesData) { 289 | var series = new TimeSeries({ 290 | datapoints: seriesData.datapoints, 291 | alias: seriesData.target.replace(/"|,|;|=|:|{|}/g, '_') 292 | }); 293 | series.flotpairs = series.getFlotPairs(this.panel.nullPointMode); 294 | return series; 295 | } 296 | }, { 297 | key: 'addSeriesOverride', 298 | value: function addSeriesOverride(override) { 299 | this.panel.seriesOverrides.push(override || {}); 300 | } 301 | }, { 302 | key: 'addTreeMapGroup', 303 | value: function addTreeMapGroup(group) { 304 | this.panel.treeMap.groups.push(group || {}); 305 | } 306 | }, { 307 | key: 'removeSeriesOverride', 308 | value: function removeSeriesOverride(override) { 309 | this.panel.seriesOverrides = _.without(this.panel.seriesOverrides, override); 310 | this.render(); 311 | } 312 | }, { 313 | key: 'removeTreeMapGroup', 314 | value: function removeTreeMapGroup(group) { 315 | this.panel.treeMap.groups = _.without(this.panel.treeMap.groups, group); 316 | this.render(); 317 | } 318 | }, { 319 | key: 'updateThresholds', 320 | value: function updateThresholds() { 321 | var thresholdCount = this.panel.thresholds.length; 322 | var colorCount = this.panel.colors.length; 323 | this.refresh(); 324 | } 325 | }, { 326 | key: 'changeColor', 327 | value: function changeColor(colorIndex, color) { 328 | this.panel.colors[colorIndex] = color; 329 | } 330 | }, { 331 | key: 'removeColor', 332 | value: function removeColor(colorIndex) { 333 | this.panel.colors.splice(colorIndex, 1); 334 | } 335 | }, { 336 | key: 'addColor', 337 | value: function addColor() { 338 | this.panel.colors.push('rgba(255, 255, 255, 1)'); 339 | } 340 | }, { 341 | key: 'getGradientForValue', 342 | value: function getGradientForValue(data, value) { 343 | var min = Math.min.apply(Math, data.thresholds); 344 | var max = Math.max.apply(Math, data.thresholds); 345 | var absoluteDistance = max - min; 346 | var valueDistanceFromMin = value - min; 347 | var xPercent = valueDistanceFromMin / absoluteDistance; 348 | // Get the smaller number to clamp at 0.99 max 349 | xPercent = Math.min(0.99, xPercent); 350 | // Get the larger number to clamp at 0.01 min 351 | xPercent = Math.max(0.01, xPercent); 352 | 353 | return getColorByXPercentage(this.canvas, xPercent); 354 | } 355 | }, { 356 | key: 'applyOverrides', 357 | value: function applyOverrides(seriesItemAlias) { 358 | var seriesItem = {}, 359 | colorData = {}, 360 | overrides = {}; 361 | console.info('applying overrides for seriesItem'); 362 | console.debug(seriesItemAlias); 363 | console.debug(this.panel.seriesOverrides); 364 | for (var i = 0; i <= this.panel.seriesOverrides.length; i++) { 365 | console.debug('comparing:'); 366 | console.debug(this.panel.seriesOverrides[i]); 367 | if (this.panel.seriesOverrides[i] && this.panel.seriesOverrides[i].alias == seriesItemAlias) { 368 | overrides = this.panel.seriesOverrides[i]; 369 | } 370 | } 371 | colorData.thresholds = (overrides.thresholds || this.panel.thresholds).split(',').map(function (strVale) { 372 | return Number(strVale.trim()); 373 | }); 374 | colorData.colorMap = this.panel.colors; 375 | seriesItem.colorData = colorData; 376 | 377 | seriesItem.valueName = overrides.valueName || this.panel.valueName; 378 | 379 | return seriesItem; 380 | } 381 | }, { 382 | key: 'invertColorOrder', 383 | value: function invertColorOrder() { 384 | this.panel.colors.reverse(); 385 | this.refresh(); 386 | } 387 | }, { 388 | key: 'addTreeMapId', 389 | value: function addTreeMapId() { 390 | this.panel.treeMap.ids.push(''); 391 | this.refresh(); 392 | } 393 | }, { 394 | key: 'removeTreeMapId', 395 | value: function removeTreeMapId(pos) { 396 | this.panel.treeMap.ids.splice(pos, 1); 397 | this.refresh(); 398 | } 399 | }, { 400 | key: 'changeTreeMapId', 401 | value: function changeTreeMapId(idString, pos) { 402 | this.panel.treeMap.ids[pos] = idString; 403 | } 404 | }, { 405 | key: 'link', 406 | value: function link(scope, elem, attrs, ctrl) { 407 | var chartElement = elem.find('.heatmap'); 408 | chartElement.append('
'); 409 | var chartContainer = $(document.getElementById(ctrl.containerDivId)); 410 | console.debug('found chartContainer'); 411 | console.debug(chartContainer); 412 | elem.css('height', ctrl.height + 'px'); 413 | 414 | var canvas = elem.find('.canvas')[0]; 415 | ctrl.canvas = canvas; 416 | var gradientValueMax = elem.find('.gradient-value-max')[0]; 417 | var gradientValueMin = elem.find('.gradient-value-min')[0]; 418 | 419 | var visFormat = { 420 | "text": function text(_text, opts) { 421 | if (opts.key == 'timestamp') { 422 | var timestamp = moment(Number(_text)); 423 | return timestamp.format(ctrl.panel.treeMap.timestampFormat); 424 | } else if (ctrl.getGroupKeys().indexOf(opts.key) > -1) { 425 | return _text; 426 | } else { 427 | return d3plus.string.title(_text, opts); 428 | } 429 | } 430 | }; 431 | 432 | function render(data) { 433 | updateSize(); 434 | updateCanvasStyle(); 435 | updateChart(data); 436 | } 437 | 438 | function updateCanvasStyle() { 439 | canvas.width = Math.max(chartElement[0].clientWidth, 100); 440 | var canvasContext = canvas.getContext("2d"); 441 | canvasContext.clearRect(0, 0, canvas.width, canvas.height); 442 | 443 | var grd = canvasContext.createLinearGradient(0, 0, canvas.width, 0); 444 | var colorWidth = 1 / Math.max(ctrl.panel.colors.length, 1); 445 | for (var i = 0; i < ctrl.panel.colors.length; i++) { 446 | var currentColor = ctrl.panel.colors[i]; 447 | grd.addColorStop(Math.min(colorWidth * i, 1), currentColor); 448 | } 449 | canvasContext.fillStyle = grd; 450 | canvasContext.fillRect(0, 0, canvas.width, 3); 451 | ctrl.canvasContext = canvasContext; 452 | 453 | gradientValueMax.innerText = Math.max.apply(Math, ctrl.panel.thresholds.split(',')); 454 | gradientValueMin.innerText = Math.min.apply(Math, ctrl.panel.thresholds.split(',')); 455 | } 456 | 457 | function updateSize() { 458 | elem.css('height', ctrl.height + 'px'); 459 | } 460 | 461 | function getVisSize(dataPoint) { 462 | if (ctrl.panel.treeMap.sizeByFunction == 'constant') return 1;else { 463 | return dataPoint[ctrl.panel.treeMap.sizeByFunction] || dataPoint.value; 464 | } 465 | } 466 | 467 | function getVisColor(dataPoint) { 468 | var value = dataPoint[ctrl.panel.treeMap.colorByFunction] || dataPoint.value; 469 | var rgbColor = ctrl.getGradientForValue({ thresholds: ctrl.panel.thresholds.split(',') }, value); 470 | var hexColor = colorToHex(rgbColor); 471 | return hexColor; 472 | } 473 | 474 | function updateChart(data) { 475 | d3.select("#" + ctrl.containerDivId).selectAll('*').remove(); 476 | 477 | // Make sure the necessary IDs are added 478 | var idKeys = Array.from(ctrl.panel.treeMap.ids); 479 | if (idKeys.length == 0) { 480 | ensureArrayContains(idKeys, 'alias'); 481 | } 482 | if (ctrl.panel.treeMap.enableTimeBlocks) { 483 | ensureArrayContains(idKeys, 'timestamp'); 484 | } 485 | 486 | // Setup Aggregations 487 | var aggs = {}; 488 | aggs.value = ctrl.panel.treeMap.aggregationFunction; 489 | aggs.current = ctrl.panel.treeMap.aggregationFunction; 490 | aggs.count = 'sum'; 491 | aggs.total = 'sum'; 492 | aggs.avg = 'mean'; 493 | aggs.min = 'min'; 494 | aggs.max = 'max'; 495 | 496 | d3plus.viz().dev(ctrl.panel.treeMap.debug).aggs(aggs).container("#" + ctrl.containerDivId).legend(ctrl.panel.treeMap.showLegend).data(data) 497 | //.type("tree_map") 498 | .type({ "mode": ctrl.panel.treeMap.mode }) // sets the mode of visualization display based on type 499 | .id({ 500 | "value": _.uniq(idKeys), 501 | "grouping": ctrl.panel.treeMap.enableGrouping 502 | }).depth(Number(ctrl.panel.treeMap.depth)).size(getVisSize).height(ctrl.height).width(ctrl.width).color(getVisColor).format(visFormat).draw(); 503 | } 504 | 505 | this.events.on('render', function onRender(data) { 506 | if (typeof d3plus !== 'undefined' && data) { 507 | render(data); 508 | ctrl.renderingCompleted(); 509 | } else { 510 | console.info('d3plus is not loaded yet'); 511 | } 512 | }); 513 | } 514 | }]); 515 | 516 | return HeatmapCtrl; 517 | }(MetricsPanelCtrl))); 518 | 519 | ;HeatmapCtrl.templateUrl = 'module.html'; 520 | 521 | _export('HeatmapCtrl', HeatmapCtrl); 522 | 523 | _export('MetricsPanelCtrl', HeatmapCtrl); 524 | } 525 | }; 526 | }); 527 | //# sourceMappingURL=heatmapControl.js.map 528 | -------------------------------------------------------------------------------- /dist/heatmapControl.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/heatmapControl.js"],"names":["ensureArrayContains","array","value","indexOf","push","colorToHex","color","substr","digits","replace","split","length","red","parseInt","green","blue","rgba","toString","getColorByXPercentage","canvas","xPercent","x","width","context","getContext","p","getImageData","data","TimeSeries","kbn","MetricsPanelCtrl","heatmapEditor","displayEditor","pluginName","_","moment","panelOptions","aggregationFunctions","treeMap","modes","timestampFormats","panelDefaults","seriesOverrides","thresholds","colors","legend","show","min","max","avg","current","total","maxDataPoints","mappingType","nullPointMode","format","valueMaps","op","text","mode","groups","key","colorByFunction","sizeByFunction","enableTimeBlocks","enableGrouping","debug","depth","ids","nodeSizeProperty","HeatmapCtrl","$scope","$injector","$sce","defaults","panel","options","chartId","id","containerDivId","events","on","onInitEditMode","bind","onDataReceived","initializePanel","d3plusPath","_this","meta","SystemJS","config","import","then","d3plusLoaded","console","log","emit","err","getPanelContainer","html","addEditorTab","$","document","getElementById","dataList","info","undefined","series","map","seriesHandler","preparedData","d3plusDataProcessor","render","group","dataArray","resultArray","hasGroups","dataIndex","newDataItem","Object","assign","stats","groupArray","groupIndex","regex","stringToJsRegex","matches","alias","match","timeBlockArray","dataSeries","dataPointIndex","flotpairs","dataSeriesCopy","datapoints","count","timestamp","seriesData","target","getFlotPairs","override","without","thresholdCount","colorCount","refresh","colorIndex","splice","Math","apply","absoluteDistance","valueDistanceFromMin","seriesItemAlias","seriesItem","colorData","overrides","i","strVale","Number","trim","colorMap","valueName","reverse","pos","idString","scope","elem","attrs","ctrl","chartElement","find","append","chartContainer","css","height","gradientValueMax","gradientValueMin","visFormat","opts","timestampFormat","getGroupKeys","d3plus","string","title","updateSize","updateCanvasStyle","updateChart","clientWidth","canvasContext","clearRect","grd","createLinearGradient","colorWidth","currentColor","addColorStop","fillStyle","fillRect","innerText","getVisSize","dataPoint","getVisColor","rgbColor","getGradientForValue","hexColor","d3","select","selectAll","remove","idKeys","Array","from","aggs","aggregationFunction","viz","dev","container","showLegend","type","uniq","size","draw","onRender","renderingCompleted","templateUrl"],"mappings":"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","file":"heatmapControl.js","sourcesContent":["import './libs/d3/d3'; \r\nimport TimeSeries from 'app/core/time_series2';\r\nimport kbn from 'app/core/utils/kbn';\r\nimport {MetricsPanelCtrl} from 'app/plugins/sdk';\r\nimport {heatmapEditor, displayEditor, pluginName} from './properties';\r\nimport _ from 'lodash';\r\nimport moment from 'moment';\r\nimport './series_overrides_heatmap_ctrl';\r\nimport './css/heatmap.css!';\r\n\r\nconst panelOptions = {\r\n\taggregationFunctions: ['avg', 'min', 'max', 'total', 'current', 'count'],\r\n\ttreeMap:{\r\n \tmodes: ['squarify', 'slice', 'dice', 'slice-dice'],\r\n \taggregationFunctions: ['sum', 'min', 'max', 'extent', 'mean', 'median', 'quantile', 'variance', 'deviation'],\r\n \ttimestampFormats: ['YYYY-MM-DDTHH', 'YYYY-MM-DDTHH:mm', 'YYYY-MM-DDTHH:mm:ss', 'YYYY-MM-DDTHH:mm:ss.sssZ']\r\n\t}\r\n};\r\n\r\nconst panelDefaults = {\r\n\t// other style overrides\r\n seriesOverrides: [],\r\n\tthresholds: '0,10',\r\n\tcolors: ['rgba(50, 172, 45, 1)', 'rgba(241, 255, 0, 1)', 'rgba(245, 54, 54, 1)'],\r\n\tlegend: {\r\n\t\tshow: true,\r\n\t\tmin: true,\r\n\t\tmax: true,\r\n\t\tavg: true,\r\n\t\tcurrent: true,\r\n\t\ttotal: true\r\n\t},\r\n\tmaxDataPoints: 100,\r\n\tmappingType: 1,\r\n\tnullPointMode: 'connected',\r\n\tformat: 'none',\r\n valueMaps: [\r\n { value: 'null', op: '=', text: 'N/A' }\r\n ],\r\n treeMap: {\r\n \tmode: 'squarify',\r\n \tgroups: [{key:'server', value: '/^.*\\./g'}],\r\n \tcolorByFunction: 'max',\r\n \tsizeByFunction: 'constant',\r\n \tenableTimeBlocks: false,\r\n \tenableGrouping: true,\r\n \tdebug: false,\r\n \tdepth: 0,\r\n \tids: ['alias'],\r\n \tnodeSizeProperty: \"value\"\r\n }\r\n};\r\n\r\nclass HeatmapCtrl extends MetricsPanelCtrl {\r\n\tconstructor($scope, $injector, $sce) {\r\n\t\tsuper($scope, $injector);\r\n\t\t_.defaults(this.panel, panelDefaults);\r\n\t\t\r\n\t\tthis.options = panelOptions;\r\n\t\tthis.panel.chartId = 'chart_' + this.panel.id;\r\n\t\tthis.containerDivId = 'container_'+this.panel.chartId;\r\n\t\tthis.$sce = $sce;\r\n\t\tthis.events.on('init-edit-mode', this.onInitEditMode.bind(this));\r\n\t\tthis.events.on('data-received', this.onDataReceived.bind(this));\r\n\t\tthis.events.on('data-snapshot-load', this.onDataReceived.bind(this));\r\n\t\tthis.initializePanel();\r\n\t}\r\n\t\r\n\tinitializePanel(){\r\n\t\tvar d3plusPath = 'plugins/'+pluginName+'/libs/d3plus/d3plus.full.js';\r\n\t\tvar _this = this;\r\n\t\tvar meta = {};\r\n\t\tmeta[d3plusPath] = {\r\n\t\t\t format: 'global'\r\n\t };\r\n\t\t\r\n\t\tSystemJS.config({\r\n\t\t\t meta: meta\r\n\t\t\t});\r\n\r\n\t\tSystemJS.import(d3plusPath).then(function d3plusLoaded(){\r\n\t\t\tconsole.log('d3plus is loaded');\r\n\t\t\t_this.events.emit('data-received');\r\n\t\t});\r\n\t}\r\n\t\r\n\thandleError(err){\r\n\t\tthis.getPanelContainer().html('

Error:

' + err + '
');\r\n\t}\r\n\t\r\n\tonInitEditMode() {\r\n\t\tthis.addEditorTab('Heatmap', heatmapEditor, 2);\r\n\t\tthis.addEditorTab('Display', displayEditor, 3);\r\n\t}\r\n\t\r\n\tgetPanelContainer(){\r\n\t\treturn $(document.getElementById(this.containerDivId));\r\n\t}\r\n\t\r\n\tonDataReceived(dataList){\r\n\t\tconsole.info('received data');\r\n\t\tconsole.debug(dataList);\r\n\t\tif(undefined != dataList) {\r\n\t\t\tthis.series = dataList.map(this.seriesHandler.bind(this));\r\n\t\t\tconsole.info('mapped dataList to series');\r\n\t\t}\r\n\r\n\t\tvar preparedData = this.d3plusDataProcessor(this.series);\r\n\t\tthis.render(preparedData);\r\n\t}\r\n\t\r\n\tgetGroupKeys(){\r\n\t\treturn this.panel.treeMap.groups.map(function(group){\r\n\t\t\treturn group.key;\r\n\t\t});\r\n\t}\r\n\t\r\n\t/**\r\n\t * Prepare data for d3plus\r\n\t */\r\n\td3plusDataProcessor(dataArray){\r\n\t\tvar resultArray = [];\r\n\t\tvar hasGroups = (this.panel.treeMap.groups.length > 0)\r\n\t\t\r\n\t\tif(!hasGroups){\r\n\t\t\t// just add the original items since there are no groups\r\n\t\t\tfor (var dataIndex=0; dataIndex < dataArray.length; dataIndex++){\r\n\t\t\t\tvar newDataItem = Object.assign({}, dataArray[dataIndex], dataArray[dataIndex].stats);\r\n\t\t\t\tresultArray.push(newDataItem);\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\t// Process Groups\r\n\t\t\tvar groupArray = [];\r\n\t\t\tfor(var groupIndex=0; groupIndex 0){\r\n\t\t\t\t\t\tnewDataItem[key] = matches[0];\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tnewDataItem[key] = 'NA';\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tresultArray.push(newDataItem);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t\r\n\t\t// If we're using timeBlocks mode\r\n\t\t// replace the aggregated series with individual records\r\n\t\tif(this.panel.treeMap.enableTimeBlocks){\r\n\t\t\tconsole.info('creating timeblock records')\r\n\t\t\tvar timeBlockArray = [];\r\n\t\t\tfor (var dataIndex=0; dataIndex < resultArray.length; dataIndex++){\r\n\t\t\t\tconsole.debug('dataIndex:'+dataIndex+', alias:'+resultArray[dataIndex].alias);\r\n\t\t\t\tvar dataSeries = resultArray[dataIndex];\r\n\t\t\t\tfor(var dataPointIndex=0; dataPointIndex < dataSeries.flotpairs.length; dataPointIndex++){\r\n\t\t\t\t\tvar dataSeriesCopy = Object.assign({}, dataSeries);\r\n\t\t\t\t\tdelete dataSeriesCopy.datapoints;\r\n\t\t\t\t\tdelete dataSeriesCopy.flotpairs;\r\n\t\t\t\t\tdataSeriesCopy.count = 1;\r\n\t\t\t\t\tdataSeriesCopy.timestamp = dataSeries.flotpairs[dataPointIndex][0];\r\n\t\t\t\t\tdataSeriesCopy.value = dataSeries.flotpairs[dataPointIndex][1];\r\n\t\t\t\t\ttimeBlockArray.push(dataSeriesCopy);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tresultArray = timeBlockArray;\r\n\t\t} \r\n\t\t\r\n\t\treturn resultArray;\r\n\t}\r\n\t\r\n\t/**\r\n\t * Series Handler\r\n\t */\r\n\tseriesHandler(seriesData) {\r\n\t\tvar series = new TimeSeries({\r\n\t\t\tdatapoints: seriesData.datapoints,\r\n\t\t\talias: seriesData.target.replace(/\"|,|;|=|:|{|}/g, '_')\r\n\t\t});\r\n\t series.flotpairs = series.getFlotPairs(this.panel.nullPointMode);\r\n\t return series;\r\n\t} // End seriesHandler()\r\n\t\r\n\taddSeriesOverride(override) {\r\n\t\tthis.panel.seriesOverrides.push(override || {});\r\n\t}\r\n\t\r\n\taddTreeMapGroup(group) {\r\n\t\tthis.panel.treeMap.groups.push(group || {});\r\n\t}\r\n\r\n\tremoveSeriesOverride(override) {\r\n\t\tthis.panel.seriesOverrides = _.without(this.panel.seriesOverrides, override);\r\n\t this.render();\r\n\t}\r\n\t\r\n\tremoveTreeMapGroup(group) {\r\n\t\tthis.panel.treeMap.groups = _.without(this.panel.treeMap.groups, group);\r\n\t this.render();\r\n\t}\r\n\t\r\n\tupdateThresholds(){\r\n\t\tvar thresholdCount = this.panel.thresholds.length;\r\n\t\tvar colorCount = this.panel.colors.length;\r\n\t\tthis.refresh();\r\n\t}\r\n\t\r\n\tchangeColor(colorIndex, color){\r\n\t\tthis.panel.colors[colorIndex] = color;\r\n\t}\r\n\t\r\n\tremoveColor(colorIndex){\r\n\t\tthis.panel.colors.splice(colorIndex,1);\r\n\t}\r\n\t\r\n\taddColor(){\r\n\t\tthis.panel.colors.push('rgba(255, 255, 255, 1)');\r\n\t}\r\n\t\r\n\tgetGradientForValue(data, value){\r\n\t\tvar min = Math.min.apply(Math, data.thresholds);\r\n\t\tvar max = Math.max.apply(Math, data.thresholds);\r\n\t\tvar absoluteDistance = max - min;\r\n\t\tvar valueDistanceFromMin = value - min;\r\n\t\tvar xPercent = valueDistanceFromMin/absoluteDistance;\r\n\t\t// Get the smaller number to clamp at 0.99 max\r\n\t\txPercent = Math.min(0.99, xPercent);\r\n\t\t// Get the larger number to clamp at 0.01 min\r\n\t\txPercent = Math.max(0.01, xPercent);\r\n\t\t\r\n\t\treturn getColorByXPercentage(this.canvas, xPercent);\r\n\t}\r\n\t\r\n\tapplyOverrides(seriesItemAlias){\r\n\t\tvar seriesItem = {}, colorData = {}, overrides = {};\r\n\t\tconsole.info('applying overrides for seriesItem');\r\n\t\tconsole.debug(seriesItemAlias);\r\n\t\tconsole.debug(this.panel.seriesOverrides);\r\n\t\tfor(var i=0; i<=this.panel.seriesOverrides.length; i++){\r\n\t\t\tconsole.debug('comparing:');\r\n\t\t\tconsole.debug(this.panel.seriesOverrides[i]);\r\n\t\t\tif (this.panel.seriesOverrides[i] && this.panel.seriesOverrides[i].alias == seriesItemAlias){\r\n\t\t\t\toverrides = this.panel.seriesOverrides[i];\r\n\t\t\t}\r\n\t\t}\r\n\t\tcolorData.thresholds = (overrides.thresholds || this.panel.thresholds).split(',').map(function(strVale) {\r\n\t\t\treturn Number(strVale.trim());\r\n\t\t});\r\n\t\tcolorData.colorMap = this.panel.colors;\r\n\t\tseriesItem.colorData = colorData;\r\n\t\t\r\n\t\tseriesItem.valueName = overrides.valueName || this.panel.valueName;\r\n\t\t\r\n\t\treturn seriesItem;\r\n\t}\r\n\t\r\n\tinvertColorOrder() {\r\n\t this.panel.colors.reverse();\r\n\t this.refresh();\r\n\t}\r\n\t\r\n\taddTreeMapId(){\r\n\t\tthis.panel.treeMap.ids.push('');\r\n\t\tthis.refresh();\r\n\t}\r\n\t\r\n\tremoveTreeMapId(pos){\r\n\t\tthis.panel.treeMap.ids.splice(pos,1);\r\n\t\tthis.refresh();\r\n\t}\r\n\t\r\n\tchangeTreeMapId(idString, pos){\r\n\t\tthis.panel.treeMap.ids[pos] = idString;\r\n\t}\r\n\t\r\n\t// #############################################\r\n\t// link \r\n\t// #############################################\r\n\r\n\tlink(scope, elem, attrs, ctrl) {\r\n\t\tvar chartElement = elem.find('.heatmap');\r\n\t\tchartElement.append('
');\r\n\t var chartContainer = $(document.getElementById(ctrl.containerDivId));\r\n \tconsole.debug('found chartContainer');\r\n \tconsole.debug(chartContainer);\r\n \telem.css('height', ctrl.height + 'px');\r\n \t\r\n \tvar canvas = elem.find('.canvas')[0];\r\n\t ctrl.canvas = canvas;\r\n\t var gradientValueMax = elem.find('.gradient-value-max')[0];\r\n\t var gradientValueMin = elem.find('.gradient-value-min')[0];\r\n\t \r\n\r\n \tvar visFormat =\r\n\t\t{ \r\n\t\t\t\"text\" : function(text, opts) {\r\n\t\t\t\tif(opts.key == 'timestamp'){\r\n\t\t\t\t\tvar timestamp = moment(Number(text));\r\n\t\t\t\t\treturn timestamp.format(ctrl.panel.treeMap.timestampFormat);\r\n\t\t\t\t} \r\n\t\t\t\telse if(ctrl.getGroupKeys().indexOf(opts.key)>-1) {\r\n\t\t\t\t\treturn text;\r\n\t\t\t\t}\r\n\t\t\t\telse{\r\n\t\t\t\t\treturn d3plus.string.title(text, opts);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t};\r\n \t\r\n\r\n \tfunction render(data){\r\n \t\tupdateSize();\r\n \t\tupdateCanvasStyle();\r\n \t\tupdateChart(data);\r\n \t}\r\n \t\r\n \tfunction updateCanvasStyle(){\r\n\t \tcanvas.width = Math.max(chartElement[0].clientWidth, 100);\r\n\t\t\tvar canvasContext = canvas.getContext(\"2d\");\r\n\t\t\tcanvasContext.clearRect(0, 0, canvas.width, canvas.height);\r\n\t\t\t\r\n\t\t\tvar grd = canvasContext.createLinearGradient(0, 0, canvas.width, 0);\r\n\t\t\tvar colorWidth = 1 / Math.max(ctrl.panel.colors.length, 1);\r\n\t\t\tfor(var i=0; i 2 |
Development
3 |
4 | 5 | 6 |
7 | 8 |
9 |
Group Options
10 | 11 |
12 | 13 | 14 |
15 |
16 | 17 | 18 |
19 |
20 | 21 | 24 |
25 |
26 | 27 | 28 | 29 | 34 |
35 | 36 |
37 | 38 |
39 | 40 | 43 |
44 |
45 | 48 |
49 |
50 |
51 | 52 |
53 |
54 |
55 |
Grouping Regex match example: /server[0-3]/i
56 | 57 |
58 |
59 | 60 |
61 |
62 | 63 |
64 | 65 |
66 | 67 |
68 |
69 | 70 |
71 | 72 |
73 | 76 |
77 |
78 |
79 | 80 | 83 |
84 |
-------------------------------------------------------------------------------- /dist/img/heatmap.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savantly-net/grafana-heatmap/d2244abb0e46f5b31a5818a3bec5c1101075e1c8/dist/img/heatmap.PNG -------------------------------------------------------------------------------- /dist/img/icn-heatmap-panel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /dist/img/timestamp_data.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savantly-net/grafana-heatmap/d2244abb0e46f5b31a5818a3bec5c1101075e1c8/dist/img/timestamp_data.PNG -------------------------------------------------------------------------------- /dist/module.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 6 |
7 | 8 |
9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
minmaxavgtotalcurrent
{{series.alias}}{{series.stats.min}}{{series.stats.max}}{{series.stats.avg}}{{series.stats.total}}{{series.stats.current}}
30 |
31 |
-------------------------------------------------------------------------------- /dist/module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | System.register(['./properties', './heatmapControl'], function (_export, _context) { 4 | "use strict"; 5 | 6 | var pluginName, HeatmapCtrl; 7 | return { 8 | setters: [function (_properties) { 9 | pluginName = _properties.pluginName; 10 | }, function (_heatmapControl) { 11 | HeatmapCtrl = _heatmapControl.HeatmapCtrl; 12 | }], 13 | execute: function () { 14 | _export('PanelCtrl', HeatmapCtrl); 15 | } 16 | }; 17 | }); 18 | //# sourceMappingURL=module.js.map 19 | -------------------------------------------------------------------------------- /dist/module.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/module.js"],"names":["pluginName","HeatmapCtrl"],"mappings":";;;;;;;;AAAQA,gB,eAAAA,U;;AACAC,iB,mBAAAA,W;;;2BASPA,W","file":"module.js","sourcesContent":["import {pluginName} from './properties';\r\nimport {HeatmapCtrl} from './heatmapControl';\r\n/*import {loadPluginCss} from 'app/plugins/sdk';\r\n\r\nloadPluginCss({\r\n dark: 'plugins/'+pluginName+'/libs/mermaid/dist/mermaid.css',\r\n light: 'plugins/'+pluginName+'/libs/mermaid/dist/mermaid.css'\r\n});*/\r\n\r\nexport {\r\n\tHeatmapCtrl as PanelCtrl\r\n}; "]} -------------------------------------------------------------------------------- /dist/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "panel", 3 | "name": "Heatmap", 4 | "id": "savantly-heatmap-panel", 5 | 6 | "info": { 7 | "description": "Heatmap panel for grafana", 8 | "author": { 9 | "name": "Jeremy Branham", 10 | "url": "https://github.com/jdbranham" 11 | }, 12 | "keywords": ["heatmap", "panel"], 13 | "links": [ 14 | {"name": "Project site", "url": "https://github.com/savantly-net/grafana-heatmap"}, 15 | {"name": "Apache License", "url": "https://github.com/savantly-net/grafana-heatmap/blob/master/LICENSE"} 16 | ], 17 | "version": "0.2.0", 18 | "updated": "2016-10-16", 19 | "logos": { 20 | "small": "img/icn-heatmap-panel.svg", 21 | "large": "img/icn-heatmap-panel.svg" 22 | } 23 | }, 24 | 25 | "dependencies": { 26 | "grafanaVersion": "3.x.x", 27 | "plugins": [ ] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /dist/properties.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | System.register([], function (_export, _context) { 4 | "use strict"; 5 | 6 | var pluginName, heatmapEditor, displayEditor; 7 | return { 8 | setters: [], 9 | execute: function () { 10 | _export('pluginName', pluginName = 'savantly-heatmap-panel'); 11 | 12 | _export('heatmapEditor', heatmapEditor = 'public/plugins/' + pluginName + '/heatmapEditor.html'); 13 | 14 | _export('displayEditor', displayEditor = 'public/plugins/' + pluginName + '/displayEditor.html'); 15 | 16 | _export('pluginName', pluginName); 17 | 18 | _export('heatmapEditor', heatmapEditor); 19 | 20 | _export('displayEditor', displayEditor); 21 | } 22 | }; 23 | }); 24 | //# sourceMappingURL=properties.js.map 25 | -------------------------------------------------------------------------------- /dist/properties.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/properties.js"],"names":["pluginName","heatmapEditor","displayEditor"],"mappings":";;;;;;;;;yBACIA,U,GAAa,wB;;4BAChBC,a,GAAgB,oBAAmBD,UAAnB,GAA+B,qB;;4BAC/CE,a,GAAgB,oBAAmBF,UAAnB,GAA+B,qB;;yBAG/CA,U;;4BACAC,a;;4BACAC,a","file":"properties.js","sourcesContent":["\r\nvar pluginName = 'savantly-heatmap-panel',\r\n\theatmapEditor = 'public/plugins/'+ pluginName +'/heatmapEditor.html',\r\n\tdisplayEditor = 'public/plugins/'+ pluginName +'/displayEditor.html';\r\n\r\nexport {\r\n\tpluginName,\r\n\theatmapEditor,\r\n\tdisplayEditor\r\n}"]} -------------------------------------------------------------------------------- /dist/series_overrides_heatmap_ctrl.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | System.register([], function (_export, _context) { 4 | "use strict"; 5 | 6 | return { 7 | setters: [], 8 | execute: function () { 9 | angular.module('grafana.controllers').controller('SeriesOverridesHeatmapCtrl', ['$scope', '$element', 'popoverSrv', function ($scope, $element, popoverSrv) { 10 | $scope.overrideMenu = []; 11 | $scope.currentOverrides = []; 12 | $scope.override = $scope.override || {}; 13 | 14 | $scope.addOverrideOption = function (name, propertyName, values) { 15 | var option = {}; 16 | option.text = name; 17 | option.propertyName = propertyName; 18 | option.index = $scope.overrideMenu.length; 19 | option.values = values; 20 | 21 | option.submenu = _.map(values, function (value) { 22 | return { text: String(value), value: value }; 23 | }); 24 | 25 | $scope.overrideMenu.push(option); 26 | }; 27 | 28 | $scope.setOverride = function (item, subItem) { 29 | // handle color overrides 30 | if (item.propertyName === 'color') { 31 | $scope.openColorSelector(); 32 | return; 33 | } 34 | 35 | $scope.override[item.propertyName] = subItem.value; 36 | $scope.updateCurrentOverrides(); 37 | $scope.ctrl.render(); 38 | }; 39 | 40 | $scope.colorSelected = function (color) { 41 | $scope.override['color'] = color; 42 | $scope.updateCurrentOverrides(); 43 | $scope.ctrl.render(); 44 | }; 45 | 46 | $scope.thresholdsChanged = function (thresholds) { 47 | $scope.override['thresholds'] = thresholds.value; 48 | $scope.updateCurrentOverrides(); 49 | $scope.ctrl.render(); 50 | }; 51 | 52 | $scope.openColorSelector = function () { 53 | popoverSrv.show({ 54 | element: $element.find(".dropdown")[0], 55 | position: 'top center', 56 | openOn: 'click', 57 | template: '', 58 | model: { 59 | autoClose: true, 60 | colorSelected: $scope.colorSelected 61 | }, 62 | onClose: function onClose() { 63 | $scope.ctrl.render(); 64 | } 65 | }); 66 | }; 67 | 68 | $scope.removeOverride = function (option) { 69 | delete $scope.override[option.propertyName]; 70 | $scope.updateCurrentOverrides(); 71 | $scope.ctrl.refresh(); 72 | }; 73 | 74 | $scope.getSeriesNames = function () { 75 | return _.map($scope.ctrl.seriesList, function (series) { 76 | return series.alias; 77 | }); 78 | }; 79 | 80 | $scope.updateCurrentOverrides = function () { 81 | $scope.currentOverrides = []; 82 | _.each($scope.overrideMenu, function (option) { 83 | var value = $scope.override[option.propertyName]; 84 | if (_.isUndefined(value)) { 85 | return; 86 | } 87 | $scope.currentOverrides.push({ 88 | name: option.text, 89 | propertyName: option.propertyName, 90 | value: String(value) 91 | }); 92 | }); 93 | }; 94 | 95 | //$scope.addOverrideOption('Color', 'color', ['change']); 96 | $scope.addOverrideOption('Thresholds', 'thresholds', ['custom']); 97 | $scope.addOverrideOption('Value', 'valueName', ['avg', 'min', 'max', 'total', 'current']); 98 | $scope.updateCurrentOverrides(); 99 | }]); 100 | } 101 | }; 102 | }); 103 | //# sourceMappingURL=series_overrides_heatmap_ctrl.js.map 104 | -------------------------------------------------------------------------------- /dist/series_overrides_heatmap_ctrl.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/series_overrides_heatmap_ctrl.js"],"names":["angular","module","controller","$scope","$element","popoverSrv","overrideMenu","currentOverrides","override","addOverrideOption","name","propertyName","values","option","text","index","length","submenu","_","map","value","String","push","setOverride","item","subItem","openColorSelector","updateCurrentOverrides","ctrl","render","colorSelected","color","thresholdsChanged","thresholds","show","element","find","position","openOn","template","model","autoClose","onClose","removeOverride","refresh","getSeriesNames","seriesList","series","alias","each","isUndefined"],"mappings":"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","file":"series_overrides_heatmap_ctrl.js","sourcesContent":[" angular.module('grafana.controllers').controller('SeriesOverridesHeatmapCtrl', ['$scope', '$element', 'popoverSrv', function($scope, $element, popoverSrv) {\n $scope.overrideMenu = [];\n $scope.currentOverrides = [];\n $scope.override = $scope.override || {};\n\n $scope.addOverrideOption = function(name, propertyName, values) {\n var option = {};\n option.text = name;\n option.propertyName = propertyName;\n option.index = $scope.overrideMenu.length;\n option.values = values;\n\n option.submenu = _.map(values, function(value) {\n return { text: String(value), value: value };\n });\n\n $scope.overrideMenu.push(option);\n };\n\n $scope.setOverride = function(item, subItem) {\n // handle color overrides\n if (item.propertyName === 'color') {\n $scope.openColorSelector();\n return;\n }\n\n $scope.override[item.propertyName] = subItem.value;\n $scope.updateCurrentOverrides();\n $scope.ctrl.render();\n };\n\n $scope.colorSelected = function(color) {\n $scope.override['color'] = color;\n $scope.updateCurrentOverrides();\n $scope.ctrl.render();\n };\n \n $scope.thresholdsChanged = function(thresholds){\n \t$scope.override['thresholds'] = thresholds.value;\n \t$scope.updateCurrentOverrides();\n \t$scope.ctrl.render();\n }\n\n $scope.openColorSelector = function() {\n popoverSrv.show({\n element: $element.find(\".dropdown\")[0],\n position: 'top center',\n openOn: 'click',\n template: '',\n model: {\n autoClose: true,\n colorSelected: $scope.colorSelected,\n },\n onClose: function() {\n $scope.ctrl.render();\n }\n });\n };\n \n $scope.removeOverride = function(option) {\n delete $scope.override[option.propertyName];\n $scope.updateCurrentOverrides();\n $scope.ctrl.refresh();\n };\n\n $scope.getSeriesNames = function() {\n return _.map($scope.ctrl.seriesList, function(series) {\n return series.alias;\n });\n };\n\n $scope.updateCurrentOverrides = function() {\n $scope.currentOverrides = [];\n _.each($scope.overrideMenu, function(option) {\n var value = $scope.override[option.propertyName];\n if (_.isUndefined(value)) { return; }\n $scope.currentOverrides.push({\n name: option.text,\n propertyName: option.propertyName,\n value: String(value)\n });\n });\n };\n\n //$scope.addOverrideOption('Color', 'color', ['change']);\n $scope.addOverrideOption('Thresholds', 'thresholds', ['custom']);\n $scope.addOverrideOption('Value', 'valueName', ['avg', 'min', 'max', 'total', 'current']);\n $scope.updateCurrentOverrides();\n }]);\n"]} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grafana-heatmap", 3 | "version": "0.2.0", 4 | "description": "A heatmap panel plugin for Grafana", 5 | "main": "src/module.js", 6 | "scripts": { 7 | "lint": "eslint --color .", 8 | "test": "grunt" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/savantly-net/grafana-heatmap.git" 13 | }, 14 | "keywords": [ 15 | "plugin", 16 | "heatmap", 17 | "panel" 18 | ], 19 | "author": "Jeremy Branham", 20 | "license": "Apache-2.0", 21 | "bugs": { 22 | "url": "https://github.com/savantly-net/grafana-heatmap/issues" 23 | }, 24 | "homepage": "https://github.com/savantly-net/grafana-heatmap#readme", 25 | "devDependencies": { 26 | "babel": "~6.5.1", 27 | "babel-eslint": "^6.0.0", 28 | "babel-plugin-transform-es2015-for-of": "^6.8.0", 29 | "babel-plugin-transform-es2015-modules-systemjs": "^6.5.0", 30 | "babel-preset-es2015": "^6.5.0", 31 | "eslint": "^2.5.1", 32 | "eslint-config-airbnb": "^6.2.0", 33 | "eslint-plugin-import": "^1.4.0", 34 | "grunt": "~0.4.5", 35 | "grunt-babel": "~6.0.0", 36 | "grunt-contrib-clean": "~0.6.0", 37 | "grunt-contrib-copy": "~0.8.2", 38 | "grunt-contrib-uglify": "~0.11.0", 39 | "grunt-contrib-watch": "^0.6.1", 40 | "grunt-execute": "~0.2.2", 41 | "grunt-sass": "^1.2.1", 42 | "grunt-systemjs-builder": "^0.2.5", 43 | "load-grunt-tasks": "~3.2.0" 44 | }, 45 | "dependencies": { 46 | "lodash": "~4.0.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/css/heatmap.scss: -------------------------------------------------------------------------------- 1 | $fill-default: #cde498; 2 | $path-default: green; 3 | $stroke-default: #13540c; 4 | $text-color: #333; 5 | $text-fill: #333; 6 | $label-bg-color: #e8e8e8; 7 | $cluster-fill: #cdffb2; 8 | $cluster-stroke: #6eaa49; 9 | $message-stroke: #777; 10 | $message-fill: #D8D9DA; 11 | $task-fill: #487e3a; 12 | 13 | .diagram { 14 | overflow: auto; 15 | } 16 | 17 | .gradient-values-container .gradient-value-max { 18 | float: right; 19 | } 20 | 21 | .diagram g.label div { 22 | color: $text-color; 23 | } 24 | 25 | .diagram div p { 26 | text-align: center; 27 | } 28 | 29 | .diagram svg { 30 | margin: auto; 31 | display: block; 32 | } 33 | 34 | g.node > * { 35 | fill: white; 36 | } 37 | 38 | svg > g { 39 | overflow: visible; 40 | display: block; 41 | } 42 | 43 | .diagram-value { 44 | background-color: white; 45 | padding: 3px; 46 | margin-top: 8px; 47 | } 48 | 49 | /** LEGEND **/ 50 | div.graph-legend-wrapper .graph-legend-table { 51 | display: inline-table; 52 | } 53 | 54 | 55 | 56 | /* Flowchart variables */ 57 | /* Sequence Diagram variables */ 58 | /* Gantt chart variables */ 59 | .mermaid .label { 60 | font-family: 'trebuchet ms', verdana, arial; 61 | color: $text-color; 62 | } 63 | .node rect, 64 | .node circle, 65 | .node ellipse, 66 | .node polygon { 67 | fill: $fill-default; 68 | stroke: $stroke-default; 69 | stroke-width: 1px; 70 | } 71 | .edgePath .path { 72 | stroke: green; 73 | stroke-width: 1.5px; 74 | } 75 | .edgeLabel { 76 | background-color: $label-bg-color; 77 | } 78 | .cluster rect { 79 | fill: $cluster-fill !important; 80 | rx: 4 !important; 81 | stroke: $cluster-stroke !important; 82 | stroke-width: 1px !important; 83 | } 84 | .cluster text { 85 | fill: $text-color; 86 | } 87 | .actor { 88 | stroke: $stroke-default; 89 | fill: $fill-default; 90 | } 91 | text.actor { 92 | fill: black; 93 | stroke: none; 94 | } 95 | .actor-line { 96 | stroke: grey; 97 | } 98 | .messageLine0 { 99 | stroke-width: 1.5; 100 | stroke-dasharray: "2 2"; 101 | marker-end: "url(#arrowhead)"; 102 | stroke: $path-default; 103 | } 104 | .messageLine1 { 105 | stroke-width: 1.5; 106 | stroke-dasharray: "2 2"; 107 | stroke: $path-default; 108 | } 109 | #arrowhead { 110 | fill: $path-default; 111 | } 112 | #crosshead path { 113 | fill: $path-default !important; 114 | stroke: $path-default !important; 115 | } 116 | .messageText { 117 | fill: $message-fill; 118 | stroke: none; 119 | } 120 | .labelBox { 121 | stroke: #326932; 122 | fill: $fill-default; 123 | } 124 | .labelText { 125 | fill: black; 126 | stroke: none; 127 | } 128 | .loopText { 129 | fill: $message-fill; 130 | stroke: none; 131 | } 132 | .loopLine { 133 | stroke-width: 2; 134 | stroke-dasharray: "2 2"; 135 | marker-end: "url(#arrowhead)"; 136 | stroke: #326932; 137 | } 138 | .note { 139 | stroke: $cluster-stroke; 140 | fill: #fff5ad; 141 | } 142 | .noteText { 143 | fill: black; 144 | stroke: none; 145 | font-family: 'trebuchet ms', verdana, arial; 146 | font-size: 14px; 147 | } 148 | /** Section styling */ 149 | .diagram .section { 150 | stroke: none; 151 | opacity: 0.2; 152 | } 153 | .section0 { 154 | fill: $cluster-stroke; 155 | } 156 | .section2 { 157 | fill: $cluster-stroke; 158 | } 159 | .section1, 160 | .section3 { 161 | fill: white; 162 | opacity: 0.2; 163 | } 164 | .sectionTitle0 { 165 | fill: $text-color; 166 | } 167 | .sectionTitle1 { 168 | fill: $text-color; 169 | } 170 | .sectionTitle2 { 171 | fill: $text-color; 172 | } 173 | .sectionTitle3 { 174 | fill: $text-color; 175 | } 176 | .sectionTitle { 177 | text-anchor: start; 178 | font-size: 11px; 179 | text-height: 14px; 180 | } 181 | /* Grid and axis */ 182 | .grid .tick { 183 | stroke: lightgrey; 184 | opacity: 0.3; 185 | shape-rendering: crispEdges; 186 | } 187 | .grid path { 188 | stroke-width: 0; 189 | } 190 | /* Today line */ 191 | .today { 192 | fill: none; 193 | stroke: red; 194 | stroke-width: 2px; 195 | } 196 | /* Task styling */ 197 | /* Default task */ 198 | .task { 199 | stroke-width: 2; 200 | } 201 | .taskText { 202 | text-anchor: middle; 203 | font-size: 11px; 204 | } 205 | .taskTextOutsideRight { 206 | fill: black; 207 | text-anchor: start; 208 | font-size: 11px; 209 | } 210 | .taskTextOutsideLeft { 211 | fill: black; 212 | text-anchor: end; 213 | font-size: 11px; 214 | } 215 | /* Specific task settings for the sections*/ 216 | .taskText0, 217 | .taskText1, 218 | .taskText2, 219 | .taskText3 { 220 | fill: white; 221 | } 222 | .task0, 223 | .task1, 224 | .task2, 225 | .task3 { 226 | fill: $task-fill; 227 | stroke: $stroke-default; 228 | } 229 | .taskTextOutside0, 230 | .taskTextOutside2 { 231 | fill: black; 232 | } 233 | .taskTextOutside1, 234 | .taskTextOutside3 { 235 | fill: black; 236 | } 237 | /* Active task */ 238 | .active0, 239 | .active1, 240 | .active2, 241 | .active3 { 242 | fill: $fill-default; 243 | stroke: $stroke-default; 244 | } 245 | .activeText0, 246 | .activeText1, 247 | .activeText2, 248 | .activeText3 { 249 | fill: black !important; 250 | } 251 | /* Completed task */ 252 | .done0, 253 | .done1, 254 | .done2, 255 | .done3 { 256 | stroke: grey; 257 | fill: lightgrey; 258 | stroke-width: 2; 259 | } 260 | .doneText0, 261 | .doneText1, 262 | .doneText2, 263 | .doneText3 { 264 | fill: black !important; 265 | } 266 | /* Tasks on the critical line */ 267 | .crit0, 268 | .crit1, 269 | .crit2, 270 | .crit3 { 271 | stroke: #ff8888; 272 | fill: red; 273 | stroke-width: 2; 274 | } 275 | .activeCrit0, 276 | .activeCrit1, 277 | .activeCrit2, 278 | .activeCrit3 { 279 | stroke: #ff8888; 280 | fill: $fill-default; 281 | stroke-width: 2; 282 | } 283 | .doneCrit0, 284 | .doneCrit1, 285 | .doneCrit2, 286 | .doneCrit3 { 287 | stroke: #ff8888; 288 | fill: lightgrey; 289 | stroke-width: 2; 290 | cursor: pointer; 291 | shape-rendering: crispEdges; 292 | } 293 | .doneCritText0, 294 | .doneCritText1, 295 | .doneCritText2, 296 | .doneCritText3 { 297 | fill: black !important; 298 | } 299 | .activeCritText0, 300 | .activeCritText1, 301 | .activeCritText2, 302 | .activeCritText3 { 303 | fill: black !important; 304 | } 305 | .titleText { 306 | text-anchor: middle; 307 | font-size: 18px; 308 | fill: black; 309 | } 310 | /* 311 | 312 | 313 | */ 314 | g.classGroup text { 315 | fill: $stroke-default; 316 | stroke: none; 317 | font-family: 'trebuchet ms', verdana, arial; 318 | font-size: 14px; 319 | } 320 | g.classGroup rect { 321 | fill: $fill-default; 322 | stroke: $stroke-default; 323 | } 324 | g.classGroup line { 325 | stroke: $stroke-default; 326 | stroke-width: 1; 327 | } 328 | svg .classLabel .box { 329 | stroke: none; 330 | stroke-width: 0; 331 | fill: $fill-default; 332 | opacity: 0.5; 333 | } 334 | svg .classLabel .label { 335 | fill: $stroke-default; 336 | } 337 | .relation { 338 | stroke: $stroke-default; 339 | stroke-width: 1; 340 | fill: none; 341 | } 342 | .composition { 343 | fill: $stroke-default; 344 | stroke: $stroke-default; 345 | stroke-width: 1; 346 | } 347 | #compositionStart { 348 | fill: $stroke-default; 349 | stroke: $stroke-default; 350 | stroke-width: 1; 351 | } 352 | #compositionEnd { 353 | fill: $stroke-default; 354 | stroke: $stroke-default; 355 | stroke-width: 1; 356 | } 357 | .aggregation { 358 | fill: $fill-default; 359 | stroke: $stroke-default; 360 | stroke-width: 1; 361 | } 362 | #aggregationStart { 363 | fill: $fill-default; 364 | stroke: $stroke-default; 365 | stroke-width: 1; 366 | } 367 | #aggregationEnd { 368 | fill: $fill-default; 369 | stroke: $stroke-default; 370 | stroke-width: 1; 371 | } 372 | #dependencyStart { 373 | fill: $stroke-default; 374 | stroke: $stroke-default; 375 | stroke-width: 1; 376 | } 377 | #dependencyEnd { 378 | fill: $stroke-default; 379 | stroke: $stroke-default; 380 | stroke-width: 1; 381 | } 382 | #extensionStart { 383 | fill: $stroke-default; 384 | stroke: $stroke-default; 385 | stroke-width: 1; 386 | } 387 | #extensionEnd { 388 | fill: $stroke-default; 389 | stroke: $stroke-default; 390 | stroke-width: 1; 391 | } 392 | .node text { 393 | font-family: 'trebuchet ms', verdana, arial; 394 | font-size: 14px; 395 | } 396 | div.mermaidTooltip { 397 | position: absolute; 398 | text-align: center; 399 | max-width: 200px; 400 | padding: 2px; 401 | font-family: 'trebuchet ms', verdana, arial; 402 | font-size: 12px; 403 | background: $cluster-fill; 404 | border: 1px solid $cluster-stroke; 405 | border-radius: 2px; 406 | pointer-events: none; 407 | z-index: 100; 408 | } 409 | -------------------------------------------------------------------------------- /src/displayEditor.html: -------------------------------------------------------------------------------- 1 |
2 |
Legend
3 |
4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 | 15 |
16 |
17 | 18 |
19 |
Options
20 |
21 | 22 | 25 |
26 | 34 |
35 | 38 | 42 |
43 |
44 | 45 |
46 | 50 |
51 | 52 | 53 |   54 | 55 | 56 | 57 | 58 | Invert 59 | 60 | 61 |
62 |
63 | 64 | 65 |
66 |
67 | 68 | 72 |
73 |
74 | 75 |
76 |
77 |
78 |
Series specific overrides Regex match example: /server[0-3]/i
79 |
80 |
81 | 82 |
83 |
84 | 85 |
86 |
87 | 99 |
100 | 101 |
102 | 103 | 104 |
105 | 106 |
107 |
108 |
109 | 110 |
111 | 114 |
115 |
116 |
117 | 118 | 121 |
122 |
-------------------------------------------------------------------------------- /src/heatmapControl.js: -------------------------------------------------------------------------------- 1 | import './libs/d3/d3'; 2 | import TimeSeries from 'app/core/time_series2'; 3 | import kbn from 'app/core/utils/kbn'; 4 | import {MetricsPanelCtrl} from 'app/plugins/sdk'; 5 | import {heatmapEditor, displayEditor, pluginName} from './properties'; 6 | import _ from 'lodash'; 7 | import moment from 'moment'; 8 | import './series_overrides_heatmap_ctrl'; 9 | import './css/heatmap.css!'; 10 | 11 | const panelOptions = { 12 | aggregationFunctions: ['avg', 'min', 'max', 'total', 'current', 'count'], 13 | treeMap:{ 14 | modes: ['squarify', 'slice', 'dice', 'slice-dice'], 15 | aggregationFunctions: ['sum', 'min', 'max', 'extent', 'mean', 'median', 'quantile', 'variance', 'deviation'], 16 | timestampFormats: ['YYYY-MM-DDTHH', 'YYYY-MM-DDTHH:mm', 'YYYY-MM-DDTHH:mm:ss', 'YYYY-MM-DDTHH:mm:ss.sssZ'] 17 | } 18 | }; 19 | 20 | const panelDefaults = { 21 | // other style overrides 22 | seriesOverrides: [], 23 | thresholds: '0,10', 24 | colors: ['rgba(50, 172, 45, 1)', 'rgba(241, 255, 0, 1)', 'rgba(245, 54, 54, 1)'], 25 | legend: { 26 | show: true, 27 | min: true, 28 | max: true, 29 | avg: true, 30 | current: true, 31 | total: true 32 | }, 33 | maxDataPoints: 100, 34 | mappingType: 1, 35 | nullPointMode: 'connected', 36 | format: 'none', 37 | valueMaps: [ 38 | { value: 'null', op: '=', text: 'N/A' } 39 | ], 40 | treeMap: { 41 | mode: 'squarify', 42 | groups: [{key:'server', value: '/^.*\./g'}], 43 | colorByFunction: 'max', 44 | sizeByFunction: 'constant', 45 | enableTimeBlocks: false, 46 | enableGrouping: true, 47 | debug: false, 48 | depth: 0, 49 | ids: ['alias'], 50 | nodeSizeProperty: "value" 51 | } 52 | }; 53 | 54 | class HeatmapCtrl extends MetricsPanelCtrl { 55 | constructor($scope, $injector, $sce) { 56 | super($scope, $injector); 57 | _.defaults(this.panel, panelDefaults); 58 | 59 | this.options = panelOptions; 60 | this.panel.chartId = 'chart_' + this.panel.id; 61 | this.containerDivId = 'container_'+this.panel.chartId; 62 | this.$sce = $sce; 63 | this.events.on('init-edit-mode', this.onInitEditMode.bind(this)); 64 | this.events.on('data-received', this.onDataReceived.bind(this)); 65 | this.events.on('data-snapshot-load', this.onDataReceived.bind(this)); 66 | this.initializePanel(); 67 | } 68 | 69 | initializePanel(){ 70 | var d3plusPath = 'plugins/'+pluginName+'/libs/d3plus/d3plus.full.js'; 71 | var _this = this; 72 | var meta = {}; 73 | meta[d3plusPath] = { 74 | format: 'global' 75 | }; 76 | 77 | SystemJS.config({ 78 | meta: meta 79 | }); 80 | 81 | SystemJS.import(d3plusPath).then(function d3plusLoaded(){ 82 | console.log('d3plus is loaded'); 83 | _this.events.emit('data-received'); 84 | }); 85 | } 86 | 87 | handleError(err){ 88 | this.getPanelContainer().html('

Error:

' + err + '
'); 89 | } 90 | 91 | onInitEditMode() { 92 | this.addEditorTab('Heatmap', heatmapEditor, 2); 93 | this.addEditorTab('Display', displayEditor, 3); 94 | } 95 | 96 | getPanelContainer(){ 97 | return $(document.getElementById(this.containerDivId)); 98 | } 99 | 100 | onDataReceived(dataList){ 101 | console.info('received data'); 102 | console.debug(dataList); 103 | if(undefined != dataList) { 104 | this.series = dataList.map(this.seriesHandler.bind(this)); 105 | console.info('mapped dataList to series'); 106 | } 107 | 108 | var preparedData = this.d3plusDataProcessor(this.series); 109 | this.render(preparedData); 110 | } 111 | 112 | getGroupKeys(){ 113 | return this.panel.treeMap.groups.map(function(group){ 114 | return group.key; 115 | }); 116 | } 117 | 118 | /** 119 | * Prepare data for d3plus 120 | */ 121 | d3plusDataProcessor(dataArray){ 122 | var resultArray = []; 123 | var hasGroups = (this.panel.treeMap.groups.length > 0) 124 | 125 | if(!hasGroups){ 126 | // just add the original items since there are no groups 127 | for (var dataIndex=0; dataIndex < dataArray.length; dataIndex++){ 128 | var newDataItem = Object.assign({}, dataArray[dataIndex], dataArray[dataIndex].stats); 129 | resultArray.push(newDataItem); 130 | } 131 | } else { 132 | // Process Groups 133 | var groupArray = []; 134 | for(var groupIndex=0; groupIndex 0){ 153 | newDataItem[key] = matches[0]; 154 | } else { 155 | newDataItem[key] = 'NA'; 156 | } 157 | } 158 | resultArray.push(newDataItem); 159 | } 160 | } 161 | 162 | 163 | // If we're using timeBlocks mode 164 | // replace the aggregated series with individual records 165 | if(this.panel.treeMap.enableTimeBlocks){ 166 | console.info('creating timeblock records') 167 | var timeBlockArray = []; 168 | for (var dataIndex=0; dataIndex < resultArray.length; dataIndex++){ 169 | console.debug('dataIndex:'+dataIndex+', alias:'+resultArray[dataIndex].alias); 170 | var dataSeries = resultArray[dataIndex]; 171 | for(var dataPointIndex=0; dataPointIndex < dataSeries.flotpairs.length; dataPointIndex++){ 172 | var dataSeriesCopy = Object.assign({}, dataSeries); 173 | delete dataSeriesCopy.datapoints; 174 | delete dataSeriesCopy.flotpairs; 175 | dataSeriesCopy.count = 1; 176 | dataSeriesCopy.timestamp = dataSeries.flotpairs[dataPointIndex][0]; 177 | dataSeriesCopy.value = dataSeries.flotpairs[dataPointIndex][1]; 178 | timeBlockArray.push(dataSeriesCopy); 179 | } 180 | } 181 | resultArray = timeBlockArray; 182 | } 183 | 184 | return resultArray; 185 | } 186 | 187 | /** 188 | * Series Handler 189 | */ 190 | seriesHandler(seriesData) { 191 | var series = new TimeSeries({ 192 | datapoints: seriesData.datapoints, 193 | alias: seriesData.target.replace(/"|,|;|=|:|{|}/g, '_') 194 | }); 195 | series.flotpairs = series.getFlotPairs(this.panel.nullPointMode); 196 | return series; 197 | } // End seriesHandler() 198 | 199 | addSeriesOverride(override) { 200 | this.panel.seriesOverrides.push(override || {}); 201 | } 202 | 203 | addTreeMapGroup(group) { 204 | this.panel.treeMap.groups.push(group || {}); 205 | } 206 | 207 | removeSeriesOverride(override) { 208 | this.panel.seriesOverrides = _.without(this.panel.seriesOverrides, override); 209 | this.render(); 210 | } 211 | 212 | removeTreeMapGroup(group) { 213 | this.panel.treeMap.groups = _.without(this.panel.treeMap.groups, group); 214 | this.render(); 215 | } 216 | 217 | updateThresholds(){ 218 | var thresholdCount = this.panel.thresholds.length; 219 | var colorCount = this.panel.colors.length; 220 | this.refresh(); 221 | } 222 | 223 | changeColor(colorIndex, color){ 224 | this.panel.colors[colorIndex] = color; 225 | } 226 | 227 | removeColor(colorIndex){ 228 | this.panel.colors.splice(colorIndex,1); 229 | } 230 | 231 | addColor(){ 232 | this.panel.colors.push('rgba(255, 255, 255, 1)'); 233 | } 234 | 235 | getGradientForValue(data, value){ 236 | var min = Math.min.apply(Math, data.thresholds); 237 | var max = Math.max.apply(Math, data.thresholds); 238 | var absoluteDistance = max - min; 239 | var valueDistanceFromMin = value - min; 240 | var xPercent = valueDistanceFromMin/absoluteDistance; 241 | // Get the smaller number to clamp at 0.99 max 242 | xPercent = Math.min(0.99, xPercent); 243 | // Get the larger number to clamp at 0.01 min 244 | xPercent = Math.max(0.01, xPercent); 245 | 246 | return getColorByXPercentage(this.canvas, xPercent); 247 | } 248 | 249 | applyOverrides(seriesItemAlias){ 250 | var seriesItem = {}, colorData = {}, overrides = {}; 251 | console.info('applying overrides for seriesItem'); 252 | console.debug(seriesItemAlias); 253 | console.debug(this.panel.seriesOverrides); 254 | for(var i=0; i<=this.panel.seriesOverrides.length; i++){ 255 | console.debug('comparing:'); 256 | console.debug(this.panel.seriesOverrides[i]); 257 | if (this.panel.seriesOverrides[i] && this.panel.seriesOverrides[i].alias == seriesItemAlias){ 258 | overrides = this.panel.seriesOverrides[i]; 259 | } 260 | } 261 | colorData.thresholds = (overrides.thresholds || this.panel.thresholds).split(',').map(function(strVale) { 262 | return Number(strVale.trim()); 263 | }); 264 | colorData.colorMap = this.panel.colors; 265 | seriesItem.colorData = colorData; 266 | 267 | seriesItem.valueName = overrides.valueName || this.panel.valueName; 268 | 269 | return seriesItem; 270 | } 271 | 272 | invertColorOrder() { 273 | this.panel.colors.reverse(); 274 | this.refresh(); 275 | } 276 | 277 | addTreeMapId(){ 278 | this.panel.treeMap.ids.push(''); 279 | this.refresh(); 280 | } 281 | 282 | removeTreeMapId(pos){ 283 | this.panel.treeMap.ids.splice(pos,1); 284 | this.refresh(); 285 | } 286 | 287 | changeTreeMapId(idString, pos){ 288 | this.panel.treeMap.ids[pos] = idString; 289 | } 290 | 291 | // ############################################# 292 | // link 293 | // ############################################# 294 | 295 | link(scope, elem, attrs, ctrl) { 296 | var chartElement = elem.find('.heatmap'); 297 | chartElement.append('
'); 298 | var chartContainer = $(document.getElementById(ctrl.containerDivId)); 299 | console.debug('found chartContainer'); 300 | console.debug(chartContainer); 301 | elem.css('height', ctrl.height + 'px'); 302 | 303 | var canvas = elem.find('.canvas')[0]; 304 | ctrl.canvas = canvas; 305 | var gradientValueMax = elem.find('.gradient-value-max')[0]; 306 | var gradientValueMin = elem.find('.gradient-value-min')[0]; 307 | 308 | 309 | var visFormat = 310 | { 311 | "text" : function(text, opts) { 312 | if(opts.key == 'timestamp'){ 313 | var timestamp = moment(Number(text)); 314 | return timestamp.format(ctrl.panel.treeMap.timestampFormat); 315 | } 316 | else if(ctrl.getGroupKeys().indexOf(opts.key)>-1) { 317 | return text; 318 | } 319 | else{ 320 | return d3plus.string.title(text, opts); 321 | } 322 | } 323 | }; 324 | 325 | 326 | function render(data){ 327 | updateSize(); 328 | updateCanvasStyle(); 329 | updateChart(data); 330 | } 331 | 332 | function updateCanvasStyle(){ 333 | canvas.width = Math.max(chartElement[0].clientWidth, 100); 334 | var canvasContext = canvas.getContext("2d"); 335 | canvasContext.clearRect(0, 0, canvas.width, canvas.height); 336 | 337 | var grd = canvasContext.createLinearGradient(0, 0, canvas.width, 0); 338 | var colorWidth = 1 / Math.max(ctrl.panel.colors.length, 1); 339 | for(var i=0; i 2 |
Development
3 |
4 | 5 | 6 |
7 | 8 |
9 |
Group Options
10 | 11 |
12 | 13 | 14 |
15 |
16 | 17 | 18 |
19 |
20 | 21 | 24 |
25 |
26 | 27 | 28 | 29 | 34 |
35 | 36 |
37 | 38 |
39 | 40 | 43 |
44 |
45 | 48 |
49 |
50 |
51 | 52 |
53 |
54 |
55 |
Grouping Regex match example: /server[0-3]/i
56 | 57 |
58 |
59 | 60 |
61 |
62 | 63 |
64 | 65 |
66 | 67 |
68 |
69 | 70 |
71 | 72 |
73 | 76 |
77 |
78 |
79 | 80 | 83 |
84 |
-------------------------------------------------------------------------------- /src/img/heatmap.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savantly-net/grafana-heatmap/d2244abb0e46f5b31a5818a3bec5c1101075e1c8/src/img/heatmap.PNG -------------------------------------------------------------------------------- /src/img/icn-heatmap-panel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /src/img/timestamp_data.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savantly-net/grafana-heatmap/d2244abb0e46f5b31a5818a3bec5c1101075e1c8/src/img/timestamp_data.PNG -------------------------------------------------------------------------------- /src/module.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 6 |
7 | 8 |
9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
minmaxavgtotalcurrent
{{series.alias}}{{series.stats.min}}{{series.stats.max}}{{series.stats.avg}}{{series.stats.total}}{{series.stats.current}}
30 |
31 |
-------------------------------------------------------------------------------- /src/module.js: -------------------------------------------------------------------------------- 1 | import {pluginName} from './properties'; 2 | import {HeatmapCtrl} from './heatmapControl'; 3 | /*import {loadPluginCss} from 'app/plugins/sdk'; 4 | 5 | loadPluginCss({ 6 | dark: 'plugins/'+pluginName+'/libs/mermaid/dist/mermaid.css', 7 | light: 'plugins/'+pluginName+'/libs/mermaid/dist/mermaid.css' 8 | });*/ 9 | 10 | export { 11 | HeatmapCtrl as PanelCtrl 12 | }; -------------------------------------------------------------------------------- /src/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "panel", 3 | "name": "Heatmap", 4 | "id": "savantly-heatmap-panel", 5 | 6 | "info": { 7 | "description": "Heatmap panel for grafana", 8 | "author": { 9 | "name": "Jeremy Branham", 10 | "url": "https://github.com/jdbranham" 11 | }, 12 | "keywords": ["heatmap", "panel"], 13 | "links": [ 14 | {"name": "Project site", "url": "https://github.com/savantly-net/grafana-heatmap"}, 15 | {"name": "Apache License", "url": "https://github.com/savantly-net/grafana-heatmap/blob/master/LICENSE"} 16 | ], 17 | "version": "0.2.0", 18 | "updated": "2016-10-16", 19 | "logos": { 20 | "small": "img/icn-heatmap-panel.svg", 21 | "large": "img/icn-heatmap-panel.svg" 22 | } 23 | }, 24 | 25 | "dependencies": { 26 | "grafanaVersion": "3.x.x", 27 | "plugins": [ ] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/properties.js: -------------------------------------------------------------------------------- 1 | 2 | var pluginName = 'savantly-heatmap-panel', 3 | heatmapEditor = 'public/plugins/'+ pluginName +'/heatmapEditor.html', 4 | displayEditor = 'public/plugins/'+ pluginName +'/displayEditor.html'; 5 | 6 | export { 7 | pluginName, 8 | heatmapEditor, 9 | displayEditor 10 | } -------------------------------------------------------------------------------- /src/series_overrides_heatmap_ctrl.js: -------------------------------------------------------------------------------- 1 | angular.module('grafana.controllers').controller('SeriesOverridesHeatmapCtrl', ['$scope', '$element', 'popoverSrv', function($scope, $element, popoverSrv) { 2 | $scope.overrideMenu = []; 3 | $scope.currentOverrides = []; 4 | $scope.override = $scope.override || {}; 5 | 6 | $scope.addOverrideOption = function(name, propertyName, values) { 7 | var option = {}; 8 | option.text = name; 9 | option.propertyName = propertyName; 10 | option.index = $scope.overrideMenu.length; 11 | option.values = values; 12 | 13 | option.submenu = _.map(values, function(value) { 14 | return { text: String(value), value: value }; 15 | }); 16 | 17 | $scope.overrideMenu.push(option); 18 | }; 19 | 20 | $scope.setOverride = function(item, subItem) { 21 | // handle color overrides 22 | if (item.propertyName === 'color') { 23 | $scope.openColorSelector(); 24 | return; 25 | } 26 | 27 | $scope.override[item.propertyName] = subItem.value; 28 | $scope.updateCurrentOverrides(); 29 | $scope.ctrl.render(); 30 | }; 31 | 32 | $scope.colorSelected = function(color) { 33 | $scope.override['color'] = color; 34 | $scope.updateCurrentOverrides(); 35 | $scope.ctrl.render(); 36 | }; 37 | 38 | $scope.thresholdsChanged = function(thresholds){ 39 | $scope.override['thresholds'] = thresholds.value; 40 | $scope.updateCurrentOverrides(); 41 | $scope.ctrl.render(); 42 | } 43 | 44 | $scope.openColorSelector = function() { 45 | popoverSrv.show({ 46 | element: $element.find(".dropdown")[0], 47 | position: 'top center', 48 | openOn: 'click', 49 | template: '', 50 | model: { 51 | autoClose: true, 52 | colorSelected: $scope.colorSelected, 53 | }, 54 | onClose: function() { 55 | $scope.ctrl.render(); 56 | } 57 | }); 58 | }; 59 | 60 | $scope.removeOverride = function(option) { 61 | delete $scope.override[option.propertyName]; 62 | $scope.updateCurrentOverrides(); 63 | $scope.ctrl.refresh(); 64 | }; 65 | 66 | $scope.getSeriesNames = function() { 67 | return _.map($scope.ctrl.seriesList, function(series) { 68 | return series.alias; 69 | }); 70 | }; 71 | 72 | $scope.updateCurrentOverrides = function() { 73 | $scope.currentOverrides = []; 74 | _.each($scope.overrideMenu, function(option) { 75 | var value = $scope.override[option.propertyName]; 76 | if (_.isUndefined(value)) { return; } 77 | $scope.currentOverrides.push({ 78 | name: option.text, 79 | propertyName: option.propertyName, 80 | value: String(value) 81 | }); 82 | }); 83 | }; 84 | 85 | //$scope.addOverrideOption('Color', 'color', ['change']); 86 | $scope.addOverrideOption('Thresholds', 'thresholds', ['custom']); 87 | $scope.addOverrideOption('Value', 'valueName', ['avg', 'min', 'max', 'total', 'current']); 88 | $scope.updateCurrentOverrides(); 89 | }]); 90 | --------------------------------------------------------------------------------