Component demo · Instrument readouts and controls
Thermometer
index.htm as it is written with the smart-industrial package
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<title>Thermometer Overview Demo</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="styles.css" />
</head>
<body class="viewport" theme="industrial">
<div class="smart-demo-container">
<section id="overview">
<h2>Smart.Thermometer shows a temperature as the instrument that measures it: a bulb, a capillary stem and a column standing at the reading.</h2>
<p>
A level indicator empties to nothing; a thermometer does not. The bulb is the sensing element and always holds liquid, the column rises from it, and the scale belongs to the stem rather than to the whole instrument. Beyond that the component carries what a thermometer carries and a tank has no use for: a second scale in another temperature unit, the maximum and minimum reached since the memory was last reset, a setpoint marker, and alarm bands the liquid itself can take its colour from.
</p>
</section>
<section>
<h2>A reading, with the scale on both sides</h2>
<p>
<code>min</code>, <code>max</code> and <code>value</code> are in the unit the instrument is written in. <code>scalePosition</code> chooses which sides carry a scale, and <code>ticksVisibility</code> and <code>labelsVisibility</code> decide how finely it is marked.
</p>
<div class="demo-row">
<div class="demo-instrument">
<smart-thermometer id="ambient" label="Ambient" min="-20" max="50" value="21.4" precision-digits="1" show-value></smart-thermometer>
<span>Ambient</span>
</div>
<div class="demo-instrument">
<smart-thermometer id="jacket" label="Jacket" min="0" max="200" value="138" scale-position="both" show-value precision-digits="0"></smart-thermometer>
<span>Jacket, scale both sides</span>
</div>
<div class="demo-instrument">
<smart-thermometer id="bare" label="Freezer" min="-40" max="10" value="-18" ticks-visibility="major" labels-visibility="endPoints" show-value precision-digits="0"></smart-thermometer>
<span>Endpoints only</span>
</div>
</div>
</section>
<section>
<h2>Two units at once</h2>
<p>
<code>secondaryUnit</code> puts a second scale down the far side in celsius, fahrenheit or kelvin. It is ticked at round values of <em>its own</em> unit and placed by converting each mark back, so the two scales do not line up — which is exactly what a dual-scale thermometer looks like, and the only honest way to draw one.
</p>
<div class="demo-row">
<div class="demo-instrument">
<smart-thermometer id="dual" label="Oven" min="0" max="250" value="180" unit="°C" secondary-unit="fahrenheit" scale-position="both" show-value precision-digits="0"></smart-thermometer>
<span>°C and °F</span>
</div>
<div class="demo-instrument">
<smart-thermometer id="cryo" label="Cryostat" min="0" max="320" value="77" unit="K" secondary-unit="celsius" scale-position="both" show-value precision-digits="0"></smart-thermometer>
<span>K and °C</span>
</div>
</div>
</section>
<section>
<h2>Alarm bands, a setpoint and the max-min memory</h2>
<p>
<code>ranges</code> paints the bands behind the column, <code>colorByRange</code> lets the liquid take the colour of the band it has reached, and <code>showExtremes</code> leaves the two markers a max-min thermometer leaves behind. <code>resetExtremes()</code> puts them back on the reading.
</p>
<div class="demo-row">
<div class="demo-instrument">
<smart-thermometer id="reactor" label="Reactor R-101" min="0" max="160" value="92" setpoint="110" show-setpoint show-extremes color-by-range show-value precision-digits="1" scale-position="both"></smart-thermometer>
<span>Reactor R-101</span>
</div>
<div class="demo-controls">
<label>Reading <input type="range" id="reading" min="0" max="160" step="0.5" value="92" /></label>
<label>Setpoint <input type="range" id="setpoint" min="0" max="160" step="1" value="110" /></label>
<button type="button" id="reset">Reset the memory</button>
<button type="button" id="swing">Run a batch</button>
<p class="demo-log" id="log">Highest and lowest hold where the reading has been.</p>
</div>
</div>
</section>
<section>
<h2>Laid flat, and without the bulb</h2>
<p>
<code>orientation="horizontal"</code> lays the instrument along a strip, filling from the bulb at the left. Setting <code>bulb</code> to false drops the reservoir for a thermometer drawn into a panel cut-out, where the bulb sits behind the fascia. The page sets it from script, because a boolean attribute is on whenever it is present, even as <code>bulb="false"</code>.
</p>
<div class="demo-column">
<smart-thermometer id="flat" label="Inlet" orientation="horizontal" min="0" max="120" value="64" show-value precision-digits="0"></smart-thermometer>
<smart-thermometer id="cutout" label="Outlet" orientation="horizontal" min="0" max="120" value="88" bulb="false" show-value precision-digits="0"></smart-thermometer>
</div>
</section>
<section>
<h2>A reading that is not to be trusted</h2>
<p>
<code>quality</code> is carried by every component that extends the scale engine. A <code>bad</code> reading is hatched and an <code>uncertain</code> or <code>stale</code> one is dimmed, so a value that must not be acted on never looks like one that can be.
</p>
<div class="demo-row">
<div class="demo-instrument"><smart-thermometer min="0" max="100" value="46" show-value precision-digits="0"></smart-thermometer><span>good</span></div>
<div class="demo-instrument"><smart-thermometer min="0" max="100" value="46" quality="uncertain" show-value precision-digits="0"></smart-thermometer><span>uncertain</span></div>
<div class="demo-instrument"><smart-thermometer min="0" max="100" value="46" quality="bad" show-value precision-digits="0"></smart-thermometer><span>bad</span></div>
<div class="demo-instrument"><smart-thermometer min="0" max="100" value="46" quality="stale" show-value precision-digits="0"></smart-thermometer><span>stale</span></div>
</div>
</section>
</div>
<!-- scripts -->
<script type="module" src="node_modules/smart-industrial/source/modules/smart.thermometer.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>
</body>
</html>
index.js
/// <reference path="../../../source/typescript/smart.elements.d.ts" />
/* Thermometer overview.
*
* The reading comes from the process; the instrument only shows it. What the host does own
* here is the memory: resetExtremes() is the pin on a max-min thermometer, and nothing but
* an operator should move it. */
window.onload = function () {
const reactor = document.getElementById('reactor'),
reading = document.getElementById('reading'),
setpoint = document.getElementById('setpoint'),
reset = document.getElementById('reset'),
swing = document.getElementById('swing'),
log = document.getElementById('log');
/* The alarm bands the reactor is read against. */
reactor.ranges = [
{ startValue: 0, endValue: 60, className: 'ok' },
{ startValue: 60, endValue: 125, className: 'warning' },
{ startValue: 125, endValue: 160, className: 'critical' }
];
reactor.showRanges = true;
function say() {
log.textContent = 'Reading ' + Number(reactor.value).toFixed(1) + ' °C · highest ' +
Number(reactor.highest).toFixed(1) + ' · lowest ' + Number(reactor.lowest).toFixed(1) +
' · setpoint ' + Number(reactor.setpoint).toFixed(0);
}
reading.addEventListener('input', function () {
reactor.value = Number(reading.value);
say();
});
setpoint.addEventListener('input', function () {
reactor.setpoint = Number(setpoint.value);
say();
});
reset.addEventListener('click', function () {
reactor.resetExtremes();
say();
});
/* A heat-up and cool-down, which is what the memory is for: the peak is gone from the
reading within a minute and still on the instrument an hour later. */
let timer = null;
swing.addEventListener('click', function () {
if (timer) {
clearInterval(timer);
timer = null;
swing.textContent = 'Run a batch';
return;
}
let step = 0;
swing.textContent = 'Stop';
timer = setInterval(function () {
step += 1;
const ramp = step < 40 ? 20 + step * 3.2 : Math.max(24, 148 - (step - 40) * 2.4);
reactor.value = Math.round(ramp * 10) / 10;
reading.value = reactor.value;
say();
if (step > 100) {
clearInterval(timer);
timer = null;
swing.textContent = 'Run a batch';
}
}, 120);
});
reactor.resetExtremes();
say();
/* A boolean attribute is on whenever it is present, so bulb="false" in the markup
leaves the bulb drawn; the property turns it off. */
document.getElementById('cutout').bulb = false;
};
styles.css
.smart-demo-container {
max-width: 900px;
}
section {
margin-bottom: 34px;
}
section h2 {
font-size: 16px;
font-weight: 500;
margin: 0 0 8px;
}
section p {
color: var(--smart-text-secondary-color);
margin: 0 0 14px;
max-width: 74ch;
}
code {
background: var(--smart-ui-state-hover);
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
padding: 1px 5px;
}
.demo-row {
display: flex;
flex-wrap: wrap;
gap: 40px;
align-items: flex-start;
padding: 18px 22px;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
}
.demo-column {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 26px;
padding: 18px 22px;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
}
.demo-instrument {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
font-size: 12px;
color: var(--smart-text-secondary-color);
}
.demo-controls {
display: flex;
flex-direction: column;
gap: 10px;
min-width: 240px;
}
.demo-controls label {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
}
.demo-controls input[type="range"] {
flex: 1;
}
.demo-controls button {
align-self: flex-start;
background: transparent;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
color: var(--smart-text-secondary-color);
cursor: pointer;
font-family: inherit;
font-size: 11px;
letter-spacing: .06em;
padding: 7px 14px;
text-transform: uppercase;
}
.demo-controls button:hover {
background: var(--smart-ui-state-hover);
border-color: var(--smart-ui-state-border-hover);
color: var(--smart-ui-state-color-hover);
}
.demo-log {
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
margin: 12px 0 0;
color: var(--smart-text-tertiary-color);
}
smart-thermometer {
--smart-thermometer-length: 190px;
}
.demo-column smart-thermometer {
--smart-thermometer-length: 360px;
}
The same demo in Angular, generated from the HTML demo. The frameworks guide has the project setup.
angular/app.component.html
<div class="angular-root" theme="industrial">
<div class="smart-demo-container">
<section id="overview">
<h2>Smart.Thermometer shows a temperature as the instrument that measures it: a bulb, a capillary stem and a column standing at the reading.</h2>
<p>
A level indicator empties to nothing; a thermometer does not. The bulb is the sensing element and always holds liquid, the column rises from it, and the scale belongs to the stem rather than to the whole instrument. Beyond that the component carries what a thermometer carries and a tank has no use for: a second scale in another temperature unit, the maximum and minimum reached since the memory was last reset, a setpoint marker, and alarm bands the liquid itself can take its colour from.
</p>
</section>
<section>
<h2>A reading, with the scale on both sides</h2>
<p>
<code>min</code>, <code>max</code> and <code>value</code> are in the unit the instrument is written in. <code>scalePosition</code> chooses which sides carry a scale, and <code>ticksVisibility</code> and <code>labelsVisibility</code> decide how finely it is marked.
</p>
<div class="demo-row">
<div class="demo-instrument">
<smart-thermometer id="ambient" label="Ambient" [min]="-20" [max]="50" value="21.4" [precisionDigits]="1" [showValue]="true"></smart-thermometer>
<span>Ambient</span>
</div>
<div class="demo-instrument">
<smart-thermometer id="jacket" label="Jacket" [min]="0" [max]="200" value="138" scale-position="both" [showValue]="true" [precisionDigits]="0"></smart-thermometer>
<span>Jacket, scale both sides</span>
</div>
<div class="demo-instrument">
<smart-thermometer id="bare" label="Freezer" [min]="-40" [max]="10" value="-18" ticks-visibility="major" labels-visibility="endPoints" [showValue]="true" [precisionDigits]="0"></smart-thermometer>
<span>Endpoints only</span>
</div>
</div>
</section>
<section>
<h2>Two units at once</h2>
<p>
<code>secondaryUnit</code> puts a second scale down the far side in celsius, fahrenheit or kelvin. It is ticked at round values of <em>its own</em> unit and placed by converting each mark back, so the two scales do not line up — which is exactly what a dual-scale thermometer looks like, and the only honest way to draw one.
</p>
<div class="demo-row">
<div class="demo-instrument">
<smart-thermometer id="dual" label="Oven" [min]="0" [max]="250" value="180" unit="°C" secondary-unit="fahrenheit" scale-position="both" [showValue]="true" [precisionDigits]="0"></smart-thermometer>
<span>°C and °F</span>
</div>
<div class="demo-instrument">
<smart-thermometer id="cryo" label="Cryostat" [min]="0" [max]="320" value="77" unit="K" secondary-unit="celsius" scale-position="both" [showValue]="true" [precisionDigits]="0"></smart-thermometer>
<span>K and °C</span>
</div>
</div>
</section>
<section>
<h2>Alarm bands, a setpoint and the max-min memory</h2>
<p>
<code>ranges</code> paints the bands behind the column, <code>colorByRange</code> lets the liquid take the colour of the band it has reached, and <code>showExtremes</code> leaves the two markers a max-min thermometer leaves behind. <code>resetExtremes()</code> puts them back on the reading.
</p>
<div class="demo-row">
<div class="demo-instrument">
<smart-thermometer id="reactor" label="Reactor R-101" [min]="0" [max]="160" value="92" [setpoint]="110" [showSetpoint]="true" [showExtremes]="true" [colorByRange]="true" [showValue]="true" [precisionDigits]="1" scale-position="both"></smart-thermometer>
<span>Reactor R-101</span>
</div>
<div class="demo-controls">
<label>Reading <input type="range" id="reading" min="0" max="160" step="0.5" value="92" /></label>
<label>Setpoint <input type="range" id="setpoint" min="0" max="160" step="1" value="110" /></label>
<button type="button" id="reset">Reset the memory</button>
<button type="button" id="swing">Run a batch</button>
<p class="demo-log" id="log">Highest and lowest hold where the reading has been.</p>
</div>
</div>
</section>
<section>
<h2>Laid flat, and without the bulb</h2>
<p>
<code>orientation="horizontal"</code> lays the instrument along a strip, filling from the bulb at the left. Setting <code>bulb</code> to false drops the reservoir for a thermometer drawn into a panel cut-out, where the bulb sits behind the fascia. The page sets it from script, because a boolean attribute is on whenever it is present, even as <code>bulb="false"</code>.
</p>
<div class="demo-column">
<smart-thermometer id="flat" label="Inlet" orientation="horizontal" [min]="0" [max]="120" value="64" [showValue]="true" [precisionDigits]="0"></smart-thermometer>
<smart-thermometer id="cutout" label="Outlet" orientation="horizontal" [min]="0" [max]="120" value="88" [bulb]="false" [showValue]="true" [precisionDigits]="0"></smart-thermometer>
</div>
</section>
<section>
<h2>A reading that is not to be trusted</h2>
<p>
<code>quality</code> is carried by every component that extends the scale engine. A <code>bad</code> reading is hatched and an <code>uncertain</code> or <code>stale</code> one is dimmed, so a value that must not be acted on never looks like one that can be.
</p>
<div class="demo-row">
<div class="demo-instrument"><smart-thermometer [min]="0" [max]="100" value="46" [showValue]="true" [precisionDigits]="0"></smart-thermometer><span>good</span></div>
<div class="demo-instrument"><smart-thermometer [min]="0" [max]="100" value="46" quality="uncertain" [showValue]="true" [precisionDigits]="0"></smart-thermometer><span>uncertain</span></div>
<div class="demo-instrument"><smart-thermometer [min]="0" [max]="100" value="46" quality="bad" [showValue]="true" [precisionDigits]="0"></smart-thermometer><span>bad</span></div>
<div class="demo-instrument"><smart-thermometer [min]="0" [max]="100" value="46" quality="stale" [showValue]="true" [precisionDigits]="0"></smart-thermometer><span>stale</span></div>
</div>
</section>
</div>
</div>
angular/app.component.ts
import { Component, AfterViewInit, OnDestroy } from '@angular/core';
import { ThermometerModule } from 'smart-industrial/angular/thermometer';
import { runDemo } from './demo';
/**
* Smart.UI Industrial - Thermometer in Angular.
*
* The template is the HTML demo's markup: the elements are the wrappers, so what is
* written on them is checked against their inputs. demo.ts drives them the way the
* plant's data would, once every element in the view has rendered.
*/
@Component({
selector: 'app-root',
standalone: true,
imports: [ThermometerModule],
templateUrl: './app.component.html',
styleUrl: './app.component.css'
})
export class AppComponent implements AfterViewInit, OnDestroy {
private unmounted = false;
ngAfterViewInit(): void {
const root = document.querySelector('.angular-root') as HTMLElement,
pending: Promise<void>[] = [];
root.querySelectorAll('*').forEach((element: any) => {
if (typeof element.whenRendered === 'function' && !element.isRendered) {
pending.push(element.whenRendered());
}
});
Promise.all(pending).then(() => {
if (!this.unmounted) {
runDemo();
}
});
}
ngOnDestroy(): void {
this.unmounted = true;
}
}
angular/demo.ts
// @ts-nocheck
/* The HTML demo's script, as it was written: page code that feeds the elements, not a typed
API. The types are on the wrappers in app.component.ts. */
/* Thermometer overview.
*
* The reading comes from the process; the instrument only shows it. What the host does own
* here is the memory: resetExtremes() is the pin on a max-min thermometer, and nothing but
* an operator should move it. */
export function runDemo(): void {
const reactor = document.getElementById('reactor'),
reading = document.getElementById('reading'),
setpoint = document.getElementById('setpoint'),
reset = document.getElementById('reset'),
swing = document.getElementById('swing'),
log = document.getElementById('log');
/* The alarm bands the reactor is read against. */
reactor.ranges = [
{ startValue: 0, endValue: 60, className: 'ok' },
{ startValue: 60, endValue: 125, className: 'warning' },
{ startValue: 125, endValue: 160, className: 'critical' }
];
reactor.showRanges = true;
function say() {
log.textContent = 'Reading ' + Number(reactor.value).toFixed(1) + ' °C · highest ' +
Number(reactor.highest).toFixed(1) + ' · lowest ' + Number(reactor.lowest).toFixed(1) +
' · setpoint ' + Number(reactor.setpoint).toFixed(0);
}
reading.addEventListener('input', function () {
reactor.value = Number(reading.value);
say();
});
setpoint.addEventListener('input', function () {
reactor.setpoint = Number(setpoint.value);
say();
});
reset.addEventListener('click', function () {
reactor.resetExtremes();
say();
});
/* A heat-up and cool-down, which is what the memory is for: the peak is gone from the
reading within a minute and still on the instrument an hour later. */
let timer = null;
swing.addEventListener('click', function () {
if (timer) {
clearInterval(timer);
timer = null;
swing.textContent = 'Run a batch';
return;
}
let step = 0;
swing.textContent = 'Stop';
timer = setInterval(function () {
step += 1;
const ramp = step < 40 ? 20 + step * 3.2 : Math.max(24, 148 - (step - 40) * 2.4);
reactor.value = Math.round(ramp * 10) / 10;
reading.value = reactor.value;
say();
if (step > 100) {
clearInterval(timer);
timer = null;
swing.textContent = 'Run a batch';
}
}, 120);
});
reactor.resetExtremes();
say();
/* A boolean attribute is on whenever it is present, so bulb="false" in the markup
leaves the bulb drawn; the property turns it off. */
document.getElementById('cutout').bulb = false;
}
angular/app.component.css
.smart-demo-container {
max-width: 900px;
}
section {
margin-bottom: 34px;
}
section h2 {
font-size: 16px;
font-weight: 500;
margin: 0 0 8px;
}
section p {
color: var(--smart-text-secondary-color);
margin: 0 0 14px;
max-width: 74ch;
}
code {
background: var(--smart-ui-state-hover);
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
padding: 1px 5px;
}
.demo-row {
display: flex;
flex-wrap: wrap;
gap: 40px;
align-items: flex-start;
padding: 18px 22px;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
}
.demo-column {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 26px;
padding: 18px 22px;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
}
.demo-instrument {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
font-size: 12px;
color: var(--smart-text-secondary-color);
}
.demo-controls {
display: flex;
flex-direction: column;
gap: 10px;
min-width: 240px;
}
.demo-controls label {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
}
.demo-controls input[type="range"] {
flex: 1;
}
.demo-controls button {
align-self: flex-start;
background: transparent;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
color: var(--smart-text-secondary-color);
cursor: pointer;
font-family: inherit;
font-size: 11px;
letter-spacing: .06em;
padding: 7px 14px;
text-transform: uppercase;
}
.demo-controls button:hover {
background: var(--smart-ui-state-hover);
border-color: var(--smart-ui-state-border-hover);
color: var(--smart-ui-state-color-hover);
}
.demo-log {
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
margin: 12px 0 0;
color: var(--smart-text-tertiary-color);
}
smart-thermometer {
--smart-thermometer-length: 190px;
}
.demo-column smart-thermometer {
--smart-thermometer-length: 360px;
}
The same demo in React, generated from the HTML demo. The frameworks guide has the project setup.
react/App.jsx
import 'smart-industrial/source/styles/smart.default.css';
import 'smart-industrial/source/styles/smart.industrial-elements.css';
import 'smart-industrial/source/styles/smart.industrial.css';
import 'smart-industrial/source/styles/smart.industrial-dark.css';
import './App.css';
import React from 'react';
import { Thermometer } from 'smart-industrial/react/thermometer';
/* The demo logic is the HTML demo's: it feeds the elements the way a plant's data would. */
/* Thermometer overview.
*
* The reading comes from the process; the instrument only shows it. What the host does own
* here is the memory: resetExtremes() is the pin on a max-min thermometer, and nothing but
* an operator should move it. */
const App = () => {
const root = React.useRef(null);
React.useEffect(() => {
/* The elements render after React has mounted them, and the demo's logic - written
for a page's load event - reads their parts, so it waits for every element in the
tree to have rendered first. */
const pending = Array.from(root.current.querySelectorAll('*'))
.filter((element) => typeof element.whenRendered === 'function' && !element.isRendered)
.map((element) => element.whenRendered());
let unmounted = false;
Promise.all(pending).then(() => {
if (unmounted) {
return;
}
const reactor = document.getElementById('reactor'),
reading = document.getElementById('reading'),
setpoint = document.getElementById('setpoint'),
reset = document.getElementById('reset'),
swing = document.getElementById('swing'),
log = document.getElementById('log');
/* The alarm bands the reactor is read against. */
reactor.ranges = [
{ startValue: 0, endValue: 60, className: 'ok' },
{ startValue: 60, endValue: 125, className: 'warning' },
{ startValue: 125, endValue: 160, className: 'critical' }
];
reactor.showRanges = true;
function say() {
log.textContent = 'Reading ' + Number(reactor.value).toFixed(1) + ' °C · highest ' +
Number(reactor.highest).toFixed(1) + ' · lowest ' + Number(reactor.lowest).toFixed(1) +
' · setpoint ' + Number(reactor.setpoint).toFixed(0);
}
reading.addEventListener('input', function () {
reactor.value = Number(reading.value);
say();
});
setpoint.addEventListener('input', function () {
reactor.setpoint = Number(setpoint.value);
say();
});
reset.addEventListener('click', function () {
reactor.resetExtremes();
say();
});
/* A heat-up and cool-down, which is what the memory is for: the peak is gone from the
reading within a minute and still on the instrument an hour later. */
let timer = null;
swing.addEventListener('click', function () {
if (timer) {
clearInterval(timer);
timer = null;
swing.textContent = 'Run a batch';
return;
}
let step = 0;
swing.textContent = 'Stop';
timer = setInterval(function () {
step += 1;
const ramp = step < 40 ? 20 + step * 3.2 : Math.max(24, 148 - (step - 40) * 2.4);
reactor.value = Math.round(ramp * 10) / 10;
reading.value = reactor.value;
say();
if (step > 100) {
clearInterval(timer);
timer = null;
swing.textContent = 'Run a batch';
}
}, 120);
});
reactor.resetExtremes();
say();
/* A boolean attribute is on whenever it is present, so bulb="false" in the markup
leaves the bulb drawn; the property turns it off. */
document.getElementById('cutout').bulb = false;
});
return () => {
unmounted = true;
};
}, []);
return (
<div className="react-root" ref={root} theme="industrial">
<div className="smart-demo-container">
<section id="overview">
<h2>Smart.Thermometer shows a temperature as the instrument that measures it: a bulb, a capillary stem and a column standing at the reading.</h2>
<p>A level indicator empties to nothing; a thermometer does not. The bulb is the sensing element and always holds liquid, the column rises from it, and the scale belongs to the stem rather than to the whole instrument. Beyond that the component carries what a thermometer carries and a tank has no use for: a second scale in another temperature unit, the maximum and minimum reached since the memory was last reset, a setpoint marker, and alarm bands the liquid itself can take its colour from.</p>
</section>
<section>
<h2>A reading, with the scale on both sides</h2>
<p><code>min</code>, <code>max</code> and <code>value</code> are in the unit the instrument is written in. <code>scalePosition</code> chooses which sides carry a scale, and <code>ticksVisibility</code> and <code>labelsVisibility</code> decide how finely it is marked.</p>
<div className="demo-row">
<div className="demo-instrument">
<Thermometer id="ambient" label="Ambient" min={-20} max={50} value="21.4" precisionDigits={1} showValue></Thermometer>
<span>Ambient</span>
</div>
<div className="demo-instrument">
<Thermometer id="jacket" label="Jacket" min={0} max={200} value="138" scalePosition="both" showValue precisionDigits={0}></Thermometer>
<span>Jacket, scale both sides</span>
</div>
<div className="demo-instrument">
<Thermometer id="bare" label="Freezer" min={-40} max={10} value="-18" ticksVisibility="major" labelsVisibility="endPoints" showValue precisionDigits={0}></Thermometer>
<span>Endpoints only</span>
</div>
</div>
</section>
<section>
<h2>Two units at once</h2>
<p><code>secondaryUnit</code> puts a second scale down the far side in celsius, fahrenheit or kelvin. It is ticked at round values of <em>its own</em> unit and placed by converting each mark back, so the two scales do not line up — which is exactly what a dual-scale thermometer looks like, and the only honest way to draw one.</p>
<div className="demo-row">
<div className="demo-instrument">
<Thermometer id="dual" label="Oven" min={0} max={250} value="180" unit="°C" secondaryUnit="fahrenheit" scalePosition="both" showValue precisionDigits={0}></Thermometer>
<span>°C and °F</span>
</div>
<div className="demo-instrument">
<Thermometer id="cryo" label="Cryostat" min={0} max={320} value="77" unit="K" secondaryUnit="celsius" scalePosition="both" showValue precisionDigits={0}></Thermometer>
<span>K and °C</span>
</div>
</div>
</section>
<section>
<h2>Alarm bands, a setpoint and the max-min memory</h2>
<p><code>ranges</code> paints the bands behind the column, <code>colorByRange</code> lets the liquid take the colour of the band it has reached, and <code>showExtremes</code> leaves the two markers a max-min thermometer leaves behind. <code>resetExtremes()</code> puts them back on the reading.</p>
<div className="demo-row">
<div className="demo-instrument">
<Thermometer id="reactor" label="Reactor R-101" min={0} max={160} value="92" setpoint={110} showSetpoint showExtremes colorByRange showValue precisionDigits={1} scalePosition="both"></Thermometer>
<span>Reactor R-101</span>
</div>
<div className="demo-controls">
<label>Reading <input type="range" id="reading" min="0" max="160" step="0.5" defaultValue="92" /></label>
<label>Setpoint <input type="range" id="setpoint" min="0" max="160" step="1" defaultValue="110" /></label>
<button type="button" id="reset">Reset the memory</button>
<button type="button" id="swing">Run a batch</button>
<p className="demo-log" id="log">Highest and lowest hold where the reading has been.</p>
</div>
</div>
</section>
<section>
<h2>Laid flat, and without the bulb</h2>
<p><code>orientation="horizontal"</code> lays the instrument along a strip, filling from the bulb at the left. Setting <code>bulb</code> to false drops the reservoir for a thermometer drawn into a panel cut-out, where the bulb sits behind the fascia. The page sets it from script, because a boolean attribute is on whenever it is present, even as <code>bulb="false"</code>.</p>
<div className="demo-column">
<Thermometer id="flat" label="Inlet" orientation="horizontal" min={0} max={120} value="64" showValue precisionDigits={0}></Thermometer>
<Thermometer id="cutout" label="Outlet" orientation="horizontal" min={0} max={120} value="88" bulb={false} showValue precisionDigits={0}></Thermometer>
</div>
</section>
<section>
<h2>A reading that is not to be trusted</h2>
<p><code>quality</code> is carried by every component that extends the scale engine. A <code>bad</code> reading is hatched and an <code>uncertain</code> or <code>stale</code> one is dimmed, so a value that must not be acted on never looks like one that can be.</p>
<div className="demo-row">
<div className="demo-instrument">
<Thermometer min={0} max={100} value="46" showValue precisionDigits={0}></Thermometer>
<span>good</span>
</div>
<div className="demo-instrument">
<Thermometer min={0} max={100} value="46" quality="uncertain" showValue precisionDigits={0}></Thermometer>
<span>uncertain</span>
</div>
<div className="demo-instrument">
<Thermometer min={0} max={100} value="46" quality="bad" showValue precisionDigits={0}></Thermometer>
<span>bad</span>
</div>
<div className="demo-instrument">
<Thermometer min={0} max={100} value="46" quality="stale" showValue precisionDigits={0}></Thermometer>
<span>stale</span>
</div>
</div>
</section>
</div>
</div>
);
};
export default App;
react/App.css
.smart-demo-container {
max-width: 900px;
}
section {
margin-bottom: 34px;
}
section h2 {
font-size: 16px;
font-weight: 500;
margin: 0 0 8px;
}
section p {
color: var(--smart-text-secondary-color);
margin: 0 0 14px;
max-width: 74ch;
}
code {
background: var(--smart-ui-state-hover);
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
padding: 1px 5px;
}
.demo-row {
display: flex;
flex-wrap: wrap;
gap: 40px;
align-items: flex-start;
padding: 18px 22px;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
}
.demo-column {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 26px;
padding: 18px 22px;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
}
.demo-instrument {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
font-size: 12px;
color: var(--smart-text-secondary-color);
}
.demo-controls {
display: flex;
flex-direction: column;
gap: 10px;
min-width: 240px;
}
.demo-controls label {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
}
.demo-controls input[type="range"] {
flex: 1;
}
.demo-controls button {
align-self: flex-start;
background: transparent;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
color: var(--smart-text-secondary-color);
cursor: pointer;
font-family: inherit;
font-size: 11px;
letter-spacing: .06em;
padding: 7px 14px;
text-transform: uppercase;
}
.demo-controls button:hover {
background: var(--smart-ui-state-hover);
border-color: var(--smart-ui-state-border-hover);
color: var(--smart-ui-state-color-hover);
}
.demo-log {
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
margin: 12px 0 0;
color: var(--smart-text-tertiary-color);
}
smart-thermometer {
--smart-thermometer-length: 190px;
}
.demo-column smart-thermometer {
--smart-thermometer-length: 360px;
}
The same demo in Vue, generated from the HTML demo. The frameworks guide has the project setup.
vue/App.vue
<template>
<div class="vue-root" theme="industrial" v-pre>
<div class="smart-demo-container">
<section id="overview">
<h2>Smart.Thermometer shows a temperature as the instrument that measures it: a bulb, a capillary stem and a column standing at the reading.</h2>
<p>
A level indicator empties to nothing; a thermometer does not. The bulb is the sensing element and always holds liquid, the column rises from it, and the scale belongs to the stem rather than to the whole instrument. Beyond that the component carries what a thermometer carries and a tank has no use for: a second scale in another temperature unit, the maximum and minimum reached since the memory was last reset, a setpoint marker, and alarm bands the liquid itself can take its colour from.
</p>
</section>
<section>
<h2>A reading, with the scale on both sides</h2>
<p>
<code>min</code>, <code>max</code> and <code>value</code> are in the unit the instrument is written in. <code>scalePosition</code> chooses which sides carry a scale, and <code>ticksVisibility</code> and <code>labelsVisibility</code> decide how finely it is marked.
</p>
<div class="demo-row">
<div class="demo-instrument">
<smart-thermometer id="ambient" label="Ambient" min="-20" max="50" value="21.4" precision-digits="1" show-value></smart-thermometer>
<span>Ambient</span>
</div>
<div class="demo-instrument">
<smart-thermometer id="jacket" label="Jacket" min="0" max="200" value="138" scale-position="both" show-value precision-digits="0"></smart-thermometer>
<span>Jacket, scale both sides</span>
</div>
<div class="demo-instrument">
<smart-thermometer id="bare" label="Freezer" min="-40" max="10" value="-18" ticks-visibility="major" labels-visibility="endPoints" show-value precision-digits="0"></smart-thermometer>
<span>Endpoints only</span>
</div>
</div>
</section>
<section>
<h2>Two units at once</h2>
<p>
<code>secondaryUnit</code> puts a second scale down the far side in celsius, fahrenheit or kelvin. It is ticked at round values of <em>its own</em> unit and placed by converting each mark back, so the two scales do not line up — which is exactly what a dual-scale thermometer looks like, and the only honest way to draw one.
</p>
<div class="demo-row">
<div class="demo-instrument">
<smart-thermometer id="dual" label="Oven" min="0" max="250" value="180" unit="°C" secondary-unit="fahrenheit" scale-position="both" show-value precision-digits="0"></smart-thermometer>
<span>°C and °F</span>
</div>
<div class="demo-instrument">
<smart-thermometer id="cryo" label="Cryostat" min="0" max="320" value="77" unit="K" secondary-unit="celsius" scale-position="both" show-value precision-digits="0"></smart-thermometer>
<span>K and °C</span>
</div>
</div>
</section>
<section>
<h2>Alarm bands, a setpoint and the max-min memory</h2>
<p>
<code>ranges</code> paints the bands behind the column, <code>colorByRange</code> lets the liquid take the colour of the band it has reached, and <code>showExtremes</code> leaves the two markers a max-min thermometer leaves behind. <code>resetExtremes()</code> puts them back on the reading.
</p>
<div class="demo-row">
<div class="demo-instrument">
<smart-thermometer id="reactor" label="Reactor R-101" min="0" max="160" value="92" setpoint="110" show-setpoint show-extremes color-by-range show-value precision-digits="1" scale-position="both"></smart-thermometer>
<span>Reactor R-101</span>
</div>
<div class="demo-controls">
<label>Reading <input type="range" id="reading" min="0" max="160" step="0.5" value="92" /></label>
<label>Setpoint <input type="range" id="setpoint" min="0" max="160" step="1" value="110" /></label>
<button type="button" id="reset">Reset the memory</button>
<button type="button" id="swing">Run a batch</button>
<p class="demo-log" id="log">Highest and lowest hold where the reading has been.</p>
</div>
</div>
</section>
<section>
<h2>Laid flat, and without the bulb</h2>
<p>
<code>orientation="horizontal"</code> lays the instrument along a strip, filling from the bulb at the left. Setting <code>bulb</code> to false drops the reservoir for a thermometer drawn into a panel cut-out, where the bulb sits behind the fascia. The page sets it from script, because a boolean attribute is on whenever it is present, even as <code>bulb="false"</code>.
</p>
<div class="demo-column">
<smart-thermometer id="flat" label="Inlet" orientation="horizontal" min="0" max="120" value="64" show-value precision-digits="0"></smart-thermometer>
<smart-thermometer id="cutout" label="Outlet" orientation="horizontal" min="0" max="120" value="88" bulb="false" show-value precision-digits="0"></smart-thermometer>
</div>
</section>
<section>
<h2>A reading that is not to be trusted</h2>
<p>
<code>quality</code> is carried by every component that extends the scale engine. A <code>bad</code> reading is hatched and an <code>uncertain</code> or <code>stale</code> one is dimmed, so a value that must not be acted on never looks like one that can be.
</p>
<div class="demo-row">
<div class="demo-instrument"><smart-thermometer min="0" max="100" value="46" show-value precision-digits="0"></smart-thermometer><span>good</span></div>
<div class="demo-instrument"><smart-thermometer min="0" max="100" value="46" quality="uncertain" show-value precision-digits="0"></smart-thermometer><span>uncertain</span></div>
<div class="demo-instrument"><smart-thermometer min="0" max="100" value="46" quality="bad" show-value precision-digits="0"></smart-thermometer><span>bad</span></div>
<div class="demo-instrument"><smart-thermometer min="0" max="100" value="46" quality="stale" show-value precision-digits="0"></smart-thermometer><span>stale</span></div>
</div>
</section>
</div>
</div>
</template>
<script>
import { onMounted } from "vue";
import "smart-industrial/source/styles/smart.default.css";
import "smart-industrial/source/styles/smart.industrial-elements.css";
import "smart-industrial/source/styles/smart.industrial.css";
import "smart-industrial/source/styles/smart.industrial-dark.css";
import "smart-industrial/source/modules/smart.thermometer.js";
/* Thermometer overview.
*
* The reading comes from the process; the instrument only shows it. What the host does own
* here is the memory: resetExtremes() is the pin on a max-min thermometer, and nothing but
* an operator should move it. */
export default {
name: "app",
setup() {
onMounted(() => {
/* The elements render after Vue has mounted them, and the demo's logic - written for
a page's load event - reads their parts, so it waits for every element in the tree
to have rendered first. */
const pending = Array.from(document.querySelector(".vue-root").querySelectorAll("*"))
.filter((element) => typeof element.whenRendered === "function" && !element.isRendered)
.map((element) => element.whenRendered());
Promise.all(pending).then(() => {
const reactor = document.getElementById('reactor'),
reading = document.getElementById('reading'),
setpoint = document.getElementById('setpoint'),
reset = document.getElementById('reset'),
swing = document.getElementById('swing'),
log = document.getElementById('log');
/* The alarm bands the reactor is read against. */
reactor.ranges = [
{ startValue: 0, endValue: 60, className: 'ok' },
{ startValue: 60, endValue: 125, className: 'warning' },
{ startValue: 125, endValue: 160, className: 'critical' }
];
reactor.showRanges = true;
function say() {
log.textContent = 'Reading ' + Number(reactor.value).toFixed(1) + ' °C · highest ' +
Number(reactor.highest).toFixed(1) + ' · lowest ' + Number(reactor.lowest).toFixed(1) +
' · setpoint ' + Number(reactor.setpoint).toFixed(0);
}
reading.addEventListener('input', function () {
reactor.value = Number(reading.value);
say();
});
setpoint.addEventListener('input', function () {
reactor.setpoint = Number(setpoint.value);
say();
});
reset.addEventListener('click', function () {
reactor.resetExtremes();
say();
});
/* A heat-up and cool-down, which is what the memory is for: the peak is gone from the
reading within a minute and still on the instrument an hour later. */
let timer = null;
swing.addEventListener('click', function () {
if (timer) {
clearInterval(timer);
timer = null;
swing.textContent = 'Run a batch';
return;
}
let step = 0;
swing.textContent = 'Stop';
timer = setInterval(function () {
step += 1;
const ramp = step < 40 ? 20 + step * 3.2 : Math.max(24, 148 - (step - 40) * 2.4);
reactor.value = Math.round(ramp * 10) / 10;
reading.value = reactor.value;
say();
if (step > 100) {
clearInterval(timer);
timer = null;
swing.textContent = 'Run a batch';
}
}, 120);
});
reactor.resetExtremes();
say();
/* A boolean attribute is on whenever it is present, so bulb="false" in the markup
leaves the bulb drawn; the property turns it off. */
document.getElementById('cutout').bulb = false;
});
});
}
};
</script>
<style>
.smart-demo-container {
max-width: 900px;
}
section {
margin-bottom: 34px;
}
section h2 {
font-size: 16px;
font-weight: 500;
margin: 0 0 8px;
}
section p {
color: var(--smart-text-secondary-color);
margin: 0 0 14px;
max-width: 74ch;
}
code {
background: var(--smart-ui-state-hover);
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
padding: 1px 5px;
}
.demo-row {
display: flex;
flex-wrap: wrap;
gap: 40px;
align-items: flex-start;
padding: 18px 22px;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
}
.demo-column {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 26px;
padding: 18px 22px;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
}
.demo-instrument {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
font-size: 12px;
color: var(--smart-text-secondary-color);
}
.demo-controls {
display: flex;
flex-direction: column;
gap: 10px;
min-width: 240px;
}
.demo-controls label {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
}
.demo-controls input[type="range"] {
flex: 1;
}
.demo-controls button {
align-self: flex-start;
background: transparent;
border: 1px solid var(--smart-border);
border-radius: var(--smart-border-radius);
color: var(--smart-text-secondary-color);
cursor: pointer;
font-family: inherit;
font-size: 11px;
letter-spacing: .06em;
padding: 7px 14px;
text-transform: uppercase;
}
.demo-controls button:hover {
background: var(--smart-ui-state-hover);
border-color: var(--smart-ui-state-border-hover);
color: var(--smart-ui-state-color-hover);
}
.demo-log {
font-family: Consolas, "Courier New", monospace;
font-size: 12px;
margin: 12px 0 0;
color: var(--smart-text-tertiary-color);
}
smart-thermometer {
--smart-thermometer-length: 190px;
}
.demo-column smart-thermometer {
--smart-thermometer-length: 360px;
}
</style>
What the demo shows
- A reading, with the scale on both sides
- Two units at once
- Alarm bands, a setpoint and the max-min memory
- Laid flat, and without the bulb
- A reading that is not to be trusted
Also in instrument readouts and controls
Bar GraphBit FieldJog PanelKnobMomentary ButtonNumeric KeypadPanel MeterSelector SwitchSeven SegmentTouch Keyboard