├── .gitignore ├── LICENSE ├── README.md ├── gulpfile.js ├── heatmap.gif ├── index.js ├── package.json └── public ├── color_directive.js ├── colors.html ├── colors.js ├── heatmap.html ├── heatmap.js ├── heatmap.less ├── heatmap_tooltip.html ├── heatmap_tooltip.less ├── heatmap_tooltip_directive.js ├── heatmap_vis_params.html ├── lib ├── heatmap_controller.js └── heatmap_directive.js ├── tooltip_directive.js └── vis ├── components ├── axis │ ├── axis.js │ ├── rotate.js │ └── truncate.js ├── colorbrewer │ ├── LICENSE.md │ └── colorbrewer.js ├── control │ └── events.js ├── elements │ ├── g.js │ ├── rect.js │ └── text.js ├── layout │ ├── generator.js │ └── layout.js ├── legend │ └── legend.js ├── utils │ ├── attrs.js │ ├── builder.js │ └── valuator.js └── visualization │ ├── generator.js │ ├── heatmap.js │ └── heatmap_layout.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Shelby Sturgis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kibana Heatmap Plugin 2 | A Heatmap Plugin for Kibana 4 3 | 4 | ![Kibana Heatmap](heatmap.gif) 5 | 6 | ### Requirements 7 | Kibana 4.3+ 8 | 9 | ### Installation steps 10 | 1. Download and unpack [Kibana](https://www.elastic.co/downloads/kibana). 11 | 2. From the Kibana root directory, install the plugin with the following command: 12 | 13 | To install the version of this heatmap that works with Kibana 4.3 - 4.5: 14 | ``` 15 | $ bin/kibana plugin -i heatmap -u https://github.com/stormpython/heatmap/archive/1.0.0.zip 16 | ``` 17 | 18 | *Higher versions of Kibana are not supported yet* 19 | 20 | ### Issues 21 | Please file issues [here](https://github.com/stormpython/heatmap/issues). 22 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var path = require('path'); 3 | var path = require('path'); 4 | var mkdirp = require('mkdirp'); 5 | var Rsync = require('rsync'); 6 | var Promise = require('bluebird'); 7 | var eslint = require('gulp-eslint'); 8 | var watch = require('gulp-watch'); 9 | 10 | var pkg = require('./package.json'); 11 | 12 | var kibanaPluginDir = path.resolve(__dirname, '../kibana/installedPlugins/heatmap'); 13 | 14 | var include = ['package.json', 'index.js', 'public', 'node_modules']; 15 | var exclude = Object.keys(pkg.devDependencies).map(function (name) { 16 | return path.join('node_modules', name); 17 | }); 18 | 19 | function syncPluginTo(dest, done) { 20 | mkdirp(dest, function (err) { 21 | if (err) return done(err); 22 | Promise.all(include.map(function (name) { 23 | var source = path.resolve(__dirname, name); 24 | return new Promise(function (resolve, reject) { 25 | var rsync = new Rsync(); 26 | rsync 27 | .source(source) 28 | .destination(dest) 29 | .flags('uav') 30 | .recursive(true) 31 | .set('delete') 32 | .exclude(exclude) 33 | .output(function (data) { 34 | process.stdout.write(data.toString('utf8')); 35 | }); 36 | rsync.execute(function (err) { 37 | if (err) { 38 | console.log(err); 39 | return reject(err); 40 | } 41 | resolve(); 42 | }); 43 | }); 44 | })) 45 | .then(function () { 46 | done(); 47 | }) 48 | .catch(done); 49 | }); 50 | } 51 | 52 | gulp.task('sync', function (done) { 53 | syncPluginTo(kibanaPluginDir, done); 54 | }); 55 | 56 | gulp.task('lint', function (done) { 57 | return gulp.src(['server/**/*.js', 'public/**/*.js', 'public/**/*.jsx']) 58 | // eslint() attaches the lint output to the eslint property 59 | // of the file object so it can be used by other modules. 60 | .pipe(eslint()) 61 | // eslint.format() outputs the lint results to the console. 62 | // Alternatively use eslint.formatEach() (see Docs). 63 | .pipe(eslint.formatEach()) 64 | // To have the process exit with an error code (1) on 65 | // lint error, return the stream and pipe to failOnError last. 66 | .pipe(eslint.failOnError()); 67 | }); 68 | 69 | const batch = require('gulp-batch'); 70 | 71 | gulp.task('dev', ['sync'], function (done) { 72 | watch(['package.json', 'index.js', 'public/**/*', 'server/**/*'], batch(function(events, done) { 73 | gulp.start(['sync', 'lint'], done); 74 | })); 75 | }); 76 | -------------------------------------------------------------------------------- /heatmap.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stormpython/heatmap/8f05bdc23ca03cf1b276e710499d8b7ddfde8acc/heatmap.gif -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = function (kibana) { 2 | return new kibana.Plugin({ 3 | uiExports: { 4 | visTypes: ['plugins/heatmap/heatmap'] 5 | } 6 | }); 7 | }; 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "heatmap", 3 | "version": "0.1.0", 4 | "devDependencies": { 5 | "bluebird": "3.1.1", 6 | "gulp": "3.9.0", 7 | "gulp-batch": "1.0.5", 8 | "gulp-eslint": "1.1.1", 9 | "gulp-watch": "4.3.5", 10 | "mkdirp": "0.5.1", 11 | "path": "0.12.7", 12 | "rsync": "0.4.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /public/color_directive.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var colorbrewer = require('plugins/heatmap/vis/components/colorbrewer/colorbrewer'); 3 | var gElement = require('plugins/heatmap/vis/components/elements/g'); 4 | var rectElement = require('plugins/heatmap/vis/components/elements/rect'); 5 | 6 | var module = require('ui/modules').get('heatmap'); 7 | 8 | module.directive('colorMap', function () { 9 | function link (scope, element, attrs) { 10 | scope.colorScale = colorbrewer[scope.name]; 11 | scope.colors = scope.colorScale[scope.value]; 12 | scope.min = _.first(Object.keys(scope.colorScale)); 13 | scope.max = _.last(Object.keys(scope.colorScale)); 14 | 15 | function render(colors) { 16 | var size = 15; 17 | var padding = 2; 18 | var g = gElement(); 19 | var rect = rectElement() 20 | .x(function (d, i) { return i * size + padding; }) 21 | .y(0) 22 | .width(size) 23 | .height(size) 24 | .fill(function (d) { return d; }) 25 | 26 | function draw(selection) { 27 | selection.each(function (data, index) { 28 | d3.select(this) 29 | .datum(data) 30 | .call(g) 31 | .select('g') 32 | .call(rect); 33 | }); 34 | } 35 | 36 | d3.select(element[0]).select('svg.colors') 37 | .datum(colors) 38 | .call(draw); 39 | } 40 | 41 | scope.$watch('name', function (newVal, oldVal) { 42 | scope.colorScale = colorbrewer[newVal]; 43 | scope.colors = scope.colorScale[scope.value]; 44 | scope.min = _.first(Object.keys(scope.colorScale)); 45 | scope.max = _.last(Object.keys(scope.colorScale)); 46 | render(scope.colors); 47 | }); 48 | 49 | scope.$watch('value', function (newVal, oldVal) { 50 | scope.value = newVal; 51 | scope.colors = scope.colorScale[newVal]; 52 | render(scope.colors); 53 | }); 54 | } 55 | 56 | return { 57 | restrict: 'E', 58 | scope: { 59 | name: '=', 60 | value: '=' 61 | }, 62 | template: require('plugins/heatmap/colors.html'), 63 | link: link 64 | }; 65 | }); 66 | -------------------------------------------------------------------------------- /public/colors.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 | 7 |
8 | -------------------------------------------------------------------------------- /public/colors.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { displayName: 'Yellow - Green', name: 'YlGn', min: 3, max: 9, value: 6 }, 3 | { displayName: 'Yellow - Green - Blue', name: 'YlGnBu', min: 3, max: 9, value: 6 }, 4 | { displayName: 'Green - Blue', name: 'GnBu', min: 3, max: 9, value: 6 }, 5 | { displayName: 'Blue - Green', name: 'BuGn', min: 3, max: 9, value: 6 }, 6 | { displayName: 'Purple - Blue - Green', name: 'PuBuGn', min: 3, max: 9, value: 6 }, 7 | { displayName: 'Purple - Blue' , name: 'PuBu' , min: 3, max: 9, value: 6 }, 8 | { displayName: 'Blue - Purple', name: 'BuPu', min: 3, max: 9, value: 6 }, 9 | { displayName: 'Red - Purple', name: 'RdPu', min: 3, max: 9, value: 6 }, 10 | { displayName: 'Purple - Red', name: 'PuRd', min: 3, max: 9, value: 6 }, 11 | { displayName: 'Orange - Red', name: 'OrRd', min: 3, max: 9, value: 6 }, 12 | { displayName: 'Yellow - Orange - Red', name: 'YlOrRd', min: 3, max: 9, value: 6 }, 13 | { displayName: 'Yellow - Orange - Brown', name: 'YlOrBr', min: 3, max: 9, value: 6 }, 14 | { displayName: 'Purples', name: 'Purples', min: 3, max: 9, value: 6 }, 15 | { displayName: 'Blues', name: 'Blues', min: 3, max: 9, value: 6 }, 16 | { displayName: 'Greens', name: 'Greens', min: 3, max: 9, value: 6 }, 17 | { displayName: 'Oranges', name: 'Oranges', min: 3, max: 9, value: 6 }, 18 | { displayName: 'Reds', name: 'Reds', min: 3, max: 9, value: 6 }, 19 | { displayName: 'Greys', name: 'Greys', min: 3, max: 9, value: 6 }, 20 | { displayName: 'Purple - Orange', name: 'PuOr', min: 3, max: 11, value: 6 }, 21 | { displayName: 'Brown - Blue - Green', name: 'BrBG', min: 3, max: 11, value: 6 }, 22 | { displayName: 'Purple - Red - Green', name: 'PRGn', min: 3, max: 11, value: 6 }, 23 | { displayName: 'Purple - Yellow - Green', name: 'PiYG', min: 3, max: 11, value: 6 }, 24 | { displayName: 'Red - Blue', name: 'RdBu', min: 3, max: 11, value: 6 }, 25 | { displayName: 'Red - Grey', name: 'RdGy', min: 3, max: 11, value: 6 }, 26 | { displayName: 'Red - Yellow - Blue', name: 'RdYlBu', min: 3, max: 11, value: 6 }, 27 | { displayName: 'Spectral', name: 'Spectral', min: 3, max: 11, value: 6 }, 28 | { displayName: 'Red - Yellow - Green', name: 'RdYlGn', min: 3, max: 11, value: 6 }, 29 | { displayName: 'Accent', name: 'Accent', min: 3, max: 8, value: 6 }, 30 | { displayName: 'Dark2', name: 'Dark2', min: 3, max: 8, value: 6 }, 31 | { displayName: 'Paired', name: 'Paired', min: 3, max: 12, value: 6 }, 32 | { displayName: 'Pastel1', name: 'Pastel1', min: 3, max: 9, value: 6 }, 33 | { displayName: 'Pastel2', name: 'Pastel2', min: 3, max: 8, value: 6 }, 34 | { displayName: 'Set1', name: 'Set1', min: 3, max: 9, value: 6 }, 35 | { displayName: 'Set2', name: 'Set2', min: 3, max: 8, value: 6 }, 36 | { displayName: 'Set3', name: 'Set3', min: 3, max: 12, value: 6 } 37 | ]; 38 | -------------------------------------------------------------------------------- /public/heatmap.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | -------------------------------------------------------------------------------- /public/heatmap.js: -------------------------------------------------------------------------------- 1 | require('plugins/heatmap/heatmap.less'); 2 | require('plugins/heatmap/heatmap_tooltip.less'); 3 | require('plugins/heatmap/color_directive.js'); 4 | require('plugins/heatmap/lib/heatmap_controller.js'); 5 | require('plugins/heatmap/lib/heatmap_directive.js'); 6 | require('plugins/heatmap/heatmap_tooltip_directive.js'); 7 | 8 | 9 | function HeatmapProvider(Private) { 10 | var TemplateVisType = Private(require('ui/template_vis_type/TemplateVisType')); 11 | var Schemas = Private(require('ui/Vis/Schemas')); 12 | var colors = require('plugins/heatmap/colors.js'); 13 | 14 | return new TemplateVisType({ 15 | name: 'heatmap', 16 | title: 'Heatmap', 17 | description: 'A heat map is a graphical representation of data where the individual ' + 18 | 'values contained in a matrix are represented as colors.', 19 | icon: 'fa-th', 20 | template: require('plugins/heatmap/heatmap.html'), 21 | params: { 22 | defaults: { 23 | margin: { top: 20, right: 200, bottom: 100, left: 100 }, 24 | stroke: '#ffffff', 25 | strokeWidth: 1, 26 | padding: 0, 27 | legendNumberFormat: 'number', 28 | color: colors[0].name, 29 | numberOfColors: 6, 30 | rowAxis: { filterBy: 0 }, 31 | columnAxis: { filterBy: 0 } 32 | }, 33 | colors: colors, 34 | legendNumberFormats: ['number', 'bytes', 'currency', 'percentage'], 35 | editor: require('plugins/heatmap/heatmap_vis_params.html') 36 | }, 37 | schemas: new Schemas([ 38 | { 39 | group: 'metrics', 40 | name: 'metric', 41 | title: 'Cell', 42 | min: 1, 43 | aggFilter: ['avg', 'sum', 'count', 'min', 'max', 'median', 'cardinality'], 44 | defaults: [ 45 | { schema: 'metric', type: 'count' } 46 | ] 47 | }, 48 | { 49 | group: 'buckets', 50 | name: 'columns', 51 | icon: 'fa fa-ellipsis-v', 52 | title: 'Columns', 53 | min: 0, 54 | max: 1, 55 | aggFilter: '!geohash_grid' 56 | }, 57 | { 58 | group: 'buckets', 59 | name: 'rows', 60 | icon: 'fa fa-ellipsis-h', 61 | title: 'Rows', 62 | min: 0, 63 | max: 1, 64 | aggFilter: '!geohash_grid' 65 | } 66 | ]) 67 | }); 68 | } 69 | 70 | require('ui/registry/vis_types').register(HeatmapProvider); 71 | -------------------------------------------------------------------------------- /public/heatmap.less: -------------------------------------------------------------------------------- 1 | .heatmap-vis { 2 | display: flex; 3 | flex: 1 1 100%; 4 | position: relative; 5 | } 6 | 7 | .axis line, 8 | .axis path { 9 | display: none; 10 | } 11 | 12 | input[type="range"] { 13 | display: inline; 14 | width: 200px; 15 | } 16 | -------------------------------------------------------------------------------- /public/heatmap_tooltip.html: -------------------------------------------------------------------------------- 1 |
2 | 8 |
-------------------------------------------------------------------------------- /public/heatmap_tooltip.less: -------------------------------------------------------------------------------- 1 | @import (reference) "~ui/styles/variables"; 2 | 3 | .heatmap-tooltip { 4 | position: absolute; 5 | width: auto; 6 | padding: 5px !important; 7 | background: fadeout(@gray-darker, 7%); 8 | -webkit-border-radius: 4px; 9 | -moz-border-radius: 4px; 10 | border-radius: 4px; 11 | -webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4); 12 | -moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4); 13 | box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4); 14 | pointer-events: none; 15 | white-space: nowrap; 16 | color: #ecf0f1 !important; 17 | } 18 | 19 | .heatmap-tooltip-list { 20 | padding: 0; 21 | list-style-type: none; 22 | } 23 | .heatmap-tooltip-list span{ 24 | font-size: 12px; 25 | } 26 | .heatmap-tooltip-list span.key{ 27 | font-weight: bold; 28 | } -------------------------------------------------------------------------------- /public/heatmap_tooltip_directive.js: -------------------------------------------------------------------------------- 1 | var d3 = require("d3"); 2 | var _ = require("lodash"); 3 | var module = require('ui/modules').get('heatmap'); 4 | 5 | module.directive('tooltip', function () { 6 | 7 | function controller($scope) { 8 | $scope.isShown = false; 9 | /* 10 | * Make sure that the items array is populated before tooltip is shown. 11 | * The items variable is an array of objects, e.g. 12 | * [ 13 | * { key: "Column", value: "Tuesday" }, 14 | * { key: "Row", value: "12pm" }, 15 | * { key: "Count", value: 12 } 16 | * ] 17 | */ 18 | this.showOnHover = function () { 19 | $scope.isShown = !!($scope.items && _.isArray($scope.items) && $scope.items.length); 20 | }; 21 | 22 | this.hideOnOut = function(){ 23 | $scope.isShown = false; 24 | }; 25 | } 26 | 27 | function link(scope, element, attrs, ctrl) { 28 | function render($scope) { 29 | d3.select(_.first(element)) 30 | .style("top", $scope.top + "px") 31 | .style("left", $scope.left + "px"); 32 | 33 | ctrl.showOnHover(); 34 | } 35 | 36 | scope.$watchGroup(["top", "left", "items"], function (newVal, oldVal, scope) { 37 | render(scope); 38 | }, 250); 39 | 40 | scope.$watch("ngShow", function (newVal) { 41 | ctrl.hideOnOut(); 42 | }); 43 | }; 44 | 45 | return { 46 | restrict: "E", 47 | scope: { 48 | top: "=", 49 | left: "=", 50 | items: "=", 51 | ngShow: "=" 52 | }, 53 | replace: true, 54 | controller: controller, 55 | link: link, 56 | template: require("plugins/heatmap/heatmap_tooltip.html") 57 | }; 58 | }); -------------------------------------------------------------------------------- /public/heatmap_vis_params.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |

Color Scales

6 | 7 | 12 | 13 |
14 | 15 | 16 |
17 |

Set Margins

18 |
19 | 20 | 21 | {{vis.params.margin.left}} 22 |
23 |
24 | 25 | 26 | {{vis.params.margin.top}} 27 |
28 |
29 | 30 | 31 | {{vis.params.margin.right}} 32 |
33 |
34 | 35 | 36 | {{vis.params.margin.bottom}} 37 |
38 |
39 | 40 | 41 |
42 |

Padding

43 | 44 | {{vis.params.padding}} 45 |
46 | 47 | 48 |
49 |

Stroke Options

50 |
51 | 52 | 53 |
54 |
55 | 56 | 57 |
58 |
59 | 60 | 61 |
62 |

Axis Options

63 |
64 | Filter row axis every: 65 | 66 | value. 67 |
68 | Filter column axis every: 69 | 70 | value. 71 |
72 |
73 | 74 | 75 |
76 |

Legend Number Format

77 | 82 |
83 | 84 |
85 | -------------------------------------------------------------------------------- /public/lib/heatmap_controller.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var module = require('ui/modules').get('heatmap'); 3 | 4 | module.controller('HeatmapController', function ($scope, Private) { 5 | var tabifyAggResponse = Private(require('ui/agg_response/tabify/tabify')); 6 | 7 | function getLabel(agg, name) { 8 | return agg.bySchemaName[name] ? agg.bySchemaName[name][0].makeLabel() : ''; 9 | } 10 | 11 | function processTableGroups(tableGroups, $scope) { 12 | var columnAggId = _.first(_.pluck($scope.vis.aggs.bySchemaName['columns'], 'id')); 13 | var rowAggId = _.first(_.pluck($scope.vis.aggs.bySchemaName['rows'], 'id')); 14 | var metricsAggId = _.first(_.pluck($scope.vis.aggs.bySchemaName['metric'], 'id')); 15 | var dataLabels = { [columnAggId]: 'col', [rowAggId]: 'row', [metricsAggId]: 'value' }; 16 | 17 | var cells = []; 18 | 19 | tableGroups.tables.forEach(function (table) { 20 | table.rows.forEach(function (row) { 21 | var cell = {}; 22 | 23 | table.columns.forEach(function (column, i) { 24 | var fieldFormatter = table.aggConfig(column).fieldFormatter(); 25 | // Median metric aggs use the parentId and not the id field 26 | var key = column.aggConfig.parentId ? dataLabels[column.aggConfig.parentId] : dataLabels[column.aggConfig.id]; 27 | 28 | if (key) { 29 | cell[key] = key !== 'value' ? fieldFormatter(row[i]) : row[i]; 30 | } 31 | }); 32 | 33 | // if no columns or rows, then return '_all' 34 | if (!cell.col && !cell.row) { 35 | cell['col'] = '_all'; 36 | } 37 | 38 | cells.push(cell); 39 | }); 40 | }); 41 | 42 | return cells; 43 | }; 44 | 45 | $scope.$watch('esResponse', function (resp) { 46 | if (!resp) { 47 | $scope.data = null; 48 | return; 49 | } 50 | 51 | // Add row, column, and metric titles as vis parameters 52 | _.merge($scope.vis.params, { 53 | rowAxis: { title: getLabel($scope.vis.aggs, 'rows') }, 54 | columnAxis: { title: getLabel($scope.vis.aggs, 'columns') }, 55 | legendTitle: getLabel($scope.vis.aggs, 'metric') 56 | }); 57 | 58 | $scope.data = [{ 59 | cells: processTableGroups(tabifyAggResponse($scope.vis, resp), $scope) 60 | }]; 61 | 62 | $scope.eventListeners = { 63 | mouseover: [ mouseover ], 64 | mouseout: [ mouseout ] 65 | }; 66 | 67 | function mouseover(event) { 68 | var target = d3.select(event.target); 69 | var isHeatmapCell = (target.attr("class") === "cell"); 70 | var OFFSET = 50; 71 | 72 | if (isHeatmapCell) { 73 | // get data bound to heatmap cell 74 | var d = _.first(target.data()); 75 | // Custom code for tooltip functionality goes here 76 | $scope.$apply(function () { 77 | var params = $scope.vis.params; 78 | $scope.tooltipItems = Object.keys(d) 79 | .filter(function (key) { return key !== "data"; }) 80 | .map(function (key) { 81 | 82 | var title = d3.selectAll('text.title'); 83 | var value = d[key]; 84 | if (key.toUpperCase() === 'ROW') { 85 | key = params.columnAxis.title || 'ROW'; 86 | } 87 | if (key.toUpperCase() === 'COL') { 88 | key = params.rowAxis.title || 'COL'; 89 | } 90 | return { 91 | key: key.toUpperCase(), 92 | value: value 93 | }; 94 | }); 95 | 96 | $scope.top = d.data.row + parseInt(params.margin.top) + OFFSET; 97 | $scope.left = d.data.col + parseInt(params.margin.left) + OFFSET; 98 | }); 99 | } 100 | }; 101 | 102 | function mouseout(event){ 103 | $scope.$apply(function () { 104 | $scope.tooltipItems = []; 105 | $scope.top = 0; 106 | $scope.left = 0; 107 | }); 108 | } 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /public/lib/heatmap_directive.js: -------------------------------------------------------------------------------- 1 | var d3 = require('d3'); 2 | var _ = require('lodash'); 3 | var visGenerator = require('plugins/heatmap/vis/index'); 4 | 5 | var module = require('ui/modules').get('heatmap'); 6 | 7 | module.directive('heatmap', function () { 8 | function link (scope, element, attrs) { 9 | angular.element(document).ready(function () { 10 | var vis = visGenerator(); 11 | var svg = d3.select(element[0]); 12 | 13 | function onSizeChange() { 14 | return { 15 | width: element.parent().width(), 16 | height: element.parent().height() 17 | }; 18 | } 19 | 20 | function getSize() { 21 | var size = onSizeChange(); 22 | return [size.width, size.height]; 23 | }; 24 | 25 | function render(data, opts, eventListeners) { 26 | var chartSize; 27 | 28 | opts = opts || {}; 29 | eventListeners = eventListeners || {}; 30 | chartSize = getSize(); 31 | 32 | vis.options(opts) 33 | .listeners(eventListeners) 34 | .size(chartSize); 35 | 36 | if (data) { 37 | svg.datum(data).call(vis); 38 | } 39 | }; 40 | 41 | scope.$watch('data', function (newVal, oldVal) { 42 | render(newVal, scope.options, scope.eventListeners); 43 | }); 44 | 45 | scope.$watch('options', function (newVal, oldVal) { 46 | render(scope.data, newVal, scope.eventListeners); 47 | }); 48 | 49 | scope.$watch('eventListeners', function (newVal, oldVal) { 50 | render(scope.data, scope.options, newVal); 51 | }); 52 | 53 | scope.$watch(onSizeChange, _.debounce(function () { 54 | render(scope.data, scope.options, scope.eventListeners); 55 | }, 250), true); 56 | 57 | element.bind('resize', function () { 58 | scope.$apply(); 59 | }); 60 | }); 61 | } 62 | 63 | return { 64 | restrict: 'E', 65 | scope: { 66 | data: '=', 67 | options: '=', 68 | eventListeners: '=' 69 | }, 70 | template: '', 71 | replace: 'true', 72 | link: link 73 | }; 74 | }); 75 | -------------------------------------------------------------------------------- /public/tooltip_directive.js: -------------------------------------------------------------------------------- 1 | var _ = require("lodash"); 2 | var module = require('ui/modules').get('heatmap'); 3 | 4 | module.directive('tooltip', function () { 5 | debugger; 6 | function controller($scope) { 7 | $scope.isShown = false; 8 | debugger; 9 | /* 10 | * Make sure that the items array is populated before tooltip is shown. 11 | * The items variable is an array of objects, e.g. 12 | * [ 13 | * { key: "Column", value: "Tuesday" }, 14 | * { key: "Row", value: "12pm" }, 15 | * { key: "Count", value: 12 } 16 | * ] 17 | */ 18 | this.showOnHover = function () { 19 | $scope.isShown = !!($scope.items && _.isArray($scope.items) && $scope.items.length); 20 | }; 21 | } 22 | 23 | function link(scope, element, attrs, ctrl) { 24 | function render($scope) { 25 | debugger; 26 | d3.select(_.first(element)) 27 | .style("top", $scope.top + "px") 28 | .style("left", $scope.left + "px"); 29 | 30 | ctrl.showOnHover(); 31 | } 32 | 33 | scope.$watchGroup(["top", "left", "items"], function (newVal, oldVal, scope) { 34 | debugger; 35 | render(scope); 36 | }, 250); 37 | } 38 | 39 | return { 40 | restrict: "E", 41 | scope: { 42 | top: "=", 43 | left: "=", 44 | items: "=" 45 | }, 46 | replace: true, 47 | controller: controller, 48 | link: link, 49 | template: require("plugins/heatmap/heatmap_tooltip.html") 50 | }; 51 | }); -------------------------------------------------------------------------------- /public/vis/components/axis/axis.js: -------------------------------------------------------------------------------- 1 | var d3 = require('d3'); 2 | var _ = require('lodash'); 3 | var rotate = require('plugins/heatmap/vis/components/axis/rotate'); 4 | 5 | function axes() { 6 | var scale = d3.scale.linear(); 7 | var orientation = 'left'; 8 | var rotateLabels = false; 9 | var rotateOptions = {}; 10 | var ticks = {}; 11 | var title = {}; 12 | var transform = 'translate(0,0)'; 13 | var cssClass = 'axis'; 14 | var axis = d3.svg.axis(); 15 | var rotation = rotate(); 16 | 17 | function generator(selection) { 18 | selection.each(function (data) { 19 | axis.orient(orientation) 20 | .scale(scale) 21 | .ticks(ticks.number || 10) 22 | .tickValues(ticks.values || null) 23 | .tickSize(ticks.size || 6) 24 | .innerTickSize(ticks.innerTickSize || 6) 25 | .outerTickSize(ticks.outerTickSize || 6) 26 | .tickPadding(ticks.padding || 3) 27 | .tickFormat(ticks.format || null); 28 | 29 | var g = d3.select(this).selectAll('g.' + cssClass) 30 | .data([data]); 31 | 32 | g.exit().remove(); 33 | g.enter().append('g'); 34 | 35 | // Attach axis 36 | g.attr('class', cssClass + ' axis') 37 | .attr('transform', transform) 38 | .call(axis); 39 | 40 | if (rotateLabels) { 41 | var axisLength; 42 | 43 | if (_.isFunction(scale.rangeBand)) { 44 | axisLength = Math.abs(_.last(scale.range()) + scale.rangeBand()); 45 | } else { 46 | axisLength = Math.abs(scale.range()[1] - scale.range()[0]); 47 | } 48 | 49 | rotation 50 | .axisLength(axisLength) 51 | .measure(rotateOptions.measure || 'width') 52 | .text({ 53 | transform: rotateOptions.transform || 'translate(0,0)rotate(-45)' 54 | }); 55 | 56 | g.call(rotation); 57 | } 58 | 59 | var text = g.selectAll('text.title') 60 | .data([data]); 61 | 62 | text.exit().remove(); 63 | text.enter().append('text') 64 | .attr('class', title.class || 'title'); 65 | 66 | text 67 | .attr('x', title.x || 6) 68 | .attr('y', title.y || 6) 69 | .attr('dx', title.dx || '') 70 | .attr('dy', title.dy || '.71em') 71 | .attr('transform', title.transform || 'translate(0,0)') 72 | .style('text-anchor', title.anchor || 'end') 73 | .text(title.text || ''); 74 | }); 75 | } 76 | 77 | // Public API 78 | generator.scale = function (v) { 79 | if (!arguments.length) return scale; 80 | scale = v; 81 | return generator; 82 | }; 83 | 84 | generator.orientation = function (v) { 85 | if (!arguments.length) return orientation; 86 | orientation = v; 87 | return generator; 88 | }; 89 | 90 | generator.class = function (v) { 91 | if (!arguments.length) return cssClass; 92 | cssClass = v; 93 | return generator; 94 | }; 95 | 96 | generator.transform = function (v) { 97 | if (!arguments.length) return transform; 98 | transform = v; 99 | return generator; 100 | }; 101 | 102 | generator.ticks = function (v) { 103 | if (!arguments.length) return ticks; 104 | ticks.number = typeof v.number !== 'undefined' ? v.number : ticks.number; 105 | ticks.values = typeof v.values !== 'undefined' ? v.values : ticks.values; 106 | ticks.size = typeof v.size !== 'undefined' ? v.size : ticks.size; 107 | ticks.padding = typeof v.padding !== 'undefined' ? v.padding : ticks.padding; 108 | ticks.format = typeof v.format !== 'undefined' ? v.format : ticks.format; 109 | ticks.innerTickSize = typeof v.innerTickSize !== 'undefined' ? v.innerTickSize : ticks.innerTickSize; 110 | ticks.outerTickSize = typeof v.outerTickSize !== 'undefined' ? v.outerTickSize : ticks.outerTickSize; 111 | return generator; 112 | }; 113 | 114 | generator.rotateLabels = function (v) { 115 | if (!arguments.length) return rotateLabels; 116 | rotateLabels = v; 117 | return generator; 118 | }; 119 | 120 | generator.rotateOptions = function (v) { 121 | if (!arguments.length) return rotateOptions; 122 | rotateOptions.measure = typeof v.measure !== 'undefined' ? v.measure : rotateOptions.measure; 123 | rotateOptions.transform = typeof v.transform !== 'undefined' ? v.transform : rotateOptions.transform; 124 | return generator; 125 | }; 126 | 127 | generator.title = function (v) { 128 | if (!arguments.length) return title; 129 | title.class = typeof v.class !== 'undefined' ? _.class : title.class; 130 | title.x = typeof v.x !== 'undefined' ? v.x : title.x; 131 | title.y = typeof v.y !== 'undefined' ? v.y : title.y; 132 | title.dx = typeof v.dx !== 'undefined' ? v.dx : title.dx; 133 | title.dy = typeof v.dy !== 'undefined' ? v.dy : title.dy; 134 | title.transform = typeof v.transform !== 'undefined' ? v.transform : title.transform; 135 | title.anchor = typeof v.anchor !== 'undefined' ? v.anchor : title.anchor; 136 | title.text = typeof v.text !== 'undefined' ? v.text : title.text; 137 | return generator; 138 | }; 139 | 140 | return generator; 141 | }; 142 | 143 | module.exports = axes; 144 | -------------------------------------------------------------------------------- /public/vis/components/axis/rotate.js: -------------------------------------------------------------------------------- 1 | var d3 = require('d3'); 2 | var truncate = require('plugins/heatmap/vis/components/axis/truncate'); 3 | 4 | function rotate() { 5 | var axisLength = 100; 6 | var measure = 'width'; 7 | var labelPadding = 5; 8 | var truncateLength = 10; 9 | var text = {}; 10 | 11 | function component(g) { 12 | g.each(function () { 13 | var ticks = d3.select(this).selectAll('.tick text'); 14 | var numOfTicks = ticks[0].length; 15 | var maxTickLabelLength = (axisLength / numOfTicks) - labelPadding; 16 | var isRotated = false; 17 | 18 | ticks.each(function () { 19 | var labelLength = this.getBBox()[measure]; 20 | if (labelLength >= maxTickLabelLength) { 21 | isRotated = true; 22 | } 23 | }); 24 | 25 | // Rotate and truncate 26 | if (isRotated) { 27 | ticks 28 | .attr('transform', text.transform || 'translate(0,0)rotate(-45)') 29 | .attr('x', text.x || 0) 30 | .attr('y', text.y || 6) 31 | .attr('dx', text.dx || '') 32 | .attr('dy', text.dy || '.71em') 33 | .style('text-anchor', text.anchor || 'end'); 34 | 35 | // Truncation logic goes here 36 | ticks.each(function () { 37 | d3.select(this).call(truncate().maxCharLength(truncateLength)); 38 | }); 39 | } else { 40 | // Default transform 41 | ticks.attr('transform', text.defaultTransform || 'translate(0,0)'); 42 | } 43 | }); 44 | } 45 | 46 | // Public API 47 | component.axisLength = function (_) { 48 | if (!arguments.length) return axisLength; 49 | axisLength = typeof _ === 'number' ? _ : axisLength; 50 | return component; 51 | }; 52 | 53 | component.measure = function (_) { 54 | if (!arguments.length) return measure; 55 | measure = typeof _ === 'string' ? _ : measure; 56 | return component; 57 | }; 58 | 59 | component.labelPadding = function (_) { 60 | if (!arguments.length) return labelPadding; 61 | labelPadding = typeof _ === 'number' ? _ : labelPadding; 62 | return component; 63 | }; 64 | 65 | component.truncateLength = function (_) { 66 | if (!arguments.length) return truncateLength; 67 | truncateLength = typeof _ === 'number' ? _ : truncateLength; 68 | return component; 69 | }; 70 | 71 | component.text = function (_) { 72 | if (!arguments.length) return text; 73 | text.transform = typeof _.transform !== 'undefined' ? _.transform : text.transform; 74 | text.defaultTransform = typeof _.defaultTransform !== 'undefined' ? _.defaultTransform : text.defaultTransform; 75 | text.x = typeof _.x !== 'undefined' ? _.x : text.x; 76 | text.y = typeof _.y !== 'undefined' ? _.y : text.y; 77 | text.dx = typeof _.dx !== 'undefined' ? _.dx : text.dx; 78 | text.dy = typeof _.dy !== 'undefined' ? _.dy : text.dy; 79 | text.anchor = typeof _.anchor !== 'undefined' ? _.anchor : text.anchor; 80 | return component; 81 | }; 82 | 83 | return component; 84 | }; 85 | 86 | module.exports = rotate; 87 | -------------------------------------------------------------------------------- /public/vis/components/axis/truncate.js: -------------------------------------------------------------------------------- 1 | var d3 = require('d3'); 2 | 3 | function truncate() { 4 | var maxCharLength = 10; 5 | 6 | function component(text) { 7 | text.each(function () { 8 | var txt = d3.select(this); 9 | var labelCharLength = txt.text().length; 10 | 11 | // Shorten label and append .. 12 | if (labelCharLength > maxCharLength) { 13 | var truncatedLabel = txt.text().slice(0, maxCharLength) + '..'; 14 | txt.text(truncatedLabel); 15 | } 16 | }); 17 | } 18 | 19 | // Public API 20 | component.maxCharLength = function (_) { 21 | if (!arguments.length) return maxCharLength; 22 | maxCharLength = typeof _ === 'number' ? _ : maxCharLength; 23 | return component; 24 | }; 25 | 26 | return component; 27 | }; 28 | 29 | module.exports = truncate; 30 | -------------------------------------------------------------------------------- /public/vis/components/colorbrewer/LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache-Style Software License for ColorBrewer software and ColorBrewer Color 2 | Schemes 3 | 4 | Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The Pennsylvania State 5 | University. 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); you may not 8 | use this file except in compliance with the License. You may obtain a copy of 9 | the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | License for the specific language governing permissions and limitations under 17 | the License. 18 | 19 | Redistribution and use in source and binary forms, with or without 20 | modification, are permitted provided that the following conditions are met: 21 | 22 | 1. Redistributions as source code must retain the above copyright notice, this 23 | list of conditions and the following disclaimer. 24 | 25 | 2. The end-user documentation included with the redistribution, if any, must 26 | include the following acknowledgment: "This product includes color 27 | specifications and designs developed by Cynthia Brewer 28 | (http://colorbrewer.org/)." Alternately, this acknowledgment may appear in the 29 | software itself, if and wherever such third-party acknowledgments normally 30 | appear. 31 | 32 | 4. The name "ColorBrewer" must not be used to endorse or promote products 33 | derived from this software without prior written permission. For written 34 | permission, please contact Cynthia Brewer at cbrewer@psu.edu. 35 | 36 | 5. Products derived from this software may not be called "ColorBrewer", nor 37 | may "ColorBrewer" appear in their name, without prior written permission of 38 | Cynthia Brewer. 39 | -------------------------------------------------------------------------------- /public/vis/components/colorbrewer/colorbrewer.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | YlGn: { 3 | 3: ["#f7fcb9","#addd8e","#31a354"], 4 | 4: ["#ffffcc","#c2e699","#78c679","#238443"], 5 | 5: ["#ffffcc","#c2e699","#78c679","#31a354","#006837"], 6 | 6: ["#ffffcc","#d9f0a3","#addd8e","#78c679","#31a354","#006837"], 7 | 7: ["#ffffcc","#d9f0a3","#addd8e","#78c679","#41ab5d","#238443","#005a32"], 8 | 8: ["#ffffe5","#f7fcb9","#d9f0a3","#addd8e","#78c679","#41ab5d","#238443","#005a32"], 9 | 9: ["#ffffe5","#f7fcb9","#d9f0a3","#addd8e","#78c679","#41ab5d","#238443","#006837","#004529"] 10 | }, 11 | YlGnBu: { 12 | 3: ["#edf8b1","#7fcdbb","#2c7fb8"], 13 | 4: ["#ffffcc","#a1dab4","#41b6c4","#225ea8"], 14 | 5: ["#ffffcc","#a1dab4","#41b6c4","#2c7fb8","#253494"], 15 | 6: ["#ffffcc","#c7e9b4","#7fcdbb","#41b6c4","#2c7fb8","#253494"], 16 | 7: ["#ffffcc","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#0c2c84"], 17 | 8: ["#ffffd9","#edf8b1","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#0c2c84"], 18 | 9: ["#ffffd9","#edf8b1","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#253494","#081d58"] 19 | }, 20 | GnBu: { 21 | 3: ["#e0f3db","#a8ddb5","#43a2ca"], 22 | 4: ["#f0f9e8","#bae4bc","#7bccc4","#2b8cbe"], 23 | 5: ["#f0f9e8","#bae4bc","#7bccc4","#43a2ca","#0868ac"], 24 | 6: ["#f0f9e8","#ccebc5","#a8ddb5","#7bccc4","#43a2ca","#0868ac"], 25 | 7: ["#f0f9e8","#ccebc5","#a8ddb5","#7bccc4","#4eb3d3","#2b8cbe","#08589e"], 26 | 8: ["#f7fcf0","#e0f3db","#ccebc5","#a8ddb5","#7bccc4","#4eb3d3","#2b8cbe","#08589e"], 27 | 9: ["#f7fcf0","#e0f3db","#ccebc5","#a8ddb5","#7bccc4","#4eb3d3","#2b8cbe","#0868ac","#084081"] 28 | }, 29 | BuGn: { 30 | 3: ["#e5f5f9","#99d8c9","#2ca25f"], 31 | 4: ["#edf8fb","#b2e2e2","#66c2a4","#238b45"], 32 | 5: ["#edf8fb","#b2e2e2","#66c2a4","#2ca25f","#006d2c"], 33 | 6: ["#edf8fb","#ccece6","#99d8c9","#66c2a4","#2ca25f","#006d2c"], 34 | 7: ["#edf8fb","#ccece6","#99d8c9","#66c2a4","#41ae76","#238b45","#005824"], 35 | 8: ["#f7fcfd","#e5f5f9","#ccece6","#99d8c9","#66c2a4","#41ae76","#238b45","#005824"], 36 | 9: ["#f7fcfd","#e5f5f9","#ccece6","#99d8c9","#66c2a4","#41ae76","#238b45","#006d2c","#00441b"] 37 | }, 38 | PuBuGn: { 39 | 3: ["#ece2f0","#a6bddb","#1c9099"], 40 | 4: ["#f6eff7","#bdc9e1","#67a9cf","#02818a"], 41 | 5: ["#f6eff7","#bdc9e1","#67a9cf","#1c9099","#016c59"], 42 | 6: ["#f6eff7","#d0d1e6","#a6bddb","#67a9cf","#1c9099","#016c59"], 43 | 7: ["#f6eff7","#d0d1e6","#a6bddb","#67a9cf","#3690c0","#02818a","#016450"], 44 | 8: ["#fff7fb","#ece2f0","#d0d1e6","#a6bddb","#67a9cf","#3690c0","#02818a","#016450"], 45 | 9: ["#fff7fb","#ece2f0","#d0d1e6","#a6bddb","#67a9cf","#3690c0","#02818a","#016c59","#014636"] 46 | }, 47 | PuBu: { 48 | 3: ["#ece7f2","#a6bddb","#2b8cbe"], 49 | 4: ["#f1eef6","#bdc9e1","#74a9cf","#0570b0"], 50 | 5: ["#f1eef6","#bdc9e1","#74a9cf","#2b8cbe","#045a8d"], 51 | 6: ["#f1eef6","#d0d1e6","#a6bddb","#74a9cf","#2b8cbe","#045a8d"], 52 | 7: ["#f1eef6","#d0d1e6","#a6bddb","#74a9cf","#3690c0","#0570b0","#034e7b"], 53 | 8: ["#fff7fb","#ece7f2","#d0d1e6","#a6bddb","#74a9cf","#3690c0","#0570b0","#034e7b"], 54 | 9: ["#fff7fb","#ece7f2","#d0d1e6","#a6bddb","#74a9cf","#3690c0","#0570b0","#045a8d","#023858"] 55 | }, 56 | BuPu: { 57 | 3: ["#e0ecf4","#9ebcda","#8856a7"], 58 | 4: ["#edf8fb","#b3cde3","#8c96c6","#88419d"], 59 | 5: ["#edf8fb","#b3cde3","#8c96c6","#8856a7","#810f7c"], 60 | 6: ["#edf8fb","#bfd3e6","#9ebcda","#8c96c6","#8856a7","#810f7c"], 61 | 7: ["#edf8fb","#bfd3e6","#9ebcda","#8c96c6","#8c6bb1","#88419d","#6e016b"], 62 | 8: ["#f7fcfd","#e0ecf4","#bfd3e6","#9ebcda","#8c96c6","#8c6bb1","#88419d","#6e016b"], 63 | 9: ["#f7fcfd","#e0ecf4","#bfd3e6","#9ebcda","#8c96c6","#8c6bb1","#88419d","#810f7c","#4d004b"] 64 | }, 65 | RdPu: { 66 | 3: ["#fde0dd","#fa9fb5","#c51b8a"], 67 | 4: ["#feebe2","#fbb4b9","#f768a1","#ae017e"], 68 | 5: ["#feebe2","#fbb4b9","#f768a1","#c51b8a","#7a0177"], 69 | 6: ["#feebe2","#fcc5c0","#fa9fb5","#f768a1","#c51b8a","#7a0177"], 70 | 7: ["#feebe2","#fcc5c0","#fa9fb5","#f768a1","#dd3497","#ae017e","#7a0177"], 71 | 8: ["#fff7f3","#fde0dd","#fcc5c0","#fa9fb5","#f768a1","#dd3497","#ae017e","#7a0177"], 72 | 9: ["#fff7f3","#fde0dd","#fcc5c0","#fa9fb5","#f768a1","#dd3497","#ae017e","#7a0177","#49006a"] 73 | }, 74 | PuRd: { 75 | 3: ["#e7e1ef","#c994c7","#dd1c77"], 76 | 4: ["#f1eef6","#d7b5d8","#df65b0","#ce1256"], 77 | 5: ["#f1eef6","#d7b5d8","#df65b0","#dd1c77","#980043"], 78 | 6: ["#f1eef6","#d4b9da","#c994c7","#df65b0","#dd1c77","#980043"], 79 | 7: ["#f1eef6","#d4b9da","#c994c7","#df65b0","#e7298a","#ce1256","#91003f"], 80 | 8: ["#f7f4f9","#e7e1ef","#d4b9da","#c994c7","#df65b0","#e7298a","#ce1256","#91003f"], 81 | 9: ["#f7f4f9","#e7e1ef","#d4b9da","#c994c7","#df65b0","#e7298a","#ce1256","#980043","#67001f"] 82 | }, 83 | OrRd: { 84 | 3: ["#fee8c8","#fdbb84","#e34a33"], 85 | 4: ["#fef0d9","#fdcc8a","#fc8d59","#d7301f"], 86 | 5: ["#fef0d9","#fdcc8a","#fc8d59","#e34a33","#b30000"], 87 | 6: ["#fef0d9","#fdd49e","#fdbb84","#fc8d59","#e34a33","#b30000"], 88 | 7: ["#fef0d9","#fdd49e","#fdbb84","#fc8d59","#ef6548","#d7301f","#990000"], 89 | 8: ["#fff7ec","#fee8c8","#fdd49e","#fdbb84","#fc8d59","#ef6548","#d7301f","#990000"], 90 | 9: ["#fff7ec","#fee8c8","#fdd49e","#fdbb84","#fc8d59","#ef6548","#d7301f","#b30000","#7f0000"] 91 | }, 92 | YlOrRd: { 93 | 3: ["#ffeda0","#feb24c","#f03b20"], 94 | 4: ["#ffffb2","#fecc5c","#fd8d3c","#e31a1c"], 95 | 5: ["#ffffb2","#fecc5c","#fd8d3c","#f03b20","#bd0026"], 96 | 6: ["#ffffb2","#fed976","#feb24c","#fd8d3c","#f03b20","#bd0026"], 97 | 7: ["#ffffb2","#fed976","#feb24c","#fd8d3c","#fc4e2a","#e31a1c","#b10026"], 98 | 8: ["#ffffcc","#ffeda0","#fed976","#feb24c","#fd8d3c","#fc4e2a","#e31a1c","#b10026"], 99 | 9: ["#ffffcc","#ffeda0","#fed976","#feb24c","#fd8d3c","#fc4e2a","#e31a1c","#bd0026","#800026"] 100 | }, 101 | YlOrBr: { 102 | 3: ["#fff7bc","#fec44f","#d95f0e"], 103 | 4: ["#ffffd4","#fed98e","#fe9929","#cc4c02"], 104 | 5: ["#ffffd4","#fed98e","#fe9929","#d95f0e","#993404"], 105 | 6: ["#ffffd4","#fee391","#fec44f","#fe9929","#d95f0e","#993404"], 106 | 7: ["#ffffd4","#fee391","#fec44f","#fe9929","#ec7014","#cc4c02","#8c2d04"], 107 | 8: ["#ffffe5","#fff7bc","#fee391","#fec44f","#fe9929","#ec7014","#cc4c02","#8c2d04"], 108 | 9: ["#ffffe5","#fff7bc","#fee391","#fec44f","#fe9929","#ec7014","#cc4c02","#993404","#662506"] 109 | }, 110 | Purples: { 111 | 3: ["#efedf5","#bcbddc","#756bb1"], 112 | 4: ["#f2f0f7","#cbc9e2","#9e9ac8","#6a51a3"], 113 | 5: ["#f2f0f7","#cbc9e2","#9e9ac8","#756bb1","#54278f"], 114 | 6: ["#f2f0f7","#dadaeb","#bcbddc","#9e9ac8","#756bb1","#54278f"], 115 | 7: ["#f2f0f7","#dadaeb","#bcbddc","#9e9ac8","#807dba","#6a51a3","#4a1486"], 116 | 8: ["#fcfbfd","#efedf5","#dadaeb","#bcbddc","#9e9ac8","#807dba","#6a51a3","#4a1486"], 117 | 9: ["#fcfbfd","#efedf5","#dadaeb","#bcbddc","#9e9ac8","#807dba","#6a51a3","#54278f","#3f007d"] 118 | }, 119 | Blues: { 120 | 3: ["#deebf7","#9ecae1","#3182bd"], 121 | 4: ["#eff3ff","#bdd7e7","#6baed6","#2171b5"], 122 | 5: ["#eff3ff","#bdd7e7","#6baed6","#3182bd","#08519c"], 123 | 6: ["#eff3ff","#c6dbef","#9ecae1","#6baed6","#3182bd","#08519c"], 124 | 7: ["#eff3ff","#c6dbef","#9ecae1","#6baed6","#4292c6","#2171b5","#084594"], 125 | 8: ["#f7fbff","#deebf7","#c6dbef","#9ecae1","#6baed6","#4292c6","#2171b5","#084594"], 126 | 9: ["#f7fbff","#deebf7","#c6dbef","#9ecae1","#6baed6","#4292c6","#2171b5","#08519c","#08306b"] 127 | }, 128 | Greens: { 129 | 3: ["#e5f5e0","#a1d99b","#31a354"], 130 | 4: ["#edf8e9","#bae4b3","#74c476","#238b45"], 131 | 5: ["#edf8e9","#bae4b3","#74c476","#31a354","#006d2c"], 132 | 6: ["#edf8e9","#c7e9c0","#a1d99b","#74c476","#31a354","#006d2c"], 133 | 7: ["#edf8e9","#c7e9c0","#a1d99b","#74c476","#41ab5d","#238b45","#005a32"], 134 | 8: ["#f7fcf5","#e5f5e0","#c7e9c0","#a1d99b","#74c476","#41ab5d","#238b45","#005a32"], 135 | 9: ["#f7fcf5","#e5f5e0","#c7e9c0","#a1d99b","#74c476","#41ab5d","#238b45","#006d2c","#00441b"] 136 | }, 137 | Oranges: { 138 | 3: ["#fee6ce","#fdae6b","#e6550d"], 139 | 4: ["#feedde","#fdbe85","#fd8d3c","#d94701"], 140 | 5: ["#feedde","#fdbe85","#fd8d3c","#e6550d","#a63603"], 141 | 6: ["#feedde","#fdd0a2","#fdae6b","#fd8d3c","#e6550d","#a63603"], 142 | 7: ["#feedde","#fdd0a2","#fdae6b","#fd8d3c","#f16913","#d94801","#8c2d04"], 143 | 8: ["#fff5eb","#fee6ce","#fdd0a2","#fdae6b","#fd8d3c","#f16913","#d94801","#8c2d04"], 144 | 9: ["#fff5eb","#fee6ce","#fdd0a2","#fdae6b","#fd8d3c","#f16913","#d94801","#a63603","#7f2704"] 145 | }, 146 | Reds: { 147 | 3: ["#fee0d2","#fc9272","#de2d26"], 148 | 4: ["#fee5d9","#fcae91","#fb6a4a","#cb181d"], 149 | 5: ["#fee5d9","#fcae91","#fb6a4a","#de2d26","#a50f15"], 150 | 6: ["#fee5d9","#fcbba1","#fc9272","#fb6a4a","#de2d26","#a50f15"], 151 | 7: ["#fee5d9","#fcbba1","#fc9272","#fb6a4a","#ef3b2c","#cb181d","#99000d"], 152 | 8: ["#fff5f0","#fee0d2","#fcbba1","#fc9272","#fb6a4a","#ef3b2c","#cb181d","#99000d"], 153 | 9: ["#fff5f0","#fee0d2","#fcbba1","#fc9272","#fb6a4a","#ef3b2c","#cb181d","#a50f15","#67000d"] 154 | }, 155 | Greys: { 156 | 3: ["#f0f0f0","#bdbdbd","#636363"], 157 | 4: ["#f7f7f7","#cccccc","#969696","#525252"], 158 | 5: ["#f7f7f7","#cccccc","#969696","#636363","#252525"], 159 | 6: ["#f7f7f7","#d9d9d9","#bdbdbd","#969696","#636363","#252525"], 160 | 7: ["#f7f7f7","#d9d9d9","#bdbdbd","#969696","#737373","#525252","#252525"], 161 | 8: ["#ffffff","#f0f0f0","#d9d9d9","#bdbdbd","#969696","#737373","#525252","#252525"], 162 | 9: ["#ffffff","#f0f0f0","#d9d9d9","#bdbdbd","#969696","#737373","#525252","#252525","#000000"] 163 | }, 164 | PuOr: { 165 | 3: ["#f1a340","#f7f7f7","#998ec3"], 166 | 4: ["#e66101","#fdb863","#b2abd2","#5e3c99"], 167 | 5: ["#e66101","#fdb863","#f7f7f7","#b2abd2","#5e3c99"], 168 | 6: ["#b35806","#f1a340","#fee0b6","#d8daeb","#998ec3","#542788"], 169 | 7: ["#b35806","#f1a340","#fee0b6","#f7f7f7","#d8daeb","#998ec3","#542788"], 170 | 8: ["#b35806","#e08214","#fdb863","#fee0b6","#d8daeb","#b2abd2","#8073ac","#542788"], 171 | 9: ["#b35806","#e08214","#fdb863","#fee0b6","#f7f7f7","#d8daeb","#b2abd2","#8073ac","#542788"], 172 | 10: ["#7f3b08","#b35806","#e08214","#fdb863","#fee0b6","#d8daeb","#b2abd2","#8073ac","#542788","#2d004b"], 173 | 11: ["#7f3b08","#b35806","#e08214","#fdb863","#fee0b6","#f7f7f7","#d8daeb","#b2abd2","#8073ac","#542788","#2d004b"] 174 | }, 175 | BrBG: { 176 | 3: ["#d8b365","#f5f5f5","#5ab4ac"], 177 | 4: ["#a6611a","#dfc27d","#80cdc1","#018571"], 178 | 5: ["#a6611a","#dfc27d","#f5f5f5","#80cdc1","#018571"], 179 | 6: ["#8c510a","#d8b365","#f6e8c3","#c7eae5","#5ab4ac","#01665e"], 180 | 7: ["#8c510a","#d8b365","#f6e8c3","#f5f5f5","#c7eae5","#5ab4ac","#01665e"], 181 | 8: ["#8c510a","#bf812d","#dfc27d","#f6e8c3","#c7eae5","#80cdc1","#35978f","#01665e"], 182 | 9: ["#8c510a","#bf812d","#dfc27d","#f6e8c3","#f5f5f5","#c7eae5","#80cdc1","#35978f","#01665e"], 183 | 10: ["#543005","#8c510a","#bf812d","#dfc27d","#f6e8c3","#c7eae5","#80cdc1","#35978f","#01665e","#003c30"], 184 | 11: ["#543005","#8c510a","#bf812d","#dfc27d","#f6e8c3","#f5f5f5","#c7eae5","#80cdc1","#35978f","#01665e","#003c30"] 185 | }, 186 | PRGn: { 187 | 3: ["#af8dc3","#f7f7f7","#7fbf7b"], 188 | 4: ["#7b3294","#c2a5cf","#a6dba0","#008837"], 189 | 5: ["#7b3294","#c2a5cf","#f7f7f7","#a6dba0","#008837"], 190 | 6: ["#762a83","#af8dc3","#e7d4e8","#d9f0d3","#7fbf7b","#1b7837"], 191 | 7: ["#762a83","#af8dc3","#e7d4e8","#f7f7f7","#d9f0d3","#7fbf7b","#1b7837"], 192 | 8: ["#762a83","#9970ab","#c2a5cf","#e7d4e8","#d9f0d3","#a6dba0","#5aae61","#1b7837"], 193 | 9: ["#762a83","#9970ab","#c2a5cf","#e7d4e8","#f7f7f7","#d9f0d3","#a6dba0","#5aae61","#1b7837"], 194 | 10: ["#40004b","#762a83","#9970ab","#c2a5cf","#e7d4e8","#d9f0d3","#a6dba0","#5aae61","#1b7837","#00441b"], 195 | 11: ["#40004b","#762a83","#9970ab","#c2a5cf","#e7d4e8","#f7f7f7","#d9f0d3","#a6dba0","#5aae61","#1b7837","#00441b"] 196 | }, 197 | PiYG: { 198 | 3: ["#e9a3c9","#f7f7f7","#a1d76a"], 199 | 4: ["#d01c8b","#f1b6da","#b8e186","#4dac26"], 200 | 5: ["#d01c8b","#f1b6da","#f7f7f7","#b8e186","#4dac26"], 201 | 6: ["#c51b7d","#e9a3c9","#fde0ef","#e6f5d0","#a1d76a","#4d9221"], 202 | 7: ["#c51b7d","#e9a3c9","#fde0ef","#f7f7f7","#e6f5d0","#a1d76a","#4d9221"], 203 | 8: ["#c51b7d","#de77ae","#f1b6da","#fde0ef","#e6f5d0","#b8e186","#7fbc41","#4d9221"], 204 | 9: ["#c51b7d","#de77ae","#f1b6da","#fde0ef","#f7f7f7","#e6f5d0","#b8e186","#7fbc41","#4d9221"], 205 | 10: ["#8e0152","#c51b7d","#de77ae","#f1b6da","#fde0ef","#e6f5d0","#b8e186","#7fbc41","#4d9221","#276419"], 206 | 11: ["#8e0152","#c51b7d","#de77ae","#f1b6da","#fde0ef","#f7f7f7","#e6f5d0","#b8e186","#7fbc41","#4d9221","#276419"] 207 | }, 208 | RdBu: { 209 | 3: ["#ef8a62","#f7f7f7","#67a9cf"], 210 | 4: ["#ca0020","#f4a582","#92c5de","#0571b0"], 211 | 5: ["#ca0020","#f4a582","#f7f7f7","#92c5de","#0571b0"], 212 | 6: ["#b2182b","#ef8a62","#fddbc7","#d1e5f0","#67a9cf","#2166ac"], 213 | 7: ["#b2182b","#ef8a62","#fddbc7","#f7f7f7","#d1e5f0","#67a9cf","#2166ac"], 214 | 8: ["#b2182b","#d6604d","#f4a582","#fddbc7","#d1e5f0","#92c5de","#4393c3","#2166ac"], 215 | 9: ["#b2182b","#d6604d","#f4a582","#fddbc7","#f7f7f7","#d1e5f0","#92c5de","#4393c3","#2166ac"], 216 | 10: ["#67001f","#b2182b","#d6604d","#f4a582","#fddbc7","#d1e5f0","#92c5de","#4393c3","#2166ac","#053061"], 217 | 11: ["#67001f","#b2182b","#d6604d","#f4a582","#fddbc7","#f7f7f7","#d1e5f0","#92c5de","#4393c3","#2166ac","#053061"] 218 | }, 219 | RdGy: { 220 | 3: ["#ef8a62","#ffffff","#999999"], 221 | 4: ["#ca0020","#f4a582","#bababa","#404040"], 222 | 5: ["#ca0020","#f4a582","#ffffff","#bababa","#404040"], 223 | 6: ["#b2182b","#ef8a62","#fddbc7","#e0e0e0","#999999","#4d4d4d"], 224 | 7: ["#b2182b","#ef8a62","#fddbc7","#ffffff","#e0e0e0","#999999","#4d4d4d"], 225 | 8: ["#b2182b","#d6604d","#f4a582","#fddbc7","#e0e0e0","#bababa","#878787","#4d4d4d"], 226 | 9: ["#b2182b","#d6604d","#f4a582","#fddbc7","#ffffff","#e0e0e0","#bababa","#878787","#4d4d4d"], 227 | 10: ["#67001f","#b2182b","#d6604d","#f4a582","#fddbc7","#e0e0e0","#bababa","#878787","#4d4d4d","#1a1a1a"], 228 | 11: ["#67001f","#b2182b","#d6604d","#f4a582","#fddbc7","#ffffff","#e0e0e0","#bababa","#878787","#4d4d4d","#1a1a1a"] 229 | }, 230 | RdYlBu: { 231 | 3: ["#fc8d59","#ffffbf","#91bfdb"], 232 | 4: ["#d7191c","#fdae61","#abd9e9","#2c7bb6"], 233 | 5: ["#d7191c","#fdae61","#ffffbf","#abd9e9","#2c7bb6"], 234 | 6: ["#d73027","#fc8d59","#fee090","#e0f3f8","#91bfdb","#4575b4"], 235 | 7: ["#d73027","#fc8d59","#fee090","#ffffbf","#e0f3f8","#91bfdb","#4575b4"], 236 | 8: ["#d73027","#f46d43","#fdae61","#fee090","#e0f3f8","#abd9e9","#74add1","#4575b4"], 237 | 9: ["#d73027","#f46d43","#fdae61","#fee090","#ffffbf","#e0f3f8","#abd9e9","#74add1","#4575b4"], 238 | 10: ["#a50026","#d73027","#f46d43","#fdae61","#fee090","#e0f3f8","#abd9e9","#74add1","#4575b4","#313695"], 239 | 11: ["#a50026","#d73027","#f46d43","#fdae61","#fee090","#ffffbf","#e0f3f8","#abd9e9","#74add1","#4575b4","#313695"] 240 | }, 241 | Spectral: { 242 | 3: ["#fc8d59","#ffffbf","#99d594"], 243 | 4: ["#d7191c","#fdae61","#abdda4","#2b83ba"], 244 | 5: ["#d7191c","#fdae61","#ffffbf","#abdda4","#2b83ba"], 245 | 6: ["#d53e4f","#fc8d59","#fee08b","#e6f598","#99d594","#3288bd"], 246 | 7: ["#d53e4f","#fc8d59","#fee08b","#ffffbf","#e6f598","#99d594","#3288bd"], 247 | 8: ["#d53e4f","#f46d43","#fdae61","#fee08b","#e6f598","#abdda4","#66c2a5","#3288bd"], 248 | 9: ["#d53e4f","#f46d43","#fdae61","#fee08b","#ffffbf","#e6f598","#abdda4","#66c2a5","#3288bd"], 249 | 10: ["#9e0142","#d53e4f","#f46d43","#fdae61","#fee08b","#e6f598","#abdda4","#66c2a5","#3288bd","#5e4fa2"], 250 | 11: ["#9e0142","#d53e4f","#f46d43","#fdae61","#fee08b","#ffffbf","#e6f598","#abdda4","#66c2a5","#3288bd","#5e4fa2"] 251 | }, 252 | RdYlGn: { 253 | 3: ["#fc8d59","#ffffbf","#91cf60"], 254 | 4: ["#d7191c","#fdae61","#a6d96a","#1a9641"], 255 | 5: ["#d7191c","#fdae61","#ffffbf","#a6d96a","#1a9641"], 256 | 6: ["#d73027","#fc8d59","#fee08b","#d9ef8b","#91cf60","#1a9850"], 257 | 7: ["#d73027","#fc8d59","#fee08b","#ffffbf","#d9ef8b","#91cf60","#1a9850"], 258 | 8: ["#d73027","#f46d43","#fdae61","#fee08b","#d9ef8b","#a6d96a","#66bd63","#1a9850"], 259 | 9: ["#d73027","#f46d43","#fdae61","#fee08b","#ffffbf","#d9ef8b","#a6d96a","#66bd63","#1a9850"], 260 | 10: ["#a50026","#d73027","#f46d43","#fdae61","#fee08b","#d9ef8b","#a6d96a","#66bd63","#1a9850","#006837"], 261 | 11: ["#a50026","#d73027","#f46d43","#fdae61","#fee08b","#ffffbf","#d9ef8b","#a6d96a","#66bd63","#1a9850","#006837"] 262 | }, 263 | Accent: { 264 | 3: ["#7fc97f","#beaed4","#fdc086"], 265 | 4: ["#7fc97f","#beaed4","#fdc086","#ffff99"], 266 | 5: ["#7fc97f","#beaed4","#fdc086","#ffff99","#386cb0"], 267 | 6: ["#7fc97f","#beaed4","#fdc086","#ffff99","#386cb0","#f0027f"], 268 | 7: ["#7fc97f","#beaed4","#fdc086","#ffff99","#386cb0","#f0027f","#bf5b17"], 269 | 8: ["#7fc97f","#beaed4","#fdc086","#ffff99","#386cb0","#f0027f","#bf5b17","#666666"] 270 | }, 271 | Dark2: { 272 | 3: ["#1b9e77","#d95f02","#7570b3"], 273 | 4: ["#1b9e77","#d95f02","#7570b3","#e7298a"], 274 | 5: ["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e"], 275 | 6: ["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e","#e6ab02"], 276 | 7: ["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e","#e6ab02","#a6761d"], 277 | 8: ["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e","#e6ab02","#a6761d","#666666"] 278 | }, 279 | Paired: { 280 | 3: ["#a6cee3","#1f78b4","#b2df8a"], 281 | 4: ["#a6cee3","#1f78b4","#b2df8a","#33a02c"], 282 | 5: ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99"], 283 | 6: ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c"], 284 | 7: ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f"], 285 | 8: ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00"], 286 | 9: ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6"], 287 | 10: ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6","#6a3d9a"], 288 | 11: ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6","#6a3d9a","#ffff99"], 289 | 12: ["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6","#6a3d9a","#ffff99","#b15928"] 290 | }, 291 | Pastel1: { 292 | 3: ["#fbb4ae","#b3cde3","#ccebc5"], 293 | 4: ["#fbb4ae","#b3cde3","#ccebc5","#decbe4"], 294 | 5: ["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6"], 295 | 6: ["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6","#ffffcc"], 296 | 7: ["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6","#ffffcc","#e5d8bd"], 297 | 8: ["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6","#ffffcc","#e5d8bd","#fddaec"], 298 | 9: ["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6","#ffffcc","#e5d8bd","#fddaec","#f2f2f2"] 299 | }, 300 | Pastel2: { 301 | 3: ["#b3e2cd","#fdcdac","#cbd5e8"], 302 | 4: ["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4"], 303 | 5: ["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4","#e6f5c9"], 304 | 6: ["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4","#e6f5c9","#fff2ae"], 305 | 7: ["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4","#e6f5c9","#fff2ae","#f1e2cc"], 306 | 8: ["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4","#e6f5c9","#fff2ae","#f1e2cc","#cccccc"] 307 | }, 308 | Set1: { 309 | 3: ["#e41a1c","#377eb8","#4daf4a"], 310 | 4: ["#e41a1c","#377eb8","#4daf4a","#984ea3"], 311 | 5: ["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00"], 312 | 6: ["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00","#ffff33"], 313 | 7: ["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00","#ffff33","#a65628"], 314 | 8: ["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00","#ffff33","#a65628","#f781bf"], 315 | 9: ["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00","#ffff33","#a65628","#f781bf","#999999"] 316 | }, 317 | Set2: { 318 | 3: ["#66c2a5","#fc8d62","#8da0cb"], 319 | 4: ["#66c2a5","#fc8d62","#8da0cb","#e78ac3"], 320 | 5: ["#66c2a5","#fc8d62","#8da0cb","#e78ac3","#a6d854"], 321 | 6: ["#66c2a5","#fc8d62","#8da0cb","#e78ac3","#a6d854","#ffd92f"], 322 | 7: ["#66c2a5","#fc8d62","#8da0cb","#e78ac3","#a6d854","#ffd92f","#e5c494"], 323 | 8: ["#66c2a5","#fc8d62","#8da0cb","#e78ac3","#a6d854","#ffd92f","#e5c494","#b3b3b3"] 324 | }, 325 | Set3: { 326 | 3: ["#8dd3c7","#ffffb3","#bebada"], 327 | 4: ["#8dd3c7","#ffffb3","#bebada","#fb8072"], 328 | 5: ["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3"], 329 | 6: ["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462"], 330 | 7: ["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69"], 331 | 8: ["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5"], 332 | 9: ["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5","#d9d9d9"], 333 | 10: ["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5","#d9d9d9","#bc80bd"], 334 | 11: ["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5","#d9d9d9","#bc80bd","#ccebc5"], 335 | 12: ["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5","#d9d9d9","#bc80bd","#ccebc5","#ffed6f"] 336 | } 337 | }; 338 | -------------------------------------------------------------------------------- /public/vis/components/control/events.js: -------------------------------------------------------------------------------- 1 | var d3 = require('d3'); 2 | var _ = require('lodash'); 3 | 4 | // Adds event listeners to DOM elements 5 | function events() { 6 | var processor = function (e) { return e; }; 7 | var listeners = {}; 8 | var element; 9 | 10 | function control(selection) { 11 | selection.each(function () { 12 | if (!element) { 13 | element = d3.select(this); 14 | } 15 | 16 | d3.entries(listeners).forEach(function (d) { 17 | element.on(d.key, function () { 18 | d3.event.stopPropagation(); // => event.stopPropagation() 19 | _.forEach(d.value, function (listener) { 20 | listener.call(this, processor(d3.event)); 21 | }); 22 | }); 23 | }); 24 | }); 25 | } 26 | 27 | // Public API 28 | control.processor = function (v) { 29 | if (!arguments.length) { return processor; } 30 | processor = _.isFunction(v) ? v : processor; 31 | return control; 32 | }; 33 | 34 | control.listeners = function (v) { 35 | if (!arguments.length) { return listeners; } 36 | listeners = _.isPlainObject(v) ? v : listeners; 37 | return control; 38 | }; 39 | 40 | return control; 41 | }; 42 | 43 | module.exports = events; 44 | -------------------------------------------------------------------------------- /public/vis/components/elements/g.js: -------------------------------------------------------------------------------- 1 | var d3 = require('d3'); 2 | var _ = require('lodash'); 3 | 4 | function gGenerator() { 5 | var cssClass = 'group'; 6 | var transform = 'translate(0,0)'; 7 | 8 | function generator(selection) { 9 | selection.each(function (data, index) { 10 | var g = d3.select(this).selectAll('g.' + cssClass) 11 | .data(data); 12 | 13 | g.exit().remove(); 14 | 15 | g.enter().append('g') 16 | .attr('class', cssClass); 17 | 18 | g.attr('transform', transform); 19 | }); 20 | } 21 | 22 | // Public API 23 | generator.cssClass = function (v) { 24 | if (!arguments.length) { return cssClass; } 25 | cssClass = _.isString(v) ? v : cssClass; 26 | return generator; 27 | }; 28 | 29 | generator.transform = function (v) { 30 | if (!arguments.length) { return transform; } 31 | transform = d3.functor(v); 32 | return generator; 33 | }; 34 | 35 | return generator; 36 | } 37 | 38 | module.exports = gGenerator; 39 | -------------------------------------------------------------------------------- /public/vis/components/elements/rect.js: -------------------------------------------------------------------------------- 1 | var d3 = require('d3'); 2 | 3 | function rect() { 4 | var color = d3.scale.category10(); 5 | var x = function (d) { return d.x; }; 6 | var y = function (d) { return d.y; }; 7 | var rx = function (d) { return d.rx || 0; }; 8 | var ry = function (d) { return d.ry || 0; }; 9 | var width = function (d) { return d.width; }; 10 | var height = function (d) { return d.height; }; 11 | var cssClass = 'cell'; 12 | var fill = colorFill; 13 | var stroke = colorFill; 14 | var strokeWidth = 0; 15 | var fillOpacity = 1; 16 | var strokeOpacity; 17 | 18 | function element(selection) { 19 | selection.each(function (data) { 20 | var cells = d3.select(this).selectAll('rect.' + cssClass) 21 | .data(data); 22 | 23 | cells.exit().remove(); 24 | 25 | cells.enter().append('rect') 26 | .attr('class', cssClass); 27 | 28 | cells 29 | .attr('x', x) 30 | .attr('y', y) 31 | .attr('rx', rx) 32 | .attr('ry', ry) 33 | .attr('width', width) 34 | .attr('height', height) 35 | .style('fill', fill) 36 | .style('fill-opacity', fillOpacity) 37 | .style('stroke', stroke) 38 | .style('stroke-width', strokeWidth) 39 | .style('stroke-opacity', strokeOpacity); 40 | }); 41 | } 42 | 43 | function colorFill(d, i) { 44 | return color(i); 45 | } 46 | 47 | // Public API 48 | element.x = function (_) { 49 | if (!arguments.length) return x; 50 | x = d3.functor(_); 51 | return element; 52 | }; 53 | 54 | element.y = function (_) { 55 | if (!arguments.length) return y; 56 | y = d3.functor(_); 57 | return element; 58 | }; 59 | 60 | element.rx = function (_) { 61 | if (!arguments.length) return rx; 62 | rx = d3.functor(_); 63 | return element; 64 | }; 65 | 66 | element.ry = function (_) { 67 | if (!arguments.length) return ry; 68 | ry = d3.functor(_); 69 | return element; 70 | }; 71 | 72 | element.width = function (_) { 73 | if (!arguments.length) return width; 74 | width = d3.functor(_); 75 | return element; 76 | }; 77 | 78 | element.height = function (_) { 79 | if (!arguments.length) return height; 80 | height = d3.functor(_); 81 | return element; 82 | }; 83 | 84 | element.class= function (_) { 85 | if (!arguments.length) return cssClass; 86 | cssClass = _; 87 | return element; 88 | }; 89 | 90 | element.fill = function (_) { 91 | if (!arguments.length) return fill; 92 | fill = _; 93 | return element; 94 | }; 95 | 96 | element.fillOpacity = function (_) { 97 | if (!arguments.length) return fillOpacity; 98 | fillOpacity = _; 99 | return element; 100 | }; 101 | 102 | element.stroke = function (_) { 103 | if (!arguments.length) return stroke; 104 | stroke = _; 105 | return element; 106 | }; 107 | 108 | element.strokeWidth = function (_) { 109 | if (!arguments.length) return strokeWidth; 110 | strokeWidth = _; 111 | return element; 112 | }; 113 | 114 | element.strokeOpacity = function (_) { 115 | if (!arguments.length) return strokeOpacity; 116 | strokeOpacity = _; 117 | return element; 118 | }; 119 | 120 | return element; 121 | }; 122 | 123 | module.exports = rect; 124 | -------------------------------------------------------------------------------- /public/vis/components/elements/text.js: -------------------------------------------------------------------------------- 1 | var d3 = require('d3'); 2 | 3 | function text() { 4 | var x = function (d) { return d.x || 0; }; 5 | var y = function (d) { return d.y || 0; }; 6 | var dx = function (d) { return d.dx || ''; }; 7 | var dy = function (d) { return d.dy || ''; }; 8 | var transform = null; 9 | var cssClass = 'text'; 10 | var fill = '#ffffff'; 11 | var anchor = 'middle'; 12 | var texts = ''; 13 | 14 | function element(selection) { 15 | selection.each(function (data) { 16 | var text = d3.select(this).selectAll('text.' + cssClass) 17 | .data(data); 18 | 19 | text.exit().remove(); 20 | 21 | text.enter().append('text') 22 | .attr('class', cssClass); 23 | 24 | text 25 | .attr('transform', transform) 26 | .attr('x', x) 27 | .attr('y', y) 28 | .attr('dx', dx) 29 | .attr('dy', dy) 30 | .style('fill', fill) 31 | .style('text-anchor', anchor) 32 | .text(texts); 33 | }); 34 | } 35 | 36 | // Public API 37 | element.x = function (_) { 38 | if (!arguments.length) { return x; } 39 | x = d3.functor(_); 40 | return element; 41 | }; 42 | 43 | element.y = function (_) { 44 | if (!arguments.length) { return y; } 45 | y = d3.functor(_); 46 | return element; 47 | }; 48 | 49 | element.dx = function (_) { 50 | if (!arguments.length) { return dx; } 51 | dx = d3.functor(_); 52 | return element; 53 | }; 54 | 55 | element.dy = function (_) { 56 | if (!arguments.length) { return dy; } 57 | dy = d3.functor(_); 58 | return element; 59 | }; 60 | 61 | element.transform = function (_) { 62 | if (!arguments.length) { return transform; } 63 | transform = _; 64 | return element; 65 | }; 66 | 67 | element.class= function (_) { 68 | if (!arguments.length) { return cssClass; } 69 | cssClass = _; 70 | return element; 71 | }; 72 | 73 | element.anchor = function (_) { 74 | if (!arguments.length) { return anchor; } 75 | anchor = _; 76 | return element; 77 | }; 78 | 79 | element.fill = function (_) { 80 | if (!arguments.length) { return fill; } 81 | fill = _; 82 | return element; 83 | }; 84 | 85 | element.text = function (_) { 86 | if (!arguments.length) { return texts; } 87 | texts = _; 88 | return element; 89 | }; 90 | 91 | return element; 92 | }; 93 | 94 | module.exports = text; 95 | -------------------------------------------------------------------------------- /public/vis/components/layout/generator.js: -------------------------------------------------------------------------------- 1 | var d3 = require('d3'); 2 | var attrs = require('plugins/heatmap/vis/components/utils/attrs'); 3 | var baseLayout = require('plugins/heatmap/vis/components/layout/layout'); 4 | var gGenerator = require('plugins/heatmap/vis/components/elements/g'); 5 | 6 | function layoutGenerator() { 7 | var layout = baseLayout(); 8 | var group = gGenerator(); 9 | 10 | function generator(selection) { 11 | selection.each(function (data) { 12 | group.cssClass('chart') 13 | .transform(function (d) { 14 | return 'translate(' + d.dx + ',' + d.dy + ')'; 15 | }); 16 | 17 | d3.select(this) 18 | .datum(layout(data)) 19 | .call(group); 20 | }); 21 | } 22 | 23 | // Public API 24 | generator.attr = attrs(generator)(layout); 25 | 26 | return generator; 27 | } 28 | 29 | module.exports = layoutGenerator; 30 | -------------------------------------------------------------------------------- /public/vis/components/layout/layout.js: -------------------------------------------------------------------------------- 1 | var d3 = require('d3'); 2 | var _ = require('lodash'); 3 | 4 | function formatType(length, type, cols) { 5 | var output = {}; 6 | 7 | switch (type) { 8 | case 'grid': 9 | output.rows = cols ? Math.ceil(length / cols) : Math.round(Math.sqrt(length)); 10 | output.columns = cols || Math.ceil(Math.sqrt(length)); 11 | break; 12 | 13 | case 'columns': 14 | output.rows = 1; 15 | output.columns = length; 16 | break; 17 | 18 | default: 19 | output.rows = length; 20 | output.columns = 1; 21 | break; 22 | } 23 | 24 | return output; 25 | } 26 | 27 | function baseLayout() { 28 | var type = 'grid'; // available types: 'rows', 'columns', 'grid' 29 | var size = [250, 250]; // [width, height] 30 | var rowScale = d3.scale.linear(); 31 | var columnScale = d3.scale.linear(); 32 | var numOfCols = 0; 33 | 34 | function layout(data) { 35 | var format = formatType(data.length, type, numOfCols); 36 | var rows = format.rows; 37 | var columns = format.columns; 38 | var cellWidth = size[0] / columns; 39 | var cellHeight = size[1] / rows; 40 | var cell = 0; 41 | var newData = []; 42 | 43 | rowScale.domain([0, rows]).range([0, size[1]]); 44 | columnScale.domain([0, columns]).range([0, size[0]]); 45 | 46 | d3.range(rows).forEach(function (row) { 47 | d3.range(columns).forEach(function (col) { 48 | var datum = data[cell]; 49 | var obj = { 50 | dx: columnScale(col), 51 | dy: rowScale(row), 52 | width: cellWidth, 53 | height: cellHeight 54 | }; 55 | 56 | function reduce(a, b) { 57 | a[b] = datum[b]; 58 | return a; 59 | } 60 | 61 | if (!datum) { return; } 62 | 63 | // Do not mutate the original data, return a new object 64 | newData.push(Object.keys(datum).reduce(reduce, obj)); 65 | cell += 1; 66 | }); 67 | }); 68 | 69 | return newData; 70 | } 71 | 72 | // Public API 73 | layout.type = function (v) { 74 | if (!arguments.length) { return type; } 75 | type = _.isString(v) ? v : type; 76 | return layout; 77 | }; 78 | 79 | layout.columns = function (v) { 80 | if (!arguments.length) { return numOfCols; } 81 | numOfCols = _.isNumber(v) ? v : numOfCols; 82 | return layout; 83 | }; 84 | 85 | layout.size = function (v) { 86 | if (!arguments.length) { return size; } 87 | size = (_.isArray(v) && _.size(v) === 2 && _.all(v, _.isNumber)) ? v : size; 88 | return layout; 89 | }; 90 | 91 | return layout; 92 | } 93 | 94 | module.exports = baseLayout; 95 | -------------------------------------------------------------------------------- /public/vis/components/legend/legend.js: -------------------------------------------------------------------------------- 1 | var d3 = require('d3'); 2 | var numeral = require('numeral'); 3 | var gGenerator = require('plugins/heatmap/vis/components/elements/g'); 4 | var rectGenerator = require('plugins/heatmap/vis/components/elements/rect'); 5 | var textGenerator = require('plugins/heatmap/vis/components/elements/text'); 6 | 7 | function legend() { 8 | var cssClass = 'legend'; 9 | var transform = 'translate(0,0)'; 10 | var rectWidth = 20; 11 | var rectHeight = 20; 12 | var fill = function (d) { return d.color; }; 13 | var fillOpacity = 1; 14 | var stroke = '#ffffff'; 15 | var strokeWidth = 1; 16 | var strokeOpacity = 1; 17 | var textPadding = 5; 18 | var textAnchor = 'start'; 19 | var textFill = '#848e96'; 20 | var title = 'Legend'; 21 | var numberFormat = '0a'; 22 | var scale = d3.scale.quantize(); 23 | var g = gGenerator(); 24 | var block = gGenerator(); 25 | var legendGBlock = gGenerator(); 26 | var legendTitle = textGenerator(); 27 | var rect = rectGenerator(); 28 | var svgText = textGenerator(); 29 | 30 | function formatNumber(num, format) { 31 | return numeral(num).format(format); 32 | } 33 | 34 | function generator(selection) { 35 | selection.each(function (datum) { 36 | g.cssClass(cssClass).transform(transform); 37 | 38 | legendGBlock.cssClass('legend-title').transform(transform); 39 | legendTitle.x(0).y(-10).dx('').dy('.32em').fill(textFill) 40 | .anchor('start') 41 | .text(title); 42 | 43 | // Add Legend title 44 | d3.select(this) 45 | .datum([[datum]]) 46 | .call(legendGBlock) 47 | .selectAll('g.legend-title') 48 | .call(legendTitle); 49 | 50 | // Adds g elements 51 | d3.select(this) 52 | .datum([datum]) 53 | .call(g) 54 | .selectAll('g.' + cssClass) 55 | .each(function (data) { 56 | var upperLimit = data.pop(); 57 | 58 | block.cssClass('block') 59 | .transform(function (d, i) { 60 | return 'translate(0,' + (rectHeight * i) + ')'; 61 | }); 62 | 63 | // Adds rects and text 64 | d3.select(this) 65 | .datum(data) 66 | .call(block) 67 | .selectAll('g.block') 68 | .each(function (d, i) { 69 | rect 70 | .class('legend-cell') 71 | .x(0) 72 | .y(0) 73 | .rx(0) 74 | .ry(0) 75 | .width(rectWidth) 76 | .height(rectHeight) 77 | .fill(function (d, i) { return scale(d); }) 78 | .fillOpacity(fillOpacity) 79 | .stroke(stroke) 80 | .strokeWidth(strokeWidth) 81 | .strokeOpacity(strokeOpacity); 82 | 83 | svgText 84 | .class('legend-text') 85 | .x(function () { return rectWidth + textPadding; }) 86 | .y(function () { return rectHeight / 2; }) 87 | .dx('') 88 | .dy('.32em') 89 | .fill(textFill) 90 | .anchor(textAnchor) 91 | .text(function () { 92 | var formattedNumber = formatNumber(Math.round(d), numberFormat); 93 | 94 | if (i === data.length - 1) { 95 | return formattedNumber + ' - ' + formatNumber(Math.round(upperLimit), numberFormat); 96 | } 97 | return formattedNumber + ' - ' + formatNumber(Math.round(data[i + 1]), numberFormat); 98 | }); 99 | 100 | d3.select(this) 101 | .datum([d]) 102 | .call(rect) 103 | .call(svgText); 104 | }); 105 | }); 106 | }); 107 | } 108 | 109 | // Public API 110 | generator.class = function (v) { 111 | if (!arguments.length) { return cssClass; } 112 | cssClass = v; 113 | return generator; 114 | }; 115 | 116 | generator.transform = function (v) { 117 | if (!arguments.length) { return transform; } 118 | transform = v; 119 | return generator; 120 | }; 121 | 122 | generator.rectWidth = function (v) { 123 | if (!arguments.length) { return rectWidth; } 124 | rectWidth = v; 125 | return generator; 126 | }; 127 | 128 | generator.rectHeight = function (v) { 129 | if (!arguments.length) { return rectHeight; } 130 | rectHeight = v; 131 | return generator; 132 | }; 133 | 134 | generator.fill = function (v) { 135 | if (!arguments.length) { return fill; } 136 | fill = v; 137 | return generator; 138 | }; 139 | 140 | generator.fillOpacity = function (v) { 141 | if (!arguments.length) { return fillOpacity; } 142 | fillOpacity = v; 143 | return generator; 144 | }; 145 | 146 | generator.stroke = function (v) { 147 | if (!arguments.length) { return stroke; } 148 | stroke = v; 149 | return generator; 150 | }; 151 | 152 | generator.strokeWidth = function (v) { 153 | if (!arguments.length) { return strokeWidth; } 154 | strokeWidth = v; 155 | return generator; 156 | }; 157 | 158 | generator.strokeOpacity = function (v) { 159 | if (!arguments.length) { return strokeOpacity; } 160 | strokeOpacity = v; 161 | return generator; 162 | }; 163 | 164 | generator.text = function (v) { 165 | if (!arguments.length) { return text; } 166 | text = v; 167 | return generator; 168 | }; 169 | 170 | generator.textPadding = function (v) { 171 | if (!arguments.length) { return textPadding; } 172 | textPadding = v; 173 | return generator; 174 | }; 175 | 176 | generator.textAnchor = function (v) { 177 | if (!arguments.length) { return textAnchor; } 178 | textAnchor = v; 179 | return generator; 180 | }; 181 | 182 | generator.textFill = function (v) { 183 | if (!arguments.length) { return textFill; } 184 | textFill = v; 185 | return generator; 186 | }; 187 | 188 | generator.title = function (v) { 189 | if (!arguments.length) { return title; } 190 | title = v; 191 | return generator; 192 | }; 193 | 194 | generator.numberFormat = function (v) { 195 | var formats = { 196 | number: '0a', 197 | currency: '($0a)', 198 | bytes: '0b', 199 | percentage: '0%' 200 | }; 201 | 202 | if (!arguments.length) { return numberFormat; } 203 | numberFormat = formats[v] ? formats[v] : formats.number; 204 | return generator; 205 | }; 206 | 207 | generator.scale = function (v) { 208 | if (!arguments.length) { return scale; } 209 | scale = v; 210 | return generator; 211 | }; 212 | 213 | return generator; 214 | } 215 | 216 | module.exports = legend; 217 | -------------------------------------------------------------------------------- /public/vis/components/utils/attrs.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var builder = require('plugins/heatmap/vis/components/utils/builder'); 3 | 4 | function attrs(generator) { 5 | return function () { 6 | var funcs = _.toArray(arguments); 7 | 8 | function filterFunctions(arr, attr) { 9 | return _.filter(arr, function (func) { 10 | return _.isFunction(func[attr]); 11 | }); 12 | } 13 | 14 | function getValue(arr, attr) { 15 | if (!arr.length) { return; } 16 | 17 | if (arr.length === 1) { return arr[0][attr](); } 18 | 19 | return _.map(arr, function (func) { 20 | return func[attr](); 21 | }); 22 | } 23 | 24 | return function (attr, value) { 25 | if (_.isString(attr)) { 26 | if (!value) { 27 | return getValue(filterFunctions(funcs, attr), attr); 28 | } 29 | 30 | _.forEach(filter(funcs, attr), function (func) { 31 | func[attr](value); 32 | }); 33 | } 34 | 35 | if (!value && _.isPlainObject(attr)) { 36 | _.forEach(funcs, function (func) { 37 | builder(attr, func); 38 | }); 39 | } 40 | 41 | return generator; 42 | }; 43 | }; 44 | }; 45 | 46 | module.exports = attrs; 47 | -------------------------------------------------------------------------------- /public/vis/components/utils/builder.js: -------------------------------------------------------------------------------- 1 | var d3 = require('d3'); 2 | var _ = require('lodash'); 3 | 4 | function builder(obj, func) { 5 | if (!_.isPlainObject(obj)) { 6 | throw new Error('builder expects a javascript Object ({}) as its first argument'); 7 | } 8 | 9 | if (!_.isFunction(func)) { 10 | throw new Error('builder expects a function as its second argument'); 11 | } 12 | 13 | d3.entries(obj).forEach(function (d) { 14 | if (_.isFunction(func[d.key])) { 15 | func[d.key](d.value); 16 | } 17 | }); 18 | 19 | return func; 20 | }; 21 | 22 | module.exports = builder; 23 | -------------------------------------------------------------------------------- /public/vis/components/utils/valuator.js: -------------------------------------------------------------------------------- 1 | var d3 = require('d3'); 2 | var _ = require('lodash'); 3 | 4 | function valuator(v) { 5 | if (_.isFunction(v)) { return v; } 6 | if (_.isString(v) || _.isNumber(v)) { 7 | return function (d) { return d[v]; }; 8 | } 9 | return d3.functor(v); 10 | }; 11 | 12 | module.exports = valuator; 13 | -------------------------------------------------------------------------------- /public/vis/components/visualization/generator.js: -------------------------------------------------------------------------------- 1 | var d3 = require('d3'); 2 | var _ = require('lodash'); 3 | var builder = require('plugins/heatmap/vis/components/utils/builder'); 4 | var heatmap = require('plugins/heatmap/vis/components/visualization/heatmap'); 5 | 6 | function chartGenerator() { 7 | var opts = {}; 8 | 9 | function generator(selection) { 10 | selection.each(function (data) { 11 | var dataOpts = (data && data.options) || {}; 12 | var accessor = opts.accessor || dataOpts.accessor || 'cells'; 13 | var chart = heatmap() 14 | .width(data.width) 15 | .height(data.height) 16 | .accessor(accessor); 17 | 18 | _.forEach([opts, dataOpts], function (o) { 19 | builder(o, chart); 20 | }); 21 | 22 | d3.select(this).call(chart); // Draw Chart 23 | }); 24 | } 25 | 26 | // Public API 27 | generator.options = function (v) { 28 | if (!arguments.length) { return opts; } 29 | opts = _.isPlainObject(v) && !_.isArray(v) ? v : opts; 30 | return generator; 31 | }; 32 | 33 | return generator; 34 | } 35 | 36 | module.exports = chartGenerator; 37 | -------------------------------------------------------------------------------- /public/vis/components/visualization/heatmap.js: -------------------------------------------------------------------------------- 1 | var d3 = require('d3'); 2 | var _ = require('lodash'); 3 | var axis = require('plugins/heatmap/vis/components/axis/axis'); 4 | var colorbrewer = require('plugins/heatmap/vis/components/colorbrewer/colorbrewer'); 5 | var gGenerator = require('plugins/heatmap/vis/components/elements/g'); 6 | var layout = require('plugins/heatmap/vis/components/visualization/heatmap_layout'); 7 | var legendGenerator = require('plugins/heatmap/vis/components/legend/legend'); 8 | var rect = require('plugins/heatmap/vis/components/elements/rect'); 9 | var valuator = require('plugins/heatmap/vis/components/utils/valuator'); 10 | 11 | function heatmap() { 12 | var accessor = function (d) { return d; }; 13 | var width = 960; 14 | var height = 500; 15 | var margin = { top: 20, right: 20, bottom: 20, left: 50 }; 16 | var rowValue = function (d) { return d.row; }; 17 | var colValue = function (d) { return d.col; }; 18 | var metric = function (d) { return d.value; }; 19 | var cAxis = { title: '', filterBy: 0, rotateLabels: true, rotationAngle: '-45' }; 20 | var rAxis = { title: '', filterBy: 0, rotateLabels: true, rotationAngle: '-30'}; 21 | var padding = 0; 22 | // var sort = { row: false, col: false }; 23 | // var reverse = { row: false, col: false }; 24 | var cellClass = 'cell'; 25 | var colorScale = d3.scale.quantile(); 26 | var colors = "PuBuGn"; 27 | var numberOfColors = 6; 28 | var opacityScale = d3.scale.linear(); 29 | var opacityRange = [1, 1]; 30 | var fill = metric; 31 | var fillOpacity = metric; 32 | var stroke = 'none'; 33 | var strokeWidth = 0; 34 | var legendTitle = 'Legend'; 35 | var legendNumberFormat = 'number'; 36 | var gridLayout = layout(); 37 | var columnAxis = axis(); 38 | var rowAxis = axis(); 39 | var cells = rect(); 40 | var legend = legendGenerator(); 41 | var g = gGenerator(); 42 | 43 | // Creates a unique array of items 44 | function getDomain(data, accessor) { 45 | return data 46 | .map(function (item) { 47 | return accessor.call(this, item); 48 | }) 49 | .filter(function (item, index, array) { 50 | return array.indexOf(item) === index; 51 | }); 52 | } 53 | 54 | function chart(selection) { 55 | selection.each(function (data, index) { 56 | var metrics = accessor.call(this, data, index); 57 | var adjustedWidth = width - margin.left - margin.right; 58 | var adjustedHeight = height - margin.top - margin.bottom; 59 | var colDomain = getDomain(metrics, colValue); 60 | var rowDomain = getDomain(metrics, rowValue); 61 | var colorRange = colorbrewer[colors][numberOfColors]; 62 | var colorDomain = [Math.min(0, d3.min(metrics, metric)), Math.max(d3.max(metrics, metric), 1)]; 63 | var columnScale = d3.scale.ordinal() 64 | .domain(colDomain) 65 | .rangeBands([0, adjustedWidth], padding); 66 | var rowScale = d3.scale.ordinal() 67 | .domain(rowDomain) 68 | .rangeBands([0, adjustedHeight], padding); 69 | var container; 70 | 71 | gridLayout 72 | .row(rowValue) 73 | .column(colValue) 74 | .value(metric) 75 | .columnScale(columnScale) 76 | .rowScale(rowScale) 77 | .fill(fill) 78 | .fillScale(colorScale.domain(colorDomain).range(colorRange)) 79 | .fillOpacity(fillOpacity) 80 | .opacityScale(opacityScale.domain(colorDomain).range(opacityRange)); 81 | 82 | columnAxis 83 | .scale(columnScale) 84 | .class('column') 85 | .orientation('bottom') 86 | .transform('translate(0,' + adjustedHeight + ')') 87 | .ticks({ 88 | values: columnScale.domain().filter(function (d, i) { 89 | return !(i % cAxis.filterBy); 90 | }) 91 | }) 92 | .title({ 93 | x: adjustedWidth / 2, 94 | y: margin.bottom * (2 / 3), 95 | anchor: 'middle', 96 | text: cAxis.title 97 | }) 98 | .rotateLabels(cAxis.rotateLabels) 99 | .rotateOptions({ 100 | transform: 'translate(0,0)rotate(' + cAxis.rotationAngle + ')' 101 | }); 102 | 103 | rowAxis 104 | .scale(rowScale) 105 | .class('row') 106 | .ticks({ 107 | values: rowScale.domain().filter(function (d, i) { 108 | return !(i % rAxis.filterBy); 109 | }) 110 | }) 111 | .title({ 112 | transform: 'rotate(-90)', 113 | x: -adjustedHeight / 2, 114 | y: -margin.left * (8 / 9), 115 | anchor: 'middle', 116 | text: rAxis.title 117 | }) 118 | .rotateLabels(rAxis.rotateLabels) 119 | .rotateOptions({ 120 | transform: 'translate(-10,-8)rotate(' + rAxis.rotationAngle + ')', 121 | measure: 'height' 122 | }); 123 | 124 | cells 125 | .class(cellClass) 126 | .x(function (d) { return d.data.col; }) 127 | .y(function (d) { return d.data.row; }) 128 | .width(columnScale.rangeBand()) 129 | .height(rowScale.rangeBand()) 130 | .fill(function (d) { return d.data.fill; }) 131 | .fillOpacity(function (d) { return d.data.opacity; }) 132 | .stroke(stroke) 133 | .strokeWidth(strokeWidth); 134 | 135 | legend 136 | .transform(function () { 137 | var x = adjustedWidth + (margin.right / 9); 138 | var y = adjustedHeight - Math.floor(adjustedHeight * (2 / 3)); 139 | return 'translate(' + x + ',' + y + ')'; 140 | }) 141 | .scale(colorScale) 142 | .numberFormat(legendNumberFormat) 143 | .title(legendTitle); 144 | 145 | g.cssClass('container') 146 | .transform('translate(' + margin.left + ',' + margin.top + ')'); 147 | 148 | container = d3.select(this) 149 | .datum([{}]) 150 | .call(g) // One container to rule them all! 151 | .select('g.container'); 152 | 153 | // Draw Heatmap Chart 154 | container 155 | .datum(gridLayout(metrics)) 156 | .call(columnAxis) 157 | .call(rowAxis) 158 | .call(cells); 159 | 160 | // Legend 161 | container 162 | .datum([Math.min(0, colorScale.domain()[0])].concat(colorScale.quantiles()).concat(colorScale.domain()[1])) 163 | .call(legend); 164 | }); 165 | } 166 | 167 | // Public API 168 | chart.accessor = function (v) { 169 | if (!arguments.length) { return accessor; } 170 | accessor = valuator(v); 171 | return chart; 172 | }; 173 | 174 | chart.width = function (v) { 175 | if (!arguments.length) { return width; } 176 | width = v; 177 | return chart; 178 | }; 179 | 180 | chart.height = function (v) { 181 | if (!arguments.length) { return height; } 182 | height = v; 183 | return chart; 184 | }; 185 | 186 | chart.margin = function (v) { 187 | if (!arguments.length) { return margin; } 188 | margin.top = typeof v.top !== 'undefined' ? v.top : margin.top; 189 | margin.right = typeof v.right !== 'undefined' ? v.right : margin.right; 190 | margin.bottom = typeof v.bottom !== 'undefined' ? v.bottom : margin.bottom; 191 | margin.left = typeof v.left !== 'undefined' ? v.left : margin.left; 192 | return chart; 193 | }; 194 | 195 | chart.row = function (v) { 196 | if (!arguments.length) { return rowValue; } 197 | rowValue = valuator(v); 198 | return chart; 199 | }; 200 | 201 | chart.column = function (v) { 202 | if (!arguments.length) { return colValue; } 203 | colValue = valuator(v); 204 | return chart; 205 | }; 206 | 207 | chart.value = function (v) { 208 | if (!arguments.length) { return metric; } 209 | metric = valuator(v); 210 | return chart; 211 | }; 212 | 213 | chart.columnAxis = function (v) { 214 | if (!arguments.length) { return cAxis; } 215 | cAxis.title = typeof v.title !== 'undefined' ? v.title : cAxis.title; 216 | cAxis.filterBy = typeof v.filterBy !== 'undefined' ? v.filterBy : cAxis.filterBy; 217 | return chart; 218 | }; 219 | 220 | chart.rowAxis = function (v) { 221 | if (!arguments.length) { return rAxis; } 222 | rAxis.title = typeof v.title !== 'undefined' ? v.title : rAxis.title; 223 | rAxis.filterBy = typeof v.filterBy !== 'undefined' ? v.filterBy : rAxis.filterBy; 224 | return chart; 225 | }; 226 | 227 | chart.padding = function (v) { 228 | if (!arguments.length) { return padding; } 229 | padding = v; 230 | return chart; 231 | }; 232 | 233 | chart.class = function (v) { 234 | if (!arguments.length) { return cellClass; } 235 | cellClass = v; 236 | return chart; 237 | }; 238 | 239 | chart.colorScale = function (v) { 240 | if (!arguments.length) { return colorScale; } 241 | colorScale = v; 242 | return chart; 243 | }; 244 | 245 | chart.color = function (v) { 246 | if (!arguments.length) { return colors; } 247 | colors = v; 248 | return chart; 249 | }; 250 | 251 | chart.numberOfColors = function (v) { 252 | if (!arguments.length) { return numberOfColors; } 253 | numberOfColors = v; 254 | return chart; 255 | }; 256 | 257 | chart.opacityScale = function (v) { 258 | if (!arguments.length) { return opacityScale; } 259 | opacityScale = v; 260 | return chart; 261 | }; 262 | 263 | chart.opacityRange = function (v) { 264 | if (!arguments.length) { return opacityRange; } 265 | opacityRange = v; 266 | return chart; 267 | }; 268 | 269 | chart.fill = function (v) { 270 | if (!arguments.length) { return fill; } 271 | fill = v; 272 | return chart; 273 | }; 274 | 275 | chart.fillOpacity = function (v) { 276 | if (!arguments.length) { return fillOpacity; } 277 | fillOpacity = v; 278 | return chart; 279 | }; 280 | 281 | chart.stroke = function (v) { 282 | if (!arguments.length) { return stroke; } 283 | stroke = v; 284 | return chart; 285 | }; 286 | 287 | chart.strokeWidth = function (v) { 288 | if (!arguments.length) { return strokeWidth; } 289 | strokeWidth = v; 290 | return chart; 291 | }; 292 | 293 | chart.legendTitle = function (v) { 294 | if (!arguments.length) { return legendTitle; } 295 | legendTitle = v; 296 | return chart; 297 | }; 298 | 299 | chart.legendNumberFormat = function (v) { 300 | if (!arguments.length) { return legendNumberFormat; } 301 | legendNumberFormat = v; 302 | return chart; 303 | }; 304 | 305 | return chart; 306 | } 307 | 308 | module.exports = heatmap; 309 | -------------------------------------------------------------------------------- /public/vis/components/visualization/heatmap_layout.js: -------------------------------------------------------------------------------- 1 | var d3 = require('d3'); 2 | 3 | function heatmapLayout() { 4 | var row = function (d) { return d.row; }; 5 | var column = function (d) { return d.col; }; 6 | var value = function (d) { return d.value; }; 7 | var fill = value; 8 | var fillOpacity = value; 9 | var rowScale = d3.scale.linear(); 10 | var columnScale = d3.scale.linear(); 11 | var fillScale = d3.scale.linear(); 12 | var opacityScale = d3.scale.linear().range([1, 1]); 13 | 14 | function layout(data) { 15 | return data.map(function (d, i) { 16 | return { 17 | row: row.call(data, d, i), 18 | col: column.call(data, d, i), 19 | value: value.call(data, d, i), 20 | data: { 21 | row: rowScale(row.call(data, d, i)), 22 | col: columnScale(column.call(data, d, i)), 23 | fill: fillScale(fill.call(data, d, i)), 24 | opacity: opacityScale(fillOpacity.call(data, d, i)) 25 | } 26 | }; 27 | }); 28 | } 29 | 30 | // Public API 31 | layout.row = function (v) { 32 | if (!arguments.length) { return row; } 33 | row = v; 34 | return layout; 35 | }; 36 | 37 | layout.column = function (v) { 38 | if (!arguments.length) { return column; } 39 | column = v; 40 | return layout; 41 | }; 42 | 43 | layout.value = function (v) { 44 | if (!arguments.length) { return value; } 45 | value = v; 46 | return layout; 47 | }; 48 | 49 | layout.fill = function (v) { 50 | if (!arguments.length) { return fill; } 51 | fill = v; 52 | return layout; 53 | }; 54 | 55 | layout.fillOpacity = function (v) { 56 | if (!arguments.length) { return fillOpacity; } 57 | fillOpacity = v; 58 | return layout; 59 | }; 60 | 61 | layout.rowScale = function (v) { 62 | if (!arguments.length) { return rowScale; } 63 | rowScale = v; 64 | return layout; 65 | }; 66 | 67 | layout.columnScale = function (v) { 68 | if (!arguments.length) { return columnScale; } 69 | columnScale = v; 70 | return layout; 71 | }; 72 | 73 | layout.fillScale = function (v) { 74 | if (!arguments.length) { return fillScale; } 75 | fillScale = v; 76 | return layout; 77 | }; 78 | 79 | layout.opacityScale = function (v) { 80 | if (!arguments.length) { return opacityScale; } 81 | opacityScale = v; 82 | return layout; 83 | }; 84 | 85 | return layout; 86 | } 87 | 88 | module.exports = heatmapLayout; 89 | -------------------------------------------------------------------------------- /public/vis/index.js: -------------------------------------------------------------------------------- 1 | var d3 = require('d3'); 2 | var _ = require('lodash'); 3 | var control = require('plugins/heatmap/vis/components/control/events'); 4 | var layoutGenerator = require('plugins/heatmap/vis/components/layout/generator'); 5 | var chartGenerator = require('plugins/heatmap/vis/components/visualization/generator'); 6 | 7 | function vis() { 8 | var events = control(); 9 | var layout = layoutGenerator(); 10 | var chart = chartGenerator(); 11 | var opts = {}; 12 | var listeners = {}; 13 | var size = [250, 250]; 14 | 15 | function generator(selection) { 16 | selection.each(function (data) { 17 | events.listeners(listeners); 18 | 19 | layout.attr({ 20 | type: opts.layout || 'grid', 21 | columns: opts.numOfColumns || 0, 22 | size: size 23 | }); 24 | 25 | chart.options(opts); 26 | 27 | d3.select(this) 28 | .attr('width', '100%') 29 | .attr('height', size[1]) 30 | .call(events) 31 | .call(layout) 32 | .selectAll('g.chart') 33 | .call(chart); 34 | }); 35 | } 36 | 37 | // Public API 38 | 39 | generator.options = function (v) { 40 | if (!arguments.length) { return opts; } 41 | opts = _.isPlainObject(v) ? v : opts; 42 | return generator; 43 | }; 44 | 45 | generator.listeners = function (v) { 46 | if (!arguments.length) { return listeners; } 47 | listeners = _.isPlainObject(v) ? v : listeners; 48 | return generator; 49 | }; 50 | 51 | generator.size = function (v) { 52 | if (!arguments.length) { return size; } 53 | size = (_.isArray(v) && _.size(v) === 2) ? v : size; 54 | return generator; 55 | }; 56 | 57 | return generator; 58 | } 59 | 60 | module.exports = vis; 61 | --------------------------------------------------------------------------------