Reputation: 1
I'm trying to create a heatmap using canvas QML but it doesn't work correctly.
I based it on the heatmap tutorial using JS HTML5 canvas and converted it to QML code. The link is hear: http://zhangwenli.com/blog/2015/06/12/drawing-heatmap-with-html-canvas.
But the heatmap is drawn by messy rectangles stacked on top of each other. I don't know how to make it smoother
Result
Expected result
Code:
import QtQuick 2.15
import QtQuick.Controls 2.15
ApplicationWindow {
visible: true
width: 800
height: 600
Canvas {
id: mainCanvas
width: 800
height: 600
property var data: []
onPaint: {
var ctx = mainCanvas.getContext("2d");
var brushSize = 10;
var brushBlurSize = 10;
var r = brushSize + brushBlurSize;
var d = r * 2;
var len = data.length;
for (var i = 0; i < len; ++i) {
var p = data[i];
var x = p[0];
var y = p[1];
var alpha = p[2];
ctx.globalAlpha = alpha;
ctx.drawImage(brushCanvas, x - r, y - r);
var gradient = gradientCanvas.getContext("2d").getImageData(0, 0, 1, 256).data;
var colorOffset = Math.floor(alpha * 255);
var red = gradient[colorOffset * 4];
var green = gradient[colorOffset * 4 + 1];
var blue = gradient[colorOffset * 4 + 2];
//Set fill color from gradient
ctx.fillStyle = "rgb(" + red + "," + green + "," + blue + ")";
ctx.fillRect(x - r, y - r, d, d);
}
}
Component.onCompleted: {
// Generate random data with alpha values
for (var i = 0; i < 1000; ++i) {
data.push([Math.random() * 400, Math.random() * 300, Math.random()]);
}
for (var i = 0; i < 100; ++i) {
data.push([Math.random() * 20 + i / 2 + 100,
Math.random() * 20 + 200, Math.random()]);
}
for (var i = 0; i < 100; ++i) {
data.push([Math.random() * 20 + i / 2 + 300,
Math.random() * 20 - i / 3 + 200, Math.random()]);
}
mainCanvas.requestPaint();
}
}
Canvas {
id: brushCanvas
width: 40
height: 40
onPaint: {
var ctx = brushCanvas.getContext("2d");
var brushSize = 10;
var brushBlurSize = 10;
var r = brushSize + brushBlurSize;
var d = r * 2;
brushCanvas.width = d;
brushCanvas.height = d;
ctx.shadowOffsetX = d;
ctx.shadowBlur = brushBlurSize;
ctx.shadowColor = "black";
ctx.beginPath();
ctx.arc(-r, r, brushSize, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
}
}
Canvas {
id: gradientCanvas
x: 100
width: 1
height: 256
onPaint: {
var ctx = gradientCanvas.getContext("2d");
var levels = 256;
var gradient = ctx.createLinearGradient(0, 0, 0, levels);
var gradientColors = {
0.4: 'blue',
0.5: 'cyan',
0.6: 'lime',
0.8: 'yellow',
1.0: 'red'
};
for (var pos in gradientColors) {
gradient.addColorStop(pos, gradientColors[pos]);
}
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, gradientCanvas.width, gradientCanvas.height);
var gradientPixels = ctx.getImageData(0, 0, 1, levels).data;
}
}
}
Upvotes: 0
Views: 81
Reputation: 26214
I applied the following fixes to your algorithm:
The code is in QML since that's your original asking question, but, at the same time, it is terribly inefficient.
import QtQuick
import QtQuick.Controls
Page {
Canvas {
id: mainCanvas
width: 400
height: 300
anchors.centerIn: parent
property var data: []
property int brush: 10
onPaint: {
let ctx = mainCanvas.getContext("2d");
let heatmap = new Array(height);
for (let y = 0; y < height; ++y) {
heatmap[y] = new Array(width);
for (let x = 0; x < width; ++x) {
heatmap[y][x] = 0.0;
}
}
let len = data.length;
for (let i = 0; i < len; ++i) {
let p = data[i];
let x = Math.trunc(p[0]);
let y = Math.trunc(p[1]);
let alpha = p[2];
for (let dy = -brush; dy <= brush; ++dy)
{
for (let dx = -brush; dx <= brush; ++dx)
{
if (y + dy < 0 || y + dy >= height || x + dx < 0 || x + dx >= width) continue;
let d = Math.sqrt(dx * dx + dy * dy);
let dalpha = alpha - d / brush
if (dalpha > heatmap[y + dy][x + dx]) {
heatmap[y + dy][x + dx] = dalpha;
}
}
}
}
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let alpha = heatmap[y][x];
let h = (1 - alpha) * 0.7;
ctx.fillStyle = Qt.hsla(h, 1.0, 0.5, 1.0);
ctx.fillRect(x, y, 1, 1);
}
}
}
Component.onCompleted: {
// Generate random data with alpha values
for (var i = 0; i < 1000; ++i) {
data.push([Math.random() * 400, Math.random() * 300, Math.random()]);
}
for (var i = 0; i < 100; ++i) {
data.push([Math.random() * 20 + i / 2 + 100,
Math.random() * 20 + 200, Math.random()]);
}
for (var i = 0; i < 100; ++i) {
data.push([Math.random() * 20 + i / 2 + 300,
Math.random() * 20 - i / 3 + 200, Math.random()]);
}
mainCanvas.requestPaint();
}
}
}
You can Try it Online!
Upvotes: 0