@syncfusion/ej2-circulargauge
Version:
Essential JS 2 CircularGauge Components
802 lines (776 loc) • 46.6 kB
text/typescript
/* eslint-disable max-len */
import { CircularGauge } from '../circular-gauge';
import { Axis, Label, Range, Tick } from './axis';
import { stringToNumber, toPixel, textElement, appendPath, getAngleFromValue, getLocationFromAngle, getPathArc, getRoundedPathArc, VisibleLabels, getDegree, isCompleteAngle, PathOption, GaugeLocation, TextOption } from '../utils/helper-common';
import { getRangeColor } from '../utils/helper-axis-renderer';
import { TickModel } from './axis-model';
import { getRangePalette } from '../model/theme';
import { isNullOrUndefined} from '@syncfusion/ej2-base';
import { FontModel } from '../model/base-model';
/**
* Specifies the Axis rendering for circular gauge
*/
export class AxisRenderer {
private majorValues: number[];
private gauge: CircularGauge;
/**
* Constructor for axis renderer.
*
* @param {CircularGauge} gauge - Specifies the instance of the gauge
* @private.
*/
constructor(gauge: CircularGauge) {
this.gauge = gauge;
}
/**
* Method to render the axis element of the circular gauge.
*
* @param {Axis} axis - Specifies the axis.
* @param {number} index - Specifies the index.
* @param {Element} element - Specifies the element.
* @param {CircularGauge} gauge - Specifies the gauge.
* @returns {void}
* @private
*/
public drawAxisOuterLine(axis: Axis, index: number, element: Element, gauge: CircularGauge): void {
const background: string = axis.background;
this.setRangeColor(axis);
if (background !== null) {
appendPath(
new PathOption(
gauge.element.id + '_AxisOuterLine_' + index,
background, 0, 'transparent', null, '0',
getPathArc(gauge.midPoint, 0, 360, (Math.min(axis.rect.width, axis.rect.height) / 2)), '',
'pointer-events:none;'),
element, gauge
);
}
}
/**
* Method to check the angles.
*
* @param {Axis} axis - Specifies the axis.
* @returns {void}
* @private
*/
public checkAngles(axis: Axis): void {
axis.startAngle = axis.startAngle >= 360 ? 360 : axis.startAngle <= -360 ? -360 : axis.startAngle;
axis.endAngle = axis.endAngle >= 360 ? 360 : axis.endAngle <= -360 ? -360 : axis.endAngle;
}
/**
* Method to render the axis line of the circular gauge.
*
* @param {Axis} axis - Specifies the axis.
* @param {number} index - Specifies the index.
* @param {Element} element - Specifies the element.
* @param {CircularGauge} gauge - Specifies the gauge.
* @returns {void}
* @private
*/
public drawAxisLine(axis: Axis, index: number, element: Element, gauge: CircularGauge): void {
let startAngle: number = axis.startAngle;
let endAngle: number = axis.endAngle;
const color: string = axis.lineStyle.color || this.gauge.themeStyle.lineColor;
if (axis.lineStyle.width > 0 && this.gauge.allowComponentRender) {
startAngle = !isCompleteAngle(startAngle, endAngle) ? startAngle : [0, endAngle = 360][0];
appendPath(
new PathOption(
gauge.element.id + '_AxisLine_' + index, 'transparent', axis.lineStyle.width, color,
null, axis.lineStyle.dashArray,
getPathArc(gauge.midPoint, startAngle - 90, endAngle - 90, axis.currentRadius),
'', gauge.allowLoadingAnimation ? 'visibility: hidden; pointer-events:none;' : 'pointer-events:none;'),
element, gauge
);
}
}
/**
* Method to render the axis labels of the circular gauge.
*
* @param {Axis} axis - Specifies the axis.
* @param {number} index - Specifies the index.
* @param {Element} element - Specifies the element.
* @param {CircularGauge} gauge - Specifies the gauge.
* @returns {void}
* @private
*/
public drawAxisLabels(axis: Axis, index: number, element: Element, gauge: CircularGauge): void {
const labelElement: Element = gauge.renderer.createGroup({
id: gauge.element.id + '_Axis_Labels_' + index, style: gauge.allowLoadingAnimation ? 'visibility: hidden;' : 'pointer-events:auto;'
});
const min: number = axis.visibleRange.min;
const max: number = axis.visibleRange.max;
const labelCollection: VisibleLabels[] = axis.visibleLabels;
let location: GaugeLocation;
let textWidth: number; let textHeight: number; let labelsVisible: boolean = true;
let currentTextWidth: number; let currentTextHeight: number;
let previousLocation: GaugeLocation; let currentLocation: GaugeLocation;
let lastLabelLocation: GaugeLocation;
let lastLabelAngle: number; let lastLabelAnchor: string;
let lastTextWidth: number; let lastTextHeight: number;
const style: Label = <Label>axis.labelStyle; let anchor: string; let angle: number;
let label: VisibleLabels; let radius: number = axis.currentRadius;
let checkLabelOpposed: number = 0;
checkLabelOpposed = (style.position === 'Inside' && axis.majorTicks.position === 'Outside' &&
axis.minorTicks.position === 'Outside') || (style.position === 'Outside' &&
axis.minorTicks.position === 'Inside' && axis.majorTicks.position === 'Inside') ?
axis.lineStyle.width + axis.currentRadius / 20 :
(style.position === axis.majorTicks.position ? axis.currentRadius / 20 : axis.currentRadius / 40);
const labelPadding: number = axis.labelStyle.shouldMaintainPadding ? 10 : checkLabelOpposed;
const color: string = style.font.color || this.gauge.themeStyle.labelColor;
if (style.position === 'Outside') {
radius += (axis.nearSize - (axis.maxLabelSize.height + axis.lineStyle.width / 2)) + (labelPadding / 2);
} else if (style.position === 'Cross') {
radius = radius - (axis.maxLabelSize.height / 4) - axis.labelStyle.offset;
} else {
radius -= (axis.farSize - (axis.maxLabelSize.height + axis.lineStyle.width / 2) + (style.autoAngle ? labelPadding : 0));
}
//To get and store lastlabelposition
if (axis.hideIntersectingLabel) {
lastLabelAngle = Math.round(getAngleFromValue(labelCollection[labelCollection.length - 1].value, max,
min, axis.startAngle, axis.endAngle, axis.direction === 'ClockWise'));
lastLabelLocation = getLocationFromAngle(lastLabelAngle, radius, gauge.midPoint);
lastLabelAnchor = this.findAnchor(lastLabelLocation, style, lastLabelAngle, labelCollection[labelCollection.length - 1]);
lastTextWidth = (!axis.showLastLabel && (isCompleteAngle(axis.startAngle, axis.endAngle)) && (style.hiddenLabel !== 'First')) ?
labelCollection[0].size.width : labelCollection[labelCollection.length - 1].size.width;
lastTextHeight = (!axis.showLastLabel && (isCompleteAngle(axis.startAngle, axis.endAngle)) && (style.hiddenLabel !== 'First')) ?
(!style.autoAngle ? labelCollection[0].size.height : labelCollection[0].size.width) :
(!style.autoAngle ? labelCollection[labelCollection.length - 1].size.height :
labelCollection[labelCollection.length - 1].size.width);
lastTextHeight = lastTextHeight - this.offsetAxisLabelsize(lastLabelAngle, lastTextHeight);
lastLabelLocation = this.getAxisLabelStartPosition(lastLabelLocation, lastTextWidth, lastLabelAnchor);
}
for (let i: number = 0, length: number = labelCollection.length; i < length; i++) {
label = labelCollection[i as number];
angle = Math.round(getAngleFromValue(label.value, max, min, axis.startAngle, axis.endAngle, axis.direction === 'ClockWise'));
location = getLocationFromAngle(angle, radius, gauge.midPoint);
anchor = this.findAnchor(location, style, angle, label);
//To get the current label and previous label position for initial stage
if (axis.hideIntersectingLabel) {
currentLocation = getLocationFromAngle(angle, radius, gauge.midPoint);
currentTextWidth = label.size.width;
currentTextHeight = !style.autoAngle ? label.size.height : currentTextWidth;
currentTextHeight = currentTextHeight - this.offsetAxisLabelsize(angle, currentTextHeight);
currentLocation = this.getAxisLabelStartPosition(currentLocation, currentTextWidth, anchor);
if (i === 0) {
previousLocation = getLocationFromAngle(angle, radius, gauge.midPoint);
textWidth = label.size.width;
textHeight = !style.autoAngle ? label.size.height : textWidth;
textHeight = textHeight - this.offsetAxisLabelsize(angle, textHeight);
previousLocation = this.getAxisLabelStartPosition(previousLocation, textWidth, anchor);
}
}
if ((i === 0 && style.hiddenLabel === 'First') || (i === (length - 1) && style.hiddenLabel === 'Last')) {
continue;
}
const textFont: FontModel = {
size: style.font.size || this.gauge.themeStyle.fontSize,
color: style.font.color,
fontFamily: style.font.fontFamily || this.gauge.themeStyle.labelFontFamily,
fontWeight: style.font.fontWeight || this.gauge.themeStyle.fontWeight,
fontStyle: style.font.fontStyle,
opacity: style.font.opacity
};
if (axis.hideIntersectingLabel && (i !== 0)) {
//To remove the labels which is intersecting with last label.
const lastlabel: boolean = ((i !== (labelCollection.length - 1)) && ((isCompleteAngle(axis.startAngle, axis.endAngle) ||
axis.showLastLabel))) ? this.FindAxisLabelCollision(lastLabelLocation, lastTextWidth, lastTextHeight, currentLocation,
currentTextWidth, currentTextHeight) : true;
//Checking wether the axis label is intersecting with previous label or not.
labelsVisible = (this.FindAxisLabelCollision(previousLocation, textWidth, textHeight, currentLocation, currentTextWidth,
currentTextHeight) && lastlabel);
} else {
labelsVisible = true;
}
if (labelsVisible || (i === labelCollection.length - 1)) {
//To hide first and last label based on requirement
label.text = (!axis.showLastLabel && ((isCompleteAngle(axis.startAngle, axis.endAngle) && style.hiddenLabel !== 'First') ||
!labelsVisible)
&& axis.hideIntersectingLabel && (i === (length - 1))) ? '' : label.text;
label.text = (axis.showLastLabel && axis.hideIntersectingLabel && isCompleteAngle(axis.startAngle, axis.endAngle)
&& (i === 0)) ? '' : label.text;
const labelTextElement: Element = textElement(
new TextOption(
gauge.element.id + '_Axis_' + index + '_Label_' + i,
location.x, location.y, anchor, label.text,
style.autoAngle ? 'rotate(' + (angle + 90) + ',' + (location.x) + ',' + location.y + ')' : '', 'auto'
),
textFont, style.useRangeColor ? getRangeColor(label.value, <Range[]>axis.ranges, color) : color,
labelElement, 'pointer-events:auto;'
);
labelTextElement.setAttribute('aria-label', label.text);
labelTextElement.setAttribute('role', 'region');
if (axis.hideIntersectingLabel) {
textWidth = label.size.width;
textHeight = !style.autoAngle ? label.size.height : textWidth;
textHeight = textHeight - this.offsetAxisLabelsize(angle, textHeight);
previousLocation.x = currentLocation.x;
previousLocation.y = currentLocation.y;
}
}
}
element.appendChild(labelElement);
}
/**
* Method to find the anchor of the axis label.
*
* @param {GaugeLocation} location - Specifies the location.
* @param {Label} style - Specifies the label style.
* @param {number} angle - Specifies the angle.
* @param {VisibleLabels} label - Specifies the labels.
* @returns {string} - Returns the anchor.
* @private
*/
private findAnchor(location: GaugeLocation, style: Label, angle: number, label: VisibleLabels): string {
if (style.autoAngle) {
return 'middle';
}
const anchor: string = style.position === 'Inside' ?
((angle > 120 && angle < 240) ? 'start' : ((300 < angle || angle < 60) ? 'end' : 'middle')) :
((angle > 120 && angle < 240) ? 'end' : ((300 < angle || angle < 60) ? 'start' : 'middle'));
location.y += style.position === 'Inside' ?
((angle >= 240 && angle <= 300) ? (label.size.height / 2) :
(angle >= 60 && angle <= 120) ? 0 : label.size.height / 4) :
((angle >= 240 && angle <= 300) ? 0 :
(angle >= 60 && angle <= 120) ? label.size.height / 2 : label.size.height / 4);
return anchor;
}
/**
* Methode to check whether the labels are intersecting or not.
*
* @param {GaugeLocation} previousLocation - Specifies the previous location.
* @param {number} previousWidth - Specifies the previous width.
* @param {number} previousHeight - Specifies the previous height.
* @param {GaugeLocation} currentLocation - Specifies the current location.
* @param {number} currentWidth - Specifies the current width.
* @param {number} currentHeight - Specifies the current height.
* @returns {boolean} - Returns the boolean value.
* @private
*/
private FindAxisLabelCollision(previousLocation: GaugeLocation, previousWidth: number, previousHeight: number,
currentLocation: GaugeLocation, currentWidth: number, currentHeight: number): boolean {
const labelVisisble: boolean = ((previousLocation.x > (currentLocation.x + (currentWidth))) ||
((previousLocation.x + (previousWidth)) < (currentLocation.x)) ||
((previousLocation.y + (previousHeight)) < (currentLocation.y)) ||
((previousLocation.y) > (currentLocation.y + (currentHeight))));
return labelVisisble;
}
/**
* Methode to get anchor position of label as start.
*
* @param {GaugeLocation} actualLocation - Specifies the actual location.
* @param {number} textWidth - Specifies the text width.
* @param {string} anchorPosition - Specifies the anchor position.
* @returns {GaugeLocation} - Returns the gauge location.
* @private
*/
private getAxisLabelStartPosition(actualLocation: GaugeLocation, textWidth: number, anchorPosition: string): GaugeLocation {
if (anchorPosition === 'end') {
actualLocation.x = actualLocation.x - textWidth;
} else if (anchorPosition === 'middle') {
actualLocation.x = actualLocation.x - (textWidth / 2);
}
return actualLocation;
}
/**
* Methode to offset label height and width based on angle.
*
* @param {number} angle - Specifies the angle.
* @param {number} size - Specifies the size.
* @returns {number} - Returns the fineal size.
* @private
*/
private offsetAxisLabelsize(angle: number, size: number): number {
const finalSize: number = ((angle >= 20 && angle <= 60) || (angle >= 120 && angle <= 160) || (angle >= 200 && angle <= 240) ||
(angle >= 300 && angle <= 340)) ? size / 5 : 0;
return finalSize;
}
/**
* Method to render the axis minor tick lines of the circular gauge.
*
* @param {Axis} axis - Specifies the axis.
* @param {number} index - Specifies the index.
* @param {Element} element - Specifies the element.
* @param {CircularGauge} gauge - Specifies the gauge.
* @returns {void}
* @private
*/
public drawMinorTickLines(axis: Axis, index: number, element: Element, gauge: CircularGauge): void {
const minorTickElements: Element = gauge.renderer.createGroup({
id: gauge.element.id + '_Axis_MinorTickLines_' + index
});
const minorLineStyle: TickModel = axis.minorTicks;
const minorInterval: number = minorLineStyle.interval !== null ?
minorLineStyle.interval : (axis.visibleRange.interval / 2);
const isRangeColor: boolean = minorLineStyle.useRangeColor;
const color: string = minorLineStyle.color || this.gauge.themeStyle.minorTickColor;
if (minorLineStyle.width && minorLineStyle.height && minorInterval) {
let j: number = 0;
for (let i: number = axis.visibleRange.min, max: number = axis.visibleRange.max; i <= max; i += minorInterval) {
if (this.majorValues.indexOf(+i.toFixed(3)) < 0) {
const tickElement: HTMLElement = appendPath(
new PathOption(
gauge.element.id + '_Axis_Minor_TickLine_' + index + '_' + j++, 'transparent', minorLineStyle.width,
isRangeColor ? getRangeColor(i, <Range[]>axis.ranges, color) : color,
null, minorLineStyle.dashArray, this.calculateTicks(i, <Tick>minorLineStyle, axis), '',
gauge.allowLoadingAnimation ? 'visibility: hidden;pointer-events: auto;' : 'pointer-events:auto;'
),
minorTickElements, gauge
) as HTMLElement;
tickElement.setAttribute('data-interval', i.toString());
}
}
element.appendChild(minorTickElements);
}
}
/**
* Method to render the axis major tick lines of the circular gauge.
*
* @param {Axis} axis - Specifies the axis.
* @param {number} index - Specifies the index.
* @param {Element} element - Specifies the element.
* @param {CircularGauge} gauge - Specifies the gauge.
* @returns {void}
* @private
*/
public drawMajorTickLines(axis: Axis, index: number, element: Element, gauge: CircularGauge): void {
const majorTickElements: Element = gauge.renderer.createGroup({
id: gauge.element.id + '_Axis_MajorTickLines_' + index
});
const majorLineStyle: TickModel = axis.majorTicks;
const isRangeColor: boolean = majorLineStyle.useRangeColor;
this.majorValues = [];
const color: string = majorLineStyle.color || this.gauge.themeStyle.majorTickColor;
if (majorLineStyle.width && majorLineStyle.height && axis.visibleRange.interval) {
let j: number = 0;
for (let i: number = axis.visibleRange.min, max: number = axis.visibleRange.max,
interval: number = axis.visibleRange.interval; i <= max; i += interval) {
this.majorValues.push(+i.toFixed(3));
const tickElement: HTMLElement = appendPath(
new PathOption(
gauge.element.id + '_Axis_Major_TickLine_' + index + '_' + j, 'transparent', majorLineStyle.width,
isRangeColor ? getRangeColor(i, <Range[]>axis.ranges, color) : color,
null, majorLineStyle.dashArray, this.calculateTicks(i, <Tick>majorLineStyle, axis), '',
gauge.allowLoadingAnimation ? 'visibility: hidden;pointer-events:auto;' : 'pointer-events:auto;'
),
majorTickElements, gauge
) as HTMLElement;
tickElement.setAttribute('data-interval', i.toString());
j++;
}
element.appendChild(majorTickElements);
}
}
/**
* Method to calcualte the tick elements for the circular gauge.
*
* @param {number} value - Specifies the value.
* @param {Tick} options - Specifies the options.
* @param {Axis} axis - Specifies the axis.
* @returns {string} - Returns the string.
* @private
*/
public calculateTicks(value: number, options: Tick, axis: Axis): string {
const axisLineWidth: number = (axis.lineStyle.width / 2) + options.offset;
const angle: number = getAngleFromValue(
value, axis.visibleRange.max, axis.visibleRange.min,
axis.startAngle, axis.endAngle, axis.direction === 'ClockWise'
);
const start: GaugeLocation =
getLocationFromAngle(angle, axis.currentRadius +
(options.position === 'Outside' ? axisLineWidth : options.position === 'Cross' ?
options.height / 2 - options.offset : -axisLineWidth),
this.gauge.midPoint);
const end: GaugeLocation =
getLocationFromAngle(angle, axis.currentRadius +
(options.position === 'Outside' ? axisLineWidth : options.position === 'Cross' ?
options.height / 2 - options.offset : -axisLineWidth) +
(options.position === 'Outside' ? options.height : -options.height),
this.gauge.midPoint);
return 'M ' + start.x + ' ' + start.y + ' L ' + end.x + ' ' + end.y + ' ';
}
/**
* Method to render the range path of the circular gauge.
*
* @param {Axis} axis - Specifies the axis.
* @param {Range} range - Specifies the range.
* @param {number} startWidth - Specifies the startwidth for the range.
* @param {number} endWidth - Specifies the endwidth for the range.
* @param {number} rangeIndex - Specifies the index of the range.
* @param {number} index - Specifies the index of the axis.
* @param {Element} rangeElement - Specifies the element.
* @param {number} colorIndex - Specifies the index of the lineargradient colorstop.
* @returns {void}
* @private
*/
public drawRangePath(
axis: Axis, range: Range, startWidth: number, endWidth: number,
rangeIndex: number, index: number, rangeElement: Element, colorIndex: number
): void {
let startValue: number; let direction: string;
let endValue: number;
const location: GaugeLocation = this.gauge.midPoint;
let startAngle: number;
let endAngle: number;
const isClockWise: boolean = axis.direction === 'ClockWise';
const min: number = axis.visibleRange.min;
const max: number = axis.visibleRange.max;
let roundedStartAngle: number;
let roundedEndAngle: number;
let oldStart: number;
let oldEnd: number;
let gradientRangeColor: string;
if (range.isLinearCircularGradient) {
const rangeSplitValue: number = ((range.end - range.start) / range.linearGradient.colorStop.length);
const rangeStart: number = range.linearGradient.colorStop.length > 1 ?
(range.start + (rangeSplitValue * (colorIndex))) : range.start;
const rangeEnd: number = range.linearGradient.colorStop.length > 1 ? (rangeStart + rangeSplitValue) : range.end;
startValue = Math.min(Math.max(rangeStart, min), rangeEnd);
endValue = Math.min(Math.max(rangeStart, rangeEnd), max);
} else {
startValue = Math.min(Math.max(range.start, min), range.end);
endValue = Math.min(Math.max(range.start, range.end), max);
}
startAngle = getAngleFromValue(startValue, max, min, axis.startAngle, axis.endAngle, isClockWise);
endAngle = getAngleFromValue(endValue, max, min, axis.startAngle, axis.endAngle, isClockWise);
const isAngleCross360: boolean = (startAngle > endAngle);
if (axis.rangeGap != null && axis.rangeGap > 0 && range.start !== range.end
|| (!isNullOrUndefined(range.linearGradient) && !range.isLinearCircularGradient
&& (colorIndex === (!isNullOrUndefined(range.linearGradient.colorStop) && range.linearGradient.colorStop.length - 1)))) {
startAngle = (rangeIndex === 0 && !axis.startAndEndRangeGap) ? startAngle :
colorIndex === 0 && range.isLinearCircularGradient ? axis.direction === 'AntiClockWise' ?
startAngle - (axis.rangeGap / Math.PI) :
startAngle + (axis.rangeGap / Math.PI) : !range.isLinearCircularGradient
? (axis.direction === 'AntiClockWise' ? startAngle - (axis.rangeGap / Math.PI) : startAngle + (axis.rangeGap / Math.PI)) : startAngle;
endAngle = (rangeIndex === axis.ranges.length - 1 && !axis.startAndEndRangeGap) ? endAngle :
!isNullOrUndefined(range.linearGradient) && colorIndex === range.linearGradient.colorStop.length - 1
&& range.isLinearCircularGradient ?
axis.direction === 'AntiClockWise' ? endAngle + (axis.rangeGap / Math.PI) :
endAngle - (axis.rangeGap / Math.PI) : !range.isLinearCircularGradient ?
(axis.direction === 'AntiClockWise' ? endAngle + (axis.rangeGap / Math.PI) : endAngle - (axis.rangeGap / Math.PI)) : endAngle;
}
if (this.gauge.allowComponentRender) {
if ((startValue !== endValue) && (isAngleCross360 ? startAngle < (endAngle + 360) : (startAngle < endAngle)) && ((range.start >= min && range.end <= max) || (range.end >= min && range.start <= max))) {
endAngle = isClockWise ? endAngle : [startAngle, startAngle = endAngle][0];
endWidth = isClockWise ? endWidth : [startWidth, startWidth = endWidth][0];
const radius: number = typeof range.roundedCornerRadius === 'string' ? parseFloat(<string>range.roundedCornerRadius) : range.roundedCornerRadius;
const process: number = (radius * 0.25);
const degreeValue: number = getDegree(startAngle, endAngle);
oldStart = ((((range.currentRadius - (startWidth / 2)) * ((startAngle * Math.PI) / 180) -
(radius / process)) / (range.currentRadius - (startWidth / 2))) * 180) / Math.PI;
oldEnd = ((((range.currentRadius - (endWidth / 2)) * ((endAngle * Math.PI) / 180) +
(radius / process)) / (range.currentRadius - (endWidth / 2))) * 180) / Math.PI;
roundedStartAngle = ((((range.currentRadius) * ((startAngle * Math.PI) / 180) +
(degreeValue < (range.roundedCornerRadius / 2) && range.isLinearCircularGradient
? degreeValue <= 1 ? 0 : (radius / 4) : radius)) / (range.currentRadius)) * 180) / Math.PI;
roundedEndAngle = ((((range.currentRadius) * ((endAngle * Math.PI) / 180) -
(degreeValue < (range.roundedCornerRadius / 2) && range.isLinearCircularGradient
? degreeValue <= 1 ? 0 : (radius / 4) : radius)) / (range.currentRadius)) * 180) / Math.PI;
if (roundedStartAngle > roundedEndAngle && (roundedStartAngle - roundedEndAngle) <= radius) {
roundedStartAngle = startAngle;
roundedEndAngle = endAngle;
}
if (this.gauge.gradientModule && ((!isNullOrUndefined(range.linearGradient)
&& !isNullOrUndefined(range.linearGradient.colorStop)) || (!isNullOrUndefined(range.radialGradient)
&& !isNullOrUndefined(range.radialGradient.colorStop)))) {
if (range.isLinearCircularGradient) {
endAngle -= isCompleteAngle(startAngle, endAngle) ? 0.0001 : 0;
const degree: number = getDegree(startAngle, endAngle);
const rangeColorLength: number = range.linearGradient.colorStop.length;
const degreeRange: number = ((axis.startAngle === axis.endAngle ?
(axis.startAngle === 0 && axis.endAngle === 0 ? 360 : axis.startAngle) :
(axis.endAngle - axis.startAngle)) - degree * (rangeColorLength - 1));
let degreeRangeValue: number;
if (degreeRange <= 360 && degreeRange >= 270) {
degreeRangeValue = 270;
} else if (degreeRange <= 270 && degreeRange >= 180) {
degreeRangeValue = 180;
} else if (degreeRange <= 180 && degreeRange >= 90) {
degreeRangeValue = 90;
} else if (degreeRange <= 90 && degreeRange >= 0) {
degreeRangeValue = 0;
}
const gradientDegree: number = axis.direction === 'AntiClockWise' ?
(axis.startAngle === axis.endAngle ? 0 : axis.startAngle) + degree * ((rangeColorLength - 1) - colorIndex)
: axis.startAngle + degree * (colorIndex);
let gradientAngle: number = axis.startAngle < axis.endAngle ? axis.direction === 'AntiClockWise'
? axis.ranges.length > 1 ? rangeIndex === 0 ? (360 - (axis.startAngle
+ (degree * (colorIndex)))) : (axis.startAngle + (degree * (colorIndex + 1))) :
axis.startAngle + (degreeRangeValue + degree * ((rangeColorLength - 1) - colorIndex)) : axis.startAngle
+ (degree * (colorIndex)) : axis.endAngle === 360 || axis.startAngle === axis.endAngle
? axis.direction === 'AntiClockWise' ? axis.startAngle === axis.endAngle ?
(axis.startAngle === 0 && axis.endAngle === 0 ? 0 : 360) - axis.startAngle +
degreeRangeValue + (degree * ((rangeColorLength - 1) - colorIndex))
: degree * ((rangeColorLength - 1) - colorIndex) : degree * (colorIndex) :
gradientDegree < 360 ? gradientDegree : gradientDegree - 360;
range.gradientAngle = rangeIndex === 0 ? axis.rangeGap ? gradientAngle + axis.rangeGap
: gradientAngle : axis.rangeGap > 0 ? axis.ranges[rangeIndex - 1]['gradientAngle'] + axis.rangeGap
: axis.ranges[rangeIndex - 1]['gradientAngle'];
if (axis.direction === 'AntiClockWise' && (axis.ranges.length > 1
? colorIndex === rangeColorLength - 1 : colorIndex === 0)) {
range.gradientAntiAngle = gradientAngle;
}
if (rangeIndex !== 0) {
gradientAngle = axis.direction === 'AntiClockWise' ? axis.ranges.length > 1 ?
axis.ranges[rangeIndex - 1]['gradientAntiAngle'] - gradientAngle + axis.startAngle :
axis.ranges[rangeIndex - 1]['gradientAntiAngle'] + gradientAngle :
range.gradientAngle + gradientAngle - axis.startAngle;
range.gradientAngle = axis.rangeGap != null && axis.rangeGap > 0 ? colorIndex === rangeColorLength - 1 ?
gradientAngle + axis.ranges[rangeIndex - 1]['gradientAngle'] : gradientAngle : gradientAngle;
if (axis.direction === 'AntiClockWise' && (axis.ranges.length > 1
? colorIndex === rangeColorLength - 1 : colorIndex === 0)) {
range.gradientAntiAngle = gradientAngle;
}
}
if (gradientAngle > 45 && gradientAngle <= 115
|| (gradientAngle >= 0 && gradientAngle <= 45 && (rangeColorLength - 1) <= 2)) {
direction = axis.direction === 'AntiClockWise' ? 'bottom' : 'top';
} else if (gradientAngle > 115 && gradientAngle < 170) {
direction = axis.direction === 'AntiClockWise' ? 'left' : 'right';
} else if (gradientAngle >= 170 && gradientAngle <= 280) {
direction = axis.direction === 'AntiClockWise' ? 'top' : 'bottom';
} else if (gradientAngle > 280 && gradientAngle <= 360
|| (gradientAngle >= 0 && gradientAngle <= 45 && (rangeColorLength - 1) >= 2)) {
direction = axis.direction === 'AntiClockWise' ? 'right' : 'left';
}
}
gradientRangeColor = this.gauge.gradientModule.getGradientColorString(
range, colorIndex, direction, rangeIndex
);
}
range.rangeColor = gradientRangeColor ? gradientRangeColor : range.rangeColor;
if (range.roundedCornerRadius) {
if (range.isLinearCircularGradient && range.linearGradient.colorStop.length > 1) {
if (colorIndex === 0 || colorIndex === range.linearGradient.colorStop.length - 1) {
if (axis.direction === 'ClockWise') {
this.roundedRangeAppendPathCalculation(
range, rangeIndex, index, startWidth, endWidth, rangeElement,
(colorIndex === range.linearGradient.colorStop.length - 1
? Math.floor(startAngle) : Math.floor(roundedStartAngle)),
(colorIndex !== 0 ? Math.ceil(roundedEndAngle) : Math.ceil(endAngle)),
((colorIndex === range.linearGradient.colorStop.length - 1) ? startAngle : oldStart),
(colorIndex !== 0 ? oldEnd : endAngle), location, colorIndex
);
} else {
this.roundedRangeAppendPathCalculation(
range, rangeIndex, index, startWidth, endWidth, rangeElement,
(colorIndex === 0 ? Math.floor(startAngle) : Math.floor(roundedStartAngle)),
(colorIndex === range.linearGradient.colorStop.length - 1
? Math.ceil(endAngle) : Math.ceil(roundedEndAngle)),
((colorIndex === 0) ? startAngle : oldStart),
(colorIndex === range.linearGradient.colorStop.length - 1 ? endAngle : oldEnd),
location, colorIndex
);
}
} else {
this.rangeAppendPathCalculation(
range, rangeIndex, index, startWidth, endWidth, rangeElement,
Math.floor(startAngle), Math.ceil(endAngle), colorIndex
);
}
} else {
this.roundedRangeAppendPathCalculation(
range, rangeIndex, index, startWidth, endWidth, rangeElement,
Math.floor(roundedStartAngle), Math.ceil(roundedEndAngle), oldStart,
oldEnd, location, colorIndex
);
}
} else {
this.rangeAppendPathCalculation(
range, rangeIndex, index, startWidth, endWidth, rangeElement,
Math.floor(startAngle), Math.ceil(endAngle), colorIndex
);
}
} else if ((range.start === range.end) && ((range.start >= min && range.end <= max) || (range.end >= min && range.start <= max))) {
this.rangeAppendPathCalculation(
range, rangeIndex, index, startWidth, endWidth, rangeElement,
Math.floor(startAngle), Math.ceil(endAngle), colorIndex
);
}
}
}
/**
* Method to render the rounded range path of the circular gauge.
*
* @param {Range} range - Specifies the range.
* @param {number} rangeIndex - Specifies the index of the range.
* @param {number} index - Specifies the index of the axis.
* @param {number} startWidth - Specifies the startwidth for the range.
* @param {number} endWidth - Specifies the endwidth for the range.
* @param {Element} rangeElement - Specifies the element.
* @param {number} roundedStartAngle - Specifies the rounded path of the start angle.
* @param {number} roundedEndAngle - Specifies the rounded path of the end angle.
* @param {number} oldStart - Specifies the rounded path of the old start value.
* @param {number} oldEnd - Specifies the rounded path of the old end value..
* @param {GaugeLocation} location - Specifies the location.
* @param {number} colorIndex - Specifies the index of the lineargradient colorstop.
* @returns {void}
* @private
*/
public roundedRangeAppendPathCalculation(
range: Range, rangeIndex: number,
index: number, startWidth: number, endWidth: number, rangeElement: Element,
roundedStartAngle: number, roundedEndAngle: number, oldStart: number, oldEnd: number,
location: GaugeLocation, colorIndex?: number
): void {
range.pathElement.push(appendPath(
new PathOption(
(!range.isLinearCircularGradient ? this.gauge.element.id + '_Axis_' + index + '_Range_' + rangeIndex
: this.gauge.element.id + '_Axis_' + index + '_Range_' + rangeIndex + '_Circular_' + colorIndex),
range.rangeColor, 0, range.rangeColor, range.opacity, '0',
getRoundedPathArc(
location,
Math.floor(roundedStartAngle), Math.ceil(roundedEndAngle), oldStart, oldEnd,
range.currentRadius, startWidth, endWidth, range, this.gauge.axes[index as number] as Axis
),
'', ''
),
rangeElement, this.gauge
));
}
/**
* Method to render the rounded range path of the circular gauge.
*
* @param {Range} range - Specifies the range.
* @param {number} rangeIndex - Specifies the index of the range.
* @param {number} index - Specifies the index of the axis.
* @param {number} startWidth - Specifies the startwidth for the range.
* @param {number} endWidth - Specifies the endwidth for the range.
* @param {Element} rangeElement - Specifies the element.
* @param {number} startAngle - Specifies the rounded path of the start angle.
* @param {number} endAngle - Specifies the rounded path of the end angle.
* @param {number} colorIndex - Specifies the index of the lineargradient colorstop.
* @returns {void}
* @private
*/
public rangeAppendPathCalculation(
range: Range, rangeIndex: number,
index: number, startWidth: number, endWidth: number, rangeElement: Element, startAngle: number,
endAngle: number, colorIndex?: number
): void {
range.pathElement.push(appendPath(
new PathOption(
!range.isLinearCircularGradient ? this.gauge.element.id + '_Axis_' + index + '_Range_' +
rangeIndex : this.gauge.element.id + '_Axis_' + index + '_Range_' +
rangeIndex + '_Circular_' + colorIndex,
range.rangeColor,
0, range.rangeColor, range.opacity, '0',
getPathArc(
this.gauge.midPoint,
Math.floor(startAngle), Math.ceil(endAngle),
range.currentRadius, startWidth,
endWidth, range, this.gauge.axes[index as number] as Axis
),
'', ''
),
rangeElement, this.gauge
));
}
/**
* Method to render the axis range of the circular gauge.
*
* @param {Axis} axis - Specifies the axis.
* @param {number} index - Specifies the index.
* @param {Element} element - Specifies the element.
* @returns {void}
* @private
*/
public drawAxisRange(axis: Axis, index: number, element: Element): void {
const ele: Element = (document.getElementById(this.gauge.element.id + '_Axis_Ranges_ ' + index));
const rangeElement: Element = (ele) ? document.getElementById(this.gauge.element.id + '_Axis_Ranges_ ' + index) :
this.gauge.renderer.createGroup({ id: this.gauge.element.id + '_Axis_Ranges_' + index,
style: this.gauge.allowLoadingAnimation ? 'opacity: 0;' : '' });
let startWidth: number; let startEndDifference: number;
let endWidth: number; let previousEndWidth: number; let previousStartWidth: number;
axis.ranges.map((range: Range, rangeIndex: number) => {
range.isLinearCircularGradient = !isNullOrUndefined(this.gauge.gradientModule)
&& !isNullOrUndefined(range.linearGradient) && isNullOrUndefined(range.linearGradient.startValue)
&& isNullOrUndefined(range.linearGradient.endValue) && !isNullOrUndefined(range.linearGradient.colorStop);
range.pathElement = [];
if (!isNullOrUndefined(range.offset) && (<string>range.offset).length > 0) {
range.currentDistanceFromScale = stringToNumber(<string>range.offset, axis.currentRadius);
} else {
range.currentDistanceFromScale = <number>range.offset;
}
this.calculateRangeRadius(axis, range);
if (!isNullOrUndefined((<string>range.startWidth)) && (<string>range.startWidth).length > 0) {
startWidth = toPixel(<string>range.startWidth, range.currentRadius);
} else {
startWidth = <number>range.startWidth;
}
if (!isNullOrUndefined((<string>range.endWidth)) && (<string>range.endWidth).length > 0) {
endWidth = toPixel(<string>range.endWidth, range.currentRadius);
} else {
endWidth = <number>range.endWidth;
}
range.currentRadius = this.calculateRangeRadiusWithPosition(axis, range, startWidth);
if (range.isLinearCircularGradient) {
for (let i: number = 0; i < range.linearGradient.colorStop.length; i++) {
if (i <= (range.linearGradient.colorStop.length - 1)) {
previousEndWidth = i === 0 ? endWidth : previousEndWidth;
previousStartWidth = i === 0 ? startWidth : previousStartWidth;
startEndDifference = (Math.abs(previousStartWidth - previousEndWidth) / (range.linearGradient.colorStop.length));
if (i > 0) {
startWidth = endWidth;
endWidth = previousStartWidth > previousEndWidth ? startWidth - startEndDifference
: startWidth + startEndDifference;
} else {
endWidth = previousStartWidth > previousEndWidth ? startWidth - startEndDifference
: startWidth + startEndDifference;
}
} else {
startWidth = previousStartWidth > previousEndWidth ? startWidth - startEndDifference
: startWidth + startEndDifference;
endWidth = (previousEndWidth);
}
this.drawRangePath(axis, range, startWidth, endWidth, rangeIndex, index, rangeElement, i);
}
} else {
if (!(range.start === range.end && axis.direction === 'AntiClockWise' && axis.startAngle === axis.endAngle)) {
this.drawRangePath(axis, range, startWidth, endWidth, rangeIndex, index, rangeElement, null);
}
}
}
);
element.appendChild(rangeElement);
}
/**
* Method to calculate the radius of the axis range.
*
* @return {void}
*/
private calculateRangeRadius(axis: Axis, range: Range): void {
const radius: string = range.radius !== null ? range.radius : '100%';
range.currentRadius = stringToNumber(
radius, axis.currentRadius
);
}
private calculateRangeRadiusWithPosition(axis: Axis, range: Range, startWidth: number): number {
const actualRadius: number = !isNullOrUndefined(range.position) && range.position !== 'Auto' && isNullOrUndefined(range.radius) ?
(range.position === 'Outside' ? (range.currentRadius + axis.lineStyle.width / 2 + range.currentDistanceFromScale) :
range.position === 'Inside' ? (range.currentRadius - axis.lineStyle.width / 2 - range.currentDistanceFromScale) :
(range.currentRadius + startWidth / 2 - range.currentDistanceFromScale)) : range.currentRadius;
return actualRadius;
}
/**
* Method to get the range color of the circular gauge.
*
* @param {Axis} axis - Specifies the axis
* @returns {void}
* @private
*/
public setRangeColor(axis: Axis): void {
const rangeColors: string[] = getRangePalette(this.gauge.theme);
axis.ranges.map((range: Range, index: number) => {
range.rangeColor = range.color ? range.color : rangeColors[index % rangeColors.length];
});
}
/**
*
* @returns {void}
* @private
*/
public destroy(): void {
this.gauge = null;
this.majorValues = [];
}
}