UNPKG

fluid-meter

Version:

a dependency free library that draws a circular meter where progress is displayed as the amount of liquid on it

636 lines 23.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CircularFluidMeter = void 0; const BaseMeter_1 = require("../../base/BaseMeter"); const BubblesLayer_1 = require("./Layers/BubblesLayer"); const FluidLayer_1 = require("./Layers/FluidLayer"); const ColorUtils_1 = require("../../utils/ColorUtils"); const CircularFluidMeterConfig_1 = require("./CircularFluidMeterConfig"); const ResponsiveUtils_1 = require("../../utils/ResponsiveUtils"); const MathUtils_1 = require("../../utils/MathUtils"); class CircularFluidMeter extends BaseMeter_1.BaseMeter { constructor(container, config) { super(container); Object.defineProperty(this, "_fluidConfiguration", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_layers", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_bubbles", { enumerable: true, configurable: true, writable: true, value: new BubblesLayer_1.BubblesLayer() }); Object.defineProperty(this, "_meterDiameter", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "_targetProgress", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_progress", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_maxProgress", { enumerable: true, configurable: true, writable: true, value: void 0 }); // computed value used to animate the variation of fluid when the progress changes Object.defineProperty(this, "_progressStepSpeed", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "_calculatedBorderWidth", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "_borderWidth", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_borderColor", { enumerable: true, configurable: true, writable: true, value: '#ff00ff' }); Object.defineProperty(this, "_padding", { enumerable: true, configurable: true, writable: true, value: 15 }); Object.defineProperty(this, "_backgroundColor", { enumerable: true, configurable: true, writable: true, value: '#ff00ff' }); Object.defineProperty(this, "_textColor", { enumerable: true, configurable: true, writable: true, value: '' }); Object.defineProperty(this, "_fontFamily", { enumerable: true, configurable: true, writable: true, value: 'Arial' }); Object.defineProperty(this, "_calculatedFontSize", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "_fontSize", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_textDropShadow", { enumerable: true, configurable: true, writable: true, value: true }); Object.defineProperty(this, "_textShadowOpacity", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_textShadowColor", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_showProgress", { enumerable: true, configurable: true, writable: true, value: true }); Object.defineProperty(this, "_showBubbles", { enumerable: true, configurable: true, writable: true, value: true }); Object.defineProperty(this, "_bubbleColor", { enumerable: true, configurable: true, writable: true, value: '#ffffff' }); Object.defineProperty(this, "_use3D", { enumerable: true, configurable: true, writable: true, value: true }); Object.defineProperty(this, "_dropShadow", { enumerable: true, configurable: true, writable: true, value: true }); Object.defineProperty(this, "_dropShadowColor", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_progressFormatter", { enumerable: true, configurable: true, writable: true, value: (value) => `${value}%` }); const computedConfig = Object.assign(Object.assign({}, CircularFluidMeterConfig_1.defaultConfig), config); this._progress = computedConfig.initialProgress; this._maxProgress = computedConfig.maxProgress; this._borderWidth = computedConfig.borderWidth; this._borderColor = computedConfig.borderColor; this._padding = computedConfig.padding; this._targetProgress = this._progress; this._backgroundColor = computedConfig.backgroundColor; this._fluidConfiguration = computedConfig.fluidConfiguration; this._textColor = computedConfig.textColor; this._textDropShadow = computedConfig.textDropShadow; this._textShadowColor = computedConfig.textShadowColor; this._textShadowOpacity = computedConfig.textShadowOpacity; this._showProgress = computedConfig.showProgress; this._fontFamily = computedConfig.fontFamily; this._fontSize = computedConfig.fontSize; this._showBubbles = computedConfig.showBubbles; this._bubbleColor = computedConfig.bubbleColor; this._use3D = computedConfig.use3D; this._dropShadow = computedConfig.dropShadow; this._dropShadowColor = computedConfig.dropShadowColor; this._progressFormatter = computedConfig.progressFormatter; this.calculateDrawingValues(); } get progress() { return this._progress; } set progress(value) { this._targetProgress = (0, MathUtils_1.clamp)(value, 0, this._maxProgress); } get maxProgress() { return this._maxProgress; } get borderWidth() { return this._borderWidth; } set borderWidth(borderWidth) { this._borderWidth = borderWidth; this.calculateDrawingValues(); } get borderColor() { return this._borderColor; } set borderColor(color) { this._borderColor = color; } get meterPadding() { return this._padding; } set meterPadding(padding) { this._padding = padding; this.calculateDrawingValues(); } get backgroundColor() { return this._backgroundColor; } set backgroundColor(color) { this._backgroundColor = color; } get textColor() { return this._textColor; } set textColor(color) { this._textColor = color; } get fontFamily() { return this._fontFamily; } set fontFamily(family) { this._fontFamily = family; } get fontSize() { return this._fontSize; } set fontSize(size) { this._fontSize = size; } get textDropShadow() { return this._textDropShadow; } set textDropShadow(dropShadow) { this._textDropShadow = dropShadow; } get textShadowOpacity() { return this._textShadowOpacity; } set textShadowOpacity(alphaLevel) { this._textShadowOpacity = (0, MathUtils_1.clamp)(alphaLevel, 0, 1); } get textShadowColor() { return this._textShadowColor; } set textShadowColor(color) { this._textShadowColor = color; } get showProgress() { return this._showProgress; } set showProgress(show) { this._showProgress = show; } get showBubbles() { return this._showBubbles; } set showBubbles(show) { this._showBubbles = show; } get bubbleColor() { return this._bubbleColor; } set bubbleColor(color) { this._bubbleColor = color; } get use3D() { return this._use3D; } set use3D(show) { this._use3D = show; } get dropShadow() { return this._dropShadow; } set dropShadow(drop) { this._dropShadow = drop; } get dropShadowColor() { return this._dropShadowColor; } set dropShadowColor(color) { this._dropShadowColor = color; } set progressFormatter(formatter) { this._progressFormatter = formatter; } draw() { this.clear(); if (this._meterDiameter <= 0 || !this._width || !this._height) { return; } if (this._dropShadow) { this._context.save(); this._context.beginPath(); this._context.shadowColor = this._dropShadowColor; this._context.shadowBlur = 10; this._context.shadowOffsetY = 5; this._context.arc(this._width / 2, this._height / 2, this._meterDiameter / 2 - 1, 0, 2 * Math.PI); this._context.closePath(); this._context.fill(); this._context.restore(); } this.drawBackground(); // #region clip this._context.save(); this._context.arc(this._width / 2, this._height / 2, this._meterDiameter / 2 - this._calculatedBorderWidth, 0, Math.PI * 2); this._context.clip(); // #endregion if (this._layers) { this.drawLayer(this._layers[0], false); this.drawLayer(this._layers[1]); } if (this._showBubbles) { this._bubbles.updateBubbleCount(); this.drawBubbles(); } if (this._showProgress) { this.drawText(); } // restore clip this._context.restore(); // can draw in whole canvas again this.drawForeground(); } clear() { this._context.clearRect(0, 0, this._width, this._height); } /** * calculates the values required to correctly draw all components. * should be called on init and on resize or when some key value * changes such as border width or padding */ calculateDrawingValues() { this._meterDiameter = this.calculateMeterDiameter(); this._layers = FluidLayer_1.FluidLayerHelper.buildFluidLayersFromConfiguration(this._fluidConfiguration, this._meterDiameter); // other computed values this._progressStepSpeed = this._maxProgress / 6; // responsive (if required) const screenWidth = window.innerWidth; if (typeof this._borderWidth == 'number') { this._calculatedBorderWidth = this._borderWidth; } else { this._calculatedBorderWidth = (0, ResponsiveUtils_1.getResponsiveValue)(screenWidth, this._borderWidth); } if (typeof this._fontSize == 'number') { this._calculatedFontSize = this._fontSize; } else { this._calculatedFontSize = (0, ResponsiveUtils_1.getResponsiveValue)(screenWidth, this._fontSize); } // values for the bubble layer this.updateBubbleLayer(); this._bubbles.reset(); } updateBubbleLayer() { const meterBottomLimit = this.getMeterBottomLimit(); let yThreshold = this.getFluidLevel(); if (this._layers) { yThreshold += this._layers[0].waveAmplitude; } let minY = meterBottomLimit * 0.85; if (minY < yThreshold) { minY = yThreshold; } const maxY = meterBottomLimit; const minX = this._width / 2 - this._meterDiameter / 2; const maxX = this._width / 2 + this._meterDiameter / 2; this._bubbles.minY = minY; this._bubbles.maxY = maxY; this._bubbles.minX = minX; this._bubbles.maxX = maxX; this._bubbles.yThreshold = yThreshold; this._bubbles.averageSize = this._meterDiameter * 0.006; this._bubbles.averageSpeed = (this._meterDiameter * 2) / 14; // should take X seconds to go from bottom to top this._bubbles.speedDeviation = this._bubbles.averageSpeed * 0.25; // calculate the amount of bubbles depending on the fill percentage let maxBubbles = this._width * 0.1; if (this._progress < this._maxProgress * 0.5 && this._progress >= this._maxProgress * 0.25) { maxBubbles = maxBubbles * 0.5; } else if (this._progress < this._maxProgress * 0.25 && this._progress >= this._maxProgress * 0.12) { maxBubbles = maxBubbles * 0.18; } else if (this._progress < this._maxProgress * 0.12) { maxBubbles = maxBubbles * 0.04; } this._bubbles.total = maxBubbles; } // bottom limit where fluid gets drawn getMeterBottomLimit() { return (this._height - (this._height - this._meterDiameter) / 2 - this._calculatedBorderWidth); } // returns the line where the fluid makes waves getFluidLevel() { let waveAmplitudeCalculation = 0; if (this._layers) { waveAmplitudeCalculation = this._layers[0].waveAmplitude / 2; } const meterFillPercentage = ((this._meterDiameter - waveAmplitudeCalculation - this._calculatedBorderWidth * 2) * this._progress) / this._maxProgress; return this.getMeterBottomLimit() - meterFillPercentage; } updateProgress() { if (this._progress === this._targetProgress) { return; } if (this._progress < this._targetProgress) { this._progress += this._progressStepSpeed * this._elapsed; if (this._progress > this._targetProgress) { this._progress = this._targetProgress; } } else if (this._progress > this._targetProgress) { this._progress -= this._progressStepSpeed * this._elapsed; if (this._progress < this._targetProgress) { this._progress = this._targetProgress; } } this.updateBubbleLayer(); } /** * draws a fluid layer * @param layer layer to draw * @param canUse3d will add gradients and details to give an impression of depth */ drawLayer(layer, canUse3d = true) { // calculate wave angle let angle = layer.angle + layer.waveSpeed * this._elapsed; if (angle > Math.PI * 2) { angle = angle - Math.PI * 2; } layer.angle = angle; // calculate horizontal position layer.horizontalPosition += layer.horizontalSpeed * this._elapsed; let x = 0; let y = 0; const amplitude = layer.waveAmplitude * Math.sin(layer.angle); const meterBottom = this.getMeterBottomLimit(); const fluidAmount = this.getFluidLevel(); this.updateProgress(); this._context.save(); this._context.beginPath(); this._context.lineTo(0, fluidAmount); while (x < this._width) { y = fluidAmount + amplitude * Math.sin((x + layer.horizontalPosition) / layer.frequency); this._context.lineTo(x, y); x++; } this._context.lineTo(x, this._height); this._context.lineTo(0, this._height); this._context.closePath(); if (this._use3D && canUse3d) { const x1 = this._width / 2; const y1 = meterBottom; const r1 = this._meterDiameter * 0.05; const gradientBackgroundFill = this._context.createRadialGradient(x1, y1, r1, x1, y1, this._meterDiameter * 0.65); const startColor = layer.color; const endColor = ColorUtils_1.ColorUtils.pSBC(-0.8, layer.color); gradientBackgroundFill.addColorStop(0, startColor); if (endColor) { gradientBackgroundFill.addColorStop(1, endColor); } this._context.fillStyle = gradientBackgroundFill; } else { this._context.fillStyle = layer.color; } this._context.fill(); this._context.restore(); } drawText() { const text = this._progressFormatter(this._progress); this._context.save(); this._context.font = `${this._calculatedFontSize}px ${this._fontFamily}`; this._context.fillStyle = this._textColor; this._context.textAlign = 'center'; this._context.textBaseline = 'middle'; if (this._textDropShadow) { this._context.save(); this._context.shadowColor = this._textShadowColor; this._context.shadowBlur = 7; this._context.globalAlpha = this._textShadowOpacity; this._context.fillText(text, this._width / 2, this._height / 2); this._context.restore(); } this._context.fillText(text, this._width / 2, this._height / 2); this._context.restore(); } drawBackground() { this._context.save(); this._context.beginPath(); this._context.arc(this._width / 2, this._height / 2, this._meterDiameter / 2 - this._calculatedBorderWidth, 0, 2 * Math.PI); this._context.closePath(); if (this._use3D) { const x1 = this._width / 2; const y1 = this._height / 2; const r1 = this._meterDiameter * 0.1; const gradientBackgroundFill = this._context.createRadialGradient(x1, y1, r1, x1, y1, this._meterDiameter * 0.75); const startColor = this._backgroundColor; const endColor = ColorUtils_1.ColorUtils.pSBC(-0.8, this.backgroundColor); gradientBackgroundFill.addColorStop(0, startColor); if (endColor) { gradientBackgroundFill.addColorStop(0.9, endColor); } this._context.fillStyle = gradientBackgroundFill; } else { this._context.fillStyle = this.backgroundColor; } this._context.fill(); this._context.restore(); } drawForeground() { this._context.save(); this._context.lineWidth = this._calculatedBorderWidth; this._context.strokeStyle = this._borderColor; this._context.beginPath(); this._context.arc(this._width / 2, this._height / 2, this._meterDiameter / 2 - this._calculatedBorderWidth / 2, 0, 2 * Math.PI); this._context.closePath(); this._context.stroke(); // inner border const innerBorderColor = ColorUtils_1.ColorUtils.pSBC(-0.35, this._borderColor); const innerBorderWidth = this._calculatedBorderWidth * 0.25; this._context.lineWidth = innerBorderWidth; this._context.strokeStyle = innerBorderColor || this._borderColor; this._context.beginPath(); this._context.arc(this._width / 2, this._height / 2, this._meterDiameter / 2 - this._calculatedBorderWidth * 0.85 - innerBorderWidth / 2, 0, 2 * Math.PI); this._context.closePath(); this._context.stroke(); // outer border const outerBorderColor = ColorUtils_1.ColorUtils.pSBC(0.05, this._borderColor); const outerBorderWidth = this._calculatedBorderWidth * 0.15; this._context.lineWidth = outerBorderWidth; this._context.strokeStyle = outerBorderColor || this._borderColor; this._context.beginPath(); this._context.arc(this._width / 2, this._height / 2, this._meterDiameter / 2 - outerBorderWidth / 2, 0, 2 * Math.PI); this._context.closePath(); this._context.stroke(); this._context.restore(); // details if (this._use3D) { this._context.save(); this._context.shadowColor = '#ffffff'; this._context.shadowBlur = 17; const availableSurface = this._meterDiameter - this._calculatedBorderWidth; let x = this._width / 2 - availableSurface / 4.5; let y = this._height / 2 - availableSurface / 5.27; let size = availableSurface * 0.045; const shineTop = new Path2D(); shineTop.arc(-this._width * 2, y, size, 0, 2 * Math.PI); this._context.shadowOffsetX = this._width * 2 + x; this._context.globalAlpha = 0.7; this._context.fill(shineTop); this._context.restore(); this._context.save(); this._context.shadowColor = '#ffffff'; this._context.shadowBlur = 11; x = this._width / 2 + availableSurface / 5; y = this._height / 2 + availableSurface / 4; size = availableSurface * 0.025; const shineBottom = new Path2D(); shineBottom.arc(-this._width * 2, y, size, 0, 2 * Math.PI); this._context.shadowOffsetX = this._width * 2 + x; this._context.globalAlpha = 0.45; this._context.fill(shineBottom); this._context.restore(); } } drawBubbles() { this._context.save(); this._bubbles.bubbles.forEach((bubble) => { bubble.update(this._elapsed); if (bubble.isDead || bubble.y < this._bubbles.yThreshold) { this._bubbles.resetBubble(bubble); } this._context.beginPath(); this._context.strokeStyle = this._bubbleColor; this._context.arc(bubble.x - bubble.currentRadius / 2, bubble.y - bubble.currentRadius / 2, bubble.currentRadius, 0, 2 * Math.PI); this._context.filter = `opacity(${bubble.currentOpacity})`; this._context.stroke(); this._context.closePath(); }); this._context.restore(); } calculateMeterDiameter() { if (this._width >= this._height) { return this._height - this._padding; } else { return this._width - this._padding; } } resize() { super.resize(); this.calculateDrawingValues(); this._bubbles.reset(); } } exports.CircularFluidMeter = CircularFluidMeter; //# sourceMappingURL=CircularFluidMeter.js.map