2019-11-25 13:34:51 +00:00
|
|
|
import { Component, OnInit,Input,ViewChild,OnChanges,ChangeDetectorRef,Output, EventEmitter,SimpleChanges } from '@angular/core';
|
|
|
|
import { DatePipe } from '@angular/common';
|
|
|
|
import {NgbPopover} from '@ng-bootstrap/ng-bootstrap';
|
|
|
|
|
|
|
|
export interface TimeSpan {
|
|
|
|
startDate:Date;
|
|
|
|
endDate:Date;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
selector: 'fm-timespan',
|
|
|
|
templateUrl: './timespan.component.html',
|
|
|
|
styleUrls: ['./timespan.component.css']
|
|
|
|
})
|
|
|
|
export class TimespanComponent implements OnInit, OnChanges {
|
|
|
|
|
|
|
|
scale:number = 1000 * 60 * 60 ; // milliseconds / pixel ( 1 hour )
|
|
|
|
unitScales:number[] = [1,1000,1000*60,1000*60*60,1000*60*60*24,1000*60*60*24*7,1000*60*60*24*31,1000*60*60*24*31*3,1000*60*60*24*365.25];
|
|
|
|
units:string[] = [ 'millisecond','second','minute','hour','day','week','month','quarter','year'];
|
|
|
|
quarters:string[] = ['KW1','KW2','KW3','KW4'];
|
|
|
|
unitScale:number = 3;
|
|
|
|
viewMinDate:Date;
|
|
|
|
viewMaxDate:Date;
|
|
|
|
extentMinDate:Date;
|
|
|
|
extentMaxDate:Date;
|
|
|
|
cursorDate:Date;
|
|
|
|
leftGripMove:boolean = false;
|
|
|
|
rightGripMove:boolean = false;
|
|
|
|
rangeGripMove:boolean = false;
|
|
|
|
viewPan:boolean = false;
|
|
|
|
downX:number = -1;
|
|
|
|
mouseX: number = -1;
|
|
|
|
mouseY: number = -1;
|
|
|
|
elementWidth:number;
|
|
|
|
elementHeight:number;
|
|
|
|
lastOffsetInPixels:number=0;
|
|
|
|
@ViewChild('timeLine', { static: true }) canvasRef;
|
|
|
|
@ViewChild('popoverStart', { static: true }) public popoverStart:NgbPopover;
|
|
|
|
@ViewChild('popoverEnd', { static: true }) public popoverEnd:NgbPopover;
|
|
|
|
@Input() collapsed: boolean = true;
|
|
|
|
@Input() startDate: Date = new Date(2018,1,3);
|
|
|
|
@Input() endDate: Date = new Date(2018,1,5);
|
|
|
|
@Input() unit:string;
|
|
|
|
@Input() color:string = '#000000';
|
|
|
|
@Input() background:string = '#ffffff';
|
|
|
|
@Input() hoverColor:string ='#ffffff';
|
|
|
|
@Input() hoverBackground:string ='#0000ff';
|
|
|
|
@Input() lineColor:string='#000000';
|
|
|
|
@Input() lineWidth:number=1;
|
|
|
|
@Input() padding:number = 4;
|
|
|
|
@Output() change:EventEmitter<TimeSpan> = new EventEmitter();
|
|
|
|
public caption:string = "2016/2017";
|
|
|
|
public marginLeft:number = 100;
|
|
|
|
public startPopoverLeft:number=110;
|
|
|
|
public endPopoverLeft:number=120;
|
|
|
|
public rangeWidth:number =75;
|
|
|
|
public startCaption={};
|
|
|
|
public endCaption={};
|
|
|
|
private ratio:number=1;
|
|
|
|
private initialized:boolean=false;
|
|
|
|
private ctx:CanvasRenderingContext2D;
|
|
|
|
public posibleUnits:number[] = [];
|
|
|
|
public height:number = 0;
|
|
|
|
public lineHeight:number = 0;
|
|
|
|
|
|
|
|
constructor(private changeDetectorRef: ChangeDetectorRef,private datePipe: DatePipe) { }
|
|
|
|
|
|
|
|
setCanvasSize() {
|
|
|
|
let canvas = this.canvasRef.nativeElement;
|
|
|
|
this.elementWidth = canvas.offsetWidth;
|
|
|
|
this.elementHeight = canvas.offsetHeight;
|
|
|
|
canvas.height = this.elementHeight * this.ratio;
|
|
|
|
canvas.width = this.elementWidth * this.ratio;
|
|
|
|
}
|
|
|
|
|
|
|
|
getPosibleUnits(scale:number):number[] {
|
|
|
|
let posibleUnits = [];
|
|
|
|
for(let u of [3,4,6,8]) {
|
|
|
|
if((this.unitScale <=u) )
|
|
|
|
posibleUnits.push(u);
|
|
|
|
}
|
|
|
|
return posibleUnits;
|
|
|
|
}
|
|
|
|
|
|
|
|
getLineHeight():number {
|
|
|
|
return (parseInt(this.ctx.font.match(/\d+/)[0], 10)/ this.ratio) + (2*this.padding) ;
|
|
|
|
}
|
|
|
|
|
|
|
|
getHeight():number {
|
|
|
|
|
|
|
|
return (this.posibleUnits.length * this.getLineHeight());
|
|
|
|
}
|
|
|
|
|
|
|
|
ngOnInit() {
|
|
|
|
this.ratio = 2;
|
|
|
|
this.unitScale = this.getUnitScale(this.unit);
|
|
|
|
let canvas:HTMLCanvasElement = this.canvasRef.nativeElement;
|
|
|
|
this.ctx = canvas.getContext('2d');
|
|
|
|
this.elementWidth = canvas.offsetWidth;
|
|
|
|
this.elementHeight = canvas.offsetHeight;
|
|
|
|
this.ctx.font=`normal ${this.ratio*10}pt Sans-serif`;
|
|
|
|
this.startDate = new Date(this.startDate.getTime() + this.getUnitDateOffset(this.startDate,this.unitScale,0));
|
|
|
|
this.endDate = new Date(this.endDate.getTime() + this.getUnitDateOffset(this.endDate,this.unitScale,1));
|
|
|
|
this.change.emit({startDate:this.startDate,endDate:this.endDate});
|
|
|
|
let rangeInMilliseconds = this.endDate.getTime() - this.startDate.getTime();
|
|
|
|
this.scale = this.getFitScale(rangeInMilliseconds,this.elementWidth);
|
|
|
|
this.posibleUnits=this.getPosibleUnits(this.scale);
|
|
|
|
this.height=this.getHeight();
|
|
|
|
this.lineHeight= this.getLineHeight();
|
|
|
|
this.setCanvasSize();
|
|
|
|
let center = (this.startDate.getTime()+this.endDate.getTime())/2;
|
|
|
|
this.viewMinDate = new Date(center - (this.elementWidth/2* this.scale));
|
|
|
|
this.viewMaxDate = new Date(center + (this.elementWidth/2* this.scale));
|
|
|
|
this.updateStyle(this.startDate,this.endDate);
|
|
|
|
this.startCaption={popoverCaption:this.getStartCaption(this.startDate,this.unitScale,true)};
|
|
|
|
this.endCaption={popoverCaption:this.getEndCaption(this.endDate,this.unitScale,true)};
|
|
|
|
this.redraw();
|
|
|
|
this.initialized=true;
|
|
|
|
}
|
|
|
|
|
|
|
|
getStartEndCaption(date:Date,otherDate:Date,unitScale:number,suffix:boolean = false,extended:boolean=true):string {
|
|
|
|
let showSuffix = false;
|
|
|
|
otherDate=new Date(otherDate.getTime()-1); // fix year edge case
|
|
|
|
if(unitScale == 3) {
|
|
|
|
let format="HH:00";
|
|
|
|
if(extended) {
|
|
|
|
if(suffix || date.getFullYear() != otherDate.getFullYear())
|
|
|
|
format="d MMM yyyy:HH:00";
|
|
|
|
else if(date.getMonth() !== otherDate.getMonth())
|
|
|
|
format="d MMM HH:00";
|
|
|
|
}
|
|
|
|
return this.datePipe.transform(date,format);
|
|
|
|
|
|
|
|
}
|
|
|
|
if(unitScale == 4) {
|
|
|
|
let format="d";
|
|
|
|
if(extended) {
|
|
|
|
if(suffix || date.getFullYear() != otherDate.getFullYear())
|
|
|
|
format="d MMM yyyy";
|
|
|
|
else if(date.getMonth() !== otherDate.getMonth())
|
|
|
|
format="d MMM"
|
|
|
|
}
|
|
|
|
return this.datePipe.transform(date,format);
|
|
|
|
|
|
|
|
}
|
|
|
|
if(unitScale == 6) {
|
|
|
|
let format = "MMM";
|
|
|
|
if(extended) {
|
|
|
|
if(suffix || date.getFullYear() != otherDate.getFullYear())
|
|
|
|
format="MMM yyyy";
|
|
|
|
}
|
|
|
|
return this.datePipe.transform(date,format);
|
|
|
|
}
|
|
|
|
if(unitScale == 7) {
|
|
|
|
let q = Math.trunc(date.getMonth() /3 );
|
|
|
|
return this.quarters[q];
|
|
|
|
}
|
|
|
|
if(unitScale == 8) {
|
|
|
|
return this.datePipe.transform(date,"yyyy");
|
|
|
|
}
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
getStartCaption(startDate:Date,unitScale:number,suffix:boolean=false,extended:boolean=true):string {
|
|
|
|
return this.getStartEndCaption(new Date(startDate.getTime() + (this.unitScales[unitScale]/2)), this.endDate,unitScale,suffix,extended);
|
|
|
|
}
|
|
|
|
|
|
|
|
getEndCaption(endDate:Date,unitScale:number,suffix:boolean=true):string {
|
|
|
|
return this.getStartEndCaption(new Date(endDate.getTime() - (this.unitScales[unitScale]/2)),this.startDate, unitScale,suffix);
|
|
|
|
}
|
|
|
|
|
|
|
|
getCaption(startDate:Date,endDate:Date,unitScale:number):string {
|
|
|
|
let startCaption=this.getStartCaption(startDate,unitScale);
|
|
|
|
let endCaption=this.getEndCaption(endDate,unitScale);
|
|
|
|
if((endDate.getTime() - startDate.getTime()) < (1.5*this.unitScales[this.unitScale]))
|
|
|
|
return endCaption;
|
|
|
|
return `${startCaption}-${endCaption}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
public updatePopoverText(popover:NgbPopover, text:string): void {
|
|
|
|
const isOpen = popover.isOpen();
|
|
|
|
if (isOpen) {
|
|
|
|
popover.close();
|
|
|
|
popover.open({popoverCaption:text});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
getFitScale(rangeInMilliSeconds:number,elementWidth:number):number {
|
|
|
|
let width = elementWidth*0.33;
|
|
|
|
return rangeInMilliSeconds/width;
|
|
|
|
}
|
|
|
|
|
|
|
|
getUnitScale(unit:string):number {
|
|
|
|
if(!unit) return 3; // hour
|
|
|
|
for(var _i=0;_i<this.units.length;_i++) {
|
|
|
|
if(this.units[_i]==unit.toLowerCase()) return _i;
|
|
|
|
}
|
|
|
|
throw new Error(`Invalid unit : {{unit}} `);
|
|
|
|
}
|
|
|
|
|
|
|
|
getUnitDateOffset(date:Date, unitScale:number,tick:number):number {
|
|
|
|
var offsetDate:Date;
|
|
|
|
if(unitScale==0)
|
|
|
|
offsetDate = new Date(date.getFullYear(),date.getMonth(),date.getDate() ,date.getHours() ,date.getMinutes(),date.getSeconds(),date.getMilliseconds()+ tick);
|
|
|
|
if(unitScale==1)
|
|
|
|
offsetDate = new Date(date.getFullYear(),date.getMonth(),date.getDate() ,date.getHours() ,date.getMinutes(),date.getSeconds() + tick,0);
|
|
|
|
if(unitScale==2)
|
|
|
|
offsetDate = new Date(date.getFullYear(),date.getMonth(),date.getDate() ,date.getHours() ,date.getMinutes() + tick,0,0);
|
|
|
|
if(unitScale==3)
|
|
|
|
offsetDate = new Date(date.getFullYear(),date.getMonth(),date.getDate() ,date.getHours()+ tick ,0,0,0);
|
|
|
|
if(unitScale==4)
|
|
|
|
offsetDate = new Date(date.getFullYear(),date.getMonth(),date.getDate() + tick ,0,0,0,0);
|
|
|
|
if(unitScale==6)
|
|
|
|
offsetDate = new Date(date.getFullYear(),date.getMonth()+tick,1,0,0,0,0);
|
|
|
|
if(unitScale==7) {
|
|
|
|
var month = (tick * 3);
|
|
|
|
offsetDate = new Date(date.getFullYear(),month,1,0,0,0,0);
|
|
|
|
}
|
|
|
|
if(unitScale==8)
|
|
|
|
offsetDate = new Date(date.getFullYear()+tick,0,1,0,0,0,0);
|
|
|
|
return offsetDate.getTime()-date.getTime();
|
|
|
|
}
|
|
|
|
|
|
|
|
getUnitTextWidth(unitScale:number):number {
|
|
|
|
switch(unitScale) {
|
|
|
|
case 3:return this.ctx.measureText("88:88").width/this.ratio;
|
|
|
|
case 4:return this.ctx.measureText("88").width/this.ratio;
|
|
|
|
case 5:return this.ctx.measureText("WWW").width/this.ratio;
|
|
|
|
case 6:return this.ctx.measureText("WW").width/this.ratio;
|
|
|
|
case 7:return this.ctx.measureText("WW").width/this.ratio;
|
|
|
|
case 8:return this.ctx.measureText("8888").width/this.ratio;
|
|
|
|
default: return this.ctx.measureText("WW").width/this.ratio;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
getNextTick(viewStartDate:Date, tick:number,step:number,unitScale:number):number {
|
|
|
|
let unitTextWidth = this.getUnitTextWidth(unitScale);
|
|
|
|
let dateOffset =this.getUnitDateOffset(viewStartDate,unitScale,tick);
|
|
|
|
let date = new Date(viewStartDate.getTime() + dateOffset);
|
|
|
|
let nextTick=tick+step+Math.trunc(step/2);
|
|
|
|
let nextDateOffset =this.getUnitDateOffset(viewStartDate,unitScale,nextTick);
|
|
|
|
let nextDate = new Date(viewStartDate.getTime() + nextDateOffset);
|
|
|
|
let n=1;
|
|
|
|
switch(unitScale) {
|
|
|
|
case 4:n=nextDate.getDate()-1;break;
|
|
|
|
case 6:n=nextDate.getMonth();break;
|
|
|
|
case 8:n=nextDate.getFullYear();break;
|
|
|
|
default: n = 1;break;
|
|
|
|
}
|
|
|
|
let a = Math.trunc(n / step)*step;
|
|
|
|
nextTick=nextTick-n+a;
|
|
|
|
if(nextTick<=tick) return tick+step;
|
|
|
|
return nextTick;
|
|
|
|
}
|
|
|
|
|
|
|
|
getSteps(unitScale:number):number[] {
|
|
|
|
if(unitScale==4)
|
|
|
|
return [1,14];
|
|
|
|
if(unitScale==6)
|
|
|
|
return [1,3,6];
|
|
|
|
return [1,2,3,4,5];
|
|
|
|
}
|
|
|
|
|
|
|
|
drawUnits(yOffset:number,width:number,viewStartDate:Date,unitScale:number):number {
|
|
|
|
let oneUnit = (this.getUnitDateOffset(viewStartDate,unitScale,1)- this.getUnitDateOffset(viewStartDate,unitScale,0)) / this.scale;
|
|
|
|
this.ctx.font=`normal ${this.ratio*10}pt Sans-serif`;
|
|
|
|
let lineHeight = this.getLineHeight();
|
|
|
|
let dateOffset = this.getUnitDateOffset(viewStartDate,unitScale,0);
|
|
|
|
let pixelOffset = (dateOffset / this.scale);
|
|
|
|
let caption = this.getStartCaption(new Date(viewStartDate.getTime()+dateOffset),unitScale,false,false);
|
|
|
|
let unitTextWidth=this.getUnitTextWidth(unitScale);
|
|
|
|
this.ctx.beginPath();
|
|
|
|
this.ctx.strokeStyle=this.lineColor;
|
|
|
|
let steps=this.getSteps(unitScale);
|
|
|
|
let s=0;
|
|
|
|
let step=steps[s];
|
|
|
|
let steppedOneUnit=oneUnit*step;
|
|
|
|
while(unitTextWidth > (steppedOneUnit-(2*this.padding)) && s < steps.length -1) {
|
|
|
|
step=steps[++s];
|
|
|
|
steppedOneUnit=oneUnit*step;
|
|
|
|
}
|
|
|
|
if(steppedOneUnit - (2*this.padding) < unitTextWidth) return yOffset;
|
|
|
|
this.ctx.moveTo(0,yOffset*this.ratio);
|
|
|
|
this.ctx.lineTo(width*this.ratio,yOffset*this.ratio);
|
|
|
|
this.ctx.stroke();
|
|
|
|
var x:number = pixelOffset;
|
|
|
|
var nextDateOffset = this.getUnitDateOffset(viewStartDate,unitScale,1);
|
|
|
|
var nextX:number = (nextDateOffset / this.scale);
|
|
|
|
var n=0;
|
|
|
|
while(x < width) {
|
|
|
|
this.ctx.fillStyle=this.color;
|
|
|
|
//mouseover
|
|
|
|
if(this.mouseX> x && this.mouseX <nextX && this.mouseY > yOffset && this.mouseY <( yOffset + lineHeight) && !this.leftGripMove && !this.rightGripMove && !this.rangeGripMove&& !this.viewPan) {
|
|
|
|
this.ctx.fillStyle=this.hoverBackground;
|
|
|
|
this.ctx.fillRect((x+0.5)*this.ratio,(yOffset+0.5)*this.ratio,(nextX-x)*this.ratio,lineHeight*this.ratio);
|
|
|
|
this.ctx.fillStyle=this.hoverColor;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.ctx.moveTo((x+0.5)*this.ratio,(yOffset+0.5)*this.ratio);
|
|
|
|
this.ctx.lineTo((x+0.5)*this.ratio,(yOffset+lineHeight+0.5)*this.ratio);
|
|
|
|
this.ctx.stroke();
|
|
|
|
|
|
|
|
if(unitTextWidth < steppedOneUnit - (2*this.padding) && x > 0) {
|
|
|
|
this.ctx.fillText(caption,(x+this.padding)*this.ratio,(yOffset+lineHeight-this.padding)*this.ratio);
|
|
|
|
} else if((unitTextWidth < (steppedOneUnit - (2*this.padding) +pixelOffset)) && (unitTextWidth < (steppedOneUnit-(2*this.padding)))) {
|
|
|
|
this.ctx.fillText(caption, (this.padding*this.ratio),(yOffset+lineHeight-this.padding)*this.ratio);
|
|
|
|
} else if(x < 0 && (unitTextWidth <steppedOneUnit - (2*this.padding))) {
|
|
|
|
this.ctx.fillText(caption, ((x+steppedOneUnit-this.padding-unitTextWidth) *this.ratio),(yOffset+ lineHeight-this.padding)*this.ratio);
|
|
|
|
}
|
|
|
|
n=this.getNextTick(viewStartDate,n,step,unitScale);
|
|
|
|
dateOffset = this.getUnitDateOffset(viewStartDate,unitScale,n);
|
|
|
|
nextDateOffset = this.getUnitDateOffset(viewStartDate,unitScale,this.getNextTick(viewStartDate,n,step,unitScale));
|
|
|
|
nextX = (nextDateOffset / this.scale);
|
|
|
|
pixelOffset = (dateOffset / this.scale);
|
|
|
|
caption= this.getStartCaption(new Date(viewStartDate.getTime()+dateOffset),unitScale,false,false);
|
|
|
|
x=pixelOffset;
|
|
|
|
}
|
|
|
|
return lineHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
redraw() {
|
|
|
|
let yOffset=0;
|
|
|
|
let canvas = this.canvasRef.nativeElement;
|
|
|
|
let height = canvas.offsetHeight;
|
|
|
|
let width = canvas.offsetWidth;
|
|
|
|
this.ctx.lineWidth = this.lineWidth;// *this.ratio;
|
|
|
|
this.ctx.clearRect(0,0,width *this.ratio,height*this.ratio);
|
|
|
|
for(let unit of this.posibleUnits) {
|
|
|
|
if(this.unitScale <=unit) yOffset+=this.drawUnits(yOffset,width,this.viewMinDate,unit);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
handleClick() {
|
|
|
|
this.collapsed = !this.collapsed;
|
|
|
|
}
|
|
|
|
|
|
|
|
updateStyle(startDate:Date,endDate:Date) {
|
|
|
|
let rangeInMilliseconds = endDate.getTime() - startDate.getTime();
|
|
|
|
let range = rangeInMilliseconds / this.scale;
|
|
|
|
let left = (startDate.getTime() - this.viewMinDate.getTime()) / this.scale;
|
|
|
|
this.startPopoverLeft=(left-10);
|
|
|
|
this.endPopoverLeft=(left+range+10);
|
|
|
|
this.marginLeft = (left - 15);
|
|
|
|
this.rangeWidth = range;
|
|
|
|
this.updatePopoverText(this.popoverStart,this.getStartCaption(startDate,this.unitScale,true));
|
|
|
|
this.updatePopoverText(this.popoverEnd,this.getEndCaption(endDate,this.unitScale,true));
|
|
|
|
this.caption=this.getCaption(startDate,endDate,this.unitScale);
|
|
|
|
}
|
|
|
|
|
|
|
|
snapToUnit(date:Date,unitScale:number):Date {
|
|
|
|
var d = new Date(date.getTime() + (this.unitScales[this.unitScale]/2));
|
|
|
|
var offsetInMilliseconds =this.getUnitDateOffset(d,this.unitScale,0)
|
|
|
|
return new Date(d.getTime()+offsetInMilliseconds);
|
|
|
|
}
|
|
|
|
|
|
|
|
getEndDate(offsetInPixels:number):Date {
|
|
|
|
let oneUnit = this.unitScales[this.unitScale];
|
|
|
|
let offsetInMilliseconds = offsetInPixels * this.scale;
|
|
|
|
if(this.leftGripMove) {
|
|
|
|
if(this.startDate.getTime() + offsetInMilliseconds > this.endDate.getTime() - oneUnit) {
|
|
|
|
return this.snapToUnit(new Date(this.startDate.getTime() + offsetInMilliseconds + oneUnit),this.unitScale);
|
|
|
|
}
|
|
|
|
} else if(this.rightGripMove || this.rangeGripMove) {
|
|
|
|
return this.snapToUnit(new Date(this.endDate.getTime() + offsetInMilliseconds),this.unitScale);
|
|
|
|
}
|
|
|
|
return this.endDate;
|
|
|
|
}
|
|
|
|
|
|
|
|
getStartDate(offsetInPixels:number):Date {
|
|
|
|
let oneUnit = this.unitScales[this.unitScale];
|
|
|
|
let offsetInMilliseconds = offsetInPixels * this.scale;
|
|
|
|
if(this.leftGripMove || this.rangeGripMove) {
|
|
|
|
return this.snapToUnit(new Date(this.startDate.getTime() + offsetInMilliseconds),this.unitScale);
|
|
|
|
} else if(this.rightGripMove) {
|
|
|
|
if(this.endDate.getTime() + offsetInMilliseconds < this.startDate.getTime() + oneUnit) {
|
|
|
|
return this.snapToUnit(new Date(this.endDate.getTime() + offsetInMilliseconds - oneUnit),this.unitScale);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return this.startDate;
|
|
|
|
}
|
|
|
|
|
|
|
|
updateControl(event:MouseEvent|TouchEvent) {
|
|
|
|
let offsetInPixels = this.getClientX(event) - this.downX;
|
|
|
|
if(this.leftGripMove || this.rightGripMove || this.rangeGripMove) {
|
|
|
|
let startDate = this.getStartDate(offsetInPixels);
|
|
|
|
let endDate = this.getEndDate(offsetInPixels);
|
|
|
|
this.updateStyle(startDate,endDate)
|
|
|
|
this.changeDetectorRef.detectChanges();
|
|
|
|
} else if(this.viewPan) {
|
|
|
|
let offsetInMilliseconds = offsetInPixels*this.scale;
|
|
|
|
this.viewMinDate = new Date(this.viewMinDate.getTime()-offsetInMilliseconds);
|
|
|
|
this.viewMaxDate = new Date(this.viewMaxDate.getTime()-offsetInMilliseconds);
|
|
|
|
this.updateStyle(this.startDate,this.endDate);
|
|
|
|
this.redraw();
|
|
|
|
this.changeDetectorRef.detectChanges();
|
|
|
|
this.downX=this.getClientX(event);
|
|
|
|
}
|
|
|
|
this.lastOffsetInPixels=offsetInPixels
|
|
|
|
}
|
|
|
|
|
|
|
|
isMouseEvent(arg: any): arg is MouseEvent {
|
|
|
|
return arg.clientX !== undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
getClientX(event:MouseEvent|TouchEvent) {
|
|
|
|
if(this.isMouseEvent(event)) {
|
|
|
|
return (event as MouseEvent).clientX;
|
|
|
|
} else {
|
|
|
|
return (event as TouchEvent).touches[0].clientX;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
handleRightGripMouseDown(event:MouseEvent) {
|
|
|
|
this.rightGripMove=true;
|
|
|
|
this.downX = this.getClientX(event);
|
|
|
|
this.popoverEnd.open(this.endCaption);
|
|
|
|
event.preventDefault();
|
|
|
|
}
|
|
|
|
|
|
|
|
handleRightGripMouseEnter(event:MouseEvent) {
|
|
|
|
this.mouseX=-1;
|
|
|
|
this.mouseY=-1;
|
|
|
|
this.redraw();
|
|
|
|
if(!this.rangeGripMove && !this.leftGripMove && !this.rightGripMove) this.popoverEnd.open(this.endCaption);
|
|
|
|
}
|
|
|
|
|
|
|
|
handleRightGripMouseLeave(event:MouseEvent) {
|
|
|
|
if(!this.rightGripMove) this.popoverEnd.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
handleLeftGripMouseDown(event:MouseEvent|TouchEvent) {
|
|
|
|
this.leftGripMove=true;
|
|
|
|
this.downX = this.getClientX(event);
|
|
|
|
this.popoverStart.open(this.startCaption);
|
|
|
|
event.preventDefault();
|
|
|
|
}
|
|
|
|
|
|
|
|
handleLeftGripMouseEnter(event:MouseEvent|TouchEvent) {
|
|
|
|
this.mouseX=-1;
|
|
|
|
this.mouseY=-1;
|
|
|
|
this.redraw();
|
|
|
|
if(!this.rangeGripMove && !this.leftGripMove && !this.rightGripMove) this.popoverStart.open(this.startCaption);
|
|
|
|
}
|
|
|
|
|
|
|
|
handleLeftGripMouseLeave(event:MouseEvent) {
|
|
|
|
if(!this.leftGripMove) this.popoverStart.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
handleRangeGripMouseEnter(event:MouseEvent) {
|
|
|
|
this.mouseX=-1;
|
|
|
|
this.mouseY=-1;
|
|
|
|
this.redraw();
|
|
|
|
}
|
|
|
|
|
|
|
|
handleRangeGripMouseDown(event:MouseEvent|TouchEvent) {
|
|
|
|
this.rangeGripMove=true;
|
|
|
|
this.downX = this.getClientX(event);
|
|
|
|
event.preventDefault();
|
|
|
|
}
|
|
|
|
|
|
|
|
handleViewPanMouseDown(event:MouseEvent|TouchEvent) {
|
|
|
|
this.viewPan=true;
|
|
|
|
this.downX =this.getClientX(event);
|
|
|
|
event.preventDefault();
|
|
|
|
}
|
|
|
|
|
|
|
|
handleMouseUp(event:MouseEvent|TouchEvent) {
|
|
|
|
//this.updateControl(event);
|
|
|
|
this.startDate = this.getStartDate(this.lastOffsetInPixels);
|
|
|
|
this.endDate = this.getEndDate(this.lastOffsetInPixels);
|
|
|
|
this.popoverStart.close();
|
|
|
|
this.popoverEnd.close();
|
|
|
|
this.startCaption={popoverCaption:this.getStartCaption(this.startDate,this.unitScale,true)};
|
|
|
|
this.endCaption={popoverCaption:this.getEndCaption(this.endDate,this.unitScale,true)};
|
|
|
|
if(this.leftGripMove || this.rightGripMove || this.rangeGripMove) {
|
|
|
|
this.change.emit({ startDate:this.startDate,endDate:this.endDate});
|
|
|
|
}
|
|
|
|
this.rightGripMove=false;
|
|
|
|
this.leftGripMove=false;
|
|
|
|
this.rangeGripMove=false;
|
|
|
|
this.viewPan = false;
|
|
|
|
this.lastOffsetInPixels=0;
|
|
|
|
}
|
|
|
|
|
|
|
|
handleMouseMove(event:MouseEvent) {
|
|
|
|
this.mouseX = -1;
|
|
|
|
this.mouseY = -1;
|
|
|
|
if(!this.leftGripMove && ! this.rightGripMove && !this.rangeGripMove && !this.viewPan) {
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
this.updateControl(event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
handleCanvasMouseMove(event:MouseEvent) {
|
|
|
|
this.mouseX = event.offsetX;
|
|
|
|
this.mouseY = event.offsetY;
|
|
|
|
this.redraw();
|
|
|
|
}
|
|
|
|
|
|
|
|
handleCanvasMouseLeave(event:MouseEvent) {
|
|
|
|
this.mouseX = -1;
|
|
|
|
this.mouseY = -1;
|
|
|
|
this.redraw();
|
|
|
|
}
|
|
|
|
|
|
|
|
canZoom(currentScale:number, direction:number):boolean {
|
|
|
|
let nextScale=currentScale;
|
|
|
|
if(direction<0 ) {
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
nextScale*=1.1;
|
|
|
|
let canZoom=false;
|
|
|
|
let oneUnit = (this.getUnitDateOffset(this.viewMinDate,8,1)- this.getUnitDateOffset(this.viewMinDate,8,0)) / nextScale;
|
|
|
|
let unitTextWidth=this.getUnitTextWidth(8);
|
|
|
|
let steps=this.getSteps(8);
|
|
|
|
let s=0;
|
|
|
|
let step=steps[s];
|
|
|
|
let steppedOneUnit=oneUnit*step;
|
|
|
|
while(unitTextWidth > (steppedOneUnit-(2*this.padding)) && s < steps.length -1) {
|
|
|
|
step=steps[++s];
|
|
|
|
steppedOneUnit=oneUnit*step;
|
|
|
|
}
|
|
|
|
return unitTextWidth < (steppedOneUnit-(2*this.padding)) && s < steps.length;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
handleMouseWheel(event:WheelEvent) {
|
|
|
|
if(!this.canZoom(this.scale,event.deltaY)) return;
|
|
|
|
let oldOffsetInMilliseconds = event.clientX * this.scale;
|
|
|
|
if(event.deltaY>=0)
|
|
|
|
this.scale*=1.1;
|
|
|
|
else
|
|
|
|
this.scale/=1.1;
|
|
|
|
this.posibleUnits=this.getPosibleUnits(this.scale);
|
|
|
|
this.height=this.getHeight();
|
|
|
|
this.changeDetectorRef.detectChanges();
|
|
|
|
this.setCanvasSize();
|
|
|
|
let newOffsetInMilliseconds = event.clientX * this.scale;
|
|
|
|
let offsetInMilliseconds = newOffsetInMilliseconds-oldOffsetInMilliseconds;
|
|
|
|
this.viewMinDate = new Date(this.viewMinDate.getTime()-offsetInMilliseconds);
|
|
|
|
this.viewMaxDate = new Date(this.viewMaxDate.getTime()-offsetInMilliseconds);
|
|
|
|
this.updateStyle(this.startDate,this.endDate);
|
|
|
|
this.redraw();
|
|
|
|
this.changeDetectorRef.detectChanges();
|
|
|
|
}
|
|
|
|
|
|
|
|
handleZoomOut() {
|
|
|
|
if(!this.canZoom(this.scale,1)) return;
|
|
|
|
this.scale*=1.1;
|
|
|
|
this.posibleUnits=this.getPosibleUnits(this.scale);
|
|
|
|
this.height=this.getHeight();
|
|
|
|
this.setCanvasSize();
|
|
|
|
this.redraw();
|
|
|
|
this.updateStyle(this.startDate,this.endDate);
|
|
|
|
}
|
|
|
|
|
|
|
|
handleZoomIn() {
|
|
|
|
if(!this.canZoom(this.scale,-1)) return;
|
|
|
|
this.scale/=1.1;
|
|
|
|
this.posibleUnits=this.getPosibleUnits(this.scale);
|
|
|
|
this.height=this.getHeight();
|
|
|
|
this.setCanvasSize();
|
|
|
|
this.redraw();
|
|
|
|
this.updateStyle(this.startDate,this.endDate);
|
|
|
|
}
|
|
|
|
|
|
|
|
handleResize(event:any) {
|
|
|
|
if(this.initialized) {
|
|
|
|
this.setCanvasSize();
|
|
|
|
this.updateStyle(this.startDate,this.endDate);
|
|
|
|
this.redraw();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ngOnChanges (changes: SimpleChanges) {
|
|
|
|
if(this.initialized) {
|
|
|
|
this.setCanvasSize();
|
|
|
|
this.updateStyle(this.startDate,this.endDate);
|
|
|
|
this.redraw();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|