diff --git a/bbtimeline-background-canvas.js b/bbtimeline-background-canvas.js new file mode 100644 index 0000000..9f6a859 --- /dev/null +++ b/bbtimeline-background-canvas.js @@ -0,0 +1,208 @@ +class BBTimelineBackgroundCanvas extends BBTimelineCanvas { + constructor(parentEl, el) { + super(parentEl, el); + + const a = this; + + a.Margin = 0; + a.StepHeight = 0; + a.NoStep = 0; + + a.initialiseComponents(); + } + + initialiseComponents() { + super.initialiseComponents(); + + const a = this; + + a.Margin = (a.Parent.Marker.BorderWidth * 2); + a.StepHeight = a.Parent.Marker.Width + a.Margin; + a.NoStep = Math.floor(a.GraphRectangle.H / a.StepHeight); + + a.Invalidate(); + } + + + get GraphRectangle() { + const a = this; + + if (a.Parent.XAxis.Position == 'top') { + return { + X: a.ClientRectangle.X, + Y: (a.ClientRectangle.Y + a.XAxisHeight), + W: a.ClientRectangle.W, + H: (a.ClientRectangle.H - a.XAxisHeight) + }; + } else { + return { + X: a.ClientRectangle.X, + Y: a.ClientRectangle.Y, + W: a.ClientRectangle.W, + H: (a.ClientRectangle.H - a.XAxisHeight) + }; + } + } + + get XAxisHeight() { + const a = this; + const labelSize = a.measureText(a.Parent.Axis.Font, "0"); + + return labelSize.H + a.Parent.Axis.LabelSpacing + (a.Parent.XAxis.DayLineHeight * 2); + } + + get XAxisPositions() { + const a = this; + const endPosX = (a.GraphRectangle.X + a.GraphRectangle.W); + + let result = []; + let x = a.GraphRectangle.X; + let date = a.Parent.ConvertToDate(a.Parent.ShowDate); + + // Rollback one day + date.setDate(date.getDate() - 1); + + while (true) { + if (x >= endPosX) { + break; + } + + result.push({ + Date: a.Parent.DateToInternalString(date), + X: x + }); + + x += (a.Parent.XAxis.HourLineSpace * a.Parent.XAxis.NoPartPerDay); + date.setDate(date.getDate() + 1); + } + + return result; + } + + + Invalidate() { + const a = this; + + a.Clear(); + + a.drawAxis(); + a.drawXAxisTicks(); + a.drawXAxisLabels(); + + if (a.Parent.Debug) a.drawRectangle(a.ClientRectangle, "red"); + if (a.Parent.Debug) a.drawRectangle(a.GraphRectangle, "red"); + } + + + drawAxis() { + const a = this; + + a.CTX.beginPath(); + + if (a.Parent.XAxis.Position == 'top') { + a.CTX.moveTo(a.GraphRectangle.X, (a.GraphRectangle.Y + a.GraphRectangle.H)); + a.CTX.lineTo(a.GraphRectangle.X, a.GraphRectangle.Y); + a.CTX.lineTo((a.GraphRectangle.X + a.GraphRectangle.W), a.GraphRectangle.Y); + } else { + a.CTX.moveTo(a.GraphRectangle.X, a.GraphRectangle.Y); + a.CTX.lineTo(a.GraphRectangle.X, (a.GraphRectangle.Y +a.GraphRectangle.H)); + a.CTX.lineTo((a.GraphRectangle.X + a.GraphRectangle.W), (a.GraphRectangle.Y + a.GraphRectangle.H)); + } + + a.CTX.lineWidth = a.Parent.Axis.LineWidth; + a.CTX.strokeStyle = a.Parent.Axis.LineColour1; + a.CTX.stroke(); + } + + drawXAxisLabels() { + const a = this; + const labelSize = a.measureText(a.Parent.Axis.Font, "0"); + + let posY = 0; + let posDayY = 0; + let posMonthY = 0; + + if (a.Parent.XAxis.Position == 'top') { + posY = (a.GraphRectangle.Y - a.Parent.Axis.LineWidth) - 2; + posDayY = (posY - (labelSize.H + a.Parent.XAxis.DayLineHeight)); + posMonthY = (posDayY - (labelSize.H + a.Parent.Axis.LabelSpacing)); + } else { + posY = (a.GraphRectangle.Y + a.GraphRectangle.H) + a.Parent.Axis.LineWidth; + posDayY = (posY + a.Parent.XAxis.DayLineHeight); + posMonthY = (posDayY + labelSize.H + a.Parent.Axis.LabelSpacing); + } + + a.XAxisPositions.forEach(function(e, i) { + const date = a.Parent.ConvertToDate(e.Date); + + let writeLabel = false; + if ((i == 0)) { + // Don't label first entry if too close to the next month + if (date.getDate() < 25) { + writeLabel = true; + } + } else if (date.getDate() == 1) { + writeLabel = true; + } + + // if (i == 0) { + // // Don't write first date + // } else { + // Write date (dd) + a.drawText(e.X, posDayY, a.Parent.DateToString(date, "dd"), a.Parent.Axis.Font, a.Parent.Axis.LabelColour, "center"); + // } + + // Write month (MMMM) on first of the month + if (writeLabel) { + a.drawText(e.X, posMonthY, a.Parent.DateToString(date, "MMMM yyyy"), a.Parent.Axis.Font, a.Parent.Axis.LabelColour, "left"); + } + }); + } + + drawXAxisTicks() { + const a = this; + + let startPosX = a.GraphRectangle.X; + const endPosX = (a.GraphRectangle.X + a.GraphRectangle.W); + let posY = 0; + let posDayY = 0; + let posHourY = 0; + + if (a.Parent.XAxis.Position == 'top') { + posY = (a.GraphRectangle.Y - a.Parent.Axis.LineWidth); + posDayY = (posY - a.Parent.XAxis.DayLineHeight); + posHourY = (posY - a.Parent.XAxis.HourLineHeight); + } else { + posY = (a.GraphRectangle.Y + a.GraphRectangle.H) + a.Parent.Axis.LineWidth; + posDayY = (posY + a.Parent.XAxis.DayLineHeight); + posHourY = (posY + a.Parent.XAxis.HourLineHeight); + } + + let i = 0; + + while (true) { + if (startPosX >= endPosX) { + break; + } + + a.CTX.beginPath(); + a.CTX.moveTo(startPosX, posY); + + if ((i % a.Parent.XAxis.NoPartPerDay) == 0) { + a.CTX.lineTo(startPosX, posDayY); + a.CTX.strokeStyle = a.Parent.XAxis.DayLineColour; + } else { + a.CTX.lineTo(startPosX, posHourY); + a.CTX.strokeStyle = a.Parent.XAxis.HourLineColour; + } + + a.CTX.lineWidth = a.Parent.Axis.LineWidth; + a.CTX.stroke(); + + startPosX += a.Parent.XAxis.HourLineSpace; + + i++; + } + } + +} \ No newline at end of file diff --git a/bbtimeline-canvas.js b/bbtimeline-canvas.js new file mode 100644 index 0000000..adeeaa2 --- /dev/null +++ b/bbtimeline-canvas.js @@ -0,0 +1,177 @@ +class BBTimelineCanvas { + constructor(parentEl, el) { + const a = this; + + a.Parent = parentEl; + a.Container = el; + a.CTX = a.Container.getContext("2d"); + + a.initialiseComponents(); + } + + initialiseComponents() { + const a = this; + + a.Container.style.width = a.Parent.Size.W + "px"; + a.Container.style.height = a.Parent.Size.H + "px"; + a.Container.style.position = 'absolute'; + a.Container.style.border = 'none'; + + a.CTX.canvas.width = a.Parent.Size.W; + a.CTX.canvas.height = a.Parent.Size.H; + + a.Clear(); + } + + + get ClientRectangle() { + const a = this; + + return { + X: a.Parent.Padding.Left, + Y: a.Parent.Padding.Top, + W: (a.Parent.Size.W - (a.Parent.Padding.Left + a.Parent.Padding.Right)), + H: (a.Parent.Size.H - (a.Parent.Padding.Top + a.Parent.Padding.Bottom)) + }; + } + + + Clear() { + const a = this; + + a.CTX.clearRect(0, 0, a.CTX.canvas.width, a.CTX.canvas.height); + } + + Invalidate() { + // placeholder + } + + drawCircle(x, y, width, borderWidth, borderColour, backColour) { + const a = this; + const calcBorderWidth = (borderWidth * 2); + const calcWidth = width - calcBorderWidth; + const offset = a.half(width); + + a.CTX.beginPath(); + a.CTX.arc(x, y, calcWidth, 0, 2 * Math.PI, false); + a.CTX.fillStyle = backColour; + a.CTX.fill(); + a.CTX.lineWidth = borderWidth; + a.CTX.strokeStyle = borderColour; + a.CTX.stroke(); + + const result = { + X: x - (offset + borderWidth), + Y: y - (offset + borderWidth), + W: (width + calcBorderWidth), + H: (width + calcBorderWidth) + }; + + return result; + } + + drawRectangle(rectangle, colour) { + const a = this; + + a.CTX.beginPath(); + a.CTX.rect(rectangle.X, rectangle.Y, rectangle.W, rectangle.H); + //a.ctx.fillStyle = 'yellow'; + //a.ctx.fill(); + a.CTX.lineWidth = 1; + a.CTX.strokeStyle = colour; + a.CTX.stroke(); + + return rectangle; + } + + drawText(x, y, label, font, foreColour, align) { + const a = this; + + a.CTX.font = font; + a.CTX.fillStyle = foreColour; + + let size = a.measureText(font, label); + size.Y = y; + + switch (align) { + case "center": + size.X = (x - size.X); + break; + case "right": + size.X = (x - size.W); + break; + case "left": + default: + size.X = x; + break; + } + + a.CTX.fillText(label, size.X, (size.Y + size.H)); + + return size; + } + + drawVerticalLine(x, y1, y2, width, colour) { + const a = this; + + a.CTX.beginPath(); + a.CTX.moveTo(x, y1); + a.CTX.lineTo(x, (y2 - width)); + a.CTX.lineWidth = width; + a.CTX.strokeStyle = colour; + a.CTX.stroke(); + + const result = { + X: x, + Y: y1, + W: width, + H: (y2 - y1) + }; + + return result; + } + + + half(value) { + return (value / 2); + } + + measureText(font, value) { + const a = this; + + a.CTX.font = font; + const size = a.CTX.measureText(value); + + return { + W: size.width, + H: size.fontBoundingBoxAscent, + X: a.half(size.width), + Y: a.half(size.fontBoundingBoxAscent) + }; + } + + isPointInRectangle(rect, point) { + const x2 = (rect.X + rect.W); + const y2 = (rect.Y + rect.H); + + return ((point.X >= rect.X) && (point.X <= x2) && (point.Y >= rect.Y) && (point.Y <= y2)); + } + + combineRectangle(rect1, rect2) { + const x2 = Math.max((rect1.X + rect1.W), (rect2.X + rect2.W)); + const y2 = Math.max((rect1.Y + rect1.H), (rect2.Y + rect2.H)); + + const rect = { + X: Math.min(rect1.X, rect2.X), + Y: Math.min(rect1.Y, rect2.Y), + W: 0, + H: 0 + }; + + rect.W = x2 - rect.X; + rect.H = y2 - rect.Y; + + return rect; + } + +} \ No newline at end of file diff --git a/bbtimeline-flourish-canvas.js b/bbtimeline-flourish-canvas.js new file mode 100644 index 0000000..8b0cc7e --- /dev/null +++ b/bbtimeline-flourish-canvas.js @@ -0,0 +1,33 @@ +class BBTimelineFlourishCanvas extends BBTimelineCanvas { + constructor(parentEl, el) { + super(parentEl, el); + + const a = this; + + a.XPos = -1; + } + + Invalidate() { + const a = this; + + a.Clear(); + + if (a.XPos < 0) { + a.Clear(); + } else { + let posY = 0; + + if (a.Parent.XAxis.Position == 'top') { + posY = a.Parent.Layer.Background.GraphRectangle.Y; + } else { + posY = (a.Parent.Layer.Background.GraphRectangle.Y + a.Parent.Layer.Background.GraphRectangle.H); + } + + // a.drawVerticalLine(a.XPos, a.Parent.Layer.Background.GraphRectangle.Y, (a.Parent.Layer.Background.GraphRectangle.Y + a.Parent.Layer.Background.GraphRectangle.H), a.Parent.MarkerLabel.Line.Width, a.Parent.HotTrack.Colour); + a.drawCircle(a.XPos, posY, a.Parent.HotTrack.Width, 0, a.Parent.HotTrack.Colour, a.Parent.HotTrack.Colour); + + } + + } + +} \ No newline at end of file diff --git a/bbtimeline-foreground-canvas.js b/bbtimeline-foreground-canvas.js new file mode 100644 index 0000000..1649e92 --- /dev/null +++ b/bbtimeline-foreground-canvas.js @@ -0,0 +1,191 @@ +class BBTimelineForegroundCanvas extends BBTimelineCanvas { + constructor(parentEl, el) { + super(parentEl, el); + + const a = this; + + // a.initialiseComponents(); + } + + initialiseComponents() { + super.initialiseComponents(); + + const a = this; + + a.CTX.canvas.addEventListener('mousedown', function (e) { + if (!a.Parent.Enabled) { + return; + } + + var event = a.Parent.FindEventsByCoords(e.offsetX, e.offsetY); + if (event == null) { + return; + } + + if (a.Parent.Debug) console.log(event); + + a.Parent.OnMouseDown(this, e, event); + }); + + a.CTX.canvas.addEventListener('click', function (e) { + if (!a.Parent.Enabled) { + return; + } + + var event = a.Parent.FindEventsByCoords(e.offsetX, e.offsetY); + if (event == null) { + return; + } + + if (a.Parent.Debug) console.log(event); + + a.Parent.OnClick(this, e, event); + }); + + a.CTX.canvas.addEventListener('dblclick', function (e) { + if (!a.Parent.Enabled) { + return; + } + + var event = a.Parent.FindEventsByCoords(e.offsetX, e.offsetY); + if (event == null) { + return; + } + + if (a.Parent.Debug) console.log(event); + + a.Parent.OnDblClick(this, e, event); + }); + + if (a.Parent.EnableHotTracking) { + a.CTX.canvas.addEventListener('mousemove', function (e) { + if (!a.Parent.Enabled) { + return; + } + + var event = a.Parent.FindEventsByCoords(e.offsetX, e.offsetY); + if (event != null) { + a.Container.style.cursor = 'pointer'; + } else { + a.Container.style.cursor = 'default'; + } + + // Hottracking + if (a.Parent.EnableHotTracking) { + const point = { X: e.offsetX, Y: e.offsetY }; + if (a.isPointInRectangle(a.Parent.Layer.Background.GraphRectangle, point)) { + if (a.Parent.Debug) console.log(point); + + a.Parent.Layer.Flourish.XPos = point.X; + + a.Parent.OnMouseMove(this, e, event); + } else { + // Clear hot tracking + a.Parent.Layer.Flourish.XPos = -1; + } + + a.Parent.Layer.Flourish.Invalidate(); + } + + }); + } + + a.Invalidate(); + } + + Invalidate() { + const a = this; + const rect = a.Parent.Layer.Background.GraphRectangle; + const margin = a.Parent.Layer.Background.Margin; + + a.Clear(); + + let startPosY = 0; + const visibleEvents = a.Parent.VisibleEvents; + + if (a.Parent.XAxis.Position == 'top') { + startPosY = (rect.Y + a.Parent.Marker.Width + 20); + } else { + startPosY = (rect.Y + a.Parent.Marker.Width); + } + + // Clear for collisions detection + visibleEvents.forEach(function (e, i) { + e.HitBox = { X: 0, Y:0, W: 0, H: 0}; + }); + + visibleEvents.forEach(function (e, i) { + // Calculate Y position + let posY = a.calcMarkerPosition(e.Position.X, startPosY); + let posY2 = 0; + + if (a.Parent.XAxis.Position == 'top') { + posY2 = a.Parent.Layer.Background.GraphRectangle.Y; + } else { + posY2 = (a.Parent.Layer.Background.GraphRectangle.Y + a.Parent.Layer.Background.GraphRectangle.H); + } + + if (a.Parent.MarkerLabel.Line.Width > 0) { + a.drawVerticalLine(e.Position.X, posY, posY2, a.Parent.MarkerLabel.Line.Width, a.Parent.MarkerLabel.Line.Colour); + } + + const markerRectangle = a.drawCircle(e.Position.X, posY, a.Parent.Marker.Width, a.Parent.Marker.BorderWidth, e.BorderColour, e.BackColour); + + e.Position = { X: e.Position.X, Y: posY }; + + if (a.Parent.ShowMarkerLabel) { + const labelRectangle = a.drawText((markerRectangle.X + markerRectangle.W + margin), markerRectangle.Y, e.Label, a.Parent.MarkerLabel.Font, a.Parent.MarkerLabel.Colour, "left"); + labelRectangle.W += a.Parent.MarkerLabel.Margin; + + e.HitBox = a.combineRectangle(markerRectangle, labelRectangle); + } else { + e.HitBox = markerRectangle; + } + + if (a.Parent.Debug) a.drawRectangle(e.HitBox, "red"); + if (a.Parent.Debug) console.log(e); + }); + } + + OnMouseDown(e) { + if (!a.Parent.Enabled) { + return; + } + + var event = a.Parent.FindEventsByCoords(e.offsetX, e.offsetY); + if (event == null) { + return; + } + + if (a.Parent.Debug) console.log(event); + + console.log("!"); + a.Parent.OnMouseDown(this, e, event); + } + + calcMarkerPosition(x, y) { + const a = this; + const rect = a.Parent.Layer.Background.GraphRectangle; + + // Calculate Y position + let hasMoved = false; + let posY = y; + for (let i=0; i= endPosX) { - break; - } - - a.ctx.beginPath(); - a.ctx.moveTo(startPosX, posY); - - if ((i % a.Axis.X.NoPartPerDay) == 0) { - a.ctx.lineTo(startPosX, (posY + a.Axis.X.DayLineHeight)); - a.ctx.strokeStyle = a.Axis.X.DayLineColour; - } else { - 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; - - const result = a.getXAxis(); - const posY = (a.GraphRectangle.Y + a.GraphRectangle.H) + a.Axis.LineWidth; - - result.forEach(function(e, i) { - const date = a.ConvertToDate(e.Date); - - let writeLabel = false; - if ((i == 0)) { - // Don't label first entry if too close to the next month - if (date.getDate() < 25) { - writeLabel = true; - } - } else if (date.getDate() == 1) { - writeLabel = true; - } - - // if (i == 0) { - // return; - // } - - const labelSize = a.drawText(e.X, (posY + a.Axis.X.DayLineHeight), a.DateToString(date, "dd"), a.Axis.Font, a.Axis.LabelColour, "center"); - - // Write month on first of the month - if (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; - const width = a.Marker.Width - (a.Marker.BorderWidth * 2); - - a.ctx.beginPath(); - a.ctx.arc(x, y, width, 0, 2 * Math.PI, false); - a.ctx.fillStyle = backColour; - a.ctx.fill(); - a.ctx.lineWidth = a.Marker.BorderWidth; - a.ctx.strokeStyle = borderColour; - a.ctx.stroke(); - - return 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 = (x - size.OffsetLeft); - break; - case "right": - x = (x - size.Width); - break; - case "left": - default: - // do nothing - break; - } - - a.ctx.fillText(label, x, (y + size.Height)); - - return size; - } - - drawRectangle(rectangle) { - const a = this; - - a.ctx.beginPath(); - a.ctx.rect(rectangle.X, rectangle.Y, rectangle.W, rectangle.H); - //a.ctx.fillStyle = 'yellow'; - //a.ctx.fill(); - a.ctx.lineWidth = 1; - a.ctx.strokeStyle = 'red'; - a.ctx.stroke(); - } - - drawVerticalLine(x, y) { - const a = this; - const linePosY = (a.GraphRectangle.Y + a.GraphRectangle.H); - - if (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; - const endPosX = (a.GraphRectangle.X + a.GraphRectangle.W); - - let result = []; - let x = a.GraphRectangle.X; - let date = a.ConvertToDate(a.ShowDate); - - // Rollback one day - date.setDate(date.getDate() - 1); - - while (true) { - if (x >= endPosX) { - break; - } - - 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; - const offset = a.half(a.Marker.Width); - - const result = { - X: x - (offset + a.Marker.BorderWidth), - Y: y - (offset + a.Marker.BorderWidth), - W: (a.Marker.Width + (a.Marker.BorderWidth * 2)), - H: (a.Marker.Width + (a.Marker.BorderWidth * 2)) - }; - - return result; - } - - 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) - }; + return a.DateToString(date, a.DateParsePattern); } ConvertToDate(value) { return new Date(Date.parse(value)); } + + OnMouseDown(sender, e, event) { /* delegate */ } + + OnMouseMove(sender, e, event) { /* delegate */ } + + OnClick(sender, e, event) { /* delegate */ } + + OnDblClick(sender, e, event) { /* delegate */ } + + + initialiseComponents() { + const a = this; + + a.Container.innerHTML = ""; + + const canvasList = a.Container.getElementsByTagName("canvas"); + + a.Layer.Background = new BBTimelineBackgroundCanvas(a, canvasList[0]); + a.Layer.Flourish = new BBTimelineFlourishCanvas(a, canvasList[1]); + a.Layer.Markers = new BBTimelineForegroundCanvas(a, canvasList[2]); + + } + } \ No newline at end of file diff --git a/bbtimeline.min.js b/bbtimeline.min.js index 0463649..2d55388 100644 --- a/bbtimeline.min.js +++ b/bbtimeline.min.js @@ -1,5 +1,4 @@ /** * BBTimeline - * @version v0.1.0.089 beta (2023/10/14 1658) + * @version v0.1.1.121 beta (2023/10/18 2058) */ -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=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)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=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))}} \ No newline at end of file diff --git a/demo-test.html b/demo-test.html index 26ade58..128ed04 100644 --- a/demo-test.html +++ b/demo-test.html @@ -11,6 +11,11 @@ + + + + + @@ -19,20 +24,35 @@
- +

-

-

+ +
+ +

+ +

+ +
+

+ + + +

+

+ +

+

@@ -47,8 +67,8 @@

- - + +


@@ -88,16 +108,6 @@ body { padding: 20px; } -canvas { - border-style: solid; - border-width: 1px; - border-color: #000000; - width: 100%; - height: 300px; - padding: 0; - margin: 0; -} - textarea { border-style: solid; border-width: 1px; @@ -107,6 +117,7 @@ textarea { width: 100%; } + .column1 { flex: 70%; padding: 20px; @@ -119,6 +130,17 @@ textarea { display: flex; } + +#myCanvas { + border-style: solid; + border-width: 1px; + border-color: #000000; + width: 100%; + height: 300px; + padding: 0; + margin: 0; +} +