Download visualization/static/viewer.js from tmepass000/matter-os: direct link, hf CLI and curl.
- Browser
- Download file 6.07 kB
-
https://huggingface.co/tmepass000/matter-os/resolve/main/visualization/static/viewer.js
- Command line
-
hf download hf://tmepass000/matter-os/visualization/static/viewer.js
-
curl -L -o viewer.js https://huggingface.co/tmepass000/matter-os/resolve/main/visualization/static/viewer.js
6.07 kB
| /* MATTER OS structure viewer - dependency-free canvas 3D renderer (Three.js/3Dmol-style interaction). | |
| * Works offline (no CDN). Drag = rotate, wheel = zoom, shift-drag = pan. | |
| * Overlays: bonds (graph, opacity = edge confidence), unit cell, forces, labels, trajectory slider (initial -> relaxed). | |
| * Colours encode element only (Jmol palette); evidence state is shown as text, never as styling. | |
| */ | |
| (function (global) { | |
| function MatterViewer(canvas) { | |
| this.c = canvas; this.ctx = canvas.getContext('2d'); | |
| this.rot = [[1,0,0],[0,1,0],[0,0,1]]; this.zoom = 1; this.pan = [0,0]; | |
| this.data = null; this.frame = null; | |
| this.show = {bonds: true, cell: true, labels: false, forces: false}; | |
| var self = this, drag = null; | |
| canvas.addEventListener('mousedown', function (e) { drag = {x: e.clientX, y: e.clientY, shift: e.shiftKey}; }); | |
| window.addEventListener('mouseup', function () { drag = null; }); | |
| window.addEventListener('mousemove', function (e) { | |
| if (!drag) return; | |
| var dx = e.clientX - drag.x, dy = e.clientY - drag.y; drag.x = e.clientX; drag.y = e.clientY; | |
| if (drag.shift) { self.pan[0] += dx; self.pan[1] += dy; } | |
| else { self.rotate(dy * 0.01, dx * 0.01); } | |
| self.draw(); | |
| }); | |
| canvas.addEventListener('wheel', function (e) { e.preventDefault(); self.zoom *= (e.deltaY < 0 ? 1.1 : 0.9); self.draw(); }, {passive: false}); | |
| } | |
| function mul(a, b) { var r = [[0,0,0],[0,0,0],[0,0,0]]; for (var i=0;i<3;i++) for (var j=0;j<3;j++) for (var k=0;k<3;k++) r[i][j]+=a[i][k]*b[k][j]; return r; } | |
| MatterViewer.prototype.rotate = function (ax, ay) { | |
| var cx = Math.cos(ax), sx = Math.sin(ax), cy = Math.cos(ay), sy = Math.sin(ay); | |
| var Rx = [[1,0,0],[0,cx,-sx],[0,sx,cx]], Ry = [[cy,0,sy],[0,1,0],[-sy,0,cy]]; | |
| this.rot = mul(Ry, mul(Rx, this.rot)); | |
| }; | |
| MatterViewer.prototype.load = function (data) { | |
| this.data = data; this.frame = null; | |
| var P = data.atoms.map(function (a) { return a.position; }); | |
| var c = [0,0,0]; P.forEach(function (p) { c[0]+=p[0]; c[1]+=p[1]; c[2]+=p[2]; }); | |
| this.center = c.map(function (x) { return x / Math.max(1, P.length); }); | |
| var r = 1; P.forEach(function (p) { r = Math.max(r, Math.hypot(p[0]-c[0]/P.length, p[1]-c[1]/P.length, p[2]-c[2]/P.length)); }); | |
| if (data.cell) r = Math.max(r, 0.6 * Math.hypot.apply(null, data.cell[0].map(function (_, i) { return data.cell[0][i] + data.cell[1][i] + data.cell[2][i]; }))); | |
| this.scale = 0.42 * Math.min(this.c.width, this.c.height) / (r + 1.5); | |
| this.draw(); | |
| }; | |
| MatterViewer.prototype.positions = function () { | |
| var d = this.data; | |
| if (this.frame !== null && d.trajectory) { | |
| var f = this.frame, A = d.trajectory[0], B = d.trajectory[1]; | |
| return A.map(function (p, i) { return [0,1,2].map(function (k) { return p[k] + f * (B[i][k] - p[k]); }); }); | |
| } | |
| return d.atoms.map(function (a) { return a.position; }); | |
| }; | |
| MatterViewer.prototype.project = function (p) { | |
| var q = [p[0]-this.center[0], p[1]-this.center[1], p[2]-this.center[2]], R = this.rot; | |
| var x = R[0][0]*q[0]+R[0][1]*q[1]+R[0][2]*q[2], y = R[1][0]*q[0]+R[1][1]*q[1]+R[1][2]*q[2], z = R[2][0]*q[0]+R[2][1]*q[1]+R[2][2]*q[2]; | |
| var s = this.scale * this.zoom; | |
| return [this.c.width/2 + this.pan[0] + x*s, this.c.height/2 + this.pan[1] - y*s, z]; | |
| }; | |
| MatterViewer.prototype.draw = function () { | |
| var ctx = this.ctx, d = this.data, self = this; if (!d) return; | |
| ctx.fillStyle = '#10141a'; ctx.fillRect(0, 0, this.c.width, this.c.height); | |
| var P = this.positions(), S = P.map(function (p) { return self.project(p); }); | |
| if (this.show.cell && d.cell) { | |
| var o = [0,0,0], C = d.cell, corners = []; | |
| for (var i=0;i<2;i++) for (var j=0;j<2;j++) for (var k=0;k<2;k++) corners.push([0,1,2].map(function (m) { return i*C[0][m]+j*C[1][m]+k*C[2][m]; })); | |
| var edges = [[0,1],[0,2],[0,4],[1,3],[1,5],[2,3],[2,6],[3,7],[4,5],[4,6],[5,7],[6,7]]; | |
| ctx.strokeStyle = '#5a6b7d'; ctx.lineWidth = 1; ctx.setLineDash([4,3]); | |
| edges.forEach(function (e) { var a = self.project(corners[e[0]]), b = self.project(corners[e[1]]); ctx.beginPath(); ctx.moveTo(a[0],a[1]); ctx.lineTo(b[0],b[1]); ctx.stroke(); }); | |
| ctx.setLineDash([]); | |
| } | |
| var items = []; | |
| if (this.show.bonds) d.bonds.forEach(function (b) { items.push({z: (S[b.a][2]+S[b.b][2])/2 - 0.01, bond: b}); }); | |
| d.atoms.forEach(function (a, i) { items.push({z: S[i][2], atom: i}); }); | |
| items.sort(function (u, v) { return u.z - v.z; }); | |
| var s = this.scale * this.zoom; | |
| items.forEach(function (it) { | |
| if (it.bond) { | |
| var a = S[it.bond.a], b = S[it.bond.b]; | |
| ctx.strokeStyle = 'rgba(200,200,200,' + (0.25 + 0.65 * (it.bond.confidence == null ? 1 : it.bond.confidence)) + ')'; | |
| ctx.lineWidth = Math.max(1.5, 0.12 * s); ctx.beginPath(); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.stroke(); | |
| } else { | |
| var i = it.atom, at = d.atoms[i], p = S[i], r = Math.max(3, 0.45 * at.radius * s); | |
| var g = ctx.createRadialGradient(p[0]-r/3, p[1]-r/3, r/6, p[0], p[1], r); | |
| g.addColorStop(0, '#ffffff'); g.addColorStop(0.25, at.color); g.addColorStop(1, '#000000'); | |
| ctx.fillStyle = g; ctx.beginPath(); ctx.arc(p[0], p[1], r, 0, 2*Math.PI); ctx.fill(); | |
| if (self.show.labels) { ctx.fillStyle = '#e6edf3'; ctx.font = '11px monospace'; ctx.fillText(at.symbol + i, p[0]+r, p[1]-r); } | |
| if (self.show.forces && d.overlays && d.overlays.forces) { | |
| var f = d.overlays.forces[i], q = self.project([P[i][0]+f[0], P[i][1]+f[1], P[i][2]+f[2]]); | |
| ctx.strokeStyle = '#f0b429'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(p[0], p[1]); ctx.lineTo(q[0], q[1]); ctx.stroke(); | |
| } | |
| } | |
| }); | |
| ctx.fillStyle = '#9fb0c0'; ctx.font = '12px sans-serif'; | |
| ctx.fillText(d.formula + ' | ' + (d.evidence_state ? d.evidence_state.maturity + ' ' + d.evidence_state.label : ''), 10, 18); | |
| ctx.fillText('style does not imply validation', 10, this.c.height - 10); | |
| }; | |
| global.MatterViewer = MatterViewer; | |
| })(window); | |