BBTimeline/bbtimeline.min.js

5 lines
12 KiB
JavaScript

/**
* BBTimeline
* @version v0.1.0.089 beta (2023/10/14 1658)
*/
class BBTimeline{constructor(el){const a=this;a.Container=document.getElementById(el),a.DateParsePattern="yyyy-MM-dd",a.Debug=!1,a.Padding={Left:20,Top:20,Right:20,Bottom:0},a.Size={W:a.Container.innerWidth||a.Container.clientWidth,H:a.Container.innerHeight||a.Container.clientHeight},a.ctx=a.Container.getContext("2d"),a.ctx.canvas.width=a.Size.W,a.ctx.canvas.height=a.Size.H,a.Axis={LineColour1:"#CFCFCF",LineWidth:1,Font:"8pt Arial",LabelColour:"#000000",LabelSpacing:6,X:{NoPartPerDay:4,HourLineSpace:6,HourLineHeight:10,HourLineColour:"#A6A6A6",DayLineHeight:20,DayLineColour:"#282828"}},a.Marker={BorderColour:"#3A5D9C",BorderWidth:2,BackColour:"#D4DEEF",Width:10,ForeColour:"#3A5D9C",Font:"9pt Arial",CollisionMargin:8},a.HighlightLine={Colour:"#A6A6A6",Width:1},a.Events=[],a.StartDate=a.DateToString(new Date,a.DateParsePattern),a.ShowDate=a.StartDate,a.GraphRectangle=a.calcGraphRectangle(),a.Enabled=!1,a.initialiseComponents()}AddEvent(date,label,options){const a=this,_options=Object.assign(a.GenerateEventItem(),options);let event=a.FindEvent(date);null==event&&(a.Events.push(a.GenerateEvent(date)),event=a.FindEvent(date)),null!=label&&(event.Label=label),event.Events.push(_options)}CalcEndDate(){const a=this,calcdays=Math.floor(a.GraphRectangle.W/(a.Axis.X.NoPartPerDay*a.Axis.X.HourLineSpace));let date=a.ConvertToDate(a.ShowDate);return date.setDate(date.getDate()+calcdays),date.setDate(date.getDate()-1),a.DateToString(date,a.DateParsePattern)}Clear(){const a=this;a.ctx.clearRect(0,0,a.ctx.canvas.width,a.ctx.canvas.height),a.StartDate=a.DateToString(new Date,a.DateParsePattern),a.ShowDate=a.StartDate,a.Enabled=!1,a.Events=[]}DeleteMarker(date){const a=this;for(let i=0;i<a.Events.length;i++)a.Events[i].Date==date&&a.Events.splice(i,1)}FindDatePosition(date){const a=undefined,points=this.getXAxis();for(let i=0;i<points.length;i++)if(points[i].Date==date)return points[i];return null}FindVisibleEvents(){const a=this;let result=[];const availableX=undefined;return a.getXAxis().forEach((function(e){const event=a.FindEvent(e.Date);null!=event&&(event.Position.X=e.X,result.push(event))})),result}FindEvent(date){const a=this;for(let i=0;i<a.Events.length;i++)if(a.Events[i].Date==date)return a.Events[i];return null}FindEventsByCoords(x,y){const a=this;for(let i=0;i<a.Events.length;i++){const e=a.Events[i].HitBox;if(null==a.Events[i].HitBox)continue;const x2=e.X+e.W,y2=e.Y+e.H;if(x>=e.X&&x<=x2&&y>=e.Y&&y<=y2)return a.Events[i]}return null}Load(startDate){const a=this;a.StartDate=startDate,a.Show(startDate)}Show(date){const a=this;a.ConvertToDate(date)<a.ConvertToDate(a.StartDate)&&(date=a.StartDate),a.ShowDate=date,a.Enabled=!0,a.Invalidate(!0,!0)}ShowNext(){const a=this;let date=a.ConvertToDate(a.ShowDate);date.setMonth(date.getMonth()+1),a.Show(a.DateToString(date,a.DateParsePattern))}ShowPrevious(){const a=this;let date=a.ConvertToDate(a.ShowDate);date.setMonth(date.getMonth()-1),a.Show(a.DateToString(date,a.DateParsePattern))}UpdateLabel(date,label){const a=this;let event=a.FindEvent(date);null!=event&&(event.Label=label,a.Invalidate(!1,!0))}UpdateMarker(date,borderColour,backColour){const a=this;let event=a.FindEvent(date);null!=event&&(event.BorderColour=borderColour,event.BackColour=backColour,a.Invalidate(!1,!0))}initialiseComponents(){const a=this;a.ctx.canvas.addEventListener("mousedown",(function(e){if(a.Enabled){var event=a.FindEventsByCoords(e.offsetX,e.offsetY);null!=event&&(a.Debug&&console.log(event),a.OnMouseDown(this,e,event))}})),a.ctx.canvas.addEventListener("click",(function(e){if(a.Enabled){var event=a.FindEventsByCoords(e.offsetX,e.offsetY);null!=event&&(a.Debug&&console.log(event),a.OnClick(this,e,event))}}))}Invalidate(redrawAxis,redrawMarkers){const a=this;if(redrawAxis&&(a.ctx.clearRect(0,0,a.ctx.canvas.width,a.ctx.canvas.height),a.drawAxis(),a.drawXAxis(),a.drawXAxisLabels()),redrawMarkers){a.clearChart();const startPosY=a.GraphRectangle.Y+a.Marker.Width,visibleEvents=a.FindVisibleEvents();a.Debug&&console.log(visibleEvents),visibleEvents.forEach((function(e,i){let posY=a.calcMarkerPosition(e.Position.X,startPosY);a.drawVerticalLine(e.Position.X,posY);const markerRectangle=a.drawMarker(e.Position.X,posY,e.BorderColour,e.BackColour),labelSize=a.drawText(markerRectangle.X+markerRectangle.W+a.GraphRectangle.Margin,markerRectangle.Y,e.Label,a.Marker.Font,a.Marker.ForeColour,"left");e.Position={X:e.Position.X,Y:posY},e.HitBox={X:markerRectangle.X,Y:markerRectangle.Y,W:markerRectangle.W+a.GraphRectangle.Margin+labelSize.Width+a.Marker.CollisionMargin,H:markerRectangle.H},a.Debug&&a.drawRectangle(e.HitBox),a.Debug&&console.log(e.HitBox)}))}a.Debug&&a.drawRectangle(a.GraphRectangle)}GenerateEvent(date){const a=this;return{Date:date,Label:"",Position:{X:0,Y:0},Events:[],HitBox:null,BorderColour:a.Marker.BorderColour,BackColour:a.Marker.BackColour}}GenerateEventItem(){return{Title:"",Description:"",Link:"",Tag:null}}DateToString(date,pattern){let result=pattern;return result=result.replace("fffffff",date.getMilliseconds().toString().padStart(7,"0")),result=result.replace("ffffff",date.getMilliseconds().toString().padStart(6,"0")),result=result.replace("fffff",date.getMilliseconds().toString().padStart(5,"0")),result=result.replace("yyyy",date.getFullYear().toString().padStart(4,"0")),result=result.replace("MMMM","{1}"),result=result.replace("dddd","{2}"),result=result.replace("ffff",date.getMilliseconds().toString().padStart(4,"0")),result=result.replace("yyy",date.getFullYear().toString().padStart(3,"0")),result=result.replace("MMM","{3}"),result=result.replace("ddd","{4}"),result=result.replace("fff",date.getMilliseconds().toString().padStart(3,"0")),result=result.replace("zzz",""),result=result.replace("yy",date.getFullYear().toString().slice(-2)),result=result.replace("MM",(date.getMonth()+1).toString().padStart(2,"0")),result=result.replace("dd",date.getDate().toString().padStart(2,"0")),result=result.replace("HH",date.getHours().toString().padStart(2,"0")),result=result.replace("hh",(date.getHours()>12?date.getHours()-12:date.getHours()).toString().padStart(2,"0")),result=result.replace("mm",date.getMinutes().toString().padStart(2,"0")),result=result.replace("ss",date.getSeconds().toString().padStart(2,"0")),result=result.replace("ff",date.getMilliseconds().toString().padStart(2,"0")),result=result.replace("tt","{5}"),result=result.replace("zz",""),result=result.replace("y",date.getFullYear().toString()),result=result.replace("M",(date.getMonth()+1).toString()),result=result.replace("d",date.getDate().toString()),result=result.replace("H",date.getHours().toString()),result=result.replace("h",(date.getHours()>12?date.getHours()-12:date.getHours()).toString()),result=result.replace("m",date.getMinutes().toString()),result=result.replace("s",date.getSeconds().toString()),result=result.replace("z",""),result=result.replace("t","{6}"),result=result.replace("Z",""),result=result.replace("{1}",date.toLocaleString("default",{month:"long"})),result=result.replace("{2}",date.toLocaleString("default",{weekday:"long"})),result=result.replace("{3}",date.toLocaleString("default",{month:"short"})),result=result.replace("{4}",date.toLocaleString("default",{weekday:"short"})),result=result.replace("{5}",date.getHours()>=12?"PM":"AM"),result=result.replace("{6}",date.getHours()>=12?"P":"A"),result}OnMouseDown(sender,e,event){}OnClick(sender,e,event){}calcGraphRectangle(){const a=this,xAxisHeight=a.calcXAxisHeight();let result={X:a.Padding.Left,Y:a.Padding.Top,W:a.Size.W-(a.Padding.Left+a.Padding.Right),H:a.Size.H-(a.Padding.Top+a.Padding.Bottom)-xAxisHeight,Margin:2*a.Marker.BorderWidth};return result.StepHeight=a.Marker.Width+result.Margin,result.NoStep=Math.floor(result.H/result.StepHeight),result}calcXAxisHeight(){const a=this,labelSize=undefined,result=undefined;return a.measureText(a.Axis.Font,"0").Height+a.Axis.LabelSpacing+2*a.Axis.X.DayLineHeight}calcMarkerPosition(x,y){const a=this;let hasMoved=!1,posY=y;for(let i=0;i<a.GraphRectangle.NoStep;i++){var clippedEvent;if(posY=y+a.GraphRectangle.StepHeight*i,null==a.FindEventsByCoords(x,posY)){hasMoved=!0;break}}return hasMoved||(posY=y),posY}clearChart(){const a=this,rect={X:a.GraphRectangle.X,Y:a.GraphRectangle.Y,W:a.GraphRectangle.W,H:a.GraphRectangle.H};rect.X+=a.Axis.LineWidth,rect.Y-=a.Padding.Top,rect.W-=a.Axis.LineWidth,rect.W+=a.Padding.Right,rect.H-=a.Axis.LineWidth,rect.H+=a.Padding.Top,a.ctx.clearRect(rect.X,rect.Y,rect.W,rect.H),a.Debug&&a.drawRectangle(rect);const visibleEvents=undefined;a.FindVisibleEvents().forEach((function(e,i){e.Position={X:0,Y:0},e.HitBox=null}))}drawAxis(){const a=this;a.ctx.beginPath(),a.ctx.moveTo(a.GraphRectangle.X,a.GraphRectangle.Y),a.ctx.lineTo(a.GraphRectangle.X,a.GraphRectangle.H+a.GraphRectangle.Y),a.ctx.lineTo(a.GraphRectangle.W+a.GraphRectangle.X,a.GraphRectangle.H+a.GraphRectangle.Y),a.ctx.lineWidth=a.Axis.LineWidth,a.ctx.strokeStyle=a.Axis.LineColour1,a.ctx.stroke()}drawXAxis(){const a=this;let startPosX=a.GraphRectangle.X;const endPosX=a.GraphRectangle.X+a.GraphRectangle.W,posY=a.GraphRectangle.Y+a.GraphRectangle.H+a.Axis.LineWidth;let i=0;for(;!(startPosX>=endPosX);)a.ctx.beginPath(),a.ctx.moveTo(startPosX,posY),i%a.Axis.X.NoPartPerDay==0?(a.ctx.lineTo(startPosX,posY+a.Axis.X.DayLineHeight),a.ctx.strokeStyle=a.Axis.X.DayLineColour):(a.ctx.lineTo(startPosX,posY+a.Axis.X.HourLineHeight),a.ctx.strokeStyle=a.Axis.X.HourLineColour),a.ctx.lineWidth=a.Axis.LineWidth,a.ctx.stroke(),startPosX+=a.Axis.X.HourLineSpace,i++}drawXAxisLabels(){const a=this,result=a.getXAxis(),posY=a.GraphRectangle.Y+a.GraphRectangle.H+a.Axis.LineWidth;result.forEach((function(e,i){const date=a.ConvertToDate(e.Date);let writeLabel=!1;0==i?date.getDate()<25&&(writeLabel=!0):1==date.getDate()&&(writeLabel=!0);const labelSize=a.drawText(e.X,posY+a.Axis.X.DayLineHeight,a.DateToString(date,"dd"),a.Axis.Font,a.Axis.LabelColour,"center");writeLabel&&a.drawText(e.X,posY+a.Axis.X.DayLineHeight+labelSize.Height+a.Axis.LabelSpacing,a.DateToString(date,"MMMM yyyy"),a.Axis.Font,a.Axis.LabelColour,"left")}))}drawMarker(x,y,borderColour,backColour){const a=this,width=a.Marker.Width-2*a.Marker.BorderWidth;return a.ctx.beginPath(),a.ctx.arc(x,y,width,0,2*Math.PI,!1),a.ctx.fillStyle=backColour,a.ctx.fill(),a.ctx.lineWidth=a.Marker.BorderWidth,a.ctx.strokeStyle=borderColour,a.ctx.stroke(),a.measureMarker(x,y)}drawText(x,y,label,font,foreColour,align){const a=this;a.ctx.font=font,a.ctx.fillStyle=foreColour;const size=a.measureText(font,label);switch(align){case"center":x-=size.OffsetLeft;break;case"right":x-=size.Width}return a.ctx.fillText(label,x,y+size.Height),size}drawRectangle(rectangle){const a=this;a.ctx.beginPath(),a.ctx.rect(rectangle.X,rectangle.Y,rectangle.W,rectangle.H),a.ctx.lineWidth=1,a.ctx.strokeStyle="red",a.ctx.stroke()}drawVerticalLine(x,y){const a=this,linePosY=a.GraphRectangle.Y+a.GraphRectangle.H;y<=0&&(y=a.GraphRectangle.Y+a.HighlightLine.Width),a.ctx.beginPath(),a.ctx.moveTo(x,y),a.ctx.lineTo(x,linePosY-a.HighlightLine.Width),a.ctx.lineWidth=a.HighlightLine.Width,a.ctx.strokeStyle=a.HighlightLine.Colour,a.ctx.stroke()}getXAxis(){const a=this,endPosX=a.GraphRectangle.X+a.GraphRectangle.W;let result=[],x=a.GraphRectangle.X,date=a.ConvertToDate(a.ShowDate);for(date.setDate(date.getDate()-1);!(x>=endPosX);)result.push({Date:a.DateToString(date,a.DateParsePattern),X:x}),x+=a.Axis.X.HourLineSpace*a.Axis.X.NoPartPerDay,date.setDate(date.getDate()+1);return result}half(value){return value/2}measureMarker(x,y){const a=this,offset=a.half(a.Marker.Width),result=undefined;return{X:x-(offset+a.Marker.BorderWidth),Y:y-(offset+a.Marker.BorderWidth),W:a.Marker.Width+2*a.Marker.BorderWidth,H:a.Marker.Width+2*a.Marker.BorderWidth}}measureText(font,value){const a=this;a.ctx.font=font;const size=a.ctx.measureText(value);return{Width:size.width,Height:size.fontBoundingBoxAscent,OffsetLeft:a.half(size.width),OffsetTop:a.half(size.fontBoundingBoxAscent)}}ConvertToDate(value){return new Date(Date.parse(value))}}