121 lines
4.5 KiB
JavaScript
Executable File
121 lines
4.5 KiB
JavaScript
Executable File
// common code for gauges components
|
|
|
|
window.GaugeComponents = window.GaugeComponents || {};
|
|
|
|
Object.assign(window.GaugeComponents, { // as a namespace
|
|
|
|
// utility functions
|
|
clampValue: function(value, min, max) {
|
|
return Math.min(max, Math.max(min, value));
|
|
},
|
|
|
|
pickEntryFromRange: function(ranges, value) {
|
|
if (!Array.isArray(ranges)) return null;
|
|
for (const entry of ranges) {
|
|
if (value >= entry.from && value <= entry.to) return entry;
|
|
}
|
|
return null;
|
|
},
|
|
|
|
/**
|
|
* Creates an SVG element with namespace
|
|
*/
|
|
createSVGElement: function(tagName, attributes = {}) {
|
|
const element = document.createElementNS('http://www.w3.org/2000/svg', tagName);
|
|
Object.entries(attributes).forEach(([key, value]) => {
|
|
element.setAttribute(key, value);
|
|
});
|
|
return element;
|
|
},
|
|
|
|
/**
|
|
* Gets CSS custom property value from computed styles
|
|
*/
|
|
getCSSVar: function(varName) {
|
|
return getComputedStyle(document.documentElement).getPropertyValue(varName).trim();
|
|
},
|
|
|
|
resolveColor: function(colorSpec, value, pct) {
|
|
if (Array.isArray(colorSpec)) {
|
|
const match = this.pickEntryFromRange(colorSpec, value);
|
|
if (match && match.color) return match.color;
|
|
}
|
|
if (typeof colorSpec === 'string' && colorSpec !== '') return colorSpec;
|
|
if (pct < 60) return this.getCSSVar('--success') || '#10b981';
|
|
if (pct < 85) return this.getCSSVar('--warning') || '#f59e0b';
|
|
return this.getCSSVar('--error') || '#ef4444';
|
|
},
|
|
|
|
formatValue: function(value, precision, suffix) {
|
|
const numericValue = Number(value);
|
|
const normalizedPrecision = Number.isFinite(precision) ? precision : 1;
|
|
if (!Number.isFinite(numericValue)) return '--';
|
|
return numericValue.toFixed(normalizedPrecision) + (suffix || '');
|
|
},
|
|
|
|
gaugeArcPoint: function(pct, radius = 50) {
|
|
const angle = Math.PI - (pct / 100) * Math.PI;
|
|
return {
|
|
x: 60 + radius * Math.cos(angle),
|
|
y: 60 - radius * Math.sin(angle),
|
|
};
|
|
},
|
|
|
|
updateWatermark: function(prefix, pct) {
|
|
const now = Date.now();
|
|
this._watermarks = this._watermarks || {};
|
|
let watermark = this._watermarks[prefix];
|
|
const resetWindow = 10 * 60 * 1000;
|
|
if (!watermark || (now - watermark.resetTs) > resetWindow) {
|
|
watermark = { lo: pct, hi: pct, resetTs: now };
|
|
this._watermarks[prefix] = watermark;
|
|
} else {
|
|
if (pct < watermark.lo) watermark.lo = pct;
|
|
if (pct > watermark.hi) watermark.hi = pct;
|
|
}
|
|
return watermark;
|
|
},
|
|
|
|
renderWatermarkTick: function(lineId, pct) {
|
|
const line = document.getElementById(lineId);
|
|
if (!line) return;
|
|
if (pct == null) {
|
|
line.setAttribute('opacity', '0');
|
|
return;
|
|
}
|
|
const outer = this.gaugeArcPoint(pct, 53);
|
|
const inner = this.gaugeArcPoint(pct, 43);
|
|
line.setAttribute('x1', outer.x.toFixed(1));
|
|
line.setAttribute('y1', outer.y.toFixed(1));
|
|
line.setAttribute('x2', inner.x.toFixed(1));
|
|
line.setAttribute('y2', inner.y.toFixed(1));
|
|
line.setAttribute('opacity', '0.7');
|
|
},
|
|
|
|
updateArcGauge: function(options) {
|
|
const value = Number(options.value);
|
|
const max = Number(options.max || 100);
|
|
const normalizedValue = Number.isFinite(value) ? value : 0;
|
|
const pct = this.clampValue(max === 0 ? 0 : (normalizedValue / max) * 100, 0, 100);
|
|
const arcLength = (pct / 100) * 157.08;
|
|
const arc = document.getElementById(options.arcId);
|
|
const text = document.getElementById(options.textId);
|
|
const meta = options.metaId ? document.getElementById(options.metaId) : null;
|
|
if (arc) {
|
|
arc.setAttribute('stroke-dasharray', arcLength.toFixed(1) + ' 157.08');
|
|
arc.setAttribute('stroke', this.resolveColor(options.color, normalizedValue, pct));
|
|
}
|
|
if (text) {
|
|
text.textContent = this.formatValue(normalizedValue, options.precision, options.suffix);
|
|
}
|
|
if (meta && options.meta != null) {
|
|
meta.textContent = options.meta;
|
|
}
|
|
if (options.watermarkPrefix) {
|
|
const watermark = this.updateWatermark(options.watermarkPrefix, pct);
|
|
this.renderWatermarkTick(options.watermarkPrefix + 'WmLo', watermark.lo);
|
|
this.renderWatermarkTick(options.watermarkPrefix + 'WmHi', watermark.hi);
|
|
}
|
|
}
|
|
|
|
}); |