preface

Data contains value, but the value of data needs to be discovered and explored by IT technology. Visualization can help people better analyze data, and the quality of information largely depends on its presentation. In data analysis, thermal map is undoubtedly a very good way. It is widely used in many industries.

Recently, the effect display of 3D thermal map is needed in the project. I searched related materials on the Internet and found that most of them are 2D effects or pseudo-3D, while 3D particle effects are not very good for performance experience, so I took the opportunity to write a 3D thermal map effect.

Demo: www.hightopo.com/demo/heatMa…

Partial renderings:

Application scenarios

Thermal map of personnel distribution in the building. By observing the color of an area, we can judge the real-time flow of people in that area, and know which area is crowded and which area is not crowded. This scenario can be applied to the police monitoring in the building. When an emergency occurs, scientifically and efficiently formulate diversion strategies to provide powerful help and support and reduce losses. It can also be used for fire warning and monitoring real-time temperature in the area.

Thermal diagram of indoor equipment temperature. Traditional data center reporting method is boring, not strong sense of reality, poor interactivity and so on. With the visual presentation of 3D thermal map, operation and maintenance management personnel of computer room can greatly improve work efficiency and reduce the possibility of work errors.

The overall train of thought

After deserialization of the scene, the initial parameters of the thermal map were set, and the thermal map model obtained after initialization was added to the scene to simulate the 3D thermal map effect. Finally, functions such as scanning, skin changing and temperature hint were added.

1. Data preparation

Draw the area of the thermal map in the scene, as shown

Firstly, areaNode is determined to generate thermal diagram, and then information of 20 points is randomly generated, including coordinate position (coordinate is a vertex of red cuboid) and thermal value temperature.

Here is the main code for this section:

function getTemplateList(areaNode, hot, num) {
    let heatRect = areaNode.getRect();
    let { width, height } = heatRect;
    let rackTall = areaNode.getTall();
    hot = hot + this.random(20);
    let templateList = [];
    for (leti = 0; i < num; I++) {templatelist. push({position: {x: 0.2 * width + this.random(0.6 * width), y: 0.2 * height + this.random(0.6 * height), z: 0.1 * rackTall + this.random(0.8 * rackTall)}, temperature: hot}); }return templateList;
}
let heatMapArea_1 = dm.getDataByTag('heatMapArea_1');
let templateList_1 = this.getTemplateList(
    heatMapArea_1,
    70,
    20
);Copy the code

2. The initialization

The thermal map was generated using the HT-life. js plug-in.

After hot spot data is ready, set the parameters of the heat map. The following table describes the parameters.

// Default configurationletOpacity: 0, opacity: 0, opacity: 0, opacity: 0, opacity: 0, opacity: 0, opacity: 0'rgba (0162255,0.14)'And 0.2:'rgba (48255183,0.60)'And 0.4:'rgba(255,245,48,0.70)'And 0.6:'rgba (255,73,18,0.90)'And 0.8:'rgba (217,22,0,0.95)',
        1: 'RGB (0, 179)
    },
    colorStopFn: function (v, step) { returnv * step * step }, }; // Get region datalet rackTall = areaNode.getTall();
let heatRect = areaNode.getRect();
let { width, height } = heatRect;
if (width === 0 || height === 0) return; // Heat map initializationletTHD = this. THD = new ht. Thermodynamic. Thermodynamic3d (g3d, {/ / hot tries to take up space in the box: New ht.math. Vector3(width, height, rackTall), // Configure the minimum and maximum temperature min: config.min, Max: config. Max, // render interval for each piece: 40, / / forfalse, the intersection time values of temperature regions are not accumulated. Take the highest temperature remainMax:falseOpacity: config.colorstopfn: config.colorstopfn, // color range gradient: config.colorconfig});Copy the code

3. Load the thermal map

Will be the first step to generate the hot spots, set the data object of THD, call THD. CreateThermodynamicNode () to generate a heat map of 3 d primitives. Set its related information and add the pixel to the 3D scene. Such a simple 3D thermal map is complete.

// Load the heat mapfunctionloadThermodynamic(thd, areaNode, templateList, config) { thd.setData(templateList); / / x, y, z planelet node = this.heatNode = thd.createThermodynamicNode(config.size, config.size, config.size);
    let p3 = areaNode.p3();
    node.setAnchorElevation(0);
    node.p3(p3);
    node.s({
        'interactive': true.'preventDefaultWhenInteractive': false.'3d.movable': false."wf.visible": false
    });
    g3d.dm().add(node);
}Copy the code

With the main body out of the way, let’s start talking about some of the demo’s features.

4. Temperature warning

Since IT was difficult for me to determine the current mouse coordinates (x, Y,z) in the 3D scene, I placed the TIP panel on the 2D drawing and embedded the 2D drawing in the upper layer of the 3D scene. The tip panel is controlled by listening to the onMove event in the 3D scene. Tip explicit and implicit control: When the mouse moves into the thermal map area, TIP is displayed, otherwise hidden. I ran into a problem here because I made everything except the thermal map block non-interactive, and when the mouse moved out of the area, the onMove event could not be heard, resulting in a bug that the tip panel was always there. I used setTimeout to solve this problem, and it was automatically hidden after a 1s delay, but it turned out that there was no need to abuse setTimeout at all, just hide the tip when listening onLeave. Tip value control: thd.getHeatMapValue(e. vent,’middle’) can be used to obtain the temperature of the current mouse thermal map area by calling hT-rett. js. The value property of tip can be changed in real time. The code is as follows:

G3d. mi(e => {if (e.kind === 'onMove') {
        let { clientX, clientY } = e.event;
        if (this.templateTip) {
            let value1 = this.thd1.getHeatMapValue(e.event, 'middle');
            let value2 = this.thd2.getHeatMapValue(e.event, 'middle');
            if (value1 || value1 === 0 || value2 || value2 === 0) {
                let position = g2d.getLogicalPoint({ x: clientX, y: clientY })
                this.templateTip.a('value', value1 || value2 || 0)
                let { width, height } = this.templateTip.getRect()
                this.templateTip.setPosition({ x: position.x + width / 2, y: position.y - height / 2 })
            }
        }
    } else if (kind === 'onLeave') {
        let tag = data.getTag()
        if (tag && tag.hasOwnProperty('hoverBlock') > -1) {
            this.g2d.getView().style.cursor = 'default';
        }
        this.templateTip && this.setVisible(this.templateTip, false)}})Copy the code

5. Scan

The third step of THD. CreateThermodynamicNode replace (). During the generation of thermal map objects, a model is not directly returned, but a certain direction is selected for “cutting”, the length of this direction is divided into N parts, and the thermal image of each piece is obtained by thD. getHeatMap() method. The value of n could theoretically be any value, but in order to render better, I chose 50 here so that the first render would not take too long. For each cut face, we dynamically create an HT. Node relative to the thermal area, and then use HT.default.setimage () to register the cut face as a picture to set the map for the Node (just set the two faces in the cut direction). Finally, add all nodes to the dataModel (the model that holds Data in HT).

There are two options for scanning. The first method is to create only one node instead of n nodes when cutting the patch in Step 3, and then dynamically set the map and coordinate of the node to simulate the scanning effect. The second one still creates N nodes, and then hides them all. One node is displayed by controlling at different times to simulate the scanning function. I used the second one, because the first one requires frequent modification of multiple properties, and the second one just controls’ 3D.visible ‘.

The main code is as follows:

let length;
if (dir === 'z') {
    length = rackTall;
}
else if (dir === 'x') {
    length = width;
}
else if (dir === 'y') {
    length = height;
}
let size = config.size;
for (letindex = 0; index < size; Const offset = length/size;let timer = setTimeout(() => {
        let ctx = thd.getHeatMap(index * offset, dir, colorConfig);
        let floor = this.getHeatFloor(
            areaNode,
            dir,
            ctx,
            index,
            size,
            config
        );
        this.floors.push(floor);
        dm.add(floor);
    }, 0);
    this.timers.push(timer);
}
function start() {
    this.hide();
    this.anim = true;
    this.count = 0;
    let frames = this.floors.length;
    letParams = {frames, // The number of frames to ease: 50, // The number of frames to ease: t => {return t;
        },
        finishFunc: () => {
            if(this.anim) { this.start(); } }, action: (v, t) => { this.count++; this.show(this.count); }}; this.scanning = ht.Default.startAnim(params); }function hide(index) {
    if (index || index === 0) {
        this.floors.forEach((i, j) => {
            if (index === j) {
                i.s('3d.visible'.false);
            }
            else {
                i.s('3d.visible'.true); }}); }else {
        this.floors.forEach(i => {
            i.s('3d.visible'.false); }); }}function show(index) {
    if (index || index === 0) {
        this.floors.forEach((i, j) => {
            if (index === j) {
                i.s('3d.visible'.true);
            }
            else {
                i.s('3d.visible'.false); }}); }else {
        this.floors.forEach(i => {
            i.s('3d.visible'.true); }); }}Copy the code

The first way to implement the main code:

getHeatFloor(node, dir, config) {
    let { width, height } = node.getRect();
    let rackTall = node.getTall();
    let s3 = [1, rackTall, height];
    let floor = new ht.Node();
    floor.setTag('hotspot'); Floor.setanchor3d ({x: 0.5, y: 0.5, z: 0.5}); floor.s3(s3); floor.s({ interactive:true,
        preventDefaultWhenInteractive: false.'3d.selectable': true.'3d.movable': false.'all.visible': false,
        [Top + '.visible'] :true,
        [Top + '.opacity']: config.opacity,
        [Top + '.transparent'] :true,
        [Top + '.reverse.flip'] :true,
        [Top + '.color'] :'rgba (51255231,0.10)'
    });
    return floor
}
getHeatFloorInfo(node, dir, ctx, index, size, config) {
    let { width, height } = node.getRect();
    let rackTall = node.getTall();
    let point = node.getPosition3d();
    let part = 0;
    let p3, s3;

    let Top = 'top';
    if(! dir) { dir ='z'; } // The yZ direction of the thermal diagram is opposite to the Yz direction of the HT. Dir =z indicates the vertical directionif (dir === 'x') {
        Top = 'left';
        part = (width / size) * index;
        p3 = [
            point[0] - width / 2 + part,
            point[1] + rackTall / 2,
            point[2]
        ];
        // p3 = [point[0] + part, point[1], point[2]];
        s3 = [1, rackTall, height];
    }
    else if (dir === 'y') {
        Top = 'front';
        part = (height / size) * index;
        p3 = [
            point[0],
            point[1] + rackTall / 2,
            point[2] - height / 2 + part
        ];
        s3 = [width, rackTall, 1];
    }
    else if (dir === 'z') {
        Top = 'top';
        part = (rackTall / size) * index;
        p3 = [point[0], point[1] + part, point[2]];
        s3 = [width, 1, height];
    }
    let heatName = this.generateUUID();
    ht.Default.setImage('heatMap' + heatName, ctx);
    this.heatFloorInfo.push(
        {
            img: 'heatMap' + heatName,
            p3
        }
    )
}
show(index){
    let info = this.heatFloorInfo[index]
    this.floor.p3(info.p3)
    this.floor.s('3d.visible'.true);
    this.floor.s('top.image', info.img); // Manually refresh this.floor.iv(); }Copy the code

6. Change skin

To dynamically change the background color and wall color of HT.graph3D. Graph3dView according to different scene values.

Code:

function changeSkin() {
        let backgroundColor = this.g3d.dm().getBackground(),
            dark_bg = this.g3d.dm().getDataByTag('dark_skin'),
            light_bg = this.g3d.dm().getDataByTag('light_skin');
        if(backgroundColor ! = ='RGB (255255255).) {
            this.g3d.dm().setBackground('RGB (255255255).);
        } else {
            this.g3d.dm().setBackground('rgb(0,0,0)');
        }

        dark_bg.s('2d.visible', !dark_bg.s('2d.visible'));
        dark_bg.s('3d.visible', !dark_bg.s('3d.visible'));

        light_bg.s('2d.visible', !light_bg.s('2d.visible'));
        light_bg.s('3d.visible', !light_bg.s('3d.visible'));
    }Copy the code

Ht-re-kathrum.js is currently in beta testing and will be updated when it is relatively mature. If you are interested in learning more about how to build 2D/3D visualizations, check out the examples in other articles and HT will give you some amazing things.