UNPKG

@syncfusion/ej2-progressbar

Version:
402 lines (393 loc) 23.1 kB
import { ProgressBar } from '../../progressbar'; import { ProgressAnimation } from '../utils/progress-animation'; import { PathOption, getElement, Size, measureText } from '@syncfusion/ej2-svg-base'; import { ITextRenderEventArgs } from '../model/progress-interface'; import { stringToNumber, getPathArc, degreeToLocation, ProgressLocation } from '../utils/helper'; import { Segment } from './segment-progress'; import { TextOption } from '../utils/helper'; import { ModeType } from '../utils'; import { animationMode } from '@syncfusion/ej2-base'; /** * Progressbar of type circular */ export class Circular { private progress: ProgressBar; public delay: number; private segment: Segment = new Segment(); private animation: ProgressAnimation = new ProgressAnimation(); private isRange: boolean; private centerX: number; private centerY: number; private maxThickness: number; private availableSize: number; private trackEndAngle: number; // Defines end position of circular progress. public endPosition: ProgressLocation; // Defines buffer end position of circular progress. public bufferEndPosition: ProgressLocation; constructor(progress: ProgressBar) { this.progress = progress; } /** * To render the circular track. * * @returns {void} */ public renderCircularTrack(): void { const progress: ProgressBar = this.progress; const circularTrackGroup: Element = progress.renderer.createGroup({ 'id': progress.element.id + '_CircularTrackGroup' }); let radius: number; let endAngle: number; const startAngle: number = progress.startAngle; progress.totalAngle = (progress.endAngle - progress.startAngle) % 360; progress.totalAngle = (progress.totalAngle <= 0 ? (360 + progress.totalAngle) : progress.totalAngle); progress.totalAngle -= (progress.totalAngle === 360) ? 0.01 : 0; this.trackEndAngle = endAngle = (progress.startAngle + ( (progress.enableRtl) ? -progress.totalAngle : +progress.totalAngle)) % 360; this.centerX = progress.progressRect.x + (progress.progressRect.width / 2); this.centerY = progress.progressRect.y + (progress.progressRect.height / 2); this.maxThickness = Math.max(progress.trackThickness, progress.progressThickness) || Math.max(progress.themeStyle.circularProgressThickness, progress.themeStyle.circularTrackThickness); this.availableSize = (Math.min(progress.progressRect.height, progress.progressRect.width) / 2) - this.maxThickness / 2; radius = stringToNumber(progress.radius, this.availableSize); radius = (radius === null) ? 0 : radius; const stroke: string = (progress.argsData.trackColor || progress.themeStyle.circularTrackColor); const fill: string = (progress.enablePieProgress) ? (progress.argsData.trackColor || progress.themeStyle.circularTrackColor) : 'none'; const strokeWidth: number = (progress.enablePieProgress) ? 0 : (progress.trackThickness || progress.themeStyle.circularTrackThickness); const circularPath: string = getPathArc( this.centerX, this.centerY, radius, startAngle, endAngle, progress.enableRtl, progress.enablePieProgress); this.isRange = (this.progress.rangeColors[0].color !== '' || this.progress.rangeColors[0].start !== null || this.progress.rangeColors[0].end !== null); const option: PathOption = new PathOption( progress.element.id + '_Circulartrack', fill, strokeWidth, stroke, progress.themeStyle.trackOpacity, '0', circularPath ); const circularTrack: Element = progress.renderer.drawPath(option); progress.trackWidth = (<SVGPathElement>circularTrack).getTotalLength(); if (progress.segmentCount > 1 && !progress.enableProgressSegments && !progress.enablePieProgress && !this.isRange) { progress.segmentSize = progress.calculateSegmentSize(progress.trackWidth, strokeWidth); circularTrack.setAttribute('stroke-dasharray', progress.segmentSize); } if (progress.cornerRadius === 'Round' && !progress.enablePieProgress && !this.isRange) { circularTrack.setAttribute('stroke-linecap', 'round'); } circularTrackGroup.appendChild(circularTrack); progress.svgObject.appendChild(circularTrackGroup); } /** * Renders circular progress to update previous progress. * * @param {number} previousEnd - The previous end value of the progress. * @param {number} previousTotalEnd - The previous total end value of the progress. * @param {boolean} refresh - Indicates whether to refresh the progress. * @returns {void} */ public renderCircularProgress(previousEnd?: number, previousTotalEnd?: number, refresh?: boolean): void { const progress: ProgressBar = this.progress; const startAngle: number = progress.startAngle; let endAngle: number; let totalAngle: number; let radius: number; let previousPath: string; let progressTotalAngle: number; let progressEnd: number; let circularProgress: Element; let linearClipPath: Element; let circularProgressGroup: Element; let segmentWidth: number; if (!refresh) { circularProgressGroup = progress.renderer.createGroup({ 'id': progress.element.id + '_CircularProgressGroup' }); } else { circularProgressGroup = getElement(progress.element.id + '_CircularProgressGroup'); } radius = stringToNumber(progress.innerRadius, this.availableSize); radius = (radius === null) ? 0 : radius; progress.previousTotalEnd = progressEnd = progress.calculateProgressRange(progress.argsData.value > progress.maximum ? progress.maximum : progress.argsData.value); const progressEndAngle: number = (progress.startAngle + ((progress.enableRtl) ? -progressEnd : progressEnd)) % 360; progress.previousEndAngle = endAngle = ((progress.isIndeterminate && !progress.enableProgressSegments) ? (progress.startAngle + ( (progress.enableRtl) ? -progress.totalAngle : progress.totalAngle)) % 360 : progressEndAngle ); progressTotalAngle = (progressEnd - progress.startAngle) % 360; progressTotalAngle = (progressTotalAngle <= 0 ? (360 + progressTotalAngle) : progressTotalAngle); progressTotalAngle -= (progressTotalAngle === 360) ? 0.01 : 0; const circularPath: string = getPathArc( this.centerX, this.centerY, radius, startAngle, endAngle, progress.enableRtl, progress.enablePieProgress); const stroke: string = this.checkingCircularProgressColor(); const fill: string = (progress.enablePieProgress) ? stroke : 'none'; const thickness: number = (progress.progressThickness || progress.themeStyle.circularProgressThickness); const strokeWidth: number = (progress.enablePieProgress) ? 0 : thickness; const option: PathOption = new PathOption( progress.element.id + '_Circularprogress', fill, strokeWidth, stroke, progress.themeStyle.progressOpacity, '0', circularPath ); progress.progressWidth = (<SVGPathElement>progress.renderer.drawPath(option)).getTotalLength(); progress.segmentSize = this.validateSegmentSize(progress, thickness); this.endPosition = degreeToLocation(this.centerX, this.centerY, radius, endAngle); if (progress.secondaryProgress !== null && !progress.isIndeterminate) { this.renderCircularBuffer(progress, radius, progressTotalAngle); } if (progress.argsData.value !== null) { if (progress.segmentColor.length !== 0 && !progress.isIndeterminate && !progress.enablePieProgress) { totalAngle = (!progress.enableProgressSegments) ? progress.totalAngle : progressTotalAngle; segmentWidth = (!progress.enableProgressSegments) ? progress.trackWidth : progress.progressWidth; circularProgress = this.segment.createCircularSegment( progress, '_CircularProgressSegment', this.centerX, this.centerY, radius, progress.argsData.value, progress.themeStyle.progressOpacity, thickness, totalAngle, segmentWidth ); } else if (this.isRange && !progress.isIndeterminate) { circularProgress = this.segment.createCircularRange(this.centerX, this.centerY, radius, progress); } else { if (!refresh) { circularProgress = progress.renderer.drawPath(option); } else { circularProgress = getElement(progress.element.id + '_Circularprogress'); previousPath = circularProgress.getAttribute('d'); circularProgress.setAttribute('stroke', stroke); circularProgress.setAttribute('d', circularPath); } if (progress.segmentCount > 1 && !progress.enablePieProgress) { circularProgress.setAttribute('stroke-dasharray', progress.segmentSize); } if (progress.cornerRadius === 'Round' && startAngle !== endAngle) { circularProgress.setAttribute('stroke-linecap', 'round'); } } circularProgressGroup.appendChild(circularProgress); if (progress.isActive && !progress.isIndeterminate && !progress.enablePieProgress) { this.renderActiveState( circularProgressGroup, radius, strokeWidth, circularPath, progressEndAngle, progressEnd, refresh ); } if (((progress.animation.enable && animationMode !== 'Disable') || animationMode === 'Enable') || progress.isIndeterminate) { this.delay = (progress.secondaryProgress !== null) ? 300 : progress.animation.delay; linearClipPath = progress.createClipPath(progress.clipPath, null, refresh ? previousPath : '', refresh); circularProgressGroup.appendChild(progress.clipPath); if (((progress.animation.enable && animationMode !== 'Disable') || animationMode === 'Enable') && !progress.isIndeterminate && !progress.isActive) { (<SVGPathElement>circularProgress).style.clipPath = 'url(#' + progress.element.id + '_clippath)'; this.animation.doCircularAnimation( this.centerX, this.centerY, radius, progressEndAngle, progressEnd, linearClipPath, progress, thickness, this.delay, refresh ? previousEnd : null, refresh ? previousTotalEnd : null ); } if (progress.isIndeterminate) { if (progress.enableProgressSegments) { linearClipPath.setAttribute('d', getPathArc( this.centerX, this.centerY, radius + (thickness / 2), progress.startAngle, this.trackEndAngle, progress.enableRtl, true) ); } circularProgress.setAttribute('style', 'clip-path:url(#' + progress.element.id + '_clippath)'); this.animation.doCircularIndeterminate( (!progress.enableProgressSegments) ? linearClipPath : circularProgress, progress, startAngle, progressEndAngle, this.centerX, this.centerY, radius, thickness, linearClipPath ); } } progress.svgObject.appendChild(circularProgressGroup); } } /** * Renders circular buffer for the progress bar. * * @param {ProgressBar} progress - The progress bar control. * @param {number} radius - The radius of the circular buffer. * @param {number} progressTotalAngle - The total angle covered by the progress. * @returns {void} * @private */ private renderCircularBuffer(progress: ProgressBar, radius: number, progressTotalAngle: number): void { let bufferClipPath: Element; let circularBuffer: Element; let segmentWidth: number; let totalAngle: number; const circularBufferGroup: Element = progress.renderer.createGroup({ 'id': progress.element.id + '_ CircularBufferGroup' }); const bufferEnd: number = progress.calculateProgressRange(progress.secondaryProgress > progress.maximum ? progress.maximum : progress.secondaryProgress); const endAngle: number = (progress.startAngle + ((progress.enableRtl) ? -bufferEnd : bufferEnd)) % 360; const circularPath: string = getPathArc( this.centerX, this.centerY, radius, progress.startAngle, endAngle, progress.enableRtl, progress.enablePieProgress ); this.bufferEndPosition = degreeToLocation(this.centerX, this.centerY, radius, endAngle); const stroke: string = progress.secondaryProgressColor ? progress.secondaryProgressColor : progress.themeStyle.bufferColor || this.checkingCircularProgressColor(); const fill: string = (progress.enablePieProgress) ? stroke : 'none'; const strokeWidth: number = (progress.enablePieProgress) ? 0 : (progress.secondaryProgressThickness ? progress.secondaryProgressThickness : (progress.progressThickness || progress.themeStyle.circularProgressThickness)); const option: PathOption = new PathOption( progress.element.id + '_Circularbuffer', fill, strokeWidth, stroke, progress.themeStyle.bufferOpacity, '0', circularPath ); if (progress.segmentColor.length !== 0 && !progress.isIndeterminate && !progress.enablePieProgress && !this.isRange) { totalAngle = (!progress.enableProgressSegments) ? progress.totalAngle : progressTotalAngle; segmentWidth = (!progress.enableProgressSegments) ? progress.trackWidth : progress.progressWidth; circularBuffer = this.segment.createCircularSegment( progress, '_CircularBufferSegment', this.centerX, this.centerY, radius, progress.secondaryProgress > progress.maximum ? progress.maximum : progress.secondaryProgress, progress.themeStyle.bufferOpacity, strokeWidth, totalAngle, segmentWidth ); } else { circularBuffer = progress.renderer.drawPath(option); if (progress.segmentCount > 1 && !progress.enablePieProgress && !this.isRange) { circularBuffer.setAttribute('stroke-dasharray', progress.segmentSize); } if (progress.cornerRadius === 'Round' && !this.isRange) { circularBuffer.setAttribute('stroke-linecap', 'round'); } } circularBufferGroup.appendChild(circularBuffer); if (((progress.animation.enable && animationMode !== 'Disable') || animationMode === 'Enable') && !progress.isActive) { bufferClipPath = progress.createClipPath(progress.bufferClipPath, null, '', false); circularBufferGroup.appendChild(progress.bufferClipPath); circularBuffer.setAttribute('style', 'clip-path:url(#' + progress.element.id + '_clippathBuffer)'); this.animation.doCircularAnimation( this.centerX, this.centerY, radius, endAngle, bufferEnd, bufferClipPath, progress, (progress.progressThickness || progress.themeStyle.circularProgressThickness), progress.animation.delay ); } progress.svgObject.appendChild(circularBufferGroup); } /** * To render the circular Label. * * @param {boolean} isProgressRefresh - Indicates whether progress should be refreshed. Defaults to false. * @returns {void} */ public renderCircularLabel(isProgressRefresh: boolean = false): void { let end: number; let circularLabel: Element; let centerY: number; let textSize: Size; let option: TextOption; const percentage: number = 100; const progress: ProgressBar = this.progress; const labelText: string = progress.labelStyle.text; const circularLabelGroup: Element = progress.renderer.createGroup({ 'id': progress.element.id + '_CircularLabelGroup' }); if (document.getElementById(circularLabelGroup.id)) { document.getElementById(circularLabelGroup.id).remove(); } const labelValue: number = ((progress.value - progress.minimum) / (progress.maximum - progress.minimum)) * percentage; const circularValue: number = (progress.value < progress.minimum) ? 0 : +labelValue.toFixed(2); const argsData: ITextRenderEventArgs = { cancel: false, text: labelText ? labelText : String(circularValue) + '%', color: progress.labelStyle.color || progress.themeStyle.circularLabelFont.color }; progress.trigger('textRender', argsData); if (!argsData.cancel) { textSize = measureText(argsData.text, progress.labelStyle, progress.themeStyle.circularLabelFont); centerY = this.centerY + (textSize.height / 2); option = new TextOption( progress.element.id + '_circularLabel', progress.labelStyle.size || progress.themeStyle.circularLabelFont.size, progress.labelStyle.fontStyle || progress.themeStyle.circularLabelFont.fontStyle, progress.labelStyle.fontFamily || progress.themeStyle.circularLabelFont.fontFamily, progress.labelStyle.fontWeight || progress.themeStyle.circularLabelFont.fontWeight, 'middle', argsData.color, this.centerX, centerY, progress.progressRect.width, progress.progressRect.height ); circularLabel = progress.renderer.createText(option, argsData.text); circularLabelGroup.appendChild(circularLabel); if (((progress.animation.enable && animationMode !== 'Disable') || animationMode === 'Enable') && !progress.isIndeterminate) { end = ((progress.value - progress.minimum) / (progress.maximum - progress.minimum)) * progress.totalAngle; end = (progress.value < progress.minimum) ? 0 : end; this.animation.doLabelAnimation(circularLabel, isProgressRefresh ? progress.previousWidth : progress.startAngle, end, progress, this.delay); } progress.svgObject.appendChild(circularLabelGroup); progress.previousWidth = end; } } /** * Renders the active state of the circular progress. * * @param {Element} progressGroup - The group element containing the progress. * @param {number} radius - The radius of the circular progress. * @param {number} strokeWidth - The width of the progress stroke. * @param {string} circularPath - The path representing the circular progress. * @param {number} endAngle - The angle at which the progress ends. * @param {number} totalEnd - The total end value of the progress. * @param {boolean} refresh - Indicates whether the progress should be refreshed. * @returns {void} * @private */ private renderActiveState( progressGroup: Element, radius: number, strokeWidth: number, circularPath: string, endAngle: number, totalEnd: number, refresh: boolean ): void { let circularActive: Element; let option: PathOption; const progress: ProgressBar = this.progress; const thickness: number = strokeWidth + 1; if (!refresh) { option = new PathOption( progress.element.id + '_CircularActiveProgress', 'none', thickness, '#ffffff', 0.5, '0', circularPath ); circularActive = progress.renderer.drawPath(option); } else { circularActive = getElement(progress.element.id + '_CircularActiveProgress'); circularActive.setAttribute('d', circularPath); } if (progress.segmentCount > 1) { circularActive.setAttribute('stroke-dasharray', progress.segmentSize); } if (progress.cornerRadius === 'Round') { circularActive.setAttribute('stroke-linecap', 'round'); } const activeClip: Element = progress.createClipPath(progress.clipPath, null, '', refresh); circularActive.setAttribute('style', 'clip-path:url(#' + progress.element.id + '_clippath)'); progressGroup.appendChild(circularActive); progressGroup.appendChild(progress.clipPath); this.animation.doCircularAnimation( this.centerX, this.centerY, radius, endAngle, totalEnd, activeClip, progress, thickness, 0, null, null, circularActive ); } /** * Validates the segment size for the progress bar. * * @param {ProgressBar} progress - The progress bar control. * @param {number} thickness - The thickness of the progress segments. * @returns {string} - The validated segment size. * @private */ private validateSegmentSize(progress: ProgressBar, thickness: number): string { let validSegment: string; let progressSegment: number; const rDiff: number = parseInt(progress.radius, 10) - parseInt(progress.innerRadius, 10); if (rDiff !== 0 && !progress.enableProgressSegments) { progressSegment = progress.trackWidth + ( (rDiff < 0) ? (progress.trackWidth * Math.abs(rDiff)) / parseInt(progress.radius, 10) : -(progress.trackWidth * Math.abs(rDiff)) / parseInt(progress.radius, 10) ); validSegment = progress.calculateSegmentSize(progressSegment, thickness); } else if (progress.enableProgressSegments) { validSegment = progress.calculateSegmentSize(progress.progressWidth, thickness); } else { validSegment = progress.segmentSize; } return validSegment; } /** * Checks and retrieves the color for the circular progress. * * @returns {string} - The color for the circular progress. * @private */ private checkingCircularProgressColor(): string { let circularColor: string; const progress: ProgressBar = this.progress; const role: ModeType = progress.role; switch (role) { case 'Success': circularColor = progress.themeStyle.success; break; case 'Info': circularColor = progress.themeStyle.info; break; case 'Warning': circularColor = progress.themeStyle.warning; break; case 'Danger': circularColor = progress.themeStyle.danger; break; default: circularColor = (progress.argsData.progressColor || progress.themeStyle.circularProgressColor); } return circularColor; } }