@syncfusion/ej2-circulargauge
Version:
Essential JS 2 CircularGauge Components
1,276 lines (1,175 loc) • 76.3 kB
text/typescript
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable max-len */
/**
* Circular Gauge
*/
import { Property, NotifyPropertyChanges, Component, INotifyPropertyChanged, animationMode } from '@syncfusion/ej2-base';
import { Complex, Browser, isNullOrUndefined } from '@syncfusion/ej2-base';
import { Event, EmitType, EventHandler, Collection, Internationalization, ModuleDeclaration } from '@syncfusion/ej2-base';
import { remove, createElement } from '@syncfusion/ej2-base';
import { SvgRenderer } from '@syncfusion/ej2-svg-base';
import { CircularGaugeModel } from './circular-gauge-model';
import { ILoadedEventArgs, IAnimationCompleteEventArgs, IVisiblePointer } from './model/interface';
import { IVisibleRange, IThemeStyle } from './model/interface';
import { IAxisLabelRenderEventArgs, IRadiusCalculateEventArgs, IPointerDragEventArgs, IResizeEventArgs } from './model/interface';
import { ITooltipRenderEventArgs, ILegendRenderEventArgs, IAnnotationRenderEventArgs }from './model/interface';
import { IMouseEventArgs, IPrintEventArgs } from './model/interface';
import { removeElement, getElement, stringToNumber, measureText, toPixel, textElement, getAngleFromValue, getAngleFromLocation, getPathArc, getPointer, RectOption, Size, GaugeLocation, Rect, TextOption } from './utils/helper-common';
import { setStyles, getValueFromAngle, getRange } from './utils/helper-circular-gauge';
import { GaugeTheme } from './utils/enum';
import { Border, Margin, Font, TooltipSettings, LegendSettings } from './model/base';
import { BorderModel, MarginModel, FontModel, TooltipSettingsModel } from './model/base-model';
import { Axis, Range, Pointer, Annotation, VisibleRangeModel } from './axes/axis';
import { Annotations } from './annotations/annotations';
import { GaugeTooltip } from './user-interaction/tooltip';
import { AxisModel } from './axes/axis-model';
import { load, loaded, gaugeMouseMove, gaugeMouseLeave, gaugeMouseDown, pointerMove } from './model/constants';
import { rangeMove, pointerStart, rangeStart, pointerEnd, rangeEnd } from './model/constants';
import { gaugeMouseUp, dragEnd, dragMove, dragStart, resized } from './model/constants';
import { AxisLayoutPanel } from './axes/axis-panel';
import { getThemeStyle } from './model/theme';
import { textTrim, titleTooltip } from './utils/helper-legend';
import { Legend } from './legend/legend';
import { LegendSettingsModel } from './model/base-model';
import { PdfPageOrientation } from '@syncfusion/ej2-pdf-export';
import { ExportType } from '../circular-gauge/utils/enum';
import { PdfExport } from './model/pdf-export';
import { ImageExport } from './model/image-export';
import { Print } from './model/print';
import { Gradient } from './axes/gradient';
/**
* Represents the circular gauge control. This is used to customize the properties of the circular gauge to visualize the data in circular scale.
* ```html
* <div id="gauge"/>
* <script>
* var gaugeObj = new CircularGauge();
* gaugeObj.appendTo("#gauge");
* </script>
* ```
*/
@NotifyPropertyChanges
export class CircularGauge extends Component<HTMLElement> implements INotifyPropertyChanged {
//Module Declaration of circular gauge
/**
* Sets and gets the module that is used to add annotation in the circular gauge.
*
* @private
*/
public annotationsModule: Annotations;
/**
* Sets and gets the module that is used to add Print in the circular gauge.
*
* @private
*/
public printModule : Print;
/**
* Sets and gets the module that is used to add ImageExport in the circular gauge.
*
* @private
*/
public imageExportModule: ImageExport;
/**
* Sets and gets the module that is used to add pdfExport in the circular gauge.
*
* @private
*/
public pdfExportModule: PdfExport;
/**
* Sets and gets the module that is used to show the tooltip in the circular gauge.
*
* @private
*/
public tooltipModule: GaugeTooltip;
/**
* Sets and gets the module that is used to manipulate and add legend to the circular gauge.
*
* @private
*/
public legendModule: Legend;
/**
* Sets and gets the module that enables the gradient option for pointer and ranges.
*
* @private
*/
public gradientModule: Gradient;
/**
* Sets and gets the width of the circular gauge as a string in order to provide input as both like '100px' or '100%'.
* If specified as '100%, gauge will render to the full width of its parent element.
*
* @default null
*/
@Property(null)
public width: string;
/**
* Sets and gets the height of the circular gauge as a string in order to provide input as both like '100px' or '100%'.
* If specified as '100%, gauge will render to the full height of its parent element.
*
* @default null
*/
@Property(null)
public height: string;
/**
* Sets and gets the options for customizing the style properties of the gauge border.
*/
@Complex<BorderModel>({ color: 'transparent', width: 0 }, Border)
public border: BorderModel;
/**
*
*/
/**
* Sets and gets the background color of the gauge. This property accepts value in hex code, rgba string as a valid CSS color string.
*
* @default null
*/
@Property(null)
public background: string;
/**
* Sets and gets the title for circular gauge.
*
* @default ''
*/
@Property('')
public title: string;
/**
* Sets and gets the duration of animation in milliseconds in circular gauge.
*
* @default 0
*/
@Property(0)
public animationDuration: number;
/**
* Sets and gets the options for customizing the title for circular gauge.
*/
@Complex<FontModel>({ size: null, color: null, fontWeight: null, fontFamily: null }, Font)
public titleStyle: FontModel;
/**
* Sets and gets the options to customize the left, right, top and bottom margins of the circular gauge.
*/
@Complex<MarginModel>({}, Margin)
public margin: MarginModel;
/**
* Sets and gets the options for customizing the axes of circular gauge.
*/
@Collection<AxisModel>([{}], Axis)
public axes: AxisModel[];
/**
* Sets and gets the options for customizing the tooltip of gauge.
*/
@Complex<TooltipSettingsModel>({}, TooltipSettings)
public tooltip: TooltipSettingsModel;
/**
* Enables and disables drag movement of the pointer in the circular gauge.
*
* @default false
*/
@Property(false)
public enablePointerDrag: boolean;
/**
* Enables and disables the drag movement of the ranges in the circular gauge.
*
* @default false
*/
@Property(false)
public enableRangeDrag: boolean;
/**
* Enables and disables the print functionality in circular gauge.
*
* @default false
*/
@Property(false)
public allowPrint: boolean;
/**
* Enables and disables the export to image functionality in circular gauge.
*
* @default false
*/
@Property(false)
public allowImageExport: boolean;
/**
* Enables and disables the export to pdf functionality in circular gauge.
*
* @default false
*/
@Property(false)
public allowPdfExport: boolean;
/**
* Allow the range element to be rendered ahead of the axis element, when this property is set to "true".
*
* @default true
*/
@Property(true)
public allowRangePreRender: boolean;
/**
* Sets and gets the X coordinate of the center of the circular gauge.
*
* @default null
*/
@Property(null)
public centerX: string;
/**
* Sets and gets the Y coordinate of the center of the circular gauge.
*
* @default null
*/
@Property(null)
public centerY: string;
/**
* Enables and disables placing the half or quarter circle in center, if `centerX` and `centerY` properties are not specified.
*
* @default false
*/
@Property(false)
public moveToCenter: boolean;
/**
* Sets and gets the theme styles supported for circular gauge. When the theme is set, the styles associated with the theme will be set in the gauge.
*
* @default Material
*/
@Property('Material')
public theme: GaugeTheme;
/**
* Enables and disables the grouping separator should be used for a number.
*
* @default false
*/
@Property(false)
public useGroupingSeparator: boolean;
/**
* Sets and gets the information about gauge for assistive technology.
*
* @default null
*/
@Property(null)
public description: string;
/**
* Sets and gets the tab index value for the circular gauge.
*
* @default 0
*/
@Property(0)
public tabIndex: number;
/**
* Enables or disables the ability of the gauge to be rendered to the complete width. The left, right, top and bottom spacing will not be considered in the gauge when this property is disabled.
*
* @default true
*/
@Property(true)
public allowMargin: boolean;
/**
* Sets and gets the options for customizing the legend of the circular gauge.
*/
@Complex<LegendSettingsModel>({}, LegendSettings)
public legendSettings: LegendSettingsModel;
/**
* Triggers after the circular gauge gets loaded.
*
* @event loaded
*/
@Event()
public loaded: EmitType<ILoadedEventArgs>;
/**
* Triggers before the circular gauge gets loaded.
*
* @event load
*/
@Event()
public load: EmitType<ILoadedEventArgs>;
/**
* Triggers after the animation gets completed for pointers.
*
* @event animationComplete
*/
@Event()
public animationComplete: EmitType<IAnimationCompleteEventArgs>;
/**
* Triggers before each axis label gets rendered.
*
* @event axisLabelRender
*/
@Event()
public axisLabelRender: EmitType<IAxisLabelRenderEventArgs>;
/**
* Triggers before the radius for the circular gauge gets calculated.
*
* @event radiusCalculate
*/
@Event()
public radiusCalculate: EmitType<IRadiusCalculateEventArgs>;
/**
* Triggers before each annotation for the circular gauge gets rendered.
*
* @event annotationRender
*/
@Event()
public annotationRender: EmitType<IAnnotationRenderEventArgs>;
/**
* Triggers before each legend for the circular gauge gets rendered.
*
* @event legendRender
* @deprecated
*/
@Event()
public legendRender: EmitType<ILegendRenderEventArgs>;
/**
* Triggers before the tooltip for pointer of the circular gauge gets rendered.
*
* @event tooltipRender
*/
@Event()
public tooltipRender: EmitType<ITooltipRenderEventArgs>;
/**
* Triggers before the pointer is dragged.
*
* @event dragStart
*/
@Event()
public dragStart: EmitType<IPointerDragEventArgs>;
/**
* Triggers while dragging the pointers.
*
* @event dragMove
*/
@Event()
public dragMove: EmitType<IPointerDragEventArgs>;
/**
* Triggers after the pointer is dragged.
*
* @event dragEnd
*/
@Event()
public dragEnd: EmitType<IPointerDragEventArgs>;
/**
* Triggers on hovering the circular gauge.
*
* @event gaugeMouseMove
*/
@Event()
public gaugeMouseMove: EmitType<IMouseEventArgs>;
/**
* Triggers while cursor leaves the circular gauge.
*
* @event gaugeMouseLeave
*/
@Event()
public gaugeMouseLeave: EmitType<IMouseEventArgs>;
/**
* Triggers on mouse down.
*
* @event gaugeMouseDown
*/
@Event()
public gaugeMouseDown: EmitType<IMouseEventArgs>;
/**
* Triggers when mouse up action is performed over the circular gauge.
*
* @event gaugeMouseUp
*/
@Event()
public gaugeMouseUp: EmitType<IMouseEventArgs>;
/**
* Triggers to notify the resize of the circular gauge when the window is resized.
*
* @event resized
*/
@Event()
public resized: EmitType<IResizeEventArgs>;
/**
* Triggers before the prints gets started.
*
* @event beforePrint
*/
@Event()
public beforePrint: EmitType<IPrintEventArgs>;
/** @private */
public renderer: SvgRenderer;
/** @private */
public loadingAnimationDuration: number[];
/** @private */
public allowLoadingAnimation: boolean = false;
/** @private */
public svgObject: Element;
/** @private */
public availableSize: Size;
/** @private */
public intl: Internationalization;
/** @private */
private resizeTo: number;
/** @private */
public midPoint: GaugeLocation;
/** @private */
public activePointer: Pointer;
/** @private */
public activeAxis: Axis;
/** @private */
public activeRange: Range;
/** @private */
public gaugeRect: Rect;
/** @private */
public animatePointer: boolean;
/** @private */
public startValue: number;
/** @private */
public endValue: number;
/** @private */
private isRangeUpdate: boolean = false;
/** @private */
public centerXpoint: string;
/** @private */
public centerYpoint: string;
/** @private */
public allowComponentRender: boolean;
/** @private */
private clearTimeout: number;
/** @private */
public isPropertyChange: boolean;
/** @private */
public isAnimationProgress: boolean = true;
/** @private */
public isResize: boolean = false;
/** @private */
public isOverAllAnimationComplete: boolean = false;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private resizeEvent: any;
/**
* Render axis panel for gauge.
*
* @hidden
* @private
*/
public gaugeAxisLayoutPanel: AxisLayoutPanel;
/**
* @private
*/
public themeStyle: IThemeStyle;
/** @private */
public isDrag: boolean = false;
/** @private */
public isTouch: boolean;
/** @private */
public mouseX: number;
/** @private */
public mouseY: number;
/** @private */
public allowPointerDrag: boolean;
/** @private */
public isPointerDragged: boolean;
/**
* @private
*/
public gradientCount: number = 0;
/**
* Constructor for creating the widget
*
* @param {CircularGaugeModel} options - Specifies the options
* @param {string} element - Specifies the element
* @hidden
*/
constructor(options?: CircularGaugeModel, element?: string | HTMLElement) {
super(options, element);
CircularGauge.Inject(Gradient);
if (element) {
this.appendTo(element);
}
}
/**
* To create svg object, renderer and binding events for the container.
*
* @returns {void}
*/
protected preRender(): void {
if (!isNullOrUndefined(this.element)) {
this.unWireEvents();
this.trigger(load, { gauge: this });
this.initPrivateVariable();
this.setCulture();
this.createSvg();
this.wireEvents();
}
}
/**
* To render the circular gauge elements
*
* @returns {void}
*/
protected render(): void {
if (!isNullOrUndefined(this.element)) {
this.setTheme();
this.calculateBounds();
this.isPropertyChange = false;
this.allowLoadingAnimation = ((this.animationDuration === 0 && animationMode === 'Enable') || this.animationDuration > 0)
&& !this.isOverAllAnimationComplete;
this.renderElements(true);
this.renderAnimation();
this.renderComplete();
}
}
private setTheme(): void {
this.themeStyle = getThemeStyle(this.theme);
}
/**
* Method to unbind events for circular gauge
*
* @returns {void}
*/
private unWireEvents(): void {
EventHandler.remove(this.element, Browser.touchStartEvent, this.gaugeOnMouseDown);
EventHandler.remove(this.element, Browser.touchMoveEvent, this.mouseMove);
EventHandler.remove(this.element, Browser.touchEndEvent, this.mouseEnd);
EventHandler.remove(this.element, 'click', this.gaugeOnMouseClick);
EventHandler.remove(this.element, 'contextmenu', this.gaugeRightClick);
EventHandler.remove(
this.element, (Browser.isPointer ? 'pointerleave' : 'mouseleave'),
this.mouseLeave
);
window.removeEventListener(
(Browser.isTouch && ('orientation' in window && 'onorientationchange' in window)) ? 'orientationchange' : 'resize',
this.resizeEvent
);
}
/**
* Method to bind events for circular gauge
*
* @returns {void}
*/
private wireEvents(): void {
/*! Bind the Event handler */
EventHandler.add(this.element, Browser.touchStartEvent, this.gaugeOnMouseDown, this);
EventHandler.add(this.element, Browser.touchMoveEvent, this.mouseMove, this);
EventHandler.add(this.element, Browser.touchEndEvent, this.mouseEnd, this);
EventHandler.add(this.element, 'click', this.gaugeOnMouseClick, this);
EventHandler.add(this.element, 'contextmenu', this.gaugeRightClick, this);
EventHandler.add(
this.element,
(Browser.isPointer ? 'pointerleave' : 'mouseleave'), this.mouseLeave, this
);
this.resizeEvent = this.gaugeResize.bind(this);
window.addEventListener(
(Browser.isTouch && ('orientation' in window && 'onorientationchange' in window)) ? 'orientationchange' : 'resize',
this.resizeEvent
);
/*! Apply the style for circular gauge */
this.setGaugeStyle(<HTMLElement>this.element);
}
/**
* Handles the mouse click on accumulation chart.
*
* @param {PointerEvent} e - Specifies the pointer event
* @returns {boolean} - Returns the boolean value
* @private
*/
public gaugeOnMouseClick(e: PointerEvent): boolean {
this.setMouseXY(e);
if (this.legendModule && this.legendSettings.visible) {
this.legendModule.click(e);
}
return false;
}
/**
* Handles the mouse move.
*
* @param {PointerEvent} e - Specifies the pointer event
* @returns {boolean} - Returns the boolean value
* @private
*/
public mouseMove(e: PointerEvent): boolean {
this.setMouseXY(e);
const args: IMouseEventArgs = this.getMouseArgs(e, 'touchmove', gaugeMouseMove);
this.trigger('gaugeMouseMove', args, (observedArgs: IMouseEventArgs) => {
let dragArgs: IPointerDragEventArgs;
let pointerDrag: boolean = false;
if ((!isNullOrUndefined(this.activePointer) ? this.activePointer.pathElement[0].id === args.target.id : true)) {
const pointerIndex: number = args.target.id.indexOf('_Pointer_') > -1 ? parseInt(args.target.id.slice(-1), 10) : null;
const axisIndex: number = args.target.id.indexOf('_Axis_') > -1 ? parseInt(args.target.id.split('_Axis_')[1], 10) : null;
pointerDrag = (this.allowPointerDrag && !isNullOrUndefined(axisIndex) && !isNullOrUndefined(pointerIndex))
? this.axes[axisIndex as number].pointers[pointerIndex as number].enableDrag
: this.enablePointerDrag;
this.isPointerDragged = pointerDrag;
}
if (!args.cancel) {
if ((this.isPointerDragged || this.enableRangeDrag) && this.svgObject.getAttribute('cursor') !== 'grabbing') {
if ((args.target.id.indexOf('_Pointer_') !== -1 && this.isPointerDragged) || (this.enableRangeDrag && args.target.id.indexOf('_Range_') !== -1)) {
this.svgObject.setAttribute('cursor', 'pointer');
} else {
this.svgObject.setAttribute('cursor', 'auto');
}
} else if (this.svgObject.getAttribute('cursor') !== 'grabbing') {
this.svgObject.setAttribute('cursor', 'auto');
}
const svgElement: HTMLElement = <HTMLElement>getElement(this.element.id + '_svg');
const extraWidth: number = this.element.getBoundingClientRect().left - svgElement.getBoundingClientRect().left;
if (this.isPointerDragged && this.activePointer) {
this.isDrag = true;
e.preventDefault();
const dragPointInd: number = parseInt(this.activePointer.pathElement[0].id.slice(-1), 10);
const dragAxisInd: number = parseInt(this.activePointer.pathElement[0].id.split('_Axis_')[1], 10);
dragArgs = {
axis: this.activeAxis,
pointer: this.activePointer,
previousValue: this.activePointer.currentValue,
name: dragMove,
type: pointerMove,
currentValue: null,
axisIndex: dragAxisInd,
pointerIndex: dragPointInd
};
this.pointerDrag(new GaugeLocation(args.x + extraWidth, args.y), dragAxisInd, dragPointInd);
dragArgs.currentValue = this.activePointer.currentValue;
this.trigger(dragMove, dragArgs);
this.activeRange = null;
}
else if ( this.enableRangeDrag && this.activeRange) {
this.isDrag = true;
e.preventDefault();
const dragAxisInd: number = parseInt(this.activeRange.pathElement[0].id.split('_Axis_')[1], 10);
const dragRangeInd: number = parseInt(this.activeRange.pathElement[0].id.split('Range_')[1], 10);
dragArgs = {
axis: this.activeAxis,
name: dragMove,
type: rangeMove,
range: this.activeRange,
axisIndex: dragAxisInd,
rangeIndex: dragRangeInd
};
this.rangeDrag(new GaugeLocation(args.x + extraWidth, args.y), dragAxisInd, dragRangeInd);
this.trigger(dragMove, dragArgs);
}
}
});
this.notify(Browser.touchMoveEvent, e);
titleTooltip(e, e.clientX, e.clientY, this, false);
return false;
}
/**
* Handles the mouse leave.
*
* @param {PointerEvent} e - Specifies the pointer event
* @returns {boolean} - Returns the boolean value
* @private
*/
public mouseLeave(e: PointerEvent): boolean {
this.setMouseXY(e);
this.activeAxis = null;
this.activePointer = null;
this.activeRange = null;
this.svgObject.setAttribute('cursor', 'auto');
const args: IMouseEventArgs = this.getMouseArgs(e, 'touchmove', gaugeMouseLeave);
this.trigger(gaugeMouseLeave, args);
return false;
}
/**
* Handles the mouse right click.
*
* @param {MouseEvent | PointerEvent} event - Specifies the pointer or mouse event.
* @returns {boolean} - Returns the boolean value.
* @private
*/
public gaugeRightClick(event: MouseEvent | PointerEvent): boolean {
if (event.buttons === 2 || (<PointerEvent>event).pointerType === 'touch') {
event.preventDefault();
event.stopPropagation();
return false;
}
return true;
}
/**
* Handles the pointer draf while mouse move on gauge.
*
* @param {GaugeLocation} location - Specifies the location of the gauge
* @param {number} axisIndex - Specifies the axis index
* @param {number} pointerIndex - Specifies the pointer index
* @returns {void}
* @private
*/
public pointerDrag(location: GaugeLocation, axisIndex?: number, pointerIndex?: number): void {
const axis: Axis = this.activeAxis;
const range: VisibleRangeModel = axis.visibleRange;
const value: number = getValueFromAngle(
getAngleFromLocation(this.midPoint, location), range.max, range.min,
axis.startAngle, axis.endAngle,
axis.direction === 'ClockWise'
);
if (value >= range.min && value <= range.max) {
this.axes[axisIndex as number].pointers[pointerIndex as number].value = value;
this.activePointer.currentValue = value;
this.gaugeAxisLayoutPanel.pointerRenderer.setPointerValue(axis, this.activePointer, value);
}
}
/**
* Handles the range draf while mouse move on gauge.
*
* @param {GaugeLocation} location - Specifies the gauge location
* @param {number} axisIndex - Specifies the axis index
* @param {number} rangeIndex - Specifies the range index
* @returns {void}
* @private
*/
public rangeDrag(location: GaugeLocation, axisIndex: number, rangeIndex: number): void {
if (this.activeAxis) {
const axis: Axis = this.activeAxis;
const range: VisibleRangeModel = axis.visibleRange;
const value : number = getValueFromAngle(
getAngleFromLocation(this.midPoint, location), range.max, range.min,
axis.startAngle, axis.endAngle,
axis.direction === 'ClockWise'
);
if (value >= range.min && value <= range.max) {
const previousValue1: number = this.activeRange.currentValue;
this.activeRange.currentValue = value;
const add : number = (this.activeRange.end - this.activeRange.start);
const div : number = add / 2;
const avg: number = parseFloat(this.activeRange.start.toString()) + div;
const start: number = typeof this.activeRange.start === 'string' ? parseFloat(<string>this.activeRange.start) : this.activeRange.start;
const end: number = typeof this.activeRange.end === 'string' ? parseFloat(<string>this.activeRange.end) : this.activeRange.end;
this.startValue = (value < avg) ? value : ((previousValue1 < avg) ? previousValue1 : ((start < end) ? this.activeRange.start : this.activeRange.end));
this.endValue = (value < avg) ? ((previousValue1 > avg) ? previousValue1 : ((start < end) ? this.activeRange.end : this.activeRange.start)) : value;
this.axes[axisIndex as number].ranges[rangeIndex as number].start = this.startValue;
this.axes[axisIndex as number].ranges[rangeIndex as number].end = this.endValue;
if (this.isTouch) {
this.setRangeValue(axisIndex, rangeIndex, this.startValue, this.endValue);
}
}
}
}
/**
* Handles the mouse down on gauge.
*
* @param {PointerEvent} e - Specifies the pointer event
* @returns {boolean} - Returns the boolean value
* @private
*/
public gaugeOnMouseDown(e: PointerEvent): boolean {
this.setMouseXY(e);
let currentPointer: IVisiblePointer;
let currentRange: IVisibleRange;
const args: IMouseEventArgs = this.getMouseArgs(e, 'touchstart', gaugeMouseDown);
let pointerDrag: boolean = false;
const pointerIndex: number = args.target.id.indexOf('_Pointer_') > -1 ? parseInt(args.target.id.slice(-1), 10) : null;
const axisIndex: number = args.target.id.indexOf('_Axis_') > -1 ? parseInt(args.target.id.split('_Axis_')[1], 10) : null;
if (!isNullOrUndefined(axisIndex) && !isNullOrUndefined(pointerIndex)) {
pointerDrag = this.allowPointerDrag ? this.axes[axisIndex as number].pointers[pointerIndex as number].enableDrag
: this.enablePointerDrag;
}
this.trigger('gaugeMouseDown', args, (observedArgs: IMouseEventArgs) => {
if (!args.cancel &&
args.target.id.indexOf(this.element.id + '_Axis_') >= 0 &&
args.target.id.indexOf('_Pointer_') >= 0 ) {
currentPointer = getPointer(args.target.id, this);
this.activeAxis = <Axis>this.axes[currentPointer.axisIndex];
this.activePointer = <Pointer>this.activeAxis.pointers[currentPointer.pointerIndex];
if (isNullOrUndefined(this.activePointer.pathElement)) {
this.activePointer.pathElement = [e.target as Element];
}
if (this.activePointer.type === 'Marker' && this.activePointer.markerShape === 'Text' && this.activePointer.pathElement.length === 0) {
this.activePointer.pathElement = [e.target as Element];
}
const pointInd: number = parseInt(this.activePointer.pathElement[0].id.slice(-1), 10);
const axisInd: number = parseInt(this.activePointer.pathElement[0].id.split('_Axis_')[1], 10);
this.trigger(dragStart, {
axis: this.activeAxis,
name: dragStart,
type: pointerStart,
pointer: this.activePointer,
currentValue: this.activePointer.currentValue,
pointerIndex: pointInd,
axisIndex: axisInd
} as IPointerDragEventArgs);
if (pointerDrag) {
this.svgObject.setAttribute('cursor', 'grabbing');
}
}
else if (!args.cancel &&
args.target.id.indexOf(this.element.id + '_Axis_') >= 0 &&
args.target.id.indexOf('_Range_') >= 0
)
{
currentRange = getRange(args.target.id, this);
this.activeAxis = <Axis>this.axes[currentRange.axisIndex];
this.activeRange = <Range>this.activeAxis.ranges[currentRange.rangeIndex];
if (isNullOrUndefined(this.activeRange.pathElement )) {
this.activeRange.pathElement = [e.target as Element];
}
const rangeInd: number = parseInt(this.activeRange.pathElement[0].id.split('Range_')[1], 10);
const axisInd: number = parseInt(this.activeRange.pathElement[0].id.split('_Axis_')[1], 10);
this.trigger(dragStart, {
axis: this.activeAxis,
name: dragStart,
type: rangeStart,
range: this.activeRange,
axisIndex: axisInd,
rangeIndex: rangeInd
} as IPointerDragEventArgs);
if (this.enableRangeDrag) {
this.svgObject.setAttribute('cursor', 'grabbing');
}
}
});
return false;
}
/**
* Handles the mouse end.
*
* @param {PointerEvent} e - Specifies the pointer event
* @returns {boolean} - Returns the boolean value
* @private
*/
public mouseEnd(e: PointerEvent): boolean {
this.setMouseXY(e);
const args: IMouseEventArgs = this.getMouseArgs(e, 'touchend', gaugeMouseUp);
this.isTouch = e.pointerType === 'touch' || e.pointerType === '2' || e.type === 'touchend';
this.trigger(gaugeMouseUp, args);
let pointerDrag: boolean = false;
if (this.activeAxis && this.activePointer) {
const pointerIndex: number = parseInt(this.activePointer.pathElement[0].id.slice(-1), 10);
const axisIndex: number = parseInt(this.activePointer.pathElement[0].id.split('_Axis_')[1], 10);
if (!isNullOrUndefined(axisIndex) && !isNullOrUndefined(pointerIndex)) {
pointerDrag = this.allowPointerDrag ? this.axes[axisIndex as number].pointers[pointerIndex as number].enableDrag
: this.enablePointerDrag;
}
if (pointerDrag) {
this.svgObject.setAttribute('cursor', 'auto');
this.trigger(dragEnd, {
name: dragEnd,
type: pointerEnd,
axis: this.activeAxis,
pointer: this.activePointer,
currentValue: this.activePointer.currentValue,
axisIndex: axisIndex,
pointerIndex: pointerIndex
} as IPointerDragEventArgs);
this.activeAxis = null;
this.activePointer = null;
this.isPointerDragged = false;
}
}
else if (this.activeAxis && this.activeRange && this.enableRangeDrag) {
this.svgObject.setAttribute('cursor', 'auto');
const rangeInd: number = parseInt(this.activeRange.pathElement[0].id.slice(-1), 10);
const axisInd: number = parseInt(this.activeRange.pathElement[0].id.split('_Axis_')[1], 10);
this.trigger(dragEnd, {
name: dragEnd,
type: rangeEnd,
axis: this.activeAxis,
range: this.activeRange,
axisIndex: axisInd,
rangeIndex: rangeInd
} as IPointerDragEventArgs);
this.activeAxis = null;
this.activeRange = null;
}
if (!isNullOrUndefined(this.activePointer)) {
this.activePointer = null;
}
this.isDrag = false;
this.svgObject.setAttribute('cursor', 'auto');
this.notify(Browser.touchEndEvent, e);
if (e.type.indexOf('touch') > -1 && (args.target.id === (this.element.id + '_CircularGaugeTitle') || args.target.id.indexOf('_gauge_legend_') > -1)) {
const touchArg: TouchEvent = <TouchEvent & PointerEvent>e;
titleTooltip(e, touchArg.changedTouches[0].pageX, touchArg.changedTouches[0].pageY, this, true);
}
return false;
}
/**
* Handles the mouse event arguments.
*
* @param {PointerEvent} e - Specifies the pointer event
* @param {string} type - Specifies the type
* @param {string} name - Specifies the name
* @returns {IMouseEventArgs} - Returns the mouse event args
* @private
*/
private getMouseArgs(e: PointerEvent, type: string, name: string): IMouseEventArgs {
const rect: ClientRect = this.element.getBoundingClientRect();
const location: GaugeLocation = new GaugeLocation(-rect.left, -rect.top);
const isTouch: boolean = (e.type === type);
location.x += isTouch ? (<TouchEvent & PointerEvent>e).changedTouches[0].clientX : e.clientX;
location.y += isTouch ? (<TouchEvent & PointerEvent>e).changedTouches[0].clientY : e.clientY;
return {
cancel: false, name: name,
x: location.x, y: location.y,
target: isTouch ? <Element>(<TouchEvent & PointerEvent>e).target : <Element>e.target
};
}
/**
* Handles the gauge resize.
*
* @param {Event} e - Specifies the event
* @returns {boolean} - Returns the boolean value
* @private
*/
public gaugeResize(e: Event): boolean {
if (!this.isDestroyed) {
// eslint-disable-next-line prefer-const
let args: IResizeEventArgs = {
gauge: this,
previousSize: this.availableSize,
name: resized,
cancel: false,
currentSize: this.calculateSvgSize()
};
this.trigger(resized, args);
if (!args.cancel) {
if (this.resizeTo) {
clearTimeout(this.resizeTo);
}
if (!isNullOrUndefined(this.element) && this.element.classList.contains('e-circulargauge')) {
this.animatePointer = false;
this.resizeTo = window.setTimeout(
(): void => {
this.isResize = true;
this.isPropertyChange = true;
this.createSvg();
this.calculateBounds();
this.allowLoadingAnimation = false;
if (this.isOverAllAnimationComplete) {
this.loadingAnimationDuration = [];
}
this.renderElements();
this.isResize = false;
},
500);
}
}
}
return false;
}
/**
* Applying styles for circular gauge elements
*
* @param {HTMLElement} element - Specifies the html element
* @returns {void}
*/
private setGaugeStyle(element: HTMLElement): void {
element.style.touchAction = 'element';
element.style.msTouchAction = 'element';
element.style.msContentZooming = 'none';
element.style.msUserSelect = 'none';
element.style.webkitUserSelect = 'none';
element.style.position = 'relative';
}
/**
* Method to set culture for gauge
*
* @returns {void}
*/
private setCulture(): void {
this.intl = new Internationalization();
}
/**
* Methods to create svg element for circular gauge.
*
* @returns {void}
*/
private createSvg(): void {
this.removeSvg();
if (isNullOrUndefined(this.renderer)) {
this.renderer = new SvgRenderer(this.element.id);
}
if (isNullOrUndefined(this.gaugeAxisLayoutPanel)) {
this.gaugeAxisLayoutPanel = new AxisLayoutPanel(this);
}
this.availableSize = this.calculateSvgSize();
this.svgObject = this.renderer.createSvg({
id: this.element.id + '_svg',
width: this.availableSize.width,
height: this.availableSize.height
});
}
/**
* To Remove the SVG from circular gauge.
*
* @returns {void}
* @private
*/
public removeSvg(): void {
if (!isNullOrUndefined(this.element)) {
removeElement(this.element.id + '_Secondary_Element');
if (this.svgObject) {
while (this.svgObject.childNodes.length > 0) {
while (this.svgObject.childNodes.length > 0) {
this.svgObject.removeChild(this.svgObject.firstChild);
}
if (!this.svgObject.hasChildNodes() && this.svgObject.parentNode) {
remove(this.svgObject);
}
}
if (!this.svgObject.hasChildNodes() && this.svgObject.parentNode) {
remove(this.svgObject);
}
}
removeElement(this.element.id + '_svg');
this.clearTemplate();
}
}
/**
* To initialize the circular gauge private variable.
*
* @returns {void}
* @private
*/
private initPrivateVariable(): void {
if (this.element.id === '') {
const collection: number = document.getElementsByClassName('e-circulargauge').length;
this.element.id = 'circulargauge_control_' + collection;
}
this.renderer = new SvgRenderer(this.element.id);
this.gaugeAxisLayoutPanel = new AxisLayoutPanel(this);
this.animatePointer = true;
}
/**
* To calculate the size of the circular gauge element.
*
* @returns {void}
*/
private calculateSvgSize(): Size {
const containerWidth: number = this.element.offsetWidth;
const containerHeight: number = this.element.offsetHeight;
const borderWidth: number = parseInt(this.element.style.borderWidth.split('px').join(''), 10) * 2;
let width: number = stringToNumber(this.width, containerWidth) || containerWidth || 600;
let height: number = stringToNumber(this.height, containerHeight) || containerHeight || 450;
width = !isNaN(borderWidth) ? (width - borderWidth) : width;
height = !isNaN(borderWidth) ? (height - borderWidth) : height;
return new Size(width, height);
}
/**
* To calculate the spacing of the circular gauge element.
*
* @param {number} top - Specifies the top value
* @param {number} left - Specifies the left value
* @param {number} width - Specifies the width
* @param {number} height - Specifies the height
* @param {number} radius - Specifies the radius
* @param {number} titleHeight - Specifies the titleHeight
* @param {number} isUpperAngle - Specifies the isUpperAngle
* @param {number} isLowerAngle - Specifies the isLowerAngle
* @param {number} isFullPercent - Specifies the boolean value
* @param {number} isUpper - Specifies the boolean value
* @param {number} isLower - Specifies the boolean value
* @returns {void}
*/
/* eslint-disable max-len */
private radiusAndCenterCalculation(top: number, left: number, width: number, height: number,
radius: number, titleHeight: number, isUpperAngle: boolean, isLowerAngle: boolean,
isFullPercent: boolean, radiusPercent: number, isUpper: boolean, isLower: boolean): void {
let rect: Rect; const bottom : number = this.margin.bottom + this.border.width; let minRadius : number;
let widthRadius : number; let centerX: number; let centerY: number;
if (this.moveToCenter && this.axes.length === 1 &&
isNullOrUndefined(this.centerXpoint) && isNullOrUndefined(this.centerYpoint)) {
rect = new Rect(left, top, width, height);
} else {
if (!this.allowMargin) {
if (!isNullOrUndefined(this.legendModule) && (width > height) && (this.legendSettings.position === 'Top' || this.legendSettings.position === 'Bottom')) {
minRadius = Math.min(width, height) / 2;
rect = new Rect(
(left + (width / 2) - minRadius), (top + (height / 2) - minRadius),
minRadius * 2, minRadius * 2
);
} else {
if (width > height && (isLowerAngle && isLower || isUpperAngle && isUpper)) {
widthRadius = ((width) / 2);
const heightValue : number = isUpper && isLower ? (height / 2) : (height * (3 / 4));
if (widthRadius > heightValue) {
widthRadius = heightValue;
}
rect = new Rect(
(left + (width / 2) - widthRadius), (top + (height / 2) - widthRadius),
widthRadius * 2, widthRadius * 2
);
} else {
if (height > width) {
const heightRadius : number = height / 2;
rect = new Rect((left + (width / 2) - radius), (top + (height / 2) - heightRadius), radius * 2, heightRadius * 2);
} else {
rect = new Rect(
(left + (width / 2) - radius), (top + (height / 2) - radius),
radius * 2, radius * 2
);
}
}
}
} else {
rect = new Rect(
(left + (width / 2) - radius), (top + (height / 2) - radius),
radius * 2, radius * 2
);
}
}
this.gaugeRect = rect;
if (this.legendModule && this.legendSettings.visible) {
this.legendModule.getLegendOptions(this.axes as Axis[]);
this.legendModule.calculateLegendBounds(this.gaugeRect, this.availableSize);
}
if (!this.allowMargin) {
if (!isNullOrUndefined(this.legendModule) && (isUpperAngle || isLowerAngle) && (width > height) && (this.legendSettings.position === 'Top' || this.legendSettings.position === 'Bottom')) {
const difference : number = height - this.gaugeRect.height;
this.gaugeRect.width = width - ((this.availableSize.width - this.gaugeRect.width) / 2);
this.gaugeRect.y = this.gaugeRect.y - difference;
this.gaugeRect.height = this.gaugeRect.height + difference + ((this.availableSize.height - this.gaugeRect.height) / 2);
}
else if (!isNullOrUndefined(this.legendModule) && (isUpperAngle || isLowerAngle) && (width > height) && (this.legendSettings.position === 'Left' || this.legendSettings.position === 'Right')) {
const difference : number = this.gaugeRect.height - this.gaugeRect.width;
this.gaugeRect.x = this.legendSettings.position === 'Right'
? this.gaugeRect.x + this.margin.right : this.gaugeRect.x;
this.gaugeRect.width = this.legendSettings.position === 'Left' ?
Math.abs(width - ((this.availableSize.width - this.gaugeRect.width + difference) / 2))
: Math.abs(width - ((this.availableSize.width - this.gaugeRect.width) / 2) - 10);
}
centerX = this.centerXpoint !== null ?
stringToNumber(this.centerXpoint, this.availableSize.width) : this.gaugeRect.x + (this.gaugeRect.width / 2);
if ((isUpperAngle || isLowerAngle) && !isNullOrUndefined(this.legendModule)) {
centerX = (this.legendSettings.position === 'Top' || this.legendSettings.position === 'Bottom')
? this.availableSize.width / 2 : this.legendSettings.position === 'Right' ? (this.gaugeRect.width / 2) + this.margin.right :
centerX;
}
centerY = ((isUpperAngle || isLowerAngle) ? (isUpperAngle ?
(( (this.gaugeRect.height * (3 / 4) + this.gaugeRect.y) - bottom))
: (((this.gaugeRect.height * (1 / 4)) + (this.gaugeRect.y)) )) : this.gaugeRect.y + (this.gaugeRect.height / 2));
centerY = !isFullPercent && (isUpperAngle || isLowerAngle) ? (this.gaugeRect.height / 2) + this.gaugeRect.y + (radiusPercent * (3 / 4) * (1 / 2)) : centerY;
if (!isNullOrUndefined(this.axes) && this.axes.length > 1 && !isNullOrUndefined(this.midPoint)) {
isUpper = isUpperAngle ? isUpperAngle : isUpper;
isLower = isLowerAngle ? isLowerAngle : isLower;
if (isUpper && isLower) {
centerY = (this.availableSize.height / 2) - bottom;
}
}
} else {
centerX = this.centerXpoint !== null ?
stringToNumber(this.centerXpoint, this.availableSize.width) : this.gaugeRect.x + (this.gaugeRect.width / 2);
centerY = this.centerYpoint !== null ?
stringToNumber(this.centerYpoint, this.availableSize.height) : this.gaugeRect.y + (this.gaugeRect.height / 2);
}
this.midPoint = new GaugeLocation(centerX, centerY);
}
/**
* Method to calculate the availble size for circular gauge.
*
* @returns {void}
*/
private calculateBounds(): void {
const padding: number = 5; let rect: Rect;
const margin: MarginModel = this.margin;
let titleHeight: number = 0;
if (this.title) {
titleHeight = measureText(this.title, this.titleStyle).height + padding;
}
const top: number = margin.top + titleHeight + this.border.width;
const left: number = margin.left + this.border.width; let isUpper : boolean = false ; let isLower : boolean = false;
const width: number = this.availableSize.width - left - margin.right - this.border.width;
const height: number = this.availableSize.height - top - this.border.width - margin.bottom;
const radius: number = Math.min(width, height) / 2;
this.centerXpoint = (this.centerX === '') ? null : this.centerX;
this.centerYpoint = (this.centerY === '') ? null : this.centerY;
if (this.moveToCenter && this.axes.length === 1 &&
isNullOrUndefined(this.centerXpoint) && isNullOrUndefined(this.centerYpoint)) {
rect = new Rect(left, top, width, height);
}
if (!this.allowMargin) {
for (let j: number = 0; j < this.axes.length; j++) {
const isUpperAngle : boolean = 270 <= this.axes[j as number].startAngle && this.axes[j as number].startAngle <= 360 &&
0 <= this.axes[j as number].endAngle && this.axes[j as number].endAngle <= 90;
const isLowerAngle : boolean = 90 >= this.axes[j as number].startAngle && this.axes[j as number].startAngle <= 180 &&
180 <= this.axes[j as number].endAngle && 270 <= this.axes[j as number].endAngle && 0 !== this.axes[j as number].startAngle &&
360 !== this.axes[j as number].endAngle;
isUpper = isUpperAngle ? isUpperAngle : isUpper;
isLower = isLowerAngle ? isLowerAngle : isLower;
const isFullPercent : boolean = this.axes[j as number].radius !== null ? parseInt(this.axes[0].radius.split('%')[0], 10) >= 100 : true;
const radiusPercent : number = this.axes[j