├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitignore
├── README.md
├── bower.json
├── dist
├── react-heatmap.js
└── react-heatmap.min.js
├── example
└── src
│ ├── .gitignore
│ ├── example.js
│ ├── example.less
│ └── index.html
├── gulpfile.js
├── lib
└── ReactHeatmap.js
├── package.json
└── src
└── ReactHeatmap.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # This file is for unifying the coding style for different editors and IDEs
2 | # editorconfig.org
3 | root = true
4 |
5 | [*]
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = false
9 | insert_final_newline = true
10 | indent_style = tab
11 |
12 | [*.json]
13 | indent_style = space
14 | indent_size = 2
15 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | .publish/*
2 | dist/*
3 | example/dist/*
4 | lib/*
5 | node_modules/*
6 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "env": {
4 | "browser": true,
5 | "node": true
6 | },
7 | "plugins": [
8 | "react"
9 | ],
10 | "rules": {
11 | "curly": [2, "multi-line"],
12 | "quotes": [2, "single", "avoid-escape"],
13 | "react/display-name": 0,
14 | "react/jsx-boolean-value": 1,
15 | "react/jsx-quotes": 1,
16 | "react/jsx-no-undef": 1,
17 | "react/jsx-sort-props": 0,
18 | "react/jsx-sort-prop-types": 1,
19 | "react/jsx-uses-react": 1,
20 | "react/jsx-uses-vars": 1,
21 | "react/no-did-mount-set-state": 1,
22 | "react/no-did-update-set-state": 1,
23 | "react/no-multi-comp": 1,
24 | "react/no-unknown-property": 1,
25 | "react/prop-types": 1,
26 | "react/react-in-jsx-scope": 1,
27 | "react/self-closing-comp": 1,
28 | "react/wrap-multilines": 1,
29 | "semi": 2,
30 | "strict": 0
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Coverage tools
11 | lib-cov
12 | coverage
13 | coverage.html
14 | .cover*
15 |
16 | # Dependency directory
17 | node_modules
18 |
19 | # Example build directory
20 | example/dist
21 | .publish
22 |
23 | # Editor and other tmp files
24 | *.swp
25 | *.un~
26 | *.iml
27 | *.ipr
28 | *.iws
29 | *.sublime-*
30 | .idea/
31 | *.DS_Store
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Heatmap
2 |
3 | A very simple port of `heatmap.js` for `React`. The idea behind this component is to be able to display a heatmap over any type of content (image, div, components ...). By default, the heatmap will always take all available width and height of its container.
4 |
5 |
6 | ## Demo & Examples
7 |
8 | Live demo: [JonathanWi.github.io/react-heatmap](http://JonathanWi.github.io/react-heatmap/)
9 |
10 | To build the examples locally, run:
11 |
12 | ```bash
13 | npm install
14 | npm start
15 | ```
16 |
17 | Then open [`localhost:8000`](http://localhost:8000) in a browser.
18 |
19 |
20 | ## Installation
21 |
22 | The easiest way to use react-heatmap is to install it from NPM and include it in your own React build process (using [Browserify](http://browserify.org), [Webpack](http://webpack.github.io/), etc).
23 |
24 | You can also use the standalone build by including `dist/react-heatmap.js` in your page. If you use this, make sure you have already included React, and it is available as a global variable.
25 |
26 | ```bash
27 | npm install react-heatmap --save
28 | ```
29 |
30 |
31 | ## Usage
32 |
33 | This component is pretty straightforward and only expecting 2 simple parameters (`max` and `data`; if you're unfamiliar with these, take a look at the [`heatmap.js documentation`](http://www.patrick-wied.at/static/heatmapjs));
34 |
35 | ```js
36 | const ReactHeatmap = require('react-heatmap');
37 |
38 | const data = [{ x: 10, y: 15, value: 5}, { x: 50, y: 50, value: 2}, ...];
39 |
40 |
41 | ```
42 |
43 | ## Properties
44 |
45 |
46 | General component description.
47 |
48 | Props
49 | -----
50 | Prop | Type | Default | Required | Description
51 | --------------------- | -------- | ------------------------- | -------- | -----------
52 | max|int|5|No|Maximum value for intensity
53 | data|array|[]|No|Heatmap array of dots
54 | unit|string|percent|No|Can be either `percent` or `pixels`. If percent, a `x` value like `26` is considered **26% of the container from the top left**
55 |
56 |
57 | ## Development (`src`, `lib` and the build process)
58 |
59 | **NOTE:** The source code for the component is in `src`. A transpiled CommonJS version (generated with Babel) is available in `lib` for use with node.js, browserify and webpack. A UMD bundle is also built to `dist`, which can be included without the need for any build system.
60 |
61 | To build, watch and serve the examples (which will also watch the component source), run `npm start`. If you just want to watch changes to `src` and rebuild `lib`, run `npm run watch` (this is useful if you are working with `npm link`).
62 |
63 | ## License
64 |
65 | [MIT License](https://en.wikipedia.org/wiki/MIT_License) Copyright (c) 2016 Jonathan Widawski.
66 |
67 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-heatmap",
3 | "version": "0.0.0",
4 | "description": "React Heatmap",
5 | "main": "dist/react-heatmap.min.js",
6 | "homepage": "https://github.com/JonathanWi/react-heatmap",
7 | "authors": [
8 | "Jonathan Widawski"
9 | ],
10 | "moduleType": [
11 | "amd",
12 | "globals",
13 | "node"
14 | ],
15 | "keywords": [
16 | "react",
17 | "react-component"
18 | ],
19 | "license": "MIT",
20 | "ignore": [
21 | ".editorconfig",
22 | ".gitignore",
23 | "package.json",
24 | "src",
25 | "node_modules",
26 | "example",
27 | "test"
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/dist/react-heatmap.js:
--------------------------------------------------------------------------------
1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.ReactHeatmap = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
117 | args[_key - 1] = arguments[_key];
118 | }
119 |
120 | var argIndex = 0;
121 | var message = 'Warning: ' + format.replace(/%s/g, function () {
122 | return args[argIndex++];
123 | });
124 | if (typeof console !== 'undefined') {
125 | console.error(message);
126 | }
127 | try {
128 | // --- Welcome to debugging React ---
129 | // This error was thrown as a convenience so that you can use this stack
130 | // to find the callsite that caused this warning to fire.
131 | throw new Error(message);
132 | } catch (x) {}
133 | };
134 |
135 | warning = function warning(condition, format) {
136 | if (format === undefined) {
137 | throw new Error('`warning(condition, format, ...args)` requires a warning ' + 'message argument');
138 | }
139 |
140 | if (format.indexOf('Failed Composite propType: ') === 0) {
141 | return; // Ignore CompositeComponent proptype check.
142 | }
143 |
144 | if (!condition) {
145 | for (var _len2 = arguments.length, args = Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) {
146 | args[_key2 - 2] = arguments[_key2];
147 | }
148 |
149 | printWarning.apply(undefined, [format].concat(args));
150 | }
151 | };
152 | }
153 |
154 | module.exports = warning;
155 | },{"./emptyFunction":1}],4:[function(require,module,exports){
156 | /*
157 | * heatmapjs v2.0.1 | JavaScript Heatmap Library
158 | *
159 | * Copyright 2008-2014 Patrick Wied - All rights reserved.
160 | * Dual licensed under MIT and Beerware license
161 | *
162 | * :: 2016-02-03 23:43
163 | */
164 | ;(function (name, context, factory) {
165 |
166 | // Supports UMD. AMD, CommonJS/Node.js and browser context
167 | if (typeof module !== "undefined" && module.exports) {
168 | module.exports = factory();
169 | } else if (typeof define === "function" && define.amd) {
170 | define(factory);
171 | } else {
172 | context[name] = factory();
173 | }
174 |
175 | })("h337", this, function () {
176 |
177 | // Heatmap Config stores default values and will be merged with instance config
178 | var HeatmapConfig = {
179 | defaultRadius: 40,
180 | defaultRenderer: 'canvas2d',
181 | defaultGradient: { 0.25: "rgb(0,0,255)", 0.55: "rgb(0,255,0)", 0.85: "yellow", 1.0: "rgb(255,0,0)"},
182 | defaultMaxOpacity: 1,
183 | defaultMinOpacity: 0,
184 | defaultBlur: .85,
185 | defaultXField: 'x',
186 | defaultYField: 'y',
187 | defaultValueField: 'value',
188 | plugins: {}
189 | };
190 | var Store = (function StoreClosure() {
191 |
192 | var Store = function Store(config) {
193 | this._coordinator = {};
194 | this._data = [];
195 | this._radi = [];
196 | this._min = 0;
197 | this._max = 1;
198 | this._xField = config['xField'] || config.defaultXField;
199 | this._yField = config['yField'] || config.defaultYField;
200 | this._valueField = config['valueField'] || config.defaultValueField;
201 |
202 | if (config["radius"]) {
203 | this._cfgRadius = config["radius"];
204 | }
205 | };
206 |
207 | var defaultRadius = HeatmapConfig.defaultRadius;
208 |
209 | Store.prototype = {
210 | // when forceRender = false -> called from setData, omits renderall event
211 | _organiseData: function(dataPoint, forceRender) {
212 | var x = dataPoint[this._xField];
213 | var y = dataPoint[this._yField];
214 | var radi = this._radi;
215 | var store = this._data;
216 | var max = this._max;
217 | var min = this._min;
218 | var value = dataPoint[this._valueField] || 1;
219 | var radius = dataPoint.radius || this._cfgRadius || defaultRadius;
220 |
221 | if (!store[x]) {
222 | store[x] = [];
223 | radi[x] = [];
224 | }
225 |
226 | if (!store[x][y]) {
227 | store[x][y] = value;
228 | radi[x][y] = radius;
229 | } else {
230 | store[x][y] += value;
231 | }
232 |
233 | if (store[x][y] > max) {
234 | if (!forceRender) {
235 | this._max = store[x][y];
236 | } else {
237 | this.setDataMax(store[x][y]);
238 | }
239 | return false;
240 | } else{
241 | return {
242 | x: x,
243 | y: y,
244 | value: value,
245 | radius: radius,
246 | min: min,
247 | max: max
248 | };
249 | }
250 | },
251 | _unOrganizeData: function() {
252 | var unorganizedData = [];
253 | var data = this._data;
254 | var radi = this._radi;
255 |
256 | for (var x in data) {
257 | for (var y in data[x]) {
258 |
259 | unorganizedData.push({
260 | x: x,
261 | y: y,
262 | radius: radi[x][y],
263 | value: data[x][y]
264 | });
265 |
266 | }
267 | }
268 | return {
269 | min: this._min,
270 | max: this._max,
271 | data: unorganizedData
272 | };
273 | },
274 | _onExtremaChange: function() {
275 | this._coordinator.emit('extremachange', {
276 | min: this._min,
277 | max: this._max
278 | });
279 | },
280 | addData: function() {
281 | if (arguments[0].length > 0) {
282 | var dataArr = arguments[0];
283 | var dataLen = dataArr.length;
284 | while (dataLen--) {
285 | this.addData.call(this, dataArr[dataLen]);
286 | }
287 | } else {
288 | // add to store
289 | var organisedEntry = this._organiseData(arguments[0], true);
290 | if (organisedEntry) {
291 | this._coordinator.emit('renderpartial', {
292 | min: this._min,
293 | max: this._max,
294 | data: [organisedEntry]
295 | });
296 | }
297 | }
298 | return this;
299 | },
300 | setData: function(data) {
301 | var dataPoints = data.data;
302 | var pointsLen = dataPoints.length;
303 |
304 |
305 | // reset data arrays
306 | this._data = [];
307 | this._radi = [];
308 |
309 | for(var i = 0; i < pointsLen; i++) {
310 | this._organiseData(dataPoints[i], false);
311 | }
312 | this._max = data.max;
313 | this._min = data.min || 0;
314 |
315 | this._onExtremaChange();
316 | this._coordinator.emit('renderall', this._getInternalData());
317 | return this;
318 | },
319 | removeData: function() {
320 | // TODO: implement
321 | },
322 | setDataMax: function(max) {
323 | this._max = max;
324 | this._onExtremaChange();
325 | this._coordinator.emit('renderall', this._getInternalData());
326 | return this;
327 | },
328 | setDataMin: function(min) {
329 | this._min = min;
330 | this._onExtremaChange();
331 | this._coordinator.emit('renderall', this._getInternalData());
332 | return this;
333 | },
334 | setCoordinator: function(coordinator) {
335 | this._coordinator = coordinator;
336 | },
337 | _getInternalData: function() {
338 | return {
339 | max: this._max,
340 | min: this._min,
341 | data: this._data,
342 | radi: this._radi
343 | };
344 | },
345 | getData: function() {
346 | return this._unOrganizeData();
347 | }/*,
348 |
349 | TODO: rethink.
350 |
351 | getValueAt: function(point) {
352 | var value;
353 | var radius = 100;
354 | var x = point.x;
355 | var y = point.y;
356 | var data = this._data;
357 |
358 | if (data[x] && data[x][y]) {
359 | return data[x][y];
360 | } else {
361 | var values = [];
362 | // radial search for datapoints based on default radius
363 | for(var distance = 1; distance < radius; distance++) {
364 | var neighbors = distance * 2 +1;
365 | var startX = x - distance;
366 | var startY = y - distance;
367 |
368 | for(var i = 0; i < neighbors; i++) {
369 | for (var o = 0; o < neighbors; o++) {
370 | if ((i == 0 || i == neighbors-1) || (o == 0 || o == neighbors-1)) {
371 | if (data[startY+i] && data[startY+i][startX+o]) {
372 | values.push(data[startY+i][startX+o]);
373 | }
374 | } else {
375 | continue;
376 | }
377 | }
378 | }
379 | }
380 | if (values.length > 0) {
381 | return Math.max.apply(Math, values);
382 | }
383 | }
384 | return false;
385 | }*/
386 | };
387 |
388 |
389 | return Store;
390 | })();
391 |
392 | var Canvas2dRenderer = (function Canvas2dRendererClosure() {
393 |
394 | var _getColorPalette = function(config) {
395 | var gradientConfig = config.gradient || config.defaultGradient;
396 | var paletteCanvas = document.createElement('canvas');
397 | var paletteCtx = paletteCanvas.getContext('2d');
398 |
399 | paletteCanvas.width = 256;
400 | paletteCanvas.height = 1;
401 |
402 | var gradient = paletteCtx.createLinearGradient(0, 0, 256, 1);
403 | for (var key in gradientConfig) {
404 | gradient.addColorStop(key, gradientConfig[key]);
405 | }
406 |
407 | paletteCtx.fillStyle = gradient;
408 | paletteCtx.fillRect(0, 0, 256, 1);
409 |
410 | return paletteCtx.getImageData(0, 0, 256, 1).data;
411 | };
412 |
413 | var _getPointTemplate = function(radius, blurFactor) {
414 | var tplCanvas = document.createElement('canvas');
415 | var tplCtx = tplCanvas.getContext('2d');
416 | var x = radius;
417 | var y = radius;
418 | tplCanvas.width = tplCanvas.height = radius*2;
419 |
420 | if (blurFactor == 1) {
421 | tplCtx.beginPath();
422 | tplCtx.arc(x, y, radius, 0, 2 * Math.PI, false);
423 | tplCtx.fillStyle = 'rgba(0,0,0,1)';
424 | tplCtx.fill();
425 | } else {
426 | var gradient = tplCtx.createRadialGradient(x, y, radius*blurFactor, x, y, radius);
427 | gradient.addColorStop(0, 'rgba(0,0,0,1)');
428 | gradient.addColorStop(1, 'rgba(0,0,0,0)');
429 | tplCtx.fillStyle = gradient;
430 | tplCtx.fillRect(0, 0, 2*radius, 2*radius);
431 | }
432 |
433 |
434 |
435 | return tplCanvas;
436 | };
437 |
438 | var _prepareData = function(data) {
439 | var renderData = [];
440 | var min = data.min;
441 | var max = data.max;
442 | var radi = data.radi;
443 | var data = data.data;
444 |
445 | var xValues = Object.keys(data);
446 | var xValuesLen = xValues.length;
447 |
448 | while(xValuesLen--) {
449 | var xValue = xValues[xValuesLen];
450 | var yValues = Object.keys(data[xValue]);
451 | var yValuesLen = yValues.length;
452 | while(yValuesLen--) {
453 | var yValue = yValues[yValuesLen];
454 | var value = data[xValue][yValue];
455 | var radius = radi[xValue][yValue];
456 | renderData.push({
457 | x: xValue,
458 | y: yValue,
459 | value: value,
460 | radius: radius
461 | });
462 | }
463 | }
464 |
465 | return {
466 | min: min,
467 | max: max,
468 | data: renderData
469 | };
470 | };
471 |
472 |
473 | function Canvas2dRenderer(config) {
474 | var container = config.container;
475 | var shadowCanvas = this.shadowCanvas = document.createElement('canvas');
476 | var canvas = this.canvas = config.canvas || document.createElement('canvas');
477 | var renderBoundaries = this._renderBoundaries = [10000, 10000, 0, 0];
478 |
479 | var computed = getComputedStyle(config.container) || {};
480 |
481 | canvas.className = 'heatmap-canvas';
482 |
483 | this._width = canvas.width = shadowCanvas.width = +(computed.width.replace(/px/,''));
484 | this._height = canvas.height = shadowCanvas.height = +(computed.height.replace(/px/,''));
485 |
486 | this.shadowCtx = shadowCanvas.getContext('2d');
487 | this.ctx = canvas.getContext('2d');
488 |
489 | // @TODO:
490 | // conditional wrapper
491 |
492 | canvas.style.cssText = shadowCanvas.style.cssText = 'position:absolute;left:0;top:0;';
493 |
494 | container.style.position = 'relative';
495 | container.appendChild(canvas);
496 |
497 | this._palette = _getColorPalette(config);
498 | this._templates = {};
499 |
500 | this._setStyles(config);
501 | };
502 |
503 | Canvas2dRenderer.prototype = {
504 | renderPartial: function(data) {
505 | this._drawAlpha(data);
506 | this._colorize();
507 | },
508 | renderAll: function(data) {
509 | // reset render boundaries
510 | this._clear();
511 | this._drawAlpha(_prepareData(data));
512 | this._colorize();
513 | },
514 | _updateGradient: function(config) {
515 | this._palette = _getColorPalette(config);
516 | },
517 | updateConfig: function(config) {
518 | if (config['gradient']) {
519 | this._updateGradient(config);
520 | }
521 | this._setStyles(config);
522 | },
523 | setDimensions: function(width, height) {
524 | this._width = width;
525 | this._height = height;
526 | this.canvas.width = this.shadowCanvas.width = width;
527 | this.canvas.height = this.shadowCanvas.height = height;
528 | },
529 | _clear: function() {
530 | this.shadowCtx.clearRect(0, 0, this._width, this._height);
531 | this.ctx.clearRect(0, 0, this._width, this._height);
532 | },
533 | _setStyles: function(config) {
534 | this._blur = (config.blur == 0)?0:(config.blur || config.defaultBlur);
535 |
536 | if (config.backgroundColor) {
537 | this.canvas.style.backgroundColor = config.backgroundColor;
538 | }
539 |
540 | this._opacity = (config.opacity || 0) * 255;
541 | this._maxOpacity = (config.maxOpacity || config.defaultMaxOpacity) * 255;
542 | this._minOpacity = (config.minOpacity || config.defaultMinOpacity) * 255;
543 | this._useGradientOpacity = !!config.useGradientOpacity;
544 | },
545 | _drawAlpha: function(data) {
546 | var min = this._min = data.min;
547 | var max = this._max = data.max;
548 | var data = data.data || [];
549 | var dataLen = data.length;
550 | // on a point basis?
551 | var blur = 1 - this._blur;
552 |
553 | while(dataLen--) {
554 |
555 | var point = data[dataLen];
556 |
557 | var x = point.x;
558 | var y = point.y;
559 | var radius = point.radius;
560 | // if value is bigger than max
561 | // use max as value
562 | var value = Math.min(point.value, max);
563 | var rectX = x - radius;
564 | var rectY = y - radius;
565 | var shadowCtx = this.shadowCtx;
566 |
567 |
568 |
569 |
570 | var tpl;
571 | if (!this._templates[radius]) {
572 | this._templates[radius] = tpl = _getPointTemplate(radius, blur);
573 | } else {
574 | tpl = this._templates[radius];
575 | }
576 | // value from minimum / value range
577 | // => [0, 1]
578 | shadowCtx.globalAlpha = (value-min)/(max-min);
579 |
580 | shadowCtx.drawImage(tpl, rectX, rectY);
581 |
582 | // update renderBoundaries
583 | if (rectX < this._renderBoundaries[0]) {
584 | this._renderBoundaries[0] = rectX;
585 | }
586 | if (rectY < this._renderBoundaries[1]) {
587 | this._renderBoundaries[1] = rectY;
588 | }
589 | if (rectX + 2*radius > this._renderBoundaries[2]) {
590 | this._renderBoundaries[2] = rectX + 2*radius;
591 | }
592 | if (rectY + 2*radius > this._renderBoundaries[3]) {
593 | this._renderBoundaries[3] = rectY + 2*radius;
594 | }
595 |
596 | }
597 | },
598 | _colorize: function() {
599 | var x = this._renderBoundaries[0];
600 | var y = this._renderBoundaries[1];
601 | var width = this._renderBoundaries[2] - x;
602 | var height = this._renderBoundaries[3] - y;
603 | var maxWidth = this._width;
604 | var maxHeight = this._height;
605 | var opacity = this._opacity;
606 | var maxOpacity = this._maxOpacity;
607 | var minOpacity = this._minOpacity;
608 | var useGradientOpacity = this._useGradientOpacity;
609 |
610 | if (x < 0) {
611 | x = 0;
612 | }
613 | if (y < 0) {
614 | y = 0;
615 | }
616 | if (x + width > maxWidth) {
617 | width = maxWidth - x;
618 | }
619 | if (y + height > maxHeight) {
620 | height = maxHeight - y;
621 | }
622 |
623 | var img = this.shadowCtx.getImageData(x, y, width, height);
624 | var imgData = img.data;
625 | var len = imgData.length;
626 | var palette = this._palette;
627 |
628 |
629 | for (var i = 3; i < len; i+= 4) {
630 | var alpha = imgData[i];
631 | var offset = alpha * 4;
632 |
633 |
634 | if (!offset) {
635 | continue;
636 | }
637 |
638 | var finalAlpha;
639 | if (opacity > 0) {
640 | finalAlpha = opacity;
641 | } else {
642 | if (alpha < maxOpacity) {
643 | if (alpha < minOpacity) {
644 | finalAlpha = minOpacity;
645 | } else {
646 | finalAlpha = alpha;
647 | }
648 | } else {
649 | finalAlpha = maxOpacity;
650 | }
651 | }
652 |
653 | imgData[i-3] = palette[offset];
654 | imgData[i-2] = palette[offset + 1];
655 | imgData[i-1] = palette[offset + 2];
656 | imgData[i] = useGradientOpacity ? palette[offset + 3] : finalAlpha;
657 |
658 | }
659 |
660 | img.data = imgData;
661 | this.ctx.putImageData(img, x, y);
662 |
663 | this._renderBoundaries = [1000, 1000, 0, 0];
664 |
665 | },
666 | getValueAt: function(point) {
667 | var value;
668 | var shadowCtx = this.shadowCtx;
669 | var img = shadowCtx.getImageData(point.x, point.y, 1, 1);
670 | var data = img.data[3];
671 | var max = this._max;
672 | var min = this._min;
673 |
674 | value = (Math.abs(max-min) * (data/255)) >> 0;
675 |
676 | return value;
677 | },
678 | getDataURL: function() {
679 | return this.canvas.toDataURL();
680 | }
681 | };
682 |
683 |
684 | return Canvas2dRenderer;
685 | })();
686 |
687 | var Renderer = (function RendererClosure() {
688 |
689 | var rendererFn = false;
690 |
691 | if (HeatmapConfig['defaultRenderer'] === 'canvas2d') {
692 | rendererFn = Canvas2dRenderer;
693 | }
694 |
695 | return rendererFn;
696 | })();
697 |
698 |
699 | var Util = {
700 | merge: function() {
701 | var merged = {};
702 | var argsLen = arguments.length;
703 | for (var i = 0; i < argsLen; i++) {
704 | var obj = arguments[i]
705 | for (var key in obj) {
706 | merged[key] = obj[key];
707 | }
708 | }
709 | return merged;
710 | }
711 | };
712 | // Heatmap Constructor
713 | var Heatmap = (function HeatmapClosure() {
714 |
715 | var Coordinator = (function CoordinatorClosure() {
716 |
717 | function Coordinator() {
718 | this.cStore = {};
719 | };
720 |
721 | Coordinator.prototype = {
722 | on: function(evtName, callback, scope) {
723 | var cStore = this.cStore;
724 |
725 | if (!cStore[evtName]) {
726 | cStore[evtName] = [];
727 | }
728 | cStore[evtName].push((function(data) {
729 | return callback.call(scope, data);
730 | }));
731 | },
732 | emit: function(evtName, data) {
733 | var cStore = this.cStore;
734 | if (cStore[evtName]) {
735 | var len = cStore[evtName].length;
736 | for (var i=0; is?(e?this.setDataMax(o[r][i]):this._max=o[r][i],!1):{x:r,y:i,value:c,radius:l,min:u,max:s}},_unOrganizeData:function(){var t=[],e=this._data,n=this._radi;for(var r in e)for(var i in e[r])t.push({x:r,y:i,radius:n[r][i],value:e[r][i]});return{min:this._min,max:this._max,data:t}},_onExtremaChange:function(){this._coordinator.emit("extremachange",{min:this._min,max:this._max})},addData:function(){if(arguments[0].length>0)for(var t=arguments[0],e=t.length;e--;)this.addData.call(this,t[e]);else{var n=this._organiseData(arguments[0],!0);n&&this._coordinator.emit("renderpartial",{min:this._min,max:this._max,data:[n]})}return this},setData:function(t){var e=t.data,n=e.length;this._data=[],this._radi=[];for(var r=0;rthis._renderBoundaries[2]&&(this._renderBoundaries[2]=d+2*l),h+2*l>this._renderBoundaries[3]&&(this._renderBoundaries[3]=h+2*l)}},_colorize:function(){var t=this._renderBoundaries[0],e=this._renderBoundaries[1],n=this._renderBoundaries[2]-t,r=this._renderBoundaries[3]-e,i=this._width,a=this._height,o=this._opacity,s=this._maxOpacity,u=this._minOpacity,c=this._useGradientOpacity;t<0&&(t=0),e<0&&(e=0),t+n>i&&(n=i-t),e+r>a&&(r=a-e);for(var l=this.shadowCtx.getImageData(t,e,n,r),f=l.data,d=f.length,h=this._palette,p=3;p0?o:y>0},getDataURL:function(){return this.canvas.toDataURL()}},t}(),r=function(){var e=!1;return"canvas2d"===t.defaultRenderer&&(e=n),e}(),i={merge:function(){for(var t={},e=arguments.length,n=0;n>",E={array:d("array"),bool:d("boolean"),func:d("function"),number:d("number"),object:d("object"),string:d("string"),symbol:d("symbol"),any:h(),arrayOf:p,element:y(),instanceOf:v,node:b(),objectOf:_,oneOf:m,oneOfType:g,shape:x,exact:w};return l.prototype=Error.prototype,E.checkPropTypes=u,E.PropTypes=E,E}},{"./checkPropTypes":6,"./lib/ReactPropTypesSecret":10,"fbjs/lib/emptyFunction":1,"fbjs/lib/invariant":2,"fbjs/lib/warning":3,"object-assign":5}],9:[function(t,e,n){e.exports=t("./factoryWithThrowingShims")()},{"./factoryWithThrowingShims":7,"./factoryWithTypeCheckers":8}],10:[function(t,e,n){"use strict";var r="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED";e.exports=r},{}],11:[function(t,e,n){(function(r){"use strict";function i(t){return t&&t.__esModule?t:{"default":t}}function a(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function o(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}Object.defineProperty(n,"__esModule",{value:!0});var s=function(){function t(t,e){for(var n=0;n
37 |
38 |
39 |
40 |
41 |
42 | );
43 | }
44 | });
45 |
46 | ReactDOM.render(, document.getElementById('app'));
47 |
--------------------------------------------------------------------------------
/example/src/example.less:
--------------------------------------------------------------------------------
1 | /*
2 | // Examples Stylesheet
3 | // -------------------
4 | */
5 |
6 | body {
7 | font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
8 | font-size: 14px;
9 | color: #333;
10 | margin: 0;
11 | padding: 0;
12 | }
13 |
14 | a {
15 | color: #08c;
16 | text-decoration: none;
17 | }
18 |
19 | a:hover {
20 | text-decoration: underline;
21 | }
22 |
23 | .container {
24 | margin-left: auto;
25 | margin-right: auto;
26 | max-width: 720px;
27 | padding: 1em;
28 | }
29 |
30 | .footer {
31 | margin-top: 50px;
32 | border-top: 1px solid #eee;
33 | padding: 20px 0;
34 | font-size: 12px;
35 | color: #999;
36 | }
37 |
38 | h1, h2, h3, h4, h5, h6 {
39 | color: #222;
40 | font-weight: 100;
41 | margin: 0.5em 0;
42 | }
43 |
44 | label {
45 | color: #999;
46 | display: inline-block;
47 | font-size: 0.85em;
48 | font-weight: bold;
49 | margin: 1em 0;
50 | text-transform: uppercase;
51 | }
52 |
53 | .hint {
54 | margin: 15px 0;
55 | font-style: italic;
56 | color: #999;
57 | }
58 |
--------------------------------------------------------------------------------
/example/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | React Heatmap
4 |
5 |
6 |
7 |
8 |
React Heatmap
9 |
10 |
11 |
12 |
13 |
14 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var initGulpTasks = require('react-component-gulp-tasks');
3 |
4 | /**
5 | * Tasks are added by the react-component-gulp-tasks package
6 | *
7 | * See https://github.com/JedWatson/react-component-gulp-tasks
8 | * for documentation.
9 | *
10 | * You can also add your own additional gulp tasks if you like.
11 | */
12 |
13 | var taskConfig = {
14 |
15 | component: {
16 | name: 'ReactHeatmap',
17 | dependencies: [
18 | 'classnames',
19 | 'react',
20 | 'react-dom'
21 | ],
22 | lib: 'lib'
23 | },
24 |
25 | example: {
26 | src: 'example/src',
27 | dist: 'example/dist',
28 | files: [
29 | 'index.html',
30 | '.gitignore'
31 | ],
32 | scripts: [
33 | 'example.js'
34 | ],
35 | less: [
36 | 'example.less'
37 | ]
38 | }
39 |
40 | };
41 |
42 | initGulpTasks(gulp, taskConfig);
43 |
--------------------------------------------------------------------------------
/lib/ReactHeatmap.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, '__esModule', {
4 | value: true
5 | });
6 |
7 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
8 |
9 | var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
10 |
11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
12 |
13 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
14 |
15 | function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
16 |
17 | var _react = require('react');
18 |
19 | var _react2 = _interopRequireDefault(_react);
20 |
21 | var _propTypes = require('prop-types');
22 |
23 | var _propTypes2 = _interopRequireDefault(_propTypes);
24 |
25 | var _reactDom = require('react-dom');
26 |
27 | var _reactDom2 = _interopRequireDefault(_reactDom);
28 |
29 | var _heatmapjsBuildHeatmapJs = require('heatmapjs/build/heatmap.js');
30 |
31 | var _heatmapjsBuildHeatmapJs2 = _interopRequireDefault(_heatmapjsBuildHeatmapJs);
32 |
33 | var ReactHeatmap = (function (_Component) {
34 | _inherits(ReactHeatmap, _Component);
35 |
36 | function ReactHeatmap(props) {
37 | _classCallCheck(this, ReactHeatmap);
38 |
39 | _get(Object.getPrototypeOf(ReactHeatmap.prototype), 'constructor', this).call(this, props);
40 | this.setData = this.setData.bind(this);
41 | }
42 |
43 | _createClass(ReactHeatmap, [{
44 | key: 'componentDidMount',
45 | value: function componentDidMount() {
46 | this.heatmap = _heatmapjsBuildHeatmapJs2['default'].create({
47 | container: _reactDom2['default'].findDOMNode(this)
48 | });
49 | this.setData(this.props.max, this.props.data);
50 | }
51 | }, {
52 | key: 'componentWillReceiveProps',
53 | value: function componentWillReceiveProps(newProps) {
54 | this.setData(newProps.max, newProps.data);
55 | }
56 | }, {
57 | key: 'setData',
58 | value: function setData(max, data) {
59 | this.heatmap.setData({
60 | max: max,
61 | data: this.computeData(data)
62 | });
63 | }
64 | }, {
65 | key: 'computeData',
66 | value: function computeData(data) {
67 | var _this = this;
68 |
69 | if (this.props.unit === 'percent') {
70 | var _ret = (function () {
71 | var container = {};
72 | container.width = _reactDom2['default'].findDOMNode(_this).offsetWidth;
73 | container.height = _reactDom2['default'].findDOMNode(_this).offsetHeight;
74 | return {
75 | v: data.map(function (values, index) {
76 | return {
77 | x: values.x / 100 * container.width,
78 | y: values.y / 100 * container.height,
79 | value: values.value
80 | };
81 | })
82 | };
83 | })();
84 |
85 | if (typeof _ret === 'object') return _ret.v;
86 | } else {
87 | return data;
88 | }
89 | }
90 | }, {
91 | key: 'render',
92 | value: function render() {
93 | return _react2['default'].createElement('div', { style: { width: '100%', height: '100%' } });
94 | }
95 | }]);
96 |
97 | return ReactHeatmap;
98 | })(_react.Component);
99 |
100 | ReactHeatmap.propTypes = {
101 | max: _propTypes2['default'].number,
102 | data: _propTypes2['default'].array,
103 | unit: _propTypes2['default'].string
104 | };
105 |
106 | ReactHeatmap.defaultProps = {
107 | max: 5,
108 | data: [],
109 | unit: 'percent'
110 | };
111 |
112 | exports['default'] = ReactHeatmap;
113 | module.exports = exports['default'];
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-heatmap",
3 | "version": "1.0.6",
4 | "description": "React Heatmap",
5 | "main": "lib/ReactHeatmap.js",
6 | "author": "Jonathan Widawski",
7 | "homepage": "https://github.com/JonathanWi/react-heatmap",
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/JonathanWi/react-heatmap.git"
11 | },
12 | "bugs": {
13 | "url": "https://github.com/JonathanWi/react-heatmap/issues"
14 | },
15 | "dependencies": {
16 | "classnames": "^2.1.2",
17 | "heatmapjs": "git+https://github.com/JonathanWi/heatmap.js.git"
18 | },
19 | "devDependencies": {
20 | "babel-eslint": "^4.1.3",
21 | "eslint": "^1.6.0",
22 | "eslint-plugin-react": "^3.5.1",
23 | "gulp": "^3.9.0",
24 | "react": "^0.14.6",
25 | "react-component-gulp-tasks": "^0.7.6",
26 | "react-dom": "^0.14.0"
27 | },
28 | "peerDependencies": {
29 | "react": "^0.14.6"
30 | },
31 | "browserify-shim": {
32 | "react": "global:React"
33 | },
34 | "scripts": {
35 | "build": "gulp clean && NODE_ENV=production gulp build",
36 | "examples": "gulp dev:server",
37 | "lint": "eslint ./; true",
38 | "publish:site": "NODE_ENV=production gulp publish:examples",
39 | "release": "NODE_ENV=production gulp release",
40 | "start": "gulp dev",
41 | "test": "echo \"no tests yet\" && exit 0",
42 | "watch": "gulp watch:lib"
43 | },
44 | "keywords": [
45 | "react",
46 | "react-component"
47 | ]
48 | }
49 |
--------------------------------------------------------------------------------
/src/ReactHeatmap.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import PropTypes from 'prop-types';
3 | import ReactDOM from 'react-dom'
4 | import Heatmap from 'heatmapjs/build/heatmap.js'
5 |
6 | class ReactHeatmap extends Component {
7 |
8 | constructor(props) {
9 | super(props);
10 | this.setData = this.setData.bind(this);
11 | }
12 |
13 | componentDidMount() {
14 | this.heatmap = Heatmap.create({
15 | container: ReactDOM.findDOMNode(this)
16 | });
17 | this.setData(this.props.max, this.props.data);
18 | }
19 |
20 | componentWillReceiveProps(newProps) {
21 | this.setData(newProps.max, newProps.data);
22 | }
23 |
24 | setData(max, data) {
25 | this.heatmap.setData({
26 | max: max,
27 | data: this.computeData(data)
28 | });
29 | }
30 |
31 | computeData(data) {
32 | if(this.props.unit === 'percent') {
33 | let container = {};
34 | container.width = ReactDOM.findDOMNode(this).offsetWidth;
35 | container.height = ReactDOM.findDOMNode(this).offsetHeight;
36 | return data.map(function(values, index) {
37 | return {
38 | x : Math.round(values.x/100 * container.width),
39 | y : Math.round(values.y/100 * container.height),
40 | value: values.value
41 | }
42 | })
43 | } else {
44 | return data;
45 | }
46 | }
47 |
48 | render () {
49 | return(
50 |
51 | );
52 | }
53 | }
54 |
55 | ReactHeatmap.propTypes = {
56 | max : PropTypes.number,
57 | data : PropTypes.array,
58 | unit : PropTypes.string
59 | }
60 |
61 | ReactHeatmap.defaultProps = {
62 | max: 5,
63 | data: [],
64 | unit: 'percent'
65 | }
66 |
67 | export default ReactHeatmap
68 |
--------------------------------------------------------------------------------