Use Threejs and D3 to visualize the global new crown epidemic

Use Threejs and D3 to visualize the global new crown epidemic

Not much to say, let s look at the overall effect first. This article mainly explains the realization of the earth

Core demand

  • The earth is translucent, you can see the back
  • Dot matrix global map
  • Generate the corresponding column according to the latitude and longitude of the data
  • The larger the value, the darker and longer the color and height of the column

Introduce Threejs and D3

< script src = "https://cdn.staticfile.org/three.js/r125/three.min.js" > </script > < script src = "https://d3js.org/d3.v6.js " > </script > < script src = "https://unpkg.com/three@0.125.0/examples/js/controls/OrbitControls.js" > </script > < script src = "https://unpkg .com/3.@ 0.125.0/examples/JS/utils/BufferGeometryUtils.js " > </Script > copy the code

Basic HTML structure

< div id = "box" style = "width: 100%; height: 100%" > < canvas id = "canvas" style = "width: 100%; height: 100%"/> </div > Copy code

Define the necessary variables

const box = document .getElementById( "box" ); const canvas = document .getElementById( "canvas" ); let glRender; //webgl renderer let camera; //camera let earthMesh; //Mesh let scene of the earth ; //scene, a large container, can be understood as the body in html let meshGroup; //all Mesh containers, All the Meshes will be placed here for our management. It can be understood as a div let controls; //track controller, which realizes the control of the overall scene. Copy the code

Initialize related variables

//Create a webgl renderer glRender = new THREE.WebGLRenderer({ canvas, alpha : true }); glRender.setSize(canvas.clientWidth, canvas.clientHeight, false ); //Create a scene scene = new THREE.Scene(); //Create a camera const fov = 45 ; const aspect = canvas.clientWidth/canvas.clientHeight; const near = 1 ; const far = 4000 ; camera = new THREE.PerspectiveCamera(fov, aspect, near, far); //Set an appropriate position camera.position.z = 400 ; //Track controller controls = new THREE.OrbitControls(camera, canvas); controls.target.set( 0 , 0 , 0 ); //Create a container meshGroup = new THREE.Group(); //Add the container to the scene scene.add(meshGroup); Copy code

Create the earth

//First create a sphere const globeRadius = 100 ; //Sphere radius const globeSegments = 64 ; //The number of spheres, the larger the number, the smoother, the greater the performance consumption const geometry = new THREE.SphereGeometry( globeRadius, globeSegments, globeSegments ); //Create a sphere material to make the sphere have texture const material = new THREE.MeshBasicMaterial({ transparent : true , //set whether to be transparent opacity : 0.5 , //transparency color : 0x000000 , //color }); //Generate the earth's mesh object earthMesh = new THREE.Mesh(geometry, material); //add the earth to the mesh container meshGroup.add(earthMesh); Copy code

The basic elements are created, but there is no display on the interface, we still need to render the scene

function screenRender () { glRender.render(scene, camera); controls.update(); requestAnimationFrame(screenRender); } screenRender() Copy code

At this time you should see a circular object, next we start to make a dot matrix map

Use drawing processing tools to draw the texture of the dot matrix Mercator projection

We record the coordinates of all the dots on the map, and save the final result in mapPoints.js

export default { "points" : [ { "x" : 1.5 , "y" : 2031.5 }, { "x" : 1.5 , "y" : 2016.5 }, ... ] } Copy code

Create a dot matrix map

// Import dot matrix data import mapPoints from "./mapPoints.js" ; /** * Method of generating dotted world map */ function createMapPoints () { //The basic material of the point. const material = new THREE.MeshBasicMaterial({ color : "#AAA" , }); const sphere = []; //Loop through all points and map 2D coordinates to 3D coordinates for ( let point of mapPoints.points) { const pos = convertFlatCoordsToSphereCoords(point.x, point.y); if (pos.x && pos.y && pos.z) { //Generate dot matrix const pingGeometry = new THREE.SphereGeometry( 0.4 , 5 , 5 ); pingGeometry.translate(pos.x, pos.y, pos.z); sphere.push(pingGeometry); } } //Combine all dots to generate a mesh object const earthMapPoints = new THREE.Mesh( THREE.BufferGeometryUtils.mergeBufferGeometries(sphere), material ); //Add to the mesh container meshGroup.add(earthMapPoints); } /** * We need to get an array of 2-dimensional points, loop through it and convert each point to its 3-dimensional position. This is the function to perform conversion. Depending on the size of the template projection you created, you may need to adjust the first few variables */ Const globeWidth = 4098/2 ; const globeHeight = 1968/2 ; function convertFlatCoordsToSphereCoords ( x, y ) { let latitude = ((x-globeWidth)/globeWidth) * -180 ; let longitude = ((y-globeHeight)/globeHeight) * -90 ; latitude = (latitude * Math .PI)/180 ; longitude = (longitude * Math .PI)/180 ; const radius = Math .cos(longitude) * globeRadius; const x = Math .cos(latitude) * radius; const y = Math .sin(longitude) * globeRadius; const z = Math .sin(latitude) * radius; return { x, y, z, }; } Copy code

Call the createMapPoints method after meshGroup.add(earthMesh)

... //Add the earth to the mesh container meshGroup.add(earthMesh); //Create a bitmap createMapPoints(); //Render the scene screenRender(); ... Copy code

Pretty!

Next, we generate the cylinder, the data is collected in disease.sh , and converted into a structure that is convenient for us to use, and saved in data.js

//The converted data import data from "./data.js" //Define the color mapping relationship of the data, which can be dynamically calculated based on the actual data const colors = [ "#ffdfe0" , "#ffc0c0" , "#FF0000" , "#ee7070" , "#c80200" , "#900000" , "#510000" , "#290000" ]; const domain = [ 1000 , 3000 , 10000 , 50000 , 100000 , 500000 , 1000000 , 1000000 ]; Copy code

Create a method for generating cylinders

function createBar () { if (!data || data.length === 0 ) return ; let color; //d3 scale const scale = d3.scaleLinear().domain(domain).range(colors); //Loop through the data data.forEach( ( {lat, lng, value: size} ) => { //Get the color corresponding to the data through the scale color = scale(size); const pos = convertLatLngToSphereCoords(lat, lng, globeRadius); if (pos.x && pos.y && pos.z) { //We use cubes to generate histograms const geometry = new THREE.BoxGeometry( 2 , 2 , 1 ) ; //Move the cube Z to stand on the surface of the earth geometry.applyMatrix4( new THREE.Matrix4().makeTranslation( 0 , 0 , -0.5 ) ); //Generate cube mesh const barMesh = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial({ color, }) ); //set location barMesh.position.set(pos.x, pos.y, pos.z); //set heading barMesh.lookAt(earthMesh.position); //Set the length of the column according to the data, except for 20000. In order to prevent the column from being too long, you can adjust it according to the actual situation, or make the parameter barMesh.scale.z = Math .max(size/20000 , 0.1 ); barMesh.updateMatrix(); //Join the mesh container meshGroup.add(barMesh); } }); } // convert latitude and longitude to spherical coordinates function convertLatLngToSphereCoords ( latitude, longitude, radius ) { const phi = (latitude * Math .PI)/180 ; const theta = ((longitude- 180 ) * Math .PI)/180 ; const x = -(radius + -1 ) * Math .cos(phi) * Math .cos(theta); const y = (radius + -1 ) * Math .sin(phi); const z = (radius + -1 ) * Math.cos(phi) * Math .sin(theta); return { x, y, z, }; } Copy code

Call createBar() after createMapPoints()

... //call the createMapPoints method after this method meshGroup.add(earthMesh); //Create a bitmap createMapPoints(); //Create a histogram createBar() //Render the scene screenRender(); ... Copy code

final effect

The complete code of the tutorial: github.com/drinkjs/Ear...

Overall effect code: github.com/drinkjs/moj...