From 0bbed0dfeee35b4a2e1094e91baba8119cdab5dc Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 22 Oct 2023 22:14:12 +0100 Subject: [PATCH] Changed to multi-layer for hot-tracking and other flourishes Changed default colours Added X-Axis top and bottom positions --- bbtimeline-background-canvas.js | 106 +++++++++++++++++-------- bbtimeline-canvas.js | 135 +++++++++++++++++++++++++------- bbtimeline-flourish-canvas.js | 29 ++++--- bbtimeline-foreground-canvas.js | 109 ++++++++++---------------- bbtimeline.js | 62 ++++++--------- bbtimeline.min.js | 9 --- demo-test.html | 40 ++++++++-- 7 files changed, 299 insertions(+), 191 deletions(-) diff --git a/bbtimeline-background-canvas.js b/bbtimeline-background-canvas.js index 05a3708..9f6a859 100644 --- a/bbtimeline-background-canvas.js +++ b/bbtimeline-background-canvas.js @@ -4,7 +4,6 @@ class BBTimelineBackgroundCanvas extends BBTimelineCanvas { const a = this; - a.GraphRectangle = { X: 0, Y: 0, W: 0, H: 0 }; a.Margin = 0; a.StepHeight = 0; a.NoStep = 0; @@ -17,20 +16,6 @@ class BBTimelineBackgroundCanvas extends BBTimelineCanvas { const a = this; - a.ClientRectangle = { - 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)) - }; - - a.GraphRectangle = { - X: a.ClientRectangle.X, - Y: a.ClientRectangle.Y, - W: a.ClientRectangle.W, - H: (a.ClientRectangle.H - a.XAxisHeight) - }; - a.Margin = (a.Parent.Marker.BorderWidth * 2); a.StepHeight = a.Parent.Marker.Width + a.Margin; a.NoStep = Math.floor(a.GraphRectangle.H / a.StepHeight); @@ -39,11 +24,31 @@ class BBTimelineBackgroundCanvas extends BBTimelineCanvas { } + 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.Height + a.Parent.Axis.LabelSpacing + (a.Parent.Axis.X.DayLineHeight * 2); + return labelSize.H + a.Parent.Axis.LabelSpacing + (a.Parent.XAxis.DayLineHeight * 2); } get XAxisPositions() { @@ -67,7 +72,7 @@ class BBTimelineBackgroundCanvas extends BBTimelineCanvas { X: x }); - x += (a.Parent.Axis.X.HourLineSpace * a.Parent.Axis.X.NoPartPerDay); + x += (a.Parent.XAxis.HourLineSpace * a.Parent.XAxis.NoPartPerDay); date.setDate(date.getDate() + 1); } @@ -93,9 +98,17 @@ class BBTimelineBackgroundCanvas extends BBTimelineCanvas { 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)); + + 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(); @@ -103,7 +116,21 @@ class BBTimelineBackgroundCanvas extends BBTimelineCanvas { drawXAxisLabels() { const a = this; - const posY = (a.GraphRectangle.Y + a.GraphRectangle.H) + a.Parent.Axis.LineWidth; + 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); @@ -119,14 +146,15 @@ class BBTimelineBackgroundCanvas extends BBTimelineCanvas { } // if (i == 0) { - // return; + // // 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"); // } - const labelSize = a.drawText(e.X, (posY + a.Parent.Axis.X.DayLineHeight), a.Parent.DateToString(date, "dd"), a.Parent.Axis.Font, a.Parent.Axis.LabelColour, "center"); - - // Write month on first of the month + // Write month (MMMM) on first of the month if (writeLabel) { - a.drawText(e.X, (posY + a.Parent.Axis.X.DayLineHeight + labelSize.Height + a.Parent.Axis.LabelSpacing), a.Parent.DateToString(date, "MMMM yyyy"), a.Parent.Axis.Font, a.Parent.Axis.LabelColour, "left"); + a.drawText(e.X, posMonthY, a.Parent.DateToString(date, "MMMM yyyy"), a.Parent.Axis.Font, a.Parent.Axis.LabelColour, "left"); } }); } @@ -136,7 +164,19 @@ class BBTimelineBackgroundCanvas extends BBTimelineCanvas { let startPosX = a.GraphRectangle.X; const endPosX = (a.GraphRectangle.X + a.GraphRectangle.W); - const posY = (a.GraphRectangle.Y + a.GraphRectangle.H) + a.Parent.Axis.LineWidth; + 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; @@ -148,18 +188,18 @@ class BBTimelineBackgroundCanvas extends BBTimelineCanvas { a.CTX.beginPath(); a.CTX.moveTo(startPosX, posY); - if ((i % a.Parent.Axis.X.NoPartPerDay) == 0) { - a.CTX.lineTo(startPosX, (posY + a.Parent.Axis.X.DayLineHeight)); - a.CTX.strokeStyle = a.Parent.Axis.X.DayLineColour; + if ((i % a.Parent.XAxis.NoPartPerDay) == 0) { + a.CTX.lineTo(startPosX, posDayY); + a.CTX.strokeStyle = a.Parent.XAxis.DayLineColour; } else { - a.CTX.lineTo(startPosX, (posY + a.Parent.Axis.X.HourLineHeight)); - a.CTX.strokeStyle = a.Parent.Axis.X.HourLineColour; + 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.Axis.X.HourLineSpace; + startPosX += a.Parent.XAxis.HourLineSpace; i++; } diff --git a/bbtimeline-canvas.js b/bbtimeline-canvas.js index 6f2a5d3..adeeaa2 100644 --- a/bbtimeline-canvas.js +++ b/bbtimeline-canvas.js @@ -6,8 +6,6 @@ class BBTimelineCanvas { a.Container = el; a.CTX = a.Container.getContext("2d"); - a.ClientRectangle = { X: 0, Y: 0, W: 0, H: 0 }; - a.initialiseComponents(); } @@ -26,6 +24,18 @@ class BBTimelineCanvas { } + 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; @@ -36,31 +46,28 @@ class BBTimelineCanvas { // placeholder } - - drawText(x, y, label, font, foreColour, align) { + 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.font = font; - a.CTX.fillStyle = foreColour; + 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 size = a.measureText(font, label); + const result = { + X: x - (offset + borderWidth), + Y: y - (offset + borderWidth), + W: (width + calcBorderWidth), + H: (width + calcBorderWidth) + }; - 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; + return result; } drawRectangle(rectangle, colour) { @@ -73,8 +80,58 @@ class BBTimelineCanvas { 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); } @@ -86,11 +143,35 @@ class BBTimelineCanvas { const size = a.CTX.measureText(value); return { - Width: size.width, - Height: size.fontBoundingBoxAscent, - OffsetLeft: a.half(size.width), - OffsetTop: a.half(size.fontBoundingBoxAscent) + 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 index ef38b88..8b0cc7e 100644 --- a/bbtimeline-flourish-canvas.js +++ b/bbtimeline-flourish-canvas.js @@ -2,21 +2,32 @@ class BBTimelineFlourishCanvas extends BBTimelineCanvas { constructor(parentEl, el) { super(parentEl, el); + const a = this; + + a.XPos = -1; } - DrawVerticalLine(x) { + Invalidate() { const a = this; - const rect = a.Parent.Layer.Background.GraphRectangle; - const linePosY = (rect.Y + rect.H); a.Clear(); - a.CTX.beginPath(); - a.CTX.moveTo(x, rect.Y); - a.CTX.lineTo(x, (linePosY - a.Parent.Marker.Line.Width)); - a.CTX.lineWidth = 1; - a.CTX.strokeStyle = a.Parent.HotTrack.Colour; - a.CTX.stroke(); + 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 index 31fe2fb..730c9ef 100644 --- a/bbtimeline-foreground-canvas.js +++ b/bbtimeline-foreground-canvas.js @@ -63,21 +63,30 @@ class BBTimelineForegroundCanvas extends BBTimelineCanvas { return; } - if (!a.Parent.EnableHotTracking) { - return; - } - - const point = { X: e.offsetX, Y: e.offsetY }; - if (a.Parent.HasInterception(a.Parent.Layer.Background.GraphRectangle, point)) { - if (a.Parent.Debug) console.log(point); - - a.Parent.DrawHotTracking(point.X); - - a.Parent.OnMouseMove(this, e, point); + var event = a.Parent.FindEventsByCoords(e.offsetX, e.offsetY); + if (event != null) { + a.Container.style.cursor = 'pointer'; } else { - // Clear hot tracking - a.Parent.DrawHotTracking(-1); + 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(); + } + }); } @@ -92,25 +101,33 @@ class BBTimelineForegroundCanvas extends BBTimelineCanvas { a.Clear(); const startPosY = (rect.Y + a.Parent.Marker.Width); - const visibleEvents = a.Parent.VisibleEvents; + + // 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); - a.drawVerticalLine(e.Position.X, posY); + if (a.Parent.MarkerLabel.Line.Width > 0) { + a.drawVerticalLine(e.Position.X, posY, (a.Parent.Layer.Background.GraphRectangle.Y + a.Parent.Layer.Background.GraphRectangle.H), a.Parent.MarkerLabel.Line.Width, a.Parent.MarkerLabel.Line.Colour); + } - const markerRectangle = a.drawMarker(e.Position.X, posY, e.BorderColour, e.BackColour); - const labelSize = a.drawText((markerRectangle.X + markerRectangle.W + margin), markerRectangle.Y, e.Label, a.Parent.Marker.Font, a.Parent.Marker.ForeColour, "left"); + 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 }; - e.HitBox = { - X: markerRectangle.X, - Y: markerRectangle.Y, - W: (markerRectangle.W + margin + labelSize.Width + a.Parent.Marker.CollisionMargin), - H: markerRectangle.H - }; + 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); @@ -158,50 +175,4 @@ class BBTimelineForegroundCanvas extends BBTimelineCanvas { return posY; } - drawMarker(x, y, borderColour, backColour) { - const a = this; - const width = a.Parent.Marker.Width - (a.Parent.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.Parent.Marker.BorderWidth; - a.CTX.strokeStyle = borderColour; - a.CTX.stroke(); - - return a.measureMarker(x, y); - } - - drawVerticalLine(x, y) { - const a = this; - const rect = a.Parent.Layer.Background.GraphRectangle; - const linePosY = (rect.Y + rect.H); - - if (y <= 0) { - y = (rect.Y + a.Parent.Marker.Line.Width); - } - - a.CTX.beginPath(); - a.CTX.moveTo(x, y); - a.CTX.lineTo(x, (linePosY - a.Parent.Marker.Line.Width)); - a.CTX.lineWidth = a.Parent.Marker.Line.Width; - a.CTX.strokeStyle = a.Parent.Marker.Line.Colour; - a.CTX.stroke(); - } - - measureMarker(x, y) { - const a = this; - const offset = a.half(a.Parent.Marker.Width); - - const result = { - X: x - (offset + a.Parent.Marker.BorderWidth), - Y: y - (offset + a.Parent.Marker.BorderWidth), - W: (a.Parent.Marker.Width + (a.Parent.Marker.BorderWidth * 2)), - H: (a.Parent.Marker.Width + (a.Parent.Marker.BorderWidth * 2)) - }; - - return result; - } - } \ No newline at end of file diff --git a/bbtimeline.js b/bbtimeline.js index df6cb3b..e921b16 100644 --- a/bbtimeline.js +++ b/bbtimeline.js @@ -11,7 +11,7 @@ class BBTimeline { Left: 20, Top: 20, Right: 20, - Bottom: 0 + Bottom: 20 }; a.Size = { W: a.Container.innerWidth || a.Container.clientWidth, @@ -26,29 +26,32 @@ class BBTimeline { a.DateParsePattern = "yyyy-MM-dd"; a.Axis = { - LineColour1: "#CFCFCF", + LineColour1: "#000000", LineWidth: 1, Font: "8pt Arial", LabelColour: "#000000", - LabelSpacing: 6, - X: { - NoPartPerDay: 4, - HourLineSpace: 6, - HourLineHeight: 10, - HourLineColour: "#A6A6A6", - DayLineHeight: 20, - DayLineColour: "#282828" - } + LabelSpacing: 6 + }; + a.XAxis = { + NoPartPerDay: 4, + HourLineSpace: 6, + HourLineHeight: 10, + HourLineColour: "#EAEAEA", + DayLineHeight: 20, + DayLineColour: "#9E9E9E", + Position: 'top' }; a.Marker = { BorderColour: "#3A5D9C", BorderWidth: 2, BackColour: "#D4DEEF", - Width: 10, - ForeColour: "#3A5D9C", + Width: 10 + }; + a.MarkerLabel = { + Colour: "#3A5D9C", Font: "9pt Arial", - CollisionMargin: 8, + Margin: 8, Line: { Colour: "#A6A6A6", Width: 1, @@ -56,12 +59,14 @@ class BBTimeline { }; a.HotTrack = { - Colour: "#1D7F1D" + Colour: "#F57C00", + Width: 3 }; a.Events = []; a.StartDate = a.DateToInternalString(new Date()); a.ShowDate = a.StartDate; + a.ShowMarkerLabel = true; a.Enabled = false; a.Debug = false; @@ -102,7 +107,7 @@ class BBTimeline { const a = this; const clientWidth = (a.Size.W - (a.Padding.Left + a.Padding.Right)); - return Math.floor(clientWidth / (a.Axis.X.NoPartPerDay * a.Axis.X.HourLineSpace)); + return Math.floor(clientWidth / (a.XAxis.NoPartPerDay * a.XAxis.HourLineSpace)); } get VisibleStartDate() { @@ -305,15 +310,11 @@ class BBTimeline { Invalidate(redrawAxis, redrawMarkers) { const a = this; - if (redrawAxis) { - a.Layer.Background.Invalidate(); - } + if (redrawAxis) a.Layer.Background.Invalidate(); a.Layer.Flourish.Clear(); - if (redrawMarkers) { - a.Layer.Markers.Invalidate(); - } + if (redrawMarkers) a.Layer.Markers.Invalidate(); } @@ -369,27 +370,10 @@ class BBTimeline { return a.DateToString(date, a.DateParsePattern); } - DrawHotTracking(x) { - const a = this; - - if (x < 0) { - a.Layer.Flourish.Clear(); - } else { - a.Layer.Flourish.DrawVerticalLine(x); - } - } - ConvertToDate(value) { return new Date(Date.parse(value)); } - HasInterception(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)); - } - OnMouseDown(sender, e, event) { /* delegate */ } diff --git a/bbtimeline.min.js b/bbtimeline.min.js index 11f2cb2..2d55388 100644 --- a/bbtimeline.min.js +++ b/bbtimeline.min.js @@ -2,12 +2,3 @@ * BBTimeline * @version v0.1.1.121 beta (2023/10/18 2058) */ -class BBTimelineCanvas{constructor(t,e){const n=this;n.Parent=t,n.Container=e,n.CTX=n.Container.getContext("2d"),n.ClientRectangle={X:0,Y:0,W:0,H:0},n.initialiseComponents()}initialiseComponents(){const t=this;t.Container.style.width=t.Parent.Size.W+"px",t.Container.style.height=t.Parent.Size.H+"px",t.Container.style.position="absolute",t.Container.style.border="none",t.CTX.canvas.width=t.Parent.Size.W,t.CTX.canvas.height=t.Parent.Size.H,t.Clear()}Clear(){const t=this;t.CTX.clearRect(0,0,t.CTX.canvas.width,t.CTX.canvas.height)}Invalidate(){}drawText(t,e,n,i,s,a){const o=this;o.CTX.font=i,o.CTX.fillStyle=s;const r=o.measureText(i,n);switch(a){case"center":t-=r.OffsetLeft;break;case"right":t-=r.Width}return o.CTX.fillText(n,t,e+r.Height),r}drawRectangle(t,e){const n=this;n.CTX.beginPath(),n.CTX.rect(t.X,t.Y,t.W,t.H),n.CTX.lineWidth=1,n.CTX.strokeStyle=e,n.CTX.stroke()}half(t){return t/2}measureText(t,e){const n=this;n.CTX.font=t;const i=n.CTX.measureText(e);return{Width:i.width,Height:i.fontBoundingBoxAscent,OffsetLeft:n.half(i.width),OffsetTop:n.half(i.fontBoundingBoxAscent)}}} - -class BBTimelineBackgroundCanvas extends BBTimelineCanvas{constructor(e,t){super(e,t);const a=this;a.GraphRectangle={X:0,Y:0,W:0,H:0},a.Margin=0,a.StepHeight=0,a.NoStep=0,a.initialiseComponents()}initialiseComponents(){super.initialiseComponents();const e=this;e.ClientRectangle={X:e.Parent.Padding.Left,Y:e.Parent.Padding.Top,W:e.Parent.Size.W-(e.Parent.Padding.Left+e.Parent.Padding.Right),H:e.Parent.Size.H-(e.Parent.Padding.Top+e.Parent.Padding.Bottom)},e.GraphRectangle={X:e.ClientRectangle.X,Y:e.ClientRectangle.Y,W:e.ClientRectangle.W,H:e.ClientRectangle.H-e.XAxisHeight},e.Margin=2*e.Parent.Marker.BorderWidth,e.StepHeight=e.Parent.Marker.Width+e.Margin,e.NoStep=Math.floor(e.GraphRectangle.H/e.StepHeight),e.Invalidate()}get XAxisHeight(){const e=this;return e.measureText(e.Parent.Axis.Font,"0").Height+e.Parent.Axis.LabelSpacing+2*e.Parent.Axis.X.DayLineHeight}get XAxisPositions(){const e=this,t=e.GraphRectangle.X+e.GraphRectangle.W;let a=[],n=e.GraphRectangle.X,i=e.Parent.ConvertToDate(e.Parent.ShowDate);for(i.setDate(i.getDate()-1);!(n>=t);)a.push({Date:e.Parent.DateToInternalString(i),X:n}),n+=e.Parent.Axis.X.HourLineSpace*e.Parent.Axis.X.NoPartPerDay,i.setDate(i.getDate()+1);return a}Invalidate(){const e=this;e.Clear(),e.drawAxis(),e.drawXAxisTicks(),e.drawXAxisLabels(),e.Parent.Debug&&e.drawRectangle(e.ClientRectangle,"red"),e.Parent.Debug&&e.drawRectangle(e.GraphRectangle,"red")}drawAxis(){const e=this;e.CTX.beginPath(),e.CTX.moveTo(e.GraphRectangle.X,e.GraphRectangle.Y),e.CTX.lineTo(e.GraphRectangle.X,e.GraphRectangle.H+e.GraphRectangle.Y),e.CTX.lineTo(e.GraphRectangle.W+e.GraphRectangle.X,e.GraphRectangle.H+e.GraphRectangle.Y),e.CTX.lineWidth=e.Parent.Axis.LineWidth,e.CTX.strokeStyle=e.Parent.Axis.LineColour1,e.CTX.stroke()}drawXAxisLabels(){const e=this,t=e.GraphRectangle.Y+e.GraphRectangle.H+e.Parent.Axis.LineWidth;e.XAxisPositions.forEach((function(a,n){const i=e.Parent.ConvertToDate(a.Date);let r=!1;0==n?i.getDate()<25&&(r=!0):1==i.getDate()&&(r=!0);const s=e.drawText(a.X,t+e.Parent.Axis.X.DayLineHeight,e.Parent.DateToString(i,"dd"),e.Parent.Axis.Font,e.Parent.Axis.LabelColour,"center");r&&e.drawText(a.X,t+e.Parent.Axis.X.DayLineHeight+s.Height+e.Parent.Axis.LabelSpacing,e.Parent.DateToString(i,"MMMM yyyy"),e.Parent.Axis.Font,e.Parent.Axis.LabelColour,"left")}))}drawXAxisTicks(){const e=this;let t=e.GraphRectangle.X;const a=e.GraphRectangle.X+e.GraphRectangle.W,n=e.GraphRectangle.Y+e.GraphRectangle.H+e.Parent.Axis.LineWidth;let i=0;for(;!(t>=a);)e.CTX.beginPath(),e.CTX.moveTo(t,n),i%e.Parent.Axis.X.NoPartPerDay==0?(e.CTX.lineTo(t,n+e.Parent.Axis.X.DayLineHeight),e.CTX.strokeStyle=e.Parent.Axis.X.DayLineColour):(e.CTX.lineTo(t,n+e.Parent.Axis.X.HourLineHeight),e.CTX.strokeStyle=e.Parent.Axis.X.HourLineColour),e.CTX.lineWidth=e.Parent.Axis.LineWidth,e.CTX.stroke(),t+=e.Parent.Axis.X.HourLineSpace,i++}} - -class BBTimelineFlourishCanvas extends BBTimelineCanvas{constructor(e,r){super(e,r)}DrawVerticalLine(e){const r=this,a=r.Parent.Layer.Background.GraphRectangle,n=a.Y+a.H;r.Clear(),r.CTX.beginPath(),r.CTX.moveTo(e,a.Y),r.CTX.lineTo(e,n-r.Parent.Marker.Line.Width),r.CTX.lineWidth=1,r.CTX.strokeStyle=r.Parent.HotTrack.Colour,r.CTX.stroke()}} - -class BBTimelineForegroundCanvas extends BBTimelineCanvas{constructor(e,r){super(e,r)}initialiseComponents(){super.initialiseComponents();const e=this;e.CTX.canvas.addEventListener("mousedown",(function(r){if(e.Parent.Enabled){var n=e.Parent.FindEventsByCoords(r.offsetX,r.offsetY);null!=n&&(e.Parent.Debug&&console.log(n),e.Parent.OnMouseDown(this,r,n))}})),e.CTX.canvas.addEventListener("click",(function(r){if(e.Parent.Enabled){var n=e.Parent.FindEventsByCoords(r.offsetX,r.offsetY);null!=n&&(e.Parent.Debug&&console.log(n),e.Parent.OnClick(this,r,n))}})),e.CTX.canvas.addEventListener("dblclick",(function(r){if(e.Parent.Enabled){var n=e.Parent.FindEventsByCoords(r.offsetX,r.offsetY);null!=n&&(e.Parent.Debug&&console.log(n),e.Parent.OnDblClick(this,r,n))}})),e.Parent.EnableHotTracking&&e.CTX.canvas.addEventListener("mousemove",(function(r){if(!e.Parent.Enabled)return;if(!e.Parent.EnableHotTracking)return;const n={X:r.offsetX,Y:r.offsetY};e.Parent.HasInterception(e.Parent.Layer.Background.GraphRectangle,n)?(e.Parent.Debug&&console.log(n),e.Parent.DrawHotTracking(n.X),e.Parent.OnMouseMove(this,r,n)):e.Parent.DrawHotTracking(-1)})),e.Invalidate()}Invalidate(){const e=this,r=e.Parent.Layer.Background.GraphRectangle,n=e.Parent.Layer.Background.Margin;e.Clear();const t=r.Y+e.Parent.Marker.Width;e.Parent.VisibleEvents.forEach((function(r,a){let o=e.calcMarkerPosition(r.Position.X,t);e.drawVerticalLine(r.Position.X,o);const i=e.drawMarker(r.Position.X,o,r.BorderColour,r.BackColour),s=e.drawText(i.X+i.W+n,i.Y,r.Label,e.Parent.Marker.Font,e.Parent.Marker.ForeColour,"left");r.Position={X:r.Position.X,Y:o},r.HitBox={X:i.X,Y:i.Y,W:i.W+n+s.Width+e.Parent.Marker.CollisionMargin,H:i.H},e.Parent.Debug&&e.drawRectangle(r.HitBox,"red"),e.Parent.Debug&&console.log(r)}))}OnMouseDown(e){if(a.Parent.Enabled){var r=a.Parent.FindEventsByCoords(e.offsetX,e.offsetY);null!=r&&(a.Parent.Debug&&console.log(r),console.log("!"),a.Parent.OnMouseDown(this,e,r))}}calcMarkerPosition(e,r){const n=this;n.Parent.Layer.Background.GraphRectangle;let t=!1,a=r;for(let o=0;o=n.X&&e<=o&&t>=n.Y&&t<=i)return a.Events[r]}return null}Load(e){this.StartDate=e,this.Show(e)}Show(e){const t=this;t.ConvertToDate(e)12?e.getHours()-12:e.getHours()).toString().padStart(2,"0")),a=a.replace("mm",e.getMinutes().toString().padStart(2,"0")),a=a.replace("ss",e.getSeconds().toString().padStart(2,"0")),a=a.replace("ff",e.getMilliseconds().toString().padStart(2,"0")),a=a.replace("tt","{5}"),a=a.replace("zz",""),a=a.replace("y",e.getFullYear().toString()),a=a.replace("M",(e.getMonth()+1).toString()),a=a.replace("d",e.getDate().toString()),a=a.replace("H",e.getHours().toString()),a=a.replace("h",(e.getHours()>12?e.getHours()-12:e.getHours()).toString()),a=a.replace("m",e.getMinutes().toString()),a=a.replace("s",e.getSeconds().toString()),a=a.replace("z",""),a=a.replace("t","{6}"),a=a.replace("Z",""),a=a.replace("{1}",e.toLocaleString("default",{month:"long"})),a=a.replace("{2}",e.toLocaleString("default",{weekday:"long"})),a=a.replace("{3}",e.toLocaleString("default",{month:"short"})),a=a.replace("{4}",e.toLocaleString("default",{weekday:"short"})),a=a.replace("{5}",e.getHours()>=12?"PM":"AM"),a=a.replace("{6}",e.getHours()>=12?"P":"A"),a}DateToInternalString(e){return this.DateToString(e,this.DateParsePattern)}DrawHotTracking(e){const t=this;e<0?t.Layer.Flourish.Clear():t.Layer.Flourish.DrawVerticalLine(e)}ConvertToDate(e){return new Date(Date.parse(e))}HasInterception(e,t){const a=e.X+e.W,r=e.Y+e.H;return t.X>=e.X&&t.X<=a&&t.Y>=e.Y&&t.Y<=r}OnMouseDown(e,t,a){}OnMouseMove(e,t,a){}OnClick(e,t,a){}OnDblClick(e,t,a){}initialiseComponents(){const e=this;e.Container.innerHTML="";const t=e.Container.getElementsByTagName("canvas");e.Layer.Background=new BBTimelineBackgroundCanvas(e,t[0]),e.Layer.Flourish=new BBTimelineFlourishCanvas(e,t[1]),e.Layer.Markers=new BBTimelineForegroundCanvas(e,t[2])}} \ No newline at end of file diff --git a/demo-test.html b/demo-test.html index a239771..a6057f0 100644 --- a/demo-test.html +++ b/demo-test.html @@ -10,13 +10,13 @@ - + - + @@ -34,10 +34,25 @@

- -

+ +
+ +

+ +

+ +
+

+ + + +

+

+ +

+

@@ -176,6 +191,21 @@ function ToggleHotTracking() { timeline1.Invalidate(true, true); } +function ToggleShowLabel() { + timeline1.ShowMarkerLabel = !timeline1.ShowMarkerLabel; + timeline1.Invalidate(true, true); +} + +function ToggleXAxisPosition() { + timeline1.XAxis.Position = (timeline1.XAxis.Position == 'top' ? 'bottom' : 'top'); + timeline1.Invalidate(true, true); +} + +function ToggleMarkerTail() { + timeline1.MarkerLabel.Line.Width = ((timeline1.MarkerLabel.Line.Width <= 0) ? 1 : 0); + timeline1.Invalidate(true, true); +} + function Refresh() { timeline1.Invalidate(true, true); }