I have series of lines, and I need to color all the area beneath or between lines with a color.
For the sake of simplicity, I have created a snippet with closed lines. In reality, i have a program that draws many lines (like a stock market graph) , and I need to color all the area below the graph. see also the screenshot.
would appreciate any help or even new approach that maybe I need to take.
<html>
<head>
<title>My first three.js app</title>
<style>
body {
margin: 0;
}
canvas {
width: 100%;
height: 100%
}
</style>
</head>
<body>
<script src="https://threejs.org/build/three.min.js"></script>
<script>
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 500);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
function createLine(startX,startY,endX,endY) {
const geometry = new THREE.Geometry();
geometry.vertices.push(new THREE.Vector3(startX, startY, 0));
geometry.vertices.push(new THREE.Vector3(endX, endY, 0));
const material = new THREE.LineBasicMaterial({
color: 0xffffff
});
return new THREE.Line(geometry, material);
}
scene.add(createLine(0,0,1,0));
scene.add(createLine(1,0,1,1));
scene.add(createLine(1,1,0,1));
scene.add(createLine(0,1,0,0));
camera.position.z = 5;
var animate = function () {
requestAnimationFrame(animate);
renderer.render(scene, camera);
};
animate();
</script>
</body>
</html>
You can use THREE.Shape
and THREE.ShapeGeometry
to draw a filled polygon by THREE.Mesh
:
function createPolygon( poly ) {
var shape = new THREE.Shape();
shape.moveTo( poly[0][0], poly[0][1] );
for (var i = 1; i < poly.length; ++ i)
shape.lineTo( poly[i][0], poly[i][1] );
shape.lineTo( poly[0][0], poly[0][1] );
var geometry = new THREE.ShapeGeometry( shape );
var material = new THREE.MeshBasicMaterial( {
color: 0x800000
} );
return new THREE.Mesh(geometry, material);
}
var poly = [[0,1],[0.25,0],[0.5,0.5],[0.75,0],[1,1]];
scene.add(createPolygon(poly))
var renderer, scene, camera, controls;
init();
animate();
function createLine(startX,startY,endX,endY) {
const geometry = new THREE.Geometry();
geometry.vertices.push(new THREE.Vector3(startX, startY, 0));
geometry.vertices.push(new THREE.Vector3(endX, endY, 0));
const material = new THREE.LineBasicMaterial({
color: 0xffffff
});
return new THREE.Line(geometry, material);
}
function createPolygon( poly ) {
var shape = new THREE.Shape();
shape.moveTo( poly[0][0], poly[0][1] );
for (var i = 1; i < poly.length; ++ i)
shape.lineTo( poly[i][0], poly[i][1] );
shape.lineTo( poly[0][0], poly[0][1] );
var geometry = new THREE.ShapeGeometry( shape );
var material = new THREE.MeshBasicMaterial( {
color: 0x800000
} );
return new THREE.Mesh(geometry, material);
}
function init() {
// renderer
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 1000 );
//camera.position.set( 20, 20, 20 );
camera.position.z = 5;
window.onresize = resize;
var light = new THREE.HemisphereLight( 0xeeeeee, 0x888888, 1 );
light.position.set( 0, 20, 0 );
scene.add( light );
var poly = [[0,1],[0.25,0],[0.5,0.5],[0.75,0],[1,1]];
scene.add(createPolygon(poly))
for (var i = 0; i < poly.length-1; ++i)
{
var i2 = i<poly.length-1 ? i+1 : 0
scene.add(createLine(poly[i][0],poly[i][1],poly[i2][0],poly[i2][1]));
}
}
function resize() {
var aspect = window.innerWidth / window.innerHeight;
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = aspect;
camera.updateProjectionMatrix();
}
function animate() {
requestAnimationFrame( animate );
renderer.render( scene, camera );
}
body {
margin: 0;
}
canvas {
width: 100%;
height: 100%
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.js"></script>
To fill it with colour, you have to use a geometry with faces (THREE.Face3()
). To have it, you need to trialngulate a shape or to use an existing geometry, which fits to your needs, with some changes of its vertices.
Below, there is a very rough concept, using an existing geometry (THREE.PlaneGeometry()
), which colours the area below the graph. But keep in mind, that such approach is okay to use, when you need to show only positive or only negative values in one graph, otherwise, the result will look strange (or better to say, ugly).
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 2, 10);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var points = [],
pointCount = 21;
var plane, line;
scene.add(new THREE.GridHelper( pointCount - 1, pointCount -1 ));
for (let i = 0; i < pointCount; i++) {
points.push({
initValue: THREE.Math.randFloat(-1, 1),
amplitude: THREE.Math.randFloat(1, 2),
speed: THREE.Math.randFloat(.5, 2)
});
}
createGraph( pointCount );
function createGraph( pointCount ) {
var planeGeom = new THREE.PlaneGeometry(pointCount - 1, 1, pointCount - 1, 1);
planeGeom.translate(0, .5, 0);
plane = new THREE.Mesh(planeGeom, new THREE.MeshBasicMaterial({
color: "red",
wireframe: false,
side: THREE.DoubleSide,
transparent: true,
opacity: .75
}));
scene.add(plane);
var lineGeom = new THREE.Geometry();
for (let i = 0; i < plane.geometry.parameters.widthSegments + 1; i++) {
lineGeom.vertices.push(planeGeom.vertices[i]); // share the upper points of the plane
}
line = new THREE.Line(lineGeom, new THREE.LineBasicMaterial({
color: "aqua"
}));
plane.add(line);
}
var time = 0;
render();
function render() {
time = Date.now() * .001;
requestAnimationFrame(render);
points.forEach( ( p, idx ) => {
plane.geometry.vertices[idx].y = 2.5 + Math.sin( (time + p.initValue) * p.speed) * p.amplitude; // the trick is that indices of upper vertices are from 0 to N consequently in row (r87),
// thus we can assign the data from the `points` array to their Y-coordinates to form the graph
});
plane.geometry.verticesNeedUpdate = true; // the most important thing when you change coordiantes of vertices
line.geometry.verticesNeedUpdate = true; // the most important thing when you change coordiantes of vertices
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
You need to create 1 big shape taking into account the whole graph you want to fill. So, just like in your image but then include the axis.
Then you can fill the shape with any kind of material.
Here are some examples on the threejs website:
https://threejs.org/examples/?q=shape#webgl_geometry_extrude_shapes2 https://threejs.org/examples/?q=shape#webgl_geometry_shapes
You probably wont need to extrude like in those examples.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With