simscript
Version:
A Discrete Event Simulation Library in TypeScript
145 lines (144 loc) • 5.01 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Tally = void 0;
const util_1 = require("./util");
class Tally {
constructor(options) {
this._cnt = 0;
this._min = 0;
this._max = 0;
this._sum = 0;
this._sum2 = 0;
this._histo = null;
this._histoParms = null;
util_1.setOptions(this, options);
}
get min() {
return this._min;
}
get max() {
return this._max;
}
get cnt() {
return this._cnt;
}
get avg() {
return this._cnt > 0
? this._sum / this._cnt
: 0;
}
get var() {
return this._cnt > 0 && this._max > this._min
? Math.max(0, (this._sum2 - this._sum * this._sum / this._cnt) / this._cnt)
: 0;
}
get stdev() {
return Math.sqrt(this.var);
}
add(value, weight = 1) {
util_1.assert(weight >= 0, 'tally weights must be >= 0');
if (!this._cnt || value > this._max)
this._max = value;
if (!this._cnt || value < this._min)
this._min = value;
this._cnt += weight;
this._sum += value * weight;
this._sum2 += value * value * weight;
if (this._histo) {
value = util_1.clamp(value, this._histoParms.min, this._histoParms.max);
let bin = Math.floor(value / this._histoParms.size);
let cnt = this._histo.get(bin) || 0;
this._histo.set(bin, cnt + weight);
}
}
getHistogram() {
if (this._histo) {
const bins = this._histo, keys = Array.from(bins.keys());
keys.sort((a, b) => a - b);
for (let i = 1; i < keys.length; i++) {
if (keys[i] > keys[i - 1] + 1) {
keys.splice(i, 0, keys[i - 1] + 1);
}
}
const binSize = this._histoParms.size;
let h = keys.map(key => {
return {
from: key * binSize,
to: (key + 1) * binSize,
count: bins.get(key) || 0
};
});
if (h.length) {
const parms = this._histoParms, min = parms.min, max = parms.max;
if (min != null && h[0].from > this.min) {
h[0].from = this.min;
}
if (max != null && h[h.length - 1].to < this.max) {
h[h.length - 1].to = this.max;
}
}
return h;
}
return null;
}
getHistogramChart(title = '', scale = 1) {
const histo = this.getHistogram();
if (!histo || !histo.length) {
return '';
}
let maxCnt = 0;
histo.forEach(e => maxCnt = Math.max(maxCnt, e.count));
const barWidth = Math.round(1 / histo.length * 100), dec = this._histoParms.size < 1 ? 1 : 0;
let bars = '';
histo.forEach((e, index) => {
const cls = this.avg >= e.from && this.avg <= e.to ? ' class="avg"' : '', hei = Math.round(e.count / maxCnt * 100), x = index * barWidth, gap = 5;
bars += `<g${cls}>
<title>${e.count} (${Math.round(e.count / this.cnt * 100)}%)</title>
<rect
${cls}
x="calc(${x}% + ${gap}px)"
y="calc(${100 - hei}% - 1.2em)"
width="calc(${barWidth}% - ${2 * gap}px)"
height="${hei}%" />
<text
${cls}
x="${(x + barWidth / 2)}%"
y="100%"
text-anchor="middle"
dominant-baseline="text-top">
${util_1.format(e.from * scale, dec)}-${util_1.format(e.to * scale, dec)}
</text>
</g>`;
});
return `
<figure class="ss-histogram">
<figcaption>${title}</figcaption>
<svg width="100%" height="100%">
${bars}
</svg>
</figure>`;
}
setHistogramParameters(binSize, min = null, max = null) {
if (!binSize) {
this._histoParms = null;
this._histo = null;
}
else {
util_1.assert(binSize > 0, 'bin size must be positive');
util_1.assert(min == null || max == null || min <= max, 'histogram min must be <= max');
this._histoParms = {
size: binSize,
min: min,
max: max
};
this._histo = new Map();
}
}
reset() {
this._cnt = this._max = this._min = this._sum = this._sum2 = 0;
if (this._histo) {
this._histo.clear();
}
}
}
exports.Tally = Tally;