Operator screen
Vibration Analysis
A bearing under analysis: spectrum with order markers, shaft orbit, run-up spectrogram and third-octave levels against a limit, with the ISO 10816-3 zone.
index.htm as it is written with the smart-industrial package; industrial-demos.css is the screens' shared layout
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<title>Vibration Analysis - Smart.Industrial</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" type="text/css" href="node_modules/smart-industrial/source/styles/smart.default.css" />
<link rel="stylesheet" type="text/css" href="node_modules/smart-industrial/source/styles/smart.industrial-elements.css" />
<link rel="stylesheet" type="text/css" href="node_modules/smart-industrial/source/styles/smart.industrial.css" />
<link rel="stylesheet" type="text/css" href="node_modules/smart-industrial/source/styles/smart.industrial-dark.css" />
<link rel="stylesheet" type="text/css" href="industrial-demos.css" />
<link rel="stylesheet" type="text/css" href="styles.css" />
</head>
<body class="viewport" theme="industrial-dark">
<div class="hmi-screen">
<header class="hmi-header">
<div class="hmi-identity">
<span class="hmi-plant">VIBRATION ANALYSIS</span>
<span class="hmi-area">Turbine T-3 · Bearing 2 · Proximity probes X and Y, casing accelerometer</span>
</div>
<div class="vib-tiles">
<smart-status-tile id="speedTile" label="Speed" unit="rpm" precision-digits="0"></smart-status-tile>
<smart-status-tile id="velocityTile" label="Velocity, RMS" unit="mm/s" precision-digits="2" footnote="ISO 10816-3 group 1, rigid"></smart-status-tile>
<smart-status-tile id="orbitTile" label="Orbit, Sp-p" unit="µm" precision-digits="0"></smart-status-tile>
</div>
<div class="hmi-header-actions">
<button class="hmi-button" id="defectButton" type="button">Bearing defect</button>
<button class="hmi-button hmi-button-primary" id="runUpButton" type="button">Run up</button>
<button class="hmi-theme-toggle" id="themeToggle" type="button">Light</button>
</div>
</header>
<section class="hmi-body vib-body">
<div class="hmi-panel">
<div class="hmi-panel-header"><span>Spectrum, casing accelerometer</span><span class="hmi-panel-note" id="spectrumNote">1x and BPFO marked</span></div>
<div class="hmi-panel-content vib-panel-content">
<smart-spectrum id="spectrum" sample-rate="10240" unit="g" frequency-scale="log" min-frequency="5"
averaging="exponential" averages="6" show-peaks="4" min-prominence="8"
min-level="-90" max-level="-10" label=""></smart-spectrum>
</div>
</div>
<div class="hmi-panel">
<div class="hmi-panel-header"><span>Shaft orbit</span><span class="hmi-panel-note">1x filtered, keyphasor marked</span></div>
<div class="hmi-panel-content vib-panel-content">
<smart-polar-plot id="orbit" radius-unit="µm" radius-max="150" angle-origin="top" angle-direction="clockwise" show-legend="false"></smart-polar-plot>
</div>
</div>
<div class="hmi-panel">
<div class="hmi-panel-header"><span>Run-up spectrogram</span><span class="hmi-panel-note" id="spectrogramNote">order lines climb with the speed</span></div>
<div class="hmi-panel-content vib-panel-content">
<smart-intensity-graph id="spectrogram" mode="chart" history-length="180" x-label="Time" x-unit="s" y-label="Frequency" y-unit="Hz" z-unit="dB" color-map="inferno" show-cursor-legend="false"></smart-intensity-graph>
</div>
</div>
<div class="hmi-panel">
<div class="hmi-panel-header"><span>Third-octave levels</span><span class="hmi-panel-note">against the acceptance limit</span></div>
<div class="hmi-panel-content vib-panel-content">
<smart-octave-bands id="bands" resolution="third" sample-rate="10240" unit="g" frequency-min="10" frequency-max="4000" averaging="slow" level-min="-70" level-max="-10" show-overall="false"></smart-octave-bands>
</div>
</div>
</section>
</div>
<!-- scripts -->
<script type="module" src="node_modules/smart-industrial/source/modules/smart.statustile.js"></script>
<script type="module" src="node_modules/smart-industrial/source/modules/smart.spectrum.js"></script>
<script type="module" src="node_modules/smart-industrial/source/modules/smart.polarplot.js"></script>
<script type="module" src="node_modules/smart-industrial/source/modules/smart.intensitygraph.js"></script>
<script type="module" src="node_modules/smart-industrial/source/modules/smart.octavebands.js"></script>
<script type="module" src="index.js"></script>
<script>
/* Shown once every element has rendered, so nothing moves on the way in (styles/demos.css). */
addEventListener('load', function () {
var all = Array.prototype.slice.call(document.querySelectorAll('*')),
ready = function () { document.body.classList.add('ready'); };
Promise.all(all.map(function (e) { return e.localName.indexOf('smart-') === 0 ? customElements.whenDefined(e.localName) : null; })).then(function () {
return Promise.all(all.filter(function (e) { return typeof e.whenRendered === 'function' && !e.isRendered; }).map(function (e) { return e.whenRendered(); }));
}).then(function () { requestAnimationFrame(function () { requestAnimationFrame(ready); }); }, ready);
});
</script>
<script>
/* The site's light/dark switch is the reader's choice: a screen opened from the light site
in a new tab opens light too. Framed, the site's page sets the theme itself. */
addEventListener('load', function () {
var light = false,
toggle = document.querySelector('.hmi-theme-toggle');
try { light = window.top === window && localStorage.getItem('smart-industrial-dark') === 'false'; } catch (e) { }
/* after the screen's own onload, which is where its button starts listening */
if (light && toggle) { setTimeout(function () { if (document.body.getAttribute('theme') === 'industrial-dark') { toggle.click(); } }, 0); }
});
</script>
</body>
</html>
index.js
/// <reference path="../../../source/typescript/smart.elements.d.ts" />
/* Vibration analysis screen.
*
* One shaft model feeds every display: the casing accelerometer block goes to the
* spectrum, the spectrogram and the octave bands; the two proximity probes draw the
* orbit. The model has a 1x unbalance, a 2x from misalignment, a bearing outer race
* defect that can be switched on, and a run-up that takes the speed from 600 to 3000 rpm.
* Replace the model with the monitor's data and nothing else on the screen changes. */
function gaussian() {
return Math.sqrt(-2 * Math.log(Math.random() || 1e-12)) * Math.cos(2 * Math.PI * Math.random());
}
window.onload = function () {
const spectrum = document.getElementById('spectrum'),
orbit = document.getElementById('orbit'),
spectrogram = document.getElementById('spectrogram'),
bands = document.getElementById('bands'),
speedTile = document.getElementById('speedTile'),
velocityTile = document.getElementById('velocityTile'),
orbitTile = document.getElementById('orbitTile'),
spectrumNote = document.getElementById('spectrumNote'),
defectButton = document.getElementById('defectButton'),
runUpButton = document.getElementById('runUpButton'),
themeToggle = document.getElementById('themeToggle');
const RATE = 10240, BLOCK = 4096, BPFO_ORDER = 3.57;
let rpm = 2970, targetRpm = 2970, defect = false, phase = 0, keyphasorPhase = 0, elapsed = 0, coloured = 0;
/* ── Theme ───────────────────────────────────────────────────────── */
function applyTheme(theme) {
document.body.setAttribute('theme', theme);
const elements = document.querySelectorAll('smart-status-tile, smart-spectrum, smart-polar-plot, smart-intensity-graph, smart-octave-bands');
for (let i = 0; i < elements.length; i++) {
elements[i].theme = theme;
}
themeToggle.textContent = theme === 'industrial-dark' ? 'Light' : 'Dark';
}
themeToggle.addEventListener('click', function () {
applyTheme(document.body.getAttribute('theme') === 'industrial-dark' ? 'industrial' : 'industrial-dark');
});
/* ── The shaft ───────────────────────────────────────────────────── */
function accelerometerBlock() {
const block = new Float64Array(BLOCK),
dt = 1 / RATE,
shaft = rpm / 60,
bpfo = BPFO_ORDER * shaft,
/* Unbalance grows with the square of speed, as a centrifugal force does. */
unbalance = 0.05 * Math.pow(rpm / 2970, 2);
for (let i = 0; i < BLOCK; i++) {
const t = phase + i * dt;
coloured = coloured * 0.97 + gaussian() * 0.004;
block[i] = unbalance * Math.sin(2 * Math.PI * shaft * t) +
0.014 * Math.sin(2 * Math.PI * 2 * shaft * t + 0.6) +
(defect ? 0.02 * Math.sin(2 * Math.PI * bpfo * t) + 0.012 * Math.sin(2 * Math.PI * 2 * bpfo * t + 0.7) + 0.006 * Math.sin(2 * Math.PI * 3 * bpfo * t + 1.9) : 0) +
coloured + gaussian() * 0.0006;
}
phase += BLOCK * dt;
return block;
}
function probeRevolution() {
const x = [], y = [], fx = [], fy = [],
amplitude = 45 * Math.pow(rpm / 2970, 2),
second = 10,
samples = 180;
for (let i = 0; i < samples; i++) {
const theta = (i / samples) * 2 * Math.PI + keyphasorPhase,
ex = amplitude * Math.cos(theta),
ey = amplitude * 0.62 * Math.sin(theta);
x.push(ex + second * Math.cos(2 * theta + 0.6) + gaussian() * 1.5);
y.push(ey + second * 0.8 * Math.sin(2 * theta + 0.6) + gaussian() * 1.5);
fx.push(ex);
fy.push(ey);
}
keyphasorPhase += 0.03;
return { x: x, y: y, fx: fx, fy: fy, peak: 2 * amplitude };
}
/* Overall velocity for the ISO tile, integrated from the acceleration model: the 1x
component dominates, so v = a / (2π f), in mm/s from g. */
function velocity() {
const shaft = rpm / 60,
a1 = 0.05 * Math.pow(rpm / 2970, 2) * 9.81,
a2 = 0.014 * 9.81,
v1 = a1 / (2 * Math.PI * shaft) * 1000 / Math.SQRT2,
v2 = a2 / (2 * Math.PI * 2 * shaft) * 1000 / Math.SQRT2,
vd = defect ? 0.02 * 9.81 / (2 * Math.PI * BPFO_ORDER * shaft) * 1000 / Math.SQRT2 : 0;
return Math.sqrt(v1 * v1 + v2 * v2 + vd * vd);
}
/* ── Wiring ──────────────────────────────────────────────────────── */
spectrogram.yMin = 0;
spectrogram.yMax = 256;
spectrogram.zMin = -75;
spectrogram.zMax = -15;
bands.limit = [{ frequency: 10, level: -25 }, { frequency: 100, level: -22 }, { frequency: 1000, level: -30 }, { frequency: 4000, level: -40 }];
velocityTile.trendPolarity = 'down';
function markers() {
const shaft = rpm / 60,
bpfo = BPFO_ORDER * shaft;
spectrum.markers = [
{ frequency: shaft, label: '1× ' + shaft.toFixed(1) + ' Hz' },
{ frequency: bpfo, label: 'BPFO ' + bpfo.toFixed(1) + ' Hz' }
];
}
function tick() {
/* The run-up: the speed moves towards its target at 80 rpm per tick. */
if (rpm !== targetRpm) {
rpm += Math.sign(targetRpm - rpm) * Math.min(80, Math.abs(targetRpm - rpm));
markers();
}
const block = accelerometerBlock();
spectrum.push(block);
bands.push(block);
/* The spectrogram takes the block's spectrum up to 256 Hz, 2.5 Hz per bin. */
const result = Smart.DSP.spectrum(block, { sampleRate: RATE, window: 'hann' }),
rows = Math.floor(256 / result.binWidth),
column = new Float64Array(rows);
for (let i = 0; i < rows; i++) {
column[i] = 20 * Math.log10(Math.max(1e-6, result.magnitudes[i]));
}
spectrogram.push(column, elapsed);
elapsed += BLOCK / RATE;
const rev = probeRevolution();
orbit.plots = [
{ id: 'raw', label: 'Raw', x: rev.x, y: rev.y },
{ id: 'filtered', label: '1x filtered', x: rev.fx, y: rev.fy, closed: true, lineWidth: 2 }
];
orbit.markers = [{ x: rev.x[0], y: rev.y[0], label: 'KΦ' }];
const v = velocity();
speedTile.value = rpm;
speedTile.state = rpm > 3100 ? 'warning' : 'ok';
velocityTile.value = v;
/* ISO 10816-3, group 1, rigid foundation: A up to 2.3, B to 4.5, C to 7.1, D above. */
velocityTile.state = v > 7.1 ? 'critical' : v > 4.5 ? 'warning' : v > 2.3 ? 'advisory' : 'ok';
velocityTile.footnote = 'ISO 10816-3 zone ' + (v > 7.1 ? 'D' : v > 4.5 ? 'C' : v > 2.3 ? 'B' : 'A');
orbitTile.value = rev.peak;
orbitTile.state = rev.peak > 120 ? 'warning' : 'ok';
}
defectButton.addEventListener('click', function () {
defect = !defect;
defectButton.textContent = defect ? 'Clear the defect' : 'Bearing defect';
spectrumNote.textContent = defect ? 'BPFO and its harmonics have appeared' : '1x and BPFO marked';
});
runUpButton.addEventListener('click', function () {
if (targetRpm === 2970 && rpm === 2970) {
rpm = 600;
targetRpm = 2970;
runUpButton.textContent = 'Running up...';
spectrogram.clear();
elapsed = 0;
setTimeout(function () {
runUpButton.textContent = 'Run up';
}, 4000);
}
});
markers();
applyTheme('industrial-dark');
for (let i = 0; i < 6; i++) {
tick();
}
setInterval(tick, 400);
};
styles.css
/* Vibration analysis screen: four displays fed by one shaft model. */
html,
body {
height: 100%;
margin: 0;
}
body.viewport {
background: var(--smart-industrial-canvas, var(--smart-body-background-color));
color: var(--smart-text-color);
font-family: var(--smart-font-family);
font-size: 13px;
overflow: hidden;
}
.hmi-screen {
display: grid;
grid-template-rows: auto 1fr;
gap: 10px;
padding: 10px;
height: 100%;
box-sizing: border-box;
}
.hmi-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 28px;
padding: 10px 14px;
background: var(--smart-industrial-panel-background);
border: 1px solid var(--smart-industrial-panel-border);
border-radius: 4px;
box-shadow: var(--smart-industrial-panel-shadow);
}
.hmi-identity {
display: flex;
flex-direction: column;
gap: 2px;
}
.hmi-plant {
font-size: 16px;
font-weight: 600;
letter-spacing: .08em;
}
.hmi-area {
color: var(--smart-text-tertiary-color);
font-size: 11px;
letter-spacing: .04em;
}
.hmi-header-actions {
display: flex;
align-items: center;
gap: 10px;
}
.hmi-conn[data-state="running"] .hmi-conn-dot {
background: var(--smart-industrial-alarm-warning);
}
.vib-tiles {
display: flex;
gap: 10px;
flex: 0 1 auto;
}
.vib-tiles smart-status-tile {
width: 150px;
--smart-status-tile-height: 72px;
}
.vib-body {
display: grid;
grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
gap: 10px;
min-height: 0;
}
.vib-panel-content {
display: flex;
min-height: 0;
padding: 8px;
}
.vib-panel-content > * {
flex: 1 1 auto;
height: 100%;
min-height: 240px;
--smart-spectrum-height: auto;
--smart-polar-height: auto;
--smart-intensity-height: auto;
--smart-octave-height: auto;
}
@media (max-width: 1100px) {
body.viewport {
overflow: auto;
}
.hmi-screen {
height: auto;
}
.vib-body {
grid-template-columns: 1fr;
grid-template-rows: none;
}
.vib-panel-content > * {
height: 300px;
}
}