Skip to content

Commit

Permalink
feat: Add scaleWithZoom option to heatmaps.
Browse files Browse the repository at this point in the history
  • Loading branch information
manthey committed Dec 20, 2021
1 parent 57e42b9 commit 7885515
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 18 deletions.
3 changes: 3 additions & 0 deletions examples/heatmap/index.pug
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ block append mainContent
.form-group(title="Use either a Gaussian distribution or a solid circle with a blurred edge for each point. If a Gaussian is used, the total radius is the sume of the radius and blur radius values.")
label(for="gaussian") Gaussian Points
input#gaussian(type="checkbox", placeholder="true", checked="checked")
.form-group(title="If true, scale the point size with zoom. In this case, the radius is specified in pixels a zoom-level 0.")
label(for="scaleWithZoom") Scale With Zoom
input#scaleWithZoom(type="checkbox", placeholder="false")
.form-group(title="Color Gradient. Entries with intensities of 0 and 1 are needed to form a valid color gradient.")
label Color Gradient
table.gradient
Expand Down
3 changes: 2 additions & 1 deletion examples/heatmap/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ $(function () {
ctlvalue = value ? value : 'adderall';
break;
case 'gaussian':
case 'scaleWithZoom':
ctlvalue = value === 'true';
heatmapOptions.style[key] = value;
break;
Expand Down Expand Up @@ -100,7 +101,6 @@ $(function () {
heatmapOptions[key] = ctlvalue = parseInt(value, 10);
}
break;
// add gaussian and binning when they are added as features
}
if (ctlvalue !== undefined) {
$('#' + ctlkey).val(ctlvalue);
Expand Down Expand Up @@ -229,6 +229,7 @@ $(function () {
fetch_data();
break;
case 'gaussian':
case 'scaleWithZoom':
heatmapOptions.style[param] = processedValue;
heatmap.style(param, processedValue);
heatmap.draw();
Expand Down
44 changes: 28 additions & 16 deletions src/canvas/heatmapFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,22 @@ var canvas_heatmapFeature = function (arg) {
};

/**
* Create circle for each data point.
* Create a circle to render at each data point.
*
* @returns {this}
*/
this._createCircle = function () {
var circle, ctx, r, r2, blur, gaussian;
var circle, ctx, r, r2, blur, gaussian, scale;
r = m_this.style('radius');
blur = m_this.style('blurRadius');
gaussian = m_this.style('gaussian');
scale = m_this.style('scaleWithZoom');
if (scale) {
let zoom = this.layer().map().zoom();
scale = Math.pow(2, zoom);
r *= scale;
blur *= scale;
}
if (!m_this._circle || m_this._circle.gaussian !== gaussian ||
m_this._circle.radius !== r || m_this._circle.blurRadius !== blur) {
circle = m_this._circle = document.createElement('canvas');
Expand Down Expand Up @@ -307,9 +314,12 @@ var canvas_heatmapFeature = function (arg) {
layer = m_this.layer(),
mapSize = map.size();

if (m_this.style('scaleWithZoom')) {
radius *= Math.pow(2, map.zoom());
}
/* Determine if we should bin the data */
if (binned === true || binned === 'auto') {
binned = Math.max(Math.floor(radius / 8), 3);
binned = Math.max(Math.floor(radius / 8), Math.max(1.5, Math.min(3, radius / 2.5)));
if (m_this.binned() === 'auto') {
var numbins = (Math.ceil((mapSize.width + radius * 2) / binned) *
Math.ceil((mapSize.height + radius * 2) / binned));
Expand All @@ -327,20 +337,22 @@ var canvas_heatmapFeature = function (arg) {
context2d.setTransform(1, 0, 0, 1, 0, 0);
context2d.clearRect(0, 0, mapSize.width, mapSize.height);
m_heatMapTransform = '';
map.scheduleAnimationFrame(m_this._setTransform, false);
layer.canvas().css({transform: ''});

m_this._createCircle();
m_this._computeGradient();
if (!binned) {
m_this._renderPoints(context2d, map, data, radius);
} else {
m_this._renderBinnedData(context2d, map, data, radius, binned);
if (radius > 0.5 && radius < 8192) {
map.scheduleAnimationFrame(m_this._setTransform, false);
layer.canvas().css({transform: ''});

m_this._createCircle();
m_this._computeGradient();
if (!binned) {
m_this._renderPoints(context2d, map, data, radius);
} else {
m_this._renderBinnedData(context2d, map, data, radius, binned);
}
canvas = layer.canvas()[0];
pixelArray = context2d.getImageData(0, 0, canvas.width, canvas.height);
m_this._colorize(pixelArray.data, m_this._grad);
context2d.putImageData(pixelArray, 0, 0);
}
canvas = layer.canvas()[0];
pixelArray = context2d.getImageData(0, 0, canvas.width, canvas.height);
m_this._colorize(pixelArray.data, m_this._grad);
context2d.putImageData(pixelArray, 0, 0);

m_heatMapPosition = {
zoom: map.zoom(),
Expand Down
6 changes: 5 additions & 1 deletion src/heatmapFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ var transform = require('./transform');
* approximation. The total weight of the gaussian area is approximately the
* `9/16 r^2`. The sum of `radius + blurRadius` is used as the radius for
* the gaussian distribution.
* @property {boolean} [scaleWithZoom=false] If truthy, the value for radius
* and blurRadius scale with zoom. In this case, the values for radius and
* blurRadius are the values at zoom-level zero.
*/

/**
Expand Down Expand Up @@ -238,7 +241,8 @@ var heatmapFeature = function (arg) {
0.25: {r: 0, g: 0, b: 1, a: 0.5},
0.5: {r: 0, g: 1, b: 1, a: 0.6},
0.75: {r: 1, g: 1, b: 0, a: 0.7},
1: {r: 1, g: 0, b: 0, a: 0.8}}
1: {r: 1, g: 0, b: 0, a: 0.8}},
scaleWithZoom: false
},
arg.style === undefined ? {} : arg.style
);
Expand Down
14 changes: 14 additions & 0 deletions tests/cases/heatmap.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,20 @@ describe('canvas heatmap', function () {
stepAnimationFrame(Date.now());
expect(feature1._binned).toBe(r / 8);
});

it('scaleWithZoom', function () {
// animation frames are already mocked
var r = 0.80;
feature1.style({radius: r, blurRadius: 0, scaleWithZoom: true});
map.draw();
stepAnimationFrame(Date.now());
expect(feature1._binned).toBe(102);
feature1.style('scaleWithZoom', false);
map.draw();
stepAnimationFrame(Date.now());
expect(feature1._binned).toBe(1.5);
});

it('Remove a feature from a layer', function () {
layer.deleteFeature(feature1).draw();
expect(layer.children().length).toBe(0);
Expand Down

0 comments on commit 7885515

Please sign in to comment.