← back to Stock Vshape Viewer
3D volume-extruded ribbons: each layer's z-thickness swells/pinches with daily volume (replaces flat ribbon + separate volume bars); depth capped to lane spacing
9b99f9ea311b1f23ed04b566fb65c2fd2ea07751 · 2026-08-27 14:05:00 -0700 · Steve
Files touched
Diff
commit 9b99f9ea311b1f23ed04b566fb65c2fd2ea07751
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Aug 27 14:05:00 2026 -0700
3D volume-extruded ribbons: each layer's z-thickness swells/pinches with daily volume (replaces flat ribbon + separate volume bars); depth capped to lane spacing
---
public/app.js | 31 ++++++++++++++++---------------
1 file changed, 16 insertions(+), 15 deletions(-)
diff --git a/public/app.js b/public/app.js
index 0518532..24e55db 100644
--- a/public/app.js
+++ b/public/app.js
@@ -121,18 +121,28 @@ function rebuildScene() {
const g = new THREE.Group();
const baseColor = new THREE.Color(an.fits ? PASS : FAIL);
- // filled ribbon (close area)
+ // VOLUME-EXTRUDED 3D ribbon — z-thickness at each column swells with that day's volume
+ const vmax = Math.max(...D.days.map(d => d.vol || 0)) || 1;
+ const depthAt = i => 0.1 + ((D.days[i].vol || 0) / vmax) * 1.35; // half-depth (bloat), capped to fit lane spacing
const pos = [], idx = [];
+ // 4 verts per column: 0 baseFront(+d), 1 topFront(+d), 2 baseBack(-d), 3 topBack(-d)
for (let i = 0; i < n; i++) {
- const x = idxToX(n, i), y = priceToY(D, D.days[i].close);
- pos.push(x, 0.02, 0); pos.push(x, y, 0);
+ const x = idxToX(n, i), y = priceToY(D, D.days[i].close), d = depthAt(i);
+ pos.push(x, 0.02, d); pos.push(x, y, d);
+ pos.push(x, 0.02, -d); pos.push(x, y, -d);
+ }
+ const Vp = 4;
+ for (let i = 0; i < n - 1; i++) {
+ const A = Vp * i, B = Vp * (i + 1);
+ idx.push(A + 0, A + 1, B + 1, A + 0, B + 1, B + 0); // front (+d)
+ idx.push(A + 2, B + 3, A + 3, A + 2, B + 2, B + 3); // back (-d)
+ idx.push(A + 1, A + 3, B + 3, A + 1, B + 3, B + 1); // top ridge (the price surface)
}
- for (let i = 0; i < n - 1; i++) { const a = 2 * i, b = 2 * i + 1, c = 2 * (i + 1), d = 2 * (i + 1) + 1; idx.push(a, b, d, a, d, c); }
const geo = new THREE.BufferGeometry();
geo.setAttribute('position', new THREE.Float32BufferAttribute(pos, 3));
geo.setIndex(idx); geo.computeVertexNormals();
- const mat = new THREE.MeshStandardMaterial({ color: baseColor, transparent: true, opacity: 0.5,
- side: THREE.DoubleSide, metalness: 0.2, roughness: 0.6, emissive: baseColor, emissiveIntensity: 0.12 });
+ const mat = new THREE.MeshStandardMaterial({ color: baseColor, transparent: true, opacity: 0.62,
+ side: THREE.DoubleSide, metalness: 0.25, roughness: 0.55, emissive: baseColor, emissiveIntensity: 0.14 });
const ribbon = new THREE.Mesh(geo, mat); ribbon.userData.ticker = t; g.add(ribbon); laneMeshes.push(ribbon);
// price line
@@ -148,15 +158,6 @@ function rebuildScene() {
new THREE.LineBasicMaterial({ color: col, transparent: true, opacity: 0.85 })));
}
- // volume
- const vmax = Math.max(...D.days.map(d => d.vol || 0)) || 1;
- for (let i = 0; i < n; i++) {
- const h = ((D.days[i].vol || 0) / vmax) * 1.7 + 0.02;
- const bar = new THREE.Mesh(new THREE.BoxGeometry(LANE_LEN / n * 0.55, h, 0.5),
- new THREE.MeshBasicMaterial({ color: baseColor, transparent: true, opacity: 0.26 }));
- bar.position.set(idxToX(n, i), -h / 2 - 0.1, 0); g.add(bar);
- }
-
// anchor spheres + labels
for (const [pt, type] of [[an.eh, 'high'], [an.tr, 'trough'], [an.rh, 'newhigh']]) {
const c = COL[type], y = priceToY(D, pt.price), x = idxToX(n, pt.i);
← 4d8a127 Volume-by-price as a left-anchored bar-graph layer (bars gro
·
back to Stock Vshape Viewer
·
Panel spec 14/14 green; serve /favicon.ico as 204 to elimina 254436e →