Threejs heatmap with color/coordinate/intensity

Hi,
I’ve modified the heatmap code to support multiple value ranges. However, I’m encountering some console errors, and the 3D model is not loading as expected.

Could someone please help me troubleshoot this issue?
https://codepen.io/prisoner849/pen/OJwqxBy

console error:

Thanks in advance!

1 Like

Nobody, except you, knows what and how you modified.
Any chance to provide a minimal editable live code example, that demonstrates the issue? jsfiddle, codepen, codesandbox etc.

Apologies for the confusion — the updates I made in the previously shared link were not saved or reflected correctly. Please check the latest link.
https://stackblitz.com/edit/three-js-example-4z8pxvtb?file=index.js,style.css

While debugging, I noticed the value ‘10’ is hardcoded. it should be dynamic.

Do it this way, for example:
изображение

after doing above changes i getting this error

It makes sense to update to the latest revision of three.js (from what I see, you’re using r109)

okay @prisoner849

The same issue persists even with the latest version. @prisoner849

Why do you use .toArray() here:
heatPoints: { value: heatmapData.map((d) => d.coordinate.toArray()) }
Three.js does it internally, IIRC.
Just leave it as is d.coordinate.

2 Likes

Full index.js code. Just in case:

import './style';
import * as THREE from 'three';
import GUI from 'lil-gui';

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  100
);
camera.position.z = 5;

const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
window.addEventListener("resize", () => {
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(innerWidth, innerHeight);  
});

// 🌟 Step 2: Heatmap Data (Color, Coordinates, Intensity)
const heatmapData = [
  {
    color: new THREE.Color(1, 0, 0),
    coordinate: new THREE.Vector3(-2, 1, 0),
    intensity: 2.3,
  }, // Red (Hot)
  {
    color: new THREE.Color(0, 0, 1),
    coordinate: new THREE.Vector3(2, -1, 0),
    intensity: 1.3,
  }, // Blue (Cold)
  {
    color: new THREE.Color(0, 1, 0),
    coordinate: new THREE.Vector3(-1, -1, 0),
    intensity: 1.8,
  }, // Green
  {
    color: new THREE.Color(1, 1, 0),
    coordinate: new THREE.Vector3(1, 1, 0),
    intensity: 1.5,
  }, // Yellow
];
// const MAX_POINTS = 100; // Define a reasonable max size
// const dataCount = heatmapData.length; // Actual count
// 🌟 Step 3: Plane Geometry & Shader Material
const geometry = new THREE.PlaneGeometry(15, 15);
const material = new THREE.ShaderMaterial({
  uniforms: {
    heatPoints: { value: heatmapData.map((d) => {return d.coordinate.clone()}) },
    colors: { value: heatmapData.map((d) => {return d.color.clone()}) },
    intensities: { value: heatmapData.map((d) => {return d.intensity}) },
  },
  vertexShader: `
  varying vec3 vPos;
  void main() {
      vPos = position;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  }`,
  fragmentShader: `
  uniform vec3 colors[${heatmapData.length}];
  uniform vec3 heatPoints[${heatmapData.length}];
  uniform float intensities[${heatmapData.length}];
  varying vec3 vPos;
  void main() {
      vec3 heatColor = vec3(0.0);
      for (int i = 0; i < ${heatmapData.length}; i++) { // Use actual count instead of a fixed size
float dist = distance(vPos, heatPoints[i]);
float heat = exp(-dist * intensities[i]); // Smooth blending
heatColor += colors[i] * heat;
}
     gl_FragColor = vec4(heatColor, 1.0);
  }`,
});

// 🌟 Step 4: Create Heatmap Mesh & Add to Scene
const heatMapMesh = new THREE.Mesh(geometry, material);
scene.add(heatMapMesh);

// 🌟 Step 5: GUI Controls (Adjust Colors & Intensities)
const gui = new GUI();
heatmapData.forEach((_, index) => {
  gui.addColor(material.uniforms.colors.value, index).name(`Color ${index}`);
  gui.add(material.uniforms.intensities.value, index, 0.5, 5.0).name(`Intensity ${index}`);
});

animate();

function animate() {
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}

Result:

3 Likes

thank you @prisoner849

I need to apply to gltf.scene.

const loader = new THREE.GLTFLoader();
loader.load(‘model.gltf’, function (gltf) {
scene.add(gltf.scene);
});

It’s unknown, how your model looks like and what the final goal. :thinking:


The model described above requires color to be applied based on the intensity values at each corresponding position.

1 Like



const dataCount = heatmapData.length; 

 const material = new ShaderMaterial(

{ uniforms: {
 heatPoints: { value: heatmapData.map(d => d.coordinate) }, 
 colors: { value: heatmapData.map(d => d.color) },
 intensities: { value: heatmapData.map(d => d.intensity) }, }, 

vertexShader: ` varying vec3 vPos; void main() { vPos = position; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`, fragmentShader: ` uniform vec3 colors[${dataCount - 1}]; uniform vec3 heatPoints[${dataCount - 1}]; uniform float intensities[${dataCount - 1}]; varying vec3 vPos; void main() { vec3 heatColor = vec3(0.0); for (int i = 0; i < ${dataCount - 1}; i++) { // Use actual count instead of a fixed size float dist = distance(vPos, heatPoints[i]); float heat = exp(-dist * intensities[i]); // Smooth blending heatColor += colors[i] * heat; } gl_FragColor = vec4(heatColor, 1.0); }` }); 


object3D.children.forEach((child: any) => { if (child.type === 'Mesh') { const childMesh = (child as Mesh); childMesh.material = material; } });
    public getObject3D(): Observable<Group> {

        // const modelUrl = url || 'https://devum-client-public-bucket.s3.amazonaws.com/public/images/3d_model/VantalationFan%20%282%29.gltf';
        //    'assets/350-380.gltf'

        return new Observable<Group<any>>((observer) => {
            const model = new GLTFLoader();
            model['name'] = "modelUrl;"
            model.load('assets/350-380.gltf', (gltf: GLTF) => {
                const group = new Group();
                group.add(gltf.scene);
                observer.next(gltf.scene);
            }, (_event) => {
            }, (error) => {
                console.error("ThreeJsListControlRenderHelper - error", error);
                observer.error(error);
            });
        });
    }

I tried using the above logic, but it didn’t work as expected.

Just out of curiousity, how does it look like?

1 Like

Is this the result you get with the code, or is it just a reference picture of the desired result?

1 Like

It is the reference image.

The output should match the reference image when using that code