diff --git a/README.md b/README.md index 457e58ac..c2e96a80 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,17 @@ Dynamic Heatmaps for the Web. [](http://www.patrick-wied.at/static/heatmapjs/?utm_source=gh "View the heatmap.js website with usage examples, showcases, best practises, plugins ( googlemaps heatmap, leaflet) and more.") +## mars3d update +- fix: An error "Cannot assign to read only property 'data' of object '#'" +- add: Added support for typescript in index.d.ts +- The NPM package was released `@mars3d/heatmap.js` + + ## How to get started The fastest way to get started is to install heatmap.js with bower. Just run the following command: -`bower install heatmap.js-amd` +`bower install @mars3d/heatmap.js-amd` This will download the latest working version of heatmap.js and put it in your bower_components folder. @@ -20,7 +26,7 @@ The file you're ultimately looking for is **heatmap.js** or **heatmap.min.js** heatmap.js is also hosted on npm: -`npm install heatmap.js` +`npm install @mars3d/heatmap.js` diff --git a/build/heatmap.js b/build/heatmap.js index 3eee39ea..c95e5631 100644 --- a/build/heatmap.js +++ b/build/heatmap.js @@ -251,7 +251,7 @@ var Canvas2dRenderer = (function Canvas2dRendererClosure() { var _getColorPalette = function(config) { var gradientConfig = config.gradient || config.defaultGradient; var paletteCanvas = document.createElement('canvas'); - var paletteCtx = paletteCanvas.getContext('2d'); + var paletteCtx = paletteCanvas.getContext('2d', { willReadFrequently: true }); paletteCanvas.width = 256; paletteCanvas.height = 1; @@ -269,7 +269,7 @@ var Canvas2dRenderer = (function Canvas2dRendererClosure() { var _getPointTemplate = function(radius, blurFactor) { var tplCanvas = document.createElement('canvas'); - var tplCtx = tplCanvas.getContext('2d'); + var tplCtx = tplCanvas.getContext('2d', { willReadFrequently: true }); var x = radius; var y = radius; tplCanvas.width = tplCanvas.height = radius*2; @@ -340,8 +340,8 @@ var Canvas2dRenderer = (function Canvas2dRendererClosure() { this._width = canvas.width = shadowCanvas.width = config.width || +(computed.width.replace(/px/,'')); this._height = canvas.height = shadowCanvas.height = config.height || +(computed.height.replace(/px/,'')); - this.shadowCtx = shadowCanvas.getContext('2d'); - this.ctx = canvas.getContext('2d'); + this.shadowCtx = shadowCanvas.getContext('2d', { willReadFrequently: true }); + this.ctx = canvas.getContext('2d', { willReadFrequently: true }); // @TODO: // conditional wrapper @@ -524,7 +524,6 @@ var Canvas2dRenderer = (function Canvas2dRendererClosure() { } - img.data = imgData; this.ctx.putImageData(img, x, y); this._renderBoundaries = [1000, 1000, 0, 0]; diff --git a/build/heatmap.min.js b/build/heatmap.min.js index f92a6605..09146b1a 100644 --- a/build/heatmap.min.js +++ b/build/heatmap.min.js @@ -6,4 +6,4 @@ * * :: 2016-09-05 01:16 */ -(function(a,b,c){if(typeof module!=="undefined"&&module.exports){module.exports=c()}else if(typeof define==="function"&&define.amd){define(c)}else{b[a]=c()}})("h337",this,function(){var a={defaultRadius:40,defaultRenderer:"canvas2d",defaultGradient:{.25:"rgb(0,0,255)",.55:"rgb(0,255,0)",.85:"yellow",1:"rgb(255,0,0)"},defaultMaxOpacity:1,defaultMinOpacity:0,defaultBlur:.85,defaultXField:"x",defaultYField:"y",defaultValueField:"value",plugins:{}};var b=function h(){var b=function d(a){this._coordinator={};this._data=[];this._radi=[];this._min=10;this._max=1;this._xField=a["xField"]||a.defaultXField;this._yField=a["yField"]||a.defaultYField;this._valueField=a["valueField"]||a.defaultValueField;if(a["radius"]){this._cfgRadius=a["radius"]}};var c=a.defaultRadius;b.prototype={_organiseData:function(a,b){var d=a[this._xField];var e=a[this._yField];var f=this._radi;var g=this._data;var h=this._max;var i=this._min;var j=a[this._valueField]||1;var k=a.radius||this._cfgRadius||c;if(!g[d]){g[d]=[];f[d]=[]}if(!g[d][e]){g[d][e]=j;f[d][e]=k}else{g[d][e]+=j}var l=g[d][e];if(l>h){if(!b){this._max=l}else{this.setDataMax(l)}return false}else if(l0){var a=arguments[0];var b=a.length;while(b--){this.addData.call(this,a[b])}}else{var c=this._organiseData(arguments[0],true);if(c){if(this._data.length===0){this._min=this._max=c.value}this._coordinator.emit("renderpartial",{min:this._min,max:this._max,data:[c]})}}return this},setData:function(a){var b=a.data;var c=b.length;this._data=[];this._radi=[];for(var d=0;d0){this._drawAlpha(a);this._colorize()}},renderAll:function(a){this._clear();if(a.data.length>0){this._drawAlpha(c(a));this._colorize()}},_updateGradient:function(b){this._palette=a(b)},updateConfig:function(a){if(a["gradient"]){this._updateGradient(a)}this._setStyles(a)},setDimensions:function(a,b){this._width=a;this._height=b;this.canvas.width=this.shadowCanvas.width=a;this.canvas.height=this.shadowCanvas.height=b},_clear:function(){this.shadowCtx.clearRect(0,0,this._width,this._height);this.ctx.clearRect(0,0,this._width,this._height)},_setStyles:function(a){this._blur=a.blur==0?0:a.blur||a.defaultBlur;if(a.backgroundColor){this.canvas.style.backgroundColor=a.backgroundColor}this._width=this.canvas.width=this.shadowCanvas.width=a.width||this._width;this._height=this.canvas.height=this.shadowCanvas.height=a.height||this._height;this._opacity=(a.opacity||0)*255;this._maxOpacity=(a.maxOpacity||a.defaultMaxOpacity)*255;this._minOpacity=(a.minOpacity||a.defaultMinOpacity)*255;this._useGradientOpacity=!!a.useGradientOpacity},_drawAlpha:function(a){var c=this._min=a.min;var d=this._max=a.max;var a=a.data||[];var e=a.length;var f=1-this._blur;while(e--){var g=a[e];var h=g.x;var i=g.y;var j=g.radius;var k=Math.min(g.value,d);var l=h-j;var m=i-j;var n=this.shadowCtx;var o;if(!this._templates[j]){this._templates[j]=o=b(j,f)}else{o=this._templates[j]}var p=(k-c)/(d-c);n.globalAlpha=p<.01?.01:p;n.drawImage(o,l,m);if(lthis._renderBoundaries[2]){this._renderBoundaries[2]=l+2*j}if(m+2*j>this._renderBoundaries[3]){this._renderBoundaries[3]=m+2*j}}},_colorize:function(){var a=this._renderBoundaries[0];var b=this._renderBoundaries[1];var c=this._renderBoundaries[2]-a;var d=this._renderBoundaries[3]-b;var e=this._width;var f=this._height;var g=this._opacity;var h=this._maxOpacity;var i=this._minOpacity;var j=this._useGradientOpacity;if(a<0){a=0}if(b<0){b=0}if(a+c>e){c=e-a}if(b+d>f){d=f-b}var k=this.shadowCtx.getImageData(a,b,c,d);var l=k.data;var m=l.length;var n=this._palette;for(var o=3;o0){r=g}else{if(p>0;return b},getDataURL:function(){return this.canvas.toDataURL()}};return d}();var d=function j(){var b=false;if(a["defaultRenderer"]==="canvas2d"){b=c}return b}();var e={merge:function(){var a={};var b=arguments.length;for(var c=0;ch){if(!b){this._max=l}else{this.setDataMax(l)}return false}else if(l0){var a=arguments[0];var b=a.length;while(b--){this.addData.call(this,a[b])}}else{var c=this._organiseData(arguments[0],true);if(c){if(this._data.length===0){this._min=this._max=c.value}this._coordinator.emit("renderpartial",{min:this._min,max:this._max,data:[c]})}}return this},setData:function(a){var b=a.data;var c=b.length;this._data=[];this._radi=[];for(var d=0;d0){this._drawAlpha(a);this._colorize()}},renderAll:function(a){this._clear();if(a.data.length>0){this._drawAlpha(c(a));this._colorize()}},_updateGradient:function(b){this._palette=a(b)},updateConfig:function(a){if(a["gradient"]){this._updateGradient(a)}this._setStyles(a)},setDimensions:function(a,b){this._width=a;this._height=b;this.canvas.width=this.shadowCanvas.width=a;this.canvas.height=this.shadowCanvas.height=b},_clear:function(){this.shadowCtx.clearRect(0,0,this._width,this._height);this.ctx.clearRect(0,0,this._width,this._height)},_setStyles:function(a){this._blur=a.blur==0?0:a.blur||a.defaultBlur;if(a.backgroundColor){this.canvas.style.backgroundColor=a.backgroundColor}this._width=this.canvas.width=this.shadowCanvas.width=a.width||this._width;this._height=this.canvas.height=this.shadowCanvas.height=a.height||this._height;this._opacity=(a.opacity||0)*255;this._maxOpacity=(a.maxOpacity||a.defaultMaxOpacity)*255;this._minOpacity=(a.minOpacity||a.defaultMinOpacity)*255;this._useGradientOpacity=!!a.useGradientOpacity},_drawAlpha:function(a){var c=this._min=a.min;var d=this._max=a.max;var a=a.data||[];var e=a.length;var f=1-this._blur;while(e--){var g=a[e];var h=g.x;var i=g.y;var j=g.radius;var k=Math.min(g.value,d);var l=h-j;var m=i-j;var n=this.shadowCtx;var o;if(!this._templates[j]){this._templates[j]=o=b(j,f)}else{o=this._templates[j]}var p=(k-c)/(d-c);n.globalAlpha=p<.01?.01:p;n.drawImage(o,l,m);if(lthis._renderBoundaries[2]){this._renderBoundaries[2]=l+2*j}if(m+2*j>this._renderBoundaries[3]){this._renderBoundaries[3]=m+2*j}}},_colorize:function(){var a=this._renderBoundaries[0];var b=this._renderBoundaries[1];var c=this._renderBoundaries[2]-a;var d=this._renderBoundaries[3]-b;var e=this._width;var f=this._height;var g=this._opacity;var h=this._maxOpacity;var i=this._minOpacity;var j=this._useGradientOpacity;if(a<0){a=0}if(b<0){b=0}if(a+c>e){c=e-a}if(b+d>f){d=f-b}var k=this.shadowCtx.getImageData(a,b,c,d);var l=k.data;var m=l.length;var n=this._palette;for(var o=3;o0){r=g}else{if(p>0;return b},getDataURL:function(){return this.canvas.toDataURL()}};return d}();var d=function j(){var b=false;if(a["defaultRenderer"]==="canvas2d"){b=c}return b}();var e={merge:function(){var a={};var b=arguments.length;for(var c=0;c +// Rhys van der Waerden +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.4 + +export as namespace h337; + +/** + * Create a heatmap instance. A Heatmap can be customized with the configObject. + * + * @example Simple configuration with standard gradient + * + * // create configuration object + * var config = { + * container: document.getElementById('heatmapContainer'), + * radius: 10, + * maxOpacity: .5, + * minOpacity: 0, + * blur: .75 + * }; + * // create heatmap with configuration + * var heatmapInstance = h337.create(config); + * + * @example Custom gradient configuration + * + * // create configuration object + * var config = { + * container: document.getElementById('heatmapContainer'), + * radius: 10, + * maxOpacity: .5, + * minOpacity: 0, + * blur: .75, + * gradient: { + * // enter n keys between 0 and 1 here + * // for gradient color customization + * '.5': 'blue', + * '.8': 'red', + * '.95': 'white' + * } + * }; + * var heatmapInstance = h337.create(config); + */ +export function create< + V extends string = 'value', + X extends string = 'x', + Y extends string = 'y' +>( + configObject: HeatmapConfiguration +): Heatmap; + +export function register(pluginKey: string, plugin: any): void; + +/** + * Heatmap instances are returned by h337.create. A heatmap instance has its own + * internal datastore and renderer where you can manipulate data. As a result + * the heatmap gets updated (either partially or completely, depending on + * whether it's necessary). + */ +export class Heatmap { + /** + * Use this functionality only for adding datapoints on the fly, not for data + * initialization! heatmapInstance.addData adds a single or multiple + * datapoints to the heatmap's datastore. + * + * @example A single datapoint + * + * var dataPoint = { + * x: 5, // x coordinate of the datapoint, a number + * y: 5, // y coordinate of the datapoint, a number + * value: 100 // the value at datapoint(x, y) + * }; + * heatmapInstance.addData(dataPoint); + * + * @example multiple datapoints + * + * // for data initialization use setData!! + * var dataPoints = [dataPoint, dataPoint, dataPoint, dataPoint]; + * heatmapInstance.addData(dataPoints); + */ + addData(dataPoint: DataPoint | ReadonlyArray>): this; + + /** + * Initialize a heatmap instance with the given dataset. Removes all + * previously existing points from the heatmap instance and re-initializes + * the datastore. + * + * @example + * + * var data = { + * max: 100, + * min: 0, + * data: [ + * dataPoint, dataPoint, dataPoint, dataPoint + * ] + * }; + * heatmapInstance.setData(data); + */ + setData(data: HeatmapData>): this; + + /** + * Changes the upper bound of your dataset and triggers a complete + * rerendering. + * + * @example + * + * heatmapInstance.setDataMax(200); + * // setting the maximum value triggers a complete rerendering of the heatmap + * heatmapInstance.setDataMax(100); + */ + setDataMax(number: number): this; + + /** + * Changes the lower bound of your dataset and triggers a complete + * rerendering. + * + * @example + * + * heatmapInstance.setDataMin(10); + * // setting the minimum value triggers a complete rerendering of the heatmap + * heatmapInstance.setDataMin(0); + */ + setDataMin(number: number): this; + + /** + * Reconfigures a heatmap instance after it has been initialized. Triggers a + * complete rerendering. + * + * NOTE: This returns a reference to itself, but also offers an opportunity + * to change the `xField`, `yField` and `valueField` options, which can + * change the type of the `Heatmap` instance. + * + * @example + * + * var nuConfig = { + * radius: 10, + * maxOpacity: .5, + * minOpacity: 0, + * blur: .75 + * }; + * heatmapInstance.configure(nuConfig); + */ + configure< + Vn extends string = V, + Xn extends string = X, + Yn extends string = Y + >(configObject: HeatmapConfiguration): Heatmap; + + /** + * Returns value at datapoint position. + * + * The returned value is an interpolated value based on the gradient blending + * if point is not in store. + * + * @example + * + * heatmapInstance.addData({ x: 10, y: 10, value: 100}); + * // get the value at x=10, y=10 + * heatmapInstance.getValueAt({ x: 10, y: 10 }); // returns 100 + */ + getValueAt(point: { x: number, y: number }): number; + + /** + * Returns a persistable and reimportable (with setData) JSON object. + * + * @example + * + * var currentData = heatmapInstance.getData(); + * // now let's create a new instance and set the data + * var heatmap2 = h337.create(config); + * heatmap2.setData(currentData); // now both heatmap instances have the same content + */ + getData(): HeatmapData; + + /** + * Returns dataURL string. + * + * The returned value is the base64 encoded dataURL of the heatmap instance. + * + * @example + * + * heatmapInstance.getDataURL(); // data:image/png;base64... + * // ready for saving locally or on the server + */ + getDataURL(): string; + + /** + * Repaints the whole heatmap canvas. + */ + repaint(): this; +} + +export interface BaseHeatmapConfiguration { + /** + * A background color string in form of hexcode, color name, or rgb(a) + */ + backgroundColor?: string; + + /** + * The blur factor that will be applied to all datapoints. The higher the + * blur factor is, the smoother the gradients will be + * Default value: 0.85 + */ + blur?: number; + + /** + * An object that represents the gradient. + * Syntax: {[key: number in range [0,1]]: color} + */ + gradient?: { [key: string]: string }; + + /** + * The maximal opacity the highest value in the heatmap will have. (will be + * overridden if opacity set) + * Default value: 0.6 + */ + maxOpacity?: number; + + /** + * The minimum opacity the lowest value in the heatmap will have (will be + * overridden if opacity set) + */ + minOpacity?: number; + + /** + * A global opacity for the whole heatmap. This overrides maxOpacity and + * minOpacity if set + * Default value: 0.6 + */ + opacity?: number; + + /** + * The radius each datapoint will have (if not specified on the datapoint + * itself) + */ + radius?: number; + + /** + * Scales the radius based on map zoom. + */ + scaleRadius?: boolean; + + /** + * The property name of the value/weight in a datapoint + * Default value: 'value' + */ + valueField?: V; + + /** + * Pass a callback to receive extrema change updates. Useful for DOM + * legends. + */ + onExtremaChange?: () => void; + + /** + * Indicate whether the heatmap should use a global extrema or a local + * extrema (the maximum and minimum of the currently displayed viewport) + */ + useLocalExtrema?: boolean; +} + +/** + * Configuration object of a heatmap + */ +export interface HeatmapConfiguration< + V extends string = 'value', + X extends string = 'x', + Y extends string = 'y', +> extends BaseHeatmapConfiguration { + /** + * A DOM node where the heatmap canvas should be appended (heatmap will adapt to + * the node's size) + */ + container: HTMLElement; + + /** + * The property name of your x coordinate in a datapoint + * Default value: 'x' + */ + xField?: X; + + /** + * The property name of your y coordinate in a datapoint + * Default value: 'y' + */ + yField?: Y; +} + +export interface HeatmapOverlayConfiguration< + V extends string = 'value', + TLat extends string = 'lat', + TLong extends string = 'lng', +> extends BaseHeatmapConfiguration { + /** + * The property name of your latitude coordinate in a datapoint + * Default value: 'x' + */ + latField?: TLat; + + /** + * The property name of your longitude coordinate in a datapoint + * Default value: 'y' + */ + lngField?: TLong; +} + +/** + * A single data point on a heatmap. The interface of the data point can be + * overridden by providing alternative values for `xKey` and `yKey` in the + * config object. + */ +export type DataPoint< + V extends string = 'value', + X extends string = 'x', + Y extends string = 'y', +> = Record; + +/** + * Type of data returned by `Heatmap#hello`, which ignores custom `xField`, + * `yField` and `valueField`. + */ +export interface DataCircle { + x: number; + y: number; + value: number; + radius: number; +} + +/** + * An object representing the set of data points on a heatmap + */ +export interface HeatmapData { + /** + * An array of data points + */ + data: ReadonlyArray; + + /** + * Max value of the valueField + */ + max: number; + + /** + * Min value of the valueField + */ + min: number; +} diff --git a/package.json b/package.json index 498f1a09..2029595d 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,9 @@ { - "name": "heatmap.js", - "version": "2.0.5", + "name": "@mars3d/heatmap.js", + "version": "2.0.7", "description": "Dynamic JavaScript Heatmaps for the Web", - "homepage": "https://www.patrick-wied.at/static/heatmapjs/", - "author": { - "name": "Patrick Wied", - "email": "heatmapjs@patrick-wied.at", - "url": "https://www.patrick-wied.at/" - }, "main": "build/heatmap.js", + "types": "index.d.ts", "devDependencies": { "grunt": ">= 0.4.1", "coffee-script": ">= 1.6.3", @@ -17,6 +12,14 @@ "grunt-contrib-watch": "0.2.0rc7", "grunt-contrib-jshint": ">= 0.3.0" }, + "scripts": { + "build": "grunt build", + "push": "npm publish --access public" + }, + "files": [ + "build", + "index.d.ts" + ], "keywords": [ "heatmap", "heatmaps", @@ -26,21 +29,18 @@ "leaflet heatmap", "leaflet" ], - "files": [ - "build", - "plugins", - "examples", - "docs", - "package.json", - "LICENSE", - "README.md" - ], - "buildFiles": [ - "src/config.js", - "src/data.js", - "src/renderer/canvas2d.js", - "src/renderer.js", - "src/util.js", - "src/core.js" - ] + "repository": { + "type": "git", + "url": "https://github.com/muyao1987/heatmap.js.git" + }, + "bugs": { + "url": "https://github.com/muyao1987/heatmap.js/issues", + "email": "wh@marsgis.cn" + }, + "author": { + "name": "火星科技 木遥", + "url": "http://www.marsgis.cn" + }, + "license": "ISC", + "homepage": "https://github.com/muyao1987/heatmap.js" } diff --git a/src/renderer/canvas2d.js b/src/renderer/canvas2d.js index 259c8d14..f55746c5 100644 --- a/src/renderer/canvas2d.js +++ b/src/renderer/canvas2d.js @@ -4,7 +4,7 @@ var Canvas2dRenderer = (function Canvas2dRendererClosure() { var _getColorPalette = function(config) { var gradientConfig = config.gradient || config.defaultGradient; var paletteCanvas = document.createElement('canvas'); - var paletteCtx = paletteCanvas.getContext('2d'); + var paletteCtx = paletteCanvas.getContext('2d', { willReadFrequently: true }); paletteCanvas.width = 256; paletteCanvas.height = 1; @@ -22,7 +22,7 @@ var Canvas2dRenderer = (function Canvas2dRendererClosure() { var _getPointTemplate = function(radius, blurFactor) { var tplCanvas = document.createElement('canvas'); - var tplCtx = tplCanvas.getContext('2d'); + var tplCtx = tplCanvas.getContext('2d', { willReadFrequently: true }); var x = radius; var y = radius; tplCanvas.width = tplCanvas.height = radius*2; @@ -93,8 +93,8 @@ var Canvas2dRenderer = (function Canvas2dRendererClosure() { this._width = canvas.width = shadowCanvas.width = config.width || +(computed.width.replace(/px/,'')); this._height = canvas.height = shadowCanvas.height = config.height || +(computed.height.replace(/px/,'')); - this.shadowCtx = shadowCanvas.getContext('2d'); - this.ctx = canvas.getContext('2d'); + this.shadowCtx = shadowCanvas.getContext('2d', { willReadFrequently: true }); + this.ctx = canvas.getContext('2d', { willReadFrequently: true }); // @TODO: // conditional wrapper @@ -277,7 +277,7 @@ var Canvas2dRenderer = (function Canvas2dRendererClosure() { } - img.data = imgData; + // img.data = imgData; this.ctx.putImageData(img, x, y); this._renderBoundaries = [1000, 1000, 0, 0];