ngx-circular-slider
Version:
check out the repo on stackblitz: [demo](https://stackblitz.com/github/craftworksgmbh/ngx-circular-slider)
467 lines (458 loc) • 19.1 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core'), require('rxjs/Observable'), require('rxjs/add/observable/fromEvent'), require('rxjs/add/observable/merge'), require('rxjs/add/operator/switchMapTo'), require('rxjs/add/operator/takeUntil'), require('rxjs/add/operator/throttleTime'), require('rxjs/add/operator/do'), require('d3-interpolate'), require('@angular/common')) :
typeof define === 'function' && define.amd ? define(['exports', '@angular/core', 'rxjs/Observable', 'rxjs/add/observable/fromEvent', 'rxjs/add/observable/merge', 'rxjs/add/operator/switchMapTo', 'rxjs/add/operator/takeUntil', 'rxjs/add/operator/throttleTime', 'rxjs/add/operator/do', 'd3-interpolate', '@angular/common'], factory) :
(factory((global['ngx-cs-slider'] = {}),global['@angular/core'],global.Observable,null,null,null,null,null,null,global.d3Interpolate,global['@angular/common']));
}(this, (function (exports,core,Observable,fromEvent,merge,switchMapTo,takeUntil,throttleTime,_do,d3Interpolate,common) { 'use strict';
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
var THROTTLE_DEFAULT = 50;
var DEFAULT_PROPS = {
segments: 6,
strokeWidth: 40,
radius: 145,
gradientColorFrom: '#ff9800',
gradientColorTo: '#ffcf00',
bgCircleColor: '#171717',
showClockFace: true,
clockFaceColor: '#9d9d9d'
};
var NgxCircularSliderComponent = /** @class */ (function () {
function NgxCircularSliderComponent() {
this.update = new core.EventEmitter();
this.props = DEFAULT_PROPS;
this.startAngle = 0;
this.angleLength = 0;
}
/**
* @param {?} evt
* @return {?}
*/
NgxCircularSliderComponent.extractMouseEventCoords = /**
* @param {?} evt
* @return {?}
*/
function (evt) {
var /** @type {?} */ coords = (evt instanceof MouseEvent ? {
x: evt.clientX,
y: evt.clientY
} : { x: evt.changedTouches.item(0).clientX, y: evt.changedTouches.item(0).clientY });
return coords;
};
/**
* @return {?}
*/
NgxCircularSliderComponent.prototype.ngOnInit = /**
* @return {?}
*/
function () {
this.setCircleCenter();
this.onUpdate();
this.setObservables();
};
/**
* @param {?} changes
* @return {?}
*/
NgxCircularSliderComponent.prototype.ngOnChanges = /**
* @param {?} changes
* @return {?}
*/
function (changes) {
if (changes.props) {
this.props = (changes.props.firstChange ? Object.assign(DEFAULT_PROPS, changes.props.currentValue) : DEFAULT_PROPS);
}
this.onUpdate();
};
/**
* @return {?}
*/
NgxCircularSliderComponent.prototype.ngOnDestroy = /**
* @return {?}
*/
function () {
this.closeStreams();
};
/**
* @return {?}
*/
NgxCircularSliderComponent.prototype.onUpdate = /**
* @return {?}
*/
function () {
this.calcStartAndStop();
this.createSegments();
this.update.emit({ startAngle: this.startAngle, angleLength: this.angleLength });
};
/**
* @return {?}
*/
NgxCircularSliderComponent.prototype.setObservables = /**
* @return {?}
*/
function () {
var _this = this;
var /** @type {?} */ mouseMove$ = Observable.Observable.merge(Observable.Observable.fromEvent(document, 'mousemove'), Observable.Observable.fromEvent(document, 'touchmove'));
var /** @type {?} */ mouseUp$ = Observable.Observable.merge(Observable.Observable.fromEvent(document, 'mouseup'), Observable.Observable.fromEvent(document, 'touchend'));
this.startSubscription = Observable.Observable.merge(Observable.Observable.fromEvent(this.startIcon.nativeElement, 'touchstart'), Observable.Observable.fromEvent(this.startIcon.nativeElement, 'mousedown')).switchMapTo(mouseMove$
.takeUntil(mouseUp$))
.throttleTime(THROTTLE_DEFAULT)
.subscribe(function (res) {
_this.handleStartPan(res);
});
this.stopSubscription = Observable.Observable.merge(Observable.Observable.fromEvent(this.stopIcon.nativeElement, 'touchstart'), Observable.Observable.fromEvent(this.stopIcon.nativeElement, 'mousedown')).switchMapTo(mouseMove$
.takeUntil(mouseUp$))
.throttleTime(THROTTLE_DEFAULT)
.subscribe(function (res) {
_this.handleStopPan(res);
});
};
/**
* @return {?}
*/
NgxCircularSliderComponent.prototype.closeStreams = /**
* @return {?}
*/
function () {
if (this.startSubscription) {
this.startSubscription.unsubscribe();
this.startSubscription = null;
}
if (this.stopSubscription) {
this.stopSubscription.unsubscribe();
this.stopSubscription = null;
}
};
/**
* @param {?} evt
* @return {?}
*/
NgxCircularSliderComponent.prototype.handleStartPan = /**
* @param {?} evt
* @return {?}
*/
function (evt) {
var /** @type {?} */ coords = NgxCircularSliderComponent.extractMouseEventCoords(evt);
this.setCircleCenter();
var /** @type {?} */ currentAngleStop = (this.startAngle + this.angleLength) % (2 * Math.PI);
var /** @type {?} */ newAngle = Math.atan2(coords.y - this.circleCenterY, coords.x - this.circleCenterX) + Math.PI / 2;
if (newAngle < 0) {
newAngle += 2 * Math.PI;
}
var /** @type {?} */ newAngleLength = currentAngleStop - newAngle;
if (newAngleLength < 0) {
newAngleLength += 2 * Math.PI;
}
this.startAngle = newAngle;
this.angleLength = newAngleLength % (2 * Math.PI);
this.onUpdate();
};
/**
* @param {?} evt
* @return {?}
*/
NgxCircularSliderComponent.prototype.handleStopPan = /**
* @param {?} evt
* @return {?}
*/
function (evt) {
var /** @type {?} */ coords = NgxCircularSliderComponent.extractMouseEventCoords(evt);
this.setCircleCenter();
var /** @type {?} */ newAngle = Math.atan2(coords.y - this.circleCenterY, coords.x - this.circleCenterX) + Math.PI / 2;
var /** @type {?} */ newAngleLength = (newAngle - this.startAngle) % (2 * Math.PI);
if (newAngleLength < 0) {
newAngleLength += 2 * Math.PI;
}
this.angleLength = newAngleLength;
this.onUpdate();
};
/**
* @return {?}
*/
NgxCircularSliderComponent.prototype.calcStartAndStop = /**
* @return {?}
*/
function () {
this.start = this.calculateArcCircle(0, this.props.segments, this.props.radius, this.startAngle, this.angleLength);
this.stop = this.calculateArcCircle(this.props.segments - 1, this.props.segments, this.props.radius, this.startAngle, this.angleLength);
};
/**
* @param {?} index
* @param {?} segments
* @param {?} gradientColorFrom
* @param {?} gradientColorTo
* @return {?}
*/
NgxCircularSliderComponent.prototype.calculateArcColor = /**
* @param {?} index
* @param {?} segments
* @param {?} gradientColorFrom
* @param {?} gradientColorTo
* @return {?}
*/
function (index, segments, gradientColorFrom, gradientColorTo) {
var /** @type {?} */ interpolate = d3Interpolate.interpolateHcl(gradientColorFrom, gradientColorTo);
return {
fromColor: interpolate(index / segments),
toColor: interpolate((index + 1) / segments),
};
};
/**
* @param {?} indexInput
* @param {?} segments
* @param {?} radius
* @param {?=} startAngleInput
* @param {?=} angleLengthInput
* @return {?}
*/
NgxCircularSliderComponent.prototype.calculateArcCircle = /**
* @param {?} indexInput
* @param {?} segments
* @param {?} radius
* @param {?=} startAngleInput
* @param {?=} angleLengthInput
* @return {?}
*/
function (indexInput, segments, radius, startAngleInput, angleLengthInput) {
if (startAngleInput === void 0) { startAngleInput = 0; }
if (angleLengthInput === void 0) { angleLengthInput = 2 * Math.PI; }
// Add 0.0001 to the possible angle so when start = stop angle, whole circle is drawn
var /** @type {?} */ startAngle = startAngleInput % (2 * Math.PI);
var /** @type {?} */ angleLength = angleLengthInput % (2 * Math.PI);
var /** @type {?} */ index = indexInput + 1;
var /** @type {?} */ fromAngle = angleLength / segments * (index - 1) + startAngle;
var /** @type {?} */ toAngle = angleLength / segments * index + startAngle;
var /** @type {?} */ fromX = radius * Math.sin(fromAngle);
var /** @type {?} */ fromY = -radius * Math.cos(fromAngle);
var /** @type {?} */ realToX = radius * Math.sin(toAngle);
var /** @type {?} */ realToY = -radius * Math.cos(toAngle);
// add 0.005 to start drawing a little bit earlier so segments stick together
var /** @type {?} */ toX = radius * Math.sin(toAngle + 0.005);
var /** @type {?} */ toY = -radius * Math.cos(toAngle + 0.005);
return {
fromX: fromX,
fromY: fromY,
toX: toX,
toY: toY,
realToX: realToX,
realToY: realToY
};
};
/**
* @return {?}
*/
NgxCircularSliderComponent.prototype.createSegments = /**
* @return {?}
*/
function () {
this.segments = [];
for (var /** @type {?} */ i = 0; i < this.props.segments; i++) {
var /** @type {?} */ id = i;
var /** @type {?} */ colors = this.calculateArcColor(id, this.props.segments, this.props.gradientColorFrom, this.props.gradientColorTo);
var /** @type {?} */ arcs = this.calculateArcCircle(id, this.props.segments, this.props.radius, this.startAngle, this.angleLength);
this.segments.push({
id: id,
d: "M " + arcs.fromX.toFixed(2) + " " + arcs.fromY.toFixed(2) + " A " + this.props.radius + " " + this.props.radius + " \n 0 0 1 " + arcs.toX.toFixed(2) + " " + arcs.toY.toFixed(2),
colors: Object.assign({}, colors),
arcs: Object.assign({}, arcs)
});
}
};
/**
* @return {?}
*/
NgxCircularSliderComponent.prototype.setCircleCenter = /**
* @return {?}
*/
function () {
// todo: nicer solution to use document.body?
var /** @type {?} */ bodyRect = document.body.getBoundingClientRect();
var /** @type {?} */ elemRect = this.circle.nativeElement.getBoundingClientRect();
var /** @type {?} */ px = elemRect.left - bodyRect.left;
var /** @type {?} */ py = elemRect.top - bodyRect.top;
var /** @type {?} */ halfOfContainer = this.getContainerWidth() / 2;
this.circleCenterX = px + halfOfContainer;
this.circleCenterY = py + halfOfContainer;
};
/**
* @return {?}
*/
NgxCircularSliderComponent.prototype.getContainerWidth = /**
* @return {?}
*/
function () {
var _a = this.props, strokeWidth = _a.strokeWidth, radius = _a.radius;
return strokeWidth + radius * 2 + 2;
};
/**
* @param {?} index
* @return {?}
*/
NgxCircularSliderComponent.prototype.getGradientId = /**
* @param {?} index
* @return {?}
*/
function (index) {
return "gradient" + index;
};
/**
* @param {?} index
* @return {?}
*/
NgxCircularSliderComponent.prototype.getGradientUrl = /**
* @param {?} index
* @return {?}
*/
function (index) {
return "url(#gradient" + index + ")";
};
/**
* @return {?}
*/
NgxCircularSliderComponent.prototype.getTranslate = /**
* @return {?}
*/
function () {
return " translate(\n " + (this.props.strokeWidth / 2 + this.props.radius + 1) + ",\n " + (this.props.strokeWidth / 2 + this.props.radius + 1) + " )";
};
/**
* @param {?} x
* @param {?} y
* @return {?}
*/
NgxCircularSliderComponent.prototype.getTranslateFrom = /**
* @param {?} x
* @param {?} y
* @return {?}
*/
function (x, y) {
return " translate(" + x + ", " + y + ")";
};
NgxCircularSliderComponent.decorators = [
{ type: core.Component, args: [{
selector: 'ngx-cs-slider',
template: "<svg #circle [attr.height]=\"getContainerWidth()\" [attr.width]=\"getContainerWidth()\"> <defs> <linearGradient *ngFor=\"let segment of segments\" [attr.key]=\"segment.id\" [attr.id]=\"getGradientId(segment.id)\" [attr.x1]=\"segment.arcs.fromX.toFixed(2)\" [attr.y1]=\"segment.arcs.fromY.toFixed(2)\" [attr.x2]=\"segment.arcs.toX.toFixed(2)\" [attr.y2]=\"segment.arcs.toY.toFixed(2)\"> <stop offset=\"0%\" [attr.stop-color]=\"segment.colors.fromColor\"/> <stop offset=\"1\" [attr.stop-color]=\"segment.colors.toColor\"/> </linearGradient> </defs> <!-- circle-donut --> <g [attr.transform]=\"getTranslate()\"> <circle [attr.r]=\"props.radius\" [attr.stroke-width]=\"props.strokeWidth\" [attr.stroke]=\"props.bgCircleColor\" fill=\"transparent\"/> <g ngx-cs-clock-face *ngIf=\"props.showClockFace\" [radius]=\"(props.radius-props.strokeWidth/2)\" [stroke]=\"props.clockFaceColor\"> </g> <path *ngFor=\"let segment of segments\" [attr.d]=\"segment.d\" [attr.key]=\"segment.id\" [attr.stroke-width]=\"this.props.strokeWidth\" [attr.stroke]=\"getGradientUrl(segment.id)\" fill=\"transparent\"/> <!-- start icon --> <g #startIcon [attr.fill]=\"this.props.gradientColorFrom\" [attr.transform]=\"getTranslateFrom(start.fromX, start.fromY)\"> <circle [attr.r]=\"((this.props.strokeWidth - 1) / 2)\" [attr.fill]=\"this.props.bgCircleColor\" [attr.stroke]=\"this.props.gradientColorFrom\" stroke-width=\"1\"/> <ng-content select=\".acs-start-icon\"></ng-content> </g> <!-- stop icon --> <g #stopIcon [attr.fill]=\"props.gradientColorTo\" [attr.transform]=\"getTranslateFrom(stop.toX, stop.toY)\"> <circle [attr.r]=\"((this.props.strokeWidth - 1) / 2)\" [attr.fill]=\"this.props.bgCircleColor\" [attr.stroke]=\"this.props.gradientColorTo\" stroke-width=\"1\"/> <ng-content select=\".acs-stop-icon\"></ng-content> </g> </g> </svg> ",
styles: [":host { display: inline-block; } "]
},] },
];
/** @nocollapse */
NgxCircularSliderComponent.ctorParameters = function () { return []; };
NgxCircularSliderComponent.propDecorators = {
"props": [{ type: core.Input },],
"startAngle": [{ type: core.Input },],
"angleLength": [{ type: core.Input },],
"update": [{ type: core.Output },],
"circle": [{ type: core.ViewChild, args: ['circle',] },],
"stopIcon": [{ type: core.ViewChild, args: ['stopIcon',] },],
"startIcon": [{ type: core.ViewChild, args: ['startIcon',] },],
};
return NgxCircularSliderComponent;
}());
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
var DEFAULT_RANGE = 48;
var DEFAULT_TIME_RANGE = 12;
/**
* @record
*/
var NgXCSClockFaceComponent = /** @class */ (function () {
function NgXCSClockFaceComponent() {
this.clockLines = [];
this.clockTexts = [];
}
/**
* @return {?}
*/
NgXCSClockFaceComponent.prototype.ngOnChanges = /**
* @return {?}
*/
function () {
this.faceRadius = this.radius - 5;
this.textRadius = this.radius - 26;
this.createClockLines();
this.createClockTexts();
};
/**
* @return {?}
*/
NgXCSClockFaceComponent.prototype.createClockLines = /**
* @return {?}
*/
function () {
for (var /** @type {?} */ i = 0; i < DEFAULT_RANGE; i++) {
var /** @type {?} */ cos = Math.cos(2 * Math.PI / DEFAULT_RANGE * i);
var /** @type {?} */ sin = Math.sin(2 * Math.PI / DEFAULT_RANGE * i);
this.clockLines.push({
id: i,
strokeWidth: (i % 4 === 0 ? 3 : 1),
x1: cos * this.faceRadius,
y1: sin * this.faceRadius,
x2: cos * (this.faceRadius - 7),
y2: sin * (this.faceRadius - 7)
});
}
};
/**
* @return {?}
*/
NgXCSClockFaceComponent.prototype.createClockTexts = /**
* @return {?}
*/
function () {
for (var /** @type {?} */ i = 0; i < DEFAULT_TIME_RANGE; i++) {
this.clockTexts.push({
id: i,
x: this.textRadius * Math.cos(2 * Math.PI / 12 * i - Math.PI / 2 + Math.PI / 6),
y: this.textRadius * Math.sin(2 * Math.PI / 12 * i - Math.PI / 2 + Math.PI / 6)
});
}
};
NgXCSClockFaceComponent.decorators = [
{ type: core.Component, args: [{
selector: '[ngx-cs-clock-face]',
template: "<svg:g> <line *ngFor=\"let clockLine of clockLines\" [attr.key]=\"clockLine.id\" [attr.stroke]=\"stroke\" [attr.stroke-width]=\"clockLine.strokeWidth\" [attr.x1]=\"clockLine.x1\" [attr.y1]=\"clockLine.y1\" [attr.x2]=\"clockLine.x2\" [attr.y2]=\"clockLine.y2\"> </line> <svg:g transform=\"translate(0, 5)\"> <text *ngFor=\"let clockText of clockTexts\" [attr.key]=\"clockText.id\" [attr.fill]=\"stroke\" font-size=\"16\" text-anchor=\"middle\" [attr.x]=\"clockText.x\" [attr.y]=\"clockText.y\"> {{clockText.id + 1}} </text> </svg:g> </svg:g> ",
styles: [""]
},] },
];
/** @nocollapse */
NgXCSClockFaceComponent.ctorParameters = function () { return []; };
NgXCSClockFaceComponent.propDecorators = {
"radius": [{ type: core.Input },],
"stroke": [{ type: core.Input },],
};
return NgXCSClockFaceComponent;
}());
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
var NgxCircularSliderModule = /** @class */ (function () {
function NgxCircularSliderModule() {
}
NgxCircularSliderModule.decorators = [
{ type: core.NgModule, args: [{
imports: [
common.CommonModule
],
declarations: [
NgxCircularSliderComponent,
NgXCSClockFaceComponent
],
exports: [
NgxCircularSliderComponent
]
},] },
];
return NgxCircularSliderModule;
}());
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
exports.NgxCircularSliderComponent = NgxCircularSliderComponent;
exports.NgxCircularSliderModule = NgxCircularSliderModule;
Object.defineProperty(exports, '__esModule', { value: true });
})));