UNPKG

@nuralogix.ai/anura-web-core-sdk

Version:

Anura Web Core SDK

522 lines (519 loc) 24.9 kB
const objectFit = { CONTAIN: "contain", COVER: "cover", NONE: "none" }; class AnuraMask { version = "1.0.0"; #borderColor = "#39cb3a"; #starFillColor = "#39cb3a"; #starBorderColor = "#d1d1d1"; #pulseRateColor = "red"; #pulseRateLabelColor = "#ffffff"; #backgroundColor = "#ffffff"; #countDownLabelColor = "#000000"; #faceNotCenteredColor = "#fc6a0f"; #scaleX = 1; #scaleY = 1; #diameter = 0.8; #topMargin = 0.06; #bottomMargin = 0.02; #duration = 30; #offsetX = 0; #offsetY = 0; objectFit = objectFit.COVER; #svg; #defs; #background; #faceBorder; #ovalBorder; #borderTicks; #progressPath; #countDownCircle; #countDownLabel; #countDown; #pulseRateIcon; #pulseRateText; #intermediateResults; #starRatingGroup; #frameInfo = { faceTrackerHeight: 0, faceTrackerWidth: 0, mediaStreamHeight: 0, mediaStreamWidth: 0 }; constructor(settings) { if (settings) this.#updateSettings(settings); this.#svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); this.#defs = this.#getSVGDefs(); this.#background = this.#getBackground(); this.#faceBorder = this.#getFaceBorder(); this.#ovalBorder = this.#getOvalBorder(); this.#borderTicks = this.#getBorderTicks(); this.#progressPath = this.#getProgressPath(); this.#countDownCircle = this.#getCountDownCircle(); this.#countDownLabel = this.#getCountDownLabel(); this.#countDown = this.#getCountDown(); this.#pulseRateIcon = this.#getPulseRateIcon(); this.#pulseRateText = this.#getPulseRateText(); this.#intermediateResults = this.#getIntermediateResults(); this.#starRatingGroup = this.#getStarRatingGroup(); this.#appendChilds(); } #updateSettings(settings) { if (typeof settings.starFillColor === "string") { this.#starFillColor = settings.starFillColor; } if (typeof settings.starBorderColor === "string") { this.#starBorderColor = settings.starBorderColor; } if (typeof settings.pulseRateColor === "string") { this.#pulseRateColor = settings.pulseRateColor; } if (typeof settings.pulseRateLabelColor === "string") { this.#pulseRateLabelColor = settings.pulseRateLabelColor; } if (typeof settings.backgroundColor === "string") { this.#backgroundColor = settings.backgroundColor; } if (typeof settings.countDownLabelColor === "string") { this.#countDownLabelColor = settings.countDownLabelColor; } if (typeof settings.faceNotCenteredColor === "string") { this.#faceNotCenteredColor = settings.faceNotCenteredColor; } if (typeof settings.diameter === "number" && settings.diameter > 0 && settings.diameter <= 1) { this.#diameter = settings.diameter; } if (typeof settings.topMargin === "number" && settings.topMargin > 0 && settings.topMargin <= 1) { this.#topMargin = settings.topMargin; } if (typeof settings.bottomMargin === "number" && settings.bottomMargin > 0 && settings.bottomMargin <= 1) { this.#bottomMargin = settings.bottomMargin; } } #appendChilds() { this.#svg.appendChild(this.#defs); this.#svg.appendChild(this.#background); this.#svg.appendChild(this.#faceBorder); this.#svg.appendChild(this.#ovalBorder); this.#svg.appendChild(this.#borderTicks); this.#svg.appendChild(this.#progressPath); this.#svg.appendChild(this.#countDown); this.#svg.appendChild(this.#intermediateResults); this.#svg.appendChild(this.#starRatingGroup); this.#svg.style.position = "absolute"; this.#svg.style.top = "0"; this.#svg.style.left = "0"; this.#svg.style.zIndex = "11"; } #getSVGDefs() { const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs"); const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); path.setAttribute("d", ""); path.setAttribute("fill", "none"); path.setAttribute("stroke", "black"); const mask = document.createElementNS("http://www.w3.org/2000/svg", "mask"); mask.setAttribute("id", "cutout-mask"); const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); rect.setAttribute("fill", this.#backgroundColor); rect.setAttribute("width", "100%"); rect.setAttribute("height", "100%"); const cutOutpath = document.createElementNS("http://www.w3.org/2000/svg", "path"); cutOutpath.setAttribute("fill", "black"); cutOutpath.setAttribute("d", ""); mask.appendChild(rect); mask.appendChild(cutOutpath); defs.appendChild(path); defs.appendChild(mask); return defs; } #getBackground() { const background = document.createElementNS("http://www.w3.org/2000/svg", "rect"); background.setAttribute("fill", this.#backgroundColor); background.setAttribute("width", "100%"); background.setAttribute("height", "100%"); background.setAttribute("mask", "url(#cutout-mask)"); return background; } #getFaceBorder() { const strokeWidth = "3"; const faceBorder = document.createElementNS("http://www.w3.org/2000/svg", "g"); faceBorder.setAttribute("opacity", "1"); const animate = document.createElementNS("http://www.w3.org/2000/svg", "animate"); animate.setAttribute("attributeName", "opacity"); animate.setAttribute("values", "0;1;0"); animate.setAttribute("dur", "2s"); animate.setAttribute("repeatCount", "indefinite"); faceBorder.appendChild(animate); const topLeftToRight = document.createElementNS("http://www.w3.org/2000/svg", "line"); const topLeftToBottom = document.createElementNS("http://www.w3.org/2000/svg", "line"); const topRightToLeft = document.createElementNS("http://www.w3.org/2000/svg", "line"); const topRightToBottom = document.createElementNS("http://www.w3.org/2000/svg", "line"); const bottomRightToleft = document.createElementNS("http://www.w3.org/2000/svg", "line"); const bottomRightToTop = document.createElementNS("http://www.w3.org/2000/svg", "line"); const bottomLeftToRight = document.createElementNS("http://www.w3.org/2000/svg", "line"); const bottomLeftToTop = document.createElementNS("http://www.w3.org/2000/svg", "line"); [ topLeftToRight, topLeftToBottom, topRightToLeft, topRightToBottom, bottomRightToleft, bottomRightToTop, bottomLeftToRight, bottomLeftToTop ].forEach((element) => { element.setAttribute("x1", "0"); element.setAttribute("y1", "0"); element.setAttribute("x2", "0"); element.setAttribute("y2", "0"); element.setAttribute("stroke", this.#faceNotCenteredColor); element.setAttribute("stroke-width", strokeWidth); element.setAttribute("stroke-linecap", "round"); faceBorder.appendChild(element); }); return faceBorder; } #drawFaceBorder(topX, topY, bottomX, bottomY) { const sides = this.#faceBorder.children; const topLeftToRight = sides[1]; const topLeftToBottom = sides[2]; const topRightToLeft = sides[3]; const topRightToBottom = sides[4]; const bottomRightToleft = sides[5]; const bottomRightToTop = sides[6]; const bottomLeftToRight = sides[7]; const bottomLeftToTop = sides[8]; const length = 20; topLeftToRight.setAttribute("x1", topX.toFixed(2)); topLeftToRight.setAttribute("y1", topY.toFixed(2)); topLeftToRight.setAttribute("x2", (topX + length).toFixed(2)); topLeftToRight.setAttribute("y2", topY.toFixed(2)); topLeftToBottom.setAttribute("x1", topX.toFixed(2)); topLeftToBottom.setAttribute("y1", topY.toFixed(2)); topLeftToBottom.setAttribute("x2", topX.toFixed(2)); topLeftToBottom.setAttribute("y2", (topY + length).toFixed(2)); topRightToLeft.setAttribute("x1", bottomX.toFixed(2)); topRightToLeft.setAttribute("y1", topY.toFixed(2)); topRightToLeft.setAttribute("x2", (bottomX - length).toFixed(2)); topRightToLeft.setAttribute("y2", topY.toFixed(2)); topRightToBottom.setAttribute("x1", bottomX.toFixed(2)); topRightToBottom.setAttribute("y1", topY.toFixed(2)); topRightToBottom.setAttribute("x2", bottomX.toFixed(2)); topRightToBottom.setAttribute("y2", (topY + length).toFixed(2)); bottomRightToleft.setAttribute("x1", bottomX.toFixed(2)); bottomRightToleft.setAttribute("y1", bottomY.toFixed(2)); bottomRightToleft.setAttribute("x2", (bottomX - length).toFixed(2)); bottomRightToleft.setAttribute("y2", bottomY.toFixed(2)); bottomRightToTop.setAttribute("x1", bottomX.toFixed(2)); bottomRightToTop.setAttribute("y1", bottomY.toFixed(2)); bottomRightToTop.setAttribute("x2", bottomX.toFixed(2)); bottomRightToTop.setAttribute("y2", (bottomY - length).toFixed(2)); bottomLeftToRight.setAttribute("x1", topX.toFixed(2)); bottomLeftToRight.setAttribute("y1", bottomY.toFixed(2)); bottomLeftToRight.setAttribute("x2", (topX + length).toFixed(2)); bottomLeftToRight.setAttribute("y2", bottomY.toFixed(2)); bottomLeftToTop.setAttribute("x1", topX.toFixed(2)); bottomLeftToTop.setAttribute("y1", bottomY.toFixed(2)); bottomLeftToTop.setAttribute("x2", topX.toFixed(2)); bottomLeftToTop.setAttribute("y2", (bottomY - length).toFixed(2)); } #getOvalBorder() { const ovalBorder = document.createElementNS("http://www.w3.org/2000/svg", "path"); ovalBorder.setAttribute("d", ""); ovalBorder.setAttribute("fill", "none"); return ovalBorder; } #getTick(startX, endX, startY, endY, position) { const tick = document.createElementNS("http://www.w3.org/2000/svg", "line"); tick.setAttribute("x1", startX.toFixed(0)); tick.setAttribute("y1", startY.toFixed(0)); tick.setAttribute("x2", endX.toFixed(0)); tick.setAttribute("y2", endY.toFixed(0)); tick.setAttribute("stroke", this.#borderColor); tick.setAttribute("stroke-width", "2"); tick.setAttribute("opacity", "0.8"); tick.setAttribute("data-position", position.toString()); return tick; } #getBorderTicks() { const borderTicks = document.createElementNS("http://www.w3.org/2000/svg", "g"); return borderTicks; } #getProgressPath() { const progressPath = document.createElementNS("http://www.w3.org/2000/svg", "path"); progressPath.setAttribute("fill", "none"); progressPath.setAttribute("stroke", this.#borderColor); progressPath.setAttribute("stroke-width", "4"); progressPath.setAttribute("stroke-linecap", "round"); progressPath.setAttribute("stroke-linejoin", "round"); return progressPath; } #getCountDownCircle() { const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle"); circle.setAttribute("visibility", "hidden"); circle.setAttribute("r", "12"); circle.setAttribute("fill", this.#borderColor); return circle; } #getCountDownLabel() { const label = document.createElementNS("http://www.w3.org/2000/svg", "text"); label.setAttribute("visibility", "hidden"); label.setAttribute("x", "0"); label.setAttribute("y", "0"); label.setAttribute("fill", this.#countDownLabelColor); label.setAttribute("font-size", "14"); label.setAttribute("font-weight", "bold"); label.setAttribute("font-family", "Arial"); return label; } #getCountDown() { const countDown = document.createElementNS("http://www.w3.org/2000/svg", "g"); countDown.appendChild(this.#countDownCircle); countDown.appendChild(this.#countDownLabel); return countDown; } #getPulseRateIcon() { const pulseRateIcon = document.createElementNS("http://www.w3.org/2000/svg", "path"); pulseRateIcon.setAttribute("d", "M2 9.1371C2 14 6.01943 16.5914 8.96173 18.9109C10 19.7294 11 20.5 12 20.5C13 20.5 14 19.7294 15.0383 18.9109C17.9806 16.5914 22 14 22 9.1371C22 4.27416 16.4998 0.825464 12 5.50063C7.50016 0.825464 2 4.27416 2 9.1371Z"); pulseRateIcon.setAttribute("fill", this.#pulseRateColor); pulseRateIcon.setAttribute("transform", "translate(-12,-10.66)"); return pulseRateIcon; } #getPulseRateText() { const pulseRateText = document.createElementNS("http://www.w3.org/2000/svg", "text"); pulseRateText.setAttribute("x", "0"); pulseRateText.setAttribute("y", "2"); pulseRateText.setAttribute("fill", this.#pulseRateLabelColor); pulseRateText.setAttribute("font-size", "14"); pulseRateText.setAttribute("font-weight", "bold"); pulseRateText.setAttribute("font-family", "Arial"); pulseRateText.setAttribute("text-anchor", "middle"); pulseRateText.setAttribute("dominant-baseline", "middle"); return pulseRateText; } #getIntermediateResults() { const intermediateResults = document.createElementNS("http://www.w3.org/2000/svg", "g"); intermediateResults.setAttribute("visibility", "hidden"); const animateGroup = document.createElementNS("http://www.w3.org/2000/svg", "g"); const animateTransform = document.createElementNS("http://www.w3.org/2000/svg", "animateTransform"); animateTransform.setAttribute("attributeName", "transform"); animateTransform.setAttribute("type", "scale"); animateTransform.setAttribute("values", "2;2.15;2"); animateTransform.setAttribute("dur", "1s"); animateTransform.setAttribute("repeatCount", "indefinite"); animateTransform.setAttribute("additive", "replace"); animateGroup.appendChild(animateTransform); animateGroup.appendChild(this.#pulseRateIcon); intermediateResults.appendChild(animateGroup); intermediateResults.appendChild(this.#pulseRateText); return intermediateResults; } #getStarIcon(id, translate) { const starIcon = document.createElementNS("http://www.w3.org/2000/svg", "g"); starIcon.setAttribute("transform", `translate(${translate}, 0)`); const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); path.setAttribute("d", "M11.2691 4.41115C11.5006 3.89177 11.6164 3.63208 11.7776 3.55211C11.9176 3.48263 12.082 3.48263 12.222 3.55211C12.3832 3.63208 12.499 3.89177 12.7305 4.41115L14.5745 8.54808C14.643 8.70162 14.6772 8.77839 14.7302 8.83718C14.777 8.8892 14.8343 8.93081 14.8982 8.95929C14.9705 8.99149 15.0541 9.00031 15.2213 9.01795L19.7256 9.49336C20.2911 9.55304 20.5738 9.58288 20.6997 9.71147C20.809 9.82316 20.8598 9.97956 20.837 10.1342C20.8108 10.3122 20.5996 10.5025 20.1772 10.8832L16.8125 13.9154C16.6877 14.0279 16.6252 14.0842 16.5857 14.1527C16.5507 14.2134 16.5288 14.2807 16.5215 14.3503C16.5132 14.429 16.5306 14.5112 16.5655 14.6757L17.5053 19.1064C17.6233 19.6627 17.6823 19.9408 17.5989 20.1002C17.5264 20.2388 17.3934 20.3354 17.2393 20.3615C17.0619 20.3915 16.8156 20.2495 16.323 19.9654L12.3995 17.7024C12.2539 17.6184 12.1811 17.5765 12.1037 17.56C12.0352 17.5455 11.9644 17.5455 11.8959 17.56C11.8185 17.5765 11.7457 17.6184 11.6001 17.7024L7.67662 19.9654C7.18404 20.2495 6.93775 20.3915 6.76034 20.3615C6.60623 20.3354 6.47319 20.2388 6.40075 20.1002C6.31736 19.9408 6.37635 19.6627 6.49434 19.1064L7.4341 14.6757C7.46898 14.5112 7.48642 14.429 7.47814 14.3503C7.47081 14.2807 7.44894 14.2134 7.41394 14.1527C7.37439 14.0842 7.31195 14.0279 7.18708 13.9154L3.82246 10.8832C3.40005 10.5025 3.18884 10.3122 3.16258 10.1342C3.13978 9.97956 3.19059 9.82316 3.29993 9.71147C3.42581 9.58288 3.70856 9.55304 4.27406 9.49336L8.77835 9.01795C8.94553 9.00031 9.02911 8.99149 9.10139 8.95929C9.16534 8.93081 9.2226 8.8892 9.26946 8.83718C9.32241 8.77839 9.35663 8.70162 9.42508 8.54808L11.2691 4.41115Z"); path.setAttribute("fill", "none"); path.setAttribute("stroke", this.#starBorderColor); path.setAttribute("stroke-width", "2"); path.setAttribute("stroke-linecap", "round"); path.setAttribute("stroke-linejoin", "round"); path.setAttribute("id", id); starIcon.appendChild(path); return starIcon; } #getStarRatingGroup() { const starRatingGroup = document.createElementNS("http://www.w3.org/2000/svg", "g"); const starCount = 5; const starWidth = 24; const starSpacing = 10; for (let i = 0; i < starCount; i += 1) { starRatingGroup.appendChild(this.#getStarIcon( (i + 1).toString(), i * (starWidth + starSpacing) )); } return starRatingGroup; } #getSvgPoint(x, y) { const point = this.#svg.createSVGPoint(); point.x = x; point.y = y; return point; } #isFaceInsideOval(topX, topY, bottomX, bottomY, percentCompleted) { const topLeft = this.#getSvgPoint(topX, topY); const topRight = this.#getSvgPoint(bottomX, topY); const bottomLeft = this.#getSvgPoint(topX, bottomY); const bottomRight = this.#getSvgPoint(bottomX, bottomY); const isInside = this.#ovalBorder.isPointInFill(topLeft) && this.#ovalBorder.isPointInFill(topRight) && this.#ovalBorder.isPointInFill(bottomLeft) && this.#ovalBorder.isPointInFill(bottomRight); this.#progressPath.setAttribute("stroke", isInside ? this.#borderColor : this.#faceNotCenteredColor); Array.from(this.#borderTicks.children).forEach((tick) => { tick.setAttribute("stroke", isInside ? this.#borderColor : this.#faceNotCenteredColor); }); if (isInside) { this.#faceBorder.setAttribute("visibility", "hidden"); } else { if (percentCompleted === 0) { this.#faceBorder.setAttribute("visibility", "visible"); this.#drawFaceBorder(topX, topY, bottomX, bottomY); } } } #getInterPupillaryDistance(annotations, face) { let interPupillaryDistance = 0; if (annotations.silhouette && annotations.silhouette.length && face.posePoints && face.posePoints["3.11"] && face.posePoints["3.8"]) { interPupillaryDistance = Math.floor( face.posePoints["3.11"].point[0] - face.posePoints["3.8"].point[0] ); const minValue = 15; const maxValue = 25; if (interPupillaryDistance < minValue) interPupillaryDistance = minValue; if (interPupillaryDistance > maxValue) interPupillaryDistance = maxValue; interPupillaryDistance -= minValue; } return interPupillaryDistance; } #setSize(width, height) { this.#svg.setAttribute("viewbox", `0 0 ${Math.trunc(width)} ${Math.trunc(height)}`); this.#svg.setAttribute("width", `${Math.trunc(width)}px`); this.#svg.setAttribute("height", `${Math.trunc(height)}px`); } getSvg() { return this.#svg; } #setFrameInfo(frameInfo, videoElementSize) { this.#frameInfo = frameInfo; const { faceTrackerHeight, faceTrackerWidth, mediaStreamHeight, mediaStreamWidth } = frameInfo; const { width, height, offsetX, offsetY } = videoElementSize; this.#offsetX = offsetX; this.#offsetY = offsetY; if (this.objectFit === objectFit.NONE) { this.#scaleX = mediaStreamWidth / faceTrackerWidth; this.#scaleY = mediaStreamHeight / faceTrackerHeight; } else { this.#scaleX = width / faceTrackerWidth; this.#scaleY = height / faceTrackerHeight; } } resize(resizeInfo, settings) { if (settings) this.#updateSettings(settings); const { mediaElementSize, videoElementSize, frameInfo } = resizeInfo; this.#setSize(mediaElementSize.width, mediaElementSize.height); this.#setFrameInfo(frameInfo, videoElementSize); const { width, height } = mediaElementSize; const shapeBottomMargin = height * this.#bottomMargin; const shapeTopMargin = height * this.#topMargin; let shapeWidth = width * this.#diameter; const infoHeight = 35 + 25 + 35; let shapeHeight = height - shapeBottomMargin - shapeTopMargin - shapeWidth - infoHeight; if (shapeHeight < 0) { shapeWidth = shapeWidth + shapeHeight; shapeHeight = 0; } const startX = width / 2 - shapeWidth / 2; const centerX = startX + shapeWidth / 2; const centerY = shapeTopMargin + (shapeWidth + shapeHeight) / 2; const radius = shapeWidth / 2; const left = startX; const right = startX + shapeWidth; const topArcY = shapeTopMargin + radius; const bottomArcY = shapeTopMargin + shapeHeight + radius; const topCenterX = startX + radius; const intermediateResultsX = centerX; const intermediateResultsY = bottomArcY + radius + 35; const starsY = intermediateResultsY + 30; const starCount = 5; const starSpacing = 10; const starWidth = 24; const totalStarsWidth = starCount * starWidth + (starCount - 1) * starSpacing; const starsStartX = intermediateResultsX - starWidth / 2 - totalStarsWidth / 2 + starWidth / 2; this.#starRatingGroup.setAttribute("transform", `translate(${starsStartX}, ${starsY})`); this.#intermediateResults.setAttribute("transform", `translate(${intermediateResultsX}, ${intermediateResultsY})`); const pathD = ` M ${topCenterX},${shapeTopMargin} A ${radius},${radius} 0 0 1 ${right},${topArcY} L ${right},${bottomArcY} A ${radius},${radius} 0 0 1 ${left},${bottomArcY} L ${left},${topArcY} A ${radius},${radius} 0 0 1 ${topCenterX},${shapeTopMargin} `; this.#ovalBorder.setAttribute("d", pathD.trim()); this.#defs.children[0].setAttribute("d", pathD.trim()); this.#defs.children[1].children[1].setAttribute("d", pathD.trim()); const pathLength = this.#ovalBorder.getTotalLength(); this.#progressPath.setAttribute("stroke-dasharray", pathLength.toFixed(0)); this.#progressPath.setAttribute("stroke-dashoffset", pathLength.toFixed(0)); while (this.#borderTicks.firstChild) { this.#borderTicks.removeChild(this.#borderTicks.firstChild); } const tickCount = Math.floor(pathLength / 5); const tickLength = 2; for (let i = 0; i < tickCount; i++) { const position = i / tickCount * pathLength; const point = this.#ovalBorder.getPointAtLength(position); const dx = point.x - centerX; const dy = point.y - centerY; const length = Math.sqrt(dx * dx + dy * dy); const ux = dx / length; const uy = dy / length; const tickStartX = point.x - ux * tickLength; const tickStartY = point.y - uy * tickLength; const tickEndX = point.x + ux * tickLength; const tickEndY = point.y + uy * tickLength; const tick = this.#getTick(tickStartX, tickEndX, tickStartY, tickEndY, position); this.#borderTicks.appendChild(tick); } } draw({ face, annotations, starRating, percentCompleted }) { const { faceRect } = face; const { x, y, width: w, height: h } = faceRect; const topX = this.#offsetX + this.#scaleX * x; const topY = this.#offsetY + this.#scaleY * y; const bottomX = this.#offsetX + (w + x) * this.#scaleX; const bottomY = this.#offsetY + (y + h) * this.#scaleY; this.#isFaceInsideOval(topX, topY, bottomX, bottomY, percentCompleted); const interPupillaryDistance = this.#getInterPupillaryDistance(annotations, face); const distanceColor = `hsl(${interPupillaryDistance / 10 * 120},100%,50%)`; this.#borderColor = distanceColor; this.#countDownCircle.setAttribute("fill", distanceColor); if (percentCompleted > 0) { this.#countDownCircle.setAttribute("visibility", "visible"); this.#countDownLabel.setAttribute("visibility", "visible"); const pathLength = this.#ovalBorder.getTotalLength(); const distance = percentCompleted / 100 * pathLength; const point = this.#ovalBorder.getPointAtLength(distance); Array.from(this.#borderTicks.children).forEach((tick) => { const position = parseFloat(tick.getAttribute("data-position")); tick.style.display = position < distance ? "none" : ""; }); this.#progressPath.setAttribute("d", this.#ovalBorder.getAttribute("d")); this.#progressPath.setAttribute("stroke-dashoffset", (pathLength - distance).toString()); this.#countDown.setAttribute("transform", `translate(${point.x},${point.y})`); this.#countDownLabel.textContent = Math.trunc(this.#duration - percentCompleted * this.#duration / 100).toString(); const bbox = this.#countDownLabel.getBBox(); const offsetX = -bbox.width / 2 - bbox.x; const offsetY = -bbox.height / 2 - bbox.y; this.#countDownLabel.setAttribute("transform", `translate(${offsetX},${offsetY})`); } for (let i = 0; i < 5; i += 1) { this.#starRatingGroup.children[i].children[0].setAttribute("fill", starRating >= i ? this.#starFillColor : "none"); this.#starRatingGroup.children[i].children[0].setAttribute("stroke", starRating >= i ? this.#starFillColor : this.#starBorderColor); } } setMaskVisibility(isVisible) { this.#svg.style.display = isVisible ? "block" : "none"; } setIntermediateResults(points) { let pulseRate = ""; const pointList = Object.keys(points); if (pointList.includes("HR_BPM")) pulseRate = parseInt(points.HR_BPM.value, 10).toString(); this.#intermediateResults.setAttribute("visibility", pulseRate ? "visible" : "hidden"); this.#pulseRateText.textContent = pulseRate; } } export { AnuraMask, objectFit };