UNPKG

psychart

Version:

View air conditions on a psychrometric chart

377 lines (376 loc) 18.4 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.Pumpchart = void 0; var SMath = require("smath"); var chart_1 = require("../chart"); var defaults_1 = require("./defaults"); var viridis_1 = require("viridis"); var lib_1 = require("./lib"); var units_1 = require("./units"); var dimensional_1 = require("dimensional"); /** * Show a pump's relationship between flow rate and pressure at different operating conditions. */ var Pumpchart = /** @class */ (function (_super) { __extends(Pumpchart, _super); /** * Create a new Pumpchart with custom options. * @param options Customization options for the new Pumpchart. */ function Pumpchart(options) { if (options === void 0) { options = {}; } var _this = _super.call(this, options, defaults_1.defaultOptions) || this; /** * Layers of the SVG as groups. */ _this.g = { hilites: document.createElementNS(_this.NS, 'g'), curves: document.createElementNS(_this.NS, 'g'), axes: document.createElementNS(_this.NS, 'g'), data: document.createElementNS(_this.NS, 'g'), text: document.createElementNS(_this.NS, 'g'), tips: document.createElementNS(_this.NS, 'g'), }; // Append all groups to the SVG and clear highlights on click. Object.values(_this.g).forEach(function (group) { return _this.svg.appendChild(group); }); _this.svg.addEventListener('click', function () { return chart_1.Chart.clearChildren(_this.g.hilites); }); // Compute the axes limits and intervals _this.maxFlow = 1.1 * SMath.clamp(_this.options.curve.pump.maxFlow, 0, Infinity); _this.maxHead = 1.1 * SMath.clamp(_this.options.curve.pump.maxHead, 0, Infinity); var flowStep = Pumpchart.getStep(_this.maxFlow, _this.options.size.x / _this.options.font.size / 6); var headStep = Pumpchart.getStep(_this.maxHead, _this.options.size.y / _this.options.font.size / 3); // Create the axes. var xFlowAxis = _this.createPath([ { flow: 0, head: 0 }, { flow: _this.maxFlow, head: 0 }, ]); var yHeadAxis = _this.createPath([ { flow: 0, head: 0 }, { flow: 0, head: _this.maxHead }, ]); xFlowAxis.setAttribute('stroke', _this.options.axisColor); yHeadAxis.setAttribute('stroke', _this.options.axisColor); xFlowAxis.setAttribute('stroke-width', "".concat(_this.options.axisWidth * 2, "px")); yHeadAxis.setAttribute('stroke-width', "".concat(_this.options.axisWidth * 2, "px")); xFlowAxis.setAttribute('stroke-linecap', 'round'); yHeadAxis.setAttribute('stroke-linecap', 'round'); _this.g.axes.append(xFlowAxis, yHeadAxis); for (var flow = flowStep; flow < _this.maxFlow; flow += flowStep) { // Draw iso-flow vertical lines var isoFlowLine = _this.createPath([ { flow: flow, head: 0 }, { flow: flow, head: _this.maxHead }, ], false); isoFlowLine.setAttribute('stroke', _this.options.axisColor); isoFlowLine.setAttribute('stroke-width', _this.options.axisWidth + 'px'); isoFlowLine.setAttribute('stroke-linecap', 'round'); _this.g.axes.appendChild(isoFlowLine); // Show axis label _this.drawLabel("".concat(flow).concat(_this.options.units.flow), { flow: flow, head: 0 }, 2 /* TextAnchor.N */, "Flow [".concat(_this.options.units.flow, "]")); } for (var head = headStep; head < _this.maxHead; head += headStep) { // Draw iso-head horizontal lines var isoHeadLine = _this.createPath([ { flow: 0, head: head }, { flow: _this.maxFlow, head: head }, ], false); isoHeadLine.setAttribute('stroke', _this.options.axisColor); isoHeadLine.setAttribute('stroke-width', _this.options.axisWidth + 'px'); isoHeadLine.setAttribute('stroke-linecap', 'round'); _this.g.axes.appendChild(isoHeadLine); // Show axis label _this.drawLabel("".concat(head).concat(_this.options.units.head), { flow: 0, head: head }, 4 /* TextAnchor.E */, "Head [".concat(_this.options.units.head, "]")); } // Draw the system curve var sysColor = viridis_1.Color.hex(_this.options.systemCurveColor); var nmax = SMath.clamp(_this.options.speed.max, 0, Infinity); // max speed var nop = SMath.clamp(_this.options.speed.operation, 0, nmax); // operation speed var qmax = (0, lib_1.zero)(function (q) { return _this.s(q) - _this.p(q, nmax); }, 0, 1e6); // flow @ max speed var qop = (0, lib_1.zero)(function (q) { return _this.s(q) - _this.p(q, nop); }, 0, 1e6); // flow @ operation var opPt = { flow: qop, head: _this.p(qop, nop), speed: nop }; // operation point _this.drawCurve('System Curve', sysColor, 2 * _this.options.axisWidth, function (q) { return _this.s(q); }, 0, qmax); // Draw operation axis lines var operation = _this.createPath([ { flow: 0, head: opPt.head }, opPt, { flow: opPt.flow, head: 0 }, ], false); operation.setAttribute('fill', 'none'); operation.setAttribute('stroke', sysColor.toString()); operation.setAttribute('stroke-width', "".concat(_this.options.axisWidth, "px")); operation.setAttribute('stroke-linecap', 'round'); _this.g.curves.append(operation); // Draw the operating point _this.plot(opPt, { name: 'Operation Point', radius: 5 * _this.options.axisWidth, color: _this.options.systemCurveColor, }); // Draw concentric pump performance curves var pumpColor = viridis_1.Color.hex(_this.options.pumpCurveColor); _this.drawCurve("Performance Curve at ".concat(SMath.round2(nmax, 0.1)).concat(_this.options.units.speed), pumpColor, _this.options.axisWidth * 2, function (q) { return _this.p(q, nmax); }); _this.options.speed.steps.forEach(function (speed) { _this.drawCurve('', pumpColor, _this.options.axisWidth, function (q) { return _this.p(q, speed); }, 0); }); // Copy over the operation point to the curves layer if (_this.g.data.lastChild) { _this.g.curves.appendChild(_this.g.data.lastChild); _this.clearData(); } return _this; } /** * Get the ideal axis interval */ Pumpchart.getStep = function (range, maxIntervals) { var steps = [1, 2, 2.5, 5]; var magnitude = 1; while (magnitude < 10) { for (var _i = 0, steps_1 = steps; _i < steps_1.length; _i++) { var stepi = steps_1[_i]; var step = stepi * (Math.pow(10, magnitude)); if (range / step < maxIntervals) { return step; } } magnitude++; } return 1; }; /** * Get the list of all available units for flow. * @returns A list of units */ Pumpchart.getFlowUnits = function () { return Object.keys(units_1.FlowUnits); }; /** * Get the list of all available units for head. * @returns A list of units */ Pumpchart.getHeadUnits = function () { return Object.keys(units_1.HeadUnits); }; /** * Get the list of all available units for speed. * @returns A list of units */ Pumpchart.getSpeedUnits = function () { return Object.keys(units_1.SpeedUnits); }; /** * Get the list of all available units for power. * @returns A list of units */ Pumpchart.getPowerUnits = function () { return Object.keys(units_1.PowerUnits); }; /** * Convert a state to an (x,y) coordinate. * @param state Any state in this system * @returns An (x,y) coordinate on the screen */ Pumpchart.prototype.state2xy = function (state) { var xMin = this.options.padding.x; var xMax = this.options.size.x - this.options.padding.x; var yMin = this.options.padding.y; var yMax = this.options.size.y - this.options.padding.y; return { x: SMath.clamp(SMath.translate(state.flow, 0, this.maxFlow, xMin, xMax), xMin, xMax), y: SMath.clamp(SMath.translate(state.head, 0, this.maxHead, yMax, yMin), yMin, yMax), }; }; /** * Create a SVG path element from an array of states. * @param data An array of states * @param closePath Whether or not to close the path * @returns A `<path>` element containing the array of states */ Pumpchart.prototype.createPath = function (data, closePath) { var _this = this; if (closePath === void 0) { closePath = false; } var path = document.createElementNS(this.NS, 'path'); path.setAttribute('d', 'M ' + data.map(function (pt) { var xy = _this.state2xy(pt); return xy.x + ',' + xy.y; }).join(' ') + (closePath ? ' z' : '')); return path; }; /** * Draw an axis label * @param content Label text content * @param location Label location (state) * @param color Label font color * @param anchor Label text anchor * @param tooltip Optional tooltip text on mouse hover */ Pumpchart.prototype.drawLabel = function (content, location, anchor, tooltip) { var _this = this; var textColor = viridis_1.Color.hex(this.options.textColor); var label = this.createLabel(content, this.state2xy(location), textColor, anchor, 0); this.g.text.appendChild(label); if (tooltip) { label.addEventListener('mouseover', function (e) { return _this.drawTooltip(tooltip, { x: e.offsetX, y: e.offsetY }, textColor, _this.g.tips); }); label.addEventListener('mouseleave', function () { return chart_1.Chart.clearChildren(_this.g.tips); }); } }; /** * Draw a curve `h = f(q)` on the curves layer. */ Pumpchart.prototype.drawCurve = function (tooltip, color, width, h, min, max, steps) { var _this = this; if (min === void 0) { min = 0; } if (max === void 0) { max = this.maxFlow; } if (steps === void 0) { steps = 1e3; } var states = SMath.linspace(min, max, steps).map(function (q) { return { flow: q, head: h(q) }; }); var curve = this.createPath(states, false); curve.setAttribute('fill', 'none'); curve.setAttribute('stroke', color.toString()); curve.setAttribute('stroke-width', "".concat(width, "px")); curve.setAttribute('stroke-linecap', 'round'); this.g.curves.appendChild(curve); if (tooltip) { curve.addEventListener('mouseover', function (e) { return _this.drawTooltip(tooltip, { x: e.offsetX, y: e.offsetY }, color, _this.g.tips); }); curve.addEventListener('mouseleave', function () { return chart_1.Chart.clearChildren(_this.g.tips); }); } }; /** * Draw a custom circle onto any layer. */ Pumpchart.prototype.drawCircle = function (tooltip, color, state, radius, allowHighlight, parent) { var _this = this; var circle = document.createElementNS(this.NS, 'circle'); var center = this.state2xy(state); circle.setAttribute('fill', color.toString()); circle.setAttribute('cx', "".concat(center.x, "px")); circle.setAttribute('cy', "".concat(center.y, "px")); circle.setAttribute('r', "".concat(radius, "px")); parent.appendChild(circle); if (tooltip) { circle.addEventListener('mouseover', function (e) { return _this.drawTooltip(tooltip, { x: e.offsetX, y: e.offsetY }, color, _this.g.tips); }); circle.addEventListener('mouseleave', function () { return chart_1.Chart.clearChildren(_this.g.tips); }); } if (allowHighlight) { circle.addEventListener('click', function (e) { e.stopPropagation(); _this.drawCircle('', viridis_1.Color.hex(_this.options.highlightColor), state, radius * 2, false, _this.g.hilites); }); } }; /** * Represents the pump curve `h = p(q)` * @param q Flow rate * @param speed Pump speed * @returns Head gained by the fluid by the pump */ Pumpchart.prototype.p = function (q, speed) { var n = SMath.clamp(SMath.normalize(speed, 0, this.options.speed.max), 0.01, 1); var h0 = SMath.clamp(this.options.curve.pump.maxHead * n, 0, Infinity); var q0 = SMath.clamp(this.options.curve.pump.maxFlow * n, 0, Infinity); return h0 * (1 - Math.pow((q / q0), 2)); }; /** * Represents the system curve `h = s(q)` * @param q Flow rate * @returns Head loss from the fluid by the system */ Pumpchart.prototype.s = function (q) { var h0 = SMath.clamp(this.options.curve.system.static, 0, Infinity); var k = SMath.clamp(this.options.curve.system.friction, 0, Infinity); return h0 + k * Math.pow(q, 2); }; /** * Plot a single data point. * @param state The current state of the system * @param config Display options for plotting data */ Pumpchart.prototype.plot = function (state, config) { var _a; var _this = this; var _b; if (config === void 0) { config = {}; } var options = chart_1.Chart.setDefaults(config, defaults_1.defaultDataOptions); var hasTimeStamp = Number.isFinite(options.timestamp); var nmax = SMath.clamp(this.options.speed.max, 0, Infinity); var speedEstimator = function (n) { return _this.p(state.flow, n) - state.head; }; var speed; try { speed = (_b = state.speed) !== null && _b !== void 0 ? _b : (0, lib_1.zero)(speedEstimator, 0, nmax); } catch (_c) { speed = nmax; } // Calculate the efficiency if power is given var efficiency = 0; var output = 0; if (typeof state.power === 'number') { var headQty = new dimensional_1.Quantity(state.head, units_1.HeadUnits[this.options.units.head]); if (units_1.HeadUnits[this.options.units.head].dimensions.is(dimensional_1.dimensions.Length)) { // Need to multiply by specific weight to get the head in units of pressure var specWeight = new dimensional_1.Quantity(SMath.clamp(this.options.specificGravity, 0, Infinity), dimensional_1.units.gram.over(dimensional_1.units.centimeter.pow(3)).times(dimensional_1.units.Gs)); headQty = headQty.times(specWeight); } // Efficiency = Power_{out} / Power_{in} // Power_{out} = Pressure * FlowRate var flowQty = new dimensional_1.Quantity(state.flow, units_1.FlowUnits[this.options.units.flow]); var powQty = new dimensional_1.Quantity(state.power, units_1.PowerUnits[this.options.units.power]); var eta = headQty.times(flowQty).over(powQty); output = headQty.times(flowQty).as(powQty.units).quantity; efficiency = eta.as(dimensional_1.units.Unitless).quantity * 100; } var tip = (options.name ? "".concat(options.name, "\n") : '') + (hasTimeStamp ? "".concat(new Date(options.timestamp).toLocaleString(), "\n") : '') + "Flow = ".concat(SMath.round2(state.flow, 0.1)).concat(this.options.units.flow) + "\nHead = ".concat(SMath.round2(state.head, 0.1)).concat(this.options.units.head) + "\nSpeed = ".concat(SMath.round2(speed, 0.1)).concat(this.options.units.speed).concat(typeof state.speed === 'number' ? '' : ' (est.)') + (typeof state.power === 'number' ? ("\nPower = ".concat(SMath.round2(state.power, 0.1)).concat(this.options.units.power) + "\nOutput = ".concat(SMath.round2(output, 0.1)).concat(this.options.units.power) + "\nEfficiency = ".concat(SMath.round2(efficiency, 0.1), "%")) : ''); // Determine the assigned color for this data point var gradientMin = 0; var gradientMax = 0; var gradientValue = 0; var useGradient = false; if (this.options.colorizeBy === 'time' && hasTimeStamp) { gradientMin = this.options.timestamp.start; gradientMax = this.options.timestamp.stop; gradientValue = options.timestamp; useGradient = true; } else if (this.options.colorizeBy === 'efficiency' && typeof state.power === 'number') { gradientMin = 0; gradientMax = 100; gradientValue = efficiency; useGradient = true; } if (useGradient && this.options.flipGradient) { _a = [gradientMax, gradientMin], gradientMin = _a[0], gradientMax = _a[1]; } var color = useGradient ? viridis_1.Palette[this.options.gradient].getColor(gradientValue, gradientMin, gradientMax) : viridis_1.Color.hex(options.color); this.drawCircle(tip, color, state, options.radius, true, this.g.data); }; /** * Clear all the data from this chart. */ Pumpchart.prototype.clearData = function () { chart_1.Chart.clearChildren(this.g.data); chart_1.Chart.clearChildren(this.g.hilites); }; return Pumpchart; }(chart_1.Chart)); exports.Pumpchart = Pumpchart;